@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
@@ -54,6 +54,7 @@
54
54
  import { getSchedulerView } from "./scheduler-view.svelte";
55
55
  import { getSelectionBarView } from "./selection-bar-view.svelte";
56
56
  import { getBoardView } from "./board-view.svelte";
57
+ import { getGanttView } from "./gantt-view.svelte";
57
58
  import { getChartView } from "./chart-view.svelte";
58
59
  let props: Props<TFeatures, TData> = $props();
59
60
  // Per-instance base for every DOM id the grid mints. `$props.id()` is stable
@@ -78,6 +79,9 @@
78
79
  const boardConfig = $derived(opt.board);
79
80
  // Scheduler / calendar mode: same "view of the grid" seam as the board.
80
81
  const schedulerConfig = $derived(opt.scheduler);
82
+ // Gantt mode: a task table beside a time chart. Same seam again; the renderer
83
+ // ships in @svgrid/enterprise (enableGanttView).
84
+ const ganttConfig = $derived(opt.gantt);
81
85
  // Chart mode: another "view of the grid". Unlike board/scheduler, the renderer
82
86
  // is free - a built-in default (SvGridChartView, lazy-loaded below) wraps the
83
87
  // standalone SvChart, overridable via the `chart-view` seam (getChartView).
@@ -210,6 +214,10 @@
210
214
  const columnResizeEnabled = $derived(opt.columnResize === true);
211
215
  const selectionColumnWidth = $derived(ctrl.selectionColumnWidth);
212
216
  const rowNumberColumnWidth = $derived(ctrl.rowNumberColumnWidth);
217
+ const detailToggleColumnWidth = $derived(ctrl.detailToggleColumnWidth);
218
+ const detailToggleColumnLeft = $derived(ctrl.detailToggleColumnLeft);
219
+ const showDetailToggleEffective = $derived(ctrl.showDetailToggleEffective);
220
+ const detailToggleOf = $derived(ctrl.detailToggleOf);
213
221
  const showRowNumbersEffective = $derived(ctrl.showRowNumbersEffective);
214
222
  const columnMenuFor = $derived(ctrl.columnMenuFor);
215
223
  const filterMenuFor = $derived(ctrl.filterMenuFor);
@@ -272,7 +280,9 @@
272
280
  if (!rowDragManagedEffective || rowDropIndex !== rowIndex) return "";
273
281
  return rowDropSide === "after"
274
282
  ? "sv-grid-row-drop-after"
275
- : "sv-grid-row-drop-before";
283
+ : rowDropSide === "into"
284
+ ? "sv-grid-row-drop-into"
285
+ : "sv-grid-row-drop-before";
276
286
  }
277
287
  const getColumnBaseValue = $derived(ctrl.getColumnBaseValue);
278
288
  const hasConditionalFormats = $derived(ctrl.hasConditionalFormats);
@@ -408,8 +418,15 @@
408
418
  ctrl.columnVirtualizationEnabled,
409
419
  );
410
420
  const virtualRows = $derived(ctrl.virtualRows);
421
+ // The first body row on screen: where a failed run that started above
422
+ // the window repeats its message.
423
+ const firstRenderedRowIndex = $derived(
424
+ rowVirtualizationEnabled ? (virtualRows[0]?.index ?? 0) : 0,
425
+ );
411
426
  const frozenRowCount = $derived(ctrl.frozenRowCount);
412
427
  const frozenRowList = $derived(ctrl.frozenRowList);
428
+ const stickyGroupRows = $derived(ctrl.stickyGroupRows);
429
+ const stickyTopOfRow = $derived(ctrl.stickyTopOfRow);
413
430
  // Merged cells. A merge is drawn once per band (the frozen rows, the
414
431
  // scrolling body), by the top-left cell of the part the band shows, with
415
432
  // its spans clamped to the rendered window: a td never spans into rows
@@ -536,12 +553,17 @@
536
553
  // normal cell path and their aggregates land under the right columns; only
537
554
  // the class distinguishes them. Typed here rather than cast inline - an `as`
538
555
  // inside template markup type-checks but breaks the vitest/build parse.
556
+ // The client-side controller sets the flags on the row objects it builds;
557
+ // a row model (server-side grouping) sets them on the data it hands over,
558
+ // so both places are read and either model's totals get the same chrome.
539
559
  const isGroupFooterRow = (row: Row<TData>): boolean =>
