@svgrid/grid 2.1.21 → 2.1.23
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 +9 -2
- package/dist/DockNodeView.svelte +306 -0
- package/dist/DockNodeView.svelte.d.ts +7 -0
- package/dist/DockPopoutHost.svelte +17 -0
- package/dist/DockPopoutHost.svelte.d.ts +14 -0
- package/dist/GridMenus.svelte +83 -3
- package/dist/SvAutoComplete.svelte +5 -3
- package/dist/SvCarousel.svelte +20 -42
- package/dist/SvColorInput.svelte +4 -3
- package/dist/SvColorInput.svelte.d.ts +1 -1
- package/dist/SvComboBox.svelte +7 -3
- package/dist/SvCommand.svelte +28 -85
- package/dist/SvCountryInput.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDateTimePicker.svelte +9 -3
- package/dist/SvDateTimePicker.svelte.d.ts +3 -0
- package/dist/SvDockLayout.svelte +244 -0
- package/dist/SvDockLayout.svelte.d.ts +77 -0
- package/dist/SvDockManager.svelte +922 -0
- package/dist/SvDockManager.svelte.d.ts +106 -0
- package/dist/SvDrawer.svelte +69 -47
- package/dist/SvDrawer.svelte.d.ts +4 -0
- package/dist/SvDropDownList.svelte +85 -13
- package/dist/SvDropDownList.svelte.d.ts +3 -0
- package/dist/SvField.svelte +21 -4
- package/dist/SvField.svelte.d.ts +1 -0
- package/dist/SvForm.svelte +201 -91
- package/dist/SvForm.svelte.d.ts +15 -22
- package/dist/SvGrid.controller.svelte.d.ts +32 -1
- package/dist/SvGrid.controller.svelte.js +156 -27
- package/dist/SvGrid.css +193 -5
- package/dist/SvGrid.svelte +357 -12
- package/dist/SvGrid.types.d.ts +452 -1
- package/dist/SvGridDropdown.svelte +8 -1
- package/dist/SvGridSelect.svelte +1 -0
- package/dist/SvListBox.svelte +36 -14
- package/dist/SvMaskedInput.svelte +4 -3
- package/dist/SvMaskedInput.svelte.d.ts +1 -1
- package/dist/SvMenu.svelte +1 -1
- package/dist/SvMenuList.svelte +23 -71
- package/dist/SvMenuList.svelte.d.ts +2 -13
- package/dist/SvModal.svelte +12 -29
- package/dist/SvMultiSelect.svelte +1 -0
- package/dist/SvNumberInput.svelte +4 -3
- package/dist/SvNumberInput.svelte.d.ts +1 -1
- package/dist/SvPagination.svelte +18 -18
- package/dist/SvPasswordInput.svelte +3 -2
- package/dist/SvPasswordInput.svelte.d.ts +1 -1
- package/dist/SvPhoneInput.svelte +3 -2
- package/dist/SvPhoneInput.svelte.d.ts +1 -1
- package/dist/SvPopover.svelte +1 -1
- package/dist/SvStepper.svelte +6 -9
- package/dist/SvTagsInput.svelte +3 -2
- package/dist/SvTagsInput.svelte.d.ts +1 -1
- package/dist/SvTooltip.svelte +12 -23
- package/dist/SvTour.svelte +32 -4
- package/dist/SvTreeSelect.svelte +1 -0
- package/dist/build-api.js +76 -54
- package/dist/cdn/svgrid.js +10907 -6444
- package/dist/cdn/svgrid.svelte-external.js +9622 -5475
- package/dist/columns.js +6 -0
- package/dist/conditional-formatting.d.ts +67 -3
- package/dist/conditional-formatting.js +124 -17
- package/dist/createCarousel.svelte.d.ts +72 -0
- package/dist/createCarousel.svelte.js +80 -0
- package/dist/createCommand.svelte.d.ts +62 -0
- package/dist/createCommand.svelte.js +109 -0
- package/dist/createForm.svelte.d.ts +62 -0
- package/dist/createForm.svelte.js +374 -0
- package/dist/createMenu.svelte.d.ts +88 -0
- package/dist/createMenu.svelte.js +153 -0
- package/dist/createOverlay.svelte.d.ts +38 -0
- package/dist/createOverlay.svelte.js +68 -0
- package/dist/createPagination.d.ts +74 -0
- package/dist/createPagination.js +71 -0
- package/dist/createPopoverSelect.svelte.js +6 -12
- package/dist/createStepper.d.ts +44 -0
- package/dist/createStepper.js +26 -0
- package/dist/createTooltip.svelte.d.ts +32 -0
- package/dist/createTooltip.svelte.js +61 -0
- package/dist/datetime/timezone.d.ts +36 -0
- package/dist/datetime/timezone.js +110 -0
- package/dist/dock-context.d.ts +52 -0
- package/dist/dock-context.js +1 -0
- package/dist/dock-manager-model.d.ts +136 -0
- package/dist/dock-manager-model.js +514 -0
- package/dist/dock-model.d.ts +105 -0
- package/dist/dock-model.js +288 -0
- package/dist/dock-popout.d.ts +30 -0
- package/dist/dock-popout.js +81 -0
- package/dist/editor-contract.d.ts +2 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +35 -2
- package/dist/filtering/excel-filters.d.ts +18 -1
- package/dist/filtering/excel-filters.js +56 -0
- package/dist/form-field.d.ts +73 -0
- package/dist/form-field.js +15 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.js +40 -4
- package/dist/list-nav.d.ts +18 -0
- package/dist/list-nav.js +30 -0
- package/dist/list-option.d.ts +2 -0
- package/dist/menu-item.d.ts +20 -0
- package/dist/menu-item.js +1 -0
- package/dist/menus.d.ts +5 -0
- package/dist/menus.js +68 -16
- package/dist/panel-resize.d.ts +25 -0
- package/dist/panel-resize.js +40 -0
- package/dist/popover.d.ts +29 -3
- package/dist/popover.js +22 -6
- package/dist/recurrence.d.ts +33 -7
- package/dist/recurrence.js +151 -13
- package/dist/row-drag.d.ts +8 -0
- package/dist/row-drag.js +11 -0
- package/dist/scheduler-ical.d.ts +30 -0
- package/dist/scheduler-ical.js +180 -0
- package/dist/scheduler-model.d.ts +269 -0
- package/dist/scheduler-model.js +602 -0
- package/dist/scheduler-view.svelte.d.ts +22 -0
- package/dist/scheduler-view.svelte.js +13 -0
- package/dist/spreadsheet.js +20 -12
- package/dist/ui-app.types.d.ts +3 -0
- package/package.json +5 -1
- package/src/DockNodeView.svelte +306 -0
- package/src/DockPopoutHost.svelte +17 -0
- package/src/GridMenus.svelte +83 -3
- package/src/SvAutoComplete.svelte +5 -3
- package/src/SvCarousel.svelte +20 -42
- package/src/SvCarousel.test.ts +68 -0
- package/src/SvColorInput.svelte +4 -3
- package/src/SvComboBox.svelte +7 -3
- package/src/SvCommand.svelte +28 -85
- package/src/SvCommand.test.ts +78 -0
- package/src/SvCountryInput.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDateTimePicker.svelte +9 -3
- package/src/SvDockLayout.svelte +244 -0
- package/src/SvDockManager.svelte +922 -0
- package/src/SvDrawer.svelte +69 -47
- package/src/SvDropDownList.svelte +85 -13
- package/src/SvField.svelte +21 -4
- package/src/SvForm.svelte +201 -91
- package/src/SvForm.test.ts +299 -0
- package/src/SvGrid.controller.svelte.ts +152 -28
- package/src/SvGrid.css +193 -5
- package/src/SvGrid.svelte +357 -12
- package/src/SvGrid.types.ts +463 -1
- package/src/SvGridDropdown.svelte +8 -1
- package/src/SvGridSelect.svelte +1 -0
- package/src/SvListBox.svelte +36 -14
- package/src/SvMaskedInput.svelte +4 -3
- package/src/SvMenu.svelte +1 -1
- package/src/SvMenu.test.ts +24 -0
- package/src/SvMenuList.svelte +23 -71
- package/src/SvModal.svelte +12 -29
- package/src/SvModal.test.ts +82 -0
- package/src/SvMultiSelect.svelte +1 -0
- package/src/SvNumberInput.svelte +4 -3
- package/src/SvPagination.svelte +18 -18
- package/src/SvPasswordInput.svelte +3 -2
- package/src/SvPhoneInput.svelte +3 -2
- package/src/SvPopover.svelte +1 -1
- package/src/SvStepper.svelte +6 -9
- package/src/SvTagsInput.svelte +3 -2
- package/src/SvTooltip.svelte +12 -23
- package/src/SvTooltip.test.ts +73 -0
- package/src/SvTour.svelte +32 -4
- package/src/SvTreeSelect.svelte +1 -0
- package/src/build-api.ts +74 -51
- package/src/columns.ts +5 -0
- package/src/conditional-formatting.test.ts +104 -0
- package/src/conditional-formatting.ts +176 -19
- package/src/createCarousel.svelte.ts +104 -0
- package/src/createCommand.svelte.ts +125 -0
- package/src/createForm.svelte.ts +333 -0
- package/src/createMenu.svelte.ts +202 -0
- package/src/createOverlay.svelte.ts +101 -0
- package/src/createPagination.test.ts +65 -0
- package/src/createPagination.ts +84 -0
- package/src/createPopoverSelect.svelte.ts +5 -9
- package/src/createStepper.test.ts +62 -0
- package/src/createStepper.ts +61 -0
- package/src/createTooltip.svelte.ts +86 -0
- package/src/datetime/timezone.test.ts +69 -0
- package/src/datetime/timezone.ts +134 -0
- package/src/dock-context.ts +47 -0
- package/src/dock-manager-model.test.ts +275 -0
- package/src/dock-manager-model.ts +593 -0
- package/src/dock-model.test.ts +163 -0
- package/src/dock-model.ts +368 -0
- package/src/dock-popout.ts +94 -0
- package/src/editor-contract.ts +2 -0
- package/src/filter-operators.test.ts +59 -0
- package/src/filter-operators.ts +35 -2
- package/src/filtering/excel-filters.test.ts +86 -1
- package/src/filtering/excel-filters.ts +65 -0
- package/src/form-field.ts +87 -0
- package/src/index.ts +158 -4
- package/src/list-nav.test.ts +49 -0
- package/src/list-nav.ts +29 -0
- package/src/list-option.ts +2 -0
- package/src/menu-item.ts +21 -0
- package/src/menus.test.ts +48 -0
- package/src/menus.ts +73 -16
- package/src/panel-resize.ts +52 -0
- package/src/popover.bounds.test.ts +93 -0
- package/src/popover.ts +51 -7
- package/src/recurrence.test.ts +88 -1
- package/src/recurrence.ts +166 -15
- package/src/row-drag.ts +10 -0
- package/src/scheduler-ical.test.ts +68 -0
- package/src/scheduler-ical.ts +180 -0
- package/src/scheduler-model.test.ts +546 -0
- package/src/scheduler-model.ts +853 -0
- package/src/scheduler-view.svelte.ts +33 -0
- package/src/spreadsheet.ts +20 -12
- package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
- package/src/ui-app.types.ts +3 -0
- package/src/virtual.test.ts +6 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheduler-view registry - the injection seam for the calendar / scheduler
|
|
3
|
+
* *view of the grid*. Setting the `scheduler` prop on `<SvGrid>` switches the
|
|
4
|
+
* table for a calendar, but the renderer itself ships in `@svgrid/enterprise`
|
|
5
|
+
* (a paid feature). This tiny reactive holder lets enterprise register that
|
|
6
|
+
* renderer at import time; the grid looks it up and mounts it, or shows an
|
|
7
|
+
* upsell placeholder when it is absent. Mirrors the `editor-registry` pattern.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // in @svgrid/enterprise, at module load:
|
|
11
|
+
* import { registerSchedulerView } from '@svgrid/grid'
|
|
12
|
+
* import SvGridScheduler from './SvGridScheduler.svelte'
|
|
13
|
+
* registerSchedulerView(SvGridScheduler)
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { Component } from 'svelte'
|
|
17
|
+
|
|
18
|
+
let renderer = $state<Component<any> | null>(null)
|
|
19
|
+
|
|
20
|
+
/** Register the component that renders `scheduler` mode. Enterprise calls this. */
|
|
21
|
+
export function registerSchedulerView(component: Component<any>): void {
|
|
22
|
+
renderer = component
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The registered scheduler renderer, or null when enterprise is not installed. */
|
|
26
|
+
export function getSchedulerView(): Component<any> | null {
|
|
27
|
+
return renderer
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whether a scheduler renderer has been registered. */
|
|
31
|
+
export function hasSchedulerView(): boolean {
|
|
32
|
+
return renderer != null
|
|
33
|
+
}
|
package/src/spreadsheet.ts
CHANGED
|
@@ -265,6 +265,18 @@ function apply(root: HTMLElement, opts: SpreadsheetActionOptions): void {
|
|
|
265
265
|
export function spreadsheetLayout(node: HTMLElement, opts: SpreadsheetActionOptions) {
|
|
266
266
|
let current = opts
|
|
267
267
|
let frame = 0
|
|
268
|
+
const OBSERVE_OPTIONS: MutationObserverInit = {
|
|
269
|
+
childList: true,
|
|
270
|
+
subtree: true,
|
|
271
|
+
attributes: true,
|
|
272
|
+
attributeFilter: [
|
|
273
|
+
'data-svgrid-row', 'data-col-id', 'style',
|
|
274
|
+
// Watch selection-range attrs so we can transfer them from
|
|
275
|
+
// hidden covered cells to merge origins.
|
|
276
|
+
'data-range-top', 'data-range-bottom', 'data-range-left',
|
|
277
|
+
'data-range-right', 'data-selected-range',
|
|
278
|
+
],
|
|
279
|
+
}
|
|
268
280
|
// The MutationObserver fires for EVERY DOM change inside the grid -
|
|
269
281
|
// batch them into one rAF so a big virtualization scroll doesn't run
|
|
270
282
|
// apply() dozens of times in a tick.
|
|
@@ -272,22 +284,18 @@ export function spreadsheetLayout(node: HTMLElement, opts: SpreadsheetActionOpti
|
|
|
272
284
|
if (frame) return
|
|
273
285
|
frame = requestAnimationFrame(() => {
|
|
274
286
|
frame = 0
|
|
287
|
+
// Stop observing while apply() mutates the DOM itself (it writes cell
|
|
288
|
+
// `style.display`/`position` and appends overlay children). Otherwise
|
|
289
|
+
// those writes re-trigger this observer and loop at 60fps on merged
|
|
290
|
+
// cells (#50). A full apply() recomputes from the live DOM, so discarding
|
|
291
|
+
// the queued records here loses nothing.
|
|
292
|
+
observer.disconnect()
|
|
275
293
|
apply(node, current)
|
|
294
|
+
observer.observe(node, OBSERVE_OPTIONS)
|
|
276
295
|
})
|
|
277
296
|
}
|
|
278
297
|
const observer = new MutationObserver(schedule)
|
|
279
|
-
observer.observe(node,
|
|
280
|
-
childList: true,
|
|
281
|
-
subtree: true,
|
|
282
|
-
attributes: true,
|
|
283
|
-
attributeFilter: [
|
|
284
|
-
'data-svgrid-row', 'data-col-id', 'style',
|
|
285
|
-
// Watch selection-range attrs so we can transfer them from
|
|
286
|
-
// hidden covered cells to merge origins.
|
|
287
|
-
'data-range-top', 'data-range-bottom', 'data-range-left',
|
|
288
|
-
'data-range-right', 'data-selected-range',
|
|
289
|
-
],
|
|
290
|
-
})
|
|
298
|
+
observer.observe(node, OBSERVE_OPTIONS)
|
|
291
299
|
// First-paint pass: schedule the same way so we don't run before the
|
|
292
300
|
// grid's initial render landed.
|
|
293
301
|
schedule()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: driving the imperative filter API from a reactive `$effect`.
|
|
3
|
+
*
|
|
4
|
+
* The filter mutators (`setFilter` / `clearFilter` / `setFacetFilter` /
|
|
5
|
+
* `clearAllFilters`) do a read-modify-write on the grid's filter state
|
|
6
|
+
* (e.g. `{ ...filterMenuValues, [id]: ... }`). If those internal reads are
|
|
7
|
+
* captured as dependencies of a caller's `$effect`, the subsequent write
|
|
8
|
+
* re-dirties the effect and it loops forever (Svelte throws
|
|
9
|
+
* `effect_update_depth_exceeded`). This bit demo 141, whose "top performers
|
|
10
|
+
* only" toggle drove `api.setFilter` from an `$effect` and hung the grid for
|
|
11
|
+
* ~7s before crashing.
|
|
12
|
+
*
|
|
13
|
+
* The fix wraps those mutators in `untrack`, so this pattern must now settle
|
|
14
|
+
* to a stable filtered row set in a bounded number of flushes.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from 'vitest'
|
|
18
|
+
import { mount, unmount, flushSync } from 'svelte'
|
|
19
|
+
import SvGrid from './SvGrid.svelte'
|
|
20
|
+
import {
|
|
21
|
+
columnFilteringFeature,
|
|
22
|
+
createCoreRowModel,
|
|
23
|
+
createFilteredRowModel,
|
|
24
|
+
createSortedRowModel,
|
|
25
|
+
rowSortingFeature,
|
|
26
|
+
sortFns,
|
|
27
|
+
tableFeatures,
|
|
28
|
+
} from './index'
|
|
29
|
+
import type { ColumnDef, SvGridApi } from './index'
|
|
30
|
+
|
|
31
|
+
type Row = { id: number; name: string; team: string; salary: number }
|
|
32
|
+
|
|
33
|
+
const features = tableFeatures({ columnFilteringFeature, rowSortingFeature })
|
|
34
|
+
|
|
35
|
+
const sampleRows: Row[] = [
|
|
36
|
+
{ id: 1, name: 'Ada Lovelace', team: 'Research', salary: 142_000 },
|
|
37
|
+
{ id: 2, name: 'Grace Hopper', team: 'Compilers', salary: 158_000 },
|
|
38
|
+
{ id: 3, name: 'Alan Turing', team: 'Research', salary: 138_000 },
|
|
39
|
+
{ id: 4, name: 'Margaret Hamilton', team: 'Apollo', salary: 165_000 },
|
|
40
|
+
{ id: 5, name: 'Linus Torvalds', team: 'Kernel', salary: 175_000 },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
const sampleColumns: ColumnDef<typeof features, Row>[] = [
|
|
44
|
+
{ field: 'name', header: 'Name', width: 200 },
|
|
45
|
+
{ field: 'team', header: 'Team', width: 160 },
|
|
46
|
+
{ field: 'salary', header: 'Salary', width: 140 },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
function mountGrid(): { api: SvGridApi<typeof features, Row>; destroy: () => void } {
|
|
50
|
+
const target = document.createElement('div')
|
|
51
|
+
document.body.appendChild(target)
|
|
52
|
+
let capturedApi: SvGridApi<typeof features, Row> | null = null
|
|
53
|
+
const app = mount(SvGrid, {
|
|
54
|
+
target,
|
|
55
|
+
props: {
|
|
56
|
+
data: sampleRows,
|
|
57
|
+
columns: sampleColumns,
|
|
58
|
+
features,
|
|
59
|
+
_rowModels: {
|
|
60
|
+
coreRowModel: createCoreRowModel(),
|
|
61
|
+
filteredRowModel: createFilteredRowModel(),
|
|
62
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
63
|
+
},
|
|
64
|
+
rowHeight: 36,
|
|
65
|
+
containerHeight: 480,
|
|
66
|
+
virtualization: false,
|
|
67
|
+
onApiReady(api: SvGridApi<typeof features, Row>) {
|
|
68
|
+
capturedApi = api
|
|
69
|
+
},
|
|
70
|
+
} as any,
|
|
71
|
+
})
|
|
72
|
+
flushSync()
|
|
73
|
+
if (!capturedApi) throw new Error('onApiReady never fired')
|
|
74
|
+
return { api: capturedApi, destroy: () => { unmount(app); target.remove() } }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe('imperative filter API driven from a reactive $effect', () => {
|
|
78
|
+
it('setFilter/clearFilter inside an $effect settle without an update loop', () => {
|
|
79
|
+
const { api, destroy } = mountGrid()
|
|
80
|
+
// Independent root so we own the effect lifecycle and can flush it.
|
|
81
|
+
let topOnly = $state(false)
|
|
82
|
+
let runs = 0
|
|
83
|
+
const stop = $effect.root(() => {
|
|
84
|
+
$effect(() => {
|
|
85
|
+
runs += 1
|
|
86
|
+
// Reads happen inside the mutators; the guard against looping is that
|
|
87
|
+
// those reads are untracked, so this effect must NOT re-run per write.
|
|
88
|
+
if (topOnly) api.setFilter('salary', { operator: 'greaterThan', value: '150000' })
|
|
89
|
+
else api.clearFilter('salary')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
try {
|
|
93
|
+
flushSync()
|
|
94
|
+
// Turning the filter on must narrow the set and NOT blow the update depth.
|
|
95
|
+
topOnly = true
|
|
96
|
+
// If the mutator's reads leaked, this flush would throw
|
|
97
|
+
// effect_update_depth_exceeded.
|
|
98
|
+
expect(() => flushSync()).not.toThrow()
|
|
99
|
+
|
|
100
|
+
const displayed = api.getDisplayedRows()
|
|
101
|
+
expect(displayed.map((r) => (r as Row).id).sort()).toEqual([2, 4, 5])
|
|
102
|
+
|
|
103
|
+
// The effect ran a bounded number of times: once on mount + once for the
|
|
104
|
+
// toggle. A leaked dependency would push this into the hundreds.
|
|
105
|
+
expect(runs).toBeLessThanOrEqual(3)
|
|
106
|
+
|
|
107
|
+
// Toggling back off clears cleanly too.
|
|
108
|
+
topOnly = false
|
|
109
|
+
expect(() => flushSync()).not.toThrow()
|
|
110
|
+
expect(api.getDisplayedRows().length).toBe(sampleRows.length)
|
|
111
|
+
} finally {
|
|
112
|
+
stop()
|
|
113
|
+
destroy()
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|
package/src/ui-app.types.ts
CHANGED
|
@@ -72,6 +72,9 @@ export type TourStep = {
|
|
|
72
72
|
target?: string | HTMLElement
|
|
73
73
|
title?: string
|
|
74
74
|
content: string
|
|
75
|
+
/** Optional illustration shown as a banner above the title. A URL / data-URI
|
|
76
|
+
* (rendered as an `<img>`) or inline SVG/HTML markup (author-controlled). */
|
|
77
|
+
image?: string
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/** One entry in a timeline. */
|
package/src/virtual.test.ts
CHANGED
|
@@ -48,10 +48,12 @@ describe('SvListBox virtual', () => {
|
|
|
48
48
|
const rendered = target.querySelectorAll('.sv-listbox__opt').length
|
|
49
49
|
expect(rendered).toBeGreaterThan(0)
|
|
50
50
|
expect(rendered).toBeLessThan(60) // nowhere near 5000
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
expect(
|
|
51
|
+
// Top + bottom spacers + the windowed rows reserve the full scroll height,
|
|
52
|
+
// keeping the scrollbar honest without positioning rows out of flow.
|
|
53
|
+
const spacers = target.querySelectorAll<HTMLElement>('.sv-listbox__spacer')
|
|
54
|
+
expect(spacers.length).toBe(2)
|
|
55
|
+
const spacerPx = [...spacers].reduce((sum, s) => sum + parseFloat(s.style.height || '0'), 0)
|
|
56
|
+
expect(spacerPx + rendered * 32).toBe(5000 * 32)
|
|
55
57
|
} finally { unmount(app); target.remove() }
|
|
56
58
|
})
|
|
57
59
|
|