@box-kite/react 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/.claude/rules/box-kite-rules.md +23 -1
  2. package/.claude/skills/box-kite/SKILL.md +134 -427
  3. package/.claude/skills/box-kite/references/components.md +235 -0
  4. package/.claude/skills/box-kite/references/extending.md +42 -0
  5. package/.claude/skills/box-kite/references/patterns.md +27 -0
  6. package/.claude/skills/box-kite/references/styling.md +222 -0
  7. package/.cursor/rules/box-kite.mdc +73 -0
  8. package/AGENTS.md +102 -0
  9. package/BOX_AI_CONTEXT.md +1124 -153
  10. package/BOX_KITE_AI_CONTEXT.md +1124 -153
  11. package/README.md +36 -2
  12. package/a11y.cjs +1 -1
  13. package/a11y.d.ts +1 -1
  14. package/a11y.mjs +3 -2
  15. package/anchor.cjs +1 -0
  16. package/anchor.d.ts +14 -0
  17. package/anchor.mjs +3 -0
  18. package/behavior.cjs +1 -1
  19. package/behavior.mjs +118 -136
  20. package/box.cjs +1 -1
  21. package/box.mjs +1 -1
  22. package/client.cjs +1 -1
  23. package/client.mjs +15 -14
  24. package/components/accordion.cjs +1 -0
  25. package/components/accordion.d.ts +114 -0
  26. package/components/accordion.mjs +251 -0
  27. package/components/button.cjs +1 -1
  28. package/components/button.d.ts +3 -0
  29. package/components/button.mjs +1 -1
  30. package/components/chart.cjs +1 -1
  31. package/components/chart.d.ts +2 -1
  32. package/components/chart.mjs +159 -184
  33. package/components/checkbox.cjs +1 -1
  34. package/components/checkbox.d.ts +8 -0
  35. package/components/checkbox.mjs +1 -1
  36. package/components/combobox/comboboxChips.d.ts +21 -0
  37. package/components/combobox/comboboxListbox.d.ts +47 -0
  38. package/components/combobox.cjs +1 -0
  39. package/components/combobox.d.ts +121 -0
  40. package/components/combobox.mjs +563 -0
  41. package/components/dataGrid/components/dataGridAggregateValue.d.ts +10 -0
  42. package/components/dataGrid/components/dataGridCell.d.ts +2 -1
  43. package/components/dataGrid/components/dataGridCellEditor.d.ts +15 -0
  44. package/components/dataGrid/components/dataGridCellPlaceholder.d.ts +14 -0
  45. package/components/dataGrid/components/dataGridCellTree.d.ts +16 -0
  46. package/components/dataGrid/components/dataGridDataSourceError.d.ts +12 -0
  47. package/components/dataGrid/components/dataGridExport.d.ts +10 -0
  48. package/components/dataGrid/components/dataGridFooter.d.ts +11 -0
  49. package/components/dataGrid/contracts/dataGridContract.d.ts +474 -16
  50. package/components/dataGrid/export/gridExport.d.ts +10 -0
  51. package/components/dataGrid/gridNavigationContext.d.ts +9 -1
  52. package/components/dataGrid/models/aggregateCellModel.d.ts +34 -0
  53. package/components/dataGrid/models/aggregationModel.d.ts +32 -0
  54. package/components/dataGrid/models/cellKeys.d.ts +11 -0
  55. package/components/dataGrid/models/cellModel.d.ts +27 -1
  56. package/components/dataGrid/models/columnModel.d.ts +12 -2
  57. package/components/dataGrid/models/dataSourceModel.d.ts +177 -0
  58. package/components/dataGrid/models/editModel.d.ts +77 -0
  59. package/components/dataGrid/models/exportModel.d.ts +51 -0
  60. package/components/dataGrid/models/gridModel.d.ts +108 -21
  61. package/components/dataGrid/models/groupRowCellModel.d.ts +11 -3
  62. package/components/dataGrid/models/groupRowModel.d.ts +22 -2
  63. package/components/dataGrid/models/rowModel.d.ts +13 -1
  64. package/components/dataGrid/models/sourceGroupRowModel.d.ts +45 -0
  65. package/components/dataGrid/models/sourceLevel.d.ts +71 -0
  66. package/components/dataGrid/models/sourceRowModel.d.ts +29 -0
  67. package/components/dataGrid/models/sourceTreeRowModel.d.ts +23 -0
  68. package/components/dataGrid/models/treeModel.d.ts +94 -0
  69. package/components/dataGrid/models/treeRow.d.ts +22 -0
  70. package/components/dataGrid/models/treeRowModel.d.ts +50 -0
  71. package/components/dataGrid.cjs +1 -1
  72. package/components/dataGrid.d.ts +4 -2
  73. package/components/dataGrid.mjs +4484 -2755
  74. package/components/dialog.cjs +1 -0
  75. package/components/dialog.d.ts +151 -0
  76. package/components/dialog.mjs +223 -0
  77. package/components/dropdown.cjs +1 -1
  78. package/components/dropdown.d.ts +30 -0
  79. package/components/dropdown.mjs +256 -244
  80. package/components/icon.cjs +1 -1
  81. package/components/icon.d.ts +2 -1
  82. package/components/icon.mjs +3 -3
  83. package/components/menu.cjs +1 -0
  84. package/components/menu.d.ts +234 -0
  85. package/components/menu.mjs +422 -0
  86. package/components/overlay.cjs +1 -1
  87. package/components/overlay.d.ts +30 -26
  88. package/components/overlay.mjs +45 -75
  89. package/components/popover.cjs +1 -0
  90. package/components/popover.d.ts +100 -0
  91. package/components/popover.mjs +69 -0
  92. package/components/presence.d.ts +6 -0
  93. package/components/progress.cjs +1 -0
  94. package/components/progress.d.ts +47 -0
  95. package/components/progress.mjs +28 -0
  96. package/components/radioButton.cjs +1 -1
  97. package/components/radioButton.d.ts +8 -0
  98. package/components/radioButton.mjs +1 -1
  99. package/components/radioGroup.cjs +1 -1
  100. package/components/radioGroup.d.ts +15 -2
  101. package/components/radioGroup.mjs +34 -30
  102. package/components/select.d.ts +9 -0
  103. package/components/semantics.cjs +1 -1
  104. package/components/semantics.d.ts +33 -28
  105. package/components/semantics.mjs +3 -3
  106. package/components/slider.cjs +1 -0
  107. package/components/slider.d.ts +98 -0
  108. package/components/slider.mjs +250 -0
  109. package/components/svg.cjs +1 -1
  110. package/components/svg.d.ts +25 -23
  111. package/components/svg.mjs +3 -3
  112. package/components/switch.cjs +1 -1
  113. package/components/switch.d.ts +9 -0
  114. package/components/switch.mjs +1 -1
  115. package/components/tabs.cjs +1 -0
  116. package/components/tabs.d.ts +87 -0
  117. package/components/tabs.mjs +324 -0
  118. package/components/textarea.cjs +1 -1
  119. package/components/textarea.d.ts +14 -0
  120. package/components/textarea.mjs +1 -1
  121. package/components/textbox.cjs +1 -1
  122. package/components/textbox.d.ts +12 -0
  123. package/components/textbox.mjs +1 -1
  124. package/components/toaster.cjs +1 -0
  125. package/components/toaster.d.ts +106 -0
  126. package/components/toaster.mjs +295 -0
  127. package/components/tooltip.cjs +1 -1
  128. package/components/tooltip.d.ts +39 -4
  129. package/components/tooltip.mjs +56 -54
  130. package/docs/a11y.md +199 -0
  131. package/docs/anchor.md +139 -0
  132. package/docs/components.md +48 -0
  133. package/docs/index.md +11 -0
  134. package/docs/props.md +1777 -0
  135. package/effects.cjs +1 -1
  136. package/effects.mjs +1 -1
  137. package/gridExport.cjs +2 -0
  138. package/gridExport.mjs +244 -0
  139. package/identity.cjs +1 -0
  140. package/identity.mjs +12 -0
  141. package/keyboard.cjs +1 -0
  142. package/keyboard.mjs +30 -0
  143. package/layer.cjs +1 -0
  144. package/layer.mjs +127 -0
  145. package/motion.cjs +1 -1
  146. package/motion.mjs +1 -1
  147. package/package.json +134 -10
  148. package/placement.cjs +1 -0
  149. package/placement.mjs +180 -0
  150. package/platform.cjs +1 -1
  151. package/platform.mjs +18 -5
  152. package/react/anchor/useAnchorPosition.d.ts +79 -0
  153. package/react/dialog/useScrollLock.d.ts +12 -0
  154. package/react/hooks/usePortalContainer.d.ts +6 -1
  155. package/react/hooks/useVirtualization.d.ts +24 -35
  156. package/react/popover/usePopoverLayer.d.ts +87 -0
  157. package/react-shared.cjs +1 -1
  158. package/react-shared.mjs +19 -2
  159. package/rsc.cjs +1 -1
  160. package/rsc.mjs +1 -1
  161. package/utils/accordion/accordionUtils.d.ts +48 -0
  162. package/utils/anchor/anchorUtils.d.ts +79 -0
  163. package/utils/array/lazyList.d.ts +10 -0
  164. package/utils/chart/chartPalette.d.ts +15 -0
  165. package/utils/chart/chartUtils.d.ts +2 -8
  166. package/utils/combobox/comboboxUtils.d.ts +89 -0
  167. package/utils/dom/domUtils.d.ts +6 -0
  168. package/utils/environment/environmentUtils.d.ts +10 -0
  169. package/utils/export/csvUtils.d.ts +23 -0
  170. package/utils/export/downloadUtils.d.ts +12 -0
  171. package/utils/export/xlsxUtils.d.ts +56 -0
  172. package/utils/export/zipUtils.d.ts +26 -0
  173. package/utils/keyboard/typeaheadUtils.d.ts +31 -0
  174. package/utils/menu/menuUtils.d.ts +25 -0
  175. package/utils/slider/sliderUtils.d.ts +67 -0
  176. package/utils/tabs/tabsUtils.d.ts +75 -0
  177. package/utils/toast/toastStore.d.ts +73 -0
  178. package/utils/virtual/virtualUtils.d.ts +47 -0
  179. /package/react/{a11y → identity}/useIdentifier.d.ts +0 -0
