@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.24

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 (142) hide show
  1. package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
  2. package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
  3. package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
  4. package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
  5. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
  6. package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
  7. package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
  8. package/dist/components/core/Alert/Alert.js +2 -1
  9. package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
  10. package/dist/components/core/Badge/Badge.js +6 -17
  11. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
  12. package/dist/components/core/Calendar/Calendar.js +1 -1
  13. package/dist/components/core/Carousel/CarouselBody.js +6 -1
  14. package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
  15. package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
  16. package/dist/components/core/DragDrop/DragDrop.js +170 -17
  17. package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
  18. package/dist/components/core/Drawer/Drawer.d.ts +1 -1
  19. package/dist/components/core/Drawer/Drawer.js +2 -0
  20. package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
  21. package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
  22. package/dist/components/core/Drawer/DrawerTitle.js +2 -2
  23. package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
  24. package/dist/components/core/Masonry/Masonry.js +7 -2
  25. package/dist/components/core/Modal/Modal.d.ts +1 -1
  26. package/dist/components/core/Modal/Modal.js +3 -3
  27. package/dist/components/core/Modal/Modal.types.d.ts +2 -0
  28. package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
  29. package/dist/components/core/Modal/ModalTitle.js +2 -2
  30. package/dist/components/core/NavItem/NavItem.js +13 -8
  31. package/dist/components/core/Pagination/Pagination.js +1 -1
  32. package/dist/components/core/Popover/PopoverBody.js +3 -27
  33. package/dist/components/core/Popover/PopoverTrigger.js +1 -1
  34. package/dist/components/core/Progress/Progress.js +4 -4
  35. package/dist/components/core/Rating/Rating.js +1 -1
  36. package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
  37. package/dist/components/core/Segmented/Segmented.js +6 -6
  38. package/dist/components/core/Segmented/SegmentedItem.js +28 -2
  39. package/dist/components/core/Skeleton/Skeleton.js +2 -2
  40. package/dist/components/core/Sortable/Sortable.d.ts +1 -1
  41. package/dist/components/core/Sortable/Sortable.js +20 -4
  42. package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
  43. package/dist/components/core/Tabs/Tabs.js +6 -4
  44. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  45. package/dist/components/core/Tabs/TabsBody.js +3 -3
  46. package/dist/components/core/Tabs/TabsList.js +19 -21
  47. package/dist/components/core/Tabs/TabsTrigger.js +47 -28
  48. package/dist/components/core/Toast/Toast.js +1 -1
  49. package/dist/components/core/Toast/ToastProvider.js +3 -2
  50. package/dist/components/core/Tooltip/Tooltip.js +25 -34
  51. package/dist/components/core/Tree/Tree.js +178 -8
  52. package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
  53. package/dist/components/data-display/Table/Table.js +2 -2
  54. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  55. package/dist/components/data-display/Table/TableFilter.js +21 -18
  56. package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
  57. package/dist/components/effects/Overlay/Overlay.js +4 -9
  58. package/dist/components/effects/PageTransition/PageTransition.js +17 -6
  59. package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
  60. package/dist/components/forms/Checkbox/Checkbox.js +11 -1
  61. package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
  62. package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
  63. package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
  64. package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
  65. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
  66. package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
  67. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
  68. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
  69. package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
  70. package/dist/components/forms/ColorPicker/index.d.ts +1 -1
  71. package/dist/components/forms/Combobox/Combobox.js +20 -45
  72. package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
  73. package/dist/components/forms/CronInput/CronInput.js +10 -4
  74. package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
  75. package/dist/components/forms/DatePicker/DatePicker.js +2 -2
  76. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
  77. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
  78. package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
  79. package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
  80. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
  81. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
  82. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
  83. package/dist/components/forms/FileUpload/FileUpload.js +14 -10
  84. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
  85. package/dist/components/forms/FormLabel/FormLabel.js +2 -2
  86. package/dist/components/forms/Input/Input.js +5 -3
  87. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  88. package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
  89. package/dist/components/forms/InputAddress/InputAddress.js +8 -9
  90. package/dist/components/forms/InputCounter/InputCounter.js +25 -18
  91. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
  92. package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
  93. package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
  94. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
  95. package/dist/components/forms/InputOTP/InputOTP.js +26 -17
  96. package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
  97. package/dist/components/forms/InputTag/InputTag.js +12 -39
  98. package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
  99. package/dist/components/forms/Mention/Mention.js +13 -6
  100. package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
  101. package/dist/components/forms/Radio/Radio.js +12 -2
  102. package/dist/components/forms/Radio/Radio.theme.js +5 -1
  103. package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
  104. package/dist/components/forms/Radio/RadioGroup.js +8 -3
  105. package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
  106. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
  107. package/dist/components/forms/Select/Select.d.ts +2 -0
  108. package/dist/components/forms/Select/Select.js +26 -56
  109. package/dist/components/forms/Select/Select.types.d.ts +14 -0
  110. package/dist/components/forms/Signature/Signature.js +1 -1
  111. package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
  112. package/dist/components/forms/Slider/Slider.js +18 -4
  113. package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
  114. package/dist/components/forms/Switch/Switch.js +12 -19
  115. package/dist/components/forms/Switch/Switch.theme.js +12 -2
  116. package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
  117. package/dist/components/forms/Textarea/Textarea.js +8 -3
  118. package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
  119. package/dist/components/forms/TimePicker/TimePicker.js +1 -1
  120. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
  121. package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
  122. package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
  123. package/dist/components/forms/index.js +1 -1
  124. package/dist/components/layout/index.js +4 -0
  125. package/dist/hooks/index.d.ts +1 -0
  126. package/dist/hooks/useClickOutside.d.ts +10 -4
  127. package/dist/hooks/useClickOutside.js +44 -24
  128. package/dist/hooks/useDarkMode.d.ts +1 -0
  129. package/dist/hooks/useDarkMode.js +42 -0
  130. package/dist/hooks/useFormStateMachine.d.ts +1 -1
  131. package/dist/hooks/useNetworkStatus.js +1 -1
  132. package/dist/hooks/useOverlay.js +4 -0
  133. package/dist/hooks/useScaleBackground.js +5 -2
  134. package/dist/hooks/useSelectPortal.js +14 -5
  135. package/dist/index.d.ts +0 -2
  136. package/dist/index.js +1 -57
  137. package/dist/shadcn-compat.css +80 -0
  138. package/dist/styles.css +1 -1
  139. package/dist/utils/stateMachine.d.ts +4 -4
  140. package/package.json +11 -10
  141. package/tailwind.preset.cjs +32 -3
  142. /package/{src/theme-plugin.css → dist/theme.css} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import React__default, { useCallback, useState, useMemo } from 'react';
