@svgrid/grid 2.7.0 → 3.0.1

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 (264) hide show
  1. package/CHANGELOG.md +116 -88
  2. package/README.md +199 -199
  3. package/dist/FlexRender.svelte +96 -96
  4. package/dist/GridFooter.svelte +181 -181
  5. package/dist/SvAutoComplete.svelte +169 -169
  6. package/dist/SvAvatar.svelte +75 -75
  7. package/dist/SvCalendar.svelte +503 -503
  8. package/dist/SvCarousel.svelte +141 -141
  9. package/dist/SvCheckBox.svelte +102 -102
  10. package/dist/SvCircularProgress.svelte +109 -109
  11. package/dist/SvColorInput.svelte +181 -181
  12. package/dist/SvComboBox.svelte +279 -279
  13. package/dist/SvContextMenu.svelte +116 -116
  14. package/dist/SvCountryInput.svelte +163 -163
  15. package/dist/SvDrawer.svelte +254 -254
  16. package/dist/SvDropDownList.svelte +378 -378
  17. package/dist/SvDurationInput.svelte +126 -126
  18. package/dist/SvField.svelte +293 -293
  19. package/dist/SvForm.svelte +437 -437
  20. package/dist/SvGridCellEditor.svelte +748 -744
  21. package/dist/SvGridChart.svelte +1716 -1716
  22. package/dist/SvGridChartPanel.svelte +485 -485
  23. package/dist/SvGridChartView.svelte +70 -70
  24. package/dist/SvGridDropdown.svelte +728 -728
  25. package/dist/SvGridSelect.svelte +270 -270
  26. package/dist/SvGroupCell.svelte +116 -116
  27. package/dist/SvListBox.svelte +334 -334
  28. package/dist/SvMaskedInput.svelte +122 -122
  29. package/dist/SvMenu.svelte +124 -124
  30. package/dist/SvMenuList.svelte +146 -146
  31. package/dist/SvMultiSelect.svelte +293 -293
  32. package/dist/SvNumberInput.svelte +172 -172
  33. package/dist/SvOtpInput.svelte +158 -158
  34. package/dist/SvPasswordInput.svelte +151 -151
  35. package/dist/SvPhoneInput.svelte +133 -133
  36. package/dist/SvPopover.svelte +197 -197
  37. package/dist/SvProgress.svelte +116 -116
  38. package/dist/SvRadioGroup.svelte +107 -107
  39. package/dist/SvRating.svelte +112 -112
  40. package/dist/SvResult.svelte +73 -73
  41. package/dist/SvRichText.svelte +211 -211
  42. package/dist/SvRowGroupPanel.svelte +170 -170
  43. package/dist/SvScrollArea.svelte +61 -61
  44. package/dist/SvSlider.svelte +203 -203
  45. package/dist/SvSwitchButton.svelte +108 -108
  46. package/dist/SvTagsInput.svelte +115 -115
  47. package/dist/SvTextInput.svelte +147 -147
  48. package/dist/SvTimePicker.svelte +245 -245
  49. package/dist/SvToaster.svelte +159 -159
  50. package/dist/SvToggleButton.svelte +85 -85
  51. package/dist/SvTooltip.svelte +161 -161
  52. package/dist/SvTour.svelte +208 -208
  53. package/dist/SvTree.svelte +444 -444
  54. package/dist/SvTreeSelect.svelte +239 -239
  55. package/dist/cdn/{GridMenus-C4oLsJyj.js → GridMenus-CYzOvmNQ.js} +3 -3
  56. package/dist/cdn/{GridMenus-yEivjNwl.js → GridMenus-DFB6iwF8.js} +3 -3
  57. package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
  58. package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
  59. package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
  60. package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
  61. package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
  62. package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
  63. package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
  64. package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
  65. package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
  66. package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
  67. package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
  68. package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
  69. package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
  70. package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
  71. package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
  72. package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
  73. package/dist/cdn/{row-drag-touch-Ddaa-QeB.js → row-drag-touch-CycRR65n.js} +12 -10
  74. package/dist/cdn/{src-COC0vnpg.js → src-C8QjSaq-.js} +53 -55
  75. package/dist/cdn/{src-pYGV2rMR.js → src-D0hO2eYN.js} +52 -54
  76. package/dist/cdn/svgrid.js +7 -7
  77. package/dist/cdn/svgrid.svelte-external.js +7 -7
  78. package/dist/chart-export.js +8 -8
  79. package/dist/editing.js +22 -13
  80. package/dist/row-drag-touch.d.ts +5 -1
  81. package/dist/row-drag-touch.js +15 -4
  82. package/dist/row-drag.js +7 -3
  83. package/package.json +11 -11
  84. package/src/FlexRender.svelte +96 -96
  85. package/src/GridFooter.svelte +181 -181
  86. package/src/SvAutoComplete.svelte +169 -169
  87. package/src/SvAvatar.svelte +75 -75
  88. package/src/SvCalendar.svelte +503 -503
  89. package/src/SvCalendar.test.ts +226 -226
  90. package/src/SvCarousel.svelte +141 -141
  91. package/src/SvCheckBox.svelte +102 -102
  92. package/src/SvCircularProgress.svelte +109 -109
  93. package/src/SvColorInput.svelte +181 -181
  94. package/src/SvComboBox.svelte +279 -279
  95. package/src/SvContextMenu.svelte +116 -116
  96. package/src/SvCountryInput.svelte +163 -163
  97. package/src/SvDrawer.svelte +254 -254
  98. package/src/SvDropDownList.svelte +378 -378
  99. package/src/SvDurationInput.svelte +126 -126
  100. package/src/SvField.svelte +293 -293
  101. package/src/SvForm.svelte +437 -437
  102. package/src/SvForm.test.ts +411 -411
  103. package/src/SvGrid.types.ts +2092 -2092
  104. package/src/SvGridCellEditor.svelte +748 -744
  105. package/src/SvGridChart.svelte +1716 -1716
  106. package/src/SvGridChartPanel.svelte +485 -485
  107. package/src/SvGridChartView.svelte +70 -70
  108. package/src/SvGridDropdown.svelte +728 -728
  109. package/src/SvGridSelect.svelte +270 -270
  110. package/src/SvGroupCell.svelte +116 -116
  111. package/src/SvListBox.svelte +334 -334
  112. package/src/SvMaskedInput.svelte +122 -122
  113. package/src/SvMenu.svelte +124 -124
  114. package/src/SvMenu.test.ts +97 -97
  115. package/src/SvMenuList.svelte +146 -146
  116. package/src/SvMultiSelect.svelte +293 -293
  117. package/src/SvNumberInput.svelte +172 -172
  118. package/src/SvOtpInput.svelte +158 -158
  119. package/src/SvPasswordInput.svelte +151 -151
  120. package/src/SvPhoneInput.svelte +133 -133
  121. package/src/SvPopover.svelte +197 -197
  122. package/src/SvProgress.svelte +116 -116
  123. package/src/SvRadioGroup.svelte +107 -107
  124. package/src/SvRating.svelte +112 -112
  125. package/src/SvResult.svelte +73 -73
  126. package/src/SvRichText.svelte +211 -211
  127. package/src/SvRowGroupPanel.svelte +170 -170
  128. package/src/SvScrollArea.svelte +61 -61
  129. package/src/SvSlider.svelte +203 -203
  130. package/src/SvSwitchButton.svelte +108 -108
  131. package/src/SvTagsInput.svelte +115 -115
  132. package/src/SvTextInput.svelte +147 -147
  133. package/src/SvTimePicker.svelte +245 -245
  134. package/src/SvToaster.svelte +159 -159
  135. package/src/SvToaster.test.ts +95 -95
  136. package/src/SvToggleButton.svelte +85 -85
  137. package/src/SvTooltip.svelte +161 -161
  138. package/src/SvTour.svelte +208 -208
  139. package/src/SvTree.svelte +444 -444
  140. package/src/SvTreeSelect.svelte +239 -239
  141. package/src/a11y/dismissable.test.ts +119 -119
  142. package/src/a11y/dismissable.ts +114 -114
  143. package/src/a11y.contract.test.ts +49 -49
  144. package/src/a11y.test.ts +59 -59
  145. package/src/a11y.ts +61 -61
  146. package/src/ai.test.ts +502 -502
  147. package/src/ai.ts +1419 -1419
  148. package/src/build-api.coverage.test.ts +633 -633
  149. package/src/build-api.ts +846 -846
  150. package/src/builtin-editors.grid.test.ts +83 -83
  151. package/src/cell-formatting.ts +171 -171
  152. package/src/cell-render.test.ts +513 -513
  153. package/src/cell-render.ts +496 -496
  154. package/src/cell-values.ts +148 -148
  155. package/src/chart-export.ts +202 -202
  156. package/src/chart-view.svelte.ts +36 -36
  157. package/src/chart.ts +2321 -2321
  158. package/src/collaboration.test.ts +104 -104
  159. package/src/collaboration.ts +167 -167
  160. package/src/column-groups.ts +78 -78
  161. package/src/core.performance.test.ts +30 -30
  162. package/src/core.ts +1865 -1865
  163. package/src/createAutocomplete.svelte.ts +132 -132
  164. package/src/createCombobox.svelte.ts +191 -191
  165. package/src/createCountryInput.svelte.ts +157 -157
  166. package/src/createDropdownList.svelte.ts +168 -168
  167. package/src/createForm.svelte.ts +386 -386
  168. package/src/createGrid.svelte.ts +42 -42
  169. package/src/createGrid.test.ts +10 -10
  170. package/src/createGridState.svelte.ts +17 -17
  171. package/src/createListbox.svelte.ts +250 -250
  172. package/src/createMenu.svelte.ts +224 -224
  173. package/src/createPopoverSelect.svelte.ts +213 -213
  174. package/src/createSlider.svelte.ts +191 -191
  175. package/src/createTooltip.svelte.ts +144 -144
  176. package/src/createTree.svelte.ts +322 -322
  177. package/src/datetime/date-core.ts +206 -206
  178. package/src/datetime/date-restrict.ts +61 -61
  179. package/src/datetime/timezone.ts +135 -135
  180. package/src/dock-manager-model.ts +596 -596
  181. package/src/dock-model.ts +374 -374
  182. package/src/editing.test.ts +974 -974
  183. package/src/editing.ts +618 -609
  184. package/src/editor-contract.ts +171 -171
  185. package/src/editor-registry.grid.test.ts +144 -144
  186. package/src/editor-registry.ts +122 -122
  187. package/src/export-data-api.test.ts +126 -126
  188. package/src/export-format.test.ts +107 -107
  189. package/src/export-format.ts +601 -601
  190. package/src/filter-operators.ts +160 -160
  191. package/src/filtering/excel-filters.ts +325 -325
  192. package/src/flex-render.ts +3 -3
  193. package/src/form-field.ts +127 -127
  194. package/src/group-display.test.ts +167 -167
  195. package/src/group-display.ts +200 -200
  196. package/src/headless.ts +87 -87
  197. package/src/js-scroller.svelte.ts +173 -173
  198. package/src/keyboard-handlers.ts +270 -270
  199. package/src/keyboard.test.ts +59 -59
  200. package/src/keyboard.ts +97 -97
  201. package/src/list-nav.test.ts +49 -49
  202. package/src/list-nav.ts +29 -29
  203. package/src/list-option.test.ts +56 -56
  204. package/src/list-option.ts +179 -179
  205. package/src/menus.ts +597 -597
  206. package/src/merge-objects.ts +48 -48
  207. package/src/overlays.test.ts +90 -90
  208. package/src/positioning.ts +268 -268
  209. package/src/render-component.ts +28 -28
  210. package/src/row-drag-touch.ts +226 -211
  211. package/src/row-drag.test.ts +401 -353
  212. package/src/row-drag.ts +419 -415
  213. package/src/row-resize.test.ts +524 -524
  214. package/src/row-resize.ts +228 -228
  215. package/src/scheduler-ical.ts +181 -181
  216. package/src/scheduler-model.test.ts +562 -562
  217. package/src/scheduler-model.ts +873 -873
  218. package/src/selection.test.ts +885 -885
  219. package/src/server-data-source.test.ts +383 -383
  220. package/src/server-data-source.ts +469 -469
  221. package/src/sparkline.test.ts +68 -68
  222. package/src/sparkline.ts +169 -169
  223. package/src/spreadsheet.test.ts +488 -488
  224. package/src/spreadsheet.ts +312 -312
  225. package/src/static-functions.ts +11 -11
  226. package/src/subscribe.ts +38 -38
  227. package/src/summaries.ts +113 -113
  228. package/src/svgrid-wrapper.types.ts +563 -563
  229. package/src/svgrid.async-editor-options.test.ts +273 -273
  230. package/src/svgrid.auto-row-height.test.ts +204 -204
  231. package/src/svgrid.behavior.test.ts +910 -910
  232. package/src/svgrid.charting.test.ts +534 -534
  233. package/src/svgrid.comments-autocomplete.test.ts +127 -127
  234. package/src/svgrid.context-menu.test.ts +147 -147
  235. package/src/svgrid.features.test.ts +157 -157
  236. package/src/svgrid.filter-depth.test.ts +163 -163
  237. package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
  238. package/src/svgrid.filter-menu-scroll.test.ts +112 -112
  239. package/src/svgrid.grand-total.test.ts +188 -188
  240. package/src/svgrid.group-display-mode.test.ts +171 -171
  241. package/src/svgrid.group-footers.test.ts +121 -121
  242. package/src/svgrid.group-pagination.test.ts +153 -153
  243. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  244. package/src/svgrid.tree-data.test.ts +186 -186
  245. package/src/svgrid.wrapper.test.ts +63 -63
  246. package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
  247. package/src/test-setup.ts +62 -62
  248. package/src/themes/index.ts +288 -288
  249. package/src/toast-store.svelte.ts +250 -250
  250. package/src/toast-store.test.ts +147 -147
  251. package/src/tree-row-model.test.ts +168 -168
  252. package/src/ui-buttons.test.ts +144 -144
  253. package/src/ui-inputs.test.ts +118 -118
  254. package/src/ui-localization.test.ts +113 -113
  255. package/src/ui-range.test.ts +70 -70
  256. package/src/ui-selection.test.ts +155 -155
  257. package/src/ui-tier1.test.ts +142 -142
  258. package/src/virtual.test.ts +88 -88
  259. package/src/virtualization/column-virtualizer.test.ts +27 -27
  260. package/src/virtualization/column-virtualizer.ts +30 -30
  261. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  262. package/src/virtualization/types.ts +30 -30
  263. package/src/virtualization/virtualizer.test.ts +47 -47
  264. package/src/virtualization/virtualizer.ts +322 -322
