@deftui/react 0.2.5 → 0.3.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 (49) hide show
  1. package/README.md +2 -2
  2. package/dist/index.d.mts +7 -5
  3. package/dist/index.mjs +4 -4
  4. package/dist/styles.css +3 -0
  5. package/dist/vendor/README.md +10 -9
  6. package/dist/vendor/coss/calendar.d.mts +4 -0
  7. package/dist/vendor/coss/calendar.mjs +70 -0
  8. package/dist/vendor/coss/card.d.mts +23 -0
  9. package/dist/vendor/coss/card.mjs +160 -0
  10. package/dist/vendor/coss/dialog.d.mts +1 -1
  11. package/dist/vendor/coss/dialog.mjs +1 -1
  12. package/dist/vendor/coss/table.d.mts +15 -0
  13. package/dist/vendor/coss/table.mjs +73 -0
  14. package/dist/vendor/reui.io/cascader/cascader-async.d.mts +20 -0
  15. package/dist/vendor/reui.io/cascader/cascader-async.mjs +578 -0
  16. package/dist/vendor/reui.io/cascader/cascader-context.d.mts +12 -0
  17. package/dist/vendor/reui.io/cascader/cascader-context.mjs +56 -0
  18. package/dist/vendor/reui.io/cascader/cascader-footer.d.mts +60 -0
  19. package/dist/vendor/reui.io/cascader/cascader-footer.mjs +445 -0
  20. package/dist/vendor/reui.io/cascader/cascader-i18n.mjs +64 -0
  21. package/dist/vendor/reui.io/cascader/cascader-item.d.mts +92 -0
  22. package/dist/vendor/reui.io/cascader/cascader-item.mjs +533 -0
  23. package/dist/vendor/reui.io/cascader/cascader-lib.d.mts +3 -0
  24. package/dist/vendor/reui.io/cascader/cascader-lib.mjs +688 -0
  25. package/dist/vendor/reui.io/cascader/cascader-nav.d.mts +56 -0
  26. package/dist/vendor/reui.io/cascader/cascader-nav.mjs +310 -0
  27. package/dist/vendor/reui.io/cascader/cascader-types.d.mts +140 -0
  28. package/dist/vendor/reui.io/cascader/cascader-virtual.d.mts +18 -0
  29. package/dist/vendor/reui.io/cascader/cascader-virtual.mjs +159 -0
  30. package/dist/vendor/reui.io/cascader/cascader.d.mts +299 -0
  31. package/dist/vendor/reui.io/cascader/cascader.mjs +1470 -0
  32. package/dist/vendor/reui.io/timeline.d.mts +21 -0
  33. package/dist/vendor/reui.io/timeline.mjs +127 -0
  34. package/dist/vendor/tailored/lightbox.d.mts +1 -1
  35. package/dist/vendor/tailored/lightbox.mjs +1 -1
  36. package/dist/widgets/datetime.d.mts +1 -1
  37. package/dist/widgets/datetime.mjs +6 -1
  38. package/dist/widgets/export.d.mts +28 -0
  39. package/dist/widgets/index.d.mts +69 -20
  40. package/dist/widgets/index.mjs +61 -11
  41. package/package.json +2 -2
  42. package/dist/vendor/shadcn/calendar.d.mts +0 -10
  43. package/dist/vendor/shadcn/calendar.mjs +0 -107
  44. package/dist/vendor/shadcn/card.d.mts +0 -11
  45. package/dist/vendor/shadcn/card.mjs +0 -55
  46. package/dist/vendor/shadcn/table.d.mts +0 -9
  47. package/dist/vendor/shadcn/table.mjs +0 -59
  48. package/dist/vendor/tailored/file-tree.d.mts +0 -21
  49. package/dist/vendor/tailored/file-tree.mjs +0 -146