2
+ import React__default, { useCallback, useState, useRef, useMemo } from 'react';
3
3
  import { motion, AnimatePresence, useReducedMotion } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { treeTheme } from './Tree.theme.js';
@@ -7,6 +7,32 @@ import { Checkbox } from '../../forms/Checkbox/Checkbox.js';
7
7
  import { ChevronRightIcon, DocumentIcon } from '@heroicons/react/24/outline';
8
8
  import { getReducedMotionVariants, treeNodeVariants, expandIconVariants, childNodeVariants, childrenStagger } from './Tree.animations.js';
9
9
 
10
+ // Build a flat ordered list of visible (non-collapsed) node ids via depth-first traversal.
11
+ // Disabled nodes are included in the list so that ArrowDown/Up skip over them in the
12
+ // keyboard handler rather than here — keeping the traversal pure.
13
+ const buildVisibleNodeList = (nodes, expandedSet) => {
14
+ const result = [];
15
+ const traverse = (currentNodes) => {
16
+ for (const node of currentNodes) {
17
+ result.push(node.id);
18
+ if (node.children && node.children.length > 0 && expandedSet.has(node.id)) {
19
+ traverse(node.children);
20
+ }
21
+ }
22
+ };
23
+ traverse(nodes);
24
+ return result;
25
+ };
26
+ // Build a map from node id → parent node id for ArrowLeft "move to parent" navigation.
27
+ const buildParentMap = (nodes, parentId = null, acc = new Map()) => {
28
+ for (const node of nodes) {
29
+ acc.set(node.id, parentId);
30
+ if (node.children && node.children.length > 0) {
31
+ buildParentMap(node.children, node.id, acc);
32
+ }
33
+ }
34
+ return acc;
35
+ };
10
36
  // Simplified selection utilities (merged from Tree.utils.ts)
11
37
  const getDescendantIds = (node) => {
12
38
  const descendants = [];
@@ -72,7 +98,7 @@ const calculateSelection = (nodeId, selectedKeys, data) => {
72
98
  indeterminateKeys
73
99
  };
74
100
  };
75
- const InternalTreeNode = React__default.memo(({ node, level, expanded, selected, isIndeterminate, selectable, checkable, hierarchicalSelection, expandable, expandedKeysSet, selectedKeysSet, indeterminateKeysSet, onNodeClick, onToggleExpand, theme, className, shouldReduceMotion, }) => {
101
+ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected, isIndeterminate, selectable, checkable, hierarchicalSelection, expandable, expandedKeysSet, selectedKeysSet, indeterminateKeysSet, onNodeClick, onToggleExpand, onKeyboardNavigate, nodeRefs, focusedNodeId, onFocusNode, theme, className, shouldReduceMotion, }) => {
76
102
  var _a;
77
103
  const hasChildren = node.children && node.children.length > 0;
78
104
  const isExpandable = expandable && hasChildren;
@@ -80,20 +106,40 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
80
106
  const nodeVariants = shouldReduceMotion ? getReducedMotionVariants(treeNodeVariants) : treeNodeVariants;
81
107
  const iconVariants = shouldReduceMotion ? getReducedMotionVariants(expandIconVariants) : expandIconVariants;
82
108
  const childVariants = shouldReduceMotion ? getReducedMotionVariants(childNodeVariants) : childNodeVariants;
109
+ // Register/unregister this node's DOM element in the shared refs map.
110
+ const itemRef = useCallback((el) => {
111
+ if (el) {
112
+ nodeRefs.current.set(node.id, el);
113
+ }
114
+ else {
115
+ nodeRefs.current.delete(node.id);
116
+ }
117
+ }, [node.id, nodeRefs]);
118
+ // Roving tabIndex: only the focused node is reachable via Tab.
119
+ // If no node is focused yet, the first root node (level 0) gets tabIndex 0 as
120
+ // a sensible initial entry point — this is handled at the Tree root.
121
+ const isFocused = focusedNodeId === node.id;
122
+ const tabIndexValue = node.disabled ? -1 : (isFocused ? 0 : -1);
83
123
  const handleNodeClick = useCallback((e) => {
84
124
  e.stopPropagation();
85
125
  if (node.disabled)
86
126
  return;
127
+ onFocusNode(node.id);
87
128
  if (selectable) {
88
129
  onNodeClick(node);
89
130
  }
90
- }, [node, selectable, onNodeClick]);
131
+ }, [node, selectable, onNodeClick, onFocusNode]);
91
132
  const handleExpandClick = useCallback((e) => {
92
133
  e.stopPropagation();
93
134
  if (isExpandable && !node.disabled) {
94
135
  onToggleExpand(node);
95
136
  }
96
137
  }, [isExpandable, node, onToggleExpand]);
