@svgrid/grid 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +122 -41
- package/dist/SvGrid.controller.svelte.js +846 -207
- package/dist/SvGrid.css +178 -2
- package/dist/SvGrid.svelte +717 -720
- package/dist/SvGrid.types.d.ts +566 -12
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +119 -28
- package/dist/cdn/GridMenus-CID62rlI.js +636 -0
- package/dist/cdn/GridMenus-DLL-qZeu.js +635 -0
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/server-block-cache-CGoWz-87.js +237 -0
- package/dist/cdn/{src-D6XvA1Ij.js → src-Bklofsad.js} +10615 -10155
- package/dist/cdn/{src-Bugf5XlI.js → src-jcFMkBnN.js} +7111 -6651
- package/dist/cdn/svgrid.js +27 -15
- package/dist/cdn/svgrid.svelte-external.js +27 -15
- package/dist/cdn/validate-C7Rn_Bns.js +76 -0
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +36 -13
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +272 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/gantt-view.svelte.d.ts +24 -0
- package/dist/gantt-view.svelte.js +13 -0
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +16 -3
- package/dist/grid-messages.js +10 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +29 -10
- package/dist/index.js +23 -5
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-model.d.ts +133 -0
- package/dist/row-model.js +16 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +18 -1
- package/dist/selection.js +138 -18
- package/dist/server-block-cache.d.ts +214 -0
- package/dist/server-block-cache.js +531 -0
- package/dist/server-data-source.d.ts +240 -6
- package/dist/server-data-source.js +220 -20
- package/dist/server.d.ts +13 -0
- package/dist/server.js +13 -0
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +21 -3
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +11 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +819 -179
- package/src/SvGrid.css +178 -2
- package/src/SvGrid.svelte +717 -720
- package/src/SvGrid.types.ts +564 -11
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +118 -22
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.rowmodel-cache.test.ts +51 -0
- package/src/core.ts +40 -12
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +264 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/gantt-stub.test.svelte +38 -0
- package/src/gantt-view.svelte.ts +35 -0
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.ts +28 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +5 -6
- package/src/index.ts +186 -12
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-model.ts +146 -0
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +33 -2
- package/src/selection.ts +143 -21
- package/src/server-block-cache.test.ts +645 -0
- package/src/server-block-cache.ts +677 -0
- package/src/server-data-source.infinite.test.ts +343 -0
- package/src/server-data-source.ts +450 -27
- package/src/server.ts +47 -0
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.gantt-seam.test.ts +199 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
- package/src/svgrid.upsell-license.test.ts +7 -4
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +28 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/SvGroupCell.svelte +0 -141
- package/dist/SvGroupCell.svelte.d.ts +0 -49
- package/dist/SvRowGroupPanel.svelte +0 -186
- package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
- package/dist/cdn/GridMenus-CHOMnW-c.js +0 -602
- package/dist/cdn/GridMenus-DhdPF700.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-1PMSp2LZ.js +0 -1481
- package/dist/cdn/SvGridChart-D02avhyr.js +0 -1480
- package/dist/cdn/SvGridChartPanel-DJnViOv4.js +0 -834
- package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +0 -810
- package/dist/cdn/SvGridChartView-C--HhdUo.js +0 -56
- package/dist/cdn/SvGridChartView-KyyFG-wY.js +0 -55
- package/dist/cdn/chart-DLg3_zTQ.js +0 -1652
- package/dist/cdn/validate-_CDJzgIo.js +0 -75
- package/dist/server-group-model.d.ts +0 -98
- package/dist/server-group-model.js +0 -263
- package/src/SvGroupCell.svelte +0 -141
- package/src/SvRowGroupPanel.svelte +0 -186
- package/src/server-group-model.test.ts +0 -294
- package/src/server-group-model.ts +0 -370
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
A row-group panel (like the "drag columns to group" bar in enterprise grids).
|
|
3
|
-
Shows the current group-by columns as chips you can remove or drag to reorder,
|
|
4
|
-
plus a "+ Group by" menu to add one. It is also a drop target: a column drag
|
|
5
|
-
that carries `text/sv-column` (its column id) adds that column. Wire `onChange`
|
|
6
|
-
to a server group model's `setGroupBy`, or to client grouping.
|
|
7
|
-
|
|
8
|
-
<SvRowGroupPanel
|
|
9
|
-
columns={[{ id: 'region', label: 'Region' }, { id: 'country', label: 'Country' }]}
|
|
10
|
-
groupBy={view.groupBy}
|
|
11
|
-
onChange={(g) => ctl.setGroupBy(g)}
|
|
12
|
-
/>
|
|
13
|
-
-->
|
|
14
|
-
<script lang="ts">
|
|
15
|
-
import { GRID_ICON_GLYPHS, type GridIconName, type GridIcons } from './grid-icons'
|
|
16
|
-
|
|
17
|
-
type Col = { id: string; label: string }
|
|
18
|
-
type Props = {
|
|
19
|
-
/** Columns that can be grouped on. */
|
|
20
|
-
columns: Col[]
|
|
21
|
-
/** Current group-by column ids, outer to inner. */
|
|
22
|
-
groupBy: string[]
|
|
23
|
-
/** Called with the new group-by order. */
|
|
24
|
-
onChange: (groupBy: string[]) => void
|
|
25
|
-
/** Text shown when nothing is grouped. */
|
|
26
|
-
placeholder?: string
|
|
27
|
-
/**
|
|
28
|
-
* Icon overrides, the same map `<SvGrid icons>` takes. This panel draws
|
|
29
|
-
* `drag-handle`, `remove` and `breadcrumb-separator`. You mount it
|
|
30
|
-
* yourself, so pass the same object you gave the grid to keep them
|
|
31
|
-
* consistent.
|
|
32
|
-
*/
|
|
33
|
-
icons?: GridIcons
|
|
34
|
-
}
|
|
35
|
-
let { columns, groupBy, onChange, placeholder = 'Drag a column here to group by it', icons }: Props = $props()
|
|
36
|
-
|
|
37
|
-
const labelOf = (id: string) => columns.find((c) => c.id === id)?.label ?? id
|
|
38
|
-
const available = $derived(columns.filter((c) => !groupBy.includes(c.id)))
|
|
39
|
-
|
|
40
|
-
let dragIndex = $state<number | null>(null)
|
|
41
|
-
|
|
42
|
-
function remove(id: string) {
|
|
43
|
-
onChange(groupBy.filter((g) => g !== id))
|
|
44
|
-
}
|
|
45
|
-
function add(id: string) {
|
|
46
|
-
if (id && !groupBy.includes(id)) onChange([...groupBy, id])
|
|
47
|
-
}
|
|
48
|
-
function reorder(to: number) {
|
|
49
|
-
if (dragIndex === null || dragIndex === to) return
|
|
50
|
-
const next = [...groupBy]
|
|
51
|
-
const [moved] = next.splice(dragIndex, 1)
|
|
52
|
-
next.splice(to, 0, moved!)
|
|
53
|
-
dragIndex = null
|
|
54
|
-
onChange(next)
|
|
55
|
-
}
|
|
56
|
-
function onPanelDrop(e: DragEvent) {
|
|
57
|
-
const id = e.dataTransfer?.getData('text/sv-column')
|
|
58
|
-
if (id) add(id)
|
|
59
|
-
}
|
|
60
|
-
/** Keyboard equivalent of dragging a chip: moves the chip at `i` one slot earlier/later,
|
|
61
|
-
* reusing `reorder()` (via `dragIndex`) so there is a single source of truth for the move. */
|
|
62
|
-
function moveByKeyboard(i: number, delta: number) {
|
|
63
|
-
const to = i + delta
|
|
64
|
-
if (to < 0 || to >= groupBy.length) return
|
|
65
|
-
dragIndex = i
|
|
66
|
-
reorder(to)
|
|
67
|
-
}
|
|
68
|
-
function onChipKeydown(e: KeyboardEvent, i: number) {
|
|
69
|
-
if (!e.altKey) return
|
|
70
|
-
if (e.key === 'ArrowLeft') {
|
|
71
|
-
e.preventDefault()
|
|
72
|
-
moveByKeyboard(i, -1)
|
|
73
|
-
} else if (e.key === 'ArrowRight') {
|
|
74
|
-
e.preventDefault()
|
|
75
|
-
moveByKeyboard(i, 1)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
</script>
|
|
79
|
-
|
|
80
|
-
<!-- Same override-or-default resolution SvGrid uses, kept local because this
|
|
81
|
-
component is mounted by the consumer and never sees the grid's snippet. -->
|
|
82
|
-
{#snippet ic(name: GridIconName)}
|
|
83
|
-
{@const override = icons?.[name]}
|
|
84
|
-
{#if override}{@render override()}{:else}{GRID_ICON_GLYPHS[name]}{/if}
|
|
85
|
-
{/snippet}
|
|
86
|
-
|
|
87
|
-
<div
|
|
88
|
-
class="sv-rowgroup-panel"
|
|
89
|
-
role="group"
|
|
90
|
-
aria-label="Row groups"
|
|
91
|
-
ondragover={(e) => e.preventDefault()}
|
|
92
|
-
ondrop={onPanelDrop}
|
|
93
|
-
>
|
|
94
|
-
<span class="sv-rgp-label" aria-hidden="true">Group by:</span>
|
|
95
|
-
{#if groupBy.length === 0}
|
|
96
|
-
<span class="sv-rgp-empty">{placeholder}</span>
|
|
97
|
-
{:else}
|
|
98
|
-
{#each groupBy as id, i (id)}
|
|
99
|
-
{#if i > 0}<span class="sv-rgp-sep" aria-hidden="true">{@render ic('breadcrumb-separator')}</span>{/if}
|
|
100
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
|
|
101
|
-
<span
|
|
102
|
-
class="sv-rgp-chip"
|
|
103
|
-
class:sv-rgp-chip-drag={dragIndex === i}
|
|
104
|
-
role="group"
|
|
105
|
-
tabindex="0"
|
|
106
|
-
aria-label={`Grouped by ${labelOf(id)}, position ${i + 1} of ${groupBy.length}. Press Alt+Arrow keys to reorder.`}
|
|
107
|
-
draggable="true"
|
|
108
|
-
ondragstart={() => (dragIndex = i)}
|
|
109
|
-
ondragend={() => (dragIndex = null)}
|
|
110
|
-
ondragover={(e) => e.preventDefault()}
|
|
111
|
-
ondrop={() => reorder(i)}
|
|
112
|
-
onkeydown={(e) => onChipKeydown(e, i)}
|
|
113
|
-
>
|
|
114
|
-
<span class="sv-rgp-grip" aria-hidden="true">{@render ic('drag-handle')}</span>
|
|
115
|
-
{labelOf(id)}
|
|
116
|
-
<button type="button" class="sv-rgp-x" onclick={() => remove(id)} aria-label={`Stop grouping by ${labelOf(id)}`}>{@render ic('remove')}</button>
|
|
117
|
-
</span>
|
|
118
|
-
{/each}
|
|
119
|
-
{/if}
|
|
120
|
-
{#if available.length}
|
|
121
|
-
<select
|
|
122
|
-
class="sv-rgp-add"
|
|
123
|
-
aria-label="Add a group column"
|
|
124
|
-
onchange={(e) => { add(e.currentTarget.value); e.currentTarget.value = '' }}
|
|
125
|
-
>
|
|
126
|
-
<option value="">+ Group by</option>
|
|
127
|
-
{#each available as c (c.id)}<option value={c.id}>{c.label}</option>{/each}
|
|
128
|
-
</select>
|
|
129
|
-
{/if}
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<style>
|
|
133
|
-
.sv-rowgroup-panel {
|
|
134
|
-
display: flex;
|
|
135
|
-
align-items: center;
|
|
136
|
-
flex-wrap: wrap;
|
|
137
|
-
gap: 6px;
|
|
138
|
-
padding: 8px 10px;
|
|
139
|
-
border: 1px dashed var(--sg-border, #cbd5e1);
|
|
140
|
-
border-radius: 10px;
|
|
141
|
-
background: var(--sg-header-bg, #f8fafc);
|
|
142
|
-
font-size: 13px;
|
|
143
|
-
}
|
|
144
|
-
.sv-rgp-label { color: var(--sg-muted, #64748b); font-weight: 600; }
|
|
145
|
-
.sv-rgp-empty { color: var(--sg-muted, #94a3b8); font-style: italic; }
|
|
146
|
-
.sv-rgp-sep { color: var(--sg-muted, #94a3b8); }
|
|
147
|
-
.sv-rgp-chip {
|
|
148
|
-
display: inline-flex;
|
|
149
|
-
align-items: center;
|
|
150
|
-
gap: 6px;
|
|
151
|
-
padding: 3px 6px 3px 8px;
|
|
152
|
-
border-radius: 999px;
|
|
153
|
-
background: var(--sg-bg, #fff);
|
|
154
|
-
border: 1px solid var(--sg-border, #e2e8f0);
|
|
155
|
-
cursor: grab;
|
|
156
|
-
user-select: none;
|
|
157
|
-
font-weight: 600;
|
|
158
|
-
}
|
|
159
|
-
.sv-rgp-chip-drag { opacity: 0.5; }
|
|
160
|
-
.sv-rgp-grip { color: var(--sg-muted, #94a3b8); cursor: grab; }
|
|
161
|
-
.sv-rgp-x {
|
|
162
|
-
display: inline-flex;
|
|
163
|
-
align-items: center;
|
|
164
|
-
justify-content: center;
|
|
165
|
-
width: 16px;
|
|
166
|
-
height: 16px;
|
|
167
|
-
border: 0;
|
|
168
|
-
border-radius: 50%;
|
|
169
|
-
background: none;
|
|
170
|
-
color: var(--sg-muted, #64748b);
|
|
171
|
-
cursor: pointer;
|
|
172
|
-
font-size: 14px;
|
|
173
|
-
line-height: 1;
|
|
174
|
-
}
|
|
175
|
-
.sv-rgp-x:hover { background: var(--sg-header-bg, #f1f5f9); color: var(--sg-fg, #0f172a); }
|
|
176
|
-
.sv-rgp-add {
|
|
177
|
-
font: inherit;
|
|
178
|
-
font-size: 13px;
|
|
179
|
-
padding: 4px 8px;
|
|
180
|
-
border-radius: 8px;
|
|
181
|
-
border: 1px solid var(--sg-border, #e2e8f0);
|
|
182
|
-
background: var(--sg-bg, #fff);
|
|
183
|
-
color: var(--sg-accent, #2563eb);
|
|
184
|
-
cursor: pointer;
|
|
185
|
-
}
|
|
186
|
-
</style>
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createServerGroupModel } from './server-group-model'
|
|
3
|
-
import type { ServerDataSource, ServerDisplayRow, ServerGroupRow } from './server-data-source'
|
|
4
|
-
|
|
5
|
-
type Row = { country: string; city: string; amount: number }
|
|
6
|
-
|
|
7
|
-
const DATA: Row[] = [
|
|
8
|
-
{ country: 'DE', city: 'Berlin', amount: 10 },
|
|
9
|
-
{ country: 'DE', city: 'Berlin', amount: 5 },
|
|
10
|
-
{ country: 'DE', city: 'Munich', amount: 7 },
|
|
11
|
-
{ country: 'FR', city: 'Paris', amount: 3 },
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* A fake backend that groups + aggregates DATA per the request's groupBy /
|
|
16
|
-
* groupKeys, exactly as a real SQL GROUP BY would - returns group rows for a
|
|
17
|
-
* group level and leaf rows at the bottom.
|
|
18
|
-
*/
|
|
19
|
-
function makeGroupSource(data: Row[], delay = 0): ServerDataSource<Row> & { calls: number } {
|
|
20
|
-
return {
|
|
21
|
-
calls: 0,
|
|
22
|
-
async getRows(req) {
|
|
23
|
-
this.calls += 1
|
|
24
|
-
if (delay) await new Promise((r) => setTimeout(r, delay))
|
|
25
|
-
const subset = data.filter((r) =>
|
|
26
|
-
req.groupKeys!.every((k, i) => String((r as Record<string, unknown>)[req.groupBy![i]!]) === k),
|
|
27
|
-
)
|
|
28
|
-
const level = req.groupKeys!.length
|
|
29
|
-
if (level < req.groupBy!.length) {
|
|
30
|
-
const field = req.groupBy![level]!
|
|
31
|
-
const map = new Map<string, Record<string, unknown>>()
|
|
32
|
-
for (const r of subset) {
|
|
33
|
-
const key = String((r as Record<string, unknown>)[field])
|
|
34
|
-
const agg = map.get(key) ?? { [field]: (r as Record<string, unknown>)[field] }
|
|
35
|
-
for (const a of req.aggregations!) {
|
|
36
|
-
const cur = (agg[a.col] as number) ?? 0
|
|
37
|
-
agg[a.col] = a.fn === 'count' ? cur + 1 : cur + ((r as Record<string, unknown>)[a.col] as number)
|
|
38
|
-
}
|
|
39
|
-
map.set(key, agg)
|
|
40
|
-
}
|
|
41
|
-
const groups = [...map.values()]
|
|
42
|
-
return { rows: groups.slice(req.startRow, req.endRow) as unknown as Row[], rowCount: groups.length }
|
|
43
|
-
}
|
|
44
|
-
return { rows: subset.slice(req.startRow, req.endRow), rowCount: subset.length }
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const flush = () => new Promise((r) => setTimeout(r, 0))
|
|
50
|
-
const groups = (rows: ServerDisplayRow<Row>[]) => rows.filter((r): r is ServerGroupRow<Row> => r.kind === 'group')
|
|
51
|
-
|
|
52
|
-
describe('createServerGroupModel', () => {
|
|
53
|
-
it('refresh() loads the top-level groups with aggregates', async () => {
|
|
54
|
-
const src = makeGroupSource(DATA)
|
|
55
|
-
let state = createServerGroupModel<Row>(src, {
|
|
56
|
-
groupBy: ['country', 'city'],
|
|
57
|
-
aggregations: [{ col: 'amount', fn: 'sum' }],
|
|
58
|
-
onChange: (s) => (state = s as never),
|
|
59
|
-
}).getState()
|
|
60
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
61
|
-
groupBy: ['country', 'city'],
|
|
62
|
-
aggregations: [{ col: 'amount', fn: 'sum' }],
|
|
63
|
-
onChange: (s) => (state = s as never),
|
|
64
|
-
})
|
|
65
|
-
ctl.refresh()
|
|
66
|
-
await flush()
|
|
67
|
-
const g = groups(state.displayRows)
|
|
68
|
-
expect(g.map((r) => r.key)).toEqual(['DE', 'FR'])
|
|
69
|
-
expect(g.every((r) => r.level === 0 && !r.expanded)).toBe(true)
|
|
70
|
-
expect(g[0]!.aggregates.amount).toBe(22) // 10 + 5 + 7
|
|
71
|
-
expect(g[1]!.aggregates.amount).toBe(3)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('expanding a group lazily fetches and splices its child groups', async () => {
|
|
75
|
-
const src = makeGroupSource(DATA)
|
|
76
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
77
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
78
|
-
groupBy: ['country', 'city'],
|
|
79
|
-
aggregations: [{ col: 'amount', fn: 'sum' }],
|
|
80
|
-
onChange: (s) => (state = s as never),
|
|
81
|
-
})
|
|
82
|
-
ctl.refresh()
|
|
83
|
-
await flush()
|
|
84
|
-
const callsBefore = src.calls
|
|
85
|
-
ctl.expandGroup(['DE'])
|
|
86
|
-
await flush()
|
|
87
|
-
expect(src.calls).toBe(callsBefore + 1) // one fetch for DE's children
|
|
88
|
-
const rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
89
|
-
// DE (expanded), Berlin, Munich, FR
|
|
90
|
-
expect(rows.map((r) => (r.kind === 'group' ? r.key : 'leaf'))).toEqual(['DE', 'Berlin', 'Munich', 'FR'])
|
|
91
|
-
const berlin = rows.find((r) => r.kind === 'group' && r.key === 'Berlin') as ServerGroupRow<Row>
|
|
92
|
-
expect(berlin.level).toBe(1)
|
|
93
|
-
expect(berlin.aggregates.amount).toBe(15) // 10 + 5
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('expanding a bottom-level group returns leaf rows', async () => {
|
|
97
|
-
const src = makeGroupSource(DATA)
|
|
98
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
99
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
100
|
-
groupBy: ['country', 'city'],
|
|
101
|
-
onChange: (s) => (state = s as never),
|
|
102
|
-
})
|
|
103
|
-
ctl.refresh()
|
|
104
|
-
await flush()
|
|
105
|
-
ctl.expandGroup(['DE'])
|
|
106
|
-
await flush()
|
|
107
|
-
ctl.expandGroup(['DE', 'Berlin'])
|
|
108
|
-
await flush()
|
|
109
|
-
const rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
110
|
-
const leaves = rows.filter((r) => r.kind === 'leaf')
|
|
111
|
-
expect(leaves).toHaveLength(2) // the two Berlin rows
|
|
112
|
-
expect(leaves.every((r) => r.level === 2)).toBe(true)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('collapse removes the children again', async () => {
|
|
116
|
-
const src = makeGroupSource(DATA)
|
|
117
|
-
// Reads go through ctl.getState() below, so the change callback is a no-op.
|
|
118
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
119
|
-
groupBy: ['country', 'city'],
|
|
120
|
-
onChange: () => {},
|
|
121
|
-
})
|
|
122
|
-
ctl.refresh()
|
|
123
|
-
await flush()
|
|
124
|
-
ctl.expandGroup(['DE'])
|
|
125
|
-
await flush()
|
|
126
|
-
ctl.collapseGroup(['DE'])
|
|
127
|
-
const rows = ctl.getState().displayRows
|
|
128
|
-
expect(rows.map((r) => (r.kind === 'group' ? r.key : 'leaf'))).toEqual(['DE', 'FR'])
|
|
129
|
-
expect(ctl.isExpanded(['DE'])).toBe(false)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('setFilter resets the tree and refetches from the top', async () => {
|
|
133
|
-
const src = makeGroupSource(DATA)
|
|
134
|
-
const ctl = createServerGroupModel<Row>(src, { groupBy: ['country'], onChange: () => {} })
|
|
135
|
-
ctl.refresh()
|
|
136
|
-
await flush()
|
|
137
|
-
const before = src.calls
|
|
138
|
-
ctl.setFilter({ global: 'x' })
|
|
139
|
-
await flush()
|
|
140
|
-
expect(src.calls).toBeGreaterThan(before)
|
|
141
|
-
expect(ctl.getState().filterModel).toEqual({ global: 'x' })
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('a stale expand response is dropped after a reset (race safety)', async () => {
|
|
145
|
-
const src = makeGroupSource(DATA, 20) // slow
|
|
146
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
147
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
148
|
-
groupBy: ['country', 'city'],
|
|
149
|
-
onChange: (s) => (state = s as never),
|
|
150
|
-
})
|
|
151
|
-
ctl.refresh()
|
|
152
|
-
await new Promise((r) => setTimeout(r, 30))
|
|
153
|
-
ctl.expandGroup(['DE']) // in-flight, slow
|
|
154
|
-
ctl.setGroupBy(['city']) // reset (bumps generation) before DE's children land
|
|
155
|
-
await new Promise((r) => setTimeout(r, 60))
|
|
156
|
-
const rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
157
|
-
// Regrouped by city at the top; no stale DE subtree leaked in.
|
|
158
|
-
expect(rows.every((r) => r.level === 0)).toBe(true)
|
|
159
|
-
expect(rows.some((r) => r.kind === 'group' && r.key === 'DE')).toBe(false)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('pages a group\'s children in blocks (load more)', async () => {
|
|
163
|
-
const data: Row[] = [
|
|
164
|
-
{ country: 'DE', city: 'B', amount: 1 },
|
|
165
|
-
{ country: 'DE', city: 'M', amount: 2 },
|
|
166
|
-
{ country: 'DE', city: 'H', amount: 3 },
|
|
167
|
-
{ country: 'DE', city: 'K', amount: 4 },
|
|
168
|
-
]
|
|
169
|
-
const src = makeGroupSource(data)
|
|
170
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
171
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
172
|
-
groupBy: ['country'],
|
|
173
|
-
pageSize: 2,
|
|
174
|
-
onChange: (s) => (state = s as never),
|
|
175
|
-
})
|
|
176
|
-
ctl.refresh()
|
|
177
|
-
await flush()
|
|
178
|
-
ctl.expandGroup(['DE'])
|
|
179
|
-
await flush()
|
|
180
|
-
let rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
181
|
-
expect(rows.filter((r) => r.kind === 'leaf')).toHaveLength(2) // first block
|
|
182
|
-
const more = rows.find((r) => r.kind === 'more')
|
|
183
|
-
expect(more && more.kind === 'more' && more.remaining).toBe(2)
|
|
184
|
-
|
|
185
|
-
ctl.loadMoreChildren(['DE'])
|
|
186
|
-
await flush()
|
|
187
|
-
rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
188
|
-
expect(rows.filter((r) => r.kind === 'leaf')).toHaveLength(4) // all loaded
|
|
189
|
-
expect(rows.some((r) => r.kind === 'more')).toBe(false) // no more affordance
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
it('emits a subtotal footer row after an expanded group when groupFooters is on', async () => {
|
|
193
|
-
const src = makeGroupSource(DATA)
|
|
194
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
195
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
196
|
-
groupBy: ['country', 'city'],
|
|
197
|
-
aggregations: [{ col: 'amount', fn: 'sum' }],
|
|
198
|
-
groupFooters: true,
|
|
199
|
-
onChange: (s) => (state = s as never),
|
|
200
|
-
})
|
|
201
|
-
ctl.refresh()
|
|
202
|
-
await flush()
|
|
203
|
-
ctl.expandGroup(['DE'])
|
|
204
|
-
await flush()
|
|
205
|
-
const rows = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
206
|
-
const footers = rows.filter((r) => r.kind === 'footer')
|
|
207
|
-
expect(footers).toHaveLength(1)
|
|
208
|
-
expect(footers[0]!.kind === 'footer' && footers[0]!.key).toBe('DE')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('shows skeleton rows while a group\'s first block is loading', async () => {
|
|
212
|
-
const src = makeGroupSource(DATA, 20) // slow
|
|
213
|
-
let state = { displayRows: [] as ServerDisplayRow<Row>[] } as never
|
|
214
|
-
const ctl = createServerGroupModel<Row>(src, {
|
|
215
|
-
groupBy: ['country', 'city'],
|
|
216
|
-
skeletonRows: 3,
|
|
217
|
-
onChange: (s) => (state = s as never),
|
|
218
|
-
})
|
|
219
|
-
ctl.refresh()
|
|
220
|
-
await new Promise((r) => setTimeout(r, 35)) // root loaded
|
|
221
|
-
ctl.expandGroup(['DE']) // fetch in flight -> synchronous emit with skeletons
|
|
222
|
-
const loading = (state as { displayRows: ServerDisplayRow<Row>[] }).displayRows
|
|
223
|
-
expect(loading.filter((r) => r.kind === 'skeleton')).toHaveLength(3)
|
|
224
|
-
await new Promise((r) => setTimeout(r, 40)) // DE loads
|
|
225
|
-
expect((state as { displayRows: ServerDisplayRow<Row>[] }).displayRows.some((r) => r.kind === 'skeleton')).toBe(false)
|
|
226
|
-
})
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
type Node = { id: string; parentId: string | null; name: string; kids: boolean }
|
|
230
|
-
const TREE: Node[] = [
|
|
231
|
-
{ id: '1', parentId: null, name: 'Root A', kids: true },
|
|
232
|
-
{ id: '2', parentId: null, name: 'Root B', kids: false },
|
|
233
|
-
{ id: '1a', parentId: '1', name: 'Child A1', kids: true },
|
|
234
|
-
{ id: '1b', parentId: '1', name: 'Child A2', kids: false },
|
|
235
|
-
{ id: '1a1', parentId: '1a', name: 'Leaf A1a', kids: false },
|
|
236
|
-
]
|
|
237
|
-
|
|
238
|
-
/** A fake self-referential tree backend: return the children of the node at the end of groupKeys. */
|
|
239
|
-
function makeTreeSource(nodes: Node[]): ServerDataSource<Node> & { calls: number } {
|
|
240
|
-
return {
|
|
241
|
-
calls: 0,
|
|
242
|
-
async getRows(req) {
|
|
243
|
-
this.calls += 1
|
|
244
|
-
const parent = req.groupKeys!.length ? req.groupKeys![req.groupKeys!.length - 1] : null
|
|
245
|
-
const children = nodes.filter((n) => (n.parentId ?? null) === (parent ?? null))
|
|
246
|
-
return { rows: children, rowCount: children.length }
|
|
247
|
-
},
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
describe('createServerGroupModel - treeData', () => {
|
|
252
|
-
const opts = (onChange: (s: unknown) => void) => ({
|
|
253
|
-
treeData: true,
|
|
254
|
-
getRowId: (n: Node) => n.id,
|
|
255
|
-
hasChildren: (n: Node) => n.kids,
|
|
256
|
-
onChange: onChange as never,
|
|
257
|
-
})
|
|
258
|
-
const nameOf = (r: ServerDisplayRow<Node>) => ('data' in r ? r.data.name : `(${r.kind})`)
|
|
259
|
-
|
|
260
|
-
it('throws without getRowId/hasChildren', () => {
|
|
261
|
-
expect(() => createServerGroupModel<Node>(makeTreeSource(TREE), { treeData: true, onChange: () => {} })).toThrow()
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
it('loads roots, marking expandable nodes as groups and leaves as leaves', async () => {
|
|
265
|
-
const src = makeTreeSource(TREE)
|
|
266
|
-
let state = { displayRows: [] as ServerDisplayRow<Node>[] } as never
|
|
267
|
-
const ctl = createServerGroupModel<Node>(src, opts((s) => (state = s as never)))
|
|
268
|
-
ctl.refresh()
|
|
269
|
-
await flush()
|
|
270
|
-
const rows = (state as { displayRows: ServerDisplayRow<Node>[] }).displayRows
|
|
271
|
-
expect(rows.map((r) => [r.kind, nameOf(r)])).toEqual([
|
|
272
|
-
['group', 'Root A'], // kids: true
|
|
273
|
-
['leaf', 'Root B'], // kids: false
|
|
274
|
-
])
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
it('expanding a node fetches and splices its children by id path', async () => {
|
|
278
|
-
const src = makeTreeSource(TREE)
|
|
279
|
-
let state = { displayRows: [] as ServerDisplayRow<Node>[] } as never
|
|
280
|
-
const ctl = createServerGroupModel<Node>(src, opts((s) => (state = s as never)))
|
|
281
|
-
ctl.refresh()
|
|
282
|
-
await flush()
|
|
283
|
-
ctl.expandGroup(['1'])
|
|
284
|
-
await flush()
|
|
285
|
-
let rows = (state as { displayRows: ServerDisplayRow<Node>[] }).displayRows
|
|
286
|
-
expect(rows.map(nameOf)).toEqual(['Root A', 'Child A1', 'Child A2', 'Root B'])
|
|
287
|
-
// drill one more level: Child A1 -> Leaf A1a
|
|
288
|
-
ctl.expandGroup(['1', '1a'])
|
|
289
|
-
await flush()
|
|
290
|
-
rows = (state as { displayRows: ServerDisplayRow<Node>[] }).displayRows
|
|
291
|
-
expect(rows.map(nameOf)).toEqual(['Root A', 'Child A1', 'Leaf A1a', 'Child A2', 'Root B'])
|
|
292
|
-
expect(rows.find((r) => nameOf(r) === 'Leaf A1a')!.level).toBe(2)
|
|
293
|
-
})
|
|
294
|
-
})
|