@@ -1,18 +1,20 @@
1
1
  import { ComponentsAndVariants } from '../../../types';
2
- import { ColumnFilters, DataGridProps, FilterValue, Key, PaginationState, PinPositionInput, SortDirection } from '../contracts/dataGridContract';
2
+ import { ColumnFilters, DataGridCsvOptions, DataGridProps, DataGridXlsxOptions, ExportConfig, FilterValue, Key, PaginationState, PinPositionInput, SortDirection } from '../contracts/dataGridContract';
3
+ import { default as AggregationModel } from './aggregationModel';
3
4
  import { default as ColumnModel } from './columnModel';
4
5
  import { default as ColumnVisibilityModel } from './columnVisibilityModel';
6
+ import { default as DataSourceModel } from './dataSourceModel';
5
7
  import { default as DetailRowModel } from './detailRowModel';
8
+ import { default as EditModel } from './editModel';
9
+ import { default as ExportModel } from './exportModel';
6
10
  import { default as FilterModel } from './filterModel';
7
11
  import { default as GroupRowModel } from './groupRowModel';
8
12
  import { default as PaginationModel } from './paginationModel';
9
13
  import { default as RowModel } from './rowModel';
14
+ import { default as TreeModel } from './treeModel';
10
15
  import { default as ViewportModel } from './viewportModel';
11
- export declare const ROW_NUMBER_CELL_KEY: Key;
16
+ export { GROUPING_CELL_KEY, ROW_DETAIL_CELL_KEY, ROW_NUMBER_CELL_KEY, ROW_SELECTION_CELL_KEY } from './cellKeys';
12
17
  export declare const DEFAULT_ROW_NUMBER_COLUMN_WIDTH = 70;
