@svgrid/grid 3.0.3 → 3.0.5

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 (325) hide show
  1. package/README.md +6 -5
  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 +122 -41
  8. package/dist/SvGrid.controller.svelte.js +846 -207
  9. package/dist/SvGrid.css +178 -2
  10. package/dist/SvGrid.svelte +717 -720
  11. package/dist/SvGrid.types.d.ts +566 -12
  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 +119 -28
  29. package/dist/cdn/GridMenus-CID62rlI.js +636 -0
  30. package/dist/cdn/GridMenus-DLL-qZeu.js +635 -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/server-block-cache-CGoWz-87.js +237 -0
  67. package/dist/cdn/{src-D6XvA1Ij.js → src-Bklofsad.js} +10615 -10155
  68. package/dist/cdn/{src-Bugf5XlI.js → src-jcFMkBnN.js} +7111 -6651
  69. package/dist/cdn/svgrid.js +27 -15
  70. package/dist/cdn/svgrid.svelte-external.js +27 -15
  71. package/dist/cdn/validate-C7Rn_Bns.js +76 -0
  72. package/dist/chart-axes.d.ts +53 -0
  73. package/dist/chart-axes.js +351 -0
  74. package/dist/chart-cartesian.d.ts +88 -0
  75. package/dist/chart-cartesian.js +1862 -0
  76. package/dist/chart-decimate.d.ts +51 -0
  77. package/dist/chart-decimate.js +199 -0
  78. package/dist/chart-export-pdf.d.ts +47 -0
  79. package/dist/chart-export-pdf.js +187 -0
  80. package/dist/chart-export.d.ts +2 -1
  81. package/dist/chart-export.js +18 -3
  82. package/dist/chart-financial.d.ts +91 -0
  83. package/dist/chart-financial.js +175 -0
  84. package/dist/chart-flow.d.ts +10 -0
  85. package/dist/chart-flow.js +298 -0
  86. package/dist/chart-format.d.ts +18 -0
  87. package/dist/chart-format.js +65 -0
  88. package/dist/chart-grid.d.ts +8 -0
  89. package/dist/chart-grid.js +190 -0
  90. package/dist/chart-hierarchy.d.ts +17 -0
  91. package/dist/chart-hierarchy.js +146 -0
  92. package/dist/chart-indicators.d.ts +115 -0
  93. package/dist/chart-indicators.js +389 -0
  94. package/dist/chart-messages.d.ts +95 -0
  95. package/dist/chart-messages.js +92 -0
  96. package/dist/chart-motion.d.ts +16 -0
  97. package/dist/chart-motion.js +141 -0
  98. package/dist/chart-panel-messages.d.ts +265 -0
  99. package/dist/chart-panel-messages.js +285 -0
  100. package/dist/chart-pivot.d.ts +87 -0
  101. package/dist/chart-pivot.js +156 -0
  102. package/dist/chart-polar.d.ts +25 -0
  103. package/dist/chart-polar.js +700 -0
  104. package/dist/chart-samples.d.ts +16 -0
  105. package/dist/chart-samples.js +153 -0
  106. package/dist/chart-scale.d.ts +113 -0
  107. package/dist/chart-scale.js +405 -0
  108. package/dist/chart-stats.d.ts +111 -0
  109. package/dist/chart-stats.js +419 -0
  110. package/dist/chart-stream.d.ts +45 -0
  111. package/dist/chart-stream.js +91 -0
  112. package/dist/chart-summary.d.ts +27 -0
  113. package/dist/chart-summary.js +205 -0
  114. package/dist/chart-sync.svelte.d.ts +32 -0
  115. package/dist/chart-sync.svelte.js +23 -0
  116. package/dist/chart-table.d.ts +35 -0
  117. package/dist/chart-table.js +165 -0
  118. package/dist/chart-trend.d.ts +19 -0
  119. package/dist/chart-trend.js +61 -0
  120. package/dist/chart-types.d.ts +1456 -0
  121. package/dist/chart-types.js +6 -0
  122. package/dist/chart-validate.d.ts +43 -0
  123. package/dist/chart-validate.js +272 -0
  124. package/dist/chart-zoom.d.ts +40 -0
  125. package/dist/chart-zoom.js +145 -0
  126. package/dist/chart.d.ts +186 -865
  127. package/dist/chart.js +460 -2204
  128. package/dist/clipboard.d.ts +1 -0
  129. package/dist/clipboard.js +137 -18
  130. package/dist/column-resize.d.ts +3 -0
  131. package/dist/column-resize.js +4 -1
  132. package/dist/columns.js +3 -0
  133. package/dist/command-context.d.ts +19 -0
  134. package/dist/command-context.js +116 -0
  135. package/dist/core.d.ts +7 -0
  136. package/dist/core.js +36 -13
  137. package/dist/createPopoverSelect.svelte.d.ts +2 -2
  138. package/dist/editing.d.ts +2 -8
  139. package/dist/editing.js +272 -33
  140. package/dist/fill-patterns.d.ts +0 -5
  141. package/dist/fill-patterns.js +56 -2
  142. package/dist/gantt-view.svelte.d.ts +24 -0
  143. package/dist/gantt-view.svelte.js +13 -0
  144. package/dist/grid-icons.d.ts +2 -2
  145. package/dist/grid-icons.js +2 -2
  146. package/dist/grid-messages.d.ts +16 -3
  147. package/dist/grid-messages.js +10 -0
  148. package/dist/history.d.ts +83 -0
  149. package/dist/history.js +133 -0
  150. package/dist/index.d.ts +29 -10
  151. package/dist/index.js +23 -5
  152. package/dist/keyboard-handlers.js +88 -31
  153. package/dist/keyboard.d.ts +55 -0
  154. package/dist/keyboard.js +132 -0
  155. package/dist/menus.d.ts +3 -1
  156. package/dist/menus.js +7 -2
  157. package/dist/merges.d.ts +97 -0
  158. package/dist/merges.js +147 -0
  159. package/dist/row-model.d.ts +133 -0
  160. package/dist/row-model.js +16 -0
  161. package/dist/row-resize.d.ts +3 -0
  162. package/dist/row-resize.js +14 -0
  163. package/dist/scroll-sync.d.ts +1 -0
  164. package/dist/scroll-sync.js +24 -0
  165. package/dist/selection.d.ts +18 -1
  166. package/dist/selection.js +138 -18
  167. package/dist/server-block-cache.d.ts +214 -0
  168. package/dist/server-block-cache.js +531 -0
  169. package/dist/server-data-source.d.ts +240 -6
  170. package/dist/server-data-source.js +220 -20
  171. package/dist/server.d.ts +13 -0
  172. package/dist/server.js +13 -0
  173. package/dist/shortcut-registry.d.ts +145 -0
  174. package/dist/shortcut-registry.js +44 -0
  175. package/dist/svgrid-wrapper.types.d.ts +213 -22
  176. package/dist/validate.d.ts +4 -0
  177. package/dist/validate.js +21 -3
  178. package/dist/virtualization/virtualizer.js +38 -17
  179. package/package.json +11 -1
  180. package/src/GridMenus.svelte +44 -2
  181. package/src/SvChartMenu.svelte +69 -0
  182. package/src/SvChartPanes.svelte +113 -0
  183. package/src/SvChartPanes.test.ts +66 -0
  184. package/src/SvGrid.controller.svelte.ts +819 -179
  185. package/src/SvGrid.css +178 -2
  186. package/src/SvGrid.svelte +717 -720
  187. package/src/SvGrid.types.ts +564 -11
  188. package/src/SvGridCellEditor.svelte +17 -0
  189. package/src/SvGridChart.svelte +2257 -329
  190. package/src/SvGridChart.test.ts +1419 -1
  191. package/src/SvGridChart.types.ts +336 -0
  192. package/src/SvGridChartBuilder.svelte +323 -0
  193. package/src/SvGridChartPanel.svelte +221 -275
  194. package/src/SvGridChartPickers.svelte +417 -0
  195. package/src/SvGridChartView.svelte +13 -14
  196. package/src/SvModal.svelte +7 -1
  197. package/src/SvModal.test.ts +9 -0
  198. package/src/ai.test.ts +57 -2
  199. package/src/ai.ts +166 -22
  200. package/src/aria.d.ts +11 -0
  201. package/src/build-api.coverage.test.ts +184 -0
  202. package/src/build-api.ts +118 -22
  203. package/src/chart-axes.test.ts +573 -0
  204. package/src/chart-axes.ts +342 -0
  205. package/src/chart-cartesian.ts +1863 -0
  206. package/src/chart-decimate.test.ts +249 -0
  207. package/src/chart-decimate.ts +186 -0
  208. package/src/chart-export-pdf.test.ts +91 -0
  209. package/src/chart-export-pdf.ts +207 -0
  210. package/src/chart-export.test.ts +14 -0
  211. package/src/chart-export.ts +13 -3
  212. package/src/chart-financial.ts +208 -0
  213. package/src/chart-flow.ts +286 -0
  214. package/src/chart-format.ts +67 -0
  215. package/src/chart-grid.ts +179 -0
  216. package/src/chart-hierarchy.ts +135 -0
  217. package/src/chart-indicators.test.ts +443 -0
  218. package/src/chart-indicators.ts +384 -0
  219. package/src/chart-messages.test.ts +21 -0
  220. package/src/chart-messages.ts +184 -0
  221. package/src/chart-motion.test.ts +71 -0
  222. package/src/chart-motion.ts +148 -0
  223. package/src/chart-panel-messages.test.ts +29 -0
  224. package/src/chart-panel-messages.ts +550 -0
  225. package/src/chart-pivot.test.ts +54 -0
  226. package/src/chart-pivot.ts +225 -0
  227. package/src/chart-polar.ts +674 -0
  228. package/src/chart-samples.ts +106 -0
  229. package/src/chart-scale.ts +394 -0
  230. package/src/chart-series-types.test.ts +714 -0
  231. package/src/chart-stats.ts +409 -0
  232. package/src/chart-stream.test.ts +75 -0
  233. package/src/chart-stream.ts +114 -0
  234. package/src/chart-summary.test.ts +90 -0
  235. package/src/chart-summary.ts +197 -0
  236. package/src/chart-sync.svelte.ts +41 -0
  237. package/src/chart-table.test.ts +76 -0
  238. package/src/chart-table.ts +178 -0
  239. package/src/chart-trend.ts +47 -0
  240. package/src/chart-types.ts +1309 -0
  241. package/src/chart-validate.test.ts +100 -0
  242. package/src/chart-validate.ts +247 -0
  243. package/src/chart-zoom.test.ts +137 -0
  244. package/src/chart-zoom.ts +124 -0
  245. package/src/chart.coverage.test.ts +81 -0
  246. package/src/chart.test.ts +56 -3
  247. package/src/chart.ts +557 -2784
  248. package/src/clipboard.test.ts +151 -0
  249. package/src/clipboard.ts +145 -16
  250. package/src/column-resize.ts +7 -1
  251. package/src/columns.ts +2 -0
  252. package/src/command-context.test.ts +200 -0
  253. package/src/command-context.ts +137 -0
  254. package/src/core.rowmodel-cache.test.ts +51 -0
  255. package/src/core.ts +40 -12
  256. package/src/editing.test.ts +281 -3
  257. package/src/editing.ts +264 -38
  258. package/src/fill-patterns.test.ts +35 -0
  259. package/src/fill-patterns.ts +56 -2
  260. package/src/gantt-stub.test.svelte +38 -0
  261. package/src/gantt-view.svelte.ts +35 -0
  262. package/src/grid-icons.ts +2 -2
  263. package/src/grid-messages.ts +28 -3
  264. package/src/history.test.ts +196 -0
  265. package/src/history.ts +162 -0
  266. package/src/icon-seam.test.ts +5 -6
  267. package/src/index.ts +186 -12
  268. package/src/keyboard-handlers.coverage.test.ts +126 -6
  269. package/src/keyboard-handlers.ts +91 -30
  270. package/src/keyboard-shortcuts.seam.test.ts +200 -0
  271. package/src/keyboard.test.ts +124 -1
  272. package/src/keyboard.ts +142 -0
  273. package/src/menus.test.ts +26 -0
  274. package/src/menus.ts +10 -3
  275. package/src/merges.test.ts +112 -0
  276. package/src/merges.ts +168 -0
  277. package/src/pivot.grid.test.ts +40 -5
  278. package/src/row-model.ts +146 -0
  279. package/src/row-resize.test.ts +44 -0
  280. package/src/row-resize.ts +17 -0
  281. package/src/scroll-sync.test.ts +42 -1
  282. package/src/scroll-sync.ts +25 -0
  283. package/src/selection.multi-range.test.ts +34 -0
  284. package/src/selection.test.ts +33 -2
  285. package/src/selection.ts +143 -21
  286. package/src/server-block-cache.test.ts +645 -0
  287. package/src/server-block-cache.ts +677 -0
  288. package/src/server-data-source.infinite.test.ts +343 -0
  289. package/src/server-data-source.ts +450 -27
  290. package/src/server.ts +47 -0
  291. package/src/shortcut-registry.test.ts +97 -0
  292. package/src/shortcut-registry.ts +181 -0
  293. package/src/svgrid-wrapper.types.ts +184 -13
  294. package/src/svgrid.behavior.test.ts +135 -0
  295. package/src/svgrid.charting.test.ts +513 -3
  296. package/src/svgrid.context-menu.test.ts +31 -0
  297. package/src/svgrid.gantt-seam.test.ts +199 -0
  298. package/src/svgrid.new-features.wrapper.test.ts +10 -3
  299. package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
  300. package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
  301. package/src/svgrid.upsell-license.test.ts +7 -4
  302. package/src/validate.test.ts +29 -0
  303. package/src/validate.ts +28 -3
  304. package/src/virtualization/virtualizer.ts +39 -17
  305. package/dist/SvGroupCell.svelte +0 -141
  306. package/dist/SvGroupCell.svelte.d.ts +0 -49
  307. package/dist/SvRowGroupPanel.svelte +0 -186
  308. package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
  309. package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
  310. package/dist/cdn/GridMenus-DhdPF700.js +0 -603
  311. package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
  312. package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
  313. package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
  314. package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
  315. package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
  316. package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
  317. package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
  318. package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
  319. package/dist/cdn/validate-_CDJzgIo.js +0 -75
  320. package/dist/server-group-model.d.ts +0 -98
  321. package/dist/server-group-model.js +0 -263
  322. package/src/SvGroupCell.svelte +0 -141
  323. package/src/SvRowGroupPanel.svelte +0 -186
  324. package/src/server-group-model.test.ts +0 -294
  325. package/src/server-group-model.ts +0 -370
