@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/editing.js
CHANGED
|
@@ -133,6 +133,22 @@ export function createEditing(ctx) {
|
|
|
133
133
|
// belongs to the same Ctrl+Z as the value.
|
|
134
134
|
runHistoryGroup(ctx, () => commitEditingCell());
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* True when a column with `rejectInvalid` refuses `value`: its `validate`
|
|
138
|
+
* hook returns `false` or a message. Without the flag nothing is refused.
|
|
139
|
+
*/
|
|
140
|
+
function editRejected(column, value, rowData) {
|
|
141
|
+
const def = column?.columnDef;
|
|
142
|
+
if (!def?.rejectInvalid || typeof def.validate !== "function")
|
|
143
|
+
return false;
|
|
144
|
+
const verdict = def.validate({
|
|
145
|
+
value,
|
|
146
|
+
row: rowData,
|
|
147
|
+
rowIndex: ctx.internalData.indexOf(rowData),
|
|
148
|
+
column,
|
|
149
|
+
});
|
|
150
|
+
return verdict === false || (typeof verdict === "string" && verdict.length > 0);
|
|
151
|
+
}
|
|
136
152
|
function commitEditingCell() {
|
|
137
153
|
if (!ctx.editingCell)
|
|
138
154
|
return;
|
|
@@ -165,6 +181,12 @@ export function createEditing(ctx) {
|
|
|
165
181
|
columnId: ctx.editingCell.columnId,
|
|
166
182
|
});
|
|
167
183
|
}
|
|
184
|
+
// A refused edit closes the editor and leaves the row, the overlay,
|
|
185
|
+
// the history and the consumer untouched.
|
|
186
|
+
if (editRejected(column, finalValue, rowData)) {
|
|
187
|
+
ctx.editingCell = null;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
168
190
|
rowData[column.columnDef.field] = finalValue;
|
|
169
191
|
}
|
|
170
192
|
const key = getCellKey(ctx.editingCell.rowId, ctx.editingCell.columnId);
|
|
@@ -764,6 +786,8 @@ export function createEditing(ctx) {
|
|
|
764
786
|
columnId: column.id,
|
|
765
787
|
})
|
|
766
788
|
: parsed;
|
|
789
|
+
if (editRejected(column, finalValue, row.original))
|
|
790
|
+
continue;
|
|
767
791
|
if (oldValue !== finalValue) {
|
|
768
792
|
row.original[field] = finalValue;
|
|
769
793
|
const key = getCellKey(fr.rowId, column.id);
|
package/dist/export-format.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export { computeColumnStat, contrastText, formatsNeedingStats, resolveCellFormat
|
|
|
21
21
|
/** Best-effort coercion of a cell value to a Date. Accepts Date, epoch ms
|
|
22
22
|
* numbers, and parseable date strings. Returns null when it isn't a date. */
|
|
23
23
|
export declare function coerceExportDate(value: unknown): Date | null;
|
|
24
|
+
/**
|
|
25
|
+
* A Date as ISO 8601 in LOCAL time with its offset, e.g.
|
|
26
|
+
* `2024-01-15T00:00:00.000+02:00`. Text exports used `toISOString()`, which
|
|
27
|
+
* is UTC: local midnight on the 15th came out as `2024-01-14T22:00:00.000Z`
|
|
28
|
+
* east of UTC, and anything reading the date part got the wrong day (#103).
|
|
29
|
+
* Same instant, but the date part is the one the grid showed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function toLocalIsoString(d: Date): string;
|
|
24
32
|
/**
|
|
25
33
|
* Format a raw cell value to its on-screen display string using the column's
|
|
26
34
|
* `format` config. Mirrors the grid's own numeric / currency / percent / date
|
package/dist/export-format.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatNumericWithConfig, getDateFormatter, resolveDatePattern, } from './cell-formatting.js';
|
|
1
|
+
import { formatNumericWithConfig, getDateFormatter, parseDateValue, resolveDatePattern, } from './cell-formatting.js';
|
|
2
2
|
// Re-export the pure conditional-formatting engine here too, so the
|
|
3
3
|
// Svelte-free `@svgrid/grid/format` subpath carries it for @svgrid/enterprise's
|
|
4
4
|
// export code (which must not pull in SvGrid.svelte).
|
|
@@ -6,17 +6,24 @@ export { computeColumnStat, contrastText, formatsNeedingStats, resolveCellFormat
|
|
|
6
6
|
/** Best-effort coercion of a cell value to a Date. Accepts Date, epoch ms
|
|
7
7
|
* numbers, and parseable date strings. Returns null when it isn't a date. */
|
|
8
8
|
export function coerceExportDate(value) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
return parseDateValue(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A Date as ISO 8601 in LOCAL time with its offset, e.g.
|
|
13
|
+
* `2024-01-15T00:00:00.000+02:00`. Text exports used `toISOString()`, which
|
|
14
|
+
* is UTC: local midnight on the 15th came out as `2024-01-14T22:00:00.000Z`
|
|
15
|
+
* east of UTC, and anything reading the date part got the wrong day (#103).
|
|
16
|
+
* Same instant, but the date part is the one the grid showed.
|
|
17
|
+
*/
|
|
18
|
+
export function toLocalIsoString(d) {
|
|
19
|
+
const pad = (n, w = 2) => String(Math.abs(n)).padStart(w, '0');
|
|
20
|
+
const offset = -d.getTimezoneOffset();
|
|
21
|
+
const sign = offset < 0 ? '-' : '+';
|
|
22
|
+
const year = d.getFullYear();
|
|
23
|
+
return ((year < 0 ? '-' + pad(year, 6) : pad(year, 4)) +
|
|
24
|
+
`-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
25
|
+
`T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}` +
|
|
26
|
+
`${sign}${pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)}`);
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* Format a raw cell value to its on-screen display string using the column's
|
|
@@ -190,7 +197,7 @@ function csvCell(value, delimiter) {
|
|
|
190
197
|
const s = value == null
|
|
191
198
|
? ''
|
|
192
199
|
: value instanceof Date
|
|
193
|
-
? value
|
|
200
|
+
? toLocalIsoString(value)
|
|
194
201
|
: String(value);
|
|
195
202
|
if (s === '')
|
|
196
203
|
return s;
|
|
@@ -231,7 +238,7 @@ const HTML_ESCAPE = {
|
|
|
231
238
|
"'": ''',
|
|
232
239
|
};
|
|
233
240
|
function htmlCell(value) {
|
|
234
|
-
const s = value == null ? '' : value instanceof Date ? value
|
|
241
|
+
const s = value == null ? '' : value instanceof Date ? toLocalIsoString(value) : String(value);
|
|
235
242
|
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPE[c]);
|
|
236
243
|
}
|
|
237
244
|
/**
|
|
@@ -311,7 +318,7 @@ export async function serializeJson(rows, fields, opts = {}) {
|
|
|
311
318
|
}
|
|
312
319
|
/** Escape a Markdown table cell: pipes and newlines would break the row. */
|
|
313
320
|
function mdCell(value) {
|
|
314
|
-
const s = value == null ? '' : value instanceof Date ? value
|
|
321
|
+
const s = value == null ? '' : value instanceof Date ? toLocalIsoString(value) : String(value);
|
|
315
322
|
return s.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/\r?\n/g, '<br>');
|
|
316
323
|
}
|
|
317
324
|
/**
|
|
@@ -351,7 +358,7 @@ const XML_ESCAPE = {
|
|
|
351
358
|
"'": ''',
|
|
352
359
|
};
|
|
353
360
|
function xmlText(value) {
|
|
354
|
-
const s = value == null ? '' : value instanceof Date ? value
|
|
361
|
+
const s = value == null ? '' : value instanceof Date ? toLocalIsoString(value) : String(value);
|
|
355
362
|
// Stripping control characters is the point: XML 1.0 forbids them outright,
|
|
356
363
|
// so a stray \x00 from a data source would produce an unopenable file.
|
|
357
364
|
// eslint-disable-next-line no-control-regex
|
package/dist/grid-messages.d.ts
CHANGED
|
@@ -76,6 +76,10 @@ export type GridChromeMessages = {
|
|
|
76
76
|
chartRange: string;
|
|
77
77
|
rowLoadFailed: string;
|
|
78
78
|
rowLoadRetry: string;
|
|
79
|
+
expandGroup: string;
|
|
80
|
+
collapseGroup: string;
|
|
81
|
+
openDetail: string;
|
|
82
|
+
closeDetail: string;
|
|
79
83
|
pivotUpsellTitle: string;
|
|
80
84
|
pivotUpsellBody: string;
|
|
81
85
|
ganttUpsellTitle: string;
|
package/dist/grid-messages.js
CHANGED
|
@@ -86,6 +86,10 @@ export const defaultGridMessages = {
|
|
|
86
86
|
chartRange: 'Chart selected range',
|
|
87
87
|
rowLoadFailed: 'Could not load these rows.',
|
|
88
88
|
rowLoadRetry: 'Retry',
|
|
89
|
+
expandGroup: 'Expand group',
|
|
90
|
+
collapseGroup: 'Collapse group',
|
|
91
|
+
openDetail: 'Open details',
|
|
92
|
+
closeDetail: 'Close details',
|
|
89
93
|
pivotUpsellTitle: 'Pivot mode',
|
|
90
94
|
pivotUpsellBody: 'Pivot mode is an Enterprise feature. Install @svgrid/enterprise and call enablePivot() to use it.',
|
|
91
95
|
ganttUpsellTitle: 'Gantt view',
|
package/dist/headless.d.ts
CHANGED
|
@@ -27,3 +27,8 @@ export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, crea
|
|
|
27
27
|
export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte.js';
|
|
28
28
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
29
29
|
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
30
|
+
export { createVirtualizer } from './virtualization/virtualizer.js';
|
|
31
|
+
export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
|
|
32
|
+
export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
|
|
33
|
+
export type { VirtualItem, VirtualizerOptions, VirtualizerState } from './virtualization/types.js';
|
|
34
|
+
export { getGridCellDomId, getGridCellA11yProps, getGridHeaderA11yProps, getGridRootA11yProps, getGridRowA11yProps, type GridCellA11yInput, type GridColumnA11yInput, type GridSortDirection, } from './a11y.js';
|
package/dist/headless.js
CHANGED
|
@@ -27,3 +27,15 @@ export { columnFilteringFeature, columnGroupingFeature, createCoreRowModel, crea
|
|
|
27
27
|
export { createGrid, createSvGrid } from './createGrid.svelte.js';
|
|
28
28
|
export { createGridState, createSvGridState } from './createGridState.svelte.js';
|
|
29
29
|
export { subscribeGrid, subscribeSvGrid } from './subscribe.js';
|
|
30
|
+
// The virtualizers are pure window arithmetic (scroll offset in, visible
|
|
31
|
+
// range out) with no DOM of their own, and a custom renderer over 50k rows
|
|
32
|
+
// needs one. They were only on the barrel, so the Headless demos imported
|
|
33
|
+
// the barrel to reach them and paid for the whole thing.
|
|
34
|
+
export { createVirtualizer } from './virtualization/virtualizer.js';
|
|
35
|
+
export { createSvelteVirtualizer } from './virtualization/svelte-virtualizer.svelte.js';
|
|
36
|
+
export { createColumnVirtualizer } from './virtualization/column-virtualizer.js';
|
|
37
|
+
// The WAI-ARIA attribute factories: plain objects to spread onto your own
|
|
38
|
+
// markup (`role`, `aria-sort`, `aria-rowindex`, ...). No DOM is touched here
|
|
39
|
+
// either; a custom renderer that wants keyboard navigation reads
|
|
40
|
+
// `activeCell` from the store and spreads these.
|
|
41
|
+
export { getGridCellDomId, getGridCellA11yProps, getGridHeaderA11yProps, getGridRootA11yProps, getGridRowA11yProps, } from './a11y.js';
|
package/dist/history.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export type HistoryStep = {
|
|
|
38
38
|
undo: () => void;
|
|
39
39
|
redo: () => void;
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* The consumer's own mark on the step, copied from `api.setHistoryTag` at
|
|
43
|
+
* the moment it is recorded. The grid never reads it: it is there for a
|
|
44
|
+
* consumer that shows one of several data sets through the grid and has
|
|
45
|
+
* to know which one a step belongs to before it lets Ctrl+Z apply it (a
|
|
46
|
+
* workbook with several sheets, say). Kept as given, so an object tag
|
|
47
|
+
* compares by identity. Not serialised.
|
|
48
|
+
*/
|
|
49
|
+
tag?: unknown;
|
|
41
50
|
};
|
|
42
51
|
/** Ctx surface these helpers touch. The controller satisfies it structurally. */
|
|
43
52
|
type HistoryCtx = {
|
|
@@ -49,8 +58,20 @@ type HistoryCtx = {
|
|
|
49
58
|
* inside it joins that group, so a command does not have to thread a group
|
|
50
59
|
* id down through the writers it calls. */
|
|
51
60
|
historyGroupId: string | undefined;
|
|
61
|
+
/** Stamped on every step recorded while it is set; see `HistoryStep.tag`. */
|
|
62
|
+
historyTag?: unknown;
|
|
52
63
|
applyHistoryStep(step: HistoryStep, direction: 'undo' | 'redo'): void;
|
|
53
64
|
};
|
|
65
|
+
/** What the next undo would apply: the tag of the step Ctrl+Z takes next
|
|
66
|
+
* (a group carries one tag, so its last step's is the group's). Null when
|
|
67
|
+
* there is nothing to undo. */
|
|
68
|
+
export declare function peekUndoHistory(ctx: HistoryCtx): {
|
|
69
|
+
tag: unknown;
|
|
70
|
+
} | null;
|
|
71
|
+
/** The redo counterpart of `peekUndoHistory`. */
|
|
72
|
+
export declare function peekRedoHistory(ctx: HistoryCtx): {
|
|
73
|
+
tag: unknown;
|
|
74
|
+
} | null;
|
|
54
75
|
/** Undo one action. Returns false when there is nothing left to undo. */
|
|
55
76
|
export declare function undoHistory(ctx: HistoryCtx): boolean;
|
|
56
77
|
/** Redo one action. Returns false when there is nothing left to redo. */
|
package/dist/history.js
CHANGED
|
@@ -41,6 +41,34 @@ function groupEnd(history, start) {
|
|
|
41
41
|
i += 1;
|
|
42
42
|
return i;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The last `limit` ACTIONS of a history, whole groups kept. The cap used
|
|
46
|
+
* to count steps, and a sort of 5,000 rows is 20,000 steps in one group:
|
|
47
|
+
* it cut the group to its last 200 cells, so Ctrl+Z put a fifth of a
|
|
48
|
+
* column back and left the rest sorted. An action is one press of Ctrl+Z,
|
|
49
|
+
* so that is what the buffer counts; the steps inside are the cost of it.
|
|
50
|
+
*/
|
|
51
|
+
function lastActions(history, limit) {
|
|
52
|
+
let i = history.length;
|
|
53
|
+
let actions = 0;
|
|
54
|
+
while (i > 0 && actions < limit) {
|
|
55
|
+
i = groupStart(history, i - 1);
|
|
56
|
+
actions += 1;
|
|
57
|
+
}
|
|
58
|
+
return i === 0 ? history : history.slice(i);
|
|
59
|
+
}
|
|
60
|
+
/** What the next undo would apply: the tag of the step Ctrl+Z takes next
|
|
61
|
+
* (a group carries one tag, so its last step's is the group's). Null when
|
|
62
|
+
* there is nothing to undo. */
|
|
63
|
+
export function peekUndoHistory(ctx) {
|
|
64
|
+
const step = ctx.historyPtr >= 0 ? ctx.history[ctx.historyPtr] : undefined;
|
|
65
|
+
return step ? { tag: step.tag } : null;
|
|
66
|
+
}
|
|
67
|
+
/** The redo counterpart of `peekUndoHistory`. */
|
|
68
|
+
export function peekRedoHistory(ctx) {
|
|
69
|
+
const step = ctx.historyPtr < ctx.history.length - 1 ? ctx.history[ctx.historyPtr + 1] : undefined;
|
|
70
|
+
return step ? { tag: step.tag } : null;
|
|
71
|
+
}
|
|
44
72
|
/** Undo one action. Returns false when there is nothing left to undo. */
|
|
45
73
|
export function undoHistory(ctx) {
|
|
46
74
|
if (ctx.historyPtr < 0)
|
|
@@ -94,11 +122,13 @@ export function pushHistory(ctx, steps, groupId) {
|
|
|
94
122
|
// Outside one, a group of one is just a step. Tagging it would make undo do
|
|
95
123
|
// the same work through a slower path and would show up in serialized state.
|
|
96
124
|
const id = ctx.historyGroupId ?? (groupId && steps.length > 1 ? groupId : undefined);
|
|
125
|
+
const tag = ctx.historyTag;
|
|
97
126
|
let next = ctx.history.slice(0, ctx.historyPtr + 1);
|
|
98
|
-
for (const step of steps)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
for (const step of steps) {
|
|
128
|
+
const stamped = tag === undefined ? step : { ...step, tag };
|
|
129
|
+
next.push(id ? { ...stamped, groupId: id } : stamped);
|
|
130
|
+
}
|
|
131
|
+
next = lastActions(next, ctx.UNDO_LIMIT);
|
|
102
132
|
ctx.history = next;
|
|
103
133
|
ctx.historyPtr = ctx.history.length - 1;
|
|
104
134
|
ctx.historyVersion += 1;
|
package/dist/index.d.ts
CHANGED
|
@@ -213,7 +213,7 @@ export { resolveColumnTypes, inferCellDataType, type CellDataType } from './colu
|
|
|
213
213
|
export { computeColumnGroupMeta, hiddenLeavesForCollapse, type ColumnGroupMeta, type ColumnGroupShow, } from './column-groups.js';
|
|
214
214
|
export { createHyperFormulaSheet, type HyperFormulaSheet, type HyperFormulaSheetConfig, type HyperFormulaInstance, } from './hyperformula-adapter.js';
|
|
215
215
|
export { createCollaboration, broadcastChannelTransport, type CollabUser, type CollabCell, type CollabPresence, type CollabMessage, type CollabTransport, type Collaboration, } from './collaboration.js';
|
|
216
|
-
export { createServerDataSource, type ServerDataSource, type ServerSelectionRule, type ServerRequest, type ServerResult, type ServerController, type ServerControllerOptions, type ServerState, type ServerSortModel, type ServerFilterModel, type ServerAggregation, type ServerGroupRow, type ServerLeafRow, type ServerMoreRow, type ServerFooterRow, type ServerSkeletonRow, type ServerGrandTotalRow, type ServerPlaceholderRow, type ServerDisplayRow, } from './server-data-source.js';
|
|
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
218
|
export { toServerFilterColumns, type GridRowModel, type GridFilterState, type GridRowModelSort, } from './row-model.js';
|
|
219
219
|
export { createNamedViews, memoryViews, localStorageViews, attachAutoSavedView, type SavedView, type ViewStorage, type NamedViews, type AutoSavedViewOptions, } from './named-views.js';
|
|
@@ -166,6 +166,28 @@ export function createKeyboard(ctx) {
|
|
|
166
166
|
row.toggleExpanded?.();
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
|
+
// The same key on a server-side tree: a group opens or closes, a leaf
|
|
170
|
+
// opens or closes its detail panel when the grid draws one.
|
|
171
|
+
const sg = ctx.props.serverGroup;
|
|
172
|
+
if (event.ctrlKey && sg && row) {
|
|
173
|
+
const data = row.original;
|
|
174
|
+
if (sg.isGroup(data)) {
|
|
175
|
+
sg.onToggle(data);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (sg.toggleDetail && ctx.props.renderDetailRow && (sg.hasDetail?.(data) ?? true)) {
|
|
179
|
+
sg.toggleDetail(data);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ...and on a client-side master-detail: the app's own toggle.
|
|
184
|
+
if (event.ctrlKey && row && ctx.props.onDetailToggle && ctx.props.renderDetailRow) {
|
|
185
|
+
const data = row.original;
|
|
186
|
+
if (!ctx.props.isDetailRow?.(data, current.rowIndex) && (ctx.props.hasDetail?.(data) ?? true)) {
|
|
187
|
+
ctx.props.onDetailToggle(data, current.rowIndex);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
169
191
|
if (column?.columnDef.editorType === "checkbox") {
|
|
170
192
|
ctx.toggleBooleanCell(current.rowIndex, current.colIndex);
|
|
171
193
|
return;
|
|
@@ -257,7 +279,8 @@ export function createKeyboard(ctx) {
|
|
|
257
279
|
ctx.setSelection(next.rowIndex, next.colIndex);
|
|
258
280
|
}
|
|
259
281
|
function onWindowKeydown(event) {
|
|
260
|
-
if (event.key === "Escape" &&
|
|
282
|
+
if (event.key === "Escape" &&
|
|
283
|
+
(ctx.columnMenuFor || ctx.operatorMenuFor || ctx.filterMenuFor || ctx.inSuggestFor || ctx.chooseColumnsPos)) {
|
|
261
284
|
ctx.closeMenus();
|
|
262
285
|
}
|
|
263
286
|
}
|
package/dist/keyboard.js
CHANGED
|
@@ -37,6 +37,10 @@ export function getKeyboardIntent(event) {
|
|
|
37
37
|
// Tab → move right (Shift+Tab → left), wraps at row boundaries
|
|
38
38
|
// Delete → clear contents of the selected cells
|
|
39
39
|
// F2 / Space → start editing the active cell
|
|
40
|
+
// Ctrl+Enter acts on the row rather than moving: a group opens or closes,
|
|
41
|
+
// a row with a detail panel shows or hides it (see the activate branch).
|
|
42
|
+
if (event.key === 'Enter' && (event.ctrlKey || event.metaKey))
|
|
43
|
+
return 'activate';
|
|
40
44
|
if (event.key === 'Enter')
|
|
41
45
|
return event.shiftKey ? 'moveUp' : 'moveDown';
|
|
42
46
|
if (event.key === 'Tab')
|
package/dist/row-drag-touch.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type TouchDragDeps = {
|
|
|
3
3
|
ctx: any;
|
|
4
4
|
managed: () => boolean;
|
|
5
5
|
originalAt: (rowIndex: number) => unknown;
|
|
6
|
-
commitDropOnRow: (rowIndex: number, side: "before" | "after") => void;
|
|
6
|
+
commitDropOnRow: (rowIndex: number, side: "before" | "after" | "into") => void;
|
|
7
7
|
clearIndicators: () => void;
|
|
8
8
|
cancelPendingLeave: () => void;
|
|
9
9
|
/** Publish the dragged row on the shared cross-grid bus. */
|
package/dist/row-drag.js
CHANGED
|
@@ -98,7 +98,27 @@ export function createRowDrag(ctx) {
|
|
|
98
98
|
e.dataTransfer.dropEffect = "move";
|
|
99
99
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
100
100
|
ctx.rowDropIndex = rowIndex;
|
|
101
|
-
ctx.rowDropSide =
|
|
101
|
+
ctx.rowDropSide = sideAt(rowIndex, rect, e.clientY);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Which edge the pointer is on. A custom drop handler also gets the middle
|
|
105
|
+
* of a group or tree row as "into" (the row becomes its child); the
|
|
106
|
+
* managed reorder only knows before and after.
|
|
107
|
+
*/
|
|
108
|
+
function sideAt(rowIndex, rect, y) {
|
|
109
|
+
const rel = (y - rect.top) / Math.max(rect.height, 1);
|
|
110
|
+
if (ctx.props.onRowDrop && rel >= 0.25 && rel <= 0.75 && canNest(rowIndex))
|
|
111
|
+
return "into";
|
|
112
|
+
return rel < 0.5 ? "before" : "after";
|
|
113
|
+
}
|
|
114
|
+
function canNest(rowIndex) {
|
|
115
|
+
const row = ctx.allRows[rowIndex];
|
|
116
|
+
if (!row)
|
|
117
|
+
return false;
|
|
118
|
+
const sg = ctx.props.serverGroup;
|
|
119
|
+
if (sg)
|
|
120
|
+
return !!sg.isGroup(row.original);
|
|
121
|
+
return typeof row.getCanExpand === "function" && row.getCanExpand();
|
|
102
122
|
}
|
|
103
123
|
function onRowDragLeave(rowIndex) {
|
|
104
124
|
// A leave from a row that is not the current target is already stale.
|
|
@@ -137,6 +157,15 @@ export function createRowDrag(ctx) {
|
|
|
137
157
|
const sourceGridId = bus.gridId;
|
|
138
158
|
const sameGrid = sourceGridId === gridId;
|
|
139
159
|
clearIndicators();
|
|
160
|
+
// A custom handler owns the drop: the grid reports it and leaves its
|
|
161
|
+
// data alone (a row model's rows are not the grid's to splice).
|
|
162
|
+
if (sameGrid && ctx.props.onRowDrop) {
|
|
163
|
+
bus = null;
|
|
164
|
+
ctx.props.onRowDrop({ row: dragged, target, targetIndex: rowIndex, side });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (side === "into")
|
|
168
|
+
side = "after";
|
|
140
169
|
const data = ctx.internalData.slice();
|
|
141
170
|
if (sameGrid) {
|
|
142
171
|
const from = data.indexOf(dragged);
|
|
@@ -183,6 +212,11 @@ export function createRowDrag(ctx) {
|
|
|
183
212
|
const sourceGridId = bus.gridId;
|
|
184
213
|
const sameGrid = sourceGridId === gridId;
|
|
185
214
|
clearIndicators();
|
|
215
|
+
if (sameGrid && ctx.props.onRowDrop) {
|
|
216
|
+
bus = null;
|
|
217
|
+
ctx.props.onRowDrop({ row: dragged, target: null, targetIndex: null, side: "after" });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
186
220
|
const data = ctx.internalData.slice();
|
|
187
221
|
if (sameGrid) {
|
|
188
222
|
const from = data.indexOf(dragged);
|
package/dist/row-model.d.ts
CHANGED
|
@@ -84,6 +84,12 @@ export type GridRowModel<TData> = {
|
|
|
84
84
|
level: (row: TData) => number;
|
|
85
85
|
expanded?: (row: TData) => boolean;
|
|
86
86
|
onToggle: (row: TData) => void;
|
|
87
|
+
/** Open or close the detail panel under a leaf. Ctrl+Enter on a row calls it when `renderDetailRow` is set, and so does the `showDetailToggle` chevron. */
|
|
88
|
+
toggleDetail?: (row: TData) => void;
|
|
89
|
+
/** Whether the detail under a leaf is open: the chevron's direction. */
|
|
90
|
+
detailOpen?: (row: TData) => boolean;
|
|
91
|
+
/** Whether a row can open a detail; the chevron is left out where this is false. */
|
|
92
|
+
hasDetail?: (row: TData) => boolean;
|
|
87
93
|
};
|
|
88
94
|
/** Selection the grid does not own - see the `rowSelectionModel` prop. */
|
|
89
95
|
selection?: {
|
package/dist/row-resize.js
CHANGED
|
@@ -31,6 +31,19 @@ export function rowResize(node, opts) {
|
|
|
31
31
|
let drag = null;
|
|
32
32
|
/** The last press on a strip, for telling a double-click from two drags. */
|
|
33
33
|
let lastPress = null;
|
|
34
|
+
/**
|
|
35
|
+
* A focusable `role="separator"` is a splitter, and a splitter carries its
|
|
36
|
+
* position: axe's `aria-required-attr` fails one without `aria-valuenow`,
|
|
37
|
+
* and a screen reader reading the strip says nothing useful without it. The
|
|
38
|
+
* value is the row's height in pixels, between the action's min and max.
|
|
39
|
+
*/
|
|
40
|
+
function setStripValue(strip, height) {
|
|
41
|
+
const min = current.min ?? 20;
|
|
42
|
+
const max = current.max ?? 320;
|
|
43
|
+
strip.setAttribute('aria-valuemin', String(min));
|
|
44
|
+
strip.setAttribute('aria-valuemax', String(max));
|
|
45
|
+
strip.setAttribute('aria-valuenow', String(Math.round(Math.max(min, Math.min(max, height || min)))));
|
|
46
|
+
}
|
|
34
47
|
function rowIndexOf(tr) {
|
|
35
48
|
const dataRow = tr.querySelector('[data-svgrid-row]');
|
|
36
49
|
const val = dataRow?.dataset.svgridRow;
|
|
@@ -43,6 +56,7 @@ export function rowResize(node, opts) {
|
|
|
43
56
|
const max = current.max ?? 320;
|
|
44
57
|
const next = Math.max(min, Math.min(max, drag.startHeight + (e.clientY - drag.startY)));
|
|
45
58
|
drag.trEl.style.height = `${Math.round(next)}px`;
|
|
59
|
+
setStripValue(drag.strip, next);
|
|
46
60
|
current.onResizeMove?.(drag.rowIndex, Math.round(next));
|
|
47
61
|
}
|
|
48
62
|
function onPointerUp(e) {
|
|
@@ -94,6 +108,7 @@ export function rowResize(node, opts) {
|
|
|
94
108
|
const max = current.max ?? 320;
|
|
95
109
|
const next = Math.round(Math.max(min, Math.min(max, tr.getBoundingClientRect().height + delta)));
|
|
96
110
|
tr.style.height = `${next}px`;
|
|
111
|
+
setStripValue(t, next);
|
|
97
112
|
current.onResizeMove?.(rowIndex, next);
|
|
98
113
|
current.onResize(rowIndex, next);
|
|
99
114
|
}
|
|
@@ -164,8 +179,13 @@ export function rowResize(node, opts) {
|
|
|
164
179
|
: null);
|
|
165
180
|
if (!gutter)
|
|
166
181
|
continue;
|
|
167
|
-
|
|
182
|
+
const already = gutter.querySelector(`:scope > .${STRIP_CLASS}`);
|
|
183
|
+
if (already) {
|
|
184
|
+
// The row may have been resized from outside the action (a saved
|
|
185
|
+
// height, a double-click autofit); keep the separator's value honest.
|
|
186
|
+
setStripValue(already, tr.getBoundingClientRect().height);
|
|
168
187
|
continue;
|
|
188
|
+
}
|
|
169
189
|
// Anchor the strip to the gutter cell + allow it to overflow
|
|
170
190
|
// downward into the row gap so the hit-area straddles the
|
|
171
191
|
// row boundary the way Excel does.
|
|
@@ -180,6 +200,7 @@ export function rowResize(node, opts) {
|
|
|
180
200
|
// A separator used as an interactive splitter has to be focusable so
|
|
181
201
|
// keyboard-only users can reach it and drive `onKeyDown` (#79).
|
|
182
202
|
strip.tabIndex = 0;
|
|
203
|
+
setStripValue(strip, tr.getBoundingClientRect().height);
|
|
183
204
|
// Inline the geometry + pointer-events so the strip works even
|
|
184
205
|
// before SvGrid.css is parsed; the hover tint still comes from
|
|
185
206
|
// the stylesheet. We keep the strip fully inside the gutter
|
package/dist/selection.js
CHANGED
|
@@ -522,7 +522,7 @@ export function createSelection(ctx) {
|
|
|
522
522
|
// Also commit a fill-handle drag if one was in progress - the user
|
|
523
523
|
// released the mouse, time to apply the pattern.
|
|
524
524
|
if (ctx.fillDrag)
|
|
525
|
-
ctx.onFillPointerUp();
|
|
525
|
+
ctx.onFillPointerUp(event);
|
|
526
526
|
if (ctx.moveDrag)
|
|
527
527
|
ctx.onMovePointerUp(event);
|
|
528
528
|
}
|
|
@@ -161,6 +161,11 @@ export type BlockCache<TData> = {
|
|
|
161
161
|
lastRowKnown(): boolean;
|
|
162
162
|
/** Re-fetch the blocks that failed. */
|
|
163
163
|
retryFailed(): void;
|
|
164
|
+
/**
|
|
165
|
+
* Re-fetch the failed block holding `rowIndex`, and only that one; the
|
|
166
|
+
* Retry on a failed row. A row whose block has not failed is a no-op.
|
|
167
|
+
*/
|
|
168
|
+
retryFailedAt(rowIndex: number): void;
|
|
164
169
|
/**
|
|
165
170
|
* Re-fetch the loaded blocks in place, keeping the row count and scroll
|
|
166
171
|
* position. What you want after a mutation lands on the server.
|
|
@@ -353,6 +353,14 @@ export function createBlockCache(options) {
|
|
|
353
353
|
if (any)
|
|
354
354
|
emit();
|
|
355
355
|
},
|
|
356
|
+
retryFailedAt(rowIndex) {
|
|
357
|
+
const index = blockOf(Math.max(0, rowIndex));
|
|
358
|
+
if (blocks.get(index)?.status !== 'failed')
|
|
359
|
+
return;
|
|
360
|
+
blocks.delete(index);
|
|
361
|
+
ensureBlock(index);
|
|
362
|
+
emit();
|
|
363
|
+
},
|
|
356
364
|
refresh() {
|
|
357
365
|
// Keep the count and the scroll position: drop the DATA, re-request the
|
|
358
366
|
// same blocks. The user sees skeletons where they were, not a jump home.
|
|
@@ -60,10 +60,18 @@ export type ServerFilterModel = {
|
|
|
60
60
|
*/
|
|
61
61
|
expression?: GridPredicateExpr;
|
|
62
62
|
};
|
|
63
|
-
/**
|
|
63
|
+
/** The aggregate functions every backend helper understands. */
|
|
64
|
+
export type ServerAggFn = 'sum' | 'avg' | 'min' | 'max' | 'count';
|
|
65
|
+
/**
|
|
66
|
+
* One value column to roll up per group. `fn` is one of the five built-ins
|
|
67
|
+
* or any name your backend knows (`median`, `countDistinct`, a domain
|
|
68
|
+
* aggregate): the request carries it untouched. The SvelteKit planner emits
|
|
69
|
+
* only the built-ins plus the names it was told to allow, since `fn` ends
|
|
70
|
+
* up in SQL.
|
|
71
|
+
*/
|
|
64
72
|
export type ServerAggregation = {
|
|
65
73
|
col: string;
|
|
66
|
-
fn:
|
|
74
|
+
fn: ServerAggFn | (string & {});
|
|
67
75
|
};
|
|
68
76
|
/**
|
|
69
77
|
* One range of rows as the grid asks for it. A flat request carries the
|
|
@@ -121,6 +129,16 @@ export type ServerRequest = {
|
|
|
121
129
|
* transport posts the whole request to your endpoint.
|
|
122
130
|
*/
|
|
123
131
|
context?: unknown;
|
|
132
|
+
/**
|
|
133
|
+
* Aborted when the grid no longer wants the answer: the block was purged
|
|
134
|
+
* or evicted, the sort or filter changed, a group was collapsed, the
|
|
135
|
+
* controller was disposed. Hand it to `fetch` and a query the user has
|
|
136
|
+
* scrolled past is cancelled at the server instead of completing for
|
|
137
|
+
* nothing; a rejection carrying the abort is ignored by the grid. Not
|
|
138
|
+
* serialisable - the SvelteKit transport reads it and leaves it out of
|
|
139
|
+
* the body.
|
|
140
|
+
*/
|
|
141
|
+
signal?: AbortSignal;
|
|
124
142
|
};
|
|
125
143
|
/**
|
|
126
144
|
* A group row in the server-side group/tree model - one distinct key at a
|
|
@@ -227,12 +245,26 @@ export type ServerPlaceholderRow = {
|
|
|
227
245
|
readonly kind: 'placeholder';
|
|
228
246
|
readonly state: 'loading' | 'failed';
|
|
229
247
|
};
|
|
248
|
+
/**
|
|
249
|
+
* The detail panel under a leaf whose detail is open: master-detail on a
|
|
250
|
+
* server-side row model. The grid draws it through `renderDetailRow`
|
|
251
|
+
* (mark it with `isDetailRow`); `master` is the leaf it belongs to.
|
|
252
|
+
*/
|
|
253
|
+
export type ServerDetailRow<TData> = {
|
|
254
|
+
kind: 'detail';
|
|
255
|
+
id: string;
|
|
256
|
+
level: number;
|
|
257
|
+
route?: string[];
|
|
258
|
+
/** The leaf's id, as `getRowId` names it. */
|
|
259
|
+
masterId: string;
|
|
260
|
+
master: TData;
|
|
261
|
+
};
|
|
230
262
|
/**
|
|
231
263
|
* Every row a server row model can put on screen. Each carries an `id` and
|
|
232
264
|
* a `level`. The block-cached model adds {@link ServerPlaceholderRow} for
|
|
233
265
|
* rows not yet loaded - see its own `ServerRowModelDisplayRow`.
|
|
234
266
|
*/
|
|
235
|
-
export type ServerDisplayRow<TData> = ServerGroupRow<TData> | ServerLeafRow<TData> | ServerMoreRow | ServerFooterRow<TData> | ServerSkeletonRow | ServerGrandTotalRow<TData>;
|
|
267
|
+
export type ServerDisplayRow<TData> = ServerGroupRow<TData> | ServerLeafRow<TData> | ServerDetailRow<TData> | ServerMoreRow | ServerFooterRow<TData> | ServerSkeletonRow | ServerGrandTotalRow<TData>;
|
|
236
268
|
/**
|
|
237
269
|
* What `getRows` answers with: the rows for the requested range and the
|
|
238
270
|
* count after filtering, plus the grand total and the pivot fields when
|
|
@@ -30,6 +30,8 @@ export function createServerDataSource(source, options) {
|
|
|
30
30
|
// Monotonic request id: only the latest fetch is allowed to land, so a slow
|
|
31
31
|
// response for an old sort/filter can't clobber a newer one.
|
|
32
32
|
let requestSeq = 0;
|
|
33
|
+
/** The page fetch in flight; a newer one, or dispose, aborts it. */
|
|
34
|
+
let pageAbort = null;
|
|
33
35
|
let disposed = false;
|
|
34
36
|
// Once per controller: a misconfigured backend would otherwise log on every
|
|
35
37
|
// page, scroll and filter change.
|
|
@@ -65,7 +67,7 @@ export function createServerDataSource(source, options) {
|
|
|
65
67
|
maxConcurrentRequests: options.maxConcurrentRequests,
|
|
66
68
|
blockLoadDebounceMs: options.blockLoadDebounceMs,
|
|
67
69
|
initialRowCount: options.initialRowCount,
|
|
68
|
-
fetch: async (startRow, endRow) => {
|
|
70
|
+
fetch: async (startRow, endRow, signal) => {
|
|
69
71
|
const result = await source.getRows({
|
|
70
72
|
startRow,
|
|
71
73
|
endRow,
|
|
@@ -78,6 +80,7 @@ export function createServerDataSource(source, options) {
|
|
|
78
80
|
groupBy: [],
|
|
79
81
|
groupKeys: [],
|
|
80
82
|
aggregations: [],
|
|
83
|
+
signal,
|
|
81
84
|
});
|
|
82
85
|
noteExpressionApplied(result);
|
|
83
86
|
return { rows: result.rows, rowCount: result.rowCount };
|
|
@@ -121,6 +124,9 @@ export function createServerDataSource(source, options) {
|
|
|
121
124
|
if (disposed)
|
|
122
125
|
return;
|
|
123
126
|
const id = ++requestSeq;
|
|
127
|
+
pageAbort?.abort();
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
pageAbort = controller;
|
|
124
130
|
state.loading = true;
|
|
125
131
|
state.error = null;
|
|
126
132
|
emit();
|
|
@@ -138,6 +144,7 @@ export function createServerDataSource(source, options) {
|
|
|
138
144
|
groupBy: [],
|
|
139
145
|
groupKeys: [],
|
|
140
146
|
aggregations: [],
|
|
147
|
+
signal: controller.signal,
|
|
141
148
|
});
|
|
142
149
|
if (disposed || id !== requestSeq)
|
|
143
150
|
return; // stale
|
|
@@ -322,7 +329,7 @@ export function createServerDataSource(source, options) {
|
|
|
322
329
|
getRowId: options.getRowId ? (row) => options.getRowId(row) : undefined,
|
|
323
330
|
// Only infinite mode has unloaded rows to stand in for.
|
|
324
331
|
rowPlaceholder: infinite ? (row) => rowPlaceholderState(row) : undefined,
|
|
325
|
-
retryRow: infinite ? () => cache?.
|
|
332
|
+
retryRow: infinite ? (_row, rowIndex) => cache?.retryFailedAt(rowIndex) : undefined,
|
|
326
333
|
/**
|
|
327
334
|
* A getter, not a snapshot: the grid re-reads this after every change
|
|
328
335
|
* notification, and a frozen object would leave the pager on page 1
|
|
@@ -392,6 +399,8 @@ export function createServerDataSource(source, options) {
|
|
|
392
399
|
getState: () => ({ ...state }),
|
|
393
400
|
dispose() {
|
|
394
401
|
disposed = true;
|
|
402
|
+
pageAbort?.abort();
|
|
403
|
+
pageAbort = null;
|
|
395
404
|
cache?.dispose();
|
|
396
405
|
cache = null;
|
|
397
406
|
// An in-flight fetch's resolution short-circuits on `disposed`, so it
|