13
- export declare const ROW_SELECTION_CELL_KEY: Key;
14
- export declare const GROUPING_CELL_KEY: Key;
15
- export declare const ROW_DETAIL_CELL_KEY: Key;
16
18
  export default class GridModel<TRow> {
17
19
  props: DataGridProps<TRow>;
18
20
  constructor(props: DataGridProps<TRow>, onChange?: () => void);
@@ -30,6 +32,17 @@ export default class GridModel<TRow> {
30
32
  * lazily on next access).
31
33
  */
32
34
  setProps(props: DataGridProps<TRow>): void;
35
+ /**
36
+ * The rows the grid was handed. `data` is optional because `def.dataSource` replaces it, and every
37
+ * reader goes through here so that "no rows yet" is one empty array rather than a check at each site.
38
+ */
39
+ get data(): TRow[];
40
+ /**
41
+ * Every row the grid can act on right now. With a datasource that is the blocks it holds rather than
42
+ * the table behind them — a select-all cannot reach rows nobody has fetched, and saying otherwise is
43
+ * the one thing a server-backed grid must not do.
44
+ */
45
+ get loadedRows(): TRow[];
33
46
  get componentName(): keyof ComponentsAndVariants;
34
47
  private _identifier;
35
48
  /**
@@ -59,27 +72,34 @@ export default class GridModel<TRow> {
59
72
  get globalFilterValue(): string;
60
73
  private _columnFilters;
61
74
  get columnFilters(): ColumnFilters<TRow>;
75
+ /** Which columns the global filter searches: the ones named, or every column of the caller's own. */
76
+ private readonly globalFilterColumns;
62
77
  /**
63
- * Apply global filter (fuzzy search across all or specified columns)
64
- */
65
- private applyGlobalFilter;
66
- /**
67
- * Apply column-level filters
78
+ * Whether one row survives the filters — all three of them, in the order they cost: the caller's own
79
+ * predicates, the global search box, then the column filters. A row rather than an array, because a
80
+ * tree is filtered a node at a time (a match keeps its ancestors) rather than in one pass.
68
81
  */
69
- private applyColumnFilters;
82
+ rowMatchesFilters(row: TRow): boolean;
83
+ /** Whether anything is filtering at all — what says a filtering pass can be skipped outright. */
84
+ get hasRowFilters(): boolean;
85
+ private matchesGlobalFilter;
86
+ private matchesColumnFilters;
70
87
  /**
71
88
  * Check if a cell value matches a filter
72
89
  */
73
90
  private matchesFilter;
74
- /**
75
- * Apply external predicate filters from the filters prop
76
- */
77
- private applyExternalFilters;
78
91
  /**
79
92
  * Get filtered data (applies external, global, then column filters)
80
93
  */
81
94
  get filteredData(): TRow[];
82
95
  private fireServerStateChange;
96
+ /** Both pager callbacks: the one carrying a reason, and the `(page, pageSize)` pair it replaces. */
97
+ private firePaginationChange;
98
+ /**
99
+ * A query that changed underneath the pager sends it back to the first page. Returns where the page now
100
+ * is, which is what the server-state snapshot asks the server for.
101
+ */
102
+ private resetToFirstPage;
83
103
  /**
84
104
  * Set global filter value
85
105
  */
@@ -111,8 +131,17 @@ export default class GridModel<TRow> {
111
131
  filtered: number;
112
132
  total: number;
113
133
  };
114
- readonly rows: import('../../../utils/memo').Memo<GroupRowModel<TRow>[] | RowModel<TRow>[]>;
115
- readonly flatRows: import('../../../utils/memo').Memo<(GroupRowModel<TRow> | RowModel<TRow> | DetailRowModel<TRow>)[]>;
134
+ /** How many rows the query holds altogether: the server's count where there is one, else what is here. */
135
+ get totalRowCount(): number;
136
+ readonly rows: import('../../../utils/memo').Memo<(RowModel<TRow> | GroupRowModel<TRow>)[]>;
137
+ /**
138
+ * The columns the group row's label cell covers: the grouping cell itself and the data columns beside it in
139
+ * its own pin, stopping at the first one carrying an aggregate — past that each column draws a value of its
140
+ * own, and a number under the wrong heading is worse than a label with less room. With nothing aggregating
141
+ * the span is every one of them, which is what it was before aggregates existed.
142
+ */
143
+ readonly groupingSpan: import('../../../utils/memo').Memo<ColumnModel<TRow>[]>;
144
+ readonly flatRows: import('../../../utils/memo').Memo<(RowModel<TRow> | GroupRowModel<TRow> | DetailRowModel<TRow>)[]>;
116
145
  get rowHeight(): number;
117
146
  readonly sizes: import('../../../utils/memo').Memo<Record<string, string>>;
118
147
  private _containerWidth;
@@ -139,6 +168,13 @@ export default class GridModel<TRow> {
139
168
  expandedGroupRow: Set<Key>;
140
169
  private _expandedDetailRows;
141
170
  get expandedDetailRows(): Set<Key>;
171
+ /** The row whose panel a user action just opened, for the row itself to bring into view once. */
172
+ private _revealDetailKey;
173
+ /**
174
+ * Taken by the detail row on the render that puts it in the DOM, and only then: an `auto` panel's
175
+ * real height exists nowhere else, and the virtualization offsets carry an estimate for it.
176
+ */
177
+ takeDetailReveal(rowKey: Key): boolean;
142
178
  toggleDetailRow: (rowKey: Key) => void;
143
179
  private _page;
144
180
  private _pageSize?;
@@ -161,6 +197,34 @@ export default class GridModel<TRow> {
161
197
  readonly filter: FilterModel<TRow>;
162
198
  /** Pagination concern (navigation + row-range derivations). */
163
199
  readonly pagination: PaginationModel<TRow>;
200
+ /** Aggregation concern (which columns aggregate, and the footer's grand totals). */
201
+ readonly aggregation: AggregationModel<TRow>;
202
+ /** Export concern (the columns, rows and levels both file formats are written from). */
203
+ readonly exporter: ExportModel<TRow>;
204
+ /** Server row model: the blocks ~def.dataSource~ has answered, and the ones still in flight. */
205
+ readonly source: DataSourceModel<TRow>;
206
+ /** Tree data: the shape `def.treeData` describes, and which of its rows are open. */
207
+ readonly tree: TreeModel<TRow>;
208
+ /** Cell editing: which cell is open, what a commit is judged by, and the values the grid has accepted. */
209
+ readonly edits: EditModel<TRow>;
210
+ /**
211
+ * Throw the fetched blocks away and ask for them again. The grid invalidates on its own whenever it
212
+ * changes the query; this is for the half it cannot see — a filter of the page's own, a row somebody
213
+ * saved, a tenant that changed underneath the closure ~getRows~ was written in.
214
+ */
215
+ refresh: () => void;
216
+ /**
217
+ * Which export buttons the top bar shows, and the options they press with. `export: true` resolves to
218
+ * one shared record rather than a fresh one, so the buttons' handlers keep their identity.
219
+ */
220
+ get exportConfig(): ExportConfig | undefined;
221
+ /**
222
+ * Download the grid as a CSV. Both writers are behind an `import()`, so the format is fetched on the
223
+ * first press and a grid nobody exports from carries neither.
224
+ */
225
+ exportCsv: (options?: DataGridCsvOptions) => Promise<void>;
226
+ /** Download the grid as an `.xlsx`, with its groups as Excel's own outline levels. */
227
+ exportXlsx: (options?: DataGridXlsxOptions) => Promise<void>;
164
228
  /** Whether any user-facing column is currently visible (else the empty-columns state shows). */
165
229
  get hasVisibleColumns(): boolean;
166
230
  get isEmpty(): boolean;
@@ -190,14 +254,37 @@ export default class GridModel<TRow> {
190
254
  pinColumn: (uniqueKey: string, pin?: PinPositionInput) => void;
191
255
  toggleGrouping: (columnKey: Key) => void;
192
256
  unGroupAll: () => void;
193
- toggleGroupRow: (groupRowKey: Key) => void;
257
+ /** Grouping changed, so every open path names a group that is not there and every block is stale. */
258
+ private collapseGroups;
259
+ /** Bumped by every expand and collapse, so the adapter's fetch effect asks for the level just opened. */
260
+ private _expansion;
261
+ get expansionVersion(): number;
262
+ /**
263
+ * Whether a group row is open. Explicit state wins: a group somebody has opened or shut is where they
264
+ * left it, and `def.groupDefaultExpanded` answers for every group nobody has touched. Client-side
265
+ * grouping only — with a datasource, a level that has never been fetched has no rows to open.
266
+ */
267
+ isGroupExpanded(groupRowKey: Key, depth: number): boolean;
268
+ /** Group rows shut by hand, which is what a `groupDefaultExpanded` grid needs to remember. */
269
+ private _collapsedGroupRow;
270
+ toggleGroupRow: (groupRowKey: Key, expanded?: boolean) => void;
271
+ /**
272
+ * A tree row opened or shut. The walk over the rows is a different walk — and where the tree is lazy the
273
+ * level just opened has not been asked for yet, which is what the bumped version tells the fetch effect.
274
+ */
275
+ treeExpansionChanged(): void;
194
276
  toggleRowSelection: (rowKey: Key) => void;
195
- toggleRowsSelection: (rowKeys: Key[]) => void;
277
+ /** `all` is the header checkbox rather than a count: a group covering every row is still not select-all. */
278
+ toggleRowsSelection: (rowKeys: Key[], all?: boolean) => void;
196
279
  toggleSelectAllRows: () => void;
197
280
  toggleColumnVisibility: (columnKey: Key) => void;
198
281
  setWidth: (columnKey: Key, width: number) => void;
199
- groupColumns: Set<Key>;
200
- hiddenColumns: Set<Key>;
282
+ private _groupColumns?;
283
+ get groupColumns(): Set<Key>;
284
+ set groupColumns(value: Set<Key>);
285
+ private _hiddenColumns?;
286
+ get hiddenColumns(): Set<Key>;
287
+ set hiddenColumns(value: Set<Key>);
201
288
  columnWidths: Map<Key, number>;
202
289
  private _sortColumn?;
203
290
  get sortColumn(): Key | undefined;
@@ -1,12 +1,14 @@
1
+ import { default as AggregateCellModel } from './aggregateCellModel';
1
2
  import { default as ColumnModel } from './columnModel';
2
3
  import { default as GridModel } from './gridModel';
3
4
  import { default as GroupRowModel } from './groupRowModel';
4
5
  /**
5
6
  * How a cell participates in a group row's layout: `grouping` is the expand/label cell spanning the
6
- * grouped columns, `selection` the select-all cell, `spacer` a pinned or leading cell with its own value,
7
- * and `hidden` a data column absorbed into the grouping cell's span.
7
+ * grouped columns, `selection` the select-all cell, `aggregate` a column totalling the rows under the group,
8
+ * `spacer` a pinned or leading cell with its own value, and `hidden` a data column absorbed into the
9
+ * grouping cell's span.
8
10
  */
9
- export type GroupRowCellKind = 'grouping' | 'selection' | 'spacer' | 'hidden';
11
+ export type GroupRowCellKind = 'grouping' | 'selection' | 'aggregate' | 'spacer' | 'hidden';
10
12
  export default class GroupRowCellModel<TRow> {
11
13
  readonly grid: GridModel<TRow>;
12
14
  readonly row: GroupRowModel<TRow>;
@@ -17,6 +19,12 @@ export default class GroupRowCellModel<TRow> {
17
19
  get isFirst(): boolean;
18
20
  get isLast(): boolean;
19
21
  get cellKind(): GroupRowCellKind;
22
+ /**
23
+ * The aggregate over the rows under this group, on the columns that have one. A server group brings its
24
+ * own total down with it, since the rows it is over were never in the browser to add up.
25
+ */
26
+ private readonly _aggregate;
27
+ get aggregate(): AggregateCellModel<TRow> | null;
20
28
  get depthPadding(): number;
21
29
  get gridColumnSpan(): number;
22
30
  get widthVar(): string;
@@ -1,4 +1,4 @@
1
- import { Key } from '../contracts/dataGridContract';
1
+ import { AggregateValue, Key } from '../contracts/dataGridContract';
2
2
  import { default as ColumnModel } from './columnModel';
3
3
  import { default as DetailRowModel } from './detailRowModel';
4
4
  import { default as GridModel } from './gridModel';
@@ -9,8 +9,26 @@ export default class GroupRowModel<TRow> {
9
9
  readonly groupColumn: ColumnModel<TRow>;
10
10
  readonly rows: RowModel<TRow>[] | GroupRowModel<TRow>[];
11
11
  readonly rowIndex: number;
12
- readonly groupValue: Key;
13
12
  constructor(grid: GridModel<TRow>, groupColumn: ColumnModel<TRow>, rows: RowModel<TRow>[] | GroupRowModel<TRow>[], rowIndex: number, groupValue: Key);
13
+ protected _groupValue: Key;
14
+ /**
15
+ * The value this group collects. A getter rather than a parameter property so a subclass can read it
16
+ * from somewhere else — a field would shadow the override outright, which is what `useDefineForClassFields`
17
+ * does to a base class's own properties.
18
+ */
19
+ get groupValue(): Key;
20
+ /** What the expand cell reads: the value, and how many rows are under it. */
21
+ get label(): string;
22
+ /** Whether the group's rows have arrived at all. Always, until a server answers a level — see `SourceGroupRowModel`. */
23
+ get placeholder(): boolean;
24
+ /** Whether the select-all checkbox is offered: a group can only select the rows the grid holds. */
25
+ get selectable(): boolean;
26
+ /**
27
+ * An aggregate the grid was handed rather than one it works out. Nothing server-side can be totalled in
28
+ * the browser — the rows are not here — so a server group row carries its own totals and this is where
29
+ * they come from; `undefined` means "compute it", which is every client-side group.
30
+ */
31
+ aggregateValue(_column: ColumnModel<TRow>): AggregateValue | undefined;
14
32
  get key(): Key;
15
33
  parentRow?: GroupRowModel<TRow>;
16
34
  private readonly _cells;
@@ -41,6 +59,8 @@ export default class GroupRowModel<TRow> {
41
59
  get allRows(): RowModel<TRow>[];
42
60
  get groupingColumn(): ColumnModel<TRow>;
43
61
  get groupingColumnGridColumn(): number;
62
+ /** Whether the label cell has swallowed this column, which is what leaves it with no cell of its own. */
63
+ spans(column: ColumnModel<TRow>): boolean;
44
64
  readonly kind: "group";
45
65
  toggleRow(): void;
46
66
  /** Select/deselect every leaf row under this group. */
@@ -5,9 +5,15 @@ import { default as GridModel } from './gridModel';
5
5
  import { default as GroupRowModel } from './groupRowModel';
6
6
  export default class RowModel<TRow> {
7
7
  readonly grid: GridModel<TRow>;
8
- readonly data: TRow;
9
8
  readonly rowIndex: number;
10
9
  constructor(grid: GridModel<TRow>, data: TRow, rowIndex: number);
10
+ protected _data: TRow;
11
+ /**
12
+ * The row's values. A getter rather than a parameter property so a subclass can read them from
13
+ * somewhere else — `SourceRowModel` reads them out of the block cache, which is what lets a block
14
+ * arriving change what a row shows without a single model being rebuilt.
15
+ */
16
+ get data(): TRow;
11
17
  private _key?;
12
18
  /**
13
19
  * Resolved lazily: getRowKey() can call crypto.randomUUID(), so building a model for every
@@ -18,6 +24,8 @@ export default class RowModel<TRow> {
18
24
  parentRow?: GroupRowModel<TRow>;
19
25
  readonly count = 1;
20
26
  readonly kind: "row";
27
+ /** Whether this is a row the server has not sent yet — see `SourceRowModel`, which answers it live. */
28
+ get placeholder(): boolean;
21
29
  /** Whether clicking the row toggles its detail panel. */
22
30
  get expandOnRowClick(): boolean;
23
31
  toggleDetail: () => void;
@@ -28,6 +36,10 @@ export default class RowModel<TRow> {
28
36
  /** Where a cell starts in column-index space: one cell per column, so its own ordinal. */
29
37
  columnOf(cell: number): number;
30
38
  get selected(): boolean;
39
+ /** Half-selected, which only a tree row with the cascade on can be. */
40
+ get indeterminate(): boolean;
41
+ /** Select or deselect this row. A tree row under `selection: 'cascade'` takes its subtree with it. */
42
+ toggleSelection: () => void;
31
43
  get expanded(): boolean;
32
44
  get flatRows(): (RowModel<TRow> | DetailRowModel<TRow>)[];
33
45
  get allRows(): this;
@@ -0,0 +1,45 @@
1
+ import { AggregateValue, Key } from '../contracts/dataGridContract';
2
+ import { default as ColumnModel } from './columnModel';
3
+ import { default as GridModel } from './gridModel';
4
+ import { default as GroupRowModel } from './groupRowModel';
5
+ import { default as SourceLevel } from './sourceLevel';
6
+ /**
7
+ * One group row of a `def.dataSource` grid: a row of a level the server answered with groups rather than
8
+ * data. It reads its value, its count and its totals *through* the block cache, the way `SourceRowModel`
9
+ * reads a leaf row's — so a block arriving fills in fifty group rows with no model rebuilt.
10
+ *
11
+ * Its children are not here and never were: a group holds a level of its own, fetched when somebody opens
12
+ * it. That is what `count` (the server's number) and `allRows` (empty) are both saying, and it is why the
13
+ * select-all checkbox is not offered — a group cannot select rows the browser has never seen.
14
+ */
15
+ export default class SourceGroupRowModel<TRow> extends GroupRowModel<TRow> {
16
+ private readonly level;
17
+ constructor(grid: GridModel<TRow>, level: SourceLevel<TRow>, groupColumn: ColumnModel<TRow>, rowIndex: number);
18
+ private get row();
19
+ /** The row the server sent for this group: its value, and whatever totals came with it. */
20
+ get data(): TRow | undefined;
21
+ get placeholder(): boolean;
22
+ get groupValue(): Key;
23
+ /** The path that names this group's own level, and the identity the expansion set holds it by. */
24
+ get groupKeys(): Key[];
25
+ /**
26
+ * Keyed by the path rather than by position, so a group stays open while the block under it is dropped
27
+ * and fetched again. A group nobody has yet is keyed by where it sits — `getRowKey` has nothing to read.
28
+ */
29
+ get key(): Key;
30
+ get depth(): number;
31
+ /** The server's count, or nothing at all — a level it could not count cheaply shows the value alone. */
32
+ get count(): number;
33
+ get label(): string;
34
+ /** The totals came down with the group row, because the rows they are over are not in the browser. */
35
+ aggregateValue(column: ColumnModel<TRow>): AggregateValue | undefined;
36
+ get selectable(): boolean;
37
+ get selected(): boolean;
38
+ get indeterminate(): boolean;
39
+ get expanded(): boolean;
40
+ /** Nothing under it is in the browser, so nothing under it can be selected or exported. */
41
+ get allRows(): never[];
42
+ toggleRow(): void;
43
+ }
44
+ /** The key a group path is held by, in `expandedGroupRow` and in this model. */
45
+ export declare function groupPathKey(groupKeys: Key[]): Key;
@@ -0,0 +1,71 @@
1
+ import { Key } from '../contracts/dataGridContract';
2
+ import { default as DataSourceModel } from './dataSourceModel';
3
+ /** Where one block of rows has got to. A block is the unit of a request, of a wait and of a failure. */
4
+ export type BlockStatus = 'loading' | 'loaded' | 'error';
5
+ /**
6
+ * One level of a `def.dataSource` grid: the blocks answering a single path, and the count and end-of-data
7
+ * they arrived with. With nothing opened there is exactly one of these; a group or a tree row being opened
8
+ * adds another, and which of the two the path *is* is the only thing that differs between them.
9
+ *
10
+ * A level knows the row that opened it (`parent`, `parentRowIndex`), because a group's children are worth
11
+ * nothing without it: eviction has to keep the chain above a level in play or the rows on screen would
12
+ * lose the group row they hang off.
13
+ */
14
+ export default class SourceLevel<TRow> {
15
+ private readonly source;
16
+ /** What names this level, outermost first: group values, or a tree row's keys. `[]` is the top. */
17
+ readonly path: Key[];
18
+ readonly parent?: SourceLevel<TRow> | undefined;
19
+ /** Where in `parent` the group row that opened this one sits. A sort can move it. */
20
+ parentRowIndex: number;
21
+ constructor(source: DataSourceModel<TRow>,
22
+ /** What names this level, outermost first: group values, or a tree row's keys. `[]` is the top. */
23
+ path: Key[], parent?: SourceLevel<TRow> | undefined,
24
+ /** Where in `parent` the group row that opened this one sits. A sort can move it. */
25
+ parentRowIndex?: number,
26
+ /** A count carried over an invalidation, so a filter keystroke does not collapse the scrollbar. */
27
+ keepCount?: number);
28
+ private blocks;
29
+ private _totalCount?;
30
+ /** False once disposed of: a request still in flight must not write into a level nobody can reach. */
31
+ private alive;
32
+ /** True once a block came back shorter than it was asked for: there is nothing past it. */
33
+ private _reachedEnd;
34
+ /** Its own identity, and the key it is held under. JSON, so no path value can spell another path. */
35
+ static keyOf(path: Key[]): string;
36
+ get key(): string;
37
+ /** The group this level sits inside — its path, unless the path is a tree's. */
38
+ get groupKeys(): Key[];
39
+ /** The row this level holds the children of — its path, when the path is a tree's. */
40
+ get treeKeys(): Key[];
41
+ /** What a row of this level that has not arrived is keyed by — unique per level, stable per position. */
42
+ get placeholderKey(): string;
43
+ /** How deep this level sits — for a grouped source, which column it groups by. */
44
+ get depth(): number;
45
+ /** Whether its rows are groups rather than data — true until the last grouped column has been named. */
46
+ get isGroupLevel(): boolean;
47
+ /**
48
+ * How many rows this level lays out. The server's count when there is one; otherwise what has been
49
+ * loaded, plus one block to scroll into — which is what keeps an uncounted source reachable.
50
+ */
51
+ get rowCount(): number;
52
+ get totalCount(): number | undefined;
53
+ rowAt(index: number): TRow | undefined;
54
+ /** How many leaf rows a group row holds, when the server said. */
55
+ groupCountAt(index: number): number | undefined;
56
+ statusAt(index: number): BlockStatus | 'missing';
57
+ get isLoading(): boolean;
58
+ failedBlocks(): number[];
59
+ errorAt(index: number): unknown;
60
+ has(index: number): boolean;
61
+ /** Which blocks are in hand, loaded or not — what eviction and the row walks read. */
62
+ loadedBlocks(): {
63
+ index: number;
64
+ rows: TRow[];
65
+ }[];
66
+ drop(index: number): void;
67
+ /** Abort everything in flight and forget the lot — a level whose query or whose group has gone. */
68
+ dispose(): void;
69
+ retry(): void;
70
+ load(index: number): void;
71
+ }
@@ -0,0 +1,29 @@
1
+ import { Key } from '../contracts/dataGridContract';
2
+ import { default as GridModel } from './gridModel';
3
+ import { default as RowModel } from './rowModel';
4
+ import { default as SourceLevel } from './sourceLevel';
5
+ /**
6
+ * One row of a `def.dataSource` grid, whether or not the server has answered for it yet. It reads its
7
+ * values *through* the block cache rather than holding them, which is the whole point: a block arriving
8
+ * changes what fifty rows show with no model rebuilt, and the million rows below the viewport cost one
9
+ * proxy each — that is, nothing, until somebody scrolls to one.
10
+ *
11
+ * Its key follows the same rule. A loaded row is keyed by its data, so a selection or an open detail
12
+ * panel survives the block being dropped and fetched again; a row that is still a skeleton is keyed by
13
+ * its position, since `getRowKey` would mint a fresh id on every render and React would remount it.
14
+ */
15
+ export default class SourceRowModel<TRow> extends RowModel<TRow> {
16
+ /** The level it belongs to: the top one, or the group or tree row somebody opened. */
17
+ protected readonly sourceLevel: SourceLevel<TRow>;
18
+ constructor(grid: GridModel<TRow>,
19
+ /** The level it belongs to: the top one, or the group or tree row somebody opened. */
20
+ sourceLevel: SourceLevel<TRow>, rowIndex: number);
21
+ private get row();
22
+ get data(): TRow;
23
+ get placeholder(): boolean;
24
+ get key(): Key;
25
+ /** Nothing to open and nothing to select: a row nobody has yet is not a row anybody can act on. */
26
+ get expandOnRowClick(): boolean;
27
+ get expanded(): boolean;
28
+ get selected(): boolean;
29
+ }
@@ -0,0 +1,23 @@
1
+ import { default as SourceRowModel } from './sourceRowModel';
2
+ import { TreeRow } from './treeRow';
3
+ /**
4
+ * One row of a lazy tree: a `def.dataSource` row that may hold rows of its own, fetched when somebody
5
+ * opens it. Everything it knows about the tree it reads off its level — how deep it sits, how many
6
+ * siblings it has — so a block landing fills in fifty rows of a tree with no model rebuilt.
7
+ *
8
+ * What is under it is not here and never was, which is the difference from the eager `TreeRowModel`: it
9
+ * cannot walk its own subtree, so a cascading selection is not offered and a row the server has not
10
+ * answered for grows no chevron.
11
+ */
12
+ export default class SourceTreeRowModel<TRow> extends SourceRowModel<TRow> implements TreeRow {
13
+ readonly isTreeRow = true;
14
+ /** How deep the level is, which is how deep every row of it is. */
15
+ get level(): number;
16
+ get position(): number;
17
+ /** The server's count, or `-1` — ARIA's "nobody knows", which is the honest answer to an uncounted level. */
18
+ get siblings(): number;
19
+ /** A row the server has not answered for holds nothing anybody can ask about yet. */
20
+ get hasChildren(): boolean;
21
+ get treeExpanded(): boolean;
22
+ toggleTree: () => void;
23
+ }
@@ -0,0 +1,94 @@
1
+ import { Key, TreeDataConfig } from '../contracts/dataGridContract';
2
+ import { default as GridModel } from './gridModel';
3
+ import { default as TreeRowModel } from './treeRowModel';
4
+ /** One row of the tree and everything under it, before any of it is a model. */
5
+ export interface TreeNode<TRow> {
6
+ data: TRow;
7
+ children: TreeNode<TRow>[];
8
+ }
9
+ /**
10
+ * Rows that hold rows. The shape of the tree comes out of the data — nested in it, or named by a path on
11
+ * every row — and everything else here is what the grid does with it: filtering that keeps a match's
12
+ * ancestors, a sort that happens inside each parent, and which rows are open.
13
+ */
14
+ export default class TreeModel<TRow> {
15
+ readonly grid: GridModel<TRow>;
16
+ constructor(grid: GridModel<TRow>);
17
+ get config(): TreeDataConfig<TRow> | undefined;
18
+ /** Whether the grid is a tree at all — the chevrons, the indent, `role="treegrid"` and the arrow keys. */
19
+ get enabled(): boolean;
20
+ /**
21
+ * Whether the tree is in the browser. The two halves are the same feature from opposite ends: eager
22
+ * reads the shape off `data` and holds the lot, lazy asks the datasource for one level at a time — so
23
+ * everything here that walks the data is the eager half's, and the source owns the other.
24
+ */
25
+ get isEager(): boolean;
26
+ get isLazy(): boolean;
27
+ /**
28
+ * Whether a lazy tree's row holds anything — the one thing a row's own values cannot say, since what
29
+ * is under it has never been in the browser. Eager rows answer it from their node instead.
30
+ */
31
+ rowHasChildren(row: TRow): boolean;
32
+ /** Which column carries the chevrons: the one named, or the first of the caller's own. */
33
+ get columnKey(): Key | undefined;
34
+ /** How far one level indents, on the ÷4 spacing scale. */
35
+ get indent(): number;
36
+ /** Whether ticking a row ticks everything under it. Never on a lazy tree: it cannot reach what it has not got. */
37
+ get cascades(): boolean;
38
+ /** Whether the tree is read off a path per row rather than nested in the rows. */
39
+ private get isFlat();
40
+ private childrenOf;
41
+ private pathOf;
42
+ private buildNested;
43
+ /**
44
+ * The flat shape: every row carries its own path, and the tree is what those paths describe. Shallower
45
+ * rows are taken first (a stable sort, so siblings keep the order they were handed in) — a child cannot
46
+ * find a parent that has not been seen yet, and the data is under no obligation to be in order.
47
+ */
48
+ private buildFlat;
49
+ /**
50
+ * What the filters leave: a row that matches, and every ancestor of one — a match nobody can see the
51
+ * path to is a match the grid has hidden. A parent that matches on its own stays as a leaf, since the
52
+ * rows under it did not.
53
+ */
54
+ private prune;
55
+ /** A sort happens inside each parent: a tree sorted across its levels would not be a tree any more. */
56
+ private sortNodes;
57
+ /**
58
+ * The tree the data describes, filtered and sorted. Its own memo rather than part of `grid.rows`:
59
+ * opening a row rebuilds the models and must not rebuild the tree underneath them.
60
+ */
61
+ readonly nodes: import('../../../utils/memo').Memo<TreeNode<TRow>[]>;
62
+ /**
63
+ * Every row of the tree the filters left, in display order and whatever is open — what a select-all
64
+ * reaches, what the footer totals and what an export writes. `data` is only the top of a nested tree,
65
+ * so nothing that means "every row" can read it.
66
+ */
67
+ readonly allRows: import('../../../utils/memo').Memo<TRow[]>;
68
+ /** Every row in the data, filters ignored: the filter's own options, and what the row count is out of. */
69
+ everyRow(): TRow[];
70
+ /**
71
+ * One model per row that is showing, in display order. A shut row's children are not built at all,
72
+ * which is what keeps a tree nobody has opened the cost of its top level.
73
+ */
74
+ rowList(): TreeRowModel<TRow>[];
75
+ /** Rows opened and rows shut since the grid mounted. Explicit state wins over `defaultExpanded`. */
76
+ private _opened;
77
+ private _closed;
78
+ private _cachedKeys?;
79
+ /** A key list as a set, rebuilt only when the list itself changes — this is read once per row. */
80
+ private keySet;
81
+ private defaultExpanded;
82
+ /**
83
+ * Whether anything could be open at all. A lazy tree's walk reads a key off every row in hand to find
84
+ * the open ones, and a tree nobody has touched is the common case — so it is worth one check first.
85
+ */
86
+ get mayExpand(): boolean;
87
+ isExpanded(key: Key, level: number): boolean;
88
+ /**
89
+ * Every open row, walked down the tree as it is displayed — a row under a shut one is not open however
90
+ * it is marked. Only walked when somebody is listening, since it costs a key for every row it reaches.
91
+ */
92
+ private expandedKeys;
93
+ toggle: (key: Key, level: number) => void;
94
+ }
@@ -0,0 +1,22 @@
1
+ import { default as RowModel } from './rowModel';
2
+ /**
3
+ * What the grid needs of a row that holds rows. Two models answer it and neither can extend the other:
4
+ * the eager `TreeRowModel` holds its node, and the lazy `SourceTreeRowModel` reads its level out of the
5
+ * block cache — so the chevron, the indent and the sideways keys work off this rather than off a class.
6
+ */
7
+ export interface TreeRow {
8
+ /** Duck tag, so the guard below needs neither class in scope and no import cycle with them. */
9
+ readonly isTreeRow: true;
10
+ /** 0 at the top of the tree. `aria-level` is this plus one. */
11
+ readonly level: number;
12
+ /** Where it sits among its siblings, and how many of them there are — `aria-posinset`/`aria-setsize`. */
13
+ readonly position: number;
14
+ readonly siblings: number;
15
+ /** Whether there is anything under it: a row with nothing under it never grows a chevron. */
16
+ readonly hasChildren: boolean;
17
+ /** Whether the rows under it are showing. */
18
+ readonly treeExpanded: boolean;
19
+ readonly toggleTree: () => void;
20
+ }
21
+ /** Whether a row of the grid is one of the tree's. Narrows to a row, since every tree row is one. */
22
+ export declare function isTreeRow<TRow>(row: unknown): row is RowModel<TRow> & TreeRow;
@@ -0,0 +1,50 @@
1
+ import { Key } from '../contracts/dataGridContract';
2
+ import { default as DetailRowModel } from './detailRowModel';
3
+ import { default as GridModel } from './gridModel';
4
+ import { default as RowModel } from './rowModel';
5
+ import { TreeNode } from './treeModel';
6
+ import { TreeRow } from './treeRow';
7
+ /**
8
+ * One row of a `def.treeData` tree: a row of the grid in every sense — its own values, its own cells,
9
+ * its own detail panel — that happens to hold rows of its own. The rows under it are its siblings in the
10
+ * same rowgroup rather than its children in the DOM, which is what `aria-level` is for.
11
+ */
12
+ export default class TreeRowModel<TRow> extends RowModel<TRow> implements TreeRow {
13
+ private readonly node;
14
+ /** 0 at the top of the tree. `aria-level` is this plus one. */
15
+ readonly level: number;
16
+ /** Where it sits among its siblings, and how many of them there are — `aria-posinset`/`aria-setsize`. */
17
+ readonly position: number;
18
+ readonly siblings: number;
19
+ readonly treeParent?: TreeRowModel<TRow> | undefined;
20
+ readonly isTreeRow = true;
21
+ constructor(grid: GridModel<TRow>, node: TreeNode<TRow>, rowIndex: number,
22
+ /** 0 at the top of the tree. `aria-level` is this plus one. */
23
+ level: number,
24
+ /** Where it sits among its siblings, and how many of them there are — `aria-posinset`/`aria-setsize`. */
25
+ position: number, siblings: number, treeParent?: TreeRowModel<TRow> | undefined);
26
+ /** Built by `TreeModel` right after this row, and only where it is open: a shut row renders nothing. */
27
+ children: TreeRowModel<TRow>[];
28
+ /** Whether there is anything under it at all — read off the data, so a shut row still knows. */
29
+ get hasChildren(): boolean;
30
+ /** Whether the rows under it are showing. A row with nothing under it is never open. */
31
+ get treeExpanded(): boolean;
32
+ toggleTree: () => void;
33
+ /**
34
+ * Every key in this row's subtree, its own first. Walked over the *data* rather than the models, so a
35
+ * shut row can still select what is under it; cached per model, and the models are rebuilt whenever
36
+ * the tree is.
37
+ */
38
+ private readonly _subtreeKeys;
39
+ get subtreeKeys(): Key[];
40
+ /**
41
+ * With `selection: 'cascade'` a row is selected when everything under it is — a parent whose children
42
+ * are not all ticked is not itself ticked, which is what makes the indeterminate state mean something.
43
+ */
44
+ get selected(): boolean;
45
+ /** Some of the subtree is selected and some is not. Always false without the cascade. */
46
+ get indeterminate(): boolean;
47
+ toggleSelection: () => void;
48
+ /** This row, its detail panel if one is open, and then everything under it — in display order. */
49
+ get flatRows(): (RowModel<TRow> | DetailRowModel<TRow>)[];
50
+ }