package/src/SvGrid.svelte CHANGED
@@ -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
@@ -54,6 +54,7 @@
54
54
  import { getSchedulerView } from "./scheduler-view.svelte";
55
55
  import { getSelectionBarView } from "./selection-bar-view.svelte";
56
56
  import { getBoardView } from "./board-view.svelte";
57
+ import { getGanttView } from "./gantt-view.svelte";
57
58
  import { getChartView } from "./chart-view.svelte";
58
59
  let props: Props<TFeatures, TData> = $props();
59
60
  // Per-instance base for every DOM id the grid mints. `$props.id()` is stable
@@ -78,6 +79,9 @@
78
79
  const boardConfig = $derived(opt.board);
79
80
  // Scheduler / calendar mode: same "view of the grid" seam as the board.
80
81
  const schedulerConfig = $derived(opt.scheduler);
82
+ // Gantt mode: a task table beside a time chart. Same seam again; the renderer
83
+ // ships in @svgrid/enterprise (enableGanttView).
84
+ const ganttConfig = $derived(opt.gantt);
81
85
  // Chart mode: another "view of the grid". Unlike board/scheduler, the renderer
82
86
  // is free - a built-in default (SvGridChartView, lazy-loaded below) wraps the
83
87
  // standalone SvChart, overridable via the `chart-view` seam (getChartView).
@@ -387,8 +391,9 @@
387
391
  );
388
392
 
389
393
  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.
394
+ // A height the user dragged, or set through the api, wins over the
395
+ // declared one, so the row stays where they put it across re-renders
396
+ // and virtualization recycling.
392
397
  const dragged = ctrl.rowResizeHeightPx(i);
393
398
  if (dragged != null) return dragged;
394
399
  const rh = opt.rowHeight;
@@ -402,10 +407,52 @@
402
407
  autoRowHeightOn
403
408
  ? `min-height: ${measuredRowHeightPx(i) ?? rowSizePx(i)}px;`
404
409
  : `height: ${rowSizePx(i)}px;`;
410
+ const collapsedColumns = $derived(ctrl.collapsedColumns);
405
411
  const columnVirtualizationEnabled = $derived(
406
412
  ctrl.columnVirtualizationEnabled,
407
413
  );
