@enhanced-dom/virtualizer 0.1.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.md +21 -0
- package/README.md +1 -0
- package/index.d.ts +26 -0
- package/index.js +333 -0
- package/package.json +28 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present CD9B4A105290E17E0948E021DF4105107C88693C59C0B891CCC08366C51AEA990902A6A156AC87D88A2FC41422A5E1C3C4071F251F19441C4516000EC25F87DF
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Helpers for item list virtualization in 1d or 2d
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type IVirtualizerEntry = {
|
|
2
|
+
level?: number;
|
|
3
|
+
id: string | number;
|
|
4
|
+
maxSize?: number;
|
|
5
|
+
weight?: number;
|
|
6
|
+
} & ({
|
|
7
|
+
minSize: number;
|
|
8
|
+
fixedSize?: number;
|
|
9
|
+
} | {
|
|
10
|
+
minSize?: number;
|
|
11
|
+
fixedSize: number;
|
|
12
|
+
});
|
|
13
|
+
export interface IVirtualizerConfig {
|
|
14
|
+
viewportSize: number;
|
|
15
|
+
offset?: number;
|
|
16
|
+
overscan?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface IVirtualizerResult extends Pick<IVirtualizerEntry, 'id'> {
|
|
19
|
+
size: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
relativeOffset?: number;
|
|
22
|
+
minOffset?: number;
|
|
23
|
+
maxOffset?: number;
|
|
24
|
+
level?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const virtualize: (config: IVirtualizerConfig, entries: IVirtualizerEntry[]) => IVirtualizerResult[];
|
package/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
var TreePartType;
|
|
2
|
+
(function (TreePartType) {
|
|
3
|
+
TreePartType["NODE"] = "node";
|
|
4
|
+
TreePartType["FOREST"] = "forest";
|
|
5
|
+
})(TreePartType || (TreePartType = {}));
|
|
6
|
+
class StickyGroupNode {
|
|
7
|
+
static isInstance(treePart) {
|
|
8
|
+
return (treePart === null || treePart === void 0 ? void 0 : treePart.type) === TreePartType.NODE;
|
|
9
|
+
}
|
|
10
|
+
constructor(id, level) {
|
|
11
|
+
this.parent = null;
|
|
12
|
+
this.children = [];
|
|
13
|
+
this.type = TreePartType.NODE;
|
|
14
|
+
this._size = null;
|
|
15
|
+
this._depth = null;
|
|
16
|
+
this.id = id;
|
|
17
|
+
this.level = level;
|
|
18
|
+
}
|
|
19
|
+
addChild(child) {
|
|
20
|
+
child.parent = this;
|
|
21
|
+
this.children.push(child);
|
|
22
|
+
return child;
|
|
23
|
+
}
|
|
24
|
+
getAncestor(level) {
|
|
25
|
+
var _a;
|
|
26
|
+
if (this.level > level) {
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
return (_a = this.parent) === null || _a === void 0 ? void 0 : _a.getAncestor(level);
|
|
30
|
+
}
|
|
31
|
+
findNode(id) {
|
|
32
|
+
if (this.id == id) {
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
return this.children.map((c) => c.findNode(id)).find((n) => !!n);
|
|
36
|
+
}
|
|
37
|
+
findNextSibling() {
|
|
38
|
+
var _a;
|
|
39
|
+
const currentNodeIndex = this.parent.children.indexOf(this);
|
|
40
|
+
return (_a = this.parent.children[currentNodeIndex + 1]) !== null && _a !== void 0 ? _a : this.parent.findNextSibling();
|
|
41
|
+
}
|
|
42
|
+
findPreviousSibling() {
|
|
43
|
+
var _a;
|
|
44
|
+
const currentNodeIndex = this.parent.children.indexOf(this);
|
|
45
|
+
return (_a = this.parent.children[currentNodeIndex - 1]) !== null && _a !== void 0 ? _a : (StickyGroupNode.isInstance(this.parent) ? this.parent : null);
|
|
46
|
+
}
|
|
47
|
+
hasChildren() {
|
|
48
|
+
return !!this.children.length;
|
|
49
|
+
}
|
|
50
|
+
get size() {
|
|
51
|
+
if (this._size == null) {
|
|
52
|
+
this._size = this.hasChildren() ? Math.max(...this.children.map((c) => c.size)) + 1 : 0;
|
|
53
|
+
}
|
|
54
|
+
return this._size;
|
|
55
|
+
}
|
|
56
|
+
get depth() {
|
|
57
|
+
if (this._depth == null) {
|
|
58
|
+
this._depth = this.parent.depth + 1;
|
|
59
|
+
}
|
|
60
|
+
return this._depth;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class StickyGroupForest {
|
|
64
|
+
constructor() {
|
|
65
|
+
this.children = [];
|
|
66
|
+
this.type = TreePartType.FOREST;
|
|
67
|
+
this._size = null;
|
|
68
|
+
}
|
|
69
|
+
addChild(child) {
|
|
70
|
+
child.parent = this;
|
|
71
|
+
this.children.push(child);
|
|
72
|
+
}
|
|
73
|
+
hasChildren() {
|
|
74
|
+
return !!this.children.length;
|
|
75
|
+
}
|
|
76
|
+
getAncestor() {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
size(id) {
|
|
80
|
+
var _a;
|
|
81
|
+
if (id != null) {
|
|
82
|
+
return (_a = this.findNode(id)) === null || _a === void 0 ? void 0 : _a.size;
|
|
83
|
+
}
|
|
84
|
+
if (this._size == null) {
|
|
85
|
+
this._size = this.hasChildren() ? Math.max(...this.children.map((c) => c.size)) + 1 : 0;
|
|
86
|
+
}
|
|
87
|
+
return this._size;
|
|
88
|
+
}
|
|
89
|
+
get depth() {
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
findNode(id) {
|
|
93
|
+
return this.children.map((c) => c.findNode(id)).find((n) => !!n);
|
|
94
|
+
}
|
|
95
|
+
findAncestors(id) {
|
|
96
|
+
let node = this.findNode(id);
|
|
97
|
+
if (!node) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const ancestors = [];
|
|
101
|
+
while (node.parent !== this) {
|
|
102
|
+
node = node.parent;
|
|
103
|
+
ancestors.unshift(node);
|
|
104
|
+
}
|
|
105
|
+
return ancestors;
|
|
106
|
+
}
|
|
107
|
+
findNextSibling() {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
findPreviousSibling() {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export const virtualize = (config, entries) => {
|
|
115
|
+
var _a, _b, _c;
|
|
116
|
+
const { offset, overscan, viewportSize } = {
|
|
117
|
+
offset: 0,
|
|
118
|
+
overscan: 5,
|
|
119
|
+
...config,
|
|
120
|
+
};
|
|
121
|
+
// first, let us figure out if the size is smaller than the viewport
|
|
122
|
+
// while we iterate over the entries, we should also find out some 'statisticts' about weights
|
|
123
|
+
let lowerBoundSize = 0;
|
|
124
|
+
const entriesWithDynamicSpace = [];
|
|
125
|
+
const unboundedEntries = [];
|
|
126
|
+
const entriesOffsets = [];
|
|
127
|
+
const stickyGroups = new StickyGroupForest();
|
|
128
|
+
const entryById = {};
|
|
129
|
+
let currentStickyGroupNode = null;
|
|
130
|
+
let minimumExplicitWeight = Infinity;
|
|
131
|
+
let minimumDerivedWeight = Infinity;
|
|
132
|
+
for (const e of entries) {
|
|
133
|
+
const entryMinSize = (_a = e.fixedSize) !== null && _a !== void 0 ? _a : e.minSize;
|
|
134
|
+
entriesOffsets.push({ id: e.id, offset: lowerBoundSize, size: entryMinSize });
|
|
135
|
+
lowerBoundSize += entryMinSize;
|
|
136
|
+
entryById[e.id] = e;
|
|
137
|
+
if (e.level != null) {
|
|
138
|
+
currentStickyGroupNode =
|
|
139
|
+
((_b = currentStickyGroupNode === null || currentStickyGroupNode === void 0 ? void 0 : currentStickyGroupNode.level) !== null && _b !== void 0 ? _b : 0) > e.level ? currentStickyGroupNode : currentStickyGroupNode === null || currentStickyGroupNode === void 0 ? void 0 : currentStickyGroupNode.getAncestor(e.level);
|
|
140
|
+
if (!currentStickyGroupNode) {
|
|
141
|
+
currentStickyGroupNode = new StickyGroupNode(e.id, e.level);
|
|
142
|
+
stickyGroups.addChild(currentStickyGroupNode);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
currentStickyGroupNode = currentStickyGroupNode.addChild(new StickyGroupNode(e.id, e.level));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (e.fixedSize == null && (e.weight || e.maxSize == null || e.maxSize > e.minSize)) {
|
|
149
|
+
entriesWithDynamicSpace.push(e);
|
|
150
|
+
if (e.maxSize == null) {
|
|
151
|
+
unboundedEntries.push(e.id);
|
|
152
|
+
}
|
|
153
|
+
if (e.weight && e.weight < minimumExplicitWeight) {
|
|
154
|
+
minimumExplicitWeight = e.weight;
|
|
155
|
+
}
|
|
156
|
+
// TODO: we assume that the input is 'sane', and maxSize > minSize if given
|
|
157
|
+
if (e.maxSize && e.maxSize - e.minSize < minimumDerivedWeight) {
|
|
158
|
+
minimumDerivedWeight = e.maxSize - e.minSize;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!isFinite(minimumExplicitWeight)) {
|
|
163
|
+
minimumExplicitWeight = 1;
|
|
164
|
+
}
|
|
165
|
+
if (!isFinite(minimumDerivedWeight)) {
|
|
166
|
+
minimumDerivedWeight = 1;
|
|
167
|
+
}
|
|
168
|
+
if (lowerBoundSize <= viewportSize) {
|
|
169
|
+
// we will show ALL entries
|
|
170
|
+
const totalSpaceToDistribute = viewportSize - lowerBoundSize;
|
|
171
|
+
if (entriesWithDynamicSpace.some((e) => e.weight)) {
|
|
172
|
+
// we will attempt distribute the rest of the available space among entries that have not maxSize based on their weights
|
|
173
|
+
const totalExplicitWeights = entriesWithDynamicSpace.reduce((acc, e) => {
|
|
174
|
+
var _a;
|
|
175
|
+
return acc + ((_a = e.weight) !== null && _a !== void 0 ? _a : minimumExplicitWeight);
|
|
176
|
+
}, 0);
|
|
177
|
+
const unitOfSpace = totalSpaceToDistribute / totalExplicitWeights;
|
|
178
|
+
let remainingSpaceToDistribute = totalSpaceToDistribute;
|
|
179
|
+
const transformedEntries = entries.map((e) => {
|
|
180
|
+
var _a;
|
|
181
|
+
const transformedEntry = { id: e.id, floating: false, size: e.fixedSize };
|
|
182
|
+
if (transformedEntry.size == null) {
|
|
183
|
+
let computedSize = e.minSize;
|
|
184
|
+
if (remainingSpaceToDistribute > 0) {
|
|
185
|
+
const entryWeight = (_a = e.weight) !== null && _a !== void 0 ? _a : minimumExplicitWeight;
|
|
186
|
+
let entryExtraSpace = Math.min(remainingSpaceToDistribute, Math.max(Math.round(entryWeight * unitOfSpace), 1));
|
|
187
|
+
computedSize = e.minSize + entryExtraSpace;
|
|
188
|
+
if (e.maxSize && computedSize > e.maxSize) {
|
|
189
|
+
computedSize = e.maxSize;
|
|
190
|
+
entryExtraSpace = e.maxSize - e.minSize;
|
|
191
|
+
}
|
|
192
|
+
remainingSpaceToDistribute -= entryExtraSpace;
|
|
193
|
+
}
|
|
194
|
+
transformedEntry.size = computedSize;
|
|
195
|
+
}
|
|
196
|
+
return transformedEntry;
|
|
197
|
+
});
|
|
198
|
+
if (remainingSpaceToDistribute > 0 && unboundedEntries.length) {
|
|
199
|
+
const outstandingUnitOfSpace = remainingSpaceToDistribute / unboundedEntries.length;
|
|
200
|
+
transformedEntries.forEach((e) => {
|
|
201
|
+
if (unboundedEntries.includes(e.id) && remainingSpaceToDistribute > 0) {
|
|
202
|
+
const extraEntrySpace = e.id === unboundedEntries[unboundedEntries.length - 1] ? remainingSpaceToDistribute : Math.round(outstandingUnitOfSpace);
|
|
203
|
+
e.size = e.size + extraEntrySpace;
|
|
204
|
+
remainingSpaceToDistribute -= extraEntrySpace;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return transformedEntries;
|
|
209
|
+
}
|
|
210
|
+
// we attempt to distribute the remaining space based on the maxSize - minSize
|
|
211
|
+
const totalDerivedWeights = entriesWithDynamicSpace.reduce((acc, e) => {
|
|
212
|
+
return acc + (e.maxSize ? e.maxSize - e.minSize : minimumDerivedWeight);
|
|
213
|
+
}, 0);
|
|
214
|
+
if (minimumDerivedWeight) {
|
|
215
|
+
const unitOfSpace = totalSpaceToDistribute / totalDerivedWeights;
|
|
216
|
+
let remainingSpaceToDistribute = totalSpaceToDistribute;
|
|
217
|
+
const transformedEntries = entries.map((e) => {
|
|
218
|
+
const transformedEntry = { id: e.id, size: e.fixedSize };
|
|
219
|
+
if (transformedEntry.size == null) {
|
|
220
|
+
let computedSize = e.minSize;
|
|
221
|
+
if (remainingSpaceToDistribute > 0) {
|
|
222
|
+
const entryWeight = e.maxSize ? e.maxSize - e.minSize : minimumDerivedWeight;
|
|
223
|
+
let entryExtraSpace = Math.min(remainingSpaceToDistribute, Math.max(Math.round(entryWeight * unitOfSpace), 1));
|
|
224
|
+
computedSize = e.minSize + entryExtraSpace;
|
|
225
|
+
if (e.maxSize && computedSize > e.maxSize) {
|
|
226
|
+
computedSize = e.maxSize;
|
|
227
|
+
entryExtraSpace = e.maxSize - e.minSize;
|
|
228
|
+
}
|
|
229
|
+
remainingSpaceToDistribute -= entryExtraSpace;
|
|
230
|
+
}
|
|
231
|
+
transformedEntry.size = computedSize;
|
|
232
|
+
}
|
|
233
|
+
return transformedEntry;
|
|
234
|
+
});
|
|
235
|
+
if (remainingSpaceToDistribute > 0 && unboundedEntries.length) {
|
|
236
|
+
const outstandingUnitOfSpace = remainingSpaceToDistribute / unboundedEntries.length;
|
|
237
|
+
transformedEntries.forEach((e) => {
|
|
238
|
+
if (unboundedEntries.includes(e.id) && remainingSpaceToDistribute > 0) {
|
|
239
|
+
const extraEntrySpace = e.id === unboundedEntries[unboundedEntries.length - 1] ? remainingSpaceToDistribute : Math.round(outstandingUnitOfSpace);
|
|
240
|
+
e.size = e.size + extraEntrySpace;
|
|
241
|
+
remainingSpaceToDistribute -= extraEntrySpace;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return transformedEntries;
|
|
246
|
+
}
|
|
247
|
+
// no luck distributing the weights - we will return the entries as they are...
|
|
248
|
+
return entries.map((e) => { var _a; return ({ id: e.id, floating: false, size: (_a = e.fixedSize) !== null && _a !== void 0 ? _a : e.minSize }); });
|
|
249
|
+
}
|
|
250
|
+
// we will show only 'some' entries. Let's figure out which ones are in the window
|
|
251
|
+
const firstOffsetInWindow = entriesOffsets.find((o) => o.offset <= offset && o.offset + o.size >= offset);
|
|
252
|
+
if (!firstOffsetInWindow) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
const firstEntryIdxInWindow = entriesOffsets.indexOf(firstOffsetInWindow);
|
|
256
|
+
const firstEntryIdx = Math.max(0, firstEntryIdxInWindow - overscan);
|
|
257
|
+
const firstOffsetOutOfWindow = entriesOffsets.find((o) => o.offset >= offset + viewportSize);
|
|
258
|
+
const lastEntryIdxInWindow = firstOffsetOutOfWindow ? entriesOffsets.indexOf(firstOffsetOutOfWindow) - 1 : entries.length - 1;
|
|
259
|
+
const lastEntryIdx = Math.min(entries.length - 1, lastEntryIdxInWindow + overscan);
|
|
260
|
+
// take the entries corresponding to the viewport and include the overscan
|
|
261
|
+
const selectedEntries = entries.slice(firstEntryIdx, lastEntryIdx + 1);
|
|
262
|
+
const entryOffsetById = entriesOffsets.reduce((acc, eo) => {
|
|
263
|
+
acc[eo.id] = { start: eo.offset, end: eo.offset + eo.size };
|
|
264
|
+
return acc;
|
|
265
|
+
}, {});
|
|
266
|
+
let transformedEntries = selectedEntries.map((e) => {
|
|
267
|
+
var _a;
|
|
268
|
+
return ({
|
|
269
|
+
id: e.id,
|
|
270
|
+
size: (_a = e.fixedSize) !== null && _a !== void 0 ? _a : e.minSize,
|
|
271
|
+
offset: entryOffsetById[e.id].start,
|
|
272
|
+
level: e.level,
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
// adjust selected entries off the window which have a stickyGroup
|
|
276
|
+
if (stickyGroups.hasChildren()) {
|
|
277
|
+
// first, we will adjust the maxOffset for the sticky entries within the view:
|
|
278
|
+
transformedEntries.forEach((e) => {
|
|
279
|
+
if (e.level) {
|
|
280
|
+
const entryGroupNode = stickyGroups.findNode(e.id);
|
|
281
|
+
const nextSiblingGroupNode = entryGroupNode.findNextSibling();
|
|
282
|
+
e.maxOffset = nextSiblingGroupNode ? entryOffsetById[nextSiblingGroupNode.id].start - e.size : null;
|
|
283
|
+
e.minOffset = e.offset;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const firstStickyEntryOutsideTheWindow = entries
|
|
287
|
+
.slice(0, firstEntryIdx)
|
|
288
|
+
.reverse()
|
|
289
|
+
.find((e) => !!e.level);
|
|
290
|
+
if (firstStickyEntryOutsideTheWindow) {
|
|
291
|
+
const stickyAncestors = stickyGroups.findAncestors(firstStickyEntryOutsideTheWindow.id);
|
|
292
|
+
// then, find and transform preceding sticky entries
|
|
293
|
+
const stickyTransformedEntries = stickyAncestors.map((stickyGroupNode) => {
|
|
294
|
+
var _a;
|
|
295
|
+
const entry = entryById[stickyGroupNode.id];
|
|
296
|
+
const size = (_a = entry.fixedSize) !== null && _a !== void 0 ? _a : entry.minSize;
|
|
297
|
+
return {
|
|
298
|
+
id: entry.id,
|
|
299
|
+
size,
|
|
300
|
+
offset: entryOffsetById[entry.id].start,
|
|
301
|
+
level: entry.level,
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
transformedEntries = [
|
|
305
|
+
...stickyTransformedEntries,
|
|
306
|
+
{
|
|
307
|
+
id: firstStickyEntryOutsideTheWindow.id,
|
|
308
|
+
size: (_c = firstStickyEntryOutsideTheWindow.fixedSize) !== null && _c !== void 0 ? _c : firstStickyEntryOutsideTheWindow.minSize,
|
|
309
|
+
offset: entryOffsetById[firstStickyEntryOutsideTheWindow.id].start,
|
|
310
|
+
level: firstStickyEntryOutsideTheWindow.level,
|
|
311
|
+
},
|
|
312
|
+
...transformedEntries,
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
// then figure out the level of the sticky entries based on the depth of the tree of group nodes
|
|
316
|
+
const relativeOffsets = {};
|
|
317
|
+
transformedEntries
|
|
318
|
+
.filter((te) => !!te.level)
|
|
319
|
+
.sort((e1, e2) => e2.level - e1.level)
|
|
320
|
+
.forEach((te) => {
|
|
321
|
+
var _a;
|
|
322
|
+
const stickyGroupNode = stickyGroups.findNode(te.id);
|
|
323
|
+
const nextSiblingGroupNode = stickyGroupNode.findNextSibling();
|
|
324
|
+
te.maxOffset = nextSiblingGroupNode ? entryOffsetById[nextSiblingGroupNode.id].start - te.size : lowerBoundSize - te.size;
|
|
325
|
+
te.minOffset = te.offset;
|
|
326
|
+
const parentGroupNode = StickyGroupNode.isInstance(stickyGroupNode.parent) ? stickyGroupNode.parent : null;
|
|
327
|
+
const levelOffset = (_a = relativeOffsets[parentGroupNode === null || parentGroupNode === void 0 ? void 0 : parentGroupNode.id]) !== null && _a !== void 0 ? _a : 0;
|
|
328
|
+
te.relativeOffset = levelOffset;
|
|
329
|
+
relativeOffsets[te.id] = levelOffset + te.size;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return transformedEntries;
|
|
333
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@enhanced-dom/virtualizer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Helpers for item list virtualization in 1d or 2d",
|
|
5
|
+
"repository": "https://github.com/enhanced-dom/virtualizer.git",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"author": "CD9B4A105290E17E0948E021DF4105107C88693C59C0B891CCC08366C51AEA990902A6A156AC87D88A2FC41422A5E1C3C4071F251F19441C4516000EC25F87DF",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@enhanced-dom/build": "^0.1.0",
|
|
13
|
+
"@enhanced-dom/jest": "^0.1.0",
|
|
14
|
+
"@enhanced-dom/lint": "^0.1.4",
|
|
15
|
+
"@enhanced-dom/react": "^0.1.0",
|
|
16
|
+
"@enhanced-dom/webpack": "^0.1.0",
|
|
17
|
+
"@types/jest": "^29.5.12",
|
|
18
|
+
"@types/react": "^17.0.75",
|
|
19
|
+
"@types/react-dom": "^17.0.25",
|
|
20
|
+
"typescript": "^5.3.3",
|
|
21
|
+
"lodash.debounce": "^4.0.8",
|
|
22
|
+
"use-lodash-debounce": "^1.3.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18",
|
|
26
|
+
"npm": ">=10"
|
|
27
|
+
}
|
|
28
|
+
}
|