@proyecto-viviana/solidaria-components 0.3.2 → 0.4.0

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 (398) hide show
  1. package/dist/ActionBar.js +184 -0
  2. package/dist/ActionBar.js.map +1 -0
  3. package/dist/ActionBar.jsx +128 -0
  4. package/dist/ActionBar.jsx.map +1 -0
  5. package/dist/ActionGroup.js +179 -0
  6. package/dist/ActionGroup.js.map +1 -0
  7. package/dist/ActionGroup.jsx +129 -0
  8. package/dist/ActionGroup.jsx.map +1 -0
  9. package/dist/Alert.js +111 -0
  10. package/dist/Alert.js.map +1 -0
  11. package/dist/Alert.jsx +82 -0
  12. package/dist/Alert.jsx.map +1 -0
  13. package/dist/Autocomplete.js +130 -0
  14. package/dist/Autocomplete.js.map +1 -0
  15. package/dist/Autocomplete.jsx +116 -0
  16. package/dist/Autocomplete.jsx.map +1 -0
  17. package/dist/Breadcrumbs.d.ts.map +1 -1
  18. package/dist/Breadcrumbs.js +334 -0
  19. package/dist/Breadcrumbs.js.map +1 -0
  20. package/dist/Breadcrumbs.jsx +266 -0
  21. package/dist/Breadcrumbs.jsx.map +1 -0
  22. package/dist/Button.d.ts.map +1 -1
  23. package/dist/Button.js +348 -0
  24. package/dist/Button.js.map +1 -0
  25. package/dist/Button.jsx +314 -0
  26. package/dist/Button.jsx.map +1 -0
  27. package/dist/Calendar.d.ts +4 -4
  28. package/dist/Calendar.d.ts.map +1 -1
  29. package/dist/Calendar.js +538 -0
  30. package/dist/Calendar.js.map +1 -0
  31. package/dist/Calendar.jsx +333 -0
  32. package/dist/Calendar.jsx.map +1 -0
  33. package/dist/CalendarPicker.d.ts +33 -0
  34. package/dist/CalendarPicker.d.ts.map +1 -0
  35. package/dist/CalendarPicker.js +44 -0
  36. package/dist/CalendarPicker.js.map +1 -0
  37. package/dist/CalendarPicker.jsx +44 -0
  38. package/dist/CalendarPicker.jsx.map +1 -0
  39. package/dist/Checkbox.d.ts +83 -4
  40. package/dist/Checkbox.d.ts.map +1 -1
  41. package/dist/Checkbox.js +923 -0
  42. package/dist/Checkbox.js.map +1 -0
  43. package/dist/Checkbox.jsx +707 -0
  44. package/dist/Checkbox.jsx.map +1 -0
  45. package/dist/Collection.d.ts.map +1 -1
  46. package/dist/Collection.js +202 -0
  47. package/dist/Collection.js.map +1 -0
  48. package/dist/Collection.jsx +152 -0
  49. package/dist/Collection.jsx.map +1 -0
  50. package/dist/Color.d.ts.map +1 -1
  51. package/dist/Color.js +1720 -0
  52. package/dist/Color.js.map +1 -0
  53. package/dist/Color.jsx +1344 -0
  54. package/dist/Color.jsx.map +1 -0
  55. package/dist/ColorEditor.js +208 -0
  56. package/dist/ColorEditor.js.map +1 -0
  57. package/dist/ColorEditor.jsx +120 -0
  58. package/dist/ColorEditor.jsx.map +1 -0
  59. package/dist/ComboBox.d.ts +4 -4
  60. package/dist/ComboBox.d.ts.map +1 -1
  61. package/dist/ComboBox.js +992 -0
  62. package/dist/ComboBox.js.map +1 -0
  63. package/dist/ComboBox.jsx +714 -0
  64. package/dist/ComboBox.jsx.map +1 -0
  65. package/dist/ContextualHelpTrigger.js +146 -0
  66. package/dist/ContextualHelpTrigger.js.map +1 -0
  67. package/dist/ContextualHelpTrigger.jsx +109 -0
  68. package/dist/ContextualHelpTrigger.jsx.map +1 -0
  69. package/dist/DateField.d.ts +3 -3
  70. package/dist/DateField.d.ts.map +1 -1
  71. package/dist/DateField.js +4 -0
  72. package/dist/DateField.jsx +4 -0
  73. package/dist/DateField2.js +499 -0
  74. package/dist/DateField2.js.map +1 -0
  75. package/dist/DateField2.jsx +317 -0
  76. package/dist/DateField2.jsx.map +1 -0
  77. package/dist/DatePicker.d.ts +4 -4
  78. package/dist/DatePicker.d.ts.map +1 -1
  79. package/dist/DatePicker.js +991 -0
  80. package/dist/DatePicker.js.map +1 -0
  81. package/dist/DatePicker.jsx +682 -0
  82. package/dist/DatePicker.jsx.map +1 -0
  83. package/dist/Dialog.d.ts.map +1 -1
  84. package/dist/Dialog.js +337 -0
  85. package/dist/Dialog.js.map +1 -0
  86. package/dist/Dialog.jsx +192 -0
  87. package/dist/Dialog.jsx.map +1 -0
  88. package/dist/Disclosure.d.ts.map +1 -1
  89. package/dist/Disclosure.js +328 -0
  90. package/dist/Disclosure.js.map +1 -0
  91. package/dist/Disclosure.jsx +230 -0
  92. package/dist/Disclosure.jsx.map +1 -0
  93. package/dist/DragAndDrop.d.ts.map +1 -1
  94. package/dist/DragAndDrop.js +176 -0
  95. package/dist/DragAndDrop.js.map +1 -0
  96. package/dist/DragAndDrop.jsx +160 -0
  97. package/dist/DragAndDrop.jsx.map +1 -0
  98. package/dist/DropZone.d.ts.map +1 -1
  99. package/dist/DropZone.js +189 -0
  100. package/dist/DropZone.js.map +1 -0
  101. package/dist/DropZone.jsx +144 -0
  102. package/dist/DropZone.jsx.map +1 -0
  103. package/dist/FieldError.js +66 -0
  104. package/dist/FieldError.js.map +1 -0
  105. package/dist/FieldError.jsx +47 -0
  106. package/dist/FieldError.jsx.map +1 -0
  107. package/dist/FileTrigger.js +79 -0
  108. package/dist/FileTrigger.js.map +1 -0
  109. package/dist/FileTrigger.jsx +49 -0
  110. package/dist/FileTrigger.jsx.map +1 -0
  111. package/dist/Focusable.js +70 -0
  112. package/dist/Focusable.js.map +1 -0
  113. package/dist/Focusable.jsx +69 -0
  114. package/dist/Focusable.jsx.map +1 -0
  115. package/dist/Form.js +99 -0
  116. package/dist/Form.js.map +1 -0
  117. package/dist/Form.jsx +73 -0
  118. package/dist/Form.jsx.map +1 -0
  119. package/dist/GridList.d.ts +34 -2
  120. package/dist/GridList.d.ts.map +1 -1
  121. package/dist/GridList.js +723 -0
  122. package/dist/GridList.js.map +1 -0
  123. package/dist/GridList.jsx +552 -0
  124. package/dist/GridList.jsx.map +1 -0
  125. package/dist/HiddenTimeInput.js +104 -0
  126. package/dist/HiddenTimeInput.js.map +1 -0
  127. package/dist/HiddenTimeInput.jsx +69 -0
  128. package/dist/HiddenTimeInput.jsx.map +1 -0
  129. package/dist/Icon.d.ts.map +1 -1
  130. package/dist/Icon.js +127 -0
  131. package/dist/Icon.js.map +1 -0
  132. package/dist/Icon.jsx +72 -0
  133. package/dist/Icon.jsx.map +1 -0
  134. package/dist/Keyboard.js +30 -0
  135. package/dist/Keyboard.js.map +1 -0
  136. package/dist/Keyboard.jsx +24 -0
  137. package/dist/Keyboard.jsx.map +1 -0
  138. package/dist/Landmark.js +134 -0
  139. package/dist/Landmark.js.map +1 -0
  140. package/dist/Landmark.jsx +118 -0
  141. package/dist/Landmark.jsx.map +1 -0
  142. package/dist/Link.d.ts.map +1 -1
  143. package/dist/Link.js +219 -0
  144. package/dist/Link.js.map +1 -0
  145. package/dist/Link.jsx +189 -0
  146. package/dist/Link.jsx.map +1 -0
  147. package/dist/ListBox.d.ts.map +1 -1
  148. package/dist/ListBox.js +670 -0
  149. package/dist/ListBox.js.map +1 -0
  150. package/dist/ListBox.jsx +508 -0
  151. package/dist/ListBox.jsx.map +1 -0
  152. package/dist/Menu.d.ts +27 -9
  153. package/dist/Menu.d.ts.map +1 -1
  154. package/dist/Menu.js +1254 -0
  155. package/dist/Menu.js.map +1 -0
  156. package/dist/Menu.jsx +1042 -0
  157. package/dist/Menu.jsx.map +1 -0
  158. package/dist/Meter.d.ts.map +1 -1
  159. package/dist/Meter.js +123 -0
  160. package/dist/Meter.js.map +1 -0
  161. package/dist/Meter.jsx +107 -0
  162. package/dist/Meter.jsx.map +1 -0
  163. package/dist/Modal.d.ts.map +1 -1
  164. package/dist/Modal.js +380 -0
  165. package/dist/Modal.js.map +1 -0
  166. package/dist/Modal.jsx +314 -0
  167. package/dist/Modal.jsx.map +1 -0
  168. package/dist/NumberField.d.ts +3 -3
  169. package/dist/NumberField.d.ts.map +1 -1
  170. package/dist/NumberField.js +573 -0
  171. package/dist/NumberField.js.map +1 -0
  172. package/dist/NumberField.jsx +443 -0
  173. package/dist/NumberField.jsx.map +1 -0
  174. package/dist/Popover.d.ts +10 -2
  175. package/dist/Popover.d.ts.map +1 -1
  176. package/dist/Popover.js +464 -0
  177. package/dist/Popover.js.map +1 -0
  178. package/dist/Popover.jsx +333 -0
  179. package/dist/Popover.jsx.map +1 -0
  180. package/dist/Pressable.js +69 -0
  181. package/dist/Pressable.js.map +1 -0
  182. package/dist/Pressable.jsx +68 -0
  183. package/dist/Pressable.jsx.map +1 -0
  184. package/dist/ProgressBar.d.ts.map +1 -1
  185. package/dist/ProgressBar.js +130 -0
  186. package/dist/ProgressBar.js.map +1 -0
  187. package/dist/ProgressBar.jsx +114 -0
  188. package/dist/ProgressBar.jsx.map +1 -0
  189. package/dist/RadioGroup.d.ts +82 -4
  190. package/dist/RadioGroup.d.ts.map +1 -1
  191. package/dist/RadioGroup.js +931 -0
  192. package/dist/RadioGroup.js.map +1 -0
  193. package/dist/RadioGroup.jsx +681 -0
  194. package/dist/RadioGroup.jsx.map +1 -0
  195. package/dist/RangeCalendar.d.ts.map +1 -1
  196. package/dist/RangeCalendar.js +410 -0
  197. package/dist/RangeCalendar.js.map +1 -0
  198. package/dist/RangeCalendar.jsx +253 -0
  199. package/dist/RangeCalendar.jsx.map +1 -0
  200. package/dist/RouterProvider.js +118 -0
  201. package/dist/RouterProvider.js.map +1 -0
  202. package/dist/RouterProvider.jsx +112 -0
  203. package/dist/RouterProvider.jsx.map +1 -0
  204. package/dist/SearchField.d.ts +2 -2
  205. package/dist/SearchField.d.ts.map +1 -1
  206. package/dist/SearchField.js +643 -0
  207. package/dist/SearchField.js.map +1 -0
  208. package/dist/SearchField.jsx +526 -0
  209. package/dist/SearchField.jsx.map +1 -0
  210. package/dist/Select.d.ts.map +1 -1
  211. package/dist/Select.js +1031 -0
  212. package/dist/Select.js.map +1 -0
  213. package/dist/Select.jsx +744 -0
  214. package/dist/Select.jsx.map +1 -0
  215. package/dist/SelectionIndicator.d.ts.map +1 -1
  216. package/dist/SelectionIndicator.js +86 -0
  217. package/dist/SelectionIndicator.js.map +1 -0
  218. package/dist/SelectionIndicator.jsx +50 -0
  219. package/dist/SelectionIndicator.jsx.map +1 -0
  220. package/dist/Separator.js +97 -0
  221. package/dist/Separator.js.map +1 -0
  222. package/dist/Separator.jsx +85 -0
  223. package/dist/Separator.jsx.map +1 -0
  224. package/dist/SharedElementTransition.d.ts.map +1 -1
  225. package/dist/SharedElementTransition.js +165 -0
  226. package/dist/SharedElementTransition.js.map +1 -0
  227. package/dist/SharedElementTransition.jsx +138 -0
  228. package/dist/SharedElementTransition.jsx.map +1 -0
  229. package/dist/Slider.d.ts +33 -1
  230. package/dist/Slider.d.ts.map +1 -1
  231. package/dist/Slider.js +515 -0
  232. package/dist/Slider.js.map +1 -0
  233. package/dist/Slider.jsx +403 -0
  234. package/dist/Slider.jsx.map +1 -0
  235. package/dist/StepList.js +198 -0
  236. package/dist/StepList.js.map +1 -0
  237. package/dist/StepList.jsx +141 -0
  238. package/dist/StepList.jsx.map +1 -0
  239. package/dist/Switch.d.ts +65 -2
  240. package/dist/Switch.d.ts.map +1 -1
  241. package/dist/Switch.js +534 -0
  242. package/dist/Switch.js.map +1 -0
  243. package/dist/Switch.jsx +393 -0
  244. package/dist/Switch.jsx.map +1 -0
  245. package/dist/Table.d.ts +48 -0
  246. package/dist/Table.d.ts.map +1 -1
  247. package/dist/Table.js +1759 -0
  248. package/dist/Table.js.map +1 -0
  249. package/dist/Table.jsx +1348 -0
  250. package/dist/Table.jsx.map +1 -0
  251. package/dist/Tabs.d.ts +0 -4
  252. package/dist/Tabs.d.ts.map +1 -1
  253. package/dist/Tabs.js +595 -0
  254. package/dist/Tabs.js.map +1 -0
  255. package/dist/Tabs.jsx +367 -0
  256. package/dist/Tabs.jsx.map +1 -0
  257. package/dist/TagGroup.d.ts.map +1 -1
  258. package/dist/TagGroup.js +378 -0
  259. package/dist/TagGroup.js.map +1 -0
  260. package/dist/TagGroup.jsx +270 -0
  261. package/dist/TagGroup.jsx.map +1 -0
  262. package/dist/Text.d.ts +18 -6
  263. package/dist/Text.d.ts.map +1 -1
  264. package/dist/Text.js +42 -0
  265. package/dist/Text.js.map +1 -0
  266. package/dist/Text.jsx +35 -0
  267. package/dist/Text.jsx.map +1 -0
  268. package/dist/TextField.d.ts +6 -6
  269. package/dist/TextField.d.ts.map +1 -1
  270. package/dist/TextField.js +436 -0
  271. package/dist/TextField.js.map +1 -0
  272. package/dist/TextField.jsx +403 -0
  273. package/dist/TextField.jsx.map +1 -0
  274. package/dist/TimeField.d.ts +3 -3
  275. package/dist/TimeField.d.ts.map +1 -1
  276. package/dist/TimeField.js +372 -0
  277. package/dist/TimeField.js.map +1 -0
  278. package/dist/TimeField.jsx +224 -0
  279. package/dist/TimeField.jsx.map +1 -0
  280. package/dist/Toast.d.ts +14 -0
  281. package/dist/Toast.d.ts.map +1 -1
  282. package/dist/Toast.js +551 -0
  283. package/dist/Toast.js.map +1 -0
  284. package/dist/Toast.jsx +361 -0
  285. package/dist/Toast.jsx.map +1 -0
  286. package/dist/ToggleButton.d.ts.map +1 -1
  287. package/dist/ToggleButton.js +123 -0
  288. package/dist/ToggleButton.js.map +1 -0
  289. package/dist/ToggleButton.jsx +88 -0
  290. package/dist/ToggleButton.jsx.map +1 -0
  291. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  292. package/dist/ToggleButtonGroup.js +114 -0
  293. package/dist/ToggleButtonGroup.js.map +1 -0
  294. package/dist/ToggleButtonGroup.jsx +85 -0
  295. package/dist/ToggleButtonGroup.jsx.map +1 -0
  296. package/dist/Toolbar.js +112 -0
  297. package/dist/Toolbar.js.map +1 -0
  298. package/dist/Toolbar.jsx +92 -0
  299. package/dist/Toolbar.jsx.map +1 -0
  300. package/dist/Tooltip.d.ts.map +1 -1
  301. package/dist/Tooltip.js +539 -0
  302. package/dist/Tooltip.js.map +1 -0
  303. package/dist/Tooltip.jsx +465 -0
  304. package/dist/Tooltip.jsx.map +1 -0
  305. package/dist/Tree.d.ts +14 -2
  306. package/dist/Tree.d.ts.map +1 -1
  307. package/dist/Tree.js +1424 -0
  308. package/dist/Tree.js.map +1 -0
  309. package/dist/Tree.jsx +1172 -0
  310. package/dist/Tree.jsx.map +1 -0
  311. package/dist/Virtualizer.d.ts +16 -2
  312. package/dist/Virtualizer.d.ts.map +1 -1
  313. package/dist/Virtualizer.js +2 -0
  314. package/dist/Virtualizer.jsx +2 -0
  315. package/dist/Virtualizer2.js +751 -0
  316. package/dist/Virtualizer2.js.map +1 -0
  317. package/dist/Virtualizer2.jsx +730 -0
  318. package/dist/Virtualizer2.jsx.map +1 -0
  319. package/dist/VirtualizerLayouts.d.ts +18 -0
  320. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  321. package/dist/VisuallyHidden.js +42 -0
  322. package/dist/VisuallyHidden.js.map +1 -0
  323. package/dist/VisuallyHidden.jsx +35 -0
  324. package/dist/VisuallyHidden.jsx.map +1 -0
  325. package/dist/contexts.js +33 -0
  326. package/dist/contexts.js.map +1 -0
  327. package/dist/contexts.jsx +33 -0
  328. package/dist/contexts.jsx.map +1 -0
  329. package/dist/index.d.ts +7 -6
  330. package/dist/index.d.ts.map +1 -1
  331. package/dist/index.js +70 -23932
  332. package/dist/index.jsx +70 -18337
  333. package/dist/useDragAndDrop.js +2 -0
  334. package/dist/useDragAndDrop.jsx +2 -0
  335. package/dist/useDragAndDrop2.js +395 -0
  336. package/dist/useDragAndDrop2.js.map +1 -0
  337. package/dist/useDragAndDrop2.jsx +395 -0
  338. package/dist/useDragAndDrop2.jsx.map +1 -0
  339. package/dist/utils.d.ts +83 -11
  340. package/dist/utils.d.ts.map +1 -1
  341. package/dist/utils.js +285 -0
  342. package/dist/utils.js.map +1 -0
  343. package/dist/utils.jsx +277 -0
  344. package/dist/utils.jsx.map +1 -0
  345. package/dist/virtualizer/Layout.js +144 -0
  346. package/dist/virtualizer/Layout.js.map +1 -0
  347. package/dist/virtualizer/Layout.jsx +144 -0
  348. package/dist/virtualizer/Layout.jsx.map +1 -0
  349. package/package.json +401 -4
  350. package/src/Breadcrumbs.tsx +3 -1
  351. package/src/Button.tsx +22 -2
  352. package/src/Calendar.tsx +27 -10
  353. package/src/CalendarPicker.tsx +73 -0
  354. package/src/Checkbox.tsx +539 -3
  355. package/src/Collection.tsx +9 -3
  356. package/src/Color.tsx +42 -14
  357. package/src/ComboBox.tsx +50 -6
  358. package/src/DateField.tsx +20 -2
  359. package/src/DatePicker.tsx +57 -5
  360. package/src/Dialog.tsx +13 -5
  361. package/src/Disclosure.tsx +3 -1
  362. package/src/DragAndDrop.tsx +3 -1
  363. package/src/DropZone.tsx +3 -1
  364. package/src/GridList.tsx +125 -24
  365. package/src/Icon.tsx +3 -1
  366. package/src/Link.tsx +3 -1
  367. package/src/ListBox.tsx +31 -4
  368. package/src/Menu.tsx +252 -128
  369. package/src/Meter.tsx +3 -1
  370. package/src/Modal.tsx +100 -52
  371. package/src/NumberField.tsx +51 -8
  372. package/src/Popover.tsx +40 -10
  373. package/src/ProgressBar.tsx +3 -1
  374. package/src/RadioGroup.tsx +468 -4
  375. package/src/RangeCalendar.tsx +3 -1
  376. package/src/SearchField.tsx +27 -3
  377. package/src/Select.tsx +22 -7
  378. package/src/SelectionIndicator.tsx +3 -1
  379. package/src/SharedElementTransition.tsx +3 -1
  380. package/src/Slider.tsx +159 -13
  381. package/src/Switch.tsx +395 -2
  382. package/src/Table.tsx +409 -95
  383. package/src/Tabs.tsx +16 -45
  384. package/src/TagGroup.tsx +3 -1
  385. package/src/Text.tsx +31 -10
  386. package/src/TextField.tsx +43 -5
  387. package/src/TimeField.tsx +20 -2
  388. package/src/Toast.tsx +79 -14
  389. package/src/ToggleButton.tsx +3 -1
  390. package/src/ToggleButtonGroup.tsx +3 -1
  391. package/src/Tooltip.tsx +3 -1
  392. package/src/Tree.tsx +77 -9
  393. package/src/Virtualizer.tsx +180 -31
  394. package/src/VirtualizerLayouts.ts +45 -10
  395. package/src/index.ts +50 -0
  396. package/src/utils.tsx +243 -27
  397. package/dist/index.js.map +0 -1
  398. package/dist/index.jsx.map +0 -1
