@dashforge/tw 0.8.1-beta → 0.9.1-beta

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 (204) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/COVERAGE.md +63 -0
  3. package/LICENSE +21 -0
  4. package/PERFORMANCE.md +59 -0
  5. package/REFINEMENT-NOTES.md +50 -0
  6. package/dist/index.esm.js +806 -134
  7. package/dist/src/components/Calendar/Calendar.d.ts +14 -0
  8. package/dist/src/components/Calendar/Calendar.d.ts.map +1 -0
  9. package/dist/src/components/Calendar/calendar.types.d.ts +72 -0
  10. package/dist/src/components/Calendar/calendar.types.d.ts.map +1 -0
  11. package/dist/src/components/Calendar/calendar.variants.d.ts +99 -0
  12. package/dist/src/components/Calendar/calendar.variants.d.ts.map +1 -0
  13. package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -1
  14. package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts.map +1 -1
  15. package/dist/src/components/DataGrid/resize/useColumnResize.d.ts +7 -0
  16. package/dist/src/components/DataGrid/resize/useColumnResize.d.ts.map +1 -1
  17. package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts +6 -2
  18. package/dist/src/components/DataGrid/visibility/ColumnVisibilityMenu.d.ts.map +1 -1
  19. package/dist/src/components/DatePicker/DatePicker.d.ts +14 -0
  20. package/dist/src/components/DatePicker/DatePicker.d.ts.map +1 -0
  21. package/dist/src/components/DatePicker/datePicker.types.d.ts +88 -0
  22. package/dist/src/components/DatePicker/datePicker.types.d.ts.map +1 -0
  23. package/dist/src/components/DatePicker/datePicker.variants.d.ts +97 -0
  24. package/dist/src/components/DatePicker/datePicker.variants.d.ts.map +1 -0
  25. package/dist/src/components/Dialog/Dialog.d.ts.map +1 -1
  26. package/dist/src/components/Table/Table.d.ts.map +1 -1
  27. package/dist/src/components/Table/cells/RenderChip.d.ts.map +1 -1
  28. package/dist/src/components/Table/table.types.d.ts +28 -3
  29. package/dist/src/components/Table/table.types.d.ts.map +1 -1
  30. package/dist/src/components/Typography/typography.variants.d.ts.map +1 -1
  31. package/dist/src/index.d.ts +6 -0
  32. package/dist/src/index.d.ts.map +1 -1
  33. package/eslint.config.mjs +16 -1
  34. package/package.json +10 -6
  35. package/src/components/Breadcrumbs/Breadcrumbs.perf.test.tsx +5 -2
  36. package/src/components/Calendar/Calendar.test.tsx +75 -0
  37. package/src/components/Calendar/Calendar.tsx +264 -0
  38. package/src/components/Calendar/calendar.types.ts +65 -0
  39. package/src/components/Calendar/calendar.variants.ts +64 -0
  40. package/src/components/DataGrid/DataGrid.perf.test.tsx +114 -0
  41. package/src/components/DataGrid/DataGrid.test.tsx +240 -0
  42. package/src/components/DataGrid/DataGrid.tsx +104 -16
  43. package/src/components/DataGrid/filters/ColumnFilters.test.tsx +330 -0
  44. package/src/components/DataGrid/filters/ColumnFilters.tsx +26 -2
  45. package/src/components/DataGrid/reorder/useColumnReorder.test.ts +95 -3
  46. package/src/components/DataGrid/resize/useColumnResize.test.ts +144 -0
  47. package/src/components/DataGrid/resize/useColumnResize.ts +30 -1
  48. package/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx +42 -25
  49. package/src/components/DatePicker/DatePicker.test.tsx +76 -0
  50. package/src/components/DatePicker/DatePicker.tsx +305 -0
  51. package/src/components/DatePicker/datePicker.types.ts +77 -0
  52. package/src/components/DatePicker/datePicker.variants.ts +50 -0
  53. package/src/components/Dialog/Dialog.tsx +9 -0
  54. package/src/components/Table/Table.perf.test.tsx +81 -0
  55. package/src/components/Table/Table.test.tsx +72 -1
  56. package/src/components/Table/Table.tsx +40 -4
  57. package/src/components/Table/cells/RenderChip.tsx +6 -9
  58. package/src/components/Table/cells/cells.test.tsx +247 -0
  59. package/src/components/Table/table.types.ts +28 -3
  60. package/src/components/Typography/Typography.test.tsx +15 -0
  61. package/src/components/Typography/typography.variants.ts +14 -1
  62. package/src/index.ts +21 -1
  63. package/test-output/vitest/coverage/base.css +224 -0
  64. package/test-output/vitest/coverage/block-navigation.js +87 -0
  65. package/test-output/vitest/coverage/clover.xml +2418 -0
  66. package/test-output/vitest/coverage/coverage-final.json +89 -0
  67. package/test-output/vitest/coverage/favicon.png +0 -0
  68. package/test-output/vitest/coverage/index.html +746 -0
  69. package/test-output/vitest/coverage/prettify.css +1 -0
  70. package/test-output/vitest/coverage/prettify.js +2 -0
  71. package/test-output/vitest/coverage/sort-arrow-sprite.png +0 -0
  72. package/test-output/vitest/coverage/sorter.js +210 -0
  73. package/test-output/vitest/coverage/src/components/Accordion/Accordion.tsx.html +376 -0
  74. package/test-output/vitest/coverage/src/components/Accordion/accordion.variants.ts.html +181 -0
  75. package/test-output/vitest/coverage/src/components/Accordion/index.html +131 -0
  76. package/test-output/vitest/coverage/src/components/AppShell/AppShell.tsx.html +802 -0
  77. package/test-output/vitest/coverage/src/components/AppShell/appShell.variants.ts.html +247 -0
  78. package/test-output/vitest/coverage/src/components/AppShell/index.html +131 -0
  79. package/test-output/vitest/coverage/src/components/AspectRatio/AspectRatio.tsx.html +304 -0
  80. package/test-output/vitest/coverage/src/components/AspectRatio/index.html +116 -0
  81. package/test-output/vitest/coverage/src/components/Autocomplete/Autocomplete.tsx.html +3292 -0
  82. package/test-output/vitest/coverage/src/components/Autocomplete/autocomplete.variants.ts.html +508 -0
  83. package/test-output/vitest/coverage/src/components/Autocomplete/index.html +131 -0
  84. package/test-output/vitest/coverage/src/components/Box/Box.tsx.html +334 -0
  85. package/test-output/vitest/coverage/src/components/Box/box.variants.ts.html +775 -0
  86. package/test-output/vitest/coverage/src/components/Box/index.html +131 -0
  87. package/test-output/vitest/coverage/src/components/Breadcrumbs/Breadcrumbs.tsx.html +694 -0
  88. package/test-output/vitest/coverage/src/components/Breadcrumbs/breadcrumbs.variants.ts.html +247 -0
  89. package/test-output/vitest/coverage/src/components/Breadcrumbs/index.html +131 -0
  90. package/test-output/vitest/coverage/src/components/Button/Button.tsx.html +535 -0
  91. package/test-output/vitest/coverage/src/components/Button/button.variants.ts.html +562 -0
  92. package/test-output/vitest/coverage/src/components/Button/index.html +131 -0
  93. package/test-output/vitest/coverage/src/components/Checkbox/Checkbox.tsx.html +943 -0
  94. package/test-output/vitest/coverage/src/components/Checkbox/checkbox.variants.ts.html +289 -0
  95. package/test-output/vitest/coverage/src/components/Checkbox/index.html +131 -0
  96. package/test-output/vitest/coverage/src/components/ConfirmDialog/ConfirmDialog.tsx.html +841 -0
  97. package/test-output/vitest/coverage/src/components/ConfirmDialog/confirmDialog.variants.ts.html +313 -0
  98. package/test-output/vitest/coverage/src/components/ConfirmDialog/index.html +131 -0
  99. package/test-output/vitest/coverage/src/components/Container/Container.tsx.html +307 -0
  100. package/test-output/vitest/coverage/src/components/Container/container.variants.ts.html +328 -0
  101. package/test-output/vitest/coverage/src/components/Container/index.html +131 -0
  102. package/test-output/vitest/coverage/src/components/DataGrid/DataGrid.tsx.html +3937 -0
  103. package/test-output/vitest/coverage/src/components/DataGrid/dataGrid.variants.ts.html +541 -0
  104. package/test-output/vitest/coverage/src/components/DataGrid/filters/ColumnFilters.tsx.html +1453 -0
  105. package/test-output/vitest/coverage/src/components/DataGrid/filters/index.html +116 -0
  106. package/test-output/vitest/coverage/src/components/DataGrid/index.html +131 -0
  107. package/test-output/vitest/coverage/src/components/DataGrid/reorder/index.html +116 -0
  108. package/test-output/vitest/coverage/src/components/DataGrid/reorder/useColumnReorder.ts.html +517 -0
  109. package/test-output/vitest/coverage/src/components/DataGrid/resize/index.html +116 -0
  110. package/test-output/vitest/coverage/src/components/DataGrid/resize/useColumnResize.ts.html +490 -0
  111. package/test-output/vitest/coverage/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx.html +592 -0
  112. package/test-output/vitest/coverage/src/components/DataGrid/visibility/index.html +116 -0
  113. package/test-output/vitest/coverage/src/components/DateTimePicker/DateTimePicker.tsx.html +1042 -0
  114. package/test-output/vitest/coverage/src/components/DateTimePicker/dateTimePicker.variants.ts.html +337 -0
  115. package/test-output/vitest/coverage/src/components/DateTimePicker/index.html +131 -0
  116. package/test-output/vitest/coverage/src/components/Dialog/Dialog.tsx.html +391 -0
  117. package/test-output/vitest/coverage/src/components/Dialog/dialog.variants.ts.html +283 -0
  118. package/test-output/vitest/coverage/src/components/Dialog/index.html +131 -0
  119. package/test-output/vitest/coverage/src/components/Divider/Divider.tsx.html +505 -0
  120. package/test-output/vitest/coverage/src/components/Divider/divider.variants.ts.html +433 -0
  121. package/test-output/vitest/coverage/src/components/Divider/index.html +131 -0
  122. package/test-output/vitest/coverage/src/components/Grid/Grid.tsx.html +529 -0
  123. package/test-output/vitest/coverage/src/components/Grid/grid.variants.ts.html +484 -0
  124. package/test-output/vitest/coverage/src/components/Grid/index.html +131 -0
  125. package/test-output/vitest/coverage/src/components/LeftNav/LeftNav.tsx.html +1075 -0
  126. package/test-output/vitest/coverage/src/components/LeftNav/index.html +131 -0
  127. package/test-output/vitest/coverage/src/components/LeftNav/leftNav.variants.ts.html +406 -0
  128. package/test-output/vitest/coverage/src/components/NumberField/NumberField.tsx.html +1048 -0
  129. package/test-output/vitest/coverage/src/components/NumberField/index.html +131 -0
  130. package/test-output/vitest/coverage/src/components/NumberField/numberField.variants.ts.html +367 -0
  131. package/test-output/vitest/coverage/src/components/OTPField/OTPField.tsx.html +835 -0
  132. package/test-output/vitest/coverage/src/components/OTPField/index.html +131 -0
  133. package/test-output/vitest/coverage/src/components/OTPField/otpField.variants.ts.html +301 -0
  134. package/test-output/vitest/coverage/src/components/Pagination/Pagination.tsx.html +916 -0
  135. package/test-output/vitest/coverage/src/components/Pagination/index.html +146 -0
  136. package/test-output/vitest/coverage/src/components/Pagination/pagination.helpers.ts.html +391 -0
  137. package/test-output/vitest/coverage/src/components/Pagination/pagination.variants.ts.html +436 -0
  138. package/test-output/vitest/coverage/src/components/Popover/Popover.tsx.html +250 -0
  139. package/test-output/vitest/coverage/src/components/Popover/index.html +131 -0
  140. package/test-output/vitest/coverage/src/components/Popover/popover.variants.ts.html +157 -0
  141. package/test-output/vitest/coverage/src/components/RadioGroup/RadioGroup.tsx.html +934 -0
  142. package/test-output/vitest/coverage/src/components/RadioGroup/index.html +131 -0
  143. package/test-output/vitest/coverage/src/components/RadioGroup/radioGroup.variants.ts.html +346 -0
  144. package/test-output/vitest/coverage/src/components/Skeleton/Skeleton.tsx.html +232 -0
  145. package/test-output/vitest/coverage/src/components/Skeleton/index.html +131 -0
  146. package/test-output/vitest/coverage/src/components/Skeleton/skeleton.variants.ts.html +346 -0
  147. package/test-output/vitest/coverage/src/components/Snackbar/Snackbar.tsx.html +979 -0
  148. package/test-output/vitest/coverage/src/components/Snackbar/index.html +131 -0
  149. package/test-output/vitest/coverage/src/components/Snackbar/snackbar.variants.ts.html +307 -0
  150. package/test-output/vitest/coverage/src/components/Stack/Stack.tsx.html +430 -0
  151. package/test-output/vitest/coverage/src/components/Stack/index.html +131 -0
  152. package/test-output/vitest/coverage/src/components/Stack/stack.variants.ts.html +325 -0
  153. package/test-output/vitest/coverage/src/components/Switch/Switch.tsx.html +559 -0
  154. package/test-output/vitest/coverage/src/components/Switch/index.html +131 -0
  155. package/test-output/vitest/coverage/src/components/Switch/switch.variants.ts.html +295 -0
  156. package/test-output/vitest/coverage/src/components/Table/Table.tsx.html +2677 -0
  157. package/test-output/vitest/coverage/src/components/Table/cells/RenderButton.tsx.html +154 -0
  158. package/test-output/vitest/coverage/src/components/Table/cells/RenderChip.tsx.html +385 -0
  159. package/test-output/vitest/coverage/src/components/Table/cells/RenderText.tsx.html +196 -0
  160. package/test-output/vitest/coverage/src/components/Table/cells/RenderTwoLine.tsx.html +187 -0
  161. package/test-output/vitest/coverage/src/components/Table/cells/RowActionsMenu.tsx.html +526 -0
  162. package/test-output/vitest/coverage/src/components/Table/cells/index.html +191 -0
  163. package/test-output/vitest/coverage/src/components/Table/cells/index.ts.html +127 -0
  164. package/test-output/vitest/coverage/src/components/Table/index.html +131 -0
  165. package/test-output/vitest/coverage/src/components/Table/table.variants.ts.html +706 -0
  166. package/test-output/vitest/coverage/src/components/Tabs/Tabs.tsx.html +283 -0
  167. package/test-output/vitest/coverage/src/components/Tabs/index.html +131 -0
  168. package/test-output/vitest/coverage/src/components/Tabs/tabs.variants.ts.html +292 -0
  169. package/test-output/vitest/coverage/src/components/TextField/TextField.tsx.html +820 -0
  170. package/test-output/vitest/coverage/src/components/TextField/index.html +131 -0
  171. package/test-output/vitest/coverage/src/components/TextField/textField.variants.ts.html +400 -0
  172. package/test-output/vitest/coverage/src/components/Textarea/Textarea.tsx.html +655 -0
  173. package/test-output/vitest/coverage/src/components/Textarea/index.html +131 -0
  174. package/test-output/vitest/coverage/src/components/Textarea/textarea.variants.ts.html +340 -0
  175. package/test-output/vitest/coverage/src/components/Tooltip/Tooltip.tsx.html +295 -0
  176. package/test-output/vitest/coverage/src/components/Tooltip/index.html +131 -0
  177. package/test-output/vitest/coverage/src/components/Tooltip/tooltip.variants.ts.html +169 -0
  178. package/test-output/vitest/coverage/src/components/TopBar/TopBar.tsx.html +283 -0
  179. package/test-output/vitest/coverage/src/components/TopBar/index.html +131 -0
  180. package/test-output/vitest/coverage/src/components/TopBar/topBar.variants.ts.html +199 -0
  181. package/test-output/vitest/coverage/src/components/Typography/Typography.tsx.html +421 -0
  182. package/test-output/vitest/coverage/src/components/Typography/index.html +131 -0
  183. package/test-output/vitest/coverage/src/components/Typography/typography.variants.ts.html +436 -0
  184. package/test-output/vitest/coverage/src/components/VisuallyHidden/VisuallyHidden.tsx.html +262 -0
  185. package/test-output/vitest/coverage/src/components/VisuallyHidden/index.html +116 -0
  186. package/test-output/vitest/coverage/src/components/_shared/data/getNestedValue.ts.html +181 -0
  187. package/test-output/vitest/coverage/src/components/_shared/data/index.html +236 -0
  188. package/test-output/vitest/coverage/src/components/_shared/data/useColumnAutoDetect.ts.html +385 -0
  189. package/test-output/vitest/coverage/src/components/_shared/data/useControllableState.ts.html +229 -0
  190. package/test-output/vitest/coverage/src/components/_shared/data/useDebouncedValue.ts.html +157 -0
  191. package/test-output/vitest/coverage/src/components/_shared/data/useTableFilter.ts.html +439 -0
  192. package/test-output/vitest/coverage/src/components/_shared/data/useTableSearch.ts.html +283 -0
  193. package/test-output/vitest/coverage/src/components/_shared/data/useTableSelection.ts.html +385 -0
  194. package/test-output/vitest/coverage/src/components/_shared/data/useTableSort.ts.html +508 -0
  195. package/test-output/vitest/coverage/src/components/_shared/data/useVirtualizer.ts.html +682 -0
  196. package/test-output/vitest/coverage/src/components/_shared/index.html +116 -0
  197. package/test-output/vitest/coverage/src/components/_shared/resolveValidationState.ts.html +265 -0
  198. package/test-output/vitest/coverage/src/hooks/index.html +116 -0
  199. package/test-output/vitest/coverage/src/hooks/useAccessState.ts.html +328 -0
  200. package/test-output/vitest/coverage/src/index.html +116 -0
  201. package/test-output/vitest/coverage/src/index.ts.html +1153 -0
  202. package/test-output/vitest/coverage/src/utils/cn.ts.html +172 -0
  203. package/test-output/vitest/coverage/src/utils/index.html +116 -0
  204. package/tsconfig.lib.json +7 -4
