@svgrid/grid 3.0.5 → 3.0.7
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/README.md +28 -7
- package/dist/GridMenus.svelte +34 -4
- package/dist/SvGrid.controller.svelte.d.ts +23 -3
- package/dist/SvGrid.controller.svelte.js +235 -54
- package/dist/SvGrid.css +143 -7
- package/dist/SvGrid.helpers.js +2 -4
- package/dist/SvGrid.svelte +223 -38
- package/dist/SvGrid.types.d.ts +77 -4
- package/dist/SvGridDropdown.svelte +20 -0
- package/dist/SvGridDropdown.svelte.d.ts +12 -0
- package/dist/SvModal.svelte +22 -0
- package/dist/SvModal.svelte.d.ts +7 -0
- package/dist/a11y/dismissable.d.ts +10 -0
- package/dist/a11y/dismissable.js +24 -2
- package/dist/build-api.js +81 -31
- package/dist/cdn/{GridMenus-CID62rlI.js → GridMenus-BgUS-W3o.js} +151 -142
- package/dist/cdn/GridMenus-CcwU8I4t.js +644 -0
- package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
- package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-9yz-SbdZ.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-D7-ql1Hd.js} +2 -2
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DMUOFxOe.js} +4 -4
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-N7ik0oWy.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-CB_Jnm2k.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-Ca7gF8ZK.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-9GOJRHP7.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-Dl-FujN8.js} +5 -5
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-B1LFgoPP.js} +2 -2
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-By3n7DGZ.js} +2 -2
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
- package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
- package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
- package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-Mnv84zoE.js} +28 -6
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-By7h2Guv.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-BC-tOgFY.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DUaXr-sb.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-BDtrNEiA.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-C1aOAPBZ.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{editor-registry-BhgE3S84.js → editor-registry-BVeHvlZR.js} +49 -50
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-y1wdyCcv.js} +93 -97
- package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
- package/dist/cdn/{server-block-cache-CGoWz-87.js → server-block-cache-DIqb3VZn.js} +4 -0
- package/dist/cdn/{src-jcFMkBnN.js → src-25JmJ_7g.js} +4768 -4149
- package/dist/cdn/{src-Bklofsad.js → src-kE9B98r6.js} +4796 -4177
- package/dist/cdn/svgrid.js +18 -18
- package/dist/cdn/svgrid.svelte-external.js +18 -18
- package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
- package/dist/cell-formatting.d.ts +9 -0
- package/dist/cell-formatting.js +52 -1
- package/dist/cell-render.js +25 -6
- package/dist/clipboard.d.ts +2 -2
- package/dist/clipboard.js +72 -23
- package/dist/columns.js +7 -2
- package/dist/conditional-formatting.d.ts +2 -0
- package/dist/conditional-formatting.js +2 -0
- package/dist/core.d.ts +22 -6
- package/dist/core.js +478 -40
- package/dist/editing.js +24 -0
- package/dist/export-format.d.ts +8 -0
- package/dist/export-format.js +23 -16
- package/dist/grid-messages.d.ts +4 -0
- package/dist/grid-messages.js +4 -0
- package/dist/headless.d.ts +5 -0
- package/dist/headless.js +12 -0
- package/dist/history.d.ts +21 -0
- package/dist/history.js +34 -4
- package/dist/index.d.ts +1 -1
- package/dist/keyboard-handlers.js +24 -1
- package/dist/keyboard.js +4 -0
- package/dist/row-drag-touch.d.ts +1 -1
- package/dist/row-drag.js +35 -1
- package/dist/row-model.d.ts +6 -0
- package/dist/row-resize.js +22 -1
- package/dist/selection.js +1 -1
- package/dist/server-block-cache.d.ts +5 -0
- package/dist/server-block-cache.js +8 -0
- package/dist/server-data-source.d.ts +35 -3
- package/dist/server-data-source.js +11 -2
- package/dist/server.d.ts +1 -1
- package/dist/sparkline.js +7 -5
- package/dist/svgrid-wrapper.types.d.ts +33 -0
- package/dist/validate.js +9 -1
- package/dist/virtualization/column-virtualizer.d.ts +1 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +1 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +1 -0
- package/dist/virtualization/virtualizer.d.ts +3 -0
- package/dist/virtualization/virtualizer.js +14 -1
- package/package.json +6 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +236 -57
- package/src/SvGrid.css +143 -7
- package/src/SvGrid.helpers.ts +2 -4
- package/src/SvGrid.svelte +223 -38
- package/src/SvGrid.types.ts +77 -4
- package/src/SvGridDropdown.svelte +20 -0
- package/src/SvModal.svelte +22 -0
- package/src/SvModal.test.ts +30 -1
- package/src/a11y/dismissable.test.ts +10 -1
- package/src/a11y/dismissable.ts +22 -2
- package/src/build-api.ts +85 -27
- package/src/cell-formatting.test.ts +17 -0
- package/src/cell-formatting.ts +49 -1
- package/src/cell-render.test.ts +24 -2
- package/src/cell-render.ts +24 -8
- package/src/clipboard.test.ts +107 -0
- package/src/clipboard.ts +62 -23
- package/src/columns.test.ts +4 -4
- package/src/columns.ts +7 -2
- package/src/conditional-formatting.test.ts +8 -0
- package/src/conditional-formatting.ts +3 -0
- package/src/core.sort.test.ts +86 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +478 -40
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +25 -0
- package/src/export-dates.test.ts +114 -0
- package/src/export-format.ts +26 -14
- package/src/grid-messages.test.ts +7 -0
- package/src/grid-messages.ts +10 -0
- package/src/headless.ts +24 -0
- package/src/history.test.ts +49 -1
- package/src/history.ts +48 -2
- package/src/index.ts +2 -0
- package/src/keyboard-handlers.coverage.test.ts +65 -0
- package/src/keyboard-handlers.ts +26 -1
- package/src/keyboard.ts +3 -0
- package/src/row-drag-touch.ts +2 -2
- package/src/row-drag.test.ts +51 -0
- package/src/row-drag.ts +35 -2
- package/src/row-model.ts +6 -0
- package/src/row-resize.ts +24 -1
- package/src/selection.ts +1 -1
- package/src/server-block-cache.test.ts +27 -0
- package/src/server-block-cache.ts +13 -0
- package/src/server-data-source.infinite.test.ts +62 -0
- package/src/server-data-source.test.ts +19 -0
- package/src/server-data-source.ts +48 -4
- package/src/server.ts +2 -0
- package/src/sparkline.test.ts +7 -0
- package/src/sparkline.ts +6 -4
- package/src/svgrid-wrapper.types.ts +32 -0
- package/src/svgrid.api.test.ts +21 -0
- package/src/svgrid.charting.test.ts +12 -4
- package/src/svgrid.context-menu.test.ts +12 -0
- package/src/svgrid.data-push.svelte.test.ts +94 -0
- package/src/svgrid.detail-rows.svelte.test.ts +187 -0
- package/src/svgrid.filter-menu-scroll.test.ts +9 -0
- package/src/svgrid.gantt-seam.test.ts +1 -1
- package/src/svgrid.live-update-paths.svelte.test.ts +186 -0
- package/src/svgrid.menu-scroll-close.test.ts +10 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +16 -0
- package/src/svgrid.row-pinning.test.ts +15 -1
- package/src/svgrid.selection-bar-seam.test.ts +1 -1
- package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
- package/src/svgrid.upsell-license.test.ts +1 -1
- package/src/test-setup.ts +12 -0
- package/src/transaction.test.ts +58 -0
- package/src/validate.ts +7 -1
- package/src/virtualization/svelte-virtualizer.svelte.ts +1 -0
- package/src/virtualization/virtualizer.test.ts +44 -0
- package/src/virtualization/virtualizer.ts +13 -1
- package/dist/cdn/GridMenus-DLL-qZeu.js +0 -635
package/dist/core.js
CHANGED
|
@@ -130,14 +130,54 @@ export function tableFeatures(features) {
|
|
|
130
130
|
* text; set a column's type or supply your own comparator to override.
|
|
131
131
|
*/
|
|
132
132
|
export const sortFns = {
|
|
133
|
-
auto: (a, b) =>
|
|
134
|
-
number: (a, b) =>
|
|
135
|
-
date: (a, b) =>
|
|
136
|
-
const aa = new Date(a).getTime();
|
|
137
|
-
const bb = new Date(b).getTime();
|
|
138
|
-
return aa - bb;
|
|
139
|
-
},
|
|
133
|
+
auto: (a, b) => compareAutoValues(a, b),
|
|
134
|
+
number: (a, b) => compareNumericKeys(numberSortKey(a), numberSortKey(b)),
|
|
135
|
+
date: (a, b) => compareNumericKeys(dateSortKey(a), dateSortKey(b)),
|
|
140
136
|
};
|
|
137
|
+
/** Null, undefined and the empty string: the values every built-in sort puts first. */
|
|
138
|
+
function isBlankSortValue(value) {
|
|
139
|
+
return value == null || value === '';
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Sort key for the `number` comparator. Blanks and anything that does not
|
|
143
|
+
* coerce to a number ('n/a', a placeholder) become -Infinity, so they sort
|
|
144
|
+
* together ahead of every number instead of producing NaN. A NaN key made the
|
|
145
|
+
* comparator inconsistent - NaN compared equal to everything while the numbers
|
|
146
|
+
* around it did not - and `Array.prototype.sort` then returned a visibly
|
|
147
|
+
* unsorted column (#104).
|
|
148
|
+
*/
|
|
149
|
+
function numberSortKey(value) {
|
|
150
|
+
if (isBlankSortValue(value))
|
|
151
|
+
return -Infinity;
|
|
152
|
+
const n = Number(value);
|
|
153
|
+
return Number.isNaN(n) ? -Infinity : n;
|
|
154
|
+
}
|
|
155
|
+
/** Sort key for the `date` comparator; blanks and invalid dates sort first. */
|
|
156
|
+
function dateSortKey(value) {
|
|
157
|
+
if (isBlankSortValue(value))
|
|
158
|
+
return -Infinity;
|
|
159
|
+
const t = new Date(value).getTime();
|
|
160
|
+
return Number.isNaN(t) ? -Infinity : t;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The `auto` comparator: blanks first, then numbers in numeric order, then
|
|
164
|
+
* everything else as collated text. Ordering by kind before value keeps it
|
|
165
|
+
* transitive on mixed columns - comparing a number to a string as text
|
|
166
|
+
* instead would give 2 < 10, 10 < '1a' and '1a' < 2, a cycle.
|
|
167
|
+
*/
|
|
168
|
+
function compareAutoValues(a, b) {
|
|
169
|
+
const aBlank = isBlankSortValue(a);
|
|
170
|
+
const bBlank = isBlankSortValue(b);
|
|
171
|
+
if (aBlank || bBlank)
|
|
172
|
+
return aBlank === bBlank ? 0 : aBlank ? -1 : 1;
|
|
173
|
+
const aNum = typeof a === 'number';
|
|
174
|
+
const bNum = typeof b === 'number';
|
|
175
|
+
if (aNum && bNum)
|
|
176
|
+
return compareNumericKeys(numberSortKey(a), numberSortKey(b));
|
|
177
|
+
if (aNum !== bNum)
|
|
178
|
+
return aNum ? -1 : 1;
|
|
179
|
+
return compareCollatedKeys(String(a), String(b));
|
|
180
|
+
}
|
|
141
181
|
/**
|
|
142
182
|
* Built-in match functions, named by {@link ColumnFilter}'s `fn`.
|
|
143
183
|
* `includesString` is case-insensitive substring; `equals` is strict identity.
|
|
@@ -165,6 +205,19 @@ export const filterFns = {
|
|
|
165
205
|
const ROW_CTX = Symbol('svgrid.row.ctx');
|
|
166
206
|
const ROW_VALUES = Symbol('svgrid.row.values');
|
|
167
207
|
const ROW_CELLS = Symbol('svgrid.row.cells');
|
|
208
|
+
// Where the filtered stage last put this row: the generation of that full
|
|
209
|
+
// filter and the row's index in its output. Lets a tick swap a replaced row
|
|
210
|
+
// into place without re-filtering everything (see createFilteredRowModel).
|
|
211
|
+
const ROW_FILTER_GEN = Symbol('svgrid.row.filterGen');
|
|
212
|
+
const ROW_FILTER_POS = Symbol('svgrid.row.filterPos');
|
|
213
|
+
// Stamped on the rows a sort repair drops, so the merge skips them with one
|
|
214
|
+
// property read per row instead of a Set lookup (3 ms per 100k rows).
|
|
215
|
+
const ROW_SORT_DROPPED = Symbol('svgrid.row.sortDropped');
|
|
216
|
+
// The key(s) the last full sort computed for a row: the key itself for a
|
|
217
|
+
// one-clause sort, an array for several. Read by the repair path so a tick
|
|
218
|
+
// never recomputes a kept row's key.
|
|
219
|
+
const ROW_SORT_KEY = Symbol('svgrid.row.sortKey');
|
|
220
|
+
let sortDropGeneration = 0;
|
|
168
221
|
/**
|
|
169
222
|
* The methods every base row carries, defined ONCE and assigned by reference.
|
|
170
223
|
*
|
|
@@ -276,17 +329,32 @@ export function createCoreRowModel() {
|
|
|
276
329
|
* `columnFilteringFeature`; without it there are no filters to apply.
|
|
277
330
|
*/
|
|
278
331
|
export function createFilteredRowModel() {
|
|
332
|
+
/**
|
|
333
|
+
* The last full filter, so a tick can repair it. When `getRowModel`
|
|
334
|
+
* reports which rows were replaced (`replacedRowsOf`) and the filter state
|
|
335
|
+
* and columns are the same objects as last time, each replacement is tested
|
|
336
|
+
* on its own: a row that stays in keeps its slot, a row that stays out
|
|
337
|
+
* stays out, and a row whose membership flips sends the whole thing back
|
|
338
|
+
* through the full pass, because an insertion has to land in data order.
|
|
339
|
+
* Positions come from stamps on the rows (ROW_FILTER_GEN / ROW_FILTER_POS)
|
|
340
|
+
* written by the full pass; no map is built.
|
|
341
|
+
*/
|
|
342
|
+
let cache = null;
|
|
343
|
+
let generation = 0;
|
|
279
344
|
return ({ table, rows }) => {
|
|
280
345
|
const filters = table.getState().columnFilters ?? [];
|
|
281
|
-
if (!filters.length)
|
|
346
|
+
if (!filters.length) {
|
|
347
|
+
cache = null;
|
|
282
348
|
return rows;
|
|
349
|
+
}
|
|
350
|
+
const columns = table.getAllColumns();
|
|
283
351
|
// Resolve each filter's match function once, outside the row loop.
|
|
284
352
|
const compiled = filters.map((filter) => ({
|
|
285
353
|
id: filter.id,
|
|
286
354
|
value: filter.value,
|
|
287
355
|
fn: filter.fn ? filterFns[filter.fn] : filterFns.includesString,
|
|
288
356
|
}));
|
|
289
|
-
|
|
357
|
+
const passes = (row) => {
|
|
290
358
|
for (let i = 0; i < compiled.length; i++) {
|
|
291
359
|
const filter = compiled[i];
|
|
292
360
|
// `getCellValueByColumnId` rather than `getAllCells().find(...)`.
|
|
@@ -299,7 +367,56 @@ export function createFilteredRowModel() {
|
|
|
299
367
|
return false;
|
|
300
368
|
}
|
|
301
369
|
return true;
|
|
302
|
-
}
|
|
370
|
+
};
|
|
371
|
+
if (cache && cache.filters === filters && cache.columns === columns) {
|
|
372
|
+
if (cache.input === rows)
|
|
373
|
+
return cache.output;
|
|
374
|
+
const replaced = replacedRowsOf(table);
|
|
375
|
+
if (replaced && rows.length === cache.input.length) {
|
|
376
|
+
let out = null;
|
|
377
|
+
let ok = true;
|
|
378
|
+
for (let i = 0; i < rows.length && ok; i++) {
|
|
379
|
+
const prev = cache.input[i];
|
|
380
|
+
const next = rows[i];
|
|
381
|
+
if (next === prev)
|
|
382
|
+
continue;
|
|
383
|
+
if (replaced.get(prev) !== next) {
|
|
384
|
+
ok = false;
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
const wasIn = prev[ROW_FILTER_GEN] === cache.gen;
|
|
388
|
+
const isIn = passes(next);
|
|
389
|
+
if (wasIn !== isIn) {
|
|
390
|
+
ok = false;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (!isIn)
|
|
394
|
+
continue;
|
|
395
|
+
out ?? (out = cache.output.slice());
|
|
396
|
+
const pos = prev[ROW_FILTER_POS];
|
|
397
|
+
out[pos] = next;
|
|
398
|
+
next[ROW_FILTER_GEN] = cache.gen;
|
|
399
|
+
next[ROW_FILTER_POS] = pos;
|
|
400
|
+
}
|
|
401
|
+
if (ok) {
|
|
402
|
+
const output = out ?? cache.output;
|
|
403
|
+
cache = { ...cache, input: rows, output };
|
|
404
|
+
return output;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const gen = ++generation;
|
|
409
|
+
const output = [];
|
|
410
|
+
for (let i = 0; i < rows.length; i++) {
|
|
411
|
+
const row = rows[i];
|
|
412
|
+
if (!passes(row))
|
|
413
|
+
continue;
|
|
414
|
+
row[ROW_FILTER_GEN] = gen;
|
|
415
|
+
row[ROW_FILTER_POS] = output.length;
|
|
416
|
+
output.push(row);
|
|
417
|
+
}
|
|
418
|
+
cache = { input: rows, output, filters, columns, gen };
|
|
419
|
+
return output;
|
|
303
420
|
};
|
|
304
421
|
}
|
|
305
422
|
/**
|
|
@@ -542,6 +659,19 @@ export function flattenTreeData(data, options) {
|
|
|
542
659
|
export function createExpandedRowModel() {
|
|
543
660
|
return ({ table, rows }) => {
|
|
544
661
|
const expanded = table.getState().expanded ?? {};
|
|
662
|
+
// Nothing expanded means every collapsed row hides its children and the
|
|
663
|
+
// output is the input, element for element. Returning it as is keeps the
|
|
664
|
+
// array identity the stages downstream and the render window key on, and
|
|
665
|
+
// skips a 100k-element copy per data change on a flat grid.
|
|
666
|
+
let anyExpanded = false;
|
|
667
|
+
for (const id in expanded) {
|
|
668
|
+
if (expanded[id]) {
|
|
669
|
+
anyExpanded = true;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!anyExpanded)
|
|
674
|
+
return rows;
|
|
545
675
|
const flattened = [];
|
|
546
676
|
const visit = (row) => {
|
|
547
677
|
flattened.push(row);
|
|
@@ -560,11 +690,58 @@ export function createExpandedRowModel() {
|
|
|
560
690
|
* override the built-in {@link sortFns} - useful for locale-aware or
|
|
561
691
|
* domain-specific ordering.
|
|
562
692
|
*/
|
|
693
|
+
/**
|
|
694
|
+
* Which rows the last `getRowModel` rebuild replaced, old row -> new row, for
|
|
695
|
+
* the duration of the pipeline run that follows it.
|
|
696
|
+
*
|
|
697
|
+
* A live feed replaces a few hundred row objects in a hundred-thousand-row
|
|
698
|
+
* array and hands the grid the new array. The base rows are rebuilt reusing
|
|
699
|
+
* every row whose data object is the same (see `getRowModel`), so the rebuild
|
|
700
|
+
* knows exactly which rows are new. The filtered and sorted stages read that
|
|
701
|
+
* here and repair their previous output instead of recomputing it, when the
|
|
702
|
+
* replacements are few and nothing else changed. Cleared after the run; a
|
|
703
|
+
* stage that reads it outside a run sees null and does the full work.
|
|
704
|
+
*
|
|
705
|
+
* Keyed on the grid object in a WeakMap rather than carried on the public
|
|
706
|
+
* `SvGrid` type, so the stage signature stays `({ table, rows }) => rows`.
|
|
707
|
+
*/
|
|
708
|
+
const replacedRowsByGrid = new WeakMap();
|
|
709
|
+
/** The rows `getRowModel` replaced in the rebuild this pipeline run follows, or null. */
|
|
710
|
+
export function replacedRowsOf(table) {
|
|
711
|
+
return replacedRowsByGrid.get(table) ?? null;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* The sorting stage of the row pipeline: orders the rows by the grid's
|
|
715
|
+
* `sorting` state with the built-in comparators (or the `localSortFns` map
|
|
716
|
+
* handed in), stable, equal keys keeping their data order. A tick that
|
|
717
|
+
* replaces a few rows out of many is repaired incrementally rather than
|
|
718
|
+
* re-sorted (see the note inside). Registered by `rowSortingFeature`; an app
|
|
719
|
+
* only calls this to install its own comparator set.
|
|
720
|
+
*/
|
|
563
721
|
export function createSortedRowModel(localSortFns = sortFns) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
722
|
+
/**
|
|
723
|
+
* What the last full sort produced, kept so a tick can repair it.
|
|
724
|
+
*
|
|
725
|
+
* A live feed replaces a few hundred row objects out of a hundred thousand
|
|
726
|
+
* and hands the grid a new array. Re-sorting all of it costs the same as
|
|
727
|
+
* the first sort did (115-149 ms in the browser for 100k rows, see
|
|
728
|
+
* docs/help/benchmarks.md); the order it would produce differs from the
|
|
729
|
+
* previous one only where the replaced rows land. So the previous output
|
|
730
|
+
* is kept, with its sort keys aligned to it, and when `getRowModel` says
|
|
731
|
+
* which rows were replaced (see `replacedRowsOf`) the stage drops those,
|
|
732
|
+
* sorts the replacements among themselves and merges them back in one
|
|
733
|
+
* linear pass: O(n + k log k) instead of O(n log n) key builds and
|
|
734
|
+
* comparisons.
|
|
735
|
+
*
|
|
736
|
+
* The repair is only attempted when everything else is the same object as
|
|
737
|
+
* last time - the sorting state, the columns, and every input row except
|
|
738
|
+
* the replaced ones - and it produces exactly what the full sort would:
|
|
739
|
+
* equal keys fall back to the data index, which is the order a stable sort
|
|
740
|
+
* over data-ordered input keeps.
|
|
741
|
+
*/
|
|
742
|
+
let cache = null;
|
|
743
|
+
const MISSING_KEY = Symbol('svgrid.sort.missing-key');
|
|
744
|
+
function fullSort(rows, sorting, allColumns) {
|
|
568
745
|
// Resolve every clause ONCE, before sorting.
|
|
569
746
|
//
|
|
570
747
|
// This used to live inside the comparator, so `getAllColumns().find(...)`
|
|
@@ -572,7 +749,6 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
572
749
|
// 1,528,947 array scans, and a three-clause sort made 3,933,751 (measured;
|
|
573
750
|
// `pnpm bench --case=sort-1col`). The comparator is called O(n log n)
|
|
574
751
|
// times, so anything inside it that is not O(1) sets the cost of the sort.
|
|
575
|
-
const allColumns = table.getAllColumns();
|
|
576
752
|
const clauses = [];
|
|
577
753
|
for (const clause of sorting) {
|
|
578
754
|
const column = allColumns.find((col) => col.id === clause.id);
|
|
@@ -599,17 +775,43 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
599
775
|
const n = rows.length;
|
|
600
776
|
let keys = new Array(n);
|
|
601
777
|
let compare;
|
|
778
|
+
let keyOf;
|
|
779
|
+
let autoMode;
|
|
602
780
|
if (comparator === sortFns.number) {
|
|
781
|
+
keyOf = (row) => numberSortKey(row.getCellValueByColumnId(columnId));
|
|
603
782
|
for (let i = 0; i < n; i++)
|
|
604
|
-
keys[i] =
|
|
783
|
+
keys[i] = keyOf(rows[i]);
|
|
605
784
|
compare = compareNumericKeys;
|
|
606
785
|
}
|
|
607
786
|
else if (comparator === sortFns.date) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
787
|
+
keyOf = (row) => dateSortKey(row.getCellValueByColumnId(columnId));
|
|
788
|
+
for (let i = 0; i < n; i++)
|
|
789
|
+
keys[i] = keyOf(rows[i]);
|
|
790
|
+
compare = compareNumericKeys;
|
|
791
|
+
}
|
|
792
|
+
else if (comparator === sortFns.auto && (autoMode = autoSortMode(rows, columnId)) === 'number') {
|
|
793
|
+
// A column with no declared type whose values are all numbers. Sorting
|
|
794
|
+
// it as text put 10 before 2 (#104).
|
|
795
|
+
keyOf = (row) => {
|
|
796
|
+
const value = row.getCellValueByColumnId(columnId);
|
|
797
|
+
// A text value arriving in a tick changes the mode; only a full sort
|
|
798
|
+
// can place it.
|
|
799
|
+
return typeof value === 'number' || isBlankSortValue(value)
|
|
800
|
+
? numberSortKey(value)
|
|
801
|
+
: MISSING_KEY;
|
|
802
|
+
};
|
|
803
|
+
for (let i = 0; i < n; i++)
|
|
804
|
+
keys[i] = keyOf(rows[i]);
|
|
611
805
|
compare = compareNumericKeys;
|
|
612
806
|
}
|
|
807
|
+
else if (autoMode === 'mixed') {
|
|
808
|
+
// Numbers and text in one column. Rare, so no key tricks: the raw
|
|
809
|
+
// values through the pairwise comparator, which orders by kind first.
|
|
810
|
+
keyOf = (row) => row.getCellValueByColumnId(columnId);
|
|
811
|
+
for (let i = 0; i < n; i++)
|
|
812
|
+
keys[i] = keyOf(rows[i]);
|
|
813
|
+
compare = compareAutoValues;
|
|
814
|
+
}
|
|
613
815
|
else if (comparator === sortFns.auto) {
|
|
614
816
|
const strings = new Array(n);
|
|
615
817
|
// Decide whether ranking is worth attempting BEFORE paying for it.
|
|
@@ -632,7 +834,7 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
632
834
|
const sample = new Set();
|
|
633
835
|
let sampled = 0;
|
|
634
836
|
for (let i = 0; i < n; i += stride) {
|
|
635
|
-
sample.add(
|
|
837
|
+
sample.add(autoTextKey(rows[i].getCellValueByColumnId(columnId)));
|
|
636
838
|
sampled++;
|
|
637
839
|
}
|
|
638
840
|
// Only attempt ranking when the sample suggests real repetition.
|
|
@@ -640,7 +842,7 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
640
842
|
distinct = new Set();
|
|
641
843
|
}
|
|
642
844
|
for (let i = 0; i < n; i++) {
|
|
643
|
-
const s =
|
|
845
|
+
const s = autoTextKey(rows[i].getCellValueByColumnId(columnId));
|
|
644
846
|
strings[i] = s;
|
|
645
847
|
if (distinct) {
|
|
646
848
|
distinct.add(s);
|
|
@@ -669,21 +871,35 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
669
871
|
for (let i = 0; i < n; i++)
|
|
670
872
|
keys[i] = rankOf.get(strings[i]);
|
|
671
873
|
compare = compareNumericKeys;
|
|
874
|
+
// A replacement whose text is not among the ranked values has no
|
|
875
|
+
// rank; the repair path treats that as "sort everything again".
|
|
876
|
+
keyOf = (row) => {
|
|
877
|
+
const value = row.getCellValueByColumnId(columnId);
|
|
878
|
+
// A number arriving in a tick makes the column mixed; resort fully.
|
|
879
|
+
if (typeof value === 'number')
|
|
880
|
+
return MISSING_KEY;
|
|
881
|
+
return rankOf.get(autoTextKey(value)) ?? MISSING_KEY;
|
|
882
|
+
};
|
|
672
883
|
}
|
|
673
884
|
else {
|
|
674
885
|
keys = strings;
|
|
675
886
|
compare = compareCollatedKeys;
|
|
887
|
+
keyOf = (row) => {
|
|
888
|
+
const value = row.getCellValueByColumnId(columnId);
|
|
889
|
+
return typeof value === 'number' ? MISSING_KEY : autoTextKey(value);
|
|
890
|
+
};
|
|
676
891
|
}
|
|
677
892
|
}
|
|
678
893
|
else {
|
|
894
|
+
keyOf = (row) => row.getCellValueByColumnId(columnId);
|
|
679
895
|
for (let i = 0; i < n; i++)
|
|
680
|
-
keys[i] = rows[i]
|
|
896
|
+
keys[i] = keyOf(rows[i]);
|
|
681
897
|
compare = comparator;
|
|
682
898
|
}
|
|
683
|
-
clauses.push({ keys, desc: clause.desc, compare });
|
|
899
|
+
clauses.push({ keys, desc: clause.desc, compare, keyOf });
|
|
684
900
|
}
|
|
685
901
|
if (!clauses.length)
|
|
686
|
-
return
|
|
902
|
+
return null;
|
|
687
903
|
// Sort an index array, then materialise. `Array.prototype.sort` is stable,
|
|
688
904
|
// so equal keys keep their original relative order exactly as the previous
|
|
689
905
|
// `[...rows].sort(...)` did.
|
|
@@ -730,18 +946,203 @@ export function createSortedRowModel(localSortFns = sortFns) {
|
|
|
730
946
|
const sorted = new Array(rows.length);
|
|
731
947
|
for (let i = 0; i < order.length; i++)
|
|
732
948
|
sorted[i] = rows[order[i]];
|
|
733
|
-
|
|
949
|
+
// Each row keeps its key(s), so a later tick can place a replacement
|
|
950
|
+
// among the kept rows without reading any of them again. One property
|
|
951
|
+
// write per row; an array per row only when there are several clauses.
|
|
952
|
+
if (clauses.length === 1) {
|
|
953
|
+
const keys = clauses[0].keys;
|
|
954
|
+
for (let i = 0; i < rows.length; i++)
|
|
955
|
+
rows[i][ROW_SORT_KEY] = keys[i];
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
for (let i = 0; i < rows.length; i++) {
|
|
959
|
+
const per = new Array(clauses.length);
|
|
960
|
+
for (let k = 0; k < clauses.length; k++)
|
|
961
|
+
per[k] = clauses[k].keys[i];
|
|
962
|
+
rows[i][ROW_SORT_KEY] = per;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return { sorted, clauses: clauses.map(({ desc, compare, keyOf }) => ({ desc, compare, keyOf })) };
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* The previous output with the replaced rows swapped for their
|
|
969
|
+
* replacements, in sorted position. Null when the input is not the cached
|
|
970
|
+
* input plus replacements, or a replacement has no incremental key.
|
|
971
|
+
*
|
|
972
|
+
* Every row carries the key(s) the last full sort computed for it
|
|
973
|
+
* (ROW_SORT_KEY: the key itself for one clause, an array for several), so
|
|
974
|
+
* the merge moves row references and nothing else. At 100k rows the loop
|
|
975
|
+
* that also copied an aligned key array cost 3.7 ms; this one is the
|
|
976
|
+
* reference copy alone.
|
|
977
|
+
*/
|
|
978
|
+
function repair(rows, replaced) {
|
|
979
|
+
const c = cache;
|
|
980
|
+
if (rows.length !== c.input.length)
|
|
981
|
+
return null;
|
|
982
|
+
// Which rows of THIS stage's input changed. The map may name rows a
|
|
983
|
+
// stage upstream filtered out; only the ones that reach here matter.
|
|
984
|
+
const dropGen = ++sortDropGeneration;
|
|
985
|
+
const added = [];
|
|
986
|
+
for (let i = 0; i < rows.length; i++) {
|
|
987
|
+
const prev = c.input[i];
|
|
988
|
+
const next = rows[i];
|
|
989
|
+
if (next === prev)
|
|
990
|
+
continue;
|
|
991
|
+
if (replaced.get(prev) !== next)
|
|
992
|
+
return null;
|
|
993
|
+
prev[ROW_SORT_DROPPED] = dropGen;
|
|
994
|
+
added.push(next);
|
|
995
|
+
}
|
|
996
|
+
if (!added.length)
|
|
997
|
+
return c.output;
|
|
998
|
+
const clauses = c.clauses;
|
|
999
|
+
const single = clauses.length === 1;
|
|
1000
|
+
for (let j = 0; j < added.length; j++) {
|
|
1001
|
+
const row = added[j];
|
|
1002
|
+
if (single) {
|
|
1003
|
+
const key = clauses[0].keyOf(row);
|
|
1004
|
+
if (key === MISSING_KEY)
|
|
1005
|
+
return null;
|
|
1006
|
+
row[ROW_SORT_KEY] = key;
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
const keys = new Array(clauses.length);
|
|
1010
|
+
for (let k = 0; k < clauses.length; k++) {
|
|
1011
|
+
const key = clauses[k].keyOf(row);
|
|
1012
|
+
if (key === MISSING_KEY)
|
|
1013
|
+
return null;
|
|
1014
|
+
keys[k] = key;
|
|
1015
|
+
}
|
|
1016
|
+
row[ROW_SORT_KEY] = keys;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
// Keys first, then the data index: the tie-break a stable sort over
|
|
1020
|
+
// data-ordered input applies, so the merge lands where a full sort would.
|
|
1021
|
+
const prevOut = c.output;
|
|
1022
|
+
const compareRows = single
|
|
1023
|
+
? (() => {
|
|
1024
|
+
const { compare, desc } = clauses[0];
|
|
1025
|
+
return (a, b) => {
|
|
1026
|
+
const r = compare(a[ROW_SORT_KEY], b[ROW_SORT_KEY]);
|
|
1027
|
+
if (r !== 0)
|
|
1028
|
+
return desc ? -r : r;
|
|
1029
|
+
return a.index - b.index;
|
|
1030
|
+
};
|
|
1031
|
+
})()
|
|
1032
|
+
: (a, b) => {
|
|
1033
|
+
const ka = a[ROW_SORT_KEY];
|
|
1034
|
+
const kb = b[ROW_SORT_KEY];
|
|
1035
|
+
for (let k = 0; k < clauses.length; k++) {
|
|
1036
|
+
const clause = clauses[k];
|
|
1037
|
+
const r = clause.compare(ka[k], kb[k]);
|
|
1038
|
+
if (r !== 0)
|
|
1039
|
+
return clause.desc ? -r : r;
|
|
1040
|
+
}
|
|
1041
|
+
return a.index - b.index;
|
|
1042
|
+
};
|
|
1043
|
+
// Sort the replacements among themselves (k log k)...
|
|
1044
|
+
added.sort(compareRows);
|
|
1045
|
+
// ...find where each one goes in the previous output by binary search
|
|
1046
|
+
// (k log n comparisons, against the dropped rows too - they are skipped
|
|
1047
|
+
// below, which does not move an insertion point)...
|
|
1048
|
+
const n = prevOut.length;
|
|
1049
|
+
const insertAt = new Array(added.length);
|
|
1050
|
+
let lo = 0;
|
|
1051
|
+
for (let j = 0; j < added.length; j++) {
|
|
1052
|
+
// Monotone: the replacements are sorted, so each search starts where
|
|
1053
|
+
// the previous one landed.
|
|
1054
|
+
const row = added[j];
|
|
1055
|
+
let hi = n;
|
|
1056
|
+
while (lo < hi) {
|
|
1057
|
+
const mid = (lo + hi) >>> 1;
|
|
1058
|
+
if (compareRows(row, prevOut[mid]) < 0)
|
|
1059
|
+
hi = mid;
|
|
1060
|
+
else
|
|
1061
|
+
lo = mid + 1;
|
|
1062
|
+
}
|
|
1063
|
+
insertAt[j] = lo;
|
|
1064
|
+
}
|
|
1065
|
+
// ...then one linear pass that only moves references.
|
|
1066
|
+
const out = new Array(rows.length);
|
|
1067
|
+
let w = 0;
|
|
1068
|
+
let j = 0;
|
|
1069
|
+
let nextInsert = insertAt[0] ?? -1;
|
|
1070
|
+
for (let i = 0; i < n; i++) {
|
|
1071
|
+
while (nextInsert === i) {
|
|
1072
|
+
out[w++] = added[j++];
|
|
1073
|
+
nextInsert = j < added.length ? insertAt[j] : -1;
|
|
1074
|
+
}
|
|
1075
|
+
const prev = prevOut[i];
|
|
1076
|
+
if (prev[ROW_SORT_DROPPED] === dropGen)
|
|
1077
|
+
continue;
|
|
1078
|
+
out[w++] = prev;
|
|
1079
|
+
}
|
|
1080
|
+
while (j < added.length)
|
|
1081
|
+
out[w++] = added[j++];
|
|
1082
|
+
return out;
|
|
1083
|
+
}
|
|
1084
|
+
return function sortedRowModelStage({ table, rows }) {
|
|
1085
|
+
const sorting = table.getState().sorting ?? [];
|
|
1086
|
+
if (!sorting.length) {
|
|
1087
|
+
cache = null;
|
|
1088
|
+
return rows;
|
|
1089
|
+
}
|
|
1090
|
+
const columns = table.getAllColumns();
|
|
1091
|
+
if (cache && cache.sorting === sorting && cache.columns === columns) {
|
|
1092
|
+
if (cache.input === rows)
|
|
1093
|
+
return cache.output;
|
|
1094
|
+
const replaced = replacedRowsOf(table);
|
|
1095
|
+
if (replaced) {
|
|
1096
|
+
const repaired = repair(rows, replaced);
|
|
1097
|
+
if (repaired) {
|
|
1098
|
+
cache = { ...cache, input: rows, output: repaired };
|
|
1099
|
+
return repaired;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const full = fullSort(rows, sorting, columns);
|
|
1104
|
+
if (!full) {
|
|
1105
|
+
cache = null;
|
|
1106
|
+
return rows;
|
|
1107
|
+
}
|
|
1108
|
+
cache = { input: rows, output: full.sorted, sorting, columns, clauses: full.clauses };
|
|
1109
|
+
return full.sorted;
|
|
734
1110
|
};
|
|
735
1111
|
}
|
|
736
1112
|
/**
|
|
737
|
-
* Numeric key comparison for the built-in `number
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
1113
|
+
* Numeric key comparison for the built-in `number`, `date` and numeric
|
|
1114
|
+
* `auto` comparators. Keys are never NaN (blanks and unparseable values map to
|
|
1115
|
+
* -Infinity), but two -Infinity keys subtract to NaN, so equal keys return 0
|
|
1116
|
+
* explicitly: the multi-clause sort reads any non-zero result, NaN included,
|
|
1117
|
+
* as decided and would skip the next clause.
|
|
742
1118
|
*/
|
|
743
1119
|
function compareNumericKeys(a, b) {
|
|
744
|
-
return a - b;
|
|
1120
|
+
return a === b ? 0 : a - b;
|
|
1121
|
+
}
|
|
1122
|
+
/** Text key for the `auto` comparator: blanks become '' so they collate first. */
|
|
1123
|
+
function autoTextKey(value) {
|
|
1124
|
+
return isBlankSortValue(value) ? '' : String(value);
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* How the `auto` comparator should treat a column: 'number' when every
|
|
1128
|
+
* non-blank value is a number, 'mixed' when numbers share it with anything
|
|
1129
|
+
* else, 'text' otherwise. One pass, stopping as soon as the answer is known.
|
|
1130
|
+
*/
|
|
1131
|
+
function autoSortMode(rows, columnId) {
|
|
1132
|
+
let numbers = false;
|
|
1133
|
+
let other = false;
|
|
1134
|
+
for (let i = 0; i < rows.length; i++) {
|
|
1135
|
+
const value = rows[i].getCellValueByColumnId(columnId);
|
|
1136
|
+
if (isBlankSortValue(value))
|
|
1137
|
+
continue;
|
|
1138
|
+
if (typeof value === 'number')
|
|
1139
|
+
numbers = true;
|
|
1140
|
+
else
|
|
1141
|
+
other = true;
|
|
1142
|
+
if (numbers && other)
|
|
1143
|
+
return 'mixed';
|
|
1144
|
+
}
|
|
1145
|
+
return numbers ? 'number' : 'text';
|
|
745
1146
|
}
|
|
746
1147
|
/**
|
|
747
1148
|
* Text key comparison for the built-in `auto` comparator.
|
|
@@ -788,6 +1189,8 @@ export function createSvGridCore(options) {
|
|
|
788
1189
|
let cachedBaseRowsInput = null;
|
|
789
1190
|
let cachedBaseRowsColumns = null;
|
|
790
1191
|
let cachedBaseRows = [];
|
|
1192
|
+
/** Old row -> new row for the rebuild the next pipeline run follows; see `replacedRowsOf`. */
|
|
1193
|
+
let replacedRows = null;
|
|
791
1194
|
let cachedRowCtx = null;
|
|
792
1195
|
let cachedRowModel = null;
|
|
793
1196
|
let cachedRowModelBaseRows = null;
|
|
@@ -968,7 +1371,11 @@ export function createSvGridCore(options) {
|
|
|
968
1371
|
},
|
|
969
1372
|
getRowModel() {
|
|
970
1373
|
const columns = grid.getAllColumns();
|
|
971
|
-
|
|
1374
|
+
// Read once. `options.data` is a getter when <SvGrid> drives the engine
|
|
1375
|
+
// (a $state.raw signal), and the rebuild below touched it twice per row:
|
|
1376
|
+
// 200k tracked reads per 100k-row tick, 11 ms of a 65 ms tick.
|
|
1377
|
+
const data = options.data;
|
|
1378
|
+
if (cachedBaseRowsInput !== data || cachedBaseRowsColumns !== columns) {
|
|
972
1379
|
// Same columns as last time: the shared context still describes them,
|
|
973
1380
|
// and a row whose data object sits at the same index can keep its
|
|
974
1381
|
// row object. A row model that streams blocks hands the grid a new
|
|
@@ -978,7 +1385,11 @@ export function createSvGridCore(options) {
|
|
|
978
1385
|
// an app may have changed the object in place before passing a new
|
|
979
1386
|
// array - that is the case the old rebuild covered by accident.
|
|
980
1387
|
const previous = cachedBaseRowsColumns === columns && cachedRowCtx ? cachedBaseRows : null;
|
|
981
|
-
|
|
1388
|
+
// Same length as last time: a replacement, not an add or remove, so
|
|
1389
|
+
// each new row stands in for the old row at its index and the stages
|
|
1390
|
+
// can repair rather than recompute. Anything else is structural.
|
|
1391
|
+
replacedRows = previous && previous.length === data.length ? new Map() : null;
|
|
1392
|
+
cachedBaseRowsInput = data;
|
|
982
1393
|
cachedBaseRowsColumns = columns;
|
|
983
1394
|
// O(1) column-id → index lookup so getCellValueByColumnId doesn't do
|
|
984
1395
|
// a linear `findIndex` on every cell read (was O(rows × cells × cols)).
|
|
@@ -1001,11 +1412,11 @@ export function createSvGridCore(options) {
|
|
|
1001
1412
|
};
|
|
1002
1413
|
cachedRowCtx = rowCtx;
|
|
1003
1414
|
}
|
|
1004
|
-
cachedBaseRows = new Array(
|
|
1415
|
+
cachedBaseRows = new Array(data.length);
|
|
1005
1416
|
const getRowId = options.getRowId;
|
|
1006
1417
|
const m = BASE_ROW_METHODS;
|
|
1007
|
-
for (let index = 0; index <
|
|
1008
|
-
const original =
|
|
1418
|
+
for (let index = 0; index < data.length; index++) {
|
|
1419
|
+
const original = data[index];
|
|
1009
1420
|
const kept = previous?.[index];
|
|
1010
1421
|
if (kept && kept.original === original) {
|
|
1011
1422
|
kept[ROW_VALUES] = null;
|
|
@@ -1013,6 +1424,10 @@ export function createSvGridCore(options) {
|
|
|
1013
1424
|
cachedBaseRows[index] = kept;
|
|
1014
1425
|
continue;
|
|
1015
1426
|
}
|
|
1427
|
+
// Past a quarter of the rows the merge stops being cheaper than a
|
|
1428
|
+
// sort with the ranking pass; let the stages do the full work.
|
|
1429
|
+
if (replacedRows && replacedRows.size * 4 >= data.length)
|
|
1430
|
+
replacedRows = null;
|
|
1016
1431
|
// `_values` and `_cells` stay null until something reads them - a
|
|
1017
1432
|
// 100k-row grid showing twenty rows must not materialise every row's
|
|
1018
1433
|
// values or cell objects to paint.
|
|
@@ -1024,6 +1439,10 @@ export function createSvGridCore(options) {
|
|
|
1024
1439
|
[ROW_CTX]: rowCtx,
|
|
1025
1440
|
[ROW_VALUES]: null,
|
|
1026
1441
|
[ROW_CELLS]: null,
|
|
1442
|
+
[ROW_FILTER_GEN]: 0,
|
|
1443
|
+
[ROW_FILTER_POS]: -1,
|
|
1444
|
+
[ROW_SORT_DROPPED]: 0,
|
|
1445
|
+
[ROW_SORT_KEY]: undefined,
|
|
1027
1446
|
getCanExpand: m.getCanExpand,
|
|
1028
1447
|
getIsExpanded: m.getIsExpanded,
|
|
1029
1448
|
toggleExpanded: m.toggleExpanded,
|
|
@@ -1033,7 +1452,17 @@ export function createSvGridCore(options) {
|
|
|
1033
1452
|
getCellValueByColumnId: m.getCellValueByColumnId,
|
|
1034
1453
|
};
|
|
1035
1454
|
cachedBaseRows[index] = row;
|
|
1455
|
+
if (replacedRows && kept)
|
|
1456
|
+
replacedRows.set(kept, row);
|
|
1036
1457
|
}
|
|
1458
|
+
// A new array with every object the same is the documented way to
|
|
1459
|
+
// say "I changed rows in place, re-read them" (docs/help/cells/
|
|
1460
|
+
// view-refresh.md). Nothing was replaced, so there is nothing to
|
|
1461
|
+
// repair around: the stages recompute from the current values. A
|
|
1462
|
+
// repair assumes the rows it keeps are unchanged in value as well as
|
|
1463
|
+
// identity, which is the immutable-update contract a tick follows.
|
|
1464
|
+
if (replacedRows && replacedRows.size === 0)
|
|
1465
|
+
replacedRows = null;
|
|
1037
1466
|
}
|
|
1038
1467
|
// Only the slices a pipeline stage actually READS belong in this key.
|
|
1039
1468
|
//
|
|
@@ -1075,10 +1504,19 @@ export function createSvGridCore(options) {
|
|
|
1075
1504
|
pipeline.expandedRowModel,
|
|
1076
1505
|
pipeline.paginatedRowModel,
|
|
1077
1506
|
];
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1507
|
+
// The stages read the replacements through `replacedRowsOf` while the
|
|
1508
|
+
// run lasts; a stage that runs outside one sees null.
|
|
1509
|
+
replacedRowsByGrid.set(grid, replacedRows);
|
|
1510
|
+
try {
|
|
1511
|
+
ordered.forEach((fn) => {
|
|
1512
|
+
if (fn)
|
|
1513
|
+
rows = fn({ table: grid, rows });
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
finally {
|
|
1517
|
+
replacedRowsByGrid.set(grid, null);
|
|
1518
|
+
replacedRows = null;
|
|
1519
|
+
}
|
|
1082
1520
|
cachedPipeline = options._rowModels;
|
|
1083
1521
|
cachedSlices = currentSlices;
|
|
1084
1522
|
cachedRowModelBaseRows = cachedBaseRows;
|