package/src/Table.tsx CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  createSignal,
15
15
  onCleanup,
16
16
  splitProps,
17
+ untrack,
17
18
  useContext,
18
19
  For,
19
20
  Show,
@@ -36,6 +37,7 @@ import {
36
37
  } from "@proyecto-viviana/solidaria";
37
38
  import {
38
39
  createTableState,
40
+ createTreeGridState,
39
41
  createTableCollection,
40
42
  createTableColumnResizeState,
41
43
  type TableState,
@@ -44,6 +46,7 @@ import {
44
46
  type Key,
45
47
  type SortDescriptor,
46
48
  type ColumnDefinition,
49
+ type RowDefinition,
47
50
  type ColumnSize,
48
51
  type GridNode,
49
52
  type DropTarget,
@@ -59,7 +62,7 @@ import {
59
62
  } from "./utils";
60
63
  import { SharedElementTransition } from "./SharedElementTransition";
61
64
  import { type DragAndDropHooks } from "./useDragAndDrop";
62
- import { ButtonContext, type ButtonProps } from "./Button";
65
+ import { ButtonContext, type ButtonContextValue, type ButtonProps } from "./Button";
63
66
  import {
64
67
  CollectionRendererContext,
65
68
  type CollectionRendererContextValue,
@@ -122,6 +125,35 @@ function normalizeColumnDefinitions<T>(
122
125
  });
123
126
  }
124
127
 
128
+ /** Resolves a stable, unique key for a tree-grid item (keys must be unique across levels). */
129
+ function resolveItemKey<T>(item: T, getKey: ((item: T) => Key) | undefined, index: number): Key {
130
+ if (getKey) return getKey(item);
131
+ const record = item as { id?: Key; key?: Key };
132
+ return record.id ?? record.key ?? index;
133
+ }
134
+
135
+ /**
136
+ * Turns the flat `items` into nested {@link RowDefinition}s by recursively expanding each
137
+ * item's children via `getChildItems`. The result feeds the tree-grid collection.
138
+ */
139
+ function toTreeRowDefinitions<T>(
140
+ items: Iterable<T>,
141
+ getKey: ((item: T) => Key) | undefined,
142
+ getChildItems: ((item: T) => Iterable<T> | undefined) | undefined,
143
+ ): RowDefinition<T>[] {
144
+ return Array.from(items).map((item, index) => {
145
+ const childItems = getChildItems?.(item);
146
+ const childRows = childItems
147
+ ? toTreeRowDefinitions(childItems, getKey, getChildItems)
148
+ : undefined;
149
+ return {
150
+ key: resolveItemKey(item, getKey, index),
151
+ value: item,
152
+ childRows: childRows && childRows.length > 0 ? childRows : undefined,
153
+ };
154
+ });
155
+ }
156
+
125
157
  function getRowHeaderColumnKeys<T>(columns: (ColumnDefinition<T> & { id?: Key })[]): Set<Key> {
126
158
  const keys = new Set<Key>();
127
159
 
@@ -192,6 +224,18 @@ export interface TableProps<T extends object> extends Omit<AriaTableProps, "chil
192
224
  ) => JSX.Element;
193
225
  /** Drag and drop hooks from `useDragAndDrop`. */
194
226
  dragAndDropHooks?: DragAndDropHooks<T>;
227
+ /**
228
+ * Derives the child rows of an item, turning the flat `items` into a tree grid
229
+ * (expandable rows). Providing this — or any `UNSTABLE_*expandedKeys` prop — switches the
230
+ * table into tree-grid mode. Mirrors `@react-aria/table`'s `UNSTABLE_` tree-grid feature.
231
+ */
232
+ UNSTABLE_childItems?: (item: T) => Iterable<T> | undefined;
233
+ /** The currently expanded row keys (controlled). Tree grid only. */
234
+ UNSTABLE_expandedKeys?: "all" | Iterable<Key>;
235
+ /** The initial expanded row keys (uncontrolled). Tree grid only. */
236
+ UNSTABLE_defaultExpandedKeys?: "all" | Iterable<Key>;
237
+ /** Handler called when rows are expanded or collapsed. Tree grid only. */
238
+ UNSTABLE_onExpandedChange?: (keys: Set<Key>) => void;
195
239
  }
