react-virtuoso 0.0.0-development
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 +102 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +967 -0
- package/dist/index.mjs +3803 -0
- package/dist/react-urx/index.d.ts +111 -0
- package/dist/urx/index.d.ts +7 -0
- package/package.json +107 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3803 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom";
|
|
3
|
+
const PUBLISH = 0;
|
|
4
|
+
const SUBSCRIBE = 1;
|
|
5
|
+
const RESET = 2;
|
|
6
|
+
const VALUE = 4;
|
|
7
|
+
function compose(a, b) {
|
|
8
|
+
return (arg) => a(b(arg));
|
|
9
|
+
}
|
|
10
|
+
function thrush(arg, proc) {
|
|
11
|
+
return proc(arg);
|
|
12
|
+
}
|
|
13
|
+
function curry2to1(proc, arg1) {
|
|
14
|
+
return (arg2) => proc(arg1, arg2);
|
|
15
|
+
}
|
|
16
|
+
function curry1to0(proc, arg) {
|
|
17
|
+
return () => proc(arg);
|
|
18
|
+
}
|
|
19
|
+
function tap(arg, proc) {
|
|
20
|
+
proc(arg);
|
|
21
|
+
return arg;
|
|
22
|
+
}
|
|
23
|
+
function tup(...args) {
|
|
24
|
+
return args;
|
|
25
|
+
}
|
|
26
|
+
function call(proc) {
|
|
27
|
+
proc();
|
|
28
|
+
}
|
|
29
|
+
function always(value) {
|
|
30
|
+
return () => value;
|
|
31
|
+
}
|
|
32
|
+
function joinProc(...procs) {
|
|
33
|
+
return () => {
|
|
34
|
+
procs.map(call);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function noop() {
|
|
38
|
+
}
|
|
39
|
+
function subscribe(emitter, subscription) {
|
|
40
|
+
return emitter(SUBSCRIBE, subscription);
|
|
41
|
+
}
|
|
42
|
+
function publish(publisher, value) {
|
|
43
|
+
publisher(PUBLISH, value);
|
|
44
|
+
}
|
|
45
|
+
function reset(emitter) {
|
|
46
|
+
emitter(RESET);
|
|
47
|
+
}
|
|
48
|
+
function getValue(depot) {
|
|
49
|
+
return depot(VALUE);
|
|
50
|
+
}
|
|
51
|
+
function connect(emitter, publisher) {
|
|
52
|
+
return subscribe(emitter, curry2to1(publisher, PUBLISH));
|
|
53
|
+
}
|
|
54
|
+
function handleNext(emitter, subscription) {
|
|
55
|
+
const unsub = emitter(SUBSCRIBE, (value) => {
|
|
56
|
+
unsub();
|
|
57
|
+
subscription(value);
|
|
58
|
+
});
|
|
59
|
+
return unsub;
|
|
60
|
+
}
|
|
61
|
+
function stream() {
|
|
62
|
+
const subscriptions = [];
|
|
63
|
+
return (action, arg) => {
|
|
64
|
+
switch (action) {
|
|
65
|
+
case RESET:
|
|
66
|
+
subscriptions.splice(0, subscriptions.length);
|
|
67
|
+
return;
|
|
68
|
+
case SUBSCRIBE:
|
|
69
|
+
subscriptions.push(arg);
|
|
70
|
+
return () => {
|
|
71
|
+
const indexOf = subscriptions.indexOf(arg);
|
|
72
|
+
if (indexOf > -1) {
|
|
73
|
+
subscriptions.splice(indexOf, 1);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
case PUBLISH:
|
|
77
|
+
subscriptions.slice().forEach((subscription) => {
|
|
78
|
+
subscription(arg);
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`unrecognized action ${action}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function statefulStream(initial) {
|
|
87
|
+
let value = initial;
|
|
88
|
+
const innerSubject = stream();
|
|
89
|
+
return (action, arg) => {
|
|
90
|
+
switch (action) {
|
|
91
|
+
case SUBSCRIBE:
|
|
92
|
+
const subscription = arg;
|
|
93
|
+
subscription(value);
|
|
94
|
+
break;
|
|
95
|
+
case PUBLISH:
|
|
96
|
+
value = arg;
|
|
97
|
+
break;
|
|
98
|
+
case VALUE:
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
return innerSubject(action, arg);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function eventHandler(emitter) {
|
|
105
|
+
let unsub;
|
|
106
|
+
let currentSubscription;
|
|
107
|
+
const cleanup = () => unsub && unsub();
|
|
108
|
+
return function(action, subscription) {
|
|
109
|
+
switch (action) {
|
|
110
|
+
case SUBSCRIBE:
|
|
111
|
+
if (subscription) {
|
|
112
|
+
if (currentSubscription === subscription) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
cleanup();
|
|
116
|
+
currentSubscription = subscription;
|
|
117
|
+
unsub = subscribe(emitter, subscription);
|
|
118
|
+
return unsub;
|
|
119
|
+
} else {
|
|
120
|
+
cleanup();
|
|
121
|
+
return noop;
|
|
122
|
+
}
|
|
123
|
+
case RESET:
|
|
124
|
+
cleanup();
|
|
125
|
+
currentSubscription = null;
|
|
126
|
+
return;
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`unrecognized action ${action}`);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function streamFromEmitter(emitter) {
|
|
133
|
+
return tap(stream(), (stream2) => connect(emitter, stream2));
|
|
134
|
+
}
|
|
135
|
+
function statefulStreamFromEmitter(emitter, initial) {
|
|
136
|
+
return tap(statefulStream(initial), (stream2) => connect(emitter, stream2));
|
|
137
|
+
}
|
|
138
|
+
function combineOperators(...operators) {
|
|
139
|
+
return (subscriber) => {
|
|
140
|
+
return operators.reduceRight(thrush, subscriber);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function pipe(source, ...operators) {
|
|
144
|
+
const project = combineOperators(...operators);
|
|
145
|
+
return (action, subscription) => {
|
|
146
|
+
switch (action) {
|
|
147
|
+
case SUBSCRIBE:
|
|
148
|
+
return subscribe(source, project(subscription));
|
|
149
|
+
case RESET:
|
|
150
|
+
reset(source);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function defaultComparator(previous, next) {
|
|
156
|
+
return previous === next;
|
|
157
|
+
}
|
|
158
|
+
function distinctUntilChanged(comparator = defaultComparator) {
|
|
159
|
+
let current;
|
|
160
|
+
return (done) => (next) => {
|
|
161
|
+
if (!comparator(current, next)) {
|
|
162
|
+
current = next;
|
|
163
|
+
done(next);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function filter(predicate) {
|
|
168
|
+
return (done) => (value) => {
|
|
169
|
+
predicate(value) && done(value);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function map(project) {
|
|
173
|
+
return (done) => compose(done, project);
|
|
174
|
+
}
|
|
175
|
+
function mapTo(value) {
|
|
176
|
+
return (done) => () => done(value);
|
|
177
|
+
}
|
|
178
|
+
function scan(scanner, initial) {
|
|
179
|
+
return (done) => (value) => done(initial = scanner(initial, value));
|
|
180
|
+
}
|
|
181
|
+
function skip(times) {
|
|
182
|
+
return (done) => (value) => {
|
|
183
|
+
times > 0 ? times-- : done(value);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function throttleTime(interval) {
|
|
187
|
+
let currentValue;
|
|
188
|
+
let timeout;
|
|
189
|
+
return (done) => (value) => {
|
|
190
|
+
currentValue = value;
|
|
191
|
+
if (timeout) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
timeout = setTimeout(() => {
|
|
195
|
+
timeout = void 0;
|
|
196
|
+
done(currentValue);
|
|
197
|
+
}, interval);
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function debounceTime(interval) {
|
|
201
|
+
let currentValue;
|
|
202
|
+
let timeout;
|
|
203
|
+
return (done) => (value) => {
|
|
204
|
+
currentValue = value;
|
|
205
|
+
if (timeout) {
|
|
206
|
+
clearTimeout(timeout);
|
|
207
|
+
}
|
|
208
|
+
timeout = setTimeout(() => {
|
|
209
|
+
done(currentValue);
|
|
210
|
+
}, interval);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function withLatestFrom(...sources) {
|
|
214
|
+
const values = new Array(sources.length);
|
|
215
|
+
let called = 0;
|
|
216
|
+
let pendingCall = null;
|
|
217
|
+
const allCalled = Math.pow(2, sources.length) - 1;
|
|
218
|
+
sources.forEach((source, index) => {
|
|
219
|
+
const bit = Math.pow(2, index);
|
|
220
|
+
subscribe(source, (value) => {
|
|
221
|
+
const prevCalled = called;
|
|
222
|
+
called = called | bit;
|
|
223
|
+
values[index] = value;
|
|
224
|
+
if (prevCalled !== allCalled && called === allCalled && pendingCall) {
|
|
225
|
+
pendingCall();
|
|
226
|
+
pendingCall = null;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
return (done) => (value) => {
|
|
231
|
+
const call2 = () => done([value].concat(values));
|
|
232
|
+
if (called === allCalled) {
|
|
233
|
+
call2();
|
|
234
|
+
} else {
|
|
235
|
+
pendingCall = call2;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function merge(...sources) {
|
|
240
|
+
return function(action, subscription) {
|
|
241
|
+
switch (action) {
|
|
242
|
+
case SUBSCRIBE:
|
|
243
|
+
return joinProc(...sources.map((source) => subscribe(source, subscription)));
|
|
244
|
+
case RESET:
|
|
245
|
+
return;
|
|
246
|
+
default:
|
|
247
|
+
throw new Error(`unrecognized action ${action}`);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function duc(source, comparator = defaultComparator) {
|
|
252
|
+
return pipe(source, distinctUntilChanged(comparator));
|
|
253
|
+
}
|
|
254
|
+
function combineLatest(...emitters) {
|
|
255
|
+
const innerSubject = stream();
|
|
256
|
+
const values = new Array(emitters.length);
|
|
257
|
+
let called = 0;
|
|
258
|
+
const allCalled = Math.pow(2, emitters.length) - 1;
|
|
259
|
+
emitters.forEach((source, index) => {
|
|
260
|
+
const bit = Math.pow(2, index);
|
|
261
|
+
subscribe(source, (value) => {
|
|
262
|
+
values[index] = value;
|
|
263
|
+
called = called | bit;
|
|
264
|
+
if (called === allCalled) {
|
|
265
|
+
publish(innerSubject, values);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
return function(action, subscription) {
|
|
270
|
+
switch (action) {
|
|
271
|
+
case SUBSCRIBE:
|
|
272
|
+
if (called === allCalled) {
|
|
273
|
+
subscription(values);
|
|
274
|
+
}
|
|
275
|
+
return subscribe(innerSubject, subscription);
|
|
276
|
+
case RESET:
|
|
277
|
+
return reset(innerSubject);
|
|
278
|
+
default:
|
|
279
|
+
throw new Error(`unrecognized action ${action}`);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function system(constructor, dependencies = [], { singleton } = { singleton: true }) {
|
|
284
|
+
return {
|
|
285
|
+
id: id(),
|
|
286
|
+
constructor,
|
|
287
|
+
dependencies,
|
|
288
|
+
singleton
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const id = () => Symbol();
|
|
292
|
+
function init(systemSpec) {
|
|
293
|
+
const singletons = /* @__PURE__ */ new Map();
|
|
294
|
+
const _init = ({ id: id2, constructor, dependencies, singleton }) => {
|
|
295
|
+
if (singleton && singletons.has(id2)) {
|
|
296
|
+
return singletons.get(id2);
|
|
297
|
+
}
|
|
298
|
+
const system2 = constructor(dependencies.map((e) => _init(e)));
|
|
299
|
+
if (singleton) {
|
|
300
|
+
singletons.set(id2, system2);
|
|
301
|
+
}
|
|
302
|
+
return system2;
|
|
303
|
+
};
|
|
304
|
+
return _init(systemSpec);
|
|
305
|
+
}
|
|
306
|
+
function omit(keys, obj) {
|
|
307
|
+
const result = {};
|
|
308
|
+
const index = {};
|
|
309
|
+
let idx = 0;
|
|
310
|
+
const len = keys.length;
|
|
311
|
+
while (idx < len) {
|
|
312
|
+
index[keys[idx]] = 1;
|
|
313
|
+
idx += 1;
|
|
314
|
+
}
|
|
315
|
+
for (const prop in obj) {
|
|
316
|
+
if (!index.hasOwnProperty(prop)) {
|
|
317
|
+
result[prop] = obj[prop];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
const useIsomorphicLayoutEffect$2 = typeof document !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
323
|
+
function systemToComponent(systemSpec, map2, Root) {
|
|
324
|
+
const requiredPropNames = Object.keys(map2.required || {});
|
|
325
|
+
const optionalPropNames = Object.keys(map2.optional || {});
|
|
326
|
+
const methodNames = Object.keys(map2.methods || {});
|
|
327
|
+
const eventNames = Object.keys(map2.events || {});
|
|
328
|
+
const Context = React.createContext({});
|
|
329
|
+
function applyPropsToSystem(system2, props) {
|
|
330
|
+
if (system2["propsReady"]) {
|
|
331
|
+
publish(system2["propsReady"], false);
|
|
332
|
+
}
|
|
333
|
+
for (const requiredPropName of requiredPropNames) {
|
|
334
|
+
const stream2 = system2[map2.required[requiredPropName]];
|
|
335
|
+
publish(stream2, props[requiredPropName]);
|
|
336
|
+
}
|
|
337
|
+
for (const optionalPropName of optionalPropNames) {
|
|
338
|
+
if (optionalPropName in props) {
|
|
339
|
+
const stream2 = system2[map2.optional[optionalPropName]];
|
|
340
|
+
publish(stream2, props[optionalPropName]);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (system2["propsReady"]) {
|
|
344
|
+
publish(system2["propsReady"], true);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function buildMethods(system2) {
|
|
348
|
+
return methodNames.reduce((acc, methodName) => {
|
|
349
|
+
acc[methodName] = (value) => {
|
|
350
|
+
const stream2 = system2[map2.methods[methodName]];
|
|
351
|
+
publish(stream2, value);
|
|
352
|
+
};
|
|
353
|
+
return acc;
|
|
354
|
+
}, {});
|
|
355
|
+
}
|
|
356
|
+
function buildEventHandlers(system2) {
|
|
357
|
+
return eventNames.reduce((handlers, eventName) => {
|
|
358
|
+
handlers[eventName] = eventHandler(system2[map2.events[eventName]]);
|
|
359
|
+
return handlers;
|
|
360
|
+
}, {});
|
|
361
|
+
}
|
|
362
|
+
const Component = React.forwardRef((propsWithChildren, ref) => {
|
|
363
|
+
const { children, ...props } = propsWithChildren;
|
|
364
|
+
const [system2] = React.useState(() => {
|
|
365
|
+
return tap(init(systemSpec), (system22) => applyPropsToSystem(system22, props));
|
|
366
|
+
});
|
|
367
|
+
const [handlers] = React.useState(curry1to0(buildEventHandlers, system2));
|
|
368
|
+
useIsomorphicLayoutEffect$2(() => {
|
|
369
|
+
for (const eventName of eventNames) {
|
|
370
|
+
if (eventName in props) {
|
|
371
|
+
subscribe(handlers[eventName], props[eventName]);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return () => {
|
|
375
|
+
Object.values(handlers).map(reset);
|
|
376
|
+
};
|
|
377
|
+
}, [props, handlers, system2]);
|
|
378
|
+
useIsomorphicLayoutEffect$2(() => {
|
|
379
|
+
applyPropsToSystem(system2, props);
|
|
380
|
+
});
|
|
381
|
+
React.useImperativeHandle(ref, always(buildMethods(system2)));
|
|
382
|
+
return React.createElement(
|
|
383
|
+
Context.Provider,
|
|
384
|
+
{ value: system2 },
|
|
385
|
+
Root ? React.createElement(
|
|
386
|
+
Root,
|
|
387
|
+
omit([...requiredPropNames, ...optionalPropNames, ...eventNames], props),
|
|
388
|
+
children
|
|
389
|
+
) : children
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
const usePublisher2 = (key) => {
|
|
393
|
+
return React.useCallback(curry2to1(publish, React.useContext(Context)[key]), [key]);
|
|
394
|
+
};
|
|
395
|
+
const useEmitterValue2 = (key) => {
|
|
396
|
+
const system2 = React.useContext(Context);
|
|
397
|
+
const source = system2[key];
|
|
398
|
+
const [value, setValue] = React.useState(curry1to0(getValue, source));
|
|
399
|
+
useIsomorphicLayoutEffect$2(
|
|
400
|
+
() => subscribe(source, (next) => {
|
|
401
|
+
if (next !== value) {
|
|
402
|
+
setValue(always(next));
|
|
403
|
+
}
|
|
404
|
+
}),
|
|
405
|
+
[source, value]
|
|
406
|
+
);
|
|
407
|
+
return value;
|
|
408
|
+
};
|
|
409
|
+
const useEmitter2 = (key, callback) => {
|
|
410
|
+
const context = React.useContext(Context);
|
|
411
|
+
const source = context[key];
|
|
412
|
+
useIsomorphicLayoutEffect$2(() => subscribe(source, callback), [callback, source]);
|
|
413
|
+
};
|
|
414
|
+
return {
|
|
415
|
+
Component,
|
|
416
|
+
usePublisher: usePublisher2,
|
|
417
|
+
useEmitterValue: useEmitterValue2,
|
|
418
|
+
useEmitter: useEmitter2
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
422
|
+
const useIsomorphicLayoutEffect$1 = useIsomorphicLayoutEffect;
|
|
423
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
424
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
425
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
426
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
427
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
428
|
+
return LogLevel2;
|
|
429
|
+
})(LogLevel || {});
|
|
430
|
+
const CONSOLE_METHOD_MAP = {
|
|
431
|
+
[0]: "debug",
|
|
432
|
+
[1]: "log",
|
|
433
|
+
[2]: "warn",
|
|
434
|
+
[3]: "error"
|
|
435
|
+
};
|
|
436
|
+
const getGlobalThis = () => typeof globalThis === "undefined" ? window : globalThis;
|
|
437
|
+
const loggerSystem = system(
|
|
438
|
+
() => {
|
|
439
|
+
const logLevel = statefulStream(3);
|
|
440
|
+
const log = statefulStream((label, message, level = 1) => {
|
|
441
|
+
const currentLevel = getGlobalThis()["VIRTUOSO_LOG_LEVEL"] ?? getValue(logLevel);
|
|
442
|
+
if (level >= currentLevel) {
|
|
443
|
+
console[CONSOLE_METHOD_MAP[level]](
|
|
444
|
+
"%creact-virtuoso: %c%s %o",
|
|
445
|
+
"color: #0253b3; font-weight: bold",
|
|
446
|
+
"color: initial",
|
|
447
|
+
label,
|
|
448
|
+
message
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
return {
|
|
453
|
+
log,
|
|
454
|
+
logLevel
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
[],
|
|
458
|
+
{ singleton: true }
|
|
459
|
+
);
|
|
460
|
+
function useSizeWithElRef(callback, enabled = true) {
|
|
461
|
+
const ref = React.useRef(null);
|
|
462
|
+
let callbackRef = (_el) => {
|
|
463
|
+
};
|
|
464
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
465
|
+
const observer = new ResizeObserver((entries) => {
|
|
466
|
+
const element = entries[0].target;
|
|
467
|
+
if (element.offsetParent !== null) {
|
|
468
|
+
callback(element);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
callbackRef = (elRef) => {
|
|
472
|
+
if (elRef && enabled) {
|
|
473
|
+
observer.observe(elRef);
|
|
474
|
+
ref.current = elRef;
|
|
475
|
+
} else {
|
|
476
|
+
if (ref.current) {
|
|
477
|
+
observer.unobserve(ref.current);
|
|
478
|
+
}
|
|
479
|
+
ref.current = null;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return { ref, callbackRef };
|
|
484
|
+
}
|
|
485
|
+
function useSize(callback, enabled = true) {
|
|
486
|
+
return useSizeWithElRef(callback, enabled).callbackRef;
|
|
487
|
+
}
|
|
488
|
+
function useChangedListContentsSizes(callback, itemSize, enabled, scrollContainerStateCallback, log, gap, customScrollParent) {
|
|
489
|
+
return useSizeWithElRef((el) => {
|
|
490
|
+
const ranges = getChangedChildSizes(el.children, itemSize, "offsetHeight", log);
|
|
491
|
+
let scrollableElement = el.parentElement;
|
|
492
|
+
while (!scrollableElement.dataset["virtuosoScroller"]) {
|
|
493
|
+
scrollableElement = scrollableElement.parentElement;
|
|
494
|
+
}
|
|
495
|
+
const windowScrolling = scrollableElement.lastElementChild.dataset["viewportType"] === "window";
|
|
496
|
+
const scrollTop = customScrollParent ? customScrollParent.scrollTop : windowScrolling ? window.pageYOffset || document.documentElement.scrollTop : scrollableElement.scrollTop;
|
|
497
|
+
const scrollHeight = customScrollParent ? customScrollParent.scrollHeight : windowScrolling ? document.documentElement.scrollHeight : scrollableElement.scrollHeight;
|
|
498
|
+
const viewportHeight = customScrollParent ? customScrollParent.offsetHeight : windowScrolling ? window.innerHeight : scrollableElement.offsetHeight;
|
|
499
|
+
scrollContainerStateCallback({
|
|
500
|
+
scrollTop: Math.max(scrollTop, 0),
|
|
501
|
+
scrollHeight,
|
|
502
|
+
viewportHeight
|
|
503
|
+
});
|
|
504
|
+
gap == null ? void 0 : gap(resolveGapValue$1("row-gap", getComputedStyle(el).rowGap, log));
|
|
505
|
+
if (ranges !== null) {
|
|
506
|
+
callback(ranges);
|
|
507
|
+
}
|
|
508
|
+
}, enabled);
|
|
509
|
+
}
|
|
510
|
+
function getChangedChildSizes(children, itemSize, field, log) {
|
|
511
|
+
const length = children.length;
|
|
512
|
+
if (length === 0) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
const results = [];
|
|
516
|
+
for (let i = 0; i < length; i++) {
|
|
517
|
+
const child = children.item(i);
|
|
518
|
+
if (!child || child.dataset.index === void 0) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const index = parseInt(child.dataset.index);
|
|
522
|
+
const knownSize = parseFloat(child.dataset.knownSize);
|
|
523
|
+
const size = itemSize(child, field);
|
|
524
|
+
if (size === 0) {
|
|
525
|
+
log("Zero-sized element, this should not happen", { child }, LogLevel.ERROR);
|
|
526
|
+
}
|
|
527
|
+
if (size === knownSize) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const lastResult = results[results.length - 1];
|
|
531
|
+
if (results.length === 0 || lastResult.size !== size || lastResult.endIndex !== index - 1) {
|
|
532
|
+
results.push({ startIndex: index, endIndex: index, size });
|
|
533
|
+
} else {
|
|
534
|
+
results[results.length - 1].endIndex++;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return results;
|
|
538
|
+
}
|
|
539
|
+
function resolveGapValue$1(property, value, log) {
|
|
540
|
+
if (value !== "normal" && !(value == null ? void 0 : value.endsWith("px"))) {
|
|
541
|
+
log(`${property} was not resolved to pixel value correctly`, value, LogLevel.WARN);
|
|
542
|
+
}
|
|
543
|
+
if (value === "normal") {
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
return parseInt(value ?? "0", 10);
|
|
547
|
+
}
|
|
548
|
+
function correctItemSize(el, dimension) {
|
|
549
|
+
return Math.round(el.getBoundingClientRect()[dimension]);
|
|
550
|
+
}
|
|
551
|
+
function approximatelyEqual(num1, num2) {
|
|
552
|
+
return Math.abs(num1 - num2) < 1.01;
|
|
553
|
+
}
|
|
554
|
+
function useScrollTop(scrollContainerStateCallback, smoothScrollTargetReached, scrollerElement, scrollerRefCallback = noop, customScrollParent) {
|
|
555
|
+
const scrollerRef = React.useRef(null);
|
|
556
|
+
const scrollTopTarget = React.useRef(null);
|
|
557
|
+
const timeoutRef = React.useRef(null);
|
|
558
|
+
const handler = React.useCallback(
|
|
559
|
+
(ev) => {
|
|
560
|
+
const el = ev.target;
|
|
561
|
+
const windowScroll = el === window || el === document;
|
|
562
|
+
const scrollTop = windowScroll ? window.pageYOffset || document.documentElement.scrollTop : el.scrollTop;
|
|
563
|
+
const scrollHeight = windowScroll ? document.documentElement.scrollHeight : el.scrollHeight;
|
|
564
|
+
const viewportHeight = windowScroll ? window.innerHeight : el.offsetHeight;
|
|
565
|
+
const call2 = () => {
|
|
566
|
+
scrollContainerStateCallback({
|
|
567
|
+
scrollTop: Math.max(scrollTop, 0),
|
|
568
|
+
scrollHeight,
|
|
569
|
+
viewportHeight
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
if (ev.suppressFlushSync) {
|
|
573
|
+
call2();
|
|
574
|
+
} else {
|
|
575
|
+
ReactDOM.flushSync(call2);
|
|
576
|
+
}
|
|
577
|
+
if (scrollTopTarget.current !== null) {
|
|
578
|
+
if (scrollTop === scrollTopTarget.current || scrollTop <= 0 || scrollTop === scrollHeight - viewportHeight) {
|
|
579
|
+
scrollTopTarget.current = null;
|
|
580
|
+
smoothScrollTargetReached(true);
|
|
581
|
+
if (timeoutRef.current) {
|
|
582
|
+
clearTimeout(timeoutRef.current);
|
|
583
|
+
timeoutRef.current = null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
[scrollContainerStateCallback, smoothScrollTargetReached]
|
|
589
|
+
);
|
|
590
|
+
React.useEffect(() => {
|
|
591
|
+
const localRef = customScrollParent ? customScrollParent : scrollerRef.current;
|
|
592
|
+
scrollerRefCallback(customScrollParent ? customScrollParent : scrollerRef.current);
|
|
593
|
+
handler({ target: localRef, suppressFlushSync: true });
|
|
594
|
+
localRef.addEventListener("scroll", handler, { passive: true });
|
|
595
|
+
return () => {
|
|
596
|
+
scrollerRefCallback(null);
|
|
597
|
+
localRef.removeEventListener("scroll", handler);
|
|
598
|
+
};
|
|
599
|
+
}, [scrollerRef, handler, scrollerElement, scrollerRefCallback, customScrollParent]);
|
|
600
|
+
function scrollToCallback(location) {
|
|
601
|
+
const scrollerElement2 = scrollerRef.current;
|
|
602
|
+
if (!scrollerElement2 || "offsetHeight" in scrollerElement2 && scrollerElement2.offsetHeight === 0) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const isSmooth = location.behavior === "smooth";
|
|
606
|
+
let offsetHeight;
|
|
607
|
+
let scrollHeight;
|
|
608
|
+
let scrollTop;
|
|
609
|
+
if (scrollerElement2 === window) {
|
|
610
|
+
scrollHeight = Math.max(correctItemSize(document.documentElement, "height"), document.documentElement.scrollHeight);
|
|
611
|
+
offsetHeight = window.innerHeight;
|
|
612
|
+
scrollTop = document.documentElement.scrollTop;
|
|
613
|
+
} else {
|
|
614
|
+
scrollHeight = scrollerElement2.scrollHeight;
|
|
615
|
+
offsetHeight = correctItemSize(scrollerElement2, "height");
|
|
616
|
+
scrollTop = scrollerElement2.scrollTop;
|
|
617
|
+
}
|
|
618
|
+
const maxScrollTop = scrollHeight - offsetHeight;
|
|
619
|
+
location.top = Math.ceil(Math.max(Math.min(maxScrollTop, location.top), 0));
|
|
620
|
+
if (approximatelyEqual(offsetHeight, scrollHeight) || location.top === scrollTop) {
|
|
621
|
+
scrollContainerStateCallback({ scrollTop, scrollHeight, viewportHeight: offsetHeight });
|
|
622
|
+
if (isSmooth) {
|
|
623
|
+
smoothScrollTargetReached(true);
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (isSmooth) {
|
|
628
|
+
scrollTopTarget.current = location.top;
|
|
629
|
+
if (timeoutRef.current) {
|
|
630
|
+
clearTimeout(timeoutRef.current);
|
|
631
|
+
}
|
|
632
|
+
timeoutRef.current = setTimeout(() => {
|
|
633
|
+
timeoutRef.current = null;
|
|
634
|
+
scrollTopTarget.current = null;
|
|
635
|
+
smoothScrollTargetReached(true);
|
|
636
|
+
}, 1e3);
|
|
637
|
+
} else {
|
|
638
|
+
scrollTopTarget.current = null;
|
|
639
|
+
}
|
|
640
|
+
scrollerElement2.scrollTo(location);
|
|
641
|
+
}
|
|
642
|
+
function scrollByCallback(location) {
|
|
643
|
+
scrollerRef.current.scrollBy(location);
|
|
644
|
+
}
|
|
645
|
+
return { scrollerRef, scrollByCallback, scrollToCallback };
|
|
646
|
+
}
|
|
647
|
+
const domIOSystem = system(
|
|
648
|
+
() => {
|
|
649
|
+
const scrollContainerState = stream();
|
|
650
|
+
const scrollTop = stream();
|
|
651
|
+
const deviation = statefulStream(0);
|
|
652
|
+
const smoothScrollTargetReached = stream();
|
|
653
|
+
const statefulScrollTop = statefulStream(0);
|
|
654
|
+
const viewportHeight = stream();
|
|
655
|
+
const scrollHeight = stream();
|
|
656
|
+
const headerHeight = statefulStream(0);
|
|
657
|
+
const fixedHeaderHeight = statefulStream(0);
|
|
658
|
+
const fixedFooterHeight = statefulStream(0);
|
|
659
|
+
const footerHeight = statefulStream(0);
|
|
660
|
+
const scrollTo = stream();
|
|
661
|
+
const scrollBy = stream();
|
|
662
|
+
const scrollingInProgress = statefulStream(false);
|
|
663
|
+
connect(
|
|
664
|
+
pipe(
|
|
665
|
+
scrollContainerState,
|
|
666
|
+
map(({ scrollTop: scrollTop2 }) => scrollTop2)
|
|
667
|
+
),
|
|
668
|
+
scrollTop
|
|
669
|
+
);
|
|
670
|
+
connect(
|
|
671
|
+
pipe(
|
|
672
|
+
scrollContainerState,
|
|
673
|
+
map(({ scrollHeight: scrollHeight2 }) => scrollHeight2)
|
|
674
|
+
),
|
|
675
|
+
scrollHeight
|
|
676
|
+
);
|
|
677
|
+
connect(scrollTop, statefulScrollTop);
|
|
678
|
+
return {
|
|
679
|
+
scrollContainerState,
|
|
680
|
+
scrollTop,
|
|
681
|
+
viewportHeight,
|
|
682
|
+
headerHeight,
|
|
683
|
+
fixedHeaderHeight,
|
|
684
|
+
fixedFooterHeight,
|
|
685
|
+
footerHeight,
|
|
686
|
+
scrollHeight,
|
|
687
|
+
smoothScrollTargetReached,
|
|
688
|
+
scrollTo,
|
|
689
|
+
scrollBy,
|
|
690
|
+
statefulScrollTop,
|
|
691
|
+
deviation,
|
|
692
|
+
scrollingInProgress
|
|
693
|
+
};
|
|
694
|
+
},
|
|
695
|
+
[],
|
|
696
|
+
{ singleton: true }
|
|
697
|
+
);
|
|
698
|
+
const NIL_NODE = { lvl: 0 };
|
|
699
|
+
function newAANode(k, v, lvl, l = NIL_NODE, r = NIL_NODE) {
|
|
700
|
+
return { k, v, lvl, l, r };
|
|
701
|
+
}
|
|
702
|
+
function empty(node) {
|
|
703
|
+
return node === NIL_NODE;
|
|
704
|
+
}
|
|
705
|
+
function newTree() {
|
|
706
|
+
return NIL_NODE;
|
|
707
|
+
}
|
|
708
|
+
function remove(node, key) {
|
|
709
|
+
if (empty(node))
|
|
710
|
+
return NIL_NODE;
|
|
711
|
+
const { k, l, r } = node;
|
|
712
|
+
if (key === k) {
|
|
713
|
+
if (empty(l)) {
|
|
714
|
+
return r;
|
|
715
|
+
} else if (empty(r)) {
|
|
716
|
+
return l;
|
|
717
|
+
} else {
|
|
718
|
+
const [lastKey, lastValue] = last(l);
|
|
719
|
+
return adjust(clone(node, { k: lastKey, v: lastValue, l: deleteLast(l) }));
|
|
720
|
+
}
|
|
721
|
+
} else if (key < k) {
|
|
722
|
+
return adjust(clone(node, { l: remove(l, key) }));
|
|
723
|
+
} else {
|
|
724
|
+
return adjust(clone(node, { r: remove(r, key) }));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function findMaxKeyValue(node, value, field = "k") {
|
|
728
|
+
if (empty(node)) {
|
|
729
|
+
return [-Infinity, void 0];
|
|
730
|
+
}
|
|
731
|
+
if (node[field] === value) {
|
|
732
|
+
return [node.k, node.v];
|
|
733
|
+
}
|
|
734
|
+
if (node[field] < value) {
|
|
735
|
+
const r = findMaxKeyValue(node.r, value, field);
|
|
736
|
+
if (r[0] === -Infinity) {
|
|
737
|
+
return [node.k, node.v];
|
|
738
|
+
} else {
|
|
739
|
+
return r;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return findMaxKeyValue(node.l, value, field);
|
|
743
|
+
}
|
|
744
|
+
function insert(node, k, v) {
|
|
745
|
+
if (empty(node)) {
|
|
746
|
+
return newAANode(k, v, 1);
|
|
747
|
+
}
|
|
748
|
+
if (k === node.k) {
|
|
749
|
+
return clone(node, { k, v });
|
|
750
|
+
} else if (k < node.k) {
|
|
751
|
+
return rebalance(clone(node, { l: insert(node.l, k, v) }));
|
|
752
|
+
} else {
|
|
753
|
+
return rebalance(clone(node, { r: insert(node.r, k, v) }));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function walkWithin(node, start, end) {
|
|
757
|
+
if (empty(node)) {
|
|
758
|
+
return [];
|
|
759
|
+
}
|
|
760
|
+
const { k, v, l, r } = node;
|
|
761
|
+
let result = [];
|
|
762
|
+
if (k > start) {
|
|
763
|
+
result = result.concat(walkWithin(l, start, end));
|
|
764
|
+
}
|
|
765
|
+
if (k >= start && k <= end) {
|
|
766
|
+
result.push({ k, v });
|
|
767
|
+
}
|
|
768
|
+
if (k <= end) {
|
|
769
|
+
result = result.concat(walkWithin(r, start, end));
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
function walk(node) {
|
|
774
|
+
if (empty(node)) {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
return [...walk(node.l), { k: node.k, v: node.v }, ...walk(node.r)];
|
|
778
|
+
}
|
|
779
|
+
function last(node) {
|
|
780
|
+
return empty(node.r) ? [node.k, node.v] : last(node.r);
|
|
781
|
+
}
|
|
782
|
+
function deleteLast(node) {
|
|
783
|
+
return empty(node.r) ? node.l : adjust(clone(node, { r: deleteLast(node.r) }));
|
|
784
|
+
}
|
|
785
|
+
function clone(node, args) {
|
|
786
|
+
return newAANode(
|
|
787
|
+
args.k !== void 0 ? args.k : node.k,
|
|
788
|
+
args.v !== void 0 ? args.v : node.v,
|
|
789
|
+
args.lvl !== void 0 ? args.lvl : node.lvl,
|
|
790
|
+
args.l !== void 0 ? args.l : node.l,
|
|
791
|
+
args.r !== void 0 ? args.r : node.r
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function isSingle(node) {
|
|
795
|
+
return empty(node) || node.lvl > node.r.lvl;
|
|
796
|
+
}
|
|
797
|
+
function rebalance(node) {
|
|
798
|
+
return split(skew(node));
|
|
799
|
+
}
|
|
800
|
+
function adjust(node) {
|
|
801
|
+
const { l, r, lvl } = node;
|
|
802
|
+
if (r.lvl >= lvl - 1 && l.lvl >= lvl - 1) {
|
|
803
|
+
return node;
|
|
804
|
+
} else if (lvl > r.lvl + 1) {
|
|
805
|
+
if (isSingle(l)) {
|
|
806
|
+
return skew(clone(node, { lvl: lvl - 1 }));
|
|
807
|
+
} else {
|
|
808
|
+
if (!empty(l) && !empty(l.r)) {
|
|
809
|
+
return clone(l.r, {
|
|
810
|
+
l: clone(l, { r: l.r.l }),
|
|
811
|
+
r: clone(node, {
|
|
812
|
+
l: l.r.r,
|
|
813
|
+
lvl: lvl - 1
|
|
814
|
+
}),
|
|
815
|
+
lvl
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
throw new Error("Unexpected empty nodes");
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
822
|
+
if (isSingle(node)) {
|
|
823
|
+
return split(clone(node, { lvl: lvl - 1 }));
|
|
824
|
+
} else {
|
|
825
|
+
if (!empty(r) && !empty(r.l)) {
|
|
826
|
+
const rl = r.l;
|
|
827
|
+
const rlvl = isSingle(rl) ? r.lvl - 1 : r.lvl;
|
|
828
|
+
return clone(rl, {
|
|
829
|
+
l: clone(node, {
|
|
830
|
+
r: rl.l,
|
|
831
|
+
lvl: lvl - 1
|
|
832
|
+
}),
|
|
833
|
+
r: split(clone(r, { l: rl.r, lvl: rlvl })),
|
|
834
|
+
lvl: rl.lvl + 1
|
|
835
|
+
});
|
|
836
|
+
} else {
|
|
837
|
+
throw new Error("Unexpected empty nodes");
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function rangesWithin(node, startIndex, endIndex) {
|
|
843
|
+
if (empty(node)) {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
const adjustedStart = findMaxKeyValue(node, startIndex)[0];
|
|
847
|
+
return toRanges(walkWithin(node, adjustedStart, endIndex));
|
|
848
|
+
}
|
|
849
|
+
function arrayToRanges(items, parser) {
|
|
850
|
+
const length = items.length;
|
|
851
|
+
if (length === 0) {
|
|
852
|
+
return [];
|
|
853
|
+
}
|
|
854
|
+
let { index: start, value } = parser(items[0]);
|
|
855
|
+
const result = [];
|
|
856
|
+
for (let i = 1; i < length; i++) {
|
|
857
|
+
const { index: nextIndex, value: nextValue } = parser(items[i]);
|
|
858
|
+
result.push({ start, end: nextIndex - 1, value });
|
|
859
|
+
start = nextIndex;
|
|
860
|
+
value = nextValue;
|
|
861
|
+
}
|
|
862
|
+
result.push({ start, end: Infinity, value });
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
function toRanges(nodes) {
|
|
866
|
+
return arrayToRanges(nodes, ({ k: index, v: value }) => ({ index, value }));
|
|
867
|
+
}
|
|
868
|
+
function split(node) {
|
|
869
|
+
const { r, lvl } = node;
|
|
870
|
+
return !empty(r) && !empty(r.r) && r.lvl === lvl && r.r.lvl === lvl ? clone(r, { l: clone(node, { r: r.l }), lvl: lvl + 1 }) : node;
|
|
871
|
+
}
|
|
872
|
+
function skew(node) {
|
|
873
|
+
const { l } = node;
|
|
874
|
+
return !empty(l) && l.lvl === node.lvl ? clone(l, { r: clone(node, { l: l.r }) }) : node;
|
|
875
|
+
}
|
|
876
|
+
function findIndexOfClosestSmallerOrEqual(items, value, comparator, start = 0) {
|
|
877
|
+
let end = items.length - 1;
|
|
878
|
+
while (start <= end) {
|
|
879
|
+
const index = Math.floor((start + end) / 2);
|
|
880
|
+
const item = items[index];
|
|
881
|
+
const match = comparator(item, value);
|
|
882
|
+
if (match === 0) {
|
|
883
|
+
return index;
|
|
884
|
+
}
|
|
885
|
+
if (match === -1) {
|
|
886
|
+
if (end - start < 2) {
|
|
887
|
+
return index - 1;
|
|
888
|
+
}
|
|
889
|
+
end = index - 1;
|
|
890
|
+
} else {
|
|
891
|
+
if (end === start) {
|
|
892
|
+
return index;
|
|
893
|
+
}
|
|
894
|
+
start = index + 1;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
throw new Error(`Failed binary finding record in array - ${items.join(",")}, searched for ${value}`);
|
|
898
|
+
}
|
|
899
|
+
function findClosestSmallerOrEqual(items, value, comparator) {
|
|
900
|
+
return items[findIndexOfClosestSmallerOrEqual(items, value, comparator)];
|
|
901
|
+
}
|
|
902
|
+
function findRange(items, startValue, endValue, comparator) {
|
|
903
|
+
const startIndex = findIndexOfClosestSmallerOrEqual(items, startValue, comparator);
|
|
904
|
+
const endIndex = findIndexOfClosestSmallerOrEqual(items, endValue, comparator, startIndex);
|
|
905
|
+
return items.slice(startIndex, endIndex + 1);
|
|
906
|
+
}
|
|
907
|
+
const recalcSystem = system(
|
|
908
|
+
() => {
|
|
909
|
+
const recalcInProgress = statefulStream(false);
|
|
910
|
+
return { recalcInProgress };
|
|
911
|
+
},
|
|
912
|
+
[],
|
|
913
|
+
{ singleton: true }
|
|
914
|
+
);
|
|
915
|
+
function rangeIncludes(refRange) {
|
|
916
|
+
const { size, startIndex, endIndex } = refRange;
|
|
917
|
+
return (range) => {
|
|
918
|
+
return range.start === startIndex && (range.end === endIndex || range.end === Infinity) && range.value === size;
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
function insertRanges(sizeTree, ranges) {
|
|
922
|
+
let syncStart = empty(sizeTree) ? 0 : Infinity;
|
|
923
|
+
for (const range of ranges) {
|
|
924
|
+
const { size, startIndex, endIndex } = range;
|
|
925
|
+
syncStart = Math.min(syncStart, startIndex);
|
|
926
|
+
if (empty(sizeTree)) {
|
|
927
|
+
sizeTree = insert(sizeTree, 0, size);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const overlappingRanges = rangesWithin(sizeTree, startIndex - 1, endIndex + 1);
|
|
931
|
+
if (overlappingRanges.some(rangeIncludes(range))) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
let firstPassDone = false;
|
|
935
|
+
let shouldInsert = false;
|
|
936
|
+
for (const { start: rangeStart, end: rangeEnd, value: rangeValue } of overlappingRanges) {
|
|
937
|
+
if (!firstPassDone) {
|
|
938
|
+
shouldInsert = rangeValue !== size;
|
|
939
|
+
firstPassDone = true;
|
|
940
|
+
} else {
|
|
941
|
+
if (endIndex >= rangeStart || size === rangeValue) {
|
|
942
|
+
sizeTree = remove(sizeTree, rangeStart);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (rangeEnd > endIndex && endIndex >= rangeStart) {
|
|
946
|
+
if (rangeValue !== size) {
|
|
947
|
+
sizeTree = insert(sizeTree, endIndex + 1, rangeValue);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (shouldInsert) {
|
|
952
|
+
sizeTree = insert(sizeTree, startIndex, size);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return [sizeTree, syncStart];
|
|
956
|
+
}
|
|
957
|
+
function initialSizeState() {
|
|
958
|
+
return {
|
|
959
|
+
offsetTree: [],
|
|
960
|
+
sizeTree: newTree(),
|
|
961
|
+
groupOffsetTree: newTree(),
|
|
962
|
+
lastIndex: 0,
|
|
963
|
+
lastOffset: 0,
|
|
964
|
+
lastSize: 0,
|
|
965
|
+
groupIndices: []
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function indexComparator({ index: itemIndex }, index) {
|
|
969
|
+
return index === itemIndex ? 0 : index < itemIndex ? -1 : 1;
|
|
970
|
+
}
|
|
971
|
+
function offsetComparator({ offset: itemOffset }, offset) {
|
|
972
|
+
return offset === itemOffset ? 0 : offset < itemOffset ? -1 : 1;
|
|
973
|
+
}
|
|
974
|
+
function offsetPointParser(point) {
|
|
975
|
+
return { index: point.index, value: point };
|
|
976
|
+
}
|
|
977
|
+
function rangesWithinOffsets(tree, startOffset, endOffset, minStartIndex = 0) {
|
|
978
|
+
if (minStartIndex > 0) {
|
|
979
|
+
startOffset = Math.max(startOffset, findClosestSmallerOrEqual(tree, minStartIndex, indexComparator).offset);
|
|
980
|
+
}
|
|
981
|
+
return arrayToRanges(findRange(tree, startOffset, endOffset, offsetComparator), offsetPointParser);
|
|
982
|
+
}
|
|
983
|
+
function createOffsetTree(prevOffsetTree, syncStart, sizeTree, gap) {
|
|
984
|
+
let offsetTree = prevOffsetTree;
|
|
985
|
+
let prevIndex = 0;
|
|
986
|
+
let prevSize = 0;
|
|
987
|
+
let prevOffset = 0;
|
|
988
|
+
let startIndex = 0;
|
|
989
|
+
if (syncStart !== 0) {
|
|
990
|
+
startIndex = findIndexOfClosestSmallerOrEqual(offsetTree, syncStart - 1, indexComparator);
|
|
991
|
+
const offsetInfo = offsetTree[startIndex];
|
|
992
|
+
prevOffset = offsetInfo.offset;
|
|
993
|
+
const kv = findMaxKeyValue(sizeTree, syncStart - 1);
|
|
994
|
+
prevIndex = kv[0];
|
|
995
|
+
prevSize = kv[1];
|
|
996
|
+
if (offsetTree.length && offsetTree[startIndex].size === findMaxKeyValue(sizeTree, syncStart)[1]) {
|
|
997
|
+
startIndex -= 1;
|
|
998
|
+
}
|
|
999
|
+
offsetTree = offsetTree.slice(0, startIndex + 1);
|
|
1000
|
+
} else {
|
|
1001
|
+
offsetTree = [];
|
|
1002
|
+
}
|
|
1003
|
+
for (const { start: startIndex2, value } of rangesWithin(sizeTree, syncStart, Infinity)) {
|
|
1004
|
+
const indexOffset = startIndex2 - prevIndex;
|
|
1005
|
+
const aOffset = indexOffset * prevSize + prevOffset + indexOffset * gap;
|
|
1006
|
+
offsetTree.push({
|
|
1007
|
+
offset: aOffset,
|
|
1008
|
+
size: value,
|
|
1009
|
+
index: startIndex2
|
|
1010
|
+
});
|
|
1011
|
+
prevIndex = startIndex2;
|
|
1012
|
+
prevOffset = aOffset;
|
|
1013
|
+
prevSize = value;
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
offsetTree,
|
|
1017
|
+
lastIndex: prevIndex,
|
|
1018
|
+
lastOffset: prevOffset,
|
|
1019
|
+
lastSize: prevSize
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function sizeStateReducer(state, [ranges, groupIndices, log, gap]) {
|
|
1023
|
+
if (ranges.length > 0) {
|
|
1024
|
+
log("received item sizes", ranges, LogLevel.DEBUG);
|
|
1025
|
+
}
|
|
1026
|
+
const sizeTree = state.sizeTree;
|
|
1027
|
+
let newSizeTree = sizeTree;
|
|
1028
|
+
let syncStart = 0;
|
|
1029
|
+
if (groupIndices.length > 0 && empty(sizeTree) && ranges.length === 2) {
|
|
1030
|
+
const groupSize = ranges[0].size;
|
|
1031
|
+
const itemSize = ranges[1].size;
|
|
1032
|
+
newSizeTree = groupIndices.reduce((tree, groupIndex) => {
|
|
1033
|
+
return insert(insert(tree, groupIndex, groupSize), groupIndex + 1, itemSize);
|
|
1034
|
+
}, newSizeTree);
|
|
1035
|
+
} else {
|
|
1036
|
+
[newSizeTree, syncStart] = insertRanges(newSizeTree, ranges);
|
|
1037
|
+
}
|
|
1038
|
+
if (newSizeTree === sizeTree) {
|
|
1039
|
+
return state;
|
|
1040
|
+
}
|
|
1041
|
+
const { offsetTree: newOffsetTree, lastIndex, lastSize, lastOffset } = createOffsetTree(state.offsetTree, syncStart, newSizeTree, gap);
|
|
1042
|
+
return {
|
|
1043
|
+
sizeTree: newSizeTree,
|
|
1044
|
+
offsetTree: newOffsetTree,
|
|
1045
|
+
lastIndex,
|
|
1046
|
+
lastOffset,
|
|
1047
|
+
lastSize,
|
|
1048
|
+
groupOffsetTree: groupIndices.reduce((tree, index) => {
|
|
1049
|
+
return insert(tree, index, offsetOf(index, newOffsetTree, gap));
|
|
1050
|
+
}, newTree()),
|
|
1051
|
+
groupIndices
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function offsetOf(index, tree, gap) {
|
|
1055
|
+
if (tree.length === 0) {
|
|
1056
|
+
return 0;
|
|
1057
|
+
}
|
|
1058
|
+
const { offset, index: startIndex, size } = findClosestSmallerOrEqual(tree, index, indexComparator);
|
|
1059
|
+
const itemCount = index - startIndex;
|
|
1060
|
+
const top = size * itemCount + (itemCount - 1) * gap + offset;
|
|
1061
|
+
return top > 0 ? top + gap : top;
|
|
1062
|
+
}
|
|
1063
|
+
function isGroupLocation(location) {
|
|
1064
|
+
return typeof location.groupIndex !== "undefined";
|
|
1065
|
+
}
|
|
1066
|
+
function originalIndexFromLocation(location, sizes, lastIndex) {
|
|
1067
|
+
if (isGroupLocation(location)) {
|
|
1068
|
+
return sizes.groupIndices[location.groupIndex] + 1;
|
|
1069
|
+
} else {
|
|
1070
|
+
const numericIndex = location.index === "LAST" ? lastIndex : location.index;
|
|
1071
|
+
let result = originalIndexFromItemIndex(numericIndex, sizes);
|
|
1072
|
+
result = Math.max(0, result, Math.min(lastIndex, result));
|
|
1073
|
+
return result;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function originalIndexFromItemIndex(itemIndex, sizes) {
|
|
1077
|
+
if (!hasGroups(sizes)) {
|
|
1078
|
+
return itemIndex;
|
|
1079
|
+
}
|
|
1080
|
+
let groupOffset = 0;
|
|
1081
|
+
while (sizes.groupIndices[groupOffset] <= itemIndex + groupOffset) {
|
|
1082
|
+
groupOffset++;
|
|
1083
|
+
}
|
|
1084
|
+
return itemIndex + groupOffset;
|
|
1085
|
+
}
|
|
1086
|
+
function hasGroups(sizes) {
|
|
1087
|
+
return !empty(sizes.groupOffsetTree);
|
|
1088
|
+
}
|
|
1089
|
+
const SIZE_MAP = {
|
|
1090
|
+
offsetHeight: "height",
|
|
1091
|
+
offsetWidth: "width"
|
|
1092
|
+
};
|
|
1093
|
+
const sizeSystem = system(
|
|
1094
|
+
([{ log }, { recalcInProgress }]) => {
|
|
1095
|
+
const sizeRanges = stream();
|
|
1096
|
+
const totalCount = stream();
|
|
1097
|
+
const statefulTotalCount = statefulStreamFromEmitter(totalCount, 0);
|
|
1098
|
+
const unshiftWith = stream();
|
|
1099
|
+
const shiftWith = stream();
|
|
1100
|
+
const firstItemIndex = statefulStream(0);
|
|
1101
|
+
const groupIndices = statefulStream([]);
|
|
1102
|
+
const fixedItemSize = statefulStream(void 0);
|
|
1103
|
+
const defaultItemSize = statefulStream(void 0);
|
|
1104
|
+
const itemSize = statefulStream((el, field) => correctItemSize(el, SIZE_MAP[field]));
|
|
1105
|
+
const data = statefulStream(void 0);
|
|
1106
|
+
const gap = statefulStream(0);
|
|
1107
|
+
const initial = initialSizeState();
|
|
1108
|
+
const sizes = statefulStreamFromEmitter(
|
|
1109
|
+
pipe(sizeRanges, withLatestFrom(groupIndices, log, gap), scan(sizeStateReducer, initial), distinctUntilChanged()),
|
|
1110
|
+
initial
|
|
1111
|
+
);
|
|
1112
|
+
connect(
|
|
1113
|
+
pipe(
|
|
1114
|
+
groupIndices,
|
|
1115
|
+
filter((indexes) => indexes.length > 0),
|
|
1116
|
+
withLatestFrom(sizes, gap),
|
|
1117
|
+
map(([groupIndices2, sizes2, gap2]) => {
|
|
1118
|
+
const groupOffsetTree = groupIndices2.reduce((tree, index, idx) => {
|
|
1119
|
+
return insert(tree, index, offsetOf(index, sizes2.offsetTree, gap2) || idx);
|
|
1120
|
+
}, newTree());
|
|
1121
|
+
return {
|
|
1122
|
+
...sizes2,
|
|
1123
|
+
groupIndices: groupIndices2,
|
|
1124
|
+
groupOffsetTree
|
|
1125
|
+
};
|
|
1126
|
+
})
|
|
1127
|
+
),
|
|
1128
|
+
sizes
|
|
1129
|
+
);
|
|
1130
|
+
connect(
|
|
1131
|
+
pipe(
|
|
1132
|
+
totalCount,
|
|
1133
|
+
withLatestFrom(sizes),
|
|
1134
|
+
filter(([totalCount2, { lastIndex }]) => {
|
|
1135
|
+
return totalCount2 < lastIndex;
|
|
1136
|
+
}),
|
|
1137
|
+
map(([totalCount2, { lastIndex, lastSize }]) => {
|
|
1138
|
+
return [
|
|
1139
|
+
{
|
|
1140
|
+
startIndex: totalCount2,
|
|
1141
|
+
endIndex: lastIndex,
|
|
1142
|
+
size: lastSize
|
|
1143
|
+
}
|
|
1144
|
+
];
|
|
1145
|
+
})
|
|
1146
|
+
),
|
|
1147
|
+
sizeRanges
|
|
1148
|
+
);
|
|
1149
|
+
connect(fixedItemSize, defaultItemSize);
|
|
1150
|
+
const trackItemSizes = statefulStreamFromEmitter(
|
|
1151
|
+
pipe(
|
|
1152
|
+
fixedItemSize,
|
|
1153
|
+
map((size) => size === void 0)
|
|
1154
|
+
),
|
|
1155
|
+
true
|
|
1156
|
+
);
|
|
1157
|
+
connect(
|
|
1158
|
+
pipe(
|
|
1159
|
+
defaultItemSize,
|
|
1160
|
+
filter((value) => {
|
|
1161
|
+
return value !== void 0 && empty(getValue(sizes).sizeTree);
|
|
1162
|
+
}),
|
|
1163
|
+
map((size) => [{ startIndex: 0, endIndex: 0, size }])
|
|
1164
|
+
),
|
|
1165
|
+
sizeRanges
|
|
1166
|
+
);
|
|
1167
|
+
const listRefresh = streamFromEmitter(
|
|
1168
|
+
pipe(
|
|
1169
|
+
sizeRanges,
|
|
1170
|
+
withLatestFrom(sizes),
|
|
1171
|
+
scan(
|
|
1172
|
+
({ sizes: oldSizes }, [_, newSizes]) => {
|
|
1173
|
+
return {
|
|
1174
|
+
changed: newSizes !== oldSizes,
|
|
1175
|
+
sizes: newSizes
|
|
1176
|
+
};
|
|
1177
|
+
},
|
|
1178
|
+
{ changed: false, sizes: initial }
|
|
1179
|
+
),
|
|
1180
|
+
map((value) => value.changed)
|
|
1181
|
+
)
|
|
1182
|
+
);
|
|
1183
|
+
subscribe(
|
|
1184
|
+
pipe(
|
|
1185
|
+
firstItemIndex,
|
|
1186
|
+
scan(
|
|
1187
|
+
(prev, next) => {
|
|
1188
|
+
return { diff: prev.prev - next, prev: next };
|
|
1189
|
+
},
|
|
1190
|
+
{ diff: 0, prev: 0 }
|
|
1191
|
+
),
|
|
1192
|
+
map((val) => val.diff)
|
|
1193
|
+
),
|
|
1194
|
+
(offset) => {
|
|
1195
|
+
if (offset > 0) {
|
|
1196
|
+
publish(recalcInProgress, true);
|
|
1197
|
+
publish(unshiftWith, offset);
|
|
1198
|
+
} else if (offset < 0) {
|
|
1199
|
+
publish(shiftWith, offset);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
subscribe(pipe(firstItemIndex, withLatestFrom(log)), ([index, log2]) => {
|
|
1204
|
+
if (index < 0) {
|
|
1205
|
+
log2(
|
|
1206
|
+
"`firstItemIndex` prop should not be set to less than zero. If you don't know the total count, just use a very high value",
|
|
1207
|
+
{ firstItemIndex },
|
|
1208
|
+
LogLevel.ERROR
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
const beforeUnshiftWith = streamFromEmitter(unshiftWith);
|
|
1213
|
+
connect(
|
|
1214
|
+
pipe(
|
|
1215
|
+
unshiftWith,
|
|
1216
|
+
withLatestFrom(sizes),
|
|
1217
|
+
map(([unshiftWith2, sizes2]) => {
|
|
1218
|
+
if (sizes2.groupIndices.length > 0) {
|
|
1219
|
+
throw new Error("Virtuoso: prepending items does not work with groups");
|
|
1220
|
+
}
|
|
1221
|
+
return walk(sizes2.sizeTree).reduce(
|
|
1222
|
+
(acc, { k: index, v: size }) => {
|
|
1223
|
+
return {
|
|
1224
|
+
ranges: [...acc.ranges, { startIndex: acc.prevIndex, endIndex: index + unshiftWith2 - 1, size: acc.prevSize }],
|
|
1225
|
+
prevIndex: index + unshiftWith2,
|
|
1226
|
+
prevSize: size
|
|
1227
|
+
};
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
ranges: [],
|
|
1231
|
+
prevIndex: 0,
|
|
1232
|
+
prevSize: sizes2.lastSize
|
|
1233
|
+
}
|
|
1234
|
+
).ranges;
|
|
1235
|
+
})
|
|
1236
|
+
),
|
|
1237
|
+
sizeRanges
|
|
1238
|
+
);
|
|
1239
|
+
const shiftWithOffset = streamFromEmitter(
|
|
1240
|
+
pipe(
|
|
1241
|
+
shiftWith,
|
|
1242
|
+
withLatestFrom(sizes, gap),
|
|
1243
|
+
map(([shiftWith2, { offsetTree }, gap2]) => {
|
|
1244
|
+
const newFirstItemIndex = -shiftWith2;
|
|
1245
|
+
return offsetOf(newFirstItemIndex, offsetTree, gap2);
|
|
1246
|
+
})
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
connect(
|
|
1250
|
+
pipe(
|
|
1251
|
+
shiftWith,
|
|
1252
|
+
withLatestFrom(sizes, gap),
|
|
1253
|
+
map(([shiftWith2, sizes2, gap2]) => {
|
|
1254
|
+
if (sizes2.groupIndices.length > 0) {
|
|
1255
|
+
throw new Error("Virtuoso: shifting items does not work with groups");
|
|
1256
|
+
}
|
|
1257
|
+
const newSizeTree = walk(sizes2.sizeTree).reduce((acc, { k, v }) => {
|
|
1258
|
+
return insert(acc, Math.max(0, k + shiftWith2), v);
|
|
1259
|
+
}, newTree());
|
|
1260
|
+
return {
|
|
1261
|
+
...sizes2,
|
|
1262
|
+
sizeTree: newSizeTree,
|
|
1263
|
+
...createOffsetTree(sizes2.offsetTree, 0, newSizeTree, gap2)
|
|
1264
|
+
};
|
|
1265
|
+
})
|
|
1266
|
+
),
|
|
1267
|
+
sizes
|
|
1268
|
+
);
|
|
1269
|
+
return {
|
|
1270
|
+
data,
|
|
1271
|
+
totalCount,
|
|
1272
|
+
sizeRanges,
|
|
1273
|
+
groupIndices,
|
|
1274
|
+
defaultItemSize,
|
|
1275
|
+
fixedItemSize,
|
|
1276
|
+
unshiftWith,
|
|
1277
|
+
shiftWith,
|
|
1278
|
+
shiftWithOffset,
|
|
1279
|
+
beforeUnshiftWith,
|
|
1280
|
+
firstItemIndex,
|
|
1281
|
+
gap,
|
|
1282
|
+
sizes,
|
|
1283
|
+
listRefresh,
|
|
1284
|
+
statefulTotalCount,
|
|
1285
|
+
trackItemSizes,
|
|
1286
|
+
itemSize
|
|
1287
|
+
};
|
|
1288
|
+
},
|
|
1289
|
+
tup(loggerSystem, recalcSystem),
|
|
1290
|
+
{ singleton: true }
|
|
1291
|
+
);
|
|
1292
|
+
const SUPPORTS_SCROLL_TO_OPTIONS = typeof document !== "undefined" && "scrollBehavior" in document.documentElement.style;
|
|
1293
|
+
function normalizeIndexLocation(location) {
|
|
1294
|
+
const result = typeof location === "number" ? { index: location } : location;
|
|
1295
|
+
if (!result.align) {
|
|
1296
|
+
result.align = "start";
|
|
1297
|
+
}
|
|
1298
|
+
if (!result.behavior || !SUPPORTS_SCROLL_TO_OPTIONS) {
|
|
1299
|
+
result.behavior = "auto";
|
|
1300
|
+
}
|
|
1301
|
+
if (!result.offset) {
|
|
1302
|
+
result.offset = 0;
|
|
1303
|
+
}
|
|
1304
|
+
return result;
|
|
1305
|
+
}
|
|
1306
|
+
const scrollToIndexSystem = system(
|
|
1307
|
+
([
|
|
1308
|
+
{ sizes, totalCount, listRefresh, gap },
|
|
1309
|
+
{
|
|
1310
|
+
scrollingInProgress,
|
|
1311
|
+
viewportHeight,
|
|
1312
|
+
scrollTo,
|
|
1313
|
+
smoothScrollTargetReached,
|
|
1314
|
+
headerHeight,
|
|
1315
|
+
footerHeight,
|
|
1316
|
+
fixedHeaderHeight,
|
|
1317
|
+
fixedFooterHeight
|
|
1318
|
+
},
|
|
1319
|
+
{ log }
|
|
1320
|
+
]) => {
|
|
1321
|
+
const scrollToIndex = stream();
|
|
1322
|
+
const topListHeight = statefulStream(0);
|
|
1323
|
+
let unsubscribeNextListRefresh = null;
|
|
1324
|
+
let cleartTimeoutRef = null;
|
|
1325
|
+
let unsubscribeListRefresh = null;
|
|
1326
|
+
function cleanup() {
|
|
1327
|
+
if (unsubscribeNextListRefresh) {
|
|
1328
|
+
unsubscribeNextListRefresh();
|
|
1329
|
+
unsubscribeNextListRefresh = null;
|
|
1330
|
+
}
|
|
1331
|
+
if (unsubscribeListRefresh) {
|
|
1332
|
+
unsubscribeListRefresh();
|
|
1333
|
+
unsubscribeListRefresh = null;
|
|
1334
|
+
}
|
|
1335
|
+
if (cleartTimeoutRef) {
|
|
1336
|
+
clearTimeout(cleartTimeoutRef);
|
|
1337
|
+
cleartTimeoutRef = null;
|
|
1338
|
+
}
|
|
1339
|
+
publish(scrollingInProgress, false);
|
|
1340
|
+
}
|
|
1341
|
+
connect(
|
|
1342
|
+
pipe(
|
|
1343
|
+
scrollToIndex,
|
|
1344
|
+
withLatestFrom(sizes, viewportHeight, totalCount, topListHeight, headerHeight, footerHeight, log),
|
|
1345
|
+
withLatestFrom(gap, fixedHeaderHeight, fixedFooterHeight),
|
|
1346
|
+
map(
|
|
1347
|
+
([
|
|
1348
|
+
[location, sizes2, viewportHeight2, totalCount2, topListHeight2, headerHeight2, footerHeight2, log2],
|
|
1349
|
+
gap2,
|
|
1350
|
+
fixedHeaderHeight2,
|
|
1351
|
+
fixedFooterHeight2
|
|
1352
|
+
]) => {
|
|
1353
|
+
const normalLocation = normalizeIndexLocation(location);
|
|
1354
|
+
const { align, behavior, offset } = normalLocation;
|
|
1355
|
+
const lastIndex = totalCount2 - 1;
|
|
1356
|
+
const index = originalIndexFromLocation(normalLocation, sizes2, lastIndex);
|
|
1357
|
+
let top = offsetOf(index, sizes2.offsetTree, gap2) + headerHeight2;
|
|
1358
|
+
if (align === "end") {
|
|
1359
|
+
top += fixedHeaderHeight2 + findMaxKeyValue(sizes2.sizeTree, index)[1] - viewportHeight2 + fixedFooterHeight2;
|
|
1360
|
+
if (index === lastIndex) {
|
|
1361
|
+
top += footerHeight2;
|
|
1362
|
+
}
|
|
1363
|
+
} else if (align === "center") {
|
|
1364
|
+
top += (fixedHeaderHeight2 + findMaxKeyValue(sizes2.sizeTree, index)[1] - viewportHeight2 + fixedFooterHeight2) / 2;
|
|
1365
|
+
} else {
|
|
1366
|
+
top -= topListHeight2;
|
|
1367
|
+
}
|
|
1368
|
+
if (offset) {
|
|
1369
|
+
top += offset;
|
|
1370
|
+
}
|
|
1371
|
+
const retry = (listChanged) => {
|
|
1372
|
+
cleanup();
|
|
1373
|
+
if (listChanged) {
|
|
1374
|
+
log2("retrying to scroll to", { location }, LogLevel.DEBUG);
|
|
1375
|
+
publish(scrollToIndex, location);
|
|
1376
|
+
} else {
|
|
1377
|
+
log2("list did not change, scroll successful", {}, LogLevel.DEBUG);
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
cleanup();
|
|
1381
|
+
if (behavior === "smooth") {
|
|
1382
|
+
let listChanged = false;
|
|
1383
|
+
unsubscribeListRefresh = subscribe(listRefresh, (changed) => {
|
|
1384
|
+
listChanged = listChanged || changed;
|
|
1385
|
+
});
|
|
1386
|
+
unsubscribeNextListRefresh = handleNext(smoothScrollTargetReached, () => {
|
|
1387
|
+
retry(listChanged);
|
|
1388
|
+
});
|
|
1389
|
+
} else {
|
|
1390
|
+
unsubscribeNextListRefresh = handleNext(pipe(listRefresh, watchChangesFor(150)), retry);
|
|
1391
|
+
}
|
|
1392
|
+
cleartTimeoutRef = setTimeout(() => {
|
|
1393
|
+
cleanup();
|
|
1394
|
+
}, 1200);
|
|
1395
|
+
publish(scrollingInProgress, true);
|
|
1396
|
+
log2("scrolling from index to", { index, top, behavior }, LogLevel.DEBUG);
|
|
1397
|
+
return { top, behavior };
|
|
1398
|
+
}
|
|
1399
|
+
)
|
|
1400
|
+
),
|
|
1401
|
+
scrollTo
|
|
1402
|
+
);
|
|
1403
|
+
return {
|
|
1404
|
+
scrollToIndex,
|
|
1405
|
+
topListHeight
|
|
1406
|
+
};
|
|
1407
|
+
},
|
|
1408
|
+
tup(sizeSystem, domIOSystem, loggerSystem),
|
|
1409
|
+
{ singleton: true }
|
|
1410
|
+
);
|
|
1411
|
+
function watchChangesFor(limit) {
|
|
1412
|
+
return (done) => {
|
|
1413
|
+
const timeoutRef = setTimeout(() => {
|
|
1414
|
+
done(false);
|
|
1415
|
+
}, limit);
|
|
1416
|
+
return (value) => {
|
|
1417
|
+
if (value) {
|
|
1418
|
+
done(true);
|
|
1419
|
+
clearTimeout(timeoutRef);
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
const UP = "up";
|
|
1425
|
+
const DOWN = "down";
|
|
1426
|
+
const NONE$1 = "none";
|
|
1427
|
+
const INITIAL_BOTTOM_STATE = {
|
|
1428
|
+
atBottom: false,
|
|
1429
|
+
notAtBottomBecause: "NOT_SHOWING_LAST_ITEM",
|
|
1430
|
+
state: {
|
|
1431
|
+
offsetBottom: 0,
|
|
1432
|
+
scrollTop: 0,
|
|
1433
|
+
viewportHeight: 0,
|
|
1434
|
+
scrollHeight: 0
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
const DEFAULT_AT_TOP_THRESHOLD = 0;
|
|
1438
|
+
const stateFlagsSystem = system(([{ scrollContainerState, scrollTop, viewportHeight, headerHeight, footerHeight, scrollBy }]) => {
|
|
1439
|
+
const isAtBottom = statefulStream(false);
|
|
1440
|
+
const isAtTop = statefulStream(true);
|
|
1441
|
+
const atBottomStateChange = stream();
|
|
1442
|
+
const atTopStateChange = stream();
|
|
1443
|
+
const atBottomThreshold = statefulStream(4);
|
|
1444
|
+
const atTopThreshold = statefulStream(DEFAULT_AT_TOP_THRESHOLD);
|
|
1445
|
+
const isScrolling = statefulStreamFromEmitter(
|
|
1446
|
+
pipe(
|
|
1447
|
+
merge(pipe(duc(scrollTop), skip(1), mapTo(true)), pipe(duc(scrollTop), skip(1), mapTo(false), debounceTime(100))),
|
|
1448
|
+
distinctUntilChanged()
|
|
1449
|
+
),
|
|
1450
|
+
false
|
|
1451
|
+
);
|
|
1452
|
+
const isScrollingBy = statefulStreamFromEmitter(
|
|
1453
|
+
pipe(merge(pipe(scrollBy, mapTo(true)), pipe(scrollBy, mapTo(false), debounceTime(200))), distinctUntilChanged()),
|
|
1454
|
+
false
|
|
1455
|
+
);
|
|
1456
|
+
connect(
|
|
1457
|
+
pipe(
|
|
1458
|
+
combineLatest(duc(scrollTop), duc(atTopThreshold)),
|
|
1459
|
+
map(([top, atTopThreshold2]) => top <= atTopThreshold2),
|
|
1460
|
+
distinctUntilChanged()
|
|
1461
|
+
),
|
|
1462
|
+
isAtTop
|
|
1463
|
+
);
|
|
1464
|
+
connect(pipe(isAtTop, throttleTime(50)), atTopStateChange);
|
|
1465
|
+
const atBottomState = streamFromEmitter(
|
|
1466
|
+
pipe(
|
|
1467
|
+
combineLatest(scrollContainerState, duc(viewportHeight), duc(headerHeight), duc(footerHeight), duc(atBottomThreshold)),
|
|
1468
|
+
scan((current, [{ scrollTop: scrollTop2, scrollHeight }, viewportHeight2, _headerHeight, _footerHeight, atBottomThreshold2]) => {
|
|
1469
|
+
const isAtBottom2 = scrollTop2 + viewportHeight2 - scrollHeight > -atBottomThreshold2;
|
|
1470
|
+
const state = {
|
|
1471
|
+
viewportHeight: viewportHeight2,
|
|
1472
|
+
scrollTop: scrollTop2,
|
|
1473
|
+
scrollHeight
|
|
1474
|
+
};
|
|
1475
|
+
if (isAtBottom2) {
|
|
1476
|
+
let atBottomBecause;
|
|
1477
|
+
let scrollTopDelta;
|
|
1478
|
+
if (scrollTop2 > current.state.scrollTop) {
|
|
1479
|
+
atBottomBecause = "SCROLLED_DOWN";
|
|
1480
|
+
scrollTopDelta = current.state.scrollTop - scrollTop2;
|
|
1481
|
+
} else {
|
|
1482
|
+
atBottomBecause = "SIZE_DECREASED";
|
|
1483
|
+
scrollTopDelta = current.state.scrollTop - scrollTop2 || current.scrollTopDelta;
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
atBottom: true,
|
|
1487
|
+
state,
|
|
1488
|
+
atBottomBecause,
|
|
1489
|
+
scrollTopDelta
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
let notAtBottomBecause;
|
|
1493
|
+
if (state.scrollHeight > current.state.scrollHeight) {
|
|
1494
|
+
notAtBottomBecause = "SIZE_INCREASED";
|
|
1495
|
+
} else if (viewportHeight2 < current.state.viewportHeight) {
|
|
1496
|
+
notAtBottomBecause = "VIEWPORT_HEIGHT_DECREASING";
|
|
1497
|
+
} else if (scrollTop2 < current.state.scrollTop) {
|
|
1498
|
+
notAtBottomBecause = "SCROLLING_UPWARDS";
|
|
1499
|
+
} else {
|
|
1500
|
+
notAtBottomBecause = "NOT_FULLY_SCROLLED_TO_LAST_ITEM_BOTTOM";
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
atBottom: false,
|
|
1504
|
+
notAtBottomBecause,
|
|
1505
|
+
state
|
|
1506
|
+
};
|
|
1507
|
+
}, INITIAL_BOTTOM_STATE),
|
|
1508
|
+
distinctUntilChanged((prev, next) => {
|
|
1509
|
+
return prev && prev.atBottom === next.atBottom;
|
|
1510
|
+
})
|
|
1511
|
+
)
|
|
1512
|
+
);
|
|
1513
|
+
const lastJumpDueToItemResize = statefulStreamFromEmitter(
|
|
1514
|
+
pipe(
|
|
1515
|
+
scrollContainerState,
|
|
1516
|
+
scan(
|
|
1517
|
+
(current, { scrollTop: scrollTop2, scrollHeight, viewportHeight: viewportHeight2 }) => {
|
|
1518
|
+
if (!approximatelyEqual(current.scrollHeight, scrollHeight)) {
|
|
1519
|
+
const atBottom = scrollHeight - (scrollTop2 + viewportHeight2) < 1;
|
|
1520
|
+
if (current.scrollTop !== scrollTop2 && atBottom) {
|
|
1521
|
+
return {
|
|
1522
|
+
scrollHeight,
|
|
1523
|
+
scrollTop: scrollTop2,
|
|
1524
|
+
jump: current.scrollTop - scrollTop2,
|
|
1525
|
+
changed: true
|
|
1526
|
+
};
|
|
1527
|
+
} else {
|
|
1528
|
+
return {
|
|
1529
|
+
scrollHeight,
|
|
1530
|
+
scrollTop: scrollTop2,
|
|
1531
|
+
jump: 0,
|
|
1532
|
+
changed: true
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
} else {
|
|
1536
|
+
return {
|
|
1537
|
+
scrollTop: scrollTop2,
|
|
1538
|
+
scrollHeight,
|
|
1539
|
+
jump: 0,
|
|
1540
|
+
changed: false
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
},
|
|
1544
|
+
{ scrollHeight: 0, jump: 0, scrollTop: 0, changed: false }
|
|
1545
|
+
),
|
|
1546
|
+
filter((value) => value.changed),
|
|
1547
|
+
map((value) => value.jump)
|
|
1548
|
+
),
|
|
1549
|
+
0
|
|
1550
|
+
);
|
|
1551
|
+
connect(
|
|
1552
|
+
pipe(
|
|
1553
|
+
atBottomState,
|
|
1554
|
+
map((state) => state.atBottom)
|
|
1555
|
+
),
|
|
1556
|
+
isAtBottom
|
|
1557
|
+
);
|
|
1558
|
+
connect(pipe(isAtBottom, throttleTime(50)), atBottomStateChange);
|
|
1559
|
+
const scrollDirection = statefulStream(DOWN);
|
|
1560
|
+
connect(
|
|
1561
|
+
pipe(
|
|
1562
|
+
scrollContainerState,
|
|
1563
|
+
map(({ scrollTop: scrollTop2 }) => scrollTop2),
|
|
1564
|
+
distinctUntilChanged(),
|
|
1565
|
+
scan(
|
|
1566
|
+
(acc, scrollTop2) => {
|
|
1567
|
+
if (getValue(isScrollingBy)) {
|
|
1568
|
+
return { direction: acc.direction, prevScrollTop: scrollTop2 };
|
|
1569
|
+
}
|
|
1570
|
+
return { direction: scrollTop2 < acc.prevScrollTop ? UP : DOWN, prevScrollTop: scrollTop2 };
|
|
1571
|
+
},
|
|
1572
|
+
{ direction: DOWN, prevScrollTop: 0 }
|
|
1573
|
+
),
|
|
1574
|
+
map((value) => value.direction)
|
|
1575
|
+
),
|
|
1576
|
+
scrollDirection
|
|
1577
|
+
);
|
|
1578
|
+
connect(pipe(scrollContainerState, throttleTime(50), mapTo(NONE$1)), scrollDirection);
|
|
1579
|
+
const scrollVelocity = statefulStream(0);
|
|
1580
|
+
connect(
|
|
1581
|
+
pipe(
|
|
1582
|
+
isScrolling,
|
|
1583
|
+
filter((value) => !value),
|
|
1584
|
+
mapTo(0)
|
|
1585
|
+
),
|
|
1586
|
+
scrollVelocity
|
|
1587
|
+
);
|
|
1588
|
+
connect(
|
|
1589
|
+
pipe(
|
|
1590
|
+
scrollTop,
|
|
1591
|
+
throttleTime(100),
|
|
1592
|
+
withLatestFrom(isScrolling),
|
|
1593
|
+
filter(([_, isScrolling2]) => !!isScrolling2),
|
|
1594
|
+
scan(([_, prev], [next]) => [prev, next], [0, 0]),
|
|
1595
|
+
map(([prev, next]) => next - prev)
|
|
1596
|
+
),
|
|
1597
|
+
scrollVelocity
|
|
1598
|
+
);
|
|
1599
|
+
return {
|
|
1600
|
+
isScrolling,
|
|
1601
|
+
isAtTop,
|
|
1602
|
+
isAtBottom,
|
|
1603
|
+
atBottomState,
|
|
1604
|
+
atTopStateChange,
|
|
1605
|
+
atBottomStateChange,
|
|
1606
|
+
scrollDirection,
|
|
1607
|
+
atBottomThreshold,
|
|
1608
|
+
atTopThreshold,
|
|
1609
|
+
scrollVelocity,
|
|
1610
|
+
lastJumpDueToItemResize
|
|
1611
|
+
};
|
|
1612
|
+
}, tup(domIOSystem));
|
|
1613
|
+
const propsReadySystem = system(
|
|
1614
|
+
([{ log }]) => {
|
|
1615
|
+
const propsReady = statefulStream(false);
|
|
1616
|
+
const didMount = streamFromEmitter(
|
|
1617
|
+
pipe(
|
|
1618
|
+
propsReady,
|
|
1619
|
+
filter((ready) => ready),
|
|
1620
|
+
distinctUntilChanged()
|
|
1621
|
+
)
|
|
1622
|
+
);
|
|
1623
|
+
subscribe(propsReady, (value) => {
|
|
1624
|
+
value && getValue(log)("props updated", {}, LogLevel.DEBUG);
|
|
1625
|
+
});
|
|
1626
|
+
return { propsReady, didMount };
|
|
1627
|
+
},
|
|
1628
|
+
tup(loggerSystem),
|
|
1629
|
+
{ singleton: true }
|
|
1630
|
+
);
|
|
1631
|
+
function getInitialTopMostItemIndexNumber(location, totalCount) {
|
|
1632
|
+
const lastIndex = totalCount - 1;
|
|
1633
|
+
const index = typeof location === "number" ? location : location.index === "LAST" ? lastIndex : location.index;
|
|
1634
|
+
return index;
|
|
1635
|
+
}
|
|
1636
|
+
const initialTopMostItemIndexSystem = system(
|
|
1637
|
+
([{ sizes, listRefresh, defaultItemSize }, { scrollTop }, { scrollToIndex }, { didMount }]) => {
|
|
1638
|
+
const scrolledToInitialItem = statefulStream(true);
|
|
1639
|
+
const initialTopMostItemIndex = statefulStream(0);
|
|
1640
|
+
connect(
|
|
1641
|
+
pipe(
|
|
1642
|
+
didMount,
|
|
1643
|
+
withLatestFrom(initialTopMostItemIndex),
|
|
1644
|
+
filter(([_, location]) => !!location),
|
|
1645
|
+
mapTo(false)
|
|
1646
|
+
),
|
|
1647
|
+
scrolledToInitialItem
|
|
1648
|
+
);
|
|
1649
|
+
subscribe(
|
|
1650
|
+
pipe(
|
|
1651
|
+
combineLatest(listRefresh, didMount),
|
|
1652
|
+
withLatestFrom(scrolledToInitialItem, sizes, defaultItemSize),
|
|
1653
|
+
filter(([[, didMount2], scrolledToInitialItem2, { sizeTree }, defaultItemSize2]) => {
|
|
1654
|
+
return didMount2 && (!empty(sizeTree) || defaultItemSize2 !== void 0) && !scrolledToInitialItem2;
|
|
1655
|
+
}),
|
|
1656
|
+
withLatestFrom(initialTopMostItemIndex)
|
|
1657
|
+
),
|
|
1658
|
+
([, initialTopMostItemIndex2]) => {
|
|
1659
|
+
setTimeout(() => {
|
|
1660
|
+
handleNext(scrollTop, () => {
|
|
1661
|
+
publish(scrolledToInitialItem, true);
|
|
1662
|
+
});
|
|
1663
|
+
publish(scrollToIndex, initialTopMostItemIndex2);
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
);
|
|
1667
|
+
return {
|
|
1668
|
+
scrolledToInitialItem,
|
|
1669
|
+
initialTopMostItemIndex
|
|
1670
|
+
};
|
|
1671
|
+
},
|
|
1672
|
+
tup(sizeSystem, domIOSystem, scrollToIndexSystem, propsReadySystem),
|
|
1673
|
+
{ singleton: true }
|
|
1674
|
+
);
|
|
1675
|
+
function normalizeFollowOutput(follow) {
|
|
1676
|
+
if (!follow) {
|
|
1677
|
+
return false;
|
|
1678
|
+
}
|
|
1679
|
+
return follow === "smooth" ? "smooth" : "auto";
|
|
1680
|
+
}
|
|
1681
|
+
const behaviorFromFollowOutput = (follow, isAtBottom) => {
|
|
1682
|
+
if (typeof follow === "function") {
|
|
1683
|
+
return normalizeFollowOutput(follow(isAtBottom));
|
|
1684
|
+
}
|
|
1685
|
+
return isAtBottom && normalizeFollowOutput(follow);
|
|
1686
|
+
};
|
|
1687
|
+
const followOutputSystem = system(
|
|
1688
|
+
([
|
|
1689
|
+
{ totalCount, listRefresh },
|
|
1690
|
+
{ isAtBottom, atBottomState },
|
|
1691
|
+
{ scrollToIndex },
|
|
1692
|
+
{ scrolledToInitialItem },
|
|
1693
|
+
{ propsReady, didMount },
|
|
1694
|
+
{ log },
|
|
1695
|
+
{ scrollingInProgress }
|
|
1696
|
+
]) => {
|
|
1697
|
+
const followOutput = statefulStream(false);
|
|
1698
|
+
const autoscrollToBottom = stream();
|
|
1699
|
+
let pendingScrollHandle = null;
|
|
1700
|
+
function scrollToBottom(followOutputBehavior) {
|
|
1701
|
+
publish(scrollToIndex, {
|
|
1702
|
+
index: "LAST",
|
|
1703
|
+
align: "end",
|
|
1704
|
+
behavior: followOutputBehavior
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
subscribe(
|
|
1708
|
+
pipe(
|
|
1709
|
+
combineLatest(pipe(duc(totalCount), skip(1)), didMount),
|
|
1710
|
+
withLatestFrom(duc(followOutput), isAtBottom, scrolledToInitialItem, scrollingInProgress),
|
|
1711
|
+
map(([[totalCount2, didMount2], followOutput2, isAtBottom2, scrolledToInitialItem2, scrollingInProgress2]) => {
|
|
1712
|
+
let shouldFollow = didMount2 && scrolledToInitialItem2;
|
|
1713
|
+
let followOutputBehavior = "auto";
|
|
1714
|
+
if (shouldFollow) {
|
|
1715
|
+
followOutputBehavior = behaviorFromFollowOutput(followOutput2, isAtBottom2 || scrollingInProgress2);
|
|
1716
|
+
shouldFollow = shouldFollow && !!followOutputBehavior;
|
|
1717
|
+
}
|
|
1718
|
+
return { totalCount: totalCount2, shouldFollow, followOutputBehavior };
|
|
1719
|
+
}),
|
|
1720
|
+
filter(({ shouldFollow }) => shouldFollow)
|
|
1721
|
+
),
|
|
1722
|
+
({ totalCount: totalCount2, followOutputBehavior }) => {
|
|
1723
|
+
if (pendingScrollHandle) {
|
|
1724
|
+
pendingScrollHandle();
|
|
1725
|
+
pendingScrollHandle = null;
|
|
1726
|
+
}
|
|
1727
|
+
pendingScrollHandle = handleNext(listRefresh, () => {
|
|
1728
|
+
getValue(log)("following output to ", { totalCount: totalCount2 }, LogLevel.DEBUG);
|
|
1729
|
+
scrollToBottom(followOutputBehavior);
|
|
1730
|
+
pendingScrollHandle = null;
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
);
|
|
1734
|
+
function trapNextSizeIncrease(followOutput2) {
|
|
1735
|
+
const cancel = handleNext(atBottomState, (state) => {
|
|
1736
|
+
if (followOutput2 && !state.atBottom && state.notAtBottomBecause === "SIZE_INCREASED" && !pendingScrollHandle) {
|
|
1737
|
+
getValue(log)("scrolling to bottom due to increased size", {}, LogLevel.DEBUG);
|
|
1738
|
+
scrollToBottom("auto");
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
setTimeout(cancel, 100);
|
|
1742
|
+
}
|
|
1743
|
+
subscribe(
|
|
1744
|
+
pipe(
|
|
1745
|
+
combineLatest(duc(followOutput), totalCount, propsReady),
|
|
1746
|
+
filter(([follow, , ready]) => follow && ready),
|
|
1747
|
+
scan(
|
|
1748
|
+
({ value }, [, next]) => {
|
|
1749
|
+
return { refreshed: value === next, value: next };
|
|
1750
|
+
},
|
|
1751
|
+
{ refreshed: false, value: 0 }
|
|
1752
|
+
),
|
|
1753
|
+
filter(({ refreshed }) => refreshed),
|
|
1754
|
+
withLatestFrom(followOutput, totalCount)
|
|
1755
|
+
),
|
|
1756
|
+
([, followOutput2]) => {
|
|
1757
|
+
trapNextSizeIncrease(followOutput2 !== false);
|
|
1758
|
+
}
|
|
1759
|
+
);
|
|
1760
|
+
subscribe(autoscrollToBottom, () => {
|
|
1761
|
+
trapNextSizeIncrease(getValue(followOutput) !== false);
|
|
1762
|
+
});
|
|
1763
|
+
subscribe(combineLatest(duc(followOutput), atBottomState), ([followOutput2, state]) => {
|
|
1764
|
+
if (followOutput2 && !state.atBottom && state.notAtBottomBecause === "VIEWPORT_HEIGHT_DECREASING") {
|
|
1765
|
+
scrollToBottom("auto");
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
return { followOutput, autoscrollToBottom };
|
|
1769
|
+
},
|
|
1770
|
+
tup(sizeSystem, stateFlagsSystem, scrollToIndexSystem, initialTopMostItemIndexSystem, propsReadySystem, loggerSystem, domIOSystem)
|
|
1771
|
+
);
|
|
1772
|
+
function groupCountsToIndicesAndCount(counts) {
|
|
1773
|
+
return counts.reduce(
|
|
1774
|
+
(acc, groupCount) => {
|
|
1775
|
+
acc.groupIndices.push(acc.totalCount);
|
|
1776
|
+
acc.totalCount += groupCount + 1;
|
|
1777
|
+
return acc;
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
totalCount: 0,
|
|
1781
|
+
groupIndices: []
|
|
1782
|
+
}
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
const groupedListSystem = system(([{ totalCount, groupIndices, sizes }, { scrollTop, headerHeight }]) => {
|
|
1786
|
+
const groupCounts = stream();
|
|
1787
|
+
const topItemsIndexes = stream();
|
|
1788
|
+
const groupIndicesAndCount = streamFromEmitter(pipe(groupCounts, map(groupCountsToIndicesAndCount)));
|
|
1789
|
+
connect(
|
|
1790
|
+
pipe(
|
|
1791
|
+
groupIndicesAndCount,
|
|
1792
|
+
map((value) => value.totalCount)
|
|
1793
|
+
),
|
|
1794
|
+
totalCount
|
|
1795
|
+
);
|
|
1796
|
+
connect(
|
|
1797
|
+
pipe(
|
|
1798
|
+
groupIndicesAndCount,
|
|
1799
|
+
map((value) => value.groupIndices)
|
|
1800
|
+
),
|
|
1801
|
+
groupIndices
|
|
1802
|
+
);
|
|
1803
|
+
connect(
|
|
1804
|
+
pipe(
|
|
1805
|
+
combineLatest(scrollTop, sizes, headerHeight),
|
|
1806
|
+
filter(([_, sizes2]) => hasGroups(sizes2)),
|
|
1807
|
+
map(([scrollTop2, state, headerHeight2]) => findMaxKeyValue(state.groupOffsetTree, Math.max(scrollTop2 - headerHeight2, 0), "v")[0]),
|
|
1808
|
+
distinctUntilChanged(),
|
|
1809
|
+
map((index) => [index])
|
|
1810
|
+
),
|
|
1811
|
+
topItemsIndexes
|
|
1812
|
+
);
|
|
1813
|
+
return { groupCounts, topItemsIndexes };
|
|
1814
|
+
}, tup(sizeSystem, domIOSystem));
|
|
1815
|
+
function tupleComparator(prev, current) {
|
|
1816
|
+
return !!(prev && prev[0] === current[0] && prev[1] === current[1]);
|
|
1817
|
+
}
|
|
1818
|
+
function rangeComparator(prev, next) {
|
|
1819
|
+
return !!(prev && prev.startIndex === next.startIndex && prev.endIndex === next.endIndex);
|
|
1820
|
+
}
|
|
1821
|
+
const TOP = "top";
|
|
1822
|
+
const BOTTOM = "bottom";
|
|
1823
|
+
const NONE = "none";
|
|
1824
|
+
function getOverscan(overscan, end, direction) {
|
|
1825
|
+
if (typeof overscan === "number") {
|
|
1826
|
+
return direction === UP && end === TOP || direction === DOWN && end === BOTTOM ? overscan : 0;
|
|
1827
|
+
} else {
|
|
1828
|
+
if (direction === UP) {
|
|
1829
|
+
return end === TOP ? overscan.main : overscan.reverse;
|
|
1830
|
+
} else {
|
|
1831
|
+
return end === BOTTOM ? overscan.main : overscan.reverse;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
function getViewportIncrease(value, end) {
|
|
1836
|
+
return typeof value === "number" ? value : value[end] || 0;
|
|
1837
|
+
}
|
|
1838
|
+
const sizeRangeSystem = system(
|
|
1839
|
+
([{ scrollTop, viewportHeight, deviation, headerHeight, fixedHeaderHeight }]) => {
|
|
1840
|
+
const listBoundary = stream();
|
|
1841
|
+
const topListHeight = statefulStream(0);
|
|
1842
|
+
const increaseViewportBy = statefulStream(0);
|
|
1843
|
+
const overscan = statefulStream(0);
|
|
1844
|
+
const visibleRange = statefulStreamFromEmitter(
|
|
1845
|
+
pipe(
|
|
1846
|
+
combineLatest(
|
|
1847
|
+
duc(scrollTop),
|
|
1848
|
+
duc(viewportHeight),
|
|
1849
|
+
duc(headerHeight),
|
|
1850
|
+
duc(listBoundary, tupleComparator),
|
|
1851
|
+
duc(overscan),
|
|
1852
|
+
duc(topListHeight),
|
|
1853
|
+
duc(fixedHeaderHeight),
|
|
1854
|
+
duc(deviation),
|
|
1855
|
+
duc(increaseViewportBy)
|
|
1856
|
+
),
|
|
1857
|
+
map(
|
|
1858
|
+
([
|
|
1859
|
+
scrollTop2,
|
|
1860
|
+
viewportHeight2,
|
|
1861
|
+
headerHeight2,
|
|
1862
|
+
[listTop, listBottom],
|
|
1863
|
+
overscan2,
|
|
1864
|
+
topListHeight2,
|
|
1865
|
+
fixedHeaderHeight2,
|
|
1866
|
+
deviation2,
|
|
1867
|
+
increaseViewportBy2
|
|
1868
|
+
]) => {
|
|
1869
|
+
const top = scrollTop2 - deviation2;
|
|
1870
|
+
const stickyHeaderHeight = topListHeight2 + fixedHeaderHeight2;
|
|
1871
|
+
const headerVisible = Math.max(headerHeight2 - top, 0);
|
|
1872
|
+
let direction = NONE;
|
|
1873
|
+
const topViewportAddition = getViewportIncrease(increaseViewportBy2, TOP);
|
|
1874
|
+
const bottomViewportAddition = getViewportIncrease(increaseViewportBy2, BOTTOM);
|
|
1875
|
+
listTop -= deviation2;
|
|
1876
|
+
listTop += headerHeight2 + fixedHeaderHeight2;
|
|
1877
|
+
listBottom += headerHeight2 + fixedHeaderHeight2;
|
|
1878
|
+
listBottom -= deviation2;
|
|
1879
|
+
if (listTop > scrollTop2 + stickyHeaderHeight - topViewportAddition) {
|
|
1880
|
+
direction = UP;
|
|
1881
|
+
}
|
|
1882
|
+
if (listBottom < scrollTop2 - headerVisible + viewportHeight2 + bottomViewportAddition) {
|
|
1883
|
+
direction = DOWN;
|
|
1884
|
+
}
|
|
1885
|
+
if (direction !== NONE) {
|
|
1886
|
+
return [
|
|
1887
|
+
Math.max(top - headerHeight2 - getOverscan(overscan2, TOP, direction) - topViewportAddition, 0),
|
|
1888
|
+
top - headerVisible - fixedHeaderHeight2 + viewportHeight2 + getOverscan(overscan2, BOTTOM, direction) + bottomViewportAddition
|
|
1889
|
+
];
|
|
1890
|
+
}
|
|
1891
|
+
return null;
|
|
1892
|
+
}
|
|
1893
|
+
),
|
|
1894
|
+
filter((value) => value != null),
|
|
1895
|
+
distinctUntilChanged(tupleComparator)
|
|
1896
|
+
),
|
|
1897
|
+
[0, 0]
|
|
1898
|
+
);
|
|
1899
|
+
return {
|
|
1900
|
+
listBoundary,
|
|
1901
|
+
overscan,
|
|
1902
|
+
topListHeight,
|
|
1903
|
+
increaseViewportBy,
|
|
1904
|
+
visibleRange
|
|
1905
|
+
};
|
|
1906
|
+
},
|
|
1907
|
+
tup(domIOSystem),
|
|
1908
|
+
{ singleton: true }
|
|
1909
|
+
);
|
|
1910
|
+
function probeItemSet(index, sizes, data) {
|
|
1911
|
+
if (hasGroups(sizes)) {
|
|
1912
|
+
const itemIndex = originalIndexFromItemIndex(index, sizes);
|
|
1913
|
+
const groupIndex = findMaxKeyValue(sizes.groupOffsetTree, itemIndex)[0];
|
|
1914
|
+
return [
|
|
1915
|
+
{ index: groupIndex, size: 0, offset: 0 },
|
|
1916
|
+
{ index: itemIndex, size: 0, offset: 0, data: data && data[0] }
|
|
1917
|
+
];
|
|
1918
|
+
}
|
|
1919
|
+
return [{ index, size: 0, offset: 0, data: data && data[0] }];
|
|
1920
|
+
}
|
|
1921
|
+
const EMPTY_LIST_STATE = {
|
|
1922
|
+
items: [],
|
|
1923
|
+
topItems: [],
|
|
1924
|
+
offsetTop: 0,
|
|
1925
|
+
offsetBottom: 0,
|
|
1926
|
+
top: 0,
|
|
1927
|
+
bottom: 0,
|
|
1928
|
+
topListHeight: 0,
|
|
1929
|
+
totalCount: 0,
|
|
1930
|
+
firstItemIndex: 0
|
|
1931
|
+
};
|
|
1932
|
+
function transposeItems(items, sizes, firstItemIndex) {
|
|
1933
|
+
if (items.length === 0) {
|
|
1934
|
+
return [];
|
|
1935
|
+
}
|
|
1936
|
+
if (!hasGroups(sizes)) {
|
|
1937
|
+
return items.map((item) => ({ ...item, index: item.index + firstItemIndex, originalIndex: item.index }));
|
|
1938
|
+
}
|
|
1939
|
+
const startIndex = items[0].index;
|
|
1940
|
+
const endIndex = items[items.length - 1].index;
|
|
1941
|
+
const transposedItems = [];
|
|
1942
|
+
const groupRanges = rangesWithin(sizes.groupOffsetTree, startIndex, endIndex);
|
|
1943
|
+
let currentRange = void 0;
|
|
1944
|
+
let currentGroupIndex = 0;
|
|
1945
|
+
for (const item of items) {
|
|
1946
|
+
if (!currentRange || currentRange.end < item.index) {
|
|
1947
|
+
currentRange = groupRanges.shift();
|
|
1948
|
+
currentGroupIndex = sizes.groupIndices.indexOf(currentRange.start);
|
|
1949
|
+
}
|
|
1950
|
+
let transposedItem;
|
|
1951
|
+
if (item.index === currentRange.start) {
|
|
1952
|
+
transposedItem = {
|
|
1953
|
+
type: "group",
|
|
1954
|
+
index: currentGroupIndex
|
|
1955
|
+
};
|
|
1956
|
+
} else {
|
|
1957
|
+
transposedItem = {
|
|
1958
|
+
index: item.index - (currentGroupIndex + 1) + firstItemIndex,
|
|
1959
|
+
groupIndex: currentGroupIndex
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
transposedItems.push({
|
|
1963
|
+
...transposedItem,
|
|
1964
|
+
size: item.size,
|
|
1965
|
+
offset: item.offset,
|
|
1966
|
+
originalIndex: item.index,
|
|
1967
|
+
data: item.data
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
return transposedItems;
|
|
1971
|
+
}
|
|
1972
|
+
function buildListState(items, topItems, totalCount, gap, sizes, firstItemIndex) {
|
|
1973
|
+
const { lastSize, lastOffset, lastIndex } = sizes;
|
|
1974
|
+
let offsetTop = 0;
|
|
1975
|
+
let bottom = 0;
|
|
1976
|
+
if (items.length > 0) {
|
|
1977
|
+
offsetTop = items[0].offset;
|
|
1978
|
+
const lastItem = items[items.length - 1];
|
|
1979
|
+
bottom = lastItem.offset + lastItem.size;
|
|
1980
|
+
}
|
|
1981
|
+
const itemCount = totalCount - lastIndex;
|
|
1982
|
+
const total = lastOffset + itemCount * lastSize + (itemCount - 1) * gap;
|
|
1983
|
+
const top = offsetTop;
|
|
1984
|
+
const offsetBottom = total - bottom;
|
|
1985
|
+
return {
|
|
1986
|
+
items: transposeItems(items, sizes, firstItemIndex),
|
|
1987
|
+
topItems: transposeItems(topItems, sizes, firstItemIndex),
|
|
1988
|
+
topListHeight: topItems.reduce((height, item) => item.size + height, 0),
|
|
1989
|
+
offsetTop,
|
|
1990
|
+
offsetBottom,
|
|
1991
|
+
top,
|
|
1992
|
+
bottom,
|
|
1993
|
+
totalCount,
|
|
1994
|
+
firstItemIndex
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
const listStateSystem = system(
|
|
1998
|
+
([
|
|
1999
|
+
{ sizes, totalCount, data, firstItemIndex, gap },
|
|
2000
|
+
groupedListSystem2,
|
|
2001
|
+
{ visibleRange, listBoundary, topListHeight: rangeTopListHeight },
|
|
2002
|
+
{ scrolledToInitialItem, initialTopMostItemIndex },
|
|
2003
|
+
{ topListHeight },
|
|
2004
|
+
stateFlags,
|
|
2005
|
+
{ didMount },
|
|
2006
|
+
{ recalcInProgress }
|
|
2007
|
+
]) => {
|
|
2008
|
+
const topItemsIndexes = statefulStream([]);
|
|
2009
|
+
const itemsRendered = stream();
|
|
2010
|
+
connect(groupedListSystem2.topItemsIndexes, topItemsIndexes);
|
|
2011
|
+
const listState = statefulStreamFromEmitter(
|
|
2012
|
+
pipe(
|
|
2013
|
+
combineLatest(
|
|
2014
|
+
didMount,
|
|
2015
|
+
recalcInProgress,
|
|
2016
|
+
duc(visibleRange, tupleComparator),
|
|
2017
|
+
duc(totalCount),
|
|
2018
|
+
duc(sizes),
|
|
2019
|
+
duc(initialTopMostItemIndex),
|
|
2020
|
+
scrolledToInitialItem,
|
|
2021
|
+
duc(topItemsIndexes),
|
|
2022
|
+
duc(firstItemIndex),
|
|
2023
|
+
duc(gap),
|
|
2024
|
+
data
|
|
2025
|
+
),
|
|
2026
|
+
filter(([mount, recalcInProgress2]) => {
|
|
2027
|
+
return mount && !recalcInProgress2;
|
|
2028
|
+
}),
|
|
2029
|
+
map(
|
|
2030
|
+
([
|
|
2031
|
+
,
|
|
2032
|
+
,
|
|
2033
|
+
[startOffset, endOffset],
|
|
2034
|
+
totalCount2,
|
|
2035
|
+
sizes2,
|
|
2036
|
+
initialTopMostItemIndex2,
|
|
2037
|
+
scrolledToInitialItem2,
|
|
2038
|
+
topItemsIndexes2,
|
|
2039
|
+
firstItemIndex2,
|
|
2040
|
+
gap2,
|
|
2041
|
+
data2
|
|
2042
|
+
]) => {
|
|
2043
|
+
const sizesValue = sizes2;
|
|
2044
|
+
const { sizeTree, offsetTree } = sizesValue;
|
|
2045
|
+
if (totalCount2 === 0 || startOffset === 0 && endOffset === 0) {
|
|
2046
|
+
return { ...EMPTY_LIST_STATE, totalCount: totalCount2 };
|
|
2047
|
+
}
|
|
2048
|
+
if (empty(sizeTree)) {
|
|
2049
|
+
return buildListState(
|
|
2050
|
+
probeItemSet(getInitialTopMostItemIndexNumber(initialTopMostItemIndex2, totalCount2), sizesValue, data2),
|
|
2051
|
+
[],
|
|
2052
|
+
totalCount2,
|
|
2053
|
+
gap2,
|
|
2054
|
+
sizesValue,
|
|
2055
|
+
firstItemIndex2
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
const topItems = [];
|
|
2059
|
+
if (topItemsIndexes2.length > 0) {
|
|
2060
|
+
const startIndex = topItemsIndexes2[0];
|
|
2061
|
+
const endIndex = topItemsIndexes2[topItemsIndexes2.length - 1];
|
|
2062
|
+
let offset = 0;
|
|
2063
|
+
for (const range of rangesWithin(sizeTree, startIndex, endIndex)) {
|
|
2064
|
+
const size = range.value;
|
|
2065
|
+
const rangeStartIndex = Math.max(range.start, startIndex);
|
|
2066
|
+
const rangeEndIndex = Math.min(range.end, endIndex);
|
|
2067
|
+
for (let i = rangeStartIndex; i <= rangeEndIndex; i++) {
|
|
2068
|
+
topItems.push({ index: i, size, offset, data: data2 && data2[i] });
|
|
2069
|
+
offset += size;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (!scrolledToInitialItem2) {
|
|
2074
|
+
return buildListState([], topItems, totalCount2, gap2, sizesValue, firstItemIndex2);
|
|
2075
|
+
}
|
|
2076
|
+
const minStartIndex = topItemsIndexes2.length > 0 ? topItemsIndexes2[topItemsIndexes2.length - 1] + 1 : 0;
|
|
2077
|
+
const offsetPointRanges = rangesWithinOffsets(offsetTree, startOffset, endOffset, minStartIndex);
|
|
2078
|
+
if (offsetPointRanges.length === 0) {
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
const maxIndex = totalCount2 - 1;
|
|
2082
|
+
const items = tap([], (result) => {
|
|
2083
|
+
for (const range of offsetPointRanges) {
|
|
2084
|
+
const point = range.value;
|
|
2085
|
+
let offset = point.offset;
|
|
2086
|
+
let rangeStartIndex = range.start;
|
|
2087
|
+
const size = point.size;
|
|
2088
|
+
if (point.offset < startOffset) {
|
|
2089
|
+
rangeStartIndex += Math.floor((startOffset - point.offset + gap2) / (size + gap2));
|
|
2090
|
+
const itemCount = rangeStartIndex - range.start;
|
|
2091
|
+
offset += itemCount * size + itemCount * gap2;
|
|
2092
|
+
}
|
|
2093
|
+
if (rangeStartIndex < minStartIndex) {
|
|
2094
|
+
offset += (minStartIndex - rangeStartIndex) * size;
|
|
2095
|
+
rangeStartIndex = minStartIndex;
|
|
2096
|
+
}
|
|
2097
|
+
const endIndex = Math.min(range.end, maxIndex);
|
|
2098
|
+
for (let i = rangeStartIndex; i <= endIndex; i++) {
|
|
2099
|
+
if (offset >= endOffset) {
|
|
2100
|
+
break;
|
|
2101
|
+
}
|
|
2102
|
+
result.push({ index: i, size, offset, data: data2 && data2[i] });
|
|
2103
|
+
offset += size + gap2;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
return buildListState(items, topItems, totalCount2, gap2, sizesValue, firstItemIndex2);
|
|
2108
|
+
}
|
|
2109
|
+
),
|
|
2110
|
+
filter((value) => value !== null),
|
|
2111
|
+
distinctUntilChanged()
|
|
2112
|
+
),
|
|
2113
|
+
EMPTY_LIST_STATE
|
|
2114
|
+
);
|
|
2115
|
+
connect(
|
|
2116
|
+
pipe(
|
|
2117
|
+
data,
|
|
2118
|
+
filter((data2) => data2 !== void 0),
|
|
2119
|
+
map((data2) => data2 == null ? void 0 : data2.length)
|
|
2120
|
+
),
|
|
2121
|
+
totalCount
|
|
2122
|
+
);
|
|
2123
|
+
connect(
|
|
2124
|
+
pipe(
|
|
2125
|
+
listState,
|
|
2126
|
+
map((value) => value.topListHeight)
|
|
2127
|
+
),
|
|
2128
|
+
topListHeight
|
|
2129
|
+
);
|
|
2130
|
+
connect(topListHeight, rangeTopListHeight);
|
|
2131
|
+
connect(
|
|
2132
|
+
pipe(
|
|
2133
|
+
listState,
|
|
2134
|
+
map((state) => [state.top, state.bottom])
|
|
2135
|
+
),
|
|
2136
|
+
listBoundary
|
|
2137
|
+
);
|
|
2138
|
+
connect(
|
|
2139
|
+
pipe(
|
|
2140
|
+
listState,
|
|
2141
|
+
map((state) => state.items)
|
|
2142
|
+
),
|
|
2143
|
+
itemsRendered
|
|
2144
|
+
);
|
|
2145
|
+
const endReached = streamFromEmitter(
|
|
2146
|
+
pipe(
|
|
2147
|
+
listState,
|
|
2148
|
+
filter(({ items }) => items.length > 0),
|
|
2149
|
+
withLatestFrom(totalCount, data),
|
|
2150
|
+
filter(([{ items }, totalCount2]) => items[items.length - 1].originalIndex === totalCount2 - 1),
|
|
2151
|
+
map(([, totalCount2, data2]) => [totalCount2 - 1, data2]),
|
|
2152
|
+
distinctUntilChanged(tupleComparator),
|
|
2153
|
+
map(([count]) => count)
|
|
2154
|
+
)
|
|
2155
|
+
);
|
|
2156
|
+
const startReached = streamFromEmitter(
|
|
2157
|
+
pipe(
|
|
2158
|
+
listState,
|
|
2159
|
+
throttleTime(200),
|
|
2160
|
+
filter(({ items, topItems }) => {
|
|
2161
|
+
return items.length > 0 && items[0].originalIndex === topItems.length;
|
|
2162
|
+
}),
|
|
2163
|
+
map(({ items }) => items[0].index),
|
|
2164
|
+
distinctUntilChanged()
|
|
2165
|
+
)
|
|
2166
|
+
);
|
|
2167
|
+
const rangeChanged = streamFromEmitter(
|
|
2168
|
+
pipe(
|
|
2169
|
+
listState,
|
|
2170
|
+
filter(({ items }) => items.length > 0),
|
|
2171
|
+
map(({ items }) => {
|
|
2172
|
+
let startIndex = 0;
|
|
2173
|
+
let endIndex = items.length - 1;
|
|
2174
|
+
while (items[startIndex].type === "group" && startIndex < endIndex) {
|
|
2175
|
+
startIndex++;
|
|
2176
|
+
}
|
|
2177
|
+
while (items[endIndex].type === "group" && endIndex > startIndex) {
|
|
2178
|
+
endIndex--;
|
|
2179
|
+
}
|
|
2180
|
+
return {
|
|
2181
|
+
startIndex: items[startIndex].index,
|
|
2182
|
+
endIndex: items[endIndex].index
|
|
2183
|
+
};
|
|
2184
|
+
}),
|
|
2185
|
+
distinctUntilChanged(rangeComparator)
|
|
2186
|
+
)
|
|
2187
|
+
);
|
|
2188
|
+
return { listState, topItemsIndexes, endReached, startReached, rangeChanged, itemsRendered, ...stateFlags };
|
|
2189
|
+
},
|
|
2190
|
+
tup(
|
|
2191
|
+
sizeSystem,
|
|
2192
|
+
groupedListSystem,
|
|
2193
|
+
sizeRangeSystem,
|
|
2194
|
+
initialTopMostItemIndexSystem,
|
|
2195
|
+
scrollToIndexSystem,
|
|
2196
|
+
stateFlagsSystem,
|
|
2197
|
+
propsReadySystem,
|
|
2198
|
+
recalcSystem
|
|
2199
|
+
),
|
|
2200
|
+
{ singleton: true }
|
|
2201
|
+
);
|
|
2202
|
+
const initialItemCountSystem = system(
|
|
2203
|
+
([{ sizes, firstItemIndex, data, gap }, { listState }, { didMount }]) => {
|
|
2204
|
+
const initialItemCount = statefulStream(0);
|
|
2205
|
+
connect(
|
|
2206
|
+
pipe(
|
|
2207
|
+
didMount,
|
|
2208
|
+
withLatestFrom(initialItemCount),
|
|
2209
|
+
filter(([, count]) => count !== 0),
|
|
2210
|
+
withLatestFrom(sizes, firstItemIndex, gap, data),
|
|
2211
|
+
map(([[, count], sizes2, firstItemIndex2, gap2, data2 = []]) => {
|
|
2212
|
+
let includedGroupsCount = 0;
|
|
2213
|
+
if (sizes2.groupIndices.length > 0) {
|
|
2214
|
+
for (const index of sizes2.groupIndices) {
|
|
2215
|
+
if (index - includedGroupsCount >= count) {
|
|
2216
|
+
break;
|
|
2217
|
+
}
|
|
2218
|
+
includedGroupsCount++;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
const adjustedCount = count + includedGroupsCount;
|
|
2222
|
+
const items = Array.from({ length: adjustedCount }).map((_, index) => ({ index, size: 0, offset: 0, data: data2[index] }));
|
|
2223
|
+
return buildListState(items, [], adjustedCount, gap2, sizes2, firstItemIndex2);
|
|
2224
|
+
})
|
|
2225
|
+
),
|
|
2226
|
+
listState
|
|
2227
|
+
);
|
|
2228
|
+
return { initialItemCount };
|
|
2229
|
+
},
|
|
2230
|
+
tup(sizeSystem, listStateSystem, propsReadySystem),
|
|
2231
|
+
{ singleton: true }
|
|
2232
|
+
);
|
|
2233
|
+
const scrollSeekSystem = system(
|
|
2234
|
+
([{ scrollVelocity }]) => {
|
|
2235
|
+
const isSeeking = statefulStream(false);
|
|
2236
|
+
const rangeChanged = stream();
|
|
2237
|
+
const scrollSeekConfiguration = statefulStream(false);
|
|
2238
|
+
connect(
|
|
2239
|
+
pipe(
|
|
2240
|
+
scrollVelocity,
|
|
2241
|
+
withLatestFrom(scrollSeekConfiguration, isSeeking, rangeChanged),
|
|
2242
|
+
filter(([_, config]) => !!config),
|
|
2243
|
+
map(([speed, config, isSeeking2, range]) => {
|
|
2244
|
+
const { exit, enter } = config;
|
|
2245
|
+
if (isSeeking2) {
|
|
2246
|
+
if (exit(speed, range)) {
|
|
2247
|
+
return false;
|
|
2248
|
+
}
|
|
2249
|
+
} else {
|
|
2250
|
+
if (enter(speed, range)) {
|
|
2251
|
+
return true;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
return isSeeking2;
|
|
2255
|
+
}),
|
|
2256
|
+
distinctUntilChanged()
|
|
2257
|
+
),
|
|
2258
|
+
isSeeking
|
|
2259
|
+
);
|
|
2260
|
+
subscribe(
|
|
2261
|
+
pipe(combineLatest(isSeeking, scrollVelocity, rangeChanged), withLatestFrom(scrollSeekConfiguration)),
|
|
2262
|
+
([[isSeeking2, velocity, range], config]) => isSeeking2 && config && config.change && config.change(velocity, range)
|
|
2263
|
+
);
|
|
2264
|
+
return { isSeeking, scrollSeekConfiguration, scrollVelocity, scrollSeekRangeChanged: rangeChanged };
|
|
2265
|
+
},
|
|
2266
|
+
tup(stateFlagsSystem),
|
|
2267
|
+
{ singleton: true }
|
|
2268
|
+
);
|
|
2269
|
+
const topItemCountSystem = system(([{ topItemsIndexes }]) => {
|
|
2270
|
+
const topItemCount = statefulStream(0);
|
|
2271
|
+
connect(
|
|
2272
|
+
pipe(
|
|
2273
|
+
topItemCount,
|
|
2274
|
+
filter((length) => length > 0),
|
|
2275
|
+
map((length) => Array.from({ length }).map((_, index) => index))
|
|
2276
|
+
),
|
|
2277
|
+
topItemsIndexes
|
|
2278
|
+
);
|
|
2279
|
+
return { topItemCount };
|
|
2280
|
+
}, tup(listStateSystem));
|
|
2281
|
+
const totalListHeightSystem = system(
|
|
2282
|
+
([{ footerHeight, headerHeight, fixedHeaderHeight, fixedFooterHeight }, { listState }]) => {
|
|
2283
|
+
const totalListHeightChanged = stream();
|
|
2284
|
+
const totalListHeight = statefulStreamFromEmitter(
|
|
2285
|
+
pipe(
|
|
2286
|
+
combineLatest(footerHeight, fixedFooterHeight, headerHeight, fixedHeaderHeight, listState),
|
|
2287
|
+
map(([footerHeight2, fixedFooterHeight2, headerHeight2, fixedHeaderHeight2, listState2]) => {
|
|
2288
|
+
return footerHeight2 + fixedFooterHeight2 + headerHeight2 + fixedHeaderHeight2 + listState2.offsetBottom + listState2.bottom;
|
|
2289
|
+
})
|
|
2290
|
+
),
|
|
2291
|
+
0
|
|
2292
|
+
);
|
|
2293
|
+
connect(duc(totalListHeight), totalListHeightChanged);
|
|
2294
|
+
return { totalListHeight, totalListHeightChanged };
|
|
2295
|
+
},
|
|
2296
|
+
tup(domIOSystem, listStateSystem),
|
|
2297
|
+
{ singleton: true }
|
|
2298
|
+
);
|
|
2299
|
+
function simpleMemoize(func) {
|
|
2300
|
+
let called = false;
|
|
2301
|
+
let result;
|
|
2302
|
+
return () => {
|
|
2303
|
+
if (!called) {
|
|
2304
|
+
called = true;
|
|
2305
|
+
result = func();
|
|
2306
|
+
}
|
|
2307
|
+
return result;
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
const isMobileSafari = simpleMemoize(() => {
|
|
2311
|
+
return /iP(ad|od|hone)/i.test(navigator.userAgent) && /WebKit/i.test(navigator.userAgent);
|
|
2312
|
+
});
|
|
2313
|
+
const upwardScrollFixSystem = system(
|
|
2314
|
+
([
|
|
2315
|
+
{ scrollBy, scrollTop, deviation, scrollingInProgress },
|
|
2316
|
+
{ isScrolling, isAtBottom, scrollDirection, lastJumpDueToItemResize },
|
|
2317
|
+
{ listState },
|
|
2318
|
+
{ beforeUnshiftWith, shiftWithOffset, sizes, gap },
|
|
2319
|
+
{ log },
|
|
2320
|
+
{ recalcInProgress }
|
|
2321
|
+
]) => {
|
|
2322
|
+
const deviationOffset = streamFromEmitter(
|
|
2323
|
+
pipe(
|
|
2324
|
+
listState,
|
|
2325
|
+
withLatestFrom(lastJumpDueToItemResize),
|
|
2326
|
+
scan(
|
|
2327
|
+
([, prevItems, prevTotalCount, prevTotalHeight], [{ items, totalCount, bottom, offsetBottom }, lastJumpDueToItemResize2]) => {
|
|
2328
|
+
const totalHeight = bottom + offsetBottom;
|
|
2329
|
+
let newDev = 0;
|
|
2330
|
+
if (prevTotalCount === totalCount) {
|
|
2331
|
+
if (prevItems.length > 0 && items.length > 0) {
|
|
2332
|
+
const atStart = items[0].originalIndex === 0 && prevItems[0].originalIndex === 0;
|
|
2333
|
+
if (!atStart) {
|
|
2334
|
+
newDev = totalHeight - prevTotalHeight;
|
|
2335
|
+
if (newDev !== 0) {
|
|
2336
|
+
newDev += lastJumpDueToItemResize2;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return [newDev, items, totalCount, totalHeight];
|
|
2342
|
+
},
|
|
2343
|
+
[0, [], 0, 0]
|
|
2344
|
+
),
|
|
2345
|
+
filter(([amount]) => amount !== 0),
|
|
2346
|
+
withLatestFrom(scrollTop, scrollDirection, scrollingInProgress, isAtBottom, log),
|
|
2347
|
+
filter(([, scrollTop2, scrollDirection2, scrollingInProgress2]) => {
|
|
2348
|
+
return !scrollingInProgress2 && scrollTop2 !== 0 && scrollDirection2 === UP;
|
|
2349
|
+
}),
|
|
2350
|
+
map(([[amount], , , , , log2]) => {
|
|
2351
|
+
log2("Upward scrolling compensation", { amount }, LogLevel.DEBUG);
|
|
2352
|
+
return amount;
|
|
2353
|
+
})
|
|
2354
|
+
)
|
|
2355
|
+
);
|
|
2356
|
+
function scrollByWith(offset) {
|
|
2357
|
+
if (offset > 0) {
|
|
2358
|
+
publish(scrollBy, { top: -offset, behavior: "auto" });
|
|
2359
|
+
publish(deviation, 0);
|
|
2360
|
+
} else {
|
|
2361
|
+
publish(deviation, 0);
|
|
2362
|
+
publish(scrollBy, { top: -offset, behavior: "auto" });
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
subscribe(pipe(deviationOffset, withLatestFrom(deviation, isScrolling)), ([offset, deviationAmount, isScrolling2]) => {
|
|
2366
|
+
if (isScrolling2 && isMobileSafari()) {
|
|
2367
|
+
publish(deviation, deviationAmount - offset);
|
|
2368
|
+
} else {
|
|
2369
|
+
scrollByWith(-offset);
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
subscribe(
|
|
2373
|
+
pipe(
|
|
2374
|
+
combineLatest(statefulStreamFromEmitter(isScrolling, false), deviation, recalcInProgress),
|
|
2375
|
+
filter(([is, deviation2, recalc]) => !is && !recalc && deviation2 !== 0),
|
|
2376
|
+
map(([_, deviation2]) => deviation2),
|
|
2377
|
+
throttleTime(1)
|
|
2378
|
+
),
|
|
2379
|
+
scrollByWith
|
|
2380
|
+
);
|
|
2381
|
+
connect(
|
|
2382
|
+
pipe(
|
|
2383
|
+
shiftWithOffset,
|
|
2384
|
+
map((offset) => {
|
|
2385
|
+
return { top: -offset };
|
|
2386
|
+
})
|
|
2387
|
+
),
|
|
2388
|
+
scrollBy
|
|
2389
|
+
);
|
|
2390
|
+
subscribe(
|
|
2391
|
+
pipe(
|
|
2392
|
+
beforeUnshiftWith,
|
|
2393
|
+
withLatestFrom(sizes, gap),
|
|
2394
|
+
map(([offset, { lastSize }, gap2]) => offset * lastSize + offset * gap2)
|
|
2395
|
+
),
|
|
2396
|
+
(offset) => {
|
|
2397
|
+
publish(deviation, offset);
|
|
2398
|
+
requestAnimationFrame(() => {
|
|
2399
|
+
publish(scrollBy, { top: offset });
|
|
2400
|
+
requestAnimationFrame(() => {
|
|
2401
|
+
publish(deviation, 0);
|
|
2402
|
+
publish(recalcInProgress, false);
|
|
2403
|
+
});
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
);
|
|
2407
|
+
return { deviation };
|
|
2408
|
+
},
|
|
2409
|
+
tup(domIOSystem, stateFlagsSystem, listStateSystem, sizeSystem, loggerSystem, recalcSystem)
|
|
2410
|
+
);
|
|
2411
|
+
const initialScrollTopSystem = system(
|
|
2412
|
+
([{ totalListHeight }, { didMount }, { scrollTo }]) => {
|
|
2413
|
+
const initialScrollTop = statefulStream(0);
|
|
2414
|
+
subscribe(
|
|
2415
|
+
pipe(
|
|
2416
|
+
didMount,
|
|
2417
|
+
withLatestFrom(initialScrollTop),
|
|
2418
|
+
filter(([, offset]) => offset !== 0),
|
|
2419
|
+
map(([, offset]) => ({ top: offset }))
|
|
2420
|
+
),
|
|
2421
|
+
(location) => {
|
|
2422
|
+
handleNext(
|
|
2423
|
+
pipe(
|
|
2424
|
+
totalListHeight,
|
|
2425
|
+
filter((val) => val !== 0)
|
|
2426
|
+
),
|
|
2427
|
+
() => {
|
|
2428
|
+
setTimeout(() => {
|
|
2429
|
+
publish(scrollTo, location);
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
);
|
|
2435
|
+
return {
|
|
2436
|
+
initialScrollTop
|
|
2437
|
+
};
|
|
2438
|
+
},
|
|
2439
|
+
tup(totalListHeightSystem, propsReadySystem, domIOSystem),
|
|
2440
|
+
{ singleton: true }
|
|
2441
|
+
);
|
|
2442
|
+
const alignToBottomSystem = system(
|
|
2443
|
+
([{ viewportHeight }, { totalListHeight }]) => {
|
|
2444
|
+
const alignToBottom = statefulStream(false);
|
|
2445
|
+
const paddingTopAddition = statefulStreamFromEmitter(
|
|
2446
|
+
pipe(
|
|
2447
|
+
combineLatest(alignToBottom, viewportHeight, totalListHeight),
|
|
2448
|
+
filter(([enabled]) => enabled),
|
|
2449
|
+
map(([, viewportHeight2, totalListHeight2]) => {
|
|
2450
|
+
return Math.max(0, viewportHeight2 - totalListHeight2);
|
|
2451
|
+
}),
|
|
2452
|
+
distinctUntilChanged()
|
|
2453
|
+
),
|
|
2454
|
+
0
|
|
2455
|
+
);
|
|
2456
|
+
return { alignToBottom, paddingTopAddition };
|
|
2457
|
+
},
|
|
2458
|
+
tup(domIOSystem, totalListHeightSystem),
|
|
2459
|
+
{ singleton: true }
|
|
2460
|
+
);
|
|
2461
|
+
const windowScrollerSystem = system(([{ scrollTo, scrollContainerState }]) => {
|
|
2462
|
+
const windowScrollContainerState = stream();
|
|
2463
|
+
const windowViewportRect = stream();
|
|
2464
|
+
const windowScrollTo = stream();
|
|
2465
|
+
const useWindowScroll = statefulStream(false);
|
|
2466
|
+
const customScrollParent = statefulStream(void 0);
|
|
2467
|
+
connect(
|
|
2468
|
+
pipe(
|
|
2469
|
+
combineLatest(windowScrollContainerState, windowViewportRect),
|
|
2470
|
+
map(([{ viewportHeight, scrollTop: windowScrollTop, scrollHeight }, { offsetTop }]) => {
|
|
2471
|
+
return {
|
|
2472
|
+
scrollTop: Math.max(0, windowScrollTop - offsetTop),
|
|
2473
|
+
scrollHeight,
|
|
2474
|
+
viewportHeight
|
|
2475
|
+
};
|
|
2476
|
+
})
|
|
2477
|
+
),
|
|
2478
|
+
scrollContainerState
|
|
2479
|
+
);
|
|
2480
|
+
connect(
|
|
2481
|
+
pipe(
|
|
2482
|
+
scrollTo,
|
|
2483
|
+
withLatestFrom(windowViewportRect),
|
|
2484
|
+
map(([scrollTo2, { offsetTop }]) => {
|
|
2485
|
+
return {
|
|
2486
|
+
...scrollTo2,
|
|
2487
|
+
top: scrollTo2.top + offsetTop
|
|
2488
|
+
};
|
|
2489
|
+
})
|
|
2490
|
+
),
|
|
2491
|
+
windowScrollTo
|
|
2492
|
+
);
|
|
2493
|
+
return {
|
|
2494
|
+
useWindowScroll,
|
|
2495
|
+
customScrollParent,
|
|
2496
|
+
windowScrollContainerState,
|
|
2497
|
+
windowViewportRect,
|
|
2498
|
+
windowScrollTo
|
|
2499
|
+
};
|
|
2500
|
+
}, tup(domIOSystem));
|
|
2501
|
+
const scrollIntoViewSystem = system(
|
|
2502
|
+
([
|
|
2503
|
+
{ sizes, totalCount, gap },
|
|
2504
|
+
{ scrollTop, viewportHeight, headerHeight, fixedHeaderHeight, fixedFooterHeight, scrollingInProgress },
|
|
2505
|
+
{ scrollToIndex }
|
|
2506
|
+
]) => {
|
|
2507
|
+
const scrollIntoView = stream();
|
|
2508
|
+
connect(
|
|
2509
|
+
pipe(
|
|
2510
|
+
scrollIntoView,
|
|
2511
|
+
withLatestFrom(sizes, viewportHeight, totalCount, headerHeight, fixedHeaderHeight, fixedFooterHeight, scrollTop),
|
|
2512
|
+
withLatestFrom(gap),
|
|
2513
|
+
map(([[viewLocation, sizes2, viewportHeight2, totalCount2, headerHeight2, fixedHeaderHeight2, fixedFooterHeight2, scrollTop2], gap2]) => {
|
|
2514
|
+
const { done, behavior, align, ...rest } = viewLocation;
|
|
2515
|
+
let location = null;
|
|
2516
|
+
const actualIndex = originalIndexFromLocation(viewLocation, sizes2, totalCount2 - 1);
|
|
2517
|
+
const itemTop2 = offsetOf(actualIndex, sizes2.offsetTree, gap2) + headerHeight2 + fixedHeaderHeight2;
|
|
2518
|
+
if (itemTop2 < scrollTop2 + fixedHeaderHeight2) {
|
|
2519
|
+
location = { ...rest, behavior, align: align ?? "start" };
|
|
2520
|
+
} else {
|
|
2521
|
+
const itemBottom = itemTop2 + findMaxKeyValue(sizes2.sizeTree, actualIndex)[1];
|
|
2522
|
+
if (itemBottom > scrollTop2 + viewportHeight2 - fixedFooterHeight2) {
|
|
2523
|
+
location = { ...rest, behavior, align: align ?? "end" };
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
if (location) {
|
|
2527
|
+
done && handleNext(
|
|
2528
|
+
pipe(
|
|
2529
|
+
scrollingInProgress,
|
|
2530
|
+
skip(1),
|
|
2531
|
+
filter((value) => value === false)
|
|
2532
|
+
),
|
|
2533
|
+
done
|
|
2534
|
+
);
|
|
2535
|
+
} else {
|
|
2536
|
+
done && done();
|
|
2537
|
+
}
|
|
2538
|
+
return location;
|
|
2539
|
+
}),
|
|
2540
|
+
filter((value) => value !== null)
|
|
2541
|
+
),
|
|
2542
|
+
scrollToIndex
|
|
2543
|
+
);
|
|
2544
|
+
return {
|
|
2545
|
+
scrollIntoView
|
|
2546
|
+
};
|
|
2547
|
+
},
|
|
2548
|
+
tup(sizeSystem, domIOSystem, scrollToIndexSystem, listStateSystem, loggerSystem),
|
|
2549
|
+
{ singleton: true }
|
|
2550
|
+
);
|
|
2551
|
+
const featureGroup1System = system(
|
|
2552
|
+
([
|
|
2553
|
+
sizeRange,
|
|
2554
|
+
initialItemCount,
|
|
2555
|
+
propsReady,
|
|
2556
|
+
scrollSeek,
|
|
2557
|
+
totalListHeight,
|
|
2558
|
+
initialScrollTopSystem2,
|
|
2559
|
+
alignToBottom,
|
|
2560
|
+
windowScroller,
|
|
2561
|
+
scrollIntoView
|
|
2562
|
+
]) => {
|
|
2563
|
+
return {
|
|
2564
|
+
...sizeRange,
|
|
2565
|
+
...initialItemCount,
|
|
2566
|
+
...propsReady,
|
|
2567
|
+
...scrollSeek,
|
|
2568
|
+
...totalListHeight,
|
|
2569
|
+
...initialScrollTopSystem2,
|
|
2570
|
+
...alignToBottom,
|
|
2571
|
+
...windowScroller,
|
|
2572
|
+
...scrollIntoView
|
|
2573
|
+
};
|
|
2574
|
+
},
|
|
2575
|
+
tup(
|
|
2576
|
+
sizeRangeSystem,
|
|
2577
|
+
initialItemCountSystem,
|
|
2578
|
+
propsReadySystem,
|
|
2579
|
+
scrollSeekSystem,
|
|
2580
|
+
totalListHeightSystem,
|
|
2581
|
+
initialScrollTopSystem,
|
|
2582
|
+
alignToBottomSystem,
|
|
2583
|
+
windowScrollerSystem,
|
|
2584
|
+
scrollIntoViewSystem
|
|
2585
|
+
)
|
|
2586
|
+
);
|
|
2587
|
+
const listSystem = system(
|
|
2588
|
+
([
|
|
2589
|
+
{
|
|
2590
|
+
totalCount,
|
|
2591
|
+
sizeRanges,
|
|
2592
|
+
fixedItemSize,
|
|
2593
|
+
defaultItemSize,
|
|
2594
|
+
trackItemSizes,
|
|
2595
|
+
itemSize,
|
|
2596
|
+
data,
|
|
2597
|
+
firstItemIndex,
|
|
2598
|
+
groupIndices,
|
|
2599
|
+
statefulTotalCount,
|
|
2600
|
+
gap
|
|
2601
|
+
},
|
|
2602
|
+
{ initialTopMostItemIndex, scrolledToInitialItem },
|
|
2603
|
+
domIO,
|
|
2604
|
+
followOutput,
|
|
2605
|
+
{ listState, topItemsIndexes, ...flags },
|
|
2606
|
+
{ scrollToIndex },
|
|
2607
|
+
_,
|
|
2608
|
+
{ topItemCount },
|
|
2609
|
+
{ groupCounts },
|
|
2610
|
+
featureGroup1,
|
|
2611
|
+
log
|
|
2612
|
+
]) => {
|
|
2613
|
+
connect(flags.rangeChanged, featureGroup1.scrollSeekRangeChanged);
|
|
2614
|
+
connect(
|
|
2615
|
+
pipe(
|
|
2616
|
+
featureGroup1.windowViewportRect,
|
|
2617
|
+
map((value) => value.visibleHeight)
|
|
2618
|
+
),
|
|
2619
|
+
domIO.viewportHeight
|
|
2620
|
+
);
|
|
2621
|
+
return {
|
|
2622
|
+
totalCount,
|
|
2623
|
+
data,
|
|
2624
|
+
firstItemIndex,
|
|
2625
|
+
sizeRanges,
|
|
2626
|
+
initialTopMostItemIndex,
|
|
2627
|
+
scrolledToInitialItem,
|
|
2628
|
+
topItemsIndexes,
|
|
2629
|
+
topItemCount,
|
|
2630
|
+
groupCounts,
|
|
2631
|
+
fixedItemHeight: fixedItemSize,
|
|
2632
|
+
defaultItemHeight: defaultItemSize,
|
|
2633
|
+
gap,
|
|
2634
|
+
...followOutput,
|
|
2635
|
+
statefulTotalCount,
|
|
2636
|
+
listState,
|
|
2637
|
+
scrollToIndex,
|
|
2638
|
+
trackItemSizes,
|
|
2639
|
+
itemSize,
|
|
2640
|
+
groupIndices,
|
|
2641
|
+
...flags,
|
|
2642
|
+
...featureGroup1,
|
|
2643
|
+
...domIO,
|
|
2644
|
+
...log
|
|
2645
|
+
};
|
|
2646
|
+
},
|
|
2647
|
+
tup(
|
|
2648
|
+
sizeSystem,
|
|
2649
|
+
initialTopMostItemIndexSystem,
|
|
2650
|
+
domIOSystem,
|
|
2651
|
+
followOutputSystem,
|
|
2652
|
+
listStateSystem,
|
|
2653
|
+
scrollToIndexSystem,
|
|
2654
|
+
upwardScrollFixSystem,
|
|
2655
|
+
topItemCountSystem,
|
|
2656
|
+
groupedListSystem,
|
|
2657
|
+
featureGroup1System,
|
|
2658
|
+
loggerSystem
|
|
2659
|
+
)
|
|
2660
|
+
);
|
|
2661
|
+
const WEBKIT_STICKY = "-webkit-sticky";
|
|
2662
|
+
const STICKY = "sticky";
|
|
2663
|
+
const positionStickyCssValue = simpleMemoize(() => {
|
|
2664
|
+
if (typeof document === "undefined") {
|
|
2665
|
+
return STICKY;
|
|
2666
|
+
}
|
|
2667
|
+
const node = document.createElement("div");
|
|
2668
|
+
node.style.position = WEBKIT_STICKY;
|
|
2669
|
+
return node.style.position === WEBKIT_STICKY ? WEBKIT_STICKY : STICKY;
|
|
2670
|
+
});
|
|
2671
|
+
function useWindowViewportRectRef(callback, customScrollParent) {
|
|
2672
|
+
const viewportInfo = React.useRef(null);
|
|
2673
|
+
const calculateInfo = React.useCallback(
|
|
2674
|
+
(element) => {
|
|
2675
|
+
if (element === null || !element.offsetParent) {
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
const rect = element.getBoundingClientRect();
|
|
2679
|
+
const visibleWidth = rect.width;
|
|
2680
|
+
let visibleHeight, offsetTop;
|
|
2681
|
+
if (customScrollParent) {
|
|
2682
|
+
const customScrollParentRect = customScrollParent.getBoundingClientRect();
|
|
2683
|
+
const deltaTop = rect.top - customScrollParentRect.top;
|
|
2684
|
+
visibleHeight = customScrollParentRect.height - Math.max(0, deltaTop);
|
|
2685
|
+
offsetTop = deltaTop + customScrollParent.scrollTop;
|
|
2686
|
+
} else {
|
|
2687
|
+
visibleHeight = window.innerHeight - Math.max(0, rect.top);
|
|
2688
|
+
offsetTop = rect.top + window.pageYOffset;
|
|
2689
|
+
}
|
|
2690
|
+
viewportInfo.current = {
|
|
2691
|
+
offsetTop,
|
|
2692
|
+
visibleHeight,
|
|
2693
|
+
visibleWidth
|
|
2694
|
+
};
|
|
2695
|
+
callback(viewportInfo.current);
|
|
2696
|
+
},
|
|
2697
|
+
[callback, customScrollParent]
|
|
2698
|
+
);
|
|
2699
|
+
const { callbackRef, ref } = useSizeWithElRef(calculateInfo);
|
|
2700
|
+
const scrollAndResizeEventHandler = React.useCallback(() => {
|
|
2701
|
+
calculateInfo(ref.current);
|
|
2702
|
+
}, [calculateInfo, ref]);
|
|
2703
|
+
React.useEffect(() => {
|
|
2704
|
+
if (customScrollParent) {
|
|
2705
|
+
customScrollParent.addEventListener("scroll", scrollAndResizeEventHandler);
|
|
2706
|
+
const observer = new ResizeObserver(scrollAndResizeEventHandler);
|
|
2707
|
+
observer.observe(customScrollParent);
|
|
2708
|
+
return () => {
|
|
2709
|
+
customScrollParent.removeEventListener("scroll", scrollAndResizeEventHandler);
|
|
2710
|
+
observer.unobserve(customScrollParent);
|
|
2711
|
+
};
|
|
2712
|
+
} else {
|
|
2713
|
+
window.addEventListener("scroll", scrollAndResizeEventHandler);
|
|
2714
|
+
window.addEventListener("resize", scrollAndResizeEventHandler);
|
|
2715
|
+
return () => {
|
|
2716
|
+
window.removeEventListener("scroll", scrollAndResizeEventHandler);
|
|
2717
|
+
window.removeEventListener("resize", scrollAndResizeEventHandler);
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
}, [scrollAndResizeEventHandler, customScrollParent]);
|
|
2721
|
+
return callbackRef;
|
|
2722
|
+
}
|
|
2723
|
+
const VirtuosoMockContext = React.createContext(void 0);
|
|
2724
|
+
const VirtuosoGridMockContext = React.createContext(void 0);
|
|
2725
|
+
function identity(value) {
|
|
2726
|
+
return value;
|
|
2727
|
+
}
|
|
2728
|
+
const listComponentPropsSystem = /* @__PURE__ */ system(() => {
|
|
2729
|
+
const itemContent = statefulStream((index) => `Item ${index}`);
|
|
2730
|
+
const context = statefulStream(null);
|
|
2731
|
+
const groupContent = statefulStream((index) => `Group ${index}`);
|
|
2732
|
+
const components = statefulStream({});
|
|
2733
|
+
const computeItemKey = statefulStream(identity);
|
|
2734
|
+
const headerFooterTag = statefulStream("div");
|
|
2735
|
+
const scrollerRef = statefulStream(noop);
|
|
2736
|
+
const distinctProp = (propName, defaultValue = null) => {
|
|
2737
|
+
return statefulStreamFromEmitter(
|
|
2738
|
+
pipe(
|
|
2739
|
+
components,
|
|
2740
|
+
map((components2) => components2[propName]),
|
|
2741
|
+
distinctUntilChanged()
|
|
2742
|
+
),
|
|
2743
|
+
defaultValue
|
|
2744
|
+
);
|
|
2745
|
+
};
|
|
2746
|
+
return {
|
|
2747
|
+
context,
|
|
2748
|
+
itemContent,
|
|
2749
|
+
groupContent,
|
|
2750
|
+
components,
|
|
2751
|
+
computeItemKey,
|
|
2752
|
+
headerFooterTag,
|
|
2753
|
+
scrollerRef,
|
|
2754
|
+
FooterComponent: distinctProp("Footer"),
|
|
2755
|
+
HeaderComponent: distinctProp("Header"),
|
|
2756
|
+
TopItemListComponent: distinctProp("TopItemList"),
|
|
2757
|
+
ListComponent: distinctProp("List", "div"),
|
|
2758
|
+
ItemComponent: distinctProp("Item", "div"),
|
|
2759
|
+
GroupComponent: distinctProp("Group", "div"),
|
|
2760
|
+
ScrollerComponent: distinctProp("Scroller", "div"),
|
|
2761
|
+
EmptyPlaceholder: distinctProp("EmptyPlaceholder"),
|
|
2762
|
+
ScrollSeekPlaceholder: distinctProp("ScrollSeekPlaceholder")
|
|
2763
|
+
};
|
|
2764
|
+
});
|
|
2765
|
+
const combinedSystem$2 = /* @__PURE__ */ system(([listSystem2, propsSystem]) => {
|
|
2766
|
+
return { ...listSystem2, ...propsSystem };
|
|
2767
|
+
}, tup(listSystem, listComponentPropsSystem));
|
|
2768
|
+
const DefaultScrollSeekPlaceholder$1 = ({ height }) => /* @__PURE__ */ React.createElement("div", { style: { height } });
|
|
2769
|
+
const GROUP_STYLE = { position: positionStickyCssValue(), zIndex: 1, overflowAnchor: "none" };
|
|
2770
|
+
const ITEM_STYLE = { overflowAnchor: "none" };
|
|
2771
|
+
const Items$1 = /* @__PURE__ */ React.memo(function VirtuosoItems({ showTopList = false }) {
|
|
2772
|
+
const listState = useEmitterValue$2("listState");
|
|
2773
|
+
const sizeRanges = usePublisher$2("sizeRanges");
|
|
2774
|
+
const useWindowScroll = useEmitterValue$2("useWindowScroll");
|
|
2775
|
+
const customScrollParent = useEmitterValue$2("customScrollParent");
|
|
2776
|
+
const windowScrollContainerStateCallback = usePublisher$2("windowScrollContainerState");
|
|
2777
|
+
const _scrollContainerStateCallback = usePublisher$2("scrollContainerState");
|
|
2778
|
+
const scrollContainerStateCallback = customScrollParent || useWindowScroll ? windowScrollContainerStateCallback : _scrollContainerStateCallback;
|
|
2779
|
+
const itemContent = useEmitterValue$2("itemContent");
|
|
2780
|
+
const context = useEmitterValue$2("context");
|
|
2781
|
+
const groupContent = useEmitterValue$2("groupContent");
|
|
2782
|
+
const trackItemSizes = useEmitterValue$2("trackItemSizes");
|
|
2783
|
+
const itemSize = useEmitterValue$2("itemSize");
|
|
2784
|
+
const log = useEmitterValue$2("log");
|
|
2785
|
+
const listGap = usePublisher$2("gap");
|
|
2786
|
+
const { callbackRef } = useChangedListContentsSizes(
|
|
2787
|
+
sizeRanges,
|
|
2788
|
+
itemSize,
|
|
2789
|
+
trackItemSizes,
|
|
2790
|
+
showTopList ? noop : scrollContainerStateCallback,
|
|
2791
|
+
log,
|
|
2792
|
+
listGap,
|
|
2793
|
+
customScrollParent
|
|
2794
|
+
);
|
|
2795
|
+
const [deviation, setDeviation] = React.useState(0);
|
|
2796
|
+
useEmitter$2("deviation", (value) => {
|
|
2797
|
+
if (deviation !== value) {
|
|
2798
|
+
setDeviation(value);
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2801
|
+
const EmptyPlaceholder = useEmitterValue$2("EmptyPlaceholder");
|
|
2802
|
+
const ScrollSeekPlaceholder = useEmitterValue$2("ScrollSeekPlaceholder") || DefaultScrollSeekPlaceholder$1;
|
|
2803
|
+
const ListComponent = useEmitterValue$2("ListComponent");
|
|
2804
|
+
const ItemComponent = useEmitterValue$2("ItemComponent");
|
|
2805
|
+
const GroupComponent = useEmitterValue$2("GroupComponent");
|
|
2806
|
+
const computeItemKey = useEmitterValue$2("computeItemKey");
|
|
2807
|
+
const isSeeking = useEmitterValue$2("isSeeking");
|
|
2808
|
+
const hasGroups2 = useEmitterValue$2("groupIndices").length > 0;
|
|
2809
|
+
const paddingTopAddition = useEmitterValue$2("paddingTopAddition");
|
|
2810
|
+
const containerStyle = showTopList ? {} : {
|
|
2811
|
+
boxSizing: "border-box",
|
|
2812
|
+
paddingTop: listState.offsetTop + paddingTopAddition,
|
|
2813
|
+
paddingBottom: listState.offsetBottom,
|
|
2814
|
+
marginTop: deviation
|
|
2815
|
+
};
|
|
2816
|
+
if (!showTopList && listState.totalCount === 0 && EmptyPlaceholder) {
|
|
2817
|
+
return React.createElement(EmptyPlaceholder, contextPropIfNotDomElement(EmptyPlaceholder, context));
|
|
2818
|
+
}
|
|
2819
|
+
return React.createElement(
|
|
2820
|
+
ListComponent,
|
|
2821
|
+
{
|
|
2822
|
+
...contextPropIfNotDomElement(ListComponent, context),
|
|
2823
|
+
ref: callbackRef,
|
|
2824
|
+
style: containerStyle,
|
|
2825
|
+
"data-test-id": showTopList ? "virtuoso-top-item-list" : "virtuoso-item-list"
|
|
2826
|
+
},
|
|
2827
|
+
(showTopList ? listState.topItems : listState.items).map((item) => {
|
|
2828
|
+
const index = item.originalIndex;
|
|
2829
|
+
const key = computeItemKey(index + listState.firstItemIndex, item.data, context);
|
|
2830
|
+
if (isSeeking) {
|
|
2831
|
+
return React.createElement(ScrollSeekPlaceholder, {
|
|
2832
|
+
...contextPropIfNotDomElement(ScrollSeekPlaceholder, context),
|
|
2833
|
+
key,
|
|
2834
|
+
index: item.index,
|
|
2835
|
+
height: item.size,
|
|
2836
|
+
type: item.type || "item",
|
|
2837
|
+
...item.type === "group" ? {} : { groupIndex: item.groupIndex }
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
if (item.type === "group") {
|
|
2841
|
+
return React.createElement(
|
|
2842
|
+
GroupComponent,
|
|
2843
|
+
{
|
|
2844
|
+
...contextPropIfNotDomElement(GroupComponent, context),
|
|
2845
|
+
key,
|
|
2846
|
+
"data-index": index,
|
|
2847
|
+
"data-known-size": item.size,
|
|
2848
|
+
"data-item-index": item.index,
|
|
2849
|
+
style: GROUP_STYLE
|
|
2850
|
+
},
|
|
2851
|
+
groupContent(item.index)
|
|
2852
|
+
);
|
|
2853
|
+
} else {
|
|
2854
|
+
return React.createElement(
|
|
2855
|
+
ItemComponent,
|
|
2856
|
+
{
|
|
2857
|
+
...contextPropIfNotDomElement(ItemComponent, context),
|
|
2858
|
+
key,
|
|
2859
|
+
"data-index": index,
|
|
2860
|
+
"data-known-size": item.size,
|
|
2861
|
+
"data-item-index": item.index,
|
|
2862
|
+
"data-item-group-index": item.groupIndex,
|
|
2863
|
+
item: item.data,
|
|
2864
|
+
style: ITEM_STYLE
|
|
2865
|
+
},
|
|
2866
|
+
hasGroups2 ? itemContent(item.index, item.groupIndex, item.data, context) : itemContent(item.index, item.data, context)
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
})
|
|
2870
|
+
);
|
|
2871
|
+
});
|
|
2872
|
+
const scrollerStyle = {
|
|
2873
|
+
height: "100%",
|
|
2874
|
+
outline: "none",
|
|
2875
|
+
overflowY: "auto",
|
|
2876
|
+
position: "relative",
|
|
2877
|
+
WebkitOverflowScrolling: "touch"
|
|
2878
|
+
};
|
|
2879
|
+
const viewportStyle = {
|
|
2880
|
+
width: "100%",
|
|
2881
|
+
height: "100%",
|
|
2882
|
+
position: "absolute",
|
|
2883
|
+
top: 0
|
|
2884
|
+
};
|
|
2885
|
+
const topItemListStyle = {
|
|
2886
|
+
width: "100%",
|
|
2887
|
+
position: positionStickyCssValue(),
|
|
2888
|
+
top: 0,
|
|
2889
|
+
zIndex: 1
|
|
2890
|
+
};
|
|
2891
|
+
function contextPropIfNotDomElement(element, context) {
|
|
2892
|
+
if (typeof element === "string") {
|
|
2893
|
+
return void 0;
|
|
2894
|
+
}
|
|
2895
|
+
return { context };
|
|
2896
|
+
}
|
|
2897
|
+
const Header$1 = /* @__PURE__ */ React.memo(function VirtuosoHeader() {
|
|
2898
|
+
const Header2 = useEmitterValue$2("HeaderComponent");
|
|
2899
|
+
const headerHeight = usePublisher$2("headerHeight");
|
|
2900
|
+
const headerFooterTag = useEmitterValue$2("headerFooterTag");
|
|
2901
|
+
const ref = useSize((el) => headerHeight(correctItemSize(el, "height")));
|
|
2902
|
+
const context = useEmitterValue$2("context");
|
|
2903
|
+
return Header2 ? React.createElement(headerFooterTag, { ref }, React.createElement(Header2, contextPropIfNotDomElement(Header2, context))) : null;
|
|
2904
|
+
});
|
|
2905
|
+
const Footer$1 = /* @__PURE__ */ React.memo(function VirtuosoFooter() {
|
|
2906
|
+
const Footer2 = useEmitterValue$2("FooterComponent");
|
|
2907
|
+
const footerHeight = usePublisher$2("footerHeight");
|
|
2908
|
+
const headerFooterTag = useEmitterValue$2("headerFooterTag");
|
|
2909
|
+
const ref = useSize((el) => footerHeight(correctItemSize(el, "height")));
|
|
2910
|
+
const context = useEmitterValue$2("context");
|
|
2911
|
+
return Footer2 ? React.createElement(headerFooterTag, { ref }, React.createElement(Footer2, contextPropIfNotDomElement(Footer2, context))) : null;
|
|
2912
|
+
});
|
|
2913
|
+
function buildScroller({ usePublisher: usePublisher2, useEmitter: useEmitter2, useEmitterValue: useEmitterValue2 }) {
|
|
2914
|
+
const Scroller2 = React.memo(function VirtuosoScroller({ style, children, ...props }) {
|
|
2915
|
+
const scrollContainerStateCallback = usePublisher2("scrollContainerState");
|
|
2916
|
+
const ScrollerComponent = useEmitterValue2("ScrollerComponent");
|
|
2917
|
+
const smoothScrollTargetReached = usePublisher2("smoothScrollTargetReached");
|
|
2918
|
+
const scrollerRefCallback = useEmitterValue2("scrollerRef");
|
|
2919
|
+
const context = useEmitterValue2("context");
|
|
2920
|
+
const { scrollerRef, scrollByCallback, scrollToCallback } = useScrollTop(
|
|
2921
|
+
scrollContainerStateCallback,
|
|
2922
|
+
smoothScrollTargetReached,
|
|
2923
|
+
ScrollerComponent,
|
|
2924
|
+
scrollerRefCallback
|
|
2925
|
+
);
|
|
2926
|
+
useEmitter2("scrollTo", scrollToCallback);
|
|
2927
|
+
useEmitter2("scrollBy", scrollByCallback);
|
|
2928
|
+
return React.createElement(
|
|
2929
|
+
ScrollerComponent,
|
|
2930
|
+
{
|
|
2931
|
+
ref: scrollerRef,
|
|
2932
|
+
style: { ...scrollerStyle, ...style },
|
|
2933
|
+
"data-test-id": "virtuoso-scroller",
|
|
2934
|
+
"data-virtuoso-scroller": true,
|
|
2935
|
+
tabIndex: 0,
|
|
2936
|
+
...props,
|
|
2937
|
+
...contextPropIfNotDomElement(ScrollerComponent, context)
|
|
2938
|
+
},
|
|
2939
|
+
children
|
|
2940
|
+
);
|
|
2941
|
+
});
|
|
2942
|
+
return Scroller2;
|
|
2943
|
+
}
|
|
2944
|
+
function buildWindowScroller({ usePublisher: usePublisher2, useEmitter: useEmitter2, useEmitterValue: useEmitterValue2 }) {
|
|
2945
|
+
const Scroller2 = React.memo(function VirtuosoWindowScroller({ style, children, ...props }) {
|
|
2946
|
+
const scrollContainerStateCallback = usePublisher2("windowScrollContainerState");
|
|
2947
|
+
const ScrollerComponent = useEmitterValue2("ScrollerComponent");
|
|
2948
|
+
const smoothScrollTargetReached = usePublisher2("smoothScrollTargetReached");
|
|
2949
|
+
const totalListHeight = useEmitterValue2("totalListHeight");
|
|
2950
|
+
const deviation = useEmitterValue2("deviation");
|
|
2951
|
+
const customScrollParent = useEmitterValue2("customScrollParent");
|
|
2952
|
+
const context = useEmitterValue2("context");
|
|
2953
|
+
const { scrollerRef, scrollByCallback, scrollToCallback } = useScrollTop(
|
|
2954
|
+
scrollContainerStateCallback,
|
|
2955
|
+
smoothScrollTargetReached,
|
|
2956
|
+
ScrollerComponent,
|
|
2957
|
+
noop,
|
|
2958
|
+
customScrollParent
|
|
2959
|
+
);
|
|
2960
|
+
useIsomorphicLayoutEffect$1(() => {
|
|
2961
|
+
scrollerRef.current = customScrollParent ? customScrollParent : window;
|
|
2962
|
+
return () => {
|
|
2963
|
+
scrollerRef.current = null;
|
|
2964
|
+
};
|
|
2965
|
+
}, [scrollerRef, customScrollParent]);
|
|
2966
|
+
useEmitter2("windowScrollTo", scrollToCallback);
|
|
2967
|
+
useEmitter2("scrollBy", scrollByCallback);
|
|
2968
|
+
return React.createElement(
|
|
2969
|
+
ScrollerComponent,
|
|
2970
|
+
{
|
|
2971
|
+
style: { position: "relative", ...style, ...totalListHeight !== 0 ? { height: totalListHeight + deviation } : {} },
|
|
2972
|
+
"data-virtuoso-scroller": true,
|
|
2973
|
+
...props,
|
|
2974
|
+
...contextPropIfNotDomElement(ScrollerComponent, context)
|
|
2975
|
+
},
|
|
2976
|
+
children
|
|
2977
|
+
);
|
|
2978
|
+
});
|
|
2979
|
+
return Scroller2;
|
|
2980
|
+
}
|
|
2981
|
+
const Viewport$2 = ({ children }) => {
|
|
2982
|
+
const ctx = React.useContext(VirtuosoMockContext);
|
|
2983
|
+
const viewportHeight = usePublisher$2("viewportHeight");
|
|
2984
|
+
const fixedItemHeight = usePublisher$2("fixedItemHeight");
|
|
2985
|
+
const viewportRef = useSize(compose(viewportHeight, (el) => correctItemSize(el, "height")));
|
|
2986
|
+
React.useEffect(() => {
|
|
2987
|
+
if (ctx) {
|
|
2988
|
+
viewportHeight(ctx.viewportHeight);
|
|
2989
|
+
fixedItemHeight(ctx.itemHeight);
|
|
2990
|
+
}
|
|
2991
|
+
}, [ctx, viewportHeight, fixedItemHeight]);
|
|
2992
|
+
return /* @__PURE__ */ React.createElement("div", { style: viewportStyle, ref: viewportRef, "data-viewport-type": "element" }, children);
|
|
2993
|
+
};
|
|
2994
|
+
const WindowViewport$2 = ({ children }) => {
|
|
2995
|
+
const ctx = React.useContext(VirtuosoMockContext);
|
|
2996
|
+
const windowViewportRect = usePublisher$2("windowViewportRect");
|
|
2997
|
+
const fixedItemHeight = usePublisher$2("fixedItemHeight");
|
|
2998
|
+
const customScrollParent = useEmitterValue$2("customScrollParent");
|
|
2999
|
+
const viewportRef = useWindowViewportRectRef(windowViewportRect, customScrollParent);
|
|
3000
|
+
React.useEffect(() => {
|
|
3001
|
+
if (ctx) {
|
|
3002
|
+
fixedItemHeight(ctx.itemHeight);
|
|
3003
|
+
windowViewportRect({ offsetTop: 0, visibleHeight: ctx.viewportHeight, visibleWidth: 100 });
|
|
3004
|
+
}
|
|
3005
|
+
}, [ctx, windowViewportRect, fixedItemHeight]);
|
|
3006
|
+
return /* @__PURE__ */ React.createElement("div", { ref: viewportRef, style: viewportStyle, "data-viewport-type": "window" }, children);
|
|
3007
|
+
};
|
|
3008
|
+
const TopItemListContainer = ({ children }) => {
|
|
3009
|
+
const TopItemList = useEmitterValue$2("TopItemListComponent");
|
|
3010
|
+
const headerHeight = useEmitterValue$2("headerHeight");
|
|
3011
|
+
const style = { ...topItemListStyle, marginTop: `${headerHeight}px` };
|
|
3012
|
+
const context = useEmitterValue$2("context");
|
|
3013
|
+
return React.createElement(TopItemList || "div", { style, context }, children);
|
|
3014
|
+
};
|
|
3015
|
+
const ListRoot = /* @__PURE__ */ React.memo(function VirtuosoRoot(props) {
|
|
3016
|
+
const useWindowScroll = useEmitterValue$2("useWindowScroll");
|
|
3017
|
+
const showTopList = useEmitterValue$2("topItemsIndexes").length > 0;
|
|
3018
|
+
const customScrollParent = useEmitterValue$2("customScrollParent");
|
|
3019
|
+
const TheScroller = customScrollParent || useWindowScroll ? WindowScroller$2 : Scroller$2;
|
|
3020
|
+
const TheViewport = customScrollParent || useWindowScroll ? WindowViewport$2 : Viewport$2;
|
|
3021
|
+
return /* @__PURE__ */ React.createElement(TheScroller, { ...props }, showTopList && /* @__PURE__ */ React.createElement(TopItemListContainer, null, /* @__PURE__ */ React.createElement(Items$1, { showTopList: true })), /* @__PURE__ */ React.createElement(TheViewport, null, /* @__PURE__ */ React.createElement(Header$1, null), /* @__PURE__ */ React.createElement(Items$1, null), /* @__PURE__ */ React.createElement(Footer$1, null)));
|
|
3022
|
+
});
|
|
3023
|
+
const {
|
|
3024
|
+
Component: List,
|
|
3025
|
+
usePublisher: usePublisher$2,
|
|
3026
|
+
useEmitterValue: useEmitterValue$2,
|
|
3027
|
+
useEmitter: useEmitter$2
|
|
3028
|
+
} = /* @__PURE__ */ systemToComponent(
|
|
3029
|
+
combinedSystem$2,
|
|
3030
|
+
{
|
|
3031
|
+
required: {},
|
|
3032
|
+
optional: {
|
|
3033
|
+
context: "context",
|
|
3034
|
+
followOutput: "followOutput",
|
|
3035
|
+
firstItemIndex: "firstItemIndex",
|
|
3036
|
+
itemContent: "itemContent",
|
|
3037
|
+
groupContent: "groupContent",
|
|
3038
|
+
overscan: "overscan",
|
|
3039
|
+
increaseViewportBy: "increaseViewportBy",
|
|
3040
|
+
totalCount: "totalCount",
|
|
3041
|
+
topItemCount: "topItemCount",
|
|
3042
|
+
initialTopMostItemIndex: "initialTopMostItemIndex",
|
|
3043
|
+
components: "components",
|
|
3044
|
+
groupCounts: "groupCounts",
|
|
3045
|
+
atBottomThreshold: "atBottomThreshold",
|
|
3046
|
+
atTopThreshold: "atTopThreshold",
|
|
3047
|
+
computeItemKey: "computeItemKey",
|
|
3048
|
+
defaultItemHeight: "defaultItemHeight",
|
|
3049
|
+
fixedItemHeight: "fixedItemHeight",
|
|
3050
|
+
itemSize: "itemSize",
|
|
3051
|
+
scrollSeekConfiguration: "scrollSeekConfiguration",
|
|
3052
|
+
headerFooterTag: "headerFooterTag",
|
|
3053
|
+
data: "data",
|
|
3054
|
+
initialItemCount: "initialItemCount",
|
|
3055
|
+
initialScrollTop: "initialScrollTop",
|
|
3056
|
+
alignToBottom: "alignToBottom",
|
|
3057
|
+
useWindowScroll: "useWindowScroll",
|
|
3058
|
+
customScrollParent: "customScrollParent",
|
|
3059
|
+
scrollerRef: "scrollerRef",
|
|
3060
|
+
logLevel: "logLevel"
|
|
3061
|
+
},
|
|
3062
|
+
methods: {
|
|
3063
|
+
scrollToIndex: "scrollToIndex",
|
|
3064
|
+
scrollIntoView: "scrollIntoView",
|
|
3065
|
+
scrollTo: "scrollTo",
|
|
3066
|
+
scrollBy: "scrollBy",
|
|
3067
|
+
autoscrollToBottom: "autoscrollToBottom"
|
|
3068
|
+
},
|
|
3069
|
+
events: {
|
|
3070
|
+
isScrolling: "isScrolling",
|
|
3071
|
+
endReached: "endReached",
|
|
3072
|
+
startReached: "startReached",
|
|
3073
|
+
rangeChanged: "rangeChanged",
|
|
3074
|
+
atBottomStateChange: "atBottomStateChange",
|
|
3075
|
+
atTopStateChange: "atTopStateChange",
|
|
3076
|
+
totalListHeightChanged: "totalListHeightChanged",
|
|
3077
|
+
itemsRendered: "itemsRendered",
|
|
3078
|
+
groupIndices: "groupIndices"
|
|
3079
|
+
}
|
|
3080
|
+
},
|
|
3081
|
+
ListRoot
|
|
3082
|
+
);
|
|
3083
|
+
const Scroller$2 = /* @__PURE__ */ buildScroller({ usePublisher: usePublisher$2, useEmitterValue: useEmitterValue$2, useEmitter: useEmitter$2 });
|
|
3084
|
+
const WindowScroller$2 = /* @__PURE__ */ buildWindowScroller({ usePublisher: usePublisher$2, useEmitterValue: useEmitterValue$2, useEmitter: useEmitter$2 });
|
|
3085
|
+
const Virtuoso = List;
|
|
3086
|
+
const GroupedVirtuoso = List;
|
|
3087
|
+
const INITIAL_GRID_STATE = {
|
|
3088
|
+
items: [],
|
|
3089
|
+
offsetBottom: 0,
|
|
3090
|
+
offsetTop: 0,
|
|
3091
|
+
top: 0,
|
|
3092
|
+
bottom: 0,
|
|
3093
|
+
itemHeight: 0,
|
|
3094
|
+
itemWidth: 0
|
|
3095
|
+
};
|
|
3096
|
+
const PROBE_GRID_STATE = {
|
|
3097
|
+
items: [{ index: 0 }],
|
|
3098
|
+
offsetBottom: 0,
|
|
3099
|
+
offsetTop: 0,
|
|
3100
|
+
top: 0,
|
|
3101
|
+
bottom: 0,
|
|
3102
|
+
itemHeight: 0,
|
|
3103
|
+
itemWidth: 0
|
|
3104
|
+
};
|
|
3105
|
+
const { round, ceil, floor, min, max } = Math;
|
|
3106
|
+
function buildProbeGridState(items) {
|
|
3107
|
+
return {
|
|
3108
|
+
...PROBE_GRID_STATE,
|
|
3109
|
+
items
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
function buildItems(startIndex, endIndex, data) {
|
|
3113
|
+
return Array.from({ length: endIndex - startIndex + 1 }).map(
|
|
3114
|
+
(_, i) => ({ index: i + startIndex, data: data == null ? void 0 : data[i + startIndex] })
|
|
3115
|
+
);
|
|
3116
|
+
}
|
|
3117
|
+
function gapComparator(prev, next) {
|
|
3118
|
+
return prev && prev.column === next.column && prev.row === next.row;
|
|
3119
|
+
}
|
|
3120
|
+
const gridSystem = /* @__PURE__ */ system(
|
|
3121
|
+
([
|
|
3122
|
+
{ overscan, visibleRange, listBoundary },
|
|
3123
|
+
{ scrollTop, viewportHeight, scrollBy, scrollTo, smoothScrollTargetReached, scrollContainerState, footerHeight, headerHeight },
|
|
3124
|
+
stateFlags,
|
|
3125
|
+
scrollSeek,
|
|
3126
|
+
{ propsReady, didMount },
|
|
3127
|
+
{ windowViewportRect, windowScrollTo, useWindowScroll, customScrollParent, windowScrollContainerState },
|
|
3128
|
+
log
|
|
3129
|
+
]) => {
|
|
3130
|
+
const totalCount = statefulStream(0);
|
|
3131
|
+
const initialItemCount = statefulStream(0);
|
|
3132
|
+
const gridState = statefulStream(INITIAL_GRID_STATE);
|
|
3133
|
+
const viewportDimensions = statefulStream({ height: 0, width: 0 });
|
|
3134
|
+
const itemDimensions = statefulStream({ height: 0, width: 0 });
|
|
3135
|
+
const scrollToIndex = stream();
|
|
3136
|
+
const scrollHeight = stream();
|
|
3137
|
+
const deviation = statefulStream(0);
|
|
3138
|
+
const data = statefulStream(void 0);
|
|
3139
|
+
const gap = statefulStream({ row: 0, column: 0 });
|
|
3140
|
+
connect(
|
|
3141
|
+
pipe(
|
|
3142
|
+
combineLatest(didMount, initialItemCount, data),
|
|
3143
|
+
filter(([, count]) => count !== 0),
|
|
3144
|
+
map(([, count, data2]) => {
|
|
3145
|
+
return {
|
|
3146
|
+
items: buildItems(0, count - 1, data2),
|
|
3147
|
+
top: 0,
|
|
3148
|
+
bottom: 0,
|
|
3149
|
+
offsetBottom: 0,
|
|
3150
|
+
offsetTop: 0,
|
|
3151
|
+
itemHeight: 0,
|
|
3152
|
+
itemWidth: 0
|
|
3153
|
+
};
|
|
3154
|
+
})
|
|
3155
|
+
),
|
|
3156
|
+
gridState
|
|
3157
|
+
);
|
|
3158
|
+
connect(
|
|
3159
|
+
pipe(
|
|
3160
|
+
combineLatest(
|
|
3161
|
+
duc(totalCount),
|
|
3162
|
+
visibleRange,
|
|
3163
|
+
duc(gap, gapComparator),
|
|
3164
|
+
duc(itemDimensions, (prev, next) => prev && prev.width === next.width && prev.height === next.height),
|
|
3165
|
+
data
|
|
3166
|
+
),
|
|
3167
|
+
withLatestFrom(viewportDimensions),
|
|
3168
|
+
map(([[totalCount2, [startOffset, endOffset], gap2, item, data2], viewport]) => {
|
|
3169
|
+
const { row: rowGap, column: columnGap } = gap2;
|
|
3170
|
+
const { height: itemHeight, width: itemWidth } = item;
|
|
3171
|
+
const { width: viewportWidth } = viewport;
|
|
3172
|
+
if (totalCount2 === 0 || viewportWidth === 0) {
|
|
3173
|
+
return INITIAL_GRID_STATE;
|
|
3174
|
+
}
|
|
3175
|
+
if (itemWidth === 0) {
|
|
3176
|
+
return buildProbeGridState(buildItems(0, 0, data2));
|
|
3177
|
+
}
|
|
3178
|
+
const perRow = itemsPerRow(viewportWidth, itemWidth, columnGap);
|
|
3179
|
+
let startIndex = perRow * floor((startOffset + rowGap) / (itemHeight + rowGap));
|
|
3180
|
+
let endIndex = perRow * ceil((endOffset + rowGap) / (itemHeight + rowGap)) - 1;
|
|
3181
|
+
endIndex = min(totalCount2 - 1, max(endIndex, perRow - 1));
|
|
3182
|
+
startIndex = min(endIndex, max(0, startIndex));
|
|
3183
|
+
const items = buildItems(startIndex, endIndex, data2);
|
|
3184
|
+
const { top, bottom } = gridLayout(viewport, gap2, item, items);
|
|
3185
|
+
const rowCount = ceil(totalCount2 / perRow);
|
|
3186
|
+
const totalHeight = rowCount * itemHeight + (rowCount - 1) * rowGap;
|
|
3187
|
+
const offsetBottom = totalHeight - bottom;
|
|
3188
|
+
return { items, offsetTop: top, offsetBottom, top, bottom, itemHeight, itemWidth };
|
|
3189
|
+
})
|
|
3190
|
+
),
|
|
3191
|
+
gridState
|
|
3192
|
+
);
|
|
3193
|
+
connect(
|
|
3194
|
+
pipe(
|
|
3195
|
+
data,
|
|
3196
|
+
filter((data2) => data2 !== void 0),
|
|
3197
|
+
map((data2) => data2.length)
|
|
3198
|
+
),
|
|
3199
|
+
totalCount
|
|
3200
|
+
);
|
|
3201
|
+
connect(
|
|
3202
|
+
pipe(
|
|
3203
|
+
viewportDimensions,
|
|
3204
|
+
map(({ height }) => height)
|
|
3205
|
+
),
|
|
3206
|
+
viewportHeight
|
|
3207
|
+
);
|
|
3208
|
+
connect(
|
|
3209
|
+
pipe(
|
|
3210
|
+
combineLatest(viewportDimensions, itemDimensions, gridState, gap),
|
|
3211
|
+
map(([viewportDimensions2, item, { items }, gap2]) => {
|
|
3212
|
+
const { top, bottom } = gridLayout(viewportDimensions2, gap2, item, items);
|
|
3213
|
+
return [top, bottom];
|
|
3214
|
+
}),
|
|
3215
|
+
distinctUntilChanged(tupleComparator)
|
|
3216
|
+
),
|
|
3217
|
+
listBoundary
|
|
3218
|
+
);
|
|
3219
|
+
const endReached = streamFromEmitter(
|
|
3220
|
+
pipe(
|
|
3221
|
+
duc(gridState),
|
|
3222
|
+
filter(({ items }) => items.length > 0),
|
|
3223
|
+
withLatestFrom(totalCount),
|
|
3224
|
+
filter(([{ items }, totalCount2]) => items[items.length - 1].index === totalCount2 - 1),
|
|
3225
|
+
map(([, totalCount2]) => totalCount2 - 1),
|
|
3226
|
+
distinctUntilChanged()
|
|
3227
|
+
)
|
|
3228
|
+
);
|
|
3229
|
+
const startReached = streamFromEmitter(
|
|
3230
|
+
pipe(
|
|
3231
|
+
duc(gridState),
|
|
3232
|
+
filter(({ items }) => {
|
|
3233
|
+
return items.length > 0 && items[0].index === 0;
|
|
3234
|
+
}),
|
|
3235
|
+
mapTo(0),
|
|
3236
|
+
distinctUntilChanged()
|
|
3237
|
+
)
|
|
3238
|
+
);
|
|
3239
|
+
const rangeChanged = streamFromEmitter(
|
|
3240
|
+
pipe(
|
|
3241
|
+
duc(gridState),
|
|
3242
|
+
filter(({ items }) => items.length > 0),
|
|
3243
|
+
map(({ items }) => {
|
|
3244
|
+
return {
|
|
3245
|
+
startIndex: items[0].index,
|
|
3246
|
+
endIndex: items[items.length - 1].index
|
|
3247
|
+
};
|
|
3248
|
+
}),
|
|
3249
|
+
distinctUntilChanged(rangeComparator)
|
|
3250
|
+
)
|
|
3251
|
+
);
|
|
3252
|
+
connect(rangeChanged, scrollSeek.scrollSeekRangeChanged);
|
|
3253
|
+
connect(
|
|
3254
|
+
pipe(
|
|
3255
|
+
scrollToIndex,
|
|
3256
|
+
withLatestFrom(viewportDimensions, itemDimensions, totalCount, gap),
|
|
3257
|
+
map(([location, viewport, item, totalCount2, gap2]) => {
|
|
3258
|
+
const normalLocation = normalizeIndexLocation(location);
|
|
3259
|
+
const { align, behavior, offset } = normalLocation;
|
|
3260
|
+
let index = normalLocation.index;
|
|
3261
|
+
if (index === "LAST") {
|
|
3262
|
+
index = totalCount2 - 1;
|
|
3263
|
+
}
|
|
3264
|
+
index = max(0, index, min(totalCount2 - 1, index));
|
|
3265
|
+
let top = itemTop(viewport, gap2, item, index);
|
|
3266
|
+
if (align === "end") {
|
|
3267
|
+
top = round(top - viewport.height + item.height);
|
|
3268
|
+
} else if (align === "center") {
|
|
3269
|
+
top = round(top - viewport.height / 2 + item.height / 2);
|
|
3270
|
+
}
|
|
3271
|
+
if (offset) {
|
|
3272
|
+
top += offset;
|
|
3273
|
+
}
|
|
3274
|
+
return { top, behavior };
|
|
3275
|
+
})
|
|
3276
|
+
),
|
|
3277
|
+
scrollTo
|
|
3278
|
+
);
|
|
3279
|
+
const totalListHeight = statefulStreamFromEmitter(
|
|
3280
|
+
pipe(
|
|
3281
|
+
gridState,
|
|
3282
|
+
map((gridState2) => {
|
|
3283
|
+
return gridState2.offsetBottom + gridState2.bottom;
|
|
3284
|
+
})
|
|
3285
|
+
),
|
|
3286
|
+
0
|
|
3287
|
+
);
|
|
3288
|
+
connect(
|
|
3289
|
+
pipe(
|
|
3290
|
+
windowViewportRect,
|
|
3291
|
+
map((viewportInfo) => ({ width: viewportInfo.visibleWidth, height: viewportInfo.visibleHeight }))
|
|
3292
|
+
),
|
|
3293
|
+
viewportDimensions
|
|
3294
|
+
);
|
|
3295
|
+
return {
|
|
3296
|
+
data,
|
|
3297
|
+
totalCount,
|
|
3298
|
+
viewportDimensions,
|
|
3299
|
+
itemDimensions,
|
|
3300
|
+
scrollTop,
|
|
3301
|
+
scrollHeight,
|
|
3302
|
+
overscan,
|
|
3303
|
+
scrollBy,
|
|
3304
|
+
scrollTo,
|
|
3305
|
+
scrollToIndex,
|
|
3306
|
+
smoothScrollTargetReached,
|
|
3307
|
+
windowViewportRect,
|
|
3308
|
+
windowScrollTo,
|
|
3309
|
+
useWindowScroll,
|
|
3310
|
+
customScrollParent,
|
|
3311
|
+
windowScrollContainerState,
|
|
3312
|
+
deviation,
|
|
3313
|
+
scrollContainerState,
|
|
3314
|
+
footerHeight,
|
|
3315
|
+
headerHeight,
|
|
3316
|
+
initialItemCount,
|
|
3317
|
+
gap,
|
|
3318
|
+
...scrollSeek,
|
|
3319
|
+
gridState,
|
|
3320
|
+
totalListHeight,
|
|
3321
|
+
...stateFlags,
|
|
3322
|
+
startReached,
|
|
3323
|
+
endReached,
|
|
3324
|
+
rangeChanged,
|
|
3325
|
+
propsReady,
|
|
3326
|
+
...log
|
|
3327
|
+
};
|
|
3328
|
+
},
|
|
3329
|
+
tup(sizeRangeSystem, domIOSystem, stateFlagsSystem, scrollSeekSystem, propsReadySystem, windowScrollerSystem, loggerSystem)
|
|
3330
|
+
);
|
|
3331
|
+
function gridLayout(viewport, gap, item, items) {
|
|
3332
|
+
const { height: itemHeight } = item;
|
|
3333
|
+
if (itemHeight === void 0 || items.length === 0) {
|
|
3334
|
+
return { top: 0, bottom: 0 };
|
|
3335
|
+
}
|
|
3336
|
+
const top = itemTop(viewport, gap, item, items[0].index);
|
|
3337
|
+
const bottom = itemTop(viewport, gap, item, items[items.length - 1].index) + itemHeight;
|
|
3338
|
+
return { top, bottom };
|
|
3339
|
+
}
|
|
3340
|
+
function itemTop(viewport, gap, item, index) {
|
|
3341
|
+
const perRow = itemsPerRow(viewport.width, item.width, gap.column);
|
|
3342
|
+
const rowCount = floor(index / perRow);
|
|
3343
|
+
const top = rowCount * item.height + max(0, rowCount - 1) * gap.row;
|
|
3344
|
+
return top > 0 ? top + gap.row : top;
|
|
3345
|
+
}
|
|
3346
|
+
function itemsPerRow(viewportWidth, itemWidth, gap) {
|
|
3347
|
+
return max(1, floor((viewportWidth + gap) / (itemWidth + gap)));
|
|
3348
|
+
}
|
|
3349
|
+
const gridComponentPropsSystem = /* @__PURE__ */ system(() => {
|
|
3350
|
+
const itemContent = statefulStream((index) => `Item ${index}`);
|
|
3351
|
+
const components = statefulStream({});
|
|
3352
|
+
const context = statefulStream(null);
|
|
3353
|
+
const itemClassName = statefulStream("virtuoso-grid-item");
|
|
3354
|
+
const listClassName = statefulStream("virtuoso-grid-list");
|
|
3355
|
+
const computeItemKey = statefulStream(identity);
|
|
3356
|
+
const headerFooterTag = statefulStream("div");
|
|
3357
|
+
const scrollerRef = statefulStream(noop);
|
|
3358
|
+
const distinctProp = (propName, defaultValue = null) => {
|
|
3359
|
+
return statefulStreamFromEmitter(
|
|
3360
|
+
pipe(
|
|
3361
|
+
components,
|
|
3362
|
+
map((components2) => components2[propName]),
|
|
3363
|
+
distinctUntilChanged()
|
|
3364
|
+
),
|
|
3365
|
+
defaultValue
|
|
3366
|
+
);
|
|
3367
|
+
};
|
|
3368
|
+
return {
|
|
3369
|
+
context,
|
|
3370
|
+
itemContent,
|
|
3371
|
+
components,
|
|
3372
|
+
computeItemKey,
|
|
3373
|
+
itemClassName,
|
|
3374
|
+
listClassName,
|
|
3375
|
+
headerFooterTag,
|
|
3376
|
+
scrollerRef,
|
|
3377
|
+
FooterComponent: distinctProp("Footer"),
|
|
3378
|
+
HeaderComponent: distinctProp("Header"),
|
|
3379
|
+
ListComponent: distinctProp("List", "div"),
|
|
3380
|
+
ItemComponent: distinctProp("Item", "div"),
|
|
3381
|
+
ScrollerComponent: distinctProp("Scroller", "div"),
|
|
3382
|
+
ScrollSeekPlaceholder: distinctProp("ScrollSeekPlaceholder", "div")
|
|
3383
|
+
};
|
|
3384
|
+
});
|
|
3385
|
+
const combinedSystem$1 = /* @__PURE__ */ system(([gridSystem2, gridComponentPropsSystem2]) => {
|
|
3386
|
+
return { ...gridSystem2, ...gridComponentPropsSystem2 };
|
|
3387
|
+
}, tup(gridSystem, gridComponentPropsSystem));
|
|
3388
|
+
const GridItems = /* @__PURE__ */ React.memo(function GridItems2() {
|
|
3389
|
+
const gridState = useEmitterValue$1("gridState");
|
|
3390
|
+
const listClassName = useEmitterValue$1("listClassName");
|
|
3391
|
+
const itemClassName = useEmitterValue$1("itemClassName");
|
|
3392
|
+
const itemContent = useEmitterValue$1("itemContent");
|
|
3393
|
+
const computeItemKey = useEmitterValue$1("computeItemKey");
|
|
3394
|
+
const isSeeking = useEmitterValue$1("isSeeking");
|
|
3395
|
+
const scrollHeightCallback = usePublisher$1("scrollHeight");
|
|
3396
|
+
const ItemComponent = useEmitterValue$1("ItemComponent");
|
|
3397
|
+
const ListComponent = useEmitterValue$1("ListComponent");
|
|
3398
|
+
const ScrollSeekPlaceholder = useEmitterValue$1("ScrollSeekPlaceholder");
|
|
3399
|
+
const context = useEmitterValue$1("context");
|
|
3400
|
+
const itemDimensions = usePublisher$1("itemDimensions");
|
|
3401
|
+
const gridGap = usePublisher$1("gap");
|
|
3402
|
+
const log = useEmitterValue$1("log");
|
|
3403
|
+
const listRef = useSize((el) => {
|
|
3404
|
+
const scrollHeight = el.parentElement.parentElement.scrollHeight;
|
|
3405
|
+
scrollHeightCallback(scrollHeight);
|
|
3406
|
+
const firstItem = el.firstChild;
|
|
3407
|
+
if (firstItem) {
|
|
3408
|
+
itemDimensions(firstItem.getBoundingClientRect());
|
|
3409
|
+
}
|
|
3410
|
+
gridGap({
|
|
3411
|
+
row: resolveGapValue("row-gap", getComputedStyle(el).rowGap, log),
|
|
3412
|
+
column: resolveGapValue("column-gap", getComputedStyle(el).columnGap, log)
|
|
3413
|
+
});
|
|
3414
|
+
});
|
|
3415
|
+
return React.createElement(
|
|
3416
|
+
ListComponent,
|
|
3417
|
+
{
|
|
3418
|
+
ref: listRef,
|
|
3419
|
+
className: listClassName,
|
|
3420
|
+
...contextPropIfNotDomElement(ListComponent, context),
|
|
3421
|
+
style: { paddingTop: gridState.offsetTop, paddingBottom: gridState.offsetBottom },
|
|
3422
|
+
"data-test-id": "virtuoso-item-list"
|
|
3423
|
+
},
|
|
3424
|
+
gridState.items.map((item) => {
|
|
3425
|
+
const key = computeItemKey(item.index, item.data, context);
|
|
3426
|
+
return isSeeking ? React.createElement(ScrollSeekPlaceholder, {
|
|
3427
|
+
key,
|
|
3428
|
+
...contextPropIfNotDomElement(ScrollSeekPlaceholder, context),
|
|
3429
|
+
index: item.index,
|
|
3430
|
+
height: gridState.itemHeight,
|
|
3431
|
+
width: gridState.itemWidth
|
|
3432
|
+
}) : React.createElement(
|
|
3433
|
+
ItemComponent,
|
|
3434
|
+
{ ...contextPropIfNotDomElement(ItemComponent, context), className: itemClassName, "data-index": item.index, key },
|
|
3435
|
+
itemContent(item.index, item.data, context)
|
|
3436
|
+
);
|
|
3437
|
+
})
|
|
3438
|
+
);
|
|
3439
|
+
});
|
|
3440
|
+
const Header = React.memo(function VirtuosoHeader2() {
|
|
3441
|
+
const Header2 = useEmitterValue$1("HeaderComponent");
|
|
3442
|
+
const headerHeight = usePublisher$1("headerHeight");
|
|
3443
|
+
const headerFooterTag = useEmitterValue$1("headerFooterTag");
|
|
3444
|
+
const ref = useSize((el) => headerHeight(correctItemSize(el, "height")));
|
|
3445
|
+
const context = useEmitterValue$1("context");
|
|
3446
|
+
return Header2 ? React.createElement(headerFooterTag, { ref }, React.createElement(Header2, contextPropIfNotDomElement(Header2, context))) : null;
|
|
3447
|
+
});
|
|
3448
|
+
const Footer = React.memo(function VirtuosoGridFooter() {
|
|
3449
|
+
const Footer2 = useEmitterValue$1("FooterComponent");
|
|
3450
|
+
const footerHeight = usePublisher$1("footerHeight");
|
|
3451
|
+
const headerFooterTag = useEmitterValue$1("headerFooterTag");
|
|
3452
|
+
const ref = useSize((el) => footerHeight(correctItemSize(el, "height")));
|
|
3453
|
+
const context = useEmitterValue$1("context");
|
|
3454
|
+
return Footer2 ? React.createElement(headerFooterTag, { ref }, React.createElement(Footer2, contextPropIfNotDomElement(Footer2, context))) : null;
|
|
3455
|
+
});
|
|
3456
|
+
const Viewport$1 = ({ children }) => {
|
|
3457
|
+
const ctx = React.useContext(VirtuosoGridMockContext);
|
|
3458
|
+
const itemDimensions = usePublisher$1("itemDimensions");
|
|
3459
|
+
const viewportDimensions = usePublisher$1("viewportDimensions");
|
|
3460
|
+
const viewportRef = useSize((el) => {
|
|
3461
|
+
viewportDimensions(el.getBoundingClientRect());
|
|
3462
|
+
});
|
|
3463
|
+
React.useEffect(() => {
|
|
3464
|
+
if (ctx) {
|
|
3465
|
+
viewportDimensions({ height: ctx.viewportHeight, width: ctx.viewportWidth });
|
|
3466
|
+
itemDimensions({ height: ctx.itemHeight, width: ctx.itemWidth });
|
|
3467
|
+
}
|
|
3468
|
+
}, [ctx, viewportDimensions, itemDimensions]);
|
|
3469
|
+
return /* @__PURE__ */ React.createElement("div", { style: viewportStyle, ref: viewportRef }, children);
|
|
3470
|
+
};
|
|
3471
|
+
const WindowViewport$1 = ({ children }) => {
|
|
3472
|
+
const ctx = React.useContext(VirtuosoGridMockContext);
|
|
3473
|
+
const windowViewportRect = usePublisher$1("windowViewportRect");
|
|
3474
|
+
const itemDimensions = usePublisher$1("itemDimensions");
|
|
3475
|
+
const customScrollParent = useEmitterValue$1("customScrollParent");
|
|
3476
|
+
const viewportRef = useWindowViewportRectRef(windowViewportRect, customScrollParent);
|
|
3477
|
+
React.useEffect(() => {
|
|
3478
|
+
if (ctx) {
|
|
3479
|
+
itemDimensions({ height: ctx.itemHeight, width: ctx.itemWidth });
|
|
3480
|
+
windowViewportRect({ offsetTop: 0, visibleHeight: ctx.viewportHeight, visibleWidth: ctx.viewportWidth });
|
|
3481
|
+
}
|
|
3482
|
+
}, [ctx, windowViewportRect, itemDimensions]);
|
|
3483
|
+
return /* @__PURE__ */ React.createElement("div", { ref: viewportRef, style: viewportStyle }, children);
|
|
3484
|
+
};
|
|
3485
|
+
const GridRoot = /* @__PURE__ */ React.memo(function GridRoot2({ ...props }) {
|
|
3486
|
+
const useWindowScroll = useEmitterValue$1("useWindowScroll");
|
|
3487
|
+
const customScrollParent = useEmitterValue$1("customScrollParent");
|
|
3488
|
+
const TheScroller = customScrollParent || useWindowScroll ? WindowScroller$1 : Scroller$1;
|
|
3489
|
+
const TheViewport = customScrollParent || useWindowScroll ? WindowViewport$1 : Viewport$1;
|
|
3490
|
+
return /* @__PURE__ */ React.createElement(TheScroller, { ...props }, /* @__PURE__ */ React.createElement(TheViewport, null, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(GridItems, null), /* @__PURE__ */ React.createElement(Footer, null)));
|
|
3491
|
+
});
|
|
3492
|
+
const {
|
|
3493
|
+
Component: Grid,
|
|
3494
|
+
usePublisher: usePublisher$1,
|
|
3495
|
+
useEmitterValue: useEmitterValue$1,
|
|
3496
|
+
useEmitter: useEmitter$1
|
|
3497
|
+
} = /* @__PURE__ */ systemToComponent(
|
|
3498
|
+
combinedSystem$1,
|
|
3499
|
+
{
|
|
3500
|
+
optional: {
|
|
3501
|
+
context: "context",
|
|
3502
|
+
totalCount: "totalCount",
|
|
3503
|
+
overscan: "overscan",
|
|
3504
|
+
itemContent: "itemContent",
|
|
3505
|
+
components: "components",
|
|
3506
|
+
computeItemKey: "computeItemKey",
|
|
3507
|
+
data: "data",
|
|
3508
|
+
initialItemCount: "initialItemCount",
|
|
3509
|
+
scrollSeekConfiguration: "scrollSeekConfiguration",
|
|
3510
|
+
headerFooterTag: "headerFooterTag",
|
|
3511
|
+
listClassName: "listClassName",
|
|
3512
|
+
itemClassName: "itemClassName",
|
|
3513
|
+
useWindowScroll: "useWindowScroll",
|
|
3514
|
+
customScrollParent: "customScrollParent",
|
|
3515
|
+
scrollerRef: "scrollerRef"
|
|
3516
|
+
},
|
|
3517
|
+
methods: {
|
|
3518
|
+
scrollTo: "scrollTo",
|
|
3519
|
+
scrollBy: "scrollBy",
|
|
3520
|
+
scrollToIndex: "scrollToIndex"
|
|
3521
|
+
},
|
|
3522
|
+
events: {
|
|
3523
|
+
isScrolling: "isScrolling",
|
|
3524
|
+
endReached: "endReached",
|
|
3525
|
+
startReached: "startReached",
|
|
3526
|
+
rangeChanged: "rangeChanged",
|
|
3527
|
+
atBottomStateChange: "atBottomStateChange",
|
|
3528
|
+
atTopStateChange: "atTopStateChange"
|
|
3529
|
+
}
|
|
3530
|
+
},
|
|
3531
|
+
GridRoot
|
|
3532
|
+
);
|
|
3533
|
+
const Scroller$1 = /* @__PURE__ */ buildScroller({ usePublisher: usePublisher$1, useEmitterValue: useEmitterValue$1, useEmitter: useEmitter$1 });
|
|
3534
|
+
const WindowScroller$1 = /* @__PURE__ */ buildWindowScroller({ usePublisher: usePublisher$1, useEmitterValue: useEmitterValue$1, useEmitter: useEmitter$1 });
|
|
3535
|
+
function resolveGapValue(property, value, log) {
|
|
3536
|
+
if (value !== "normal" && !(value == null ? void 0 : value.endsWith("px"))) {
|
|
3537
|
+
log(`${property} was not resolved to pixel value correctly`, value, LogLevel.WARN);
|
|
3538
|
+
}
|
|
3539
|
+
if (value === "normal") {
|
|
3540
|
+
return 0;
|
|
3541
|
+
}
|
|
3542
|
+
return parseInt(value ?? "0", 10);
|
|
3543
|
+
}
|
|
3544
|
+
const VirtuosoGrid = Grid;
|
|
3545
|
+
const tableComponentPropsSystem = /* @__PURE__ */ system(() => {
|
|
3546
|
+
const itemContent = statefulStream((index) => /* @__PURE__ */ React.createElement("td", null, "Item $", index));
|
|
3547
|
+
const context = statefulStream(null);
|
|
3548
|
+
const fixedHeaderContent = statefulStream(null);
|
|
3549
|
+
const fixedFooterContent = statefulStream(null);
|
|
3550
|
+
const components = statefulStream({});
|
|
3551
|
+
const computeItemKey = statefulStream(identity);
|
|
3552
|
+
const scrollerRef = statefulStream(noop);
|
|
3553
|
+
const distinctProp = (propName, defaultValue = null) => {
|
|
3554
|
+
return statefulStreamFromEmitter(
|
|
3555
|
+
pipe(
|
|
3556
|
+
components,
|
|
3557
|
+
map((components2) => components2[propName]),
|
|
3558
|
+
distinctUntilChanged()
|
|
3559
|
+
),
|
|
3560
|
+
defaultValue
|
|
3561
|
+
);
|
|
3562
|
+
};
|
|
3563
|
+
return {
|
|
3564
|
+
context,
|
|
3565
|
+
itemContent,
|
|
3566
|
+
fixedHeaderContent,
|
|
3567
|
+
fixedFooterContent,
|
|
3568
|
+
components,
|
|
3569
|
+
computeItemKey,
|
|
3570
|
+
scrollerRef,
|
|
3571
|
+
TableComponent: distinctProp("Table", "table"),
|
|
3572
|
+
TableHeadComponent: distinctProp("TableHead", "thead"),
|
|
3573
|
+
TableFooterComponent: distinctProp("TableFoot", "tfoot"),
|
|
3574
|
+
TableBodyComponent: distinctProp("TableBody", "tbody"),
|
|
3575
|
+
TableRowComponent: distinctProp("TableRow", "tr"),
|
|
3576
|
+
ScrollerComponent: distinctProp("Scroller", "div"),
|
|
3577
|
+
EmptyPlaceholder: distinctProp("EmptyPlaceholder"),
|
|
3578
|
+
ScrollSeekPlaceholder: distinctProp("ScrollSeekPlaceholder"),
|
|
3579
|
+
FillerRow: distinctProp("FillerRow")
|
|
3580
|
+
};
|
|
3581
|
+
});
|
|
3582
|
+
const combinedSystem = /* @__PURE__ */ system(([listSystem2, propsSystem]) => {
|
|
3583
|
+
return { ...listSystem2, ...propsSystem };
|
|
3584
|
+
}, tup(listSystem, tableComponentPropsSystem));
|
|
3585
|
+
const DefaultScrollSeekPlaceholder = ({ height }) => /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { style: { height } }));
|
|
3586
|
+
const DefaultFillerRow = ({ height }) => /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { style: { height, padding: 0, border: 0 } }));
|
|
3587
|
+
const Items = /* @__PURE__ */ React.memo(function VirtuosoItems2() {
|
|
3588
|
+
const listState = useEmitterValue("listState");
|
|
3589
|
+
const sizeRanges = usePublisher("sizeRanges");
|
|
3590
|
+
const useWindowScroll = useEmitterValue("useWindowScroll");
|
|
3591
|
+
const customScrollParent = useEmitterValue("customScrollParent");
|
|
3592
|
+
const windowScrollContainerStateCallback = usePublisher("windowScrollContainerState");
|
|
3593
|
+
const _scrollContainerStateCallback = usePublisher("scrollContainerState");
|
|
3594
|
+
const scrollContainerStateCallback = customScrollParent || useWindowScroll ? windowScrollContainerStateCallback : _scrollContainerStateCallback;
|
|
3595
|
+
const itemContent = useEmitterValue("itemContent");
|
|
3596
|
+
const trackItemSizes = useEmitterValue("trackItemSizes");
|
|
3597
|
+
const itemSize = useEmitterValue("itemSize");
|
|
3598
|
+
const log = useEmitterValue("log");
|
|
3599
|
+
const { callbackRef, ref } = useChangedListContentsSizes(
|
|
3600
|
+
sizeRanges,
|
|
3601
|
+
itemSize,
|
|
3602
|
+
trackItemSizes,
|
|
3603
|
+
scrollContainerStateCallback,
|
|
3604
|
+
log,
|
|
3605
|
+
void 0,
|
|
3606
|
+
customScrollParent
|
|
3607
|
+
);
|
|
3608
|
+
const [deviation, setDeviation] = React.useState(0);
|
|
3609
|
+
useEmitter("deviation", (value) => {
|
|
3610
|
+
if (deviation !== value) {
|
|
3611
|
+
ref.current.style.marginTop = `${value}px`;
|
|
3612
|
+
setDeviation(value);
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
const EmptyPlaceholder = useEmitterValue("EmptyPlaceholder");
|
|
3616
|
+
const ScrollSeekPlaceholder = useEmitterValue("ScrollSeekPlaceholder") || DefaultScrollSeekPlaceholder;
|
|
3617
|
+
const FillerRow = useEmitterValue("FillerRow") || DefaultFillerRow;
|
|
3618
|
+
const TableBodyComponent = useEmitterValue("TableBodyComponent");
|
|
3619
|
+
const TableRowComponent = useEmitterValue("TableRowComponent");
|
|
3620
|
+
const computeItemKey = useEmitterValue("computeItemKey");
|
|
3621
|
+
const isSeeking = useEmitterValue("isSeeking");
|
|
3622
|
+
const paddingTopAddition = useEmitterValue("paddingTopAddition");
|
|
3623
|
+
const firstItemIndex = useEmitterValue("firstItemIndex");
|
|
3624
|
+
const statefulTotalCount = useEmitterValue("statefulTotalCount");
|
|
3625
|
+
const context = useEmitterValue("context");
|
|
3626
|
+
if (statefulTotalCount === 0 && EmptyPlaceholder) {
|
|
3627
|
+
return React.createElement(EmptyPlaceholder, contextPropIfNotDomElement(EmptyPlaceholder, context));
|
|
3628
|
+
}
|
|
3629
|
+
const paddingTop = listState.offsetTop + paddingTopAddition + deviation;
|
|
3630
|
+
const paddingBottom = listState.offsetBottom;
|
|
3631
|
+
const paddingTopEl = paddingTop > 0 ? /* @__PURE__ */ React.createElement(FillerRow, { height: paddingTop, key: "padding-top", context }) : null;
|
|
3632
|
+
const paddingBottomEl = paddingBottom > 0 ? /* @__PURE__ */ React.createElement(FillerRow, { height: paddingBottom, key: "padding-bottom", context }) : null;
|
|
3633
|
+
const items = listState.items.map((item) => {
|
|
3634
|
+
const index = item.originalIndex;
|
|
3635
|
+
const key = computeItemKey(index + firstItemIndex, item.data, context);
|
|
3636
|
+
if (isSeeking) {
|
|
3637
|
+
return React.createElement(ScrollSeekPlaceholder, {
|
|
3638
|
+
...contextPropIfNotDomElement(ScrollSeekPlaceholder, context),
|
|
3639
|
+
key,
|
|
3640
|
+
index: item.index,
|
|
3641
|
+
height: item.size,
|
|
3642
|
+
type: item.type || "item"
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
return React.createElement(
|
|
3646
|
+
TableRowComponent,
|
|
3647
|
+
{
|
|
3648
|
+
...contextPropIfNotDomElement(TableRowComponent, context),
|
|
3649
|
+
key,
|
|
3650
|
+
"data-index": index,
|
|
3651
|
+
"data-known-size": item.size,
|
|
3652
|
+
"data-item-index": item.index,
|
|
3653
|
+
item: item.data,
|
|
3654
|
+
style: { overflowAnchor: "none" }
|
|
3655
|
+
},
|
|
3656
|
+
itemContent(item.index, item.data, context)
|
|
3657
|
+
);
|
|
3658
|
+
});
|
|
3659
|
+
return React.createElement(
|
|
3660
|
+
TableBodyComponent,
|
|
3661
|
+
{ ref: callbackRef, "data-test-id": "virtuoso-item-list", ...contextPropIfNotDomElement(TableBodyComponent, context) },
|
|
3662
|
+
[paddingTopEl, ...items, paddingBottomEl]
|
|
3663
|
+
);
|
|
3664
|
+
});
|
|
3665
|
+
const Viewport = ({ children }) => {
|
|
3666
|
+
const ctx = React.useContext(VirtuosoMockContext);
|
|
3667
|
+
const viewportHeight = usePublisher("viewportHeight");
|
|
3668
|
+
const fixedItemHeight = usePublisher("fixedItemHeight");
|
|
3669
|
+
const viewportRef = useSize(compose(viewportHeight, (el) => correctItemSize(el, "height")));
|
|
3670
|
+
React.useEffect(() => {
|
|
3671
|
+
if (ctx) {
|
|
3672
|
+
viewportHeight(ctx.viewportHeight);
|
|
3673
|
+
fixedItemHeight(ctx.itemHeight);
|
|
3674
|
+
}
|
|
3675
|
+
}, [ctx, viewportHeight, fixedItemHeight]);
|
|
3676
|
+
return /* @__PURE__ */ React.createElement("div", { style: viewportStyle, ref: viewportRef, "data-viewport-type": "element" }, children);
|
|
3677
|
+
};
|
|
3678
|
+
const WindowViewport = ({ children }) => {
|
|
3679
|
+
const ctx = React.useContext(VirtuosoMockContext);
|
|
3680
|
+
const windowViewportRect = usePublisher("windowViewportRect");
|
|
3681
|
+
const fixedItemHeight = usePublisher("fixedItemHeight");
|
|
3682
|
+
const customScrollParent = useEmitterValue("customScrollParent");
|
|
3683
|
+
const viewportRef = useWindowViewportRectRef(windowViewportRect, customScrollParent);
|
|
3684
|
+
React.useEffect(() => {
|
|
3685
|
+
if (ctx) {
|
|
3686
|
+
fixedItemHeight(ctx.itemHeight);
|
|
3687
|
+
windowViewportRect({ offsetTop: 0, visibleHeight: ctx.viewportHeight, visibleWidth: 100 });
|
|
3688
|
+
}
|
|
3689
|
+
}, [ctx, windowViewportRect, fixedItemHeight]);
|
|
3690
|
+
return /* @__PURE__ */ React.createElement("div", { ref: viewportRef, style: viewportStyle, "data-viewport-type": "window" }, children);
|
|
3691
|
+
};
|
|
3692
|
+
const TableRoot = /* @__PURE__ */ React.memo(function TableVirtuosoRoot(props) {
|
|
3693
|
+
const useWindowScroll = useEmitterValue("useWindowScroll");
|
|
3694
|
+
const customScrollParent = useEmitterValue("customScrollParent");
|
|
3695
|
+
const fixedHeaderHeight = usePublisher("fixedHeaderHeight");
|
|
3696
|
+
const fixedFooterHeight = usePublisher("fixedFooterHeight");
|
|
3697
|
+
const fixedHeaderContent = useEmitterValue("fixedHeaderContent");
|
|
3698
|
+
const fixedFooterContent = useEmitterValue("fixedFooterContent");
|
|
3699
|
+
const context = useEmitterValue("context");
|
|
3700
|
+
const theadRef = useSize(compose(fixedHeaderHeight, (el) => correctItemSize(el, "height")));
|
|
3701
|
+
const tfootRef = useSize(compose(fixedFooterHeight, (el) => correctItemSize(el, "height")));
|
|
3702
|
+
const TheScroller = customScrollParent || useWindowScroll ? WindowScroller : Scroller;
|
|
3703
|
+
const TheViewport = customScrollParent || useWindowScroll ? WindowViewport : Viewport;
|
|
3704
|
+
const TheTable = useEmitterValue("TableComponent");
|
|
3705
|
+
const TheTHead = useEmitterValue("TableHeadComponent");
|
|
3706
|
+
const TheTFoot = useEmitterValue("TableFooterComponent");
|
|
3707
|
+
const theHead = fixedHeaderContent ? React.createElement(
|
|
3708
|
+
TheTHead,
|
|
3709
|
+
{
|
|
3710
|
+
key: "TableHead",
|
|
3711
|
+
style: { zIndex: 1, position: "sticky", top: 0 },
|
|
3712
|
+
ref: theadRef,
|
|
3713
|
+
...contextPropIfNotDomElement(TheTHead, context)
|
|
3714
|
+
},
|
|
3715
|
+
fixedHeaderContent()
|
|
3716
|
+
) : null;
|
|
3717
|
+
const theFoot = fixedFooterContent ? React.createElement(
|
|
3718
|
+
TheTFoot,
|
|
3719
|
+
{
|
|
3720
|
+
key: "TableFoot",
|
|
3721
|
+
style: { zIndex: 1, position: "sticky", bottom: 0 },
|
|
3722
|
+
ref: tfootRef,
|
|
3723
|
+
...contextPropIfNotDomElement(TheTFoot, context)
|
|
3724
|
+
},
|
|
3725
|
+
fixedFooterContent()
|
|
3726
|
+
) : null;
|
|
3727
|
+
return /* @__PURE__ */ React.createElement(TheScroller, { ...props }, /* @__PURE__ */ React.createElement(TheViewport, null, React.createElement(TheTable, { style: { borderSpacing: 0 }, ...contextPropIfNotDomElement(TheTable, context) }, [
|
|
3728
|
+
theHead,
|
|
3729
|
+
/* @__PURE__ */ React.createElement(Items, { key: "TableBody" }),
|
|
3730
|
+
theFoot
|
|
3731
|
+
])));
|
|
3732
|
+
});
|
|
3733
|
+
const {
|
|
3734
|
+
Component: Table,
|
|
3735
|
+
usePublisher,
|
|
3736
|
+
useEmitterValue,
|
|
3737
|
+
useEmitter
|
|
3738
|
+
} = /* @__PURE__ */ systemToComponent(
|
|
3739
|
+
combinedSystem,
|
|
3740
|
+
{
|
|
3741
|
+
required: {},
|
|
3742
|
+
optional: {
|
|
3743
|
+
context: "context",
|
|
3744
|
+
followOutput: "followOutput",
|
|
3745
|
+
firstItemIndex: "firstItemIndex",
|
|
3746
|
+
itemContent: "itemContent",
|
|
3747
|
+
fixedHeaderContent: "fixedHeaderContent",
|
|
3748
|
+
fixedFooterContent: "fixedFooterContent",
|
|
3749
|
+
overscan: "overscan",
|
|
3750
|
+
increaseViewportBy: "increaseViewportBy",
|
|
3751
|
+
totalCount: "totalCount",
|
|
3752
|
+
topItemCount: "topItemCount",
|
|
3753
|
+
initialTopMostItemIndex: "initialTopMostItemIndex",
|
|
3754
|
+
components: "components",
|
|
3755
|
+
groupCounts: "groupCounts",
|
|
3756
|
+
atBottomThreshold: "atBottomThreshold",
|
|
3757
|
+
atTopThreshold: "atTopThreshold",
|
|
3758
|
+
computeItemKey: "computeItemKey",
|
|
3759
|
+
defaultItemHeight: "defaultItemHeight",
|
|
3760
|
+
fixedItemHeight: "fixedItemHeight",
|
|
3761
|
+
itemSize: "itemSize",
|
|
3762
|
+
scrollSeekConfiguration: "scrollSeekConfiguration",
|
|
3763
|
+
data: "data",
|
|
3764
|
+
initialItemCount: "initialItemCount",
|
|
3765
|
+
initialScrollTop: "initialScrollTop",
|
|
3766
|
+
alignToBottom: "alignToBottom",
|
|
3767
|
+
useWindowScroll: "useWindowScroll",
|
|
3768
|
+
customScrollParent: "customScrollParent",
|
|
3769
|
+
scrollerRef: "scrollerRef",
|
|
3770
|
+
logLevel: "logLevel"
|
|
3771
|
+
},
|
|
3772
|
+
methods: {
|
|
3773
|
+
scrollToIndex: "scrollToIndex",
|
|
3774
|
+
scrollIntoView: "scrollIntoView",
|
|
3775
|
+
scrollTo: "scrollTo",
|
|
3776
|
+
scrollBy: "scrollBy"
|
|
3777
|
+
},
|
|
3778
|
+
events: {
|
|
3779
|
+
isScrolling: "isScrolling",
|
|
3780
|
+
endReached: "endReached",
|
|
3781
|
+
startReached: "startReached",
|
|
3782
|
+
rangeChanged: "rangeChanged",
|
|
3783
|
+
atBottomStateChange: "atBottomStateChange",
|
|
3784
|
+
atTopStateChange: "atTopStateChange",
|
|
3785
|
+
totalListHeightChanged: "totalListHeightChanged",
|
|
3786
|
+
itemsRendered: "itemsRendered",
|
|
3787
|
+
groupIndices: "groupIndices"
|
|
3788
|
+
}
|
|
3789
|
+
},
|
|
3790
|
+
TableRoot
|
|
3791
|
+
);
|
|
3792
|
+
const Scroller = /* @__PURE__ */ buildScroller({ usePublisher, useEmitterValue, useEmitter });
|
|
3793
|
+
const WindowScroller = /* @__PURE__ */ buildWindowScroller({ usePublisher, useEmitterValue, useEmitter });
|
|
3794
|
+
const TableVirtuoso = Table;
|
|
3795
|
+
export {
|
|
3796
|
+
GroupedVirtuoso,
|
|
3797
|
+
LogLevel,
|
|
3798
|
+
TableVirtuoso,
|
|
3799
|
+
Virtuoso,
|
|
3800
|
+
VirtuosoGrid,
|
|
3801
|
+
VirtuosoGridMockContext,
|
|
3802
|
+
VirtuosoMockContext
|
|
3803
|
+
};
|