From df3c48bae51143219c4fa5e9048ef643feaaa530 Mon Sep 17 00:00:00 2001 From: Alexander van Elsas <58037137+avanelsas@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:01:42 +0200 Subject: [PATCH] Fix misc bugs: context menu dismiss, carousel demo, button mobile Safari, test hint, combobox category - x-context-menu: Escape and click-outside now dismiss the menu. Moved handlers from the layer element (pointer-events:none, no focus) to document-level listeners, matching x-dropdown's pattern. - x-button: add touch-action:manipulation and -webkit-tap-highlight-color:transparent to the internal button. Fixes missing press events on mobile Safari caused by all:unset resetting touch handling properties. - x-carousel demo: control panel used wrong event names (change instead of x-switch-change, input instead of x-form-field-input, etc.) and wrong detail access (e.target vs e.detail). - x-welcome-tour test: add missing ^js type hint on @detail deref to fix Closure Advanced compilation warning. - x-combobox: fix undefined category in demo gallery (was "input", should be "form"). Co-Authored-By: Claude Opus 4.6 (1M context) --- demo/x-carousel.html | 48 ++++++++--------- public/index.html | 2 +- src/baredom/components/x_button/x_button.cljs | 2 + .../x_context_menu/x_context_menu.cljs | 52 +++++++++++++------ .../x_welcome_tour/x_welcome_tour_test.cljs | 2 +- 5 files changed, 65 insertions(+), 41 deletions(-) diff --git a/demo/x-carousel.html b/demo/x-carousel.html index 54e7668..03ef5ef 100644 --- a/demo/x-carousel.html +++ b/demo/x-carousel.html @@ -200,46 +200,46 @@ // ── Control panel wiring ── const basic = document.getElementById('basic'); - document.getElementById('ctl-autoplay').addEventListener('change', e => { - basic.autoplay = e.target.checked; + document.getElementById('ctl-autoplay').addEventListener('x-switch-change', e => { + basic.autoplay = e.detail.checked; }); - document.getElementById('ctl-loop').addEventListener('change', e => { - basic.loop = e.target.checked; + document.getElementById('ctl-loop').addEventListener('x-switch-change', e => { + basic.loop = e.detail.checked; }); - document.getElementById('ctl-disabled').addEventListener('change', e => { - basic.disabled = e.target.checked; + document.getElementById('ctl-disabled').addEventListener('x-switch-change', e => { + basic.disabled = e.detail.checked; }); - document.getElementById('ctl-arrows').addEventListener('change', e => { - basic.arrows = e.target.checked; + document.getElementById('ctl-arrows').addEventListener('x-switch-change', e => { + basic.arrows = e.detail.checked; }); - document.getElementById('ctl-dots').addEventListener('change', e => { - basic.dots = e.target.checked; + document.getElementById('ctl-dots').addEventListener('x-switch-change', e => { + basic.dots = e.detail.checked; }); - document.getElementById('ctl-interval').addEventListener('input', e => { - basic.interval = parseInt(e.target.value, 10) || 3000; + document.getElementById('ctl-interval').addEventListener('x-form-field-input', e => { + basic.interval = parseInt(e.detail.value, 10) || 3000; }); - document.getElementById('ctl-current').addEventListener('input', e => { - basic.currentSlide = parseInt(e.target.value, 10) || 0; + document.getElementById('ctl-current').addEventListener('x-form-field-input', e => { + basic.currentSlide = parseInt(e.detail.value, 10) || 0; }); - document.getElementById('ctl-transition').addEventListener('change', e => { - basic.transition = e.target.value; + document.getElementById('ctl-transition').addEventListener('select-change', e => { + basic.transition = e.detail.value; }); - document.getElementById('ctl-direction').addEventListener('change', e => { - basic.direction = e.target.value; + document.getElementById('ctl-direction').addEventListener('select-change', e => { + basic.direction = e.detail.value; }); - document.getElementById('ctl-duration').addEventListener('input', e => { - const v = parseInt(e.target.value, 10) || 300; + document.getElementById('ctl-duration').addEventListener('x-form-field-input', e => { + const v = parseInt(e.detail.value, 10) || 300; basic.style.setProperty('--x-carousel-transition-duration', v + 'ms'); }); - document.getElementById('ctl-peek').addEventListener('input', e => { - const v = parseInt(e.target.value, 10) || 0; + document.getElementById('ctl-peek').addEventListener('x-form-field-input', e => { + const v = parseInt(e.detail.value, 10) || 0; basic.peek = v + 'px'; }); // ── Dynamic slides ── let dynamicCount = 2; const colors = ['#f97316','#06b6d4','#84cc16','#e11d48','#8b5cf6','#14b8a6']; - document.getElementById('add-slide').addEventListener('click', () => { + document.getElementById('add-slide').addEventListener('press', () => { dynamicCount++; const d = document.createElement('div'); d.className = 'slide'; @@ -247,7 +247,7 @@ d.textContent = 'Dynamic ' + dynamicCount; document.getElementById('dynamic').appendChild(d); }); - document.getElementById('remove-slide').addEventListener('click', () => { + document.getElementById('remove-slide').addEventListener('press', () => { const el = document.getElementById('dynamic'); if (el.lastElementChild) { el.lastElementChild.remove(); diff --git a/public/index.html b/public/index.html index 24859df..916a215 100644 --- a/public/index.html +++ b/public/index.html @@ -493,7 +493,7 @@

Library demo

