@proyecto-viviana/solid-stately 0.2.7 → 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 (254) hide show
  1. package/README.md +30 -283
  2. package/dist/autocomplete/createAutocompleteState.d.ts +2 -2
  3. package/dist/autocomplete/index.d.ts +1 -1
  4. package/dist/calendar/IncompleteDate.d.ts +61 -0
  5. package/dist/calendar/createCalendarState.d.ts +41 -16
  6. package/dist/calendar/createDateFieldState.d.ts +39 -9
  7. package/dist/calendar/createDatePickerState.d.ts +60 -0
  8. package/dist/calendar/createRangeCalendarState.d.ts +37 -10
  9. package/dist/calendar/createTimeFieldState.d.ts +34 -7
  10. package/dist/calendar/index.d.ts +7 -6
  11. package/dist/checkbox/createCheckboxGroupState.d.ts +6 -6
  12. package/dist/checkbox/index.d.ts +1 -1
  13. package/dist/collections/ListCollection.d.ts +1 -1
  14. package/dist/collections/createListState.d.ts +11 -8
  15. package/dist/collections/createMenuState.d.ts +7 -7
  16. package/dist/collections/createSelectionState.d.ts +22 -10
  17. package/dist/collections/getChildNodes.d.ts +11 -0
  18. package/dist/collections/index.d.ts +6 -5
  19. package/dist/collections/types.d.ts +12 -12
  20. package/dist/color/Color.d.ts +1 -1
  21. package/dist/color/createColorAreaState.d.ts +4 -2
  22. package/dist/color/createColorFieldState.d.ts +38 -12
  23. package/dist/color/createColorSliderState.d.ts +9 -2
  24. package/dist/color/createColorWheelState.d.ts +13 -2
  25. package/dist/color/getColorChannels.d.ts +1 -1
  26. package/dist/color/index.d.ts +6 -6
  27. package/dist/color/types.d.ts +4 -4
  28. package/dist/combobox/createComboBoxState.d.ts +34 -13
  29. package/dist/combobox/index.d.ts +1 -1
  30. package/dist/data/createAsyncList.d.ts +4 -4
  31. package/dist/data/createListData.d.ts +2 -2
  32. package/dist/data/createTreeData.d.ts +6 -2
  33. package/dist/data/index.d.ts +3 -3
  34. package/dist/datepicker/index.d.ts +2 -4
  35. package/dist/disclosure/createDisclosureState.d.ts +2 -2
  36. package/dist/disclosure/index.d.ts +1 -1
  37. package/dist/dnd/createDragState.d.ts +2 -2
  38. package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
  39. package/dist/dnd/createDropState.d.ts +2 -2
  40. package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
  41. package/dist/dnd/index.d.ts +5 -5
  42. package/dist/dnd/types.d.ts +19 -17
  43. package/dist/form/createFormValidationState.d.ts +3 -3
  44. package/dist/form/index.d.ts +1 -1
  45. package/dist/grid/createGridState.d.ts +2 -2
  46. package/dist/grid/index.d.ts +2 -2
  47. package/dist/grid/types.d.ts +27 -11
  48. package/dist/index.d.ts +31 -30
  49. package/dist/index.js +9077 -7246
  50. package/dist/index.js.map +1 -1
  51. package/dist/menu/index.d.ts +2 -2
  52. package/dist/numberfield/createNumberFieldState.d.ts +2 -2
  53. package/dist/numberfield/index.d.ts +1 -1
  54. package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
  55. package/dist/overlays/index.d.ts +1 -1
  56. package/dist/radio/createRadioGroupState.d.ts +7 -7
  57. package/dist/radio/index.d.ts +1 -1
  58. package/dist/searchfield/createSearchFieldState.d.ts +2 -2
  59. package/dist/searchfield/index.d.ts +2 -2
  60. package/dist/select/createSelectState.d.ts +14 -8
  61. package/dist/select/index.d.ts +1 -1
  62. package/dist/selection/Selection.d.ts +16 -0
  63. package/dist/selection/SelectionManager.d.ts +110 -0
  64. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  65. package/dist/selection/index.d.ts +6 -5
  66. package/dist/slider/createSliderState.d.ts +3 -3
  67. package/dist/slider/index.d.ts +2 -2
  68. package/dist/steplist/createStepListState.d.ts +48 -0
  69. package/dist/steplist/index.d.ts +1 -0
  70. package/dist/table/TableCollection.d.ts +33 -3
  71. package/dist/table/createTableColumnResizeState.d.ts +54 -0
  72. package/dist/table/createTableState.d.ts +2 -2
  73. package/dist/table/createTreeGridState.d.ts +15 -0
  74. package/dist/table/index.d.ts +6 -3
  75. package/dist/table/types.d.ts +68 -10
  76. package/dist/tabs/createTabListState.d.ts +5 -5
  77. package/dist/tabs/index.d.ts +1 -1
  78. package/dist/textfield/createTextFieldState.d.ts +2 -2
  79. package/dist/textfield/index.d.ts +1 -1
  80. package/dist/toast/createToastState.d.ts +24 -2
  81. package/dist/toast/index.d.ts +1 -1
  82. package/dist/toggle/createToggleGroupState.d.ts +4 -4
  83. package/dist/toggle/createToggleState.d.ts +2 -2
  84. package/dist/toggle/index.d.ts +2 -2
  85. package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
  86. package/dist/tooltip/index.d.ts +1 -1
  87. package/dist/tree/TreeCollection.d.ts +2 -2
  88. package/dist/tree/createTreeState.d.ts +2 -2
  89. package/dist/tree/index.d.ts +3 -3
  90. package/dist/tree/types.d.ts +27 -13
  91. package/dist/utils/index.d.ts +1 -1
  92. package/dist/utils/reactivity.d.ts +1 -1
  93. package/package.json +29 -30
  94. package/src/autocomplete/createAutocompleteState.d.ts +19 -17
  95. package/src/autocomplete/createAutocompleteState.ts +24 -30
  96. package/src/autocomplete/index.d.ts +6 -2
  97. package/src/autocomplete/index.ts +1 -1
  98. package/src/calendar/IncompleteDate.ts +430 -0
  99. package/src/calendar/createCalendarState.d.ts +139 -117
  100. package/src/calendar/createCalendarState.ts +308 -94
  101. package/src/calendar/createDateFieldState.d.ts +130 -95
  102. package/src/calendar/createDateFieldState.ts +447 -334
  103. package/src/calendar/createDatePickerState.ts +347 -0
  104. package/src/calendar/createRangeCalendarState.d.ts +154 -129
  105. package/src/calendar/createRangeCalendarState.ts +411 -85
  106. package/src/calendar/createTimeFieldState.d.ts +81 -79
  107. package/src/calendar/createTimeFieldState.ts +187 -108
  108. package/src/calendar/index.d.ts +80 -7
  109. package/src/calendar/index.ts +23 -12
  110. package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
  111. package/src/checkbox/createCheckboxGroupState.ts +20 -29
  112. package/src/checkbox/index.d.ts +6 -2
  113. package/src/checkbox/index.ts +1 -1
  114. package/src/collections/ListCollection.d.ts +25 -22
  115. package/src/collections/ListCollection.ts +6 -6
  116. package/src/collections/createListState.d.ts +73 -58
  117. package/src/collections/createListState.ts +67 -35
  118. package/src/collections/createMenuState.d.ts +36 -31
  119. package/src/collections/createMenuState.ts +21 -12
  120. package/src/collections/createSelectionState.d.ts +78 -65
  121. package/src/collections/createSelectionState.ts +66 -52
  122. package/src/collections/getChildNodes.ts +98 -0
  123. package/src/collections/index.d.ts +38 -6
  124. package/src/collections/index.ts +14 -10
  125. package/src/collections/types.d.ts +101 -101
  126. package/src/collections/types.ts +12 -12
  127. package/src/color/Color.d.ts +20 -5
  128. package/src/color/Color.ts +357 -254
  129. package/src/color/createColorAreaState.d.ts +67 -65
  130. package/src/color/createColorAreaState.ts +21 -13
  131. package/src/color/createColorFieldState.d.ts +46 -44
  132. package/src/color/createColorFieldState.ts +285 -149
  133. package/src/color/createColorSliderState.d.ts +58 -56
  134. package/src/color/createColorSliderState.ts +45 -14
  135. package/src/color/createColorWheelState.d.ts +42 -40
  136. package/src/color/createColorWheelState.ts +101 -64
  137. package/src/color/getColorChannels.ts +7 -5
  138. package/src/color/index.d.ts +36 -7
  139. package/src/color/index.ts +6 -9
  140. package/src/color/types.d.ts +68 -63
  141. package/src/color/types.ts +11 -19
  142. package/src/combobox/createComboBoxState.d.ts +107 -105
  143. package/src/combobox/createComboBoxState.ts +184 -89
  144. package/src/combobox/index.d.ts +10 -2
  145. package/src/combobox/index.ts +1 -1
  146. package/src/data/createAsyncList.ts +89 -63
  147. package/src/data/createListData.ts +67 -67
  148. package/src/data/createTreeData.ts +304 -91
  149. package/src/data/index.ts +3 -3
  150. package/src/datepicker/index.ts +6 -10
  151. package/src/disclosure/createDisclosureState.d.ts +47 -43
  152. package/src/disclosure/createDisclosureState.ts +7 -21
  153. package/src/disclosure/index.d.ts +10 -2
  154. package/src/disclosure/index.ts +1 -1
  155. package/src/dnd/createDragState.d.ts +51 -42
  156. package/src/dnd/createDragState.ts +12 -14
  157. package/src/dnd/createDraggableCollectionState.d.ts +52 -41
  158. package/src/dnd/createDraggableCollectionState.ts +14 -21
  159. package/src/dnd/createDropState.d.ts +64 -45
  160. package/src/dnd/createDropState.ts +22 -32
  161. package/src/dnd/createDroppableCollectionState.d.ts +88 -59
  162. package/src/dnd/createDroppableCollectionState.ts +40 -43
  163. package/src/dnd/index.d.ts +55 -6
  164. package/src/dnd/index.ts +6 -18
  165. package/src/dnd/types.d.ts +170 -157
  166. package/src/dnd/types.ts +24 -22
  167. package/src/form/createFormValidationState.d.ts +47 -45
  168. package/src/form/createFormValidationState.ts +30 -63
  169. package/src/form/index.d.ts +16 -2
  170. package/src/form/index.ts +1 -1
  171. package/src/grid/createGridState.d.ts +7 -4
  172. package/src/grid/createGridState.ts +78 -51
  173. package/src/grid/index.d.ts +3 -3
  174. package/src/grid/index.ts +2 -8
  175. package/src/grid/types.d.ts +136 -129
  176. package/src/grid/types.ts +34 -18
  177. package/src/index.d.ts +311 -26
  178. package/src/index.ts +59 -89
  179. package/src/menu/index.ts +2 -4
  180. package/src/numberfield/createNumberFieldState.d.ts +56 -54
  181. package/src/numberfield/createNumberFieldState.ts +26 -31
  182. package/src/numberfield/index.d.ts +6 -2
  183. package/src/numberfield/index.ts +1 -1
  184. package/src/overlays/createOverlayTriggerState.d.ts +22 -20
  185. package/src/overlays/createOverlayTriggerState.ts +3 -4
  186. package/src/overlays/index.d.ts +6 -2
  187. package/src/overlays/index.ts +1 -1
  188. package/src/radio/createRadioGroupState.d.ts +54 -52
  189. package/src/radio/createRadioGroupState.ts +27 -29
  190. package/src/radio/index.d.ts +7 -2
  191. package/src/radio/index.ts +1 -1
  192. package/src/searchfield/createSearchFieldState.d.ts +16 -14
  193. package/src/searchfield/createSearchFieldState.ts +5 -11
  194. package/src/searchfield/index.d.ts +3 -3
  195. package/src/searchfield/index.ts +2 -5
  196. package/src/select/createSelectState.d.ts +63 -61
  197. package/src/select/createSelectState.ts +42 -29
  198. package/src/select/index.d.ts +2 -2
  199. package/src/select/index.ts +1 -5
  200. package/src/selection/Selection.ts +28 -0
  201. package/src/selection/SelectionManager.ts +526 -0
  202. package/src/selection/createMultipleSelectionState.ts +247 -0
  203. package/src/selection/index.ts +12 -7
  204. package/src/slider/createSliderState.d.ts +60 -60
  205. package/src/slider/createSliderState.ts +55 -33
  206. package/src/slider/index.d.ts +3 -3
  207. package/src/slider/index.ts +2 -6
  208. package/src/ssr/index.d.ts +1 -1
  209. package/src/ssr/index.ts +2 -2
  210. package/src/steplist/createStepListState.ts +191 -0
  211. package/src/steplist/index.ts +5 -0
  212. package/src/table/TableCollection.d.ts +42 -40
  213. package/src/table/TableCollection.ts +266 -99
  214. package/src/table/createTableColumnResizeState.ts +242 -0
  215. package/src/table/createTableState.d.ts +7 -4
  216. package/src/table/createTableState.ts +30 -7
  217. package/src/table/createTreeGridState.ts +146 -0
  218. package/src/table/index.d.ts +14 -4
  219. package/src/table/index.ts +13 -3
  220. package/src/table/types.d.ts +98 -92
  221. package/src/table/types.ts +83 -13
  222. package/src/tabs/createTabListState.d.ts +59 -57
  223. package/src/tabs/createTabListState.ts +34 -39
  224. package/src/tabs/index.d.ts +8 -2
  225. package/src/tabs/index.ts +1 -1
  226. package/src/textfield/createTextFieldState.d.ts +16 -14
  227. package/src/textfield/createTextFieldState.ts +7 -18
  228. package/src/textfield/index.d.ts +6 -2
  229. package/src/textfield/index.ts +1 -1
  230. package/src/toast/createToastState.d.ts +92 -86
  231. package/src/toast/createToastState.ts +90 -52
  232. package/src/toast/index.d.ts +12 -2
  233. package/src/toast/index.ts +2 -1
  234. package/src/toggle/createToggleGroupState.ts +11 -21
  235. package/src/toggle/createToggleState.d.ts +19 -19
  236. package/src/toggle/createToggleState.ts +2 -16
  237. package/src/toggle/index.d.ts +2 -2
  238. package/src/toggle/index.ts +2 -6
  239. package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
  240. package/src/tooltip/createTooltipTriggerState.ts +12 -11
  241. package/src/tooltip/index.d.ts +7 -2
  242. package/src/tooltip/index.ts +1 -1
  243. package/src/tree/TreeCollection.d.ts +26 -23
  244. package/src/tree/TreeCollection.ts +29 -22
  245. package/src/tree/createTreeState.d.ts +7 -4
  246. package/src/tree/createTreeState.ts +70 -41
  247. package/src/tree/index.d.ts +10 -4
  248. package/src/tree/index.ts +3 -3
  249. package/src/tree/types.d.ts +131 -126
  250. package/src/tree/types.ts +30 -13
  251. package/src/utils/index.d.ts +2 -2
  252. package/src/utils/index.ts +1 -1
  253. package/src/utils/reactivity.d.ts +2 -2
  254. package/src/utils/reactivity.ts +3 -3
