From 2c8a403cc0e16a064eaf22d5659be1d6647b0ea2 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 16 Jul 2026 09:39:18 +0200 Subject: [PATCH] fix(member-live): guard the composite sort event and apply review cleanup --- assets/css/app.css | 3 +- assets/js/app.js | 52 +++++++++++-------- lib/mv_web/components/core_components.ex | 2 +- .../components/member_name_sort_header.ex | 1 + lib/mv_web/live/member_live/index.ex | 17 +++--- priv/repo/seeds_bulk_members.exs | 39 ++++++++------ 6 files changed, 66 insertions(+), 48 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index 8b998608..f13f64c8 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -923,8 +923,7 @@ * pure CSS anchor positioning (the "⋮" trigger carries anchor-name, this popover * carries position-anchor). `position-area` drops it just below the trigger, * right-aligned, and `position-try-fallbacks` flips it above when there is no - * room below. The SubfieldMenu hook only toggles it — no coordinate math. This - * mirrors the payment-badge popover approach (§548) for a consistent look/feel. + * room below. The SubfieldMenu hook only toggles it — no coordinate math. */ .popover-menu { margin: 0; diff --git a/assets/js/app.js b/assets/js/app.js index 85ea1875..745d580f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -352,6 +352,20 @@ Hooks.SortableList = { } } +// Guarded native Popover API toggles: showPopover()/hidePopover() throw on a +// detached element or a redundant open/close, so the popover hooks below share +// these wrappers instead of repeating the try/catch + :popover-open check. +function openPopover(el) { + try { + if (el && !el.matches(":popover-open")) el.showPopover() + } catch (_e) {} +} +function closePopover(el) { + try { + if (el && el.matches(":popover-open")) el.hidePopover() + } catch (_e) {} +} + // PopoverTooltip hook: shows a hint (referenced by data-tooltip-id) in the // browser TOP LAYER via the native Popover API, so it escapes the members // table's overflow clipping (overflow-x:auto forces overflow-y:auto, which clips @@ -386,19 +400,15 @@ Hooks.PopoverTooltip = { } this.show = () => { + openPopover(this.tip) + // Popover is measurable once open; positioning in the same tick avoids a + // visible flash (JS runs before the browser paints). try { - if (!this.tip.matches(":popover-open")) this.tip.showPopover() - // showPopover() makes it measurable; positioning in the same tick avoids - // a visible flash (JS runs before the browser paints). this.position() } catch (_e) {} } - this.hide = () => { - try { - if (this.tip.matches(":popover-open")) this.tip.hidePopover() - } catch (_e) {} - } + this.hide = () => closePopover(this.tip) this.onKey = (e) => { if (e.key === "Escape") this.hide() } @@ -431,8 +441,9 @@ Hooks.PopoverTooltip = { // sort sub-field. Attached to the "⋮" trigger; data-menu-id names the