fix(js): preserve pending indicator state

Track pending class and indicator state with small reference counts so overlapping requests do not hide indicators early or strip a class that existed before the request.

req: convention/007

req: convention/008
This commit is contained in:
slhx agent
2026-06-01 20:16:19 +02:00
parent 8e39d6c4ac
commit 31b812f557
2 changed files with 45 additions and 5 deletions
+37 -2
View File
@@ -8,6 +8,8 @@
const pending = new WeakMap(); const pending = new WeakMap();
const queues = new WeakMap(); const queues = new WeakMap();
const timers = new WeakMap(); const timers = new WeakMap();
const pendingClassStates = new WeakMap();
const indicatorStates = new WeakMap();
const disabledStates = new WeakMap(); const disabledStates = new WeakMap();
const sseSources = new WeakMap(); const sseSources = new WeakMap();
const atomStores = new WeakMap(); const atomStores = new WeakMap();
@@ -71,9 +73,9 @@
function showPending(el, on) { function showPending(el, on) {
const klass = el.getAttribute("data-slhx-pending-class"); const klass = el.getAttribute("data-slhx-pending-class");
if (klass) el.classList.toggle(klass, on); if (klass) togglePendingClass(el, klass, on);
const root = rootOf(el) || document; const root = rootOf(el) || document;
forEachElement(root, (i) => { if (i.hasAttribute("data-slhx-indicator")) i.hidden = !on; }); forEachElement(root, (i) => { if (i.hasAttribute("data-slhx-indicator")) toggleIndicator(i, on); });
if (el.hasAttribute("data-slhx-disable-while-pending")) { if (el.hasAttribute("data-slhx-disable-while-pending")) {
const controls = []; const controls = [];
if (isDisableControl(el)) controls.push(el); if (isDisableControl(el)) controls.push(el);
@@ -82,6 +84,39 @@
} }
} }
function togglePendingClass(el, klass, on) {
const state = pendingClassStates.get(el);
if (on) {
if (state) state.count += 1;
else pendingClassStates.set(el, { count: 1, className: klass, hadClass: el.classList.contains(klass) });
el.classList.add(klass);
return;
}
if (!state) return;
state.count -= 1;
if (state.count <= 0) {
if (state.hadClass) el.classList.add(state.className);
else el.classList.remove(state.className);
pendingClassStates.delete(el);
}
}
function toggleIndicator(indicator, on) {
const state = indicatorStates.get(indicator);
if (on) {
if (state) state.count += 1;
else indicatorStates.set(indicator, { count: 1, hidden: indicator.hidden });
indicator.hidden = false;
return;
}
if (!state) return;
state.count -= 1;
if (state.count <= 0) {
indicator.hidden = state.hidden;
indicatorStates.delete(indicator);
}
}
function toggleDisabled(control, on) { function toggleDisabled(control, on) {
const state = disabledStates.get(control); const state = disabledStates.get(control);
if (on) { if (on) {
+8 -3
View File
@@ -55,9 +55,14 @@ fn runtime_toggles_pending_conventions_around_requests() {
assert!(source.contains("function showPending(el, on)")); assert!(source.contains("function showPending(el, on)"));
assert!(source.contains("el.getAttribute(\"data-slhx-pending-class\")")); assert!(source.contains("el.getAttribute(\"data-slhx-pending-class\")"));
assert!(source.contains("el.classList.toggle(klass, on)")); assert!(source.contains("const pendingClassStates = new WeakMap()"));
assert!(source.contains("forEachElement(root, (i) => { if (i.hasAttribute(\"data-slhx-indicator\")) i.hidden = !on; })")); assert!(source.contains("function togglePendingClass(el, klass, on)"));
assert!(source.contains("i.hidden = !on")); assert!(source.contains("hadClass: el.classList.contains(klass)"));
assert!(source.contains("if (state.hadClass) el.classList.add(state.className)"));
assert!(source.contains("const indicatorStates = new WeakMap()"));
assert!(source.contains("function toggleIndicator(indicator, on)"));
assert!(source.contains("toggleIndicator(i, on)"));
assert!(source.contains("indicator.hidden = state.hidden"));
assert!(source.contains("el.hasAttribute(\"data-slhx-disable-while-pending\")")); assert!(source.contains("el.hasAttribute(\"data-slhx-disable-while-pending\")"));
assert!(source.contains("if (isDisableControl(el)) controls.push(el)")); assert!(source.contains("if (isDisableControl(el)) controls.push(el)"));
assert!(source.contains("forEachElement(el, (child) => { if (isDisableControl(child)) controls.push(child); })")); assert!(source.contains("forEachElement(el, (child) => { if (isDisableControl(child)) controls.push(child); })"));