540
- (row as { __groupFooter?: boolean }).__groupFooter === true;
560
+ (row as { __groupFooter?: boolean }).__groupFooter === true ||
561
+ (row.original as { __groupFooter?: boolean } | null)?.__groupFooter === true;
541
562
  // The grand total is a group footer that closes the whole set rather than one
542
563
  // group, so it carries both classes - shared chrome, heavier top rule.
543
564
  const isGrandTotalRow = (row: Row<TData>): boolean =>
544
- (row as { __grandTotal?: boolean }).__grandTotal === true;
565
+ (row as { __grandTotal?: boolean }).__grandTotal === true ||
566
+ (row.original as { __grandTotal?: boolean } | null)?.__grandTotal === true;
545
567
  const computeCellClass = $derived(ctrl.computeCellClass);
546
568
  const computeCellTooltip = $derived(ctrl.computeCellTooltip);
547
569
  const computeCellValidity = $derived(ctrl.computeCellValidity);
@@ -769,9 +791,9 @@
769
791
  than four copies: four gates worded four ways is how a product ends up
770
792
  explaining its own licensing inconsistently.
771
793
 
772
- Enterprise is SOFT-gated - the feature runs without a key and the grid shows
773
- an unlicensed watermark - so this says exactly that rather than implying the
774
- feature is inert without one.
794
+ Enterprise runs without a key during the evaluation and the grid shows the
795
+ watermark and a console notice, so this says exactly that rather than
796
+ implying the feature is inert without one.
775
797
  -->
