@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
|
@@ -151,6 +151,41 @@ function scanInTokens(value) {
|
|
|
151
151
|
// pull in SvGrid.svelte, which is a runtime cost (and a bundler hazard) for
|
|
152
152
|
// anything that only wants operator semantics.
|
|
153
153
|
export { ALL_FILTER_OPERATORS, SET_OPERATOR_IDS, VALUELESS_OPERATOR_IDS, RANGE_OPERATOR_IDS, isSetOperator, isValuelessOperator, isRangeOperator, } from './filter-operator-catalogue.js';
|
|
154
|
+
/**
|
|
155
|
+
* A text test, remembered per distinct cell text.
|
|
156
|
+
*
|
|
157
|
+
* The text operators fold every cell (lowercase, and for non-ASCII text NFD
|
|
158
|
+
* plus diacritic stripping) before comparing, and a column the user filters
|
|
159
|
+
* is usually a category - region, status, owner - with a handful of distinct
|
|
160
|
+
* values over every row. Folding each row again was most of a 1M-row filter
|
|
161
|
+
* (417 ms). The answer depends only on the cell's text, so it is kept per
|
|
162
|
+
* text. A column that turns out mostly unique (more than half of the first
|
|
163
|
+
* 2,048 lookups missed) stops remembering and tests directly, and the memo
|
|
164
|
+
* never grows past MEMO_CAP entries.
|
|
165
|
+
*/
|
|
166
|
+
const MEMO_CAP = 16384;
|
|
167
|
+
function memoText(test) {
|
|
168
|
+
let memo = new Map();
|
|
169
|
+
let lookups = 0;
|
|
170
|
+
let hits = 0;
|
|
171
|
+
return (cellValue) => {
|
|
172
|
+
const s = typeof cellValue === 'string' ? cellValue : String(cellValue ?? '');
|
|
173
|
+
if (memo === null)
|
|
174
|
+
return test(s);
|
|
175
|
+
lookups++;
|
|
176
|
+
const known = memo.get(s);
|
|
177
|
+
if (known !== undefined) {
|
|
178
|
+
hits++;
|
|
179
|
+
return known;
|
|
180
|
+
}
|
|
181
|
+
const result = test(s);
|
|
182
|
+
if (memo.size < MEMO_CAP)
|
|
183
|
+
memo.set(s, result);
|
|
184
|
+
if (lookups >= 2048 && hits * 2 < lookups)
|
|
185
|
+
memo = null;
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
154
189
|
/**
|
|
155
190
|
* Compile a filter once, then test many rows against it.
|
|
156
191
|
*
|
|
@@ -168,23 +203,23 @@ export function compileExcelFilter(filter, options) {
|
|
|
168
203
|
const needle = fold(filter.value);
|
|
169
204
|
switch (filter.operator) {
|
|
170
205
|
case 'contains':
|
|
171
|
-
return (
|
|
206
|
+
return memoText((s) => fold(s).includes(needle));
|
|
172
207
|
case 'notContains':
|
|
173
208
|
// An empty needle is no constraint (mirrors `contains` returning true),
|
|
174
209
|
// so nothing is excluded until the user types something.
|
|
175
210
|
if (needle === '')
|
|
176
211
|
return () => true;
|
|
177
|
-
return (
|
|
212
|
+
return memoText((s) => !fold(s).includes(needle));
|
|
178
213
|
case 'equals':
|
|
179
|
-
return (
|
|
214
|
+
return memoText((s) => fold(s) === needle);
|
|
180
215
|
case 'notEquals':
|
|
181
216
|
if (needle === '')
|
|
182
217
|
return () => true;
|
|
183
|
-
return (
|
|
218
|
+
return memoText((s) => fold(s) !== needle);
|
|
184
219
|
case 'startsWith':
|
|
185
|
-
return (
|
|
220
|
+
return memoText((s) => fold(s).startsWith(needle));
|
|
186
221
|
case 'endsWith':
|
|
187
|
-
return (
|
|
222
|
+
return memoText((s) => fold(s).endsWith(needle));
|
|
188
223
|
case 'regex': {
|
|
189
224
|
// Case-insensitive by default (matches the accent/case-folded feel of
|
|
190
225
|
// the other text operators). An invalid pattern matches nothing rather
|
package/dist/headless.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, createTreeRowModel, createSvGridCore, flattenTreeData, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, type Cell, type CellContext, type CellSpanParams, type ValueParserParams, type EditorContext, type CellData, type ActiveCellState, type CellFormatConfig, type CellFormatter, type Column, type ColumnDef, type ColumnDefTemplate, type ColumnFilter, type ColumnFiltersState, type ExpandedState, type GridColumnDef, type GridColumns, type GroupAggregator, type GroupingState, type Header, type HeaderContext, type HeaderGroup, type PaginationState, type Row, type RowData, type RowModel, type RowModelFactory, type RowSelectionState, type SortingState, type Store, type SvGrid as SvGridInstance, type SvGridOptions, type TableFeatures, type TreeRowModelOptions, type FlattenTreeOptions, type Updater, } from './core.js';
|
|
27
27
|
export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte.js';
|
|
28
28
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
29
|
-
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
29
|
+
export { subscribeGrid, subscribeSvGrid } from './subscribe.svelte.js';
|
|
30
30
|
export { createVirtualizer } from './virtualization/virtualizer.js';
|
|
31
31
|
export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
|
|
32
32
|
export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
|
package/dist/headless.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, createTreeRowModel, createSvGridCore, flattenTreeData, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, } from './core.js';
|
|
27
27
|
export { createGrid, createSvGrid } from './createGrid.svelte.js';
|
|
28
28
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
29
|
-
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
29
|
+
export { subscribeGrid, subscribeSvGrid } from './subscribe.svelte.js';
|
|
30
30
|
// The virtualizers are pure window arithmetic (scroll offset in, visible
|
|
31
31
|
// range out) with no DOM of their own, and a custom renderer over 50k rows
|
|
32
32
|
// needs one. They were only on the barrel, so the Headless demos imported
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { coerceExportDate, formatValueForExport, projectGridRows, toExcelNumFmt,
|
|
|
3
3
|
export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, createTreeRowModel, flattenTreeData, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, type Cell, type CellContext, type CellSpanParams, type ValueParserParams, type EditorContext, type CellData, type ActiveCellState, type CellFormatConfig, type CellFormatter, type Column, type ColumnDef, type ColumnDefTemplate, type GridColumnDef, type GridColumns, type GroupAggregator, type Header, type HeaderContext, type HeaderGroup, type Row, type RowData, type SortingState, type SvGrid as SvGridInstance, type SvGridOptions, type TableFeatures, type TreeRowModelOptions, type FlattenTreeOptions, type Updater, } from './core.js';
|
|
4
4
|
export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte.js';
|
|
5
5
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
6
|
-
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
6
|
+
export { subscribeGrid, subscribeSvGrid } from './subscribe.svelte.js';
|
|
7
7
|
export { default as SvGrid } from './SvGrid.svelte';
|
|
8
8
|
export type { BoardConfig, BoardLane, BoardCardMoveEvent, BoardCardCommitEvent, BoardLayout, BoardSubtask, BoardComment, BoardMenuContext, BoardLaneMenuContext, BoardDrawerConfig } from './SvGrid.types.js';
|
|
9
9
|
export { registerBoardView, getBoardView, hasBoardView } from './board-view.svelte.js';
|
|
@@ -215,6 +215,7 @@ export { createHyperFormulaSheet, type HyperFormulaSheet, type HyperFormulaSheet
|
|
|
215
215
|
export { createCollaboration, broadcastChannelTransport, type CollabUser, type CollabCell, type CollabPresence, type CollabMessage, type CollabTransport, type Collaboration, } from './collaboration.js';
|
|
216
216
|
export { createServerDataSource, type ServerDataSource, type ServerSelectionRule, type ServerRequest, type ServerResult, type ServerController, type ServerControllerOptions, type ServerState, type ServerSortModel, type ServerFilterModel, type ServerAggFn, type ServerAggregation, type ServerGroupRow, type ServerLeafRow, type ServerMoreRow, type ServerFooterRow, type ServerSkeletonRow, type ServerGrandTotalRow, type ServerPlaceholderRow, type ServerDisplayRow, type ServerDetailRow, } from './server-data-source.js';
|
|
217
217
|
export { createBlockCache, createRowPlaceholder, rowPlaceholderState, type BlockCache, type BlockCacheOptions, type BlockCacheState, type BlockFetchResult, type BlockState, } from './server-block-cache.js';
|
|
218
|
+
export { createWindowedData, windowedSourceOf, isWindowedData, type WindowedSource, } from './windowed-data.js';
|
|
218
219
|
export { toServerFilterColumns, type GridRowModel, type GridFilterState, type GridRowModelSort, } from './row-model.js';
|
|
219
220
|
export { createNamedViews, memoryViews, localStorageViews, attachAutoSavedView, type SavedView, type ViewStorage, type NamedViews, type AutoSavedViewOptions, } from './named-views.js';
|
|
220
221
|
export { resolveCellFormat, computeColumnStat, lerpColor, rgba, contrastText, formatNeedsStats, formatsNeedingStats, type ConditionalFormat, type ConditionalFormatSpec, type ColorScaleFormat, type ColorScaleStop, type ScaleBounds, type DataBarFormat, type IconSetFormat, type RuleFormat, type ResolvedCellFormat, } from './conditional-formatting.js';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { coerceExportDate, formatValueForExport, projectGridRows, toExcelNumFmt,
|
|
|
3
3
|
export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, createTreeRowModel, flattenTreeData, applyGroupAggregate, filterFns, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFns, tableFeatures, } from './core.js';
|
|
4
4
|
export { createGrid, createSvGrid } from './createGrid.svelte.js';
|
|
5
5
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
6
|
-
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
6
|
+
export { subscribeGrid, subscribeSvGrid } from './subscribe.svelte.js';
|
|
7
7
|
export { default as SvGrid } from './SvGrid.svelte';
|
|
8
8
|
export { registerBoardView, getBoardView, hasBoardView } from './board-view.svelte.js';
|
|
9
9
|
// Built-in AI helpers (free): natural-language filter, smart-fill, summarize,
|
|
@@ -255,6 +255,7 @@ export { createHyperFormulaSheet, } from './hyperformula-adapter.js';
|
|
|
255
255
|
export { createCollaboration, broadcastChannelTransport, } from './collaboration.js';
|
|
256
256
|
export { createServerDataSource, } from './server-data-source.js';
|
|
257
257
|
export { createBlockCache, createRowPlaceholder, rowPlaceholderState, } from './server-block-cache.js';
|
|
258
|
+
export { createWindowedData, windowedSourceOf, isWindowedData, } from './windowed-data.js';
|
|
258
259
|
export { toServerFilterColumns, } from './row-model.js';
|
|
259
260
|
export { createNamedViews, memoryViews, localStorageViews, attachAutoSavedView, } from './named-views.js';
|
|
260
261
|
export { resolveCellFormat, computeColumnStat, lerpColor, rgba, contrastText, formatNeedsStats, formatsNeedingStats, } from './conditional-formatting.js';
|
|
@@ -189,7 +189,11 @@ export function createKeyboard(ctx) {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
if (column?.columnDef.editorType === "checkbox") {
|
|
192
|
-
|
|
192
|
+
// Same rule as a click: `editable: false` (or an editable callback
|
|
193
|
+
// saying no) keeps the checkbox read-only.
|
|
194
|
+
if (ctx.isCellEditableAt?.(current.rowIndex, current.colIndex) ?? true) {
|
|
195
|
+
ctx.toggleBooleanCell(current.rowIndex, current.colIndex);
|
|
196
|
+
}
|
|
193
197
|
return;
|
|
194
198
|
}
|
|
195
199
|
ctx.onCellDoubleClick(current.rowIndex, current.colIndex);
|
package/dist/selection.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type RowData, type TableFeatures } from "./index.js";
|
|
2
2
|
import "./sv-grid-scrollbar.js";
|
|
3
3
|
export declare function createSelection<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(ctx: any): {
|
|
4
|
-
isRowSelected: (rowId: string) => any;
|
|
5
|
-
toggleRowSelectionById: (rowId: string) => void;
|
|
4
|
+
isRowSelected: (rowId: string, original?: unknown) => any;
|
|
5
|
+
toggleRowSelectionById: (rowId: string, original?: unknown) => void;
|
|
6
6
|
toggleSelectAllRows: () => void;
|
|
7
7
|
setSelectAllRows: (select: boolean) => void;
|
|
8
8
|
setActiveCell: (rowIndex: number, colIndex: number) => void;
|
package/dist/selection.js
CHANGED
|
@@ -6,21 +6,33 @@ import "./sv-grid-scrollbar.js";
|
|
|
6
6
|
import { getColumnBaseValue, isGroupRow, } from "./cell-values.js";
|
|
7
7
|
import { expandRectToMerges, originOf, endOf } from "./merges.js";
|
|
8
8
|
export function createSelection(ctx) {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* A selection model needs the row's data. The body passes the row it is
|
|
11
|
+
* drawing as `original`; without it the row is looked up by id, a scan of
|
|
12
|
+
* every row - on a server model's windowed million rows, once per drawn
|
|
13
|
+
* row, that scan held the page for seconds after a sort.
|
|
14
|
+
*/
|
|
15
|
+
function rowOriginal(rowId) {
|
|
16
|
+
const row = ctx.allRows.find((r) => r.id === rowId);
|
|
17
|
+
return row ? { found: true, original: row.original } : { found: false };
|
|
18
|
+
}
|
|
19
|
+
function isRowSelected(rowId, original) {
|
|
10
20
|
const model = ctx.props.rowSelectionModel;
|
|
11
21
|
if (model) {
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
if (original !== undefined)
|
|
23
|
+
return model.isSelected(rowId, original);
|
|
24
|
+
const hit = rowOriginal(rowId);
|
|
25
|
+
return hit.found ? model.isSelected(rowId, hit.original) : false;
|
|
14
26
|
}
|
|
15
27
|
return Boolean(ctx.rowSelectionState[rowId]);
|
|
16
28
|
}
|
|
17
|
-
function toggleRowSelectionById(rowId) {
|
|
29
|
+
function toggleRowSelectionById(rowId, original) {
|
|
18
30
|
const model = ctx.props.rowSelectionModel;
|
|
19
31
|
if (model) {
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
32
|
+
const hit = original !== undefined ? { found: true, original } : rowOriginal(rowId);
|
|
33
|
+
if (!hit.found)
|
|
22
34
|
return;
|
|
23
|
-
model.toggle(rowId,
|
|
35
|
+
model.toggle(rowId, hit.original, !model.isSelected(rowId, hit.original));
|
|
24
36
|
return;
|
|
25
37
|
}
|
|
26
38
|
ctx.grid.setRowSelection((prev) => ({ ...prev, [rowId]: !prev[rowId] }));
|
|
@@ -446,8 +458,20 @@ export function createSelection(ctx) {
|
|
|
446
458
|
}
|
|
447
459
|
/** Look up a column by id without depending on `buildApi`'s private
|
|
448
460
|
* closure (those helpers don't exist at this scope). */
|
|
461
|
+
// id -> column, rebuilt when the column list changes. Called per header
|
|
462
|
+
// cell as columns scroll into view, so a linear `find` made every scroll
|
|
463
|
+
// frame cost more the more columns the grid has.
|
|
464
|
+
let columnsById = new Map();
|
|
465
|
+
let columnsByIdFor = null;
|
|
449
466
|
function findColumnById(columnId) {
|
|
450
|
-
|
|
467
|
+
const all = ctx.allColumns;
|
|
468
|
+
if (all !== columnsByIdFor) {
|
|
469
|
+
columnsById = new Map();
|
|
470
|
+
for (const c of all)
|
|
471
|
+
columnsById.set(c.id, c);
|
|
472
|
+
columnsByIdFor = all;
|
|
473
|
+
}
|
|
474
|
+
return columnsById.get(columnId);
|
|
451
475
|
}
|
|
452
476
|
function onCellPointerDown(rowIndex, colIndex, event) {
|
|
453
477
|
if (event.button !== 0)
|
|
@@ -622,10 +646,20 @@ export function createSelection(ctx) {
|
|
|
622
646
|
columnId: column.id,
|
|
623
647
|
row: row.original,
|
|
624
648
|
});
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
649
|
+
// A declared checkbox editor toggles on click unless the cell is not
|
|
650
|
+
// editable. A value that only HAPPENS to be boolean is drawn as a read-only
|
|
651
|
+
// checkbox, so it toggles only when inline editing is on - it used to flip
|
|
652
|
+
// and write the row on any click, in grids with editing switched off. A
|
|
653
|
+
// column with its own `cell` renderer draws (and handles) the value itself.
|
|
654
|
+
const declaredCheckbox = column.columnDef.editorType === "checkbox";
|
|
655
|
+
const inferredCheckbox = !declaredCheckbox &&
|
|
656
|
+
typeof cellValue === "boolean" &&
|
|
657
|
+
column.columnDef.cell == null;
|
|
658
|
+
if (declaredCheckbox || inferredCheckbox) {
|
|
659
|
+
const editable = ctx.isCellEditableAt?.(rowIndex, colIndex) ?? true;
|
|
660
|
+
if (editable && (declaredCheckbox || ctx.editingEnabled)) {
|
|
661
|
+
ctx.toggleBooleanCell(rowIndex, colIndex);
|
|
662
|
+
}
|
|
629
663
|
setActiveCell(rowIndex, colIndex);
|
|
630
664
|
setSelection(rowIndex, colIndex);
|
|
631
665
|
ctx.editingCell = null;
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block cache for server-backed rows - the engine behind infinite scrolling.
|
|
3
|
-
*
|
|
4
|
-
* Paging asks for "page 3". Infinite scrolling asks a different question: the
|
|
5
|
-
* user is looking at rows 4,000 to 4,020 of a table with a million rows, so
|
|
6
|
-
* fetch the block that covers them, keep a bounded number of recently-seen
|
|
7
|
-
* blocks, and render something sensible for every row nobody has fetched yet.
|
|
8
|
-
* That is all this module does, in plain TypeScript with no Svelte and no DOM.
|
|
9
|
-
*
|
|
10
|
-
* The shape of the problem (and most of the vocabulary) is the same one every
|
|
11
|
-
* grid with a server row model solves, so the options here are deliberately
|
|
12
|
-
* recognisable: `blockSize`, `maxBlocksInCache`, `maxConcurrentRequests`,
|
|
13
|
-
* `blockLoadDebounceMs`, `initialRowCount`, `overflowRows`.
|
|
14
|
-
*
|
|
15
|
-
* Three things it takes seriously:
|
|
16
|
-
*
|
|
17
|
-
* - **Unloaded rows still have to render.** `rows()` always returns a dense
|
|
18
|
-
* array, with a shared sentinel object in every slot that has no data. The
|
|
19
|
-
* grid recognises those (see {@link rowPlaceholderState}) and draws a
|
|
20
|
-
* skeleton or a retry affordance instead of an empty row, so the scrollbar
|
|
21
|
-
* never lies and scrolling never leaves a hole.
|
|
22
|
-
* - **The row count may be unknown.** A backend that cannot cheaply count
|
|
23
|
-
* says so (omit `rowCount`, or send `-1`) and the list grows by a block at
|
|
24
|
-
* a time until a short block proves where the end is.
|
|
25
|
-
* - **Requests are not free.** In-flight blocks are deduplicated, at most
|
|
26
|
-
* `maxConcurrentRequests` are open at once, a fast scroll debounces rather
|
|
27
|
-
* than firing a request per frame, and blocks abandoned by eviction or
|
|
28
|
-
* purge are aborted.
|
|
29
|
-
*
|
|
30
|
-
* Enterprise's server row model runs one of these per group level; the free
|
|
31
|
-
* flat controller runs exactly one.
|
|
32
|
-
*/
|
|
33
1
|
/**
|
|
34
2
|
* The mark that says "this row is not data yet".
|
|
35
3
|
*
|
|
@@ -155,6 +123,13 @@ export type BlockCache<TData> = {
|
|
|
155
123
|
setViewport(startIndex: number, endIndex: number): void;
|
|
156
124
|
/** The dense row array to hand the grid. Placeholders fill unloaded slots. */
|
|
157
125
|
rows(): ReadonlyArray<TData>;
|
|
126
|
+
/**
|
|
127
|
+
* The same rows as windowed data (see windowed-data.ts): `length` is the
|
|
128
|
+
* row count and each entry is read through `getRow` when the grid asks for
|
|
129
|
+
* it, so handing it over costs O(1) whatever the count. A new array per
|
|
130
|
+
* change, like `rows()`.
|
|
131
|
+
*/
|
|
132
|
+
windowedRows(): ReadonlyArray<TData>;
|
|
158
133
|
/** One row, without building the array. Returns a placeholder when unloaded. */
|
|
159
134
|
getRow(index: number): TData | typeof LOADING_ROW | typeof FAILED_ROW;
|
|
160
135
|
rowCount(): number | null;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* Enterprise's server row model runs one of these per group level; the free
|
|
31
31
|
* flat controller runs exactly one.
|
|
32
32
|
*/
|
|
33
|
+
import { createWindowedData } from './windowed-data.js';
|
|
33
34
|
/**
|
|
34
35
|
* The mark that says "this row is not data yet".
|
|
35
36
|
*
|
|
@@ -99,6 +100,7 @@ export function createBlockCache(options) {
|
|
|
99
100
|
let debounceTimer = null;
|
|
100
101
|
let emitScheduled = false;
|
|
101
102
|
let rowsCache = null;
|
|
103
|
+
let windowedCache = null;
|
|
102
104
|
const blockOf = (rowIndex) => Math.floor(rowIndex / blockSize);
|
|
103
105
|
const startOf = (blockIndex) => blockIndex * blockSize;
|
|
104
106
|
/**
|
|
@@ -156,7 +158,7 @@ export function createBlockCache(options) {
|
|
|
156
158
|
* mutations notifies once. Every mutating path ends here.
|
|
157
159
|
*/
|
|
158
160
|
function emit() {
|
|
159
|
-
rowsCache = null; // rebuilt lazily, on the next read
|
|
161
|
+
rowsCache = windowedCache = null; // rebuilt lazily, on the next read
|
|
160
162
|
if (disposed || emitScheduled)
|
|
161
163
|
return;
|
|
162
164
|
emitScheduled = true;
|
|
@@ -281,6 +283,15 @@ export function createBlockCache(options) {
|
|
|
281
283
|
ensureBlock(i);
|
|
282
284
|
}
|
|
283
285
|
}
|
|
286
|
+
function rowAt(index) {
|
|
287
|
+
const block = blocks.get(blockOf(index));
|
|
288
|
+
if (!block)
|
|
289
|
+
return LOADING_ROW;
|
|
290
|
+
if (block.status === 'failed')
|
|
291
|
+
return FAILED_ROW;
|
|
292
|
+
const row = block.rows[index - startOf(blockOf(index))];
|
|
293
|
+
return row ?? LOADING_ROW;
|
|
294
|
+
}
|
|
284
295
|
function abortAll() {
|
|
285
296
|
for (const block of blocks.values())
|
|
286
297
|
block.controller?.abort();
|
|
@@ -330,15 +341,13 @@ export function createBlockCache(options) {
|
|
|
330
341
|
rowsCache = out;
|
|
331
342
|
return out;
|
|
332
343
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return FAILED_ROW;
|
|
339
|
-
const row = block.rows[index - startOf(blockOf(index))];
|
|
340
|
-
return row ?? LOADING_ROW;
|
|
344
|
+
windowedRows() {
|
|
345
|
+
if (windowedCache)
|
|
346
|
+
return windowedCache;
|
|
347
|
+
windowedCache = createWindowedData(currentLength(), (i) => rowAt(i));
|
|
348
|
+
return windowedCache;
|
|
341
349
|
},
|
|
350
|
+
getRow: rowAt,
|
|
342
351
|
rowCount: () => (countKnown ? count : null),
|
|
343
352
|
lastRowKnown: () => countKnown,
|
|
344
353
|
retryFailed() {
|
|
@@ -376,7 +385,7 @@ export function createBlockCache(options) {
|
|
|
376
385
|
blocks.clear();
|
|
377
386
|
count = null;
|
|
378
387
|
countKnown = false;
|
|
379
|
-
rowsCache = null;
|
|
388
|
+
rowsCache = windowedCache = null;
|
|
380
389
|
fetchViewport();
|
|
381
390
|
emit();
|
|
382
391
|
},
|
|
@@ -524,7 +533,7 @@ export function createBlockCache(options) {
|
|
|
524
533
|
controller: null,
|
|
525
534
|
});
|
|
526
535
|
}
|
|
527
|
-
rowsCache = null;
|
|
536
|
+
rowsCache = windowedCache = null;
|
|
528
537
|
}
|
|
529
538
|
/** Forget every block from `fromBlock` on, aborting any in flight. */
|
|
530
539
|
function dropLoadedFrom(fromBlock) {
|
|
@@ -534,6 +543,6 @@ export function createBlockCache(options) {
|
|
|
534
543
|
block.controller?.abort();
|
|
535
544
|
blocks.delete(index);
|
|
536
545
|
}
|
|
537
|
-
rowsCache = null;
|
|
546
|
+
rowsCache = windowedCache = null;
|
|
538
547
|
}
|
|
539
548
|
}
|
|
@@ -53,7 +53,9 @@ export function createServerDataSource(source, options) {
|
|
|
53
53
|
function emitFromCache() {
|
|
54
54
|
if (!cache || disposed)
|
|
55
55
|
return;
|
|
56
|
-
|
|
56
|
+
// Windowed: the grid reads only the rows it shows, so a block landing
|
|
57
|
+
// costs the same at 100M rows as at 10K.
|
|
58
|
+
const rows = cache.windowedRows();
|
|
57
59
|
state.rows = rows;
|
|
58
60
|
state.rowCount = cache.rowCount();
|
|
59
61
|
state.lastRowKnown = cache.lastRowKnown();
|
package/dist/server.d.ts
CHANGED
|
@@ -10,4 +10,5 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { createServerDataSource, type ServerDataSource, type ServerSelectionRule, type ServerRequest, type ServerResult, type ServerController, type ServerControllerOptions, type ServerState, type ServerSortModel, type ServerFilterModel, type ServerAggFn, type ServerAggregation, type ServerGroupRow, type ServerLeafRow, type ServerMoreRow, type ServerFooterRow, type ServerSkeletonRow, type ServerGrandTotalRow, type ServerPlaceholderRow, type ServerDisplayRow, type ServerDetailRow, } from './server-data-source.js';
|
|
12
12
|
export { createBlockCache, createRowPlaceholder, rowPlaceholderState, type BlockCache, type BlockCacheOptions, type BlockCacheState, type BlockFetchResult, type BlockState, } from './server-block-cache.js';
|
|
13
|
+
export { createWindowedData, windowedSourceOf, isWindowedData, type WindowedSource, } from './windowed-data.js';
|
|
13
14
|
export { toServerFilterColumns, type GridRowModel, type GridFilterState, type GridRowModelSort, } from './row-model.js';
|
package/dist/server.js
CHANGED
|
@@ -10,4 +10,5 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { createServerDataSource, } from './server-data-source.js';
|
|
12
12
|
export { createBlockCache, createRowPlaceholder, rowPlaceholderState, } from './server-block-cache.js';
|
|
13
|
+
export { createWindowedData, windowedSourceOf, isWindowedData, } from './windowed-data.js';
|
|
13
14
|
export { toServerFilterColumns, } from './row-model.js';
|
package/dist/spreadsheet.js
CHANGED
|
@@ -88,6 +88,13 @@ const OVERLAY_CLASS = 'sv-cell-border-overlay';
|
|
|
88
88
|
* every relevant DOM mutation inside the grid, and whenever the action
|
|
89
89
|
* options update. */
|
|
90
90
|
function apply(root, opts) {
|
|
91
|
+
// Merges here are colspan / rowspan on the rendered cells, which only the
|
|
92
|
+
// table algorithm honours: keep this grid's rows in table layout (the
|
|
93
|
+
// flex-row rules in SvGrid.css skip a table marked this way).
|
|
94
|
+
for (const table of root.querySelectorAll('table.sv-grid-table')) {
|
|
95
|
+
if (table.getAttribute('data-row-layout') !== 'table')
|
|
96
|
+
table.setAttribute('data-row-layout', 'table');
|
|
97
|
+
}
|
|
91
98
|
// ---- 1. Clean up anything the LAST run added -----------------------
|
|
92
99
|
for (const td of root.querySelectorAll(`td[${MARK}]`)) {
|
|
93
100
|
td.removeAttribute('colspan');
|
|
@@ -16,7 +16,11 @@ const shallowCompare = (a, b) => {
|
|
|
16
16
|
* comparison. Call from `<script>` so the selector is contextually typed.
|
|
17
17
|
*/
|
|
18
18
|
export function subscribeGrid(grid, selector) {
|
|
19
|
-
|
|
19
|
+
// $state, not a plain variable: core.ts keeps a deliberately framework-free
|
|
20
|
+
// store, so this Svelte adapter is where reactivity belongs. Without it a
|
|
21
|
+
// `$derived` reading .current never re-runs and the docs' headless examples
|
|
22
|
+
// render their seeded state forever.
|
|
23
|
+
let current = $state(selector(grid.store.state));
|
|
20
24
|
grid.store.subscribe(() => {
|
|
21
25
|
const next = selector(grid.store.state);
|
|
22
26
|
if (!shallowCompare(current, next))
|
package/dist/validate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { rowPlaceholderState } from './server-block-cache.js';
|
|
2
2
|
/** How many rows to sample when deciding whether a `field` exists. */
|
|
3
3
|
const FIELD_SAMPLE_ROWS = 10;
|
|
4
|
+
/** How many rows to look through for that sample, placeholders included. */
|
|
5
|
+
const FIELD_SCAN_ROWS = 1000;
|
|
4
6
|
const DOCS = 'https://svgrid.com/docs/getting-started/3-data-and-columns/';
|
|
5
7
|
/** Every leaf column, flattened through group columns. */
|
|
6
8
|
function leaves(columns, out = []) {
|
|
@@ -23,10 +25,15 @@ export function validateGridConfig(input) {
|
|
|
23
25
|
// no data keys at all and say nothing about the columns.
|
|
24
26
|
// Stops at the sample size: this runs on every data change, and a filter
|
|
25
27
|
// over all the rows first was O(n) per tick on a live feed.
|
|
28
|
+
// The scan is bounded too: a row model scrolled to the middle of a million
|
|
29
|
+
// rows, or just purged by a sort, hands over a list that is placeholders
|
|
30
|
+
// up to the loaded block, and walking all of them for ten real rows was
|
|
31
|
+
// O(n) per block landing. Finding none means nothing to judge by.
|
|
26
32
|
const sample = [];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
const data = input.data ?? [];
|
|
34
|
+
const scan = Math.min(data.length, FIELD_SCAN_ROWS);
|
|
35
|
+
for (let i = 0; i < scan && sample.length < FIELD_SAMPLE_ROWS; i += 1) {
|
|
36
|
+
const row = data[i];
|
|
30
37
|
if (!rowPlaceholderState(row))
|
|
31
38
|
sample.push(row);
|
|
32
39
|
}
|
|
@@ -148,17 +155,9 @@ export function validateGridConfig(input) {
|
|
|
148
155
|
'so summary bars will under-report. Remove `treeData` and set ' +
|
|
149
156
|
'`gantt.parentField` instead.');
|
|
150
157
|
}
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// which means the natural way to write this silently does nothing.
|
|
155
|
-
const pinned = (input.initialColumnPinning?.left?.length ?? 0) +
|
|
156
|
-
(input.initialColumnPinning?.right?.length ?? 0);
|
|
157
|
-
if (pinned > 0 && input.columnVirtualization !== false) {
|
|
158
|
-
messages.push('[svgrid] `initialColumnPinning` is set while column virtualization is on ' +
|
|
159
|
-
'(its default), so the pinned columns will not stick - the virtualizer ' +
|
|
160
|
-
'recycles column nodes. Add `columnVirtualization={false}`.');
|
|
161
|
-
}
|
|
158
|
+
// (7. was "pinning versus column virtualization". Pinned columns now render
|
|
159
|
+
// as their own runs beside the virtual window, so the pair is supported and
|
|
160
|
+
// there is nothing to warn about.)
|
|
162
161
|
// ---- 8. Server-mode contracts left half-wired -----------------------------
|
|
163
162
|
// Each of these makes the grid hand control to the consumer. Miss the other
|
|
164
163
|
// half and the feature looks broken rather than unconfigured.
|
|
@@ -4,6 +4,10 @@ export declare function createColumnVirtualizer(input: {
|
|
|
4
4
|
viewportWidth: number;
|
|
5
5
|
scrollOffset?: number;
|
|
6
6
|
overscan?: number;
|
|
7
|
+
/** Columns kept behind the scroll direction; defaults to `overscan`. */
|
|
8
|
+
overscanBehind?: number;
|
|
9
|
+
/** Fewest columns left ahead of a scroll before the window moves. */
|
|
10
|
+
overscanMin?: number;
|
|
7
11
|
/** Either a uniform size (number) or a per-column size function. */
|
|
8
12
|
estimateSize?: number | ColumnSizeEstimator;
|
|
9
13
|
}): {
|
|
@@ -6,6 +6,8 @@ export function createColumnVirtualizer(input) {
|
|
|
6
6
|
viewportHeight: input.viewportWidth,
|
|
7
7
|
scrollOffset: input.scrollOffset ?? 0,
|
|
8
8
|
overscan: input.overscan ?? 4,
|
|
9
|
+
overscanBehind: input.overscanBehind,
|
|
10
|
+
overscanMin: input.overscanMin,
|
|
9
11
|
});
|
|
10
12
|
return {
|
|
11
13
|
...virtualizer,
|
|
@@ -15,8 +15,28 @@ export type VirtualizerOptions = {
|
|
|
15
15
|
*/
|
|
16
16
|
estimateSize: number | ((index: number) => number);
|
|
17
17
|
overscan?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Items kept on the side the scroll is moving away from (`overscan` is kept
|
|
20
|
+
* ahead of it). Defaults to `overscan`, a symmetric window. Before the
|
|
21
|
+
* first scroll there is no direction and both sides get `overscan`.
|
|
22
|
+
*/
|
|
23
|
+
overscanBehind?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Keep the rendered window while a scroll leaves at least this many items
|
|
26
|
+
* rendered ahead of it, and move the window only when the scroll runs past
|
|
27
|
+
* that: the window is then rebuilt with the full `overscan` ahead, so it
|
|
28
|
+
* moves `overscan - overscanMin + 1` items at a time instead of one. Each
|
|
29
|
+
* move costs about the same whether one item enters or several, so fewer,
|
|
30
|
+
* larger moves are less work, and the scroll frames in between change
|
|
31
|
+
* nothing. A window built at rest, or after a jump to items outside the
|
|
32
|
+
* rendered window, carries `overscanMin` instead of `overscan`. Undefined
|
|
33
|
+
* moves the window on every item boundary, always with `overscan`.
|
|
34
|
+
*/
|
|
35
|
+
overscanMin?: number;
|
|
18
36
|
viewportHeight: number;
|
|
19
37
|
scrollOffset?: number;
|
|
38
|
+
/** Internal: the sign of the last scroll movement (-1, 0, 1). */
|
|
39
|
+
scrollDirection?: number;
|
|
20
40
|
};
|
|
21
41
|
export type VirtualizerState = {
|
|
22
42
|
items: Array<VirtualItem>;
|