@svgrid/grid 1.0.0
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/LICENSE +33 -0
- package/README.md +39 -0
- package/dist/FlexRender.svelte +96 -0
- package/dist/FlexRender.svelte.d.ts +49 -0
- package/dist/SvGrid.svelte +8742 -0
- package/dist/SvGrid.svelte.d.ts +381 -0
- package/dist/SvGridChart.svelte +653 -0
- package/dist/SvGridChart.svelte.d.ts +25 -0
- package/dist/SvGridChart.test.d.ts +1 -0
- package/dist/SvGridChart.test.js +62 -0
- package/dist/SvGridDropdown.svelte +675 -0
- package/dist/SvGridDropdown.svelte.d.ts +40 -0
- package/dist/a11y.contract.test.d.ts +1 -0
- package/dist/a11y.contract.test.js +39 -0
- package/dist/a11y.d.ts +38 -0
- package/dist/a11y.js +37 -0
- package/dist/a11y.test.d.ts +1 -0
- package/dist/a11y.test.js +44 -0
- package/dist/cell-formatting.d.ts +17 -0
- package/dist/cell-formatting.js +141 -0
- package/dist/cell-formatting.test.d.ts +1 -0
- package/dist/cell-formatting.test.js +234 -0
- package/dist/chart-export.d.ts +14 -0
- package/dist/chart-export.js +112 -0
- package/dist/chart.d.ts +226 -0
- package/dist/chart.js +732 -0
- package/dist/chart.test.d.ts +1 -0
- package/dist/chart.test.js +289 -0
- package/dist/collaboration.d.ts +74 -0
- package/dist/collaboration.js +98 -0
- package/dist/collaboration.test.d.ts +1 -0
- package/dist/collaboration.test.js +65 -0
- package/dist/conditional-formatting.d.ts +91 -0
- package/dist/conditional-formatting.js +170 -0
- package/dist/conditional-formatting.test.d.ts +1 -0
- package/dist/conditional-formatting.test.js +87 -0
- package/dist/core.coverage.test.d.ts +1 -0
- package/dist/core.coverage.test.js +186 -0
- package/dist/core.d.ts +370 -0
- package/dist/core.js +568 -0
- package/dist/core.performance.test.d.ts +1 -0
- package/dist/core.performance.test.js +29 -0
- package/dist/createGrid.svelte.d.ts +6 -0
- package/dist/createGrid.svelte.js +17 -0
- package/dist/createGrid.test.d.ts +1 -0
- package/dist/createGrid.test.js +9 -0
- package/dist/createGridState.svelte.d.ts +3 -0
- package/dist/createGridState.svelte.js +13 -0
- package/dist/editors/cell-editors.d.ts +23 -0
- package/dist/editors/cell-editors.js +97 -0
- package/dist/editors/cell-editors.test.d.ts +1 -0
- package/dist/editors/cell-editors.test.js +75 -0
- package/dist/fill-patterns.d.ts +30 -0
- package/dist/fill-patterns.js +207 -0
- package/dist/fill-patterns.test.d.ts +1 -0
- package/dist/fill-patterns.test.js +81 -0
- package/dist/filtering/excel-filters.d.ts +26 -0
- package/dist/filtering/excel-filters.js +64 -0
- package/dist/filtering/excel-filters.test.d.ts +1 -0
- package/dist/filtering/excel-filters.test.js +116 -0
- package/dist/filtering/locale-filter.test.d.ts +13 -0
- package/dist/filtering/locale-filter.test.js +189 -0
- package/dist/flex-render.d.ts +1 -0
- package/dist/flex-render.js +2 -0
- package/dist/flex-render.test.d.ts +1 -0
- package/dist/flex-render.test.js +142 -0
- package/dist/group-aggregate.test.d.ts +1 -0
- package/dist/group-aggregate.test.js +32 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +27 -0
- package/dist/keyboard.d.ts +8 -0
- package/dist/keyboard.js +91 -0
- package/dist/keyboard.test.d.ts +1 -0
- package/dist/keyboard.test.js +27 -0
- package/dist/merge-objects.d.ts +9 -0
- package/dist/merge-objects.js +28 -0
- package/dist/named-views.d.ts +42 -0
- package/dist/named-views.js +80 -0
- package/dist/named-views.test.d.ts +1 -0
- package/dist/named-views.test.js +57 -0
- package/dist/new-features.test.d.ts +1 -0
- package/dist/new-features.test.js +183 -0
- package/dist/render-component.d.ts +13 -0
- package/dist/render-component.js +14 -0
- package/dist/render-component.test.d.ts +1 -0
- package/dist/render-component.test.js +41 -0
- package/dist/server-data-source.d.ts +76 -0
- package/dist/server-data-source.js +83 -0
- package/dist/server-data-source.test.d.ts +1 -0
- package/dist/server-data-source.test.js +81 -0
- package/dist/sparkline.d.ts +56 -0
- package/dist/sparkline.js +97 -0
- package/dist/sparkline.test.d.ts +1 -0
- package/dist/sparkline.test.js +52 -0
- package/dist/static-functions.d.ts +1 -0
- package/dist/static-functions.js +1 -0
- package/dist/subscribe.d.ts +9 -0
- package/dist/subscribe.js +31 -0
- package/dist/subscribe.test.d.ts +1 -0
- package/dist/subscribe.test.js +93 -0
- package/dist/sv-grid-scrollbar.d.ts +1 -0
- package/dist/sv-grid-scrollbar.js +310 -0
- package/dist/svgrid-wrapper.types.d.ts +341 -0
- package/dist/svgrid-wrapper.types.js +1 -0
- package/dist/svgrid.api-extensions.test.d.ts +14 -0
- package/dist/svgrid.api-extensions.test.js +334 -0
- package/dist/svgrid.api.test.d.ts +15 -0
- package/dist/svgrid.api.test.js +169 -0
- package/dist/svgrid.behavior.test.d.ts +15 -0
- package/dist/svgrid.behavior.test.js +605 -0
- package/dist/svgrid.column-reorder.test.d.ts +15 -0
- package/dist/svgrid.column-reorder.test.js +224 -0
- package/dist/svgrid.features.test.d.ts +1 -0
- package/dist/svgrid.features.test.js +129 -0
- package/dist/svgrid.interaction.test.d.ts +9 -0
- package/dist/svgrid.interaction.test.js +318 -0
- package/dist/svgrid.locale-filtering.test.d.ts +15 -0
- package/dist/svgrid.locale-filtering.test.js +261 -0
- package/dist/svgrid.new-features.wrapper.test.d.ts +1 -0
- package/dist/svgrid.new-features.wrapper.test.js +136 -0
- package/dist/svgrid.row-pinning.test.d.ts +14 -0
- package/dist/svgrid.row-pinning.test.js +250 -0
- package/dist/svgrid.set-filter.test.d.ts +17 -0
- package/dist/svgrid.set-filter.test.js +268 -0
- package/dist/svgrid.wrapper.test.d.ts +1 -0
- package/dist/svgrid.wrapper.test.js +33 -0
- package/dist/test-setup.d.ts +1 -0
- package/dist/test-setup.js +29 -0
- package/dist/transaction.test.d.ts +1 -0
- package/dist/transaction.test.js +89 -0
- package/dist/virtualization/column-virtualizer.d.ts +20 -0
- package/dist/virtualization/column-virtualizer.js +19 -0
- package/dist/virtualization/column-virtualizer.test.d.ts +1 -0
- package/dist/virtualization/column-virtualizer.test.js +25 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +11 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +20 -0
- package/dist/virtualization/types.d.ts +28 -0
- package/dist/virtualization/types.js +1 -0
- package/dist/virtualization/virtualizer.d.ts +13 -0
- package/dist/virtualization/virtualizer.js +232 -0
- package/dist/virtualization/virtualizer.test.d.ts +1 -0
- package/dist/virtualization/virtualizer.test.js +41 -0
- package/package.json +69 -0
- package/src/FlexRender.svelte +96 -0
- package/src/SvGrid.svelte +8742 -0
- package/src/SvGridChart.svelte +653 -0
- package/src/SvGridChart.test.ts +69 -0
- package/src/SvGridDropdown.svelte +675 -0
- package/src/a11y.contract.test.ts +49 -0
- package/src/a11y.test.ts +59 -0
- package/src/a11y.ts +59 -0
- package/src/cell-formatting.test.ts +273 -0
- package/src/cell-formatting.ts +169 -0
- package/src/chart-export.ts +144 -0
- package/src/chart.test.ts +311 -0
- package/src/chart.ts +934 -0
- package/src/collaboration.test.ts +74 -0
- package/src/collaboration.ts +161 -0
- package/src/conditional-formatting.test.ts +107 -0
- package/src/conditional-formatting.ts +239 -0
- package/src/core.coverage.test.ts +230 -0
- package/src/core.performance.test.ts +30 -0
- package/src/core.ts +991 -0
- package/src/createGrid.svelte.ts +42 -0
- package/src/createGrid.test.ts +10 -0
- package/src/createGridState.svelte.ts +17 -0
- package/src/editors/cell-editors.test.ts +80 -0
- package/src/editors/cell-editors.ts +127 -0
- package/src/fill-patterns.test.ts +93 -0
- package/src/fill-patterns.ts +229 -0
- package/src/filtering/excel-filters.test.ts +131 -0
- package/src/filtering/excel-filters.ts +101 -0
- package/src/filtering/locale-filter.test.ts +220 -0
- package/src/flex-render.test.ts +143 -0
- package/src/flex-render.ts +3 -0
- package/src/group-aggregate.test.ts +48 -0
- package/src/index.ts +159 -0
- package/src/keyboard.test.ts +59 -0
- package/src/keyboard.ts +97 -0
- package/src/merge-objects.ts +48 -0
- package/src/named-views.test.ts +66 -0
- package/src/named-views.ts +120 -0
- package/src/new-features.test.ts +217 -0
- package/src/render-component.test.ts +51 -0
- package/src/render-component.ts +28 -0
- package/src/server-data-source.test.ts +88 -0
- package/src/server-data-source.ts +163 -0
- package/src/sparkline.test.ts +59 -0
- package/src/sparkline.ts +164 -0
- package/src/static-functions.ts +11 -0
- package/src/subscribe.test.ts +103 -0
- package/src/subscribe.ts +38 -0
- package/src/sv-grid-scrollbar.ts +347 -0
- package/src/svgrid-wrapper.types.ts +382 -0
- package/src/svgrid.api-extensions.test.ts +362 -0
- package/src/svgrid.api.test.ts +192 -0
- package/src/svgrid.behavior.test.ts +657 -0
- package/src/svgrid.column-reorder.test.ts +234 -0
- package/src/svgrid.features.test.ts +157 -0
- package/src/svgrid.interaction.test.ts +355 -0
- package/src/svgrid.locale-filtering.test.ts +259 -0
- package/src/svgrid.new-features.wrapper.test.ts +164 -0
- package/src/svgrid.row-pinning.test.ts +266 -0
- package/src/svgrid.set-filter.test.ts +270 -0
- package/src/svgrid.wrapper.test.ts +35 -0
- package/src/test-setup.ts +37 -0
- package/src/transaction.test.ts +100 -0
- package/src/virtualization/column-virtualizer.test.ts +27 -0
- package/src/virtualization/column-virtualizer.ts +30 -0
- package/src/virtualization/svelte-virtualizer.svelte.ts +24 -0
- package/src/virtualization/types.ts +30 -0
- package/src/virtualization/virtualizer.test.ts +47 -0
- package/src/virtualization/virtualizer.ts +270 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
function clamp(value, min, max) {
|
|
2
|
+
return Math.min(Math.max(value, min), max);
|
|
3
|
+
}
|
|
4
|
+
function sameOptions(a, b) {
|
|
5
|
+
return (a.count === b.count &&
|
|
6
|
+
a.estimateSize === b.estimateSize &&
|
|
7
|
+
(a.overscan ?? 6) === (b.overscan ?? 6) &&
|
|
8
|
+
a.viewportHeight === b.viewportHeight &&
|
|
9
|
+
(a.scrollOffset ?? 0) === (b.scrollOffset ?? 0));
|
|
10
|
+
}
|
|
11
|
+
function sameState(a, b) {
|
|
12
|
+
if (a.totalSize !== b.totalSize ||
|
|
13
|
+
a.startIndex !== b.startIndex ||
|
|
14
|
+
a.endIndex !== b.endIndex ||
|
|
15
|
+
a.viewportHeight !== b.viewportHeight ||
|
|
16
|
+
a.items.length !== b.items.length) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Per-item start+size check: catches column-resize cases where the total
|
|
20
|
+
// is unchanged (e.g. column A grew, column B shrunk to compensate under
|
|
21
|
+
// fit-to-width). Without this, the virtualizer skips the emit and the
|
|
22
|
+
// grid renders with stale widths.
|
|
23
|
+
for (let i = 0; i < a.items.length; i += 1) {
|
|
24
|
+
const ai = a.items[i];
|
|
25
|
+
const bi = b.items[i];
|
|
26
|
+
if (ai.index !== bi.index || ai.start !== bi.start || ai.size !== bi.size)
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Slot-based keying: the key is the POSITION of the item inside the
|
|
32
|
+
// visible window (slot 0 = topmost rendered row, slot 1 = next, etc.)
|
|
33
|
+
// - NOT the data index. As the user scrolls, the window's startIndex
|
|
34
|
+
// changes but the slot keys stay the same (0..N-1), so Svelte recycles
|
|
35
|
+
// the existing <tr> DOM nodes and only updates their data + position.
|
|
36
|
+
//
|
|
37
|
+
// This avoids the mount/unmount churn that previously fired on every
|
|
38
|
+
// scroll tick: with data-index keys, scrolling down 1 row meant the old
|
|
39
|
+
// topmost key disappeared and a new bottommost key appeared, forcing
|
|
40
|
+
// Svelte to unmount the top <tr> and mount a fresh one at the bottom.
|
|
41
|
+
// Slot-based keys keep the same N <tr> nodes alive for the lifetime of
|
|
42
|
+
// the grid; scroll just translates them via the top spacer.
|
|
43
|
+
function buildUniformItems(startIndex, endIndex, estimateSize) {
|
|
44
|
+
const items = [];
|
|
45
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
46
|
+
const start = index * estimateSize;
|
|
47
|
+
items.push({
|
|
48
|
+
index,
|
|
49
|
+
start,
|
|
50
|
+
size: estimateSize,
|
|
51
|
+
end: start + estimateSize,
|
|
52
|
+
key: `virtual_slot_${index - startIndex}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return items;
|
|
56
|
+
}
|
|
57
|
+
function buildVariableItems(startIndex, endIndex, offsets) {
|
|
58
|
+
const items = [];
|
|
59
|
+
for (let index = startIndex; index <= endIndex; index += 1) {
|
|
60
|
+
const start = offsets[index] ?? 0;
|
|
61
|
+
const end = offsets[index + 1] ?? start;
|
|
62
|
+
items.push({
|
|
63
|
+
index,
|
|
64
|
+
start,
|
|
65
|
+
size: end - start,
|
|
66
|
+
end,
|
|
67
|
+
key: `virtual_slot_${index - startIndex}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return items;
|
|
71
|
+
}
|
|
72
|
+
function createState(options,
|
|
73
|
+
/** Pre-built cumulative offsets for the variable-size path. */
|
|
74
|
+
offsets) {
|
|
75
|
+
const count = Math.max(options.count, 0);
|
|
76
|
+
const overscan = Math.max(options.overscan ?? 6, 0);
|
|
77
|
+
const viewportHeight = Math.max(options.viewportHeight, 0);
|
|
78
|
+
if (typeof options.estimateSize === 'function' && offsets) {
|
|
79
|
+
const totalSize = offsets[count] ?? 0;
|
|
80
|
+
const maxOffset = Math.max(totalSize - viewportHeight, 0);
|
|
81
|
+
const scrollOffset = clamp(options.scrollOffset ?? 0, 0, maxOffset);
|
|
82
|
+
// First index whose end > scrollOffset.
|
|
83
|
+
let lo = 0;
|
|
84
|
+
let hi = count;
|
|
85
|
+
while (lo < hi) {
|
|
86
|
+
const mid = (lo + hi) >>> 1;
|
|
87
|
+
if ((offsets[mid + 1] ?? 0) <= scrollOffset)
|
|
88
|
+
lo = mid + 1;
|
|
89
|
+
else
|
|
90
|
+
hi = mid;
|
|
91
|
+
}
|
|
92
|
+
const visibleStart = lo;
|
|
93
|
+
// First index whose start >= scrollOffset + viewportHeight.
|
|
94
|
+
const viewportEnd = scrollOffset + viewportHeight;
|
|
95
|
+
lo = visibleStart;
|
|
96
|
+
hi = count;
|
|
97
|
+
while (lo < hi) {
|
|
98
|
+
const mid = (lo + hi) >>> 1;
|
|
99
|
+
if ((offsets[mid] ?? 0) < viewportEnd)
|
|
100
|
+
lo = mid + 1;
|
|
101
|
+
else
|
|
102
|
+
hi = mid;
|
|
103
|
+
}
|
|
104
|
+
const visibleEnd = Math.max(lo - 1, visibleStart);
|
|
105
|
+
const startIndex = count === 0 ? 0 : clamp(visibleStart - overscan, 0, count - 1);
|
|
106
|
+
const endIndex = count === 0 ? -1 : clamp(visibleEnd + overscan, 0, count - 1);
|
|
107
|
+
return {
|
|
108
|
+
items: endIndex >= startIndex ? buildVariableItems(startIndex, endIndex, offsets) : [],
|
|
109
|
+
totalSize,
|
|
110
|
+
startIndex,
|
|
111
|
+
endIndex,
|
|
112
|
+
scrollOffset,
|
|
113
|
+
viewportHeight,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Uniform-size fast path (original behavior).
|
|
117
|
+
const estimateSize = Math.max(typeof options.estimateSize === 'number' ? options.estimateSize : 1, 1);
|
|
118
|
+
const totalSize = count * estimateSize;
|
|
119
|
+
const maxOffset = Math.max(totalSize - viewportHeight, 0);
|
|
120
|
+
const scrollOffset = clamp(options.scrollOffset ?? 0, 0, maxOffset);
|
|
121
|
+
const visibleStart = Math.floor(scrollOffset / estimateSize);
|
|
122
|
+
const visibleCount = Math.ceil(viewportHeight / estimateSize);
|
|
123
|
+
const visibleEnd = Math.min(visibleStart + visibleCount, Math.max(count - 1, 0));
|
|
124
|
+
const startIndex = count === 0 ? 0 : clamp(visibleStart - overscan, 0, count - 1);
|
|
125
|
+
const endIndex = count === 0 ? -1 : clamp(visibleEnd + overscan, 0, count - 1);
|
|
126
|
+
return {
|
|
127
|
+
items: endIndex >= startIndex ? buildUniformItems(startIndex, endIndex, estimateSize) : [],
|
|
128
|
+
totalSize,
|
|
129
|
+
startIndex,
|
|
130
|
+
endIndex,
|
|
131
|
+
scrollOffset,
|
|
132
|
+
viewportHeight,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export function createVirtualizer(initial) {
|
|
136
|
+
let options = initial;
|
|
137
|
+
let offsetCache = null;
|
|
138
|
+
function getOffsets() {
|
|
139
|
+
if (typeof options.estimateSize !== 'function')
|
|
140
|
+
return null;
|
|
141
|
+
const count = Math.max(options.count, 0);
|
|
142
|
+
if (offsetCache &&
|
|
143
|
+
offsetCache.fn === options.estimateSize &&
|
|
144
|
+
offsetCache.count === count) {
|
|
145
|
+
return offsetCache.offsets;
|
|
146
|
+
}
|
|
147
|
+
const sizeFn = options.estimateSize;
|
|
148
|
+
const offsets = new Array(count + 1);
|
|
149
|
+
offsets[0] = 0;
|
|
150
|
+
for (let i = 0; i < count; i += 1) {
|
|
151
|
+
offsets[i + 1] = offsets[i] + Math.max(sizeFn(i), 1);
|
|
152
|
+
}
|
|
153
|
+
offsetCache = { fn: sizeFn, count, offsets };
|
|
154
|
+
return offsets;
|
|
155
|
+
}
|
|
156
|
+
let state = createState(options, getOffsets());
|
|
157
|
+
const listeners = new Set();
|
|
158
|
+
function emit() {
|
|
159
|
+
listeners.forEach((listener) => listener());
|
|
160
|
+
}
|
|
161
|
+
function recalc() {
|
|
162
|
+
const next = createState(options, getOffsets());
|
|
163
|
+
if (sameState(state, next))
|
|
164
|
+
return;
|
|
165
|
+
state = next;
|
|
166
|
+
emit();
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
setOptions(next) {
|
|
170
|
+
const merged = { ...options, ...next };
|
|
171
|
+
if (sameOptions(options, merged))
|
|
172
|
+
return;
|
|
173
|
+
options = merged;
|
|
174
|
+
recalc();
|
|
175
|
+
},
|
|
176
|
+
setScrollOffset(scrollOffset) {
|
|
177
|
+
if ((options.scrollOffset ?? 0) === scrollOffset)
|
|
178
|
+
return;
|
|
179
|
+
options = { ...options, scrollOffset };
|
|
180
|
+
recalc();
|
|
181
|
+
},
|
|
182
|
+
setViewportHeight(viewportHeight) {
|
|
183
|
+
if (options.viewportHeight === viewportHeight)
|
|
184
|
+
return;
|
|
185
|
+
options = { ...options, viewportHeight };
|
|
186
|
+
recalc();
|
|
187
|
+
},
|
|
188
|
+
scrollToIndex(index) {
|
|
189
|
+
const boundedIndex = clamp(index, 0, Math.max(options.count - 1, 0));
|
|
190
|
+
// Read the current state's totals so the offset is correct under both
|
|
191
|
+
// uniform and per-index sizing.
|
|
192
|
+
const totalSize = state.totalSize;
|
|
193
|
+
let targetOffset;
|
|
194
|
+
if (typeof options.estimateSize === 'function') {
|
|
195
|
+
// Use the cached cumulative offsets if present.
|
|
196
|
+
const offsets = getOffsets();
|
|
197
|
+
if (offsets) {
|
|
198
|
+
targetOffset = offsets[boundedIndex] ?? 0;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
let acc = 0;
|
|
202
|
+
const sizeFn = options.estimateSize;
|
|
203
|
+
for (let i = 0; i < boundedIndex; i += 1)
|
|
204
|
+
acc += Math.max(sizeFn(i), 1);
|
|
205
|
+
targetOffset = acc;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
targetOffset = boundedIndex * Math.max(options.estimateSize, 1);
|
|
210
|
+
}
|
|
211
|
+
const maxOffset = Math.max(totalSize - options.viewportHeight, 0);
|
|
212
|
+
const nextOffset = clamp(targetOffset, 0, maxOffset);
|
|
213
|
+
if ((options.scrollOffset ?? 0) === nextOffset)
|
|
214
|
+
return;
|
|
215
|
+
options = { ...options, scrollOffset: nextOffset };
|
|
216
|
+
recalc();
|
|
217
|
+
},
|
|
218
|
+
getVirtualItems() {
|
|
219
|
+
return state.items;
|
|
220
|
+
},
|
|
221
|
+
getTotalSize() {
|
|
222
|
+
return state.totalSize;
|
|
223
|
+
},
|
|
224
|
+
getState() {
|
|
225
|
+
return state;
|
|
226
|
+
},
|
|
227
|
+
subscribe(listener) {
|
|
228
|
+
listeners.add(listener);
|
|
229
|
+
return () => listeners.delete(listener);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createVirtualizer } from './virtualizer';
|
|
3
|
+
describe('virtualizer', () => {
|
|
4
|
+
it('computes bounded virtual range with overscan', () => {
|
|
5
|
+
const v = createVirtualizer({
|
|
6
|
+
count: 100000,
|
|
7
|
+
estimateSize: 36,
|
|
8
|
+
overscan: 8,
|
|
9
|
+
viewportHeight: 360,
|
|
10
|
+
scrollOffset: 0,
|
|
11
|
+
});
|
|
12
|
+
const items = v.getVirtualItems();
|
|
13
|
+
expect(items.length).toBeGreaterThan(0);
|
|
14
|
+
expect(items[0]?.index).toBe(0);
|
|
15
|
+
expect(items[items.length - 1]?.index).toBe(18);
|
|
16
|
+
});
|
|
17
|
+
it('updates range when scroll offset changes', () => {
|
|
18
|
+
const v = createVirtualizer({
|
|
19
|
+
count: 100000,
|
|
20
|
+
estimateSize: 40,
|
|
21
|
+
overscan: 5,
|
|
22
|
+
viewportHeight: 400,
|
|
23
|
+
scrollOffset: 0,
|
|
24
|
+
});
|
|
25
|
+
v.setScrollOffset(4000);
|
|
26
|
+
const state = v.getState();
|
|
27
|
+
expect(state.startIndex).toBeLessThanOrEqual(100);
|
|
28
|
+
expect(state.endIndex).toBeGreaterThanOrEqual(110);
|
|
29
|
+
});
|
|
30
|
+
it('scrollToIndex clamps target offset', () => {
|
|
31
|
+
const v = createVirtualizer({
|
|
32
|
+
count: 100,
|
|
33
|
+
estimateSize: 30,
|
|
34
|
+
overscan: 2,
|
|
35
|
+
viewportHeight: 300,
|
|
36
|
+
scrollOffset: 0,
|
|
37
|
+
});
|
|
38
|
+
v.scrollToIndex(9999);
|
|
39
|
+
expect(v.getState().scrollOffset).toBe(2700);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svgrid/grid",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SvGrid - a headless-first, Svelte 5-native data grid engine and render component.",
|
|
5
|
+
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/sv-grid/sv-grid.git",
|
|
10
|
+
"directory": "packages/grid"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://svgrid.com",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"svelte",
|
|
15
|
+
"grid",
|
|
16
|
+
"table",
|
|
17
|
+
"headless",
|
|
18
|
+
"datagrid"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"module": "dist/index.js",
|
|
23
|
+
"svelte": "./dist/index.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"svelte": "./dist/index.js",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./static-functions": {
|
|
31
|
+
"types": "./dist/static-functions.d.ts",
|
|
32
|
+
"svelte": "./dist/static-functions.js",
|
|
33
|
+
"import": "./dist/static-functions.js"
|
|
34
|
+
},
|
|
35
|
+
"./flex-render": {
|
|
36
|
+
"types": "./dist/flex-render.d.ts",
|
|
37
|
+
"svelte": "./dist/flex-render.js",
|
|
38
|
+
"import": "./dist/flex-render.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=16"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"src"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"clean": "rimraf ./build && rimraf ./dist",
|
|
51
|
+
"test:eslint": "eslint ./src",
|
|
52
|
+
"test:types": "svelte-check --tsconfig ./tsconfig.json",
|
|
53
|
+
"test:lib": "vitest run --coverage",
|
|
54
|
+
"test:build": "publint --strict",
|
|
55
|
+
"build": "svelte-package --input ./src --output ./dist"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@sveltejs/package": "^2.5.7",
|
|
59
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
60
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
61
|
+
"eslint-plugin-svelte": "^3.17.1",
|
|
62
|
+
"jsdom": "^29.1.1",
|
|
63
|
+
"svelte": "^5.55.5",
|
|
64
|
+
"svelte-check": "^4.4.6"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"svelte": "^5.0.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="TData extends RowData"
|
|
4
|
+
>
|
|
5
|
+
import { isFunction } from './core'
|
|
6
|
+
import {
|
|
7
|
+
RenderComponentConfig,
|
|
8
|
+
RenderSnippetConfig,
|
|
9
|
+
} from './render-component'
|
|
10
|
+
import type {
|
|
11
|
+
Cell,
|
|
12
|
+
CellContext,
|
|
13
|
+
ColumnDefTemplate,
|
|
14
|
+
Header,
|
|
15
|
+
HeaderContext,
|
|
16
|
+
RowData,
|
|
17
|
+
} from './core'
|
|
18
|
+
|
|
19
|
+
type Props =
|
|
20
|
+
| {
|
|
21
|
+
content?: ColumnDefTemplate<
|
|
22
|
+
| HeaderContext<TData>
|
|
23
|
+
| CellContext<TData>
|
|
24
|
+
>
|
|
25
|
+
context:
|
|
26
|
+
| HeaderContext<TData>
|
|
27
|
+
| CellContext<TData>
|
|
28
|
+
cell?: never
|
|
29
|
+
header?: never
|
|
30
|
+
footer?: never
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
cell: Cell<TData>
|
|
34
|
+
content?: never
|
|
35
|
+
context?: never
|
|
36
|
+
header?: never
|
|
37
|
+
footer?: never
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
header: Header<TData>
|
|
41
|
+
content?: never
|
|
42
|
+
context?: never
|
|
43
|
+
cell?: never
|
|
44
|
+
footer?: never
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
footer: Header<TData>
|
|
48
|
+
content?: never
|
|
49
|
+
context?: never
|
|
50
|
+
cell?: never
|
|
51
|
+
header?: never
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let props: Props = $props()
|
|
55
|
+
|
|
56
|
+
const resolved = $derived.by(() => {
|
|
57
|
+
if ('cell' in props && props.cell) {
|
|
58
|
+
return {
|
|
59
|
+
content: props.cell.column.columnDef.cell,
|
|
60
|
+
context: props.cell.getContext(),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if ('header' in props && props.header) {
|
|
64
|
+
return {
|
|
65
|
+
content: props.header.column.columnDef.header,
|
|
66
|
+
context: props.header.getContext(),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if ('footer' in props && props.footer) {
|
|
70
|
+
return {
|
|
71
|
+
content: props.footer.column.columnDef.footer,
|
|
72
|
+
context: props.footer.getContext(),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
content: props.content,
|
|
77
|
+
context: props.context,
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const result = $derived(
|
|
82
|
+
isFunction(resolved.content)
|
|
83
|
+
? resolved.content(resolved.context as any)
|
|
84
|
+
: undefined,
|
|
85
|
+
)
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
{#if typeof resolved.content === 'string'}
|
|
89
|
+
{resolved.content}
|
|
90
|
+
{:else if result instanceof RenderComponentConfig}
|
|
91
|
+
<result.component {...result.props} />
|
|
92
|
+
{:else if result instanceof RenderSnippetConfig}
|
|
93
|
+
{@render result.snippet(result.params)}
|
|
94
|
+
{:else if result !== undefined}
|
|
95
|
+
{result}
|
|
96
|
+
{/if}
|