@svgrid/grid 3.0.5 → 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 (148) hide show
  1. package/README.md +28 -7
  2. package/dist/GridMenus.svelte +34 -4
  3. package/dist/SvGrid.controller.svelte.d.ts +21 -3
  4. package/dist/SvGrid.controller.svelte.js +205 -50
  5. package/dist/SvGrid.css +127 -2
  6. package/dist/SvGrid.svelte +223 -38
  7. package/dist/SvGrid.types.d.ts +77 -4
  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 +81 -31
  15. package/dist/cdn/{GridMenus-CID62rlI.js → GridMenus-C-NtjlrJ.js} +150 -141
  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-CGoWz-87.js → server-block-cache-DIqb3VZn.js} +4 -0
  45. package/dist/cdn/{src-jcFMkBnN.js → src-CgoeSM8k.js} +4697 -4131
  46. package/dist/cdn/{src-Bklofsad.js → src-DfwxB1TH.js} +4727 -4161
  47. package/dist/cdn/svgrid.js +17 -17
  48. package/dist/cdn/svgrid.svelte-external.js +17 -17
  49. package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
  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 +365 -25
  59. package/dist/editing.js +24 -0
  60. package/dist/grid-messages.d.ts +4 -0
  61. package/dist/grid-messages.js +4 -0
  62. package/dist/headless.d.ts +5 -0
  63. package/dist/headless.js +12 -0
  64. package/dist/history.d.ts +21 -0
  65. package/dist/history.js +34 -4
  66. package/dist/index.d.ts +1 -1
  67. package/dist/keyboard-handlers.js +24 -1
  68. package/dist/keyboard.js +4 -0
  69. package/dist/row-drag-touch.d.ts +1 -1
  70. package/dist/row-drag.js +35 -1
  71. package/dist/row-model.d.ts +6 -0
  72. package/dist/row-resize.js +22 -1
  73. package/dist/selection.js +1 -1
  74. package/dist/server-block-cache.d.ts +5 -0
  75. package/dist/server-block-cache.js +8 -0
  76. package/dist/server-data-source.d.ts +35 -3
  77. package/dist/server-data-source.js +11 -2
  78. package/dist/server.d.ts +1 -1
  79. package/dist/sparkline.js +7 -5
  80. package/dist/svgrid-wrapper.types.d.ts +33 -0
  81. package/dist/validate.js +9 -1
  82. package/package.json +6 -1
  83. package/src/GridMenus.svelte +34 -4
  84. package/src/SvGrid.controller.svelte.ts +206 -53
  85. package/src/SvGrid.css +127 -2
  86. package/src/SvGrid.svelte +223 -38
  87. package/src/SvGrid.types.ts +77 -4
  88. package/src/SvGridDropdown.svelte +20 -0
  89. package/src/SvModal.svelte +22 -0
  90. package/src/SvModal.test.ts +30 -1
  91. package/src/a11y/dismissable.test.ts +10 -1
  92. package/src/a11y/dismissable.ts +22 -2
  93. package/src/build-api.ts +85 -27
  94. package/src/cell-formatting.test.ts +17 -0
  95. package/src/cell-formatting.ts +18 -1
  96. package/src/cell-render.test.ts +24 -2
  97. package/src/cell-render.ts +24 -8
  98. package/src/clipboard.test.ts +107 -0
  99. package/src/clipboard.ts +62 -23
  100. package/src/columns.test.ts +4 -4
  101. package/src/columns.ts +7 -2
  102. package/src/conditional-formatting.test.ts +8 -0
  103. package/src/conditional-formatting.ts +3 -0
  104. package/src/core.tick-repair.test.ts +287 -0
  105. package/src/core.ts +374 -26
  106. package/src/editing.test.ts +52 -0
  107. package/src/editing.ts +25 -0
  108. package/src/grid-messages.test.ts +7 -0
  109. package/src/grid-messages.ts +10 -0
  110. package/src/headless.ts +24 -0
  111. package/src/history.test.ts +49 -1
  112. package/src/history.ts +48 -2
  113. package/src/index.ts +2 -0
  114. package/src/keyboard-handlers.coverage.test.ts +65 -0
  115. package/src/keyboard-handlers.ts +26 -1
  116. package/src/keyboard.ts +3 -0
  117. package/src/row-drag-touch.ts +2 -2
  118. package/src/row-drag.test.ts +51 -0
  119. package/src/row-drag.ts +35 -2
  120. package/src/row-model.ts +6 -0
  121. package/src/row-resize.ts +24 -1
  122. package/src/selection.ts +1 -1
  123. package/src/server-block-cache.test.ts +27 -0
  124. package/src/server-block-cache.ts +13 -0
  125. package/src/server-data-source.infinite.test.ts +62 -0
  126. package/src/server-data-source.test.ts +19 -0
  127. package/src/server-data-source.ts +48 -4
  128. package/src/server.ts +2 -0
  129. package/src/sparkline.test.ts +7 -0
  130. package/src/sparkline.ts +6 -4
  131. package/src/svgrid-wrapper.types.ts +32 -0
  132. package/src/svgrid.api.test.ts +21 -0
  133. package/src/svgrid.charting.test.ts +12 -4
  134. package/src/svgrid.context-menu.test.ts +12 -0
  135. package/src/svgrid.detail-rows.svelte.test.ts +187 -0
  136. package/src/svgrid.filter-menu-scroll.test.ts +9 -0
  137. package/src/svgrid.gantt-seam.test.ts +1 -1
  138. package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
  139. package/src/svgrid.menu-scroll-close.test.ts +10 -0
  140. package/src/svgrid.row-model-seam.svelte.test.ts +16 -0
  141. package/src/svgrid.row-pinning.test.ts +15 -1
  142. package/src/svgrid.selection-bar-seam.test.ts +1 -1
  143. package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
  144. package/src/svgrid.upsell-license.test.ts +1 -1
  145. package/src/test-setup.ts +12 -0
  146. package/src/transaction.test.ts +58 -0
  147. package/src/validate.ts +7 -1
  148. package/dist/cdn/GridMenus-DLL-qZeu.js +0 -635