@@ -0,0 +1,445 @@
1
+ "use client";
2
+ import { useCascaderActions } from "./cascader-context.mjs";
3
+ import { CASCADER_ACTION_CLASS, CascaderGroup, CascaderLabel } from "./cascader-item.mjs";
4
+ import { CASCADER_LIST_PAD_CLASS, getCascaderFooterStops, isCascaderRtl } from "./cascader-lib.mjs";
5
+ import { cn } from "cn";
6
+ import * as React$1 from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { ChevronRightIcon } from "lucide-react";
9
+ import { Popover } from "@base-ui/react";
10
+ import { useDirection } from "@base-ui/react/direction-provider";
11
+ // src/vendor/reui.io/cascader/cascader-footer.tsx
12
+ /**
13
+ * The pinned footer, and the side-anchored flyout a footer row can open. These
14
+ * are COMMANDS: nothing here joins the selection, the filter set or the
15
+ * highlight. The flyout is a Base UI `Popover` rendered as a REACT CHILD of
16
+ * `Combobox.Popup` with its OWN `Portal` and NO `container`: a nested portal
17
+ * resolves to the parent portal node, so it is a DOM sibling of the combobox
18
+ * popup (not clipped, not `aria-hidden`) but a React descendant, which is what
19
+ * the outside-press and focus-out whitelists read. `Combobox` builds no
20
+ * `FloatingTree`, so the flyout is not consulted first and one Escape would
21
+ * dismiss both; hence `CascaderSubmenu` registering with the root to turn one
22
+ * Escape into two. `Combobox.List` clicks its highlighted row on Enter, hence
23
+ * the footer sitting outside `CascaderList`. And a `Positioner` throws without
24
+ * its `Portal`, while `modal` stays `false` on the `Root` so the combobox
25
+ * keeps its own dismissal behaviour.
26
+ */
27
+ /**
28
+ * Keys the option list acts on, swallowed at the footer boundary. Escape and
29
+ * Tab are absent on purpose: Escape must reach the root, Tab must keep moving.
30
+ */
31
+ const FOOTER_SWALLOWED_KEYS = /* @__PURE__ */ new Set([
32
+ "Enter",
33
+ " ",
34
+ "ArrowUp",
35
+ "ArrowDown",
36
+ "Home",
37
+ "End",
38
+ "PageUp",
39
+ "PageDown"
40
+ ]);
41
+ /**
42
+ * Actions pinned below the list, a SIBLING of `CascaderList`. Children win
43
+ * over the root's `actions` prop; with neither it renders nothing.
44
+ */
45
+ function CascaderFooter({ className, children, onKeyDown, ...props }) {
46
+ const { actions, labels } = useCascaderActions();
47
+ const hasChildren = React$1.Children.count(children) > 0;
48
+ const handleKeyDown = React$1.useCallback((event) => {
49
+ onKeyDown?.(event);
50
+ if (event.defaultPrevented) return;
51
+ if (!FOOTER_SWALLOWED_KEYS.has(event.key)) return;
52
+ event.stopPropagation();
53
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
54
+ const footer = event.currentTarget;
55
+ const stops = getCascaderFooterStops(footer);
56
+ const active = document.activeElement;
57
+ const index = active ? stops.indexOf(active) : -1;
58
+ if (index === -1) return;
59
+ event.preventDefault();
60
+ const next = event.key === "ArrowDown" ? stops[index + 1] : stops[index - 1];
61
+ if (next) {
62
+ next.focus();
63
+ return;
64
+ }
65
+ if (event.key === "ArrowUp" && index > 0) return;
66
+ footer.closest("[data-slot=\"cascader-panel\"]")?.querySelector("[data-slot=\"cascader-input\"]")?.focus();
67
+ }, [onKeyDown]);
68
+ if (!hasChildren && actions.length === 0) return null;
69
+ return /* @__PURE__ */ jsx("div", {
70
+ "data-slot": "cascader-footer",
71
+ role: "group",
72
+ "aria-label": labels.actionsLabel,
73
+ onKeyDown: handleKeyDown,
74
+ className: cn("border-border/60 flex shrink-0 flex-col gap-0.5 border-t", CASCADER_LIST_PAD_CLASS, "p-(--cascader-list-pad,4px)", className),
75
+ ...props,
76
+ children: hasChildren ? children : /* @__PURE__ */ jsx(CascaderFooterActions, { actions })
77
+ });
78
+ }
79
+ function CascaderFooterActions({ actions }) {
80
+ return /* @__PURE__ */ jsx(Fragment, { children: actions.map((action, i) => action.items?.length ? /* @__PURE__ */ jsxs(CascaderSubmenu, { children: [/* @__PURE__ */ jsx(CascaderSubmenuTrigger, {
81
+ icon: action.icon,
82
+ disabled: action.disabled,
83
+ children: action.label
84
+ }), /* @__PURE__ */ jsx(CascaderSubmenuContent, { children: /* @__PURE__ */ jsx(CascaderActionList, { items: action.items }) })] }, actionKey(action, i)) : /* @__PURE__ */ jsx(CascaderAction, {
85
+ icon: action.icon,
86
+ disabled: action.disabled,
87
+ onSelect: action.onSelect,
88
+ children: action.label
89
+ }, actionKey(action, i))) });
90
+ }
91
+ function actionKey(action, index) {
92
+ if (action.value != null) return action.value;
93
+ if (typeof action.label === "string") return action.label;
94
+ return String(index);
95
+ }
96
+ /**
97
+ * Consecutive entries sharing a `group`, as runs not buckets: two separated
98
+ * runs with the same name stay two, so the author's order survives.
99
+ */
100
+ function groupActionRuns(items) {
101
+ const runs = [];
102
+ for (const item of items) {
103
+ const last = runs[runs.length - 1];
104
+ if (last && last.group === item.group) last.items.push(item);
105
+ else runs.push({
106
+ group: item.group,
107
+ items: [item]
108
+ });
109
+ }
110
+ return runs;
111
+ }
112
+ /**
113
+ * Flyout body for a data-driven submenu. A named run becomes a real
114
+ * `CascaderGroup`; unnamed runs stay unwrapped, as an unnamed group is noise.
115
+ */
116
+ function CascaderActionList({ items }) {
117
+ const { close } = useCascaderSubmenu();
118
+ const runs = React$1.useMemo(() => groupActionRuns(items), [items]);
119
+ const renderAction = (item, i) => /* @__PURE__ */ jsx(CascaderAction, {
120
+ icon: item.icon,
121
+ disabled: item.disabled,
122
+ onSelect: () => {
123
+ item.onSelect?.();
124
+ close();
125
+ },
126
+ children: item.label
127
+ }, actionKey(item, i));
128
+ return /* @__PURE__ */ jsx(Fragment, { children: runs.map((run, runIndex) => run.group ? /* @__PURE__ */ jsxs(CascaderGroup, {
129
+ className: "gap-0.5",
130
+ children: [/* @__PURE__ */ jsx(CascaderLabel, { children: run.group }), run.items.map(renderAction)]
131
+ }, `${run.group}-${runIndex}`) : /* @__PURE__ */ jsx(React$1.Fragment, { children: run.items.map(renderAction) }, `run-${runIndex}`)) });
132
+ }
133
+ /**
134
+ * The cascader popup's panel per style, spelled with `style-<name>:` variants
135
+ * rather than ReUI theme CSS so an installed footer needs only Tailwind.
136
+ */
137
+ const FLYOUT_SURFACE_CLASS = "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-72 overflow-hidden ring-1 duration-100 ring-foreground/10 rounded-md shadow-md";
138
+ /**
139
+ * One footer command, shaped like a row and deliberately NOT one. A real
140
+ * `<button>`, never a `Combobox.Item`: an item would join the arrow-key ring,
141
+ * appear in `filteredItems`, and vanish the moment a query matched nothing -
142
+ * exactly when "Create new attribute" is most useful. Disabled is
143
+ * `aria-disabled`, never the native attribute: that one is not a tab stop and
144
+ * the panel's Tab order reads `button:not([disabled])`, so a footer whose ONLY
145
+ * row is a disabled command had no stop after the search field (measured on
146
+ * `c-cascader-8`). Staying focusable costs the guards below, plus the
147
+ * greyed-out look: `CASCADER_ACTION_CLASS` keys that off `aria-disabled`
148
+ * instead of `:disabled`.
149
+ */
150
+ function CascaderAction({ className, icon, children, onSelect, onClick, onKeyDown, disabled, ...props }) {
151
+ const inMenu = React$1.useContext(CascaderMenuContext);
152
+ const handleClick = React$1.useCallback((event) => {
153
+ if (disabled) {
154
+ event.preventDefault();
155
+ return;
156
+ }
157
+ onClick?.(event);
158
+ if (event.defaultPrevented) return;
159
+ onSelect?.();
160
+ }, [
161
+ disabled,
162
+ onClick,
163
+ onSelect
164
+ ]);
165
+ const handleKeyDown = React$1.useCallback((event) => {
166
+ if (disabled) {
167
+ if (event.key === "Enter" || event.key === " ") event.preventDefault();
168
+ return;
169
+ }
170
+ onKeyDown?.(event);
171
+ }, [disabled, onKeyDown]);
172
+ return /* @__PURE__ */ jsxs("button", {
173
+ type: "button",
174
+ "data-slot": "cascader-action",
175
+ ...disabled ? {
176
+ "aria-disabled": true,
177
+ "data-disabled": ""
178
+ } : null,
179
+ onClick: handleClick,
180
+ onKeyDown: handleKeyDown,
181
+ ...inMenu ? {
182
+ role: "menuitem",
183
+ tabIndex: -1
184
+ } : null,
185
+ className: cn(CASCADER_ACTION_CLASS, className),
186
+ ...props,
187
+ children: [icon ? /* @__PURE__ */ jsx("span", {
188
+ "data-slot": "cascader-action-icon",
189
+ className: "text-muted-foreground flex shrink-0 items-center justify-center",
190
+ children: icon
191
+ }) : null, /* @__PURE__ */ jsx("span", {
192
+ className: "min-w-0 flex-1 truncate text-start",
193
+ children
194
+ })]
195
+ });
196
+ }
197
+ /** Marks the subtree INSIDE a flyout. See `inMenu` in `CascaderAction`. */
198
+ const CascaderMenuContext = React$1.createContext(false);
199
+ /**
200
+ * Every entry a menu's roving focus may land on, in DOM order, read from the
201
+ * DOM because the entries are whatever the consumer composed. A disabled
202
+ * `CascaderAction` is INCLUDED: it carries `aria-disabled`, not the native
203
+ * attribute, so `:not([disabled])` excludes only a consumer's own natively
204
+ * disabled `menuitem`, which cannot take focus.
205
+ */
206
+ function menuItems(popup) {
207
+ if (!popup) return [];
208
+ return Array.from(popup.querySelectorAll("[role=\"menuitem\"]:not([disabled])"));
209
+ }
210
+ const CascaderSubmenuContext = React$1.createContext(void 0);
211
+ /** The flyout's own state. `close()` is the one a custom entry usually wants. */
212
+ function useCascaderSubmenu() {
213
+ const context = React$1.useContext(CascaderSubmenuContext);
214
+ if (!context) throw new Error("useCascaderSubmenu must be used within a CascaderSubmenu");
215
+ return context;
216
+ }
217
+ /**
218
+ * A footer row plus the flyout it opens. Registers with the cascader root
219
+ * while open, which turns one Escape into two. Cleared in an EFFECT, so the
220
+ * flyout still reads as open during the event that closed it.
221
+ */
222
+ function CascaderSubmenu({ open: openProp, defaultOpen = false, onOpenChange, children }) {
223
+ const { setFlyoutOpen } = useCascaderActions();
224
+ const key = React$1.useId();
225
+ const triggerId = React$1.useId();
226
+ const rowRef = React$1.useRef(null);
227
+ const keyboardRef = React$1.useRef(false);
228
+ const [uncontrolled, setUncontrolled] = React$1.useState(defaultOpen);
229
+ const open = openProp ?? uncontrolled;
230
+ const setOpen = React$1.useCallback((next) => {
231
+ if (openProp == null) setUncontrolled(next);
232
+ onOpenChange?.(next);
233
+ }, [openProp, onOpenChange]);
234
+ const close = React$1.useCallback(() => setOpen(false), [setOpen]);
235
+ React$1.useEffect(() => {
236
+ setFlyoutOpen(key, open);
237
+ return () => setFlyoutOpen(key, false);
238
+ }, [
239
+ setFlyoutOpen,
240
+ key,
241
+ open
242
+ ]);
243
+ const context = React$1.useMemo(() => ({
244
+ rowRef,
245
+ open,
246
+ setOpen,
247
+ close,
248
+ triggerId,
249
+ keyboardRef
250
+ }), [
251
+ open,
252
+ setOpen,
253
+ close,
254
+ triggerId
255
+ ]);
256
+ return /* @__PURE__ */ jsx(CascaderSubmenuContext.Provider, {
257
+ value: context,
258
+ children: /* @__PURE__ */ jsx(Popover.Root, {
259
+ open,
260
+ onOpenChange: setOpen,
261
+ modal: false,
262
+ children
263
+ })
264
+ });
265
+ }
266
+ /**
267
+ * The footer row that opens the flyout, and its anchor. `aria-haspopup="menu"`
268
+ * rather than the `dialog` Base UI would announce: what opens is a list of
269
+ * commands with roving focus. `disabled` is intercepted, not forwarded:
270
+ * `Popover.Trigger` runs it through `useButton`, which writes the NATIVE
271
+ * attribute for a native `<button>` and takes the row out of the panel's Tab
272
+ * ring, the defect `CascaderAction` documents, and this component exposes no
273
+ * `focusableWhenDisabled` to opt out. Published as `aria-disabled`, with the
274
+ * arrow keys, Enter and Space, and the click closed by hand (`useClick`
275
+ * ignores `defaultPrevented`).
276
+ */
277
+ function CascaderSubmenuTrigger({ className, icon, children, onKeyDown, onClick, disabled, ...props }) {
278
+ const { labels } = useCascaderActions();
279
+ const { rowRef, setOpen, triggerId, keyboardRef } = useCascaderSubmenu();
280
+ const direction = useDirection();
281
+ const handleClick = React$1.useCallback((event) => {
282
+ if (disabled) {
283
+ event.preventDefault();
284
+ event.preventBaseUIHandler();
285
+ return;
286
+ }
287
+ onClick?.(event);
288
+ }, [disabled, onClick]);
289
+ const handleKeyDown = React$1.useCallback((event) => {
290
+ if (disabled) {
291
+ if (event.key === "Enter" || event.key === " ") event.preventDefault();
292
+ return;
293
+ }
294
+ onKeyDown?.(event);
295
+ if (event.defaultPrevented) return;
296
+ const openKey = isCascaderRtl(event.currentTarget, direction) ? "ArrowLeft" : "ArrowRight";
297
+ if (event.key === "Enter" || event.key === " ") {
298
+ keyboardRef.current = true;
299
+ return;
300
+ }
301
+ if (event.key !== openKey) return;
302
+ event.preventDefault();
303
+ keyboardRef.current = true;
304
+ setOpen(true);
305
+ }, [
306
+ disabled,
307
+ onKeyDown,
308
+ direction,
309
+ setOpen,
310
+ keyboardRef
311
+ ]);
312
+ return /* @__PURE__ */ jsxs(Popover.Trigger, {
313
+ ref: rowRef,
314
+ id: triggerId,
315
+ "data-slot": "cascader-submenu-trigger",
316
+ "aria-haspopup": "menu",
317
+ ...disabled ? {
318
+ "aria-disabled": true,
319
+ "data-disabled": ""
320
+ } : null,
321
+ onClick: handleClick,
322
+ onKeyDown: handleKeyDown,
323
+ className: cn(CASCADER_ACTION_CLASS, "aria-expanded:bg-accent aria-expanded:text-accent-foreground", className),
324
+ ...props,
325
+ children: [
326
+ icon ? /* @__PURE__ */ jsx("span", {
327
+ "data-slot": "cascader-action-icon",
328
+ className: "text-muted-foreground flex shrink-0 items-center justify-center",
329
+ children: icon
330
+ }) : null,
331
+ /* @__PURE__ */ jsx("span", {
332
+ className: "min-w-0 flex-1 truncate text-start",
333
+ children
334
+ }),
335
+ /* @__PURE__ */ jsxs("span", {
336
+ className: "sr-only",
337
+ children: [", ", labels.submenuAffordance]
338
+ }),
339
+ /* @__PURE__ */ jsx(ChevronRightIcon, {
340
+ "aria-hidden": "true",
341
+ className: "text-muted-foreground -me-0.5 size-4 shrink-0 rtl:-scale-x-100"
342
+ })
343
+ ]
344
+ });
345
+ }
346
+ /**
347
+ * The flyout itself. `Portal` with NO `container`: a nested portal resolves to
348
+ * the parent portal node, the mechanism the file header describes.
349
+ */
350
+ function CascaderSubmenuContent({ className, children, onKeyDown, side = "inline-end", align = "end", sideOffset = 8, alignOffset = 0, ...props }) {
351
+ const { rowRef, close, triggerId, keyboardRef } = useCascaderSubmenu();
352
+ const direction = useDirection();
353
+ const popupRef = React$1.useRef(null);
354
+ const typeaheadRef = React$1.useRef({
355
+ buffer: "",
356
+ at: 0
357
+ });
358
+ /**
359
+ * Keyboard opens land on the first entry; a pointer open falls through to
360
+ * Base UI's default, the popup, so a click paints no focus ring. Not an
361
+ * effect of our own: Base UI's focus manager runs on open and wins the race.
362
+ */
363
+ const initialFocus = React$1.useCallback(() => {
364
+ const byKeyboard = keyboardRef.current;
365
+ keyboardRef.current = false;
366
+ if (!byKeyboard) return true;
367
+ return menuItems(popupRef.current)[0] ?? true;
368
+ }, [keyboardRef]);
369
+ const closeAndReturn = React$1.useCallback(() => {
370
+ close();
371
+ rowRef.current?.focus();
372
+ }, [close, rowRef]);
373
+ const handleKeyDown = React$1.useCallback((event) => {
374
+ onKeyDown?.(event);
375
+ event.stopPropagation();
376
+ if (event.defaultPrevented) return;
377
+ const popup = popupRef.current;
378
+ const items = menuItems(popup);
379
+ if (items.length === 0) return;
380
+ const active = document.activeElement;
381
+ const index = active ? items.indexOf(active) : -1;
382
+ const move = (next) => {
383
+ event.preventDefault();
384
+ items[(next + items.length) % items.length]?.focus();
385
+ };
386
+ const closeKey = isCascaderRtl(event.currentTarget, direction) ? "ArrowRight" : "ArrowLeft";
387
+ switch (event.key) {
388
+ case "ArrowDown": return move(index + 1);
389
+ case "ArrowUp": return move(index === -1 ? items.length - 1 : index - 1);
390
+ case "Home": return move(0);
391
+ case "End": return move(items.length - 1);
392
+ case closeKey:
393
+ event.preventDefault();
394
+ return closeAndReturn();
395
+ case "Tab":
396
+ close();
397
+ rowRef.current?.focus();
398
+ return;
399
+ }
400
+ if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey) return;
401
+ const now = event.timeStamp;
402
+ const state = typeaheadRef.current;
403
+ state.buffer = now - state.at > 500 ? event.key : state.buffer + event.key;
404
+ state.at = now;
405
+ const prefix = state.buffer.toLowerCase();
406
+ const from = index === -1 ? 0 : index;
407
+ const hit = [...items.slice(state.buffer.length > 1 ? from : from + 1), ...items.slice(0, state.buffer.length > 1 ? from : from + 1)].find((item) => (item.textContent ?? "").trim().toLowerCase().startsWith(prefix));
408
+ if (hit) {
409
+ event.preventDefault();
410
+ hit.focus();
411
+ }
412
+ }, [
413
+ onKeyDown,
414
+ direction,
415
+ close,
416
+ closeAndReturn,
417
+ rowRef
418
+ ]);
419
+ return /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
420
+ anchor: rowRef,
421
+ side,
422
+ align,
423
+ sideOffset,
424
+ alignOffset,
425
+ className: "isolate z-50",
426
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
427
+ ref: popupRef,
428
+ initialFocus,
429
+ "data-slot": "cascader-submenu-content",
430
+ role: "menu",
431
+ "aria-orientation": "vertical",
432
+ "aria-labelledby": triggerId,
433
+ tabIndex: -1,
434
+ onKeyDown: handleKeyDown,
435
+ "data-menu-target": "",
436
+ className: cn(FLYOUT_SURFACE_CLASS, "flex max-w-(--available-width) min-w-48 flex-col gap-0.5 outline-hidden", CASCADER_LIST_PAD_CLASS, "p-(--cascader-list-pad,4px)", className),
437
+ ...props,
438
+ children: /* @__PURE__ */ jsx(CascaderMenuContext.Provider, {
439
+ value: true,
440
+ children
441
+ })
442
+ })
443
+ }) });
444
+ }
445
+ export { CascaderAction, CascaderFooter, CascaderSubmenu, CascaderSubmenuContent, CascaderSubmenuTrigger, useCascaderSubmenu };
@@ -0,0 +1,64 @@
1
+ // src/vendor/reui.io/cascader/cascader-i18n.tsx
2
+ /** Name of the root level. Hoisted so the root announcement can reuse it. */
3
+ const ROOT_LEVEL = "Top level";
4
+ /** Hoisted for the same reason: several defaults end in an item count. */
5
+ const itemCount = (count) => `${count} ${count === 1 ? "item" : "items"}`;
6
+ /**
7
+ * English defaults. Every string the primitive can render lives here, so a
8
+ * consumer can translate the whole surface by passing `labels`.
9
+ */
10
+ const CASCADER_LABELS = {
11
+ search: (parentLabel) => parentLabel ? `Search ${parentLabel}...` : "Search...",
12
+ back: "Back",
13
+ loading: "Loading...",
14
+ loadingMore: "Loading more...",
15
+ loadMore: "Load more",
16
+ error: "Could not load items.",
17
+ retry: "Retry",
18
+ empty: "No results found.",
19
+ selectedCount: (count) => `${count} selected`,
20
+ breadcrumbLabel: "Breadcrumb",
21
+ chipsLabel: "Selected items",
22
+ removeChip: (label) => `Remove ${label}`,
23
+ pathSeparator: "/",
24
+ rootLevel: ROOT_LEVEL,
25
+ itemCount,
26
+ branchAffordance: "submenu",
27
+ selectedState: "selected",
28
+ partiallySelectedState: "partially selected",
29
+ columnsLabel: "Levels",
30
+ actionsLabel: "Actions",
31
+ submenuAffordance: "opens a menu",
32
+ panelLabel: "Options",
33
+ keyboardHint: (mode, dir) => {
34
+ const open = dir === "rtl" ? "Left" : "Right";
35
+ const back = dir === "rtl" ? "Right" : "Left";
36
+ if (mode === "tree") return `Use the ${open} Arrow key to expand and the ${back} Arrow key to collapse.`;
37
+ if (mode === "columns") return `Use the ${open} Arrow key to open the next column and the ${back} Arrow key to go back.`;
38
+ return `Use the ${open} Arrow key to open a branch and the ${back} Arrow key to go back.`;
39
+ },
40
+ rootAnnouncement: (count) => `${ROOT_LEVEL}, ${itemCount(count)}`,
41
+ expandedAnnouncement: (label, count) => `${label} expanded, ${itemCount(count)}`,
42
+ collapsedAnnouncement: (label) => `${label} collapsed`,
43
+ levelAnnouncement: (parentLabel, depth, count) => `${parentLabel}, level ${depth}, ${itemCount(count)}`,
44
+ resultsAnnouncement: (count) => count === 1 ? "1 result" : `${count} results`,
45
+ maxReachedAnnouncement: (max) => `Selection limit of ${max} reached`,
46
+ cascadeAnnouncement: (label, count, selecting) => `${label} ${selecting ? "selected" : "deselected"}, ${itemCount(count)} followed`,
47
+ searchingAnnouncement: "Searching..."
48
+ };
49
+ /**
50
+ * Shallow-merges consumer overrides over the defaults, so `labels` can carry a
51
+ * single key without restating the rest.
52
+ */
53
+ function resolveCascaderLabels(labels) {
54
+ if (!labels) return CASCADER_LABELS;
55
+ return {
56
+ ...CASCADER_LABELS,
57
+ ...labels
58
+ };
59
+ }
60
+ /** Resolves the search placeholder, which may be a string or a function. */
61
+ function resolveCascaderSearchLabel(labels, parentLabel) {
62
+ return typeof labels.search === "function" ? labels.search(parentLabel) : labels.search;
63
+ }
64
+ export { CASCADER_LABELS, resolveCascaderLabels, resolveCascaderSearchLabel };
@@ -0,0 +1,92 @@
1
+ import { CascaderNode } from "./cascader-types.mjs";
2
+ import * as React$1 from "react";
3
+ import { Combobox } from "@base-ui/react";
4
+ type VetoableEvent = {
5
+ preventBaseUIHandler?: () => void;
6
+ };
7
+ /** A row press. The veto hook is optional: an `as="button"` row is outside Base
8
+ * UI's listbox and gets a plain React event. */
9
+ export type CascaderRowEvent = React$1.MouseEvent<HTMLElement> & VetoableEvent;
10
+ export interface CascaderItemProps extends Omit<Combobox.Item.Props, "value" | "children" | "className" | "style" | "onClick" | "onMouseUp"> {
11
+ /** No state callback: the row merges `className` and `style` itself. */
12
+ className?: string;
13
+ style?: React$1.CSSProperties;
14
+ onClick?: (event: CascaderRowEvent) => void;
15
+ onMouseUp?: (event: CascaderRowEvent) => void;
16
+ node: CascaderNode;
17
+ /** Explicit render index. Required while virtualized and DELIBERATELY IGNORED
18
+ * otherwise: it self-registers the row and breaks `aria-activedescendant`. */
19
+ index?: number;
20
+ /** Indentation depth. Drives `--cascader-indent` in tree mode. */
21
+ depth?: number;
22
+ /** Renders the ancestor chain under the label, for deep and global search. */
23
+ showPath?: boolean;
24
+ /** `option` renders a real `Combobox.Item`; `button` renders identical markup
25
+ * outside the listbox, for the ancestor columns in columns mode. */
26
+ as?: "option" | "button";
27
+ /** Tree mode: this row is an expanded branch. */
28
+ expanded?: boolean;
29
+ /** Indent each depth by this many pixels. Tree mode only. */
30
+ indent?: number;
31
+ /** Whether the node has children. Defaults to the cascader's own answer, as do
32
+ * the three below: callers pass them because they already hold the level,
33
+ * which keeps a memoised row off the volatile context. */
34
+ branch?: boolean;
35
+ /** Whether the node may be committed. Defaults to the cascader's own answer. */
36
+ selectable?: boolean;
37
+ /** Whether the node is currently selected. Defaults to the cascader's answer. */
38
+ selected?: boolean;
39
+ /** Some but not all of the loaded subtree selected. `cascade` only. */
40
+ indeterminate?: boolean;
41
+ /** Selected nodes below this one, at any depth. Drives the trailing count. */
42
+ selectedCount?: number;
43
+ /** Paging row only: a request for this level is in flight. */
44
+ loading?: boolean;
45
+ /** Paging row only: this level's last request failed. */
46
+ error?: boolean;
47
+ /** Branch row only: this node's OWN children are being fetched. The chevron
48
+ * (or, in tree mode, the expander) becomes a spinner IN PLACE, in the same
49
+ * 16px box: no skeleton row appears below, nothing reflows. */
50
+ childrenLoading?: boolean;
51
+ /** Branch row only: the last fetch of this node's children failed. The same
52
+ * box becomes the retry. The press path is unchanged: it goes through
53
+ * `navigate`, which refires the failed level rather than moving. */
54
+ childrenError?: boolean;
55
+ children?: React$1.ReactNode;
56
+ }
57
+ export interface CascaderItemsProps {
58
+ /** Replaces the default row for every item. */
59
+ children?: (node: CascaderNode, index: number) => React$1.ReactNode;
60
+ }
61
+ /** Whatever the active view is: the current level, the deepest column, or the
62
+ * flattened tree rows. Lives here so `cascader.tsx` never imports a row
63
+ * component, which would cycle with the context. `index` is deliberately not
64
+ * passed; see `CascaderItemProps`. */
65
+ declare function CascaderItems({ children }: CascaderItemsProps): React$1.JSX.Element;
66
+ export interface CascaderGroupProps extends Omit<Combobox.Group.Props, "className"> {
67
+ /** Base UI also accepts a state callback here; the group has no state. */
68
+ className?: string;
69
+ }
70
+ /** A run of related rows, named by the `CascaderLabel` inside it: the group is
71
+ * `role="group"` and Base UI points its `aria-labelledby` at the label, where a
72
+ * heading alone inside a listbox is dropped from the accessibility tree.
73
+ * Grouping is composition, not data: `CascaderItems` renders one flat run per
74
+ * level, so its indices line up with `listRef`. */
75
+ declare function CascaderGroup({ className, ...props }: CascaderGroupProps): React$1.JSX.Element;
76
+ export interface CascaderLabelProps extends Omit<Combobox.GroupLabel.Props, "className" | "style"> {
77
+ /** No state callback; outside a group this is not a Base UI part at all. */
78
+ className?: string;
79
+ style?: React$1.CSSProperties;
80
+ }
81
+ /** A heading. Inside `CascaderGroup` it is the group's accessible name; outside
82
+ * one (the footer flyout is a menu, not a listbox) it is decoration only. */
83
+ declare function CascaderLabel(props: CascaderLabelProps): React$1.JSX.Element;
84
+ export interface CascaderSeparatorProps extends Omit<Combobox.Separator.Props, "className"> {
85
+ /** Base UI also accepts a state callback here; only the orientation varies. */
86
+ className?: string;
87
+ }
88
+ /** Decorative on purpose: Base UI's separator is `role="separator"`, which a
89
+ * `listbox` may not own, and a run that needs separating for a screen reader
90
+ * needs a `CascaderGroup`, not a line. A consumer can pass the role back. */
91
+ declare function CascaderSeparator({ className, ...props }: CascaderSeparatorProps): React$1.JSX.Element;
92
+ export { CascaderGroup, CascaderItems, CascaderLabel, CascaderSeparator };