@svgrid/grid 3.0.4 → 3.0.6
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 +32 -11
- package/dist/GridMenus.svelte +34 -4
- package/dist/SvGrid.controller.svelte.d.ts +33 -5
- package/dist/SvGrid.controller.svelte.js +397 -58
- package/dist/SvGrid.css +200 -2
- package/dist/SvGrid.svelte +350 -40
- package/dist/SvGrid.types.d.ts +433 -7
- 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 +92 -37
- package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-C-NtjlrJ.js} +155 -146
- package/dist/cdn/GridMenus-TssHej3q.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-BlyCSp0q.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.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-DVVKOSI9.js} +1 -1
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
- package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
- package/dist/cdn/server-block-cache-DIqb3VZn.js +241 -0
- package/dist/cdn/{src-V1uu8iE9.js → src-CgoeSM8k.js} +5339 -4753
- package/dist/cdn/{src-BKhZ6eXd.js → src-DfwxB1TH.js} +9817 -9231
- package/dist/cdn/svgrid.js +28 -27
- package/dist/cdn/svgrid.svelte-external.js +28 -27
- package/dist/cdn/validate-AZoD8BoR.js +80 -0
- package/dist/cell-formatting.js +18 -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 +401 -38
- package/dist/editing.js +34 -0
- package/dist/gantt-view.svelte.d.ts +24 -0
- package/dist/gantt-view.svelte.js +13 -0
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +10 -0
- package/dist/grid-messages.js +9 -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 +5 -4
- package/dist/index.js +3 -3
- 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 +139 -0
- package/dist/row-model.js +16 -0
- package/dist/row-resize.js +22 -1
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +31 -3
- package/dist/server-block-cache.d.ts +219 -0
- package/dist/server-block-cache.js +539 -0
- package/dist/server-data-source.d.ts +274 -8
- package/dist/server-data-source.js +229 -20
- package/dist/server.d.ts +13 -0
- package/dist/server.js +13 -0
- package/dist/sparkline.js +7 -5
- package/dist/svgrid-wrapper.types.d.ts +33 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +29 -3
- package/package.json +11 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +397 -61
- package/src/SvGrid.css +200 -2
- package/src/SvGrid.svelte +350 -40
- package/src/SvGrid.types.ts +453 -7
- 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 +96 -31
- package/src/cell-formatting.test.ts +17 -0
- package/src/cell-formatting.ts +18 -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.rowmodel-cache.test.ts +51 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +407 -38
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +33 -0
- package/src/gantt-stub.test.svelte +38 -0
- package/src/gantt-view.svelte.ts +35 -0
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.test.ts +7 -0
- package/src/grid-messages.ts +22 -0
- package/src/headless.ts +24 -0
- package/src/history.test.ts +49 -1
- package/src/history.ts +48 -2
- package/src/icon-seam.test.ts +2 -4
- package/src/index.ts +38 -10
- 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 +152 -0
- package/src/row-resize.ts +24 -1
- package/src/selection.test.ts +3 -0
- package/src/selection.ts +33 -3
- package/src/server-block-cache.test.ts +672 -0
- package/src/server-block-cache.ts +690 -0
- package/src/server-data-source.infinite.test.ts +405 -0
- package/src/server-data-source.test.ts +19 -0
- package/src/server-data-source.ts +496 -29
- package/src/server.ts +49 -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.detail-rows.svelte.test.ts +187 -0
- package/src/svgrid.filter-menu-scroll.test.ts +9 -0
- package/src/svgrid.gantt-seam.test.ts +199 -0
- package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
- package/src/svgrid.menu-scroll-close.test.ts +10 -0
- package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +305 -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 +8 -5
- package/src/test-setup.ts +12 -0
- package/src/transaction.test.ts +58 -0
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +34 -3
- package/dist/SvGroupCell.svelte +0 -141
- package/dist/SvGroupCell.svelte.d.ts +0 -49
- package/dist/SvRowGroupPanel.svelte +0 -186
- package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
- package/dist/cdn/GridMenus-7kbpnnBW.js +0 -635
- package/dist/cdn/validate-_CDJzgIo.js +0 -75
- package/dist/server-group-model.d.ts +0 -98
- package/dist/server-group-model.js +0 -263
- package/src/SvGroupCell.svelte +0 -141
- package/src/SvRowGroupPanel.svelte +0 -186
- package/src/server-group-model.test.ts +0 -294
- package/src/server-group-model.ts +0 -370
|
@@ -309,6 +309,60 @@ export function createSvGridController<
|
|
|
309
309
|
// preserves reference identity for non-overridden keys, which the columns-identity
|
|
310
310
|
// column cache and the `data`/`columns` sync effects rely on.
|
|
311
311
|
let optionOverrides = $state<Record<string, unknown>>({});
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A `rowModel` answers for the props it can, so a server-backed grid is
|
|
315
|
+
* one prop rather than a dozen.
|
|
316
|
+
*
|
|
317
|
+
* Bumped by the model itself through `subscribe` (see the effect below),
|
|
318
|
+
* which is what makes `getRows()` and `isLoading()` - plain method calls,
|
|
319
|
+
* invisible to the reactive system - re-read when the model changes.
|
|
320
|
+
*/
|
|
321
|
+
let rowModelVersion = $state(0);
|
|
322
|
+
const rowModelProps = $derived.by(function rowModelProps_d() {
|
|
323
|
+
const model = rawProps.rowModel;
|
|
324
|
+
if (!model) return null;
|
|
325
|
+
rowModelVersion; // read, so a notification re-derives
|
|
326
|
+
const paging = model.pagination;
|
|
327
|
+
return {
|
|
328
|
+
data: model.getRows(),
|
|
329
|
+
loading: model.isLoading(),
|
|
330
|
+
loadingOverlay: true,
|
|
331
|
+
getRowId: model.getRowId,
|
|
332
|
+
// A model that sorts or filters server-side is telling the grid not
|
|
333
|
+
// to do it locally; one that does neither leaves the grid in charge.
|
|
334
|
+
externalSort: model.setSort ? true : undefined,
|
|
335
|
+
externalFilter: model.setFilter ? true : undefined,
|
|
336
|
+
onSortingChange: model.setSort ? (s: any) => model.setSort!(s) : undefined,
|
|
337
|
+
onFiltersChange: model.setFilter ? (f: any) => model.setFilter!(f) : undefined,
|
|
338
|
+
onVisibleRangeChange: model.setViewport
|
|
339
|
+
? (r: { startIndex: number; endIndex: number }) =>
|
|
340
|
+
model.setViewport!(r.startIndex, r.endIndex)
|
|
341
|
+
: undefined,
|
|
342
|
+
rowPlaceholder: model.rowPlaceholder,
|
|
343
|
+
onRetryRow: model.retryRow,
|
|
344
|
+
serverGroup: model.group,
|
|
345
|
+
rowSelectionModel: model.selection,
|
|
346
|
+
serverFilterValues: model.filterValues
|
|
347
|
+
? (columnId: string) => model.filterValues!(columnId)
|
|
348
|
+
: undefined,
|
|
349
|
+
pinnedTopRows: model.pinnedTopRows,
|
|
350
|
+
pinnedBottomRows: model.pinnedBottomRows,
|
|
351
|
+
pivotResultColumns: model.pivotResultColumns ?? undefined,
|
|
352
|
+
externalPagination: paging ? true : undefined,
|
|
353
|
+
rowCount: paging?.rowCount,
|
|
354
|
+
pageIndex: paging?.pageIndex,
|
|
355
|
+
pageSize: paging?.pageSize,
|
|
356
|
+
pageSizeOptions: paging?.pageSizes,
|
|
357
|
+
onPaginationChange: paging
|
|
358
|
+
? (p: { pageIndex: number; pageSize: number }) => {
|
|
359
|
+
if (p.pageSize !== paging.pageSize) paging.setPageSize(p.pageSize);
|
|
360
|
+
else paging.setPage(p.pageIndex);
|
|
361
|
+
}
|
|
362
|
+
: undefined,
|
|
363
|
+
} as Partial<Props<TFeatures, TData>>;
|
|
364
|
+
});
|
|
365
|
+
|
|
312
366
|
const props = new Proxy(rawProps, {
|
|
313
367
|
// Read the override as a PROPERTY (tracked by `$state`), not via `in` (the `has`
|
|
314
368
|
// trap is not tracked) - so a reactive read of `props.X` re-runs when an override
|
|
@@ -318,7 +372,21 @@ export function createSvGridController<
|
|
|
318
372
|
get: (t, k) => {
|
|
319
373
|
const v =
|
|
320
374
|
typeof k === "string" ? optionOverrides[k] : undefined;
|
|
321
|
-
|
|
375
|
+
if (v !== undefined) return v;
|
|
376
|
+
// Pivot mode swaps the column list: `pivotResultColumns` (the prop, or
|
|
377
|
+
// the row model's) stands in for `columns` while it is set, and
|
|
378
|
+
// `columns` itself stays the app's own list for when pivot ends.
|
|
379
|
+
if (k === "columns") {
|
|
380
|
+
const pivot =
|
|
381
|
+
Reflect.get(t, "pivotResultColumns") ??
|
|
382
|
+
(rowModelProps as Record<string, unknown> | null)?.pivotResultColumns;
|
|
383
|
+
if (pivot) return pivot;
|
|
384
|
+
}
|
|
385
|
+
const own = Reflect.get(t, k);
|
|
386
|
+
// A prop written on the grid always beats the row model, so adopting
|
|
387
|
+
// `rowModel` is never all-or-nothing.
|
|
388
|
+
if (own !== undefined || typeof k !== "string") return own;
|
|
389
|
+
return (rowModelProps as Record<string, unknown> | null)?.[k];
|
|
322
390
|
},
|
|
323
391
|
}) as Props<TFeatures, TData>;
|
|
324
392
|
|
|
@@ -447,7 +515,11 @@ export function createSvGridController<
|
|
|
447
515
|
// previously declared it twice and open-coded the push in three places.
|
|
448
516
|
type HistoryStep = SharedHistoryStep
|
|
449
517
|
const UNDO_LIMIT = 200
|
|
450
|
-
|
|
518
|
+
// Raw: pushHistory always assigns a fresh array and nothing renders from a
|
|
519
|
+
// step, so deep proxies would cost 200 wrapped objects for nothing. It also
|
|
520
|
+
// keeps a consumer's `tag` the object it passed, which a proxy would not be:
|
|
521
|
+
// the sheet shell compares tags by identity.
|
|
522
|
+
let history = $state.raw<HistoryStep[]>([])
|
|
451
523
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
452
524
|
* undo() decrements; redo() increments. New edits truncate everything
|
|
453
525
|
* past the pointer (the classic "you can't redo after editing" rule). */
|
|
@@ -460,6 +532,9 @@ export function createSvGridController<
|
|
|
460
532
|
* $state: it is written and read synchronously inside a single call and
|
|
461
533
|
* nothing renders from it. */
|
|
462
534
|
let historyGroupId: string | undefined = undefined
|
|
535
|
+
/** The consumer's mark for the steps recorded from now on (`api.setHistoryTag`);
|
|
536
|
+
* not $state for the same reason. */
|
|
537
|
+
let historyTag: unknown = undefined
|
|
463
538
|
|
|
464
539
|
// ---- Hover tooltip (custom popover, not native title=) ---------------
|
|
465
540
|
// Triggered by per-column `tooltip` field OR per-cell `notes` prop.
|
|
@@ -531,7 +606,25 @@ export function createSvGridController<
|
|
|
531
606
|
// catch-all gridStateVersion, so navigation doesn't rebuild 1M rows.
|
|
532
607
|
let dataStateVersion = $state(0);
|
|
533
608
|
const selectionColumnWidth = 44;
|
|
609
|
+
const detailToggleColumnWidth = 36;
|
|
534
610
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
611
|
+
const showDetailToggleEffective = $derived(props.showDetailToggle === true);
|
|
612
|
+
/**
|
|
613
|
+
* What the chevron on a row does, or null when the row has no detail to
|
|
614
|
+
* open: a detail row itself, a group or placeholder on a server model, a
|
|
615
|
+
* row `hasDetail` rules out, or a grid with nothing to call.
|
|
616
|
+
*/
|
|
617
|
+
function detailToggleOf(row: Row<TData>, rowIndex: number): { open: boolean; toggle: () => void } | null {
|
|
618
|
+
const data = row.original;
|
|
619
|
+
if (props.isDetailRow?.(data, rowIndex)) return null;
|
|
620
|
+
const sg = props.serverGroup;
|
|
621
|
+
if (sg) {
|
|
622
|
+
if (!sg.toggleDetail || sg.isGroup(data) || (sg.hasDetail && !sg.hasDetail(data))) return null;
|
|
623
|
+
return { open: sg.detailOpen?.(data) ?? false, toggle: () => sg.toggleDetail!(data) };
|
|
624
|
+
}
|
|
625
|
+
if (!props.onDetailToggle || (props.hasDetail && !props.hasDetail(data))) return null;
|
|
626
|
+
return { open: props.isDetailOpen?.(data) ?? false, toggle: () => props.onDetailToggle!(data, rowIndex) };
|
|
627
|
+
}
|
|
535
628
|
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
536
629
|
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
537
630
|
const autoRowHeightOn = $derived(
|
|
@@ -703,6 +796,16 @@ export function createSvGridController<
|
|
|
703
796
|
((props.selectionMode ?? "both") === "row" ||
|
|
704
797
|
(props.selectionMode ?? "both") === "both"),
|
|
705
798
|
);
|
|
799
|
+
/** The system columns to the left of the data: row numbers, selection, the detail toggle. */
|
|
800
|
+
const systemColumnsWidth = $derived(
|
|
801
|
+
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
802
|
+
(showRowSelectionEffective ? selectionColumnWidth : 0) +
|
|
803
|
+
(showDetailToggleEffective ? detailToggleColumnWidth : 0),
|
|
804
|
+
);
|
|
805
|
+
/** Where the detail-toggle column sticks: after the row numbers and the selection column. */
|
|
806
|
+
const detailToggleColumnLeft = $derived(
|
|
807
|
+
(showRowNumbersEffective ? rowNumberColumnWidth : 0) + (showRowSelectionEffective ? selectionColumnWidth : 0),
|
|
808
|
+
);
|
|
706
809
|
// `selectable` is the shortcut alias, so it wins over the fine-grained prop,
|
|
707
810
|
// which in turn wins over `selectionMode` - the same precedence the other
|
|
708
811
|
// shortcuts use. Unlike them it starts ON (selectionMode defaults to 'both'),
|
|
@@ -739,7 +842,10 @@ export function createSvGridController<
|
|
|
739
842
|
// re-synced whenever the parent passes a new array; the imperative API
|
|
740
843
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
741
844
|
// svelte-ignore state_referenced_locally
|
|
742
|
-
|
|
845
|
+
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
846
|
+
// instead), so every read falls back to empty rather than to undefined.
|
|
847
|
+
const EMPTY_DATA: ReadonlyArray<TData> = [];
|
|
848
|
+
let internalData = $state.raw<ReadonlyArray<TData>>(props.data ?? EMPTY_DATA);
|
|
743
849
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
744
850
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
745
851
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -791,7 +897,7 @@ export function createSvGridController<
|
|
|
791
897
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
792
898
|
// would keep returning the old edited values from `editedCellValues`
|
|
793
899
|
// even though the underlying data has been replaced.
|
|
794
|
-
internalData = props.data;
|
|
900
|
+
internalData = props.data ?? EMPTY_DATA;
|
|
795
901
|
editedCellValues = {};
|
|
796
902
|
});
|
|
797
903
|
$effect(() => {
|
|
@@ -819,36 +925,42 @@ export function createSvGridController<
|
|
|
819
925
|
// svelte-ignore state_referenced_locally
|
|
820
926
|
const treeDataConfig = props.treeData;
|
|
821
927
|
|
|
928
|
+
// Pagination is intentionally NOT in the grid's row-model pipeline. The
|
|
929
|
+
// wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
930
|
+
// valueFilters) on top of `grid.getRowModel().rows`. If pagination ran
|
|
931
|
+
// first, those filters would only see the visible page. Instead the wrapper
|
|
932
|
+
// paginates last - see `allRows` below.
|
|
933
|
+
//
|
|
934
|
+
// Built ONCE. Both inputs are captured at mount, and the stages keep state
|
|
935
|
+
// between runs: the sorted and filtered stages hold their last output so a
|
|
936
|
+
// data tick can repair it instead of recomputing (core.ts, `replacedRowsOf`).
|
|
937
|
+
// This used to be a getter that built fresh stages on every read, which
|
|
938
|
+
// threw that cache away on each run - a live feed re-sorted 100k rows on
|
|
939
|
+
// every tick and the row-model memo's `cachedPipeline` never matched.
|
|
940
|
+
const rowModels = {
|
|
941
|
+
coreRowModel: createCoreRowModel<TData>(),
|
|
942
|
+
filteredRowModel: createFilteredRowModel<TData>(),
|
|
943
|
+
// External-sort mode: pass the rows through untouched so the consumer
|
|
944
|
+
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
945
|
+
sortedRowModel: externalSortEnabled
|
|
946
|
+
? passthroughSortedRowModel
|
|
947
|
+
: createSortedRowModel<TData>(sortFns),
|
|
948
|
+
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
949
|
+
// be both a hierarchy node and bucketed under a group banner.
|
|
950
|
+
groupedRowModel: treeDataConfig
|
|
951
|
+
? createTreeRowModel<TData>({
|
|
952
|
+
parentField: treeDataConfig.parentField,
|
|
953
|
+
idField: treeDataConfig.idField,
|
|
954
|
+
})
|
|
955
|
+
: createGroupedRowModel<TData>(),
|
|
956
|
+
expandedRowModel: createExpandedRowModel<TData>(),
|
|
957
|
+
};
|
|
822
958
|
|
|
823
959
|
const grid = createSvGrid({
|
|
824
960
|
get _features() {
|
|
825
961
|
return resolveEffectiveFeatures();
|
|
826
962
|
},
|
|
827
|
-
|
|
828
|
-
// Pagination is intentionally NOT in the grid's row-model pipeline.
|
|
829
|
-
// The wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
830
|
-
// valueFilters) on top of `grid.getRowModel().rows`. If pagination
|
|
831
|
-
// ran first, those filters would only see the visible page. Instead
|
|
832
|
-
// the wrapper paginates last - see `allRows` below.
|
|
833
|
-
return {
|
|
834
|
-
coreRowModel: createCoreRowModel<TData>(),
|
|
835
|
-
filteredRowModel: createFilteredRowModel<TData>(),
|
|
836
|
-
// External-sort mode: pass the rows through untouched so the consumer
|
|
837
|
-
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
838
|
-
sortedRowModel: externalSortEnabled
|
|
839
|
-
? passthroughSortedRowModel
|
|
840
|
-
: createSortedRowModel<TData>(sortFns),
|
|
841
|
-
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
842
|
-
// be both a hierarchy node and bucketed under a group banner.
|
|
843
|
-
groupedRowModel: treeDataConfig
|
|
844
|
-
? createTreeRowModel<TData>({
|
|
845
|
-
parentField: treeDataConfig.parentField,
|
|
846
|
-
idField: treeDataConfig.idField,
|
|
847
|
-
})
|
|
848
|
-
: createGroupedRowModel<TData>(),
|
|
849
|
-
expandedRowModel: createExpandedRowModel<TData>(),
|
|
850
|
-
};
|
|
851
|
-
},
|
|
963
|
+
_rowModels: rowModels,
|
|
852
964
|
get columns() {
|
|
853
965
|
return internalColumns;
|
|
854
966
|
},
|
|
@@ -1037,6 +1149,20 @@ export function createSvGridController<
|
|
|
1037
1149
|
*
|
|
1038
1150
|
* Returns null when the cell should render nothing.
|
|
1039
1151
|
*/
|
|
1152
|
+
/**
|
|
1153
|
+
* Why a row has no data yet, or null when it does.
|
|
1154
|
+
*
|
|
1155
|
+
* Group rows are never placeholders, so the check is skipped for them:
|
|
1156
|
+
* their `original` is a synthetic bucket, not a row the consumer handed
|
|
1157
|
+
* us, and passing it to `rowPlaceholder` would ask a question about a row
|
|
1158
|
+
* that does not exist.
|
|
1159
|
+
*/
|
|
1160
|
+
function placeholderStateOf(row: Row<TData>): "loading" | "failed" | null {
|
|
1161
|
+
const ask = props.rowPlaceholder;
|
|
1162
|
+
if (!ask || isGroupRow(row)) return null;
|
|
1163
|
+
return ask(row.original, row.index);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1040
1166
|
function autoGroupCell(
|
|
1041
1167
|
row: Row<TData>,
|
|
1042
1168
|
columnId: string,
|
|
@@ -1299,10 +1425,8 @@ export function createSvGridController<
|
|
|
1299
1425
|
|
|
1300
1426
|
/** Cumulative pixel offsets for left- and right-pinned columns. */
|
|
1301
1427
|
const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
|
|
1302
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1303
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1304
1428
|
const left: Record<string, number> = {};
|
|
1305
|
-
let leftAcc =
|
|
1429
|
+
let leftAcc = systemColumnsWidth;
|
|
1306
1430
|
for (const id of effectivePinning.left) {
|
|
1307
1431
|
// Hidden pinned columns don't render, so they must contribute neither an
|
|
1308
1432
|
// offset entry nor width - otherwise the next visible pinned column is
|
|
@@ -1337,7 +1461,7 @@ export function createSvGridController<
|
|
|
1337
1461
|
// index currently hovered; `rowDropSide` says which edge the drop line paints.
|
|
1338
1462
|
let rowDragActive = $state<boolean>(false);
|
|
1339
1463
|
let rowDropIndex = $state<number | null>(null);
|
|
1340
|
-
let rowDropSide = $state<"before" | "after" | null>(null);
|
|
1464
|
+
let rowDropSide = $state<"before" | "after" | "into" | null>(null);
|
|
1341
1465
|
|
|
1342
1466
|
|
|
1343
1467
|
|
|
@@ -1805,15 +1929,42 @@ export function createSvGridController<
|
|
|
1805
1929
|
const rows: TData[] = [];
|
|
1806
1930
|
const ids: string[] = [];
|
|
1807
1931
|
if (!selectionBarOn) return { rows, ids };
|
|
1932
|
+
// Under an external selection model the loaded rows are the ones the
|
|
1933
|
+
// model says are selected; rows that never loaded are not in `allRows`
|
|
1934
|
+
// and are counted by `selectionBarCount` below instead. This walks
|
|
1935
|
+
// every loaded row, so it runs on every block a row model delivers:
|
|
1936
|
+
// a model that can say "nothing is selected" spares the walk, and the
|
|
1937
|
+
// props it needs are read once, not through the proxy per row.
|
|
1938
|
+
const model = props.rowSelectionModel;
|
|
1939
|
+
if (model && model.selectedCount?.() === 0) return { rows, ids };
|
|
1940
|
+
const ask = props.rowPlaceholder;
|
|
1941
|
+
const isPlaceholder = ask
|
|
1942
|
+
? (row: Row<TData>) => !isGroupRow(row) && !!ask(row.original as TData, row.index)
|
|
1943
|
+
: () => false;
|
|
1808
1944
|
for (const row of allRows) {
|
|
1809
|
-
if (isGroupRow(row)) continue;
|
|
1810
|
-
|
|
1945
|
+
if (isGroupRow(row) || isPlaceholder(row)) continue;
|
|
1946
|
+
const selected = model
|
|
1947
|
+
? model.isSelected(row.id, row.original as TData)
|
|
1948
|
+
: !!rowSelectionState[row.id];
|
|
1949
|
+
if (selected) {
|
|
1811
1950
|
rows.push(row.original as TData);
|
|
1812
1951
|
ids.push(row.id);
|
|
1813
1952
|
}
|
|
1814
1953
|
}
|
|
1815
1954
|
return { rows, ids };
|
|
1816
1955
|
});
|
|
1956
|
+
/**
|
|
1957
|
+
* What the selection bar's count chip says. A model that stores the rule
|
|
1958
|
+
* ("everything except these") knows about rows the grid never loaded, so
|
|
1959
|
+
* its number wins over counting ticks on screen; `null` from it means it
|
|
1960
|
+
* cannot say, and the bar falls back to the loaded count.
|
|
1961
|
+
*/
|
|
1962
|
+
const selectionBarCount = $derived.by(function selectionBarCount_d() {
|
|
1963
|
+
const fromModel = props.rowSelectionModel?.selectedCount?.();
|
|
1964
|
+
return fromModel ?? selectionBarTarget.ids.length;
|
|
1965
|
+
});
|
|
1966
|
+
/** Whether the bar has anything to show: the model's count when it has one, else the loaded ticks. */
|
|
1967
|
+
const selectionBarVisible = $derived(selectionBarOn && selectionBarCount > 0);
|
|
1817
1968
|
|
|
1818
1969
|
// Forward selection changes to the consumer. Skips the very first invocation
|
|
1819
1970
|
// (the initial empty state) so consumers don't get a spurious callback on mount.
|
|
@@ -1840,17 +1991,16 @@ export function createSvGridController<
|
|
|
1840
1991
|
// so consumers don't see spurious callbacks during re-renders.
|
|
1841
1992
|
let lastCellRangeSerialized = "";
|
|
1842
1993
|
$effect(() => {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
: [];
|
|
1994
|
+
// Every rectangle, the committed ones a Ctrl+click added and the
|
|
1995
|
+
// active one last, the order getSelectionRects gives: a consumer's
|
|
1996
|
+
// status bar and shading cover the whole selection, not only its last
|
|
1997
|
+
// range. Depend on selectionRanges so a change to the committed set
|
|
1998
|
+
// re-runs this too.
|
|
1999
|
+
void selectionRanges;
|
|
2000
|
+
const ranges: Array<[number, number, number, number]> = getSelectionRects().map(
|
|
2001
|
+
(r: { minRow: number; minCol: number; maxRow: number; maxCol: number }) =>
|
|
2002
|
+
[r.minRow, r.minCol, r.maxRow, r.maxCol] as [number, number, number, number],
|
|
2003
|
+
);
|
|
1854
2004
|
const serialized = JSON.stringify(ranges);
|
|
1855
2005
|
if (serialized === lastCellRangeSerialized) return;
|
|
1856
2006
|
lastCellRangeSerialized = serialized;
|
|
@@ -2833,6 +2983,55 @@ export function createSvGridController<
|
|
|
2833
2983
|
Math.max(virtualRowTotalSize - virtualRowEnd, 0),
|
|
2834
2984
|
);
|
|
2835
2985
|
|
|
2986
|
+
// A row model tells us when to re-read it. Subscribing here (rather than
|
|
2987
|
+
// polling, or asking the consumer to wrap the model in a rune) is what lets
|
|
2988
|
+
// `getRows()` be a plain method call on a plain object.
|
|
2989
|
+
$effect(() => {
|
|
2990
|
+
const model = rawProps.rowModel;
|
|
2991
|
+
if (!model) return;
|
|
2992
|
+
return model.subscribe(() => {
|
|
2993
|
+
rowModelVersion += 1;
|
|
2994
|
+
});
|
|
2995
|
+
});
|
|
2996
|
+
|
|
2997
|
+
// The index range on screen, for data sources that fetch by block.
|
|
2998
|
+
// `virtualRowStart` / `virtualRowEnd` above are PIXEL offsets; a fetcher
|
|
2999
|
+
// needs row numbers, and needs them whether or not virtualization is on.
|
|
3000
|
+
const visibleRowRange = $derived.by(function visibleRowRange_d() {
|
|
3001
|
+
if (!rowVirtualizationEnabled) {
|
|
3002
|
+
return { startIndex: 0, endIndex: Math.max(0, allRows.length - 1) };
|
|
3003
|
+
}
|
|
3004
|
+
const first = virtualRows[0];
|
|
3005
|
+
const last = virtualRows[virtualRows.length - 1];
|
|
3006
|
+
if (!first || !last) return { startIndex: 0, endIndex: 0 };
|
|
3007
|
+
return { startIndex: first.index, endIndex: last.index };
|
|
3008
|
+
});
|
|
3009
|
+
// Emitted from an effect rather than the derived so a consumer that
|
|
3010
|
+
// fetches (and therefore changes `data`) cannot re-enter the derivation.
|
|
3011
|
+
let lastReportedRange = "";
|
|
3012
|
+
$effect(() => {
|
|
3013
|
+
const report = props.onVisibleRangeChange;
|
|
3014
|
+
if (!report) return;
|
|
3015
|
+
const range = visibleRowRange;
|
|
3016
|
+
const key = range.startIndex + ":" + range.endIndex;
|
|
3017
|
+
if (key === lastReportedRange) return;
|
|
3018
|
+
lastReportedRange = key;
|
|
3019
|
+
report(range);
|
|
3020
|
+
});
|
|
3021
|
+
|
|
3022
|
+
// A paging row model that asks for `autoPageSize` gets as many rows as
|
|
3023
|
+
// the body shows without scrolling, re-measured as the viewport changes.
|
|
3024
|
+
$effect(() => {
|
|
3025
|
+
rowModelVersion;
|
|
3026
|
+
const paging = rawProps.rowModel?.pagination;
|
|
3027
|
+
if (!paging?.autoPageSize) return;
|
|
3028
|
+
const height = viewportHeight;
|
|
3029
|
+
if (height <= 0) return;
|
|
3030
|
+
const rowPx = rowSizePxOf(0) || 30;
|
|
3031
|
+
const fit = Math.max(1, Math.floor(height / rowPx));
|
|
3032
|
+
if (fit !== paging.pageSize) paging.setPageSize(fit);
|
|
3033
|
+
});
|
|
3034
|
+
|
|
2836
3035
|
// --- Huge-list scroll scaling -----------------------------------------
|
|
2837
3036
|
// Browsers cap how tall a single element may be, and mobile caps sit well
|
|
2838
3037
|
// below desktop. Past a few hundred thousand rows the true content height
|
|
@@ -2902,11 +3101,21 @@ export function createSvGridController<
|
|
|
2902
3101
|
return total;
|
|
2903
3102
|
});
|
|
2904
3103
|
/** The declared or dragged height of one row; measured under auto height. */
|
|
3104
|
+
/** A detail row's declared height, or null when the row is not one (or none is declared). */
|
|
3105
|
+
function detailRowHeightPx(i: number): number | null {
|
|
3106
|
+
const dh = props.detailRowHeight;
|
|
3107
|
+
if (dh == null || !props.isDetailRow) return null;
|
|
3108
|
+
const row = allRows[i];
|
|
3109
|
+
if (!row || !props.isDetailRow(row.original as TData, i)) return null;
|
|
3110
|
+
return typeof dh === "function" ? dh(row.original as TData) : dh;
|
|
3111
|
+
}
|
|
2905
3112
|
function rowSizePxOf(i: number): number {
|
|
2906
3113
|
if (isRowCollapsed(i)) return 0;
|
|
2907
3114
|
if (autoRowHeightOn) return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
2908
3115
|
const own = rowResizeHeightPx(i);
|
|
2909
3116
|
if (own != null) return own;
|
|
3117
|
+
const detail = detailRowHeightPx(i);
|
|
3118
|
+
if (detail != null) return detail;
|
|
2910
3119
|
const rh = props.rowHeight;
|
|
2911
3120
|
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
2912
3121
|
}
|
|
@@ -2916,10 +3125,94 @@ export function createSvGridController<
|
|
|
2916
3125
|
for (let k = 0; k < i; k += 1) top += rowSizePxOf(k);
|
|
2917
3126
|
return top;
|
|
2918
3127
|
}
|
|
3128
|
+
// --- Sticky group rows --------------------------------------------------
|
|
3129
|
+
// The group a row belongs to, kept under the header while its rows scroll
|
|
3130
|
+
// past. The ancestors of the first row under the header are the nearest
|
|
3131
|
+
// group rows above it at each shallower level; under virtualization they
|
|
3132
|
+
// are usually outside the rendered window, so the band renders a copy of
|
|
3133
|
+
// each (its height taken off the top spacer, like the frozen band), and
|
|
3134
|
+
// without virtualization the rows themselves are made sticky.
|
|
3135
|
+
type StickyGroupRow = { row: Row<TData>; rowIndex: number; top: number; size: number };
|
|
3136
|
+
const NO_STICKY: StickyGroupRow[] = [];
|
|
3137
|
+
/** Whether a row is a group and how deep it sits, for any of the three tree sources. */
|
|
3138
|
+
function rowGroupLevel(row: Row<TData>): { group: boolean; level: number } {
|
|
3139
|
+
const sg = props.serverGroup;
|
|
3140
|
+
if (sg) {
|
|
3141
|
+
const data = row.original as TData;
|
|
3142
|
+
return { group: sg.isGroup(data), level: sg.level(data) };
|
|
3143
|
+
}
|
|
3144
|
+
// Client grouping and tree data: an expandable row is the group (a tree
|
|
3145
|
+
// row is a data row too, which is why isGroupRow() excludes it, but for
|
|
3146
|
+
// the band it is the parent its children scroll under).
|
|
3147
|
+
if (typeof row.getCanExpand === "function" && row.getCanExpand()) return { group: true, level: row.depth ?? 0 };
|
|
3148
|
+
return { group: false, level: row.depth ?? 0 };
|
|
3149
|
+
}
|
|
3150
|
+
/** The first row whose bottom edge is under the header, in row-index space. */
|
|
3151
|
+
const firstRowUnderHeader = $derived.by(function firstRowUnderHeader_d() {
|
|
3152
|
+
if (allRows.length === 0) return -1;
|
|
3153
|
+
if (rowVirtualizationEnabled) {
|
|
3154
|
+
virtualizer.version;
|
|
3155
|
+
const offset = virtualizer.getState().scrollOffset;
|
|
3156
|
+
const hit = virtualRows.find((it) => it.end > offset);
|
|
3157
|
+
return hit ? hit.index : -1;
|
|
3158
|
+
}
|
|
3159
|
+
rowResizeVersion;
|
|
3160
|
+
autoRowHeightVersion;
|
|
3161
|
+
const top = scrollMetrics.scrollTop;
|
|
3162
|
+
let acc = 0;
|
|
3163
|
+
let i = 0;
|
|
3164
|
+
while (i < allRows.length - 1 && acc + rowSizePxOf(i) <= top) {
|
|
3165
|
+
acc += rowSizePxOf(i);
|
|
3166
|
+
i += 1;
|
|
3167
|
+
}
|
|
3168
|
+
return i;
|
|
3169
|
+
});
|
|
3170
|
+
const stickyGroupRows = $derived.by(function stickyGroupRows_d(): StickyGroupRow[] {
|
|
3171
|
+
if (!props.stickyGroupRows) return NO_STICKY;
|
|
3172
|
+
const first = firstRowUnderHeader;
|
|
3173
|
+
if (first < 0) return NO_STICKY;
|
|
3174
|
+
const head = allRows[first];
|
|
3175
|
+
if (!head) return NO_STICKY;
|
|
3176
|
+
// A group row at level L, or a leaf at level L, has its ancestors at
|
|
3177
|
+
// levels L-1 .. 0, each the nearest group row above at that level.
|
|
3178
|
+
let want = rowGroupLevel(head).level - 1;
|
|
3179
|
+
if (want < 0) return NO_STICKY;
|
|
3180
|
+
const found: Array<{ row: Row<TData>; rowIndex: number }> = [];
|
|
3181
|
+
for (let i = first - 1; i >= frozenRowCount && want >= 0; i -= 1) {
|
|
3182
|
+
const row = allRows[i]!;
|
|
3183
|
+
const info = rowGroupLevel(row);
|
|
3184
|
+
if (info.group && info.level === want) {
|
|
3185
|
+
found.push({ row, rowIndex: i });
|
|
3186
|
+
want -= 1;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
if (found.length === 0) return NO_STICKY;
|
|
3190
|
+
found.reverse();
|
|
3191
|
+
rowResizeVersion;
|
|
3192
|
+
autoRowHeightVersion;
|
|
3193
|
+
let top = headerHeight + frozenBandHeight;
|
|
3194
|
+
return found.map((f) => {
|
|
3195
|
+
const at = top;
|
|
3196
|
+
const size = rowSizePxOf(f.rowIndex);
|
|
3197
|
+
top += size;
|
|
3198
|
+
return { ...f, top: at, size };
|
|
3199
|
+
});
|
|
3200
|
+
});
|
|
3201
|
+
/** The band's height under virtualization, taken off the top spacer. */
|
|
3202
|
+
const stickyBandHeight = $derived(
|
|
3203
|
+
rowVirtualizationEnabled ? stickyGroupRows.reduce((sum, s) => sum + s.size, 0) : 0,
|
|
3204
|
+
);
|
|
3205
|
+
/** Without virtualization the real row sticks: its top, or undefined. */
|
|
3206
|
+
function stickyTopOfRow(rowIndex: number): number | undefined {
|
|
3207
|
+
if (rowVirtualizationEnabled || stickyGroupRows.length === 0) return undefined;
|
|
3208
|
+
return stickyGroupRows.find((s) => s.rowIndex === rowIndex)?.top;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
2919
3211
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2920
|
-
// virtualRowStart / virtualRowBottomSpacer. The frozen band
|
|
2921
|
-
// before the spacer whatever the window shows,
|
|
2922
|
-
|
|
3212
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band and the sticky
|
|
3213
|
+
// group band are in the flow before the spacer whatever the window shows,
|
|
3214
|
+
// so they come off the spacer.
|
|
3215
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight - stickyBandHeight, 0));
|
|
2923
3216
|
const rowBottomSpacer = $derived(
|
|
2924
3217
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
2925
3218
|
);
|
|
@@ -3004,10 +3297,7 @@ export function createSvGridController<
|
|
|
3004
3297
|
* `fittedColumnWidths`, so the overflow decision settles in the same
|
|
3005
3298
|
* render where fit-scaling lands - no race, no scrollbar flash. */
|
|
3006
3299
|
const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
|
|
3007
|
-
|
|
3008
|
-
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
3009
|
-
(showRowSelectionEffective ? selectionColumnWidth : 0);
|
|
3010
|
-
let total = fixedCols;
|
|
3300
|
+
let total = systemColumnsWidth;
|
|
3011
3301
|
for (const column of allColumns) total += getColumnWidth(column.id);
|
|
3012
3302
|
// +1 to tolerate sub-pixel rounding residue from `fitColumns`.
|
|
3013
3303
|
return total > viewportWidth + 1;
|
|
@@ -3459,9 +3749,13 @@ export function createSvGridController<
|
|
|
3459
3749
|
: typeof rh === "function"
|
|
3460
3750
|
? rh
|
|
3461
3751
|
: (rh ?? 30);
|
|
3462
|
-
|
|
3752
|
+
// Detail rows have their own height; only when the props say so does
|
|
3753
|
+
// the per-index lookup replace the uniform fast path.
|
|
3754
|
+
const detailOn = props.detailRowHeight != null && !!props.isDetailRow;
|
|
3755
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0 || detailOn
|
|
3463
3756
|
? (index: number) =>
|
|
3464
3757
|
rowResizeHeightPx(index) ??
|
|
3758
|
+
detailRowHeightPx(index) ??
|
|
3465
3759
|
(typeof base === "function" ? base(index) : base)
|
|
3466
3760
|
: base;
|
|
3467
3761
|
virtualizer.setOptions({
|
|
@@ -3596,7 +3890,21 @@ export function createSvGridController<
|
|
|
3596
3890
|
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3597
3891
|
// recount a selection that had not changed.
|
|
3598
3892
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
3599
|
-
|
|
3893
|
+
// An external model knows about rows the grid has never seen, so it is
|
|
3894
|
+
// the only thing that can answer honestly once the data is a window
|
|
3895
|
+
// onto a server.
|
|
3896
|
+
if (props.rowSelectionModel) return props.rowSelectionModel.headerState();
|
|
3897
|
+
// Nothing checked is the common case and needs no walk: this derived
|
|
3898
|
+
// re-runs on every data change, and on a 100k-row live feed the filter
|
|
3899
|
+
// below was 30 ms per tick spent to conclude "none".
|
|
3900
|
+
let anyChecked = false;
|
|
3901
|
+
for (const id in rowSelectionState) {
|
|
3902
|
+
if (rowSelectionState[id]) { anyChecked = true; break; }
|
|
3903
|
+
}
|
|
3904
|
+
if (!anyChecked) return "none";
|
|
3905
|
+
const selectable = allRows.filter(
|
|
3906
|
+
(row) => !isGroupRow(row) && !placeholderStateOf(row),
|
|
3907
|
+
);
|
|
3600
3908
|
if (!selectable.length) return "none";
|
|
3601
3909
|
let selected = 0;
|
|
3602
3910
|
for (const row of selectable) if (rowSelectionState[row.id]) selected += 1;
|
|
@@ -3643,8 +3951,6 @@ export function createSvGridController<
|
|
|
3643
3951
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
3644
3952
|
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3645
3953
|
if (!cols.length) return null;
|
|
3646
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3647
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
3648
3954
|
// Reserve the custom vertical scrollbar's width when it's visible. It
|
|
3649
3955
|
// overlays the right 16px of the viewport (absolute, z-index 40) and
|
|
3650
3956
|
// does NOT shrink clientWidth, so without this the last fitted column
|
|
@@ -3653,8 +3959,7 @@ export function createSvGridController<
|
|
|
3653
3959
|
const scrollbarWidth = hasVerticalOverflow ? 16 : 0;
|
|
3654
3960
|
const target =
|
|
3655
3961
|
(scrollContainer?.clientWidth ?? 0) -
|
|
3656
|
-
|
|
3657
|
-
selectionWidth -
|
|
3962
|
+
systemColumnsWidth -
|
|
3658
3963
|
scrollbarWidth;
|
|
3659
3964
|
if (target <= 0) return null;
|
|
3660
3965
|
|
|
@@ -3820,7 +4125,12 @@ export function createSvGridController<
|
|
|
3820
4125
|
for (const column of allColumns) {
|
|
3821
4126
|
const meta = isBucketableColumn(column);
|
|
3822
4127
|
if (!meta) continue;
|
|
3823
|
-
const buckets = buildBuckets(
|
|
4128
|
+
const buckets = buildBuckets(
|
|
4129
|
+
column,
|
|
4130
|
+
meta.isDate,
|
|
4131
|
+
props.data ?? EMPTY_DATA,
|
|
4132
|
+
getColumnAccessorValue,
|
|
4133
|
+
);
|
|
3824
4134
|
if (buckets) map.set(column.id, buckets);
|
|
3825
4135
|
}
|
|
3826
4136
|
return map;
|
|
@@ -4147,6 +4457,8 @@ export function createSvGridController<
|
|
|
4147
4457
|
set historyPtr(v) { historyPtr = v as never; },
|
|
4148
4458
|
get historyGroupId() { return historyGroupId; },
|
|
4149
4459
|
set historyGroupId(v) { historyGroupId = v as never; },
|
|
4460
|
+
get historyTag() { return historyTag; },
|
|
4461
|
+
set historyTag(v) { historyTag = v; },
|
|
4150
4462
|
get historyVersion() { return historyVersion; },
|
|
4151
4463
|
set historyVersion(v) { historyVersion = v as never; },
|
|
4152
4464
|
get tooltip() { return tooltip; },
|
|
@@ -4183,6 +4495,11 @@ export function createSvGridController<
|
|
|
4183
4495
|
get gridStateVersion() { return gridStateVersion; },
|
|
4184
4496
|
set gridStateVersion(v) { gridStateVersion = v as never; },
|
|
4185
4497
|
get selectionColumnWidth() { return selectionColumnWidth; },
|
|
4498
|
+
get detailToggleColumnWidth() { return detailToggleColumnWidth; },
|
|
4499
|
+
get detailToggleColumnLeft() { return detailToggleColumnLeft; },
|
|
4500
|
+
get showDetailToggleEffective() { return showDetailToggleEffective; },
|
|
4501
|
+
get systemColumnsWidth() { return systemColumnsWidth; },
|
|
4502
|
+
get detailToggleOf() { return detailToggleOf; },
|
|
4186
4503
|
get rowNumberColumnWidth() { return rowNumberColumnWidth; },
|
|
4187
4504
|
get showRowNumbersEffective() { return showRowNumbersEffective; },
|
|
4188
4505
|
get filterOperatorOptions() { return filterOperatorOptions; },
|
|
@@ -4329,6 +4646,8 @@ export function createSvGridController<
|
|
|
4329
4646
|
get selectionBarMaxVisible() { return selectionBarMaxVisible; },
|
|
4330
4647
|
get selectionBarHideClear() { return selectionBarHideClear; },
|
|
4331
4648
|
get selectionBarTarget() { return selectionBarTarget; },
|
|
4649
|
+
get selectionBarCount() { return selectionBarCount; },
|
|
4650
|
+
get selectionBarVisible() { return selectionBarVisible; },
|
|
4332
4651
|
get lastSelectionSerialized() { return lastSelectionSerialized; },
|
|
4333
4652
|
set lastSelectionSerialized(v) { lastSelectionSerialized = v as never; },
|
|
4334
4653
|
get lastCellRangeSerialized() { return lastCellRangeSerialized; },
|
|
@@ -4598,6 +4917,8 @@ export function createSvGridController<
|
|
|
4598
4917
|
get frozenRowList() { return frozenRowList; },
|
|
4599
4918
|
get frozenBandHeight() { return frozenBandHeight; },
|
|
4600
4919
|
get frozenRowTop() { return frozenRowTop; },
|
|
4920
|
+
get stickyGroupRows() { return stickyGroupRows; },
|
|
4921
|
+
get stickyTopOfRow() { return stickyTopOfRow; },
|
|
4601
4922
|
/** Record the height the user dragged a row to. */
|
|
4602
4923
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4603
4924
|
get isRowCollapsed() { return isRowCollapsed; },
|
|
@@ -4610,6 +4931,12 @@ export function createSvGridController<
|
|
|
4610
4931
|
rowResizeVersion;
|
|
4611
4932
|
return rowResizeHeightPx;
|
|
4612
4933
|
},
|
|
4934
|
+
/** The height row i is drawn at: dragged, measured, detail, declared or default. */
|
|
4935
|
+
get rowSizePxOf() {
|
|
4936
|
+
rowResizeVersion;
|
|
4937
|
+
autoRowHeightVersion;
|
|
4938
|
+
return rowSizePxOf;
|
|
4939
|
+
},
|
|
4613
4940
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
4614
4941
|
get virtualRowStart() { return virtualRowStart; },
|
|
4615
4942
|
get virtualRowEnd() { return virtualRowEnd; },
|
|
@@ -4664,7 +4991,14 @@ export function createSvGridController<
|
|
|
4664
4991
|
get isRowSelected() { return isRowSelected; },
|
|
4665
4992
|
get toggleRowSelectionById() { return toggleRowSelectionById; },
|
|
4666
4993
|
get headerSelectionState() { return headerSelectionState; },
|
|
4994
|
+
get visibleRowRange() { return visibleRowRange; },
|
|
4995
|
+
placeholderStateOf,
|
|
4996
|
+
/** Retry a failed placeholder row. No-op without `onRetryRow`. */
|
|
4997
|
+
retryRow(row: Row<TData>) {
|
|
4998
|
+
props.onRetryRow?.(row.original, row.index);
|
|
4999
|
+
},
|
|
4667
5000
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
5001
|
+
get setSelectAllRows() { return setSelectAllRows; },
|
|
4668
5002
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
4669
5003
|
set userHasActivatedCell(v) { userHasActivatedCell = v as never; },
|
|
4670
5004
|
get tabRunOrigin() { return tabRunOrigin; },
|
|
@@ -4814,7 +5148,7 @@ export function createSvGridController<
|
|
|
4814
5148
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus<TFeatures, TData>(ctx);
|
|
4815
5149
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
4816
5150
|
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste, armPasteFallback } = createEditing<TFeatures, TData>(ctx);
|
|
4817
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
5151
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4818
5152
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns<TFeatures, TData>(ctx);
|
|
4819
5153
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag<TFeatures, TData>(ctx);
|
|
4820
5154
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
@@ -4854,6 +5188,8 @@ export function createSvGridController<
|
|
|
4854
5188
|
pageSize: props.pageSize,
|
|
4855
5189
|
groupBy: props.groupBy,
|
|
4856
5190
|
treeData: props.treeData,
|
|
5191
|
+
gantt: props.gantt,
|
|
5192
|
+
serverGroup: props.serverGroup,
|
|
4857
5193
|
initialColumnPinning: props.initialColumnPinning,
|
|
4858
5194
|
columnVirtualization: props.columnVirtualization,
|
|
4859
5195
|
externalPagination: props.externalPagination,
|