@svgrid/grid 3.0.5 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +28 -7
  2. package/dist/GridMenus.svelte +34 -4
  3. package/dist/SvGrid.controller.svelte.d.ts +23 -3
  4. package/dist/SvGrid.controller.svelte.js +235 -54
  5. package/dist/SvGrid.css +143 -7
  6. package/dist/SvGrid.helpers.js +2 -4
  7. package/dist/SvGrid.svelte +223 -38
  8. package/dist/SvGrid.types.d.ts +77 -4
  9. package/dist/SvGridDropdown.svelte +20 -0
  10. package/dist/SvGridDropdown.svelte.d.ts +12 -0
  11. package/dist/SvModal.svelte +22 -0
  12. package/dist/SvModal.svelte.d.ts +7 -0
  13. package/dist/a11y/dismissable.d.ts +10 -0
  14. package/dist/a11y/dismissable.js +24 -2
  15. package/dist/build-api.js +81 -31
  16. package/dist/cdn/{GridMenus-CID62rlI.js → GridMenus-BgUS-W3o.js} +151 -142
  17. package/dist/cdn/GridMenus-CcwU8I4t.js +644 -0
  18. package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
  19. package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
  20. package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
  21. package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
  22. package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-9yz-SbdZ.js} +2 -2
  23. package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-D7-ql1Hd.js} +2 -2
  24. package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DMUOFxOe.js} +4 -4
  25. package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-N7ik0oWy.js} +4 -4
  26. package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-CB_Jnm2k.js} +4 -4
  27. package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-Ca7gF8ZK.js} +4 -4
  28. package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-9GOJRHP7.js} +5 -5
  29. package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-Dl-FujN8.js} +5 -5
  30. package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-B1LFgoPP.js} +2 -2
  31. package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-By3n7DGZ.js} +2 -2
  32. package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
  33. package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
  34. package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
  35. package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
  36. package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-Mnv84zoE.js} +28 -6
  37. package/dist/cdn/{chart-Dz7SqMXH.js → chart-By7h2Guv.js} +1 -1
  38. package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-BC-tOgFY.js} +1 -1
  39. package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DUaXr-sb.js} +1 -1
  40. package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-BDtrNEiA.js} +1 -1
  41. package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-C1aOAPBZ.js} +1 -1
  42. package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
  43. package/dist/cdn/{editor-registry-BhgE3S84.js → editor-registry-BVeHvlZR.js} +49 -50
  44. package/dist/cdn/{export-format-CUDLy2yn.js → export-format-y1wdyCcv.js} +93 -97
  45. package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
  46. package/dist/cdn/{server-block-cache-CGoWz-87.js → server-block-cache-DIqb3VZn.js} +4 -0
  47. package/dist/cdn/{src-jcFMkBnN.js → src-25JmJ_7g.js} +4768 -4149
  48. package/dist/cdn/{src-Bklofsad.js → src-kE9B98r6.js} +4796 -4177
  49. package/dist/cdn/svgrid.js +18 -18
  50. package/dist/cdn/svgrid.svelte-external.js +18 -18
  51. package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
  52. package/dist/cell-formatting.d.ts +9 -0
  53. package/dist/cell-formatting.js +52 -1
  54. package/dist/cell-render.js +25 -6
  55. package/dist/clipboard.d.ts +2 -2
  56. package/dist/clipboard.js +72 -23
  57. package/dist/columns.js +7 -2
  58. package/dist/conditional-formatting.d.ts +2 -0
  59. package/dist/conditional-formatting.js +2 -0
  60. package/dist/core.d.ts +22 -6
  61. package/dist/core.js +478 -40
  62. package/dist/editing.js +24 -0
  63. package/dist/export-format.d.ts +8 -0
  64. package/dist/export-format.js +23 -16
  65. package/dist/grid-messages.d.ts +4 -0
  66. package/dist/grid-messages.js +4 -0
  67. package/dist/headless.d.ts +5 -0
  68. package/dist/headless.js +12 -0
  69. package/dist/history.d.ts +21 -0
  70. package/dist/history.js +34 -4
  71. package/dist/index.d.ts +1 -1
  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 +6 -0
  77. package/dist/row-resize.js +22 -1
  78. package/dist/selection.js +1 -1
  79. package/dist/server-block-cache.d.ts +5 -0
  80. package/dist/server-block-cache.js +8 -0
  81. package/dist/server-data-source.d.ts +35 -3
  82. package/dist/server-data-source.js +11 -2
  83. package/dist/server.d.ts +1 -1
  84. package/dist/sparkline.js +7 -5
  85. package/dist/svgrid-wrapper.types.d.ts +33 -0
  86. package/dist/validate.js +9 -1
  87. package/dist/virtualization/column-virtualizer.d.ts +1 -0
  88. package/dist/virtualization/svelte-virtualizer.svelte.d.ts +1 -0
  89. package/dist/virtualization/svelte-virtualizer.svelte.js +1 -0
  90. package/dist/virtualization/virtualizer.d.ts +3 -0
  91. package/dist/virtualization/virtualizer.js +14 -1
  92. package/package.json +6 -1
  93. package/src/GridMenus.svelte +34 -4
  94. package/src/SvGrid.controller.svelte.ts +236 -57
  95. package/src/SvGrid.css +143 -7
  96. package/src/SvGrid.helpers.ts +2 -4
  97. package/src/SvGrid.svelte +223 -38
  98. package/src/SvGrid.types.ts +77 -4
  99. package/src/SvGridDropdown.svelte +20 -0
  100. package/src/SvModal.svelte +22 -0
  101. package/src/SvModal.test.ts +30 -1
  102. package/src/a11y/dismissable.test.ts +10 -1
  103. package/src/a11y/dismissable.ts +22 -2
  104. package/src/build-api.ts +85 -27
  105. package/src/cell-formatting.test.ts +17 -0
  106. package/src/cell-formatting.ts +49 -1
  107. package/src/cell-render.test.ts +24 -2
  108. package/src/cell-render.ts +24 -8
  109. package/src/clipboard.test.ts +107 -0
  110. package/src/clipboard.ts +62 -23
  111. package/src/columns.test.ts +4 -4
  112. package/src/columns.ts +7 -2
  113. package/src/conditional-formatting.test.ts +8 -0
  114. package/src/conditional-formatting.ts +3 -0
  115. package/src/core.sort.test.ts +86 -0
  116. package/src/core.tick-repair.test.ts +287 -0
  117. package/src/core.ts +478 -40
  118. package/src/editing.test.ts +52 -0
  119. package/src/editing.ts +25 -0
  120. package/src/export-dates.test.ts +114 -0
  121. package/src/export-format.ts +26 -14
  122. package/src/grid-messages.test.ts +7 -0
  123. package/src/grid-messages.ts +10 -0
  124. package/src/headless.ts +24 -0
  125. package/src/history.test.ts +49 -1
  126. package/src/history.ts +48 -2
  127. package/src/index.ts +2 -0
  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 +6 -0
  135. package/src/row-resize.ts +24 -1
  136. package/src/selection.ts +1 -1
  137. package/src/server-block-cache.test.ts +27 -0
  138. package/src/server-block-cache.ts +13 -0
  139. package/src/server-data-source.infinite.test.ts +62 -0
  140. package/src/server-data-source.test.ts +19 -0
  141. package/src/server-data-source.ts +48 -4
  142. package/src/server.ts +2 -0
  143. package/src/sparkline.test.ts +7 -0
  144. package/src/sparkline.ts +6 -4
  145. package/src/svgrid-wrapper.types.ts +32 -0
  146. package/src/svgrid.api.test.ts +21 -0
  147. package/src/svgrid.charting.test.ts +12 -4
  148. package/src/svgrid.context-menu.test.ts +12 -0
  149. package/src/svgrid.data-push.svelte.test.ts +94 -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 +1 -1
  153. package/src/svgrid.live-update-paths.svelte.test.ts +186 -0
  154. package/src/svgrid.menu-scroll-close.test.ts +10 -0
  155. package/src/svgrid.row-model-seam.svelte.test.ts +16 -0
  156. package/src/svgrid.row-pinning.test.ts +15 -1
  157. package/src/svgrid.selection-bar-seam.test.ts +1 -1
  158. package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
  159. package/src/svgrid.upsell-license.test.ts +1 -1
  160. package/src/test-setup.ts +12 -0
  161. package/src/transaction.test.ts +58 -0
  162. package/src/validate.ts +7 -1
  163. package/src/virtualization/svelte-virtualizer.svelte.ts +1 -0
  164. package/src/virtualization/virtualizer.test.ts +44 -0
  165. package/src/virtualization/virtualizer.ts +13 -1
  166. package/dist/cdn/GridMenus-DLL-qZeu.js +0 -635