@@ -1,16 +1,22 @@
1
1
  /**
2
2
  * TableCollection - A collection class for table data.
3
3
  * Based on @react-stately/table/TableCollection.
4
+ *
5
+ * Supports a plain (flat) table and, when an `expandedKeys` option is supplied, a
6
+ * tree grid: `childRows` are walked recursively, each parent row keeps its full
7
+ * structure (cells followed by every child row), and only rows whose ancestors are
8
+ * all expanded are flattened into the navigable body. Mirrors the structure produced
9
+ * by `@react-stately/table`'s `generateTreeGridCollection`.
4
10
  */
5
11
 
6
- import type { Key } from '../collections/types';
7
- import type { GridNode, GridNodeType } from '../grid/types';
12
+ import type { Key } from "../collections/types";
13
+ import type { GridNode, GridNodeType } from "../grid/types";
8
14
  import type {
9
15
  TableCollection as ITableCollection,
10
16
  TableCollectionOptions,
11
17
  ColumnDefinition,
12
18
  RowDefinition,
13
- } from './types';
19
+ } from "./types";
14
20
 
15
21
  /**
16
22
  * Creates a table collection from column and row definitions.
@@ -24,6 +30,10 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
24
30
  private _keyMap: Map<Key, GridNode<T>> = new Map();
25
31
  private _rowHeaderColumnKeys: Set<Key>;
26
32
  private _size: number = 0;
33
+ /** Visible rows in DFS order. Drives size, navigation, iteration and the `rows` list. */
34
+ private _flattenedRows: GridNode<T>[] = [];
35
+ private _treeColumn: Key | null = null;
36
+ private _userColumnCount: number = 0;
27
37
 
