@particle-academy/react-fancy 5.63.0 → 5.64.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [5.64.1] - 2026-10-27
15
+
16
+ ### Fixed
17
+
18
+ - **`Dropdown`: the menu can be driven from the keyboard again.** Its
19
+ arrow-key listener was attached in an effect keyed on `open` that returned
20
+ early while the menu element was still null (the menu mounts a render later,
21
+ behind `useAnimation`, and `Portal` takes one more), and nothing in its
22
+ dependencies changed once the element appeared, so it never re-ran.
23
+ ArrowDown did nothing and no item of any Dropdown was reachable by keyboard.
24
+ Found building the showcase's Features menu. Keys are now handled on the menu
25
+ element itself, and the element is tracked as state, so the timing cannot
26
+ recur. The menu now follows the WAI-ARIA menu button pattern:
27
+ - On the trigger, Enter, Space or ArrowDown opens the menu on its first
28
+ enabled item and ArrowUp on its last. A non-button trigger (an avatar
29
+ `span` with `tabIndex`) opens on Enter / Space too.
30
+ - In the menu, ArrowDown / ArrowUp move and wrap, Home / End jump, a typed
31
+ character jumps to the next item starting with it, and disabled items are
32
+ skipped.
33
+ - Escape closes the menu (still through the `useEscapeKey` layer stack, so
34
+ only the innermost layer closes) and returns focus to the trigger. Tab
35
+ closes it and continues from the trigger's place in the tab order.
36
+ Activating an item returns focus to the trigger unless its `onClick`
37
+ moved focus somewhere else.
38
+
39
+ Nothing to do as a consumer: the props are unchanged, and opening by mouse
40
+ still leaves focus on the trigger. If you worked around this with your own
41
+ arrow-key handler, you can delete it. `DropdownContextValue` gains
42
+ `focusTrigger`, plus two `@internal` refs.
43
+
44
+ ## [5.64.0] - 2026-10-26
45
+
46
+ ### Added
47
+
48
+ - **`ReorderableGrid`: cells reordered in place, row-major, by pointer or
49
+ keyboard.** Requested by MOIC for a field-order designer. A deal's details
50
+ are a 2-to-4-column grid of label/value cells, and the designer has to look
51
+ like that grid. Until now it was faked with one `Kanban.Column`'s vertical
52
+ list.
53
+ - **Order is ids.** `value` / `defaultValue` are `string[]`, and `onReorder`
54
+ gets the full new order once per committed move. It is never called for
55
+ the live preview, a cancel or a drop back in place. The order is
56
+ reconciled with `items`: unknown ids are ignored and new items are
57
+ appended, so a stale saved order never hides a cell.
58
+ - **Pointer:** a 4px threshold keeps clicks working. The other cells shift
59
+ live and wrap across rows. Pointer capture is on the root. A release
60
+ outside the grid cancels. Drag handles via `data-reorderable-handle` or
61
+ `state.handleProps`. The nearest scroll container auto-scrolls near its
62
+ edge.
63
+ - **Keyboard:** one tab stop. Space or Enter picks up and drops, arrows move
64
+ in 2-D, and Escape cancels through the `useEscapeKey` layer stack, so
65
+ inside a Modal the pick-up cancels before the Modal closes. An
66
+ `aria-live` region announces each step ("Moved to position 3 of 8, row 1
67
+ column 3"). Every message can be overridden through `labels`.
68
+ - **`locked: true`** pins a cell at its absolute index. Other cells flow
69
+ around it, and nothing can be dropped on it.
70
+ - **`moveItem(ids, from, to, { locked })`** is exported: the same rule with no
71
+ DOM, for agent bridges.
72
+ - Subpath `@particle-academy/react-fancy/reorderable-grid`. Docs:
73
+ `docs/ReorderableGrid.md`. New component only; nothing existing changed.
74
+
14
75
  ## [5.63.0] - 2026-10-26
15
76
 
16
77
  ### Added
package/README.md CHANGED
@@ -165,6 +165,7 @@ npx vite build # Build demo app (verifies imports work)
165
165
  | Editor | Toolbar chrome wrapper for contentEditable, with a Source toggle to edit raw HTML/Markdown | [docs](docs/Editor.md) |
166
166
  | CodeView | Lightweight syntax-highlighted source view (HTML highlighted; fills height) | [docs](docs/CodeView.md) |
167
167
  | Kanban | Drag-and-drop board with columns and cards | [docs](docs/Kanban.md) |
168
+ | ReorderableGrid | Row-major grid of cells reordered in place by pointer or keyboard, with pinned cells and a pure `moveItem` for agents | [docs](docs/ReorderableGrid.md) |
168
169
  | ContentRenderer | Markdown/HTML content renderer | [docs](docs/ContentRenderer.md) |
169
170
 
170
171
  ### Media Viewers
@@ -0,0 +1,293 @@
1
+ import { useAnimation } from './chunk-3K2CEAI7.js';
2
+ import { useFloatingPosition } from './chunk-MQ6VGG62.js';
3
+ import { Portal } from './chunk-RBCMKPBX.js';
4
+ import { useOutsideClick } from './chunk-RGANGU7X.js';
5
+ import { useEscapeKey } from './chunk-YX6FTJ6N.js';
6
+ import { cn } from './chunk-MMW2JGKZ.js';
7
+ import { createContext, useState, useRef, useCallback, useMemo, useEffect, useContext } from 'react';
8
+ import { jsx } from 'react/jsx-runtime';
9
+
10
+ var DropdownContext = createContext(null);
11
+ function useDropdown() {
12
+ const ctx = useContext(DropdownContext);
13
+ if (!ctx) {
14
+ throw new Error(
15
+ "Dropdown compound components must be used within <Dropdown>"
16
+ );
17
+ }
18
+ return ctx;
19
+ }
20
+
21
+ // src/components/Dropdown/dropdown-focus.ts
22
+ function menuItems(menu) {
23
+ if (!menu) return [];
24
+ return Array.from(
25
+ menu.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"]):not([disabled])')
26
+ );
27
+ }
28
+ function focusMenuItem(menu, move) {
29
+ const items = menuItems(menu);
30
+ if (items.length === 0) return -1;
31
+ const current = items.indexOf(document.activeElement);
32
+ let next;
33
+ if (move === "first") next = 0;
34
+ else if (move === "last") next = items.length - 1;
35
+ else if (current === -1) next = move === "next" ? 0 : items.length - 1;
36
+ else next = (current + (move === "next" ? 1 : -1) + items.length) % items.length;
37
+ items[next].focus();
38
+ return next;
39
+ }
40
+ function focusByChar(menu, char) {
41
+ const items = menuItems(menu);
42
+ if (items.length === 0) return -1;
43
+ const needle = char.toLowerCase();
44
+ const start = items.indexOf(document.activeElement);
45
+ for (let step = 1; step <= items.length; step++) {
46
+ const i = (start + step + items.length) % items.length;
47
+ if ((items[i].textContent ?? "").trim().toLowerCase().startsWith(needle)) {
48
+ items[i].focus();
49
+ return i;
50
+ }
51
+ }
52
+ return -1;
53
+ }
54
+ function activatesNatively(el) {
55
+ if (!(el instanceof HTMLElement)) return false;
56
+ const tag = el.tagName;
57
+ if (tag === "BUTTON" || tag === "SUMMARY") return true;
58
+ if (tag === "A") return el.hasAttribute("href");
59
+ if (tag === "INPUT") {
60
+ const type = el.type;
61
+ return type === "button" || type === "submit" || type === "reset" || type === "image";
62
+ }
63
+ return false;
64
+ }
65
+ function DropdownTrigger({ children }) {
66
+ const { setOpen, open, anchorRef, pendingFocusRef, menuRef } = useDropdown();
67
+ const onKeyDown = (e) => {
68
+ if (e.defaultPrevented) return;
69
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
70
+ e.preventDefault();
71
+ const target = e.key === "ArrowDown" ? "first" : "last";
72
+ if (open && menuRef.current) {
73
+ focusMenuItem(menuRef.current, target);
74
+ return;
75
+ }
76
+ pendingFocusRef.current = target;
77
+ setOpen(true);
78
+ return;
79
+ }
80
+ if (e.key === "Enter" || e.key === " ") {
81
+ if (!open) pendingFocusRef.current = "first";
82
+ if (activatesNatively(e.target)) return;
83
+ e.preventDefault();
84
+ if (open) pendingFocusRef.current = null;
85
+ setOpen(!open);
86
+ }
87
+ };
88
+ const onClick = (_e) => {
89
+ if (open) pendingFocusRef.current = null;
90
+ setOpen(!open);
91
+ };
92
+ return /* @__PURE__ */ jsx(
93
+ "span",
94
+ {
95
+ ref: anchorRef,
96
+ "data-react-fancy-dropdown-trigger": "",
97
+ className: "inline-flex",
98
+ onClick,
99
+ onKeyDown,
100
+ "aria-expanded": open,
101
+ "aria-haspopup": "menu",
102
+ children
103
+ }
104
+ );
105
+ }
106
+ DropdownTrigger.displayName = "DropdownTrigger";
107
+ function DropdownItems({ children, className }) {
108
+ const {
109
+ open,
110
+ setOpen,
111
+ anchorRef,
112
+ setActiveIndex,
113
+ placement,
114
+ offset,
115
+ pendingFocusRef,
116
+ menuRef,
117
+ focusTrigger
118
+ } = useDropdown();
119
+ const floatingRef = useRef(null);
120
+ const outsideRef = useRef(null);
121
+ const position = useFloatingPosition(anchorRef, floatingRef, {
122
+ placement,
123
+ offset,
124
+ enabled: open
125
+ });
126
+ const close = useCallback(() => {
127
+ pendingFocusRef.current = null;
128
+ setOpen(false);
129
+ setActiveIndex(-1);
130
+ }, [setOpen, setActiveIndex, pendingFocusRef]);
131
+ useOutsideClick(outsideRef, close, open, anchorRef);
132
+ useEscapeKey(() => {
133
+ close();
134
+ focusTrigger();
135
+ }, open);
136
+ const { mounted, className: animClass, ref: animRef } = useAnimation({
137
+ open,
138
+ enterClass: "fancy-scale-in",
139
+ exitClass: "fancy-fade-out"
140
+ });
141
+ const [menuEl, setMenuEl] = useState(null);
142
+ const setMenuRef = useCallback(
143
+ (node) => {
144
+ outsideRef.current = node;
145
+ floatingRef.current = node;
146
+ animRef.current = node;
147
+ menuRef.current = node;
148
+ setMenuEl(node);
149
+ },
150
+ [animRef, menuRef]
151
+ );
152
+ useEffect(() => {
153
+ if (!open || !menuEl) return;
154
+ const target = pendingFocusRef.current;
155
+ if (!target) return;
156
+ pendingFocusRef.current = null;
157
+ setActiveIndex(focusMenuItem(menuEl, target));
158
+ }, [open, menuEl, pendingFocusRef, setActiveIndex]);
159
+ const onKeyDown = (e) => {
160
+ const menu = e.currentTarget;
161
+ const moves = {
162
+ ArrowDown: "next",
163
+ ArrowUp: "prev",
164
+ Home: "first",
165
+ End: "last"
166
+ };
167
+ const move = moves[e.key];
168
+ if (move) {
169
+ e.preventDefault();
170
+ setActiveIndex(focusMenuItem(menu, move));
171
+ return;
172
+ }
173
+ if (e.key === "Tab") {
174
+ close();
175
+ focusTrigger();
176
+ return;
177
+ }
178
+ if (e.key.length === 1 && e.key !== " " && !e.ctrlKey && !e.metaKey && !e.altKey) {
179
+ const i = focusByChar(menu, e.key);
180
+ if (i >= 0) setActiveIndex(i);
181
+ }
182
+ };
183
+ if (!mounted) return null;
184
+ return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
185
+ "div",
186
+ {
187
+ ref: setMenuRef,
188
+ "data-react-fancy-dropdown": "",
189
+ role: "menu",
190
+ onKeyDown,
191
+ className: cn(
192
+ "fixed z-50 min-w-[8rem] rounded-xl border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] p-1 shadow-lg dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]",
193
+ animClass,
194
+ className
195
+ ),
196
+ style: { left: position.x, top: position.y },
197
+ children
198
+ }
199
+ ) });
200
+ }
201
+ DropdownItems.displayName = "DropdownItems";
202
+ function DropdownItem({
203
+ children,
204
+ onClick,
205
+ disabled = false,
206
+ danger = false,
207
+ className
208
+ }) {
209
+ const { setOpen, focusTrigger } = useDropdown();
210
+ return /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ "data-react-fancy-dropdown-item": "",
214
+ type: "button",
215
+ role: "menuitem",
216
+ disabled,
217
+ "aria-disabled": disabled,
218
+ className: cn(
219
+ "flex w-full items-center rounded-lg px-3 py-2 text-left text-sm transition-colors",
220
+ danger ? "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950" : "text-[color:var(--fancy-text-soft,var(--color-zinc-700))] hover:bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))] dark:hover:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]",
221
+ disabled && "cursor-not-allowed opacity-50",
222
+ className
223
+ ),
224
+ onClick: (e) => {
225
+ if (disabled) return;
226
+ const hadFocus = document.activeElement === e.currentTarget;
227
+ onClick?.();
228
+ setOpen(false);
229
+ if (hadFocus && document.activeElement === e.currentTarget) focusTrigger();
230
+ },
231
+ children
232
+ }
233
+ );
234
+ }
235
+ DropdownItem.displayName = "DropdownItem";
236
+ function DropdownSeparator({ className }) {
237
+ return /* @__PURE__ */ jsx(
238
+ "div",
239
+ {
240
+ "data-react-fancy-dropdown-separator": "",
241
+ role: "separator",
242
+ className: cn(
243
+ "my-1 h-px bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))]",
244
+ className
245
+ )
246
+ }
247
+ );
248
+ }
249
+ DropdownSeparator.displayName = "DropdownSeparator";
250
+ var TRIGGER_FOCUSABLE = 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
251
+ function DropdownRoot({
252
+ children,
253
+ placement = "bottom-start",
254
+ offset = 4
255
+ }) {
256
+ const [open, setOpen] = useState(false);
257
+ const [activeIndex, setActiveIndex] = useState(-1);
258
+ const anchorRef = useRef(null);
259
+ const pendingFocusRef = useRef(null);
260
+ const menuRef = useRef(null);
261
+ const focusTrigger = useCallback(() => {
262
+ const anchor = anchorRef.current;
263
+ if (!anchor) return;
264
+ const target = anchor.querySelector(TRIGGER_FOCUSABLE) ?? (anchor.tabIndex >= 0 ? anchor : null);
265
+ target?.focus();
266
+ }, []);
267
+ const ctx = useMemo(
268
+ () => ({
269
+ open,
270
+ setOpen,
271
+ anchorRef,
272
+ activeIndex,
273
+ setActiveIndex,
274
+ placement,
275
+ offset,
276
+ pendingFocusRef,
277
+ menuRef,
278
+ focusTrigger
279
+ }),
280
+ [open, anchorRef, activeIndex, placement, offset, focusTrigger]
281
+ );
282
+ return /* @__PURE__ */ jsx(DropdownContext.Provider, { value: ctx, children });
283
+ }
284
+ var Dropdown = Object.assign(DropdownRoot, {
285
+ Trigger: DropdownTrigger,
286
+ Items: DropdownItems,
287
+ Item: DropdownItem,
288
+ Separator: DropdownSeparator
289
+ });
290
+
291
+ export { Dropdown, useDropdown };
292
+ //# sourceMappingURL=chunk-BXPLERIJ.js.map
293
+ //# sourceMappingURL=chunk-BXPLERIJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/Dropdown/Dropdown.context.ts","../src/components/Dropdown/dropdown-focus.ts","../src/components/Dropdown/DropdownTrigger.tsx","../src/components/Dropdown/DropdownItems.tsx","../src/components/Dropdown/DropdownItem.tsx","../src/components/Dropdown/DropdownSeparator.tsx","../src/components/Dropdown/Dropdown.tsx"],"names":["jsx","useState","useRef","useCallback"],"mappings":"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","file":"chunk-BXPLERIJ.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { DropdownContextValue } from \"./Dropdown.types\";\n\nexport const DropdownContext = createContext<DropdownContextValue | null>(null);\n\nexport function useDropdown(): DropdownContextValue {\n const ctx = useContext(DropdownContext);\n if (!ctx) {\n throw new Error(\n \"Dropdown compound components must be used within <Dropdown>\",\n );\n }\n return ctx;\n}\n","/** Enabled menu items, in DOM order. Disabled items are skipped. */\nexport function menuItems(menu: HTMLElement | null): HTMLElement[] {\n if (!menu) return [];\n return Array.from(\n menu.querySelectorAll<HTMLElement>('[role=\"menuitem\"]:not([aria-disabled=\"true\"]):not([disabled])'),\n );\n}\n\nexport type FocusMove = \"first\" | \"last\" | \"next\" | \"prev\";\n\n/**\n * Focus an item relative to the one that currently has focus, wrapping at both\n * ends. Returns the index focused, or -1 when the menu has no enabled item.\n */\nexport function focusMenuItem(menu: HTMLElement | null, move: FocusMove): number {\n const items = menuItems(menu);\n if (items.length === 0) return -1;\n const current = items.indexOf(document.activeElement as HTMLElement);\n let next: number;\n if (move === \"first\") next = 0;\n else if (move === \"last\") next = items.length - 1;\n else if (current === -1) next = move === \"next\" ? 0 : items.length - 1;\n else next = (current + (move === \"next\" ? 1 : -1) + items.length) % items.length;\n items[next]!.focus();\n return next;\n}\n\n/**\n * Typeahead: focus the next enabled item (after the focused one, wrapping)\n * whose text starts with `char`. Returns the index focused, or -1 for no match.\n */\nexport function focusByChar(menu: HTMLElement | null, char: string): number {\n const items = menuItems(menu);\n if (items.length === 0) return -1;\n const needle = char.toLowerCase();\n const start = items.indexOf(document.activeElement as HTMLElement);\n for (let step = 1; step <= items.length; step++) {\n const i = (start + step + items.length) % items.length;\n if ((items[i]!.textContent ?? \"\").trim().toLowerCase().startsWith(needle)) {\n items[i]!.focus();\n return i;\n }\n }\n return -1;\n}\n","import type { KeyboardEvent, MouseEvent } from \"react\";\nimport { useDropdown } from \"./Dropdown.context\";\nimport { focusMenuItem } from \"./dropdown-focus\";\nimport type { DropdownTriggerProps } from \"./Dropdown.types\";\n\n/**\n * Elements the browser itself turns Enter / Space into a click on. For these\n * the trigger lets the native click open the menu; for anything else (an\n * avatar span with `tabIndex`) it opens the menu itself.\n */\nfunction activatesNatively(el: EventTarget | null): boolean {\n if (!(el instanceof HTMLElement)) return false;\n const tag = el.tagName;\n if (tag === \"BUTTON\" || tag === \"SUMMARY\") return true;\n if (tag === \"A\") return el.hasAttribute(\"href\");\n if (tag === \"INPUT\") {\n const type = (el as HTMLInputElement).type;\n return type === \"button\" || type === \"submit\" || type === \"reset\" || type === \"image\";\n }\n return false;\n}\n\nexport function DropdownTrigger({ children }: DropdownTriggerProps) {\n const { setOpen, open, anchorRef, pendingFocusRef, menuRef } = useDropdown();\n\n // WAI-ARIA menu button: Enter / Space / ArrowDown open the menu on its first\n // item, ArrowUp on its last.\n const onKeyDown = (e: KeyboardEvent<HTMLSpanElement>) => {\n if (e.defaultPrevented) return;\n\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n const target = e.key === \"ArrowDown\" ? \"first\" : \"last\";\n if (open && menuRef.current) {\n focusMenuItem(menuRef.current, target);\n return;\n }\n pendingFocusRef.current = target;\n setOpen(true);\n return;\n }\n\n if (e.key === \"Enter\" || e.key === \" \") {\n if (!open) pendingFocusRef.current = \"first\";\n if (activatesNatively(e.target)) return; // its click toggles the menu\n e.preventDefault();\n if (open) pendingFocusRef.current = null;\n setOpen(!open);\n }\n };\n\n const onClick = (_e: MouseEvent<HTMLSpanElement>) => {\n if (open) pendingFocusRef.current = null;\n setOpen(!open);\n };\n\n return (\n <span\n ref={anchorRef as React.RefObject<HTMLSpanElement>}\n data-react-fancy-dropdown-trigger=\"\"\n className=\"inline-flex\"\n onClick={onClick}\n onKeyDown={onKeyDown}\n aria-expanded={open}\n aria-haspopup=\"menu\"\n >\n {children}\n </span>\n );\n}\n\nDropdownTrigger.displayName = \"DropdownTrigger\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { focusByChar, focusMenuItem, type FocusMove } from \"./dropdown-focus\";\nimport { cn } from \"../../utils/cn\";\nimport { Portal } from \"../Portal\";\nimport { useDropdown } from \"./Dropdown.context\";\nimport { useFloatingPosition } from \"../../hooks/use-floating-position\";\nimport { useOutsideClick } from \"../../hooks/use-outside-click\";\nimport { useEscapeKey } from \"../../hooks/use-escape-key\";\nimport { useAnimation } from \"../../hooks/use-animation\";\nimport type { DropdownItemsProps } from \"./Dropdown.types\";\n\nexport function DropdownItems({ children, className }: DropdownItemsProps) {\n const {\n open,\n setOpen,\n anchorRef,\n setActiveIndex,\n placement,\n offset,\n pendingFocusRef,\n menuRef,\n focusTrigger,\n } = useDropdown();\n const floatingRef = useRef<HTMLDivElement>(null);\n const outsideRef = useRef<HTMLDivElement>(null);\n\n const position = useFloatingPosition(anchorRef, floatingRef, {\n placement,\n offset,\n enabled: open,\n });\n\n const close = useCallback(() => {\n pendingFocusRef.current = null;\n setOpen(false);\n setActiveIndex(-1);\n }, [setOpen, setActiveIndex, pendingFocusRef]);\n\n useOutsideClick(outsideRef, close, open, anchorRef);\n // Escape closes THIS menu (the innermost layer) and returns focus to where\n // the menu came from. An outside click does not move focus: the user put it\n // somewhere already.\n useEscapeKey(() => {\n close();\n focusTrigger();\n }, open);\n\n const { mounted, className: animClass, ref: animRef } = useAnimation({\n open,\n enterClass: \"fancy-scale-in\",\n exitClass: \"fancy-fade-out\",\n });\n\n // The menu element, as STATE. It does not exist when `open` first turns true:\n // `useAnimation` mounts it a render later and `Portal` a render after that.\n // Before 5.64.1 the key handler lived in an effect keyed on `open` that\n // returned early while the element was null and never re-ran, so the arrow\n // keys did nothing in any Dropdown. Keying on the element itself fixes the\n // timing for good.\n const [menuEl, setMenuEl] = useState<HTMLDivElement | null>(null);\n\n const setMenuRef = useCallback(\n (node: HTMLDivElement | null) => {\n outsideRef.current = node;\n floatingRef.current = node;\n (animRef as React.MutableRefObject<HTMLElement | null>).current = node;\n menuRef.current = node;\n setMenuEl(node);\n },\n [animRef, menuRef],\n );\n\n // Opened from the keyboard: focus the first / last item once it exists.\n useEffect(() => {\n if (!open || !menuEl) return;\n const target = pendingFocusRef.current;\n if (!target) return;\n pendingFocusRef.current = null;\n setActiveIndex(focusMenuItem(menuEl, target));\n }, [open, menuEl, pendingFocusRef, setActiveIndex]);\n\n // Keys are handled on the menu itself: focus is inside it while it is driven\n // from the keyboard, and React delivers the event through the portal.\n const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n const menu = e.currentTarget;\n const moves: Record<string, FocusMove> = {\n ArrowDown: \"next\",\n ArrowUp: \"prev\",\n Home: \"first\",\n End: \"last\",\n };\n const move = moves[e.key];\n if (move) {\n e.preventDefault();\n setActiveIndex(focusMenuItem(menu, move));\n return;\n }\n if (e.key === \"Tab\") {\n // Close, and put focus back on the trigger WITHOUT preventing the\n // default: the browser's own Tab then moves on from the trigger's place\n // in the tab order rather than from the end of <body>, where the\n // portalled menu lives.\n close();\n focusTrigger();\n return;\n }\n if (e.key.length === 1 && e.key !== \" \" && !e.ctrlKey && !e.metaKey && !e.altKey) {\n const i = focusByChar(menu, e.key);\n if (i >= 0) setActiveIndex(i);\n }\n };\n\n if (!mounted) return null;\n\n return (\n <Portal>\n <div\n ref={setMenuRef}\n data-react-fancy-dropdown=\"\"\n role=\"menu\"\n onKeyDown={onKeyDown}\n className={cn(\n \"fixed z-50 min-w-[8rem] rounded-xl border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-[color:var(--fancy-surface,var(--color-white))] p-1 shadow-lg dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n animClass,\n className,\n )}\n style={{ left: position.x, top: position.y }}\n >\n {children}\n </div>\n </Portal>\n );\n}\n\nDropdownItems.displayName = \"DropdownItems\";\n","import { cn } from \"../../utils/cn\";\nimport { useDropdown } from \"./Dropdown.context\";\nimport type { DropdownItemProps } from \"./Dropdown.types\";\n\nexport function DropdownItem({\n children,\n onClick,\n disabled = false,\n danger = false,\n className,\n}: DropdownItemProps) {\n const { setOpen, focusTrigger } = useDropdown();\n\n return (\n <button\n data-react-fancy-dropdown-item=\"\"\n type=\"button\"\n role=\"menuitem\"\n disabled={disabled}\n aria-disabled={disabled}\n className={cn(\n \"flex w-full items-center rounded-lg px-3 py-2 text-left text-sm transition-colors\",\n danger\n ? \"text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950\"\n : \"text-[color:var(--fancy-text-soft,var(--color-zinc-700))] hover:bg-[color:var(--fancy-surface-muted,var(--color-zinc-100))] dark:text-[color:var(--fancy-text-soft-dark,var(--color-zinc-300))] dark:hover:bg-[color:var(--fancy-surface-muted-dark,var(--color-zinc-800))]\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n onClick={(e) => {\n if (disabled) return;\n const hadFocus = document.activeElement === e.currentTarget;\n onClick?.();\n setOpen(false);\n // The item is about to unmount; without this, focus falls to <body>.\n // Only when the item held focus, and only if onClick did not move it.\n if (hadFocus && document.activeElement === e.currentTarget) focusTrigger();\n }}\n >\n {children}\n </button>\n );\n}\n\nDropdownItem.displayName = \"DropdownItem\";\n","import { cn } from \"../../utils/cn\";\nimport type { DropdownSeparatorProps } from \"./Dropdown.types\";\n\nexport function DropdownSeparator({ className }: DropdownSeparatorProps) {\n return (\n <div\n data-react-fancy-dropdown-separator=\"\"\n role=\"separator\"\n className={cn(\n \"my-1 h-px bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))]\",\n className,\n )}\n />\n );\n}\n\nDropdownSeparator.displayName = \"DropdownSeparator\";\n","import { useCallback, useMemo, useRef, useState } from \"react\";\nimport { DropdownContext } from \"./Dropdown.context\";\nimport { DropdownTrigger } from \"./DropdownTrigger\";\nimport { DropdownItems } from \"./DropdownItems\";\nimport { DropdownItem } from \"./DropdownItem\";\nimport { DropdownSeparator } from \"./DropdownSeparator\";\nimport type { DropdownProps } from \"./Dropdown.types\";\n\nconst TRIGGER_FOCUSABLE =\n 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction DropdownRoot({\n children,\n placement = \"bottom-start\",\n offset = 4,\n}: DropdownProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n const anchorRef = useRef<HTMLElement>(null);\n const pendingFocusRef = useRef<\"first\" | \"last\" | null>(null);\n const menuRef = useRef<HTMLElement | null>(null);\n\n const focusTrigger = useCallback(() => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const target =\n anchor.querySelector<HTMLElement>(TRIGGER_FOCUSABLE) ?? (anchor.tabIndex >= 0 ? anchor : null);\n target?.focus();\n }, []);\n\n const ctx = useMemo(\n () => ({\n open,\n setOpen,\n anchorRef,\n activeIndex,\n setActiveIndex,\n placement,\n offset,\n pendingFocusRef,\n menuRef,\n focusTrigger,\n }),\n [open, anchorRef, activeIndex, placement, offset, focusTrigger],\n );\n\n return (\n <DropdownContext.Provider value={ctx}>{children}</DropdownContext.Provider>\n );\n}\n\nexport const Dropdown = Object.assign(DropdownRoot, {\n Trigger: DropdownTrigger,\n Items: DropdownItems,\n Item: DropdownItem,\n Separator: DropdownSeparator,\n});\n"]}