package/src/SvGrid.svelte CHANGED
@@ -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}
@@ -1999,6 +1999,15 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
1999
1999
  rowIndex: number;
2000
2000
  columnId: string;
2001
2001
  }) => unknown;
2002
+ /**
2003
+ * Whether a collapsed row still takes part in a copy or a fill that
2004
+ * spans it. Left out, both skip every collapsed row, which is what
2005
+ * "filter, copy, paste" needs and what a fill dragged over a filtered
2006
+ * list expects: the series runs over the rows that show. A spreadsheet
2007
+ * answers per row: a row hidden by hand is copied and filled, as Excel
2008
+ * does, while a row a filter folded away is not.
2009
+ */
2010
+ includeCollapsedRows?: (rowIndex: number) => boolean;
2002
2011
  /**
2003
2012
  * An HTML rendering of what Ctrl+C copies, written to the clipboard as
2004
2013
  * `text/html` beside the TSV. Excel and Google Sheets read the HTML
@@ -2164,10 +2173,11 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2164
2173
  rows: TData[],
2165
2174
  ) => void;
2166
2175
  /**
2167
- * Fires whenever the cell-selection rectangle changes (mouse, keyboard,
2168
- * or `api.selectCells()`). `ranges` matches `api.getSelected()` -
2169
- * `[rowStart, colStart, rowEnd, colEnd]` rectangles in grid coords.
2170
- * Empty array when the user clears the selection.
2176
+ * Fires whenever the cell selection changes (mouse, keyboard, or
2177
+ * `api.selectCells()`). `ranges` is every selected rectangle, matching
2178
+ * `api.getSelected()`: the ones a Ctrl+click committed and the active one
2179
+ * last, each `[rowStart, colStart, rowEnd, colEnd]` in grid coords. Empty
2180
+ * array when the user clears the selection.
2171
2181
  */