28
38
  constructor(options: TableCollectionOptions<T>) {
29
39
  const {
@@ -33,79 +43,95 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
33
43
  getTextValue,
34
44
  showSelectionCheckboxes = false,
35
45
  rowHeaderColumnKeys,
46
+ expandedKeys,
36
47
  } = options;
37
48
 
38
- // Build columns
49
+ // Passing `expandedKeys` (even an empty set) selects tree-grid mode.
50
+ const isTreeGrid = expandedKeys !== undefined;
51
+
39
52
  this._columns = this.buildColumns(columns, showSelectionCheckboxes);
40
53
 
41
- // Determine row header column keys
42
54
  this._rowHeaderColumnKeys = rowHeaderColumnKeys ?? this.getDefaultRowHeaderColumnKeys();
43
55
 
44
- // Build header rows
45
56
  this._headerRows = this.buildHeaderRows();
46
57
 
47
- // Build head node
48
58
  if (this._headerRows.length > 0) {
49
59
  this._head = {
50
- type: 'headerrow' as GridNodeType,
51
- key: 'header',
60
+ type: "headerrow" as GridNodeType,
61
+ key: "header",
52
62
  index: 0,
53
63
  level: 0,
54
64
  hasChildNodes: true,
55
65
  childNodes: this._headerRows,
56
66
  value: null,
57
- textValue: '',
67
+ textValue: "",
58
68
  };
59
- this._keyMap.set('header', this._head);
69
+ this._keyMap.set("header", this._head);
70
+ }
71
+
72
+ let bodyChildren: GridNode<T>[];
73
+ if (isTreeGrid) {
74
+ const built = this.buildTreeRows(rows, expandedKeys, getKey, getTextValue);
75
+ bodyChildren = built.topLevelRows;
76
+ this._flattenedRows = built.flattenedRows;
77
+ this._userColumnCount = this.countLeafColumns();
78
+ this._treeColumn =
79
+ options.treeColumn !== undefined
80
+ ? options.treeColumn
81
+ : (this._rowHeaderColumnKeys.values().next().value ?? null);
82
+ } else {
83
+ const bodyRows = this.buildRows(rows, getKey, getTextValue);
84
+ bodyChildren = bodyRows;
85
+ this._flattenedRows = bodyRows;
86
+ this._userColumnCount = this.countLeafColumns();
87
+ this._treeColumn = null;
60
88
  }
61
89
 
62
- // Build body rows
63
- const bodyRows = this.buildRows(rows, getKey, getTextValue);
64
- this._size = bodyRows.length;
90
+ this._size = this._flattenedRows.length;
65
91
 
66
- // Build body node
67
92
  this._body = {
68
- type: 'item' as GridNodeType,
69
- key: 'body',
93
+ type: "item" as GridNodeType,
94
+ key: "body",
70
95
  index: this._headerRows.length,
71
96
  level: 0,
72
97
  hasChildNodes: true,
73
- childNodes: bodyRows,
98
+ // The body's children retain the full tree structure (top-level rows with their
99
+ // child rows nested inside) so sibling-based aria-posinset/aria-setsize work; the
100
+ // flattened visible list lives in `_flattenedRows`. For a flat table these are the
101
+ // same array.
102
+ childNodes: bodyChildren,
74
103
  value: null,
75
- textValue: '',
104
+ textValue: "",
76
105
  };
77
- this._keyMap.set('body', this._body);
106
+ this._keyMap.set("body", this._body);
78
107
 
79
- // Combine all rows
80
- this._rows = [...this._headerRows, ...bodyRows];
108
+ this._rows = [...this._headerRows, ...this._flattenedRows];
81
109
  }
