@skygraph/core 0.0.0-placeholder.0 → 0.5.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 +21 -0
- package/README.md +42 -6
- package/dist/calendar.cjs +404 -0
- package/dist/calendar.cjs.map +1 -0
- package/dist/calendar.d.cts +209 -0
- package/dist/calendar.d.ts +209 -0
- package/dist/calendar.js +12 -0
- package/dist/calendar.js.map +1 -0
- package/dist/chunk-5CCD5Q4B.js +36 -0
- package/dist/chunk-5CCD5Q4B.js.map +1 -0
- package/dist/chunk-7FA2TBSZ.js +1211 -0
- package/dist/chunk-7FA2TBSZ.js.map +1 -0
- package/dist/chunk-AB3RLBLW.js +357 -0
- package/dist/chunk-AB3RLBLW.js.map +1 -0
- package/dist/chunk-GEMALROQ.js +258 -0
- package/dist/chunk-GEMALROQ.js.map +1 -0
- package/dist/chunk-IY6LJU3L.js +256 -0
- package/dist/chunk-IY6LJU3L.js.map +1 -0
- package/dist/chunk-KMGRNPLG.js +453 -0
- package/dist/chunk-KMGRNPLG.js.map +1 -0
- package/dist/chunk-Y7FTBBYX.js +397 -0
- package/dist/chunk-Y7FTBBYX.js.map +1 -0
- package/dist/chunk-ZWB7JGAJ.js +488 -0
- package/dist/chunk-ZWB7JGAJ.js.map +1 -0
- package/dist/form.cjs +498 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +130 -0
- package/dist/form.d.ts +130 -0
- package/dist/form.js +8 -0
- package/dist/form.js.map +1 -0
- package/dist/graph.cjs +1269 -0
- package/dist/graph.cjs.map +1 -0
- package/dist/graph.d.cts +671 -0
- package/dist/graph.d.ts +671 -0
- package/dist/graph.js +34 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.cjs +3860 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-internal.cjs +339 -0
- package/dist/runtime-internal.cjs.map +1 -0
- package/dist/runtime-internal.d.cts +130 -0
- package/dist/runtime-internal.d.ts +130 -0
- package/dist/runtime-internal.js +67 -0
- package/dist/runtime-internal.js.map +1 -0
- package/dist/table.cjs +537 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +215 -0
- package/dist/table.d.ts +215 -0
- package/dist/table.js +16 -0
- package/dist/table.js.map +1 -0
- package/dist/tree.cjs +442 -0
- package/dist/tree.cjs.map +1 -0
- package/dist/tree.d.cts +76 -0
- package/dist/tree.d.ts +76 -0
- package/dist/tree.js +8 -0
- package/dist/tree.js.map +1 -0
- package/dist/types-B-rKQJ4R.d.cts +35 -0
- package/dist/types-B-rKQJ4R.d.ts +35 -0
- package/dist/virtual.cjs +283 -0
- package/dist/virtual.cjs.map +1 -0
- package/dist/virtual.d.cts +96 -0
- package/dist/virtual.d.ts +96 -0
- package/dist/virtual.js +9 -0
- package/dist/virtual.js.map +1 -0
- package/package.json +98 -18
- package/index.js +0 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin of a write event as seen by the runtime.
|
|
3
|
+
*
|
|
4
|
+
* Keep this union strictly runtime-level. Engine-specific tags (form, table,
|
|
5
|
+
* tree, custom) MUST live in `WriteEvent.meta`, NOT here. Adding an engine
|
|
6
|
+
* category to this union is an architecture violation — it makes core know
|
|
7
|
+
* about engines.
|
|
8
|
+
*/
|
|
9
|
+
type WriteSource = 'user' | 'computed' | 'transaction' | 'restore';
|
|
10
|
+
interface WriteEvent {
|
|
11
|
+
path: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
oldValue: unknown;
|
|
14
|
+
source: WriteSource;
|
|
15
|
+
/**
|
|
16
|
+
* Opaque metadata bag for engines / plugins. Middleware may read/write it.
|
|
17
|
+
* Runtime itself must never branch on specific keys here.
|
|
18
|
+
*/
|
|
19
|
+
meta?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
type NextFn = (event: WriteEvent) => void;
|
|
22
|
+
type Middleware = (event: WriteEvent, next: NextFn) => void;
|
|
23
|
+
interface Core {
|
|
24
|
+
get(path: string): unknown;
|
|
25
|
+
set(path: string, value: unknown): void;
|
|
26
|
+
subscribe(path: string, cb: (value: unknown) => void): () => void;
|
|
27
|
+
batch(fn: () => void): void;
|
|
28
|
+
transaction(fn: () => void): void;
|
|
29
|
+
computed(target: string, deps: string[], fn: (...values: unknown[]) => unknown): void;
|
|
30
|
+
use(middleware: Middleware): () => void;
|
|
31
|
+
snapshot(): Record<string, unknown>;
|
|
32
|
+
restore(snapshot: Record<string, unknown>): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type { Core as C, Middleware as M, NextFn as N, WriteEvent as W, WriteSource as a };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin of a write event as seen by the runtime.
|
|
3
|
+
*
|
|
4
|
+
* Keep this union strictly runtime-level. Engine-specific tags (form, table,
|
|
5
|
+
* tree, custom) MUST live in `WriteEvent.meta`, NOT here. Adding an engine
|
|
6
|
+
* category to this union is an architecture violation — it makes core know
|
|
7
|
+
* about engines.
|
|
8
|
+
*/
|
|
9
|
+
type WriteSource = 'user' | 'computed' | 'transaction' | 'restore';
|
|
10
|
+
interface WriteEvent {
|
|
11
|
+
path: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
oldValue: unknown;
|
|
14
|
+
source: WriteSource;
|
|
15
|
+
/**
|
|
16
|
+
* Opaque metadata bag for engines / plugins. Middleware may read/write it.
|
|
17
|
+
* Runtime itself must never branch on specific keys here.
|
|
18
|
+
*/
|
|
19
|
+
meta?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
type NextFn = (event: WriteEvent) => void;
|
|
22
|
+
type Middleware = (event: WriteEvent, next: NextFn) => void;
|
|
23
|
+
interface Core {
|
|
24
|
+
get(path: string): unknown;
|
|
25
|
+
set(path: string, value: unknown): void;
|
|
26
|
+
subscribe(path: string, cb: (value: unknown) => void): () => void;
|
|
27
|
+
batch(fn: () => void): void;
|
|
28
|
+
transaction(fn: () => void): void;
|
|
29
|
+
computed(target: string, deps: string[], fn: (...values: unknown[]) => unknown): void;
|
|
30
|
+
use(middleware: Middleware): () => void;
|
|
31
|
+
snapshot(): Record<string, unknown>;
|
|
32
|
+
restore(snapshot: Record<string, unknown>): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type { Core as C, Middleware as M, NextFn as N, WriteEvent as W, WriteSource as a };
|
package/dist/virtual.cjs
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/engines/virtual/index.ts
|
|
21
|
+
var virtual_exports = {};
|
|
22
|
+
__export(virtual_exports, {
|
|
23
|
+
createMeasureCache: () => createMeasureCache,
|
|
24
|
+
createVirtual: () => createVirtual
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(virtual_exports);
|
|
27
|
+
|
|
28
|
+
// src/engines/virtual/measure.ts
|
|
29
|
+
function createMeasureCache(options) {
|
|
30
|
+
let itemCount = options.itemCount;
|
|
31
|
+
let estimate = options.estimate;
|
|
32
|
+
const measured = /* @__PURE__ */ new Map();
|
|
33
|
+
let offsets = null;
|
|
34
|
+
let cachedTotal = 0;
|
|
35
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
36
|
+
const getEstimate = (index) => typeof estimate === "number" ? estimate : estimate(index);
|
|
37
|
+
const getItemSize = (index) => {
|
|
38
|
+
const m = measured.get(index);
|
|
39
|
+
return m !== void 0 ? m : getEstimate(index);
|
|
40
|
+
};
|
|
41
|
+
const rebuildOffsets = () => {
|
|
42
|
+
if (offsets && offsets.length === itemCount) return;
|
|
43
|
+
const arr = new Array(itemCount);
|
|
44
|
+
let acc = 0;
|
|
45
|
+
for (let i = 0; i < itemCount; i++) {
|
|
46
|
+
arr[i] = acc;
|
|
47
|
+
acc += getItemSize(i);
|
|
48
|
+
}
|
|
49
|
+
offsets = arr;
|
|
50
|
+
cachedTotal = acc;
|
|
51
|
+
};
|
|
52
|
+
const getTotalHeight = () => {
|
|
53
|
+
rebuildOffsets();
|
|
54
|
+
return cachedTotal;
|
|
55
|
+
};
|
|
56
|
+
const getItemOffset = (index) => {
|
|
57
|
+
if (index <= 0) return 0;
|
|
58
|
+
if (index >= itemCount) return getTotalHeight();
|
|
59
|
+
rebuildOffsets();
|
|
60
|
+
return offsets[index];
|
|
61
|
+
};
|
|
62
|
+
const getItemAtOffset = (offset) => {
|
|
63
|
+
if (itemCount === 0) return 0;
|
|
64
|
+
rebuildOffsets();
|
|
65
|
+
let lo = 0;
|
|
66
|
+
let hi = itemCount - 1;
|
|
67
|
+
while (lo < hi) {
|
|
68
|
+
const mid = lo + hi + 1 >>> 1;
|
|
69
|
+
if (offsets[mid] <= offset) lo = mid;
|
|
70
|
+
else hi = mid - 1;
|
|
71
|
+
}
|
|
72
|
+
return lo;
|
|
73
|
+
};
|
|
74
|
+
const invalidate = (event) => {
|
|
75
|
+
offsets = null;
|
|
76
|
+
for (const l of listeners) l(event);
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
get itemCount() {
|
|
80
|
+
return itemCount;
|
|
81
|
+
},
|
|
82
|
+
get measuredCount() {
|
|
83
|
+
return measured.size;
|
|
84
|
+
},
|
|
85
|
+
setItemCount(count) {
|
|
86
|
+
if (count === itemCount) return;
|
|
87
|
+
if (count < itemCount && measured.size > 0) {
|
|
88
|
+
for (const k of [...measured.keys()]) {
|
|
89
|
+
if (k >= count) measured.delete(k);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
itemCount = count;
|
|
93
|
+
invalidate("resize");
|
|
94
|
+
},
|
|
95
|
+
setEstimate(e) {
|
|
96
|
+
estimate = e;
|
|
97
|
+
invalidate("reset");
|
|
98
|
+
},
|
|
99
|
+
setMeasuredHeight(index, height) {
|
|
100
|
+
if (index < 0 || index >= itemCount) return false;
|
|
101
|
+
if (!Number.isFinite(height) || height < 0) return false;
|
|
102
|
+
const prev = measured.get(index);
|
|
103
|
+
if (prev === height) return false;
|
|
104
|
+
measured.set(index, height);
|
|
105
|
+
invalidate("measure");
|
|
106
|
+
return true;
|
|
107
|
+
},
|
|
108
|
+
clearMeasured(index) {
|
|
109
|
+
const had = measured.delete(index);
|
|
110
|
+
if (had) invalidate("measure");
|
|
111
|
+
return had;
|
|
112
|
+
},
|
|
113
|
+
reset() {
|
|
114
|
+
if (measured.size === 0) return;
|
|
115
|
+
measured.clear();
|
|
116
|
+
invalidate("reset");
|
|
117
|
+
},
|
|
118
|
+
hasMeasured(index) {
|
|
119
|
+
return measured.has(index);
|
|
120
|
+
},
|
|
121
|
+
getMeasuredHeight(index) {
|
|
122
|
+
return measured.get(index);
|
|
123
|
+
},
|
|
124
|
+
getEstimatedSize: getEstimate,
|
|
125
|
+
getItemSize,
|
|
126
|
+
getItemOffset,
|
|
127
|
+
getItemAtOffset,
|
|
128
|
+
getTotalHeight,
|
|
129
|
+
subscribe(listener) {
|
|
130
|
+
listeners.add(listener);
|
|
131
|
+
return () => {
|
|
132
|
+
listeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/engines/virtual/index.ts
|
|
139
|
+
function createVirtual(options) {
|
|
140
|
+
let itemCount = options.itemCount;
|
|
141
|
+
let itemHeight = options.itemHeight;
|
|
142
|
+
const overscan = options.overscan ?? 5;
|
|
143
|
+
let cache = null;
|
|
144
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
145
|
+
const ensureCache = () => {
|
|
146
|
+
if (cache) return cache;
|
|
147
|
+
cache = createMeasureCache({ itemCount, estimate: itemHeight });
|
|
148
|
+
cache.subscribe((event) => {
|
|
149
|
+
for (const l of listeners) l(event);
|
|
150
|
+
});
|
|
151
|
+
return cache;
|
|
152
|
+
};
|
|
153
|
+
const isFastPath = () => typeof itemHeight === "number" && (cache === null || cache.measuredCount === 0);
|
|
154
|
+
const getEstimate = (index) => typeof itemHeight === "number" ? itemHeight : itemHeight(index);
|
|
155
|
+
const getItemSize = (index) => {
|
|
156
|
+
if (cache) return cache.getItemSize(index);
|
|
157
|
+
return getEstimate(index);
|
|
158
|
+
};
|
|
159
|
+
const getTotalHeight = () => {
|
|
160
|
+
if (isFastPath()) return itemCount * itemHeight;
|
|
161
|
+
return ensureCache().getTotalHeight();
|
|
162
|
+
};
|
|
163
|
+
const getItemOffset = (index) => {
|
|
164
|
+
if (index <= 0) return 0;
|
|
165
|
+
if (index >= itemCount) return getTotalHeight();
|
|
166
|
+
if (isFastPath()) return index * itemHeight;
|
|
167
|
+
return ensureCache().getItemOffset(index);
|
|
168
|
+
};
|
|
169
|
+
const getItemAtOffset = (offset) => {
|
|
170
|
+
if (itemCount === 0) return 0;
|
|
171
|
+
if (isFastPath()) {
|
|
172
|
+
const h = itemHeight;
|
|
173
|
+
return Math.min(Math.floor(offset / h), itemCount - 1);
|
|
174
|
+
}
|
|
175
|
+
return ensureCache().getItemAtOffset(offset);
|
|
176
|
+
};
|
|
177
|
+
const getRange = (scrollTop, viewportHeight) => {
|
|
178
|
+
if (itemCount === 0) {
|
|
179
|
+
return { startIndex: 0, endIndex: 0, offsetTop: 0, totalHeight: 0, visibleItems: [] };
|
|
180
|
+
}
|
|
181
|
+
const total = getTotalHeight();
|
|
182
|
+
const rawStart = getItemAtOffset(scrollTop);
|
|
183
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
184
|
+
let endIndex = rawStart;
|
|
185
|
+
let accHeight = getItemOffset(rawStart) + getItemSize(rawStart);
|
|
186
|
+
const bottomEdge = scrollTop + viewportHeight;
|
|
187
|
+
while (endIndex < itemCount - 1 && accHeight < bottomEdge) {
|
|
188
|
+
endIndex++;
|
|
189
|
+
accHeight += getItemSize(endIndex);
|
|
190
|
+
}
|
|
191
|
+
endIndex = Math.min(itemCount - 1, endIndex + overscan);
|
|
192
|
+
const visibleItems = [];
|
|
193
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
194
|
+
visibleItems.push({
|
|
195
|
+
index: i,
|
|
196
|
+
offsetTop: getItemOffset(i),
|
|
197
|
+
height: getItemSize(i)
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
startIndex,
|
|
202
|
+
endIndex,
|
|
203
|
+
offsetTop: getItemOffset(startIndex),
|
|
204
|
+
totalHeight: total,
|
|
205
|
+
visibleItems
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
const scrollToIndex = (index, viewportHeight, align = "start") => {
|
|
209
|
+
const clamped = Math.max(0, Math.min(index, itemCount - 1));
|
|
210
|
+
const offset = getItemOffset(clamped);
|
|
211
|
+
const h = getItemSize(clamped);
|
|
212
|
+
switch (align) {
|
|
213
|
+
case "center":
|
|
214
|
+
return Math.max(0, offset - viewportHeight / 2 + h / 2);
|
|
215
|
+
case "end":
|
|
216
|
+
return Math.max(0, offset - viewportHeight + h);
|
|
217
|
+
default:
|
|
218
|
+
return offset;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const emit = (event) => {
|
|
222
|
+
for (const l of listeners) l(event);
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
get itemCount() {
|
|
226
|
+
return itemCount;
|
|
227
|
+
},
|
|
228
|
+
get totalHeight() {
|
|
229
|
+
return getTotalHeight();
|
|
230
|
+
},
|
|
231
|
+
get measuredCount() {
|
|
232
|
+
return cache?.measuredCount ?? 0;
|
|
233
|
+
},
|
|
234
|
+
setItemCount(count) {
|
|
235
|
+
if (count === itemCount) return;
|
|
236
|
+
itemCount = count;
|
|
237
|
+
if (cache) {
|
|
238
|
+
cache.setItemCount(count);
|
|
239
|
+
} else {
|
|
240
|
+
emit("resize");
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
setItemHeight(height) {
|
|
244
|
+
itemHeight = height;
|
|
245
|
+
if (cache) {
|
|
246
|
+
cache.setEstimate(height);
|
|
247
|
+
} else {
|
|
248
|
+
emit("reset");
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
setMeasuredHeight(index, height) {
|
|
252
|
+
return ensureCache().setMeasuredHeight(index, height);
|
|
253
|
+
},
|
|
254
|
+
clearMeasuredHeight(index) {
|
|
255
|
+
if (!cache) return false;
|
|
256
|
+
return cache.clearMeasured(index);
|
|
257
|
+
},
|
|
258
|
+
resetMeasurements() {
|
|
259
|
+
if (cache) cache.reset();
|
|
260
|
+
},
|
|
261
|
+
hasMeasured(index) {
|
|
262
|
+
return cache ? cache.hasMeasured(index) : false;
|
|
263
|
+
},
|
|
264
|
+
getEstimatedSize: getEstimate,
|
|
265
|
+
getItemSize,
|
|
266
|
+
getRange,
|
|
267
|
+
getItemOffset,
|
|
268
|
+
getItemAtOffset,
|
|
269
|
+
scrollToIndex,
|
|
270
|
+
subscribe(listener) {
|
|
271
|
+
listeners.add(listener);
|
|
272
|
+
return () => {
|
|
273
|
+
listeners.delete(listener);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
279
|
+
0 && (module.exports = {
|
|
280
|
+
createMeasureCache,
|
|
281
|
+
createVirtual
|
|
282
|
+
});
|
|
283
|
+
//# sourceMappingURL=virtual.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engines/virtual/index.ts","../src/engines/virtual/measure.ts"],"sourcesContent":["import { createMeasureCache, type MeasureCache } from './measure'\n\nexport type {\n MeasureCache,\n MeasureCacheEvent,\n MeasureCacheListener,\n MeasureCacheOptions,\n} from './measure'\nexport { createMeasureCache } from './measure'\n\nexport interface VirtualRange {\n startIndex: number\n endIndex: number\n offsetTop: number\n totalHeight: number\n visibleItems: VirtualItem[]\n}\n\nexport interface VirtualItem {\n index: number\n offsetTop: number\n height: number\n}\n\n/** Событие инвалидации, эмитимое VirtualEngine подписчикам. */\nexport type VirtualEngineEvent = 'measure' | 'reset' | 'resize'\nexport type VirtualEngineListener = (event: VirtualEngineEvent) => void\n\nexport interface VirtualEngineOptions {\n itemCount: number\n itemHeight: number | ((index: number) => number)\n overscan?: number\n}\n\nexport interface VirtualEngine {\n readonly itemCount: number\n readonly totalHeight: number\n /** Сколько строк прошли через `setMeasuredHeight`. */\n readonly measuredCount: number\n\n setItemCount(count: number): void\n setItemHeight(height: number | ((index: number) => number)): void\n\n /**\n * Записать реально измеренную (через ResizeObserver) высоту строки.\n * Возвращает `true`, если значение изменилось — полезно для контроля\n * количества `setState` в адаптере.\n */\n setMeasuredHeight(index: number, height: number): boolean\n /** Сбросить измерение для одной строки (высота вернётся к эстимейту). */\n clearMeasuredHeight(index: number): boolean\n /** Сбросить все измерения (например, при resize контейнера / смене темы). */\n resetMeasurements(): void\n /** Был ли индекс уже измерён. */\n hasMeasured(index: number): boolean\n /** Эстимейт высоты строки без учёта измерений. */\n getEstimatedSize(index: number): number\n /** Эффективная высота строки — измерение, если есть, иначе эстимейт. */\n getItemSize(index: number): number\n\n getRange(scrollTop: number, viewportHeight: number): VirtualRange\n getItemOffset(index: number): number\n getItemAtOffset(offset: number): number\n scrollToIndex(\n index: number,\n viewportHeight: number,\n align?: 'start' | 'center' | 'end',\n ): number\n\n /**\n * Подписаться на инвалидации (resize / measure / reset). Вызывается\n * только после реального изменения — повторные `setMeasuredHeight`\n * с тем же значением событие не эмитят.\n */\n subscribe(listener: VirtualEngineListener): () => void\n}\n\nexport function createVirtual(options: VirtualEngineOptions): VirtualEngine {\n let itemCount = options.itemCount\n let itemHeight = options.itemHeight\n const overscan = options.overscan ?? 5\n\n // Кэш создаётся лениво — для чисто фиксированной высоты без измерений\n // нам он не нужен, и мы сохраняем O(1) арифметику для миллионов строк.\n let cache: MeasureCache | null = null\n const listeners = new Set<VirtualEngineListener>()\n\n const ensureCache = (): MeasureCache => {\n if (cache) return cache\n cache = createMeasureCache({ itemCount, estimate: itemHeight })\n cache.subscribe((event) => {\n for (const l of listeners) l(event)\n })\n return cache\n }\n\n const isFastPath = (): boolean =>\n typeof itemHeight === 'number' && (cache === null || cache.measuredCount === 0)\n\n const getEstimate = (index: number): number =>\n typeof itemHeight === 'number' ? itemHeight : itemHeight(index)\n\n const getItemSize = (index: number): number => {\n if (cache) return cache.getItemSize(index)\n return getEstimate(index)\n }\n\n const getTotalHeight = (): number => {\n if (isFastPath()) return itemCount * (itemHeight as number)\n return ensureCache().getTotalHeight()\n }\n\n const getItemOffset = (index: number): number => {\n if (index <= 0) return 0\n if (index >= itemCount) return getTotalHeight()\n if (isFastPath()) return index * (itemHeight as number)\n return ensureCache().getItemOffset(index)\n }\n\n const getItemAtOffset = (offset: number): number => {\n if (itemCount === 0) return 0\n if (isFastPath()) {\n const h = itemHeight as number\n return Math.min(Math.floor(offset / h), itemCount - 1)\n }\n return ensureCache().getItemAtOffset(offset)\n }\n\n const getRange = (scrollTop: number, viewportHeight: number): VirtualRange => {\n if (itemCount === 0) {\n return { startIndex: 0, endIndex: 0, offsetTop: 0, totalHeight: 0, visibleItems: [] }\n }\n\n const total = getTotalHeight()\n const rawStart = getItemAtOffset(scrollTop)\n const startIndex = Math.max(0, rawStart - overscan)\n\n let endIndex = rawStart\n let accHeight = getItemOffset(rawStart) + getItemSize(rawStart)\n const bottomEdge = scrollTop + viewportHeight\n\n while (endIndex < itemCount - 1 && accHeight < bottomEdge) {\n endIndex++\n accHeight += getItemSize(endIndex)\n }\n endIndex = Math.min(itemCount - 1, endIndex + overscan)\n\n const visibleItems: VirtualItem[] = []\n for (let i = startIndex; i <= endIndex; i++) {\n visibleItems.push({\n index: i,\n offsetTop: getItemOffset(i),\n height: getItemSize(i),\n })\n }\n\n return {\n startIndex,\n endIndex,\n offsetTop: getItemOffset(startIndex),\n totalHeight: total,\n visibleItems,\n }\n }\n\n const scrollToIndex = (\n index: number,\n viewportHeight: number,\n align: 'start' | 'center' | 'end' = 'start',\n ): number => {\n const clamped = Math.max(0, Math.min(index, itemCount - 1))\n const offset = getItemOffset(clamped)\n const h = getItemSize(clamped)\n\n switch (align) {\n case 'center':\n return Math.max(0, offset - viewportHeight / 2 + h / 2)\n case 'end':\n return Math.max(0, offset - viewportHeight + h)\n default:\n return offset\n }\n }\n\n const emit = (event: VirtualEngineEvent) => {\n for (const l of listeners) l(event)\n }\n\n return {\n get itemCount() {\n return itemCount\n },\n get totalHeight() {\n return getTotalHeight()\n },\n get measuredCount() {\n return cache?.measuredCount ?? 0\n },\n\n setItemCount(count: number) {\n if (count === itemCount) return\n itemCount = count\n if (cache) {\n cache.setItemCount(count)\n } else {\n emit('resize')\n }\n },\n\n setItemHeight(height: number | ((index: number) => number)) {\n itemHeight = height\n if (cache) {\n cache.setEstimate(height)\n } else {\n emit('reset')\n }\n },\n\n setMeasuredHeight(index: number, height: number) {\n return ensureCache().setMeasuredHeight(index, height)\n },\n\n clearMeasuredHeight(index: number) {\n if (!cache) return false\n return cache.clearMeasured(index)\n },\n\n resetMeasurements() {\n if (cache) cache.reset()\n },\n\n hasMeasured(index: number) {\n return cache ? cache.hasMeasured(index) : false\n },\n\n getEstimatedSize: getEstimate,\n getItemSize,\n\n getRange,\n getItemOffset,\n getItemAtOffset,\n scrollToIndex,\n\n subscribe(listener: VirtualEngineListener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n }\n}\n","/**\n * Кэш измерений высоты для VirtualEngine с динамической высотой строк.\n *\n * Хранит реальные измеренные высоты `Map<index, number>` поверх эстимейта.\n * Префикс-сумма (offsets) пересобирается лениво при первом запросе после\n * любой инвалидации (resize / measure / reset). Подписки нужны React-адаптеру,\n * чтобы перерисоваться, когда измерение строки изменилось.\n */\n\nexport type MeasureCacheEvent = 'measure' | 'reset' | 'resize'\nexport type MeasureCacheListener = (event: MeasureCacheEvent) => void\n\nexport interface MeasureCacheOptions {\n itemCount: number\n estimate: number | ((index: number) => number)\n}\n\nexport interface MeasureCache {\n readonly itemCount: number\n /** Сколько строк уже было измерено. */\n readonly measuredCount: number\n\n setItemCount(count: number): void\n setEstimate(estimate: number | ((index: number) => number)): void\n\n /** Записать измеренную высоту. Возвращает `true`, если значение изменилось. */\n setMeasuredHeight(index: number, height: number): boolean\n /** Сбросить измерение для одной строки. */\n clearMeasured(index: number): boolean\n /** Сбросить все измерения. */\n reset(): void\n\n hasMeasured(index: number): boolean\n getMeasuredHeight(index: number): number | undefined\n\n /** Эстимейт высоты — без учёта измерений. */\n getEstimatedSize(index: number): number\n /** Эффективная высота строки (измерение или эстимейт). */\n getItemSize(index: number): number\n\n getItemOffset(index: number): number\n getItemAtOffset(offset: number): number\n getTotalHeight(): number\n\n subscribe(listener: MeasureCacheListener): () => void\n}\n\nexport function createMeasureCache(options: MeasureCacheOptions): MeasureCache {\n let itemCount = options.itemCount\n let estimate = options.estimate\n const measured = new Map<number, number>()\n let offsets: number[] | null = null\n let cachedTotal = 0\n const listeners = new Set<MeasureCacheListener>()\n\n const getEstimate = (index: number): number =>\n typeof estimate === 'number' ? estimate : estimate(index)\n\n const getItemSize = (index: number): number => {\n const m = measured.get(index)\n return m !== undefined ? m : getEstimate(index)\n }\n\n const rebuildOffsets = () => {\n if (offsets && offsets.length === itemCount) return\n const arr = new Array<number>(itemCount)\n let acc = 0\n for (let i = 0; i < itemCount; i++) {\n arr[i] = acc\n acc += getItemSize(i)\n }\n offsets = arr\n cachedTotal = acc\n }\n\n const getTotalHeight = (): number => {\n rebuildOffsets()\n return cachedTotal\n }\n\n const getItemOffset = (index: number): number => {\n if (index <= 0) return 0\n if (index >= itemCount) return getTotalHeight()\n rebuildOffsets()\n return offsets![index]!\n }\n\n const getItemAtOffset = (offset: number): number => {\n if (itemCount === 0) return 0\n rebuildOffsets()\n let lo = 0\n let hi = itemCount - 1\n while (lo < hi) {\n const mid = (lo + hi + 1) >>> 1\n if (offsets![mid]! <= offset) lo = mid\n else hi = mid - 1\n }\n return lo\n }\n\n const invalidate = (event: MeasureCacheEvent) => {\n offsets = null\n for (const l of listeners) l(event)\n }\n\n return {\n get itemCount() {\n return itemCount\n },\n get measuredCount() {\n return measured.size\n },\n\n setItemCount(count: number) {\n if (count === itemCount) return\n if (count < itemCount && measured.size > 0) {\n for (const k of [...measured.keys()]) {\n if (k >= count) measured.delete(k)\n }\n }\n itemCount = count\n invalidate('resize')\n },\n\n setEstimate(e: number | ((index: number) => number)) {\n estimate = e\n invalidate('reset')\n },\n\n setMeasuredHeight(index: number, height: number): boolean {\n if (index < 0 || index >= itemCount) return false\n if (!Number.isFinite(height) || height < 0) return false\n const prev = measured.get(index)\n if (prev === height) return false\n measured.set(index, height)\n invalidate('measure')\n return true\n },\n\n clearMeasured(index: number): boolean {\n const had = measured.delete(index)\n if (had) invalidate('measure')\n return had\n },\n\n reset() {\n if (measured.size === 0) return\n measured.clear()\n invalidate('reset')\n },\n\n hasMeasured(index: number) {\n return measured.has(index)\n },\n getMeasuredHeight(index: number) {\n return measured.get(index)\n },\n\n getEstimatedSize: getEstimate,\n getItemSize,\n getItemOffset,\n getItemAtOffset,\n getTotalHeight,\n\n subscribe(listener: MeasureCacheListener) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC+CO,SAAS,mBAAmB,SAA4C;AAC7E,MAAI,YAAY,QAAQ;AACxB,MAAI,WAAW,QAAQ;AACvB,QAAM,WAAW,oBAAI,IAAoB;AACzC,MAAI,UAA2B;AAC/B,MAAI,cAAc;AAClB,QAAM,YAAY,oBAAI,IAA0B;AAEhD,QAAM,cAAc,CAAC,UACnB,OAAO,aAAa,WAAW,WAAW,SAAS,KAAK;AAE1D,QAAM,cAAc,CAAC,UAA0B;AAC7C,UAAM,IAAI,SAAS,IAAI,KAAK;AAC5B,WAAO,MAAM,SAAY,IAAI,YAAY,KAAK;AAAA,EAChD;AAEA,QAAM,iBAAiB,MAAM;AAC3B,QAAI,WAAW,QAAQ,WAAW,UAAW;AAC7C,UAAM,MAAM,IAAI,MAAc,SAAS;AACvC,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAI,CAAC,IAAI;AACT,aAAO,YAAY,CAAC;AAAA,IACtB;AACA,cAAU;AACV,kBAAc;AAAA,EAChB;AAEA,QAAM,iBAAiB,MAAc;AACnC,mBAAe;AACf,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,CAAC,UAA0B;AAC/C,QAAI,SAAS,EAAG,QAAO;AACvB,QAAI,SAAS,UAAW,QAAO,eAAe;AAC9C,mBAAe;AACf,WAAO,QAAS,KAAK;AAAA,EACvB;AAEA,QAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAI,cAAc,EAAG,QAAO;AAC5B,mBAAe;AACf,QAAI,KAAK;AACT,QAAI,KAAK,YAAY;AACrB,WAAO,KAAK,IAAI;AACd,YAAM,MAAO,KAAK,KAAK,MAAO;AAC9B,UAAI,QAAS,GAAG,KAAM,OAAQ,MAAK;AAAA,UAC9B,MAAK,MAAM;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,CAAC,UAA6B;AAC/C,cAAU;AACV,eAAW,KAAK,UAAW,GAAE,KAAK;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,IAAI,YAAY;AACd,aAAO;AAAA,IACT;AAAA,IACA,IAAI,gBAAgB;AAClB,aAAO,SAAS;AAAA,IAClB;AAAA,IAEA,aAAa,OAAe;AAC1B,UAAI,UAAU,UAAW;AACzB,UAAI,QAAQ,aAAa,SAAS,OAAO,GAAG;AAC1C,mBAAW,KAAK,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG;AACpC,cAAI,KAAK,MAAO,UAAS,OAAO,CAAC;AAAA,QACnC;AAAA,MACF;AACA,kBAAY;AACZ,iBAAW,QAAQ;AAAA,IACrB;AAAA,IAEA,YAAY,GAAyC;AACnD,iBAAW;AACX,iBAAW,OAAO;AAAA,IACpB;AAAA,IAEA,kBAAkB,OAAe,QAAyB;AACxD,UAAI,QAAQ,KAAK,SAAS,UAAW,QAAO;AAC5C,UAAI,CAAC,OAAO,SAAS,MAAM,KAAK,SAAS,EAAG,QAAO;AACnD,YAAM,OAAO,SAAS,IAAI,KAAK;AAC/B,UAAI,SAAS,OAAQ,QAAO;AAC5B,eAAS,IAAI,OAAO,MAAM;AAC1B,iBAAW,SAAS;AACpB,aAAO;AAAA,IACT;AAAA,IAEA,cAAc,OAAwB;AACpC,YAAM,MAAM,SAAS,OAAO,KAAK;AACjC,UAAI,IAAK,YAAW,SAAS;AAC7B,aAAO;AAAA,IACT;AAAA,IAEA,QAAQ;AACN,UAAI,SAAS,SAAS,EAAG;AACzB,eAAS,MAAM;AACf,iBAAW,OAAO;AAAA,IACpB;AAAA,IAEA,YAAY,OAAe;AACzB,aAAO,SAAS,IAAI,KAAK;AAAA,IAC3B;AAAA,IACA,kBAAkB,OAAe;AAC/B,aAAO,SAAS,IAAI,KAAK;AAAA,IAC3B;AAAA,IAEA,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IAEA,UAAU,UAAgC;AACxC,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;;;AD9FO,SAAS,cAAc,SAA8C;AAC1E,MAAI,YAAY,QAAQ;AACxB,MAAI,aAAa,QAAQ;AACzB,QAAM,WAAW,QAAQ,YAAY;AAIrC,MAAI,QAA6B;AACjC,QAAM,YAAY,oBAAI,IAA2B;AAEjD,QAAM,cAAc,MAAoB;AACtC,QAAI,MAAO,QAAO;AAClB,YAAQ,mBAAmB,EAAE,WAAW,UAAU,WAAW,CAAC;AAC9D,UAAM,UAAU,CAAC,UAAU;AACzB,iBAAW,KAAK,UAAW,GAAE,KAAK;AAAA,IACpC,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MACjB,OAAO,eAAe,aAAa,UAAU,QAAQ,MAAM,kBAAkB;AAE/E,QAAM,cAAc,CAAC,UACnB,OAAO,eAAe,WAAW,aAAa,WAAW,KAAK;AAEhE,QAAM,cAAc,CAAC,UAA0B;AAC7C,QAAI,MAAO,QAAO,MAAM,YAAY,KAAK;AACzC,WAAO,YAAY,KAAK;AAAA,EAC1B;AAEA,QAAM,iBAAiB,MAAc;AACnC,QAAI,WAAW,EAAG,QAAO,YAAa;AACtC,WAAO,YAAY,EAAE,eAAe;AAAA,EACtC;AAEA,QAAM,gBAAgB,CAAC,UAA0B;AAC/C,QAAI,SAAS,EAAG,QAAO;AACvB,QAAI,SAAS,UAAW,QAAO,eAAe;AAC9C,QAAI,WAAW,EAAG,QAAO,QAAS;AAClC,WAAO,YAAY,EAAE,cAAc,KAAK;AAAA,EAC1C;AAEA,QAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAI,cAAc,EAAG,QAAO;AAC5B,QAAI,WAAW,GAAG;AAChB,YAAM,IAAI;AACV,aAAO,KAAK,IAAI,KAAK,MAAM,SAAS,CAAC,GAAG,YAAY,CAAC;AAAA,IACvD;AACA,WAAO,YAAY,EAAE,gBAAgB,MAAM;AAAA,EAC7C;AAEA,QAAM,WAAW,CAAC,WAAmB,mBAAyC;AAC5E,QAAI,cAAc,GAAG;AACnB,aAAO,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC,EAAE;AAAA,IACtF;AAEA,UAAM,QAAQ,eAAe;AAC7B,UAAM,WAAW,gBAAgB,SAAS;AAC1C,UAAM,aAAa,KAAK,IAAI,GAAG,WAAW,QAAQ;AAElD,QAAI,WAAW;AACf,QAAI,YAAY,cAAc,QAAQ,IAAI,YAAY,QAAQ;AAC9D,UAAM,aAAa,YAAY;AAE/B,WAAO,WAAW,YAAY,KAAK,YAAY,YAAY;AACzD;AACA,mBAAa,YAAY,QAAQ;AAAA,IACnC;AACA,eAAW,KAAK,IAAI,YAAY,GAAG,WAAW,QAAQ;AAEtD,UAAM,eAA8B,CAAC;AACrC,aAAS,IAAI,YAAY,KAAK,UAAU,KAAK;AAC3C,mBAAa,KAAK;AAAA,QAChB,OAAO;AAAA,QACP,WAAW,cAAc,CAAC;AAAA,QAC1B,QAAQ,YAAY,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,WAAW,cAAc,UAAU;AAAA,MACnC,aAAa;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,CACpB,OACA,gBACA,QAAoC,YACzB;AACX,UAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,YAAY,CAAC,CAAC;AAC1D,UAAM,SAAS,cAAc,OAAO;AACpC,UAAM,IAAI,YAAY,OAAO;AAE7B,YAAQ,OAAO;AAAA,MACb,KAAK;AACH,eAAO,KAAK,IAAI,GAAG,SAAS,iBAAiB,IAAI,IAAI,CAAC;AAAA,MACxD,KAAK;AACH,eAAO,KAAK,IAAI,GAAG,SAAS,iBAAiB,CAAC;AAAA,MAChD;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,UAA8B;AAC1C,eAAW,KAAK,UAAW,GAAE,KAAK;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,IAAI,YAAY;AACd,aAAO;AAAA,IACT;AAAA,IACA,IAAI,cAAc;AAChB,aAAO,eAAe;AAAA,IACxB;AAAA,IACA,IAAI,gBAAgB;AAClB,aAAO,OAAO,iBAAiB;AAAA,IACjC;AAAA,IAEA,aAAa,OAAe;AAC1B,UAAI,UAAU,UAAW;AACzB,kBAAY;AACZ,UAAI,OAAO;AACT,cAAM,aAAa,KAAK;AAAA,MAC1B,OAAO;AACL,aAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,IAEA,cAAc,QAA8C;AAC1D,mBAAa;AACb,UAAI,OAAO;AACT,cAAM,YAAY,MAAM;AAAA,MAC1B,OAAO;AACL,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AAAA,IAEA,kBAAkB,OAAe,QAAgB;AAC/C,aAAO,YAAY,EAAE,kBAAkB,OAAO,MAAM;AAAA,IACtD;AAAA,IAEA,oBAAoB,OAAe;AACjC,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,MAAM,cAAc,KAAK;AAAA,IAClC;AAAA,IAEA,oBAAoB;AAClB,UAAI,MAAO,OAAM,MAAM;AAAA,IACzB;AAAA,IAEA,YAAY,OAAe;AACzB,aAAO,QAAQ,MAAM,YAAY,KAAK,IAAI;AAAA,IAC5C;AAAA,IAEA,kBAAkB;AAAA,IAClB;AAAA,IAEA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IAEA,UAAU,UAAiC;AACzC,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Кэш измерений высоты для VirtualEngine с динамической высотой строк.
|
|
3
|
+
*
|
|
4
|
+
* Хранит реальные измеренные высоты `Map<index, number>` поверх эстимейта.
|
|
5
|
+
* Префикс-сумма (offsets) пересобирается лениво при первом запросе после
|
|
6
|
+
* любой инвалидации (resize / measure / reset). Подписки нужны React-адаптеру,
|
|
7
|
+
* чтобы перерисоваться, когда измерение строки изменилось.
|
|
8
|
+
*/
|
|
9
|
+
type MeasureCacheEvent = 'measure' | 'reset' | 'resize';
|
|
10
|
+
type MeasureCacheListener = (event: MeasureCacheEvent) => void;
|
|
11
|
+
interface MeasureCacheOptions {
|
|
12
|
+
itemCount: number;
|
|
13
|
+
estimate: number | ((index: number) => number);
|
|
14
|
+
}
|
|
15
|
+
interface MeasureCache {
|
|
16
|
+
readonly itemCount: number;
|
|
17
|
+
/** Сколько строк уже было измерено. */
|
|
18
|
+
readonly measuredCount: number;
|
|
19
|
+
setItemCount(count: number): void;
|
|
20
|
+
setEstimate(estimate: number | ((index: number) => number)): void;
|
|
21
|
+
/** Записать измеренную высоту. Возвращает `true`, если значение изменилось. */
|
|
22
|
+
setMeasuredHeight(index: number, height: number): boolean;
|
|
23
|
+
/** Сбросить измерение для одной строки. */
|
|
24
|
+
clearMeasured(index: number): boolean;
|
|
25
|
+
/** Сбросить все измерения. */
|
|
26
|
+
reset(): void;
|
|
27
|
+
hasMeasured(index: number): boolean;
|
|
28
|
+
getMeasuredHeight(index: number): number | undefined;
|
|
29
|
+
/** Эстимейт высоты — без учёта измерений. */
|
|
30
|
+
getEstimatedSize(index: number): number;
|
|
31
|
+
/** Эффективная высота строки (измерение или эстимейт). */
|
|
32
|
+
getItemSize(index: number): number;
|
|
33
|
+
getItemOffset(index: number): number;
|
|
34
|
+
getItemAtOffset(offset: number): number;
|
|
35
|
+
getTotalHeight(): number;
|
|
36
|
+
subscribe(listener: MeasureCacheListener): () => void;
|
|
37
|
+
}
|
|
38
|
+
declare function createMeasureCache(options: MeasureCacheOptions): MeasureCache;
|
|
39
|
+
|
|
40
|
+
interface VirtualRange {
|
|
41
|
+
startIndex: number;
|
|
42
|
+
endIndex: number;
|
|
43
|
+
offsetTop: number;
|
|
44
|
+
totalHeight: number;
|
|
45
|
+
visibleItems: VirtualItem[];
|
|
46
|
+
}
|
|
47
|
+
interface VirtualItem {
|
|
48
|
+
index: number;
|
|
49
|
+
offsetTop: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}
|
|
52
|
+
/** Событие инвалидации, эмитимое VirtualEngine подписчикам. */
|
|
53
|
+
type VirtualEngineEvent = 'measure' | 'reset' | 'resize';
|
|
54
|
+
type VirtualEngineListener = (event: VirtualEngineEvent) => void;
|
|
55
|
+
interface VirtualEngineOptions {
|
|
56
|
+
itemCount: number;
|
|
57
|
+
itemHeight: number | ((index: number) => number);
|
|
58
|
+
overscan?: number;
|
|
59
|
+
}
|
|
60
|
+
interface VirtualEngine {
|
|
61
|
+
readonly itemCount: number;
|
|
62
|
+
readonly totalHeight: number;
|
|
63
|
+
/** Сколько строк прошли через `setMeasuredHeight`. */
|
|
64
|
+
readonly measuredCount: number;
|
|
65
|
+
setItemCount(count: number): void;
|
|
66
|
+
setItemHeight(height: number | ((index: number) => number)): void;
|
|
67
|
+
/**
|
|
68
|
+
* Записать реально измеренную (через ResizeObserver) высоту строки.
|
|
69
|
+
* Возвращает `true`, если значение изменилось — полезно для контроля
|
|
70
|
+
* количества `setState` в адаптере.
|
|
71
|
+
*/
|
|
72
|
+
setMeasuredHeight(index: number, height: number): boolean;
|
|
73
|
+
/** Сбросить измерение для одной строки (высота вернётся к эстимейту). */
|
|
74
|
+
clearMeasuredHeight(index: number): boolean;
|
|
75
|
+
/** Сбросить все измерения (например, при resize контейнера / смене темы). */
|
|
76
|
+
resetMeasurements(): void;
|
|
77
|
+
/** Был ли индекс уже измерён. */
|
|
78
|
+
hasMeasured(index: number): boolean;
|
|
79
|
+
/** Эстимейт высоты строки без учёта измерений. */
|
|
80
|
+
getEstimatedSize(index: number): number;
|
|
81
|
+
/** Эффективная высота строки — измерение, если есть, иначе эстимейт. */
|
|
82
|
+
getItemSize(index: number): number;
|
|
83
|
+
getRange(scrollTop: number, viewportHeight: number): VirtualRange;
|
|
84
|
+
getItemOffset(index: number): number;
|
|
85
|
+
getItemAtOffset(offset: number): number;
|
|
86
|
+
scrollToIndex(index: number, viewportHeight: number, align?: 'start' | 'center' | 'end'): number;
|
|
87
|
+
/**
|
|
88
|
+
* Подписаться на инвалидации (resize / measure / reset). Вызывается
|
|
89
|
+
* только после реального изменения — повторные `setMeasuredHeight`
|
|
90
|
+
* с тем же значением событие не эмитят.
|
|
91
|
+
*/
|
|
92
|
+
subscribe(listener: VirtualEngineListener): () => void;
|
|
93
|
+
}
|
|
94
|
+
declare function createVirtual(options: VirtualEngineOptions): VirtualEngine;
|
|
95
|
+
|
|
96
|
+
export { type MeasureCache, type MeasureCacheEvent, type MeasureCacheListener, type MeasureCacheOptions, type VirtualEngine, type VirtualEngineEvent, type VirtualEngineListener, type VirtualEngineOptions, type VirtualItem, type VirtualRange, createMeasureCache, createVirtual };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Кэш измерений высоты для VirtualEngine с динамической высотой строк.
|
|
3
|
+
*
|
|
4
|
+
* Хранит реальные измеренные высоты `Map<index, number>` поверх эстимейта.
|
|
5
|
+
* Префикс-сумма (offsets) пересобирается лениво при первом запросе после
|
|
6
|
+
* любой инвалидации (resize / measure / reset). Подписки нужны React-адаптеру,
|
|
7
|
+
* чтобы перерисоваться, когда измерение строки изменилось.
|
|
8
|
+
*/
|
|
9
|
+
type MeasureCacheEvent = 'measure' | 'reset' | 'resize';
|
|
10
|
+
type MeasureCacheListener = (event: MeasureCacheEvent) => void;
|
|
11
|
+
interface MeasureCacheOptions {
|
|
12
|
+
itemCount: number;
|
|
13
|
+
estimate: number | ((index: number) => number);
|
|
14
|
+
}
|
|
15
|
+
interface MeasureCache {
|
|
16
|
+
readonly itemCount: number;
|
|
17
|
+
/** Сколько строк уже было измерено. */
|
|
18
|
+
readonly measuredCount: number;
|
|
19
|
+
setItemCount(count: number): void;
|
|
20
|
+
setEstimate(estimate: number | ((index: number) => number)): void;
|
|
21
|
+
/** Записать измеренную высоту. Возвращает `true`, если значение изменилось. */
|
|
22
|
+
setMeasuredHeight(index: number, height: number): boolean;
|
|
23
|
+
/** Сбросить измерение для одной строки. */
|
|
24
|
+
clearMeasured(index: number): boolean;
|
|
25
|
+
/** Сбросить все измерения. */
|
|
26
|
+
reset(): void;
|
|
27
|
+
hasMeasured(index: number): boolean;
|
|
28
|
+
getMeasuredHeight(index: number): number | undefined;
|
|
29
|
+
/** Эстимейт высоты — без учёта измерений. */
|
|
30
|
+
getEstimatedSize(index: number): number;
|
|
31
|
+
/** Эффективная высота строки (измерение или эстимейт). */
|
|
32
|
+
getItemSize(index: number): number;
|
|
33
|
+
getItemOffset(index: number): number;
|
|
34
|
+
getItemAtOffset(offset: number): number;
|
|
35
|
+
getTotalHeight(): number;
|
|
36
|
+
subscribe(listener: MeasureCacheListener): () => void;
|
|
37
|
+
}
|
|
38
|
+
declare function createMeasureCache(options: MeasureCacheOptions): MeasureCache;
|
|
39
|
+
|
|
40
|
+
interface VirtualRange {
|
|
41
|
+
startIndex: number;
|
|
42
|
+
endIndex: number;
|
|
43
|
+
offsetTop: number;
|
|
44
|
+
totalHeight: number;
|
|
45
|
+
visibleItems: VirtualItem[];
|
|
46
|
+
}
|
|
47
|
+
interface VirtualItem {
|
|
48
|
+
index: number;
|
|
49
|
+
offsetTop: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}
|
|
52
|
+
/** Событие инвалидации, эмитимое VirtualEngine подписчикам. */
|
|
53
|
+
type VirtualEngineEvent = 'measure' | 'reset' | 'resize';
|
|
54
|
+
type VirtualEngineListener = (event: VirtualEngineEvent) => void;
|
|
55
|
+
interface VirtualEngineOptions {
|
|
56
|
+
itemCount: number;
|
|
57
|
+
itemHeight: number | ((index: number) => number);
|
|
58
|
+
overscan?: number;
|
|
59
|
+
}
|
|
60
|
+
interface VirtualEngine {
|
|
61
|
+
readonly itemCount: number;
|
|
62
|
+
readonly totalHeight: number;
|
|
63
|
+
/** Сколько строк прошли через `setMeasuredHeight`. */
|
|
64
|
+
readonly measuredCount: number;
|
|
65
|
+
setItemCount(count: number): void;
|
|
66
|
+
setItemHeight(height: number | ((index: number) => number)): void;
|
|
67
|
+
/**
|
|
68
|
+
* Записать реально измеренную (через ResizeObserver) высоту строки.
|
|
69
|
+
* Возвращает `true`, если значение изменилось — полезно для контроля
|
|
70
|
+
* количества `setState` в адаптере.
|
|
71
|
+
*/
|
|
72
|
+
setMeasuredHeight(index: number, height: number): boolean;
|
|
73
|
+
/** Сбросить измерение для одной строки (высота вернётся к эстимейту). */
|
|
74
|
+
clearMeasuredHeight(index: number): boolean;
|
|
75
|
+
/** Сбросить все измерения (например, при resize контейнера / смене темы). */
|
|
76
|
+
resetMeasurements(): void;
|
|
77
|
+
/** Был ли индекс уже измерён. */
|
|
78
|
+
hasMeasured(index: number): boolean;
|
|
79
|
+
/** Эстимейт высоты строки без учёта измерений. */
|
|
80
|
+
getEstimatedSize(index: number): number;
|
|
81
|
+
/** Эффективная высота строки — измерение, если есть, иначе эстимейт. */
|
|
82
|
+
getItemSize(index: number): number;
|
|
83
|
+
getRange(scrollTop: number, viewportHeight: number): VirtualRange;
|
|
84
|
+
getItemOffset(index: number): number;
|
|
85
|
+
getItemAtOffset(offset: number): number;
|
|
86
|
+
scrollToIndex(index: number, viewportHeight: number, align?: 'start' | 'center' | 'end'): number;
|
|
87
|
+
/**
|
|
88
|
+
* Подписаться на инвалидации (resize / measure / reset). Вызывается
|
|
89
|
+
* только после реального изменения — повторные `setMeasuredHeight`
|
|
90
|
+
* с тем же значением событие не эмитят.
|
|
91
|
+
*/
|
|
92
|
+
subscribe(listener: VirtualEngineListener): () => void;
|
|
93
|
+
}
|
|
94
|
+
declare function createVirtual(options: VirtualEngineOptions): VirtualEngine;
|
|
95
|
+
|
|
96
|
+
export { type MeasureCache, type MeasureCacheEvent, type MeasureCacheListener, type MeasureCacheOptions, type VirtualEngine, type VirtualEngineEvent, type VirtualEngineListener, type VirtualEngineOptions, type VirtualItem, type VirtualRange, createMeasureCache, createVirtual };
|
package/dist/virtual.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|