@@ -214,6 +214,10 @@
214
214
  const columnResizeEnabled = $derived(opt.columnResize === true);
215
215
  const selectionColumnWidth = $derived(ctrl.selectionColumnWidth);
216
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);
217
221
  const showRowNumbersEffective = $derived(ctrl.showRowNumbersEffective);
218
222
  const columnMenuFor = $derived(ctrl.columnMenuFor);
219
223
  const filterMenuFor = $derived(ctrl.filterMenuFor);
@@ -276,7 +280,9 @@
276
280
  if (!rowDragManagedEffective || rowDropIndex !== rowIndex) return "";
277
281
  return rowDropSide === "after"
278
282
  ? "sv-grid-row-drop-after"
279
- : "sv-grid-row-drop-before";
283
+ : rowDropSide === "into"
284
+ ? "sv-grid-row-drop-into"
285
+ : "sv-grid-row-drop-before";
280
286
  }
281
287
  const getColumnBaseValue = $derived(ctrl.getColumnBaseValue);
282
288
  const hasConditionalFormats = $derived(ctrl.hasConditionalFormats);
@@ -419,6 +425,8 @@
419
425
  );
420
426
  const frozenRowCount = $derived(ctrl.frozenRowCount);
421
427
  const frozenRowList = $derived(ctrl.frozenRowList);