138
+ const handleFocus = useCallback(() => {
139
+ if (!node.disabled) {
140
+ onFocusNode(node.id);
141
+ }
142
+ }, [node.id, node.disabled, onFocusNode]);
97
143
  const handleKeyDown = (e) => {
98
144
  if (node.disabled)
99
145
  return;
@@ -106,16 +152,43 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
106
152
  }
107
153
  break;
108
154
  case 'ArrowRight':
155
+ e.preventDefault();
109
156
  if (isExpandable && !expanded) {
110
- e.preventDefault();
157
+ // Collapsed node with children: expand it
111
158
  onToggleExpand(node);
112
159
  }
160
+ else if (isExpandable && expanded) {
161
+ // Expanded node with children: move focus to first child
162
+ onKeyboardNavigate(node.id, 'ArrowRight');
163
+ }
164
+ // Leaf node: do nothing (APG spec)
113
165
  break;
114
166
  case 'ArrowLeft':
167
+ e.preventDefault();
115
168
  if (isExpandable && expanded) {
116
- e.preventDefault();
169
+ // Expanded node: collapse it
117
170
  onToggleExpand(node);
118
171
  }
172
+ else {
173
+ // Collapsed node or leaf: move to parent
174
+ onKeyboardNavigate(node.id, 'ArrowLeft');
175
+ }
176
+ break;
177
+ case 'ArrowDown':
178
+ e.preventDefault();
179
+ onKeyboardNavigate(node.id, 'ArrowDown');
180
+ break;
181
+ case 'ArrowUp':
182
+ e.preventDefault();
183
+ onKeyboardNavigate(node.id, 'ArrowUp');
184
+ break;
185
+ case 'Home':
186
+ e.preventDefault();
187
+ onKeyboardNavigate(node.id, 'Home');
188
+ break;
189
+ case 'End':
190
+ e.preventDefault();
191
+ onKeyboardNavigate(node.id, 'End');
119
192
  break;
120
193
  }
121
194
  };
@@ -126,10 +199,10 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
126
199
  const labelStyle = cn(theme.labelStyle, {
127
200
  'text-[var(--color-text-muted)]': node.disabled,
128
201
  });
