@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
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four seams a server-backed row model plugs into, all free:
|
|
3
|
+
*
|
|
4
|
+
* - `onVisibleRangeChange` - which rows are on screen, by index
|
|
5
|
+
* - `rowPlaceholder` / `onRetryRow` - rows whose data has not arrived
|
|
6
|
+
* - `rowSelectionModel` - selection the grid does not own
|
|
7
|
+
*
|
|
8
|
+
* They are deliberately generic: none of them mentions groups, blocks or a
|
|
9
|
+
* datasource, so the free infinite controller and the Enterprise row model
|
|
10
|
+
* plug into the same four holes, and an app with its own fetching can too.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
13
|
+
import { mount, unmount } from 'svelte'
|
|
14
|
+
import SvGrid from './SvGrid.svelte'
|
|
15
|
+
import {
|
|
16
|
+
createCoreRowModel,
|
|
17
|
+
rowSelectionFeature,
|
|
18
|
+
rowSortingFeature,
|
|
19
|
+
tableFeatures,
|
|
20
|
+
} from './index'
|
|
21
|
+
import type { ColumnDef, SvGridApi } from './index'
|
|
22
|
+
|
|
23
|
+
type Row = { id: number; name: string }
|
|
24
|
+
const features = tableFeatures({ rowSortingFeature, rowSelectionFeature })
|
|
25
|
+
const cols: ColumnDef<typeof features, Row>[] = [
|
|
26
|
+
{ field: 'name', header: 'Name', width: 160 },
|
|
27
|
+
{ field: 'id', header: 'Id', width: 80 },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const tick = () => new Promise<void>((r) => setTimeout(r))
|
|
31
|
+
const rowsOf = (n: number): Row[] =>
|
|
32
|
+
Array.from({ length: n }, (_, i) => ({ id: i, name: `Row ${i}` }))
|
|
33
|
+
|
|
34
|
+
function mountGrid(extra: Record<string, unknown>, data: Row[] = rowsOf(5)) {
|
|
35
|
+
return new Promise<{
|
|
36
|
+
api: SvGridApi<typeof features, Row>
|
|
37
|
+
target: HTMLElement
|
|
38
|
+
destroy: () => void
|
|
39
|
+
}>((res, rej) => {
|
|
40
|
+
const target = document.createElement('div')
|
|
41
|
+
document.body.appendChild(target)
|
|
42
|
+
const app = mount(SvGrid, {
|
|
43
|
+
target,
|
|
44
|
+
props: {
|
|
45
|
+
data,
|
|
46
|
+
columns: cols,
|
|
47
|
+
features,
|
|
48
|
+
_rowModels: { coreRowModel: createCoreRowModel() },
|
|
49
|
+
getRowId: (r: Row) => String(r.id),
|
|
50
|
+
containerHeight: 200,
|
|
51
|
+
virtualization: false,
|
|
52
|
+
onApiReady(api: SvGridApi<typeof features, Row>) {
|
|
53
|
+
res({ api, target, destroy: () => { unmount(app); target.remove() } })
|
|
54
|
+
},
|
|
55
|
+
...extra,
|
|
56
|
+
} as any,
|
|
57
|
+
})
|
|
58
|
+
queueMicrotask(() => {
|
|
59
|
+
if (!target.querySelector('[role="grid"]')) rej(new Error('no grid'))
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('onVisibleRangeChange', () => {
|
|
65
|
+
it('reports the whole list when virtualization is off', async () => {
|
|
66
|
+
const seen: Array<{ startIndex: number; endIndex: number }> = []
|
|
67
|
+
const { destroy } = await mountGrid({ onVisibleRangeChange: (r: any) => seen.push(r) }, rowsOf(5))
|
|
68
|
+
await tick()
|
|
69
|
+
|
|
70
|
+
expect(seen.at(-1)).toEqual({ startIndex: 0, endIndex: 4 })
|
|
71
|
+
destroy()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('reports row indices, not pixels, when virtualization is on', async () => {
|
|
75
|
+
const seen: Array<{ startIndex: number; endIndex: number }> = []
|
|
76
|
+
const { destroy } = await mountGrid(
|
|
77
|
+
{
|
|
78
|
+
onVisibleRangeChange: (r: any) => seen.push(r),
|
|
79
|
+
virtualization: true,
|
|
80
|
+
containerHeight: 200,
|
|
81
|
+
},
|
|
82
|
+
rowsOf(10_000),
|
|
83
|
+
)
|
|
84
|
+
await tick()
|
|
85
|
+
|
|
86
|
+
const last = seen.at(-1)!
|
|
87
|
+
expect(last.startIndex).toBe(0)
|
|
88
|
+
// A 200px viewport cannot be showing ten thousand rows, and the numbers
|
|
89
|
+
// are row indices - if these were the pixel offsets the old deriveds
|
|
90
|
+
// carry, endIndex would be in the hundreds.
|
|
91
|
+
expect(last.endIndex).toBeGreaterThan(0)
|
|
92
|
+
expect(last.endIndex).toBeLessThan(200)
|
|
93
|
+
destroy()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('does not fire again while the range is unchanged', async () => {
|
|
97
|
+
const seen: Array<unknown> = []
|
|
98
|
+
const { api, destroy } = await mountGrid({ onVisibleRangeChange: (r: any) => seen.push(r) })
|
|
99
|
+
await tick()
|
|
100
|
+
const count = seen.length
|
|
101
|
+
|
|
102
|
+
// Something unrelated changes; the visible rows do not.
|
|
103
|
+
api.selectRows(['1'])
|
|
104
|
+
await tick()
|
|
105
|
+
expect(seen).toHaveLength(count)
|
|
106
|
+
destroy()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('rowPlaceholder', () => {
|
|
111
|
+
it('draws a skeleton cell per column for a loading row', async () => {
|
|
112
|
+
const { target, destroy } = await mountGrid({
|
|
113
|
+
rowPlaceholder: (row: Row) => (row.id === 2 ? 'loading' : null),
|
|
114
|
+
})
|
|
115
|
+
await tick()
|
|
116
|
+
|
|
117
|
+
const placeholder = target.querySelector('.sv-grid-placeholder-row')!
|
|
118
|
+
expect(placeholder).not.toBeNull()
|
|
119
|
+
expect(placeholder.getAttribute('aria-busy')).toBe('true')
|
|
120
|
+
// One shimmer per real column, so the table keeps its shape.
|
|
121
|
+
expect(placeholder.querySelectorAll('.sv-grid-placeholder-skeleton')).toHaveLength(cols.length)
|
|
122
|
+
// And the rows around it are untouched.
|
|
123
|
+
expect(target.querySelectorAll('.sv-grid-placeholder-row')).toHaveLength(1)
|
|
124
|
+
destroy()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('draws one full-width message with a Retry button for a failed row', async () => {
|
|
128
|
+
const retried: Array<{ row: Row; index: number }> = []
|
|
129
|
+
const { target, destroy } = await mountGrid({
|
|
130
|
+
rowPlaceholder: (row: Row) => (row.id === 3 ? 'failed' : null),
|
|
131
|
+
onRetryRow: (row: Row, index: number) => retried.push({ row, index }),
|
|
132
|
+
})
|
|
133
|
+
await tick()
|
|
134
|
+
|
|
135
|
+
const failed = target.querySelector('.sv-grid-placeholder-failed')!
|
|
136
|
+
expect(failed).not.toBeNull()
|
|
137
|
+
expect(failed.textContent).toContain('Could not load')
|
|
138
|
+
expect(failed.querySelectorAll('.sv-grid-placeholder-skeleton')).toHaveLength(0)
|
|
139
|
+
|
|
140
|
+
const retry = failed.querySelector<HTMLButtonElement>('.sv-grid-placeholder-retry')!
|
|
141
|
+
retry.click()
|
|
142
|
+
expect(retried).toHaveLength(1)
|
|
143
|
+
expect(retried[0]!.row.id).toBe(3)
|
|
144
|
+
expect(retried[0]!.index).toBe(3)
|
|
145
|
+
destroy()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('gives a run of failed rows one message, and a band under it', async () => {
|
|
149
|
+
// A failed block is claimed row by row; the message and Retry sit on
|
|
150
|
+
// the first row of the run, the rest are the same tint with nothing
|
|
151
|
+
// to read, so a hundred-row block is not a hundred red lines.
|
|
152
|
+
const { target, destroy } = await mountGrid(
|
|
153
|
+
{
|
|
154
|
+
rowPlaceholder: (row: Row) => (row.id >= 2 && row.id <= 4 ? 'failed' : null),
|
|
155
|
+
onRetryRow: () => {},
|
|
156
|
+
},
|
|
157
|
+
rowsOf(7),
|
|
158
|
+
)
|
|
159
|
+
await tick()
|
|
160
|
+
const failed = [...target.querySelectorAll('.sv-grid-placeholder-failed')]
|
|
161
|
+
expect(failed).toHaveLength(3)
|
|
162
|
+
expect(failed.map((r) => r.classList.contains('sv-grid-placeholder-failed-cont'))).toEqual([false, true, true])
|
|
163
|
+
expect(target.querySelectorAll('.sv-grid-placeholder-retry')).toHaveLength(1)
|
|
164
|
+
expect(failed[0]!.textContent).toContain('Could not load')
|
|
165
|
+
expect(failed[1]!.textContent!.trim()).toBe('')
|
|
166
|
+
// Every row of the run keeps its own slot: the scrollbar does not move.
|
|
167
|
+
expect(target.querySelectorAll('tbody .sv-grid-row').length).toBe(7)
|
|
168
|
+
destroy()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('omits the Retry button when there is no handler to run', async () => {
|
|
172
|
+
const { target, destroy } = await mountGrid({
|
|
173
|
+
rowPlaceholder: (row: Row) => (row.id === 3 ? 'failed' : null),
|
|
174
|
+
})
|
|
175
|
+
await tick()
|
|
176
|
+
expect(target.querySelector('.sv-grid-placeholder-retry')).toBeNull()
|
|
177
|
+
destroy()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('keeps placeholder rows out of select-all', async () => {
|
|
181
|
+
const { api, destroy } = await mountGrid({
|
|
182
|
+
showRowSelection: true,
|
|
183
|
+
rowPlaceholder: (row: Row) => (row.id >= 3 ? 'loading' : null),
|
|
184
|
+
})
|
|
185
|
+
await tick()
|
|
186
|
+
|
|
187
|
+
api.selectAllRows()
|
|
188
|
+
await tick()
|
|
189
|
+
// Selecting a row that has not loaded would leave an id that never
|
|
190
|
+
// resolves to anything.
|
|
191
|
+
expect(api.getSelectedRowIds().sort()).toEqual(['0', '1', '2'])
|
|
192
|
+
destroy()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('changes nothing for a grid that does not use it', async () => {
|
|
196
|
+
const { target, destroy } = await mountGrid({})
|
|
197
|
+
await tick()
|
|
198
|
+
expect(target.querySelector('.sv-grid-placeholder-row')).toBeNull()
|
|
199
|
+
expect(target.querySelectorAll('.sv-grid-row').length).toBeGreaterThan(0)
|
|
200
|
+
destroy()
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe('rowSelectionModel', () => {
|
|
205
|
+
/**
|
|
206
|
+
* "Everything except these", the shape a server-side selection needs.
|
|
207
|
+
*
|
|
208
|
+
* Its state is a rune, so the header checkbox re-derives when the model
|
|
209
|
+
* changes. That is a requirement of the seam, not an accident of the test:
|
|
210
|
+
* the grid re-reads `headerState()` only when something reactive that it
|
|
211
|
+
* touched has changed.
|
|
212
|
+
*/
|
|
213
|
+
function exceptModel(totalRows: number) {
|
|
214
|
+
const state = $state({ selectAll: false, toggled: new Set<string>() })
|
|
215
|
+
return {
|
|
216
|
+
state,
|
|
217
|
+
isSelected: (rowId: string) => state.selectAll !== state.toggled.has(rowId),
|
|
218
|
+
headerState: () => {
|
|
219
|
+
if (state.selectAll) return state.toggled.size === 0 ? 'all' : 'some'
|
|
220
|
+
return state.toggled.size === 0 ? 'none' : 'some'
|
|
221
|
+
},
|
|
222
|
+
toggle: (rowId: string) => {
|
|
223
|
+
const next = new Set(state.toggled)
|
|
224
|
+
if (next.has(rowId)) next.delete(rowId)
|
|
225
|
+
else next.add(rowId)
|
|
226
|
+
state.toggled = next
|
|
227
|
+
},
|
|
228
|
+
toggleAll: (next: boolean) => {
|
|
229
|
+
state.selectAll = next
|
|
230
|
+
state.toggled = new Set()
|
|
231
|
+
},
|
|
232
|
+
selectedCount: () => (state.selectAll ? totalRows - state.toggled.size : state.toggled.size),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
it('answers the header checkbox instead of counting loaded rows', async () => {
|
|
237
|
+
const model = exceptModel(1_000_000)
|
|
238
|
+
const { target, destroy } = await mountGrid({ showRowSelection: true, rowSelectionModel: model })
|
|
239
|
+
await tick()
|
|
240
|
+
|
|
241
|
+
const header = target.querySelector('thead [role="checkbox"], thead input[type="checkbox"]')!
|
|
242
|
+
expect(header.getAttribute('aria-checked')).toBe('false')
|
|
243
|
+
|
|
244
|
+
model.toggleAll(true)
|
|
245
|
+
await vi.waitFor(() =>
|
|
246
|
+
expect(
|
|
247
|
+
target.querySelector('thead [role="checkbox"], thead input[type="checkbox"]')!
|
|
248
|
+
.getAttribute('aria-checked'),
|
|
249
|
+
).toBe('true'),
|
|
250
|
+
)
|
|
251
|
+
// Five rows are loaded; a million are selected. The grid says "all"
|
|
252
|
+
// because the model said so, which is the whole point of the seam.
|
|
253
|
+
expect(model.selectedCount()).toBe(1_000_000)
|
|
254
|
+
destroy()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('routes the header checkbox click to toggleAll, not to the loaded rows', async () => {
|
|
258
|
+
const model = exceptModel(1_000_000)
|
|
259
|
+
const { target, destroy } = await mountGrid({ showRowSelection: true, rowSelectionModel: model })
|
|
260
|
+
await tick()
|
|
261
|
+
|
|
262
|
+
const header = target.querySelector<HTMLElement>('thead [role="checkbox"], thead input[type="checkbox"]')!
|
|
263
|
+
header.click()
|
|
264
|
+
await tick()
|
|
265
|
+
|
|
266
|
+
expect(model.state.selectAll).toBe(true)
|
|
267
|
+
expect(model.selectedCount()).toBe(1_000_000)
|
|
268
|
+
destroy()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('routes a row checkbox through the model', async () => {
|
|
272
|
+
const model = exceptModel(1_000_000)
|
|
273
|
+
const { api, destroy } = await mountGrid({ showRowSelection: true, rowSelectionModel: model })
|
|
274
|
+
await tick()
|
|
275
|
+
|
|
276
|
+
api.toggleRowSelected('2')
|
|
277
|
+
await tick()
|
|
278
|
+
expect(model.state.toggled.has('2')).toBe(true)
|
|
279
|
+
expect(model.isSelected('2')).toBe(true)
|
|
280
|
+
|
|
281
|
+
model.toggleAll(true)
|
|
282
|
+
await tick()
|
|
283
|
+
api.toggleRowSelected('2')
|
|
284
|
+
// Under select-all, toggling a row means "except this one".
|
|
285
|
+
expect(model.isSelected('2')).toBe(false)
|
|
286
|
+
expect(model.selectedCount()).toBe(999_999)
|
|
287
|
+
destroy()
|
|
288
|
+
})
|
|
289
|
+
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Every Enterprise gate carries the same licensing line.
|
|
3
3
|
*
|
|
4
|
-
* There are
|
|
5
|
-
* selection bar - and they used to be four hand-written paragraphs.
|
|
6
|
-
* worded
|
|
4
|
+
* There are five of them - Kanban board, scheduler, Gantt, pivot mode and the
|
|
5
|
+
* selection bar - and they used to be four hand-written paragraphs. Gates
|
|
6
|
+
* worded one way each is how a product ends up explaining its own licensing
|
|
7
7
|
* inconsistently, so they now share one snippet. This is the test that keeps
|
|
8
|
-
* them sharing it: a
|
|
8
|
+
* them sharing it: a sixth Enterprise feature that hand-rolls its own note
|
|
9
9
|
* will show up here as a missing licensing line.
|
|
10
10
|
*
|
|
11
11
|
* None of the renderers are registered in this file, which is the point - it
|
|
@@ -17,6 +17,7 @@ import SvGrid from './SvGrid.svelte'
|
|
|
17
17
|
import {
|
|
18
18
|
createCoreRowModel,
|
|
19
19
|
hasBoardView,
|
|
20
|
+
hasGanttView,
|
|
20
21
|
hasSchedulerView,
|
|
21
22
|
hasSelectionBarView,
|
|
22
23
|
tableFeatures,
|
|
@@ -69,12 +70,14 @@ describe('Enterprise upsell notes', () => {
|
|
|
69
70
|
// Otherwise every assertion below is checking the wrong branch.
|
|
70
71
|
expect(hasBoardView()).toBe(false)
|
|
71
72
|
expect(hasSchedulerView()).toBe(false)
|
|
73
|
+
expect(hasGanttView()).toBe(false)
|
|
72
74
|
expect(hasSelectionBarView()).toBe(false)
|
|
73
75
|
})
|
|
74
76
|
|
|
75
77
|
const gates: Array<[string, Record<string, unknown>, ((api: SvGridApi<typeof features, Row>) => void)?]> = [
|
|
76
78
|
['Kanban board', { board: { groupBy: 'team' } }],
|
|
77
79
|
['scheduler', { scheduler: { startField: 'name' } }],
|
|
80
|
+
['Gantt', { gantt: { startField: 'name' } }],
|
|
78
81
|
['selection bar', { showRowSelection: true, selectionBar: true }, (api) => api.selectRows(['1'])],
|
|
79
82
|
// pivotViewOn = a pivot config AND pivot mode on; without the Pro engine
|
|
80
83
|
// `pivotResult` stays null and the upsell renders in its place.
|
package/src/validate.test.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { describe, expect, it } from 'vitest'
|
|
7
7
|
import { validateGridConfig } from './validate'
|
|
8
|
+
import { createRowPlaceholder } from './server-block-cache'
|
|
8
9
|
|
|
9
10
|
type Row = { name: string; amount: number }
|
|
10
11
|
const data: Row[] = [
|
|
@@ -48,6 +49,15 @@ describe('unknown field', () => {
|
|
|
48
49
|
expect(run({ columns: [{ id: 'actions', header: '' }] })).toEqual([])
|
|
49
50
|
})
|
|
50
51
|
|
|
52
|
+
it('looks past placeholder rows, which a row model shows before its data lands', () => {
|
|
53
|
+
const loading = [createRowPlaceholder('loading', { index: 0 }), createRowPlaceholder('loading', { index: 1 })]
|
|
54
|
+
// Only placeholders: nothing to judge the columns by, so no warning.
|
|
55
|
+
expect(run({ data: loading })).toEqual([])
|
|
56
|
+
// Placeholders ahead of real rows: the real rows are the sample.
|
|
57
|
+
expect(run({ data: [...loading, ...data] })).toEqual([])
|
|
58
|
+
expect(run({ data: [...loading, ...data], columns: [{ field: 'naem' }] })).toHaveLength(1)
|
|
59
|
+
})
|
|
60
|
+
|
|
51
61
|
it('looks through group columns', () => {
|
|
52
62
|
const msgs = run({ columns: [{ header: 'Group', columns: [{ field: 'nope' }] }] })
|
|
53
63
|
expect(msgs[0]).toContain('"nope" does not exist')
|
|
@@ -151,6 +161,25 @@ describe('treeData fields', () => {
|
|
|
151
161
|
})
|
|
152
162
|
})
|
|
153
163
|
|
|
164
|
+
describe('gantt versus treeData', () => {
|
|
165
|
+
it('flags both trees being configured at once', () => {
|
|
166
|
+
const msg = run({ gantt: { startField: 'name' }, treeData: { parentField: 'name' } }).find(
|
|
167
|
+
(m) => m.includes('`gantt` and `treeData`'),
|
|
168
|
+
)!
|
|
169
|
+
expect(msg).toBeDefined()
|
|
170
|
+
expect(msg).toContain('gantt.parentField')
|
|
171
|
+
expect(msg).toContain('summary bars will under-report')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('is silent for a Gantt on its own', () => {
|
|
175
|
+
expect(run({ gantt: { startField: 'name' } })).toEqual([])
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('is silent for treeData on its own', () => {
|
|
179
|
+
expect(run({ treeData: { parentField: 'name' } })).toEqual([])
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
154
183
|
describe('pinning versus column virtualization', () => {
|
|
155
184
|
it('flags pinning while column virtualization is on by default', () => {
|
|
156
185
|
const msg = run({ initialColumnPinning: { left: ['name'] } })[0]!
|
package/src/validate.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* changes, and must not scale with row count.
|
|
21
21
|
*/
|
|
22
22
|
import type { ColumnDef, RowData, TableFeatures } from './core'
|
|
23
|
+
import { rowPlaceholderState } from './server-block-cache'
|
|
23
24
|
|
|
24
25
|
/** How many rows to sample when deciding whether a `field` exists. */
|
|
25
26
|
const FIELD_SAMPLE_ROWS = 10
|
|
@@ -37,6 +38,10 @@ export type ValidateInput<TFeatures extends TableFeatures, TData extends RowData
|
|
|
37
38
|
pageSize?: number
|
|
38
39
|
groupBy?: ReadonlyArray<string>
|
|
39
40
|
treeData?: { parentField?: string; idField?: string; column?: string }
|
|
41
|
+
/** Gantt mode. Only its presence matters here - see rule 6b. */
|
|
42
|
+
gantt?: unknown
|
|
43
|
+
/** Server-side grouping: group rows carry their key and aggregates, not the leaf fields. */
|
|
44
|
+
serverGroup?: unknown
|
|
40
45
|
initialColumnPinning?: { left?: ReadonlyArray<string>; right?: ReadonlyArray<string> }
|
|
41
46
|
columnVirtualization?: boolean
|
|
42
47
|
externalPagination?: boolean
|
|
@@ -69,9 +74,13 @@ export function validateGridConfig<
|
|
|
69
74
|
// ---- 1. A `field` that does not exist on the data -------------------------
|
|
70
75
|
// The single most expensive silent failure: the column renders, and every
|
|
71
76
|
// cell in it is blank. Sampled across several rows so genuinely sparse data
|
|
72
|
-
// (a key absent from row 0 but present later) does not trip it.
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
// (a key absent from row 0 but present later) does not trip it. A row
|
|
78
|
+
// model shows placeholder rows before its first block lands; those carry
|
|
79
|
+
// no data keys at all and say nothing about the columns.
|
|
80
|
+
const sample = (input.data ?? []).filter((row) => !rowPlaceholderState(row)).slice(0, FIELD_SAMPLE_ROWS)
|
|
81
|
+
// Under server-side grouping the first rows are group rows, which carry
|
|
82
|
+
// a key and aggregates and none of the leaf fields; nothing to judge by.
|
|
83
|
+
if (sample.length && !input.serverGroup) {
|
|
75
84
|
const known = new Set<string>()
|
|
76
85
|
for (const row of sample) {
|
|
77
86
|
if (row && typeof row === 'object') for (const k of Object.keys(row)) known.add(k)
|
|
@@ -189,6 +198,22 @@ export function validateGridConfig<
|
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
200
|
|
|
201
|
+
// ---- 6b. gantt + treeData: two trees over one set of rows -----------------
|
|
202
|
+
// The Gantt builds its own work-breakdown tree from `gantt.parentField`,
|
|
203
|
+
// because it needs every descendant - a collapsed phase still contributes to
|
|
204
|
+
// its summary bar. `treeData` hides collapsed children from the view before
|
|
205
|
+
// the renderer sees them, so the two together silently drop tasks out of
|
|
206
|
+
// their phase's rollup the moment anything is collapsed.
|
|
207
|
+
if (input.gantt && input.treeData) {
|
|
208
|
+
messages.push(
|
|
209
|
+
'[svgrid] `gantt` and `treeData` are both set. The Gantt builds its own ' +
|
|
210
|
+
'work-breakdown tree from `gantt.parentField` and needs every ' +
|
|
211
|
+
'descendant row, but `treeData` hides collapsed children from the view, ' +
|
|
212
|
+
'so summary bars will under-report. Remove `treeData` and set ' +
|
|
213
|
+
'`gantt.parentField` instead.',
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
192
217
|
// ---- 7. Pinning that column virtualization will hide ----------------------
|
|
193
218
|
// Documented incompatibility: the virtualizer recycles column DOM nodes, so
|
|
194
219
|
// sticky pinning cannot survive it. `columnVirtualization` defaults to ON,
|
|
@@ -33,29 +33,46 @@ function sameState(a: VirtualizerState, b: VirtualizerState) {
|
|
|
33
33
|
for (let i = 0; i < a.items.length; i += 1) {
|
|
34
34
|
const ai = a.items[i]!
|
|
35
35
|
const bi = b.items[i]!
|
|
36
|
-
if (ai.index !== bi.index || ai.start !== bi.start || ai.size !== bi.size) return false
|
|
36
|
+
if (ai.index !== bi.index || ai.start !== bi.start || ai.size !== bi.size || ai.key !== bi.key) return false
|
|
37
37
|
}
|
|
38
38
|
return true
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Slot-based keying: the key is
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// the
|
|
41
|
+
// Slot-based keying: the key is a SLOT number, not the data index, so the
|
|
42
|
+
// same N <tr> nodes stay alive for the lifetime of the grid and scrolling
|
|
43
|
+
// never mounts or unmounts a row. With data-index keys, scrolling down one
|
|
44
|
+
// row meant the old topmost key disappeared and a new bottommost key
|
|
45
|
+
// appeared, so Svelte unmounted the top <tr> and mounted a fresh one at the
|
|
46
|
+
// bottom on every scroll tick.
|
|
46
47
|
//
|
|
47
|
-
//
|
|
48
|
-
// scroll
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
48
|
+
// The slot for data index i is `i mod N`, N being the window length. That
|
|
49
|
+
// matters for what a one-row scroll costs: while N holds, a row that stays
|
|
50
|
+
// inside the window keeps its slot, so Svelte keeps its <tr> and touches
|
|
51
|
+
// nothing in it, and only the row that entered gets new content - the keyed
|
|
52
|
+
// each moves that one <tr> to the other end with a single `insertBefore`.
|
|
53
|
+
// The earlier `i - startIndex` slot scheme handed EVERY rendered row a new
|
|
54
|
+
// index on every one-row scroll, so an arrow key re-rendered all ~30 rows x
|
|
55
|
+
// every rendered column to shift the data up by one.
|
|
56
|
+
//
|
|
57
|
+
// The modulus has to be EXACTLY N, not merely >= N: under a larger modulus
|
|
58
|
+
// the window's residues are a proper subset, so each one-row scroll drops
|
|
59
|
+
// one residue and picks up another, and Svelte unmounts a <tr> and mounts a
|
|
60
|
+
// fresh one instead of moving it (measured: mount + unmount per arrow key,
|
|
61
|
+
// zero moves). When N changes - the top and bottom overscan clamping on the
|
|
62
|
+
// first and last rows, a resize, variable row heights - every slot remaps
|
|
63
|
+
// and the whole window re-renders once, which is what the old scheme did on
|
|
64
|
+
// every tick.
|
|
65
|
+
function slotKey(index: number, windowLength: number): string {
|
|
66
|
+
return `virtual_slot_${index % windowLength}`
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
function buildUniformItems(
|
|
54
70
|
startIndex: number,
|
|
55
71
|
endIndex: number,
|
|
56
72
|
estimateSize: number,
|
|
57
73
|
): Array<VirtualItem> {
|
|
58
74
|
const items: Array<VirtualItem> = []
|
|
75
|
+
const windowLength = endIndex - startIndex + 1
|
|
59
76
|
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
60
77
|
const start = index * estimateSize
|
|
61
78
|
items.push({
|
|
@@ -63,7 +80,7 @@ function buildUniformItems(
|
|
|
63
80
|
start,
|
|
64
81
|
size: estimateSize,
|
|
65
82
|
end: start + estimateSize,
|
|
66
|
-
key:
|
|
83
|
+
key: slotKey(index, windowLength),
|
|
67
84
|
})
|
|
68
85
|
}
|
|
69
86
|
return items
|
|
@@ -91,8 +108,8 @@ export function buildPreMeasureItems(
|
|
|
91
108
|
if (count <= 0) return []
|
|
92
109
|
const size = Math.max(estimateSize, 1)
|
|
93
110
|
const visible = Math.ceil(Math.max(viewportHeight, 0) / size)
|
|
94
|
-
const endIndex = Math.min(visible + Math.max(overscan, 0), count - 1)
|
|
95
|
-
return buildUniformItems(0,
|
|
111
|
+
const endIndex = Math.max(Math.min(visible + Math.max(overscan, 0), count - 1), 0)
|
|
112
|
+
return buildUniformItems(0, endIndex, size)
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
function buildVariableItems(
|
|
@@ -101,6 +118,7 @@ function buildVariableItems(
|
|
|
101
118
|
offsets: Array<number>,
|
|
102
119
|
): Array<VirtualItem> {
|
|
103
120
|
const items: Array<VirtualItem> = []
|
|
121
|
+
const windowLength = endIndex - startIndex + 1
|
|
104
122
|
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
105
123
|
const start = offsets[index] ?? 0
|
|
106
124
|
const end = offsets[index + 1] ?? start
|
|
@@ -109,7 +127,7 @@ function buildVariableItems(
|
|
|
109
127
|
start,
|
|
110
128
|
size: end - start,
|
|
111
129
|
end,
|
|
112
|
-
key:
|
|
130
|
+
key: slotKey(index, windowLength),
|
|
113
131
|
})
|
|
114
132
|
}
|
|
115
133
|
return items
|
|
@@ -215,8 +233,12 @@ export function createVirtualizer(initial: VirtualizerOptions) {
|
|
|
215
233
|
const sizeFn = options.estimateSize
|
|
216
234
|
const offsets = new Array<number>(count + 1)
|
|
217
235
|
offsets[0] = 0
|
|
236
|
+
// A size of 0 is a real size: a collapsed row or column takes no room
|
|
237
|
+
// and both searches below stay monotonic over equal offsets. Only a
|
|
238
|
+
// negative size is nonsense. The floor used to be 1, which left every
|
|
239
|
+
// hidden column a one-pixel sliver.
|
|
218
240
|
for (let i = 0; i < count; i += 1) {
|
|
219
|
-
offsets[i + 1] = offsets[i]! + Math.max(sizeFn(i),
|
|
241
|
+
offsets[i + 1] = offsets[i]! + Math.max(sizeFn(i), 0)
|
|
220
242
|
}
|
|
221
243
|
offsetCache = { fn: sizeFn, count, offsets }
|
|
222
244
|
return offsets
|