428
+ const stickyGroupRows = $derived(ctrl.stickyGroupRows);
429
+ const stickyTopOfRow = $derived(ctrl.stickyTopOfRow);
422
430
  // Merged cells. A merge is drawn once per band (the frozen rows, the
423
431
  // scrolling body), by the top-left cell of the part the band shows, with
424
432
  // its spans clamped to the rendered window: a td never spans into rows
@@ -545,12 +553,17 @@
545
553
  // normal cell path and their aggregates land under the right columns; only
546
554
  // the class distinguishes them. Typed here rather than cast inline - an `as`
547
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.
548
559
  const isGroupFooterRow = (row: Row<TData>): boolean =>
549
- (row as { __groupFooter?: boolean }).__groupFooter === true;
560
+ (row as { __groupFooter?: boolean }).__groupFooter === true ||
561
+ (row.original as { __groupFooter?: boolean } | null)?.__groupFooter === true;
550
562
  // The grand total is a group footer that closes the whole set rather than one
551
563
  // group, so it carries both classes - shared chrome, heavier top rule.
552
564
  const isGrandTotalRow = (row: Row<TData>): boolean =>
553
- (row as { __grandTotal?: boolean }).__grandTotal === true;
565
+ (row as { __grandTotal?: boolean }).__grandTotal === true ||
566
+ (row.original as { __grandTotal?: boolean } | null)?.__grandTotal === true;
554
567
  const computeCellClass = $derived(ctrl.computeCellClass);
555
568
  const computeCellTooltip = $derived(ctrl.computeCellTooltip);
556
569
  const computeCellValidity = $derived(ctrl.computeCellValidity);
@@ -778,9 +791,9 @@
778
791
  than four copies: four gates worded four ways is how a product ends up
779
792
  explaining its own licensing inconsistently.
780
793
 
781
- Enterprise is SOFT-gated - the feature runs without a key and the grid shows
782
- an unlicensed watermark - so this says exactly that rather than implying the
783
- 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.
784
797
  -->