package/dist/index.esm.js CHANGED
@@ -11,10 +11,11 @@ import { useDashFieldMeta } from '@dashforge/forms';
11
11
  import * as RadixCheckbox from '@radix-ui/react-checkbox';
12
12
  import * as RadixSwitch from '@radix-ui/react-switch';
13
13
  import * as RadixRadioGroup from '@radix-ui/react-radio-group';
14
+ import { useCalendar, resolveCalendarKey, formatLongDate, parseISODate } from '@dashforge/calendar-core';
15
+ import * as RadixPopover from '@radix-ui/react-popover';
14
16
  import * as RadixDialog from '@radix-ui/react-dialog';
15
17
  import * as RadixTabs from '@radix-ui/react-tabs';
16
18
  import * as RadixTooltip from '@radix-ui/react-tooltip';
17
- import * as RadixPopover from '@radix-ui/react-popover';
18
19
  import * as RadixAccordion from '@radix-ui/react-accordion';
19
20
 
20
21
  function _extends() {
@@ -3754,6 +3755,632 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3754
3755
  });
3755
3756
  }
3756
3757
 
3758
+ /**
3759
+ * Structural slots for `<Calendar>`.
3760
+ *
3761
+ * Theme identity: every class is a bare neutral / brand token-backed
3762
+ * Tailwind class. The `dashforgePreset()` CSS-variable swap auto-inverts
3763
+ * the neutral palette, so there are NO `dark:` variants here (a `dark:` on
3764
+ * a neutral class would double-invert — see `_shared/themeIdentity.test.ts`).
3765
+ */ const calendarVariants = tv({
3766
+ slots: {
3767
+ root: 'inline-flex w-fit flex-col gap-2 rounded-lg border border-neutral-200 bg-neutral-50 p-3 select-none',
3768
+ header: 'flex items-center justify-between gap-1',
3769
+ navButton: 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-[1rem] text-neutral-600 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 disabled:pointer-events-none disabled:opacity-40',
3770
+ monthLabel: 'flex-1 text-center text-sm font-semibold text-neutral-900',
3771
+ grid: 'flex flex-col gap-1 focus:outline-none',
3772
+ weekdayRow: 'grid grid-cols-7 gap-1',
3773
+ weekday: 'flex h-8 items-center justify-center text-xs font-semibold text-neutral-500',
3774
+ weekRow: 'grid grid-cols-7 gap-1'
3775
+ },
3776
+ variants: {
3777
+ disabled: {
3778
+ true: {
3779
+ root: 'opacity-60'
3780
+ }
3781
+ }
3782
+ },
3783
+ defaultVariants: {
3784
+ disabled: false
3785
+ }
3786
+ });
3787
+ /**
3788
+ * Per-day-cell recipe. Each of the 42 grid cells is rendered with its own
3789
+ * state (`siblingMonth` / `today` / `selected` / `disabled`).
3790
+ */ const calendarDayVariants = tv({
3791
+ base: 'flex h-9 w-9 items-center justify-center rounded-md text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500',
3792
+ variants: {
3793
+ siblingMonth: {
3794
+ true: 'text-neutral-400'
3795
+ },
3796
+ today: {
3797
+ true: 'font-bold ring-1 ring-inset ring-primary-400'
3798
+ },
3799
+ selected: {
3800
+ true: 'bg-primary-600 text-white hover:bg-primary-600'
3801
+ },
3802
+ disabled: {
3803
+ true: 'cursor-default text-neutral-300 opacity-60 hover:bg-transparent'
3804
+ }
3805
+ },
3806
+ compoundVariants: [
3807
+ // A selected cell drops the "today" ring — the solid fill is enough.
3808
+ {
3809
+ selected: true,
3810
+ today: true,
3811
+ class: 'ring-0'
3812
+ }
3813
+ ],
3814
+ defaultVariants: {
3815
+ siblingMonth: false,
3816
+ today: false,
3817
+ selected: false,
3818
+ disabled: false
3819
+ }
3820
+ });
3821
+
3822
+ // Inline 16×16 stroke chevrons — no icon dependency (tw convention).
3823
+ function ChevronLeftIcon() {
3824
+ return jsx("svg", {
3825
+ width: "1em",
3826
+ height: "1em",
3827
+ viewBox: "0 0 16 16",
3828
+ fill: "none",
3829
+ "aria-hidden": "true",
3830
+ children: jsx("path", {
3831
+ d: "M10 4 6 8l4 4",
3832
+ stroke: "currentColor",
3833
+ strokeWidth: "1.5",
3834
+ strokeLinecap: "round",
3835
+ strokeLinejoin: "round"
3836
+ })
3837
+ });
3838
+ }
3839
+ function ChevronRightIcon$1() {
3840
+ return jsx("svg", {
3841
+ width: "1em",
3842
+ height: "1em",
3843
+ viewBox: "0 0 16 16",
3844
+ fill: "none",
3845
+ "aria-hidden": "true",
3846
+ children: jsx("path", {
3847
+ d: "m6 4 4 4-4 4",
3848
+ stroke: "currentColor",
3849
+ strokeWidth: "1.5",
3850
+ strokeLinecap: "round",
3851
+ strokeLinejoin: "round"
3852
+ })
3853
+ });
3854
+ }
3855
+ /**
3856
+ * `<Calendar>` — a standalone, inline month-grid date picker.
3857
+ *
3858
+ * The Tailwind presentation layer over the headless `useCalendar` engine
3859
+ * from `@dashforge/calendar-core` (the same engine the MUI `@dashforge/ui`
3860
+ * calendar consumes — only the skin differs). Implements the WCAG grid
3861
+ * pattern with roving tab-index focus.
3862
+ *
3863
+ * It is a primitive: no form-bridge / RBAC integration — use `<DatePicker>`
3864
+ * for a form-bound date field.
3865
+ */ function Calendar(props) {
3866
+ var _slotProps_root, _slotProps_header, _slotProps_grid;
3867
+ const { value, defaultValue, onChange, month, year, defaultMonth, defaultYear, onMonthChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, today, disabled = false, autoFocus = false, sx, slotProps, testId, 'aria-label': ariaLabel = 'Calendar' } = props;
3868
+ const isValueControlled = value !== undefined;
3869
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
3870
+ const selectedValue = isValueControlled ? value : internalValue;
3871
+ const handleSelect = useCallback((iso)=>{
3872
+ if (!isValueControlled) {
3873
+ setInternalValue(iso);
3874
+ }
3875
+ onChange == null ? void 0 : onChange(iso);
3876
+ }, [
3877
+ isValueControlled,
3878
+ onChange
3879
+ ]);
3880
+ const calendar = useCalendar({
3881
+ selected: selectedValue,
3882
+ month,
3883
+ year,
3884
+ defaultMonth,
3885
+ defaultYear,
3886
+ onMonthChange,
3887
+ minDate,
3888
+ maxDate,
3889
+ disabledDates,
3890
+ isDateDisabled,
3891
+ weekStartDay,
3892
+ locale,
3893
+ today,
3894
+ onSelect: handleSelect
3895
+ });
3896
+ // Roving focus: the active day cell holds DOM focus; it is moved only
3897
+ // after a keyboard interaction (or on mount when `autoFocus`). `focus`
3898
+ // uses `preventScroll` so opening the calendar inside a popup never
3899
+ // scroll-jumps the page before the popup is positioned.
3900
+ const focusedCellRef = useRef(null);
3901
+ const shouldFocusRef = useRef(autoFocus);
3902
+ useEffect(()=>{
3903
+ if (shouldFocusRef.current && focusedCellRef.current) {
3904
+ focusedCellRef.current.focus({
3905
+ preventScroll: true
3906
+ });
3907
+ shouldFocusRef.current = false;
3908
+ }
3909
+ }, [
3910
+ calendar.focusedDate
3911
+ ]);
3912
+ const handleKeyDown = useCallback((event)=>{
3913
+ if (disabled) {
3914
+ return;
3915
+ }
3916
+ const action = resolveCalendarKey(event.key);
3917
+ if (action === null) {
3918
+ return;
3919
+ }
3920
+ event.preventDefault();
3921
+ shouldFocusRef.current = true;
3922
+ switch(action){
3923
+ case 'select':
3924
+ calendar.selectDate(calendar.focusedDate);
3925
+ break;
3926
+ case 'monthForward':
3927
+ calendar.goToNextMonth();
3928
+ break;
3929
+ case 'monthBackward':
3930
+ calendar.goToPreviousMonth();
3931
+ break;
3932
+ default:
3933
+ calendar.navigate(action);
3934
+ }
3935
+ }, [
3936
+ disabled,
3937
+ calendar
3938
+ ]);
3939
+ const resolvedLocale = locale != null ? locale : 'en-US';
3940
+ const v = calendarVariants({
3941
+ disabled
3942
+ });
3943
+ return jsxs("div", {
3944
+ "data-testid": testId,
3945
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3946
+ children: [
3947
+ jsxs("div", {
3948
+ className: cn(v.header(), slotProps == null ? void 0 : (_slotProps_header = slotProps.header) == null ? void 0 : _slotProps_header.className),
3949
+ children: [
3950
+ jsx("button", {
3951
+ type: "button",
3952
+ className: v.navButton(),
3953
+ disabled: disabled,
3954
+ "aria-label": "Previous month",
3955
+ onClick: ()=>{
3956
+ calendar.goToPreviousMonth();
3957
+ },
3958
+ children: jsx(ChevronLeftIcon, {})
3959
+ }),
3960
+ jsx("span", {
3961
+ "aria-live": "polite",
3962
+ className: v.monthLabel(),
3963
+ children: calendar.monthLabel
3964
+ }),
3965
+ jsx("button", {
3966
+ type: "button",
3967
+ className: v.navButton(),
3968
+ disabled: disabled,
3969
+ "aria-label": "Next month",
3970
+ onClick: ()=>{
3971
+ calendar.goToNextMonth();
3972
+ },
3973
+ children: jsx(ChevronRightIcon$1, {})
3974
+ })
3975
+ ]
3976
+ }),
3977
+ jsxs("div", {
3978
+ role: "grid",
3979
+ "aria-label": ariaLabel,
3980
+ onKeyDown: handleKeyDown,
3981
+ className: cn(v.grid(), slotProps == null ? void 0 : (_slotProps_grid = slotProps.grid) == null ? void 0 : _slotProps_grid.className),
3982
+ children: [
3983
+ jsx("div", {
3984
+ role: "row",
3985
+ className: v.weekdayRow(),
3986
+ children: calendar.weekdayNames.map((dayName, index)=>jsx("span", {
3987
+ role: "columnheader",
3988
+ className: v.weekday(),
3989
+ children: dayName
3990
+ }, `weekday-${String(index)}`))
3991
+ }),
3992
+ calendar.weeks.map((week)=>jsx("div", {
3993
+ role: "row",
3994
+ className: v.weekRow(),
3995
+ children: week.days.map((day)=>{
3996
+ var _slotProps_day;
3997
+ const isActiveCell = day.iso === calendar.focusedDate;
3998
+ return jsx("span", {
3999
+ role: "gridcell",
4000
+ className: "flex",
4001
+ children: jsx("button", {
4002
+ type: "button",
4003
+ ref: isActiveCell ? focusedCellRef : null,
4004
+ tabIndex: isActiveCell ? 0 : -1,
4005
+ disabled: disabled,
4006
+ "aria-label": formatLongDate({
4007
+ year: day.year,
4008
+ month: day.month,
4009
+ day: day.day
4010
+ }, resolvedLocale),
4011
+ "aria-current": day.isToday ? 'date' : undefined,
4012
+ "aria-pressed": day.isSelected,
4013
+ "aria-disabled": day.isDisabled,
4014
+ onClick: ()=>{
4015
+ calendar.selectDate(day.iso);
4016
+ },
4017
+ className: cn(calendarDayVariants({
4018
+ siblingMonth: day.isSiblingMonth,
4019
+ today: day.isToday,
4020
+ selected: day.isSelected,
4021
+ disabled: day.isDisabled
4022
+ }), slotProps == null ? void 0 : (_slotProps_day = slotProps.day) == null ? void 0 : _slotProps_day.className),
4023
+ children: day.day
4024
+ })
4025
+ }, day.iso);
4026
+ })
4027
+ }, week.key))
4028
+ ]
4029
+ })
4030
+ ]
4031
+ });
4032
+ }
4033
+
4034
+ const popoverVariants = tv({
4035
+ slots: {
4036
+ content: [
4037
+ // Neutral border auto-inverts via CSS-var swap.
4038
+ 'z-50 rounded-md border border-neutral-200',
4039
+ // bg-white hardcoded (no auto-invert) so `dark:` is required.
4040
+ // Target `dark:bg-neutral-100` resolves to #171717 in dark mode
4041
+ // (one elevation tier above the page surface).
4042
+ 'bg-white dark:bg-neutral-100 p-4 shadow-lg',
4043
+ // Text auto-inverts.
4044
+ 'text-sm text-neutral-900',
4045
+ 'focus:outline-none',
4046
+ 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
4047
+ 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
4048
+ 'motion-reduce:transition-none motion-reduce:duration-0'
4049
+ ],
4050
+ // Arrow follows content bg: `fill-white` in light + matching dark target.
4051
+ arrow: 'fill-white dark:fill-neutral-100'
4052
+ }
4053
+ });
4054
+
4055
+ /**
4056
+ * Dashforge TW `<Popover>` — clickable floating content panel.
4057
+ *
4058
+ * Built on `@radix-ui/react-popover`:
4059
+ * - Focus trap inside the popover (Tab cycles within).
4060
+ * - Outside-click + Escape dismiss.
4061
+ * - APG dialog-like semantics (`role="dialog"` on content).
4062
+ * - Portal-rendered.
4063
+ *
4064
+ * Use `<Popover>` for menus / pickers / settings panels with
4065
+ * interactive children. Use `<Tooltip>` for read-only hints.
4066
+ */ function Popover(props) {
4067
+ var _slotProps_content, _slotProps_arrow;
4068
+ const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
4069
+ const v = popoverVariants();
4070
+ return jsxs(RadixPopover.Root, {
4071
+ open: open,
4072
+ onOpenChange: onOpenChange,
4073
+ children: [
4074
+ jsx(RadixPopover.Trigger, {
4075
+ asChild: true,
4076
+ children: children
4077
+ }),
4078
+ jsx(RadixPopover.Portal, {
4079
+ children: jsxs(RadixPopover.Content, {
4080
+ side: side,
4081
+ align: align,
4082
+ sideOffset: sideOffset,
4083
+ style: width != null ? {
4084
+ width
4085
+ } : undefined,
4086
+ className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
4087
+ children: [
4088
+ content,
4089
+ showArrow && jsx(RadixPopover.Arrow, {
4090
+ className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
4091
+ })
4092
+ ]
4093
+ })
4094
+ })
4095
+ ]
4096
+ });
4097
+ }
4098
+
4099
+ /**
4100
+ * Slots for `<DatePicker>` — a bridge-integrated date field whose trigger
4101
+ * is a `<button>` styled as an input, opening a `<Calendar>` popover.
4102
+ *
4103
+ * Theme identity: bare neutral / brand token-backed classes only — no
4104
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4105
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4106
+ */ const datePickerVariants = tv({
4107
+ slots: {
4108
+ root: 'flex flex-col',
4109
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4110
+ requiredMark: 'text-danger-500 ml-0.5',
4111
+ trigger: 'flex items-center gap-2 h-10 min-w-[12rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4112
+ value: 'flex-1 truncate text-left',
4113
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4114
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4115
+ helperText: 'mt-1 text-sm text-neutral-600',
4116
+ errorText: 'mt-1 text-sm text-danger-600'
4117
+ },
4118
+ variants: {
4119
+ layout: {
4120
+ stacked: {},
4121
+ inline: {
4122
+ root: 'flex-row items-start gap-3',
4123
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
4124
+ }
4125
+ },
4126
+ error: {
4127
+ true: {
4128
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
4129
+ }
4130
+ },
4131
+ fullWidth: {
4132
+ true: {
4133
+ root: 'w-full',
4134
+ trigger: 'w-full min-w-0'
4135
+ }
4136
+ }
4137
+ },
4138
+ defaultVariants: {
4139
+ layout: 'stacked',
4140
+ error: false,
4141
+ fullWidth: false
4142
+ }
4143
+ });
4144
+
4145
+ // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
4146
+ function CalendarIcon() {
4147
+ return jsxs("svg", {
4148
+ width: "1em",
4149
+ height: "1em",
4150
+ viewBox: "0 0 16 16",
4151
+ fill: "none",
4152
+ "aria-hidden": "true",
4153
+ children: [
4154
+ jsx("rect", {
4155
+ x: "2.5",
4156
+ y: "3.5",
4157
+ width: "11",
4158
+ height: "10",
4159
+ rx: "1.5",
4160
+ stroke: "currentColor",
4161
+ strokeWidth: "1.5"
4162
+ }),
4163
+ jsx("path", {
4164
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
4165
+ stroke: "currentColor",
4166
+ strokeWidth: "1.5",
4167
+ strokeLinecap: "round"
4168
+ })
4169
+ ]
4170
+ });
4171
+ }
4172
+ /**
4173
+ * Formats a stored ISO date for the trigger display, localized to a medium
4174
+ * date style (e.g. `"May 20, 2026"`). Formatting is done in UTC so the
4175
+ * displayed day always matches the stored `YYYY-MM-DD`.
4176
+ */ function formatDisplayDate(iso, locale) {
4177
+ if (iso == null || iso === '') {
4178
+ return '';
4179
+ }
4180
+ const parts = parseISODate(iso);
4181
+ if (parts === null) {
4182
+ return iso;
4183
+ }
4184
+ return new Intl.DateTimeFormat(locale, {
4185
+ dateStyle: 'medium',
4186
+ timeZone: 'UTC'
4187
+ }).format(Date.UTC(parts.year, parts.month - 1, parts.day));
4188
+ }
4189
+ /**
4190
+ * Dashforge TW `<DatePicker>` — a form-bound single-date field.
4191
+ *
4192
+ * A read-only trigger button paired with a `<Calendar>` popover (Radix
4193
+ * Popover — toggle / Escape / outside-click / focus-trap are Radix-managed).
4194
+ * Integrates with the Dashforge form bridge + RBAC, mirroring the other
4195
+ * `@dashforge/tw` form fields.
4196
+ *
4197
+ * Storage contract: an ISO `YYYY-MM-DD` string, or `null` — shared with the
4198
+ * MUI `@dashforge/ui` `DatePicker`.
4199
+ */ function DatePicker(props) {
4200
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4201
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, fullWidth, sx, slotProps, testId } = props;
4202
+ // ───── Hooks (unconditional, before any early return) ─────
4203
+ const bridge = useContext(DashFormContext);
4204
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4205
+ const fieldMeta = useDashFieldMeta(name);
4206
+ const accessState = useAccessState(access);
4207
+ const fieldId = useId();
4208
+ const helperId = `${fieldId}-help`;
4209
+ const [isOpen, setIsOpen] = useState(false);
4210
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4211
+ // StrictMode-safe unregister-on-unmount.
4212
+ const unregisterRef = useRef({
4213
+ bridge,
4214
+ name
4215
+ });
4216
+ unregisterRef.current = {
4217
+ bridge,
4218
+ name
4219
+ };
4220
+ const isMountedRef = useRef(false);
4221
+ useEffect(()=>{
4222
+ isMountedRef.current = true;
4223
+ return ()=>{
4224
+ isMountedRef.current = false;
4225
+ const { bridge: cap, name: capName } = unregisterRef.current;
4226
+ queueMicrotask(()=>{
4227
+ var _cap_unregister;
4228
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
4229
+ });
4230
+ };
4231
+ }, []);
4232
+ // ───── Derived ─────
4233
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4234
+ const effectiveReadOnly = accessState.readonly;
4235
+ const isInteractive = !effectiveDisabled && !effectiveReadOnly;
4236
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4237
+ let resolvedError = error;
4238
+ let resolvedHelperText = helperText;
4239
+ let registration = null;
4240
+ let resolvedValue;
4241
+ if (isFormMode && bridge) {
4242
+ registration = bridge.register(name, rules);
4243
+ const validation = resolveValidationState(name, bridge, error, helperText);
4244
+ resolvedError = validation.error;
4245
+ resolvedHelperText = validation.helperText;
4246
+ if (explicitValue !== undefined) {
4247
+ resolvedValue = explicitValue;
4248
+ } else {
4249
+ const bv = fieldMeta.value;
4250
+ resolvedValue = bv == null ? null : String(bv);
4251
+ }
4252
+ } else if (explicitValue !== undefined) {
4253
+ resolvedValue = explicitValue;
4254
+ } else {
4255
+ resolvedValue = internalValue;
4256
+ }
4257
+ // RHF threads its own callback ref through here in form mode.
4258
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
4259
+ const triggerRef = useCallback((instance)=>{
4260
+ if (typeof registrationRefFn === 'function') {
4261
+ registrationRefFn(instance);
4262
+ }
4263
+ }, [
4264
+ registrationRefFn
4265
+ ]);
4266
+ // ───── Render-time guards (after all hooks) ─────
4267
+ if (!isVisible) return null;
4268
+ if (!accessState.visible) return null;
4269
+ const v = datePickerVariants({
4270
+ layout,
4271
+ error: resolvedError,
4272
+ fullWidth
4273
+ });
4274
+ const resolvedLocale = locale != null ? locale : 'en-US';
4275
+ const displayValue = formatDisplayDate(resolvedValue, resolvedLocale);
4276
+ const commitValue = (next)=>{
4277
+ if (isFormMode && bridge) {
4278
+ var _registration_onChange;
4279
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
4280
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4281
+ target: {
4282
+ name,
4283
+ value: next != null ? next : ''
4284
+ },
4285
+ type: 'change'
4286
+ }));
4287
+ } else if (explicitValue === undefined) {
4288
+ setInternalValue(next);
4289
+ }
4290
+ onChange == null ? void 0 : onChange(next);
4291
+ };
4292
+ // Mark the field touched on popover close. Reads the freshest committed
4293
+ // value straight from the bridge so the touched-blur never reverts a
4294
+ // value just chosen in the calendar.
4295
+ const markTouched = ()=>{
4296
+ if (isFormMode && bridge) {
4297
+ var _registration_onBlur;
4298
+ const committed = bridge.getValue(name);
4299
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4300
+ target: {
4301
+ name,
4302
+ value: committed == null ? '' : String(committed)
4303
+ },
4304
+ type: 'blur'
4305
+ }));
4306
+ }
4307
+ };
4308
+ const handleOpenChange = (next)=>{
4309
+ if (next && !isInteractive) {
4310
+ return;
4311
+ }
4312
+ setIsOpen(next);
4313
+ if (!next) {
4314
+ markTouched();
4315
+ }
4316
+ };
4317
+ const handleCalendarChange = (next)=>{
4318
+ commitValue(next);
4319
+ setIsOpen(false);
4320
+ markTouched();
4321
+ };
4322
+ return jsxs("div", {
4323
+ "data-testid": testId,
4324
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
4325
+ children: [
4326
+ label && jsxs("label", {
4327
+ htmlFor: fieldId,
4328
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4329
+ children: [
4330
+ label,
4331
+ required && jsx("span", {
4332
+ "aria-hidden": "true",
4333
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4334
+ children: "*"
4335
+ })
4336
+ ]
4337
+ }),
4338
+ jsx(Popover, {
4339
+ open: isOpen,
4340
+ onOpenChange: handleOpenChange,
4341
+ side: "bottom",
4342
+ align: "start",
4343
+ content: jsx(Calendar, {
4344
+ value: resolvedValue,
4345
+ onChange: handleCalendarChange,
4346
+ minDate: minDate,
4347
+ maxDate: maxDate,
4348
+ disabledDates: disabledDates,
4349
+ isDateDisabled: isDateDisabled,
4350
+ weekStartDay: weekStartDay,
4351
+ locale: locale,
4352
+ "aria-label": "Choose date",
4353
+ sx: "border-0 bg-transparent p-0"
4354
+ }),
4355
+ children: jsxs("button", {
4356
+ type: "button",
4357
+ id: fieldId,
4358
+ ref: triggerRef,
4359
+ disabled: effectiveDisabled,
4360
+ "aria-invalid": resolvedError ? true : undefined,
4361
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4362
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4363
+ children: [
4364
+ jsx("span", {
4365
+ className: displayValue ? v.value() : v.placeholder(),
4366
+ children: displayValue || placeholder || 'Select a date'
4367
+ }),
4368
+ jsx("span", {
4369
+ className: v.icon(),
4370
+ children: jsx(CalendarIcon, {})
4371
+ })
4372
+ ]
4373
+ })
4374
+ }),
4375
+ resolvedHelperText && jsx("p", {
4376
+ id: helperId,
4377
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4378
+ children: resolvedHelperText
4379
+ })
4380
+ ]
4381
+ });
4382
+ }
4383
+
3757
4384
  /**
3758
4385
  * Tailwind-variants recipe for `<Breadcrumbs>`.
3759
4386
  *
@@ -5285,6 +5912,15 @@ const SnackbarContext = /*#__PURE__*/ createContext(null);
5285
5912
  * has set its own color (e.g. `<Box variant="solid" color="primary">`
5286
5913
  * paints white text).