408
414
  const virtualRows = $derived(ctrl.virtualRows);
415
+ // The first body row on screen: where a failed run that started above
416
+ // the window repeats its message.
417
+ const firstRenderedRowIndex = $derived(
418
+ rowVirtualizationEnabled ? (virtualRows[0]?.index ?? 0) : 0,
419
+ );
420
+ const frozenRowCount = $derived(ctrl.frozenRowCount);
421
+ const frozenRowList = $derived(ctrl.frozenRowList);
422
+ // Merged cells. A merge is drawn once per band (the frozen rows, the
423
+ // scrolling body), by the top-left cell of the part the band shows, with
424
+ // its spans clamped to the rendered window: a td never spans into rows
425
+ // the table has not drawn.
426
+ const mergeIndex = $derived(ctrl.mergeIndex);
427
+ const mergeColumnWindow = $derived.by(() => ({
428
+ firstCol: renderedColumns[0]?.item.index ?? 0,
429
+ lastCol: renderedColumns[renderedColumns.length - 1]?.item.index ?? -1,
430
+ }));
431
+ const mergeBodyWindow = $derived.by(() => {
432
+ const cols = mergeColumnWindow;
433
+ if (!rowVirtualizationEnabled) return { firstRow: frozenRowCount, lastRow: allRows.length - 1, ...cols };
434
+ let firstRow = Number.POSITIVE_INFINITY;
435
+ let lastRow = -1;
436
+ for (const item of virtualRows) {
437
+ if (item.index < frozenRowCount) continue;
438
+ if (item.index < firstRow) firstRow = item.index;
439
+ if (item.index > lastRow) lastRow = item.index;
440
+ }
441
+ return { firstRow: Number.isFinite(firstRow) ? firstRow : frozenRowCount, lastRow, ...cols };
442
+ });
443
+ const mergeFrozenWindow = $derived.by(() => ({ firstRow: 0, lastRow: frozenRowCount - 1, ...mergeColumnWindow }));
444
+ /** The width a td spanning `colSpan` rendered columns from `colIndex` takes. */
445
+ function mergedWidth(colIndex: number, colSpan: number, own: number): number {
446
+ if (colSpan <= 1) return own;
447
+ let total = 0;
448
+ for (const rendered of renderedColumns) {
449
+ const i = rendered.item.index;
450
+ if (i >= colIndex && i < colIndex + colSpan) total += rendered.item.size;
451
+ }
452
+ return total || own;
453
+ }
454
+ const frozenBandHeight = $derived(ctrl.frozenBandHeight);
455
+ const frozenRowTop = $derived(ctrl.frozenRowTop);
409
456
  const autoRowHeightOn = $derived(ctrl.autoRowHeightOn);
410
457
  const measureRowHeight = $derived(ctrl.measureRowHeight);
411
458
  const measuredRowHeightPx = $derived(ctrl.measuredRowHeightPx);
@@ -522,9 +569,8 @@
522
569
  const toggleSelectAllRows = $derived(ctrl.toggleSelectAllRows);
523
570
  const setActiveCell = $derived(ctrl.setActiveCell);
524
571
  const scrollActiveCellIntoView = $derived(ctrl.scrollActiveCellIntoView);
572
+ const cellSelectionState = $derived(ctrl.cellSelectionState);
525
573
  const getColumnWidth = $derived(ctrl.getColumnWidth);
526
- const getCellRangeEdges = $derived(ctrl.getCellRangeEdges);
527
- const fillHandleCell = $derived(ctrl.fillHandleCell);
528
574
  const isInFillPreview = $derived(ctrl.isInFillPreview);
529
575
  const fillMarqueeEdges = $derived(ctrl.fillMarqueeEdges);
530
576
  const startFillDrag = $derived(ctrl.startFillDrag);
@@ -755,6 +801,19 @@
755
801
  rotate-on-open both hang off it (SvGrid.css), and an override that
756
802
  dropped the class would lose both silently - which is the kind of bug
757
803
  that only shows up in someone else's app. -->