785
798
  {#snippet enterpriseLicenseNote()}
786
799
  <p class="sv-grid-upsell-license">
@@ -1460,7 +1473,7 @@
1460
1473
  type="button"
1461
1474
  class="sv-grid-group-toggle"
1462
1475
  aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1463
- aria-label={row.getIsExpanded?.() ? "Collapse group" : "Expand group"}
1476
+ aria-label={row.getIsExpanded?.() ? messages.collapseGroup : messages.expandGroup}
1464
1477
  onclick={(event) => {
1465
1478
  event.stopPropagation();
1466
1479
  row.toggleExpanded?.();
@@ -1468,7 +1481,7 @@
1468
1481
  >
1469
1482
  <span class="sv-grid-group-label">{headerLabel}: {groupValue}</span>
1470
1483
  <span class="sv-grid-group-count"
1471
- >{count} {count === 1 ? "row" : "rows"}</span
1484
+ >{count} {count === 1 ? messages.rowSuffix : messages.rowsSuffix}</span
1472
1485
  >
1473
1486
  {#each allColumns as col (col.id)}
1474
1487
  {#if col.columnDef.aggregate && col.id !== groupingColumnId}
@@ -1491,22 +1504,37 @@
1491
1504
  <!-- A full-width detail row: one colspan cell spanning every column,
1492
1505
  hosting the consumer's `renderDetailRow` snippet. Auto height (no
1493
1506
  fixed row height) so the panel grows to fit its content. -->
1494
- {#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. -->
1495
1511
  <tr
1496
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}
1497
1515
  {...getGridRowA11yProps(detailRowIndex + 1)}
1498
1516
  >
1499
1517
  <td
1500
1518
  class="sv-grid-cell sv-grid-detail-cell"
1501
1519
  colspan={allColumns.length +
1502
1520
  (showRowNumbersEffective ? 1 : 0) +
1503
- (showRowSelectionEffective ? 1 : 0)}
1521
+ (showRowSelectionEffective ? 1 : 0) +
1522
+ (showDetailToggleEffective ? 1 : 0)}
1504
1523
  >
1505
1524
  {#if opt.renderDetailRow}
1506
- {@render opt.renderDetailRow({
1507
- row: detailRow.original as TData,
1508
- rowIndex: detailRowIndex,
1509
- })}
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}
1510
1538
  {/if}
1511
1539
  </td>
1512
1540
  </tr>
@@ -1548,7 +1576,8 @@
1548
1576
  class="sv-grid-cell sv-grid-placeholder-cell"
1549
1577
  colspan={allColumns.length +
1550
1578
  (showRowNumbersEffective ? 1 : 0) +
1551
- (showRowSelectionEffective ? 1 : 0)}
1579
+ (showRowSelectionEffective ? 1 : 0) +
1580
+ (showDetailToggleEffective ? 1 : 0)}
1552
1581
  >
1553
1582
  {#if !phContinues}
1554
1583
  <div class="sv-grid-placeholder-failed-body">
@@ -1570,6 +1599,9 @@
1570
1599
  {#if showRowSelectionEffective}
1571
1600
  <td class="sv-grid-cell sv-grid-select-cell"></td>
1572
1601
  {/if}
1602
+ {#if showDetailToggleEffective}
1603
+ <td class="sv-grid-cell sv-grid-detail-toggle-cell"></td>
1604
+ {/if}
1573
1605
  {#each allColumns as col (col.id)}
1574
1606
  <td class="sv-grid-cell sv-grid-placeholder-cell">
1575
1607
  <span class="sv-grid-placeholder-skeleton" aria-hidden="true"></span>
@@ -1600,14 +1632,16 @@
1600
1632
  ctrl.placeholderStateOf(allRows[rowIndex - 1]!) === "failed"}
1601
1633
  {@render placeholderRowMarkup(row, rowIndex, phState, rowStyle, phContinues)}
1602
1634
  {:else if opt.isDetailRow?.(row.original as TData, rowIndex)}
1603
- {@render detailRowMarkup(row, rowIndex)}
1635
+ {@render detailRowMarkup(row, rowIndex, rowStyle)}
1604
1636
  {:else if isGroupRow(row) && !groupColumnMode}
1637
+ {@const stickyTop = stickyTopOfRow(rowIndex)}
1605
1638
  <tr
1606
1639
  class="sv-grid-row sv-grid-group-row"
1607
1640
  class:sv-grid-row-selected={isRowSelected(row.id)}
1608
1641
  class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1609
1642
  class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1610
- 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}
1611
1645
  aria-level={row.depth + 1}
1612
1646
  aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1613
1647
  {...getGridRowA11yProps(rowIndex + 1)}
@@ -1620,7 +1654,8 @@
1620
1654
  rowIndex}
1621
1655
  colspan={allColumns.length +
1622
1656
  (showRowNumbersEffective ? 1 : 0) +
1623
- (showRowSelectionEffective ? 1 : 0)}
1657
+ (showRowSelectionEffective ? 1 : 0) +
1658
+ (showDetailToggleEffective ? 1 : 0)}
1624
1659
  onclick={() => row.toggleExpanded?.()}
1625
1660
  >
1626
1661
  {@render groupRowContent(row)}
@@ -1628,6 +1663,7 @@
1628
1663
  </tr>
1629
1664
  {:else}
1630
1665
  {@const userRowClass = computeRowClass(row, rowIndex)}
1666
+ {@const stickyTop = stickyTopOfRow(rowIndex)}
1631
1667
  <tr
1632
1668
  class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
1633
1669
  class:sv-grid-row-selected={isRowSelected(row.id)}
@@ -1639,7 +1675,8 @@
1639
1675
  class:sv-grid-row-auto-height={autoRowHeightOn}
1640
1676
  class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1641
1677
  class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1642
- 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}
1643
1680
  {...getGridRowA11yProps(rowIndex + 1)}
1644
1681
  aria-level={sgAriaLevel(row)}
1645
1682
  aria-expanded={sgAriaExpanded(row)}
@@ -1651,14 +1688,14 @@
1651
1688
  {#if showRowNumbersEffective}
1652
1689
  <td
1653
1690
  class="sv-grid-cell sv-grid-row-number-cell"
1654
- 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;`}
1655
1692
  >{rowIndex + 1}</td
1656
1693
  >
1657
1694
  {/if}
1658
1695
  {#if showRowSelectionEffective}
1659
1696
  <td
1660
1697
  class="sv-grid-cell sv-grid-selection-cell"
1661
- 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;`}
1662
1699
  onclick={() => toggleRowSelectionById(row.id)}
1663
1700
  >
1664
1701
  <button
@@ -1679,6 +1716,26 @@
1679
1716
  ></button>
1680
1717
  </td>
1681
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}
1682
1739
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
1683
1740
  <td