5287
5914
  */ color: {
5915
+ // `inherit` is the explicit escape hatch — use it on a
5916
+ // `<Typography>` inside a coloured surface (e.g.
5917
+ // `<Box variant="solid" color="primary">` paints white text)
5918
+ // so the text picks up the surface colour. It is NO LONGER the
5919
+ // default: a bare `<Typography>` would otherwise inherit the
5920
+ // consuming app's arbitrary root colour (often OS-`prefers-
5921
+ // color-scheme` driven), which diverges from the Dashforge
5922
+ // `data-dash-tw-theme` mode and breaks colour consistency in
5923
+ // dark mode. The default is now `neutral` — see `defaultVariants`.
5288
5924
  inherit: 'text-inherit',
5289
5925
  primary: 'text-primary-700 dark:text-primary-400',
5290
5926
  secondary: 'text-secondary-700 dark:text-secondary-400',
@@ -5337,7 +5973,11 @@ const SnackbarContext = /*#__PURE__*/ createContext(null);
5337
5973
  },
5338
5974
  defaultVariants: {
5339
5975
  variant: 'body1',
5340
- color: 'inherit',
5976
+ // `neutral` (not `inherit`): a bare `<Typography>` emits
5977
+ // `text-neutral-900`, which auto-inverts via the dashforgePreset()
5978
+ // CSS-var swap and follows the Dashforge mode. Pass
5979
+ // `color="inherit"` explicitly inside a coloured surface.
5980
+ color: 'neutral',
5341
5981
  align: 'left'
5342
5982
  }