196
240
 
197
241
  export interface TableHeaderRenderProps {
@@ -324,6 +368,21 @@ export interface TableRowRenderProps {
324
368
  isHovered: boolean;
325
369
  /** Whether the row is disabled. */
326
370
  isDisabled: boolean;
371
+ /**
372
+ * Whether the row is expanded (tree grid only; `false` for a flat table).
373
+ * @selector [data-expanded]
374
+ */
375
+ isExpanded: boolean;
376
+ /**
377
+ * Whether the row has child rows (tree grid only; `false` for a flat table).
378
+ * @selector [data-has-child-items]
379
+ */
380
+ hasChildItems: boolean;
381
+ /**
382
+ * The 1-based nesting depth of the row within the table (1 for a flat table).
383
+ * @selector [data-level]
384
+ */
385
+ level: number;
327
386
  }
328
387
 
329
388
  export interface TableRowProps<T> extends SlotProps {
@@ -380,6 +439,27 @@ export interface TableCellRenderProps {
380
439
  isPressed: boolean;
381
440
  /** Whether the cell is hovered. */
382
441
  isHovered: boolean;
442
+ /**
443
+ * Whether the cell is in the tree column — the one that renders the expand/collapse
444
+ * chevron and nesting indentation (tree grid only; `false` for a flat table).
445
+ * @selector [data-tree-column]
446
+ */
447
+ isTreeColumn: boolean;
448
+ /**
449
+ * Whether the cell's row is expanded (tree grid only; `false` for a flat table).
450
+ * @selector [data-expanded]
451
+ */
452
+ isExpanded: boolean;
453
+ /**
454
+ * Whether the cell's row has child rows (tree grid only; `false` for a flat table).
455
+ * @selector [data-has-child-items]
456
+ */
457
+ hasChildItems: boolean;
458
+ /**
459
+ * The 1-based nesting depth of the cell's row within the table (1 for a flat table).
460
+ * @selector [data-level]
461
+ */
462
+ level: number;
383
463
  }
384
464
 
385
465
  export interface TableCellProps extends SlotProps {
@@ -472,6 +552,10 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
472
552
  "sortDescriptor",
473
553
  "onSortChange",
474
554
  "showSelectionCheckboxes",
555
+ "UNSTABLE_childItems",
556
+ "UNSTABLE_expandedKeys",
557
+ "UNSTABLE_defaultExpandedKeys",
558
+ "UNSTABLE_onExpandedChange",
475
559
  ],
476
560
  );
477
561
 
@@ -479,31 +563,63 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
479
563
  const normalizedColumns = createMemo(() => normalizeColumnDefinitions(stateProps.columns));
480
564
  const rowHeaderColumnKeys = createMemo(() => getRowHeaderColumnKeys(normalizedColumns()));
481
565
 
482
- const collection = createMemo(() =>
483
- createTableCollection<T>({
484
- columns: normalizedColumns(),
485
- rows: stateProps.items,
486
- getKey: stateProps.getKey,
487
- getTextValue: stateProps.getTextValue as
488
- | ((item: T, column: ColumnDefinition<T>) => string)
489
- | undefined,
490
- showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
491
- rowHeaderColumnKeys: rowHeaderColumnKeys().size > 0 ? rowHeaderColumnKeys() : undefined,
492
- }),
493
- );
494
-
495
- const state = createTableState<T, TableCollection<T>>(() => ({
496
- collection: collection(),
497
- disabledKeys: stateProps.disabledKeys,
498
- selectionMode: stateProps.selectionMode,
499
- selectionBehavior: stateProps.selectionBehavior,
500
- selectedKeys: stateProps.selectedKeys,
501
- defaultSelectedKeys: stateProps.defaultSelectedKeys,
502
- onSelectionChange: stateProps.onSelectionChange,
503
- sortDescriptor: stateProps.sortDescriptor,
504
- onSortChange: stateProps.onSortChange,
505
- showSelectionCheckboxes: stateProps.showSelectionCheckboxes,
506
- }));
566
+ // Tree-grid mode is selected structurally by the presence of any expansion prop. Like
567
+ // controlled-vs-uncontrolled, this is a one-time decision, not a reactive toggle.
568
+ const isTreeGrid =
569
+ stateProps.UNSTABLE_childItems !== undefined ||
570
+ stateProps.UNSTABLE_expandedKeys !== undefined ||
571
+ stateProps.UNSTABLE_defaultExpandedKeys !== undefined ||
572
+ stateProps.UNSTABLE_onExpandedChange !== undefined;
573
+
574
+ const getTextValue = () =>
575
+ stateProps.getTextValue as ((item: T, column: ColumnDefinition<T>) => string) | undefined;
576
+
577
+ const state: TableState<T, TableCollection<T>> = isTreeGrid
578
+ ? createTreeGridState<T, TableCollection<T>>(() => ({
579
+ columns: normalizedColumns(),
580
+ rows: toTreeRowDefinitions(
581
+ stateProps.items,
582
+ stateProps.getKey,
583
+ stateProps.UNSTABLE_childItems,
584
+ ),
585
+ getKey: stateProps.getKey,
586
+ getTextValue: getTextValue(),
587
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
588
+ rowHeaderColumnKeys: rowHeaderColumnKeys().size > 0 ? rowHeaderColumnKeys() : undefined,
589
+ disabledKeys: stateProps.disabledKeys,
590
+ disabledBehavior: stateProps.disabledBehavior,
591
+ selectionMode: stateProps.selectionMode,
592
+ selectionBehavior: stateProps.selectionBehavior,
593
+ selectedKeys: stateProps.selectedKeys,
594
+ defaultSelectedKeys: stateProps.defaultSelectedKeys,
595
+ onSelectionChange: stateProps.onSelectionChange,
596
+ sortDescriptor: stateProps.sortDescriptor,
597
+ onSortChange: stateProps.onSortChange,
598
+ UNSTABLE_expandedKeys: stateProps.UNSTABLE_expandedKeys,
599
+ UNSTABLE_defaultExpandedKeys: stateProps.UNSTABLE_defaultExpandedKeys,
600
+ UNSTABLE_onExpandedChange: stateProps.UNSTABLE_onExpandedChange,
601
+ }))
602
+ : createTableState<T, TableCollection<T>>(() => ({
603
+ collection: createTableCollection<T>({
604
+ columns: normalizedColumns(),
605
+ rows: stateProps.items,
606
+ getKey: stateProps.getKey,
607
+ getTextValue: getTextValue(),
608
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
609
+ rowHeaderColumnKeys: rowHeaderColumnKeys().size > 0 ? rowHeaderColumnKeys() : undefined,
610
+ }),
611
+ disabledKeys: stateProps.disabledKeys,
612
+ disabledBehavior: stateProps.disabledBehavior,
613
+ selectionMode: stateProps.selectionMode,
614
+ selectionBehavior: stateProps.selectionBehavior,
615
+ selectedKeys: stateProps.selectedKeys,
616
+ defaultSelectedKeys: stateProps.defaultSelectedKeys,
617
+ onSelectionChange: stateProps.onSelectionChange,
618
+ sortDescriptor: stateProps.sortDescriptor,
619
+ onSortChange: stateProps.onSortChange,
620
+ showSelectionCheckboxes: stateProps.showSelectionCheckboxes,
621
+ }));
622
+ const collection = () => state.collection;
507
623
  const parentCollectionRenderer = useCollectionRenderer<T>();
508
624
 
509
625
  const { gridProps } = createTable<T>(
@@ -707,8 +823,16 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
707
823
  dndDropIndicator(index, position) ??
708
824
  parentCollectionRenderer?.renderDropIndicator?.(index, position),
709
825
  }));