1684
1741
  class="sv-grid-cell sv-grid-cell-spacer"
@@ -1892,6 +1949,90 @@
1892
1949
  </tr>
1893
1950
  {/if}
1894
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}
1895
2036
  {#snippet pinnedRowBody(
1896
2037
  rowData: TData,
1897
2038
  where: "top" | "bottom",
@@ -1905,14 +2046,20 @@
1905
2046
  {#if showRowNumbersEffective}
1906
2047
  <td
1907
2048
  class="sv-grid-cell sv-grid-row-number-cell"
1908
- 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;`}
1909
2050
  >{#if where === "top"}{@render icon("pinned-row-top")}{:else}{@render icon("pinned-row-bottom")}{/if}</td
1910
2051
  >
1911
2052
  {/if}
1912
2053
  {#if showRowSelectionEffective}
1913
2054
  <td
1914
2055
  class="sv-grid-cell sv-grid-selection-cell"
1915
- 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;`}
1916
2063
  ></td>
1917
2064
  {/if}
1918
2065
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
@@ -1931,6 +2078,7 @@
1931
2078
  <td
1932
2079
  class={`sv-grid-cell ${userCellClass}`}
1933
2080
  data-col-id={rendered.column.id}
2081
+ data-align={getColumnAlign(rendered.column)}
1934
2082
  data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
1935
2083
  style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
1936
2084
  >{formatPinnedValue(rendered.column, value)}</td
@@ -1949,6 +2097,7 @@
1949
2097
  <div
1950
2098
  class="sv-grid-root"
1951
2099
  class:sv-grid-root-fill={opt.containerHeight === "100%"}
2100
+ class:sv-grid-active-seeded={!ctrl.userHasActivatedCell}
1952
2101
  style={chartDockReserveStyle}
1953
2102
  data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
1954
2103
  data-selbar={
@@ -2080,14 +2229,21 @@
2080
2229
  {#if showRowNumbersEffective}
2081
2230
  <th
2082
2231
  class="sv-grid-column sv-grid-row-number-column"
2083
- 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;`}
2084
2233
  aria-hidden="true"
2085
2234
  ></th>
2086
2235
  {/if}
2087
2236
  {#if showRowSelectionEffective}
2088
2237
  <th
2089
2238
  class="sv-grid-column sv-grid-selection-column"
2090
- 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;`}
2091
2247
  aria-hidden="true"
2092
2248
  ></th>
2093
2249
  {/if}
@@ -2155,9 +2311,14 @@
2155
2311
  {#if showRowNumbersEffective}
2156
2312
  <th
2157
2313
  class="sv-grid-column sv-grid-row-number-column"
2158
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2159
- aria-label="Row number"
2314
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; inset-inline-start: 0;`}
2160
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>
2161
2322
  <span class="sv-grid-row-number-head"
2162
2323
  >{@render icon("row-number")}</span
2163
2324
  >
@@ -2166,7 +2327,7 @@
2166
2327
  {#if showRowSelectionEffective}
2167
2328
  <th
2168
2329
  class="sv-grid-column sv-grid-selection-column"
2169
- 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;`}
2170
2331
  >
2171
2332
  <button
2172
2333
  type="button"
@@ -2184,6 +2345,13 @@
2184
2345
  ></button>
2185
2346
  </th>
2186
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}
2187
2355
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
2188
2356
  <th
2189
2357
  class="sv-grid-column sv-grid-column-spacer"
@@ -2412,7 +2580,7 @@
2412
2580
  {#if showRowNumbersEffective}
2413
2581
  <th
2414
2582
  class="sv-grid-column sv-grid-row-number-column"
2415
- 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;`}
2416
2584
  ></th>
2417
2585
  {/if}
2418
2586
  {#if showRowSelectionEffective}
@@ -2422,7 +2590,14 @@
2422
2590
  axe `empty-table-header` violation. -->
2423
2591
  <th
2424
2592
  class="sv-grid-column sv-grid-selection-column"
2425
- 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;`}
2426
2601
  aria-hidden="true"
2427
2602
  ></th>
2428
2603
  {/if}
@@ -2653,7 +2828,8 @@
2653
2828
  class="sv-grid-cell sv-grid-empty-cell"
2654
2829
  colSpan={allColumns.length +
2655
2830
  (showRowNumbersEffective ? 1 : 0) +
2656
- (showRowSelectionEffective ? 1 : 0)}
2831
+ (showRowSelectionEffective ? 1 : 0) +
2832
+ (showDetailToggleEffective ? 1 : 0)}
2657
2833
  >