5343
5983
  });
@@ -6915,8 +7555,11 @@ VisuallyHidden.displayName = 'VisuallyHidden';
6915
7555
  jsx(RadixDialog.Overlay, {
6916
7556
  className: cn(v.overlay(), slotProps == null ? void 0 : (_slotProps_overlay = slotProps.overlay) == null ? void 0 : _slotProps_overlay.className)
6917
7557
  }),
6918
- jsxs(RadixDialog.Content, {
6919
- className: cn(v.content(), sx, slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
7558
+ jsxs(RadixDialog.Content, _extends({
7559
+ className: cn(v.content(), sx, slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className)
7560
+ }, description == null ? {
7561
+ 'aria-describedby': undefined
7562
+ } : {}, {
6920
7563
  onPointerDownOutside: (event)=>{
6921
7564
  if (disableBackdropClose) event.preventDefault();
6922
7565
  },
@@ -6955,7 +7598,7 @@ VisuallyHidden.displayName = 'VisuallyHidden';
6955
7598
  })
6956
7599
  })
6957
7600
  ]
6958
- })
7601
+ }))
6959
7602
  ]
6960
7603
  })
6961
7604
  });
@@ -7160,71 +7803,6 @@ const tooltipVariants = tv({
7160
7803
  });
7161
7804
  }