776
798
  {#snippet enterpriseLicenseNote()}
777
799
  <p class="sv-grid-upsell-license">
@@ -1050,6 +1072,48 @@
1050
1072
  {/if}
1051
1073
  </div>
1052
1074
  </div>
1075
+ {:else if ganttConfig}
1076
+ <div
1077
+ class="sv-grid-root sv-grid-gantt-root"
1078
+ class:sv-grid-root-fill={opt.containerHeight === "100%"}
1079
+ style={`height: ${
1080
+ typeof opt.containerHeight === "string"
1081
+ ? opt.containerHeight
1082
+ : `${opt.containerHeight ?? 520}px`
1083
+ }; display: flex; flex-direction: column;`}
1084
+ >
1085
+ {#if ganttConfig.searchable !== false}
1086
+ <label class="sv-grid-board-search">
1087
+ {@render icon("search")}
1088
+ <input
1089
+ type="search"
1090
+ placeholder={ganttConfig.searchPlaceholder ??
1091
+ messages.ganttSearchPlaceholder}
1092
+ bind:value={ctrl.globalFilter}
1093
+ aria-label="Search tasks"
1094
+ />
1095
+ </label>
1096
+ {/if}
1097
+ <div style="flex: 1 1 auto; min-height: 0;">
1098
+ {#if getGanttView()}
1099
+ {@const GanttView = getGanttView()}
1100
+ <GanttView
1101
+ data={boardData}
1102
+ columns={opt.columns}
1103
+ gantt={ganttConfig}
1104
+ getRowId={opt.getRowId}
1105
+ />
1106
+ {:else}
1107
+ <!-- The Gantt renderer ships in @svgrid/enterprise. Call
1108
+ `enableGanttView()` (or `installEnterprise(api)`) to register it. -->
1109
+ <div class="sv-grid-scheduler-upsell sv-grid-gantt-upsell" role="note">
1110
+ <strong>{messages.ganttUpsellTitle}</strong>
1111
+ <p>{messages.ganttUpsellBody}</p>
1112
+ {@render enterpriseLicenseNote()}
1113
+ </div>
1114
+ {/if}
1115
+ </div>
1116
+ </div>
1053
1117
  {:else if chartViewConfig}
1054
1118
  <div
1055
1119
  class="sv-grid-root sv-grid-chart-root"
@@ -1409,7 +1473,7 @@
1409
1473
  type="button"
1410
1474
  class="sv-grid-group-toggle"
1411
1475
  aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1412
- aria-label={row.getIsExpanded?.() ? "Collapse group" : "Expand group"}
1476
+ aria-label={row.getIsExpanded?.() ? messages.collapseGroup : messages.expandGroup}
1413
1477
  onclick={(event) => {
1414
1478
  event.stopPropagation();
1415
1479
  row.toggleExpanded?.();
@@ -1417,7 +1481,7 @@
1417
1481
  >
1418
1482
  <span class="sv-grid-group-label">{headerLabel}: {groupValue}</span>
1419
1483
  <span class="sv-grid-group-count"
1420
- >{count} {count === 1 ? "row" : "rows"}</span
1484
+ >{count} {count === 1 ? messages.rowSuffix : messages.rowsSuffix}</span
1421
1485
  >
1422
1486
  {#each allColumns as col (col.id)}
1423
1487
  {#if col.columnDef.aggregate && col.id !== groupingColumnId}
@@ -1440,27 +1504,113 @@
1440
1504
  <!-- A full-width detail row: one colspan cell spanning every column,
1441
1505
  hosting the consumer's `renderDetailRow` snippet. Auto height (no
1442
1506
  fixed row height) so the panel grows to fit its content. -->
1443
- {#snippet detailRowMarkup(detailRow: Row<TData>, detailRowIndex: number)}
1507
+ {#snippet detailRowMarkup(detailRow: Row<TData>, detailRowIndex: number, rowStyle: string)}
1508
+ <!-- A declared detailRowHeight sizes the row (the virtualizer counts on
1509
+ it); the cell then scrolls what does not fit. Without one the row
1510
+ is auto height, as before. -->
1444
1511
  <tr
1445
1512
  class="sv-grid-row sv-grid-detail-row"
1513
+ class:sv-grid-detail-row-sized={opt.detailRowHeight != null}
1514
+ style={opt.detailRowHeight != null ? `--sv-detail-h: ${ctrl.rowSizePxOf(detailRowIndex)}px; ${rowStyle}` : undefined}
1446
1515
  {...getGridRowA11yProps(detailRowIndex + 1)}
1447
1516
  >
1448
1517
  <td
1449
1518
  class="sv-grid-cell sv-grid-detail-cell"
1450
1519
  colspan={allColumns.length +
1451
1520
  (showRowNumbersEffective ? 1 : 0) +
1452
- (showRowSelectionEffective ? 1 : 0)}
1521
+ (showRowSelectionEffective ? 1 : 0) +
1522
+ (showDetailToggleEffective ? 1 : 0)}
1453
1523
  >
1454
1524
  {#if opt.renderDetailRow}
1455
- {@render opt.renderDetailRow({
1456
- row: detailRow.original as TData,
1457
- rowIndex: detailRowIndex,
1458
- })}
1525
+ {#if opt.detailRowHeight != null}
1526
+ <div class="sv-grid-detail-body">
1527
+ {@render opt.renderDetailRow({
1528
+ row: detailRow.original as TData,
1529
+ rowIndex: detailRowIndex,
1530
+ })}
1531
+ </div>
1532
+ {:else}
1533
+ {@render opt.renderDetailRow({
1534
+ row: detailRow.original as TData,
1535
+ rowIndex: detailRowIndex,
1536
+ })}
1537
+ {/if}
1459
1538
  {/if}
1460
1539
  </td>
1461
1540
  </tr>
1462
1541
  {/snippet}
1463
1542
 
1543
+ <!-- A row whose data has not arrived.
1544
+
1545
+ "loading" keeps the real columns and draws a shimmer bar in each,
1546
+ so the table keeps its shape while blocks stream in - a full-width
1547
+ bar would make the whole grid jump every time one landed. "failed"
1548
+ is the opposite case: there is nothing per-column to say, just one
1549
+ message and a way to try again, so it spans the row like a detail
1550
+ cell does. Neither is selectable or editable; there is nothing
1551
+ there to act on.
1552
+
1553
+ A failed block is a run of failed rows, one per row it claimed,
1554
+ and one message serves the run: the first failed row on screen
1555
+ carries it (the run's first row, or the top of the window when
1556
+ the run started above it) and the rows under it are a plain
1557
+ tinted band. A hundred "Could not load" lines for one block
1558
+ was a wall of red. -->
1559
+ {#snippet placeholderRowMarkup(
1560
+ phRow: Row<TData>,
1561
+ phRowIndex: number,
1562
+ phState: "loading" | "failed",
1563
+ phStyle: string,
1564
+ phContinues: boolean,
1565
+ )}
1566
+ <tr
1567
+ class="sv-grid-row sv-grid-placeholder-row"
1568
+ class:sv-grid-placeholder-failed={phState === "failed"}
1569
+ class:sv-grid-placeholder-failed-cont={phContinues}
1570
+ aria-busy={phState === "loading" ? "true" : undefined}
1571
+ {...getGridRowA11yProps(phRowIndex + 1)}
1572
+ style={phStyle}
1573
+ >
1574
+ {#if phState === "failed"}
1575
+ <td
1576
+ class="sv-grid-cell sv-grid-placeholder-cell"
1577
+ colspan={allColumns.length +
1578
+ (showRowNumbersEffective ? 1 : 0) +
1579
+ (showRowSelectionEffective ? 1 : 0) +
1580
+ (showDetailToggleEffective ? 1 : 0)}
1581
+ >
1582
+ {#if !phContinues}
1583
+ <div class="sv-grid-placeholder-failed-body">
1584
+ <span class="sv-grid-placeholder-error">{messages.rowLoadFailed}</span>
1585
+ {#if opt.onRetryRow}
1586
+ <button
1587
+ type="button"
1588
+ class="sv-grid-placeholder-retry"
1589
+ onclick={() => ctrl.retryRow(phRow)}>{messages.rowLoadRetry}</button
1590
+ >
1591
+ {/if}
1592
+ </div>
1593
+ {/if}
1594
+ </td>
1595
+ {:else}
1596
+ {#if showRowNumbersEffective}
1597
+ <td class="sv-grid-cell sv-grid-rownum-cell"></td>
1598
+ {/if}
1599
+ {#if showRowSelectionEffective}
1600
+ <td class="sv-grid-cell sv-grid-select-cell"></td>
1601
+ {/if}
1602
+ {#if showDetailToggleEffective}
1603
+ <td class="sv-grid-cell sv-grid-detail-toggle-cell"></td>
1604
+ {/if}
1605
+ {#each allColumns as col (col.id)}
1606
+ <td class="sv-grid-cell sv-grid-placeholder-cell">
1607
+ <span class="sv-grid-placeholder-skeleton" aria-hidden="true"></span>
1608
+ </td>
1609
+ {/each}
1610
+ {/if}
1611
+ </tr>
1612
+ {/snippet}
1613
+
1464
1614
  <!-- A single pinned row (top or bottom). Read-only by design: no
1465
1615
  inline editing, no row-selection checkbox, no fill handle.
1466
1616
  Position-sticky CSS keeps it anchored to the top of the body or
@@ -1474,15 +1624,24 @@
1474
1624
  the virtualizer's measured size, or the declared one.
1475
1625
  -->
1476
1626
  {#snippet bodyRow(row: Row<TData>, rowIndex: number, rowStyle: string)}
1477
- {#if opt.isDetailRow?.(row.original as TData, rowIndex)}
1478
- {@render detailRowMarkup(row, rowIndex)}
1627
+ {@const phState = ctrl.placeholderStateOf(row)}
1628
+ {#if phState}
1629
+ {@const phContinues =
1630
+ phState === "failed" &&
1631
+ rowIndex > firstRenderedRowIndex &&
1632
+ ctrl.placeholderStateOf(allRows[rowIndex - 1]!) === "failed"}
1633
+ {@render placeholderRowMarkup(row, rowIndex, phState, rowStyle, phContinues)}
1634
+ {:else if opt.isDetailRow?.(row.original as TData, rowIndex)}
1635
+ {@render detailRowMarkup(row, rowIndex, rowStyle)}
1479
1636
  {:else if isGroupRow(row) && !groupColumnMode}
1637
+ {@const stickyTop = stickyTopOfRow(rowIndex)}
1480
1638
  <tr
1481
1639
  class="sv-grid-row sv-grid-group-row"
1482
1640
  class:sv-grid-row-selected={isRowSelected(row.id)}
1483
1641
  class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1484
1642
  class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1485
- style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1643
+ class:sv-grid-row-sticky-group={stickyTop !== undefined}
1644
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : stickyTop !== undefined ? `${stickyTop}px` : undefined}
1486
1645
  aria-level={row.depth + 1}
1487
1646
  aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1488
1647
  {...getGridRowA11yProps(rowIndex + 1)}
@@ -1495,7 +1654,8 @@
1495
1654
  rowIndex}
1496
1655
  colspan={allColumns.length +
1497
1656
  (showRowNumbersEffective ? 1 : 0) +
1498
- (showRowSelectionEffective ? 1 : 0)}
1657
+ (showRowSelectionEffective ? 1 : 0) +
1658
+ (showDetailToggleEffective ? 1 : 0)}
1499
1659
  onclick={() => row.toggleExpanded?.()}
1500
1660
  >
1501
1661
  {@render groupRowContent(row)}
@@ -1503,6 +1663,7 @@
1503
1663
  </tr>
1504
1664
  {:else}
1505
1665
  {@const userRowClass = computeRowClass(row, rowIndex)}
1666
+ {@const stickyTop = stickyTopOfRow(rowIndex)}
1506
1667
  <tr
1507
1668
  class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
1508
1669
  class:sv-grid-row-selected={isRowSelected(row.id)}
@@ -1514,7 +1675,8 @@
1514
1675
  class:sv-grid-row-auto-height={autoRowHeightOn}
1515
1676
  class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1516
1677
  class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1517
- style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1678
+ class:sv-grid-row-sticky-group={stickyTop !== undefined}
1679
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : stickyTop !== undefined ? `${stickyTop}px` : undefined}
1518
1680
  {...getGridRowA11yProps(rowIndex + 1)}
1519
1681
  aria-level={sgAriaLevel(row)}
1520
1682
  aria-expanded={sgAriaExpanded(row)}
@@ -1526,14 +1688,14 @@
1526
1688
  {#if showRowNumbersEffective}
1527
1689
  <td
1528
1690
  class="sv-grid-cell sv-grid-row-number-cell"
1529
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1691
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
1530
1692
  >{rowIndex + 1}</td
1531
1693
  >
1532
1694
  {/if}
1533
1695
  {#if showRowSelectionEffective}
1534
1696
  <td
1535
1697
  class="sv-grid-cell sv-grid-selection-cell"
1536
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
1698
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
1537
1699
  onclick={() => toggleRowSelectionById(row.id)}
1538
1700
  >
1539
1701
  <button
@@ -1554,6 +1716,26 @@
1554
1716
  ></button>
1555
1717
  </td>
1556
1718
  {/if}
1719
+ {#if showDetailToggleEffective}
1720
+ {@const dt = detailToggleOf(row, rowIndex)}
1721
+ <td
1722
+ class="sv-grid-cell sv-grid-detail-toggle-cell"
1723
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
1724
+ >
1725
+ {#if dt}
1726
+ <button
1727
+ type="button"
1728
+ class="sv-grid-detail-toggle"
1729
+ aria-expanded={dt.open ? "true" : "false"}
1730
+ aria-label={dt.open ? messages.closeDetail : messages.openDetail}
1731
+ onclick={(event) => {
1732
+ event.stopPropagation();
1733
+ dt.toggle();
1734
+ }}>{@render icon("chevron-right")}</button
1735
+ >
1736
+ {/if}
1737
+ </td>
1738
+ {/if}
1557
1739
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
1558
1740
  <td
1559
1741
  class="sv-grid-cell sv-grid-cell-spacer"
@@ -1767,6 +1949,90 @@
1767
1949
  </tr>
1768
1950
  {/if}
1769
1951
  {/snippet}
1952
+ <!-- A sticky copy of an ancestor group row, for the band under the header
1953
+ while its rows scroll past. The real row is elsewhere in the flow (or
1954
+ outside the rendered window), so this one carries no cell ids, no
1955
+ row index attributes and is hidden from assistive tech; the group
1956
+ cell's own expander still works, since it is the same renderer. -->
1957
+ {#snippet stickyGroupRow(row: Row<TData>, rowIndex: number, top: number, size: number, last: boolean)}
1958
+ {#if isGroupRow(row) && !groupColumnMode}
1959
+ <tr
1960
+ class="sv-grid-row sv-grid-group-row sv-grid-row-sticky-group"
1961
+ class:sv-grid-row-sticky-group-last={last}
1962
+ style:top={`${top}px`}
1963
+ style={rowHeightStyle(size)}
1964
+ aria-hidden="true"
1965
+ >
1966
+ <td
1967
+ class="sv-grid-cell sv-grid-group-cell"
1968
+ colspan={allColumns.length +
1969
+ (showRowNumbersEffective ? 1 : 0) +
1970
+ (showRowSelectionEffective ? 1 : 0) +
1971
+ (showDetailToggleEffective ? 1 : 0)}
1972
+ onclick={() => row.toggleExpanded?.()}
1973
+ >
1974
+ {@render groupRowContent(row)}
1975
+ </td>
1976
+ </tr>
1977
+ {:else}
1978
+ <tr
1979
+ class={`sv-grid-row sv-grid-row-sticky-group ${computeRowClass(row, rowIndex)}`}
1980
+ class:sv-grid-row-sticky-group-last={last}
1981
+ style:top={`${top}px`}
1982
+ style={rowHeightStyle(size)}
1983
+ aria-hidden="true"
1984
+ >
1985
+ {#if showRowNumbersEffective}
1986
+ <td
1987
+ class="sv-grid-cell sv-grid-row-number-cell"
1988
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1989
+ >{rowIndex + 1}</td
1990
+ >
1991
+ {/if}
1992
+ {#if showRowSelectionEffective}
1993
+ <td
1994
+ class="sv-grid-cell sv-grid-selection-cell"
1995
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
1996
+ ></td>
1997
+ {/if}
1998
+ {#if showDetailToggleEffective}
1999
+ <td
2000
+ class="sv-grid-cell sv-grid-detail-toggle-cell"
2001
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
2002
+ ></td>
2003
+ {/if}
2004
+ {#if columnVirtualizationEnabled && columnWindowStart > 0}
2005
+ <td
2006
+ class="sv-grid-cell sv-grid-cell-spacer"
2007
+ style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
2008
+ ></td>
2009
+ {/if}
2010
+ {#each renderedColumns as rendered (rendered.column.id)}
2011
+ {@const cellValue = getCellDisplayValue(row.id, rendered.column.id, getColumnBaseValue(row, rendered.column))}
2012
+ <td
2013
+ class={`sv-grid-cell ${computeCellClass(row, rendered.column)}`}
2014
+ class:sv-grid-cell-cf={hasConditionalFormats}
2015
+ data-col-id={rendered.column.id}
2016
+ data-align={getColumnAlign(rendered.column)}
2017
+ data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
2018
+ style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
2019
+ >
2020
+ {#if plainCellBody}
2021
+ {@render cellBody(row, rendered.column, cellValue)}
2022
+ {:else}
2023
+ {@render cellBodyWithFormat(row, rendered.column, cellValue)}
2024
+ {/if}
2025
+ </td>
2026
+ {/each}
2027
+ {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
2028
+ <td
2029
+ class="sv-grid-cell sv-grid-cell-spacer"
2030
+ style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
2031
+ ></td>
2032
+ {/if}
2033
+ </tr>
2034
+ {/if}
2035
+ {/snippet}
1770
2036
  {#snippet pinnedRowBody(
1771
2037
  rowData: TData,
1772
2038
  where: "top" | "bottom",
@@ -1780,14 +2046,20 @@
1780
2046
  {#if showRowNumbersEffective}
1781
2047
  <td
1782
2048
  class="sv-grid-cell sv-grid-row-number-cell"
1783
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2049
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
1784
2050
  >{#if where === "top"}{@render icon("pinned-row-top")}{:else}{@render icon("pinned-row-bottom")}{/if}</td
1785
2051
  >
1786
2052
  {/if}
1787
2053
  {#if showRowSelectionEffective}
1788
2054
  <td
1789
2055
  class="sv-grid-cell sv-grid-selection-cell"
1790
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2056
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2057
+ ></td>
2058
+ {/if}
2059
+ {#if showDetailToggleEffective}
2060
+ <td
2061
+ class="sv-grid-cell sv-grid-detail-toggle-cell"
2062
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
1791
2063
  ></td>
1792
2064
  {/if}
1793
2065
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
@@ -1806,6 +2078,7 @@
1806
2078
  <td
1807
2079
  class={`sv-grid-cell ${userCellClass}`}
1808
2080
  data-col-id={rendered.column.id}
2081
+ data-align={getColumnAlign(rendered.column)}
1809
2082
  data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
1810
2083
  style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
1811
2084
  >{formatPinnedValue(rendered.column, value)}</td
@@ -1824,10 +2097,11 @@
1824
2097
  <div
1825
2098
  class="sv-grid-root"
1826
2099
  class:sv-grid-root-fill={opt.containerHeight === "100%"}
2100
+ class:sv-grid-active-seeded={!ctrl.userHasActivatedCell}
1827
2101
  style={chartDockReserveStyle}
1828
2102
  data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
1829
2103
  data-selbar={
1830
- ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0
2104
+ ctrl.selectionBarVisible
1831
2105
  ? (getSelectionBarView() ? ctrl.selectionBarPosition : `upsell-${ctrl.selectionBarPosition}`)
1832
2106
  : undefined
1833
2107
  }
@@ -1955,14 +2229,21 @@
1955
2229
  {#if showRowNumbersEffective}
1956
2230
  <th
1957
2231
  class="sv-grid-column sv-grid-row-number-column"
1958
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2232
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
1959
2233
  aria-hidden="true"
1960
2234
  ></th>
1961
2235
  {/if}
1962
2236
  {#if showRowSelectionEffective}
1963
2237
  <th
1964
2238
  class="sv-grid-column sv-grid-selection-column"
1965
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2239
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2240
+ aria-hidden="true"
2241
+ ></th>
2242
+ {/if}
2243
+ {#if showDetailToggleEffective}
2244
+ <th
2245
+ class="sv-grid-column sv-grid-detail-toggle-column"
2246
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
1966
2247
  aria-hidden="true"
1967
2248
  ></th>
1968
2249
  {/if}
@@ -2030,9 +2311,14 @@
2030
2311
  {#if showRowNumbersEffective}
2031
2312
  <th
2032
2313
  class="sv-grid-column sv-grid-row-number-column"
2033
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2034
- aria-label="Row number"
2314
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
2035
2315
  >
2316
+ <!-- The corner over the row gutter. Its only content is an
2317
+ icon, which is aria-hidden, so the name comes from a
2318
+ visually-hidden span rather than an aria-label: axe's
2319
+ `empty-table-header` asks a th for text a screen
2320
+ reader can read, and a label attribute is not it. -->
2321
+ <span class="sv-grid-sr-only">Row number</span>
2036
2322
  <span class="sv-grid-row-number-head"
2037
2323
  >{@render icon("row-number")}</span
2038
2324
  >
@@ -2041,7 +2327,7 @@
2041
2327
  {#if showRowSelectionEffective}
2042
2328
  <th
2043
2329
  class="sv-grid-column sv-grid-selection-column"
2044
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2330
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2045
2331
  >
2046
2332
  <button
2047
2333
  type="button"
@@ -2059,6 +2345,13 @@
2059
2345
  ></button>
2060
2346
  </th>
2061
2347
  {/if}
2348
+ {#if showDetailToggleEffective}
2349
+ <th
2350
+ class="sv-grid-column sv-grid-detail-toggle-column"
2351
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
2352
+ aria-hidden="true"
2353
+ ></th>
2354
+ {/if}
2062
2355
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
2063
2356
  <th
2064
2357
  class="sv-grid-column sv-grid-column-spacer"
@@ -2287,7 +2580,7 @@
2287
2580
  {#if showRowNumbersEffective}
2288
2581
  <th
2289
2582
  class="sv-grid-column sv-grid-row-number-column"
2290
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2583
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
2291
2584
  ></th>
2292
2585
  {/if}
2293
2586
  {#if showRowSelectionEffective}
@@ -2297,7 +2590,14 @@
2297
2590
  axe `empty-table-header` violation. -->
2298
2591
  <th
2299
2592
  class="sv-grid-column sv-grid-selection-column"
2300
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2593
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2594
+ aria-hidden="true"
2595
+ ></th>
2596
+ {/if}
2597
+ {#if showDetailToggleEffective}
2598
+ <th
2599
+ class="sv-grid-column sv-grid-detail-toggle-column"
2600
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
2301
2601
  aria-hidden="true"
2302
2602
  ></th>
2303
2603
  {/if}
@@ -2528,7 +2828,8 @@
2528
2828
  class="sv-grid-cell sv-grid-empty-cell"
2529
2829
  colSpan={allColumns.length +
2530
2830
  (showRowNumbersEffective ? 1 : 0) +
2531
- (showRowSelectionEffective ? 1 : 0)}
2831
+ (showRowSelectionEffective ? 1 : 0) +
2832
+ (showDetailToggleEffective ? 1 : 0)}
2532
2833
  >
2533
2834
  {opt.emptyMessage ?? messages.noRows}
2534
2835
  </td>
@@ -2537,6 +2838,9 @@
2537
2838
  {#each frozenRowList as row, rowIndex (row.id)}
2538
2839
  {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2539
2840
  {/each}
2841
+ {#each stickyGroupRows as sticky, k (`sticky:${sticky.row.id}`)}
2842
+ {@render stickyGroupRow(sticky.row, sticky.rowIndex, sticky.top, sticky.size, k === stickyGroupRows.length - 1)}
2843
+ {/each}
2540
2844
  {#if rowTopSpacer > 0}
2541
2845
  <tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
2542
2846
  <td
@@ -2544,7 +2848,8 @@
2544
2848
  style={`height: ${rowTopSpacer}px; padding: 0; border: 0;`}
2545
2849
  colSpan={allColumns.length +
2546
2850
  (showRowNumbersEffective ? 1 : 0) +
2547
- (showRowSelectionEffective ? 1 : 0)}
2851
+ (showRowSelectionEffective ? 1 : 0) +
2852
+ (showDetailToggleEffective ? 1 : 0)}
2548
2853
  ></td>
2549
2854
  </tr>
2550
2855
  {/if}
@@ -2568,7 +2873,8 @@
2568
2873
  style={`height: ${rowBottomSpacer}px; padding: 0; border: 0;`}
2569
2874
  colSpan={allColumns.length +
2570
2875
  (showRowNumbersEffective ? 1 : 0) +
2571
- (showRowSelectionEffective ? 1 : 0)}
2876
+ (showRowSelectionEffective ? 1 : 0) +
2877
+ (showDetailToggleEffective ? 1 : 0)}
2572
2878
  ></td>
2573
2879
  </tr>
2574
2880
  {/if}
@@ -2602,7 +2908,7 @@
2602
2908
  <!-- Blank spacer, not a header - see the selection cell below. -->
2603
2909
  <td
2604
2910
  class="sv-grid-column sv-grid-summary-column sv-grid-row-number-column"
2605
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2911
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
2606
2912
  ></td>
2607
2913
  {/if}
2608
2914
  {#if showRowSelectionEffective}
@@ -2612,7 +2918,13 @@
2612
2918
  (axe `empty-table-header`). -->
2613
2919
  <td
2614
2920
  class="sv-grid-column sv-grid-summary-column sv-grid-selection-column"
2615
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2921
+ style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; inset-inline-start: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2922
+ ></td>
2923
+ {/if}
2924
+ {#if showDetailToggleEffective}
2925
+ <td
2926
+ class="sv-grid-column sv-grid-summary-column sv-grid-detail-toggle-column"
2927
+ style={`width: ${detailToggleColumnWidth}px; min-width: ${detailToggleColumnWidth}px; max-width: ${detailToggleColumnWidth}px; left: ${detailToggleColumnLeft}px;`}
2616
2928
  ></td>
2617
2929
  {/if}
2618
2930
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
@@ -2695,9 +3007,7 @@
2695
3007
  {/if}
2696
3008
  {#if hasMeasured && hasHorizontalOverflow}
2697
3009
  {@const horizontalContentSize =
2698
- totalColumnWidth +
2699
- (showRowNumbersEffective ? rowNumberColumnWidth : 0) +
2700
- (showRowSelectionEffective ? selectionColumnWidth : 0)}
3010
+ totalColumnWidth + ctrl.systemColumnsWidth}
2701
3011
  <sv-grid-scrollbar
2702
3012
  class="sv-grid-scrollbar sv-grid-scrollbar-horizontal"
2703
3013
  bind:this={ctrl.horizontalScrollbarEl}
@@ -3004,7 +3314,7 @@
3004
3314
  `absolute` against it, so moving it outside would anchor it to the
3005
3315
  page. The renderer is Enterprise and arrives through the
3006
3316
  selection-bar-view seam; without it, a short note in its place. -->
3007
- {#if ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0}
3317
+ {#if ctrl.selectionBarVisible}
3008
3318
  {#if getSelectionBarView()}
3009
3319
  {@const SelectionBarView = getSelectionBarView()}
3010
3320
  <SelectionBarView {ctrl} />