shared dropdown: don't reopen when the trigger is clicked while open

mara, #3775: clicking a badge with its own dropdown open reopens it
instead of closing it. Root cause: the outside-click listener only
excludes the dropdown's own ref, not the sibling trigger that opened
it — a click on the trigger closes via that listener (pointerdown
fires first), then the trigger's own onClick toggle fires straight
after and reopens it, since its closure reads the pre-close state.

Dropdown now takes an optional anchorRef (the trigger's wrapper,
which callers already have for CSS positioning) and excludes it from
the outside-click check too — the same shape MetaNav's own hand-rolled
popover already uses correctly. Wired into StatusChips's Picker +
StatusMenu and swarm-ui's ComponentsPage demo (the only three Dropdown
consumers).

Verified two ways: reverted the fix, rebuilt, confirmed the bug
reproduces via a raw-CDP interaction test (two real clicks dispatched
through headless chromium, not just a static screenshot); restored
the fix, rebuilt, confirmed it passes.
This commit is contained in:
iris 2026-08-30 15:29:59 +02:00
commit b1254f89cd
3 changed files with 33 additions and 8 deletions

View file

@ -5,7 +5,7 @@
// primitive gets a section here the same day it's added. Sample data
// only, no network calls — this page must render the same whether
// swarm-controller's API is up or not.
import { useState } from 'preact/hooks';
import { useRef, useState } from 'preact/hooks';
import type { ComponentChildren } from 'preact';
import { Panel } from '../ui/panel/Panel.js';
import { RelativeTime } from '../ui/relative-time/RelativeTime.js';
@ -101,8 +101,9 @@ function RefreshIntervalPickerSample() {
function BadgePickerSample() {
const [value, setValue] = useState('sonnet');
const [open, setOpen] = useState(false);
const anchorRef = useRef<HTMLDivElement>(null);
return (
<div class="components-badge-anchor">
<div class="components-badge-anchor" ref={anchorRef}>
<Badge
label="model"
value={value}
@ -119,6 +120,7 @@ function BadgePickerSample() {
setOpen(false);
}}
onClose={() => setOpen(false)}
anchorRef={anchorRef}
/>
</div>
);