7162
7805
 
7163
- const popoverVariants = tv({
7164
- slots: {
7165
- content: [
7166
- // Neutral border auto-inverts via CSS-var swap.
7167
- 'z-50 rounded-md border border-neutral-200',
7168
- // bg-white hardcoded (no auto-invert) so `dark:` is required.
7169
- // Target `dark:bg-neutral-100` resolves to #171717 in dark mode
7170
- // (one elevation tier above the page surface).
7171
- 'bg-white dark:bg-neutral-100 p-4 shadow-lg',
7172
- // Text auto-inverts.
7173
- 'text-sm text-neutral-900',
7174
- 'focus:outline-none',
7175
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
7176
- 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
7177
- 'motion-reduce:transition-none motion-reduce:duration-0'
7178
- ],
7179
- // Arrow follows content bg: `fill-white` in light + matching dark target.
7180
- arrow: 'fill-white dark:fill-neutral-100'
7181
- }
7182
- });
7183
-
7184
- /**
7185
- * Dashforge TW `<Popover>` — clickable floating content panel.
7186
- *
7187
- * Built on `@radix-ui/react-popover`:
7188
- * - Focus trap inside the popover (Tab cycles within).
7189
- * - Outside-click + Escape dismiss.
7190
- * - APG dialog-like semantics (`role="dialog"` on content).
7191
- * - Portal-rendered.
7192
- *
7193
- * Use `<Popover>` for menus / pickers / settings panels with
7194
- * interactive children. Use `<Tooltip>` for read-only hints.
7195
- */ function Popover(props) {
7196
- var _slotProps_content, _slotProps_arrow;
7197
- const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
7198
- const v = popoverVariants();
7199
- return jsxs(RadixPopover.Root, {
7200
- open: open,
7201
- onOpenChange: onOpenChange,
7202
- children: [
7203
- jsx(RadixPopover.Trigger, {
7204
- asChild: true,
7205
- children: children
7206
- }),
7207
- jsx(RadixPopover.Portal, {
7208
- children: jsxs(RadixPopover.Content, {
7209
- side: side,
7210
- align: align,
7211
- sideOffset: sideOffset,
7212
- style: width != null ? {
7213
- width
7214
- } : undefined,
7215
- className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
7216
- children: [
7217
- content,
7218
- showArrow && jsx(RadixPopover.Arrow, {
7219
- className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
7220
- })
7221
- ]
7222
- })
7223
- })
7224
- ]
7225
- });
7226
- }
7227
-
7228
7806
  const accordionVariants = tv({
7229
7807
  slots: {
7230
7808
  root: 'w-full',
@@ -8650,6 +9228,7 @@ function inferType(value) {
8650
9228
  const DEFAULT_LABELS$1 = {
8651
9229
  searchPlaceholder: 'Search...',
8652
9230
  noData: 'No data',
9231
+ noResults: 'No matching results',
8653
9232
  loading: 'Loading…',
8654
9233
  ariaSortNone: 'Not sorted. Click to sort ascending.',
8655
9234
  ariaSortAscending: 'Sorted ascending. Click to sort descending.',
@@ -8676,8 +9255,7 @@ const DEFAULT_LABELS$1 = {
8676
9255
  densitySpacious: 'Spacious',
8677
9256
  columnsButton: 'Columns',
8678
9257
  columnsTitle: 'Toggle columns',
8679
- columnsShowAll: 'Show all',
8680
- columnsHideAll: 'Hide all'
9258
+ columnsToggleAll: 'Toggle all columns'
8681
9259
  };
8682
9260
  const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
8683
9261
  /**
@@ -8706,6 +9284,26 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
8706
9284
  sortModel: sortModelProp, onSortChange, defaultSortModel, enableSearch = false, searchQuery: searchQueryProp, onSearchQueryChange, searchPlaceholder, searchDebounceMs = 200, filterModel: filterModelProp, onFilterChange, rowSelection = 'none', selectedRowIds: selectedRowIdsProp, onSelectionChange, bulkActions, expandable, rowActions, loading = false, loadingRowCount = 5, emptyState, stickyHeader = true, caption, showCaption = false, access: tableAccess, labels: labelsProp, variant, size, density, sx, slotProps } = props;
8707
9285
  const labels = _extends({}, DEFAULT_LABELS$1, labelsProp);
8708
9286
  const tableAccessState = useAccessState(tableAccess);
9287
+ // Dev-only guard: `getRowId` is optional (positional-index fallback)
9288
+ // but that fallback breaks row identity once rows reorder (sort) or
9289
+ // change presence (search / filter) — selection + expansion state
9290
+ // then jump to the wrong rows. Warn when it's omitted AND a feature
9291
+ // that relies on stable identity is active. Stripped in production.
9292
+ const getRowIdProvided = props.getRowId !== undefined;
9293
+ useEffect(()=>{
9294
+ if (process.env.NODE_ENV === 'production') return;
9295
+ if (getRowIdProvided) return;
9296
+ const identityMatters = rowSelection !== 'none' || expandable != null || enableSearch || cols.some((c)=>Boolean(c.sortable));
9297
+ if (identityMatters) {
9298
+ console.warn('[Dashforge Table] `getRowId` is not set — falling back to the ' + 'positional row index. With sort / search / selection / ' + 'expandable rows active, the index-based key breaks row ' + 'identity when rows reorder. Pass a stable resolver, e.g. ' + '`getRowId={(row) => row.id}`.');
9299
+ }
9300
+ }, [
9301
+ getRowIdProvided,
9302
+ rowSelection,
9303
+ expandable,
9304
+ enableSearch,
9305
+ cols
9306
+ ]);
8709
9307
  // ───── Controllable state machinery ─────
8710
9308
  const [sortModel, setSortModel] = useControllableState({
8711
9309
  controlledValue: sortModelProp,
@@ -8861,7 +9459,9 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
8861
9459
  }) : sortedRows.length === 0 ? renderEmptyState$1({
8862
9460
  totalColumnCount,
8863
9461
  emptyState,
8864
- fallback: labels.noData,
9462
+ // Distinguish a genuinely empty dataset from one
9463
+ // emptied by an active search / filter.
9464
+ fallback: rows.length > 0 && (debouncedSearchQuery.trim().length > 0 || filterModel.length > 0) ? labels.noResults : labels.noData,
8865
9465
  cellClass: cn(v.cell(), v.emptyState(), slotProps == null ? void 0 : (_slotProps_emptyState = slotProps.emptyState) == null ? void 0 : _slotProps_emptyState.className)
8866
9466
  }) : sortedRows.map((row, idx)=>{
8867
9467
  var _slotProps_row, _slotProps_cell, _slotProps_cell1, _slotProps_selectionCell, _slotProps_cell2, _slotProps_expandToggleCell, _slotProps_cell3, _slotProps_rowActionsCell, _slotProps_expandedRow;
@@ -9083,6 +9683,10 @@ function handleSortClick$1(col, event, current, setSortModel) {
9083
9683
  }
9084
9684
  function renderLoadingRows$1(args) {
9085
9685
  const { count, visibleCols, showSelectionColumn, showExpandColumn, showRowActionsColumn, rowClass, cellClass } = args;
9686
+ // No explicit row height here (unlike DataGrid's loading rows, which
9687
+ // pin `rowHeight` for the virtualization math): Table is auto-height
9688
+ // by design. Skeleton rows inherit the same density padding via
9689
+ // `cellClass`, so they line up with real rows without a fixed height.
9086
9690
  return Array.from({
9087
9691
  length: count
9088
9692
  }).map((_, i)=>jsxs("tr", {
@@ -9324,15 +9928,12 @@ function SearchIcon$1(props) {
9324
9928
  color: 'secondary',
9325
9929
  class: 'bg-secondary-100 dark:bg-secondary-950 text-secondary-700 dark:text-secondary-300'
9326
9930
  },
9327
- // solid = full bg + light text
9328
- // solid neutral: bg + text both anchored to neutral palette
9329
- // (auto-inverts). `text-white` would not invert and would break
9330
- // contrast in light mode against a `bg-neutral-700` dark bg, so
9331
- // we stick to `text-neutral-50` (light text in light mode, dark
9332
- // in dark — wait, that's wrong too). Actually `text-neutral-50`
9333
- // auto-inverts: light text in light mode (against dark bg) is
9334
- // what we want; in dark mode `bg-neutral-700` becomes light AND
9335
- // `text-neutral-50` becomes dark — contrast preserved.
9931
+ // solid = full bg + contrasting text.
9932
+ // solid neutral: both `bg-neutral-700` and `text-neutral-50` sit
9933
+ // on the neutral palette, so they auto-invert together via the
9934
+ // CSS-var swap — bg/text contrast is preserved in BOTH modes.
9935
+ // (`text-white` is deliberately NOT used here: white does not
9936
+ // auto-invert, which would flip the contrast in dark mode.)
9336
9937
  {
9337
9938
  variant: 'solid',
9338
9939
  color: 'neutral',
@@ -9863,6 +10464,17 @@ function setFilter(model, field, next) {
9863
10464
  next
9864
10465
  ];
9865
10466
  }
10467
+ /**
10468
+ * Shared `onKeyDown` for filter inputs — pressing Enter commits the
10469
+ * filter (equivalent to clicking Apply). Without this the user has to
10470
+ * leave the keyboard and click the button, which reads as broken for
10471
+ * a single-field text filter.
10472
+ */ function onEnterApply(e, apply) {
10473
+ if (e.key === 'Enter') {
10474
+ e.preventDefault();
10475
+ apply();
10476
+ }
10477
+ }
9866
10478
  const DEFAULT_FILTER_LABELS = {
9867
10479
  filterColumn: 'Filter',
9868
10480
  filterApply: 'Apply',
@@ -9948,6 +10560,11 @@ function FilterPanel(props) {
9948
10560
  function TextFilter(props) {
9949
10561
  const { field, current, onCommit, labels } = props;
9950
10562
  const [value, setValue] = useState(typeof (current == null ? void 0 : current.value) === 'string' ? current.value : '');
10563
+ const apply = ()=>onCommit(value.trim() ? {
10564
+ field,
10565
+ op: 'contains',
10566
+ value
10567
+ } : null);
9951
10568
  return jsxs("div", {
9952
10569
  className: "flex flex-col gap-2 min-w-[200px]",
9953
10570
  children: [
@@ -9955,16 +10572,13 @@ function TextFilter(props) {
9955
10572
  type: "text",
9956
10573
  value: value,
9957
10574
  onChange: (e)=>setValue(e.target.value),
10575
+ onKeyDown: (e)=>onEnterApply(e, apply),
9958
10576
  placeholder: "\u2026",
9959
10577
  autoFocus: true,
9960
10578
  className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
9961
10579
  }),
9962
10580
  jsx(FilterActions, {
9963
- onApply: ()=>onCommit(value.trim() ? {
9964
- field,
9965
- op: 'contains',
9966
- value
9967
- } : null),
10581
+ onApply: apply,
9968
10582
  onClear: ()=>{
9969
10583
  setValue('');
9970
10584
  onCommit(null);
@@ -10018,6 +10632,7 @@ function NumberRangeFilter(props) {
10018
10632
  type: "number",
10019
10633
  value: min,
10020
10634
  onChange: (e)=>setMin(e.target.value),
10635
+ onKeyDown: (e)=>onEnterApply(e, apply),
10021
10636
  autoFocus: true,
10022
10637
  className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
10023
10638
  })
@@ -10033,6 +10648,7 @@ function NumberRangeFilter(props) {
10033
10648
  type: "number",
10034
10649
  value: max,
10035
10650
  onChange: (e)=>setMax(e.target.value),
10651
+ onKeyDown: (e)=>onEnterApply(e, apply),
10036
10652
  className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
10037
10653
  })
10038
10654
  ]
@@ -10141,6 +10757,7 @@ function DateRangeFilter(props) {
10141
10757
  type: "date",
10142
10758
  value: from,
10143
10759
  onChange: (e)=>setFrom(e.target.value),
10760
+ onKeyDown: (e)=>onEnterApply(e, apply),
10144
10761
  autoFocus: true,
10145
10762
  className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
10146
10763
  })
@@ -10156,6 +10773,7 @@ function DateRangeFilter(props) {
10156
10773
  type: "date",
10157
10774
  value: to,
10158
10775
  onChange: (e)=>setTo(e.target.value),
10776
+ onKeyDown: (e)=>onEnterApply(e, apply),
10159
10777
  className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
10160
10778
  })
10161
10779
  ]
@@ -10215,8 +10833,7 @@ function FilterIcon() {
10215
10833
  const DEFAULT_VISIBILITY_LABELS = {
10216
10834
  columnsButton: 'Columns',
10217
10835
  columnsTitle: 'Toggle columns',
10218
- columnsShowAll: 'Show all',
10219
- columnsHideAll: 'Hide all'
10836
+ columnsToggleAll: 'Toggle all columns'
10220
10837
  };
10221
10838
  function ColumnVisibilityTrigger(props) {
10222
10839
  const { cols, hiddenColumns, onChange, disabled, labels: labelsProp } = props;
@@ -10232,44 +10849,40 @@ function ColumnVisibilityTrigger(props) {
10232
10849
  field
10233
10850
  ]);
10234
10851
  };
10235
- const showAll = ()=>onChange([]);
10236
- const hideAll = ()=>onChange(hideableCols.map((c)=>c.field));
10852
+ // Tri-state master: checked = every hideable column visible,
10853
+ // unchecked = every one hidden, indeterminate = mixed.
10854
+ const allVisible = hideableCols.every((c)=>!hiddenColumns.includes(c.field));
10855
+ const allHidden = hideableCols.length > 0 && hideableCols.every((c)=>hiddenColumns.includes(c.field));
10856
+ const someHidden = !allVisible && !allHidden;
10857
+ // Standard select-all semantics: when everything is visible the
10858
+ // master hides all; otherwise it shows all.
10859
+ const toggleAll = ()=>{
10860
+ onChange(allVisible ? hideableCols.map((c)=>c.field) : []);
10861
+ };
10237
10862
  return jsx(Popover, {
10238
10863
  open: open,
10239
10864
  onOpenChange: setOpen,
10240
10865
  side: "bottom",
10241
10866
  align: "end",
10242
10867
  content: jsxs("div", {
10243
- className: "flex flex-col gap-2 min-w-[220px]",
10868
+ className: "flex flex-col gap-1 min-w-[220px]",
10244
10869
  children: [
10245
10870
  jsxs("div", {
10246
- className: "flex items-center justify-between pb-2 border-b border-neutral-200",
10871
+ className: "flex items-center gap-2 px-1 pb-2 mb-1 border-b border-neutral-200",
10247
10872
  children: [
10873
+ jsx("input", {
10874
+ type: "checkbox",
10875
+ "aria-label": labels.columnsToggleAll,
10876
+ checked: allVisible,
10877
+ ref: (el)=>{
10878
+ if (el) el.indeterminate = someHidden;
10879
+ },
10880
+ onChange: toggleAll,
10881
+ className: "cursor-pointer"
10882
+ }),
10248
10883
  jsx("span", {
10249
10884
  className: "text-xs font-semibold text-neutral-700 uppercase tracking-wide",
10250
10885
  children: labels.columnsTitle
10251
- }),
10252
- jsxs("div", {
10253
- className: "flex items-center gap-2",
10254
- children: [
10255
- jsx("button", {
10256
- type: "button",
10257
- onClick: showAll,
10258
- className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
10259
- children: labels.columnsShowAll
10260
- }),
10261
- jsx("span", {
10262
- className: "text-neutral-300",
10263
- "aria-hidden": "true",
10264
- children: "\u00B7"
10265
- }),
10266
- jsx("button", {
10267
- type: "button",
10268
- onClick: hideAll,
10269
- className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
10270
- children: labels.columnsHideAll
10271
- })
10272
- ]
10273
10886
  })
10274
10887
  ]
10275
10888
  }),
@@ -10398,8 +11011,21 @@ function useColumnResize(args) {
10398
11011
  minWidth,
10399
11012
  maxWidth
10400
11013
  ]);
11014
+ const nudge = useCallback((field, currentWidth, delta, clampMin, clampMax)=>{
11015
+ const effectiveMin = Math.max(minWidth, clampMin);
11016
+ const effectiveMax = Math.min(maxWidth, clampMax);
11017
+ const next = clamp(currentWidth + delta, effectiveMin, effectiveMax);
11018
+ onChange(_extends({}, widthsRef.current, {
11019
+ [field]: next
11020
+ }));
11021
+ }, [
11022
+ onChange,
11023
+ minWidth,
11024
+ maxWidth
11025
+ ]);
10401
11026
  return {
10402
- startResize
11027
+ startResize,
11028
+ nudge
10403
11029
  };
10404
11030
  }
10405
11031
  function clamp(n, min, max) {
@@ -10494,6 +11120,7 @@ function arraysEqual(a, b) {
10494
11120
  const DEFAULT_LABELS = {
10495
11121
  searchPlaceholder: 'Search...',
10496
11122
  noData: 'No data',
11123
+ noResults: 'No matching results',
10497
11124
  loading: 'Loading…',
10498
11125
  ariaSortNone: 'Not sorted. Click to sort ascending.',
10499
11126
  ariaSortAscending: 'Sorted ascending. Click to sort descending.',
@@ -10520,8 +11147,7 @@ const DEFAULT_LABELS = {
10520
11147
  densitySpacious: 'Spacious',
10521
11148
  columnsButton: 'Columns',
10522
11149
  columnsTitle: 'Toggle columns',
10523
- columnsShowAll: 'Show all',
10524
- columnsHideAll: 'Hide all'
11150
+ columnsToggleAll: 'Toggle all columns'
10525
11151
  };
10526
11152
  /**
10527
11153
  * Dashforge TW `<DataGrid>` — virtualized data table.
@@ -10609,7 +11235,7 @@ const DEFAULT_LABELS = {
10609
11235
  defaultValue: defaultColumnWidths,
10610
11236
  onChange: onColumnWidthsChange
10611
11237
  });
10612
- const { startResize } = useColumnResize({
11238
+ const { startResize, nudge: nudgeColumnWidth } = useColumnResize({
10613
11239
  widths: columnWidths,
10614
11240
  onChange: setColumnWidths
10615
11241
  });
@@ -10626,6 +11252,22 @@ const DEFAULT_LABELS = {
10626
11252
  order: columnOrder,
10627
11253
  onChange: setColumnOrder
10628
11254
  });
11255
+ // Column reorder rides on native HTML5 drag-and-drop, whose
11256
+ // `dragstart` / `drop` events DO NOT fire for touch input. On a
11257
+ // coarse-pointer device the drag would be a dead affordance — so
11258
+ // we detect the primary pointer and gate reorder off there. Mouse
11259
+ // / trackpad / pen (fine pointer) keep the feature. A pointer-event
11260
+ // based reorder that also covers touch is tracked as a follow-up
11261
+ // in REFINEMENT-NOTES.md.
11262
+ const [coarsePointer, setCoarsePointer] = useState(false);
11263
+ useEffect(()=>{
11264
+ if (typeof window === 'undefined' || !window.matchMedia) return;
11265
+ const mq = window.matchMedia('(pointer: coarse)');
11266
+ setCoarsePointer(mq.matches);
11267
+ const onChange = (e)=>setCoarsePointer(e.matches);
11268
+ mq.addEventListener('change', onChange);
11269
+ return ()=>mq.removeEventListener('change', onChange);
11270
+ }, []);
10629
11271
  // Apply the active order to the columns list. Cols not in the
10630
11272
  // order array (e.g. newly-added columns at runtime) are appended
10631
11273
  // in their original position from `cols`.
@@ -10780,8 +11422,7 @@ const DEFAULT_LABELS = {
10780
11422
  labels: {
10781
11423
  columnsButton: labels.columnsButton,
10782
11424
  columnsTitle: labels.columnsTitle,
10783
- columnsShowAll: labels.columnsShowAll,
10784
- columnsHideAll: labels.columnsHideAll
11425
+ columnsToggleAll: labels.columnsToggleAll
10785
11426
  }
10786
11427
  })
10787
11428
  ]
@@ -10795,6 +11436,7 @@ const DEFAULT_LABELS = {
10795
11436
  } : undefined,
10796
11437
  children: jsxs("table", {
10797
11438
  className: cn(v.table(), slotProps == null ? void 0 : (_slotProps_table = slotProps.table) == null ? void 0 : _slotProps_table.className),
11439
+ "aria-rowcount": effectiveTotalCount > 0 ? effectiveTotalCount + 1 : -1,
10798
11440
  children: [
10799
11441
  caption != null && jsx("caption", {
10800
11442
  className: cn(!showCaption && 'sr-only', 'py-2 text-sm text-neutral-500'),
@@ -10803,6 +11445,7 @@ const DEFAULT_LABELS = {
10803
11445
  jsx("thead", {
10804
11446
  className: cn(stickyHeader ? v.thead() : 'bg-neutral-50', slotProps == null ? void 0 : (_slotProps_thead = slotProps.thead) == null ? void 0 : _slotProps_thead.className),
10805
11447
  children: jsxs("tr", {
11448
+ "aria-rowindex": 1,
10806
11449
  className: cn(v.headerRow(), slotProps == null ? void 0 : (_slotProps_headerRow = slotProps.headerRow) == null ? void 0 : _slotProps_headerRow.className),
10807
11450
  children: [
10808
11451
  showSelectionColumn && jsx("th", {
@@ -10832,7 +11475,8 @@ const DEFAULT_LABELS = {
10832
11475
  effectiveWidth: resolveColumnWidth(col, columnWidths),
10833
11476
  resizable: enableColumnResize && col.resizable !== false,
10834
11477
  startResize: startResize,
10835
- reorderable: enableColumnReorder && col.reorderable !== false,
11478
+ nudgeColumnWidth: nudgeColumnWidth,
11479
+ reorderable: enableColumnReorder && col.reorderable !== false && !coarsePointer,
10836
11480
  reorderHandlers: reorderHandlers,
10837
11481
  labels: labels,
10838
11482
  className: cn(v.headerCell(), col.sticky === 'left' && v.stickyLeftHeaderCell(), col.sticky === 'right' && v.stickyRightHeaderCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className),
@@ -10861,7 +11505,11 @@ const DEFAULT_LABELS = {
10861
11505
  }) : sortedRows.length === 0 ? renderEmptyState({
10862
11506
  totalColumnCount,
10863
11507
  emptyState,
10864
- fallback: labels.noData,
11508
+ // Distinguish a genuinely empty dataset from one
11509
+ // emptied by an active search / filter (works for
11510
+ // server-side mode too — a query that returns 0
11511
+ // rows still reads as "no matching results").
11512
+ fallback: rows.length > 0 && (debouncedSearchQuery.trim().length > 0 || filterModel.length > 0) ? labels.noResults : labels.noData,
10865
11513
  cellClass: cn(v.cell(), v.emptyState(), slotProps == null ? void 0 : (_slotProps_emptyState = slotProps.emptyState) == null ? void 0 : _slotProps_emptyState.className)
10866
11514
  }) : renderVirtualizedBody({
10867
11515
  vWindow,
@@ -10929,7 +11577,8 @@ const DEFAULT_LABELS = {
10929
11577
  }
10930
11578
  // ───── Header cell ─────
10931
11579
  function HeaderCell(props) {
10932
- const { col, sortModel, onSortClick, inferredType, filterModel, onFilterChange, effectiveWidth, resizable, startResize, reorderable, reorderHandlers, labels, className, buttonClassName, disabled } = props;
11580
+ var _col_minWidth, _col_maxWidth;
11581
+ const { col, sortModel, onSortClick, inferredType, filterModel, onFilterChange, effectiveWidth, resizable, startResize, nudgeColumnWidth, reorderable, reorderHandlers, labels, className, buttonClassName, disabled } = props;
10933
11582
  const align = resolveAlign(col, inferredType);
10934
11583
  const alignClass = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
10935
11584
  const sortEntry = sortModel.find((s)=>s.field === col.field);
@@ -10956,20 +11605,42 @@ function HeaderCell(props) {
10956
11605
  onDrop: reorderHandlers.drop(col.field),
10957
11606
  onDragEnd: reorderHandlers.dragEnd
10958
11607
  } : {};
11608
+ // Resize bounds — shared by the pointer-drag and keyboard paths.
11609
+ const resizeMin = (_col_minWidth = col.minWidth) != null ? _col_minWidth : 40;
11610
+ const resizeMax = (_col_maxWidth = col.maxWidth) != null ? _col_maxWidth : 1200;
11611
+ // Keyboard nudge step: 16px, or 64px with Shift held (coarse).
11612
+ const measureWidth = (handle)=>{
11613
+ var _ref;
11614
+ var _handle_parentElement;
11615
+ return (_ref = effectiveWidth != null ? effectiveWidth : (_handle_parentElement = handle.parentElement) == null ? void 0 : _handle_parentElement.getBoundingClientRect().width) != null ? _ref : 120;
11616
+ };
10959
11617
  const resizeHandle = resizable ? jsx("span", {
10960
11618
  role: "separator",
10961
11619
  "aria-orientation": "vertical",
10962
11620
  "aria-label": `Resize ${typeof col.header === 'string' ? col.header : col.field}`,
11621
+ "aria-valuemin": resizeMin,
11622
+ "aria-valuemax": resizeMax,
11623
+ "aria-valuenow": effectiveWidth != null ? effectiveWidth : undefined,
11624
+ // Keyboard-operable: focusable + ArrowLeft/Right resize. WCAG 2.1
11625
+ // keyboard operability — the drag is no longer the only path.
11626
+ tabIndex: disabled ? -1 : 0,
11627
+ // D6 guard: the parent `<th>` is `draggable` when reorderable;
11628
+ // marking the handle non-draggable means grabbing the handle
11629
+ // starts a RESIZE, never a column-reorder drag.
11630
+ draggable: false,
10963
11631
  onPointerDown: (e)=>{
10964
- var _ref, _col_minWidth, _col_maxWidth;
10965
- const handle = e.currentTarget;
10966
- const th = handle.parentElement;
10967
- const startWidth = (_ref = effectiveWidth != null ? effectiveWidth : th == null ? void 0 : th.getBoundingClientRect().width) != null ? _ref : 120;
10968
- const min = (_col_minWidth = col.minWidth) != null ? _col_minWidth : 40;
10969
- const max = (_col_maxWidth = col.maxWidth) != null ? _col_maxWidth : 1200;
10970
- startResize(col.field, startWidth, min, max)(e);
10971
- },
10972
- className: cn('absolute top-0 bottom-0 right-0 w-1.5', 'cursor-col-resize select-none touch-none', 'hover:bg-primary-300 active:bg-primary-400', 'transition-colors motion-reduce:transition-none')
11632
+ const startWidth = measureWidth(e.currentTarget);
11633
+ startResize(col.field, startWidth, resizeMin, resizeMax)(e);
11634
+ },
11635
+ onKeyDown: (e)=>{
11636
+ if (disabled) return;
11637
+ if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
11638
+ e.preventDefault();
11639
+ const step = (e.shiftKey ? 64 : 16) * (e.key === 'ArrowLeft' ? -1 : 1);
11640
+ const current = measureWidth(e.currentTarget);
11641
+ nudgeColumnWidth(col.field, current, step, resizeMin, resizeMax);
11642
+ },
11643
+ className: cn('absolute top-0 bottom-0 right-0 w-1.5', 'cursor-col-resize select-none touch-none', 'hover:bg-primary-300 active:bg-primary-400', 'focus:outline-none focus-visible:bg-primary-400', 'transition-colors motion-reduce:transition-none')
10973
11644
  }) : null;
10974
11645
  const filterType = col.filterable ? resolveFilterType(col, inferredType) : null;
10975
11646
  const filterTrigger = col.filterable && filterType ? jsx(ColumnFilterTrigger, {
@@ -11096,6 +11767,7 @@ function renderVirtualizedBody(args) {
11096
11767
  const rowId = getRowId(row, absoluteIdx);
11097
11768
  const isSelected = selection.isSelected(rowId);
11098
11769
  return jsxs("tr", {
11770
+ "aria-rowindex": absoluteIdx + 2,
11099
11771
  "data-selected": isSelected ? 'true' : 'false',
11100
11772
  "aria-selected": rowSelection !== 'none' ? isSelected : undefined,
11101
11773
  className: classes.row,
@@ -11309,4 +11981,4 @@ function SearchIcon(props) {
11309
11981
  * Package version (synced with `package.json` at publish time).
11310
11982
  */ const VERSION = '0.7.0-beta';
11311
11983
 
11312
- export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DataGrid, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
11984
+ export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Calendar, Checkbox, ConfirmDialogProvider, Container, DataGrid, DatePicker, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, calendarDayVariants, calendarVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, datePickerVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };