@svgrid/grid 3.0.2 → 3.0.4

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 (281) hide show
  1. package/README.md +2 -1
  2. package/dist/GridMenus.svelte +44 -2
  3. package/dist/SvChartMenu.svelte +69 -0
  4. package/dist/SvChartMenu.svelte.d.ts +11 -0
  5. package/dist/SvChartPanes.svelte +113 -0
  6. package/dist/SvChartPanes.svelte.d.ts +20 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +110 -39
  8. package/dist/SvGrid.controller.svelte.js +655 -200
  9. package/dist/SvGrid.css +105 -2
  10. package/dist/SvGrid.svelte +386 -514
  11. package/dist/SvGrid.types.d.ts +210 -9
  12. package/dist/SvGridCellEditor.svelte +17 -0
  13. package/dist/SvGridChart.svelte +2257 -329
  14. package/dist/SvGridChart.svelte.d.ts +6 -106
  15. package/dist/SvGridChart.types.d.ts +338 -0
  16. package/dist/SvGridChart.types.js +1 -0
  17. package/dist/SvGridChartBuilder.svelte +323 -0
  18. package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
  19. package/dist/SvGridChartPanel.svelte +221 -275
  20. package/dist/SvGridChartPickers.svelte +417 -0
  21. package/dist/SvGridChartPickers.svelte.d.ts +70 -0
  22. package/dist/SvGridChartView.svelte +13 -14
  23. package/dist/SvModal.svelte +7 -1
  24. package/dist/SvModal.svelte.d.ts +5 -0
  25. package/dist/ai.d.ts +40 -8
  26. package/dist/ai.js +135 -24
  27. package/dist/aria.d.ts +11 -0
  28. package/dist/build-api.js +108 -22
  29. package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
  30. package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
  31. package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
  32. package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
  33. package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
  34. package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
  35. package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
  36. package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
  37. package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
  38. package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
  39. package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
  40. package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
  41. package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
  42. package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
  43. package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
  44. package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
  45. package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
  46. package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
  47. package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
  48. package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
  49. package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
  50. package/dist/cdn/SvModal-CIZWCcad.js +397 -0
  51. package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
  52. package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
  53. package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
  54. package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
  55. package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
  56. package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
  57. package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
  58. package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
  59. package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
  60. package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
  61. package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
  62. package/dist/cdn/dismissable-DAHetSNk.js +44 -0
  63. package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
  64. package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
  65. package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
  66. package/dist/cdn/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
  67. package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
  68. package/dist/cdn/svgrid.js +26 -15
  69. package/dist/cdn/svgrid.svelte-external.js +26 -15
  70. package/dist/chart-axes.d.ts +53 -0
  71. package/dist/chart-axes.js +351 -0
  72. package/dist/chart-cartesian.d.ts +88 -0
  73. package/dist/chart-cartesian.js +1862 -0
  74. package/dist/chart-decimate.d.ts +51 -0
  75. package/dist/chart-decimate.js +199 -0
  76. package/dist/chart-export-pdf.d.ts +47 -0
  77. package/dist/chart-export-pdf.js +187 -0
  78. package/dist/chart-export.d.ts +2 -1
  79. package/dist/chart-export.js +18 -3
  80. package/dist/chart-financial.d.ts +91 -0
  81. package/dist/chart-financial.js +175 -0
  82. package/dist/chart-flow.d.ts +10 -0
  83. package/dist/chart-flow.js +298 -0
  84. package/dist/chart-format.d.ts +18 -0
  85. package/dist/chart-format.js +65 -0
  86. package/dist/chart-grid.d.ts +8 -0
  87. package/dist/chart-grid.js +190 -0
  88. package/dist/chart-hierarchy.d.ts +17 -0
  89. package/dist/chart-hierarchy.js +146 -0
  90. package/dist/chart-indicators.d.ts +115 -0
  91. package/dist/chart-indicators.js +389 -0
  92. package/dist/chart-messages.d.ts +95 -0
  93. package/dist/chart-messages.js +92 -0
  94. package/dist/chart-motion.d.ts +16 -0
  95. package/dist/chart-motion.js +141 -0
  96. package/dist/chart-panel-messages.d.ts +265 -0
  97. package/dist/chart-panel-messages.js +285 -0
  98. package/dist/chart-pivot.d.ts +87 -0
  99. package/dist/chart-pivot.js +156 -0
  100. package/dist/chart-polar.d.ts +25 -0
  101. package/dist/chart-polar.js +700 -0
  102. package/dist/chart-samples.d.ts +16 -0
  103. package/dist/chart-samples.js +153 -0
  104. package/dist/chart-scale.d.ts +113 -0
  105. package/dist/chart-scale.js +405 -0
  106. package/dist/chart-stats.d.ts +111 -0
  107. package/dist/chart-stats.js +419 -0
  108. package/dist/chart-stream.d.ts +45 -0
  109. package/dist/chart-stream.js +91 -0
  110. package/dist/chart-summary.d.ts +27 -0
  111. package/dist/chart-summary.js +205 -0
  112. package/dist/chart-sync.svelte.d.ts +32 -0
  113. package/dist/chart-sync.svelte.js +23 -0
  114. package/dist/chart-table.d.ts +35 -0
  115. package/dist/chart-table.js +165 -0
  116. package/dist/chart-trend.d.ts +19 -0
  117. package/dist/chart-trend.js +61 -0
  118. package/dist/chart-types.d.ts +1456 -0
  119. package/dist/chart-types.js +6 -0
  120. package/dist/chart-validate.d.ts +43 -0
  121. package/dist/chart-validate.js +272 -0
  122. package/dist/chart-zoom.d.ts +40 -0
  123. package/dist/chart-zoom.js +145 -0
  124. package/dist/chart.d.ts +186 -865
  125. package/dist/chart.js +460 -2204
  126. package/dist/clipboard.d.ts +1 -0
  127. package/dist/clipboard.js +137 -18
  128. package/dist/column-resize.d.ts +3 -0
  129. package/dist/column-resize.js +4 -1
  130. package/dist/columns.js +3 -0
  131. package/dist/command-context.d.ts +19 -0
  132. package/dist/command-context.js +116 -0
  133. package/dist/core.d.ts +7 -0
  134. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  135. package/dist/editing.d.ts +2 -8
  136. package/dist/editing.js +262 -33
  137. package/dist/fill-patterns.d.ts +0 -5
  138. package/dist/fill-patterns.js +56 -2
  139. package/dist/grid-messages.d.ts +10 -3
  140. package/dist/grid-messages.js +5 -0
  141. package/dist/history.d.ts +83 -0
  142. package/dist/history.js +133 -0
  143. package/dist/index.d.ts +24 -6
  144. package/dist/index.js +20 -2
  145. package/dist/keyboard-handlers.js +88 -31
  146. package/dist/keyboard.d.ts +55 -0
  147. package/dist/keyboard.js +132 -0
  148. package/dist/menus.d.ts +3 -1
  149. package/dist/menus.js +7 -2
  150. package/dist/merges.d.ts +97 -0
  151. package/dist/merges.js +147 -0
  152. package/dist/row-resize.d.ts +3 -0
  153. package/dist/row-resize.js +14 -0
  154. package/dist/scroll-sync.d.ts +1 -0
  155. package/dist/scroll-sync.js +24 -0
  156. package/dist/selection.d.ts +16 -0
  157. package/dist/selection.js +108 -16
  158. package/dist/shortcut-registry.d.ts +145 -0
  159. package/dist/shortcut-registry.js +44 -0
  160. package/dist/svgrid-wrapper.types.d.ts +213 -22
  161. package/dist/virtualization/virtualizer.js +38 -17
  162. package/package.json +6 -1
  163. package/src/GridMenus.svelte +44 -2
  164. package/src/SvChartMenu.svelte +69 -0
  165. package/src/SvChartPanes.svelte +113 -0
  166. package/src/SvChartPanes.test.ts +66 -0
  167. package/src/SvGrid.controller.svelte.ts +629 -172
  168. package/src/SvGrid.css +105 -2
  169. package/src/SvGrid.svelte +386 -514
  170. package/src/SvGrid.types.ts +188 -8
  171. package/src/SvGridCellEditor.svelte +17 -0
  172. package/src/SvGridChart.svelte +2257 -329
  173. package/src/SvGridChart.test.ts +1419 -1
  174. package/src/SvGridChart.types.ts +336 -0
  175. package/src/SvGridChartBuilder.svelte +323 -0
  176. package/src/SvGridChartPanel.svelte +221 -275
  177. package/src/SvGridChartPickers.svelte +417 -0
  178. package/src/SvGridChartView.svelte +13 -14
  179. package/src/SvModal.svelte +7 -1
  180. package/src/SvModal.test.ts +9 -0
  181. package/src/ai.test.ts +57 -2
  182. package/src/ai.ts +166 -22
  183. package/src/aria.d.ts +11 -0
  184. package/src/build-api.coverage.test.ts +184 -0
  185. package/src/build-api.ts +107 -18
  186. package/src/chart-axes.test.ts +573 -0
  187. package/src/chart-axes.ts +342 -0
  188. package/src/chart-cartesian.ts +1863 -0
  189. package/src/chart-decimate.test.ts +249 -0
  190. package/src/chart-decimate.ts +186 -0
  191. package/src/chart-export-pdf.test.ts +91 -0
  192. package/src/chart-export-pdf.ts +207 -0
  193. package/src/chart-export.test.ts +14 -0
  194. package/src/chart-export.ts +13 -3
  195. package/src/chart-financial.ts +208 -0
  196. package/src/chart-flow.ts +286 -0
  197. package/src/chart-format.ts +67 -0
  198. package/src/chart-grid.ts +179 -0
  199. package/src/chart-hierarchy.ts +135 -0
  200. package/src/chart-indicators.test.ts +443 -0
  201. package/src/chart-indicators.ts +384 -0
  202. package/src/chart-messages.test.ts +21 -0
  203. package/src/chart-messages.ts +184 -0
  204. package/src/chart-motion.test.ts +71 -0
  205. package/src/chart-motion.ts +148 -0
  206. package/src/chart-panel-messages.test.ts +29 -0
  207. package/src/chart-panel-messages.ts +550 -0
  208. package/src/chart-pivot.test.ts +54 -0
  209. package/src/chart-pivot.ts +225 -0
  210. package/src/chart-polar.ts +674 -0
  211. package/src/chart-samples.ts +106 -0
  212. package/src/chart-scale.ts +394 -0
  213. package/src/chart-series-types.test.ts +714 -0
  214. package/src/chart-stats.ts +409 -0
  215. package/src/chart-stream.test.ts +75 -0
  216. package/src/chart-stream.ts +114 -0
  217. package/src/chart-summary.test.ts +90 -0
  218. package/src/chart-summary.ts +197 -0
  219. package/src/chart-sync.svelte.ts +41 -0
  220. package/src/chart-table.test.ts +76 -0
  221. package/src/chart-table.ts +178 -0
  222. package/src/chart-trend.ts +47 -0
  223. package/src/chart-types.ts +1309 -0
  224. package/src/chart-validate.test.ts +100 -0
  225. package/src/chart-validate.ts +247 -0
  226. package/src/chart-zoom.test.ts +137 -0
  227. package/src/chart-zoom.ts +124 -0
  228. package/src/chart.coverage.test.ts +81 -0
  229. package/src/chart.test.ts +56 -3
  230. package/src/chart.ts +557 -2784
  231. package/src/clipboard.test.ts +151 -0
  232. package/src/clipboard.ts +145 -16
  233. package/src/column-resize.ts +7 -1
  234. package/src/columns.ts +2 -0
  235. package/src/command-context.test.ts +200 -0
  236. package/src/command-context.ts +137 -0
  237. package/src/core.ts +7 -0
  238. package/src/editing.test.ts +281 -3
  239. package/src/editing.ts +256 -38
  240. package/src/fill-patterns.test.ts +35 -0
  241. package/src/fill-patterns.ts +56 -2
  242. package/src/grid-messages.ts +16 -3
  243. package/src/history.test.ts +196 -0
  244. package/src/history.ts +162 -0
  245. package/src/icon-seam.test.ts +3 -2
  246. package/src/index.ts +150 -2
  247. package/src/keyboard-handlers.coverage.test.ts +126 -6
  248. package/src/keyboard-handlers.ts +91 -30
  249. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  250. package/src/keyboard.test.ts +124 -1
  251. package/src/keyboard.ts +142 -0
  252. package/src/menus.test.ts +26 -0
  253. package/src/menus.ts +10 -3
  254. package/src/merges.test.ts +112 -0
  255. package/src/merges.ts +168 -0
  256. package/src/pivot.grid.test.ts +40 -5
  257. package/src/row-resize.test.ts +44 -0
  258. package/src/row-resize.ts +17 -0
  259. package/src/scroll-sync.test.ts +42 -1
  260. package/src/scroll-sync.ts +25 -0
  261. package/src/selection.multi-range.test.ts +34 -0
  262. package/src/selection.test.ts +30 -2
  263. package/src/selection.ts +111 -19
  264. package/src/shortcut-registry.test.ts +97 -0
  265. package/src/shortcut-registry.ts +181 -0
  266. package/src/svgrid-wrapper.types.ts +184 -13
  267. package/src/svgrid.behavior.test.ts +135 -0
  268. package/src/svgrid.charting.test.ts +513 -3
  269. package/src/svgrid.context-menu.test.ts +31 -0
  270. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  271. package/src/virtualization/virtualizer.ts +39 -17
  272. package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
  273. package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
  274. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  275. package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
  276. package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
  277. package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
  278. package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
  279. package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
  280. package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
  281. package/dist/cdn/chart-CKJRCPOk.js +0 -1652