@@ -1,200 +1,200 @@
1
- /**
2
- * Row-grouping display helpers. Two pure, framework-free transforms:
3
- *
4
- * - `insertGroupFooters` inserts a footer/total row after each group's children
5
- * (and optionally a grand-total row at the end), carrying the group's already
6
- * computed aggregate values. Mirrors the server model's footer shape
7
- * (`server-group-model.ts`) client-side.
8
- * - `buildAutoGroupColumns` decides, for a grouping display mode, which synthetic
9
- * "auto group" columns to show and which grouped source columns to hide - the
10
- * groundwork for `singleColumn` / `multipleColumns` display types (the current
11
- * default `groupRows` renders a full-width banner and needs neither).
12
- *
13
- * Both are generic over the row / column shape (via small accessor callbacks) so
14
- * they are trivially unit-testable and carry no dependency on the grid runtime.
15
- */
16
-
17
- /** Grouping display variants. */
18
- export type GroupDisplayType = 'groupRows' | 'singleColumn' | 'multipleColumns'
19
-
20
- export type GroupFooterOptions<R> = {
21
- /** Depth of a row (group rows and their descendants). */
22
- getDepth: (row: R) => number
23
- /** True when a row is a group (banner) row. */
24
- isGroup: (row: R) => boolean
25
- /** Build a footer row from the group row it closes (carries its aggregates). */
26
- makeFooter: (groupRow: R) => R
27
- /** Insert a footer after each group's children. */
28
- includeGroupFooter?: boolean
29
- /** Append a grand-total footer at the very end. */
30
- includeGrandTotalFooter?: boolean
31
- /** Build the grand-total footer (over the whole set). Return null to skip. */
32
- makeGrandTotal?: () => R | null
33
- }
34
-
35
- /**
36
- * Insert group footer rows after each group's descendants. A group opened at
37
- * depth d closes when a later row appears at depth <= d (a sibling group or an
38
- * ancestor-level row); its footer is emitted at that point. Order is otherwise
39
- * preserved. No-op when neither footer flag is set.
40
- */
41
- export function insertGroupFooters<R>(rows: ReadonlyArray<R>, opts: GroupFooterOptions<R>): R[] {
42
- const { getDepth, isGroup, makeFooter, includeGroupFooter, includeGrandTotalFooter, makeGrandTotal } = opts
43
- if (!includeGroupFooter && !includeGrandTotalFooter) return [...rows]
44
-
45
- const out: R[] = []
46
- const openGroups: R[] = []
47
-
48
- const closeTo = (depth: number) => {
49
- while (openGroups.length > 0) {
50
- const top = openGroups[openGroups.length - 1]!
51
- if (getDepth(top) < depth) break
52
- openGroups.pop()
53
- if (includeGroupFooter) out.push(makeFooter(top))
54
- }
55
- }
56
-
57
- for (const row of rows) {
58
- // Before adding this row, close any groups it ends (same-or-shallower depth).
59
- closeTo(getDepth(row))
60
- out.push(row)
61
- if (isGroup(row)) openGroups.push(row)
62
- }
63
- // Close everything still open at the end.
64
- closeTo(Number.NEGATIVE_INFINITY)
65
-
66
- if (includeGrandTotalFooter && makeGrandTotal) {
67
- const gt = makeGrandTotal()
68
- if (gt != null) out.push(gt)
69
- }
70
- return out
71
- }
72
-
73
- export type GroupPaginationOptions<R> = {
74
- /** Depth of a row (group rows and their descendants). */
75
- getDepth: (row: R) => number
76
- /** True when a row is a group (banner) row. */
77
- isGroup: (row: R) => boolean
78
- /**
79
- * True when a group row is expanded. Defaults to treating every group as
80
- * expanded. This matters: an EXPANDED group is a header, reprinted above its
81
- * children and costing no page budget, while a COLLAPSED group is the visible
82
- * unit itself and must consume a slot - otherwise a fully collapsed grid has
83
- * no budget-consuming rows at all and every page comes back empty.
84
- */
85
- isExpanded?: (row: R) => boolean
86
- pageIndex: number
87
- pageSize: number
88
- }
89
-
90
- export type GroupPaginationResult<R> = {
91
- /** The rows to render for this page: the page's budget-consuming rows, each
92
- * preceded by whichever ancestor banners have not been printed yet. */
93
- rows: R[]
94
- /** Budget-consuming rows across every page - what the pager divides by.
95
- * Data rows plus collapsed group rows; expanded banners do not count. */
96
- dataRowCount: number
97
- }
98
-
99
- /**
100
- * Paginate a grouped row list by DATA rows, repeating each page's ancestor
101
- * group headers.
102
- *
103
- * Slicing the flat grouped list directly counts group banners against
104
- * `pageSize`, so `pageSize: 10` over ten groups of two shows almost no data
105
- * (#73). Here only non-group rows consume the page budget, and a page reprints
106
- * the group chain its first rows sit under - the same thing a spreadsheet does
107
- * when a grouped table breaks across pages.
108
- *
109
- * Ancestors are tracked with a depth stack rather than parent pointers, so this
110
- * works on the already-flattened expanded model and needs nothing from the row
111
- * objects but depth and group-ness.
112
- */
113
- export function paginateGroupedRows<R>(
114
- rows: ReadonlyArray<R>,
115
- opts: GroupPaginationOptions<R>,
116
- ): GroupPaginationResult<R> {
117
- const { getDepth, isGroup, isExpanded, pageIndex, pageSize } = opts
118
- const expanded = isExpanded ?? (() => true)
119
- const start = pageIndex * pageSize
120
- const end = start + pageSize
121
-
122
- const out: R[] = []
123
- // Expanded banners currently open, outermost first. `printed` marks the ones
124
- // this page has already emitted, so a group spanning rows 3-8 prints its
125
- // banner once, not once per row.
126
- const openGroups: Array<{ row: R; printed: boolean }> = []
127
- let unitsSeen = 0
128
-
129
- for (const row of rows) {
130
- const depth = getDepth(row)
131
- // A row at depth <= an open banner's depth closes it.
132
- while (openGroups.length > 0 && getDepth(openGroups[openGroups.length - 1]!.row) >= depth) {
133
- openGroups.pop()
134
- }
135
- if (isGroup(row) && expanded(row)) {
136
- // A header for rows that follow: reprinted per page, costs no budget.
137
- openGroups.push({ row, printed: false })
138
- continue
139
- }
140
- // Everything else is a visible unit: a data row, or a collapsed group
141
- // standing in for its whole subtree.
142
- const index = unitsSeen
143
- unitsSeen += 1
144
- if (index < start || index >= end) continue
145
- for (const g of openGroups) {
146
- if (g.printed) continue
147
- g.printed = true
148
- out.push(g.row)
149
- }
150
- out.push(row)
151
- }
152
-
153
- return { rows: out, dataRowCount: unitsSeen }
154
- }
155
-
156
- export type AutoGroupColumnSpec = {
157
- /** Synthetic column id (e.g. `__autoGroup` or `__group_<field>`). */
158
- id: string
159
- /** The grouped field this auto column stands in for (null for the single
160
- * combined auto-group column). */
161
- field: string | null
162
- /** Nesting level this column represents (0-based), for indentation. */
163
- level: number
164
- }
165
-
166
- export type AutoGroupResult = {
167
- /** Synthetic auto-group column specs to prepend to the visible columns. */
168
- autoColumns: AutoGroupColumnSpec[]
169
- /** Ids of the grouped SOURCE columns to hide (rolled into the auto columns). */
170
- hiddenSourceIds: Set<string>
171
- }
172
-
173
- /**
174
- * Decide the synthetic auto-group columns for a grouping display mode:
175
- * - `groupRows`: none (the banner path handles display) - `{ [], {} }`.
176
- * - `singleColumn`: one combined auto-group column; every grouped source column
177
- * is hidden.
178
- * - `multipleColumns`: one auto column per grouped field; the grouped source
179
- * columns are hidden.
180
- */
181
- export function buildAutoGroupColumns(
182
- grouping: ReadonlyArray<string>,
183
- displayType: GroupDisplayType,
184
- ): AutoGroupResult {
185
- if (displayType === 'groupRows' || grouping.length === 0) {
186
- return { autoColumns: [], hiddenSourceIds: new Set() }
187
- }
188
- const hiddenSourceIds = new Set(grouping)
189
- if (displayType === 'singleColumn') {
190
- return {
191
- autoColumns: [{ id: '__autoGroup', field: null, level: 0 }],
192
- hiddenSourceIds,
193
- }
194
- }
195
- // multipleColumns
196
- return {
197
- autoColumns: grouping.map((field, level) => ({ id: `__group_${field}`, field, level })),
198
- hiddenSourceIds,
199
- }
200
- }
1
+ /**
2
+ * Row-grouping display helpers. Two pure, framework-free transforms:
3
+ *
4
+ * - `insertGroupFooters` inserts a footer/total row after each group's children
5
+ * (and optionally a grand-total row at the end), carrying the group's already
6
+ * computed aggregate values. Mirrors the server model's footer shape
7
+ * (`server-group-model.ts`) client-side.
8
+ * - `buildAutoGroupColumns` decides, for a grouping display mode, which synthetic
9
+ * "auto group" columns to show and which grouped source columns to hide - the
10
+ * groundwork for `singleColumn` / `multipleColumns` display types (the current
11
+ * default `groupRows` renders a full-width banner and needs neither).
12
+ *
13
+ * Both are generic over the row / column shape (via small accessor callbacks) so
14
+ * they are trivially unit-testable and carry no dependency on the grid runtime.
15
+ */
16
+
17
+ /** Grouping display variants. */
18
+ export type GroupDisplayType = 'groupRows' | 'singleColumn' | 'multipleColumns'
19
+
20
+ export type GroupFooterOptions<R> = {
21
+ /** Depth of a row (group rows and their descendants). */
22
+ getDepth: (row: R) => number
23
+ /** True when a row is a group (banner) row. */
24
+ isGroup: (row: R) => boolean
25
+ /** Build a footer row from the group row it closes (carries its aggregates). */
26
+ makeFooter: (groupRow: R) => R
27
+ /** Insert a footer after each group's children. */
28
+ includeGroupFooter?: boolean
29
+ /** Append a grand-total footer at the very end. */
30
+ includeGrandTotalFooter?: boolean
31
+ /** Build the grand-total footer (over the whole set). Return null to skip. */
32
+ makeGrandTotal?: () => R | null
33
+ }
34
+
35
+ /**
36
+ * Insert group footer rows after each group's descendants. A group opened at
37
+ * depth d closes when a later row appears at depth <= d (a sibling group or an
38
+ * ancestor-level row); its footer is emitted at that point. Order is otherwise
39
+ * preserved. No-op when neither footer flag is set.
40
+ */
41
+ export function insertGroupFooters<R>(rows: ReadonlyArray<R>, opts: GroupFooterOptions<R>): R[] {
42
+ const { getDepth, isGroup, makeFooter, includeGroupFooter, includeGrandTotalFooter, makeGrandTotal } = opts
43
+ if (!includeGroupFooter && !includeGrandTotalFooter) return [...rows]
44
+
45
+ const out: R[] = []
46
+ const openGroups: R[] = []
47
+
48
+ const closeTo = (depth: number) => {
49
+ while (openGroups.length > 0) {
50
+ const top = openGroups[openGroups.length - 1]!
51
+ if (getDepth(top) < depth) break
52
+ openGroups.pop()
53
+ if (includeGroupFooter) out.push(makeFooter(top))
54
+ }
55
+ }
56
+
57
+ for (const row of rows) {
58
+ // Before adding this row, close any groups it ends (same-or-shallower depth).
59
+ closeTo(getDepth(row))
60
+ out.push(row)
61
+ if (isGroup(row)) openGroups.push(row)
62
+ }
63
+ // Close everything still open at the end.
64
+ closeTo(Number.NEGATIVE_INFINITY)
65
+
66
+ if (includeGrandTotalFooter && makeGrandTotal) {
67
+ const gt = makeGrandTotal()
68
+ if (gt != null) out.push(gt)
69
+ }
70
+ return out
71
+ }
72
+
73
+ export type GroupPaginationOptions<R> = {
74
+ /** Depth of a row (group rows and their descendants). */
75
+ getDepth: (row: R) => number
76
+ /** True when a row is a group (banner) row. */
77
+ isGroup: (row: R) => boolean
78
+ /**
79
+ * True when a group row is expanded. Defaults to treating every group as
80
+ * expanded. This matters: an EXPANDED group is a header, reprinted above its
81
+ * children and costing no page budget, while a COLLAPSED group is the visible
82
+ * unit itself and must consume a slot - otherwise a fully collapsed grid has
83
+ * no budget-consuming rows at all and every page comes back empty.
84
+ */
85
+ isExpanded?: (row: R) => boolean
86
+ pageIndex: number
87
+ pageSize: number
88
+ }
89
+
90
+ export type GroupPaginationResult<R> = {
91
+ /** The rows to render for this page: the page's budget-consuming rows, each
92
+ * preceded by whichever ancestor banners have not been printed yet. */
93
+ rows: R[]
94
+ /** Budget-consuming rows across every page - what the pager divides by.
95
+ * Data rows plus collapsed group rows; expanded banners do not count. */
96
+ dataRowCount: number
97
+ }
98
+
99
+ /**
100
+ * Paginate a grouped row list by DATA rows, repeating each page's ancestor
101
+ * group headers.
102
+ *
103
+ * Slicing the flat grouped list directly counts group banners against
104
+ * `pageSize`, so `pageSize: 10` over ten groups of two shows almost no data
105
+ * (#73). Here only non-group rows consume the page budget, and a page reprints
106
+ * the group chain its first rows sit under - the same thing a spreadsheet does
107
+ * when a grouped table breaks across pages.
108
+ *
109
+ * Ancestors are tracked with a depth stack rather than parent pointers, so this
110
+ * works on the already-flattened expanded model and needs nothing from the row
111
+ * objects but depth and group-ness.
112
+ */
113
+ export function paginateGroupedRows<R>(
114
+ rows: ReadonlyArray<R>,
115
+ opts: GroupPaginationOptions<R>,
116
+ ): GroupPaginationResult<R> {
117
+ const { getDepth, isGroup, isExpanded, pageIndex, pageSize } = opts
118
+ const expanded = isExpanded ?? (() => true)
119
+ const start = pageIndex * pageSize
120
+ const end = start + pageSize
121
+
122
+ const out: R[] = []
123
+ // Expanded banners currently open, outermost first. `printed` marks the ones
124
+ // this page has already emitted, so a group spanning rows 3-8 prints its
125
+ // banner once, not once per row.
126
+ const openGroups: Array<{ row: R; printed: boolean }> = []
127
+ let unitsSeen = 0
128
+
129
+ for (const row of rows) {
130
+ const depth = getDepth(row)
131
+ // A row at depth <= an open banner's depth closes it.
132
+ while (openGroups.length > 0 && getDepth(openGroups[openGroups.length - 1]!.row) >= depth) {
133
+ openGroups.pop()
134
+ }
135
+ if (isGroup(row) && expanded(row)) {
136
+ // A header for rows that follow: reprinted per page, costs no budget.
137
+ openGroups.push({ row, printed: false })
138
+ continue
139
+ }
140
+ // Everything else is a visible unit: a data row, or a collapsed group
141
+ // standing in for its whole subtree.
142
+ const index = unitsSeen
143
+ unitsSeen += 1
144
+ if (index < start || index >= end) continue
145
+ for (const g of openGroups) {
146
+ if (g.printed) continue
147
+ g.printed = true
148
+ out.push(g.row)
149
+ }
150
+ out.push(row)
151
+ }
152
+
153
+ return { rows: out, dataRowCount: unitsSeen }
154
+ }
155
+
156
+ export type AutoGroupColumnSpec = {
157
+ /** Synthetic column id (e.g. `__autoGroup` or `__group_<field>`). */
158
+ id: string
159
+ /** The grouped field this auto column stands in for (null for the single
160
+ * combined auto-group column). */
161
+ field: string | null
162
+ /** Nesting level this column represents (0-based), for indentation. */
163
+ level: number
164
+ }
165
+
166
+ export type AutoGroupResult = {
167
+ /** Synthetic auto-group column specs to prepend to the visible columns. */
168
+ autoColumns: AutoGroupColumnSpec[]
169
+ /** Ids of the grouped SOURCE columns to hide (rolled into the auto columns). */
170
+ hiddenSourceIds: Set<string>
171
+ }
172
+
173
+ /**
174
+ * Decide the synthetic auto-group columns for a grouping display mode:
175
+ * - `groupRows`: none (the banner path handles display) - `{ [], {} }`.
176
+ * - `singleColumn`: one combined auto-group column; every grouped source column
177
+ * is hidden.
178
+ * - `multipleColumns`: one auto column per grouped field; the grouped source
179
+ * columns are hidden.
180
+ */
181
+ export function buildAutoGroupColumns(
182
+ grouping: ReadonlyArray<string>,
183
+ displayType: GroupDisplayType,
184
+ ): AutoGroupResult {
185
+ if (displayType === 'groupRows' || grouping.length === 0) {
186
+ return { autoColumns: [], hiddenSourceIds: new Set() }
187
+ }
188
+ const hiddenSourceIds = new Set(grouping)
189
+ if (displayType === 'singleColumn') {
190
+ return {
191
+ autoColumns: [{ id: '__autoGroup', field: null, level: 0 }],
192
+ hiddenSourceIds,
193
+ }
194
+ }
195
+ // multipleColumns
196
+ return {
197
+ autoColumns: grouping.map((field, level) => ({ id: `__group_${field}`, field, level })),
198
+ hiddenSourceIds,
199
+ }
200
+ }
package/src/headless.ts CHANGED
@@ -1,87 +1,87 @@
1
- /**
2
- * The headless entry point: `@svgrid/grid/core`.
3
- *
4
- * Everything here is the grid's state machine - rows, columns, sorting,
5
- * filtering, grouping, pagination, expansion, selection - and nothing here
6
- * touches the DOM, ARIA, or CSS. Import from this subpath when you are writing
7
- * your own renderer, running the engine under SSR, or unit-testing row models
8
- * without a browser.
9
- *
10
- * Two entry functions, and the difference matters outside a bundler:
11
- * `createSvGrid` is the Svelte 5 wrapper - it holds its state in runes, so it
12
- * needs the Svelte compiler in the pipeline (Vite, SvelteKit, vitest). Bare
13
- * `node script.mjs` cannot run it. `createSvGridCore` is the same engine with
14
- * no runes and no reactivity, so it runs anywhere Node runs: a service, a
15
- * worker, a CLI, a plain unit test.
16
- *
17
- * Every runtime symbol here is also reachable from the main `@svgrid/grid`
18
- * barrel, so the two paths never disagree. This entry additionally exposes the
19
- * low-level types a custom renderer needs and the barrel does not re-export
20
- * (`RowModelFactory`, `Store`, `createSvGridCore`, and the individual state
21
- * types), and it makes the headless bundle cost measurable instead of leaving
22
- * it to tree-shaking a 200+ export barrel.
23
- *
24
- * See docs/why-headless.md.
25
- */
26
-
27
- export {
28
- columnFilteringFeature,
29
- columnGroupingFeature,
30
- createCoreRowModel,
31
- createExpandedRowModel,
32
- createFilteredRowModel,
33
- createGroupedRowModel,
34
- createPaginatedRowModel,
35
- createSortedRowModel,
36
- createTreeRowModel,
37
- createSvGridCore,
38
- flattenTreeData,
39
- applyGroupAggregate,
40
- filterFns,
41
- rowExpandingFeature,
42
- rowPaginationFeature,
43
- rowSelectionFeature,
44
- rowSortingFeature,
45
- sortFns,
46
- tableFeatures,
47
- type Cell,
48
- type CellContext,
49
- type CellSpanParams,
50
- type ValueParserParams,
51
- type EditorContext,
52
- type CellData,
53
- type ActiveCellState,
54
- type CellFormatConfig,
55
- type CellFormatter,
56
- type Column,
57
- type ColumnDef,
58
- type ColumnDefTemplate,
59
- type ColumnFilter,
60
- type ColumnFiltersState,
61
- type ExpandedState,
62
- type GridColumnDef,
63
- type GridColumns,
64
- type GroupAggregator,
65
- type GroupingState,
66
- type Header,
67
- type HeaderContext,
68
- type HeaderGroup,
69
- type PaginationState,
70
- type Row,
71
- type RowData,
72
- type RowModel,
73
- type RowModelFactory,
74
- type RowSelectionState,
75
- type SortingState,
76
- type Store,
77
- type SvGrid as SvGridInstance,
78
- type SvGridOptions,
79
- type TableFeatures,
80
- type TreeRowModelOptions,
81
- type FlattenTreeOptions,
82
- type Updater,
83
- } from './core'
84
-
85
- export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte'
86
- export { createGridState, createSvGridState } from './createGridState.svelte'
87
- export { subscribeGrid, subscribeSvGrid } from './subscribe'
1
+ /**
2
+ * The headless entry point: `@svgrid/grid/core`.
3
+ *
4
+ * Everything here is the grid's state machine - rows, columns, sorting,
5
+ * filtering, grouping, pagination, expansion, selection - and nothing here
6
+ * touches the DOM, ARIA, or CSS. Import from this subpath when you are writing
7
+ * your own renderer, running the engine under SSR, or unit-testing row models
8
+ * without a browser.
9
+ *
10
+ * Two entry functions, and the difference matters outside a bundler:
11
+ * `createSvGrid` is the Svelte 5 wrapper - it holds its state in runes, so it
12
+ * needs the Svelte compiler in the pipeline (Vite, SvelteKit, vitest). Bare
13
+ * `node script.mjs` cannot run it. `createSvGridCore` is the same engine with
14
+ * no runes and no reactivity, so it runs anywhere Node runs: a service, a
15
+ * worker, a CLI, a plain unit test.
16
+ *
17
+ * Every runtime symbol here is also reachable from the main `@svgrid/grid`
18
+ * barrel, so the two paths never disagree. This entry additionally exposes the
19
+ * low-level types a custom renderer needs and the barrel does not re-export
20
+ * (`RowModelFactory`, `Store`, `createSvGridCore`, and the individual state
21
+ * types), and it makes the headless bundle cost measurable instead of leaving
22
+ * it to tree-shaking a 200+ export barrel.
23
+ *
24
+ * See docs/why-headless.md.
25
+ */
26
+
27
+ export {
28
+ columnFilteringFeature,
29
+ columnGroupingFeature,
30
+ createCoreRowModel,
31
+ createExpandedRowModel,
32
+ createFilteredRowModel,
33
+ createGroupedRowModel,
34
+ createPaginatedRowModel,
35
+ createSortedRowModel,
36
+ createTreeRowModel,
37
+ createSvGridCore,
38
+ flattenTreeData,
39
+ applyGroupAggregate,
40
+ filterFns,
41
+ rowExpandingFeature,
42
+ rowPaginationFeature,
43
+ rowSelectionFeature,
44
+ rowSortingFeature,
45
+ sortFns,
46
+ tableFeatures,
47
+ type Cell,
48
+ type CellContext,
49
+ type CellSpanParams,
50
+ type ValueParserParams,
51
+ type EditorContext,
52
+ type CellData,
53
+ type ActiveCellState,
54
+ type CellFormatConfig,
55
+ type CellFormatter,
56
+ type Column,
57
+ type ColumnDef,
58
+ type ColumnDefTemplate,
59
+ type ColumnFilter,
60
+ type ColumnFiltersState,
61
+ type ExpandedState,
62
+ type GridColumnDef,
63
+ type GridColumns,
64
+ type GroupAggregator,
65
+ type GroupingState,
66
+ type Header,
67
+ type HeaderContext,
68
+ type HeaderGroup,
69
+ type PaginationState,
70
+ type Row,
71
+ type RowData,
72
+ type RowModel,
73
+ type RowModelFactory,
74
+ type RowSelectionState,
75
+ type SortingState,
76
+ type Store,
77
+ type SvGrid as SvGridInstance,
78
+ type SvGridOptions,
79
+ type TableFeatures,
80
+ type TreeRowModelOptions,
81
+ type FlattenTreeOptions,
82
+ type Updater,
83
+ } from './core'
84
+
85
+ export { createGrid, createSvGrid, type SvelteGrid } from './createGrid.svelte'
86
+ export { createGridState, createSvGridState } from './createGridState.svelte'
87
+ export { subscribeGrid, subscribeSvGrid } from './subscribe'