2172
2182
  onCellSelectionChange?: (
2173
2183
  ranges: Array<[number, number, number, number]>,
@@ -2486,6 +2496,35 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2486
2496
  * `isDetailRow` is true. Receives the row's data and its index.
2487
2497
  */
2488
2498
  renderDetailRow?: Snippet<[{ row: TData; rowIndex: number }]>;
2499
+ /**
2500
+ * The height of a detail row, in px, or a function of its data. With it
2501
+ * the virtualizer can size detail rows, so master-detail works with
2502
+ * `virtualization` on (the panel scrolls inside its cell when taller).
2503
+ * Without it a detail row is auto height, which needs
2504
+ * `virtualization={false}`.
2505
+ */
2506
+ detailRowHeight?: number | ((row: TData) => number);
2507
+ /**
2508
+ * A row-header column of chevrons that open and close detail rows. It
2509
+ * sits with the row-number and selection columns: sticky at the left,
2510
+ * no column menu, not resizable, not reorderable, outside the active
2511
+ * cell and the selection. The chevron calls `onDetailToggle` (or, on a
2512
+ * server row model, the model's own toggle) and reads `isDetailOpen`
2513
+ * for its direction; `hasDetail` hides it on rows that have nothing to
2514
+ * open. Detail rows themselves get an empty cell.
2515
+ */
2516
+ showDetailToggle?: boolean;
2517
+ /**
2518
+ * Open or close the detail under a row. The chevron of `showDetailToggle`
2519
+ * and Ctrl+Enter on a row call it; you keep the open set and insert or
2520
+ * remove the detail row in `data`. A server row model supplies its own
2521
+ * (the `rowModel` prop wires `toggleDetail`), so this is the client side.
2522
+ */
2523
+ onDetailToggle?: (row: TData, rowIndex: number) => void;
2524
+ /** Whether the detail under a row is open: the chevron's direction and `aria-expanded`. */
2525
+ isDetailOpen?: (row: TData) => boolean;
2526
+ /** Whether a row has a detail to open; the chevron is left out where this returns false. */
2527
+ hasDetail?: (row: TData) => boolean;
2489
2528
  /**
2490
2529
  * Server-side group / tree keyboard + accessibility, built into the grid. When
2491
2530
  * set, the grid uses the treegrid role and marks matching rows with
@@ -2504,6 +2543,12 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2504
2543
  expanded?: (row: TData) => boolean;
2505
2544
  /** Expand / collapse a group row. Called on ArrowRight / ArrowLeft. */
2506
2545
  onToggle: (row: TData) => void;
2546
+ /** Open / close the detail panel under a leaf. Called on Ctrl+Enter when `renderDetailRow` is set, and by the `showDetailToggle` chevron. */
2547
+ toggleDetail?: (row: TData) => void;
2548
+ /** Whether the detail under a leaf is open (the chevron's direction). */
2549
+ detailOpen?: (row: TData) => boolean;
2550
+ /** Whether a row can open a detail at all; a group, a placeholder or a detail row cannot. */
2551
+ hasDetail?: (row: TData) => boolean;
2507
2552
  };
2508
2553
  /**
2509
2554
  * Server-side set-filter values. When set, a column's filter checklist is
@@ -2531,6 +2576,17 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2531
2576
  * skips them.
2532
2577
  */
2533
2578
  frozenRows?: number;
2579
+ /**
2580
+ * Keep the group a row belongs to in view: while the rows of an open
2581
+ * group scroll past, the group's row (and its parents' rows) stay under
2582
+ * the header, the way a section heading stays put in a long list, so a
2583
+ * screen deep inside a 60,000-row group still says which group it is.
2584
+ * Works for server-side groups (`rowModel` / `serverGroup`), client
2585
+ * grouping and tree data. Under `virtualization` the band shows a copy
2586
+ * of each ancestor row; without it the rows themselves stick. Off by
2587
+ * default.
2588
+ */
2589
+ stickyGroupRows?: boolean;
2534
2590
  /**
2535
2591
  * Merged cells: rectangles drawn as one cell, a spreadsheet's Merge &
2536
2592
  * Center. Each entry is the top-left cell (display indices) and how many
@@ -2607,6 +2663,23 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
2607
2663
  fromGridId: number;
2608
2664
  toGridId: number;
2609
2665
  }) => void;
2666
+ /**
2667
+ * Take the drop yourself. With this set the grid keeps the drag
2668
+ * affordance and the drop indicator but does not touch its data: the
2669
+ * handler gets the dragged row, the row it landed on (null for the
2670
+ * empty space below the rows) and the side - `before` / `after`, or
2671
+ * `into` when it landed on the middle of a group or tree row, which
2672
+ * makes it that row's child. The path for a `rowModel`, whose rows the
2673
+ * grid does not own: hand the move to the model (`moveRow`) or to your
2674
+ * server. Only fires within one grid; a drop from another grid stays
2675
+ * managed.
2676
+ */
2677
+ onRowDrop?: (event: {
2678
+ row: TData;
2679
+ target: TData | null;
2680
+ targetIndex: number | null;
2681
+ side: "before" | "after" | "into";
2682
+ }) => void;
2610
2683
  /**
2611
2684
  * Align this grid with others that share the same non-empty
2612
2685
  * `alignedGridGroup` string: horizontal scroll and column-resize widths are