@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.
Files changed (176) hide show
  1. package/README.md +32 -11
  2. package/dist/GridMenus.svelte +34 -4
  3. package/dist/SvGrid.controller.svelte.d.ts +33 -5
  4. package/dist/SvGrid.controller.svelte.js +397 -58
  5. package/dist/SvGrid.css +200 -2
  6. package/dist/SvGrid.svelte +350 -40
  7. package/dist/SvGrid.types.d.ts +433 -7
  8. package/dist/SvGridDropdown.svelte +20 -0
  9. package/dist/SvGridDropdown.svelte.d.ts +12 -0
  10. package/dist/SvModal.svelte +22 -0
  11. package/dist/SvModal.svelte.d.ts +7 -0
  12. package/dist/a11y/dismissable.d.ts +10 -0
  13. package/dist/a11y/dismissable.js +24 -2
  14. package/dist/build-api.js +92 -37
  15. package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-C-NtjlrJ.js} +155 -146
  16. package/dist/cdn/GridMenus-TssHej3q.js +644 -0
  17. package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
  18. package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
  19. package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
  20. package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
  21. package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-BlyCSp0q.js} +1 -1
  22. package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
  23. package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
  24. package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
  25. package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
  26. package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
  27. package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
  28. package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
  29. package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
  30. package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.js} +2 -2
  31. package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
  32. package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
  33. package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
  34. package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
  35. package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-DVVKOSI9.js} +1 -1
  36. package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
  37. package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
  38. package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
  39. package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
  40. package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
  41. package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
  42. package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
  43. package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
  44. package/dist/cdn/server-block-cache-DIqb3VZn.js +241 -0
  45. package/dist/cdn/{src-V1uu8iE9.js → src-CgoeSM8k.js} +5339 -4753
  46. package/dist/cdn/{src-BKhZ6eXd.js → src-DfwxB1TH.js} +9817 -9231
  47. package/dist/cdn/svgrid.js +28 -27
  48. package/dist/cdn/svgrid.svelte-external.js +28 -27
  49. package/dist/cdn/validate-AZoD8BoR.js +80 -0
  50. package/dist/cell-formatting.js +18 -1
  51. package/dist/cell-render.js +25 -6
  52. package/dist/clipboard.d.ts +2 -2
  53. package/dist/clipboard.js +72 -23
  54. package/dist/columns.js +7 -2
  55. package/dist/conditional-formatting.d.ts +2 -0
  56. package/dist/conditional-formatting.js +2 -0
  57. package/dist/core.d.ts +22 -6
  58. package/dist/core.js +401 -38
  59. package/dist/editing.js +34 -0
  60. package/dist/gantt-view.svelte.d.ts +24 -0
  61. package/dist/gantt-view.svelte.js +13 -0
  62. package/dist/grid-icons.d.ts +2 -2
  63. package/dist/grid-icons.js +2 -2
  64. package/dist/grid-messages.d.ts +10 -0
  65. package/dist/grid-messages.js +9 -0
  66. package/dist/headless.d.ts +5 -0
  67. package/dist/headless.js +12 -0
  68. package/dist/history.d.ts +21 -0
  69. package/dist/history.js +34 -4
  70. package/dist/index.d.ts +5 -4
  71. package/dist/index.js +3 -3
  72. package/dist/keyboard-handlers.js +24 -1
  73. package/dist/keyboard.js +4 -0
  74. package/dist/row-drag-touch.d.ts +1 -1
  75. package/dist/row-drag.js +35 -1
  76. package/dist/row-model.d.ts +139 -0
  77. package/dist/row-model.js +16 -0
  78. package/dist/row-resize.js +22 -1
  79. package/dist/selection.d.ts +2 -1
  80. package/dist/selection.js +31 -3
  81. package/dist/server-block-cache.d.ts +219 -0
  82. package/dist/server-block-cache.js +539 -0
  83. package/dist/server-data-source.d.ts +274 -8
  84. package/dist/server-data-source.js +229 -20
  85. package/dist/server.d.ts +13 -0
  86. package/dist/server.js +13 -0
  87. package/dist/sparkline.js +7 -5
  88. package/dist/svgrid-wrapper.types.d.ts +33 -0
  89. package/dist/validate.d.ts +4 -0
  90. package/dist/validate.js +29 -3
  91. package/package.json +11 -1
  92. package/src/GridMenus.svelte +34 -4
  93. package/src/SvGrid.controller.svelte.ts +397 -61
  94. package/src/SvGrid.css +200 -2
  95. package/src/SvGrid.svelte +350 -40
  96. package/src/SvGrid.types.ts +453 -7
  97. package/src/SvGridDropdown.svelte +20 -0
  98. package/src/SvModal.svelte +22 -0
  99. package/src/SvModal.test.ts +30 -1
  100. package/src/a11y/dismissable.test.ts +10 -1
  101. package/src/a11y/dismissable.ts +22 -2
  102. package/src/build-api.ts +96 -31
  103. package/src/cell-formatting.test.ts +17 -0
  104. package/src/cell-formatting.ts +18 -1
  105. package/src/cell-render.test.ts +24 -2
  106. package/src/cell-render.ts +24 -8
  107. package/src/clipboard.test.ts +107 -0
  108. package/src/clipboard.ts +62 -23
  109. package/src/columns.test.ts +4 -4
  110. package/src/columns.ts +7 -2
  111. package/src/conditional-formatting.test.ts +8 -0
  112. package/src/conditional-formatting.ts +3 -0
  113. package/src/core.rowmodel-cache.test.ts +51 -0
  114. package/src/core.tick-repair.test.ts +287 -0
  115. package/src/core.ts +407 -38
  116. package/src/editing.test.ts +52 -0
  117. package/src/editing.ts +33 -0
  118. package/src/gantt-stub.test.svelte +38 -0
  119. package/src/gantt-view.svelte.ts +35 -0
  120. package/src/grid-icons.ts +2 -2
  121. package/src/grid-messages.test.ts +7 -0
  122. package/src/grid-messages.ts +22 -0
  123. package/src/headless.ts +24 -0
  124. package/src/history.test.ts +49 -1
  125. package/src/history.ts +48 -2
  126. package/src/icon-seam.test.ts +2 -4
  127. package/src/index.ts +38 -10
  128. package/src/keyboard-handlers.coverage.test.ts +65 -0
  129. package/src/keyboard-handlers.ts +26 -1
  130. package/src/keyboard.ts +3 -0
  131. package/src/row-drag-touch.ts +2 -2
  132. package/src/row-drag.test.ts +51 -0
  133. package/src/row-drag.ts +35 -2
  134. package/src/row-model.ts +152 -0
  135. package/src/row-resize.ts +24 -1
  136. package/src/selection.test.ts +3 -0
  137. package/src/selection.ts +33 -3
  138. package/src/server-block-cache.test.ts +672 -0
  139. package/src/server-block-cache.ts +690 -0
  140. package/src/server-data-source.infinite.test.ts +405 -0
  141. package/src/server-data-source.test.ts +19 -0
  142. package/src/server-data-source.ts +496 -29
  143. package/src/server.ts +49 -0
  144. package/src/sparkline.test.ts +7 -0
  145. package/src/sparkline.ts +6 -4
  146. package/src/svgrid-wrapper.types.ts +32 -0
  147. package/src/svgrid.api.test.ts +21 -0
  148. package/src/svgrid.charting.test.ts +12 -4
  149. package/src/svgrid.context-menu.test.ts +12 -0
  150. package/src/svgrid.detail-rows.svelte.test.ts +187 -0
  151. package/src/svgrid.filter-menu-scroll.test.ts +9 -0
  152. package/src/svgrid.gantt-seam.test.ts +199 -0
  153. package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
  154. package/src/svgrid.menu-scroll-close.test.ts +10 -0
  155. package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
  156. package/src/svgrid.row-model-seam.svelte.test.ts +305 -0
  157. package/src/svgrid.row-pinning.test.ts +15 -1
  158. package/src/svgrid.selection-bar-seam.test.ts +1 -1
  159. package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
  160. package/src/svgrid.upsell-license.test.ts +8 -5
  161. package/src/test-setup.ts +12 -0
  162. package/src/transaction.test.ts +58 -0
  163. package/src/validate.test.ts +29 -0
  164. package/src/validate.ts +34 -3
  165. package/dist/SvGroupCell.svelte +0 -141
  166. package/dist/SvGroupCell.svelte.d.ts +0 -49
  167. package/dist/SvRowGroupPanel.svelte +0 -186
  168. package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
  169. package/dist/cdn/GridMenus-7kbpnnBW.js +0 -635
  170. package/dist/cdn/validate-_CDJzgIo.js +0 -75
  171. package/dist/server-group-model.d.ts +0 -98
  172. package/dist/server-group-model.js +0 -263
  173. package/src/SvGroupCell.svelte +0 -141
  174. package/src/SvRowGroupPanel.svelte +0 -186
  175. package/src/server-group-model.test.ts +0 -294
  176. 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
- return v !== undefined ? v : Reflect.get(t, k);
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
- let history = $state<HistoryStep[]>([])
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
- let internalData = $state.raw<ReadonlyArray<TData>>(props.data);
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
- get _rowModels() {
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 = rowNumberWidth + selectionWidth;
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
- if (rowSelectionState[row.id]) {
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
- const a = selectionRange.anchor;
1844
- const f = selectionRange.focus;
1845
- const ranges: Array<[number, number, number, number]> =
1846
- a && f
1847
- ? [[
1848
- Math.min(a.rowIndex, f.rowIndex),
1849
- Math.min(a.colIndex, f.colIndex),
1850
- Math.max(a.rowIndex, f.rowIndex),
1851
- Math.max(a.colIndex, f.colIndex),
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 is in the flow
2921
- // before the spacer whatever the window shows, so it comes off the spacer.
2922
- const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight, 0));
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
- const fixedCols =
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
- const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0
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
- const selectable = allRows.filter((row) => !isGroupRow(row));
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
- rowNumberWidth -
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(column, meta.isDate, props.data, getColumnAccessorValue);
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,