826
+ // The root children function builds the structural `<TableHeader>`/`<TableBody>`
827
+ // subtree once. React re-invokes it on every render and reconciles the result, so
828
+ // reading focus-dependent render values (isFocused/isFocusVisible) there is cheap.
829
+ // Solid has no reconciliation: re-invoking recreates the entire thead/tbody subtree,
830
+ // which detaches the DOM-focused row and drops keyboard focus (Space/Enter never reach
831
+ // the row). So invoke it with an untracked snapshot of the render values — the insert
832
+ // below stays stable across focus changes while children still see current values at
833
+ // creation. Render values for collection structure are not reactive in practice.
710
834
  const tableChildren = () =>
711
- typeof props.children === "function" ? props.children(renderValues()) : props.children;
835
+ typeof props.children === "function" ? props.children(untrack(renderValues)) : props.children;
712
836
  const tableProps = () =>
713
837
  ({
714
838
  ref: (el: HTMLTableElement) => {
@@ -728,7 +852,6 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
728
852
  "data-empty": stateProps.items.length === 0 || undefined,
729
853
  "data-drop-target": isRootDropTarget() || undefined,
730
854
  slot: local.slot,
731
- children: tableChildren(),
732
855
  }) as JSX.HTMLAttributes<HTMLTableElement>;
733
856
 
734
857
  return (
@@ -737,7 +860,11 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
737
860
  value={state as unknown as TableState<object, TableCollection<object>>}
738
861
  >
739
862
  <CollectionRendererContext.Provider value={collectionRenderer()}>
740
- {local.render ? local.render(tableProps(), renderValues()) : <table {...tableProps()} />}
863
+ {local.render ? (
864
+ local.render({ ...tableProps(), children: tableChildren() }, renderValues())
865
+ ) : (
866
+ <table {...tableProps()}>{tableChildren()}</table>
867
+ )}
741
868
  </CollectionRendererContext.Provider>
742
869
  </TableStateContext.Provider>
743
870
  </TableContext.Provider>
@@ -1071,6 +1198,8 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
1071
1198
  context.collection,
1072
1199
  );
1073
1200
  const virtualRange = createMemo(() => {
1201
+ // Tree grids render every visible (flattened) row; virtual windowing is a follow-up.
1202
+ if (context.state.treeColumn != null) return null;
1074
1203
  if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
1075
1204
  const rowCount = items().length;
1076
1205
  const baseRange = virtualizer.getVisibleRange(rowCount);
@@ -1125,6 +1254,11 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
1125
1254
  });
1126
1255
  });