804
+ {#snippet chartToggle()}
805
+ <button
806
+ type="button"
807
+ class="sv-grid-toolbar-btn sv-grid-chart-toggle"
808
+ class:is-active={ctrl.chartPanelOpen}
809
+ aria-label={ctrl.chartPanelOpen ? "Close chart" : "Open chart"}
810
+ aria-expanded={ctrl.chartPanelOpen}
811
+ onclick={() => (ctrl.chartPanelOpen = !ctrl.chartPanelOpen)}
812
+ >
813
+ {@render icon("chart")}
814
+ Chart
815
+ </button>
816
+ {/snippet}
758
817
  {#snippet icon(name: GridIconName)}
759
818
  {@const override = opt.icons?.[name]}
760
819
  {#if override}
@@ -1000,6 +1059,48 @@
1000
1059
  {/if}
1001
1060
  </div>
1002
1061
  </div>
1062
+ {:else if ganttConfig}
1063
+ <div
1064
+ class="sv-grid-root sv-grid-gantt-root"
1065
+ class:sv-grid-root-fill={opt.containerHeight === "100%"}
1066
+ style={`height: ${
1067
+ typeof opt.containerHeight === "string"
1068
+ ? opt.containerHeight
1069
+ : `${opt.containerHeight ?? 520}px`
1070
+ }; display: flex; flex-direction: column;`}
1071
+ >
1072
+ {#if ganttConfig.searchable !== false}
1073
+ <label class="sv-grid-board-search">
1074
+ {@render icon("search")}
1075
+ <input
1076
+ type="search"
1077
+ placeholder={ganttConfig.searchPlaceholder ??
1078
+ messages.ganttSearchPlaceholder}
1079
+ bind:value={ctrl.globalFilter}
1080
+ aria-label="Search tasks"
1081
+ />
1082
+ </label>
1083
+ {/if}
1084
+ <div style="flex: 1 1 auto; min-height: 0;">
1085
+ {#if getGanttView()}
1086
+ {@const GanttView = getGanttView()}
1087
+ <GanttView
1088
+ data={boardData}
1089
+ columns={opt.columns}
1090
+ gantt={ganttConfig}
1091
+ getRowId={opt.getRowId}
1092
+ />
1093
+ {:else}
1094
+ <!-- The Gantt renderer ships in @svgrid/enterprise. Call
1095
+ `enableGanttView()` (or `installEnterprise(api)`) to register it. -->
1096
+ <div class="sv-grid-scheduler-upsell sv-grid-gantt-upsell" role="note">
1097
+ <strong>{messages.ganttUpsellTitle}</strong>
1098
+ <p>{messages.ganttUpsellBody}</p>
1099
+ {@render enterpriseLicenseNote()}
1100
+ </div>
1101
+ {/if}
1102
+ </div>
1103
+ </div>
1003
1104
  {:else if chartViewConfig}
1004
1105
  <div
1005
1106
  class="sv-grid-root sv-grid-chart-root"
@@ -1046,6 +1147,9 @@
1046
1147
  </div>
1047
1148
  </div>
1048
1149
  {:else if pivotViewOn}
1150
+ <!-- The Chart panel docks here the way it docks beside the flat grid,
1151
+ and charts the pivot on screen: the root reserves the panel's edge and
1152
+ the pivot bar carries the toggle the toolbar carries in flat mode. -->
1049
1153
  <div
1050
1154
  class="sv-grid-root sv-grid-pivot-root"
1051
1155
  class:sv-grid-root-fill={opt.containerHeight === "100%"}
@@ -1053,7 +1157,7 @@
1053
1157
  typeof opt.containerHeight === "string"
1054
1158
  ? opt.containerHeight
1055
1159
  : `${opt.containerHeight ?? 520}px`
1056
- }; display: flex; flex-direction: column;`}
1160
+ }; display: flex; flex-direction: column; ${chartDockReserveStyle}`}
1057
1161
  >
1058
1162
  <div class="sv-grid-pivot-bar">
1059
1163
  <span class="sv-grid-pivot-badge">{messages.group}</span>
@@ -1063,6 +1167,9 @@
1063
1167
  aria-pressed="true"
1064
1168
  onclick={() => ctrl.togglePivotMode()}
1065
1169
  >{messages.pivotUpsellTitle}</button>
1170
+ {#if ctrl.chartingEnabled}
1171
+ {@render chartToggle()}
1172
+ {/if}
1066
1173
  </div>
1067
1174
  <div style="flex: 1 1 auto; min-height: 0;">
1068
1175
  {#if pivotResult}
@@ -1081,6 +1188,9 @@
1081
1188
  </div>
1082
1189
  {/if}
1083
1190
  </div>
1191
+ {#if ctrl.chartingEnabled && ctrl.chartPanelOpen && ChartPanelView}
1192
+ <ChartPanelView {ctrl} />
1193
+ {/if}
1084
1194
  </div>
1085
1195
  {:else}
1086
1196
 
@@ -1402,234 +1512,602 @@
1402
1512
  </tr>
1403
1513
  {/snippet}
1404
1514
 
1405
- <!-- A single pinned row (top or bottom). Read-only by design: no
1406
- inline editing, no row-selection checkbox, no fill handle.
1407
- Position-sticky CSS keeps it anchored to the top of the body or
1408
- the bottom of the viewport while the rest scrolls. -->
1409
- {#snippet pinnedRowBody(
1410
- rowData: TData,
1411
- where: "top" | "bottom",
1412
- index: number,
1515
+ <!-- A row whose data has not arrived.
1516
+
1517
+ "loading" keeps the real columns and draws a shimmer bar in each,
1518
+ so the table keeps its shape while blocks stream in - a full-width
1519
+ bar would make the whole grid jump every time one landed. "failed"
1520
+ is the opposite case: there is nothing per-column to say, just one
1521
+ message and a way to try again, so it spans the row like a detail
1522
+ cell does. Neither is selectable or editable; there is nothing
1523
+ there to act on.
1524
+
1525
+ A failed block is a run of failed rows, one per row it claimed,
1526
+ and one message serves the run: the first failed row on screen
1527
+ carries it (the run's first row, or the top of the window when
1528
+ the run started above it) and the rows under it are a plain
1529
+ tinted band. A hundred "Could not load" lines for one block
1530
+ was a wall of red. -->
1531
+ {#snippet placeholderRowMarkup(
1532
+ phRow: Row<TData>,
1533
+ phRowIndex: number,
1534
+ phState: "loading" | "failed",
1535
+ phStyle: string,
1536
+ phContinues: boolean,
1413
1537
  )}
1414
1538
  <tr
1415
- class={`sv-grid-row sv-grid-pinned-row sv-grid-pinned-row-${where}`}
1416
- data-pinned-row={where}
1417
- data-pinned-index={index}
1539
+ class="sv-grid-row sv-grid-placeholder-row"
1540
+ class:sv-grid-placeholder-failed={phState === "failed"}
1541
+ class:sv-grid-placeholder-failed-cont={phContinues}
1542
+ aria-busy={phState === "loading" ? "true" : undefined}
1543
+ {...getGridRowA11yProps(phRowIndex + 1)}
1544
+ style={phStyle}
1418
1545
  >
1419
- {#if showRowNumbersEffective}
1420
- <td
1421
- class="sv-grid-cell sv-grid-row-number-cell"
1422
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1423
- >{#if where === "top"}{@render icon("pinned-row-top")}{:else}{@render icon("pinned-row-bottom")}{/if}</td
1424
- >
1425
- {/if}
1426
- {#if showRowSelectionEffective}
1427
- <td
1428
- class="sv-grid-cell sv-grid-selection-cell"
1429
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
1430
- ></td>
1431
- {/if}
1432
- {#if columnVirtualizationEnabled && columnWindowStart > 0}
1433
- <td
1434
- class="sv-grid-cell sv-grid-cell-spacer"
1435
- aria-hidden="true"
1436
- style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
1437
- ></td>
1438
- {/if}
1439
- {#each renderedColumns as rendered (rendered.column.id)}
1440
- {@const value = getPinnedCellValue(rowData, rendered.column)}
1441
- {@const userCellClass = computePinnedCellClass(
1442
- rowData,
1443
- rendered.column,
1444
- )}
1546
+ {#if phState === "failed"}
1445
1547
  <td
1446
- class={`sv-grid-cell ${userCellClass}`}
1447
- data-col-id={rendered.column.id}
1448
- data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
1449
- style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
1450
- >{formatPinnedValue(rendered.column, value)}</td
1548
+ class="sv-grid-cell sv-grid-placeholder-cell"
1549
+ colspan={allColumns.length +
1550
+ (showRowNumbersEffective ? 1 : 0) +
1551
+ (showRowSelectionEffective ? 1 : 0)}
1451
1552
  >
1452
- {/each}
1453
- {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
1454
- <td
1455
- class="sv-grid-cell sv-grid-cell-spacer"
1456
- aria-hidden="true"
1457
- style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
1458
- ></td>
1553
+ {#if !phContinues}
1554
+ <div class="sv-grid-placeholder-failed-body">
1555
+ <span class="sv-grid-placeholder-error">{messages.rowLoadFailed}</span>
1556
+ {#if opt.onRetryRow}
1557
+ <button
1558
+ type="button"
1559
+ class="sv-grid-placeholder-retry"
1560
+ onclick={() => ctrl.retryRow(phRow)}>{messages.rowLoadRetry}</button
1561
+ >
1562
+ {/if}
1563
+ </div>
1564
+ {/if}
1565
+ </td>
1566
+ {:else}
1567
+ {#if showRowNumbersEffective}
1568
+ <td class="sv-grid-cell sv-grid-rownum-cell"></td>
1569
+ {/if}
1570
+ {#if showRowSelectionEffective}
1571
+ <td class="sv-grid-cell sv-grid-select-cell"></td>
1572
+ {/if}
1573
+ {#each allColumns as col (col.id)}
1574
+ <td class="sv-grid-cell sv-grid-placeholder-cell">
1575
+ <span class="sv-grid-placeholder-skeleton" aria-hidden="true"></span>
1576
+ </td>
1577
+ {/each}
1459
1578
  {/if}
1460
1579
  </tr>
1461
1580
  {/snippet}
1462
1581
 
1463
- <div
1464
- class="sv-grid-root"
1465
- class:sv-grid-root-fill={opt.containerHeight === "100%"}
1466
- style={chartDockReserveStyle}
1467
- data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
1468
- data-selbar={
1469
- ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0
1470
- ? (getSelectionBarView() ? ctrl.selectionBarPosition : `upsell-${ctrl.selectionBarPosition}`)
1471
- : undefined
1472
- }
1473
- use:lazyRowResize={{
1474
- disabled: !ctrl.rowResizeOn,
1475
- // No gutter column on most grids, so fall back to the first body cell -
1476
- // otherwise `rowResize` would be a prop that silently does nothing.
1477
- anchor: "row",
1478
- onResize: (index, height) => ctrl.setRowResizeHeight(index, height),
1479
- }}
1480
- use:lazyColumnResize={{
1481
- disabled: !columnResizeEnabled,
1482
- getWidth: (columnId) => ctrl.getColumnWidth(columnId),
1483
- onResize: (columnId, width) => {
1484
- ctrl.columnWidths = { ...ctrl.columnWidths, [columnId]: width };
1485
- },
1486
- label: (columnId) => {
1487
- const col = ctrl.findColumnById(columnId);
1488
- return col ? toolPanelHeaderLabel(col) : columnId;
1489
- },
1490
- canResize: (columnId) => ctrl.columnResizable(columnId),
1491
- onAutosize: (columnId) => ctrl.autosizeColumn(columnId),
1492
- }}
1493
- >
1494
- {#if showGlobalFilterEffective}
1495
- <label class="sv-grid-global-filter">
1496
- Filter all rows
1497
- <input bind:value={ctrl.globalFilter} placeholder="Type to filter..." />
1498
- </label>
1499
- {/if}
1500
-
1501
- {#if toolPanelEnabled || ctrl.chartingEnabled || ctrl.advancedFilter}
1502
- <div class="sv-grid-toolbar">
1503
- {#if ctrl.advancedFilter}
1504
- <!--
1505
- An advanced filter is set from a panel the consumer mounts, which
1506
- may be scrolled away or in a drawer. Without something here, rows
1507
- are missing from the grid with nothing on screen to explain why or
1508
- to undo it. This is that something: state plus a way out.
1509
- -->
1510
- <span class="sv-grid-advf-chip">
1511
- {@render icon("advanced-filter")}
1512
- {messages.advancedFilterActive}
1513
- <button
1514
- type="button"
1515
- class="sv-grid-advf-clear"
1516
- aria-label={messages.advancedFilterClear}
1517
- title={messages.advancedFilterClear}
1518
- onclick={() => (ctrl.advancedFilter = null)}
1519
- >{@render icon("clear")}</button>
1520
- </span>
1521
- {/if}
1522
- {#if toolPanelEnabled}
1523
- <button
1524
- type="button"
1525
- class="sv-grid-toolbar-btn"
1526
- class:is-active={ctrl.toolPanelOpen}
1527
- aria-label={ctrl.toolPanelOpen
1528
- ? "Close tool panel"
1529
- : "Open tool panel (columns & filters)"}
1530
- aria-expanded={ctrl.toolPanelOpen}
1531
- onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
1532
- >
1533
- {@render icon("tool-panel")}
1534
- Columns &amp; Filters
1535
- </button>
1536
- {/if}
1537
- {#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>
1549
- {/if}
1550
- </div>
1551
- {/if}
1552
-
1553
- {#if hasMeasured && (opt.paginationPosition === "top" || opt.paginationPosition === "both")}
1554
- <GridFooter {ctrl} {icon} showStatus={false} top pager pageSizeOptions={opt.pageSizeOptions} />
1555
- {/if}
1556
-
1557
- <div
1558
- class="sv-grid-shell"
1559
- style={`height: ${
1560
- typeof opt.containerHeight === "string"
1561
- ? opt.containerHeight
1562
- : `${opt.containerHeight ?? 520}px`
1563
- }; --sg-thead-h: ${headerHeight}px; --sg-pinned-row-h: ${typeof opt.rowHeight === "number" ? opt.rowHeight : 30}px;`}
1564
- >
1565
- <div
1566
- class="sv-grid-container sv-grid-container-custom-scrollbars"
1567
- class:sv-grid-has-vscroll={hasMeasured && hasVerticalOverflow}
1568
- class:sv-grid-has-hscroll={hasMeasured && hasHorizontalOverflow}
1569
- class:sv-grid-narrow={ctrl.isNarrowResponsive}
1570
- bind:this={ctrl.scrollContainer}
1571
- onscroll={onBodyScroll}
1572
- style={`overflow: auto; position: relative; height: calc(100% - ${hasMeasured && hasHorizontalOverflow ? 16 : 0}px);`}
1573
- >
1574
- <table
1575
- bind:this={ctrl.gridRootEl}
1576
- class="sv-grid-table"
1577
- class:sv-grid-no-row-hover={opt.enableRowHover !== true}
1578
- {...getGridRootA11yProps({
1579
- activeDescendantId,
1580
- rowCount: allRows.length,
1581
- colCount: allColumns.length,
1582
- treegrid: !!opt.serverGroup || !!opt.treeData,
1583
- })}
1584
- onkeydown={onGridKeyDown}
1585
- onpaste={onGridPaste}
1586
- style={`min-width: ${totalColumnWidth}px;`}
1587
- >
1588
- <!-- svelte-ignore a11y_no_redundant_roles -->
1589
- <thead class="sv-grid-head" bind:this={ctrl.theadEl} role="rowgroup">
1590
- <!-- Multi-level group header rows. Only present when the
1591
- consumer's column tree has `columns: [...]` nesting.
1592
- Each TH spans the leaf widths underneath via the
1593
- precomputed `widthPx` + `colSpan` from groupHeaderRows. -->
1594
- {#each groupHeaderRows as row (row.id)}
1595
- <tr
1596
- class="sv-grid-row sv-grid-header-row sv-grid-group-header-row"
1597
- {...getGridRowA11yProps()}
1598
- style={opt.headerHeight
1599
- ? `height: ${opt.headerHeight}px;`
1600
- : undefined}
1601
- >
1602
- {#if showRowNumbersEffective}
1603
- <th
1604
- class="sv-grid-column sv-grid-row-number-column"
1605
- style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1606
- aria-hidden="true"
1607
- ></th>
1608
- {/if}
1609
- {#if showRowSelectionEffective}
1610
- <th
1611
- class="sv-grid-column sv-grid-selection-column"
1612
- style={`width: ${selectionColumnWidth}px; min-width: ${selectionColumnWidth}px; max-width: ${selectionColumnWidth}px; left: ${showRowNumbersEffective ? rowNumberColumnWidth : 0}px;`}
1613
- aria-hidden="true"
1614
- ></th>
1615
- {/if}
1616
- {#if columnVirtualizationEnabled && columnWindowStart > 0}
1617
- <th
1618
- class="sv-grid-column sv-grid-column-spacer"
1619
- aria-hidden="true"
1620
- style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
1621
- ></th>
1622
- {/if}
1623
- {#each row.cells as cell (cell.key)}
1624
- <th
1625
- class="sv-grid-column sv-grid-group-header-cell"
1626
- class:sv-grid-group-header-placeholder={cell.isPlaceholder}
1627
- colspan={cell.colSpan}
1628
- style={`width: ${cell.widthPx}px; min-width: ${cell.widthPx}px; max-width: ${cell.widthPx}px;`}
1629
- >
1630
- {#if !cell.isPlaceholder}
1631
- {#if cell.collapsible}
1632
- <button
1582
+ <!-- A single pinned row (top or bottom). Read-only by design: no
1583
+ inline editing, no row-selection checkbox, no fill handle.
1584
+ Position-sticky CSS keeps it anchored to the top of the body or
1585
+ the bottom of the viewport while the rest scrolls. -->
1586
+ <!--
1587
+ One body row, whatever path renders it: the virtualized window, the
1588
+ plain full-list body, or the frozen rows pinned above the window. The
1589
+ two loops used to carry a copy each and had drifted apart (cell
1590
+ tooltips on one, the fill handle on the other), so a feature landed in
1591
+ one mode and not the other. `rowStyle` is the height the caller knows:
1592
+ the virtualizer's measured size, or the declared one.
1593
+ -->
1594
+ {#snippet bodyRow(row: Row<TData>, rowIndex: number, rowStyle: string)}
1595
+ {@const phState = ctrl.placeholderStateOf(row)}
1596
+ {#if phState}
1597
+ {@const phContinues =
1598
+ phState === "failed" &&
1599
+ rowIndex > firstRenderedRowIndex &&
1600
+ ctrl.placeholderStateOf(allRows[rowIndex - 1]!) === "failed"}
1601
+ {@render placeholderRowMarkup(row, rowIndex, phState, rowStyle, phContinues)}
1602
+ {:else if opt.isDetailRow?.(row.original as TData, rowIndex)}
1603
+ {@render detailRowMarkup(row, rowIndex)}
1604
+ {:else if isGroupRow(row) && !groupColumnMode}
1605
+ <tr
1606
+ class="sv-grid-row sv-grid-group-row"
1607
+ class:sv-grid-row-selected={isRowSelected(row.id)}
1608
+ class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1609
+ class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1610
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1611
+ aria-level={row.depth + 1}
1612
+ aria-expanded={row.getIsExpanded?.() ? "true" : "false"}
1613
+ {...getGridRowA11yProps(rowIndex + 1)}
1614
+ class:sv-grid-row-collapsed={ctrl.isRowCollapsed(rowIndex)}
1615
+ style={rowStyle}
1616
+ >
1617
+ <td
1618
+ class="sv-grid-cell sv-grid-group-cell"
1619
+ class:sv-grid-cell-active={activeCell.rowIndex ===
1620
+ rowIndex}
1621
+ colspan={allColumns.length +
1622
+ (showRowNumbersEffective ? 1 : 0) +
1623
+ (showRowSelectionEffective ? 1 : 0)}
1624
+ onclick={() => row.toggleExpanded?.()}
1625
+ >
1626
+ {@render groupRowContent(row)}
1627
+ </td>
1628
+ </tr>
1629
+ {:else}
1630
+ {@const userRowClass = computeRowClass(row, rowIndex)}
1631
+ <tr
1632
+ class={`sv-grid-row ${userRowClass} ${rowDropClass(rowIndex)}`}
1633
+ class:sv-grid-row-selected={isRowSelected(row.id)}
1634
+ class:sv-grid-row-alt={opt.zebraRows &&
1635
+ rowIndex % 2 === 1}
1636
+ class:sv-grid-row-draggable={rowDragManagedEffective}
1637
+ class:sv-grid-group-footer-row={isGroupFooterRow(row)}
1638
+ class:sv-grid-grand-total-row={isGrandTotalRow(row)}
1639
+ class:sv-grid-row-auto-height={autoRowHeightOn}
1640
+ class:sv-grid-row-frozen={rowIndex < frozenRowCount}
1641
+ class:sv-grid-row-frozen-last={frozenRowCount > 0 && rowIndex === frozenRowCount - 1}
1642
+ style:top={rowIndex < frozenRowCount ? `${frozenRowTop(rowIndex)}px` : undefined}
1643
+ {...getGridRowA11yProps(rowIndex + 1)}
1644
+ aria-level={sgAriaLevel(row)}
1645
+ aria-expanded={sgAriaExpanded(row)}
1646
+ {...rowDragAttrs(rowIndex)}
1647
+ class:sv-grid-row-collapsed={ctrl.isRowCollapsed(rowIndex)}
1648
+ style={rowStyle}
1649
+ use:measureRowHeight={rowIndex}
1650
+ >
1651
+ {#if showRowNumbersEffective}
1652
+ <td
1653
+ class="sv-grid-cell sv-grid-row-number-cell"
1654
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1655
+ >{rowIndex + 1}</td
1656
+ >
1657
+ {/if}
1658
+ {#if showRowSelectionEffective}
1659
+ <td
1660
+ 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;`}
1662
+ onclick={() => toggleRowSelectionById(row.id)}
1663
+ >
1664
+ <button
1665
+ type="button"
1666
+ class="sv-grid-checkbox"
1667
+ role="checkbox"
1668
+ aria-checked={isRowSelected(row.id)}
1669
+ aria-label="Select row"
1670
+ onclick={(event) => {
1671
+ event.stopPropagation();
1672
+ toggleRowSelectionById(row.id);
1673
+ }}
1674
+ onkeydown={(event) =>
1675
+ toggleCheckboxWithKeyboard(event, () => {
1676
+ event.stopPropagation();
1677
+ toggleRowSelectionById(row.id);
1678
+ })}
1679
+ ></button>
1680
+ </td>
1681
+ {/if}
1682
+ {#if columnVirtualizationEnabled && columnWindowStart > 0}
1683
+ <td
1684
+ class="sv-grid-cell sv-grid-cell-spacer"
1685
+ aria-hidden="true"
1686
+ style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
1687
+ ></td>
1688
+ {/if}
1689
+ {#each renderedColumns as rendered (rendered.column.id)}
1690
+ {@const colIndex = rendered.item.index}
1691
+ {@const draw = mergeIndex
1692
+ ? mergeDrawAt(mergeIndex, rowIndex, colIndex, rowIndex < frozenRowCount ? mergeFrozenWindow : mergeBodyWindow)
1693
+ : null}
1694
+ {#if draw?.kind !== "skip"}
1695
+ {@const cellRow = draw?.kind === "continuation" ? (allRows[draw.merge.rowIndex] ?? row) : row}
1696
+ {@const cellColumn = draw?.kind === "continuation" ? (allColumns[draw.merge.colIndex] ?? rendered.column) : rendered.column}
1697
+ {@const baseValue = getColumnBaseValue(
1698
+ cellRow,
1699
+ cellColumn,
1700
+ )}
1701
+ {@const cellValue = getCellDisplayValue(
1702
+ cellRow.id,
1703
+ cellColumn.id,
1704
+ baseValue,
1705
+ )}
1706
+ {@const isEditing =
1707
+ ctrl.editingCell?.rowId === cellRow.id &&
1708
+ ctrl.editingCell?.columnId === cellColumn.id}
1709
+ {@const inRowEdit =
1710
+ !!fullRowEdit &&
1711
+ fullRowEdit.rowId === cellRow.id &&
1712
+ cellColumn.id in fullRowEdit.draft}
1713
+ {@const cellSel = cellSelectionState(rowIndex, colIndex)}
1714
+ {@const fillEdges = fillMarqueeEdges(rowIndex, colIndex)}
1715
+ {@const userCellClass = computeCellClass(
1716
+ cellRow,
1717
+ cellColumn,
1718
+ )}
1719
+ {@const cellTooltip = computeCellTooltip(
1720
+ cellRow,
1721
+ cellColumn,
1722
+ )}
1723
+ {@const cellValidity = computeCellValidity(
1724
+ cellRow,
1725
+ cellColumn,
1726
+ )}
1727
+ {@const cellNote = computeCellNote(
1728
+ cellRow,
1729
+ cellColumn,
1730
+ )}
1731
+ {@const tdWidth = draw ? mergedWidth(colIndex, draw.colSpan, rendered.item.size) : rendered.item.size}
1732
+ <td
1733
+ rowspan={draw && draw.rowSpan > 1 ? draw.rowSpan : undefined}
1734
+ colspan={draw && draw.colSpan > 1 ? draw.colSpan : undefined}
1735
+ data-merge-origin={draw ? `${draw.merge.rowIndex}:${draw.merge.colIndex}` : undefined}
1736
+ class:sv-grid-cell-merged={!!draw}
1737
+ class={`sv-grid-cell ${userCellClass}`}
1738
+ class:sv-grid-cell-editing={isEditing || inRowEdit}
1739
+ class:sv-grid-cell-collapsed={!!collapsedColumns[rendered.column.id]}
1740
+ class:sv-grid-cell-active={!!cellSel?.active}
1741
+ class:sv-grid-cell-has-fill-handle={!!cellSel?.fillHandle}
1742
+ class:sv-grid-cell-cf={hasConditionalFormats}
1743
+ class:sv-grid-cell-invalid={cellValidity.invalid}
1744
+ class:sv-grid-cell-has-note={cellNote != null}
1745
+ aria-invalid={cellValidity.invalid ? "true" : undefined}
1746
+ data-svgrid-row={rowIndex}
1747
+ data-svgrid-col={colIndex}
1748
+ data-col-id={rendered.column.id}
1749
+ data-align={getColumnAlign(rendered.column)}
1750
+ data-pinned={isColumnPinned(rendered.column.id) ??
1751
+ undefined}
1752
+ data-selected-range={cellSel?.edges ? "true" : undefined}
1753
+ data-range-top={cellSel?.edges?.top ? "true" : undefined}
1754
+ data-range-bottom={cellSel?.edges?.bottom
1755
+ ? "true"
1756
+ : undefined}
1757
+ data-range-left={cellSel?.edges?.left
1758
+ ? "true"
1759
+ : undefined}
1760
+ data-range-right={cellSel?.edges?.right
1761
+ ? "true"
1762
+ : undefined}
1763
+ data-fill-preview={isInFillPreview(rowIndex, colIndex)
1764
+ ? "true"
1765
+ : undefined}
1766
+ data-fill-top={fillEdges?.top ? "true" : undefined}
1767
+ data-fill-bottom={fillEdges?.bottom ? "true" : undefined}
1768
+ data-fill-left={fillEdges?.left ? "true" : undefined}
1769
+ data-fill-right={fillEdges?.right ? "true" : undefined}
1770
+ style={`width: ${tdWidth}px; min-width: ${tdWidth}px; max-width: ${tdWidth}px; ${cellPinStyle(rendered.column.id)}`}
1771
+ onpointerdown={(event) =>
1772
+ onCellPointerDown(rowIndex, colIndex, event)}
1773
+ onpointerenter={(event) => {
1774
+ onCellPointerEnter(rowIndex, colIndex);
1775
+ // The column tooltip fires on whole-cell hover; a
1776
+ // per-cell note waits for its corner below. A
1777
+ // validation message wins over the column's tooltip.
1778
+ const tip =
1779
+ cellValidity.invalid && cellValidity.message
1780
+ ? cellValidity.message
1781
+ : cellTooltip;
1782
+ if (tip)
1783
+ showTooltipFor(
1784
+ event.currentTarget as HTMLElement,
1785
+ tip,
1786
+ );
1787
+ }}
1788
+ onpointerleave={hideTooltip}
1789
+ ondblclick={() =>
1790
+ emitCellDoubleClick(rowIndex, colIndex)}
1791
+ onclick={() => onCellClick(rowIndex, colIndex)}
1792
+ oncontextmenu={(event) =>
1793
+ openContextMenu(
1794
+ event,
1795
+ rowIndex,
1796
+ colIndex,
1797
+ rendered.column.id,
1798
+ )}
1799
+ use:cellFlashAction={{
1800
+ rowId: cellRow.id,
1801
+ value: cellValue,
1802
+ active: !!cellColumn.columnDef.cellFlash,
1803
+ className: flashClassFor(
1804
+ cellColumn.columnDef.cellFlash,
1805
+ ),
1806
+ }}
1807
+ role="gridcell"
1808
+ id={getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex)}
1809
+ aria-colindex={colIndex + 1}
1810
+ aria-rowindex={rowIndex + 1}
1811
+ aria-selected={isRowSelected(row.id)}
1812
+ >
1813
+ {#if inRowEdit || isEditing}
1814
+ <!-- The editing cell stays empty until the lazy
1815
+ editor chunk lands, exactly as it already did
1816
+ for the dropdown / date editors. Falling back
1817
+ to the read-only value here would flash stale
1818
+ content into a cell the user is editing. -->
1819
+ {#if CellEditor}
1820
+ <CellEditor {ctrl} column={cellColumn} row={cellRow} fullRow={inRowEdit} />
1821
+ {/if}
1822
+ {:else}
1823
+ {#if plainCellBody}
1824
+ {@render cellBody(cellRow, cellColumn, cellValue)}
1825
+ {:else}
1826
+ {@render cellBodyWithFormat(
1827
+ cellRow,
1828
+ cellColumn,
1829
+ cellValue,
1830
+ )}
1831
+ {/if}
1832
+ {/if}
1833
+ {#if !isEditing && cellSel?.fillHandle}
1834
+ <!-- Excel-style fill handle: drag down/right to
1835
+ extend the selection and pattern-fill the new
1836
+ cells on release. Rendered inside the bottom-
1837
+ right cell of the selection range (or active
1838
+ cell if there's no range). -->
1839
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
1840
+ <div
1841
+ class="sv-grid-fill-handle"
1842
+ role="button"
1843
+ aria-label="Fill handle"
1844
+ tabindex={-1}
1845
+ onpointerdown={(event) =>
1846
+ startFillDrag(event, rowIndex, colIndex)}
1847
+ ondblclick={(event) => {
1848
+ // Not the cell's double-click: this fills
1849
+ // down as far as the neighbour column goes.
1850
+ event.stopPropagation();
1851
+ event.preventDefault();
1852
+ ctrl.fillDownToNeighbour();
1853
+ }}
1854
+ ></div>
1855
+ {/if}
1856
+ {#if cellValidity.invalid && cellValidity.message}
1857
+ <!-- The validation message, for assistive tech: the
1858
+ tooltip above is mouse-only, and unlike a title
1859
+ a visually-hidden span adds no second native
1860
+ tooltip competing with the custom one. -->
1861
+ <span class="sv-grid-sr-only"
1862
+ >{cellValidity.message}</span
1863
+ >
1864
+ {/if}
1865
+ {#if cellNote != null && !isEditing}
1866
+ <span
1867
+ class="sv-grid-cell-note-corner"
1868
+ aria-label="Note"
1869
+ onpointerenter={(event) => {
1870
+ event.stopPropagation();
1871
+ showTooltipFor(
1872
+ event.currentTarget as HTMLElement,
1873
+ cellNote,
1874
+ );
1875
+ }}
1876
+ onpointerleave={(event) => {
1877
+ event.stopPropagation();
1878
+ hideTooltip();
1879
+ }}
1880
+ ></span>
1881
+ {/if}
1882
+ </td>
1883
+ {/if}
1884
+ {/each}
1885
+ {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
1886
+ <td
1887
+ class="sv-grid-cell sv-grid-cell-spacer"
1888
+ aria-hidden="true"
1889
+ style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
1890
+ ></td>
1891
+ {/if}
1892
+ </tr>
1893
+ {/if}
1894
+ {/snippet}
1895
+ {#snippet pinnedRowBody(
1896
+ rowData: TData,
1897
+ where: "top" | "bottom",
1898
+ index: number,
1899
+ )}
1900
+ <tr
1901
+ class={`sv-grid-row sv-grid-pinned-row sv-grid-pinned-row-${where}`}
1902
+ data-pinned-row={where}
1903
+ data-pinned-index={index}
1904
+ >
1905
+ {#if showRowNumbersEffective}
1906
+ <td
1907
+ class="sv-grid-cell sv-grid-row-number-cell"
1908
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
1909
+ >{#if where === "top"}{@render icon("pinned-row-top")}{:else}{@render icon("pinned-row-bottom")}{/if}</td
1910
+ >
1911
+ {/if}
1912
+ {#if showRowSelectionEffective}
1913
+ <td
1914
+ 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;`}
1916
+ ></td>
1917
+ {/if}
1918
+ {#if columnVirtualizationEnabled && columnWindowStart > 0}
1919
+ <td
1920
+ class="sv-grid-cell sv-grid-cell-spacer"
1921
+ aria-hidden="true"
1922
+ style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
1923
+ ></td>
1924
+ {/if}
1925
+ {#each renderedColumns as rendered (rendered.column.id)}
1926
+ {@const value = getPinnedCellValue(rowData, rendered.column)}
1927
+ {@const userCellClass = computePinnedCellClass(
1928
+ rowData,
1929
+ rendered.column,
1930
+ )}
1931
+ <td
1932
+ class={`sv-grid-cell ${userCellClass}`}
1933
+ data-col-id={rendered.column.id}
1934
+ data-pinned={isColumnPinned(rendered.column.id) ?? undefined}
1935
+ style={`width: ${rendered.item.size}px; min-width: ${rendered.item.size}px; max-width: ${rendered.item.size}px; ${cellPinStyle(rendered.column.id)}`}
1936
+ >{formatPinnedValue(rendered.column, value)}</td
1937
+ >
1938
+ {/each}
1939
+ {#if columnVirtualizationEnabled && columnWindowRightSpacer > 0}
1940
+ <td
1941
+ class="sv-grid-cell sv-grid-cell-spacer"
1942
+ aria-hidden="true"
1943
+ style={`width: ${columnWindowRightSpacer}px; min-width: ${columnWindowRightSpacer}px; max-width: ${columnWindowRightSpacer}px;`}
1944
+ ></td>
1945
+ {/if}
1946
+ </tr>
1947
+ {/snippet}
1948
+
1949
+ <div
1950
+ class="sv-grid-root"
1951
+ class:sv-grid-root-fill={opt.containerHeight === "100%"}
1952
+ style={chartDockReserveStyle}
1953
+ data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
1954
+ data-selbar={
1955
+ ctrl.selectionBarVisible
1956
+ ? (getSelectionBarView() ? ctrl.selectionBarPosition : `upsell-${ctrl.selectionBarPosition}`)
1957
+ : undefined
1958
+ }
1959
+ use:lazyRowResize={{
1960
+ disabled: !ctrl.rowResizeOn,
1961
+ // No gutter column on most grids, so fall back to the first body cell -
1962
+ // otherwise `rowResize` would be a prop that silently does nothing.
1963
+ anchor: "row",
1964
+ onResize: (index, height) => ctrl.resizeRowByUser(index, height),
1965
+ onAutosize: (index) => ctrl.resizeRowByUser(index, null),
1966
+ }}
1967
+ use:lazyColumnResize={{
1968
+ disabled: !columnResizeEnabled,
1969
+ getWidth: (columnId) => ctrl.getColumnWidth(columnId),
1970
+ onResize: (columnId, width) => {
1971
+ ctrl.columnWidths = { ...ctrl.columnWidths, [columnId]: width };
1972
+ },
1973
+ onResizeEnd: (columnId, width, startWidth) => ctrl.resizeColumnByUser(columnId, width, startWidth),
1974
+ label: (columnId) => {
1975
+ const col = ctrl.findColumnById(columnId);
1976
+ return col ? toolPanelHeaderLabel(col) : columnId;
1977
+ },
1978
+ canResize: (columnId) => ctrl.columnResizable(columnId),
1979
+ onAutosize: (columnId) => ctrl.autosizeColumnByUser(columnId),
1980
+ }}
1981
+ >
1982
+ {#if showGlobalFilterEffective}
1983
+ <label class="sv-grid-global-filter">
1984
+ Filter all rows
1985
+ <input bind:value={ctrl.globalFilter} placeholder="Type to filter..." />
1986
+ </label>
1987
+ {/if}
1988
+
1989
+ {#if toolPanelEnabled || ctrl.chartingEnabled || ctrl.advancedFilter}
1990
+ <div class="sv-grid-toolbar">
1991
+ {#if ctrl.advancedFilter}
1992
+ <!--
1993
+ An advanced filter is set from a panel the consumer mounts, which
1994
+ may be scrolled away or in a drawer. Without something here, rows
1995
+ are missing from the grid with nothing on screen to explain why or
1996
+ to undo it. This is that something: state plus a way out.
1997
+ -->
1998
+ <span class="sv-grid-advf-chip">
1999
+ {@render icon("advanced-filter")}
2000
+ {messages.advancedFilterActive}
2001
+ <button
2002
+ type="button"
2003
+ class="sv-grid-advf-clear"
2004
+ aria-label={messages.advancedFilterClear}
2005
+ title={messages.advancedFilterClear}
2006
+ onclick={() => (ctrl.advancedFilter = null)}
2007
+ >{@render icon("clear")}</button>
2008
+ </span>
2009
+ {/if}
2010
+ {#if toolPanelEnabled}
2011
+ <button
2012
+ type="button"
2013
+ class="sv-grid-toolbar-btn"
2014
+ class:is-active={ctrl.toolPanelOpen}
2015
+ aria-label={ctrl.toolPanelOpen
2016
+ ? "Close tool panel"
2017
+ : "Open tool panel (columns & filters)"}
2018
+ aria-expanded={ctrl.toolPanelOpen}
2019
+ onclick={() => (ctrl.toolPanelOpen = !ctrl.toolPanelOpen)}
2020
+ >
2021
+ {@render icon("tool-panel")}
2022
+ Columns &amp; Filters
2023
+ </button>
2024
+ {/if}
2025
+ {#if ctrl.chartingEnabled}
2026
+ {@render chartToggle()}
2027
+ {/if}
2028
+ </div>
2029
+ {/if}
2030
+
2031
+ {#if hasMeasured && (opt.paginationPosition === "top" || opt.paginationPosition === "both")}
2032
+ <GridFooter {ctrl} {icon} showStatus={false} top pager pageSizeOptions={opt.pageSizeOptions} />
2033
+ {/if}
2034
+
2035
+ <div
2036
+ class="sv-grid-shell"
2037
+ style={`height: ${
2038
+ typeof opt.containerHeight === "string"
2039
+ ? opt.containerHeight
2040
+ : `${opt.containerHeight ?? 520}px`
2041
+ }; --sg-thead-h: ${headerHeight}px; --sg-frozen-h: ${frozenBandHeight}px; --sg-pinned-row-h: ${typeof opt.rowHeight === "number" ? opt.rowHeight : 30}px;`}
2042
+ >
2043
+ <div
2044
+ class="sv-grid-container sv-grid-container-custom-scrollbars"
2045
+ class:sv-grid-has-vscroll={hasMeasured && hasVerticalOverflow}
2046
+ class:sv-grid-has-hscroll={hasMeasured && hasHorizontalOverflow}
2047
+ class:sv-grid-narrow={ctrl.isNarrowResponsive}
2048
+ bind:this={ctrl.scrollContainer}
2049
+ onscroll={onBodyScroll}
2050
+ style={`overflow: auto; position: relative; height: calc(100% - ${hasMeasured && hasHorizontalOverflow ? 16 : 0}px);`}
2051
+ >
2052
+ <table
2053
+ bind:this={ctrl.gridRootEl}
2054
+ class="sv-grid-table"
2055
+ class:sv-grid-no-row-hover={opt.enableRowHover !== true}
2056
+ {...getGridRootA11yProps({
2057
+ activeDescendantId,
2058
+ rowCount: allRows.length,
2059
+ colCount: allColumns.length,
2060
+ treegrid: !!opt.serverGroup || !!opt.treeData,
2061
+ })}
2062
+ onkeydown={onGridKeyDown}
2063
+ onpaste={onGridPaste}
2064
+ style={`min-width: ${totalColumnWidth}px;`}
2065
+ >
2066
+ <!-- svelte-ignore a11y_no_redundant_roles -->
2067
+ <thead class="sv-grid-head" bind:this={ctrl.theadEl} role="rowgroup">
2068
+ <!-- Multi-level group header rows. Only present when the
2069
+ consumer's column tree has `columns: [...]` nesting.
2070
+ Each TH spans the leaf widths underneath via the
2071
+ precomputed `widthPx` + `colSpan` from groupHeaderRows. -->
2072
+ {#each groupHeaderRows as row (row.id)}
2073
+ <tr
2074
+ class="sv-grid-row sv-grid-header-row sv-grid-group-header-row"
2075
+ {...getGridRowA11yProps()}
2076
+ style={opt.headerHeight
2077
+ ? `height: ${opt.headerHeight}px;`
2078
+ : undefined}
2079
+ >
2080
+ {#if showRowNumbersEffective}
2081
+ <th
2082
+ class="sv-grid-column sv-grid-row-number-column"
2083
+ style={`width: ${rowNumberColumnWidth}px; min-width: ${rowNumberColumnWidth}px; max-width: ${rowNumberColumnWidth}px; left: 0;`}
2084
+ aria-hidden="true"
2085
+ ></th>
2086
+ {/if}
2087
+ {#if showRowSelectionEffective}
2088
+ <th
2089
+ 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;`}
2091
+ aria-hidden="true"
2092
+ ></th>
2093
+ {/if}
2094
+ {#if columnVirtualizationEnabled && columnWindowStart > 0}
2095
+ <th
2096
+ class="sv-grid-column sv-grid-column-spacer"
2097
+ aria-hidden="true"
2098
+ style={`width: ${columnWindowStart}px; min-width: ${columnWindowStart}px; max-width: ${columnWindowStart}px;`}
2099
+ ></th>
2100
+ {/if}
2101
+ {#each row.cells as cell (cell.key)}
2102
+ <th
2103
+ class="sv-grid-column sv-grid-group-header-cell"
2104
+ class:sv-grid-group-header-placeholder={cell.isPlaceholder}
2105
+ colspan={cell.colSpan}
2106
+ style={`width: ${cell.widthPx}px; min-width: ${cell.widthPx}px; max-width: ${cell.widthPx}px;`}
2107
+ >
2108
+ {#if !cell.isPlaceholder}
2109
+ {#if cell.collapsible}
2110
+ <button
1633
2111
  type="button"
1634
2112
  class="sv-grid-group-toggle"
1635
2113
  class:is-collapsed={cell.collapsed}
@@ -1723,6 +2201,7 @@
1723
2201
  )}
1724
2202
  <th
1725
2203
  class="sv-grid-column"
2204
+ class:sv-grid-column-collapsed={!!collapsedColumns[header.column.id]}
1726
2205
  class:is-drag-target-before={colDropOnId ===
1727
2206
  header.column.id && colDropSide === "before"}
1728
2207
  class:is-drag-target-after={colDropOnId ===
@@ -2180,6 +2659,9 @@
2180
2659
  </td>
2181
2660
  </tr>
2182
2661
  {:else if rowVirtualizationEnabled}
2662
+ {#each frozenRowList as row, rowIndex (row.id)}
2663
+ {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2664
+ {/each}
2183
2665
  {#if rowTopSpacer > 0}
2184
2666
  <tr class="sv-grid-row sv-grid-row-spacer" aria-hidden="true">
2185
2667
  <td
@@ -2191,256 +2673,17 @@
2191
2673
  ></td>
2192
2674
  </tr>
2193
2675
  {/if}
2676
+ <!-- The virtualizer counts every row, frozen ones included, so its
2677
+ offsets and sizes stay in row-index space; the frozen rows are
2678
+ rendered in the band above and SKIPPED here. Shifting the
2679
+ index instead (item i drawn as row i + N) sized every row with
2680
+ the height of the row N above it, so a row dragged taller under
2681
+ a freeze showed its neighbour's height. -->
2194
2682
  {#each virtualRows as rowItem (rowItem.key)}
2195
2683
  {@const rowIndex = rowItem.index}
2196
2684
  {@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}
2685
+ {#if row && rowIndex >= frozenRowCount}
2686
+ {@render bodyRow(row, rowIndex, rowHeightStyle(rowItem.size))}
2444
2687
  {/if}
2445
2688
  {/each}
2446
2689
  {#if rowBottomSpacer > 0}
@@ -2456,253 +2699,7 @@
2456
2699
  {/if}
2457
2700
  {:else}
2458
2701
  {#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}
2702
+ {@render bodyRow(row, rowIndex, rowStyleFor(rowIndex))}
2706
2703
  {/each}
2707
2704
  {/if}
2708
2705
  </tbody>
@@ -3132,7 +3129,7 @@
3132
3129
  `absolute` against it, so moving it outside would anchor it to the
3133
3130
  page. The renderer is Enterprise and arrives through the
3134
3131
  selection-bar-view seam; without it, a short note in its place. -->
3135
- {#if ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0}
3132
+ {#if ctrl.selectionBarVisible}
3136
3133
  {#if getSelectionBarView()}
3137
3134
  {@const SelectionBarView = getSelectionBarView()}
3138
3135
  <SelectionBarView {ctrl} />