diff --git a/slhx-js/runtime/slhx.js b/slhx-js/runtime/slhx.js index 64bdb58..94da095 100644 --- a/slhx-js/runtime/slhx.js +++ b/slhx-js/runtime/slhx.js @@ -8,6 +8,8 @@ const pending = new WeakMap(); const queues = new WeakMap(); const timers = new WeakMap(); + const pendingClassStates = new WeakMap(); + const indicatorStates = new WeakMap(); const disabledStates = new WeakMap(); const sseSources = new WeakMap(); const atomStores = new WeakMap(); @@ -71,9 +73,9 @@ function showPending(el, on) { 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; - 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")) { const controls = []; 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) { const state = disabledStates.get(control); if (on) { diff --git a/slhx-js/tests/runtime.rs b/slhx-js/tests/runtime.rs index 2662adf..69e1ad5 100644 --- a/slhx-js/tests/runtime.rs +++ b/slhx-js/tests/runtime.rs @@ -55,9 +55,14 @@ fn runtime_toggles_pending_conventions_around_requests() { assert!(source.contains("function showPending(el, on)")); assert!(source.contains("el.getAttribute(\"data-slhx-pending-class\")")); - assert!(source.contains("el.classList.toggle(klass, on)")); - assert!(source.contains("forEachElement(root, (i) => { if (i.hasAttribute(\"data-slhx-indicator\")) i.hidden = !on; })")); - assert!(source.contains("i.hidden = !on")); + assert!(source.contains("const pendingClassStates = new WeakMap()")); + assert!(source.contains("function togglePendingClass(el, klass, 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("if (isDisableControl(el)) controls.push(el)")); assert!(source.contains("forEachElement(el, (child) => { if (isDisableControl(child)) controls.push(child); })"));