sherick-ui 2.0.0 → 2.1.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 (99) hide show
  1. package/README.md +55 -199
  2. package/dist/cjs/components/Breadcrumb.cjs +47 -0
  3. package/dist/cjs/components/Breadcrumb.cjs.map +1 -0
  4. package/dist/cjs/components/Calendar.cjs +298 -0
  5. package/dist/cjs/components/Calendar.cjs.map +1 -0
  6. package/dist/cjs/components/Combobox.cjs +1 -1
  7. package/dist/cjs/components/Combobox.cjs.map +1 -1
  8. package/dist/cjs/components/Command.cjs +81 -0
  9. package/dist/cjs/components/Command.cjs.map +1 -0
  10. package/dist/cjs/components/CommandPalette.cjs +52 -0
  11. package/dist/cjs/components/CommandPalette.cjs.map +1 -0
  12. package/dist/cjs/components/DatePicker.cjs +107 -0
  13. package/dist/cjs/components/DatePicker.cjs.map +1 -0
  14. package/dist/cjs/components/DateRangePicker.cjs +165 -0
  15. package/dist/cjs/components/DateRangePicker.cjs.map +1 -0
  16. package/dist/cjs/components/DialogSurface.cjs +6 -5
  17. package/dist/cjs/components/DialogSurface.cjs.map +1 -1
  18. package/dist/cjs/components/FileUpload.cjs +203 -0
  19. package/dist/cjs/components/FileUpload.cjs.map +1 -0
  20. package/dist/cjs/components/Media.cjs +27 -0
  21. package/dist/cjs/components/Media.cjs.map +1 -0
  22. package/dist/cjs/components/Pagination.cjs +115 -0
  23. package/dist/cjs/components/Pagination.cjs.map +1 -0
  24. package/dist/cjs/components/Select.cjs +1 -1
  25. package/dist/cjs/components/Select.cjs.map +1 -1
  26. package/dist/cjs/components/Stepper.cjs +40 -0
  27. package/dist/cjs/components/Stepper.cjs.map +1 -0
  28. package/dist/cjs/components/Tabs.cjs +1 -1
  29. package/dist/cjs/components/Tabs.cjs.map +1 -1
  30. package/dist/cjs/components/TreeView.cjs +247 -0
  31. package/dist/cjs/components/TreeView.cjs.map +1 -0
  32. package/dist/cjs/components/date-family.cjs +191 -0
  33. package/dist/cjs/components/date-family.cjs.map +1 -0
  34. package/dist/cjs/components/date-field.cjs +90 -0
  35. package/dist/cjs/components/date-field.cjs.map +1 -0
  36. package/dist/cjs/components/fileUpload.rules.cjs +121 -0
  37. package/dist/cjs/components/fileUpload.rules.cjs.map +1 -0
  38. package/dist/cjs/components/tree.cjs +95 -0
  39. package/dist/cjs/components/tree.cjs.map +1 -0
  40. package/dist/cjs/components/ui.common.cjs +27 -7
  41. package/dist/cjs/components/ui.common.cjs.map +1 -1
  42. package/dist/cjs/components/ui.motion.cjs +3 -3
  43. package/dist/cjs/components/ui.motion.cjs.map +1 -1
  44. package/dist/cjs/dev.cjs +5 -0
  45. package/dist/cjs/dev.cjs.map +1 -1
  46. package/dist/cjs/index.cjs +22 -0
  47. package/dist/cjs/index.cjs.map +1 -1
  48. package/dist/esm/components/Breadcrumb.js +43 -0
  49. package/dist/esm/components/Breadcrumb.js.map +1 -0
  50. package/dist/esm/components/Calendar.js +294 -0
  51. package/dist/esm/components/Calendar.js.map +1 -0
  52. package/dist/esm/components/Combobox.js +2 -2
  53. package/dist/esm/components/Combobox.js.map +1 -1
  54. package/dist/esm/components/Command.js +77 -0
  55. package/dist/esm/components/Command.js.map +1 -0
  56. package/dist/esm/components/CommandPalette.js +48 -0
  57. package/dist/esm/components/CommandPalette.js.map +1 -0
  58. package/dist/esm/components/DatePicker.js +103 -0
  59. package/dist/esm/components/DatePicker.js.map +1 -0
  60. package/dist/esm/components/DateRangePicker.js +161 -0
  61. package/dist/esm/components/DateRangePicker.js.map +1 -0
  62. package/dist/esm/components/DialogSurface.js +6 -5
  63. package/dist/esm/components/DialogSurface.js.map +1 -1
  64. package/dist/esm/components/FileUpload.js +199 -0
  65. package/dist/esm/components/FileUpload.js.map +1 -0
  66. package/dist/esm/components/Media.js +25 -0
  67. package/dist/esm/components/Media.js.map +1 -0
  68. package/dist/esm/components/Pagination.js +111 -0
  69. package/dist/esm/components/Pagination.js.map +1 -0
  70. package/dist/esm/components/Select.js +2 -2
  71. package/dist/esm/components/Select.js.map +1 -1
  72. package/dist/esm/components/Stepper.js +36 -0
  73. package/dist/esm/components/Stepper.js.map +1 -0
  74. package/dist/esm/components/Tabs.js +2 -2
  75. package/dist/esm/components/Tabs.js.map +1 -1
  76. package/dist/esm/components/TreeView.js +243 -0
  77. package/dist/esm/components/TreeView.js.map +1 -0
  78. package/dist/esm/components/date-family.js +173 -0
  79. package/dist/esm/components/date-family.js.map +1 -0
  80. package/dist/esm/components/date-field.js +84 -0
  81. package/dist/esm/components/date-field.js.map +1 -0
  82. package/dist/esm/components/fileUpload.rules.js +114 -0
  83. package/dist/esm/components/fileUpload.rules.js.map +1 -0
  84. package/dist/esm/components/tree.js +88 -0
  85. package/dist/esm/components/tree.js.map +1 -0
  86. package/dist/esm/components/ui.common.js +23 -8
  87. package/dist/esm/components/ui.common.js.map +1 -1
  88. package/dist/esm/components/ui.motion.js +3 -3
  89. package/dist/esm/components/ui.motion.js.map +1 -1
  90. package/dist/esm/dev.js +1 -1
  91. package/dist/esm/index.js +11 -0
  92. package/dist/esm/index.js.map +1 -1
  93. package/dist/styles.css +365 -20
  94. package/dist/theme.css +15 -9
  95. package/dist/types/dev.d.cts +8 -3
  96. package/dist/types/dev.d.ts +8 -3
  97. package/dist/types/index.d.cts +600 -66
  98. package/dist/types/index.d.ts +600 -66
  99. package/package.json +1 -1
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { Popover } from '@base-ui/react/popover';
4
+ import { CalendarDays } from 'lucide-react';
5
+ import { useRef, useEffect } from 'react';
6
+ import { cn } from '../libs/utils.js';
7
+ import { density, shape, text, stateLayer, state, focusRingWithin, material, overlay, stacking } from './ui.common.js';
8
+ import { motionInkPress, motionFeedback, motionPresenceAnchored } from './ui.motion.js';
9
+
10
+ /* The parts the two date pickers share.
11
+ ====================================
12
+ A picker is a native `type=date` field with a named control beside it that opens the calendar,
13
+ so the field's surface, its trailing control and the popup that holds the grid have to be one
14
+ definition rather than two that drift. This module is private to the date family: nothing here
15
+ is published, and neither picker adds a shell of its own.
16
+
17
+ `DatePicker` and `DateRangePicker` compose Base `Field` for the label/description/error
18
+ relationship and Base `Popover` for the popup; what lives here is only the visual and
19
+ structural arrangement those compositions hold in common. */
20
+ /**
21
+ * The one field surface the date family uses: a borderless matte field whose focus treatment
22
+ * wraps the whole row, exactly as the library's other composite fields do.
23
+ *
24
+ * The row carries `group/field` and its own `data-disabled` marker so the trailing control's dim
25
+ * is neutralised inside an already dimmed field — a disabled field dims once, at the field.
26
+ */
27
+ const dateFieldRowClassName = (disabled) => cn("group/field flex min-w-0 w-full items-center ps-5 pe-3", density.normal, shape.control, material.control, motionFeedback, focusRingWithin, state.field.invalid, !disabled && state.field.hover, !disabled && state.field.focusWithin, !disabled && state.field.invalidHover, !disabled && state.field.invalidFocusWithin, disabled ? state.disabledDescendant : state.text);
28
+ /** Native date entry remains intact. Its trailing browser affordance sits outside the clipped
29
+ * input slot, leaving one visible calendar trigger in every engine (Firefox does not expose
30
+ * a picker-indicator pseudo-element). The composite row owns focus, not the clipped input. */
31
+ const dateInputClassName = "block w-[calc(100%+2.5rem)] min-w-0 bg-transparent py-3 text-inherit outline-none disabled:cursor-not-allowed";
32
+ /* The 36×44 target holds a concentric 32px hover surface, leaving air inside the 48px field.
33
+ Only the icon presses: the adjacent native text entry never activates this button. */
34
+ const datePartClassName = `group inline-flex shrink-0 items-center justify-center ${density.part} ${shape.circle} ${text.medium} ${motionFeedback} ${stateLayer.quiet} before:inset-x-0.5 before:inset-y-1.5 ${state.enabled} ${state.disabledPart} [&:not([data-disabled]):not(:disabled)]:hover:text-sherick-ink`;
35
+ /**
36
+ * The named control that opens the calendar. It is a Base `Popover.Trigger` rendered as a native
37
+ * button, so Base owns the relationship it publishes on the field (`aria-haspopup`,
38
+ * `aria-expanded`, `aria-controls`) and the pointer and keyboard activation that opens the
39
+ * surface; the button itself only carries the type it needs to stay out of form submission.
40
+ */
41
+ const DateFieldTrigger = ({ label, disabled = false }) => (jsx(Popover.Trigger, { disabled: disabled, render: jsx("button", { type: "button", "aria-label": label, disabled: disabled, className: cn(datePartClassName) }), children: jsx("span", { className: cn("inline-flex size-5 items-center justify-center [&>svg]:size-5", !disabled && motionInkPress), children: jsx(CalendarDays, { "aria-hidden": "true" }) }) }));
42
+ /**
43
+ * The calendar's popup: the same shell `Popover.Content` renders, composed straight from Base's
44
+ * public parts because the grid needs one thing the published shell does not offer — `initialFocus`
45
+ * aimed at the day the reader is on, so the grid is usable from the moment it opens.
46
+ *
47
+ * Base owns the portal, the placement and collision handling, the outside-interaction and Escape
48
+ * dismissal and the focus restoration; the surface is non-modal, so the rest of the page stays
49
+ * reachable. Sherick owns only the sheet and its presence.
50
+ */
51
+ const DateCalendarPopup = ({ title, children, anchor }) => {
52
+ const popupRef = useRef(null);
53
+ return (jsx(Popover.Portal, { children: jsx(Popover.Positioner, { anchor: anchor, side: "bottom", align: "start", sideOffset: 8, className: cn(stacking.float), children: jsxs(Popover.Popup, { ref: popupRef,
54
+ /* The roving tab stop is the calendar's own active day, so this is exactly the cell a
55
+ keyboard reader would land on. `null` leaves Base's default focus behaviour in place
56
+ if the grid has no tab stop to give. */
57
+ initialFocus: () => { var _a, _b; return (_b = (_a = popupRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('[data-day][tabindex="0"]')) !== null && _b !== void 0 ? _b : null; }, className: cn("w-max max-w-[min(24rem,var(--available-width))] max-h-[var(--available-height)] overflow-y-auto p-3 [overflow-wrap:anywhere]", overlay.popup, motionPresenceAnchored), children: [jsx(Popover.Title, { className: cn("sr-only"), children: title }), children] }) }) }));
58
+ };
59
+ /** Base Field controls entry and validity, but native reset emits no input/change event.
60
+ * Bridge only the date family's domain state, after the owning form's cancelable reset. */
61
+ const useDateFormReset = (inputRef, formId, restore) => {
62
+ const latestRestore = useRef(restore);
63
+ useEffect(() => { latestRestore.current = restore; }, [restore]);
64
+ useEffect(() => {
65
+ var _a;
66
+ const form = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.form;
67
+ if (!form)
68
+ return;
69
+ const reset = (event) => {
70
+ // A microtask can run between native listeners, before React's delegated onReset.
71
+ // The next task observes cancellation after the entire native dispatch/default action.
72
+ setTimeout(() => {
73
+ var _a;
74
+ if (!event.defaultPrevented && ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.form) === form)
75
+ latestRestore.current();
76
+ }, 0);
77
+ };
78
+ form.addEventListener("reset", reset);
79
+ return () => form.removeEventListener("reset", reset);
80
+ }, [inputRef, formId]);
81
+ };
82
+
83
+ export { DateCalendarPopup, DateFieldTrigger, dateFieldRowClassName, dateInputClassName, useDateFormReset };
84
+ //# sourceMappingURL=date-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-field.js","sources":["../../../src/components/date-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { Popover as BasePopover } from \"@base-ui/react/popover\";\nimport { CalendarDays } from \"lucide-react\";\nimport React, { useEffect, useRef, type ReactNode } from \"react\";\nimport { cn } from \"@/libs/utils\";\nimport { density, focusRingWithin, material, overlay, shape, stacking, state, stateLayer, text } from \"./ui.common\";\nimport { motionFeedback, motionInkPress, motionPresenceAnchored } from \"./ui.motion\";\n\n/* The parts the two date pickers share.\n ====================================\n A picker is a native `type=date` field with a named control beside it that opens the calendar,\n so the field's surface, its trailing control and the popup that holds the grid have to be one\n definition rather than two that drift. This module is private to the date family: nothing here\n is published, and neither picker adds a shell of its own.\n\n `DatePicker` and `DateRangePicker` compose Base `Field` for the label/description/error\n relationship and Base `Popover` for the popup; what lives here is only the visual and\n structural arrangement those compositions hold in common. */\n\n/**\n * The one field surface the date family uses: a borderless matte field whose focus treatment\n * wraps the whole row, exactly as the library's other composite fields do.\n *\n * The row carries `group/field` and its own `data-disabled` marker so the trailing control's dim\n * is neutralised inside an already dimmed field — a disabled field dims once, at the field.\n */\nexport const dateFieldRowClassName = (disabled: boolean) =>\n cn(\n \"group/field flex min-w-0 w-full items-center ps-5 pe-3\",\n density.normal,\n shape.control,\n material.control,\n motionFeedback,\n focusRingWithin,\n state.field.invalid,\n !disabled && state.field.hover,\n !disabled && state.field.focusWithin,\n !disabled && state.field.invalidHover,\n !disabled && state.field.invalidFocusWithin,\n disabled ? state.disabledDescendant : state.text\n );\n\n/** Native date entry remains intact. Its trailing browser affordance sits outside the clipped\n * input slot, leaving one visible calendar trigger in every engine (Firefox does not expose\n * a picker-indicator pseudo-element). The composite row owns focus, not the clipped input. */\nexport const dateInputClassName =\n \"block w-[calc(100%+2.5rem)] min-w-0 bg-transparent py-3 text-inherit outline-none disabled:cursor-not-allowed\";\n\n/* The 36×44 target holds a concentric 32px hover surface, leaving air inside the 48px field.\n Only the icon presses: the adjacent native text entry never activates this button. */\nconst datePartClassName = `group inline-flex shrink-0 items-center justify-center ${density.part} ${shape.circle} ${text.medium} ${motionFeedback} ${stateLayer.quiet} before:inset-x-0.5 before:inset-y-1.5 ${state.enabled} ${state.disabledPart} [&:not([data-disabled]):not(:disabled)]:hover:text-sherick-ink`;\n\nexport interface DateFieldTriggerProps {\n /** The control's accessible name. It has no visible label, so it has to be named. */\n label: string;\n disabled?: boolean;\n}\n\n/**\n * The named control that opens the calendar. It is a Base `Popover.Trigger` rendered as a native\n * button, so Base owns the relationship it publishes on the field (`aria-haspopup`,\n * `aria-expanded`, `aria-controls`) and the pointer and keyboard activation that opens the\n * surface; the button itself only carries the type it needs to stay out of form submission.\n */\nexport const DateFieldTrigger = ({ label, disabled = false }: DateFieldTriggerProps) => (\n <BasePopover.Trigger\n disabled={disabled}\n render={<button type=\"button\" aria-label={label} disabled={disabled} className={cn(datePartClassName)} />}\n >\n <span className={cn(\"inline-flex size-5 items-center justify-center [&>svg]:size-5\", !disabled && motionInkPress)}>\n <CalendarDays aria-hidden=\"true\" />\n </span>\n </BasePopover.Trigger>\n);\n\nexport interface DateCalendarPopupProps {\n /** Names the popup, through Base's own Title part. */\n title: string;\n children: ReactNode;\n anchor: React.ComponentProps<typeof BasePopover.Positioner>[\"anchor\"];\n}\n\n/**\n * The calendar's popup: the same shell `Popover.Content` renders, composed straight from Base's\n * public parts because the grid needs one thing the published shell does not offer — `initialFocus`\n * aimed at the day the reader is on, so the grid is usable from the moment it opens.\n *\n * Base owns the portal, the placement and collision handling, the outside-interaction and Escape\n * dismissal and the focus restoration; the surface is non-modal, so the rest of the page stays\n * reachable. Sherick owns only the sheet and its presence.\n */\nexport const DateCalendarPopup = ({ title, children, anchor }: DateCalendarPopupProps) => {\n const popupRef = useRef<HTMLDivElement | null>(null);\n\n return (\n <BasePopover.Portal>\n <BasePopover.Positioner\n anchor={anchor}\n side=\"bottom\"\n align=\"start\"\n sideOffset={8}\n className={cn(stacking.float)}\n >\n <BasePopover.Popup\n ref={popupRef}\n /* The roving tab stop is the calendar's own active day, so this is exactly the cell a\n keyboard reader would land on. `null` leaves Base's default focus behaviour in place\n if the grid has no tab stop to give. */\n initialFocus={() =>\n popupRef.current?.querySelector<HTMLElement>('[data-day][tabindex=\"0\"]') ?? null\n }\n className={cn(\n \"w-max max-w-[min(24rem,var(--available-width))] max-h-[var(--available-height)] overflow-y-auto p-3 [overflow-wrap:anywhere]\",\n overlay.popup,\n motionPresenceAnchored\n )}\n >\n <BasePopover.Title className={cn(\"sr-only\")}>{title}</BasePopover.Title>\n {children}\n </BasePopover.Popup>\n </BasePopover.Positioner>\n </BasePopover.Portal>\n );\n};\n\n/** Base Field controls entry and validity, but native reset emits no input/change event.\n * Bridge only the date family's domain state, after the owning form's cancelable reset. */\nexport const useDateFormReset = (\n inputRef: React.RefObject<HTMLInputElement | null>,\n formId: string | undefined,\n restore: () => void\n) => {\n const latestRestore = useRef(restore);\n useEffect(() => { latestRestore.current = restore; }, [restore]);\n useEffect(() => {\n const form = inputRef.current?.form;\n if (!form) return;\n const reset = (event: Event) => {\n // A microtask can run between native listeners, before React's delegated onReset.\n // The next task observes cancellation after the entire native dispatch/default action.\n setTimeout(() => {\n if (!event.defaultPrevented && inputRef.current?.form === form) latestRestore.current();\n }, 0);\n };\n form.addEventListener(\"reset\", reset);\n return () => form.removeEventListener(\"reset\", reset);\n }, [inputRef, formId]);\n};\n"],"names":["_jsx","BasePopover","_jsxs"],"mappings":"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C,CAAA,CAAA,CAAA,CAAI,CAAA;AAGpD,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;AAE+F,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACxF,CAAA,CAAA,CAAA,CAAA,CAAA,CAAM,kBAAkB,CAAA,CAAA,CAC7B,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AAEF,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AACwF,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,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"}
@@ -0,0 +1,114 @@
1
+ /* File selection rules for `FileUpload`.
2
+
3
+ The native chooser and the drop zone are two doors into one policy: the same accept, the same
4
+ per-file size and the same total count decide both, so a file the picker would refuse cannot
5
+ slip in through a drop. Nothing here reads a browser global — every rule works from the
6
+ metadata a `File` already carries — so the module is safe to evaluate during SSR and can be
7
+ probed without a document.
8
+
9
+ `FileUpload.tsx` composes the field around these rules; it owns no rule of its own. */
10
+ /** The identity of one selection entry: the fields that make two `File`s the same choice. */
11
+ const fileKey = (file) => `${file.name}\u0000${file.size}\u0000${file.type}\u0000${file.lastModified}`;
12
+ /** The per-file byte limit as a whole number, or `undefined` when the caller set none. */
13
+ const resolveMaxSize = (maxSize) => maxSize !== undefined && Number.isFinite(maxSize) && maxSize >= 0 ? Math.floor(maxSize) : undefined;
14
+ /**
15
+ * The total file limit, or `undefined` when the selection is unbounded. A single-file field
16
+ * holds exactly one: the picker is told nothing about it, because that limit is what makes a
17
+ * second file a replacement rather than an addition.
18
+ */
19
+ const resolveMaxFiles = (maxFiles, multiple) => {
20
+ if (!multiple)
21
+ return 1;
22
+ return maxFiles !== undefined && Number.isFinite(maxFiles) && maxFiles >= 0
23
+ ? Math.floor(maxFiles)
24
+ : undefined;
25
+ };
26
+ /** A byte limit as the copy spells it: `2 MB`, never `2097152`. */
27
+ const formatBytes = (bytes) => {
28
+ const units = ["B", "KB", "MB", "GB"];
29
+ let value = bytes;
30
+ let unit = 0;
31
+ while (value >= 1024 && unit < units.length - 1) {
32
+ value /= 1024;
33
+ unit += 1;
34
+ }
35
+ return `${Number.isInteger(value) ? value : value.toFixed(1)} ${units[unit]}`;
36
+ };
37
+ const acceptTokens = (accept) => (accept !== null && accept !== void 0 ? accept : "")
38
+ .split(",")
39
+ .map((token) => token.trim().toLowerCase())
40
+ .filter(Boolean);
41
+ /** An extension token starts with a dot; anything else has to name a media type. */
42
+ const isAcceptToken = (token) => (token.startsWith(".") ? token.length > 1 : token.includes("/"));
43
+ /**
44
+ * Whether a file satisfies the native `accept` restriction. An extension token matches the file
45
+ * name, a media type matches the reported MIME type, and a `type/*` wildcard matches a whole
46
+ * family of them. A file that reports no MIME type at all can satisfy an extension but can never
47
+ * establish a MIME-only match, which is exactly what the platform does. A malformed `accept`
48
+ * value is ignored entirely rather than taken as a restriction that refuses everything, again
49
+ * mirroring the platform: a typo must not close the field.
50
+ */
51
+ const matchesAccept = (file, accept) => {
52
+ const tokens = acceptTokens(accept);
53
+ if (tokens.length === 0 || !tokens.every(isAcceptToken))
54
+ return true;
55
+ const name = file.name.toLowerCase();
56
+ const type = file.type.toLowerCase();
57
+ return tokens.some((token) => {
58
+ if (token.startsWith("."))
59
+ return name.endsWith(token);
60
+ if (token.endsWith("/*"))
61
+ return type !== "" && type.startsWith(token.slice(0, -1));
62
+ return type !== "" && type === token;
63
+ });
64
+ };
65
+ const countMessage = (file, limit, multiple) => multiple
66
+ ? `${file.name} was not added: at most ${limit} file${limit === 1 ? "" : "s"} can be selected.`
67
+ : `${file.name} was not added: this field holds one file.`;
68
+ /**
69
+ * Applies the whole policy to one batch, from either door.
70
+ *
71
+ * Each file is judged type first, then size, then count — the caller's order of severity — and a
72
+ * refusal never removes a file that was already accepted, so the valid part of a mixed batch
73
+ * still enters. A file the selection already holds is not a failure, it is the same choice
74
+ * arriving twice, so it is neither reported nor counted against the limit.
75
+ *
76
+ * A single-file field starts from an empty selection: the first accepted file replaces what was
77
+ * there and everything after it is a count failure.
78
+ */
79
+ const selectFiles = ({ incoming, current, limits, }) => {
80
+ const maxSize = resolveMaxSize(limits.maxSize);
81
+ const maxFiles = resolveMaxFiles(limits.maxFiles, limits.multiple);
82
+ const files = limits.multiple ? [...current] : [];
83
+ const keys = new Set(current.map(fileKey));
84
+ const rejections = [];
85
+ let accepted = 0;
86
+ for (const file of incoming) {
87
+ if (!matchesAccept(file, limits.accept)) {
88
+ rejections.push({ file, code: "type", message: `${file.name} is not an accepted file type.` });
89
+ continue;
90
+ }
91
+ if (maxSize !== undefined && file.size > maxSize) {
92
+ rejections.push({
93
+ file,
94
+ code: "size",
95
+ message: `${file.name} is larger than the ${formatBytes(maxSize)} limit.`,
96
+ });
97
+ continue;
98
+ }
99
+ const key = fileKey(file);
100
+ if (keys.has(key))
101
+ continue;
102
+ if (maxFiles !== undefined && files.length >= maxFiles) {
103
+ rejections.push({ file, code: "count", message: countMessage(file, maxFiles, limits.multiple) });
104
+ continue;
105
+ }
106
+ keys.add(key);
107
+ files.push(file);
108
+ accepted += 1;
109
+ }
110
+ return { files, rejections, accepted };
111
+ };
112
+
113
+ export { fileKey, formatBytes, matchesAccept, resolveMaxFiles, resolveMaxSize, selectFiles };
114
+ //# sourceMappingURL=fileUpload.rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileUpload.rules.js","sources":["../../../src/components/fileUpload.rules.ts"],"sourcesContent":["/* File selection rules for `FileUpload`.\n\n The native chooser and the drop zone are two doors into one policy: the same accept, the same\n per-file size and the same total count decide both, so a file the picker would refuse cannot\n slip in through a drop. Nothing here reads a browser global — every rule works from the\n metadata a `File` already carries — so the module is safe to evaluate during SSR and can be\n probed without a document.\n\n `FileUpload.tsx` composes the field around these rules; it owns no rule of its own. */\n\nexport type RejectionCode = \"type\" | \"size\" | \"count\";\n\n/** One file a selection operation refused, with the reason it was refused. */\nexport interface FileRejection {\n file: File;\n code: RejectionCode;\n message: string;\n}\n\nexport interface FileSelectionLimits {\n /** The native `accept` value, comma-separated MIME types and extensions. */\n accept?: string;\n /** Per-file byte limit, already normalized. */\n maxSize?: number;\n /** Total file limit, already normalized. */\n maxFiles?: number;\n /** Whether the field holds several files or replaces its selection with one. */\n multiple: boolean;\n}\n\nexport interface FileSelection {\n /** The selection the operation leaves behind. */\n files: File[];\n /** Everything the batch refused, in the order it arrived. */\n rejections: FileRejection[];\n /** How many of the incoming files entered the selection. */\n accepted: number;\n}\n\n/** The identity of one selection entry: the fields that make two `File`s the same choice. */\nexport const fileKey = (file: File) =>\n `${file.name}\\u0000${file.size}\\u0000${file.type}\\u0000${file.lastModified}`;\n\n/** The per-file byte limit as a whole number, or `undefined` when the caller set none. */\nexport const resolveMaxSize = (maxSize?: number) =>\n maxSize !== undefined && Number.isFinite(maxSize) && maxSize >= 0 ? Math.floor(maxSize) : undefined;\n\n/**\n * The total file limit, or `undefined` when the selection is unbounded. A single-file field\n * holds exactly one: the picker is told nothing about it, because that limit is what makes a\n * second file a replacement rather than an addition.\n */\nexport const resolveMaxFiles = (maxFiles: number | undefined, multiple: boolean) => {\n if (!multiple) return 1;\n return maxFiles !== undefined && Number.isFinite(maxFiles) && maxFiles >= 0\n ? Math.floor(maxFiles)\n : undefined;\n};\n\n/** A byte limit as the copy spells it: `2 MB`, never `2097152`. */\nexport const formatBytes = (bytes: number) => {\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n return `${Number.isInteger(value) ? value : value.toFixed(1)} ${units[unit]}`;\n};\n\nconst acceptTokens = (accept?: string) =>\n (accept ?? \"\")\n .split(\",\")\n .map((token) => token.trim().toLowerCase())\n .filter(Boolean);\n\n/** An extension token starts with a dot; anything else has to name a media type. */\nconst isAcceptToken = (token: string) => (token.startsWith(\".\") ? token.length > 1 : token.includes(\"/\"));\n\n/**\n * Whether a file satisfies the native `accept` restriction. An extension token matches the file\n * name, a media type matches the reported MIME type, and a `type/*` wildcard matches a whole\n * family of them. A file that reports no MIME type at all can satisfy an extension but can never\n * establish a MIME-only match, which is exactly what the platform does. A malformed `accept`\n * value is ignored entirely rather than taken as a restriction that refuses everything, again\n * mirroring the platform: a typo must not close the field.\n */\nexport const matchesAccept = (file: File, accept?: string) => {\n const tokens = acceptTokens(accept);\n if (tokens.length === 0 || !tokens.every(isAcceptToken)) return true;\n\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n\n return tokens.some((token) => {\n if (token.startsWith(\".\")) return name.endsWith(token);\n if (token.endsWith(\"/*\")) return type !== \"\" && type.startsWith(token.slice(0, -1));\n return type !== \"\" && type === token;\n });\n};\n\nconst countMessage = (file: File, limit: number, multiple: boolean) =>\n multiple\n ? `${file.name} was not added: at most ${limit} file${limit === 1 ? \"\" : \"s\"} can be selected.`\n : `${file.name} was not added: this field holds one file.`;\n\n/**\n * Applies the whole policy to one batch, from either door.\n *\n * Each file is judged type first, then size, then count — the caller's order of severity — and a\n * refusal never removes a file that was already accepted, so the valid part of a mixed batch\n * still enters. A file the selection already holds is not a failure, it is the same choice\n * arriving twice, so it is neither reported nor counted against the limit.\n *\n * A single-file field starts from an empty selection: the first accepted file replaces what was\n * there and everything after it is a count failure.\n */\nexport const selectFiles = ({\n incoming,\n current,\n limits,\n}: {\n incoming: File[];\n current: File[];\n limits: FileSelectionLimits;\n}): FileSelection => {\n const maxSize = resolveMaxSize(limits.maxSize);\n const maxFiles = resolveMaxFiles(limits.maxFiles, limits.multiple);\n\n const files = limits.multiple ? [...current] : [];\n const keys = new Set(current.map(fileKey));\n const rejections: FileRejection[] = [];\n let accepted = 0;\n\n for (const file of incoming) {\n if (!matchesAccept(file, limits.accept)) {\n rejections.push({ file, code: \"type\", message: `${file.name} is not an accepted file type.` });\n continue;\n }\n\n if (maxSize !== undefined && file.size > maxSize) {\n rejections.push({\n file,\n code: \"size\",\n message: `${file.name} is larger than the ${formatBytes(maxSize)} limit.`,\n });\n continue;\n }\n\n const key = fileKey(file);\n if (keys.has(key)) continue;\n\n if (maxFiles !== undefined && files.length >= maxFiles) {\n rejections.push({ file, code: \"count\", message: countMessage(file, maxFiles, limits.multiple) });\n continue;\n }\n\n keys.add(key);\n files.push(file);\n accepted += 1;\n }\n\n return { files, rejections, accepted };\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * How long a typed sequence stays open for extension.
3
+ *
4
+ * A node label is a word rather than a document, so a search only spans a few characters: long
5
+ * enough that typing quickly reads as one search, short enough that the next deliberate
6
+ * character starts a new one instead of extending a stale one.
7
+ */
8
+ const typeaheadWindow = 500;
9
+ /** Whether a node holds children the tree can show. */
10
+ const isBranch = (item) => { var _a, _b; return ((_b = (_a = item.children) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0; };
11
+ /**
12
+ * The nodes in the order the tree presents them: a document-order walk that descends into an
13
+ * expanded branch and leaves a collapsed one alone.
14
+ *
15
+ * This is the one place that decides what "visible" means, and the keyboard, the typeahead and
16
+ * the roving tab stop all read it rather than walking the tree again — so they can never
17
+ * disagree about which node is where.
18
+ */
19
+ const flattenTree = (items, expanded) => {
20
+ const rows = [];
21
+ const collect = (nodes, depth, ancestors, ancestorDisabled) => {
22
+ var _a, _b;
23
+ for (const item of nodes) {
24
+ const expandedHere = isBranch(item) && expanded.has(item.value);
25
+ rows.push({
26
+ item,
27
+ depth,
28
+ parent: (_a = ancestors[ancestors.length - 1]) !== null && _a !== void 0 ? _a : null,
29
+ ancestors,
30
+ expanded: expandedHere,
31
+ ancestorDisabled,
32
+ });
33
+ if (expandedHere) {
34
+ collect((_b = item.children) !== null && _b !== void 0 ? _b : [], depth + 1, [...ancestors, item.value], ancestorDisabled || item.disabled === true);
35
+ }
36
+ }
37
+ };
38
+ collect(items, 0, [], false);
39
+ return rows;
40
+ };
41
+ /** The nearest of `ancestors`, deepest first, that the tree still shows. */
42
+ const nearestVisible = (ancestors, visible) => {
43
+ for (let index = ancestors.length - 1; index >= 0; index -= 1) {
44
+ if (visible.has(ancestors[index]))
45
+ return ancestors[index];
46
+ }
47
+ return null;
48
+ };
49
+ /**
50
+ * The APG typeahead search: the next *visible* node whose label starts with the characters typed
51
+ * so far, starting after `from` and wrapping at the end of the tree.
52
+ *
53
+ * A search moves focus and nothing else. Selection is the user's decision, so typing a letter can
54
+ * never change what is chosen, and a node that a typeahead lands on is not selected by arriving.
55
+ */
56
+ const matchTypeahead = (rows, pattern, from) => {
57
+ const query = pattern.toLowerCase();
58
+ if (query.length === 0 || rows.length === 0)
59
+ return null;
60
+ const start = rows.findIndex((row) => row.item.value === from);
61
+ for (let step = 1; step <= rows.length; step += 1) {
62
+ const row = rows[(start + step + rows.length) % rows.length];
63
+ if (row.item.label.toLowerCase().startsWith(query))
64
+ return row;
65
+ }
66
+ return null;
67
+ };
68
+ /**
69
+ * Advances the typeahead buffer by one keystroke.
70
+ *
71
+ * Characters typed in quick succession are one search — `d`, `o`, `c` looks for a label starting
72
+ * with "doc" — and the *same* character typed again cycles through the nodes that start with it
73
+ * rather than searching for a two-letter word that almost certainly does not exist. A repeat
74
+ * neither clears nor extends the buffer, so the next different character still widens the
75
+ * single-character search it was part of.
76
+ */
77
+ const typeaheadStep = (buffer, key, now) => {
78
+ if (buffer.text.length === 0 || now - buffer.time >= typeaheadWindow) {
79
+ return { buffer: { text: key, time: now }, pattern: key };
80
+ }
81
+ if (buffer.text === key) {
82
+ return { buffer: { text: buffer.text, time: now }, pattern: key };
83
+ }
84
+ return { buffer: { text: buffer.text + key, time: now }, pattern: buffer.text + key };
85
+ };
86
+
87
+ export { flattenTree, isBranch, matchTypeahead, nearestVisible, typeaheadStep, typeaheadWindow };
88
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sources":["../../../src/components/tree.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/**\n * The finite static tree a `TreeView` presents.\n *\n * A node is data, never markup: the tree renders the roles, the nesting and the indentation\n * itself, so every node in it is reachable the same way whether it is a leaf or a branch.\n * `value` identifies a node throughout the tree and is unique; `label` is what the node is\n * called — its accessible name and what a typeahead search matches.\n */\nexport interface TreeViewItem {\n value: string;\n label: string;\n icon?: ReactNode;\n disabled?: boolean;\n children?: TreeViewItem[];\n}\n\n/** One node, as the tree currently shows it. */\nexport interface TreeRow {\n item: TreeViewItem;\n /** How deep the node sits, counted from the roots. */\n depth: number;\n /** The node that contains it, or `null` at the top level. */\n parent: string | null;\n /** Its ancestors, outermost first. */\n ancestors: string[];\n /** Whether this node's own children are shown. */\n expanded: boolean;\n /**\n * Whether a node above this one is disabled. `aria-disabled` is inherited down a subtree, and\n * this tree deliberately does not mean that: a node below an unavailable branch is its own\n * node, so the row has to be able to say it is not itself unavailable.\n */\n ancestorDisabled: boolean;\n}\n\n/** The characters a typeahead search has accumulated, and when the last one arrived. */\nexport interface TypeaheadBuffer {\n text: string;\n time: number;\n}\n\n/**\n * How long a typed sequence stays open for extension.\n *\n * A node label is a word rather than a document, so a search only spans a few characters: long\n * enough that typing quickly reads as one search, short enough that the next deliberate\n * character starts a new one instead of extending a stale one.\n */\nexport const typeaheadWindow = 500;\n\n/** Whether a node holds children the tree can show. */\nexport const isBranch = (item: TreeViewItem) => (item.children?.length ?? 0) > 0;\n\n/**\n * The nodes in the order the tree presents them: a document-order walk that descends into an\n * expanded branch and leaves a collapsed one alone.\n *\n * This is the one place that decides what \"visible\" means, and the keyboard, the typeahead and\n * the roving tab stop all read it rather than walking the tree again — so they can never\n * disagree about which node is where.\n */\nexport const flattenTree = (items: TreeViewItem[], expanded: ReadonlySet<string>): TreeRow[] => {\n const rows: TreeRow[] = [];\n\n const collect = (\n nodes: TreeViewItem[],\n depth: number,\n ancestors: string[],\n ancestorDisabled: boolean\n ) => {\n for (const item of nodes) {\n const expandedHere = isBranch(item) && expanded.has(item.value);\n rows.push({\n item,\n depth,\n parent: ancestors[ancestors.length - 1] ?? null,\n ancestors,\n expanded: expandedHere,\n ancestorDisabled,\n });\n if (expandedHere) {\n collect(\n item.children ?? [],\n depth + 1,\n [...ancestors, item.value],\n ancestorDisabled || item.disabled === true\n );\n }\n }\n };\n\n collect(items, 0, [], false);\n return rows;\n};\n\n/** The nearest of `ancestors`, deepest first, that the tree still shows. */\nexport const nearestVisible = (\n ancestors: string[],\n visible: ReadonlySet<string>\n): string | null => {\n for (let index = ancestors.length - 1; index >= 0; index -= 1) {\n if (visible.has(ancestors[index])) return ancestors[index];\n }\n return null;\n};\n\n/**\n * The APG typeahead search: the next *visible* node whose label starts with the characters typed\n * so far, starting after `from` and wrapping at the end of the tree.\n *\n * A search moves focus and nothing else. Selection is the user's decision, so typing a letter can\n * never change what is chosen, and a node that a typeahead lands on is not selected by arriving.\n */\nexport const matchTypeahead = (\n rows: TreeRow[],\n pattern: string,\n from: string | null\n): TreeRow | null => {\n const query = pattern.toLowerCase();\n if (query.length === 0 || rows.length === 0) return null;\n\n const start = rows.findIndex((row) => row.item.value === from);\n for (let step = 1; step <= rows.length; step += 1) {\n const row = rows[(start + step + rows.length) % rows.length];\n if (row.item.label.toLowerCase().startsWith(query)) return row;\n }\n return null;\n};\n\n/**\n * Advances the typeahead buffer by one keystroke.\n *\n * Characters typed in quick succession are one search — `d`, `o`, `c` looks for a label starting\n * with \"doc\" — and the *same* character typed again cycles through the nodes that start with it\n * rather than searching for a two-letter word that almost certainly does not exist. A repeat\n * neither clears nor extends the buffer, so the next different character still widens the\n * single-character search it was part of.\n */\nexport const typeaheadStep = (\n buffer: TypeaheadBuffer,\n key: string,\n now: number\n): { buffer: TypeaheadBuffer; pattern: string } => {\n if (buffer.text.length === 0 || now - buffer.time >= typeaheadWindow) {\n return { buffer: { text: key, time: now }, pattern: key };\n }\n\n if (buffer.text === key) {\n return { buffer: { text: buffer.text, time: now }, pattern: key };\n }\n\n return { buffer: { text: buffer.text + key, time: now }, pattern: buffer.text + key };\n};\n"],"names":[],"mappings":"AA2CA;;;;;;AAMG;AACI,MAAM,eAAe,GAAG;AAE/B;AACO,MAAM,QAAQ,GAAG,CAAC,IAAkB,KAAI,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA,CAAC,OAAA,CAAC,CAAA,EAAA,GAAA,MAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,MAAM,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,CAAC,IAAI,CAAC,CAAA,CAAA;AAEhF;;;;;;;AAOG;MACU,WAAW,GAAG,CAAC,KAAqB,EAAE,QAA6B,KAAe;IAC7F,MAAM,IAAI,GAAc,EAAE;IAE1B,MAAM,OAAO,GAAG,CACd,KAAqB,EACrB,KAAa,EACb,SAAmB,EACnB,gBAAyB,KACvB;;AACF,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AACxB,YAAA,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;YAC/D,IAAI,CAAC,IAAI,CAAC;gBACR,IAAI;gBACJ,KAAK;gBACL,MAAM,EAAE,CAAA,EAAA,GAAA,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI;gBAC/C,SAAS;AACT,gBAAA,QAAQ,EAAE,YAAY;gBACtB,gBAAgB;AACjB,aAAA,CAAC;YACF,IAAI,YAAY,EAAE;AAChB,gBAAA,OAAO,CACL,CAAA,EAAA,GAAA,IAAI,CAAC,QAAQ,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,EAAE,EACnB,KAAK,GAAG,CAAC,EACT,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,EAC1B,gBAAgB,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAC3C;YACH;QACF;AACF,IAAA,CAAC;IAED,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC;AAC5B,IAAA,OAAO,IAAI;AACb;AAEA;MACa,cAAc,GAAG,CAC5B,SAAmB,EACnB,OAA4B,KACX;AACjB,IAAA,KAAK,IAAI,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE;QAC7D,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAAE,YAAA,OAAO,SAAS,CAAC,KAAK,CAAC;IAC5D;AACA,IAAA,OAAO,IAAI;AACb;AAEA;;;;;;AAMG;AACI,MAAM,cAAc,GAAG,CAC5B,IAAe,EACf,OAAe,EACf,IAAmB,KACD;AAClB,IAAA,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE;IACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;AAExD,IAAA,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;AAC9D,IAAA,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,EAAE;AACjD,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;AAC5D,QAAA,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC;AAAE,YAAA,OAAO,GAAG;IAChE;AACA,IAAA,OAAO,IAAI;AACb;AAEA;;;;;;;;AAQG;AACI,MAAM,aAAa,GAAG,CAC3B,MAAuB,EACvB,GAAW,EACX,GAAW,KACqC;AAChD,IAAA,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,eAAe,EAAE;AACpE,QAAA,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;IAC3D;AAEA,IAAA,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE;AACvB,QAAA,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE;IACnE;IAEA,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE;AACvF;;"}
@@ -66,6 +66,8 @@ import { motionFeedback, motionDisclose, motionStateLayer } from './ui.motion.js
66
66
  draws it from the wrapping control instead of the track it contains. */
67
67
  const focusRing = "focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-sherick-focus focus-visible:outline-offset-[3px]";
68
68
  const focusRingInset = "focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sherick-focus";
69
+ /* A focus owner that also contains a subtree paints the inset ring on its direct row only. */
70
+ const parentFocusRingInset = "[:focus-visible>&]:ring-2 [:focus-visible>&]:ring-inset [:focus-visible>&]:ring-sherick-focus";
69
71
  /* The ring of a value control that is itself the focusable element: it wears it whenever it
70
72
  holds focus, however it was focused, and whenever the surface it opened is on screen. A select
71
73
  hands the DOM focus to its list while that list is open, so the ring follows the control's
@@ -79,6 +81,8 @@ const focusRingHeld = "focus:outline-none focus:outline focus:outline-2 focus:ou
79
81
  input is `:focus-visible` only for the keyboard, so a slider's handle saves its ring for the
80
82
  keyboard and lets its own engagement answer the pointer. */
81
83
  const focusRingWithin = "has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-sherick-focus has-[:focus-visible]:outline-offset-[3px]";
84
+ /* A file drag marks the same target with the same outline as visible keyboard focus. */
85
+ const focusRingDrag = "data-[dragging]:outline data-[dragging]:outline-2 data-[dragging]:outline-sherick-focus data-[dragging]:outline-offset-[3px]";
82
86
  const groupFocusRing = "group-focus-visible:outline group-focus-visible:outline-2 group-focus-visible:outline-sherick-focus group-focus-visible:outline-offset-[3px]";
83
87
  /* Motion is not owned here. Every physical event — feedback, tactile, arrive, orient,
84
88
  relocate, direct, presence and activity — is a named recipe in `ui.motion.ts`, which
@@ -92,7 +96,7 @@ const groupFocusRing = "group-focus-visible:outline group-focus-visible:outline-
92
96
  touchable; pressing it returns it to the track.
93
97
  - floating: a surface that genuinely sits above the application.
94
98
  - control: the resting half of the tactile pair, for a part the user moves —
95
- a switch thumb, a selected segment.
99
+ a switch thumb, a selected segment or a held option/date selection.
96
100
  - recessed: the other half of that pair: a groove, a track or a well, which is
97
101
  recessed by definition, and a raised control while it is held, which
98
102
  lands at the same depth.
@@ -110,6 +114,9 @@ const elevation = {
110
114
  of the well is deepened and that boundary is what says what it is. See `selectable.markSurface`. */
111
115
  well: "shadow-sherick-well",
112
116
  };
117
+ /* An inset sheet's shaded upper wall, without a lower rim. Kept independent so
118
+ unrelated components do not bundle this recipe. */
119
+ const recessedTop = "shadow-sherick-recessed-top";
113
120
  /* Shape — semantic corner roles, never an arbitrary radius. Softness grows with
114
121
  the size of the object and the emphasis it carries:
115
122
  - control: ordinary controls and dense data regions — fields, rows, options,
@@ -177,8 +184,8 @@ const effectiveDisabled = "data-[disabled]:cursor-not-allowed data-[disabled]:op
177
184
  pressed a raised control returning to the recessed depth of its own track, plus
178
185
  a slight compression; a flat control stays flat and a floating one keeps
179
186
  its elevation
180
- selected a selected tone; depth comes from the component's anatomy — a
181
- segment inside a groove is raised, a row in a list is not
187
+ selected a held choice: selected tone plus control elevation on a segment,
188
+ option/tree row or date surface; never on mere navigation highlight
182
189
  disabled 45% opacity, no pointer affordance, no interactive state at all
183
190
  focus the shared outer focus ring, visible on keyboard focus */
184
191
  const state = {
@@ -218,7 +225,8 @@ const state = {
218
225
  field: {
219
226
  hover: "hover:bg-sherick-surface-high/[0.82]",
220
227
  focus: "focus:bg-sherick-surface-high/[0.9]",
221
- focusWithin: "focus-within:bg-sherick-surface-high/[0.9]",
228
+ /* A composite's engaged tone outranks hover, independent of variant emission order. */
229
+ focusWithin: "[&&]:focus-within:bg-sherick-surface-high/[0.9]",
222
230
  engaged: "bg-sherick-surface-high/[0.9]",
223
231
  errorHover: "hover:bg-sherick-danger/[0.10]",
224
232
  errorFocus: "focus:bg-sherick-danger/[0.13]",
@@ -229,7 +237,7 @@ const state = {
229
237
  invalid: "data-[invalid]:bg-sherick-danger/[0.075]",
230
238
  invalidHover: "data-[invalid]:hover:bg-sherick-danger/[0.10]",
231
239
  invalidEngaged: "data-[invalid]:bg-sherick-danger/[0.13]",
232
- invalidFocusWithin: "data-[invalid]:focus-within:bg-sherick-danger/[0.13]",
240
+ invalidFocusWithin: "[&&]:data-[invalid]:focus-within:bg-sherick-danger/[0.13]",
233
241
  },
234
242
  };
235
243
  /* State layers.
@@ -308,6 +316,8 @@ const hitArea = "relative after:pointer-events-auto after:absolute after:inset-[
308
316
  should sit back from the surface around them.
309
317
  - matte: a matte surface that separates from the canvas by tone alone.
310
318
  - matteHigh: the second matte step, for nesting inside another matte surface.
319
+ - matteInset: a passive sheet tucked under a stronger control; subdued without
320
+ blending into the canvas or needing a drawn rim.
311
321
  - control: the fill every text control shares, plus its placeholder tone.
312
322
  - handle: the fill of a small part the user has to find — a value control's handle.
313
323
  Surface steps sit within a few percent of their neighbours, so this role
@@ -355,6 +365,10 @@ const material = {
355
365
  acrylicDense: "bg-sherick-surface-float/[var(--sui-glass-dense-fill)] bg-[image:var(--sui-glass-gradient-dense)] text-sherick-ink backdrop-blur-[var(--sui-glass-dense-blur)] backdrop-saturate-[var(--sui-glass-dense-saturation)] backdrop-brightness-[var(--sui-glass-dense-brightness)]",
356
366
  acrylicHero: "bg-sherick-surface-overlay/[var(--sui-overlay-fill)] bg-[image:var(--sui-glass-hero-gradient)] text-sherick-ink backdrop-blur-[var(--sui-glass-hero-blur)] backdrop-saturate-[var(--sui-glass-hero-saturation)] backdrop-brightness-[var(--sui-glass-hero-brightness)]",
357
367
  };
368
+ /* Dark neutral behind native video, including poster loading and letterboxing. */
369
+ const mediaCanvas = "bg-sherick-media";
370
+ /* A passive sheet tucked below a stronger control; independently tree-shakeable. */
371
+ const matteInset = "bg-sherick-surface-high/[0.35] text-sherick-ink";
358
372
  /* Density — three control steps plus the accessible hit target.
359
373
  Density owns height and the type step, so controls of one density share a
360
374
  rhythm. A component's anatomy owns its padding: a button's inline padding is
@@ -466,8 +480,9 @@ const detail = {
466
480
  The sheet never exceeds what the viewport leaves it, and scrolls inside itself rather than
467
481
  growing. Its width is the list's own decision: a control's list is never narrower than the
468
482
  control it came from and grows to fit its own content until the viewport clamp, and a command
469
- list only ever grows to its content. A row is flat — depth never announces a state —
470
- and one row step of `stateLayer.quiet` plus `stateLayer.activeRow` carries both its hover and
483
+ list only ever grows to its content. Unselected rows and commands stay flat; held options
484
+ compose `tone.selected` with `elevation.control`. One row step of `stateLayer.quiet` plus
485
+ `stateLayer.activeRow` carries both its hover and
471
486
  its keyboard highlight, so an option and a command are highlighted by the same tone at the
472
487
  same strength. Keyboard focus adds the shared inset ring independently of that highlight.
473
488
  Selection is a tint of the sheet through `tone.selected` and never the opaque
@@ -649,5 +664,5 @@ const recipeAlphas = {
649
664
  the input itself keeps its native single-line scrolling behavior. */
650
665
  const fieldLayout = "flex min-w-0 w-full flex-col [overflow-wrap:anywhere]";
651
666
 
652
- export { density, detail, disclosure, edge, edgeTone, elevation, fieldLayout, focusRing, focusRingHeld, focusRingInset, focusRingWithin, groupFocusRing, hitArea, list, material, overlay, recipeAlphas, selectable, shape, stacking, state, stateLayer, text, tone };
667
+ export { density, detail, disclosure, edge, edgeTone, elevation, fieldLayout, focusRing, focusRingDrag, focusRingHeld, focusRingInset, focusRingWithin, groupFocusRing, hitArea, list, material, matteInset, mediaCanvas, overlay, parentFocusRingInset, recessedTop, recipeAlphas, selectable, shape, stacking, state, stateLayer, text, tone };
653
668
  //# sourceMappingURL=ui.common.js.map