@@ -3,7 +3,6 @@
3
3
  generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData"
4
4
  >
5
5
  import {
6
- getGridCellA11yProps,
7
6
  getGridCellDomId,
8
7
  getGridHeaderA11yProps,
9
8
  getGridRootA11yProps,
@@ -22,6 +21,7 @@
22
21
  RenderComponentConfig,
23
22
  } from "./render-component";
24
23
  import { buildSparkline, toSparklineValues } from "./sparkline";
24
+ import { mergeDrawAt } from "./merges";
25
25
  import { localizeOperatorLabel } from "./filter-operators";
26
26
  import { GRID_ICON_GLYPHS, type GridIconName } from "./grid-icons";
27
27
  // The whole editor UI - including the lazy SvGridDropdown / SvDateTimePicker
@@ -387,8 +387,9 @@
387
387
  );
388
388
 
389
389
  const rowSizePx = (i: number): number => {
390
- // A height the user dragged wins over the declared one, so the row stays
391
- // where they put it across re-renders and virtualization recycling.
390
+ // A height the user dragged, or set through the api, wins over the
391
+ // declared one, so the row stays where they put it across re-renders
392
+ // and virtualization recycling.
392
393
  const dragged = ctrl.rowResizeHeightPx(i);
393
394
  if (dragged != null) return dragged;
394
395
  const rh = opt.rowHeight;
@@ -402,10 +403,47 @@
402
403
  autoRowHeightOn
403
404
  ? `min-height: ${measuredRowHeightPx(i) ?? rowSizePx(i)}px;`
404
405
  : `height: ${rowSizePx(i)}px;`;
406
+ const collapsedColumns = $derived(ctrl.collapsedColumns);
405
407
  const columnVirtualizationEnabled = $derived(
406
408
  ctrl.columnVirtualizationEnabled,
407
409
  );
408
410
  const virtualRows = $derived(ctrl.virtualRows);
411
+ const frozenRowCount = $derived(ctrl.frozenRowCount);
412
+ const frozenRowList = $derived(ctrl.frozenRowList);
413
+ // Merged cells. A merge is drawn once per band (the frozen rows, the
414
+ // scrolling body), by the top-left cell of the part the band shows, with
415
+ // its spans clamped to the rendered window: a td never spans into rows
416
+ // the table has not drawn.
417
+ const mergeIndex = $derived(ctrl.mergeIndex);
418
+ const mergeColumnWindow = $derived.by(() => ({
419
+ firstCol: renderedColumns[0]?.item.index ?? 0,
420
+ lastCol: renderedColumns[renderedColumns.length - 1]?.item.index ?? -1,
421
+ }));
422
+ const mergeBodyWindow = $derived.by(() => {
423
+ const cols = mergeColumnWindow;
424
+ if (!rowVirtualizationEnabled) return { firstRow: frozenRowCount, lastRow: allRows.length - 1, ...cols };
425
+ let firstRow = Number.POSITIVE_INFINITY;
426
+ let lastRow = -1;
427
+ for (const item of virtualRows) {
428
+ if (item.index < frozenRowCount) continue;
429
+ if (item.index < firstRow) firstRow = item.index;
430
+ if (item.index > lastRow) lastRow = item.index;
431
+ }
432
+ return { firstRow: Number.isFinite(firstRow) ? firstRow : frozenRowCount, lastRow, ...cols };
433
+ });
434
+ const mergeFrozenWindow = $derived.by(() => ({ firstRow: 0, lastRow: frozenRowCount - 1, ...mergeColumnWindow }));
435
+ /** The width a td spanning `colSpan` rendered columns from `colIndex` takes. */
436
+ function mergedWidth(colIndex: number, colSpan: number, own: number): number {
437
+ if (colSpan <= 1) return own;
438
+ let total = 0;
439
+ for (const rendered of renderedColumns) {
440
+ const i = rendered.item.index;
441
+ if (i >= colIndex && i < colIndex + colSpan) total += rendered.item.size;
442
+ }
443
+ return total || own;
444
+ }
445
+ const frozenBandHeight = $derived(ctrl.frozenBandHeight);
446
+ const frozenRowTop = $derived(ctrl.frozenRowTop);
409
447
  const autoRowHeightOn = $derived(ctrl.autoRowHeightOn);
410
448
  const measureRowHeight = $derived(ctrl.measureRowHeight);
411
449
  const measuredRowHeightPx = $derived(ctrl.measuredRowHeightPx);
@@ -522,9 +560,8 @@
522
560
  const toggleSelectAllRows = $derived(ctrl.toggleSelectAllRows);
523
561
  const setActiveCell = $derived(ctrl.setActiveCell);
524
562
  const scrollActiveCellIntoView = $derived(ctrl.scrollActiveCellIntoView);
563
+ const cellSelectionState = $derived(ctrl.cellSelectionState);
525
564
  const getColumnWidth = $derived(ctrl.getColumnWidth);
526
- const getCellRangeEdges = $derived(ctrl.getCellRangeEdges);
527
- const fillHandleCell = $derived(ctrl.fillHandleCell);
528
565
  const isInFillPreview = $derived(ctrl.isInFillPreview);
529
566
  const fillMarqueeEdges = $derived(ctrl.fillMarqueeEdges);
530
567
  const startFillDrag = $derived(ctrl.startFillDrag);
@@ -755,6 +792,19 @@
755
792
  rotate-on-open both hang off it (SvGrid.css), and an override that
756
793
  dropped the class would lose both silently - which is the kind of bug
757
794
  that only shows up in someone else's app. -->
795
+ {#snippet chartToggle()}
796
+ <button
797
+ type="button"
798
+ class="sv-grid-toolbar-btn sv-grid-chart-toggle"
799
+ class:is-active={ctrl.chartPanelOpen}
800
+ aria-label={ctrl.chartPanelOpen ? "Close chart" : "Open chart"}
801
+ aria-expanded={ctrl.chartPanelOpen}
802
+ onclick={() => (ctrl.chartPanelOpen = !ctrl.chartPanelOpen)}
803
+ >
804
+ {@render icon("chart")}
805
+ Chart
806
+ </button>
807
+ {/snippet}
758
808
  {#snippet icon(name: GridIconName)}
759
809
  {@const override = opt.icons?.[name]}
760
810
  {#if override}
@@ -1046,6 +1096,9 @@
1046
1096
  </div>
1047
1097
  </div>
1048
1098
  {:else if pivotViewOn}
1099
+ <!-- The Chart panel docks here the way it docks beside the flat grid,
1100
+ and charts the pivot on screen: the root reserves the panel's edge and
1101
+ the pivot bar carries the toggle the toolbar carries in flat mode. -->
1049
1102
  <div
1050
1103
  class="sv-grid-root sv-grid-pivot-root"
1051
1104
  class:sv-grid-root-fill={opt.containerHeight === "100%"}
@@ -1053,7 +1106,7 @@
1053
1106
  typeof opt.containerHeight === "string"
1054
1107
  ? opt.containerHeight
1055
1108
  : `${opt.containerHeight ?? 520}px`
1056
- }; display: flex; flex-direction: column;`}
1109
+ }; display: flex; flex-direction: column; ${chartDockReserveStyle}`}
1057
1110
  >
1058
1111
  <div class="sv-grid-pivot-bar">
1059
1112
  <span class="sv-grid-pivot-badge">{messages.group}</span>
@@ -1063,6 +1116,9 @@
1063
1116
  aria-pressed="true"
1064
1117
  onclick={() => ctrl.togglePivotMode()}
1065
1118
  >{messages.pivotUpsellTitle}</button>
1119
+ {#if ctrl.chartingEnabled}
1120
+ {@render chartToggle()}
1121
+ {/if}
1066
1122
  </div>
1067
1123
  <div style="flex: 1 1 auto; min-height: 0;">
1068
1124
  {#if pivotResult}
@@ -1081,6 +1137,9 @@
1081
1137
  </div>
1082
1138
  {/if}
1083
1139
  </div>
1140
+ {#if ctrl.chartingEnabled && ctrl.chartPanelOpen && ChartPanelView}
1141
+ <ChartPanelView {ctrl} />
1142
+ {/if}
1084
1143
  </div>
1085
1144
  {:else}
1086
1145
 
@@ -1406,6 +1465,308 @@
1406
1465
  inline editing, no row-selection checkbox, no fill handle.
1407
1466
  Position-sticky CSS keeps it anchored to the top of the body or
1408
1467
  the bottom of the viewport while the rest scrolls. -->
1468
+ <!--
1469
+ One body row, whatever path renders it: the virtualized window, the
1470
+ plain full-list body, or the frozen rows pinned above the window. The
1471
+ two loops used to carry a copy each and had drifted apart (cell
1472
+ tooltips on one, the fill handle on the other), so a feature landed in
1473
+ one mode and not the other. `rowStyle` is the height the caller knows:
1474
+ the virtualizer's measured size, or the declared one.
1475
+ -->
1476
+ {#snippet bodyRow(row: Row<TData>, rowIndex: number, rowStyle: string)}
1477
+ {#if opt.isDetailRow?.(row.original as TData, rowIndex)}
1478
+ {@render detailRowMarkup(row, rowIndex)}
1479
+ {:else if isGroupRow(row) && !groupColumnMode}
1480
+ <tr
1481
+ class="sv-grid-row sv-grid-group-row"
1482
+ class:sv-grid-row-selected={isRowSelected(row.id)}
1483
+ class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1484
+ class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1485
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1486
+ aria-level={row.depth + 1}
1487
+ aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1488
+ {...getGridRowA11yProps(rowIndex + 1)}
1489
+ class:sv-grid-row-collapsed={ctrl.isRowCollapsed(rowIndex)}
1490
+ style={rowStyle}
1491
+ >
1492
+ <td
1493
+ class="sv-grid-cell sv-grid-group-cell"
1494
+ class:sv-grid-cell-active={activeCell.rowIndex ===
1495
+ rowIndex}
1496
+ colspan={allColumns.length +
1497
+ (showRowNumbersEffective ? 1 : 0) +
1498
+ (showRowSelectionEffective ? 1 : 0)}
1499
+ onclick={() => row.toggleExpanded?.()}
1500
+ >
1501
+ {@render groupRowContent(row)}
1502
+ </td>
1503
+ </tr>
1504
+ {:else}
1505
+ {@const userRowClass = computeRowClass(row, rowIndex)}
1506
+ <tr
1507
+ class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
1508
+ class:sv-grid-row-selected={isRowSelected(row.id)}
1509
+ class:sv-grid-row-alt={opt.zebraRows &&
1510
+ rowIndex % 2 === 1}
1511
+ class:sv-grid-row-draggable={rowDragManagedEffective}
1512
+ class:sv-grid-group-footer-row={isGroupFooterRow(row)}
1513
+ class:sv-grid-grand-total-row={isGrandTotalRow(row)}
1514
+ class:sv-grid-row-auto-height={autoRowHeightOn}
1515
+ class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1516
+ class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1517
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1518
+ {...getGridRowA11yProps(rowIndex + 1)}
1519
+ aria-level={sgAriaLevel(row)}
1520
+ aria-expanded={sgAriaExpanded(row)}
1521
+ {...rowDragAttrs(rowIndex)}
1522
+ class:sv-grid-row-collapsed={ctrl.isRowCollapsed(rowIndex)}
1523
+ style={rowStyle}
1524
+ use:measureRowHeight={rowIndex}
1525
+ >
1526
+ {#if showRowNumbersEffective}
1527
+ <td
1528
+ class="sv-grid-cell sv-grid-row-number-cell"
1529
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1530
+ >{rowIndex + 1}</td
1531
+ >
1532
+ {/if}
1533
+ {#if showRowSelectionEffective}
1534
+ <td
1535
+ 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;`}
1537
+ onclick={() => toggleRowSelectionById(row.id)}
1538
+ >
1539
+ <button
1540
+ type="button"
1541
+ class="sv-grid-checkbox"
1542
+ role="checkbox"
1543
+ aria-checked={isRowSelected(row.id)}
1544
+ aria-label="Select row"
1545
+ onclick={(event) => {
1546
+ event.stopPropagation();
1547
+ toggleRowSelectionById(row.id);
1548
+ }}
1549
+ onkeydown={(event) =>
1550
+ toggleCheckboxWithKeyboard(event, () => {
1551
+ event.stopPropagation();
1552
+ toggleRowSelectionById(row.id);
1553
+ })}
1554
+ ></button>
1555
+ </td>
1556
+ {/if}
1557
+ {#if columnVirtualizationEnabled && columnWindowStart > 0}
1558
+ <td
1559
+ class="sv-grid-cell sv-grid-cell-spacer"
1560
+ aria-hidden="true"
1561
+ style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
1562
+ ></td>
1563
+ {/if}
1564
+ {#each renderedColumns as rendered (rendered.column.id)}
1565
+ {@const colIndex = rendered.item.index}
1566
+ {@const draw = mergeIndex
1567
+ ? mergeDrawAt(mergeIndex, rowIndex, colIndex, rowIndex < frozenRowCount ? mergeFrozenWindow : mergeBodyWindow)
1568
+ : null}
1569
+ {#if draw?.kind !== "skip"}
1570
+ {@const cellRow = draw?.kind === "continuation" ? (allRows[draw.merge.rowIndex] ?? row) : row}
1571
+ {@const cellColumn = draw?.kind === "continuation" ? (allColumns[draw.merge.colIndex] ?? rendered.column) : rendered.column}
1572
+ {@const baseValue = getColumnBaseValue(
1573
+ cellRow,
1574
+ cellColumn,
1575
+ )}
1576
+ {@const cellValue = getCellDisplayValue(
1577
+ cellRow.id,
1578
+ cellColumn.id,
1579
+ baseValue,
1580
+ )}
1581
+ {@const isEditing =
1582
+ ctrl.editingCell?.rowId === cellRow.id &&
1583
+ ctrl.editingCell?.columnId === cellColumn.id}
1584
+ {@const inRowEdit =
1585
+ !!fullRowEdit &&
1586
+ fullRowEdit.rowId === cellRow.id &&
1587
+ cellColumn.id in fullRowEdit.draft}
1588
+ {@const cellSel = cellSelectionState(rowIndex, colIndex)}
1589
+ {@const fillEdges = fillMarqueeEdges(rowIndex, colIndex)}
1590
+ {@const userCellClass = computeCellClass(
1591
+ cellRow,
1592
+ cellColumn,
1593
+ )}
1594
+ {@const cellTooltip = computeCellTooltip(
1595
+ cellRow,
1596
+ cellColumn,
1597
+ )}
1598
+ {@const cellValidity = computeCellValidity(
1599
+ cellRow,
1600
+ cellColumn,
1601
+ )}
1602
+ {@const cellNote = computeCellNote(
1603
+ cellRow,
1604
+ cellColumn,
1605
+ )}
1606
+ {@const tdWidth = draw ? mergedWidth(colIndex, draw.colSpan, rendered.item.size) : rendered.item.size}
1607
+ <td
1608
+ rowspan={draw && draw.rowSpan > 1 ? draw.rowSpan : undefined}
1609
+ colspan={draw && draw.colSpan > 1 ? draw.colSpan : undefined}
1610
+ data-merge-origin={draw ? `${draw.merge.rowIndex}:${draw.merge.colIndex}` : undefined}
1611
+ class:sv-grid-cell-merged={!!draw}
1612
+ class={`sv-grid-cell ${userCellClass}`}
1613
+ class:sv-grid-cell-editing={isEditing || inRowEdit}
1614
+ class:sv-grid-cell-collapsed={!!collapsedColumns[rendered.column.id]}
1615
+ class:sv-grid-cell-active={!!cellSel?.active}
1616
+ class:sv-grid-cell-has-fill-handle={!!cellSel?.fillHandle}
1617
+ class:sv-grid-cell-cf={hasConditionalFormats}
1618
+ class:sv-grid-cell-invalid={cellValidity.invalid}
1619
+ class:sv-grid-cell-has-note={cellNote != null}
1620
+ aria-invalid={cellValidity.invalid ? "true" : undefined}
1621
+ data-svgrid-row={rowIndex}
1622
+ data-svgrid-col={colIndex}
1623
+ data-col-id={rendered.column.id}
1624
+ data-align={getColumnAlign(rendered.column)}
1625
+ data-pinned={isColumnPinned(rendered.column.id) ??
1626
+ undefined}
1627
+ data-selected-range={cellSel?.edges ? "true" : undefined}
1628
+ data-range-top={cellSel?.edges?.top ? "true" : undefined}
1629
+ data-range-bottom={cellSel?.edges?.bottom
1630
+ ? "true"
1631
+ : undefined}
1632
+ data-range-left={cellSel?.edges?.left
1633
+ ? "true"
1634
+ : undefined}
1635
+ data-range-right={cellSel?.edges?.right
1636
+ ? "true"
1637
+ : undefined}
1638
+ data-fill-preview={isInFillPreview(rowIndex, colIndex)
1639
+ ? "true"
1640
+ : undefined}
1641
+ data-fill-top={fillEdges?.top ? "true" : undefined}
1642
+ data-fill-bottom={fillEdges?.bottom ? "true" : undefined}
1643
+ data-fill-left={fillEdges?.left ? "true" : undefined}
1644
+ data-fill-right={fillEdges?.right ? "true" : undefined}
1645
+ style={`width: ${tdWidth}px; min-width: ${tdWidth}px; max-width: ${tdWidth}px; ${cellPinStyle(rendered.column.id)}`}
1646
+ onpointerdown={(event) =>
1647
+ onCellPointerDown(rowIndex, colIndex, event)}
1648
+ onpointerenter={(event) => {
1649
+ onCellPointerEnter(rowIndex, colIndex);
1650
+ // The column tooltip fires on whole-cell hover; a
1651
+ // per-cell note waits for its corner below. A
1652
+ // validation message wins over the column's tooltip.
1653
+ const tip =
1654
+ cellValidity.invalid && cellValidity.message
1655
+ ? cellValidity.message
1656
+ : cellTooltip;
1657
+ if (tip)
1658
+ showTooltipFor(
1659
+ event.currentTarget as HTMLElement,
1660
+ tip,
1661
+ );
1662
+ }}
1663
+ onpointerleave={hideTooltip}
1664
+ ondblclick={() =>
1665
+ emitCellDoubleClick(rowIndex, colIndex)}
1666
+ onclick={() => onCellClick(rowIndex, colIndex)}
1667
+ oncontextmenu={(event) =>
1668
+ openContextMenu(
1669
+ event,
1670
+ rowIndex,
1671
+ colIndex,
1672
+ rendered.column.id,
1673
+ )}
1674
+ use:cellFlashAction={{
1675
+ rowId: cellRow.id,
1676
+ value: cellValue,
1677
+ active: !!cellColumn.columnDef.cellFlash,
1678
+ className: flashClassFor(
1679
+ cellColumn.columnDef.cellFlash,
1680
+ ),
1681
+ }}
1682
+ role="gridcell"
1683
+ id={getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex)}
1684
+ aria-colindex={colIndex + 1}
1685
+ aria-rowindex={rowIndex + 1}
1686
+ aria-selected={isRowSelected(row.id)}
1687
+ >
1688
+ {#if inRowEdit || isEditing}
1689
+ <!-- The editing cell stays empty until the lazy
1690
+ editor chunk lands, exactly as it already did
1691
+ for the dropdown / date editors. Falling back
1692
+ to the read-only value here would flash stale
1693
+ content into a cell the user is editing. -->
1694
+ {#if CellEditor}
1695
+ <CellEditor {ctrl} column={cellColumn} row={cellRow} fullRow={inRowEdit} />
1696
+ {/if}
1697
+ {:else}
1698
+ {#if plainCellBody}
1699
+ {@render cellBody(cellRow, cellColumn, cellValue)}
1700
+ {:else}
1701
+ {@render cellBodyWithFormat(
1702
+ cellRow,
1703
+ cellColumn,
1704
+ cellValue,
1705
+ )}
1706
+ {/if}
1707
+ {/if}
1708
+ {#if !isEditing && cellSel?.fillHandle}
1709
+ <!-- Excel-style fill handle: drag down/right to
1710
+ extend the selection and pattern-fill the new
1711
+ cells on release. Rendered inside the bottom-
1712
+ right cell of the selection range (or active
1713
+ cell if there's no range). -->
1714
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
1715
+ <div
1716
+ class="sv-grid-fill-handle"
1717
+ role="button"
1718
+ aria-label="Fill handle"
1719
+ tabindex={-1}
1720
+ onpointerdown={(event) =>
1721
+ startFillDrag(event, rowIndex, colIndex)}
1722
+ ondblclick={(event) => {
1723
+ // Not the cell's double-click: this fills
1724
+ // down as far as the neighbour column goes.
1725
+ event.stopPropagation();
1726
+ event.preventDefault();
1727
+ ctrl.fillDownToNeighbour();
1728
+ }}
1729
+ ></div>
1730
+ {/if}
1731
+ {#if cellValidity.invalid && cellValidity.message}
1732
+ <!-- The validation message, for assistive tech: the
1733
+ tooltip above is mouse-only, and unlike a title
1734
+ a visually-hidden span adds no second native
1735
+ tooltip competing with the custom one. -->
1736
+ <span class="sv-grid-sr-only"
1737
+ >{cellValidity.message}</span
1738
+ >
1739
+ {/if}
1740
+ {#if cellNote != null && !isEditing}
1741
+ <span
1742
+ class="sv-grid-cell-note-corner"
1743
+ aria-label="Note"
1744
+ onpointerenter={(event) => {
1745
+ event.stopPropagation();
1746
+ showTooltipFor(
1747
+ event.currentTarget as HTMLElement,
1748
+ cellNote,
1749
+ );
1750
+ }}
1751
+ onpointerleave={(event) => {
1752
+ event.stopPropagation();
1753
+ hideTooltip();
1754
+ }}
1755
+ ></span>
1756
+ {/if}
1757
+ </td>
1758
+ {/if}
1759
+ {/each}
1760
+ {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
1761
+ <td
1762
+ class="sv-grid-cell sv-grid-cell-spacer"
1763
+ aria-hidden="true"
1764
+ style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
1765
+ ></td>
1766
+ {/if}
1767
+ </tr>
1768
+ {/if}
1769
+ {/snippet}
1409
1770
  {#snippet pinnedRowBody(
1410
1771
  rowData: TData,
1411
1772
  where: "top" | "bottom",
@@ -1475,7 +1836,8 @@
1475
1836
  // No gutter column on most grids, so fall back to the first body cell -
1476
1837
  // otherwise `rowResize` would be a prop that silently does nothing.
1477
1838
  anchor: "row",
1478
- onResize: (index, height) => ctrl.setRowResizeHeight(index, height),
1839
+ onResize: (index, height) => ctrl.resizeRowByUser(index, height),
1840
+ onAutosize: (index) => ctrl.resizeRowByUser(index, null),
1479
1841
  }}
1480
1842
  use:lazyColumnResize={{
1481
1843
  disabled: !columnResizeEnabled,
@@ -1483,12 +1845,13 @@
1483
1845
  onResize: (columnId, width) => {
1484
1846
  ctrl.columnWidths = { ...ctrl.columnWidths, [columnId]: width };
1485
1847
  },
1848
+ onResizeEnd: (columnId, width, startWidth) => ctrl.resizeColumnByUser(columnId, width, startWidth),
1486
1849
  label: (columnId) => {
1487
1850
  const col = ctrl.findColumnById(columnId);
1488
1851
  return col ? toolPanelHeaderLabel(col) : columnId;
1489
1852
  },
1490
1853
  canResize: (columnId) => ctrl.columnResizable(columnId),
1491
- onAutosize: (columnId) => ctrl.autosizeColumn(columnId),
1854
+ onAutosize: (columnId) => ctrl.autosizeColumnByUser(columnId),
1492
1855
  }}
1493
1856
  >
1494
1857
  {#if showGlobalFilterEffective}
@@ -1535,17 +1898,7 @@
1535
1898
  </button>
1536
1899
  {/if}
1537
1900
  {#if ctrl.chartingEnabled}
1538
- <button
1539
- type="button"
1540
- class="sv-grid-toolbar-btn sv-grid-chart-toggle"
1541
- class:is-active={ctrl.chartPanelOpen}
1542
- aria-label={ctrl.chartPanelOpen ? "Close chart" : "Open chart"}
1543
- aria-expanded={ctrl.chartPanelOpen}
1544
- onclick={() => (ctrl.chartPanelOpen = !ctrl.chartPanelOpen)}
1545
- >
1546
- {@render icon("chart")}
1547
- Chart
1548
- </button>
1901
+ {@render chartToggle()}
1549
1902
  {/if}
1550
1903
  </div>
1551
1904
  {/if}
@@ -1560,7 +1913,7 @@
1560
1913
  typeof opt.containerHeight === "string"
1561
1914
  ? opt.containerHeight
1562
1915
  : `${opt.containerHeight ?? 520}px`
1563
- }; --sg-thead-h: ${headerHeight}px; --sg-pinned-row-h: ${typeof opt.rowHeight === "number" ? opt.rowHeight : 30}px;`}
1916
+ }; --sg-thead-h: ${headerHeight}px; --sg-frozen-h: ${frozenBandHeight}px; --sg-pinned-row-h: ${typeof opt.rowHeight === "number" ? opt.rowHeight : 30}px;`}
1564
1917
  >
1565
1918
  <div
1566
1919
  class="sv-grid-container sv-grid-container-custom-scrollbars"
@@ -1723,6 +2076,7 @@
1723
2076
  )}
1724
2077
  <th
1725
2078
  class="sv-grid-column"
2079
+ class:sv-grid-column-collapsed={!!collapsedColumns[header.column.id]}
1726
2080
  class:is-drag-target-before={colDropOnId ===
1727
2081
  header.column.id && colDropSide === "before"}
1728
2082
  class:is-drag-target-after={colDropOnId ===
@@ -2180,6 +2534,9 @@
2180
2534
  </td>
2181
2535
  </tr>
2182
2536
  {:else if rowVirtualizationEnabled}
2537
+ {#each frozenRowList as row, rowIndex (row.id)}
2538
+ {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2539
+ {/each}
2183
2540
  {#if rowTopSpacer > 0}
2184
2541
  <tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
2185
2542
  <td
@@ -2191,256 +2548,17 @@
2191
2548
  ></td>
2192
2549
  </tr>
2193
2550
  {/if}
2551
+ <!-- The virtualizer counts every row, frozen ones included, so its
2552
+ offsets and sizes stay in row-index space; the frozen rows are
2553
+ rendered in the band above and SKIPPED here. Shifting the
2554
+ index instead (item i drawn as row i + N) sized every row with
2555
+ the height of the row N above it, so a row dragged taller under
2556
+ a freeze showed its neighbour's height. -->
2194
2557
  {#each virtualRows as rowItem (rowItem.key)}
2195
2558
  {@const rowIndex = rowItem.index}
2196
2559
  {@const row = allRows[rowIndex]}
2197
- {#if row}
2198
- {#if opt.isDetailRow?.(row.original as TData, rowIndex)}
2199
- {@render detailRowMarkup(row, rowIndex)}
2200
- {:else if isGroupRow(row) && !groupColumnMode}
2201
- <tr
2202
- class="sv-grid-row sv-grid-group-row"
2203
- class:sv-grid-row-selected={isRowSelected(row.id)}
2204
- aria-level={row.depth + 1}
2205
- aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
2206
- {...getGridRowA11yProps(rowIndex + 1)}
2207
- style={rowHeightStyle(rowItem.size)}
2208
- >
2209
- <td
2210
- class="sv-grid-cell sv-grid-group-cell"
2211
- class:sv-grid-cell-active={activeCell.rowIndex ===
2212
- rowIndex}
2213
- colspan={allColumns.length +
2214
- (showRowNumbersEffective ? 1 : 0) +
2215
- (showRowSelectionEffective ? 1 : 0)}
2216
- onclick={() => row.toggleExpanded?.()}
2217
- >
2218
- {@render groupRowContent(row)}
2219
- </td>
2220
- </tr>
2221
- {:else}
2222
- {@const userRowClass = computeRowClass(row, rowIndex)}
2223
- <tr
2224
- class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
2225
- class:sv-grid-row-selected={isRowSelected(row.id)}
2226
- class:sv-grid-row-alt={opt.zebraRows &&
2227
- rowIndex % 2 === 1}
2228
- class:sv-grid-row-draggable={rowDragManagedEffective}
2229
- class:sv-grid-group-footer-row={isGroupFooterRow(row)}
2230
- class:sv-grid-grand-total-row={isGrandTotalRow(row)}
2231
- class:sv-grid-row-auto-height={autoRowHeightOn}
2232
- {...getGridRowA11yProps(rowIndex + 1)}
2233
- aria-level={sgAriaLevel(row)}
2234
- aria-expanded={sgAriaExpanded(row)}
2235
- {...rowDragAttrs(rowIndex)}
2236
- style={rowHeightStyle(rowItem.size)}
2237
- use:measureRowHeight={rowIndex}
2238
- >
2239
- {#if showRowNumbersEffective}
2240
- <td
2241
- class="sv-grid-cell sv-grid-row-number-cell"
2242
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2243
- >{rowIndex + 1}</td
2244
- >
2245
- {/if}
2246
- {#if showRowSelectionEffective}
2247
- <td
2248
- class="sv-grid-cell sv-grid-selection-cell"
2249
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2250
- onclick={() => toggleRowSelectionById(row.id)}
2251
- >
2252
- <button
2253
- type="button"
2254
- class="sv-grid-checkbox"
2255
- role="checkbox"
2256
- aria-checked={isRowSelected(row.id)}
2257
- aria-label="Select row"
2258
- onclick={(event) => {
2259
- event.stopPropagation();
2260
- toggleRowSelectionById(row.id);
2261
- }}
2262
- onkeydown={(event) =>
2263
- toggleCheckboxWithKeyboard(event, () => {
2264
- event.stopPropagation();
2265
- toggleRowSelectionById(row.id);
2266
- })}
2267
- ></button>
2268
- </td>
2269
- {/if}
2270
- {#if columnVirtualizationEnabled && columnWindowStart > 0}
2271
- <td
2272
- class="sv-grid-cell sv-grid-cell-spacer"
2273
- aria-hidden="true"
2274
- style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
2275
- ></td>
2276
- {/if}
2277
- {#each renderedColumns as rendered (rendered.column.id)}
2278
- {@const colIndex = rendered.item.index}
2279
- {@const baseValue = getColumnBaseValue(
2280
- row,
2281
- rendered.column,
2282
- )}
2283
- {@const cellValue = getCellDisplayValue(
2284
- row.id,
2285
- rendered.column.id,
2286
- baseValue,
2287
- )}
2288
- {@const isEditing =
2289
- ctrl.editingCell?.rowId === row.id &&
2290
- ctrl.editingCell?.columnId === rendered.column.id}
2291
- {@const inRowEdit =
2292
- !!fullRowEdit &&
2293
- fullRowEdit.rowId === row.id &&
2294
- rendered.column.id in fullRowEdit.draft}
2295
- {@const rangeEdges = getCellRangeEdges(
2296
- rowIndex,
2297
- colIndex,
2298
- )}
2299
- {@const fillEdges = fillMarqueeEdges(rowIndex, colIndex)}
2300
- {@const hasFillHandle =
2301
- fillHandleCell &&
2302
- fillHandleCell.rowIndex === rowIndex &&
2303
- fillHandleCell.colIndex === colIndex}
2304
- {@const userCellClass = computeCellClass(
2305
- row,
2306
- rendered.column,
2307
- )}
2308
- {@const cellValidity = computeCellValidity(
2309
- row,
2310
- rendered.column,
2311
- )}
2312
- {@const cellNote = computeCellNote(
2313
- row,
2314
- rendered.column,
2315
- )}
2316
- <td
2317
- class={`sv-grid-cell ${userCellClass}`}
2318
- class:sv-grid-cell-editing={isEditing || inRowEdit}
2319
- class:sv-grid-cell-active={activeCell.rowIndex ===
2320
- rowIndex && activeCell.colIndex === colIndex}
2321
- class:sv-grid-cell-has-fill-handle={hasFillHandle}
2322
- class:sv-grid-cell-cf={hasConditionalFormats}
2323
- class:sv-grid-cell-invalid={cellValidity.invalid}
2324
- class:sv-grid-cell-has-note={cellNote != null}
2325
- aria-invalid={cellValidity.invalid ? "true" : undefined}
2326
- title={cellValidity.message ?? undefined}
2327
- data-svgrid-row={rowIndex}
2328
- data-svgrid-col={colIndex}
2329
- data-col-id={rendered.column.id}
2330
- data-align={getColumnAlign(rendered.column)}
2331
- data-pinned={isColumnPinned(rendered.column.id) ??
2332
- undefined}
2333
- data-selected-range={rangeEdges ? "true" : undefined}
2334
- data-range-top={rangeEdges?.top ? "true" : undefined}
2335
- data-range-bottom={rangeEdges?.bottom
2336
- ? "true"
2337
- : undefined}
2338
- data-range-left={rangeEdges?.left
2339
- ? "true"
2340
- : undefined}
2341
- data-range-right={rangeEdges?.right
2342
- ? "true"
2343
- : undefined}
2344
- data-fill-preview={isInFillPreview(rowIndex, colIndex)
2345
- ? "true"
2346
- : undefined}
2347
- data-fill-top={fillEdges?.top ? "true" : undefined}
2348
- data-fill-bottom={fillEdges?.bottom ? "true" : undefined}
2349
- data-fill-left={fillEdges?.left ? "true" : undefined}
2350
- data-fill-right={fillEdges?.right ? "true" : undefined}
2351
- style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
2352
- onpointerdown={(event) =>
2353
- onCellPointerDown(rowIndex, colIndex, event)}
2354
- onpointerenter={() =>
2355
- onCellPointerEnter(rowIndex, colIndex)}
2356
- ondblclick={() =>
2357
- emitCellDoubleClick(rowIndex, colIndex)}
2358
- onclick={() => onCellClick(rowIndex, colIndex)}
2359
- oncontextmenu={(event) =>
2360
- openContextMenu(
2361
- event,
2362
- rowIndex,
2363
- colIndex,
2364
- rendered.column.id,
2365
- )}
2366
- use:cellFlashAction={{
2367
- rowId: row.id,
2368
- value: cellValue,
2369
- active: !!rendered.column.columnDef.cellFlash,
2370
- className: flashClassFor(
2371
- rendered.column.columnDef.cellFlash,
2372
- ),
2373
- }}
2374
- role="gridcell"
2375
- id={getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex)}
2376
- aria-colindex={colIndex + 1}
2377
- aria-rowindex={rowIndex + 1}
2378
- aria-selected={isRowSelected(row.id)}
2379
- >
2380
- {#if inRowEdit || isEditing}
2381
- <!-- The editing cell stays empty until the lazy
2382
- editor chunk lands, exactly as it already did
2383
- for the dropdown / date editors. Falling back
2384
- to the read-only value here would flash stale
2385
- content into a cell the user is editing. -->
2386
- {#if CellEditor}
2387
- <CellEditor {ctrl} column={rendered.column} {row} fullRow={inRowEdit} />
2388
- {/if}
2389
- {:else}
2390
- {#if plainCellBody}
2391
- {@render cellBody(row, rendered.column, cellValue)}
2392
- {:else}
2393
- {@render cellBodyWithFormat(
2394
- row,
2395
- rendered.column,
2396
- cellValue,
2397
- )}
2398
- {/if}
2399
- {/if}
2400
- {#if !isEditing && fillHandleCell && fillHandleCell.rowIndex === rowIndex && fillHandleCell.colIndex === colIndex}
2401
- <!-- Excel-style fill handle: drag down/right to
2402
- extend the selection and pattern-fill the new
2403
- cells on release. Rendered inside the bottom-
2404
- right cell of the selection range (or active
2405
- cell if there's no range). -->
2406
- <!-- svelte-ignore a11y_no_static_element_interactions -->
2407
- <div
2408
- class="sv-grid-fill-handle"
2409
- role="button"
2410
- aria-label="Fill handle"
2411
- tabindex={-1}
2412
- onpointerdown={(event) =>
2413
- startFillDrag(event, rowIndex, colIndex)}
2414
- ></div>
2415
- {/if}
2416
- {#if cellNote != null && !isEditing}
2417
- <span
2418
- class="sv-grid-cell-note-corner"
2419
- aria-label="Note"
2420
- onpointerenter={(event) => {
2421
- event.stopPropagation();
2422
- showTooltipFor(
2423
- event.currentTarget as HTMLElement,
2424
- cellNote,
2425
- );
2426
- }}
2427
- onpointerleave={(event) => {
2428
- event.stopPropagation();
2429
- hideTooltip();
2430
- }}
2431
- ></span>
2432
- {/if}
2433
- </td>
2434
- {/each}
2435
- {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
2436
- <td
2437
- class="sv-grid-cell sv-grid-cell-spacer"
2438
- aria-hidden="true"
2439
- style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
2440
- ></td>
2441
- {/if}
2442
- </tr>
2443
- {/if}
2560
+ {#if row && rowIndex >= frozenRowCount}
2561
+ {@render bodyRow(row, rowIndex, rowHeightStyle(rowItem.size))}
2444
2562
  {/if}
2445
2563
  {/each}
2446
2564
  {#if rowBottomSpacer > 0}
@@ -2456,253 +2574,7 @@
2456
2574
  {/if}
2457
2575
  {:else}
2458
2576
  {#each allRows as row, rowIndex (row.id)}
2459
- {#if opt.isDetailRow?.(row.original as TData, rowIndex)}
2460
- {@render detailRowMarkup(row, rowIndex)}
2461
- {:else if isGroupRow(row) && !groupColumnMode}
2462
- <tr
2463
- class="sv-grid-row sv-grid-group-row"
2464
- class:sv-grid-row-selected={isRowSelected(row.id)}
2465
- aria-level={row.depth + 1}
2466
- aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
2467
- {...getGridRowA11yProps(rowIndex + 1)}
2468
- style={rowStyleFor(rowIndex)}
2469
- >
2470
- <td
2471
- class="sv-grid-cell sv-grid-group-cell"
2472
- class:sv-grid-cell-active={activeCell.rowIndex ===
2473
- rowIndex}
2474
- colspan={allColumns.length +
2475
- (showRowNumbersEffective ? 1 : 0) +
2476
- (showRowSelectionEffective ? 1 : 0)}
2477
- onclick={() => row.toggleExpanded?.()}
2478
- >
2479
- {@render groupRowContent(row)}
2480
- </td>
2481
- </tr>
2482
- {:else}
2483
- {@const userRowClass = computeRowClass(row, rowIndex)}
2484
- <tr
2485
- class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
2486
- class:sv-grid-row-selected={isRowSelected(row.id)}
2487
- class:sv-grid-row-alt={opt.zebraRows &&
2488
- rowIndex % 2 === 1}
2489
- class:sv-grid-row-draggable={rowDragManagedEffective}
2490
- class:sv-grid-group-footer-row={isGroupFooterRow(row)}
2491
- class:sv-grid-grand-total-row={isGrandTotalRow(row)}
2492
- class:sv-grid-row-auto-height={autoRowHeightOn}
2493
- {...getGridRowA11yProps(rowIndex + 1)}
2494
- aria-level={sgAriaLevel(row)}
2495
- aria-expanded={sgAriaExpanded(row)}
2496
- {...rowDragAttrs(rowIndex)}
2497
- style={rowStyleFor(rowIndex)}
2498
- use:measureRowHeight={rowIndex}
2499
- >
2500
- {#if showRowNumbersEffective}
2501
- <td
2502
- class="sv-grid-cell sv-grid-row-number-cell"
2503
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2504
- >{rowIndex + 1}</td
2505
- >
2506
- {/if}
2507
- {#if showRowSelectionEffective}
2508
- <td
2509
- class="sv-grid-cell sv-grid-selection-cell"
2510
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
2511
- onclick={() => toggleRowSelectionById(row.id)}
2512
- >
2513
- <button
2514
- type="button"
2515
- class="sv-grid-checkbox"
2516
- role="checkbox"
2517
- aria-checked={isRowSelected(row.id)}
2518
- aria-label="Select row"
2519
- onclick={(event) => {
2520
- event.stopPropagation();
2521
- toggleRowSelectionById(row.id);
2522
- }}
2523
- onkeydown={(event) =>
2524
- toggleCheckboxWithKeyboard(event, () => {
2525
- event.stopPropagation();
2526
- toggleRowSelectionById(row.id);
2527
- })}
2528
- ></button>
2529
- </td>
2530
- {/if}
2531
- {#if columnVirtualizationEnabled && columnWindowStart > 0}
2532
- <td
2533
- class="sv-grid-cell sv-grid-cell-spacer"
2534
- aria-hidden="true"
2535
- style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
2536
- ></td>
2537
- {/if}
2538
- {#each renderedColumns as rendered (rendered.column.id)}
2539
- {@const colIndex = rendered.item.index}
2540
- {@const baseValue = getColumnBaseValue(
2541
- row,
2542
- rendered.column,
2543
- )}
2544
- {@const cellValue = getCellDisplayValue(
2545
- row.id,
2546
- rendered.column.id,
2547
- baseValue,
2548
- )}
2549
- {@const isEditing =
2550
- ctrl.editingCell?.rowId === row.id &&
2551
- ctrl.editingCell?.columnId === rendered.column.id}
2552
- {@const inRowEdit =
2553
- !!fullRowEdit &&
2554
- fullRowEdit.rowId === row.id &&
2555
- rendered.column.id in fullRowEdit.draft}
2556
- {@const rangeEdges = getCellRangeEdges(
2557
- rowIndex,
2558
- colIndex,
2559
- )}
2560
- {@const userCellClass = computeCellClass(
2561
- row,
2562
- rendered.column,
2563
- )}
2564
- {@const cellTooltip = computeCellTooltip(
2565
- row,
2566
- rendered.column,
2567
- )}
2568
- {@const cellValidity = computeCellValidity(
2569
- row,
2570
- rendered.column,
2571
- )}
2572
- {@const cellNote = computeCellNote(row, rendered.column)}
2573
- <td
2574
- class={`sv-grid-cell ${userCellClass}`}
2575
- class:sv-grid-cell-editing={isEditing || inRowEdit}
2576
- class:sv-grid-cell-active={activeCell.rowIndex ===
2577
- rowIndex && activeCell.colIndex === colIndex}
2578
- class:sv-grid-cell-cf={hasConditionalFormats}
2579
- class:sv-grid-cell-invalid={cellValidity.invalid}
2580
- class:sv-grid-cell-has-note={cellNote != null}
2581
- aria-invalid={cellValidity.invalid ? "true" : undefined}
2582
- data-svgrid-row={rowIndex}
2583
- data-svgrid-col={colIndex}
2584
- data-col-id={rendered.column.id}
2585
- data-align={getColumnAlign(rendered.column)}
2586
- data-pinned={isColumnPinned(rendered.column.id) ??
2587
- undefined}
2588
- data-selected-range={rangeEdges ? "true" : undefined}
2589
- data-range-top={rangeEdges?.top ? "true" : undefined}
2590
- data-range-bottom={rangeEdges?.bottom
2591
- ? "true"
2592
- : undefined}
2593
- data-range-left={rangeEdges?.left ? "true" : undefined}
2594
- data-range-right={rangeEdges?.right
2595
- ? "true"
2596
- : undefined}
2597
- style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
2598
- onpointerdown={(event) =>
2599
- onCellPointerDown(rowIndex, colIndex, event)}
2600
- onpointerenter={(event) => {
2601
- onCellPointerEnter(rowIndex, colIndex);
2602
- // Column tooltip fires on whole-cell hover.
2603
- // Per-cell notes are gated on the corner hot-
2604
- // zone below (Excel-style: hover the small
2605
- // triangle to read the note). A validation
2606
- // message (when the cell is invalid) wins over
2607
- // the plain column tooltip.
2608
- const tip =
2609
- cellValidity.invalid && cellValidity.message
2610
- ? cellValidity.message
2611
- : cellTooltip;
2612
- if (tip)
2613
- showTooltipFor(
2614
- event.currentTarget as HTMLElement,
2615
- tip,
2616
- );
2617
- }}
2618
- onpointerleave={hideTooltip}
2619
- ondblclick={() =>
2620
- emitCellDoubleClick(rowIndex, colIndex)}
2621
- onclick={() => onCellClick(rowIndex, colIndex)}
2622
- oncontextmenu={(event) =>
2623
- openContextMenu(
2624
- event,
2625
- rowIndex,
2626
- colIndex,
2627
- rendered.column.id,
2628
- )}
2629
- use:cellFlashAction={{
2630
- rowId: row.id,
2631
- value: cellValue,
2632
- active: !!rendered.column.columnDef.cellFlash,
2633
- className: flashClassFor(
2634
- rendered.column.columnDef.cellFlash,
2635
- ),
2636
- }}
2637
- {...getGridCellA11yProps({
2638
- id: getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex),
2639
- rowIndex: rowIndex + 1,
2640
- colIndex: colIndex + 1,
2641
- selected: isRowSelected(row.id),
2642
- })}
2643
- >
2644
- {#if inRowEdit || isEditing}
2645
- <!-- Empty until the lazy editor chunk lands - see the
2646
- matching branch in the pinned-row body above. -->
2647
- {#if CellEditor}
2648
- <CellEditor {ctrl} column={rendered.column} {row} fullRow={inRowEdit} />
2649
- {/if}
2650
- {:else}
2651
- {@render cellBodyWithFormat(
2652
- row,
2653
- rendered.column,
2654
- cellValue,
2655
- )}
2656
- {/if}
2657
- {#if cellValidity.invalid && cellValidity.message}
2658
- <!-- The validation message, for assistive tech.
2659
- On this path the message is shown visually by
2660
- the pointerenter tooltip above, which is mouse-
2661
- only - a keyboard or screen-reader user would
2662
- get `aria-invalid` with no reason attached.
2663
- A visually-hidden span inside the cell is read
2664
- as part of the cell when focus lands on it, and
2665
- unlike `title` it adds no second native tooltip
2666
- competing with the custom one. -->
2667
- <span class="sv-grid-sr-only"
2668
- >{cellValidity.message}</span
2669
- >
2670
- {/if}
2671
- {#if cellNote != null && !isEditing}
2672
- <!-- Excel-style per-cell note indicator. The
2673
- triangle itself is the hot-zone; hover
2674
- just the corner to see the note (Excel
2675
- red-dot behaviour). The cell-level
2676
- tooltip handler only shows the column
2677
- tooltip, so the two surfaces stay
2678
- separate. -->
2679
- <span
2680
- class="sv-grid-cell-note-corner"
2681
- aria-label="Note"
2682
- onpointerenter={(event) => {
2683
- event.stopPropagation();
2684
- showTooltipFor(
2685
- event.currentTarget as HTMLElement,
2686
- cellNote,
2687
- );
2688
- }}
2689
- onpointerleave={(event) => {
2690
- event.stopPropagation();
2691
- hideTooltip();
2692
- }}
2693
- ></span>
2694
- {/if}
2695
- </td>
2696
- {/each}
2697
- {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
2698
- <td
2699
- class="sv-grid-cell sv-grid-cell-spacer"
2700
- aria-hidden="true"
2701
- style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
2702
- ></td>
2703
- {/if}
2704
- </tr>
2705
- {/if}
2577
+ {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2706
2578
  {/each}
2707
2579
  {/if}
2708
2580
  </tbody>