@svgrid/grid 3.0.0 → 3.0.2
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 +190 -181
- package/dist/GridFooter.svelte.d.ts +8 -0
- package/dist/GridMenus.svelte +18 -10
- package/dist/GridMenus.svelte.d.ts +2 -1
- 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/SvGrid.controller.svelte.d.ts +17 -0
- package/dist/SvGrid.controller.svelte.js +128 -14
- package/dist/SvGrid.css +79 -10
- package/dist/SvGrid.svelte +191 -186
- package/dist/SvGrid.types.d.ts +42 -1
- package/dist/SvGridCellEditor.svelte +748 -744
- package/dist/SvGridChart.svelte +2246 -1716
- package/dist/SvGridChart.svelte.d.ts +63 -1
- package/dist/SvGridChartPanel.svelte +633 -485
- package/dist/SvGridChartView.svelte +79 -70
- package/dist/SvGridChartView.svelte.d.ts +7 -0
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -270
- package/dist/SvGroupCell.svelte +141 -116
- package/dist/SvGroupCell.svelte.d.ts +8 -0
- 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 +186 -170
- package/dist/SvRowGroupPanel.svelte.d.ts +8 -0
- 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/ai.d.ts +11 -2
- package/dist/ai.js +51 -7
- package/dist/cdn/GridMenus-DyFA-QxA.js +602 -0
- package/dist/cdn/GridMenus-M2XP9t0_.js +603 -0
- 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-DAb9RbRs.js +1480 -0
- package/dist/cdn/SvGridChart-Yl-IXZFO.js +1481 -0
- package/dist/cdn/SvGridChartPanel-DayQpDsR.js +834 -0
- package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +810 -0
- package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-CxfakxR0.js} +5 -3
- package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-Dm_YEx9L.js} +5 -3
- 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/cell-formatting-C2Hf5gqd.js +86 -0
- package/dist/cdn/chart-CKJRCPOk.js +1652 -0
- 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/{export-format-Cv1Dll6k.js → export-format-CUDLy2yn.js} +84 -168
- package/dist/cdn/{src-B_YS5AOc.js → src-BJ2opd0j.js} +3642 -3450
- package/dist/cdn/{src-DYXXpuSk.js → src-DNbz94Ct.js} +8649 -8457
- package/dist/cdn/svgrid.js +16 -15
- package/dist/cdn/svgrid.svelte-external.js +16 -15
- package/dist/cell-formatting.d.ts +7 -0
- package/dist/cell-formatting.js +7 -1
- package/dist/chart-export.d.ts +23 -2
- package/dist/chart-export.js +88 -14
- package/dist/chart.d.ts +410 -18
- package/dist/chart.js +824 -24
- package/dist/editing.js +22 -13
- package/dist/grid-icons.d.ts +49 -0
- package/dist/grid-icons.js +86 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/package.json +1 -1
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +190 -181
- package/src/GridMenus.svelte +18 -10
- 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.controller.svelte.ts +146 -13
- package/src/SvGrid.css +79 -10
- package/src/SvGrid.svelte +191 -186
- package/src/SvGrid.types.ts +2133 -2092
- package/src/SvGridCellEditor.svelte +748 -744
- package/src/SvGridChart.svelte +2246 -1716
- package/src/SvGridChart.test.ts +243 -1
- package/src/SvGridChartPanel.svelte +633 -485
- package/src/SvGridChartView.svelte +79 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -270
- package/src/SvGroupCell.svelte +141 -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 +186 -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 +561 -502
- package/src/ai.ts +1472 -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 +177 -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.test.ts +112 -0
- package/src/chart-export.ts +270 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.test.ts +830 -1
- package/src/chart.ts +3342 -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/grid-icons.test.ts +46 -0
- package/src/grid-icons.ts +184 -0
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/icon-overrides.grid.test.ts +188 -0
- package/src/icon-seam.test.ts +100 -0
- package/src/index.ts +19 -0
- 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 +654 -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/dist/cdn/GridMenus-DMihUtma.js +0 -600
- package/dist/cdn/GridMenus-nmNDj1a3.js +0 -601
- package/dist/cdn/SvGridChart-Bs2GIR2Q.js +0 -1202
- package/dist/cdn/SvGridChart-CwhFz7GV.js +0 -1203
- package/dist/cdn/SvGridChartPanel-1WXuStzM.js +0 -561
- package/dist/cdn/SvGridChartPanel-e5_nqNP9.js +0 -585
- package/dist/cdn/chart-i4XcXyHJ.js +0 -1214
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM: `treeData` renders a client-side hierarchy (#26).
|
|
3
|
-
*
|
|
4
|
-
* The point of the design is that tree rows stay REAL data rows: they render
|
|
5
|
-
* their own cells, so editing/formatting/selection all keep working, and only
|
|
6
|
-
* gain an expander plus indentation in the tree column. Grouping's banners are
|
|
7
|
-
* synthetic full-width rows - a tree row must never be rendered like one.
|
|
8
|
-
*/
|
|
9
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
-
import { mount, unmount } from 'svelte'
|
|
11
|
-
import SvGrid from './SvGrid.svelte'
|
|
12
|
-
import {
|
|
13
|
-
createCoreRowModel,
|
|
14
|
-
createFilteredRowModel,
|
|
15
|
-
createPaginatedRowModel,
|
|
16
|
-
createSortedRowModel,
|
|
17
|
-
flattenTreeData,
|
|
18
|
-
sortFns,
|
|
19
|
-
tableFeatures,
|
|
20
|
-
rowSortingFeature,
|
|
21
|
-
} from './index'
|
|
22
|
-
|
|
23
|
-
type Node = { id: number; managerId: number | null; name: string; team: string }
|
|
24
|
-
|
|
25
|
-
const data: Node[] = [
|
|
26
|
-
{ id: 1, managerId: null, name: 'Ada', team: 'Exec' },
|
|
27
|
-
{ id: 2, managerId: 1, name: 'Grace', team: 'Eng' },
|
|
28
|
-
{ id: 3, managerId: 2, name: 'Alan', team: 'Eng' },
|
|
29
|
-
{ id: 4, managerId: 1, name: 'Linus', team: 'Ops' },
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
const columns = [
|
|
33
|
-
{ field: 'name', header: 'Name', width: 200 },
|
|
34
|
-
{ field: 'team', header: 'Team', width: 120 },
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
const features = tableFeatures({ rowSortingFeature })
|
|
38
|
-
const tick = () => new Promise<void>((r) => queueMicrotask(r))
|
|
39
|
-
|
|
40
|
-
function mountGrid(extra: Record<string, unknown> = {}) {
|
|
41
|
-
const target = document.createElement('div')
|
|
42
|
-
document.body.appendChild(target)
|
|
43
|
-
const app = mount(SvGrid, {
|
|
44
|
-
target,
|
|
45
|
-
props: {
|
|
46
|
-
data,
|
|
47
|
-
columns,
|
|
48
|
-
features,
|
|
49
|
-
treeData: { parentField: 'managerId', column: 'name' },
|
|
50
|
-
_rowModels: {
|
|
51
|
-
coreRowModel: createCoreRowModel(),
|
|
52
|
-
filteredRowModel: createFilteredRowModel(),
|
|
53
|
-
sortedRowModel: createSortedRowModel(sortFns),
|
|
54
|
-
paginatedRowModel: createPaginatedRowModel(),
|
|
55
|
-
},
|
|
56
|
-
containerHeight: 600,
|
|
57
|
-
virtualization: false,
|
|
58
|
-
...extra,
|
|
59
|
-
} as never,
|
|
60
|
-
})
|
|
61
|
-
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let cleanup: (() => void) | null = null
|
|
65
|
-
afterEach(() => { cleanup?.(); cleanup = null })
|
|
66
|
-
|
|
67
|
-
const bodyRows = (t: HTMLElement) =>
|
|
68
|
-
[...t.querySelectorAll('tbody tr.sv-grid-row')].filter(
|
|
69
|
-
(r) => !r.classList.contains('sv-grid-row-spacer'),
|
|
70
|
-
)
|
|
71
|
-
const toggles = (t: HTMLElement) =>
|
|
72
|
-
[...t.querySelectorAll<HTMLButtonElement>('.sv-grid-tree-toggle')]
|
|
73
|
-
|
|
74
|
-
describe('treeData', () => {
|
|
75
|
-
it('shows only roots until expanded', async () => {
|
|
76
|
-
const { target, destroy } = mountGrid()
|
|
77
|
-
cleanup = destroy
|
|
78
|
-
await tick()
|
|
79
|
-
expect(bodyRows(target)).toHaveLength(1)
|
|
80
|
-
expect(target.textContent).toContain('Ada')
|
|
81
|
-
expect(target.textContent).not.toContain('Grace')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('expands to reveal children', async () => {
|
|
85
|
-
const { target, destroy } = mountGrid()
|
|
86
|
-
cleanup = destroy
|
|
87
|
-
await tick()
|
|
88
|
-
toggles(target)[0]!.click()
|
|
89
|
-
await tick()
|
|
90
|
-
expect(target.textContent).toContain('Grace')
|
|
91
|
-
expect(target.textContent).toContain('Linus')
|
|
92
|
-
// Alan is a grandchild - still hidden.
|
|
93
|
-
expect(target.textContent).not.toContain('Alan')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('renders tree rows as normal cell rows, not full-width banners', async () => {
|
|
97
|
-
const { target, destroy } = mountGrid()
|
|
98
|
-
cleanup = destroy
|
|
99
|
-
await tick()
|
|
100
|
-
expect(target.querySelectorAll('tr.sv-grid-group-row')).toHaveLength(0)
|
|
101
|
-
// The root has its own cells - both columns render.
|
|
102
|
-
const cells = bodyRows(target)[0]!.querySelectorAll('.sv-grid-cell')
|
|
103
|
-
expect(cells.length).toBeGreaterThanOrEqual(2)
|
|
104
|
-
expect(bodyRows(target)[0]!.textContent).toContain('Exec')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('uses the treegrid role with aria-level and aria-expanded', async () => {
|
|
108
|
-
const { target, destroy } = mountGrid()
|
|
109
|
-
cleanup = destroy
|
|
110
|
-
await tick()
|
|
111
|
-
expect(target.querySelector('[role="treegrid"]')).not.toBeNull()
|
|
112
|
-
const root = bodyRows(target)[0]!
|
|
113
|
-
expect(root.getAttribute('aria-level')).toBe('1')
|
|
114
|
-
expect(root.getAttribute('aria-expanded')).toBe('false')
|
|
115
|
-
|
|
116
|
-
toggles(target)[0]!.click()
|
|
117
|
-
await tick()
|
|
118
|
-
expect(bodyRows(target)[0]!.getAttribute('aria-expanded')).toBe('true')
|
|
119
|
-
// A child sits one level deeper.
|
|
120
|
-
expect(bodyRows(target)[1]!.getAttribute('aria-level')).toBe('2')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('indents from ONE source only', async () => {
|
|
124
|
-
// `sv-grid-group-child-indent` exists for rows nested under a group banner
|
|
125
|
-
// and keys off `row.depth`, which tree rows also have. It used to stack on
|
|
126
|
-
// top of the tree indent, putting a 20px gap between a row's chevron and
|
|
127
|
-
// its text - the tree affordance owns indentation here.
|
|
128
|
-
const { target, destroy } = mountGrid()
|
|
129
|
-
cleanup = destroy
|
|
130
|
-
await tick()
|
|
131
|
-
toggles(target)[0]!.click()
|
|
132
|
-
await tick()
|
|
133
|
-
|
|
134
|
-
expect(target.querySelectorAll('.sv-grid-group-child-indent')).toHaveLength(0)
|
|
135
|
-
// Every name cell still carries exactly one tree indent.
|
|
136
|
-
const nameCells = [...target.querySelectorAll('[data-col-id="name"]')]
|
|
137
|
-
for (const cell of nameCells) {
|
|
138
|
-
expect(cell.querySelectorAll('.sv-grid-tree-indent')).toHaveLength(1)
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('indents by depth x indentPx, honouring the override', async () => {
|
|
143
|
-
const { target, destroy } = mountGrid({
|
|
144
|
-
treeData: { parentField: 'managerId', column: 'name', indentPx: 30 },
|
|
145
|
-
})
|
|
146
|
-
cleanup = destroy
|
|
147
|
-
await tick()
|
|
148
|
-
toggles(target)[0]!.click()
|
|
149
|
-
await tick()
|
|
150
|
-
const widths = [...target.querySelectorAll<HTMLElement>('[data-col-id="name"] .sv-grid-tree-indent')]
|
|
151
|
-
.map((e) => e.style.width)
|
|
152
|
-
// Root at 0, its children one level in.
|
|
153
|
-
expect(widths[0]).toBe('0px')
|
|
154
|
-
expect(widths[1]).toBe('30px')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('gives a leaf no expander but keeps it indented', async () => {
|
|
158
|
-
const { target, destroy } = mountGrid()
|
|
159
|
-
cleanup = destroy
|
|
160
|
-
await tick()
|
|
161
|
-
toggles(target)[0]!.click()
|
|
162
|
-
await tick()
|
|
163
|
-
// Linus is a leaf: spacer, no toggle button.
|
|
164
|
-
const linus = bodyRows(target).find((r) => r.textContent?.includes('Linus'))!
|
|
165
|
-
expect(linus.querySelector('.sv-grid-tree-toggle')).toBeNull()
|
|
166
|
-
expect(linus.querySelector('.sv-grid-tree-spacer')).not.toBeNull()
|
|
167
|
-
expect(linus.querySelector('.sv-grid-tree-indent')).not.toBeNull()
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('accepts nested data once flattened', async () => {
|
|
171
|
-
const nested = [
|
|
172
|
-
{ id: 1, name: 'Ada', team: 'Exec', reports: [{ id: 2, name: 'Grace', team: 'Eng' }] },
|
|
173
|
-
]
|
|
174
|
-
const flat = flattenTreeData(nested as never, { childrenField: 'reports' })
|
|
175
|
-
const { target, destroy } = mountGrid({
|
|
176
|
-
data: flat,
|
|
177
|
-
treeData: { parentField: '__parentId', column: 'name' },
|
|
178
|
-
})
|
|
179
|
-
cleanup = destroy
|
|
180
|
-
await tick()
|
|
181
|
-
expect(bodyRows(target)).toHaveLength(1)
|
|
182
|
-
toggles(target)[0]!.click()
|
|
183
|
-
await tick()
|
|
184
|
-
expect(target.textContent).toContain('Grace')
|
|
185
|
-
})
|
|
186
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* DOM: `treeData` renders a client-side hierarchy (#26).
|
|
3
|
+
*
|
|
4
|
+
* The point of the design is that tree rows stay REAL data rows: they render
|
|
5
|
+
* their own cells, so editing/formatting/selection all keep working, and only
|
|
6
|
+
* gain an expander plus indentation in the tree column. Grouping's banners are
|
|
7
|
+
* synthetic full-width rows - a tree row must never be rendered like one.
|
|
8
|
+
*/
|
|
9
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
+
import { mount, unmount } from 'svelte'
|
|
11
|
+
import SvGrid from './SvGrid.svelte'
|
|
12
|
+
import {
|
|
13
|
+
createCoreRowModel,
|
|
14
|
+
createFilteredRowModel,
|
|
15
|
+
createPaginatedRowModel,
|
|
16
|
+
createSortedRowModel,
|
|
17
|
+
flattenTreeData,
|
|
18
|
+
sortFns,
|
|
19
|
+
tableFeatures,
|
|
20
|
+
rowSortingFeature,
|
|
21
|
+
} from './index'
|
|
22
|
+
|
|
23
|
+
type Node = { id: number; managerId: number | null; name: string; team: string }
|
|
24
|
+
|
|
25
|
+
const data: Node[] = [
|
|
26
|
+
{ id: 1, managerId: null, name: 'Ada', team: 'Exec' },
|
|
27
|
+
{ id: 2, managerId: 1, name: 'Grace', team: 'Eng' },
|
|
28
|
+
{ id: 3, managerId: 2, name: 'Alan', team: 'Eng' },
|
|
29
|
+
{ id: 4, managerId: 1, name: 'Linus', team: 'Ops' },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const columns = [
|
|
33
|
+
{ field: 'name', header: 'Name', width: 200 },
|
|
34
|
+
{ field: 'team', header: 'Team', width: 120 },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const features = tableFeatures({ rowSortingFeature })
|
|
38
|
+
const tick = () => new Promise<void>((r) => queueMicrotask(r))
|
|
39
|
+
|
|
40
|
+
function mountGrid(extra: Record<string, unknown> = {}) {
|
|
41
|
+
const target = document.createElement('div')
|
|
42
|
+
document.body.appendChild(target)
|
|
43
|
+
const app = mount(SvGrid, {
|
|
44
|
+
target,
|
|
45
|
+
props: {
|
|
46
|
+
data,
|
|
47
|
+
columns,
|
|
48
|
+
features,
|
|
49
|
+
treeData: { parentField: 'managerId', column: 'name' },
|
|
50
|
+
_rowModels: {
|
|
51
|
+
coreRowModel: createCoreRowModel(),
|
|
52
|
+
filteredRowModel: createFilteredRowModel(),
|
|
53
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
54
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
55
|
+
},
|
|
56
|
+
containerHeight: 600,
|
|
57
|
+
virtualization: false,
|
|
58
|
+
...extra,
|
|
59
|
+
} as never,
|
|
60
|
+
})
|
|
61
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let cleanup: (() => void) | null = null
|
|
65
|
+
afterEach(() => { cleanup?.(); cleanup = null })
|
|
66
|
+
|
|
67
|
+
const bodyRows = (t: HTMLElement) =>
|
|
68
|
+
[...t.querySelectorAll('tbody tr.sv-grid-row')].filter(
|
|
69
|
+
(r) => !r.classList.contains('sv-grid-row-spacer'),
|
|
70
|
+
)
|
|
71
|
+
const toggles = (t: HTMLElement) =>
|
|
72
|
+
[...t.querySelectorAll<HTMLButtonElement>('.sv-grid-tree-toggle')]
|
|
73
|
+
|
|
74
|
+
describe('treeData', () => {
|
|
75
|
+
it('shows only roots until expanded', async () => {
|
|
76
|
+
const { target, destroy } = mountGrid()
|
|
77
|
+
cleanup = destroy
|
|
78
|
+
await tick()
|
|
79
|
+
expect(bodyRows(target)).toHaveLength(1)
|
|
80
|
+
expect(target.textContent).toContain('Ada')
|
|
81
|
+
expect(target.textContent).not.toContain('Grace')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('expands to reveal children', async () => {
|
|
85
|
+
const { target, destroy } = mountGrid()
|
|
86
|
+
cleanup = destroy
|
|
87
|
+
await tick()
|
|
88
|
+
toggles(target)[0]!.click()
|
|
89
|
+
await tick()
|
|
90
|
+
expect(target.textContent).toContain('Grace')
|
|
91
|
+
expect(target.textContent).toContain('Linus')
|
|
92
|
+
// Alan is a grandchild - still hidden.
|
|
93
|
+
expect(target.textContent).not.toContain('Alan')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('renders tree rows as normal cell rows, not full-width banners', async () => {
|
|
97
|
+
const { target, destroy } = mountGrid()
|
|
98
|
+
cleanup = destroy
|
|
99
|
+
await tick()
|
|
100
|
+
expect(target.querySelectorAll('tr.sv-grid-group-row')).toHaveLength(0)
|
|
101
|
+
// The root has its own cells - both columns render.
|
|
102
|
+
const cells = bodyRows(target)[0]!.querySelectorAll('.sv-grid-cell')
|
|
103
|
+
expect(cells.length).toBeGreaterThanOrEqual(2)
|
|
104
|
+
expect(bodyRows(target)[0]!.textContent).toContain('Exec')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('uses the treegrid role with aria-level and aria-expanded', async () => {
|
|
108
|
+
const { target, destroy } = mountGrid()
|
|
109
|
+
cleanup = destroy
|
|
110
|
+
await tick()
|
|
111
|
+
expect(target.querySelector('[role="treegrid"]')).not.toBeNull()
|
|
112
|
+
const root = bodyRows(target)[0]!
|
|
113
|
+
expect(root.getAttribute('aria-level')).toBe('1')
|
|
114
|
+
expect(root.getAttribute('aria-expanded')).toBe('false')
|
|
115
|
+
|
|
116
|
+
toggles(target)[0]!.click()
|
|
117
|
+
await tick()
|
|
118
|
+
expect(bodyRows(target)[0]!.getAttribute('aria-expanded')).toBe('true')
|
|
119
|
+
// A child sits one level deeper.
|
|
120
|
+
expect(bodyRows(target)[1]!.getAttribute('aria-level')).toBe('2')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('indents from ONE source only', async () => {
|
|
124
|
+
// `sv-grid-group-child-indent` exists for rows nested under a group banner
|
|
125
|
+
// and keys off `row.depth`, which tree rows also have. It used to stack on
|
|
126
|
+
// top of the tree indent, putting a 20px gap between a row's chevron and
|
|
127
|
+
// its text - the tree affordance owns indentation here.
|
|
128
|
+
const { target, destroy } = mountGrid()
|
|
129
|
+
cleanup = destroy
|
|
130
|
+
await tick()
|
|
131
|
+
toggles(target)[0]!.click()
|
|
132
|
+
await tick()
|
|
133
|
+
|
|
134
|
+
expect(target.querySelectorAll('.sv-grid-group-child-indent')).toHaveLength(0)
|
|
135
|
+
// Every name cell still carries exactly one tree indent.
|
|
136
|
+
const nameCells = [...target.querySelectorAll('[data-col-id="name"]')]
|
|
137
|
+
for (const cell of nameCells) {
|
|
138
|
+
expect(cell.querySelectorAll('.sv-grid-tree-indent')).toHaveLength(1)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('indents by depth x indentPx, honouring the override', async () => {
|
|
143
|
+
const { target, destroy } = mountGrid({
|
|
144
|
+
treeData: { parentField: 'managerId', column: 'name', indentPx: 30 },
|
|
145
|
+
})
|
|
146
|
+
cleanup = destroy
|
|
147
|
+
await tick()
|
|
148
|
+
toggles(target)[0]!.click()
|
|
149
|
+
await tick()
|
|
150
|
+
const widths = [...target.querySelectorAll<HTMLElement>('[data-col-id="name"] .sv-grid-tree-indent')]
|
|
151
|
+
.map((e) => e.style.width)
|
|
152
|
+
// Root at 0, its children one level in.
|
|
153
|
+
expect(widths[0]).toBe('0px')
|
|
154
|
+
expect(widths[1]).toBe('30px')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('gives a leaf no expander but keeps it indented', async () => {
|
|
158
|
+
const { target, destroy } = mountGrid()
|
|
159
|
+
cleanup = destroy
|
|
160
|
+
await tick()
|
|
161
|
+
toggles(target)[0]!.click()
|
|
162
|
+
await tick()
|
|
163
|
+
// Linus is a leaf: spacer, no toggle button.
|
|
164
|
+
const linus = bodyRows(target).find((r) => r.textContent?.includes('Linus'))!
|
|
165
|
+
expect(linus.querySelector('.sv-grid-tree-toggle')).toBeNull()
|
|
166
|
+
expect(linus.querySelector('.sv-grid-tree-spacer')).not.toBeNull()
|
|
167
|
+
expect(linus.querySelector('.sv-grid-tree-indent')).not.toBeNull()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('accepts nested data once flattened', async () => {
|
|
171
|
+
const nested = [
|
|
172
|
+
{ id: 1, name: 'Ada', team: 'Exec', reports: [{ id: 2, name: 'Grace', team: 'Eng' }] },
|
|
173
|
+
]
|
|
174
|
+
const flat = flattenTreeData(nested as never, { childrenField: 'reports' })
|
|
175
|
+
const { target, destroy } = mountGrid({
|
|
176
|
+
data: flat,
|
|
177
|
+
treeData: { parentField: '__parentId', column: 'name' },
|
|
178
|
+
})
|
|
179
|
+
cleanup = destroy
|
|
180
|
+
await tick()
|
|
181
|
+
expect(bodyRows(target)).toHaveLength(1)
|
|
182
|
+
toggles(target)[0]!.click()
|
|
183
|
+
await tick()
|
|
184
|
+
expect(target.textContent).toContain('Grace')
|
|
185
|
+
})
|
|
186
|
+
})
|
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import SvGrid from './SvGrid.svelte'
|
|
5
|
-
|
|
6
|
-
describe('SvGrid wrapper', () => {
|
|
7
|
-
it('exports a production wrapper component', () => {
|
|
8
|
-
expect(SvGrid).toBeDefined()
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('contains full-wrapper controls and aria-activedescendant wiring', () => {
|
|
12
|
-
// The wrapper spans SvGrid.svelte plus its extracted sibling modules
|
|
13
|
-
// (types + pure helpers); read them together so these "contains X"
|
|
14
|
-
// assertions hold wherever the code physically lives.
|
|
15
|
-
const source = [
|
|
16
|
-
'./SvGrid.svelte',
|
|
17
|
-
'./SvGrid.controller.svelte.ts',
|
|
18
|
-
'./GridMenus.svelte',
|
|
19
|
-
'./GridFooter.svelte',
|
|
20
|
-
'./SvGrid.types.ts',
|
|
21
|
-
'./SvGrid.helpers.ts',
|
|
22
|
-
'./filter-operators.ts',
|
|
23
|
-
'./facet-buckets.ts',
|
|
24
|
-
'./cell-values.ts',
|
|
25
|
-
'./clipboard.ts',
|
|
26
|
-
'./build-api.ts',
|
|
27
|
-
'./columns.ts',
|
|
28
|
-
'./selection.ts',
|
|
29
|
-
'./editing.ts',
|
|
30
|
-
'./cell-render.ts',
|
|
31
|
-
'./menus.ts',
|
|
32
|
-
'./summaries.ts',
|
|
33
|
-
'./keyboard-handlers.ts',
|
|
34
|
-
'./scroll-sync.ts',
|
|
35
|
-
'./features.ts',
|
|
36
|
-
]
|
|
37
|
-
.map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
|
|
38
|
-
.join('\n')
|
|
39
|
-
expect(source).toContain('Filter all rows')
|
|
40
|
-
expect(source).toContain('Previous')
|
|
41
|
-
expect(source).toContain('role="status"')
|
|
42
|
-
expect(source).toContain('role="alert"')
|
|
43
|
-
expect(source).toContain('activeDescendantId')
|
|
44
|
-
expect(source).toContain('getGridRootA11yProps')
|
|
45
|
-
expect(source).toContain('data-svgrid-header-col')
|
|
46
|
-
expect(source).toContain('data-svgrid-row')
|
|
47
|
-
expect(source).toContain('createSvelteVirtualizer')
|
|
48
|
-
expect(source).toContain('props.virtualization ?? true')
|
|
49
|
-
expect(source).toContain('virtualizer.getVirtualItems()')
|
|
50
|
-
expect(source).toContain('createColumnVirtualizer')
|
|
51
|
-
expect(source).toContain('data-selected-range')
|
|
52
|
-
expect(source).toContain('enableInlineEditing')
|
|
53
|
-
expect(source).toContain('showFilterMenu')
|
|
54
|
-
expect(source).toContain('<tfoot')
|
|
55
|
-
expect(source).toContain('editorType === "number"')
|
|
56
|
-
expect(source).toContain('editorType === "date"')
|
|
57
|
-
expect(source).toContain('editorType === "checkbox"')
|
|
58
|
-
// The controller compiles each condition once per filter change rather
|
|
59
|
-
// than calling applyExcelFilter per row; both share one switch body.
|
|
60
|
-
expect(source).toContain('compileExcelFilter')
|
|
61
|
-
expect(source).toContain('showFilterRow')
|
|
62
|
-
})
|
|
63
|
-
})
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import SvGrid from './SvGrid.svelte'
|
|
5
|
+
|
|
6
|
+
describe('SvGrid wrapper', () => {
|
|
7
|
+
it('exports a production wrapper component', () => {
|
|
8
|
+
expect(SvGrid).toBeDefined()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('contains full-wrapper controls and aria-activedescendant wiring', () => {
|
|
12
|
+
// The wrapper spans SvGrid.svelte plus its extracted sibling modules
|
|
13
|
+
// (types + pure helpers); read them together so these "contains X"
|
|
14
|
+
// assertions hold wherever the code physically lives.
|
|
15
|
+
const source = [
|
|
16
|
+
'./SvGrid.svelte',
|
|
17
|
+
'./SvGrid.controller.svelte.ts',
|
|
18
|
+
'./GridMenus.svelte',
|
|
19
|
+
'./GridFooter.svelte',
|
|
20
|
+
'./SvGrid.types.ts',
|
|
21
|
+
'./SvGrid.helpers.ts',
|
|
22
|
+
'./filter-operators.ts',
|
|
23
|
+
'./facet-buckets.ts',
|
|
24
|
+
'./cell-values.ts',
|
|
25
|
+
'./clipboard.ts',
|
|
26
|
+
'./build-api.ts',
|
|
27
|
+
'./columns.ts',
|
|
28
|
+
'./selection.ts',
|
|
29
|
+
'./editing.ts',
|
|
30
|
+
'./cell-render.ts',
|
|
31
|
+
'./menus.ts',
|
|
32
|
+
'./summaries.ts',
|
|
33
|
+
'./keyboard-handlers.ts',
|
|
34
|
+
'./scroll-sync.ts',
|
|
35
|
+
'./features.ts',
|
|
36
|
+
]
|
|
37
|
+
.map((p) => readFileSync(resolve(__dirname, p), 'utf-8'))
|
|
38
|
+
.join('\n')
|
|
39
|
+
expect(source).toContain('Filter all rows')
|
|
40
|
+
expect(source).toContain('Previous')
|
|
41
|
+
expect(source).toContain('role="status"')
|
|
42
|
+
expect(source).toContain('role="alert"')
|
|
43
|
+
expect(source).toContain('activeDescendantId')
|
|
44
|
+
expect(source).toContain('getGridRootA11yProps')
|
|
45
|
+
expect(source).toContain('data-svgrid-header-col')
|
|
46
|
+
expect(source).toContain('data-svgrid-row')
|
|
47
|
+
expect(source).toContain('createSvelteVirtualizer')
|
|
48
|
+
expect(source).toContain('props.virtualization ?? true')
|
|
49
|
+
expect(source).toContain('virtualizer.getVirtualItems()')
|
|
50
|
+
expect(source).toContain('createColumnVirtualizer')
|
|
51
|
+
expect(source).toContain('data-selected-range')
|
|
52
|
+
expect(source).toContain('enableInlineEditing')
|
|
53
|
+
expect(source).toContain('showFilterMenu')
|
|
54
|
+
expect(source).toContain('<tfoot')
|
|
55
|
+
expect(source).toContain('editorType === "number"')
|
|
56
|
+
expect(source).toContain('editorType === "date"')
|
|
57
|
+
expect(source).toContain('editorType === "checkbox"')
|
|
58
|
+
// The controller compiles each condition once per filter change rather
|
|
59
|
+
// than calling applyExcelFilter per row; both share one switch body.
|
|
60
|
+
expect(source).toContain('compileExcelFilter')
|
|
61
|
+
expect(source).toContain('showFilterRow')
|
|
62
|
+
})
|
|
63
|
+
})
|