@charcuterie/ui 1.1.0 → 2.0.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.
Files changed (116) hide show
  1. package/dist/Combobox/Combobox.d.ts +54 -0
  2. package/dist/Combobox/Combobox.d.ts.map +1 -0
  3. package/dist/Combobox/Combobox.js +216 -0
  4. package/dist/Combobox/Combobox.js.map +1 -0
  5. package/dist/Combobox/ComboboxOption.d.ts +36 -0
  6. package/dist/Combobox/ComboboxOption.d.ts.map +1 -0
  7. package/dist/Combobox/ComboboxOption.js +32 -0
  8. package/dist/Combobox/ComboboxOption.js.map +1 -0
  9. package/dist/Dialog/Dialog.d.ts +53 -0
  10. package/dist/Dialog/Dialog.d.ts.map +1 -0
  11. package/dist/Dialog/Dialog.js +52 -0
  12. package/dist/Dialog/Dialog.js.map +1 -0
  13. package/dist/Lightbox/Lightbox.d.ts +7 -7
  14. package/dist/Lightbox/Lightbox.js +9 -9
  15. package/dist/Lightbox/Lightbox.js.map +1 -1
  16. package/dist/Listbox/Listbox.d.ts +56 -0
  17. package/dist/Listbox/Listbox.d.ts.map +1 -0
  18. package/dist/Listbox/Listbox.js +151 -0
  19. package/dist/Listbox/Listbox.js.map +1 -0
  20. package/dist/Listbox/ListboxOption.d.ts +36 -0
  21. package/dist/Listbox/ListboxOption.d.ts.map +1 -0
  22. package/dist/Listbox/ListboxOption.js +42 -0
  23. package/dist/Listbox/ListboxOption.js.map +1 -0
  24. package/dist/Menu/Menu.d.ts.map +1 -1
  25. package/dist/Menu/Menu.js +47 -97
  26. package/dist/Menu/Menu.js.map +1 -1
  27. package/dist/Modal/Modal.d.ts +11 -59
  28. package/dist/Modal/Modal.d.ts.map +1 -1
  29. package/dist/Modal/Modal.js +28 -101
  30. package/dist/Modal/Modal.js.map +1 -1
  31. package/dist/Overlay/OverlayPanel.d.ts +50 -0
  32. package/dist/Overlay/OverlayPanel.d.ts.map +1 -0
  33. package/dist/Overlay/OverlayPanel.js +72 -0
  34. package/dist/Overlay/OverlayPanel.js.map +1 -0
  35. package/dist/Overlay/OverlayStack.d.ts +47 -0
  36. package/dist/Overlay/OverlayStack.d.ts.map +1 -0
  37. package/dist/Overlay/OverlayStack.js +46 -0
  38. package/dist/Overlay/OverlayStack.js.map +1 -0
  39. package/dist/Overlay/SharedBackdrop.d.ts +24 -0
  40. package/dist/Overlay/SharedBackdrop.d.ts.map +1 -0
  41. package/dist/Overlay/SharedBackdrop.js +28 -0
  42. package/dist/Overlay/SharedBackdrop.js.map +1 -0
  43. package/dist/Overlay/lockScrollBehind.d.ts.map +1 -0
  44. package/dist/Overlay/lockScrollBehind.js.map +1 -0
  45. package/dist/Overlay/overlayPanelClass.d.ts +17 -0
  46. package/dist/Overlay/overlayPanelClass.d.ts.map +1 -0
  47. package/dist/Overlay/overlayPanelClass.js +17 -0
  48. package/dist/Overlay/overlayPanelClass.js.map +1 -0
  49. package/dist/Overlay/useAnchoredOverlay.d.ts +97 -0
  50. package/dist/Overlay/useAnchoredOverlay.d.ts.map +1 -0
  51. package/dist/Overlay/useAnchoredOverlay.js +81 -0
  52. package/dist/Overlay/useAnchoredOverlay.js.map +1 -0
  53. package/dist/Popover/Popover.d.ts +16 -24
  54. package/dist/Popover/Popover.d.ts.map +1 -1
  55. package/dist/Popover/Popover.js +32 -68
  56. package/dist/Popover/Popover.js.map +1 -1
  57. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  58. package/dist/Tooltip/Tooltip.js +25 -25
  59. package/dist/Tooltip/Tooltip.js.map +1 -1
  60. package/dist/index.d.ts +7 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/mountStory.testHelpers.d.ts +1 -0
  65. package/dist/mountStory.testHelpers.d.ts.map +1 -1
  66. package/dist/mountStory.testHelpers.js +21 -1
  67. package/dist/mountStory.testHelpers.js.map +1 -1
  68. package/package.json +3 -2
  69. package/src/Combobox/Combobox.mdx +71 -0
  70. package/src/Combobox/Combobox.stories.tsx +245 -0
  71. package/src/Combobox/Combobox.test.tsx +289 -0
  72. package/src/Combobox/Combobox.tsx +513 -0
  73. package/src/Combobox/ComboboxOption.tsx +88 -0
  74. package/src/Dialog/Dialog.mdx +87 -0
  75. package/src/Dialog/Dialog.stories.tsx +397 -0
  76. package/src/Dialog/Dialog.test.tsx +340 -0
  77. package/src/Dialog/Dialog.tsx +133 -0
  78. package/src/Field/Field.test.tsx +6 -4
  79. package/src/Lightbox/Lightbox.mdx +9 -8
  80. package/src/Lightbox/Lightbox.stories.tsx +5 -5
  81. package/src/Lightbox/Lightbox.test.tsx +25 -36
  82. package/src/Lightbox/Lightbox.tsx +10 -10
  83. package/src/Listbox/Listbox.mdx +68 -0
  84. package/src/Listbox/Listbox.stories.tsx +205 -0
  85. package/src/Listbox/Listbox.test.tsx +198 -0
  86. package/src/Listbox/Listbox.tsx +295 -0
  87. package/src/Listbox/ListboxOption.tsx +104 -0
  88. package/src/Menu/Menu.mdx +11 -13
  89. package/src/Menu/Menu.test.tsx +47 -38
  90. package/src/Menu/Menu.tsx +81 -142
  91. package/src/Modal/Modal.mdx +29 -82
  92. package/src/Modal/Modal.stories.tsx +55 -188
  93. package/src/Modal/Modal.test.tsx +44 -177
  94. package/src/Modal/Modal.tsx +54 -204
  95. package/src/Overlay/OverlayPanel.tsx +195 -0
  96. package/src/Overlay/OverlayStack.test.tsx +235 -0
  97. package/src/Overlay/OverlayStack.tsx +132 -0
  98. package/src/Overlay/SharedBackdrop.tsx +40 -0
  99. package/src/Overlay/overlayPanelClass.ts +17 -0
  100. package/src/Overlay/useAnchoredOverlay.ts +197 -0
  101. package/src/Popover/Popover.mdx +17 -20
  102. package/src/Popover/Popover.stories.tsx +33 -0
  103. package/src/Popover/Popover.test.tsx +68 -32
  104. package/src/Popover/Popover.tsx +56 -95
  105. package/src/Tooltip/Tooltip.mdx +12 -9
  106. package/src/Tooltip/Tooltip.test.tsx +17 -19
  107. package/src/Tooltip/Tooltip.tsx +35 -32
  108. package/src/index.ts +13 -4
  109. package/src/mountStory.testHelpers.ts +25 -1
  110. package/src/sourceRules.test.ts +12 -3
  111. package/src/storyControls.test.ts +25 -12
  112. package/dist/Modal/lockScrollBehind.d.ts.map +0 -1
  113. package/dist/Modal/lockScrollBehind.js.map +0 -1
  114. /package/dist/{Modal → Overlay}/lockScrollBehind.d.ts +0 -0
  115. /package/dist/{Modal → Overlay}/lockScrollBehind.js +0 -0
  116. /package/src/{Modal → Overlay}/lockScrollBehind.ts +0 -0
