@symbiote-native/angular 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/babel-register-composed.cjs +11 -3
- package/build/angular/anchor-host-registry.d.ts +2 -0
- package/build/angular/anchor-host-registry.js +84 -0
- package/build/angular/anchor-host-registry.js.map +1 -0
- package/build/angular/components/input-accessory-view/index.d.ts +2 -1
- package/build/angular/components/input-accessory-view/index.js +28 -39
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/projection.js +70 -48
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.js +28 -25
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.d.ts +6 -7
- package/build/angular/components/scroll-view/sticky-header.js +82 -65
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +2 -5
- package/build/angular/components/touchable/index.js +36 -56
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +5 -20
- package/build/angular/components/virtualized-list/index.js +145 -275
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.js +2 -4
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/index.d.ts +2 -1
- package/build/angular/index.js +6 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +0 -1
- package/build/angular/renderer/index.js +5 -60
- package/build/angular/renderer/index.js.map +1 -1
- package/package.json +8 -4
- package/src/anchor-host-registry.ts +88 -0
- package/src/components/input-accessory-view/index.ts +30 -39
- package/src/components/scroll-view/projection.ts +76 -46
- package/src/components/scroll-view/shared.ts +27 -19
- package/src/components/scroll-view/sticky-header.ts +89 -64
- package/src/components/touchable/index.ts +52 -66
- package/src/components/virtualized-list/index.ts +157 -368
- package/src/components/virtualized-section-list/index.ts +6 -3
- package/src/index.ts +6 -1
- package/src/renderer/index.ts +5 -63
- package/build/bootstrap.d.ts +0 -7
- package/build/bootstrap.js +0 -14
- package/build/components/activity-indicator/index.android.d.ts +0 -9
- package/build/components/activity-indicator/index.android.js +0 -64
- package/build/components/activity-indicator/index.d.ts +0 -1
- package/build/components/activity-indicator/index.ios.d.ts +0 -5
- package/build/components/activity-indicator/index.ios.js +0 -64
- package/build/components/activity-indicator/index.js +0 -3
- package/build/components/activity-indicator/shared.d.ts +0 -64
- package/build/components/activity-indicator/shared.js +0 -424
- package/build/components/button.d.ts +0 -60
- package/build/components/button.js +0 -456
- package/build/components/flat-list/index.d.ts +0 -119
- package/build/components/flat-list/index.js +0 -691
- package/build/components/image/index.android.d.ts +0 -7
- package/build/components/image/index.android.js +0 -88
- package/build/components/image/index.d.ts +0 -1
- package/build/components/image/index.ios.d.ts +0 -7
- package/build/components/image/index.ios.js +0 -88
- package/build/components/image/index.js +0 -2
- package/build/components/image/shared.d.ts +0 -101
- package/build/components/image/shared.js +0 -405
- package/build/components/image-background.d.ts +0 -78
- package/build/components/image-background.js +0 -583
- package/build/components/input-accessory-view/index.d.ts +0 -56
- package/build/components/input-accessory-view/index.js +0 -403
- package/build/components/keyboard-avoiding-view/index.d.ts +0 -79
- package/build/components/keyboard-avoiding-view/index.js +0 -504
- package/build/components/modal/index.d.ts +0 -89
- package/build/components/modal/index.js +0 -529
- package/build/components/pressable/index.d.ts +0 -120
- package/build/components/pressable/index.js +0 -639
- package/build/components/refresh-control/index.d.ts +0 -83
- package/build/components/refresh-control/index.js +0 -509
- package/build/components/safe-area-view/index.d.ts +0 -54
- package/build/components/safe-area-view/index.js +0 -388
- package/build/components/scroll-view/index.android.d.ts +0 -16
- package/build/components/scroll-view/index.android.js +0 -228
- package/build/components/scroll-view/index.d.ts +0 -3
- package/build/components/scroll-view/index.ios.d.ts +0 -6
- package/build/components/scroll-view/index.ios.js +0 -113
- package/build/components/scroll-view/index.js +0 -5
- package/build/components/scroll-view/projection.d.ts +0 -32
- package/build/components/scroll-view/projection.js +0 -311
- package/build/components/scroll-view/shared.d.ts +0 -225
- package/build/components/scroll-view/shared.js +0 -757
- package/build/components/scroll-view/sticky-header.d.ts +0 -38
- package/build/components/scroll-view/sticky-header.js +0 -257
- package/build/components/section-list/index.d.ts +0 -113
- package/build/components/section-list/index.js +0 -768
- package/build/components/switch/index.android.d.ts +0 -17
- package/build/components/switch/index.android.js +0 -107
- package/build/components/switch/index.d.ts +0 -1
- package/build/components/switch/index.ios.d.ts +0 -16
- package/build/components/switch/index.ios.js +0 -106
- package/build/components/switch/index.js +0 -3
- package/build/components/switch/shared.d.ts +0 -37
- package/build/components/switch/shared.js +0 -176
- package/build/components/text-input.d.ts +0 -150
- package/build/components/text-input.js +0 -821
- package/build/components/touchable/index.d.ts +0 -241
- package/build/components/touchable/index.js +0 -1672
- package/build/components/touchable-native-feedback/index.d.ts +0 -80
- package/build/components/touchable-native-feedback/index.js +0 -545
- package/build/components/virtualized-list/directives.d.ts +0 -41
- package/build/components/virtualized-list/directives.js +0 -249
- package/build/components/virtualized-list/index.d.ts +0 -265
- package/build/components/virtualized-list/index.js +0 -1343
- package/build/components/virtualized-section-list/directives.d.ts +0 -28
- package/build/components/virtualized-section-list/directives.js +0 -148
- package/build/components/virtualized-section-list/index.d.ts +0 -175
- package/build/components/virtualized-section-list/index.js +0 -854
- package/build/components.d.ts +0 -39
- package/build/components.js +0 -37
- package/build/create-portal/index.d.ts +0 -17
- package/build/create-portal/index.js +0 -128
- package/build/create-tunnel/index.d.ts +0 -29
- package/build/create-tunnel/index.js +0 -181
- package/build/descriptor-to-angular/index.d.ts +0 -19
- package/build/descriptor-to-angular/index.js +0 -186
- package/build/host-instance/index.d.ts +0 -2
- package/build/host-instance/index.js +0 -36
- package/build/index.d.ts +0 -21
- package/build/index.js +0 -40
- package/build/modules/animated/animated-leaf-binder/index.d.ts +0 -14
- package/build/modules/animated/animated-leaf-binder/index.js +0 -90
- package/build/modules/animated/animated-leaf-binder.d.ts +0 -14
- package/build/modules/animated/animated-leaf-binder.js +0 -90
- package/build/modules/animated/create-animated-component.d.ts +0 -48
- package/build/modules/animated/create-animated-component.js +0 -425
- package/build/modules/animated/index.d.ts +0 -51
- package/build/modules/animated/index.js +0 -58
- package/build/modules/app-registry/index.d.ts +0 -7
- package/build/modules/app-registry/index.js +0 -22
- package/build/modules/status-bar/index.d.ts +0 -17
- package/build/modules/status-bar/index.js +0 -121
- package/build/primitives/index.d.ts +0 -32
- package/build/primitives/index.js +0 -386
- package/build/primitives/shared.d.ts +0 -83
- package/build/primitives/shared.js +0 -230
- package/build/render/index.d.ts +0 -11
- package/build/render/index.js +0 -170
- package/build/renderer/index.d.ts +0 -38
- package/build/renderer/index.js +0 -355
- package/build/services/color-scheme.service.d.ts +0 -13
- package/build/services/color-scheme.service.js +0 -72
- package/build/services/index.d.ts +0 -2
- package/build/services/index.js +0 -2
- package/build/services/window-dimensions.service.d.ts +0 -13
- package/build/services/window-dimensions.service.js +0 -83
- package/src/__tests__/responder-change-detection.test.ts +0 -102
- package/src/__tests__/responder-nested-cd.test.ts +0 -113
- package/src/angular-gaps.test.ts +0 -142
- package/src/angular-imports.test.ts +0 -100
- package/src/components/activity-indicator/activity-indicator-class-style.test.ts +0 -44
- package/src/components/activity-indicator/activity-indicator.test.ts +0 -131
- package/src/components/flat-list/column-wrapper-style-class.test.ts +0 -116
- package/src/components/flat-list/flat-list-array-style.test.ts +0 -131
- package/src/components/flat-list/flat-list-class-style.test.ts +0 -74
- package/src/components/flat-list/flat-list-scroll-containment.test.ts +0 -107
- package/src/components/flat-list/flat-list.test.ts +0 -99
- package/src/components/image/image-class-style.test.ts +0 -46
- package/src/components/image-background-class-style.test.ts +0 -48
- package/src/components/image-background-style-class.test.ts +0 -79
- package/src/components/input-accessory-view/input-accessory-view.test.ts +0 -43
- package/src/components/keyboard-avoiding-view/keyboard-avoiding-view.test.ts +0 -54
- package/src/components/modal/modal.test.ts +0 -43
- package/src/components/pressable/pressable.test.ts +0 -85
- package/src/components/refresh-control/refresh-control.test.ts +0 -41
- package/src/components/safe-area-view/safe-area-view.test.ts +0 -43
- package/src/components/scroll-view/android-scroll-view-axis-projection.test.ts +0 -123
- package/src/components/scroll-view/content-container-style-class.test.ts +0 -75
- package/src/components/scroll-view/scroll-view-class-style.test.ts +0 -134
- package/src/components/scroll-view/scroll-view-projection.test.ts +0 -194
- package/src/components/switch/android-switch-class.test.ts +0 -42
- package/src/components/switch/switch.test.ts +0 -92
- package/src/components/text-input-class-style.test.ts +0 -54
- package/src/components/touchable/touchable.test.ts +0 -102
- package/src/components/touchable-native-feedback/touchable-native-feedback.test.ts +0 -49
- package/src/components/virtualized-list/virtualized-list-class-style.test.ts +0 -76
- package/src/components/virtualized-section-list/virtualized-section-list-class-style.test.ts +0 -60
- package/src/create-portal/create-portal.test.ts +0 -90
- package/src/create-tunnel/create-tunnel.test.ts +0 -120
- package/src/descriptor-to-angular/descriptor-outlet.test.ts +0 -94
- package/src/host-instance/host-instance.test.ts +0 -49
- package/src/modules/animated/animated-leaf-binder/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-leaf-binder.test.ts +0 -116
- package/src/modules/animated/animated-scroll-view.test.ts +0 -102
- package/src/modules/app-registry/app-registry.test.ts +0 -106
- package/src/modules/status-bar/status-bar.test.ts +0 -50
- package/src/render/render.test.ts +0 -147
- package/src/renderer/renderer.test.ts +0 -202
- package/src/services/services.test.ts +0 -163
|
@@ -51,31 +51,26 @@ import {
|
|
|
51
51
|
FIRST_INDEX,
|
|
52
52
|
INVERTED_X_STYLE,
|
|
53
53
|
INVERTED_Y_STYLE,
|
|
54
|
-
NO_CONTENT_LENGTH_SENT,
|
|
55
|
-
averageMeasuredLength,
|
|
56
54
|
buildListPlan,
|
|
57
|
-
buildOffsets,
|
|
58
55
|
buildViewabilityPairs,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
computeWindow,
|
|
63
|
-
diffViewable,
|
|
64
|
-
highestMeasuredIndex,
|
|
65
|
-
maxMinimumViewTime,
|
|
66
|
-
offsetForIndex,
|
|
56
|
+
createInitialListState,
|
|
57
|
+
isSeparatorGapInRange,
|
|
58
|
+
listEffectSignature,
|
|
67
59
|
readLayoutLength,
|
|
68
60
|
readScrollOffset,
|
|
61
|
+
reduceList,
|
|
69
62
|
resolveAccessibilityProps,
|
|
70
|
-
|
|
63
|
+
resolveItemKey,
|
|
71
64
|
type IAccessibilityProps,
|
|
72
65
|
type IAccessibilityStateValue,
|
|
73
66
|
type IAriaProps,
|
|
74
|
-
type
|
|
67
|
+
type IListAction,
|
|
68
|
+
type IListEffect,
|
|
69
|
+
type IListReducerInputs,
|
|
70
|
+
type IListState,
|
|
75
71
|
type ISeparatorProps,
|
|
76
72
|
type ISeparators,
|
|
77
73
|
type IScrollViewHandle,
|
|
78
|
-
type IViewToken,
|
|
79
74
|
type IViewabilityConfig,
|
|
80
75
|
type IViewabilityConfigCallbackPair,
|
|
81
76
|
type IViewableItemsChangedInfo,
|
|
@@ -193,19 +188,6 @@ export type IVirtualizedListInputs<ItemT> = Omit<
|
|
|
193
188
|
| 'onAccessibilityEscape'
|
|
194
189
|
>;
|
|
195
190
|
|
|
196
|
-
// The windowing snapshot recomputed once per CD in ngDoCheck. Plain object (not a signal/computed):
|
|
197
|
-
// the OnPush + markForCheck cycle re-runs ngDoCheck whenever scroll / layout / measure change.
|
|
198
|
-
interface IMetricsCore {
|
|
199
|
-
count: number;
|
|
200
|
-
offsets: number[];
|
|
201
|
-
lengths: number[];
|
|
202
|
-
total: number;
|
|
203
|
-
first: number;
|
|
204
|
-
last: number;
|
|
205
|
-
target: { first: number; last: number };
|
|
206
|
-
averageLength: number;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
191
|
// One in-window cell, assembled in ngDoCheck and stamped by the template's @for. The
|
|
210
192
|
// context object is fresh each pass; VListOutletDirective folds it onto the live embedded view, so
|
|
211
193
|
// the cell view is reused (the windowing recompute does not tear cells down).
|
|
@@ -460,35 +442,19 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
460
442
|
// as contentOffset). A fresh object identity each push so the commit re-applies a repeated value.
|
|
461
443
|
commandedOffset: { x: number; y: number } | undefined = undefined;
|
|
462
444
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
445
|
+
// The one folded state cell (the Angular twin of React's stateRef): scroll offset, viewport,
|
|
446
|
+
// measured lengths, committed window, edge/viewability dedup, MVCP anchor — all in IListState,
|
|
447
|
+
// driven by the shared reduceList. `renderVersion` bumps whenever a transition changes render
|
|
448
|
+
// state, so ngDoCheck's recompute-dedup can detect it (listState is a plain object, not tracked).
|
|
449
|
+
private readonly listState: IListState<ItemT> = createInitialListState<ItemT>();
|
|
450
|
+
private renderVersion = EMPTY_OFFSET;
|
|
467
451
|
|
|
468
|
-
// --- non-render state (the Angular twin of refs that never trigger a render) ---
|
|
469
|
-
private metricsCore: IMetricsCore = {
|
|
470
|
-
count: EMPTY_OFFSET,
|
|
471
|
-
offsets: [],
|
|
472
|
-
lengths: [],
|
|
473
|
-
total: EMPTY_OFFSET,
|
|
474
|
-
first: FIRST_INDEX,
|
|
475
|
-
last: -1,
|
|
476
|
-
target: { first: FIRST_INDEX, last: -1 },
|
|
477
|
-
averageLength: EMPTY_OFFSET,
|
|
478
|
-
};
|
|
479
|
-
private readonly measured = new Map<number, number>();
|
|
480
452
|
private readonly cellMeasures = new Map<number, (event: ISymbioteEvent) => void>();
|
|
481
453
|
private readonly separatorOverrides = new Map<number, Partial<ISeparatorProps<unknown>>>();
|
|
482
|
-
// The
|
|
483
|
-
|
|
484
|
-
private sentEndForContentLength = NO_CONTENT_LENGTH_SENT;
|
|
485
|
-
private sentStartForContentLength = NO_CONTENT_LENGTH_SENT;
|
|
486
|
-
private lastViewable = new Map<string, IViewToken<ItemT>>();
|
|
454
|
+
// The adapter-owned debounce (minimumViewTime) and incremental-fill timers; the reducer only
|
|
455
|
+
// hands back a delay.
|
|
487
456
|
private viewableTimer: ReturnType<typeof setTimeout> | null = null;
|
|
488
457
|
private batchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
489
|
-
private hasInteracted = false;
|
|
490
|
-
private appliedInitialScroll = false;
|
|
491
|
-
private firstVisibleKey: string | null = null;
|
|
492
458
|
// Dedupes the after-commit effects: they run only when the windowing signature changed.
|
|
493
459
|
private lastEffectSignature = '';
|
|
494
460
|
// Dedupes ngDoCheck's own recompute. WITHOUT this, recomputeView() unconditionally rebuilds
|
|
@@ -586,22 +552,18 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
586
552
|
const offset = readScrollOffset(event, this.isHorizontal);
|
|
587
553
|
if (offset === undefined) return;
|
|
588
554
|
dlog(`Angular VirtualizedList onScroll offset=${offset}`);
|
|
589
|
-
// First scroll is the interaction that ungates waitForInteraction configs.
|
|
590
|
-
this.hasInteracted = true;
|
|
591
|
-
this.scrollOffset = offset;
|
|
592
555
|
// A real native scroll supersedes any pending commanded offset.
|
|
593
556
|
this.commandedOffset = undefined;
|
|
557
|
+
this.dispatch({ kind: 'scroll', offset });
|
|
594
558
|
// Compose, don't clobber: internal windowing ran first, now the user's onScroll.
|
|
595
559
|
this.onScroll?.(event);
|
|
596
|
-
this.cdr.markForCheck();
|
|
597
560
|
};
|
|
598
561
|
|
|
599
562
|
onLayoutTick = (event: ISymbioteEvent): void => {
|
|
600
563
|
const length = readLayoutLength(event, this.isHorizontal);
|
|
601
564
|
if (length === undefined) return;
|
|
602
565
|
dlog(`Angular VirtualizedList onLayout viewport=${length}`);
|
|
603
|
-
this.
|
|
604
|
-
this.cdr.markForCheck();
|
|
566
|
+
this.dispatch({ kind: 'layout', length });
|
|
605
567
|
};
|
|
606
568
|
|
|
607
569
|
// RefreshControl's refresh and ScrollView's accessibility events are real @Output()s now, bound
|
|
@@ -629,14 +591,110 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
629
591
|
|
|
630
592
|
private keyFor = (index: number): string => {
|
|
631
593
|
const item = this.getItem(this.data, index);
|
|
632
|
-
return
|
|
594
|
+
return resolveItemKey(item, index, this.keyExtractor);
|
|
633
595
|
};
|
|
634
596
|
|
|
597
|
+
// The reducer inputs, folded off the @Input()s each call. The edge/viewability listeners map to
|
|
598
|
+
// `.observed` (Angular's "is anyone bound to this @Output()"), so the reducer emits only when the
|
|
599
|
+
// consumer listens — the same on-demand gating the prop-callback era had.
|
|
600
|
+
private buildInputs(): IListReducerInputs<ItemT> {
|
|
601
|
+
return {
|
|
602
|
+
data: this.data,
|
|
603
|
+
getItem: this.getItem,
|
|
604
|
+
getItemCount: this.getItemCount,
|
|
605
|
+
keyExtractor: this.keyExtractor,
|
|
606
|
+
getItemLayout: this.getItemLayout,
|
|
607
|
+
horizontal: this.isHorizontal,
|
|
608
|
+
windowSize: this.windowSizeValue,
|
|
609
|
+
initialNumToRender: this.initialNumToRenderValue,
|
|
610
|
+
maxToRenderPerBatch: this.maxToRenderPerBatchValue,
|
|
611
|
+
updateCellsBatchingPeriod: this.updateCellsBatchingPeriodValue,
|
|
612
|
+
onEndReachedThreshold: this.onEndReachedThresholdValue,
|
|
613
|
+
onStartReachedThreshold: this.onStartReachedThresholdValue,
|
|
614
|
+
onEndReachedActive: this.endReached.observed,
|
|
615
|
+
onStartReachedActive: this.startReached.observed,
|
|
616
|
+
viewabilityPairs: buildViewabilityPairs(
|
|
617
|
+
this.viewableItemsChanged.observed
|
|
618
|
+
? (info: IViewableItemsChangedInfo<ItemT>): void => this.viewableItemsChanged.emit(info)
|
|
619
|
+
: undefined,
|
|
620
|
+
this.viewabilityConfig,
|
|
621
|
+
this.viewabilityConfigCallbackPairs,
|
|
622
|
+
),
|
|
623
|
+
maintainVisibleContentPosition: this.maintainVisibleContentPosition,
|
|
624
|
+
initialScrollIndex: this.initialScrollIndex,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Map a native event / imperative call to an action, run the returned effects, and mark the view
|
|
629
|
+
// dirty when render state changed (the native callbacks fire outside Angular's own bindings).
|
|
630
|
+
private dispatch(action: IListAction<ItemT>): void {
|
|
631
|
+
const inputs = this.buildInputs();
|
|
632
|
+
const result = reduceList(this.listState, action, inputs);
|
|
633
|
+
this.runEffects(result.effects, inputs);
|
|
634
|
+
if (result.changed) {
|
|
635
|
+
this.renderVersion += 1;
|
|
636
|
+
this.cdr.markForCheck();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private runEffects(effects: IListEffect<ItemT>[], inputs: IListReducerInputs<ItemT>): void {
|
|
641
|
+
for (const effect of effects) {
|
|
642
|
+
switch (effect.kind) {
|
|
643
|
+
case 'scroll-to':
|
|
644
|
+
this.scrollToPixel(effect.offset, effect.animated);
|
|
645
|
+
break;
|
|
646
|
+
case 'fire-end-reached':
|
|
647
|
+
this.endReached.emit({ distanceFromEnd: effect.distanceFromEnd });
|
|
648
|
+
break;
|
|
649
|
+
case 'fire-start-reached':
|
|
650
|
+
this.startReached.emit({ distanceFromStart: effect.distanceFromStart });
|
|
651
|
+
break;
|
|
652
|
+
case 'fire-scroll-to-index-failed':
|
|
653
|
+
this.scrollToIndexFailed.emit({
|
|
654
|
+
index: effect.index,
|
|
655
|
+
highestMeasuredFrameIndex: effect.highestMeasuredFrameIndex,
|
|
656
|
+
averageItemLength: effect.averageItemLength,
|
|
657
|
+
});
|
|
658
|
+
break;
|
|
659
|
+
case 'schedule-refill': {
|
|
660
|
+
if (this.batchTimer !== null) clearTimeout(this.batchTimer);
|
|
661
|
+
this.batchTimer = setTimeout(() => {
|
|
662
|
+
this.batchTimer = null;
|
|
663
|
+
this.dispatch({ kind: 'batch-tick' });
|
|
664
|
+
}, effect.delay);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case 'fire-viewable': {
|
|
668
|
+
const pairs = inputs.viewabilityPairs;
|
|
669
|
+
const info = effect.info;
|
|
670
|
+
const map = effect.map;
|
|
671
|
+
const fire = (): void => {
|
|
672
|
+
for (const pair of pairs) pair.onViewableItemsChanged(info);
|
|
673
|
+
this.dispatch({ kind: 'viewable-fired', map });
|
|
674
|
+
};
|
|
675
|
+
if (this.viewableTimer !== null) {
|
|
676
|
+
clearTimeout(this.viewableTimer);
|
|
677
|
+
this.viewableTimer = null;
|
|
678
|
+
}
|
|
679
|
+
if (effect.delay > EMPTY_OFFSET) {
|
|
680
|
+
this.viewableTimer = setTimeout(() => {
|
|
681
|
+
this.viewableTimer = null;
|
|
682
|
+
fire();
|
|
683
|
+
}, effect.delay);
|
|
684
|
+
} else {
|
|
685
|
+
fire();
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
635
693
|
// Once-per-CD recompute, BEFORE the template bindings are read (so the freshly computed
|
|
636
694
|
// template-bound fields render this pass — computing them in ngAfterContentChecked instead would
|
|
637
|
-
// trip ExpressionChangedAfterItHasBeenChecked).
|
|
638
|
-
//
|
|
639
|
-
//
|
|
695
|
+
// trip ExpressionChangedAfterItHasBeenChecked). The dedup guard runs refresh-metrics (which owns
|
|
696
|
+
// the controlled committedWindow throttle) only when something relevant changed, so the window
|
|
697
|
+
// grows exactly one step per meaningful CD. The projected templates (ContentChild) are resolved
|
|
640
698
|
// from the second CD on; the first paint (count/viewport still 0) corrects on the layout-driven CD.
|
|
641
699
|
ngDoCheck(): void {
|
|
642
700
|
const recomputeInputs: unknown[] = [
|
|
@@ -653,9 +711,8 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
653
711
|
this.maintainVisibleContentPosition,
|
|
654
712
|
this.style,
|
|
655
713
|
this.contentContainerStyle,
|
|
656
|
-
|
|
657
|
-
this.
|
|
658
|
-
this.measureVersion,
|
|
714
|
+
// Folds scroll / layout / measure / batch-tick: dispatch bumps renderVersion on any change.
|
|
715
|
+
this.renderVersion,
|
|
659
716
|
this.headerDir !== undefined,
|
|
660
717
|
this.footerDir !== undefined,
|
|
661
718
|
this.emptyDir !== undefined,
|
|
@@ -670,21 +727,18 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
670
727
|
) {
|
|
671
728
|
return;
|
|
672
729
|
}
|
|
673
|
-
|
|
730
|
+
// The single derive-per-CD: recompute the window off the current state before the view reads it.
|
|
731
|
+
reduceList(this.listState, { kind: 'refresh-metrics' }, this.buildInputs());
|
|
674
732
|
this.recomputeView();
|
|
675
733
|
}
|
|
676
734
|
|
|
677
735
|
ngAfterViewChecked(): void {
|
|
678
|
-
const
|
|
679
|
-
const signature = `${this.scrollOffset}|${this.viewportLength}|${m.first}|${m.last}|${m.count}|${m.total}|${this.measureVersion}`;
|
|
736
|
+
const signature = listEffectSignature(this.listState);
|
|
680
737
|
if (signature === this.lastEffectSignature) return;
|
|
681
738
|
this.lastEffectSignature = signature;
|
|
682
|
-
this.
|
|
683
|
-
this.
|
|
684
|
-
this.
|
|
685
|
-
this.effectViewability();
|
|
686
|
-
this.effectInitialScroll();
|
|
687
|
-
this.effectMaintainVisibleContentPosition();
|
|
739
|
+
const inputs = this.buildInputs();
|
|
740
|
+
const result = reduceList(this.listState, { kind: 'commit' }, inputs);
|
|
741
|
+
this.runEffects(result.effects, inputs);
|
|
688
742
|
}
|
|
689
743
|
|
|
690
744
|
ngOnDestroy(): void {
|
|
@@ -692,54 +746,8 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
692
746
|
if (this.batchTimer !== null) clearTimeout(this.batchTimer);
|
|
693
747
|
}
|
|
694
748
|
|
|
695
|
-
private recomputeMetrics(): void {
|
|
696
|
-
const count = this.getItemCount(this.data);
|
|
697
|
-
const gil = this.getItemLayout;
|
|
698
|
-
const data = this.data;
|
|
699
|
-
const fixedLayout =
|
|
700
|
-
gil !== undefined
|
|
701
|
-
? (index: number): ICellLayout => {
|
|
702
|
-
const layout = gil(data, index);
|
|
703
|
-
return { length: layout.length, offset: layout.offset };
|
|
704
|
-
}
|
|
705
|
-
: undefined;
|
|
706
|
-
const averageLength =
|
|
707
|
-
fixedLayout !== undefined
|
|
708
|
-
? count > FIRST_INDEX
|
|
709
|
-
? fixedLayout(FIRST_INDEX).length
|
|
710
|
-
: EMPTY_OFFSET
|
|
711
|
-
: averageMeasuredLength(this.measured);
|
|
712
|
-
const { offsets, lengths, total } = buildOffsets(
|
|
713
|
-
count,
|
|
714
|
-
this.measured,
|
|
715
|
-
fixedLayout,
|
|
716
|
-
averageLength,
|
|
717
|
-
);
|
|
718
|
-
const target = computeWindow(
|
|
719
|
-
count,
|
|
720
|
-
offsets,
|
|
721
|
-
lengths,
|
|
722
|
-
this.scrollOffset,
|
|
723
|
-
this.viewportLength,
|
|
724
|
-
this.windowSizeValue,
|
|
725
|
-
this.initialNumToRenderValue,
|
|
726
|
-
);
|
|
727
|
-
const throttled = throttleWindow(target, this.committedWindow, this.maxToRenderPerBatchValue);
|
|
728
|
-
this.committedWindow = throttled;
|
|
729
|
-
this.metricsCore = {
|
|
730
|
-
count,
|
|
731
|
-
offsets,
|
|
732
|
-
lengths,
|
|
733
|
-
total,
|
|
734
|
-
first: throttled.first,
|
|
735
|
-
last: throttled.last,
|
|
736
|
-
target,
|
|
737
|
-
averageLength,
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
749
|
private recomputeView(): void {
|
|
742
|
-
const m = this.
|
|
750
|
+
const m = this.listState.metrics;
|
|
743
751
|
this.itemCount = m.count;
|
|
744
752
|
const hasHeader = this.headerDir !== undefined;
|
|
745
753
|
const hasSeparators = this.separatorDir !== undefined;
|
|
@@ -774,7 +782,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
774
782
|
this.leadingSpacerStyle = null;
|
|
775
783
|
this.trailingSpacerStyle = null;
|
|
776
784
|
this.renderedStickyIndices = undefined;
|
|
777
|
-
dlog(`Angular VirtualizedList empty (viewport=${this.viewportLength})`);
|
|
785
|
+
dlog(`Angular VirtualizedList empty (viewport=${this.listState.viewportLength})`);
|
|
778
786
|
return;
|
|
779
787
|
}
|
|
780
788
|
|
|
@@ -823,7 +831,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
823
831
|
|
|
824
832
|
dlog(
|
|
825
833
|
`Angular VirtualizedList window [${m.first}, ${m.last}] of ${m.count} ` +
|
|
826
|
-
`(offset=${this.scrollOffset}, viewport=${this.viewportLength}, rendered=${cells.length})`,
|
|
834
|
+
`(offset=${this.listState.scrollOffset}, viewport=${this.listState.viewportLength}, rendered=${cells.length})`,
|
|
827
835
|
);
|
|
828
836
|
}
|
|
829
837
|
|
|
@@ -855,14 +863,12 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
855
863
|
const existing = this.cellMeasures.get(index);
|
|
856
864
|
if (existing !== undefined) return existing;
|
|
857
865
|
const measure = (event: ISymbioteEvent): void => {
|
|
858
|
-
if (this.getItemLayout !== undefined) return;
|
|
859
866
|
const length = readLayoutLength(event, this.isHorizontal);
|
|
860
867
|
if (length === undefined) return;
|
|
861
|
-
if (this.measured.get(index) === length) return;
|
|
862
|
-
this.measured.set(index, length);
|
|
863
868
|
dlog(`Angular VirtualizedList cell ${index} measured length=${length}`);
|
|
864
|
-
|
|
865
|
-
|
|
869
|
+
// The reducer guards a fixed getItemLayout and a repeated length; a no-op leaves state and the
|
|
870
|
+
// view untouched (dispatch marks for check only when something changed).
|
|
871
|
+
this.dispatch({ kind: 'measure', index, length });
|
|
866
872
|
};
|
|
867
873
|
this.cellMeasures.set(index, measure);
|
|
868
874
|
return measure;
|
|
@@ -879,8 +885,8 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
879
885
|
}
|
|
880
886
|
|
|
881
887
|
private mergeSeparator(gapIndex: number, patch: Partial<ISeparatorProps<unknown>>): void {
|
|
882
|
-
const count = this.
|
|
883
|
-
if (gapIndex
|
|
888
|
+
const count = this.listState.metrics.count;
|
|
889
|
+
if (!isSeparatorGapInRange(gapIndex, count)) return;
|
|
884
890
|
this.separatorOverrides.set(gapIndex, { ...this.separatorOverrides.get(gapIndex), ...patch });
|
|
885
891
|
this.cdr.markForCheck();
|
|
886
892
|
}
|
|
@@ -904,9 +910,15 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
904
910
|
}
|
|
905
911
|
|
|
906
912
|
// ---- imperative handle (the shared IVirtualizedListHandle surface) ----
|
|
913
|
+
// Each scroll resolves to an offset (or a scroll-to-index failure) inside the reducer, then rides
|
|
914
|
+
// the scroll-to effect through scrollToPixel.
|
|
907
915
|
|
|
908
916
|
scrollToOffset(params: { offset: number; animated?: boolean }): void {
|
|
909
|
-
this.
|
|
917
|
+
this.dispatch({
|
|
918
|
+
kind: 'scroll-to-offset',
|
|
919
|
+
offset: params.offset,
|
|
920
|
+
animated: params.animated ?? true,
|
|
921
|
+
});
|
|
910
922
|
}
|
|
911
923
|
|
|
912
924
|
scrollToIndex(params: {
|
|
@@ -915,50 +927,26 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
915
927
|
viewOffset?: number;
|
|
916
928
|
viewPosition?: number;
|
|
917
929
|
}): void {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
);
|
|
926
|
-
this.scrollToIndexFailed.emit({
|
|
927
|
-
index: params.index,
|
|
928
|
-
highestMeasuredFrameIndex: highestMeasuredIndex(this.measured),
|
|
929
|
-
averageItemLength: m.averageLength,
|
|
930
|
-
});
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
this.scrollToPixel(
|
|
934
|
-
this.offsetForIndexLocal(
|
|
935
|
-
params.index,
|
|
936
|
-
params.viewPosition ?? FIRST_INDEX,
|
|
937
|
-
params.viewOffset ?? EMPTY_OFFSET,
|
|
938
|
-
),
|
|
939
|
-
params.animated ?? true,
|
|
940
|
-
);
|
|
930
|
+
this.dispatch({
|
|
931
|
+
kind: 'scroll-to-index',
|
|
932
|
+
index: params.index,
|
|
933
|
+
animated: params.animated ?? true,
|
|
934
|
+
viewPosition: params.viewPosition ?? FIRST_INDEX,
|
|
935
|
+
viewOffset: params.viewOffset ?? EMPTY_OFFSET,
|
|
936
|
+
});
|
|
941
937
|
}
|
|
942
938
|
|
|
943
939
|
scrollToItem(params: { item: unknown; animated?: boolean; viewPosition?: number }): void {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
dlog('Angular VirtualizedList scrollToItem: item not found');
|
|
940
|
+
this.dispatch({
|
|
941
|
+
kind: 'scroll-to-item',
|
|
942
|
+
item: params.item,
|
|
943
|
+
animated: params.animated ?? true,
|
|
944
|
+
viewPosition: params.viewPosition ?? FIRST_INDEX,
|
|
945
|
+
});
|
|
955
946
|
}
|
|
956
947
|
|
|
957
948
|
scrollToEnd(params?: { animated?: boolean }): void {
|
|
958
|
-
this.
|
|
959
|
-
Math.max(EMPTY_OFFSET, this.metricsCore.total - this.viewportLength),
|
|
960
|
-
params?.animated ?? true,
|
|
961
|
-
);
|
|
949
|
+
this.dispatch({ kind: 'scroll-to-end', animated: params?.animated ?? true });
|
|
962
950
|
}
|
|
963
951
|
|
|
964
952
|
flashScrollIndicators(): void {
|
|
@@ -982,7 +970,7 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
982
970
|
}
|
|
983
971
|
|
|
984
972
|
recordInteraction(): void {
|
|
985
|
-
this.
|
|
973
|
+
this.dispatch({ kind: 'record-interaction' });
|
|
986
974
|
}
|
|
987
975
|
|
|
988
976
|
private scrollToPixel(offset: number, animated: boolean): void {
|
|
@@ -1001,203 +989,4 @@ export class VirtualizedList<ItemT = unknown>
|
|
|
1001
989
|
this.commandedOffset = target;
|
|
1002
990
|
this.cdr.markForCheck();
|
|
1003
991
|
}
|
|
1004
|
-
|
|
1005
|
-
private offsetForIndexLocal(index: number, viewPosition: number, viewOffset: number): number {
|
|
1006
|
-
const m = this.metricsCore;
|
|
1007
|
-
return offsetForIndex(
|
|
1008
|
-
index,
|
|
1009
|
-
viewPosition,
|
|
1010
|
-
viewOffset,
|
|
1011
|
-
m.count,
|
|
1012
|
-
m.offsets,
|
|
1013
|
-
m.lengths,
|
|
1014
|
-
this.viewportLength,
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// ---- after-commit effects (run in ngAfterViewChecked, the post-render seam) ----
|
|
1019
|
-
|
|
1020
|
-
// Batch fill: when the throttled window has not reached the target, schedule a re-render so the
|
|
1021
|
-
// window keeps filling toward target over successive ticks (RN's incremental fill).
|
|
1022
|
-
private effectBatchFill(): void {
|
|
1023
|
-
const m = this.metricsCore;
|
|
1024
|
-
if (this.batchTimer !== null) {
|
|
1025
|
-
clearTimeout(this.batchTimer);
|
|
1026
|
-
this.batchTimer = null;
|
|
1027
|
-
}
|
|
1028
|
-
if (m.first <= m.target.first && m.last >= m.target.last) return;
|
|
1029
|
-
this.batchTimer = setTimeout(() => {
|
|
1030
|
-
this.batchTimer = null;
|
|
1031
|
-
this.measureVersion += 1;
|
|
1032
|
-
this.cdr.markForCheck();
|
|
1033
|
-
}, this.updateCellsBatchingPeriodValue);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// onEndReached: fire only when the actual last cell is rendered AND within threshold; dedup by
|
|
1037
|
-
// content length; re-arm on scroll away from the end (RN _maybeCallOnEdgeReached).
|
|
1038
|
-
private effectEndReached(): void {
|
|
1039
|
-
if (!this.endReached.observed || this.viewportLength <= EMPTY_OFFSET) return;
|
|
1040
|
-
const m = this.metricsCore;
|
|
1041
|
-
const { distanceFromEnd, withinThreshold } = computeEndReached(
|
|
1042
|
-
m.total,
|
|
1043
|
-
this.scrollOffset,
|
|
1044
|
-
this.viewportLength,
|
|
1045
|
-
this.onEndReachedThresholdValue,
|
|
1046
|
-
);
|
|
1047
|
-
const lastCellRendered = m.last === m.count - 1;
|
|
1048
|
-
if (withinThreshold && lastCellRendered && this.sentEndForContentLength !== m.total) {
|
|
1049
|
-
this.sentEndForContentLength = m.total;
|
|
1050
|
-
dlog(
|
|
1051
|
-
`Angular VirtualizedList endReached distanceFromEnd=${distanceFromEnd} ` +
|
|
1052
|
-
`(last=${m.last} of ${m.count}, contentLength=${m.total})`,
|
|
1053
|
-
);
|
|
1054
|
-
this.endReached.emit({ distanceFromEnd });
|
|
1055
|
-
}
|
|
1056
|
-
if (!withinThreshold) this.sentEndForContentLength = NO_CONTENT_LENGTH_SENT;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// startReached: the top-edge twin of endReached.
|
|
1060
|
-
private effectStartReached(): void {
|
|
1061
|
-
if (!this.startReached.observed || this.viewportLength <= EMPTY_OFFSET) return;
|
|
1062
|
-
const m = this.metricsCore;
|
|
1063
|
-
const { distanceFromStart, withinThreshold } = computeStartReached(
|
|
1064
|
-
this.scrollOffset,
|
|
1065
|
-
this.viewportLength,
|
|
1066
|
-
this.onStartReachedThresholdValue,
|
|
1067
|
-
);
|
|
1068
|
-
const firstCellRendered = m.first === FIRST_INDEX;
|
|
1069
|
-
if (withinThreshold && firstCellRendered && this.sentStartForContentLength !== m.total) {
|
|
1070
|
-
this.sentStartForContentLength = m.total;
|
|
1071
|
-
dlog(
|
|
1072
|
-
`Angular VirtualizedList startReached distanceFromStart=${distanceFromStart} ` +
|
|
1073
|
-
`(first=${m.first}, contentLength=${m.total})`,
|
|
1074
|
-
);
|
|
1075
|
-
this.startReached.emit({ distanceFromStart });
|
|
1076
|
-
}
|
|
1077
|
-
if (!withinThreshold) this.sentStartForContentLength = NO_CONTENT_LENGTH_SENT;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// Viewability: recompute which rendered cells clear the threshold and, if the viewable set
|
|
1081
|
-
// changed, fire viewableItemsChanged + every config/callback pair, honoring minimumViewTime.
|
|
1082
|
-
private effectViewability(): void {
|
|
1083
|
-
const m = this.metricsCore;
|
|
1084
|
-
const pairs = buildViewabilityPairs(
|
|
1085
|
-
this.viewableItemsChanged.observed
|
|
1086
|
-
? (info: IViewableItemsChangedInfo<ItemT>): void => this.viewableItemsChanged.emit(info)
|
|
1087
|
-
: undefined,
|
|
1088
|
-
this.viewabilityConfig,
|
|
1089
|
-
this.viewabilityConfigCallbackPairs,
|
|
1090
|
-
);
|
|
1091
|
-
if (pairs.length === EMPTY_OFFSET || this.viewportLength <= EMPTY_OFFSET) return;
|
|
1092
|
-
if (m.count === FIRST_INDEX) return;
|
|
1093
|
-
|
|
1094
|
-
const { tokens, map } = computeViewableSet({
|
|
1095
|
-
first: m.first,
|
|
1096
|
-
last: m.last,
|
|
1097
|
-
count: m.count,
|
|
1098
|
-
offsets: m.offsets,
|
|
1099
|
-
lengths: m.lengths,
|
|
1100
|
-
scrollOffset: this.scrollOffset,
|
|
1101
|
-
viewportLength: this.viewportLength,
|
|
1102
|
-
data: this.data,
|
|
1103
|
-
getItem: this.getItem,
|
|
1104
|
-
keyExtractor: this.keyExtractor,
|
|
1105
|
-
pairs,
|
|
1106
|
-
hasInteracted: this.hasInteracted,
|
|
1107
|
-
});
|
|
1108
|
-
const diff = diffViewable(this.lastViewable, map, tokens);
|
|
1109
|
-
if (!diff.hasChanged) return;
|
|
1110
|
-
|
|
1111
|
-
const commitAndFire = (): void => {
|
|
1112
|
-
this.lastViewable = map;
|
|
1113
|
-
dlog(
|
|
1114
|
-
`Angular VirtualizedList viewable=${tokens.length} changed=${diff.changed.length} ` +
|
|
1115
|
-
`(window [${m.first}, ${m.last}])`,
|
|
1116
|
-
);
|
|
1117
|
-
const info: IViewableItemsChangedInfo<ItemT> = {
|
|
1118
|
-
viewableItems: tokens,
|
|
1119
|
-
changed: diff.changed,
|
|
1120
|
-
};
|
|
1121
|
-
for (const pair of pairs) pair.onViewableItemsChanged(info);
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
const minimumViewTime = maxMinimumViewTime(pairs);
|
|
1125
|
-
if (this.viewableTimer !== null) {
|
|
1126
|
-
clearTimeout(this.viewableTimer);
|
|
1127
|
-
this.viewableTimer = null;
|
|
1128
|
-
}
|
|
1129
|
-
if (minimumViewTime > EMPTY_OFFSET) {
|
|
1130
|
-
dlog(
|
|
1131
|
-
`Angular VirtualizedList viewability debounce ${minimumViewTime}ms (window [${m.first}, ${m.last}])`,
|
|
1132
|
-
);
|
|
1133
|
-
this.viewableTimer = setTimeout(() => {
|
|
1134
|
-
this.viewableTimer = null;
|
|
1135
|
-
commitAndFire();
|
|
1136
|
-
}, minimumViewTime);
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
commitAndFire();
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// initialScrollIndex: once the first viewport is known, jump to that index a single time.
|
|
1143
|
-
private effectInitialScroll(): void {
|
|
1144
|
-
if (this.initialScrollIndex === undefined || this.appliedInitialScroll) return;
|
|
1145
|
-
if (this.viewportLength <= EMPTY_OFFSET || this.metricsCore.count === FIRST_INDEX) return;
|
|
1146
|
-
this.appliedInitialScroll = true;
|
|
1147
|
-
// The initial jump is instant (RN does not animate initialScrollIndex).
|
|
1148
|
-
this.scrollToPixel(
|
|
1149
|
-
this.offsetForIndexLocal(this.initialScrollIndex, FIRST_INDEX, EMPTY_OFFSET),
|
|
1150
|
-
false,
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// maintainVisibleContentPosition JS anchor adjustment: track the key of the item at
|
|
1155
|
-
// minIndexForVisible; when a prepend moves it down, add the inserted extent in the leading SPACER
|
|
1156
|
-
// (the off-window items native MVCP cannot see) to scrollOffset so the anchored item does not jump
|
|
1157
|
-
// (RN getDerivedStateFromProps).
|
|
1158
|
-
private effectMaintainVisibleContentPosition(): void {
|
|
1159
|
-
const m = this.metricsCore;
|
|
1160
|
-
if (this.maintainVisibleContentPosition === undefined || m.count === FIRST_INDEX) {
|
|
1161
|
-
this.firstVisibleKey = null;
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
const minIndexForVisible = this.maintainVisibleContentPosition.minIndexForVisible;
|
|
1165
|
-
const newFirstVisibleKey =
|
|
1166
|
-
m.count > minIndexForVisible ? this.keyFor(minIndexForVisible) : null;
|
|
1167
|
-
const prevKey = this.firstVisibleKey;
|
|
1168
|
-
|
|
1169
|
-
if (prevKey !== null && newFirstVisibleKey !== null && prevKey !== newFirstVisibleKey) {
|
|
1170
|
-
let anchorIndex = -1;
|
|
1171
|
-
for (let index = minIndexForVisible; index < m.count; index += 1) {
|
|
1172
|
-
if (this.keyFor(index) === prevKey) {
|
|
1173
|
-
anchorIndex = index;
|
|
1174
|
-
break;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
if (anchorIndex > minIndexForVisible) {
|
|
1178
|
-
const spacerEnd = Math.min(anchorIndex, this.committedWindow.first);
|
|
1179
|
-
const insertedExtent =
|
|
1180
|
-
spacerEnd > minIndexForVisible
|
|
1181
|
-
? m.offsets[spacerEnd] - m.offsets[minIndexForVisible]
|
|
1182
|
-
: EMPTY_OFFSET;
|
|
1183
|
-
if (insertedExtent > EMPTY_OFFSET) {
|
|
1184
|
-
const autoThreshold = this.maintainVisibleContentPosition.autoscrollToTopThreshold;
|
|
1185
|
-
const anchoredNearTop = autoThreshold !== undefined && this.scrollOffset <= autoThreshold;
|
|
1186
|
-
if (anchoredNearTop) {
|
|
1187
|
-
dlog(
|
|
1188
|
-
`Angular VirtualizedList MVCP autoscroll-to-top (offset=${this.scrollOffset} <= ${autoThreshold})`,
|
|
1189
|
-
);
|
|
1190
|
-
this.scrollToPixel(EMPTY_OFFSET, true);
|
|
1191
|
-
} else {
|
|
1192
|
-
dlog(
|
|
1193
|
-
`Angular VirtualizedList MVCP adjust +${insertedExtent}px ` +
|
|
1194
|
-
`(anchor "${prevKey}" moved ${minIndexForVisible}->${anchorIndex})`,
|
|
1195
|
-
);
|
|
1196
|
-
this.scrollToPixel(this.scrollOffset + insertedExtent, false);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
this.firstVisibleKey = newFirstVisibleKey;
|
|
1202
|
-
}
|
|
1203
992
|
}
|