2658
2834
  {opt.emptyMessage ?? messages.noRows}
2659
2835
  </td>
@@ -2662,6 +2838,9 @@
2662
2838
  {#each frozenRowList as row, rowIndex (row.id)}
2663
2839
  {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2664
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}
2665
2844
  {#if rowTopSpacer > 0}
2666
2845
  <tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
2667
2846
  <td
@@ -2669,7 +2848,8 @@
2669
2848
  style={`height: ${rowTopSpacer}px; padding: 0; border: 0;`}
2670
2849
  colSpan={allColumns.length +
2671
2850
  (showRowNumbersEffective ? 1 : 0) +
2672
- (showRowSelectionEffective ? 1 : 0)}
2851
+ (showRowSelectionEffective ? 1 : 0) +
2852
+ (showDetailToggleEffective ? 1 : 0)}
2673
2853
  ></td>
2674
2854
  </tr>
2675
2855
  {/if}
@@ -2693,7 +2873,8 @@
2693
2873
  style={`height: ${rowBottomSpacer}px; padding: 0; border: 0;`}
2694
2874
  colSpan={allColumns.length +
2695
2875
  (showRowNumbersEffective ? 1 : 0) +
2696
- (showRowSelectionEffective ? 1 : 0)}
2876
+ (showRowSelectionEffective ? 1 : 0) +
2877
+ (showDetailToggleEffective ? 1 : 0)}
2697
2878
  ></td>
2698
2879
  </tr>
2699
2880
  {/if}
@@ -2727,7 +2908,7 @@
2727
2908
  <!-- Blank spacer, not a header - see the selection cell below. -->
2728
2909
  <td
