@svgrid/grid 3.0.8 → 3.0.9
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/dist/GridMenus.svelte +3 -2
- package/dist/SvGrid.controller.svelte.d.ts +60 -8
- package/dist/SvGrid.controller.svelte.js +475 -110
- package/dist/SvGrid.css +186 -3
- package/dist/SvGrid.svelte +281 -163
- package/dist/SvGrid.types.d.ts +24 -7
- package/dist/cdn/{GridMenus-D15bhpX5.js → GridMenus-B1sipj6y.js} +165 -165
- package/dist/cdn/{GridMenus-B8-Gbfq2.js → GridMenus-DlAzfHLk.js} +201 -201
- package/dist/cdn/server-block-cache-Dj-KPqEQ.js +1289 -0
- package/dist/cdn/{src-Dz625Fhx.js → src-BYsa66I0.js} +9738 -10174
- package/dist/cdn/{src-CleI0g9l.js → src-pA4RroLV.js} +10702 -11138
- package/dist/cdn/svgrid.js +27 -27
- package/dist/cdn/svgrid.svelte-external.js +27 -27
- package/dist/cdn/{validate-GEs4ZPSW.js → validate-XuMe4_KR.js} +2 -2
- package/dist/cell-render.js +25 -3
- package/dist/columns.js +16 -1
- package/dist/conditional-formatting.js +9 -2
- package/dist/core.d.ts +93 -0
- package/dist/core.js +313 -52
- package/dist/filtering/excel-filters.js +41 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/selection.d.ts +2 -2
- package/dist/selection.js +32 -8
- package/dist/server-block-cache.d.ts +7 -32
- package/dist/server-block-cache.js +21 -12
- package/dist/server-data-source.js +3 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/dist/spreadsheet.js +7 -0
- package/dist/validate.js +3 -11
- package/dist/virtualization/column-virtualizer.d.ts +4 -0
- package/dist/virtualization/column-virtualizer.js +2 -0
- package/dist/virtualization/types.d.ts +20 -0
- package/dist/virtualization/virtualizer.js +90 -24
- package/dist/windowed-brand.d.ts +22 -0
- package/dist/windowed-brand.js +60 -0
- package/dist/windowed-data.d.ts +6 -0
- package/dist/windowed-data.js +34 -0
- package/dist/windowed-row-model.d.ts +1 -0
- package/dist/windowed-row-model.js +97 -0
- package/package.json +1 -1
- package/src/GridMenus.svelte +3 -2
- package/src/SvGrid.controller.svelte.ts +469 -114
- package/src/SvGrid.css +186 -3
- package/src/SvGrid.svelte +281 -163
- package/src/SvGrid.types.ts +24 -7
- package/src/cell-render.ts +24 -2
- package/src/columns.ts +16 -1
- package/src/conditional-formatting.ts +6 -4
- package/src/core.sort.test.ts +61 -0
- package/src/core.ts +309 -52
- package/src/counting-cell.test.svelte +11 -0
- package/src/filtering/excel-filters.ts +39 -6
- package/src/headless.reactivity.svelte.test.ts +1 -1
- package/src/index.ts +6 -0
- package/src/new-features.test.ts +24 -0
- package/src/selection.test.ts +14 -0
- package/src/selection.ts +31 -8
- package/src/server-block-cache.ts +27 -10
- package/src/server-data-source.ts +3 -1
- package/src/server.ts +6 -0
- package/src/spreadsheet.ts +6 -0
- package/src/svgrid.behavior.test.ts +29 -0
- package/src/svgrid.column-cell-recycling.test.ts +120 -0
- package/src/svgrid.interaction.test.ts +4 -1
- package/src/svgrid.new-features.wrapper.test.ts +5 -4
- package/src/svgrid.pinned-column-virtualization.test.ts +162 -0
- package/src/svgrid.row-model-prop.svelte.test.ts +15 -3
- package/src/validate.test.ts +4 -4
- package/src/validate.ts +3 -14
- package/src/virtualization/column-virtualizer.ts +6 -0
- package/src/virtualization/types.ts +20 -0
- package/src/virtualization/virtualizer.test.ts +152 -0
- package/src/virtualization/virtualizer.ts +93 -27
- package/src/windowed-brand.ts +67 -0
- package/src/windowed-data.test.ts +105 -0
- package/src/windowed-data.ts +36 -0
- package/src/windowed-row-model.ts +113 -0
- package/dist/cdn/server-block-cache-DIqb3VZn.js +0 -241
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What marks an array as windowed data, and how to tell (windowed-data.ts has
|
|
3
|
+
* the full story). Kept apart from createWindowedData so code that only asks
|
|
4
|
+
* "is this windowed?" - the controller, the core's per-update paths - does not
|
|
5
|
+
* pull in the windowed row model that createWindowedData installs.
|
|
6
|
+
*/
|
|
7
|
+
const WINDOWED = Symbol.for('svgrid.windowedData');
|
|
8
|
+
const isIndexKey = (key) => typeof key === 'string' && key.length > 0 && key.length < 16 && /^(0|[1-9]\d*)$/.test(key);
|
|
9
|
+
/**
|
|
10
|
+
* An array of `length` entries read through `at(i)`: a real `Array` (a Proxy
|
|
11
|
+
* over an empty one) carrying the windowed mark. Each call returns a new
|
|
12
|
+
* array identity, which is how a source tells the grid its rows changed.
|
|
13
|
+
*/
|
|
14
|
+
export function makeWindowedArray(length, at) {
|
|
15
|
+
const source = { length, at };
|
|
16
|
+
const target = [];
|
|
17
|
+
return new Proxy(target, {
|
|
18
|
+
get(t, key, receiver) {
|
|
19
|
+
if (key === WINDOWED)
|
|
20
|
+
return source;
|
|
21
|
+
if (key === 'length')
|
|
22
|
+
return length;
|
|
23
|
+
if (isIndexKey(key)) {
|
|
24
|
+
const i = Number(key);
|
|
25
|
+
return i < length ? at(i) : undefined;
|
|
26
|
+
}
|
|
27
|
+
return Reflect.get(t, key, receiver);
|
|
28
|
+
},
|
|
29
|
+
has(t, key) {
|
|
30
|
+
if (key === WINDOWED)
|
|
31
|
+
return true;
|
|
32
|
+
if (isIndexKey(key))
|
|
33
|
+
return Number(key) < length;
|
|
34
|
+
return Reflect.has(t, key);
|
|
35
|
+
},
|
|
36
|
+
set() {
|
|
37
|
+
// Read-only: a write would land in the empty target and never be seen.
|
|
38
|
+
return false;
|
|
39
|
+
},
|
|
40
|
+
getOwnPropertyDescriptor(t, key) {
|
|
41
|
+
if (isIndexKey(key) && Number(key) < length) {
|
|
42
|
+
return { value: at(Number(key)), writable: false, enumerable: true, configurable: true };
|
|
43
|
+
}
|
|
44
|
+
if (key === 'length')
|
|
45
|
+
return { value: length, writable: true, enumerable: false, configurable: false };
|
|
46
|
+
return Reflect.getOwnPropertyDescriptor(t, key);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** The source behind windowed data, or null for an ordinary array. */
|
|
51
|
+
export function windowedSourceOf(data) {
|
|
52
|
+
if (data === null || typeof data !== 'object')
|
|
53
|
+
return null;
|
|
54
|
+
const source = data[WINDOWED];
|
|
55
|
+
return source ?? null;
|
|
56
|
+
}
|
|
57
|
+
/** True for windowed data. */
|
|
58
|
+
export function isWindowedData(data) {
|
|
59
|
+
return windowedSourceOf(data) !== null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { windowedSourceOf, isWindowedData, type WindowedSource } from './windowed-brand.js';
|
|
2
|
+
/**
|
|
3
|
+
* An array of `length` entries read through `at(i)`. Each call returns a new
|
|
4
|
+
* array identity, which is how a source tells the grid its rows changed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createWindowedData<T>(length: number, at: (index: number) => T | undefined): T[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windowed data: an array the grid can index without anyone having built it.
|
|
3
|
+
*
|
|
4
|
+
* A server row model knows how many rows there are and holds a few blocks of
|
|
5
|
+
* them. Handing the grid a dense array of `rowCount` entries (placeholders
|
|
6
|
+
* for everything not loaded) made every block landing cost O(rowCount): the
|
|
7
|
+
* array itself, a copy in the controller, and a row object per entry in the
|
|
8
|
+
* core. At 10M rows that was ~1 GB of heap and ~1 s per block; at 100M the
|
|
9
|
+
* grid never painted.
|
|
10
|
+
*
|
|
11
|
+
* `createWindowedData(length, at)` returns a real `Array` (a Proxy over one)
|
|
12
|
+
* whose `length` is the row count and whose entries are read through `at(i)`
|
|
13
|
+
* on demand. The grid recognises it (`windowedSourceOf`) and builds row
|
|
14
|
+
* objects only for the indices it actually reads - the rows on screen, plus
|
|
15
|
+
* whatever a user action touches. Iterating one still works (it is an array),
|
|
16
|
+
* but costs O(length), so the grid's per-update paths check for it first.
|
|
17
|
+
*
|
|
18
|
+
* Windowed data is final: the source has already sorted, filtered and grouped
|
|
19
|
+
* it, so the grid's own sort / filter / group stages do not run over it.
|
|
20
|
+
*
|
|
21
|
+
* The row model that reads it (windowed-row-model.ts) is installed by the
|
|
22
|
+
* first call here, so a grid never handed windowed data does not bundle it.
|
|
23
|
+
*/
|
|
24
|
+
import { makeWindowedArray } from './windowed-brand.js';
|
|
25
|
+
import { enableWindowedRowModel } from './windowed-row-model.js';
|
|
26
|
+
export { windowedSourceOf, isWindowedData } from './windowed-brand.js';
|
|
27
|
+
/**
|
|
28
|
+
* An array of `length` entries read through `at(i)`. Each call returns a new
|
|
29
|
+
* array identity, which is how a source tells the grid its rows changed.
|
|
30
|
+
*/
|
|
31
|
+
export function createWindowedData(length, at) {
|
|
32
|
+
enableWindowedRowModel();
|
|
33
|
+
return makeWindowedArray(length, at);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function enableWindowedRowModel(): void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The row model for windowed data (see windowed-data.ts).
|
|
3
|
+
*
|
|
4
|
+
* No build loop and no pipeline stages: `rows` is itself windowed. A row
|
|
5
|
+
* object is made the first time an index is read and kept (up to
|
|
6
|
+
* WINDOWED_ROW_CAP) while its data object is the same one, so scrolling back
|
|
7
|
+
* does not rebuild what is still loaded, and scrolling a 100M-row source end
|
|
8
|
+
* to end keeps a few thousand rows, not 100M.
|
|
9
|
+
*
|
|
10
|
+
* Installed into the core by the first createWindowedData() call rather than
|
|
11
|
+
* imported by core.ts, so a grid that is never handed windowed data does not
|
|
12
|
+
* bundle any of this.
|
|
13
|
+
*/
|
|
14
|
+
import { BASE_ROW_METHODS, ROW_CELLS, ROW_CTX, ROW_FILTER_GEN, ROW_FILTER_POS, ROW_SORT_DROPPED, ROW_SORT_KEY, ROW_VALUES, registerWindowedRowModel, } from './core.js';
|
|
15
|
+
import { makeWindowedArray, windowedSourceOf } from './windowed-brand.js';
|
|
16
|
+
/** The windowed-data version a row's memoised values belong to. */
|
|
17
|
+
const ROW_WINDOW_GEN = Symbol('svgrid.row.windowGen');
|
|
18
|
+
const WINDOWED_ROW_CAP = 20000;
|
|
19
|
+
let installed = false;
|
|
20
|
+
export function enableWindowedRowModel() {
|
|
21
|
+
if (installed)
|
|
22
|
+
return;
|
|
23
|
+
installed = true;
|
|
24
|
+
registerWindowedRowModel((ctx) => {
|
|
25
|
+
// Per grid: the model for the current input, and the row objects built
|
|
26
|
+
// so far, by index.
|
|
27
|
+
let current = null;
|
|
28
|
+
const rows = new Map();
|
|
29
|
+
let generation = 0;
|
|
30
|
+
return (data, columns) => {
|
|
31
|
+
const source = windowedSourceOf(data);
|
|
32
|
+
if (!source)
|
|
33
|
+
return null;
|
|
34
|
+
if (current && current.input === data && current.columns === columns)
|
|
35
|
+
return current.model;
|
|
36
|
+
if (!current || current.columns !== columns)
|
|
37
|
+
rows.clear();
|
|
38
|
+
const rowCtx = ctx.rowCtxFor(columns);
|
|
39
|
+
const getRowId = ctx.getRowId();
|
|
40
|
+
const gen = ++generation;
|
|
41
|
+
const m = BASE_ROW_METHODS;
|
|
42
|
+
const rowAt = (index) => {
|
|
43
|
+
if (index < 0 || index >= source.length)
|
|
44
|
+
return undefined;
|
|
45
|
+
const original = source.at(index);
|
|
46
|
+
if (original === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const kept = rows.get(index);
|
|
49
|
+
if (kept && kept.original === original) {
|
|
50
|
+
// A new data version may follow an in-place edit of the object, as
|
|
51
|
+
// in the dense path: drop the memoised values once per version.
|
|
52
|
+
if (kept[ROW_WINDOW_GEN] !== gen) {
|
|
53
|
+
kept[ROW_VALUES] = null;
|
|
54
|
+
kept[ROW_CELLS] = null;
|
|
55
|
+
kept[ROW_WINDOW_GEN] = gen;
|
|
56
|
+
}
|
|
57
|
+
return kept;
|
|
58
|
+
}
|
|
59
|
+
const row = {
|
|
60
|
+
id: getRowId ? getRowId(original, index) : String(index),
|
|
61
|
+
index,
|
|
62
|
+
original,
|
|
63
|
+
depth: 0,
|
|
64
|
+
[ROW_CTX]: rowCtx,
|
|
65
|
+
[ROW_VALUES]: null,
|
|
66
|
+
[ROW_CELLS]: null,
|
|
67
|
+
[ROW_FILTER_GEN]: 0,
|
|
68
|
+
[ROW_FILTER_POS]: -1,
|
|
69
|
+
[ROW_SORT_DROPPED]: 0,
|
|
70
|
+
[ROW_SORT_KEY]: undefined,
|
|
71
|
+
[ROW_WINDOW_GEN]: gen,
|
|
72
|
+
getCanExpand: m.getCanExpand,
|
|
73
|
+
getIsExpanded: m.getIsExpanded,
|
|
74
|
+
toggleExpanded: m.toggleExpanded,
|
|
75
|
+
getIsSelected: m.getIsSelected,
|
|
76
|
+
toggleSelected: m.toggleSelected,
|
|
77
|
+
getAllCells: m.getAllCells,
|
|
78
|
+
getCellValueByColumnId: m.getCellValueByColumnId,
|
|
79
|
+
};
|
|
80
|
+
rows.set(index, row);
|
|
81
|
+
if (rows.size > WINDOWED_ROW_CAP) {
|
|
82
|
+
// Oldest first (Map keeps insertion order); a quarter at a time.
|
|
83
|
+
let drop = WINDOWED_ROW_CAP / 4;
|
|
84
|
+
for (const key of rows.keys()) {
|
|
85
|
+
if (drop-- <= 0)
|
|
86
|
+
break;
|
|
87
|
+
rows.delete(key);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return row;
|
|
91
|
+
};
|
|
92
|
+
const model = { rows: makeWindowedArray(source.length, rowAt) };
|
|
93
|
+
current = { input: data, columns, model };
|
|
94
|
+
return model;
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svgrid/grid",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"description": "Svelte 5-native data grid and data table. Headless-first engine with virtual scrolling for 100k+ rows, Excel-style filtering, inline editing, grouping, tree data, pivot, and server-side data. Drop-in <SvGrid> component. TypeScript, SSR-ready.",
|
|
5
5
|
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
6
|
"license": "MIT",
|
package/src/GridMenus.svelte
CHANGED
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
const unpinColumn = $derived(ctrl.unpinColumn);
|
|
50
50
|
const sortDirectionByColumn = $derived(ctrl.sortDirectionByColumn);
|
|
51
51
|
const groupingColumns = $derived(ctrl.groupingColumns);
|
|
52
|
-
const columnVirtualizationEnabled = $derived(ctrl.columnVirtualizationEnabled);
|
|
53
52
|
const updateFilterOperator = $derived(ctrl.updateFilterOperator);
|
|
54
53
|
const updateFilterMenuValue = $derived(ctrl.updateFilterMenuValue);
|
|
55
54
|
const updateFilterMenuValueTo = $derived(ctrl.updateFilterMenuValueTo);
|
|
@@ -644,7 +643,9 @@
|
|
|
644
643
|
<span class="sv-grid-header-icon">{@render icon("x")}</span> Remove sort
|
|
645
644
|
</button>
|
|
646
645
|
{/if}
|
|
647
|
-
|
|
646
|
+
<!-- Pinning works with column virtualization on: the pinned columns render
|
|
647
|
+
as their own runs either side of the virtual window. -->
|
|
648
|
+
{#if menuColumnId}
|
|
648
649
|
{@const menuPinSide = isColumnPinned(menuColumnId)}
|
|
649
650
|
<div class="sv-grid-menu-sep"></div>
|
|
650
651
|
<button
|