129
- return (jsxs("div", { children: [jsxs("div", { className: nodeStyle, style: { paddingLeft: `calc(${level} * ${theme.indentSize} + 0.25rem)` }, onClick: handleNodeClick, onKeyDown: handleKeyDown, tabIndex: node.disabled ? -1 : 0, role: "treeitem", "aria-expanded": isExpandable ? expanded : false, "aria-selected": selectable ? selected : undefined, "aria-disabled": node.disabled, children: [isExpandable ? (jsx(motion.span, { className: cn(theme.expandIconStyle), variants: iconVariants, initial: "collapsed", animate: expanded ? "expanded" : "collapsed", onClick: handleExpandClick, role: "button", "aria-label": expanded ? 'Collapse' : 'Expand', tabIndex: -1, children: jsx(ChevronRightIcon, { className: "size-4", strokeWidth: 2 }) })) : (jsx("span", { className: "w-4 mr-1" })), checkable && (jsx(Checkbox, { size: 'sm', id: `tree-checkbox-${node.id}`, name: `tree-checkbox-${node.id}`, value: node.id, checked: selected, indeterminate: hierarchicalSelection && isIndeterminate, onChange: () => onNodeClick(node), state: node.disabled ? "disabled" : "default", className: "flex-shrink-0", onClick: (e) => e.stopPropagation() })), (node.icon || !isExpandable) && (jsx("span", { className: cn(theme.iconStyle), children: node.icon || jsx(DocumentIcon, { className: "w-4 h-4" }) })), jsx("span", { className: labelStyle, title: node.label, children: node.label })] }), hasChildren && (jsx(AnimatePresence, { initial: false, children: expanded && (jsx(motion.div, { variants: nodeVariants, initial: "collapsed", animate: "expanded", exit: "collapsed", style: {
202
+ return (jsxs("div", { children: [jsxs("div", { ref: itemRef, className: nodeStyle, style: { paddingLeft: `calc(${level} * ${theme.indentSize} + 0.25rem)` }, onClick: handleNodeClick, onKeyDown: handleKeyDown, onFocus: handleFocus, tabIndex: tabIndexValue, role: "treeitem", "aria-expanded": isExpandable ? expanded : undefined, "aria-selected": selectable ? selected : undefined, "aria-disabled": node.disabled, children: [isExpandable ? (jsx(motion.span, { className: cn(theme.expandIconStyle), variants: iconVariants, initial: "collapsed", animate: expanded ? "expanded" : "collapsed", onClick: handleExpandClick, "aria-label": expanded ? 'Collapse' : 'Expand', tabIndex: -1, children: jsx(ChevronRightIcon, { className: "size-4", strokeWidth: 2 }) })) : (jsx("span", { className: "w-4 mr-1" })), checkable && (jsx(Checkbox, { size: 'sm', id: `tree-checkbox-${node.id}`, name: `tree-checkbox-${node.id}`, value: node.id, checked: selected, indeterminate: hierarchicalSelection && isIndeterminate, onChange: () => onNodeClick(node), state: node.disabled ? "disabled" : "default", className: "flex-shrink-0", onClick: (e) => e.stopPropagation() })), (node.icon || !isExpandable) && (jsx("span", { className: cn(theme.iconStyle), children: node.icon || jsx(DocumentIcon, { className: "w-4 h-4" }) })), jsx("span", { className: labelStyle, title: node.label, children: node.label })] }), hasChildren && (jsx(AnimatePresence, { initial: false, children: expanded && (jsx(motion.div, { variants: nodeVariants, initial: "collapsed", animate: "expanded", exit: "collapsed", style: {
130
203
  overflow: 'hidden',
131
204
  transformOrigin: 'top'
132
- }, transition: shouldReduceMotion ? { duration: 0.01 } : childrenStagger, children: (_a = node.children) === null || _a === void 0 ? void 0 : _a.map((childNode, index) => (jsx(motion.div, { variants: childVariants, initial: "hidden", animate: "visible", exit: "hidden", custom: index, children: jsx(InternalTreeNode, { node: childNode, level: level + 1, expanded: expandedKeysSet.has(childNode.id), selected: selectedKeysSet.has(childNode.id), isIndeterminate: indeterminateKeysSet.has(childNode.id), selectable: selectable, checkable: checkable, hierarchicalSelection: hierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: onNodeClick, onToggleExpand: onToggleExpand, theme: theme, shouldReduceMotion: shouldReduceMotion }) }, childNode.id))) }, `${node.id}-children`)) }))] }));
205
+ }, transition: shouldReduceMotion ? { duration: 0.01 } : childrenStagger, children: (_a = node.children) === null || _a === void 0 ? void 0 : _a.map((childNode, index) => (jsx(motion.div, { variants: childVariants, initial: "hidden", animate: "visible", exit: "hidden", custom: index, children: jsx(InternalTreeNode, { node: childNode, level: level + 1, expanded: expandedKeysSet.has(childNode.id), selected: selectedKeysSet.has(childNode.id), isIndeterminate: indeterminateKeysSet.has(childNode.id), selectable: selectable, checkable: checkable, hierarchicalSelection: hierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: onNodeClick, onToggleExpand: onToggleExpand, onKeyboardNavigate: onKeyboardNavigate, nodeRefs: nodeRefs, focusedNodeId: focusedNodeId, onFocusNode: onFocusNode, theme: theme, shouldReduceMotion: shouldReduceMotion }) }, childNode.id))) }, `${node.id}-children`)) }))] }));
133
206
  });
134
207
  const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkable = false, hierarchicalSelection, expandable = true, defaultExpandedKeys = [], defaultSelectedKeys = [], onSelectionChange, onExpand, className, theme: customTheme = {}, ...htmlProps }, ref) => {
135
208
  var _a;
@@ -144,9 +217,106 @@ const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkabl
144
217
  const [expandedKeys, setExpandedKeys] = useState(defaultExpandedKeys);
145
218
  const [selectedKeys, setSelectedKeys] = useState(defaultSelectedKeys);
146
219
  const [indeterminateKeys, setIndeterminateKeys] = useState([]);
220
+ // Roving tabIndex: track which node currently owns tabIndex=0.
221
+ // Initialise to the first root node (if any) so the tree is keyboard-enterable.
222
+ const [focusedNodeId, setFocusedNodeId] = useState(data.length > 0 ? data[0].id : null);
223
+ // Map from node id → DOM element, populated by each InternalTreeNode on mount.
224
+ const nodeRefs = useRef(new Map());
147
225
  const expandedKeysSet = useMemo(() => new Set(expandedKeys), [expandedKeys]);
148
226
  const selectedKeysSet = useMemo(() => new Set(selectedKeys), [selectedKeys]);
149
227
  const indeterminateKeysSet = useMemo(() => new Set(indeterminateKeys), [indeterminateKeys]);
228
+ // Parent map for ArrowLeft navigation (move to parent node).
229
+ const parentMap = useMemo(() => buildParentMap(data), [data]);
230
+ // Called by InternalTreeNode when it receives focus (mouse or keyboard).
231
+ const handleFocusNode = useCallback((nodeId) => {
232
+ setFocusedNodeId(nodeId);
233
+ }, []);
234
+ // Central keyboard navigation handler: moves focus within the tree.
235
+ const handleKeyboardNavigate = useCallback((nodeId, key) => {
236
+ const visibleIds = buildVisibleNodeList(data, expandedKeysSet);
237
+ const focusById = (targetId) => {
238
+ const el = nodeRefs.current.get(targetId);
239
+ if (el) {
240
+ setFocusedNodeId(targetId);
241
+ el.focus();
242
+ }
243
+ };
244
+ const currentIndex = visibleIds.indexOf(nodeId);
245
+ switch (key) {
246
+ case 'ArrowDown': {
247
+ // Move to next visible node, skipping disabled ones.
248
+ for (let i = currentIndex + 1; i < visibleIds.length; i++) {
249
+ const candidateId = visibleIds[i];
250
+ const candidateEl = nodeRefs.current.get(candidateId);
251
+ if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
252
+ focusById(candidateId);
253
+ break;
254
+ }
255
+ }
256
+ break;
257
+ }
258
+ case 'ArrowUp': {
259
+ // Move to previous visible node, skipping disabled ones.
260
+ for (let i = currentIndex - 1; i >= 0; i--) {
261
+ const candidateId = visibleIds[i];
262
+ const candidateEl = nodeRefs.current.get(candidateId);
263
+ if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
264
+ focusById(candidateId);
265
+ break;
266
+ }
267
+ }
268
+ break;
269
+ }
270
+ case 'Home': {
271
+ // Move to the first non-disabled visible node.
272
+ for (let i = 0; i < visibleIds.length; i++) {
273
+ const candidateId = visibleIds[i];
274
+ const candidateEl = nodeRefs.current.get(candidateId);
275
+ if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
276
+ focusById(candidateId);
277
+ break;
278
+ }
279
+ }
280
+ break;
281
+ }
282
+ case 'End': {
283
+ // Move to the last non-disabled visible node.
284
+ for (let i = visibleIds.length - 1; i >= 0; i--) {
285
+ const candidateId = visibleIds[i];
286
+ const candidateEl = nodeRefs.current.get(candidateId);
287
+ if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
288
+ focusById(candidateId);
289
+ break;
290
+ }
291
+ }
292
+ break;
293
+ }
294
+ case 'ArrowRight': {
295
+ // Move focus to the first child of an already-expanded node.
296
+ // (Expand-when-collapsed is handled directly in InternalTreeNode.)
297
+ const firstChildIndex = currentIndex + 1;
298
+ if (firstChildIndex < visibleIds.length) {
299
+ const firstChildId = visibleIds[firstChildIndex];
300
+ const firstChildEl = nodeRefs.current.get(firstChildId);
301
+ if (firstChildEl && firstChildEl.getAttribute('aria-disabled') !== 'true') {
302
+ focusById(firstChildId);
303
+ }
304
+ }
305
+ break;
306
+ }
307
+ case 'ArrowLeft': {
308
+ // Move focus to the parent node (when current node is collapsed or a leaf).
309
+ const parentId = parentMap.get(nodeId);
310
+ if (parentId) {
311
+ const parentEl = nodeRefs.current.get(parentId);
312
+ if (parentEl && parentEl.getAttribute('aria-disabled') !== 'true') {
313
+ focusById(parentId);
314
+ }
315
+ }
316
+ break;
317
+ }
318
+ }
319
+ }, [data, expandedKeysSet, parentMap]);
150
320
  // Handle node selection
151
321
  const handleNodeSelect = useCallback((node) => {
152
322
  if (!selectable || node.disabled)
@@ -194,7 +364,7 @@ const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkabl
194
364
  setExpandedKeys(newExpandedKeys);
195
365
  onExpand === null || onExpand === void 0 ? void 0 : onExpand(newExpandedKeys, node, !isExpanded);
196
366
  }, [expandedKeys, expandable, onExpand]);
197
- return (jsx("div", { ref: ref, className: cn(theme.baseStyle, 'tree-container', className), role: "tree", "aria-multiselectable": checkable, ...htmlProps, children: data.map((node) => (jsx(InternalTreeNode, { node: node, level: 0, expanded: expandedKeysSet.has(node.id), selected: selectedKeysSet.has(node.id), isIndeterminate: indeterminateKeysSet.has(node.id), selectable: selectable, checkable: checkable, hierarchicalSelection: enableHierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: handleNodeSelect, onToggleExpand: handleNodeToggleExpand, theme: theme, shouldReduceMotion: shouldReduceMotion }, node.id))) }));
367
+ return (jsx("div", { ref: ref, className: cn(theme.baseStyle, 'tree-container', className), role: "tree", "aria-multiselectable": checkable, ...htmlProps, children: data.map((node) => (jsx(InternalTreeNode, { node: node, level: 0, expanded: expandedKeysSet.has(node.id), selected: selectedKeysSet.has(node.id), isIndeterminate: indeterminateKeysSet.has(node.id), selectable: selectable, checkable: checkable, hierarchicalSelection: enableHierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: handleNodeSelect, onToggleExpand: handleNodeToggleExpand, onKeyboardNavigate: handleKeyboardNavigate, nodeRefs: nodeRefs, focusedNodeId: focusedNodeId, onFocusNode: handleFocusNode, theme: theme, shouldReduceMotion: shouldReduceMotion }, node.id))) }));
198
368
  });
199
369
  TreeRoot.displayName = 'Tree';
200
370
  // Simplified compound component
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { useRef, useCallback } from 'react';
2
+ import { useRef, useEffect, useCallback } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { useGanttContext } from './GanttChart.js';
5
5
 
