@svgrid/grid 3.0.7 → 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/README.md +1 -1
- package/dist/GridMenus.svelte +3 -2
- package/dist/SvGrid.controller.svelte.d.ts +60 -8
- package/dist/SvGrid.controller.svelte.js +490 -114
- package/dist/SvGrid.css +186 -3
- package/dist/SvGrid.svelte +285 -164
- package/dist/SvGrid.types.d.ts +24 -7
- package/dist/cdn/{GridMenus-CcwU8I4t.js → GridMenus-B1sipj6y.js} +165 -165
- package/dist/cdn/{GridMenus-BgUS-W3o.js → GridMenus-DlAzfHLk.js} +201 -201
- package/dist/cdn/server-block-cache-Dj-KPqEQ.js +1289 -0
- package/dist/cdn/{src-kE9B98r6.js → src-BYsa66I0.js} +9747 -10178
- package/dist/cdn/{src-25JmJ_7g.js → src-pA4RroLV.js} +10709 -11140
- package/dist/cdn/svgrid.js +27 -27
- package/dist/cdn/svgrid.svelte-external.js +27 -27
- package/dist/cdn/validate-XuMe4_KR.js +80 -0
- 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 -47
- package/dist/filtering/excel-filters.js +41 -6
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/keyboard-handlers.js +5 -1
- package/dist/selection.d.ts +2 -2
- package/dist/selection.js +46 -12
- 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/{subscribe.js → subscribe.svelte.js} +5 -1
- package/dist/validate.js +13 -14
- 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 +483 -122
- package/src/SvGrid.css +186 -3
- package/src/SvGrid.svelte +285 -164
- 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 -49
- package/src/counting-cell.test.svelte +11 -0
- package/src/filtering/excel-filters.ts +39 -6
- package/src/headless.reactivity.svelte.test.ts +89 -0
- package/src/headless.ts +1 -1
- package/src/index.ts +7 -1
- package/src/keyboard-handlers.ts +5 -1
- package/src/new-features.test.ts +24 -0
- package/src/selection.test.ts +51 -0
- package/src/selection.ts +47 -14
- 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/{subscribe.test.ts → subscribe.svelte.test.ts} +1 -1
- package/src/{subscribe.ts → subscribe.svelte.ts} +5 -1
- package/src/svgrid.behavior.test.ts +58 -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 +20 -4
- package/src/validate.ts +13 -16
- 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
- package/dist/cdn/validate-AZoD8BoR.js +0 -80
- /package/dist/{subscribe.d.ts → subscribe.svelte.d.ts} +0 -0
package/src/SvGrid.types.ts
CHANGED
|
@@ -1851,13 +1851,17 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
1851
1851
|
* `virtualization`. **On by default**, which is what makes a 100-column grid
|
|
1852
1852
|
* scroll smoothly.
|
|
1853
1853
|
*
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1854
|
+
* Pinned columns stay rendered while it is on: the pinned-left and
|
|
1855
|
+
* pinned-right columns are drawn as their own runs either side of the
|
|
1856
|
+
* virtual window, so a wide grid with pinned edges still renders only the
|
|
1857
|
+
* columns in view plus the pinned ones.
|
|
1856
1858
|
*/
|
|
1857
1859
|
columnVirtualization?: boolean;
|
|
1858
1860
|
/**
|
|
1859
|
-
*
|
|
1860
|
-
*
|
|
1861
|
+
* Columns kept rendered beyond the viewport when `columnVirtualization`
|
|
1862
|
+
* is on: this many ahead of a horizontal scroll at all times, one behind
|
|
1863
|
+
* it. The rendered columns move three at a time, so right after a move up
|
|
1864
|
+
* to `columnOverscan + 2` are rendered ahead. Defaults to 3.
|
|
1861
1865
|
*/
|
|
1862
1866
|
columnOverscan?: number;
|
|
1863
1867
|
/**
|
|
@@ -1870,9 +1874,7 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
1870
1874
|
* column id (matches `ColumnDef.field` when no explicit id is set).
|
|
1871
1875
|
* The internal pinning state is seeded once at mount; user-driven
|
|
1872
1876
|
* pinning via the column menu still works and overrides this default.
|
|
1873
|
-
*
|
|
1874
|
-
* UI (sticky positioning can't co-exist with column virtualization
|
|
1875
|
-
* since the virtualizer recycles DOM nodes).
|
|
1877
|
+
* Works with column virtualization on (the default).
|
|
1876
1878
|
*/
|
|
1877
1879
|
initialColumnPinning?: {
|
|
1878
1880
|
left?: ReadonlyArray<string>;
|
|
@@ -2302,6 +2304,12 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
2302
2304
|
* triangles by threshold), and `rule` (apply a style when a predicate
|
|
2303
2305
|
* matches). Scope a format to specific columns with `columns: [...]`,
|
|
2304
2306
|
* or omit it to apply to every column. Later entries win on conflict.
|
|
2307
|
+
*
|
|
2308
|
+
* `colorScale` and `dataBar` apply to data rows only: group, footer and
|
|
2309
|
+
* grand-total rows carry subtotals on a different scale, so they keep the
|
|
2310
|
+
* `rule` and `iconSet` formats and drop the scaled ones. On a server row
|
|
2311
|
+
* model, give scaled formats `minValue` / `maxValue`; the grid does not
|
|
2312
|
+
* scan rows it has not loaded for a range.
|
|
2305
2313
|
*/
|
|
2306
2314
|
conditionalFormats?: ReadonlyArray<ConditionalFormat<TData>>;
|
|
2307
2315
|
/**
|
|
@@ -2600,6 +2608,15 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
|
|
|
2600
2608
|
* through the column's `editable`.
|
|
2601
2609
|
*/
|
|
2602
2610
|
mergedCells?: ReadonlyArray<{ rowIndex: number; colIndex: number; rowSpan: number; colSpan: number }>;
|
|
2611
|
+
/**
|
|
2612
|
+
* How rows are laid out. `'flex'` (the default) draws each row as a line
|
|
2613
|
+
* of fixed-width cells, so adding or removing cells in one row - what a
|
|
2614
|
+
* horizontal scroll does every frame - lays out that row only. `'table'`
|
|
2615
|
+
* uses the HTML table algorithm, which re-lays every row on such a change
|
|
2616
|
+
* but can draw a merged cell spanning rows; the grid switches to it on its
|
|
2617
|
+
* own when `mergedCells` has one.
|
|
2618
|
+
*/
|
|
2619
|
+
rowLayout?: "flex" | "table";
|
|
2603
2620
|
/**
|
|
2604
2621
|
* Rows to pin to the BOTTOM of the grid - rendered below the regular
|
|
2605
2622
|
* rows and sticky-positioned (sticks to the bottom of the viewport
|
package/src/cell-render.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "./SvGrid.helpers";
|
|
30
30
|
import {
|
|
31
31
|
getPinnedCellValue,
|
|
32
|
+
isGroupRow,
|
|
32
33
|
} from "./cell-values";
|
|
33
34
|
import { parseEditorValue } from "./editors/cell-editors";
|
|
34
35
|
|
|
@@ -36,13 +37,34 @@ export function createCellRender<
|
|
|
36
37
|
TFeatures extends TableFeatures = TableFeatures,
|
|
37
38
|
TData extends RowData = RowData,
|
|
38
39
|
>(ctx: any) {
|
|
40
|
+
// `colorScale` and `dataBar` are scaled to the data rows. A group, footer or
|
|
41
|
+
// grand-total row carries a subtotal on a different scale (a sum of a million
|
|
42
|
+
// rows against a range drawn from one), so those rows keep only the rule and
|
|
43
|
+
// icon formats. Filtered once per formats array.
|
|
44
|
+
let rowLevelFormatsFor: unknown = null;
|
|
45
|
+
let rowLevelFormats: ReadonlyArray<any> = [];
|
|
46
|
+
function aggregateRowFormats(formats: ReadonlyArray<any>): ReadonlyArray<any> {
|
|
47
|
+
if (formats !== rowLevelFormatsFor) {
|
|
48
|
+
rowLevelFormats = formats.filter((f) => f.type !== "colorScale" && f.type !== "dataBar");
|
|
49
|
+
rowLevelFormatsFor = formats;
|
|
50
|
+
}
|
|
51
|
+
return rowLevelFormats;
|
|
52
|
+
}
|
|
53
|
+
function isAggregateRow(row: Row<TData>): boolean {
|
|
54
|
+
const kind = (row.original as { __group?: { kind?: string } } | null)?.__group?.kind;
|
|
55
|
+
if (kind) return kind !== "leaf" && kind !== "detail";
|
|
56
|
+
return isGroupRow(row);
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
function cellConditionalFormat(
|
|
40
60
|
row: Row<TData>,
|
|
41
61
|
column: Column<TData>,
|
|
42
62
|
value: unknown,
|
|
43
63
|
): ResolvedCellFormat | null {
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
64
|
+
const all = ctx.props.conditionalFormats;
|
|
65
|
+
if (!all?.length) return null;
|
|
66
|
+
const formats = isAggregateRow(row) ? aggregateRowFormats(all) : all;
|
|
67
|
+
if (!formats.length) return null;
|
|
46
68
|
return resolveCellFormat(
|
|
47
69
|
value,
|
|
48
70
|
row.original,
|
package/src/columns.ts
CHANGED
|
@@ -157,10 +157,25 @@ export function createColumns<
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// id -> column for the current column list. Width math asks for every
|
|
161
|
+
// column's width in loops, so a linear `find` per call made those loops
|
|
162
|
+
// quadratic: ~1.2 s of a 5,000-column mount. Rebuilt when the list changes.
|
|
163
|
+
let columnsById: Map<string, any> = new Map();
|
|
164
|
+
let columnsByIdFor: unknown = null;
|
|
165
|
+
function columnById(columnId: string) {
|
|
166
|
+
const all = ctx.grid.getAllColumns();
|
|
167
|
+
if (all !== columnsByIdFor) {
|
|
168
|
+
columnsById = new Map();
|
|
169
|
+
for (const c of all as any[]) columnsById.set(c.id, c);
|
|
170
|
+
columnsByIdFor = all;
|
|
171
|
+
}
|
|
172
|
+
return columnsById.get(columnId);
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
/** Width before fitColumns scaling - what the columnDef/user actually set. */
|
|
161
176
|
function getColumnBaseWidth(columnId: string) {
|
|
162
177
|
if (ctx.columnWidths[columnId] !== undefined) return ctx.columnWidths[columnId];
|
|
163
|
-
const column =
|
|
178
|
+
const column = columnById(columnId);
|
|
164
179
|
if (column?.columnDef.width !== undefined) return column.columnDef.width;
|
|
165
180
|
return ctx.props.columnWidth ?? 140;
|
|
166
181
|
}
|
|
@@ -386,10 +386,12 @@ export function resolveCellFormat<TData = unknown>(
|
|
|
386
386
|
* on the same row (`compareColumn`) - those read the row, not column stats.
|
|
387
387
|
*/
|
|
388
388
|
export function formatNeedsStats(f: ConditionalFormat<any>): boolean {
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
389
|
+
if (f.type !== 'colorScale' && f.type !== 'dataBar') return false
|
|
390
|
+
if ('compareColumn' in f && f.compareColumn) return false
|
|
391
|
+
// Both ends fixed in absolute units: the column's range is never read, so
|
|
392
|
+
// there is nothing to scan (and on a server-backed grid no way to scan it).
|
|
393
|
+
if (f.minValue != null && f.maxValue != null && (f.bounds ?? 'absolute') === 'absolute') return false
|
|
394
|
+
return true
|
|
393
395
|
}
|
|
394
396
|
|
|
395
397
|
export function formatsNeedingStats(
|
package/src/core.sort.test.ts
CHANGED
|
@@ -201,6 +201,67 @@ describe('createSortedRowModel - equivalence with the original comparator', () =
|
|
|
201
201
|
}
|
|
202
202
|
})
|
|
203
203
|
|
|
204
|
+
// From 20,000 rows a one-column numeric sort orders indices with a radix
|
|
205
|
+
// sort over the float bits (and ranked text with a counting sort) instead
|
|
206
|
+
// of a comparator. Same order, ties included.
|
|
207
|
+
describe('large numeric columns (the radix path)', () => {
|
|
208
|
+
const values: unknown[] = [0, -0, -1, 1, 2.5, -2.5, 1e9, -1e9, null, undefined, '', 'n/a', Infinity, -Infinity, 7, 7, 7, 0.1, -0.1, '12']
|
|
209
|
+
const big = (count: number): Row[] =>
|
|
210
|
+
Array.from({ length: count }, (_, i) => ({ text: 't', num: values[(i * 31) % values.length], when: null, tie: i }))
|
|
211
|
+
for (const desc of [false, true]) {
|
|
212
|
+
it(`matches the comparator sort, desc=${desc}`, () => {
|
|
213
|
+
const rows = big(25_000)
|
|
214
|
+
const sorting: SortingState = [{ id: 'num', desc }]
|
|
215
|
+
expectSameOrder(actualSort(rows, COLUMNS, sorting), referenceSort(rows, COLUMNS, sorting))
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
it('matches on date keys', () => {
|
|
219
|
+
const dates = ['2020-01-01', '2021-05-05', null, 'junk', 1600000000000, '1999-12-31', '2020-01-01']
|
|
220
|
+
const rows: Row[] = Array.from({ length: 25_000 }, (_, i) => ({ text: 't', num: 0, when: dates[(i * 13) % dates.length], tie: i }))
|
|
221
|
+
for (const desc of [false, true]) {
|
|
222
|
+
const sorting: SortingState = [{ id: 'when', desc }]
|
|
223
|
+
expectSameOrder(actualSort(rows, COLUMNS, sorting), referenceSort(rows, COLUMNS, sorting))
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// Past 2,048 rows the choice is made from a sample. A 256-row sample used
|
|
229
|
+
// to miss repetition at this shape - every value a dozen times over, but
|
|
230
|
+
// spread thin enough that 256 rows hardly repeat - so a 1M-row column of
|
|
231
|
+
// 80k values was collated row by row (20M collator calls, 1.75 s).
|
|
232
|
+
describe('sampled columns (more rows than the sample)', () => {
|
|
233
|
+
const tag = (i: number) => `w${(i * 7919) % 2500}`
|
|
234
|
+
function countCollations<T>(run: () => T): { result: T; calls: number } {
|
|
235
|
+
const original = String.prototype.localeCompare
|
|
236
|
+
let calls = 0
|
|
237
|
+
String.prototype.localeCompare = function (this: string, ...args: Parameters<typeof original>) {
|
|
238
|
+
calls++
|
|
239
|
+
return original.apply(this, args)
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return { result: run(), calls }
|
|
243
|
+
} finally {
|
|
244
|
+
String.prototype.localeCompare = original
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
it('ranks a column of repeated values, and the order is unchanged', () => {
|
|
249
|
+
// 30,000 rows over 2,500 values, each about 12 times.
|
|
250
|
+
const rows: Row[] = Array.from({ length: 30_000 }, (_, i) => ({ text: tag(i), num: i, when: null, tie: 'x' }))
|
|
251
|
+
const sorting: SortingState = [{ id: 'text', desc: false }]
|
|
252
|
+
const { result, calls } = countCollations(() => actualSort(rows, COLUMNS, sorting))
|
|
253
|
+
expectSameOrder(result, referenceSort(rows, COLUMNS, sorting))
|
|
254
|
+
// Ranked: the 2,500 distinct values are collated, not the 30,000 rows.
|
|
255
|
+
expect(calls).toBeLessThan(rows.length)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('collates a mostly-unique column row by row, in the same order', () => {
|
|
259
|
+
const rows: Row[] = Array.from({ length: 30_000 }, (_, i) => ({ text: `u${(i * 7919) % 29_000}`, num: i, when: null, tie: 'x' }))
|
|
260
|
+
const sorting: SortingState = [{ id: 'text', desc: true }]
|
|
261
|
+
expectSameOrder(actualSort(rows, COLUMNS, sorting), referenceSort(rows, COLUMNS, sorting))
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
204
265
|
it('agrees with the direct path on the same data at both cardinalities', () => {
|
|
205
266
|
// 22 rows over 11 values takes the rank path (11 * 2 <= 22); 20 rows over
|
|
206
267
|
// the same 11 values does not (11 * 2 > 20). Same inputs, same order.
|