1127
1256
  const visibleItems = createMemo(() => {
1257
+ // Tree grid: render the flattened set of visible rows (top-level + expanded descendants)
1258
+ // the collection materialises in document order, so the render fn runs once per visible row.
1259
+ if (context.state.treeColumn != null) {
1260
+ return rowNodes().map((node) => node.value as T);
1261
+ }
1128
1262
  const range = virtualRange();
1129
1263
  if (!range) return items();
1130
1264
  return items().slice(range.start, range.end);
@@ -1435,7 +1569,7 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1435
1569
  if (!context) {
1436
1570
  throw new Error("TableRow must be used within a Table");
1437
1571
  }
1438
- const { state, collection } = context;
1572
+ const { state } = context;
1439
1573
  const tableContext = context as unknown as TableContextValue<T>;
1440
1574
  const registeredCellIds: string[] = [];
1441
1575
  const generatedId = createUniqueId();
@@ -1454,8 +1588,10 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1454
1588
 
1455
1589
  const [ref, setRef] = createSignal<HTMLTableRowElement | null>(null);
1456
1590
 
1591
+ // Read through `state.collection` (not the destructured snapshot) so the node tracks
1592
+ // collection rebuilds — e.g. a tree-grid row staying mounted across an expand/collapse.
1457
1593
  const rowNode = createMemo(() => {
1458
- const node = collection.getItem(rowKey());
1594
+ const node = state.collection.getItem(rowKey());
1459
1595
  if (!node) {
1460
1596
  return {
1461
1597
  type: "item" as const,
@@ -1492,7 +1628,12 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1492
1628
  const isPressed = () => rowAria.isPressed;
1493
1629
  const isInteractive = () => {
1494
1630
  const tableData = getTableData(state as TableState<object, TableCollection<object>>);
1495
- return state.selectionMode !== "none" || !!tableData?.actions.onRowAction || !!local.onAction;
1631
+ return (
1632
+ state.selectionMode !== "none" ||
1633
+ !!tableData?.actions.onRowAction ||
1634
+ !!local.onAction ||
1635
+ !!local.href
1636
+ );
1496
1637
  };
1497
1638
 
1498
1639
  const { isHovered, hoverProps } = createHover({
@@ -1549,6 +1690,13 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1549
1690
  );
1550
1691
  });
1551
1692
 
1693
+ // Tree-grid (expandable rows): nesting depth, child presence and expansion. These are
1694
+ // inert for a flat table (level 1, no children, not expanded).
1695
+ const isTreeRow = () => state.treeColumn != null;
1696
+ const rowLevel = () => rowNode().level + 1;
1697
+ const hasChildItems = () => rowAria.hasChildRows;
1698
+ const isExpanded = () => rowAria.isExpanded;
1699
+
1552
1700
  const renderValues = createMemo<TableRowRenderProps>(() => ({
1553
1701
  isSelected: isSelected(),
1554
1702
  isFocused: isFocused(),
@@ -1556,6 +1704,9 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1556
1704
  isPressed: isPressed(),
1557
1705
  isHovered: isHovered(),
1558
1706
  isDisabled: isDisabled(),
1707
+ isExpanded: isExpanded(),
1708
+ hasChildItems: hasChildItems(),
1709
+ level: rowLevel(),
1559
1710
  }));
1560
1711
 
1561
1712
  // Resolve render props (children rendered directly in JSX to avoid eager evaluation)
@@ -1610,73 +1761,197 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
1610
1761
  },
1611
1762
  };
1612
1763
  });
1613
- const buttonContextValue = createMemo(() => ({
1764
+ // A STABLE context object: its identity never changes, so the surrounding ButtonContext.Provider
1765
+ // never tears down and recreates the row's children. The slots are getters, so a slotted
1766
+ // <Button slot="drag"|"chevron"> reads the current drag/expand props each time it is (re)created —
1767
+ // e.g. when the tree column recreates the chevron on expand/collapse.
1768
+ const buttonContextValue: ButtonContextValue = {
1614
1769
  slots: {
1615
1770
  default: {},
1616
- drag: dragButtonProps(),
1771
+ get drag() {
1772
+ return dragButtonProps();
1773
+ },
1774
+ // Tree-grid expand/collapse chevron; the slotted <Button slot="chevron"> picks these up.
1775
+ // The aria hook yields DOM button attributes, merged onto the Button as passthrough props.
1776
+ get chevron() {
1777
+ return rowAria.expandButtonProps as unknown as ButtonProps;
1778
+ },
1617
1779
  },
1618
- }));
1780
+ };
1619
1781
 
1620
- const rowChildren = () => (
1621
- <ButtonContext.Provider value={buttonContextValue()}>
1622
- {typeof local.children === "function" ? (
1623
- local.columns ? (
1624
- <For each={local.columns}>
1625
- {(column) =>
1626
- (local.children as (column: TableColumnDefinition<T>) => JSX.Element)(column)
1627
- }
1628
- </For>
1629
- ) : (
1630
- (local.children as (renderProps: TableRowRenderProps) => JSX.Element)(renderValues())
1631
- )
1632
- ) : (
1633
- local.children
1634
- )}
1635
- </ButtonContext.Provider>
1636
- );
1637
- const tableRowProps = () =>
1638
- ({
1639
- ref: (el: HTMLTableRowElement) => {
1640
- setRef(el);
1641
- assignRef(local.ref, el);
1642
- },
1643
- ...domProps,
1644
- ...mergeProps(
1645
- cleanRowProps(),
1646
- cleanHoverProps(),
1647
- cleanFocusProps(),
1648
- focusWithinProps as Record<string, unknown>,
1649
- (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
1650
- (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
1651
- ),
1652
- class: renderProps.class(),
1653
- style: renderProps.style(),
1654
- "data-selected": isSelected() || undefined,
1655
- "data-focused": isFocused() || undefined,
1656
- "data-focus-visible": (isFocusVisible() && isFocused()) || undefined,
1657
- "data-focus-visible-within": dataAttr(isFocusWithin() && isGlobalFocusVisible()),
1658
- "data-pressed": isPressed() || undefined,
1659
- "data-hovered": isHovered() || undefined,
1660
- "data-disabled": isDisabled() || undefined,
1661
- "data-href": linkProps().href,
1662
- "data-target": linkProps().target,
1663
- "data-rel": linkProps().rel,
1664
- "data-download":
1665
- typeof linkProps().download === "string"
1666
- ? linkProps().download
1667
- : linkProps().download
1668
- ? ""
1669
- : undefined,
1670
- "data-ping": linkProps().ping,
1671
- "data-referrer-policy": linkProps().referrerPolicy,
1672
- "data-dragging": draggableItem()?.isDragging || undefined,
1673
- "data-drop-target": droppableItem()?.isDropTarget || undefined,
1674
- children: rowChildren(),
1675
- }) as JSX.HTMLAttributes<HTMLTableRowElement>;
1782
+ // Render props handed to the row's children as a STABLE getter proxy: reading a getter stays
1783
+ // fine-grained reactive at the point of use, without forcing the children to be re-derived (and
1784
+ // therefore disposed + recreated) whenever an unrelated render prop flips.
1785
+ const childRenderProps: TableRowRenderProps = {
1786
+ get isSelected() {
1787
+ return isSelected();
1788
+ },
1789
+ get isFocused() {
1790
+ return isFocused();
1791
+ },
1792
+ get isFocusVisible() {
1793
+ return isFocusVisible() && isFocused();
1794
+ },
1795
+ get isPressed() {
1796
+ return isPressed();
1797
+ },
1798
+ get isHovered() {
1799
+ return isHovered();
1800
+ },
1801
+ get isDisabled() {
1802
+ return isDisabled();
1803
+ },
1804
+ get isExpanded() {
1805
+ return isExpanded();
1806
+ },
1807
+ get hasChildItems() {
1808
+ return hasChildItems();
1809
+ },
1810
+ get level() {
1811
+ return rowLevel();
1812
+ },
1813
+ };
1814
+
1815
+ // Tree-grid rows carry their nesting depth as a CSS var so the tree column can indent.
1816
+ const rowStyle = () => {
1817
+ const base = renderProps.style();
1818
+ if (!isTreeRow()) return base;
1819
+ return { ...(base ?? {}), "--table-row-level": rowLevel() } as JSX.CSSProperties;
1820
+ };
1676
1821
 
1677
1822
  return (
1678
1823
  <TableRowContext.Provider value={rowContextValue}>
1679
- {local.render ? local.render(tableRowProps(), renderValues()) : <tr {...tableRowProps()} />}
1824
+ <ButtonContext.Provider value={buttonContextValue}>
1825
+ {(() => {
1826
+ // Build the row's children HERE — inside BOTH providers — so each cell instantiates under
1827
+ // this owner. TableCell/EditableCell read TableRowContext, and a slotted
1828
+ // <Button slot="drag"|"chevron"> reads ButtonContext; in Solid useContext binds at the
1829
+ // owner active when the component executes, so the children must be created inside the
1830
+ // providers, not merely wrapped by them afterwards. They are built EXACTLY ONCE: a press
1831
+ // or focus bubbling up from interactive cell content (e.g. an EditableCell's edit button)
1832
+ // flips the row's render-props signals, and re-deriving the children on every such change
1833
+ // would dispose and recreate the subtree, destroying the in-flight press. Cells claim
1834
+ // their column by render order (see getCellColumnKey), so reset the registry once before
1835
+ // this single pass.
1836
+ registeredCellIds.length = 0;
1837
+ const rowChildrenContent =
1838
+ typeof local.children === "function" ? (
1839
+ local.columns ? (
1840
+ <For each={local.columns}>
1841
+ {(column) =>
1842
+ (local.children as (column: TableColumnDefinition<T>) => JSX.Element)(column)
1843
+ }
1844
+ </For>
1845
+ ) : (
1846
+ (local.children as (renderProps: TableRowRenderProps) => JSX.Element)(
1847
+ childRenderProps,
1848
+ )
1849
+ )
1850
+ ) : (
1851
+ local.children
1852
+ );
1853
+ const tableRowProps = () =>
1854
+ ({
1855
+ ref: (el: HTMLTableRowElement) => {
1856
+ setRef(el);
1857
+ assignRef(local.ref, el);
1858
+ },
1859
+ ...domProps,
1860
+ ...mergeProps(
1861
+ cleanRowProps(),
1862
+ cleanHoverProps(),
1863
+ cleanFocusProps(),
1864
+ focusWithinProps as Record<string, unknown>,
1865
+ (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
1866
+ (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
1867
+ ),
1868
+ class: renderProps.class(),
1869
+ style: rowStyle(),
1870
+ "data-key": rowKey(),
1871
+ "data-selected": isSelected() || undefined,
1872
+ "data-focused": isFocused() || undefined,
1873
+ "data-focus-visible": (isFocusVisible() && isFocused()) || undefined,
1874
+ "data-focus-visible-within": dataAttr(isFocusWithin() && isGlobalFocusVisible()),
1875
+ "data-pressed": isPressed() || undefined,
1876
+ "data-hovered": isHovered() || undefined,
1877
+ "data-disabled": isDisabled() || undefined,
1878
+ "data-href": linkProps().href,
1879
+ "data-target": linkProps().target,
1880
+ "data-rel": linkProps().rel,
1881
+ "data-download":
1882
+ typeof linkProps().download === "string"
1883
+ ? linkProps().download
1884
+ : linkProps().download
1885
+ ? ""
1886
+ : undefined,
1887
+ "data-ping": linkProps().ping,
1888
+ "data-referrer-policy": linkProps().referrerPolicy,
1889
+ "data-dragging": draggableItem()?.isDragging || undefined,
1890
+ "data-drop-target": droppableItem()?.isDropTarget || undefined,
1891
+ "data-expanded": (isTreeRow() && isExpanded()) || undefined,
1892
+ "data-has-child-items": (isTreeRow() && hasChildItems()) || undefined,
1893
+ "data-level": isTreeRow() ? rowLevel() : undefined,
1894
+ children: rowChildrenContent,
1895
+ }) as JSX.HTMLAttributes<HTMLTableRowElement>;
1896
+
1897
+ return (
1898
+ <Show
1899
+ when={local.render}
1900
+ fallback={
1901
+ // Bind attributes individually on a STABLE <tr> (mirroring TableCell) so a
1902
+ // render-props change updates attributes in place instead of recreating the element
1903
+ // and its subtree.
1904
+ <tr
1905
+ ref={(el: HTMLTableRowElement) => {
1906
+ setRef(el);
1907
+ assignRef(local.ref, el);
1908
+ }}
1909
+ {...domProps}
1910
+ {...mergeProps(
1911
+ cleanRowProps(),
1912
+ cleanHoverProps(),
1913
+ cleanFocusProps(),
1914
+ focusWithinProps as Record<string, unknown>,
1915
+ (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
1916
+ (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
1917
+ )}
1918
+ class={renderProps.class()}
1919
+ style={rowStyle()}
1920
+ data-key={rowKey()}
1921
+ data-selected={isSelected() || undefined}
1922
+ data-focused={isFocused() || undefined}
1923
+ data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
1924
+ data-focus-visible-within={dataAttr(isFocusWithin() && isGlobalFocusVisible())}
1925
+ data-pressed={isPressed() || undefined}
1926
+ data-hovered={isHovered() || undefined}
1927
+ data-disabled={isDisabled() || undefined}
1928
+ data-href={linkProps().href}
1929
+ data-target={linkProps().target}
1930
+ data-rel={linkProps().rel}
1931
+ data-download={
1932
+ typeof linkProps().download === "string"
1933
+ ? linkProps().download
1934
+ : linkProps().download
1935
+ ? ""
1936
+ : undefined
1937
+ }
1938
+ data-ping={linkProps().ping}
1939
+ data-referrer-policy={linkProps().referrerPolicy}
1940
+ data-dragging={draggableItem()?.isDragging || undefined}
1941
+ data-drop-target={droppableItem()?.isDropTarget || undefined}
1942
+ data-expanded={(isTreeRow() && isExpanded()) || undefined}
1943
+ data-has-child-items={(isTreeRow() && hasChildItems()) || undefined}
1944
+ data-level={isTreeRow() ? rowLevel() : undefined}
1945
+ >
1946
+ {rowChildrenContent}
1947
+ </tr>
1948
+ }
1949
+ >
1950
+ {local.render!(tableRowProps(), renderValues())}
1951
+ </Show>
1952
+ );
1953
+ })()}
1954
+ </ButtonContext.Provider>
1680
1955
  </TableRowContext.Provider>
1681
1956
  );
1682
1957
  }
@@ -1706,7 +1981,7 @@ export function TableCell(props: TableCellProps): JSX.Element {
1706
1981
  throw new Error("TableCell must be used within a Table");
1707
1982
  }
1708
1983
 
1709
- const { state, collection } = tableContext;
1984
+ const { state } = tableContext;
1710
1985
  const { rowKey, rowNode } = rowContext;
1711
1986
 
1712
1987
  const [ref, setRef] = createSignal<HTMLTableCellElement | null>(null);
@@ -1717,7 +1992,7 @@ export function TableCell(props: TableCellProps): JSX.Element {
1717
1992
  const key = columnKey();
1718
1993
  if (key != null) {
1719
1994
  const cellKey = `${rowKey}-${key}`;
1720
- const node = collection.getItem(cellKey);
1995
+ const node = state.collection.getItem(cellKey);
1721
1996
  if (node) return node;
1722
1997
  }
1723
1998
 
@@ -1737,9 +2012,21 @@ export function TableCell(props: TableCellProps): JSX.Element {
1737
2012
  const key = columnKey();
1738
2013
  if (key == null) return undefined;
1739
2014
  const cellKey = `${rowKey}-${key}`;
1740
- return collection.getItem(cellKey) ? (cellNode().index ?? 0) : undefined;
2015
+ return state.collection.getItem(cellKey) ? (cellNode().index ?? 0) : undefined;
1741
2016
  });
1742
2017
 
2018
+ // Tree-grid (expandable rows): the cell mirrors its row's nesting/expansion and flags the
2019
+ // tree column (the one that renders the chevron + indentation). Inert for a flat table.
2020
+ const isTreeGridCell = () => state.treeColumn != null;
2021
+ const parentRowNode = createMemo(() => state.collection.getItem(rowKey));
2022
+ const cellLevel = () => (parentRowNode()?.level ?? rowNode.level ?? 0) + 1;
2023
+ const cellHasChildItems = () => parentRowNode()?.isExpandable ?? false;
2024
+ const cellIsExpanded = () => {
2025
+ if (!isTreeGridCell() || !cellHasChildItems()) return false;
2026
+ return state.expandedKeys === "all" || state.expandedKeys.has(rowKey);
2027
+ };
2028
+ const isTreeColumn = () => isTreeGridCell() && columnKey() === state.treeColumn;
2029
+
1743
2030
  const cellAria = createTableCell<object>(
1744
2031
  () => ({
1745
2032
  node: cellNode(),
@@ -1763,6 +2050,10 @@ export function TableCell(props: TableCellProps): JSX.Element {
1763
2050
  columnIndex: cellColumnIndex() ?? 0,
1764
2051
  isPressed: isPressed(),
1765
2052
  isHovered: isHovered(),
2053
+ isTreeColumn: isTreeColumn(),
2054
+ isExpanded: cellIsExpanded(),
2055
+ hasChildItems: cellHasChildItems(),
2056
+ level: cellLevel(),
1766
2057
  }));
1767
2058
 
1768
2059
  // Resolve render props (children rendered directly in JSX to avoid eager evaluation)
@@ -1801,11 +2092,16 @@ export function TableCell(props: TableCellProps): JSX.Element {
1801
2092
  colSpan: local.colSpan,
1802
2093
  class: renderProps.class(),
1803
2094
  style: renderProps.style(),
2095
+ "data-key": cellNode().key,
1804
2096
  "data-focused": isFocused() || undefined,
1805
2097
  "data-focus-visible": (isFocusVisible() && isFocused()) || undefined,
1806
2098
  "data-column-index": cellColumnIndex(),
1807
2099
  "data-pressed": isPressed() || undefined,
1808
2100
  "data-hovered": isHovered() || undefined,
2101
+ "data-tree-column": isTreeColumn() || undefined,
2102
+ "data-expanded": (isTreeGridCell() && cellIsExpanded()) || undefined,
2103
+ "data-has-child-items": (isTreeGridCell() && cellHasChildItems()) || undefined,
2104
+ "data-level": isTreeGridCell() ? cellLevel() : undefined,
1809
2105
  children: cellChildren(),
1810
2106
  }) as JSX.TdHTMLAttributes<HTMLTableCellElement>;
1811
2107
 
@@ -1822,11 +2118,16 @@ export function TableCell(props: TableCellProps): JSX.Element {
1822
2118
  colSpan={local.colSpan}
1823
2119
  class={renderProps.class()}
1824
2120
  style={renderProps.style()}
2121
+ data-key={cellNode().key}
1825
2122
  data-focused={isFocused() || undefined}
1826
2123
  data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
1827
2124
  data-column-index={cellColumnIndex()}
1828
2125
  data-pressed={isPressed() || undefined}
1829
2126
  data-hovered={isHovered() || undefined}
2127
+ data-tree-column={isTreeColumn() || undefined}
2128
+ data-expanded={(isTreeGridCell() && cellIsExpanded()) || undefined}
2129
+ data-has-child-items={(isTreeGridCell() && cellHasChildItems()) || undefined}
2130
+ data-level={isTreeGridCell() ? cellLevel() : undefined}
1830
2131
  >
1831
2132
  {cellChildren()}
1832
2133
  </td>
@@ -1864,6 +2165,11 @@ export function TableSelectionCheckbox(props: TableSelectionCheckboxProps): JSX.
1864
2165
  style={props.style}
1865
2166
  tabIndex={props.excludeFromTabOrder ? -1 : selectionCheckboxAria.checkboxProps.tabIndex}
1866
2167
  aria-label={props["aria-label"] ?? selectionCheckboxAria.checkboxProps["aria-label"]}
2168
+ onClick={(event) => event.stopPropagation()}
2169
+ onKeyDown={(event) => event.stopPropagation()}
2170
+ onKeyUp={(event) => event.stopPropagation()}
2171
+ onPointerDown={(event) => event.stopPropagation()}
2172
+ onPointerUp={(event) => event.stopPropagation()}
1867
2173
  />
1868
2174
  );
1869
2175
  }
@@ -1889,8 +2195,16 @@ export function TableSelectAllCheckbox(props: TableSelectAllCheckboxProps = {}):
1889
2195
  () => state as TableState<object, TableCollection<object>>,
1890
2196
  );
1891
2197
 
2198
+ let inputRef: HTMLInputElement | undefined;
2199
+ createEffect(() => {
2200
+ if (inputRef) {
2201
+ inputRef.indeterminate = selectAllCheckboxAria.isIndeterminate;
2202
+ }
2203
+ });
2204
+
1892
2205
  return (
1893
2206
  <input
2207
+ ref={inputRef}
1894
2208
  {...selectAllCheckboxAria.checkboxProps}
1895
2209
  class={props.class}
1896
2210
  style={props.style}