@svgrid/grid 2.6.20 → 2.6.22
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.
- package/CHANGELOG.md +62 -0
- package/README.md +22 -0
- package/dist/GridMenus.svelte +17 -12
- package/dist/SvGrid.controller.svelte.d.ts +14 -8
- package/dist/SvGrid.controller.svelte.js +214 -74
- package/dist/SvGrid.css +1 -1
- package/dist/SvGrid.svelte +112 -57
- package/dist/SvGrid.types.d.ts +191 -1
- package/dist/ai.d.ts +28 -0
- package/dist/ai.js +6 -0
- package/dist/cdn/{GridMenus-B0F9iBrG.js → GridMenus-BuoBPqxx.js} +137 -132
- package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
- package/dist/cdn/column-resize-DsfNXMom.js +102 -0
- package/dist/cdn/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-Cd0tearp.js → src-C9Hihx1W.js} +3497 -3487
- package/dist/cdn/{src-B1TdiyS8.js → src-D1lXwq1l.js} +8258 -8248
- package/dist/cdn/svgrid.js +10 -8
- package/dist/cdn/svgrid.svelte-external.js +10 -8
- package/dist/cdn/validate-_CDJzgIo.js +75 -0
- package/dist/cell-formatting.d.ts +2 -0
- package/dist/cell-formatting.js +2 -0
- package/dist/chart-export.d.ts +1 -0
- package/dist/chart.d.ts +31 -5
- package/dist/chart.js +9 -3
- package/dist/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +216 -4
- package/dist/core.js +532 -119
- package/dist/createTree.svelte.d.ts +3 -0
- package/dist/createTree.svelte.js +1 -0
- package/dist/datetime/date-core.d.ts +2 -0
- package/dist/datetime/date-restrict.d.ts +1 -0
- package/dist/datetime/timezone.d.ts +1 -0
- package/dist/dock-manager-model.d.ts +3 -0
- package/dist/dock-manager-model.js +1 -0
- package/dist/dock-model.d.ts +6 -0
- package/dist/dock-model.js +3 -0
- package/dist/editor-contract.d.ts +1 -0
- package/dist/filtering/excel-filters.js +28 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/list-option.d.ts +1 -0
- package/dist/menus.js +1 -1
- package/dist/positioning.d.ts +2 -0
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- package/dist/scheduler-ical.d.ts +1 -0
- package/dist/scheduler-model.d.ts +1 -0
- package/dist/selection.js +9 -0
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/dist/summaries.js +22 -0
- package/dist/svgrid-wrapper.types.d.ts +5 -0
- package/dist/toast-store.svelte.d.ts +4 -0
- package/dist/validate.d.ts +50 -0
- package/dist/validate.js +187 -0
- package/package.json +4 -1
- package/src/GridMenus.svelte +17 -12
- package/src/SvGrid.controller.svelte.ts +223 -76
- package/src/SvGrid.css +1 -1
- package/src/SvGrid.svelte +112 -57
- package/src/SvGrid.types.ts +191 -1
- package/src/ai.ts +28 -0
- package/src/cell-formatting.ts +2 -0
- package/src/chart-export.ts +1 -0
- package/src/chart.ts +31 -5
- package/src/column-groups.ts +1 -1
- package/src/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +723 -119
- package/src/createTree.svelte.ts +3 -0
- package/src/datetime/date-core.ts +2 -0
- package/src/datetime/date-restrict.ts +1 -0
- package/src/datetime/timezone.ts +1 -0
- package/src/dock-manager-model.ts +3 -0
- package/src/dock-model.ts +6 -0
- package/src/editor-contract.ts +1 -0
- package/src/filtering/excel-filters.ts +30 -0
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- package/src/group-display.ts +1 -1
- package/src/index.ts +12 -1
- package/src/list-option.ts +1 -0
- package/src/menus.ts +1 -1
- package/src/positioning.ts +2 -0
- package/src/resize-props.test.ts +361 -0
- package/src/row-resize.test.ts +31 -0
- package/src/row-resize.ts +21 -3
- package/src/scheduler-ical.ts +1 -0
- package/src/scheduler-model.ts +1 -0
- package/src/selection.ts +9 -0
- package/src/spreadsheet.ts +1 -1
- package/src/summaries.ts +21 -0
- package/src/svgrid-wrapper.types.ts +5 -0
- package/src/svgrid.summaries.test.ts +217 -0
- package/src/toast-store.svelte.ts +4 -0
- package/src/validate.test.ts +207 -0
- package/src/validate.ts +269 -0
- package/dist/cdn/GridMenus-IHK_l7m6.js +0 -489
package/dist/core.js
CHANGED
|
@@ -1,31 +1,70 @@
|
|
|
1
1
|
import { resolveColumnId } from './column-id.js';
|
|
2
2
|
/** Apply a group aggregator over a bucket's leaf rows for one column. */
|
|
3
3
|
export function applyGroupAggregate(agg, columnId, rows) {
|
|
4
|
-
|
|
4
|
+
// One pass, no intermediate arrays.
|
|
5
|
+
//
|
|
6
|
+
// This used to build a `raw` array, then a coerced one, then a filtered one -
|
|
7
|
+
// three allocations per aggregated column PER GROUP - before reducing. On a
|
|
8
|
+
// 100k-row grid grouped two levels deep, aggregation was about two thirds of
|
|
9
|
+
// the total grouping cost (213ms with three aggregators against 81ms with
|
|
10
|
+
// none), and each additional aggregated column added roughly 80ms.
|
|
11
|
+
//
|
|
12
|
+
// `count` first: it never needs to look at a value at all.
|
|
13
|
+
if (agg === 'count')
|
|
14
|
+
return rows.length;
|
|
15
|
+
if (agg === 'first') {
|
|
16
|
+
return rows.length ? rows[0].getCellValueByColumnId(columnId) : undefined;
|
|
17
|
+
}
|
|
18
|
+
if (agg === 'countDistinct') {
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
for (const row of rows)
|
|
21
|
+
seen.add(String(row.getCellValueByColumnId(columnId) ?? ''));
|
|
22
|
+
return seen.size;
|
|
23
|
+
}
|
|
5
24
|
if (typeof agg === 'function') {
|
|
6
|
-
|
|
25
|
+
// Custom aggregators keep their contract: the finite numbers, then the
|
|
26
|
+
// original row objects. Note `Number(null)` is 0 and therefore finite, so
|
|
27
|
+
// nulls DO reach the callback as zeros - long-standing behaviour.
|
|
28
|
+
const nums = [];
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const n = Number(row.getCellValueByColumnId(columnId));
|
|
31
|
+
if (Number.isFinite(n))
|
|
32
|
+
nums.push(n);
|
|
33
|
+
}
|
|
7
34
|
return agg(nums, rows.map((r) => r.original));
|
|
8
35
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
36
|
+
// sum / avg / min / max / extent share one accumulation pass.
|
|
37
|
+
let count = 0;
|
|
38
|
+
let sum = 0;
|
|
39
|
+
let min = Infinity;
|
|
40
|
+
let max = -Infinity;
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
const n = Number(row.getCellValueByColumnId(columnId));
|
|
43
|
+
if (!Number.isFinite(n))
|
|
44
|
+
continue;
|
|
45
|
+
count++;
|
|
46
|
+
// Left-to-right, matching the previous `reduce`, so float rounding is
|
|
47
|
+
// bit-identical rather than merely close.
|
|
48
|
+
sum += n;
|
|
49
|
+
// Math.min/max on scalars rather than `<`, which differs on -0, and rather
|
|
50
|
+
// than the old `Math.min(...nums)` - spreading a whole group throws
|
|
51
|
+
// RangeError once the bucket is big enough to exhaust the argument stack.
|
|
52
|
+
min = Math.min(min, n);
|
|
53
|
+
max = Math.max(max, n);
|
|
54
|
+
}
|
|
55
|
+
if (!count)
|
|
17
56
|
return undefined;
|
|
18
57
|
switch (agg) {
|
|
19
58
|
case 'sum':
|
|
20
|
-
return
|
|
59
|
+
return sum;
|
|
21
60
|
case 'avg':
|
|
22
|
-
return
|
|
61
|
+
return sum / count;
|
|
23
62
|
case 'min':
|
|
24
|
-
return
|
|
63
|
+
return min;
|
|
25
64
|
case 'max':
|
|
26
|
-
return
|
|
65
|
+
return max;
|
|
27
66
|
case 'extent':
|
|
28
|
-
return `${
|
|
67
|
+
return `${min} – ${max}`;
|
|
29
68
|
default:
|
|
30
69
|
return undefined;
|
|
31
70
|
}
|
|
@@ -47,15 +86,49 @@ function createStore(initial) {
|
|
|
47
86
|
},
|
|
48
87
|
};
|
|
49
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Click-to-sort. Injected by the `sortable` shortcut.
|
|
91
|
+
*
|
|
92
|
+
* This and the five features below are opaque markers: pass the ones you want
|
|
93
|
+
* to {@link tableFeatures} and the grid wires up the matching row model. With
|
|
94
|
+
* `<SvGrid>` you rarely name them - the boolean shortcuts (`sortable`,
|
|
95
|
+
* `filterable`, `pageable`, `groupable`) inject them for you. Reach for them
|
|
96
|
+
* directly when driving the headless core, or when you want a feature on
|
|
97
|
+
* without its UI.
|
|
98
|
+
*
|
|
99
|
+
* The names match TanStack Table v9, so a features object written for it works
|
|
100
|
+
* here unchanged.
|
|
101
|
+
*/
|
|
50
102
|
export const rowSortingFeature = { key: 'rowSortingFeature' };
|
|
103
|
+
/** Per-column filtering. Injected by the `filterable` shortcut. */
|
|
51
104
|
export const columnFilteringFeature = { key: 'columnFilteringFeature' };
|
|
105
|
+
/** Paging of the row model. Injected by the `pageable` shortcut. */
|
|
52
106
|
export const rowPaginationFeature = { key: 'rowPaginationFeature' };
|
|
107
|
+
/** Row grouping with aggregation. Injected by the `groupable` shortcut. */
|
|
53
108
|
export const columnGroupingFeature = { key: 'columnGroupingFeature' };
|
|
109
|
+
/** Row selection state (the checkbox column reads it). */
|
|
54
110
|
export const rowSelectionFeature = { key: 'rowSelectionFeature' };
|
|
111
|
+
/** Expand / collapse, for tree rows and master-detail. */
|
|
55
112
|
export const rowExpandingFeature = { key: 'rowExpandingFeature' };
|
|
113
|
+
/**
|
|
114
|
+
* Declare which features a grid uses. Identity at runtime - its whole job is to
|
|
115
|
+
* capture the exact set in the type, so `ColumnDef<typeof features, Row>` knows
|
|
116
|
+
* what is registered and anything you did not register is tree-shaken out.
|
|
117
|
+
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* Same call signature as TanStack Table v9, so a features object written for it
|
|
123
|
+
* transfers unchanged.
|
|
124
|
+
*/
|
|
56
125
|
export function tableFeatures(features) {
|
|
57
126
|
return features;
|
|
58
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Built-in comparators, chosen per column by its data type. `auto` compares as
|
|
130
|
+
* text; set a column's type or supply your own comparator to override.
|
|
131
|
+
*/
|
|
59
132
|
export const sortFns = {
|
|
60
133
|
auto: (a, b) => String(a).localeCompare(String(b)),
|
|
61
134
|
number: (a, b) => Number(a ?? 0) - Number(b ?? 0),
|
|
@@ -65,30 +138,174 @@ export const sortFns = {
|
|
|
65
138
|
return aa - bb;
|
|
66
139
|
},
|
|
67
140
|
};
|
|
141
|
+
/**
|
|
142
|
+
* Built-in match functions, named by {@link ColumnFilter}'s `fn`.
|
|
143
|
+
* `includesString` is case-insensitive substring; `equals` is strict identity.
|
|
144
|
+
*/
|
|
68
145
|
export const filterFns = {
|
|
69
146
|
includesString: (value, query) => String(value).toLowerCase().includes(query.toLowerCase()),
|
|
70
147
|
equals: (value, query) => value === query,
|
|
71
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Keys for a base row's private fields.
|
|
151
|
+
*
|
|
152
|
+
* Symbols, not string keys, and that is load-bearing. A row's shared methods
|
|
153
|
+
* need a pointer back to the table, but `_ctx` as a normal property made every
|
|
154
|
+
* row serialise the entire grid: `JSON.stringify(oneRow)` grew with the dataset
|
|
155
|
+
* (981 chars at 3 rows, 67,719 at 3,000) because `options.data` is reachable
|
|
156
|
+
* through it, so stringifying a row model was quadratic. Rows used to serialise
|
|
157
|
+
* to a small constant and must again.
|
|
158
|
+
*
|
|
159
|
+
* A symbol key is invisible to `JSON.stringify`, `Object.keys` and `for...in`,
|
|
160
|
+
* yet IS copied by object spread - which matters because several row models
|
|
161
|
+
* legitimately do `{ ...row, depth }` and the clone needs these to work.
|
|
162
|
+
* Non-enumerable string keys would have hidden them from JSON but also from the
|
|
163
|
+
* spread, silently breaking every cloned row.
|
|
164
|
+
*/
|
|
165
|
+
const ROW_CTX = Symbol('svgrid.row.ctx');
|
|
166
|
+
const ROW_VALUES = Symbol('svgrid.row.values');
|
|
167
|
+
const ROW_CELLS = Symbol('svgrid.row.cells');
|
|
168
|
+
/**
|
|
169
|
+
* The methods every base row carries, defined ONCE and assigned by reference.
|
|
170
|
+
*
|
|
171
|
+
* Rows used to be built as object literals whose methods were closures, which
|
|
172
|
+
* meant a 100k-row grid allocated 700k closures and a closure scope per row
|
|
173
|
+
* before painting anything. Measured at 100k x 9: 13.8 ms and 56.5 MB to build,
|
|
174
|
+
* against 2.0 ms and 14.5 MB for this shape - the single largest cost in
|
|
175
|
+
* mounting a large grid.
|
|
176
|
+
*
|
|
177
|
+
* They read their row through `this` rather than a captured variable, which is
|
|
178
|
+
* why they can be shared. Note they are assigned as OWN properties rather than
|
|
179
|
+
* put on a prototype: `Row` is public, several row models legitimately do
|
|
180
|
+
* `{ ...row, depth }`, and a spread copies own properties but not a prototype.
|
|
181
|
+
* A class here would silently strip every method off a cloned row.
|
|
182
|
+
*/
|
|
183
|
+
const BASE_ROW_METHODS = {
|
|
184
|
+
getCanExpand() {
|
|
185
|
+
return false;
|
|
186
|
+
},
|
|
187
|
+
getIsExpanded() {
|
|
188
|
+
return Boolean((this[ROW_CTX].store.state.expanded ?? {})[this.id]);
|
|
189
|
+
},
|
|
190
|
+
toggleExpanded() {
|
|
191
|
+
const id = this.id;
|
|
192
|
+
this[ROW_CTX].grid.setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
|
|
193
|
+
},
|
|
194
|
+
getIsSelected() {
|
|
195
|
+
return Boolean((this[ROW_CTX].store.state.rowSelection ?? {})[this.id]);
|
|
196
|
+
},
|
|
197
|
+
toggleSelected() {
|
|
198
|
+
const id = this.id;
|
|
199
|
+
this[ROW_CTX].grid.setRowSelection((prev) => ({ ...prev, [id]: !prev[id] }));
|
|
200
|
+
},
|
|
201
|
+
getAllCells() {
|
|
202
|
+
return this[ROW_CELLS] ?? buildBaseRowCells(this);
|
|
203
|
+
},
|
|
204
|
+
getCellValueByColumnId(columnId) {
|
|
205
|
+
const idx = this[ROW_CTX].columnIndexById.get(columnId);
|
|
206
|
+
if (idx === undefined)
|
|
207
|
+
return undefined;
|
|
208
|
+
if (!this[ROW_VALUES])
|
|
209
|
+
this[ROW_VALUES] = baseRowValues(this);
|
|
210
|
+
return this[ROW_VALUES][idx];
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* Materialise one row's `Cell[]`, memoised on the row.
|
|
215
|
+
*
|
|
216
|
+
* A free function taking the row rather than a method using `this`, because the
|
|
217
|
+
* cell closures need a stable reference to it and aliasing `this` inside a
|
|
218
|
+
* method is exactly the pattern that produces `self`/`that` bugs.
|
|
219
|
+
*/
|
|
220
|
+
function buildBaseRowCells(row) {
|
|
221
|
+
const { columns, columnCount, grid } = row[ROW_CTX];
|
|
222
|
+
const built = new Array(columnCount);
|
|
223
|
+
for (let i = 0; i < columnCount; i++) {
|
|
224
|
+
const column = columns[i];
|
|
225
|
+
const colIndex = i;
|
|
226
|
+
const cell = {
|
|
227
|
+
id: `${row.id}_${column.id}`,
|
|
228
|
+
row,
|
|
229
|
+
column,
|
|
230
|
+
getValue: () => {
|
|
231
|
+
if (!row[ROW_VALUES])
|
|
232
|
+
row[ROW_VALUES] = baseRowValues(row);
|
|
233
|
+
return row[ROW_VALUES][colIndex];
|
|
234
|
+
},
|
|
235
|
+
getContext: () => ({
|
|
236
|
+
cell,
|
|
237
|
+
row,
|
|
238
|
+
column,
|
|
239
|
+
table: grid,
|
|
240
|
+
getValue: () => cell.getValue(),
|
|
241
|
+
}),
|
|
242
|
+
};
|
|
243
|
+
built[i] = cell;
|
|
244
|
+
}
|
|
245
|
+
row[ROW_CELLS] = built;
|
|
246
|
+
return built;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve every column's value for one row. Kept lazy: a 100k-row grid showing
|
|
250
|
+
* twenty rows must not materialise 900k values to paint.
|
|
251
|
+
*/
|
|
252
|
+
function baseRowValues(row) {
|
|
253
|
+
const { columns, columnCount } = row[ROW_CTX];
|
|
254
|
+
const original = row.original;
|
|
255
|
+
const values = new Array(columnCount);
|
|
256
|
+
for (let i = 0; i < columnCount; i++) {
|
|
257
|
+
const def = columns[i].columnDef;
|
|
258
|
+
if (def.fieldFn)
|
|
259
|
+
values[i] = def.fieldFn(original);
|
|
260
|
+
else if (def.field)
|
|
261
|
+
values[i] = original[def.field];
|
|
262
|
+
else
|
|
263
|
+
values[i] = undefined;
|
|
264
|
+
}
|
|
265
|
+
return values;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The identity stage that starts every pipeline. Always required, even when no
|
|
269
|
+
* other stage is: it is what turns your data into rows.
|
|
270
|
+
*/
|
|
72
271
|
export function createCoreRowModel() {
|
|
73
272
|
return ({ rows }) => rows;
|
|
74
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Drops rows that fail the active {@link ColumnFiltersState}. Pairs with
|
|
276
|
+
* `columnFilteringFeature`; without it there are no filters to apply.
|
|
277
|
+
*/
|
|
75
278
|
export function createFilteredRowModel() {
|
|
76
279
|
return ({ table, rows }) => {
|
|
77
280
|
const filters = table.getState().columnFilters ?? [];
|
|
78
281
|
if (!filters.length)
|
|
79
282
|
return rows;
|
|
283
|
+
// Resolve each filter's match function once, outside the row loop.
|
|
284
|
+
const compiled = filters.map((filter) => ({
|
|
285
|
+
id: filter.id,
|
|
286
|
+
value: filter.value,
|
|
287
|
+
fn: filter.fn ? filterFns[filter.fn] : filterFns.includesString,
|
|
288
|
+
}));
|
|
80
289
|
return rows.filter((row) => {
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
290
|
+
for (let i = 0; i < compiled.length; i++) {
|
|
291
|
+
const filter = compiled[i];
|
|
292
|
+
// `getCellValueByColumnId` rather than `getAllCells().find(...)`.
|
|
293
|
+
// Both read the same lazily-built `cachedValues` array, but the latter
|
|
294
|
+
// also builds and caches the row's whole `Cell[]` - one object per
|
|
295
|
+
// column - purely to reach one field. On a 100k-row grid that is
|
|
296
|
+
// 100,000 cell arrays the filter never looks at again, and it defeats
|
|
297
|
+
// the laziness the row factory exists to provide.
|
|
298
|
+
if (!filter.fn(row.getCellValueByColumnId(filter.id), filter.value))
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
89
302
|
});
|
|
90
303
|
};
|
|
91
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Narrows the rows to the current page. Put it LAST: anything after it would
|
|
307
|
+
* only ever see one page of data.
|
|
308
|
+
*/
|
|
92
309
|
export function createPaginatedRowModel() {
|
|
93
310
|
return ({ table, rows }) => {
|
|
94
311
|
const pagination = table.getState().pagination ?? { pageIndex: 0, pageSize: rows.length || 10 };
|
|
@@ -96,6 +313,10 @@ export function createPaginatedRowModel() {
|
|
|
96
313
|
return rows.slice(start, start + pagination.pageSize);
|
|
97
314
|
};
|
|
98
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Buckets rows by the active {@link GroupingState} and inserts a group row
|
|
318
|
+
* ahead of each bucket, carrying that bucket's aggregates.
|
|
319
|
+
*/
|
|
99
320
|
export function createGroupedRowModel() {
|
|
100
321
|
return ({ table, rows }) => {
|
|
101
322
|
const grouping = table.getState().grouping ?? [];
|
|
@@ -106,7 +327,10 @@ export function createGroupedRowModel() {
|
|
|
106
327
|
// group row is built that stands in for its children - a non-group column
|
|
107
328
|
// resolves to the value shared by every leaf row, or to undefined when the
|
|
108
329
|
// leaves disagree.
|
|
109
|
-
function buildGroups(input, levelIndex, depth, idPrefix
|
|
330
|
+
function buildGroups(input, levelIndex, depth, idPrefix,
|
|
331
|
+
/** Grouping columns already fixed by an ancestor bucket, and their raw
|
|
332
|
+
* values - `undefined` where that bucket mixed several. */
|
|
333
|
+
fixedValues) {
|
|
110
334
|
if (levelIndex >= grouping.length) {
|
|
111
335
|
// Leaves: actual data rows, with their nesting depth recorded.
|
|
112
336
|
return input.map((row) => ({ ...row, depth }));
|
|
@@ -118,22 +342,51 @@ export function createGroupedRowModel() {
|
|
|
118
342
|
for (const row of input) {
|
|
119
343
|
const value = row.getCellValueByColumnId(groupKey);
|
|
120
344
|
const key = String(value ?? '');
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
345
|
+
const bucket = buckets.get(key);
|
|
346
|
+
if (bucket) {
|
|
347
|
+
bucket.rows.push(row);
|
|
348
|
+
if (!bucket.mixed && bucket.raw !== value)
|
|
349
|
+
bucket.mixed = true;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
buckets.set(key, { rows: [row], raw: value, mixed: false });
|
|
353
|
+
}
|
|
124
354
|
}
|
|
125
355
|
const groupRows = [];
|
|
126
356
|
let index = 0;
|
|
127
|
-
buckets.forEach((
|
|
357
|
+
buckets.forEach((bucket, key) => {
|
|
358
|
+
const children = bucket.rows;
|
|
128
359
|
const id = `${idPrefix}_${groupKey}_${key}`;
|
|
129
|
-
|
|
360
|
+
// Record this column as fixed for deeper levels ONLY when the bucket is
|
|
361
|
+
// homogeneous. If all rows here share a raw value, so does every subset
|
|
362
|
+
// of them, which is what makes the shortcut sound.
|
|
363
|
+
//
|
|
364
|
+
// A MIXED bucket must not be recorded at all - not even as "undefined".
|
|
365
|
+
// `null`, `undefined` and `''` share a bucket key, so a mixed bucket can
|
|
366
|
+
// still split into homogeneous children one level down, and those
|
|
367
|
+
// children have a real shared value that a scan would find. Marking the
|
|
368
|
+
// column resolved here would hand them the parent's disagreement.
|
|
369
|
+
const nextFixed = bucket.mixed ? fixedValues : new Map(fixedValues).set(groupKey, bucket.raw);
|
|
370
|
+
const subRows = buildGroups(children, levelIndex + 1, depth + 1, id, nextFixed);
|
|
130
371
|
const isDeepest = levelIndex + 1 >= grouping.length;
|
|
131
372
|
const leafCount = isDeepest
|
|
132
373
|
? subRows.length
|
|
133
374
|
: subRows.reduce((sum, sub) => sum + (sub.leafCount ?? 0), 0);
|
|
375
|
+
// Every grouping column ABOVE this level is already resolved: bucketing
|
|
376
|
+
// by it is what made it constant, so scanning the children to rediscover
|
|
377
|
+
// it is pure waste. Only the current level's key short-circuited before,
|
|
378
|
+
// so a second-level group walked all of its children to re-derive the
|
|
379
|
+
// first level's value - about 100,000 reads on the 100k x 9 two-level
|
|
380
|
+
// case, for an answer already in hand.
|
|
381
|
+
//
|
|
382
|
+
// The current level still returns the stringified bucket key rather than
|
|
383
|
+
// the raw value, because that is what it has always returned and the
|
|
384
|
+
// group row's display depends on it.
|
|
134
385
|
const resolveColumnValue = (columnId) => {
|
|
135
386
|
if (columnId === groupKey)
|
|
136
387
|
return key;
|
|
388
|
+
if (fixedValues.has(columnId))
|
|
389
|
+
return fixedValues.get(columnId);
|
|
137
390
|
let resolved;
|
|
138
391
|
let hasResolved = false;
|
|
139
392
|
for (const child of children) {
|
|
@@ -190,7 +443,7 @@ export function createGroupedRowModel() {
|
|
|
190
443
|
});
|
|
191
444
|
return groupRows;
|
|
192
445
|
}
|
|
193
|
-
return buildGroups(rows, 0, 0, 'group');
|
|
446
|
+
return buildGroups(rows, 0, 0, 'group', new Map());
|
|
194
447
|
};
|
|
195
448
|
}
|
|
196
449
|
/**
|
|
@@ -282,6 +535,10 @@ export function flattenTreeData(data, options) {
|
|
|
282
535
|
walk(data, null);
|
|
283
536
|
return out;
|
|
284
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Hides the descendants of collapsed rows. Needed for grouping, tree data and
|
|
540
|
+
* master-detail alike - all three are the same expand/collapse mechanism.
|
|
541
|
+
*/
|
|
285
542
|
export function createExpandedRowModel() {
|
|
286
543
|
return ({ table, rows }) => {
|
|
287
544
|
const expanded = table.getState().expanded ?? {};
|
|
@@ -298,31 +555,220 @@ export function createExpandedRowModel() {
|
|
|
298
555
|
return flattened;
|
|
299
556
|
};
|
|
300
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Orders rows by the active {@link SortingState}. Pass your own comparators to
|
|
560
|
+
* override the built-in {@link sortFns} - useful for locale-aware or
|
|
561
|
+
* domain-specific ordering.
|
|
562
|
+
*/
|
|
301
563
|
export function createSortedRowModel(localSortFns = sortFns) {
|
|
302
|
-
return ({ table, rows })
|
|
564
|
+
return function sortedRowModelStage({ table, rows }) {
|
|
303
565
|
const sorting = table.getState().sorting ?? [];
|
|
304
566
|
if (!sorting.length)
|
|
305
567
|
return rows;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
568
|
+
// Resolve every clause ONCE, before sorting.
|
|
569
|
+
//
|
|
570
|
+
// This used to live inside the comparator, so `getAllColumns().find(...)`
|
|
571
|
+
// ran per comparison per clause: a single-clause sort of 100k rows made
|
|
572
|
+
// 1,528,947 array scans, and a three-clause sort made 3,933,751 (measured;
|
|
573
|
+
// `pnpm bench --case=sort-1col`). The comparator is called O(n log n)
|
|
574
|
+
// times, so anything inside it that is not O(1) sets the cost of the sort.
|
|
575
|
+
const allColumns = table.getAllColumns();
|
|
576
|
+
const clauses = [];
|
|
577
|
+
for (const clause of sorting) {
|
|
578
|
+
const column = allColumns.find((col) => col.id === clause.id);
|
|
579
|
+
if (!column)
|
|
580
|
+
continue;
|
|
581
|
+
const editorType = column.columnDef.editorType;
|
|
582
|
+
const comparator = editorType === 'number'
|
|
583
|
+
? localSortFns.number
|
|
584
|
+
: editorType === 'date' || editorType === 'datetime'
|
|
585
|
+
? localSortFns.date
|
|
586
|
+
: localSortFns.auto;
|
|
587
|
+
// Precompute one sort key per row, so the comparator reads an array slot
|
|
588
|
+
// instead of walking the row's column index on every comparison. For the
|
|
589
|
+
// three built-in comparators the key is also cheaper to compare than the
|
|
590
|
+
// raw value: a timestamp rather than two `new Date()` allocations, a
|
|
591
|
+
// number rather than two `Number()` coercions, a collator rather than a
|
|
592
|
+
// fresh one per `localeCompare` call.
|
|
593
|
+
//
|
|
594
|
+
// The identity checks against `sortFns` matter: `localSortFns` is a
|
|
595
|
+
// public parameter, so a caller can substitute their own comparators.
|
|
596
|
+
// When they have, we fall through to calling their function with the raw
|
|
597
|
+
// values - still hoisted, just not specialised.
|
|
598
|
+
const columnId = column.id;
|
|
599
|
+
const n = rows.length;
|
|
600
|
+
let keys = new Array(n);
|
|
601
|
+
let compare;
|
|
602
|
+
if (comparator === sortFns.number) {
|
|
603
|
+
for (let i = 0; i < n; i++)
|
|
604
|
+
keys[i] = Number(rows[i].getCellValueByColumnId(columnId) ?? 0);
|
|
605
|
+
compare = compareNumericKeys;
|
|
320
606
|
}
|
|
321
|
-
|
|
322
|
-
|
|
607
|
+
else if (comparator === sortFns.date) {
|
|
608
|
+
for (let i = 0; i < n; i++) {
|
|
609
|
+
keys[i] = new Date(rows[i].getCellValueByColumnId(columnId)).getTime();
|
|
610
|
+
}
|
|
611
|
+
compare = compareNumericKeys;
|
|
612
|
+
}
|
|
613
|
+
else if (comparator === sortFns.auto) {
|
|
614
|
+
const strings = new Array(n);
|
|
615
|
+
// Decide whether ranking is worth attempting BEFORE paying for it.
|
|
616
|
+
//
|
|
617
|
+
// Building the distinct set and then discarding it costs about 9 ms on
|
|
618
|
+
// a 100k-row column where nearly every value is unique, and ranking
|
|
619
|
+
// saves about 19 ms where they repeat - so guessing wrong in either
|
|
620
|
+
// direction is measurable. A small stride sample answers it for well
|
|
621
|
+
// under a millisecond.
|
|
622
|
+
//
|
|
623
|
+
// Strided rather than the first N rows: data arrives sorted or
|
|
624
|
+
// clustered often enough that a prefix is a bad estimator of the whole
|
|
625
|
+
// column. Reading every k-th row is no more expensive and does not care
|
|
626
|
+
// how the rows are arranged.
|
|
627
|
+
const rankLimit = n >> 1;
|
|
628
|
+
let distinct = null;
|
|
629
|
+
if (n > 0) {
|
|
630
|
+
const sampleTarget = Math.min(n, 256);
|
|
631
|
+
const stride = Math.max(1, Math.floor(n / sampleTarget));
|
|
632
|
+
const sample = new Set();
|
|
633
|
+
let sampled = 0;
|
|
634
|
+
for (let i = 0; i < n; i += stride) {
|
|
635
|
+
sample.add(String(rows[i].getCellValueByColumnId(columnId)));
|
|
636
|
+
sampled++;
|
|
637
|
+
}
|
|
638
|
+
// Only attempt ranking when the sample suggests real repetition.
|
|
639
|
+
if (sample.size * 2 <= sampled)
|
|
640
|
+
distinct = new Set();
|
|
641
|
+
}
|
|
642
|
+
for (let i = 0; i < n; i++) {
|
|
643
|
+
const s = String(rows[i].getCellValueByColumnId(columnId));
|
|
644
|
+
strings[i] = s;
|
|
645
|
+
if (distinct) {
|
|
646
|
+
distinct.add(s);
|
|
647
|
+
if (distinct.size > rankLimit)
|
|
648
|
+
distinct = null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// Collation is by far the most expensive comparison we do - a CPU
|
|
652
|
+
// profile of a 100k text sort put 65% of the whole operation inside the
|
|
653
|
+
// collator. But a column's DISTINCT values are usually far fewer than
|
|
654
|
+
// its rows (statuses, regions, categories, owners), so rank the
|
|
655
|
+
// distinct values once and sort by rank afterwards. That turns
|
|
656
|
+
// O(n log n) collator calls into O(u log u), where u is the number of
|
|
657
|
+
// distinct values, and the resulting order is identical because rank is
|
|
658
|
+
// a monotone relabelling of the collated order - equal strings share a
|
|
659
|
+
// rank, so ties still fall through to the stable sort exactly as before.
|
|
660
|
+
//
|
|
661
|
+
// Guarded on the uniqueness ratio: when nearly every value is distinct
|
|
662
|
+
// the ranking pass cannot save any collator calls and would just add an
|
|
663
|
+
// O(n) Map build, so that case keeps comparing directly.
|
|
664
|
+
if (distinct) {
|
|
665
|
+
const ordered = Array.from(distinct).sort(compareCollatedKeys);
|
|
666
|
+
const rankOf = new Map();
|
|
667
|
+
for (let i = 0; i < ordered.length; i++)
|
|
668
|
+
rankOf.set(ordered[i], i);
|
|
669
|
+
for (let i = 0; i < n; i++)
|
|
670
|
+
keys[i] = rankOf.get(strings[i]);
|
|
671
|
+
compare = compareNumericKeys;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
keys = strings;
|
|
675
|
+
compare = compareCollatedKeys;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
for (let i = 0; i < n; i++)
|
|
680
|
+
keys[i] = rows[i].getCellValueByColumnId(columnId);
|
|
681
|
+
compare = comparator;
|
|
682
|
+
}
|
|
683
|
+
clauses.push({ keys, desc: clause.desc, compare });
|
|
684
|
+
}
|
|
685
|
+
if (!clauses.length)
|
|
686
|
+
return rows;
|
|
687
|
+
// Sort an index array, then materialise. `Array.prototype.sort` is stable,
|
|
688
|
+
// so equal keys keep their original relative order exactly as the previous
|
|
689
|
+
// `[...rows].sort(...)` did.
|
|
690
|
+
const order = new Array(rows.length);
|
|
691
|
+
for (let i = 0; i < order.length; i++)
|
|
692
|
+
order[i] = i;
|
|
693
|
+
// Single-clause sorts get a specialised comparator.
|
|
694
|
+
//
|
|
695
|
+
// Most sorts are one column, and that path runs O(n log n) times - 1.66
|
|
696
|
+
// million comparisons for 100k rows. The general loop pays a clause-array
|
|
697
|
+
// index, three property loads and an indirect call on every one of them,
|
|
698
|
+
// none of which vary once the clause list is fixed. Hoisting them into a
|
|
699
|
+
// closure and, for the numeric comparator, inlining the subtraction removes
|
|
700
|
+
// the call entirely.
|
|
701
|
+
//
|
|
702
|
+
// `keys[ib] - keys[ia]` for descending is exactly `-(keys[ia] - keys[ib])`
|
|
703
|
+
// as far as sorting is concerned: both are NaN for unorderable values,
|
|
704
|
+
// which the spec coerces to 0, and they differ only in producing 0 versus
|
|
705
|
+
// -0 for equal keys, which sorts identically.
|
|
706
|
+
if (clauses.length === 1) {
|
|
707
|
+
const { keys, compare, desc } = clauses[0];
|
|
708
|
+
if (compare === compareNumericKeys) {
|
|
709
|
+
order.sort(desc
|
|
710
|
+
? function compareOneNumericDesc(ia, ib) { return keys[ib] - keys[ia]; }
|
|
711
|
+
: function compareOneNumericAsc(ia, ib) { return keys[ia] - keys[ib]; });
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
order.sort(desc
|
|
715
|
+
? function compareOneDesc(ia, ib) { return -compare(keys[ia], keys[ib]); }
|
|
716
|
+
: function compareOneAsc(ia, ib) { return compare(keys[ia], keys[ib]); });
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
order.sort(function compareRowsByClauses(ia, ib) {
|
|
721
|
+
for (let k = 0; k < clauses.length; k++) {
|
|
722
|
+
const clause = clauses[k];
|
|
723
|
+
const result = clause.compare(clause.keys[ia], clause.keys[ib]);
|
|
724
|
+
if (result !== 0)
|
|
725
|
+
return clause.desc ? -result : result;
|
|
726
|
+
}
|
|
727
|
+
return 0;
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
const sorted = new Array(rows.length);
|
|
731
|
+
for (let i = 0; i < order.length; i++)
|
|
732
|
+
sorted[i] = rows[order[i]];
|
|
323
733
|
return sorted;
|
|
324
734
|
};
|
|
325
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* Numeric key comparison for the built-in `number` and `date` comparators.
|
|
738
|
+
* Subtraction rather than `<`/`>` on purpose: it reproduces the originals
|
|
739
|
+
* exactly, NaN included. An unparseable date or a non-numeric value yields NaN,
|
|
740
|
+
* and the sort spec turns a NaN comparison result into 0 (SortCompare coerces
|
|
741
|
+
* it), which is the behaviour callers already depend on.
|
|
742
|
+
*/
|
|
743
|
+
function compareNumericKeys(a, b) {
|
|
744
|
+
return a - b;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Text key comparison for the built-in `auto` comparator.
|
|
748
|
+
*
|
|
749
|
+
* `localeCompare`, NOT a hoisted `Intl.Collator`. The specification defines
|
|
750
|
+
* `localeCompare` with no locale or options as constructing a default collator
|
|
751
|
+
* per call, so hoisting one looks like the obvious optimisation - and it is
|
|
752
|
+
* measurably slower. V8 fast-paths `String.prototype.localeCompare` for the
|
|
753
|
+
* default locale; going through a collator object misses that path. Measured
|
|
754
|
+
* sorting 100k strings: 33 ms via `localeCompare` against 83 ms via a cached
|
|
755
|
+
* collator on ASCII, 58 ms against 99 ms with accents mixed in, and the two
|
|
756
|
+
* produce byte-identical orderings across all 100k positions.
|
|
757
|
+
*
|
|
758
|
+
* Left as its own function so the sort path has one place to change if that
|
|
759
|
+
* ever stops being true. Re-measure before "optimising" this again.
|
|
760
|
+
*/
|
|
761
|
+
function compareCollatedKeys(a, b) {
|
|
762
|
+
return a.localeCompare(b);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Build a headless grid: state, the row pipeline, and the read methods, with no
|
|
766
|
+
* DOM and no Svelte. This is the engine `<SvGrid>` renders.
|
|
767
|
+
*
|
|
768
|
+
* Most callers want `createSvGrid` (the runes-aware wrapper) or the component
|
|
769
|
+
* itself; reach for this when you are writing your own renderer or running the
|
|
770
|
+
* pipeline outside a browser.
|
|
771
|
+
*/
|
|
326
772
|
export function createSvGridCore(options) {
|
|
327
773
|
const internalState = {
|
|
328
774
|
sorting: [],
|
|
@@ -530,94 +976,61 @@ export function createSvGridCore(options) {
|
|
|
530
976
|
for (let i = 0; i < columns.length; i++)
|
|
531
977
|
columnIndexById.set(columns[i].id, i);
|
|
532
978
|
const columnCount = columns.length;
|
|
979
|
+
// One shared context for every row in this table, so a row carries a
|
|
980
|
+
// pointer rather than a closure scope. See BASE_ROW_METHODS.
|
|
981
|
+
const rowCtx = {
|
|
982
|
+
grid: grid,
|
|
983
|
+
store,
|
|
984
|
+
columns,
|
|
985
|
+
columnCount,
|
|
986
|
+
columnIndexById,
|
|
987
|
+
};
|
|
533
988
|
cachedBaseRows = new Array(options.data.length);
|
|
534
989
|
const getRowId = options.getRowId;
|
|
990
|
+
const m = BASE_ROW_METHODS;
|
|
535
991
|
for (let index = 0; index < options.data.length; index++) {
|
|
536
992
|
const original = options.data[index];
|
|
537
|
-
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
// row's full value array or cell objects up front.
|
|
541
|
-
let cachedValues = null;
|
|
542
|
-
let cachedCells = null;
|
|
543
|
-
function computeValues() {
|
|
544
|
-
const values = new Array(columnCount);
|
|
545
|
-
for (let i = 0; i < columnCount; i++) {
|
|
546
|
-
const column = columns[i];
|
|
547
|
-
if (column.columnDef.fieldFn) {
|
|
548
|
-
values[i] = column.columnDef.fieldFn(original);
|
|
549
|
-
}
|
|
550
|
-
else if (column.columnDef.field) {
|
|
551
|
-
values[i] = original[column.columnDef.field];
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
values[i] = undefined;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return values;
|
|
558
|
-
}
|
|
993
|
+
// `_values` and `_cells` stay null until something reads them - a
|
|
994
|
+
// 100k-row grid showing twenty rows must not materialise every row's
|
|
995
|
+
// values or cell objects to paint.
|
|
559
996
|
const row = {
|
|
560
|
-
id,
|
|
997
|
+
id: getRowId ? getRowId(original, index) : String(index),
|
|
561
998
|
index,
|
|
562
999
|
original,
|
|
563
1000
|
depth: 0,
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (cachedCells)
|
|
575
|
-
return cachedCells;
|
|
576
|
-
const built = new Array(columnCount);
|
|
577
|
-
for (let i = 0; i < columnCount; i++) {
|
|
578
|
-
const column = columns[i];
|
|
579
|
-
const colIndex = i;
|
|
580
|
-
const cell = {
|
|
581
|
-
id: `${id}_${column.id}`,
|
|
582
|
-
row,
|
|
583
|
-
column,
|
|
584
|
-
getValue: () => {
|
|
585
|
-
if (!cachedValues)
|
|
586
|
-
cachedValues = computeValues();
|
|
587
|
-
return cachedValues[colIndex];
|
|
588
|
-
},
|
|
589
|
-
getContext: () => ({
|
|
590
|
-
cell,
|
|
591
|
-
row,
|
|
592
|
-
column,
|
|
593
|
-
table: grid,
|
|
594
|
-
getValue: () => cell.getValue(),
|
|
595
|
-
}),
|
|
596
|
-
};
|
|
597
|
-
built[i] = cell;
|
|
598
|
-
}
|
|
599
|
-
cachedCells = built;
|
|
600
|
-
return built;
|
|
601
|
-
},
|
|
602
|
-
getCellValueByColumnId: (columnId) => {
|
|
603
|
-
const idx = columnIndexById.get(columnId);
|
|
604
|
-
if (idx === undefined)
|
|
605
|
-
return undefined;
|
|
606
|
-
if (!cachedValues)
|
|
607
|
-
cachedValues = computeValues();
|
|
608
|
-
return cachedValues[idx];
|
|
609
|
-
},
|
|
1001
|
+
[ROW_CTX]: rowCtx,
|
|
1002
|
+
[ROW_VALUES]: null,
|
|
1003
|
+
[ROW_CELLS]: null,
|
|
1004
|
+
getCanExpand: m.getCanExpand,
|
|
1005
|
+
getIsExpanded: m.getIsExpanded,
|
|
1006
|
+
toggleExpanded: m.toggleExpanded,
|
|
1007
|
+
getIsSelected: m.getIsSelected,
|
|
1008
|
+
toggleSelected: m.toggleSelected,
|
|
1009
|
+
getAllCells: m.getAllCells,
|
|
1010
|
+
getCellValueByColumnId: m.getCellValueByColumnId,
|
|
610
1011
|
};
|
|
611
1012
|
cachedBaseRows[index] = row;
|
|
612
1013
|
}
|
|
613
1014
|
}
|
|
1015
|
+
// Only the slices a pipeline stage actually READS belong in this key.
|
|
1016
|
+
//
|
|
1017
|
+
// `rowSelection` used to be here, which meant ticking one checkbox on a
|
|
1018
|
+
// 100k-row grid re-filtered and re-sorted the entire dataset to rebuild a
|
|
1019
|
+
// row array that was identical by construction. Nothing reads it: the two
|
|
1020
|
+
// consumers are `getIsSelected` closures (on data rows and on group rows)
|
|
1021
|
+
// that read `store.state` when called, so they observe a selection change
|
|
1022
|
+
// without the model being rebuilt.
|
|
1023
|
+
//
|
|
1024
|
+
// `_rowModels` is a closed set of six named slots, so no consumer stage
|
|
1025
|
+
// can be inserted that might read selection. A caller CAN supply a custom
|
|
1026
|
+
// function for one of those slots; if one ever needs a slice that is not
|
|
1027
|
+
// listed here, add it here rather than reinstating all of them.
|
|
614
1028
|
const currentSlices = {
|
|
615
1029
|
sorting: store.state.sorting,
|
|
616
1030
|
columnFilters: store.state.columnFilters,
|
|
617
1031
|
pagination: store.state.pagination,
|
|
618
1032
|
grouping: store.state.grouping,
|
|
619
1033
|
expanded: store.state.expanded,
|
|
620
|
-
rowSelection: store.state.rowSelection,
|
|
621
1034
|
};
|
|
622
1035
|
if (cachedRowModel &&
|
|
623
1036
|
cachedRowModelBaseRows === cachedBaseRows &&
|
|
@@ -626,8 +1039,7 @@ export function createSvGridCore(options) {
|
|
|
626
1039
|
cachedSlices?.columnFilters === currentSlices.columnFilters &&
|
|
627
1040
|
cachedSlices?.pagination === currentSlices.pagination &&
|
|
628
1041
|
cachedSlices?.grouping === currentSlices.grouping &&
|
|
629
|
-
cachedSlices?.expanded === currentSlices.expanded
|
|
630
|
-
cachedSlices?.rowSelection === currentSlices.rowSelection) {
|
|
1042
|
+
cachedSlices?.expanded === currentSlices.expanded) {
|
|
631
1043
|
return cachedRowModel;
|
|
632
1044
|
}
|
|
633
1045
|
let rows = cachedBaseRows;
|
|
@@ -653,6 +1065,7 @@ export function createSvGridCore(options) {
|
|
|
653
1065
|
};
|
|
654
1066
|
return grid;
|
|
655
1067
|
}
|
|
1068
|
+
/** Narrowing helper for the many options that accept a value or a function. */
|
|
656
1069
|
export function isFunction(value) {
|
|
657
1070
|
return typeof value === 'function';
|
|
658
1071
|
}
|