82
110
 
83
111
  private buildColumns(
84
112
  columns: ColumnDefinition<T>[],
85
- showSelectionCheckboxes: boolean
113
+ showSelectionCheckboxes: boolean,
86
114
  ): GridNode<T>[] {
87
115
  const result: GridNode<T>[] = [];
88
116
  let colIndex = 0;
89
117
 
90
- // Add selection column if needed
91
118
  if (showSelectionCheckboxes) {
92
119
  const selectionColumn: GridNode<T> = {
93
- type: 'column' as GridNodeType,
94
- key: '__selection__',
120
+ type: "column" as GridNodeType,
121
+ key: "__selection__",
95
122
  index: colIndex,
96
123
  column: 0,
97
124
  level: 0,
98
125
  hasChildNodes: false,
99
126
  childNodes: [],
100
127
  value: null,
101
- textValue: 'Selection',
128
+ textValue: "Selection",
102
129
  };
103
130
  result.push(selectionColumn);
104
131
  this._keyMap.set(selectionColumn.key, selectionColumn);
105
132
  colIndex++;
106
133
  }
107
134
 
108
- // Build user columns
109
135
  for (const col of columns) {
110
136
  const node = this.buildColumnNode(col, colIndex);
111
137
  result.push(node);
@@ -116,24 +142,25 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
116
142
  }
117
143
 
118
144
  private buildColumnNode(col: ColumnDefinition<T>, index: number): GridNode<T> {
145
+ const key = col.key ?? (col as ColumnDefinition<T> & { id?: Key }).id ?? index;
119
146
  const node: GridNode<T> = {
120
- type: 'column' as GridNodeType,
121
- key: col.key,
147
+ type: "column" as GridNodeType,
148
+ key,
122
149
  index,
123
150
  column: index,
124
151
  level: 0,
125
152
  hasChildNodes: (col.children?.length ?? 0) > 0,
126
153
  childNodes: [],
127
154
  value: null,
128
- textValue: col.textValue ?? col.name ?? String(col.key),
155
+ props: { columnDefinition: { ...col, key } },
156
+ textValue: col.textValue ?? col.name ?? String(key),
129
157
  };
130
158
 
131
- // Build child columns if present (for column groups)
132
159
  if (col.children && col.children.length > 0) {
133
160
  let childIndex = 0;
134
161
  for (const child of col.children) {
135
162
  const childNode = this.buildColumnNode(child, childIndex);
136
- childNode.parentKey = col.key;
163
+ childNode.parentKey = key;
137
164
  childNode.level = 1;
138
165
  node.childNodes.push(childNode);
139
166
  childIndex++;
@@ -145,23 +172,51 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
145
172
  }
146
173
 
147
174
  private getDefaultRowHeaderColumnKeys(): Set<Key> {
148
- // Default to first non-selection column as row header
175
+ const explicitRowHeaderKeys = this._columns
176
+ .filter((col) => col.key !== "__selection__")
177
+ .filter((col) => {
178
+ const definition = col.props?.columnDefinition as ColumnDefinition<T> | undefined;
179
+ return !!definition?.isRowHeader;
180
+ })
181
+ .map((col) => col.key);
182
+
183
+ if (explicitRowHeaderKeys.length > 0) {
184
+ return new Set(explicitRowHeaderKeys);
185
+ }
186
+
187
+ // Default to first non-selection column as row header.
149
188
  for (const col of this._columns) {
150
- if (col.key !== '__selection__') {
189
+ if (col.key !== "__selection__") {
151
190
  return new Set([col.key]);
152
191
  }
153
192
  }
154
193
  return new Set();
155
194
  }
156
195
 
196
+ /** Counts the leaf columns supplied by the user (excludes the selection column). */
197
+ private countLeafColumns(): number {
198
+ let count = 0;
199
+ const visit = (col: GridNode<T>) => {
200
+ if (col.key === "__selection__") {
201
+ return;
202
+ }
203
+ if (col.childNodes.length > 0) {
204
+ col.childNodes.forEach(visit);
205
+ } else {
206
+ count++;
207
+ }
208
+ };
209
+ this._columns.forEach(visit);
210
+ return count;
211
+ }
212
+
157
213
  private buildHeaderRows(): GridNode<T>[] {
158
214
  if (this._columns.length === 0) {
159
215
  return [];
160
216
  }
161
217
 
162
- // Build a single header row containing all columns
163
218
  const headerCells: GridNode<T>[] = this._columns.map((col, index) => ({
164
- type: 'column' as GridNodeType,
219
+ type: "column" as GridNodeType,
165
220
  key: `header-${col.key}`,
166
221
  index,
167
222
  column: index,
@@ -170,19 +225,19 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
170
225
  childNodes: [],
171
226
  value: null,
172
227
  textValue: col.textValue,
173
- parentKey: 'headerrow-0',
228
+ parentKey: "headerrow-0",
174
229
  }));
175
230
 
176
231
  const headerRow: GridNode<T> = {
177
- type: 'headerrow' as GridNodeType,
178
- key: 'headerrow-0',
232
+ type: "headerrow" as GridNodeType,
233
+ key: "headerrow-0",
179
234
  index: 0,
180
235
  rowIndex: 0,
181
236
  level: 0,
182
237
  hasChildNodes: true,
183
238
  childNodes: headerCells,
184
239
  value: null,
185
- textValue: '',
240
+ textValue: "",
186
241
  };
187
242
 
188
243
  this._keyMap.set(headerRow.key, headerRow);
@@ -191,10 +246,51 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
191
246
  return [headerRow];
192
247
  }
193
248
 
249
+ /**
250
+ * Builds the cell nodes for one row. Does not register the cells in the key map;
251
+ * the caller registers cells once the owning row is known to be present.
252
+ */
253
+ private buildCells(
254
+ rowKey: Key,
255
+ value: T,
256
+ rowLevel: number,
257
+ getTextValue?: (item: T, column: ColumnDefinition<T>) => string,
258
+ ): GridNode<T>[] {
259
+ return this._columns.map((col, colIndex) => {
260
+ const cellKey = `${rowKey}-${col.key}`;
261
+ const columnDefinition =
262
+ (col.props?.columnDefinition as ColumnDefinition<T> | undefined) ??
263
+ ({ key: col.key } as ColumnDefinition<T>);
264
+ const cellTextValue =
265
+ col.key === "__selection__"
266
+ ? "Selection"
267
+ : getTextValue
268
+ ? getTextValue(value, columnDefinition)
269
+ : String((value as Record<string, unknown>)?.[String(col.key)] ?? "");
270
+
271
+ const cell: GridNode<T> = {
272
+ type: this._rowHeaderColumnKeys.has(col.key)
273
+ ? ("rowheader" as GridNodeType)
274
+ : ("cell" as GridNodeType),
275
+ key: cellKey,
276
+ index: colIndex,
277
+ column: colIndex,
278
+ level: rowLevel + 1,
279
+ hasChildNodes: false,
280
+ childNodes: [],
281
+ value,
282
+ textValue: cellTextValue,
283
+ parentKey: rowKey,
284
+ };
285
+
286
+ return cell;
287
+ });
288
+ }
289
+
194
290
  private buildRows(
195
291
  rows: RowDefinition<T>[] | T[],
196
292
  getKey?: (item: T) => Key,
197
- getTextValue?: (item: T, column: ColumnDefinition<T>) => string
293
+ getTextValue?: (item: T, column: ColumnDefinition<T>) => string,
198
294
  ): GridNode<T>[] {
199
295
  const result: GridNode<T>[] = [];
200
296
  const rowIndexOffset = this._headerRows.length;
@@ -203,41 +299,19 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
203
299
  const row = rows[i];
204
300
  const isRowDef = this.isRowDefinition(row);
205
301
 
206
- const key = isRowDef ? row.key : getKey?.(row as T) ?? i;
302
+ const key = isRowDef ? row.key : (getKey?.(row as T) ?? i);
207
303
  const value = isRowDef ? row.value : (row as T);
208
- const textValue = isRowDef ? row.textValue : undefined;
209
-
210
- // Build cells for this row
211
- const cells: GridNode<T>[] = this._columns.map((col, colIndex) => {
212
- const cellKey = `${key}-${col.key}`;
213
- const cellTextValue =
214
- col.key === '__selection__'
215
- ? 'Selection'
216
- : getTextValue
217
- ? getTextValue(value, { key: col.key } as ColumnDefinition<T>)
218
- : String((value as Record<string, unknown>)?.[String(col.key)] ?? '');
219
-
220
- const cell: GridNode<T> = {
221
- type: this._rowHeaderColumnKeys.has(col.key)
222
- ? ('rowheader' as GridNodeType)
223
- : ('cell' as GridNodeType),
224
- key: cellKey,
225
- index: colIndex,
226
- column: colIndex,
227
- level: 1,
228
- hasChildNodes: false,
229
- childNodes: [],
230
- value,
231
- textValue: cellTextValue,
232
- parentKey: key,
233
- };
234
-
235
- this._keyMap.set(cellKey, cell);
236
- return cell;
237
- });
304
+ const textValue = isRowDef
305
+ ? row.textValue
306
+ : typeof (row as Record<string, unknown>)?.textValue === "string"
307
+ ? ((row as Record<string, unknown>).textValue as string)
308
+ : undefined;
309
+
310
+ const cells = this.buildCells(key, value as T, 0, getTextValue);
311
+ cells.forEach((cell) => this._keyMap.set(cell.key, cell));
238
312
 
239
313
  const rowNode: GridNode<T> = {
240
- type: 'item' as GridNodeType,
314
+ type: "item" as GridNodeType,
241
315
  key,
242
316
  index: i,
243
317
  rowIndex: rowIndexOffset + i,
@@ -245,7 +319,7 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
245
319
  hasChildNodes: true,
246
320
  childNodes: cells,
247
321
  value,
248
- textValue: textValue ?? cells.map((c) => c.textValue).join(' '),
322
+ textValue: textValue ?? cells.map((c) => c.textValue).join(" "),
249
323
  };
250
324
 
251
325
  this._keyMap.set(key, rowNode);
@@ -255,16 +329,101 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
255
329
  return result;
256
330
  }
257
331
 
332
+ /**
333
+ * Builds the tree-grid rows: every row keeps its full structure (cells then all child
334
+ * rows, expanded or not) while only the rows whose ancestors are all expanded are
335
+ * registered in the key map and pushed into the flattened body. Mirrors
336
+ * `generateTreeGridCollection` upstream.
337
+ */
338
+ private buildTreeRows(
339
+ rows: RowDefinition<T>[] | T[],
340
+ expandedKeys: "all" | Set<Key>,
341
+ getKey?: (item: T) => Key,
342
+ getTextValue?: (item: T, column: ColumnDefinition<T>) => string,
343
+ ): { topLevelRows: GridNode<T>[]; flattenedRows: GridNode<T>[] } {
344
+ const isExpanded = (key: Key): boolean => expandedKeys === "all" || expandedKeys.has(key);
345
+
346
+ // Build the full tree structure without touching the key map. `index` is the row's
347
+ // position among its same-level siblings, so aria-posinset/aria-setsize read directly.
348
+ const buildRowNode = (
349
+ row: RowDefinition<T> | T,
350
+ level: number,
351
+ parentKey: Key | null,
352
+ indexAmongSiblings: number,
353
+ ): GridNode<T> => {
354
+ const isRowDef = this.isRowDefinition(row);
355
+ const key = isRowDef ? row.key : (getKey?.(row as T) ?? indexAmongSiblings);
356
+ const value = (isRowDef ? row.value : (row as T)) as T;
357
+ const textValue = isRowDef
358
+ ? row.textValue
359
+ : typeof (row as Record<string, unknown>)?.textValue === "string"
360
+ ? ((row as Record<string, unknown>).textValue as string)
361
+ : undefined;
362
+
363
+ const cells = this.buildCells(key, value, level, getTextValue);
364
+
365
+ const childRowDefs = isRowDef ? (row.childRows ?? []) : [];
366
+ const hasChildRows = childRowDefs.length > 0 || (isRowDef && !!row.hasChildRows);
367
+
368
+ const childRowNodes = childRowDefs.map((child, childIdx) =>
369
+ buildRowNode(child, level + 1, key, childIdx),
370
+ );
371
+
372
+ const childNodes = [...cells, ...childRowNodes];
373
+ const rowNode: GridNode<T> = {
374
+ type: "item" as GridNodeType,
375
+ key,
376
+ index: indexAmongSiblings,
377
+ level,
378
+ parentKey,
379
+ hasChildNodes: true,
380
+ childNodes,
381
+ value,
382
+ textValue: textValue ?? cells.map((c) => c.textValue).join(" "),
383
+ isExpandable: hasChildRows,
384
+ isExpanded: hasChildRows && isExpanded(key),
385
+ firstChildKey: childNodes[0]?.key ?? null,
386
+ lastChildKey: childNodes[childNodes.length - 1]?.key ?? null,
387
+ };
388
+
389
+ return rowNode;
390
+ };
391
+
392
+ const topLevelRows = rows.map((row, i) => buildRowNode(row, 0, "body", i));
393
+
394
+ // Flatten the visible rows (DFS). A row is visited iff every ancestor is expanded;
395
+ // visiting a row registers it and its cells, then descends into its child rows only
396
+ // when the row itself is expanded.
397
+ const flattenedRows: GridNode<T>[] = [];
398
+ const rowIndexOffset = this._headerRows.length;
399
+ const visit = (node: GridNode<T>) => {
400
+ node.rowIndex = rowIndexOffset + flattenedRows.length;
401
+ flattenedRows.push(node);
402
+ this._keyMap.set(node.key, node);
403
+ for (const child of node.childNodes) {
404
+ if (child.type !== "item") {
405
+ this._keyMap.set(child.key, child);
406
+ }
407
+ }
408
+ if (isExpanded(node.key)) {
409
+ for (const child of node.childNodes) {
410
+ if (child.type === "item") {
411
+ visit(child);
412
+ }
413
+ }
414
+ }
415
+ };
416
+ for (const node of topLevelRows) {
417
+ visit(node);
418
+ }
419
+
420
+ return { topLevelRows, flattenedRows };
421
+ }
422
+
258
423
  private isRowDefinition(row: unknown): row is RowDefinition<T> {
259
- return (
260
- typeof row === 'object' &&
261
- row !== null &&
262
- 'key' in row &&
263
- 'value' in row
264
- );
424
+ return typeof row === "object" && row !== null && "key" in row && "value" in row;
265
425
  }
266
426
 
267
- // Collection interface implementation
268
427
  get size(): number {
269
428
  return this._size;
270
429
  }
@@ -301,6 +460,21 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
301
460
  return this._rowHeaderColumnKeys;
302
461
  }
303
462
 
463
+ /** The key of the column that renders the tree-grid affordance, or `null` for a flat table. */
464
+ get treeColumn(): Key | null {
465
+ return this._treeColumn;
466
+ }
467
+
468
+ /** The number of leaf columns provided by the user (excludes the selection column). */
469
+ get userColumnCount(): number {
470
+ return this._userColumnCount;
471
+ }
472
+
473
+ /** The key map of all present nodes (visible rows, their cells, columns and the body). */
474
+ get keyMap(): Map<Key, GridNode<T>> {
475
+ return this._keyMap;
476
+ }
477
+
304
478
  getKeys(): Iterable<Key> {
305
479
  return this._keyMap.keys();
306
480
  }
@@ -310,11 +484,10 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
310
484
  }
311
485
 
312
486
  at(index: number): GridNode<T> | null {
313
- const bodyRows = this._body.childNodes;
314
- if (index < 0 || index >= bodyRows.length) {
487
+ if (index < 0 || index >= this._flattenedRows.length) {
315
488
  return null;
316
489
  }
317
- return bodyRows[index];
490
+ return this._flattenedRows[index];
318
491
  }
319
492
 
320
493
  getChildren(key: Key): Iterable<GridNode<T>> {
@@ -324,7 +497,7 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
324
497
 
325
498
  getTextValue(key: Key): string {
326
499
  const node = this._keyMap.get(key);
327
- return node?.textValue ?? '';
500
+ return node?.textValue ?? "";
328
501
  }
329
502
 
330
503
  getCell(rowKey: Key, columnKey: Key): GridNode<T> | null {
@@ -333,14 +506,12 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
333
506
  }
334
507
 
335
508
  getFirstKey(): Key | null {
336
- // Return first body row key
337
- const bodyRows = this._body.childNodes;
509
+ const bodyRows = this._flattenedRows;
338
510
  return bodyRows.length > 0 ? bodyRows[0].key : null;
339
511
  }
340
512
 
341
513
  getLastKey(): Key | null {
342
- // Return last body row key
343
- const bodyRows = this._body.childNodes;
514
+ const bodyRows = this._flattenedRows;
344
515
  return bodyRows.length > 0 ? bodyRows[bodyRows.length - 1].key : null;
345
516
  }
346
517
 
@@ -348,8 +519,7 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
348
519
  const node = this._keyMap.get(key);
349
520
  if (!node) return null;
350
521
 
351
- // Find in body rows
352
- const bodyRows = this._body.childNodes;
522
+ const bodyRows = this._flattenedRows;
353
523
  const index = bodyRows.findIndex((r) => r.key === key);
354
524
  if (index > 0) {
355
525
  return bodyRows[index - 1].key;
@@ -361,8 +531,7 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
361
531
  const node = this._keyMap.get(key);
362
532
  if (!node) return null;
363
533
 
364
- // Find in body rows
365
- const bodyRows = this._body.childNodes;
534
+ const bodyRows = this._flattenedRows;
366
535
  const index = bodyRows.findIndex((r) => r.key === key);
367
536
  if (index >= 0 && index < bodyRows.length - 1) {
368
537
  return bodyRows[index + 1].key;
@@ -371,8 +540,8 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
371
540
  }
372
541
 
373
542
  *[Symbol.iterator](): Iterator<GridNode<T>> {
374
- // Only iterate body rows, not header rows
375
- for (const row of this._body.childNodes) {
543
+ // Only iterate visible body rows, not header rows.
544
+ for (const row of this._flattenedRows) {
376
545
  yield row;
377
546
  }
378
547
  }
@@ -381,8 +550,6 @@ export class TableCollection<T = unknown> implements ITableCollection<T> {
381
550
  /**
382
551
  * Creates a table collection from options.
383
552
  */
384
- export function createTableCollection<T>(
385
- options: TableCollectionOptions<T>
386
- ): TableCollection<T> {
553
+ export function createTableCollection<T>(options: TableCollectionOptions<T>): TableCollection<T> {
387
554
  return new TableCollection(options);
388
555
  }