2729
2910
  class="sv-grid-column sv-grid-summary-column sv-grid-row-number-column"
2730
- 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;`}
2731
2912
  ></td>
2732
2913
  {/if}
2733
2914
  {#if showRowSelectionEffective}
@@ -2737,7 +2918,13 @@
2737
2918
  (axe `empty-table-header`). -->
2738
2919
  <td
2739
2920
  class="sv-grid-column sv-grid-summary-column sv-grid-selection-column"
2740
- 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;`}
2741
2928
  ></td>
2742
2929
  {/if}
2743
2930
  {#if columnVirtualizationEnabled && columnWindowStart > 0}
@@ -2820,9 +3007,7 @@
2820
3007
  {/if}
2821
3008
  {#if hasMeasured && hasHorizontalOverflow}
2822
3009
  {@const horizontalContentSize =
2823
- totalColumnWidth +
2824
- (showRowNumbersEffective ? rowNumberColumnWidth : 0) +
2825
- (showRowSelectionEffective ? selectionColumnWidth : 0)}
3010
+ totalColumnWidth + ctrl.systemColumnsWidth}
2826
3011
  <sv-grid-scrollbar
2827
3012
  class="sv-grid-scrollbar sv-grid-scrollbar-horizontal"
2828
3013
  bind:this={ctrl.horizontalScrollbarEl}
@@ -1952,6 +1952,15 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
1952
1952
  rowIndex: number;
1953
1953
  columnId: string;
1954
1954
  }) => unknown;
1955
+ /**
1956
+ * Whether a collapsed row still takes part in a copy or a fill that
1957
+ * spans it. Left out, both skip every collapsed row, which is what
1958
+ * "filter, copy, paste" needs and what a fill dragged over a filtered
1959
+ * list expects: the series runs over the rows that show. A spreadsheet
1960
+ * answers per row: a row hidden by hand is copied and filled, as Excel
1961
+ * does, while a row a filter folded away is not.
1962
+ */
1963
+ includeCollapsedRows?: (rowIndex: number) => boolean;
1955
1964
  /**
1956
1965
  * An HTML rendering of what Ctrl+C copies, written to the clipboard as
1957
1966
  * `text/html` beside the TSV. Excel and Google Sheets read the HTML
@@ -2115,10 +2124,11 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2115
2124
  */
2116
2125
  onRowSelectionChange?: (selection: Record<string, boolean>, rows: TData[]) => void;
2117
2126
  /**
2118
- * Fires whenever the cell-selection rectangle changes (mouse, keyboard,
2119
- * or `api.selectCells()`). `ranges` matches `api.getSelected()` -
2120
- * `[rowStart, colStart, rowEnd, colEnd]` rectangles in grid coords.
2121
- * Empty array when the user clears the selection.
2127
+ * Fires whenever the cell selection changes (mouse, keyboard, or
2128
+ * `api.selectCells()`). `ranges` is every selected rectangle, matching
2129
+ * `api.getSelected()`: the ones a Ctrl+click committed and the active one
2130
+ * last, each `[rowStart, colStart, rowEnd, colEnd]` in grid coords. Empty
2131
+ * array when the user clears the selection.
2122
2132
  */
2123
2133
  onCellSelectionChange?: (ranges: Array<[number, number, number, number]>) => void;