{ name: "Fieldset", tag: "x-fieldset", file: "x-fieldset", cat: "form" }, { name: "File Download", tag: "x-file-download", file: "x-file-download", cat: "form" }, { name: "File Upload", tag: "x-file-upload", file: "x-file-upload", cat: "form" }, - { name: "Combobox", tag: "x-combobox", file: "x-combobox", cat: "input" }, + { name: "Combobox", tag: "x-combobox", file: "x-combobox", cat: "form" }, { name: "Form", tag: "x-form", file: "x-form", cat: "form" }, { name: "Form Field", tag: "x-form-field", file: "x-form-field", cat: "form" }, { name: "Gaussian Blur", tag: "x-gaussian-blur", file: "x-gaussian-blur", cat: "layout", style: "effect" }, diff --git a/src/baredom/components/x_button/x_button.cljs b/src/baredom/components/x_button/x_button.cljs index edc2fcf..38c19e5 100644 --- a/src/baredom/components/x_button/x_button.cljs +++ b/src/baredom/components/x_button/x_button.cljs @@ -228,6 +228,8 @@ "line-height:1;" "cursor:pointer;" "user-select:none;" + "touch-action:manipulation;" + "-webkit-tap-highlight-color:transparent;" "box-shadow:var(--x-button-shadow);" "transition:" "background var(--x-button-transition-duration) var(--x-button-transition-easing)," diff --git a/src/baredom/components/x_context_menu/x_context_menu.cljs b/src/baredom/components/x_context_menu/x_context_menu.cljs index 39e338d..7de7f5e 100644 --- a/src/baredom/components/x_context_menu/x_context_menu.cljs +++ b/src/baredom/components/x_context_menu/x_context_menu.cljs @@ -7,6 +7,7 @@ (def ^:private k-refs "__xContextMenuRefs") (def ^:private k-layer "__xContextMenuLayer") +(def ^:private k-doc-handlers "__xContextMenuDocH") ;; ---- Forward declarations ---- @@ -148,11 +149,6 @@ (let [key (.-key ev) ^js focused (.-activeElement (.-shadowRoot layer))] (cond - (= key "Escape") - (do (.preventDefault ev) - (.stopPropagation ev) - (close! el "keyboard")) - (= key "Tab") (close! el "keyboard") @@ -173,14 +169,6 @@ (.preventDefault ev) (.click focused))))) - on-click-backdrop - (fn [^js ev] - ;; Click outside panel closes - (let [^js panel (overlay/get-panel layer)] - (when (and panel - (not (.contains panel (.-target ev)))) - (close! el "backdrop")))) - on-item-click (fn [^js ev] (.stopPropagation ev) @@ -191,15 +179,46 @@ (close! el "select"))))] (.addEventListener layer "keydown" on-key true) - (.addEventListener layer "click" on-click-backdrop) (when panel (.addEventListener panel "click" on-item-click)) ;; store for cleanup (gobj/set layer "__onKey" on-key) - (gobj/set layer "__onClickBackdrop" on-click-backdrop) (gobj/set layer "__onItemClick" on-item-click))) +;; ---- Document-level listeners (Escape + click-outside) ---- + +(defn- add-doc-listeners! [^js el] + (let [on-doc-keydown + (fn [^js ev] + (when (= (.-key ev) "Escape") + (.preventDefault ev) + (close! el "keyboard"))) + + on-doc-click + (fn [^js ev] + (when-let [^js lyr (gobj/get el k-layer)] + (let [^js panel (overlay/get-panel lyr)] + (when (and panel + (not (.contains panel (.-target ev)))) + (close! el "backdrop")))))] + + ;; Delay by one tick so the opening click/contextmenu does not immediately close + (js/setTimeout + (fn [] + (when (.hasAttribute el model/attr-open) + (.addEventListener js/document "keydown" on-doc-keydown) + (.addEventListener js/document "click" on-doc-click) + (gobj/set el k-doc-handlers + #js {:keydown on-doc-keydown :click on-doc-click}))) + 0))) + +(defn- remove-doc-listeners! [^js el] + (when-let [handlers (gobj/get el k-doc-handlers)] + (.removeEventListener js/document "keydown" (gobj/get handlers "keydown")) + (.removeEventListener js/document "click" (gobj/get handlers "click")) + (gobj/set el k-doc-handlers nil))) + (defn- remove-layer! [^js layer] (overlay/remove-layer! layer)) @@ -209,6 +228,7 @@ (when (.hasAttribute el model/attr-open) (let [proceed? (dispatch! el model/event-close-request true #js {:reason reason})] (when proceed? + (remove-doc-listeners! el) (.removeAttribute el model/attr-open) (let [^js layer (gobj/get el k-layer)] (remove-layer! layer) @@ -240,6 +260,7 @@ (.setAttribute el model/attr-open "") (gobj/set el k-layer layer) + (add-doc-listeners! el) ;; Re-position after actual panel dimensions are known (js/requestAnimationFrame @@ -279,6 +300,7 @@ (.setAttribute el model/attr-open "") (gobj/set el k-layer layer) + (add-doc-listeners! el) (js/requestAnimationFrame (fn [] diff --git a/test/baredom/components/x_welcome_tour/x_welcome_tour_test.cljs b/test/baredom/components/x_welcome_tour/x_welcome_tour_test.cljs index 97c9b75..b5b9299 100644 --- a/test/baredom/components/x_welcome_tour/x_welcome_tour_test.cljs +++ b/test/baredom/components/x_welcome_tour/x_welcome_tour_test.cljs @@ -244,7 +244,7 @@ (.next el) (.next el) (.complete el) - (is (= 3 (.-stepsCompleted @detail))) + (is (= 3 (.-stepsCompleted ^js @detail))) (done)) 100))))