@svgrid/grid 3.0.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.
- package/CHANGELOG.md +116 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/SvAutoComplete.svelte +169 -169
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -279
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -378
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGridCellEditor.svelte +748 -744
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -270
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -334
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -293
- package/dist/SvNumberInput.svelte +172 -172
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -239
- package/dist/cdn/{GridMenus-DMihUtma.js → GridMenus-CYzOvmNQ.js} +3 -3
- package/dist/cdn/{GridMenus-nmNDj1a3.js → GridMenus-DFB6iwF8.js} +3 -3
- package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
- package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
- package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
- package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
- package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
- package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
- package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
- package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
- package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
- package/dist/cdn/{src-B_YS5AOc.js → src-C8QjSaq-.js} +42 -44
- package/dist/cdn/{src-DYXXpuSk.js → src-D0hO2eYN.js} +41 -43
- package/dist/cdn/svgrid.js +7 -7
- package/dist/cdn/svgrid.svelte-external.js +7 -7
- package/dist/chart-export.js +8 -8
- package/dist/editing.js +22 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/SvAutoComplete.svelte +169 -169
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -279
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -378
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.types.ts +2092 -2092
- package/src/SvGridCellEditor.svelte +748 -744
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -270
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -334
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -293
- package/src/SvNumberInput.svelte +172 -172
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -239
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -148
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -974
- package/src/editing.ts +618 -609
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -179
- package/src/menus.ts +597 -597
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +401 -401
- package/src/row-drag.ts +419 -419
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection.test.ts +885 -885
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +322 -322
package/src/group-display.ts
CHANGED
|
@@ -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'
|