@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,54 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import type { ListboxItem } from "../Listbox/Listbox.tsx";
3
+ export type ComboboxProps = {
4
+ className?: string;
5
+ /** Shown when nothing matches. Default "No matches". */
6
+ emptyLabel?: string;
7
+ /** The error panel state (`PathPicker`). */
8
+ error?: ReactNode;
9
+ /** A sticky footer hint (`LinkPicker`'s "type a path directly"). */
10
+ footer?: ReactNode;
11
+ /** Enter commits the raw query as a value (`AssFieldPicker`). */
12
+ isCreatable?: boolean;
13
+ /** The loading panel state. */
14
+ isLoading?: boolean;
15
+ /** Multi-select, rendered as removable chips (`LanguageCodeField`). */
16
+ isMultiple?: boolean;
17
+ /** Windowing. Auto-on above ~100 options when left undefined. */
18
+ isVirtualized?: boolean;
19
+ isVisible: boolean;
20
+ onDismiss: () => void;
21
+ onSelect: (value: string) => void;
22
+ /**
23
+ * Omit to filter internally by `textValue`; supply to own the query
24
+ * (a react-query search, say), in which case `options` arrives
25
+ * pre-filtered.
26
+ */
27
+ onQueryChange?: (query: string) => void;
28
+ options: readonly ListboxItem[];
29
+ placeholder?: string;
30
+ /** Controlled query; uncontrolled when absent. */
31
+ query?: string;
32
+ selectedValue?: readonly string[] | string;
33
+ /** The control the popup hangs off. **Cloned, not wrapped.** */
34
+ trigger: ReactElement;
35
+ };
36
+ /**
37
+ * The searchable, filtering, virtualized picker — the other half of
38
+ * the picker family, and the one that consolidates the fleet's
39
+ * hand-rolled dropdowns.
40
+ *
41
+ * The ARIA contract differs from `Listbox` and the difference is
42
+ * forced, not stylistic: **focus stays in the text input**, so the
43
+ * active option is tracked with `aria-activedescendant`, not roving
44
+ * tabindex. The input is the `role="combobox"`; the popup is a
45
+ * `role="listbox"`. Arrows move the active descendant while the caret
46
+ * stays put; Enter selects; Escape clears the query before it closes.
47
+ *
48
+ * Filtering is the consumer's when `onQueryChange` is supplied (the
49
+ * `options` then arrive pre-filtered — a react-query search owns the
50
+ * debounce, the request token, the cache), and internal on `textValue`
51
+ * otherwise.
52
+ */
53
+ export declare const Combobox: ({ className, emptyLabel, error, footer, isCreatable, isLoading, isMultiple, isVirtualized, isVisible, onDismiss, onQueryChange, onSelect, options, placeholder, query, selectedValue, trigger, }: ComboboxProps) => ReactNode;
54
+ //# sourceMappingURL=Combobox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Combobox.d.ts","sourceRoot":"","sources":["../../src/Combobox/Combobox.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAMzD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAA;IAC1C,gEAAgE;IAChE,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AA4BD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,GAAI,kMAkBtB,aAAa,KAAG,SAmZlB,CAAA"}
@@ -0,0 +1,216 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react";
3
+ import { useVirtualizer } from "@tanstack/react-virtual";
4
+ import { useEffect, useRef, useState } 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 { ComboboxOption } from "./ComboboxOption.js";
9
+ const AUTO_VIRTUALIZE_THRESHOLD = 100;
10
+ const getOptionText = (item) => (item.textValue ??
11
+ (typeof item.label === "string" ? item.label : "")).toLowerCase();
12
+ const nextEnabledIndex = (options, from, step) => {
13
+ for (let index = from + step; index >= 0 && index < options.length; index += step) {
14
+ if (!options[index]?.isDisabled) {
15
+ return index;
16
+ }
17
+ }
18
+ return from;
19
+ };
20
+ /**
21
+ * The searchable, filtering, virtualized picker — the other half of
22
+ * the picker family, and the one that consolidates the fleet's
23
+ * hand-rolled dropdowns.
24
+ *
25
+ * The ARIA contract differs from `Listbox` and the difference is
26
+ * forced, not stylistic: **focus stays in the text input**, so the
27
+ * active option is tracked with `aria-activedescendant`, not roving
28
+ * tabindex. The input is the `role="combobox"`; the popup is a
29
+ * `role="listbox"`. Arrows move the active descendant while the caret
30
+ * stays put; Enter selects; Escape clears the query before it closes.
31
+ *
32
+ * Filtering is the consumer's when `onQueryChange` is supplied (the
33
+ * `options` then arrive pre-filtered — a react-query search owns the
34
+ * debounce, the request token, the cache), and internal on `textValue`
35
+ * otherwise.
36
+ */
37
+ export const Combobox = ({ className, emptyLabel = "No matches", error, footer, isCreatable = false, isLoading = false, isMultiple = false, isVirtualized, isVisible, onDismiss, onQueryChange, onSelect, options, placeholder = "Search…", query, selectedValue, trigger, }) => {
38
+ const inputElement = useRef(null);
39
+ const listElement = useRef(null);
40
+ const isQueryControlled = query !== undefined;
41
+ const [internalQuery, setInternalQuery] = useState("");
42
+ const currentQuery = query ?? internalQuery;
43
+ const [activeIndex, setActiveIndex] = useState(0);
44
+ const [selected, setSelected] = useState(selectedValue === undefined
45
+ ? []
46
+ : typeof selectedValue === "string"
47
+ ? [selectedValue]
48
+ : [...selectedValue]);
49
+ // The consumer owns filtering when it owns the query; otherwise it is
50
+ // a plain `textValue` contains-match.
51
+ const filtered = onQueryChange === undefined
52
+ ? options.filter((option) => getOptionText(option).includes(currentQuery.toLowerCase()))
53
+ : options;
54
+ const isWindowed = isVirtualized ??
55
+ filtered.length > AUTO_VIRTUALIZE_THRESHOLD;
56
+ const { clonedTrigger, context, floatingStyles, getFloatingProps, setFloating, triggerId, } = useAnchoredOverlay({
57
+ // Escape is handled here so it can clear the query before closing.
58
+ isEscapeDismissable: false,
59
+ isVisible,
60
+ maxHeightPx: 384,
61
+ offsetValue: 4,
62
+ onDismiss,
63
+ role: "listbox",
64
+ trigger,
65
+ });
66
+ // The panel wraps an input *and* a listbox, so the wrapper is a
67
+ // plain div: the `role="listbox"`/`id` `useRole` produced go on the
68
+ // inner list, which is what the trigger's and input's `aria-controls`
69
+ // then resolve to.
70
+ const { id: listboxId, role: _discardedPanelRole, ...floatingProps } = getFloatingProps();
71
+ const setQuery = (nextQuery) => {
72
+ if (!isQueryControlled) {
73
+ setInternalQuery(nextQuery);
74
+ }
75
+ setActiveIndex(0);
76
+ onQueryChange?.(nextQuery);
77
+ };
78
+ useEffect(() => {
79
+ if (isVisible) {
80
+ inputElement.current?.focus();
81
+ }
82
+ }, [isVisible]);
83
+ const rowVirtualizer = useVirtualizer({
84
+ count: filtered.length,
85
+ estimateSize: () => 36,
86
+ getScrollElement: () => listElement.current,
87
+ overscan: 8,
88
+ });
89
+ const optionDomId = (value) => `${listboxId}-opt-${value}`;
90
+ // `activeIndex` is seeded at 0 on open and on every query reset, which
91
+ // can land on a disabled option; arrowing skips disabled but the seed
92
+ // does not. Resolve to the nearest enabled row and drive the highlight,
93
+ // `aria-activedescendant`, arrow moves and commit off *this* — never the
94
+ // raw index — so a disabled option is neither announced nor committable.
95
+ const resolvedActiveIndex = filtered.length === 0
96
+ ? -1
97
+ : filtered[activeIndex] !== undefined &&
98
+ !filtered[activeIndex]?.isDisabled
99
+ ? activeIndex
100
+ : nextEnabledIndex(filtered, -1, 1);
101
+ const activeValue = resolvedActiveIndex < 0
102
+ ? undefined
103
+ : filtered[resolvedActiveIndex]?.value;
104
+ const commitValue = (value) => {
105
+ if (isMultiple) {
106
+ setSelected((previous) => previous.includes(value)
107
+ ? previous.filter((one) => one !== value)
108
+ : [...previous, value]);
109
+ onSelect(value);
110
+ // Multi-select stays open and clears the query for the next one.
111
+ setQuery("");
112
+ inputElement.current?.focus();
113
+ return;
114
+ }
115
+ setSelected([value]);
116
+ onSelect(value);
117
+ onDismiss();
118
+ };
119
+ const handleKeyDown = (keyEvent) => {
120
+ if (keyEvent.key === "ArrowDown") {
121
+ keyEvent.preventDefault();
122
+ setActiveIndex(nextEnabledIndex(filtered, resolvedActiveIndex, 1));
123
+ return;
124
+ }
125
+ if (keyEvent.key === "ArrowUp") {
126
+ keyEvent.preventDefault();
127
+ setActiveIndex(nextEnabledIndex(filtered, resolvedActiveIndex, -1));
128
+ return;
129
+ }
130
+ if (keyEvent.key === "Enter" ||
131
+ // Tab-to-accept (`PathPicker`): the active suggestion is taken.
132
+ // Single-select only — in multi-select `commitValue` refocuses the
133
+ // input, so accepting on Tab would trap forward focus in the field.
134
+ (keyEvent.key === "Tab" &&
135
+ !keyEvent.shiftKey &&
136
+ !isMultiple &&
137
+ activeValue !== undefined)) {
138
+ if (activeValue !== undefined) {
139
+ keyEvent.preventDefault();
140
+ commitValue(activeValue);
141
+ return;
142
+ }
143
+ // Creatable: Enter on a query with no match commits it raw.
144
+ if (isCreatable &&
145
+ keyEvent.key === "Enter" &&
146
+ currentQuery.trim().length > 0) {
147
+ keyEvent.preventDefault();
148
+ commitValue(currentQuery.trim());
149
+ }
150
+ return;
151
+ }
152
+ if (keyEvent.key === "Escape") {
153
+ // Clears the query first, closes only when it is already empty.
154
+ if (currentQuery.length > 0) {
155
+ keyEvent.preventDefault();
156
+ setQuery("");
157
+ return;
158
+ }
159
+ onDismiss();
160
+ }
161
+ };
162
+ // Keep the active row in view — a real bug in every fleet picker:
163
+ // arrowing past the panel edge did not scroll.
164
+ useEffect(() => {
165
+ if (!isVisible) {
166
+ return;
167
+ }
168
+ if (isWindowed) {
169
+ rowVirtualizer.scrollToIndex(resolvedActiveIndex);
170
+ return;
171
+ }
172
+ if (activeValue !== undefined) {
173
+ listElement.current
174
+ ?.querySelector(`#${CSS.escape(`${listboxId}-opt-${activeValue}`)}`)
175
+ ?.scrollIntoView({ block: "nearest" });
176
+ }
177
+ }, [
178
+ resolvedActiveIndex,
179
+ activeValue,
180
+ isVisible,
181
+ isWindowed,
182
+ listboxId,
183
+ rowVirtualizer.scrollToIndex,
184
+ ]);
185
+ const renderOption = (option, index) => (_jsx(ComboboxOption, { id: optionDomId(option.value), isActive: index === resolvedActiveIndex, isSelected: selected.includes(option.value), item: option, onSelect: commitValue, posInSet: isWindowed ? index + 1 : undefined, setSize: isWindowed ? filtered.length : undefined }, option.value));
186
+ return (_jsxs(_Fragment, { children: [clonedTrigger, isVisible ? (_jsx(FloatingPortal, { children: _jsx(FloatingFocusManager, { context: context, initialFocus: inputElement, modal: false, children: _jsxs("div", { ...floatingProps, className: toClassName(PANEL_SURFACE_CLASS, "z-[var(--layer-modal)] flex min-w-64 flex-col overflow-hidden", className), ref: setFloating, style: floatingStyles, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-1 border-border-subtle border-b p-2", children: [isMultiple
187
+ ? selected.map((value) => {
188
+ const chip = options.find((option) => option.value === value);
189
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-sm bg-intent-neutral-surface px-1.5 py-0.5 text-content-primary text-xs", children: [chip?.textValue ?? value, _jsx("button", { "aria-label": `Remove ${chip?.textValue ?? value}`, className: "cursor-pointer text-content-secondary hover:text-content-primary", onClick: () => {
190
+ setSelected((previous) => previous.filter((one) => one !== value));
191
+ // Mirror the list toggle-off: the parent
192
+ // tracks selection through `onSelect`, so a
193
+ // chip removal must report the same value.
194
+ onSelect(value);
195
+ }, type: "button", children: "\u2715" })] }, value));
196
+ })
197
+ : null, _jsx("input", { "aria-activedescendant": activeValue === undefined
198
+ ? undefined
199
+ : optionDomId(activeValue), "aria-autocomplete": "list", "aria-controls": listboxId, "aria-expanded": "true", "aria-labelledby": triggerId, className: "min-w-24 flex-1 bg-transparent text-content-primary text-sm outline-none placeholder:text-content-muted", onChange: (changeEvent) => {
200
+ setQuery(changeEvent.target.value);
201
+ }, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputElement, role: "combobox", type: "text", value: currentQuery })] }), _jsx("div", { "aria-labelledby": triggerId, className: "min-h-0 flex-1 overflow-auto p-1", id: listboxId, ref: listElement, role: "listbox", children: isLoading ? (_jsx("p", { className: "px-2 py-3 text-center text-content-secondary text-sm", children: "Loading\u2026" })) : error ? (_jsx("div", { className: "px-2 py-3 text-center text-intent-danger-content text-sm", children: error })) : filtered.length === 0 ? (_jsx("p", { className: "px-2 py-3 text-center text-content-secondary text-sm", children: emptyLabel })) : isWindowed ? (_jsx("div", { className: "relative w-full", style: {
202
+ height: `${rowVirtualizer.getTotalSize()}px`,
203
+ }, children: rowVirtualizer
204
+ .getVirtualItems()
205
+ .map((virtualRow) => {
206
+ const option = filtered[virtualRow.index];
207
+ if (!option) {
208
+ return null;
209
+ }
210
+ return (_jsx("div", { className: "absolute start-0 top-0 w-full", style: {
211
+ height: `${virtualRow.size}px`,
212
+ transform: `translateY(${virtualRow.start}px)`,
213
+ }, children: renderOption(option, virtualRow.index) }, option.value));
214
+ }) })) : (filtered.map((option, index) => renderOption(option, index))) }), footer ? (_jsx("div", { className: "shrink-0 border-border-subtle border-t p-2 text-content-secondary text-xs", children: footer })) : null] }) }) })) : null] }));
215
+ };
216
+ //# sourceMappingURL=Combobox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Combobox.js","sourceRoot":"","sources":["../../src/Combobox/Combobox.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ListboxItem } from "../Listbox/Listbox.tsx";
3
+ export type ComboboxOptionProps = {
4
+ /** The stable DOM id `aria-activedescendant` points at. */
5
+ id: string;
6
+ isActive: boolean;
7
+ isSelected: boolean;
8
+ item: ListboxItem;
9
+ onSelect: (value: string) => void;
10
+ /**
11
+ * `aria-posinset`/`aria-setsize`, required when the list is
12
+ * virtualized: most of it is not in the DOM, so a screen reader
13
+ * would otherwise announce "2 of 12" for a 4,000-item list.
14
+ */
15
+ posInSet?: number;
16
+ setSize?: number;
17
+ };
18
+ /**
19
+ * `role="option"` with **`tabIndex={-1}`**, and both halves matter.
20
+ * The APG combobox keeps focus in the text input and tracks the
21
+ * active option with `aria-activedescendant`, so no option is ever a
22
+ * tab stop — `tabIndex={-1}` is that. It is a `<button>` (which
23
+ * ARIA-in-HTML allows `role="option"` on, and `Listbox` uses too)
24
+ * rather than a `<div>`, so `onClick` carries its own keyboard route
25
+ * and the package keeps its no-Biome-suppression record.
26
+ *
27
+ * The one caveat is not to run `expectAgentDrivable` on an individual
28
+ * option: its roving-tabindex rule would (correctly) reject a native
29
+ * control with `tabIndex={-1}`, and a combobox legitimately has zero
30
+ * tab stops in its popup. Tests query the options with `getByRole`.
31
+ *
32
+ * "Active" is a visual state only — the option the arrow keys have
33
+ * moved to, highlighted because nothing here holds real focus.
34
+ */
35
+ export declare const ComboboxOption: ({ id, isActive, isSelected, item, onSelect, posInSet, setSize, }: ComboboxOptionProps) => ReactNode;
36
+ //# sourceMappingURL=ComboboxOption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComboboxOption.d.ts","sourceRoot":"","sources":["../../src/Combobox/ComboboxOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,mBAAmB,GAAG;IAChC,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAI,kEAQ5B,mBAAmB,KAAG,SAyCxB,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { toClassName } from "../toClassName.js";
3
+ /**
4
+ * `role="option"` with **`tabIndex={-1}`**, and both halves matter.
5
+ * The APG combobox keeps focus in the text input and tracks the
6
+ * active option with `aria-activedescendant`, so no option is ever a
7
+ * tab stop — `tabIndex={-1}` is that. It is a `<button>` (which
8
+ * ARIA-in-HTML allows `role="option"` on, and `Listbox` uses too)
9
+ * rather than a `<div>`, so `onClick` carries its own keyboard route
10
+ * and the package keeps its no-Biome-suppression record.
11
+ *
12
+ * The one caveat is not to run `expectAgentDrivable` on an individual
13
+ * option: its roving-tabindex rule would (correctly) reject a native
14
+ * control with `tabIndex={-1}`, and a combobox legitimately has zero
15
+ * tab stops in its popup. Tests query the options with `getByRole`.
16
+ *
17
+ * "Active" is a visual state only — the option the arrow keys have
18
+ * moved to, highlighted because nothing here holds real focus.
19
+ */
20
+ export const ComboboxOption = ({ id, isActive, isSelected, item, onSelect, posInSet, setSize, }) => {
21
+ const { isDisabled = false, label, value } = item;
22
+ return (_jsxs("button", { "aria-posinset": posInSet, "aria-selected": isSelected, "aria-setsize": setSize, 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", isActive && "bg-intent-neutral-surface", isSelected && "bg-intent-accent-surface", isDisabled &&
23
+ "cursor-not-allowed text-content-disabled"), disabled: isDisabled, id: id,
24
+ // `onMouseDown` rather than `onClick`: the input holds focus, and
25
+ // a click would blur it first. Prevented default keeps the caret
26
+ // in the input while the choice is made.
27
+ onMouseDown: (mouseEvent) => {
28
+ mouseEvent.preventDefault();
29
+ onSelect(value);
30
+ }, role: "option", tabIndex: -1, type: "button", children: [label, isSelected ? (_jsx("span", { "aria-hidden": "true", className: "shrink-0 text-intent-accent-content", children: "\u2713" })) : null] }));
31
+ };
32
+ //# sourceMappingURL=ComboboxOption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComboboxOption.js","sourceRoot":"","sources":["../../src/Combobox/ComboboxOption.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAkB/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,EAAE,EACF,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,GACa,EAAa,EAAE;IACnC,MAAM,EAAE,UAAU,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEjD,OAAO,CACL,mCACiB,QAAQ,mBACR,UAAU,kBACX,OAAO,EACrB,SAAS,EAAE,WAAW,CACpB,6IAA6I,EAC7I,QAAQ,IAAI,2BAA2B,EACvC,UAAU,IAAI,0BAA0B,EACxC,UAAU;YACR,0CAA0C,CAC7C,EACD,QAAQ,EAAE,UAAU,EACpB,EAAE,EAAE,EAAE;QACN,kEAAkE;QAClE,iEAAiE;QACjE,yCAAyC;QACzC,WAAW,EAAE,CAAC,UAAU,EAAE,EAAE;YAC1B,UAAU,CAAC,cAAc,EAAE,CAAA;YAE3B,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjB,CAAC,EACD,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,EACZ,IAAI,EAAC,QAAQ,aAEZ,KAAK,EAEL,UAAU,CAAC,CAAC,CAAC,CACZ,8BACc,MAAM,EAClB,SAAS,EAAC,qCAAqC,uBAG1C,CACR,CAAC,CAAC,CAAC,IAAI,IACD,CACV,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,53 @@
1
+ import type { ReactNode } from "react";
2
+ export type DialogSize = "full" | "lg" | "md" | "sm" | "xl";
3
+ export type DialogProps = {
4
+ /**
5
+ * The scrolling body. Optional: a confirm dialog whose question is
6
+ * its `heading` and whose answers are its `footer` has no body, and
7
+ * the old chrome `Modal` (which extended `<dialog>`'s DOM props)
8
+ * allowed that — `Dialog` keeps it.
9
+ */
10
+ children?: ReactNode;
11
+ className?: string;
12
+ /** The footer row. Buttons, usually. */
13
+ footer?: ReactNode;
14
+ heading: string;
15
+ headingLevel?: 2 | 3 | 4;
16
+ /**
17
+ * Escape and a backdrop press both close. Off for the one honest
18
+ * case — a destructive confirm the user has to answer.
19
+ */
20
+ isDismissable?: boolean;
21
+ isVisible: boolean;
22
+ /**
23
+ * Every route out lands here: the close button, Escape, a backdrop
24
+ * press. Wire it to a `useVisibility()`'s `hide`.
25
+ */
26
+ onClose: () => void;
27
+ size?: DialogSize;
28
+ };
29
+ /**
30
+ * `Modal` plus the chrome — heading, a Close button, a scrolling body,
31
+ * an optional footer, and five sizes. This is what `Modal` *was*
32
+ * before the M8 rebuild renamed it: a dialog is one kind of modal, so
33
+ * it takes the specific name and hands the generic one back to the
34
+ * base layer.
35
+ *
36
+ * Everything modal — the portal, the scrim, Escape and outside-press
37
+ * dismiss, the focus trap, the scroll lock, the stacking — is `Modal`'s
38
+ * (via `OverlayPanel`), so this component is just the box's contents
39
+ * and the size class it wants that box to be. `SIZE_CLASS` is composed
40
+ * over `Modal`'s default surface, which is why `full` carries
41
+ * `rounded-none` to square off the base's `rounded-lg` at the viewport
42
+ * edge.
43
+ *
44
+ * ### The accessible name
45
+ *
46
+ * `heading` is required and is a string, not a `ReactNode`. It names
47
+ * both the visible header and — through `aria-labelledby` on the
48
+ * modal — the dialog itself. A dialog with no name is announced as
49
+ * "dialog" and nothing else, which is what every hand-rolled modal in
50
+ * the inventory does.
51
+ */
52
+ export declare const Dialog: ({ children, className, footer, heading, headingLevel, isDismissable, isVisible, onClose, size, }: DialogProps) => ReactNode;
53
+ //# sourceMappingURL=Dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,kGAUpB,WAAW,KAAG,SA4ChB,CAAA"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useUniqueId } from "@charcuterie/logic";
3
+ import { Button } from "../Button/Button.js";
4
+ import { Modal } from "../Modal/Modal.js";
5
+ import { toClassName } from "../toClassName.js";
6
+ /**
7
+ * `85dvh` rather than `85vh` — on a phone the two differ by the
8
+ * address bar, and a dialog whose footer is under it is a dialog
9
+ * with no buttons.
10
+ */
11
+ const SIZE_CLASS = {
12
+ sm: "max-h-[85dvh] w-full max-w-sm",
13
+ md: "max-h-[85dvh] w-full max-w-md",
14
+ lg: "max-h-[85dvh] w-full max-w-2xl",
15
+ // M5 added this, because the scale jumped from 42rem straight to
16
+ // the whole viewport and rip-deck's capture tail lands in the
17
+ // gap: a MakeMKV robot log is fixed-width `MSG:5072,0,1,"…"`
18
+ // lines that wrap badly at 42rem, and a full-screen dialog over a
19
+ // nine-bay dashboard hides the thing being diagnosed. 56rem is
20
+ // what that app's hand-rolled `<dialog>` used before the swap.
21
+ xl: "max-h-[85dvh] w-full max-w-4xl",
22
+ full: "h-[100dvh] max-h-[100dvh] w-full max-w-full rounded-none",
23
+ };
24
+ /**
25
+ * `Modal` plus the chrome — heading, a Close button, a scrolling body,
26
+ * an optional footer, and five sizes. This is what `Modal` *was*
27
+ * before the M8 rebuild renamed it: a dialog is one kind of modal, so
28
+ * it takes the specific name and hands the generic one back to the
29
+ * base layer.
30
+ *
31
+ * Everything modal — the portal, the scrim, Escape and outside-press
32
+ * dismiss, the focus trap, the scroll lock, the stacking — is `Modal`'s
33
+ * (via `OverlayPanel`), so this component is just the box's contents
34
+ * and the size class it wants that box to be. `SIZE_CLASS` is composed
35
+ * over `Modal`'s default surface, which is why `full` carries
36
+ * `rounded-none` to square off the base's `rounded-lg` at the viewport
37
+ * edge.
38
+ *
39
+ * ### The accessible name
40
+ *
41
+ * `heading` is required and is a string, not a `ReactNode`. It names
42
+ * both the visible header and — through `aria-labelledby` on the
43
+ * modal — the dialog itself. A dialog with no name is announced as
44
+ * "dialog" and nothing else, which is what every hand-rolled modal in
45
+ * the inventory does.
46
+ */
47
+ export const Dialog = ({ children, className, footer, heading, headingLevel = 2, isDismissable = true, isVisible, onClose, size = "md", }) => {
48
+ const headingId = useUniqueId();
49
+ const Heading = `h${headingLevel}`;
50
+ return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-start justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: children }), footer ? (_jsx("footer", { className: "flex shrink-0 flex-wrap justify-end gap-2 border-t border-border-subtle p-4", children: footer })) : null] }));
51
+ };
52
+ //# sourceMappingURL=Dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,mFAAmF,aACnG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAET,cAAK,SAAS,EAAC,kCAAkC,YAC9C,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC,CAAA"}
@@ -37,13 +37,13 @@ export type LightboxProps = {
37
37
  /**
38
38
  * A thumbnail that opens its own full-size view — the one image
39
39
  * interaction three apps hand-roll and none get the focus trap or
40
- * the scroll lock right. It is a **skin over `Modal`**, not a second
41
- * dialog: `Modal` owns `showModal()`, the top layer, Escape, the
42
- * `::backdrop` scrim and `lockScrollBehind`, and everything that
43
- * makes those correct in every browser the fleet runs. What this
44
- * adds is the image-specific part — a `cursor-zoom-in` trigger and
45
- * an `object-contain` image clamped to the viewport so a 2:3 poster
46
- * and a 16:9 still both land whole.
40
+ * the scroll lock right. It is a **skin over `Dialog`**, not a second
41
+ * dialog: `Dialog` (through `Modal`/`OverlayPanel`) owns the portal,
42
+ * the scrim, Escape, the focus trap and `lockScrollBehind`, and
43
+ * everything that makes those correct in every browser the fleet
44
+ * runs. What this adds is the image-specific part — a `cursor-zoom-in`
45
+ * trigger and an `object-contain` image clamped to the viewport so a
46
+ * 2:3 poster and a 16:9 still both land whole.
47
47
  *
48
48
  * ### Controlled and not, one component
49
49
  *
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useVisibility } from "@charcuterie/logic";
3
- import { Modal } from "../Modal/Modal.js";
3
+ import { Dialog } from "../Dialog/Dialog.js";
4
4
  import { toClassName } from "../toClassName.js";
5
5
  /**
6
6
  * A thumbnail that opens its own full-size view — the one image
7
7
  * interaction three apps hand-roll and none get the focus trap or
8
- * the scroll lock right. It is a **skin over `Modal`**, not a second
9
- * dialog: `Modal` owns `showModal()`, the top layer, Escape, the
10
- * `::backdrop` scrim and `lockScrollBehind`, and everything that
11
- * makes those correct in every browser the fleet runs. What this
12
- * adds is the image-specific part — a `cursor-zoom-in` trigger and
13
- * an `object-contain` image clamped to the viewport so a 2:3 poster
14
- * and a 16:9 still both land whole.
8
+ * the scroll lock right. It is a **skin over `Dialog`**, not a second
9
+ * dialog: `Dialog` (through `Modal`/`OverlayPanel`) owns the portal,
10
+ * the scrim, Escape, the focus trap and `lockScrollBehind`, and
11
+ * everything that makes those correct in every browser the fleet
12
+ * runs. What this adds is the image-specific part — a `cursor-zoom-in`
13
+ * trigger and an `object-contain` image clamped to the viewport so a
14
+ * 2:3 poster and a 16:9 still both land whole.
15
15
  *
16
16
  * ### Controlled and not, one component
17
17
  *
@@ -41,7 +41,7 @@ export const Lightbox = ({ alt, caption, className, heading, isOpen, onOpenChang
41
41
  // `cursor-zoom-in` is the affordance a bare `<img>`
42
42
  // never had, and the focus ring is the fleet's standard
43
43
  // — the same one `MediaTile`'s link wears.
44
- "group block cursor-zoom-in rounded-md outline-offset-2 focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-focus-ring", className), onClick: requestOpen, type: "button", children: thumbnail })), _jsx(Modal, { heading: heading ?? alt, isVisible: isShown, onClose: requestClose, size: "xl", children: _jsxs("figure", { className: "flex flex-col items-center gap-3", children: [_jsx("img", { alt: alt,
44
+ "group block cursor-zoom-in rounded-md outline-offset-2 focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-focus-ring", className), onClick: requestOpen, type: "button", children: thumbnail })), _jsx(Dialog, { heading: heading ?? alt, isVisible: isShown, onClose: requestClose, size: "xl", children: _jsxs("figure", { className: "flex flex-col items-center gap-3", children: [_jsx("img", { alt: alt,
45
45
  // `object-contain`, not `cover`: the enlarged view must
46
46
  // show the whole image, and the clamp is `dvh` for the
47
47
  // same address-bar reason `Modal`'s is.
@@ -1 +1 @@
1
- {"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../src/Lightbox/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAsC/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,GAAG,EACH,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,YAAY,EACZ,GAAG,EACH,SAAS,GACK,EAAa,EAAE;IAC7B,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,CAAA;IAEzC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAEjD,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC,CAAA;IAED,OAAO,CACL,8BACG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAChC,+BAMc,WAAW,OAAO,IAAI,GAAG,EAAE,EACvC,SAAS,EAAE,WAAW;gBACpB,oDAAoD;gBACpD,wDAAwD;gBACxD,2CAA2C;gBAC3C,uKAAuK,EACvK,SAAS,CACV,EACD,OAAO,EAAE,WAAW,EACpB,IAAI,EAAC,QAAQ,YAEZ,SAAS,GACH,CACV,EAED,KAAC,KAAK,IACJ,OAAO,EAAE,OAAO,IAAI,GAAG,EACvB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAC,IAAI,YAET,kBAAQ,SAAS,EAAC,kCAAkC,aAClD,cACE,GAAG,EAAE,GAAG;4BACR,wDAAwD;4BACxD,uDAAuD;4BACvD,wCAAwC;4BACxC,SAAS,EAAC,2DAA2D,EACrE,GAAG,EAAE,GAAG,GACR,EAED,OAAO,CAAC,CAAC,CAAC,CACT,qBAAY,SAAS,EAAC,wCAAwC,YAC3D,OAAO,GACG,CACd,CAAC,CAAC,CAAC,IAAI,IACD,GACH,IACP,CACJ,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../src/Lightbox/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAsC/C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EACvB,GAAG,EACH,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,YAAY,EACZ,GAAG,EACH,SAAS,GACK,EAAa,EAAE;IAC7B,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,CAAA;IAEzC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CAAA;IAEjD,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAEjD,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,IAAI,EAAE,CAAA;QACR,CAAC;QAED,YAAY,EAAE,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC,CAAA;IAED,OAAO,CACL,8BACG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAChC,+BAMc,WAAW,OAAO,IAAI,GAAG,EAAE,EACvC,SAAS,EAAE,WAAW;gBACpB,oDAAoD;gBACpD,wDAAwD;gBACxD,2CAA2C;gBAC3C,uKAAuK,EACvK,SAAS,CACV,EACD,OAAO,EAAE,WAAW,EACpB,IAAI,EAAC,QAAQ,YAEZ,SAAS,GACH,CACV,EAED,KAAC,MAAM,IACL,OAAO,EAAE,OAAO,IAAI,GAAG,EACvB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,YAAY,EACrB,IAAI,EAAC,IAAI,YAET,kBAAQ,SAAS,EAAC,kCAAkC,aAClD,cACE,GAAG,EAAE,GAAG;4BACR,wDAAwD;4BACxD,uDAAuD;4BACvD,wCAAwC;4BACxC,SAAS,EAAC,2DAA2D,EACrE,GAAG,EAAE,GAAG,GACR,EAED,OAAO,CAAC,CAAC,CAAC,CACT,qBAAY,SAAS,EAAC,wCAAwC,YAC3D,OAAO,GACG,CACd,CAAC,CAAC,CAAC,IAAI,IACD,GACF,IACR,CACJ,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,56 @@
1
+ import type { Placement } from "@floating-ui/react";
2
+ import type { ReactElement, ReactNode } from "react";
3
+ export type ListboxItem = {
4
+ isDisabled?: boolean;
5
+ label: ReactNode;
6
+ /**
7
+ * Required when `label` is rich (an icon, two lines, a badge): it is
8
+ * the type-ahead target and the accessible name a screen reader
9
+ * reads. When `label` is a plain string it is used as the fallback.
10
+ */
11
+ textValue?: string;
12
+ value: string;
13
+ };
14
+ export type ListboxProps = {
15
+ className?: string;
16
+ isVisible: boolean;
17
+ /** Outside press and Escape both land here. */
18
+ onDismiss: () => void;
19
+ onSelect: (value: string) => void;
20
+ options: readonly ListboxItem[];
21
+ placement?: Placement;
22
+ /** The initial seed. The live selection is the listbox's after that. */
23
+ selectedValue?: string;
24
+ /** The control the listbox hangs off. **Cloned, not wrapped.** */
25
+ trigger: ReactElement;
26
+ };
27
+ /**
28
+ * The single-select fancy dropdown — the rich-option sibling of the
29
+ * native `Select`. `Select` keeps type-ahead, the mobile wheel, form
30
+ * submission and autofill; `Listbox` is for the cases `<option>`
31
+ * cannot render: an icon, two lines, a trailing badge.
32
+ *
33
+ * A listbox is **not** a menu, and that distinction is the whole
34
+ * reason it is a separate component from `Menu`. An `option` *is*
35
+ * something you are choosing — so it carries `aria-selected`, it
36
+ * type-aheads (which the APG requires of a listbox and `Menu`
37
+ * deliberately omits), and a screen reader announces "listbox,
38
+ * selected, 2 of 4". A `menuitem` only ever *does* something.
39
+ *
40
+ * ### Two kinds of state, composed
41
+ *
42
+ * `useRovingFocus` owns which option is tabbable (exactly one, so Tab
43
+ * enters and leaves the group once and the arrow keys move within it),
44
+ * and `useSinglePicker` owns which option is chosen. They are the same
45
+ * two kinds `Tabs` composes, and they never disturb each other:
46
+ * arrowing moves focus without selecting, Enter selects the focused
47
+ * option.
48
+ *
49
+ * ### Portalled, and named by its trigger
50
+ *
51
+ * Like `Menu`, the panel portals to `document.body` and `useRole` names
52
+ * it by pointing `aria-labelledby` at the trigger — so there is no
53
+ * `heading` prop; the button that opens it is its label.
54
+ */
55
+ export declare const Listbox: ({ className, isVisible, onDismiss, onSelect, options, placement, selectedValue, trigger, }: ListboxProps) => ReactNode;
56
+ //# sourceMappingURL=Listbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Listbox.d.ts","sourceRoot":"","sources":["../../src/Listbox/Listbox.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKnD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQpD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,OAAO,EAAE,YAAY,CAAA;CACtB,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,OAAO,GAAI,4FASrB,YAAY,KAAG,SA+MjB,CAAA"}