@@ -10,6 +10,18 @@ const GanttSplitter = () => {
10
10
  const { theme, tablePanelWidth, setTablePanelWidth } = useGanttContext();
11
11
  const startX = useRef(0);
12
12
  const startWidth = useRef(0);
13
+ // Track active pointer listeners so they can be cleaned up on unmount during a drag
14
+ const activeListenersRef = useRef({ move: null, up: null });
15
+ // Cleanup on unmount — removes any lingering listeners if component unmounts during a drag
16
+ useEffect(() => {
17
+ return () => {
18
+ const { move, up } = activeListenersRef.current;
19
+ if (move)
20
+ document.removeEventListener("pointermove", move);
21
+ if (up)
22
+ document.removeEventListener("pointerup", up);
23
+ };
24
+ }, []);
13
25
  const onPointerDown = useCallback((e) => {
14
26
  e.preventDefault();
15
27
  startX.current = e.clientX;
@@ -20,9 +32,11 @@ const GanttSplitter = () => {
20
32
  setTablePanelWidth(next);
21
33
  };
22
34
  const onPointerUp = () => {
35
+ activeListenersRef.current = { move: null, up: null };
23
36
  document.removeEventListener("pointermove", onPointerMove);
24
37
  document.removeEventListener("pointerup", onPointerUp);
25
38
  };
39
+ activeListenersRef.current = { move: onPointerMove, up: onPointerUp };
26
40
  document.addEventListener("pointermove", onPointerMove);
27
41
  document.addEventListener("pointerup", onPointerUp);
28
42
  }, [tablePanelWidth, setTablePanelWidth]);
@@ -38,7 +38,7 @@ const TableContext = createContext({
38
38
  */
39
39
  function TableComponent(props) {
40
40
  var _a;
41
- const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, } = props;
41
+ const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, caption, } = props;
42
42
  // Column management hook
43
43
  const { visibleColumns, columnOrder, visibleColumnsList, handleToggleColumn, handleColumnReorder, handleResetToDefault, } = useTableColumns({
44
44
  columns,
@@ -148,7 +148,7 @@ function TableComponent(props) {
148
148
  selectedRows,
149
149
  onSelectionChange,
150
150
  ]);
151
- return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsx("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] })) }) })] }) }));
151
+ return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsxs("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: [caption && (jsx("caption", { className: "sr-only", children: caption })), isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] }))] }) })] }) }));
152
152
  }
153
153
  TableComponent.displayName = "Table";
154
154
  const Table = TableComponent;
@@ -193,6 +193,8 @@ export interface TableProps<T = unknown> {
193
193
  totalItems: number;
194
194
  };
195
195
  children?: ReactNode;
196
+ /** Optional accessible caption for the table, rendered as a visually-hidden <caption> element */
197
+ caption?: string;
196
198
  }
197
199
  export interface TableHeaderProps {
198
200
  children?: ReactNode;
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useState, useCallback } from 'react';
3
- import { inferFilterType, getDefaultOperator, isUnaryOperator, getOperatorsForFilterType, isBetweenOperator } from './Table.filterUtils.js';
2
+ import { useRef, useState, useCallback } from 'react';
3
+ import { inferFilterType, getDefaultOperator, isUnaryOperator, isBetweenOperator, getOperatorsForFilterType } from './Table.filterUtils.js';
4
4
  import { Button } from '../../core/Button/Button.js';
5
5
  import { Input } from '../../forms/Input/Input.js';
6
6
  import '../../forms/Input/Input.theme.js';
@@ -57,6 +57,7 @@ function FilterValueInput({ filter, column, filterType, onValueChange, }) {
57
57
  }
58
58
  function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: controlledLogic, onFilterLogicChange, data, className, }) {
59
59
  const filterableColumns = columns.filter((col) => col.filterable);
60
+ const filterIdRef = useRef(0);
60
61
  // Internal filter logic state
61
62
  const [internalLogic, setInternalLogic] = useState("and");
62
63
  const filterLogic = controlledLogic !== null && controlledLogic !== void 0 ? controlledLogic : internalLogic;
@@ -87,7 +88,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
87
88
  const resolvedType = inferFilterType(defaultColumn, data);
88
89
  return [
89
90
  {
90
- id: `filter-${Date.now()}`,
91
+ id: `filter-${++filterIdRef.current}`,
91
92
  columnId: defaultColumn.id,
92
93
  operator: getDefaultOperator(resolvedType),
93
94
  value: "",
@@ -107,7 +108,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
107
108
  return "text";
108
109
  return inferFilterType(column, data);
109
110
  };
110
- const syncFilters = (filters) => {
111
+ const syncFilters = useCallback((filters) => {
111
112
  const newFilterConfig = filters
112
113
  .filter((f) => {
113
114
  if (!f.columnId)
@@ -131,8 +132,8 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
131
132
  operator: f.operator,
132
133
  }));
133
134
  onFilterChange(newFilterConfig);
134
- };
135
- const handleAddFilter = () => {
135
+ }, [onFilterChange]);
136
+ const handleAddFilter = useCallback(() => {
136
137
  const defaultColumn = filterableColumns[0];
137
138
  if (!defaultColumn)
138
139
  return;
@@ -141,21 +142,22 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
141
142
  setActiveFilters((prev) => [
142
143
  ...prev,
143
144
  {
144
- id: `filter-${Date.now()}`,
145
+ id: `filter-${++filterIdRef.current}`,
145
146
  columnId: defaultColumn.id,
146
147
  operator: defaultOp,
147
148
  value: "",
148
149
  },
149
150
  ]);
150
- };
151
- const handleRemoveFilter = (filterId) => {
151
+ // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ }, [filterableColumns, onFilterChange]);
153
+ const handleRemoveFilter = useCallback((filterId) => {
152
154
  setActiveFilters((prev) => {
153
155
  const updated = prev.filter((f) => f.id !== filterId);
154
156
  syncFilters(updated);
155
157
  return updated;
156
158
  });
157
- };
158
- const handleFilterColumnChange = (filterId, columnId) => {
159
+ }, [syncFilters]);
160
+ const handleFilterColumnChange = useCallback((filterId, columnId) => {
159
161
  setActiveFilters((prev) => {
160
162
  const updated = prev.map((f) => {
161
163
  if (f.id !== filterId)
@@ -168,8 +170,9 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
168
170
  syncFilters(updated);
169
171
  return updated;
170
172
  });
171
- };
172
- const handleFilterOperatorChange = (filterId, operator) => {
173
+ // eslint-disable-next-line react-hooks/exhaustive-deps
174
+ }, [filterableColumns, data, syncFilters]);
175
+ const handleFilterOperatorChange = useCallback((filterId, operator) => {
173
176
  setActiveFilters((prev) => {
174
177
  const updated = prev.map((f) => {
175
178
  if (f.id !== filterId)
@@ -188,18 +191,18 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
188
191
  syncFilters(updated);
189
192
  return updated;
190
193
  });
191
- };
192
- const handleFilterValueChange = (filterId, value) => {
194
+ }, [syncFilters]);
195
+ const handleFilterValueChange = useCallback((filterId, value) => {
193
196
  setActiveFilters((prev) => {
194
197
  const updated = prev.map((f) => f.id === filterId ? { ...f, value } : f);
195
198
  syncFilters(updated);
196
199
  return updated;
197
200
  });
198
- };
199
- const handleClearAll = () => {
201
+ }, [syncFilters]);
202
+ const handleClearAll = useCallback(() => {
200
203
  setActiveFilters([]);
201
204
  onFilterChange([]);
202
- };
205
+ }, [onFilterChange]);
203
206
  const appliedFilterCount = activeFilters.filter((f) => {
204
207
  if (!f.columnId)
205
208
  return false;
@@ -204,13 +204,9 @@ const CustomCursorFollow = React.forwardRef(({ children, sideOffset = 15, align
204
204
  const { cursorPos, isActive, cursorRef } = useCustomCursor();
205
205
  const cursorFollowRef = React.useRef(null);
206
206
  const shouldReduceMotion = useReducedMotion();
207
- // Select random color for user variant if not provided (stable across renders)
208
- const resolvedColor = React.useMemo(() => {
209
- if (color)
210
- return color;
211
- const randomIndex = Math.floor(Math.random() * userVariantColors.length);
212
- return userVariantColors[randomIndex];
213
- }, [color]);
207
+ // Select random color for user variant if not provided (stable across renders via ref)
208
+ const randomColorRef = React.useRef(userVariantColors[Math.floor(Math.random() * userVariantColors.length)]);
209
+ const resolvedColor = color !== null && color !== void 0 ? color : randomColorRef.current;
214
210
  React.useImperativeHandle(ref, () => cursorFollowRef.current);
215
211
  const x = useMotionValue(0);
216
212
  const y = useMotionValue(0);
@@ -1,11 +1,12 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { useRef, useCallback, useEffect, useMemo } from 'react';
2
+ import React__default, { useRef, useCallback, useEffect } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
5
5
  import { cn } from '../../../utils/cn.js';
6
6
  import { useOverlay, lockScroll, unlockScroll } from '../../../hooks/useOverlay.js';
7
7
  import { useFocusTrap } from '../../../hooks/useFocusTrap.js';
8
8
  import { useIsClient } from '../../../hooks/useIsClient.js';
9
+ import { useDarkMode } from '../../../hooks/useDarkMode.js';
9
10
  import { overlayTheme, overlayPositionVariants } from './Overlay.theme.js';
10
11
 
11
12
  /**
@@ -138,14 +139,8 @@ const Overlay = React__default.forwardRef(({ isOpen, onClose, closeOnOverlayClic
138
139
  };
139
140
  }
140
141
  }
141
- // Detect dark mode from document (memoized to avoid DOM traversal on every animation frame)
142
- const isDarkMode = useMemo(() => {
143
- if (!isClient)
144
- return false;
145
- return (document.documentElement.classList.contains("dark") ||
146
- document.body.classList.contains("dark") ||
147
- document.querySelector(".dark") !== null);
148
- }, [isClient]);
142
+ // Detect dark mode reactively via a shared MutationObserver-backed hook (SSR-safe)
143
+ const isDarkMode = useDarkMode();
149
144
  // Determine portal target
150
145
  const target = portalTarget !== undefined
151
146
  ? portalTarget
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useRef, useEffect } from 'react';
3
3
  import { motion } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } from './PageTransition.animations.js';
@@ -7,7 +7,14 @@ import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } f
7
7
  const PageTransition = ({ children, logo, type = "slide", direction = "up", duration = 0.8, delay = 0.8, color = "bg-[var(--color-primary)]", className, parallax = true, onStart, onComplete, }) => {
8
8
  const [phase, setPhase] = useState("covered");
9
9
  const [completedPanels, setCompletedPanels] = useState(0);
10
+ // Stable refs for callbacks to avoid stale closures without re-running the effect
11
+ const onStartRef = useRef(onStart);
12
+ const onCompleteRef = useRef(onComplete);
13
+ useEffect(() => { onStartRef.current = onStart; }, [onStart]);
14
+ useEffect(() => { onCompleteRef.current = onComplete; }, [onComplete]);
10
15
  useEffect(() => {
16
+ if (typeof window === "undefined" || typeof document === "undefined")
17
+ return;
11
18
  let cancelled = false;
12
19
  const loadPromise = new Promise((resolve) => {
13
20
  if (document.readyState === "complete") {
@@ -24,8 +31,9 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
24
31
  if (!cancelled) {
25
32
  setPhase("exit");
26
33
  setTimeout(() => {
34
+ var _a;
27
35
  if (!cancelled)
28
- onStart === null || onStart === void 0 ? void 0 : onStart();
36
+ (_a = onStartRef.current) === null || _a === void 0 ? void 0 : _a.call(onStartRef);
29
37
  }, 250);
30
38
  }
31
39
  });
@@ -34,10 +42,11 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
34
42
  };
35
43
  }, [delay]);
36
44
  useEffect(() => {
45
+ var _a;
37
46
  if (type === "split" && completedPanels === 2) {
38
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
47
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
39
48
  }
40
- }, [completedPanels, type, onComplete]);
49
+ }, [completedPanels, type]);
41
50
  if (type === "split") {
42
51
  const axis = direction === "up" || direction === "down" ? "h" : "v";
43
52
  const splitVars = buildSplitVariants(axis, duration);
@@ -58,13 +67,15 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
58
67
  if (type === "slide" && parallax) {
59
68
  const logoVariants = buildLogoParallaxVariants(direction, duration);
60
69
  return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
70
+ var _a;
61
71
  if (def === "exit")
62
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
72
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
63
73
  }, children: logo && (jsx(motion.div, { className: "relative z-10 select-none", variants: logoVariants, initial: "covered", animate: phase, children: logo })) })] }));
64
74
  }
65
75
  return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
76
+ var _a;
66
77
  if (def === "exit")
67
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
78
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
68
79
  }, children: logo && jsx("div", { className: "relative z-10 select-none", children: logo }) })] }));
69
80
  };
70
81
 
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { useRef, useEffect } from 'react';
3
+ import { useReducedMotion } from 'motion/react';
3
4
  import * as THREE from 'three';
4
5
  import { cn } from '../../../utils/cn.js';
5
6
  import { variationMap } from './SpotlightBorder.types.js';
@@ -21,12 +22,10 @@ import { fragmentShader, vertexShader } from './SpotlightBorder.shaders.js';
21
22
  * ```
22
23
  */
23
24
  const SpotlightBorder = ({ children, variation = "rounded-rect", rounded, borderWidth = 2, color = "#ffffff", spotlightSize = 0.4, spotlightEdge = 0.5, glowExtent = 20, glowIntensity = 0.6, tiltIntensity = 5, shouldAnimate = true, className, style, ...props }) => {
24
- var _a;
25
25
  const outerRef = useRef(null);
26
26
  const containerRef = useRef(null);
27
- // Check for reduced motion preference
28
- const prefersReducedMotion = typeof window !== "undefined" &&
29
- ((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches);
27
+ // useReducedMotion is SSR-safe (returns null on server, actual value after hydration)
28
+ const prefersReducedMotion = useReducedMotion();
30
29
  const isAnimating = shouldAnimate && !prefersReducedMotion;
31
30
  // Refs to store THREE.js objects across effects
32
31
  const rendererRef = useRef(null);
@@ -22,6 +22,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
22
22
  const size = (_c = groupContext === null || groupContext === void 0 ? void 0 : groupContext.size) !== null && _c !== void 0 ? _c : sizeProp;
23
23
  const state = stateProp === "disabled" ? "disabled" : ((_d = groupContext === null || groupContext === void 0 ? void 0 : groupContext.state) !== null && _d !== void 0 ? _d : stateProp);
24
24
  const isDisabled = state === "disabled";
25
+ const isInvalid = state === "invalid";
25
26
  // Determine checked state
26
27
  const isInGroup = groupContext !== undefined;
27
28
  const [internalChecked, setInternalChecked] = useState(false);
@@ -42,6 +43,15 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
42
43
  inputRef.current.indeterminate = indeterminate;
43
44
  }
44
45
  }, [indeterminate]);
46
+ // Dev-only warning when no accessible name is provided
47
+ useEffect(() => {
48
+ if (process.env.NODE_ENV !== 'production') {
49
+ if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
50
+ console.warn('[Checkbox]: No accessible name provided. Add a `label` prop, `aria-label`, or `aria-labelledby`.');
51
+ }
52
+ }
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, []);
45
55
  const handleChange = (e) => {
46
56
  if (isDisabled)
47
57
  return;
@@ -59,7 +69,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
59
69
  ref(node);
60
70
  else if (ref)
61
71
  ref.current = node;
62
- }, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
72
+ }, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "aria-invalid": isInvalid || undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
63
73
  });
64
74
  CheckboxComponent.displayName = "Checkbox";
65
75
  const Checkbox = Object.assign(CheckboxComponent, {