2124
2134
  /**
@@ -2448,6 +2458,35 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2448
2458
  row: TData;
2449
2459
  rowIndex: number;
2450
2460
  }]>;
2461
+ /**
2462
+ * The height of a detail row, in px, or a function of its data. With it
2463
+ * the virtualizer can size detail rows, so master-detail works with
2464
+ * `virtualization` on (the panel scrolls inside its cell when taller).
2465
+ * Without it a detail row is auto height, which needs
2466
+ * `virtualization={false}`.
2467
+ */
2468
+ detailRowHeight?: number | ((row: TData) => number);
2469
+ /**
2470
+ * A row-header column of chevrons that open and close detail rows. It
2471
+ * sits with the row-number and selection columns: sticky at the left,
2472
+ * no column menu, not resizable, not reorderable, outside the active
2473
+ * cell and the selection. The chevron calls `onDetailToggle` (or, on a
2474
+ * server row model, the model's own toggle) and reads `isDetailOpen`
2475
+ * for its direction; `hasDetail` hides it on rows that have nothing to
2476
+ * open. Detail rows themselves get an empty cell.
2477
+ */
2478
+ showDetailToggle?: boolean;
2479
+ /**
2480
+ * Open or close the detail under a row. The chevron of `showDetailToggle`
2481
+ * and Ctrl+Enter on a row call it; you keep the open set and insert or
2482
+ * remove the detail row in `data`. A server row model supplies its own
2483
+ * (the `rowModel` prop wires `toggleDetail`), so this is the client side.
2484
+ */
2485
+ onDetailToggle?: (row: TData, rowIndex: number) => void;
2486
+ /** Whether the detail under a row is open: the chevron's direction and `aria-expanded`. */
2487
+ isDetailOpen?: (row: TData) => boolean;
2488
+ /** Whether a row has a detail to open; the chevron is left out where this returns false. */
2489
+ hasDetail?: (row: TData) => boolean;
2451
2490
  /**
2452
2491
  * Server-side group / tree keyboard + accessibility, built into the grid. When
2453
2492
  * set, the grid uses the treegrid role and marks matching rows with
@@ -2466,6 +2505,12 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2466
2505
  expanded?: (row: TData) => boolean;
2467
2506
  /** Expand / collapse a group row. Called on ArrowRight / ArrowLeft. */
2468
2507
  onToggle: (row: TData) => void;
2508
+ /** Open / close the detail panel under a leaf. Called on Ctrl+Enter when `renderDetailRow` is set, and by the `showDetailToggle` chevron. */
2509
+ toggleDetail?: (row: TData) => void;
2510
+ /** Whether the detail under a leaf is open (the chevron's direction). */
2511
+ detailOpen?: (row: TData) => boolean;
2512
+ /** Whether a row can open a detail at all; a group, a placeholder or a detail row cannot. */
2513
+ hasDetail?: (row: TData) => boolean;
2469
2514
  };
2470
2515
  /**
2471
2516
  * Server-side set-filter values. When set, a column's filter checklist is
@@ -2493,6 +2538,17 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2493
2538
  * skips them.
2494
2539
  */
2495
2540
  frozenRows?: number;
2541
+ /**
2542
+ * Keep the group a row belongs to in view: while the rows of an open
2543
+ * group scroll past, the group's row (and its parents' rows) stay under
2544
+ * the header, the way a section heading stays put in a long list, so a
2545
+ * screen deep inside a 60,000-row group still says which group it is.
2546
+ * Works for server-side groups (`rowModel` / `serverGroup`), client
2547
+ * grouping and tree data. Under `virtualization` the band shows a copy
2548
+ * of each ancestor row; without it the rows themselves stick. Off by
2549
+ * default.
2550
+ */
2551
+ stickyGroupRows?: boolean;
2496
2552
  /**
2497
2553
  * Merged cells: rectangles drawn as one cell, a spreadsheet's Merge &
2498
2554
  * Center. Each entry is the top-left cell (display indices) and how many
@@ -2574,6 +2630,23 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2574
2630
  fromGridId: number;
2575
2631
  toGridId: number;
2576
2632
  }) => void;
2633
+ /**
2634
+ * Take the drop yourself. With this set the grid keeps the drag
2635
+ * affordance and the drop indicator but does not touch its data: the
2636
+ * handler gets the dragged row, the row it landed on (null for the
2637
+ * empty space below the rows) and the side - `before` / `after`, or
2638
+ * `into` when it landed on the middle of a group or tree row, which
2639
+ * makes it that row's child. The path for a `rowModel`, whose rows the
2640
+ * grid does not own: hand the move to the model (`moveRow`) or to your
2641
+ * server. Only fires within one grid; a drop from another grid stays
2642
+ * managed.
2643
+ */
2644
+ onRowDrop?: (event: {
2645
+ row: TData;
2646
+ target: TData | null;
2647
+ targetIndex: number | null;
2648
+ side: "before" | "after" | "into";
2649
+ }) => void;
2577
2650
  /**
2578
2651
  * Align this grid with others that share the same non-empty
2579
2652
  * `alignedGridGroup` string: horizontal scroll and column-resize widths are