@@ -0,0 +1,151 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { selectTabIndex, useRovingFocus, useSinglePicker, } from "@charcuterie/logic";
3
+ import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
4
+ import { useEffect, useRef } from "react";
5
+ import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
6
+ import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
7
+ import { toClassName } from "../toClassName.js";
8
+ import { ListboxOption } from "./ListboxOption.js";
9
+ const getOptionText = (item) => (item.textValue ??
10
+ (typeof item.label === "string" ? item.label : "")).toLowerCase();
11
+ /**
12
+ * The single-select fancy dropdown — the rich-option sibling of the
13
+ * native `Select`. `Select` keeps type-ahead, the mobile wheel, form
14
+ * submission and autofill; `Listbox` is for the cases `<option>`
15
+ * cannot render: an icon, two lines, a trailing badge.
16
+ *
17
+ * A listbox is **not** a menu, and that distinction is the whole
18
+ * reason it is a separate component from `Menu`. An `option` *is*
19
+ * something you are choosing — so it carries `aria-selected`, it
20
+ * type-aheads (which the APG requires of a listbox and `Menu`
21
+ * deliberately omits), and a screen reader announces "listbox,
22
+ * selected, 2 of 4". A `menuitem` only ever *does* something.
23
+ *
24
+ * ### Two kinds of state, composed
25
+ *
26
+ * `useRovingFocus` owns which option is tabbable (exactly one, so Tab
27
+ * enters and leaves the group once and the arrow keys move within it),
28
+ * and `useSinglePicker` owns which option is chosen. They are the same
29
+ * two kinds `Tabs` composes, and they never disturb each other:
30
+ * arrowing moves focus without selecting, Enter selects the focused
31
+ * option.
32
+ *
33
+ * ### Portalled, and named by its trigger
34
+ *
35
+ * Like `Menu`, the panel portals to `document.body` and `useRole` names
36
+ * it by pointing `aria-labelledby` at the trigger — so there is no
37
+ * `heading` prop; the button that opens it is its label.
38
+ */
39
+ export const Listbox = ({ className, isVisible, onDismiss, onSelect, options, placement = "bottom-start", selectedValue, trigger, }) => {
40
+ const optionElements = useRef(new Map());
41
+ const typeahead = useRef({ buffer: "", stampMs: 0 });
42
+ const focus = useRovingFocus();
43
+ const picker = useSinglePicker({ selectedValue });
44
+ const { activeValue, registeredValues, setActiveValue } = focus;
45
+ const [firstValue] = registeredValues;
46
+ const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, triggerId, } = useAnchoredOverlay({
47
+ isVisible,
48
+ maxHeightPx: 384,
49
+ offsetValue: 4,
50
+ onDismiss,
51
+ placement,
52
+ role: "listbox",
53
+ trigger,
54
+ });
55
+ const chooseValue = (value) => {
56
+ picker.select(value);
57
+ onSelect(value);
58
+ // A single choice closes, the way the native `<select>` it stands
59
+ // in for does — the consumer's `onDismiss` is the same one an
60
+ // outside press and Escape use.
61
+ onDismiss();
62
+ };
63
+ /**
64
+ * Opening lands the focus on the chosen option, or the first when
65
+ * nothing is chosen yet — the APG rule for a listbox, and the reason
66
+ * a reopened one resumes on the current value rather than the top.
67
+ */
68
+ useEffect(() => {
69
+ if (!isVisible) {
70
+ return;
71
+ }
72
+ const seed = picker.selectedValue ?? firstValue;
73
+ if (seed !== undefined && seed !== null) {
74
+ setActiveValue(seed);
75
+ }
76
+ }, [
77
+ firstValue,
78
+ isVisible,
79
+ picker.selectedValue,
80
+ setActiveValue,
81
+ ]);
82
+ useEffect(() => {
83
+ if (!isVisible || activeValue === null) {
84
+ return;
85
+ }
86
+ optionElements.current.get(activeValue)?.focus();
87
+ }, [activeValue, isVisible]);
88
+ const runTypeahead = (character) => {
89
+ // Reset the buffer once the typing pauses, so "sk" then a beat
90
+ // then "e" is two searches rather than "ske".
91
+ const now = Date.now();
92
+ typeahead.current.buffer =
93
+ now - typeahead.current.stampMs > 500
94
+ ? character
95
+ : typeahead.current.buffer + character;
96
+ typeahead.current.stampMs = now;
97
+ const query = typeahead.current.buffer.toLowerCase();
98
+ const match = options.find((option) => !option.isDisabled &&
99
+ getOptionText(option).startsWith(query));
100
+ if (match) {
101
+ setActiveValue(match.value);
102
+ }
103
+ };
104
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context, initialFocus: -1, modal: false, children: _jsx("div", { ...getFloatingProps(), "aria-labelledby": triggerId, className: toClassName(PANEL_SURFACE_CLASS,
105
+ // The height is clamped to the viewport by the `size`
106
+ // middleware (`maxHeightPx`), written as an inline
107
+ // style; this scrolls within it.
108
+ "z-[var(--layer-modal)] flex min-w-48 flex-col gap-0.5 overflow-auto p-1", className), onKeyDown: (keyEvent) => {
109
+ const commands = {
110
+ ArrowDown: focus.next,
111
+ ArrowUp: focus.previous,
112
+ End: focus.last,
113
+ Home: focus.first,
114
+ };
115
+ const command = commands[keyEvent.key];
116
+ if (command) {
117
+ keyEvent.preventDefault();
118
+ command();
119
+ return;
120
+ }
121
+ if ((keyEvent.key === "Enter" ||
122
+ keyEvent.key === " ") &&
123
+ activeValue !== null) {
124
+ keyEvent.preventDefault();
125
+ chooseValue(activeValue);
126
+ return;
127
+ }
128
+ // Type-ahead: a single printable character with no
129
+ // modifier is a search, not a shortcut.
130
+ if (keyEvent.key.length === 1 &&
131
+ !keyEvent.altKey &&
132
+ !keyEvent.ctrlKey &&
133
+ !keyEvent.metaKey) {
134
+ runTypeahead(keyEvent.key);
135
+ }
136
+ }, ref: setFloating,
137
+ // Duplicated from `getFloatingProps()` on purpose, and
138
+ // the two cannot differ — `useRole(context, { role:
139
+ // "listbox" })` in the hook is where it comes from.
140
+ // Stated here because a linter cannot see a role through
141
+ // a spread.
142
+ role: "listbox", style: floatingStyles, children: options.map((option) => (_jsx(ListboxOption, { isSelected: picker.selectedValue === option.value, item: option, onSelect: chooseValue, registerFocus: focus.register, registerSelection: picker.register, tabIndex: selectTabIndex(focus, option.value), trackElement: (value, element) => {
143
+ if (element) {
144
+ optionElements.current.set(value, element);
145
+ }
146
+ else {
147
+ optionElements.current.delete(value);
148
+ }
149
+ } }, option.value))) }) }) })) : null] }));
150
+ };
151
+ //# sourceMappingURL=Listbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Listbox.js","sourceRoot":"","sources":["../../src/Listbox/Listbox.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ListboxItem } from "./Listbox.tsx";
3
+ export type ListboxOptionProps = {
4
+ isSelected: boolean;
5
+ item: ListboxItem;
6
+ onSelect: (value: string) => void;
7
+ /** `RovingFocus.register` — membership of the arrow-key group. */
8
+ registerFocus: (value: string) => () => void;
9
+ /** `SinglePicker.register` — membership of the choice. */
10
+ registerSelection: (value: string) => () => void;
11
+ tabIndex: number;
12
+ trackElement: (value: string, element: HTMLButtonElement | null) => void;
13
+ };
14
+ /**
15
+ * Its own file for the same reason as `MenuAction` and `TabTrigger`:
16
+ * **both registrations are effects**, and an effect cannot run in a
17
+ * loop.
18
+ *
19
+ * The two registrations are separate on purpose, exactly as
20
+ * `TabTrigger` splits them. A disabled option joins the *choice* — it
21
+ * can still be the seeded `selectedValue` and still announces
22
+ * `aria-selected` — and stays out of the *focus* group, so the arrow
23
+ * keys and type-ahead skip it without `RovingFocus` having to know
24
+ * what "disabled" means.
25
+ *
26
+ * ### `option`, not `menuitem`
27
+ *
28
+ * An `option` **is** something — the value being chosen — where a
29
+ * `menuitem` **does** something. So this carries `aria-selected`
30
+ * (which a menuitem must not), and a screen reader announces
31
+ * "listbox, selected, 2 of 4" rather than "menu, 4 items". It is a
32
+ * `<button>` with `role="option"` so it is natively focusable for the
33
+ * roving-tabindex move, the same shape `Menu` uses.
34
+ */
35
+ export declare const ListboxOption: ({ isSelected, item, onSelect, registerFocus, registerSelection, tabIndex, trackElement, }: ListboxOptionProps) => ReactNode;
36
+ //# sourceMappingURL=ListboxOption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListboxOption.d.ts","sourceRoot":"","sources":["../../src/Listbox/ListboxOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,kEAAkE;IAClE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAC5C,0DAA0D;IAC1D,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,IAAI,CAAA;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,GAAG,IAAI,KAC9B,IAAI,CAAA;CACV,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,aAAa,GAAI,2FAQ3B,kBAAkB,KAAG,SAoDvB,CAAA"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { FOCUS_RING_CLASS } from "../intentStyles.js";
4
+ import { toClassName } from "../toClassName.js";
5
+ /**
6
+ * Its own file for the same reason as `MenuAction` and `TabTrigger`:
7
+ * **both registrations are effects**, and an effect cannot run in a
8
+ * loop.
9
+ *
10
+ * The two registrations are separate on purpose, exactly as
11
+ * `TabTrigger` splits them. A disabled option joins the *choice* — it
12
+ * can still be the seeded `selectedValue` and still announces
13
+ * `aria-selected` — and stays out of the *focus* group, so the arrow
14
+ * keys and type-ahead skip it without `RovingFocus` having to know
15
+ * what "disabled" means.
16
+ *
17
+ * ### `option`, not `menuitem`
18
+ *
19
+ * An `option` **is** something — the value being chosen — where a
20
+ * `menuitem` **does** something. So this carries `aria-selected`
21
+ * (which a menuitem must not), and a screen reader announces
22
+ * "listbox, selected, 2 of 4" rather than "menu, 4 items". It is a
23
+ * `<button>` with `role="option"` so it is natively focusable for the
24
+ * roving-tabindex move, the same shape `Menu` uses.
25
+ */
26
+ export const ListboxOption = ({ isSelected, item, onSelect, registerFocus, registerSelection, tabIndex, trackElement, }) => {
27
+ const { isDisabled = false, label, value } = item;
28
+ useEffect(() => registerSelection(value), [registerSelection, value]);
29
+ useEffect(() => {
30
+ if (isDisabled) {
31
+ return;
32
+ }
33
+ return registerFocus(value);
34
+ }, [isDisabled, registerFocus, value]);
35
+ return (_jsxs("button", { "aria-selected": isSelected, className: toClassName("flex w-full cursor-pointer items-center justify-between gap-2 rounded-sm bg-transparent px-2 py-1.5 text-start text-content-primary text-sm transition-colors duration-(--duration-fast) ease-standard", "hover:bg-intent-neutral-surface", isSelected && "bg-intent-accent-surface", isDisabled &&
36
+ "cursor-not-allowed text-content-disabled hover:bg-transparent", FOCUS_RING_CLASS), disabled: isDisabled, onClick: () => {
37
+ onSelect(value);
38
+ }, ref: (element) => {
39
+ trackElement(value, element);
40
+ }, role: "option", tabIndex: tabIndex, type: "button", children: [label, isSelected ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-intent-accent-content", children: "\u2713" })) : null] }));
41
+ };
42
+ //# sourceMappingURL=ListboxOption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListboxOption.js","sourceRoot":"","sources":["../../src/Listbox/ListboxOption.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC5B,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,YAAY,GACO,EAAa,EAAE;IAClC,MAAM,EAAE,UAAU,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEjD,SAAS,CACP,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAC9B,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAC3B,CAAA;IAED,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,EAAE,CAAC;YACf,OAAM;QACR,CAAC;QAED,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;IAEtC,OAAO,CACL,mCACiB,UAAU,EACzB,SAAS,EAAE,WAAW,CACpB,wMAAwM,EACxM,iCAAiC,EACjC,UAAU,IAAI,0BAA0B,EACxC,UAAU;YACR,+DAA+D,EACjE,gBAAgB,CACjB,EACD,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,GAAG,EAAE;YACZ,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,EACD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE;YACf,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC9B,CAAC,EACD,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAC,QAAQ,aAEZ,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BAGc,MAAM,EAClB,SAAS,EAAC,qCAAqC,uBAG1C,CACR,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAYnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMpD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,iEAAiE;IACjE,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,eAAO,MAAM,IAAI,GAAI,iEAOlB,SAAS,KAAG,SA8Ld,CAAA"}
1
+ {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,SAAS,CAAA;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,iEAAiE;IACjE,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,+DAA+D;IAC/D,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,eAAO,MAAM,IAAI,GAAI,iEAOlB,SAAS,KAAG,SAuId,CAAA"}
package/dist/Menu/Menu.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { selectTabIndex, useClonedChild, useRovingFocus, } from "@charcuterie/logic";
3
- import { autoUpdate, FloatingFocusManager, flip, offset, shift, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react";
2
+ import { selectTabIndex, useRovingFocus, } from "@charcuterie/logic";
3
+ import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
4
4
  import { useEffect, useRef } from "react";
5
+ import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.js";
6
+ import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.js";
5
7
  import { toClassName } from "../toClassName.js";
6
8
  import { MenuAction } from "./MenuAction.js";
7
9
  /**
@@ -66,49 +68,17 @@ import { MenuAction } from "./MenuAction.js";
66
68
  */
67
69
  export const Menu = ({ className, isVisible, items, onDismiss, placement = "bottom-start", trigger, }) => {
68
70
  const itemElements = useRef(new Map());
69
- const panelElement = useRef(null);
70
71
  const focus = useRovingFocus();
71
72
  const { activeValue, registeredValues, setActiveValue } = focus;
72
73
  const [firstValue] = registeredValues;
73
- const { context, floatingStyles, refs } = useFloating({
74
- open: isVisible,
75
- onOpenChange: (isNextVisible) => {
76
- if (!isNextVisible) {
77
- onDismiss();
78
- }
79
- },
74
+ const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, } = useAnchoredOverlay({
75
+ isVisible,
76
+ offsetValue: 4,
77
+ onDismiss,
80
78
  placement,
81
- middleware: [offset(4), flip(), shift({ padding: 8 })],
82
- strategy: "fixed",
83
- whileElementsMounted: autoUpdate,
84
- });
85
- const { getFloatingProps, getReferenceProps } = useInteractions([
86
- useDismiss(context),
87
- useRole(context, { role: "menu" }),
88
- ]);
89
- const clonedTrigger = useClonedChild(trigger, {
90
- ...getReferenceProps(),
91
- ref: refs.setReference,
79
+ role: "menu",
80
+ trigger,
92
81
  });
93
- /**
94
- * The other half of the `isConnected` guard below. If the ref
95
- * fired while the node was still detached, nothing has shown the
96
- * popover — and a `[popover]` that was never shown is
97
- * `display: none`, so the menu renders, passes typecheck, and is
98
- * invisible to `getByRole` and to a user alike.
99
- *
100
- * Effects run after the tree is connected, so this is the
101
- * backstop. It is idempotent: `:popover-open` is the same question
102
- * the ref asks.
103
- */
104
- useEffect(() => {
105
- const panel = panelElement.current;
106
- if (isVisible &&
107
- panel?.isConnected &&
108
- !panel.matches(":popover-open")) {
109
- panel.showPopover();
110
- }
111
- }, [isVisible]);
112
82
  /**
113
83
  * Opening **makes** a member active, and this is the step the
114
84
  * first version was missing.
@@ -137,63 +107,43 @@ export const Menu = ({ className, isVisible, items, onDismiss, placement = "bott
137
107
  }
138
108
  itemElements.current.get(activeValue)?.focus();
139
109
  }, [activeValue, isVisible]);
140
- return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingFocusManager, { context: context,
141
- // `false`, so Tab leaves the menu and lands on the next
142
- // control in the page — which is what closes it, through
143
- // `useDismiss`. A trapped menu is a dialog.
144
- modal: false,
145
- // Focus is moved by the effect above, to whichever item
146
- // `RovingFocus` made active. Letting the manager also aim
147
- // it is two owners for one caret.
148
- initialFocus: -1, children: _jsx("div", { ...getFloatingProps(), className: toClassName("inset-auto m-0 flex min-w-48 flex-col gap-0.5 rounded-md border border-border-default bg-surface-overlay p-1 shadow-medium", className), onKeyDown: (keyEvent) => {
149
- const commands = {
150
- ArrowDown: focus.next,
151
- ArrowUp: focus.previous,
152
- End: focus.last,
153
- Home: focus.first,
154
- };
155
- const command = commands[keyEvent.key];
156
- if (command) {
157
- // Arrow keys scroll the page by default, and a menu
158
- // that scrolls the document behind it while moving
159
- // focus is the hand-rolled version of this
160
- // component.
161
- keyEvent.preventDefault();
162
- command();
163
- }
164
- }, popover: "manual", ref: (node) => {
165
- refs.setFloating(node);
166
- panelElement.current = node;
167
- // A ref callback rather than an effect, for the same
168
- // reason as `Popover`: a `[popover]` is
169
- // `display: none` until shown, and every child effect
170
- // — including the focus move above — would otherwise
171
- // be aiming at a hidden element.
172
- //
173
- // `isConnected` is the guard `Popover` does not need
174
- // and this does. `FloatingFocusManager` renders a pair
175
- // of focus guards around its child, so React commits
176
- // this subtree in an order where the ref can fire
177
- // while the node is still detached — and
178
- // `showPopover()` on a disconnected element throws
179
- // `InvalidStateError`, which surfaces as an unhandled
180
- // rejection rather than as a failing assertion.
181
- if (node?.isConnected &&
182
- !node.matches(":popover-open")) {
183
- node.showPopover();
184
- }
185
- },
186
- // Duplicated from `getFloatingProps()` on purpose, and
187
- // the two cannot differ — `useRole(context, { role:
188
- // "menu" })` above is where it comes from. Stated here
189
- // because a linter cannot see a role through a spread.
190
- role: "menu", style: floatingStyles, children: items.map((item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
191
- if (element) {
192
- itemElements.current.set(key, element);
193
- }
194
- else {
195
- itemElements.current.delete(key);
110
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context,
111
+ // `false`, so Tab leaves the menu and lands on the next
112
+ // control in the page — which is what closes it, through
113
+ // `useDismiss`. A trapped menu is a dialog.
114
+ modal: false,
115
+ // Focus is moved by the effect above, to whichever item
116
+ // `RovingFocus` made active. Letting the manager also aim
117
+ // it is two owners for one caret.
118
+ initialFocus: -1, children: _jsx("div", { ...getFloatingProps(), className: toClassName(PANEL_SURFACE_CLASS, "z-[var(--layer-modal)] flex min-w-48 flex-col gap-0.5 p-1", className), onKeyDown: (keyEvent) => {
119
+ const commands = {
120
+ ArrowDown: focus.next,
121
+ ArrowUp: focus.previous,
122
+ End: focus.last,
123
+ Home: focus.first,
124
+ };
125
+ const command = commands[keyEvent.key];
126
+ if (command) {
127
+ // Arrow keys scroll the page by default, and a menu
128
+ // that scrolls the document behind it while moving
129
+ // focus is the hand-rolled version of this
130
+ // component.
131
+ keyEvent.preventDefault();
132
+ command();
196
133
  }
197
- } }, item.key))) }) })) : null] }));
134
+ }, ref: setFloating,
135
+ // Duplicated from `getFloatingProps()` on purpose, and
136
+ // the two cannot differ — `useRole(context, { role:
137
+ // "menu" })` in the hook is where it comes from. Stated
138
+ // here because a linter cannot see a role through a
139
+ // spread.
140
+ role: "menu", style: floatingStyles, children: items.map((item) => (_jsx(MenuAction, { item: item, onDismiss: onDismiss, register: focus.register, tabIndex: selectTabIndex(focus, item.key), trackElement: (key, element) => {
141
+ if (element) {
142
+ itemElements.current.set(key, element);
143
+ }
144
+ else {
145
+ itemElements.current.delete(key);
146
+ }
147
+ } }, item.key))) }) }) })) : null] }));
198
148
  };
199
149
  //# sourceMappingURL=Menu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Menu.js","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"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"}
@@ -1,66 +1,18 @@
1
- import type { ComponentPropsWithRef, ReactNode } from "react";
2
- export type ModalSize = "full" | "lg" | "md" | "sm" | "xl";
3
- export type ModalProps = Omit<ComponentPropsWithRef<"dialog">, "onClose" | "open"> & {
4
- /** The footer row. Buttons, usually. */
5
- footer?: ReactNode;
6
- heading: string;
7
- headingLevel?: 2 | 3 | 4;
8
- /**
9
- * Escape and a backdrop press both close. Off for the one
10
- * honest case — a destructive confirm the user has to answer —
11
- * and off means Escape is *cancelled* rather than ignored, so
12
- * the browser cannot close a dialog the app still owns.
13
- */
1
+ import type { ReactNode } from "react";
2
+ export type ModalProps = {
3
+ "aria-label"?: string;
4
+ "aria-labelledby"?: string;
5
+ children: ReactNode;
6
+ className?: string;
7
+ /** Escape and a backdrop press both close. */
14
8
  isDismissable?: boolean;
15
9
  isVisible: boolean;
16
10
  /**
17
- * Every route out lands here: the close button, Escape, a
18
- * backdrop press. Wire it to a `useVisibility()`'s `hide`.
11
+ * Every route out lands here: Escape, a backdrop press. Wire it to a
12
+ * `useVisibility()`'s `hide`.
19
13
  */
20
14
  onClose: () => void;
21
- size?: ModalSize;
15
+ role?: "alertdialog" | "dialog";
22
16
  };
23
- /**
24
- * Four repos, and **plex-channels and ripdeck independently
25
- * converged on native `<dialog>`** — which is the design here
26
- * rather than a shortcut. `showModal()` gives a focus trap,
27
- * Escape, `::backdrop`, `inert` on everything behind it, and the
28
- * **top layer**, from the platform, correctly, in every browser
29
- * the fleet runs.
30
- *
31
- * What is not free — and the reason a component exists at all —
32
- * is that a native `<dialog>` is a *self-owning* control: call
33
- * `showModal()` and it decides it is open. That is the same
34
- * conflict Radix has, and it gets the same answer as everywhere
35
- * else in this library: **`isVisible` is the truth, and an effect
36
- * makes the element agree with it.** Nothing here reads
37
- * `dialogElement.open` as state; it is read only to avoid calling
38
- * a method that would throw.
39
- *
40
- * ### Why it is not portalled
41
- *
42
- * The top layer is a *paint* concept: a `<dialog>` renders above
43
- * everything while staying exactly where it is in the DOM. So it
44
- * needs no portal — and that matters well past tidiness, because a
45
- * portal moves the node to `document.body`, out of whatever
46
- * element a test or an agent scoped its queries to.
47
- * `canvas.getByRole("dialog")` finds this one. It would not find a
48
- * portalled one, and the story would have to reach for `screen`
49
- * and lose its scoping.
50
- *
51
- * ### `open:flex`, not `flex`
52
- *
53
- * A closed `<dialog>` is hidden by a UA rule that any
54
- * `display: flex` of ours would beat — so the modal would render
55
- * permanently, in the page flow, with no error. The layout is
56
- * therefore behind Tailwind's `open:` variant, which is the
57
- * `[open]` attribute the browser manages.
58
- *
59
- * ### The accessible name
60
- *
61
- * `heading` is required and is a string, not a `ReactNode`. A
62
- * dialog with no name is announced as "dialog" and nothing else,
63
- * which is what every hand-rolled modal in the inventory does.
64
- */
65
- export declare const Modal: ({ children, className, footer, heading, headingLevel, isDismissable, isVisible, onClose, size, ...dialogProps }: ModalProps) => ReactNode;
17
+ export declare const Modal: ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, isDismissable, isVisible, onClose, role, }: ModalProps) => ReactNode;
66
18
  //# sourceMappingURL=Modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACV,MAAM,OAAO,CAAA;AAOd,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE1D,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,qBAAqB,CAAC,QAAQ,CAAC,EAC/B,SAAS,GAAG,MAAM,CACnB,GAAG;IACF,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,KAAK,GAAI,iHAWnB,UAAU,KAAG,SAsHf,CAAA"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/Modal/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAA;CAChC,CAAA;AAqCD,eAAO,MAAM,KAAK,GAAI,+HASnB,UAAU,KAAG,SAYf,CAAA"}