chat-layout 1.2.0-6 → 1.2.0-7
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 +2 -2
- package/example/chat.ts +15 -4
- package/index.d.mts +41 -9
- package/index.mjs +738 -478
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -3596,6 +3596,40 @@ const listStateListenerRegistry = typeof FinalizationRegistry === "function" ? n
|
|
|
3596
3596
|
if (list == null) return;
|
|
3597
3597
|
deleteListStateListener(list, token);
|
|
3598
3598
|
}) : null;
|
|
3599
|
+
const listScrollMutations = /* @__PURE__ */ new WeakMap();
|
|
3600
|
+
const WRITE_LIST_SCROLL_STATE = Symbol("writeListScrollState");
|
|
3601
|
+
function normalizePosition(value) {
|
|
3602
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : void 0;
|
|
3603
|
+
}
|
|
3604
|
+
function normalizeOffset$1(value) {
|
|
3605
|
+
return Number.isFinite(value) ? value : 0;
|
|
3606
|
+
}
|
|
3607
|
+
function getListScrollMutationRecord(list) {
|
|
3608
|
+
let record = listScrollMutations.get(list);
|
|
3609
|
+
if (record == null) {
|
|
3610
|
+
record = {
|
|
3611
|
+
version: 0,
|
|
3612
|
+
source: "internal"
|
|
3613
|
+
};
|
|
3614
|
+
listScrollMutations.set(list, record);
|
|
3615
|
+
}
|
|
3616
|
+
return record;
|
|
3617
|
+
}
|
|
3618
|
+
function markListScrollMutation(list, source) {
|
|
3619
|
+
const record = getListScrollMutationRecord(list);
|
|
3620
|
+
record.version += 1;
|
|
3621
|
+
record.source = source;
|
|
3622
|
+
}
|
|
3623
|
+
function readListScrollMutation(list) {
|
|
3624
|
+
const record = getListScrollMutationRecord(list);
|
|
3625
|
+
return {
|
|
3626
|
+
version: record.version,
|
|
3627
|
+
source: record.source
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
function writeInternalListScrollState(list, state) {
|
|
3631
|
+
list[WRITE_LIST_SCROLL_STATE](state, "internal");
|
|
3632
|
+
}
|
|
3599
3633
|
function deleteListStateListener(list, token) {
|
|
3600
3634
|
const listeners = listStateListeners.get(list);
|
|
3601
3635
|
if (listeners == null) return;
|
|
@@ -3660,17 +3694,28 @@ function normalizeInsertAnimation(animation) {
|
|
|
3660
3694
|
const duration = normalizeInsertAnimationDuration(animation?.duration, animation != null);
|
|
3661
3695
|
if (duration == null) return;
|
|
3662
3696
|
const normalizedAnimation = { duration };
|
|
3663
|
-
if (typeof animation?.distance === "number" && Number.isFinite(animation.distance)) normalizedAnimation.distance = Math.max(0, animation.distance);
|
|
3664
3697
|
if (animation?.autoFollow === true) normalizedAnimation.autoFollow = true;
|
|
3665
3698
|
return normalizedAnimation;
|
|
3666
3699
|
}
|
|
3667
3700
|
var ListState = class {
|
|
3668
3701
|
#items;
|
|
3669
3702
|
#pendingDeletes = /* @__PURE__ */ new Set();
|
|
3703
|
+
#offset = 0;
|
|
3704
|
+
#position;
|
|
3670
3705
|
/** Pixel offset from the anchored item edge. */
|
|
3671
|
-
offset
|
|
3706
|
+
get offset() {
|
|
3707
|
+
return this.#offset;
|
|
3708
|
+
}
|
|
3709
|
+
set offset(value) {
|
|
3710
|
+
this.#writeScrollState({ offset: normalizeOffset$1(value) }, "external");
|
|
3711
|
+
}
|
|
3672
3712
|
/** Anchor item index, or `undefined` to use the renderer default. */
|
|
3673
|
-
position
|
|
3713
|
+
get position() {
|
|
3714
|
+
return this.#position;
|
|
3715
|
+
}
|
|
3716
|
+
set position(value) {
|
|
3717
|
+
this.#writeScrollState({ position: normalizePosition(value) }, "external");
|
|
3718
|
+
}
|
|
3674
3719
|
/** Items currently managed by the renderer. */
|
|
3675
3720
|
get items() {
|
|
3676
3721
|
return this.#items;
|
|
@@ -3700,7 +3745,7 @@ var ListState = class {
|
|
|
3700
3745
|
if (items.length === 0) return;
|
|
3701
3746
|
assertUniqueItemReferences(items, this.#items);
|
|
3702
3747
|
const normalizedAnimation = normalizeInsertAnimation(animation);
|
|
3703
|
-
if (this.position != null) this.position
|
|
3748
|
+
if (this.position != null) this.#writeScrollState({ position: this.position + items.length }, "internal");
|
|
3704
3749
|
this.#items = items.concat(this.#items);
|
|
3705
3750
|
emitListStateChange(this, {
|
|
3706
3751
|
type: "unshift",
|
|
@@ -3772,12 +3817,13 @@ var ListState = class {
|
|
|
3772
3817
|
this.#pendingDeletes.delete(item);
|
|
3773
3818
|
if (index < 0) return;
|
|
3774
3819
|
this.#items.splice(index, 1);
|
|
3775
|
-
if (this.#items.length === 0) {
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3820
|
+
if (this.#items.length === 0) this.#writeScrollState({
|
|
3821
|
+
position: void 0,
|
|
3822
|
+
offset: 0
|
|
3823
|
+
}, "internal");
|
|
3824
|
+
else if (this.position != null) {
|
|
3825
|
+
if (this.position > index) this.#writeScrollState({ position: this.position - 1 }, "internal");
|
|
3826
|
+
else if (this.position === index) this.#writeScrollState({ position: Math.min(index, this.#items.length - 1) }, "internal");
|
|
3781
3827
|
}
|
|
3782
3828
|
emitListStateChange(this, {
|
|
3783
3829
|
type: "delete-finalize",
|
|
@@ -3788,8 +3834,10 @@ var ListState = class {
|
|
|
3788
3834
|
* Sets the current anchor item and pixel offset.
|
|
3789
3835
|
*/
|
|
3790
3836
|
setAnchor(position, offset = 0) {
|
|
3791
|
-
this
|
|
3792
|
-
|
|
3837
|
+
this.#writeScrollState({
|
|
3838
|
+
position: normalizePosition(position),
|
|
3839
|
+
offset: normalizeOffset$1(offset)
|
|
3840
|
+
}, "external");
|
|
3793
3841
|
}
|
|
3794
3842
|
/**
|
|
3795
3843
|
* Replaces all items and clears scroll state.
|
|
@@ -3799,18 +3847,44 @@ var ListState = class {
|
|
|
3799
3847
|
assertUniqueItemReferences(nextItems);
|
|
3800
3848
|
this.#items = nextItems;
|
|
3801
3849
|
this.#pendingDeletes.clear();
|
|
3802
|
-
this
|
|
3803
|
-
|
|
3850
|
+
this.#writeScrollState({
|
|
3851
|
+
position: void 0,
|
|
3852
|
+
offset: 0
|
|
3853
|
+
}, "internal");
|
|
3804
3854
|
emitListStateChange(this, { type: "reset" });
|
|
3805
3855
|
}
|
|
3806
3856
|
/** Clears the current scroll anchor while keeping the items. */
|
|
3807
3857
|
resetScroll() {
|
|
3808
|
-
this
|
|
3809
|
-
|
|
3858
|
+
this.#writeScrollState({
|
|
3859
|
+
position: void 0,
|
|
3860
|
+
offset: 0
|
|
3861
|
+
}, "external");
|
|
3810
3862
|
}
|
|
3811
3863
|
/** Applies a relative pixel scroll delta. */
|
|
3812
3864
|
applyScroll(delta) {
|
|
3813
|
-
this
|
|
3865
|
+
this.#writeScrollState({ offset: this.#offset + delta }, "external");
|
|
3866
|
+
}
|
|
3867
|
+
[WRITE_LIST_SCROLL_STATE](patch, source) {
|
|
3868
|
+
this.#writeScrollState(patch, source);
|
|
3869
|
+
}
|
|
3870
|
+
#writeScrollState(patch, source) {
|
|
3871
|
+
let changed = false;
|
|
3872
|
+
if ("position" in patch) {
|
|
3873
|
+
const nextPosition = normalizePosition(patch.position);
|
|
3874
|
+
if (!Object.is(this.#position, nextPosition)) {
|
|
3875
|
+
this.#position = nextPosition;
|
|
3876
|
+
changed = true;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
if ("offset" in patch) {
|
|
3880
|
+
const nextOffset = normalizeOffset$1(patch.offset ?? 0);
|
|
3881
|
+
if (!Object.is(this.#offset, nextOffset)) {
|
|
3882
|
+
this.#offset = nextOffset;
|
|
3883
|
+
changed = true;
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
if (!changed) return;
|
|
3887
|
+
markListScrollMutation(this, source);
|
|
3814
3888
|
}
|
|
3815
3889
|
};
|
|
3816
3890
|
//#endregion
|
|
@@ -3875,11 +3949,15 @@ function memoRenderItemBy(keyOf, renderItem, options = {}) {
|
|
|
3875
3949
|
}
|
|
3876
3950
|
//#endregion
|
|
3877
3951
|
//#region src/renderer/virtualized/base-animation.ts
|
|
3952
|
+
const CONTROLLED_STATE_OFFSET_EPSILON = 1e-9;
|
|
3878
3953
|
function clamp$1(value, min, max) {
|
|
3879
3954
|
return Math.min(Math.max(value, min), max);
|
|
3880
3955
|
}
|
|
3881
3956
|
function sameState(state, position, offset) {
|
|
3882
|
-
|
|
3957
|
+
if (!Object.is(state.position, position)) return false;
|
|
3958
|
+
if (Object.is(state.offset, offset)) return true;
|
|
3959
|
+
if (!Number.isFinite(state.offset) || !Number.isFinite(offset)) return false;
|
|
3960
|
+
return Math.abs(state.offset - offset) <= CONTROLLED_STATE_OFFSET_EPSILON;
|
|
3883
3961
|
}
|
|
3884
3962
|
function resolveJumpSegmentIndex(anchor, direction, itemCount) {
|
|
3885
3963
|
if (itemCount <= 0) return;
|
|
@@ -3964,145 +4042,190 @@ function getNow() {
|
|
|
3964
4042
|
//#region src/renderer/virtualized/frame-session.ts
|
|
3965
4043
|
function prepareFrameSession(params) {
|
|
3966
4044
|
let solution = params.resolveVisibleWindow(params.now);
|
|
3967
|
-
|
|
3968
|
-
params.captureVisibleItemSnapshot(solution, viewportTranslateY);
|
|
4045
|
+
params.captureVisibleItemSnapshot(solution);
|
|
3969
4046
|
const requestSettleRedraw = params.pruneTransitionAnimations(solution.window, params.now);
|
|
3970
4047
|
if (requestSettleRedraw) {
|
|
3971
4048
|
solution = params.resolveVisibleWindow(params.now);
|
|
3972
|
-
|
|
3973
|
-
params.captureVisibleItemSnapshot(solution, viewportTranslateY);
|
|
4049
|
+
params.captureVisibleItemSnapshot(solution);
|
|
3974
4050
|
}
|
|
3975
4051
|
return {
|
|
3976
4052
|
solution,
|
|
3977
|
-
viewportTranslateY,
|
|
3978
4053
|
requestSettleRedraw
|
|
3979
4054
|
};
|
|
3980
4055
|
}
|
|
3981
4056
|
//#endregion
|
|
3982
4057
|
//#region src/renderer/virtualized/jump-controller.ts
|
|
3983
|
-
var JumpController = class {
|
|
3984
|
-
|
|
3985
|
-
#
|
|
3986
|
-
#
|
|
4058
|
+
var JumpController = class JumpController {
|
|
4059
|
+
static TRANSITION_SETTLE_SNAP_DURATION = 120;
|
|
4060
|
+
#canAutoFollowTop = false;
|
|
4061
|
+
#canAutoFollowBottom = false;
|
|
4062
|
+
#pendingAutoFollowRecomputeTop = true;
|
|
4063
|
+
#pendingAutoFollowRecomputeBottom = true;
|
|
4064
|
+
#pendingAutoFollowRecomputeReasonTop = "init";
|
|
4065
|
+
#pendingAutoFollowRecomputeReasonBottom = "init";
|
|
4066
|
+
#pendingTransitionSettleReconcile = false;
|
|
4067
|
+
#lastArmedAutoFollowBoundary;
|
|
4068
|
+
#lastObservedRenderedAutoFollowTop = false;
|
|
4069
|
+
#lastObservedRenderedAutoFollowBottom = false;
|
|
4070
|
+
#lastViewportWidth;
|
|
4071
|
+
#lastHandledScrollMutationVersion;
|
|
3987
4072
|
#jumpAnimation;
|
|
3988
|
-
#
|
|
3989
|
-
#
|
|
3990
|
-
#pendingBoundaryJumpTop = false;
|
|
3991
|
-
#pendingBoundaryJumpBottom = false;
|
|
4073
|
+
#pendingPostJumpBoundary;
|
|
4074
|
+
#pendingPostJumpBoundaryBlocked = false;
|
|
3992
4075
|
#options;
|
|
3993
4076
|
constructor(options) {
|
|
3994
4077
|
this.#options = options;
|
|
4078
|
+
this.#lastHandledScrollMutationVersion = this.#options.readScrollMutation().version;
|
|
3995
4079
|
}
|
|
3996
4080
|
beforeFrame() {
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4081
|
+
this.#handlePendingExternalScrollMutation();
|
|
4082
|
+
}
|
|
4083
|
+
noteViewportWidth(width) {
|
|
4084
|
+
if (!Number.isFinite(width)) return;
|
|
4085
|
+
if (this.#lastViewportWidth == null) {
|
|
4086
|
+
this.#lastViewportWidth = width;
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
if (Object.is(this.#lastViewportWidth, width)) return;
|
|
4090
|
+
this.#lastViewportWidth = width;
|
|
4091
|
+
this.#clearPendingPostJumpBoundary();
|
|
4092
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4093
|
+
this.#markAutoFollowRecompute(void 0, "viewport-width-change");
|
|
4000
4094
|
}
|
|
4001
4095
|
prepare(now) {
|
|
4096
|
+
if (this.#handlePendingExternalScrollMutation()) return false;
|
|
4002
4097
|
const animation = this.#jumpAnimation;
|
|
4003
4098
|
if (animation == null) return false;
|
|
4004
4099
|
if (this.#options.getItemCount() === 0) {
|
|
4005
4100
|
this.#cancelJumpAnimation();
|
|
4006
4101
|
return false;
|
|
4007
4102
|
}
|
|
4008
|
-
if (this.#controlledState != null && !sameState(this.#controlledState, this.#options.readListState().position, this.#options.readListState().offset)) {
|
|
4009
|
-
this.#clearPendingBoundaryJumps();
|
|
4010
|
-
this.#cancelJumpAnimation();
|
|
4011
|
-
return false;
|
|
4012
|
-
}
|
|
4013
4103
|
const progress = getProgress(animation.startTime, animation.duration, now);
|
|
4014
4104
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
4015
4105
|
const anchor = getAnchorAtDistance(animation.path, animation.path.totalDistance * eased);
|
|
4016
4106
|
this.#options.applyAnchor(anchor);
|
|
4017
4107
|
animation.needsMoreFrames = progress < 1;
|
|
4108
|
+
if (!animation.needsMoreFrames && this.#pendingPostJumpBoundary != null && !this.#pendingPostJumpBoundaryBlocked) this.#armAutoFollowBoundary(this.#pendingPostJumpBoundary, "jump-to-boundary-settle");
|
|
4018
4109
|
return animation.needsMoreFrames;
|
|
4019
4110
|
}
|
|
4020
4111
|
finishFrame(requestRedraw) {
|
|
4021
4112
|
const animation = this.#jumpAnimation;
|
|
4022
4113
|
if (animation == null) return requestRedraw;
|
|
4023
|
-
if (animation.needsMoreFrames)
|
|
4024
|
-
|
|
4025
|
-
return true;
|
|
4026
|
-
}
|
|
4114
|
+
if (animation.needsMoreFrames) return true;
|
|
4115
|
+
const boundary = this.#pendingPostJumpBoundaryBlocked === true ? void 0 : this.#pendingPostJumpBoundary;
|
|
4027
4116
|
const onComplete = animation.onComplete;
|
|
4028
4117
|
this.#cancelJumpAnimation();
|
|
4118
|
+
this.#clearPendingPostJumpBoundary();
|
|
4119
|
+
if (boundary != null) this.#armAutoFollowBoundary(boundary, "jump-to-boundary-settle");
|
|
4029
4120
|
onComplete?.();
|
|
4030
4121
|
return requestRedraw || this.#jumpAnimation != null;
|
|
4031
4122
|
}
|
|
4032
4123
|
commit(state) {
|
|
4033
|
-
this.#
|
|
4034
|
-
position: state.position,
|
|
4035
|
-
offset: state.offset
|
|
4036
|
-
};
|
|
4124
|
+
this.#lastHandledScrollMutationVersion = this.#options.readScrollMutation().version;
|
|
4037
4125
|
}
|
|
4038
4126
|
jumpTo(index, options = {}) {
|
|
4039
|
-
this.#
|
|
4127
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4128
|
+
this.#clearPendingPostJumpBoundary();
|
|
4040
4129
|
if (this.#options.getItemCount() === 0) {
|
|
4041
4130
|
this.#cancelJumpAnimation();
|
|
4042
4131
|
return;
|
|
4043
4132
|
}
|
|
4044
|
-
this.#startJumpToIndex(index, options
|
|
4133
|
+
this.#startJumpToIndex(index, options);
|
|
4045
4134
|
}
|
|
4046
4135
|
jumpToBoundary(boundary, options = {}) {
|
|
4047
|
-
this.#
|
|
4136
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4137
|
+
this.#clearPendingPostJumpBoundary();
|
|
4138
|
+
this.#armAutoFollowBoundary(boundary, "jump-to-boundary");
|
|
4048
4139
|
if (this.#options.getItemCount() === 0) {
|
|
4049
4140
|
this.#cancelJumpAnimation();
|
|
4050
4141
|
return;
|
|
4051
4142
|
}
|
|
4052
|
-
this.#armBoundaryJump(boundary);
|
|
4053
4143
|
this.#startJumpToIndex(boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
|
|
4054
4144
|
...options,
|
|
4055
4145
|
block: boundary === "bottom" ? "end" : "start"
|
|
4056
|
-
}, {
|
|
4057
|
-
kind: "boundary-jump",
|
|
4058
|
-
boundary
|
|
4059
4146
|
});
|
|
4060
4147
|
}
|
|
4061
|
-
|
|
4062
|
-
this.#
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4148
|
+
recomputeAutoFollowCapabilities(capabilities) {
|
|
4149
|
+
const previouslyObservedDualBoundary = this.#lastObservedRenderedAutoFollowTop && this.#lastObservedRenderedAutoFollowBottom;
|
|
4150
|
+
if (capabilities.top && capabilities.bottom && !previouslyObservedDualBoundary) {
|
|
4151
|
+
this.#setAutoFollowBoundary("top", true, "dual-boundary-promotion");
|
|
4152
|
+
this.#setAutoFollowBoundary("bottom", true, "dual-boundary-promotion");
|
|
4153
|
+
}
|
|
4154
|
+
if (this.#pendingAutoFollowRecomputeTop) {
|
|
4155
|
+
this.#setAutoFollowBoundary("top", capabilities.top, `strict-recompute:${this.#pendingAutoFollowRecomputeReasonTop}`);
|
|
4156
|
+
this.#pendingAutoFollowRecomputeTop = false;
|
|
4157
|
+
}
|
|
4158
|
+
if (this.#pendingAutoFollowRecomputeBottom) {
|
|
4159
|
+
this.#setAutoFollowBoundary("bottom", capabilities.bottom, `strict-recompute:${this.#pendingAutoFollowRecomputeReasonBottom}`);
|
|
4160
|
+
this.#pendingAutoFollowRecomputeBottom = false;
|
|
4161
|
+
}
|
|
4162
|
+
this.#syncLastArmedBoundaryFromLatchedState();
|
|
4163
|
+
if (this.#pendingTransitionSettleReconcile) {
|
|
4164
|
+
this.#reconcileLatchedAutoFollowAfterTransitionSettle(capabilities);
|
|
4165
|
+
this.#pendingTransitionSettleReconcile = false;
|
|
4166
|
+
}
|
|
4167
|
+
this.#lastObservedRenderedAutoFollowTop = capabilities.top;
|
|
4168
|
+
this.#lastObservedRenderedAutoFollowBottom = capabilities.bottom;
|
|
4169
|
+
return this.getAutoFollowCapabilities();
|
|
4066
4170
|
}
|
|
4067
|
-
|
|
4171
|
+
getAutoFollowCapabilities() {
|
|
4068
4172
|
return {
|
|
4069
|
-
top: this.#
|
|
4070
|
-
bottom: this.#
|
|
4173
|
+
top: this.#canAutoFollowTop,
|
|
4174
|
+
bottom: this.#canAutoFollowBottom
|
|
4071
4175
|
};
|
|
4072
4176
|
}
|
|
4177
|
+
reconcileAutoFollowAfterTransitionSettle() {
|
|
4178
|
+
this.#pendingTransitionSettleReconcile = true;
|
|
4179
|
+
}
|
|
4073
4180
|
handleListStateChange(change) {
|
|
4074
|
-
|
|
4181
|
+
switch (change.type) {
|
|
4182
|
+
case "reset":
|
|
4183
|
+
case "set":
|
|
4184
|
+
this.#cancelJumpAnimation();
|
|
4185
|
+
this.#clearPendingPostJumpBoundary();
|
|
4186
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4187
|
+
this.#syncScrollMutationVersion();
|
|
4188
|
+
this.#markAutoFollowRecompute(void 0, change.type);
|
|
4189
|
+
return change;
|
|
4190
|
+
case "push":
|
|
4191
|
+
case "unshift": return this.#handleBoundaryInsert(change);
|
|
4192
|
+
default: return change;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
#handleBoundaryInsert(change) {
|
|
4196
|
+
if (this.#handlePendingExternalScrollMutation()) return change;
|
|
4197
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4075
4198
|
const followChange = this.#resolveAutoFollowChange(change);
|
|
4076
|
-
const
|
|
4077
|
-
|
|
4078
|
-
if (followChange
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
}, {
|
|
4084
|
-
kind: "auto-follow",
|
|
4085
|
-
boundary: followChange.boundary
|
|
4086
|
-
});
|
|
4087
|
-
return {
|
|
4088
|
-
...followChange.change,
|
|
4089
|
-
animation: void 0
|
|
4090
|
-
};
|
|
4199
|
+
const boundary = change.type === "push" ? "bottom" : "top";
|
|
4200
|
+
if (this.#pendingPostJumpBoundary === boundary) this.#pendingPostJumpBoundaryBlocked = true;
|
|
4201
|
+
if (followChange == null || !this.#hasAutoFollowCapability(followChange.boundary)) return change;
|
|
4202
|
+
if (this.#canAutoFollowTop && this.#canAutoFollowBottom && this.#lastObservedRenderedAutoFollowTop && this.#lastObservedRenderedAutoFollowBottom) {
|
|
4203
|
+
const otherBoundary = followChange.boundary === "top" ? "bottom" : "top";
|
|
4204
|
+
this.#setAutoFollowBoundary(otherBoundary, false, "boundary-insert-narrow");
|
|
4205
|
+
this.#lastArmedAutoFollowBoundary = followChange.boundary;
|
|
4091
4206
|
}
|
|
4207
|
+
this.#clearPendingPostJumpBoundary();
|
|
4208
|
+
this.#materializeAnimatedAnchor(getNow(), followChange.direction, followChange.count);
|
|
4209
|
+
this.#startJumpToIndex(followChange.boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
|
|
4210
|
+
block: followChange.boundary === "bottom" ? "end" : "start",
|
|
4211
|
+
duration: followChange.animation?.duration
|
|
4212
|
+
});
|
|
4092
4213
|
return change;
|
|
4093
4214
|
}
|
|
4094
4215
|
#cancelJumpAnimation() {
|
|
4095
4216
|
this.#jumpAnimation = void 0;
|
|
4096
|
-
this.#controlledState = void 0;
|
|
4097
4217
|
}
|
|
4098
|
-
#startJumpToIndex(index, options
|
|
4218
|
+
#startJumpToIndex(index, options) {
|
|
4099
4219
|
const targetIndex = this.#options.clampItemIndex(index);
|
|
4100
|
-
const currentState = this.#options.normalizeListState(this.#options.readListState());
|
|
4101
4220
|
const targetBlock = options.block ?? this.#options.getDefaultJumpBlock();
|
|
4221
|
+
const settleBoundary = this.#resolveBoundaryLatchTarget(targetIndex, targetBlock);
|
|
4222
|
+
this.#materializeAnimatedAnchor(getNow());
|
|
4223
|
+
const currentState = this.#options.normalizeListState(this.#options.readListState());
|
|
4102
4224
|
const targetAnchor = this.#options.getTargetAnchor(targetIndex, targetBlock);
|
|
4103
4225
|
if (!(options.animated ?? true)) {
|
|
4104
4226
|
this.#cancelJumpAnimation();
|
|
4105
4227
|
this.#options.applyAnchor(targetAnchor);
|
|
4228
|
+
if (settleBoundary != null) this.#armAutoFollowBoundary(settleBoundary, "jump-to-boundary-instant");
|
|
4106
4229
|
options.onComplete?.();
|
|
4107
4230
|
return;
|
|
4108
4231
|
}
|
|
@@ -4110,6 +4233,7 @@ var JumpController = class {
|
|
|
4110
4233
|
if (!Number.isFinite(startAnchor)) {
|
|
4111
4234
|
this.#cancelJumpAnimation();
|
|
4112
4235
|
this.#options.applyAnchor(targetAnchor);
|
|
4236
|
+
if (settleBoundary != null) this.#armAutoFollowBoundary(settleBoundary, "jump-to-boundary-instant");
|
|
4113
4237
|
options.onComplete?.();
|
|
4114
4238
|
return;
|
|
4115
4239
|
}
|
|
@@ -4118,18 +4242,27 @@ var JumpController = class {
|
|
|
4118
4242
|
if (duration <= 0 || path.totalDistance <= Number.EPSILON) {
|
|
4119
4243
|
this.#cancelJumpAnimation();
|
|
4120
4244
|
this.#options.applyAnchor(targetAnchor);
|
|
4245
|
+
if (settleBoundary != null) this.#armAutoFollowBoundary(settleBoundary, "jump-to-boundary-instant");
|
|
4121
4246
|
options.onComplete?.();
|
|
4122
4247
|
return;
|
|
4123
4248
|
}
|
|
4249
|
+
if (settleBoundary != null) {
|
|
4250
|
+
this.#pendingPostJumpBoundary = settleBoundary;
|
|
4251
|
+
this.#pendingPostJumpBoundaryBlocked = false;
|
|
4252
|
+
}
|
|
4124
4253
|
this.#jumpAnimation = {
|
|
4125
4254
|
path,
|
|
4126
4255
|
startTime: getNow(),
|
|
4127
4256
|
duration,
|
|
4128
4257
|
needsMoreFrames: true,
|
|
4129
|
-
onComplete: options.onComplete
|
|
4130
|
-
source
|
|
4258
|
+
onComplete: options.onComplete
|
|
4131
4259
|
};
|
|
4132
|
-
|
|
4260
|
+
}
|
|
4261
|
+
#resolveBoundaryLatchTarget(index, block) {
|
|
4262
|
+
const itemCount = this.#options.getItemCount();
|
|
4263
|
+
if (itemCount <= 0) return;
|
|
4264
|
+
if (index === 0 && block === "start") return "top";
|
|
4265
|
+
if (index === itemCount - 1 && block === "end") return "bottom";
|
|
4133
4266
|
}
|
|
4134
4267
|
#resolveAutoFollowChange(change) {
|
|
4135
4268
|
switch (change.type) {
|
|
@@ -4144,45 +4277,95 @@ var JumpController = class {
|
|
|
4144
4277
|
default: return;
|
|
4145
4278
|
}
|
|
4146
4279
|
}
|
|
4147
|
-
#
|
|
4148
|
-
return this.#
|
|
4280
|
+
#hasAutoFollowCapability(boundary) {
|
|
4281
|
+
return boundary === "top" ? this.#canAutoFollowTop : this.#canAutoFollowBottom;
|
|
4149
4282
|
}
|
|
4150
|
-
#
|
|
4151
|
-
|
|
4283
|
+
#armAutoFollowBoundary(boundary, reason) {
|
|
4284
|
+
this.#setAutoFollowBoundary(boundary, true, reason);
|
|
4285
|
+
this.#lastArmedAutoFollowBoundary = boundary;
|
|
4286
|
+
if (boundary === "top") {
|
|
4287
|
+
this.#pendingAutoFollowRecomputeTop = false;
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
this.#pendingAutoFollowRecomputeBottom = false;
|
|
4152
4291
|
}
|
|
4153
|
-
#
|
|
4292
|
+
#markAutoFollowRecompute(boundary, reason) {
|
|
4293
|
+
if (boundary == null || boundary === "top") {
|
|
4294
|
+
this.#pendingAutoFollowRecomputeTop = true;
|
|
4295
|
+
this.#pendingAutoFollowRecomputeReasonTop = reason;
|
|
4296
|
+
}
|
|
4297
|
+
if (boundary == null || boundary === "bottom") {
|
|
4298
|
+
this.#pendingAutoFollowRecomputeBottom = true;
|
|
4299
|
+
this.#pendingAutoFollowRecomputeReasonBottom = reason;
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
#clearPendingPostJumpBoundary() {
|
|
4303
|
+
this.#pendingPostJumpBoundary = void 0;
|
|
4304
|
+
this.#pendingPostJumpBoundaryBlocked = false;
|
|
4305
|
+
}
|
|
4306
|
+
#clearPendingTransitionSettleReconcile() {
|
|
4307
|
+
this.#pendingTransitionSettleReconcile = false;
|
|
4308
|
+
}
|
|
4309
|
+
#materializeAnimatedAnchor(now, direction, count = 0) {
|
|
4154
4310
|
const animation = this.#jumpAnimation;
|
|
4155
4311
|
if (animation == null) return;
|
|
4156
4312
|
const progress = getProgress(animation.startTime, animation.duration, now);
|
|
4157
4313
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
4158
|
-
|
|
4314
|
+
let anchor = getAnchorAtDistance(animation.path, animation.path.totalDistance * eased);
|
|
4315
|
+
if (direction === "unshift") anchor += count;
|
|
4159
4316
|
this.#cancelJumpAnimation();
|
|
4160
|
-
this.#options.applyAnchor(
|
|
4317
|
+
this.#options.applyAnchor(anchor);
|
|
4161
4318
|
}
|
|
4162
|
-
#
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4319
|
+
#setAutoFollowBoundary(boundary, value, reason) {
|
|
4320
|
+
if (boundary === "top") this.#canAutoFollowTop = value;
|
|
4321
|
+
else this.#canAutoFollowBottom = value;
|
|
4322
|
+
}
|
|
4323
|
+
#syncLastArmedBoundaryFromLatchedState() {
|
|
4324
|
+
if (this.#canAutoFollowTop === this.#canAutoFollowBottom) return;
|
|
4325
|
+
this.#lastArmedAutoFollowBoundary = this.#canAutoFollowTop ? "top" : "bottom";
|
|
4326
|
+
}
|
|
4327
|
+
#reconcileLatchedAutoFollowAfterTransitionSettle(capabilities) {
|
|
4328
|
+
if (!this.#canAutoFollowTop && !this.#canAutoFollowBottom) return;
|
|
4329
|
+
const preferredBoundary = this.#resolvePreferredLatchedBoundary(capabilities);
|
|
4330
|
+
if (preferredBoundary == null) return;
|
|
4331
|
+
const otherBoundary = preferredBoundary === "top" ? "bottom" : "top";
|
|
4332
|
+
if (this.#hasAutoFollowCapability(otherBoundary)) this.#setAutoFollowBoundary(otherBoundary, false, "strict-recompute:set");
|
|
4333
|
+
if (!this.#readCapabilityForBoundary(capabilities, preferredBoundary)) this.#startTransitionSettleSnap(preferredBoundary);
|
|
4334
|
+
this.#syncLastArmedBoundaryFromLatchedState();
|
|
4335
|
+
}
|
|
4336
|
+
#resolvePreferredLatchedBoundary(capabilities) {
|
|
4337
|
+
if (this.#canAutoFollowTop && this.#canAutoFollowBottom) {
|
|
4338
|
+
if (capabilities.top && capabilities.bottom) return;
|
|
4339
|
+
if (this.#lastArmedAutoFollowBoundary != null) return this.#lastArmedAutoFollowBoundary;
|
|
4340
|
+
if (capabilities.top !== capabilities.bottom) return capabilities.top ? "top" : "bottom";
|
|
4341
|
+
return "bottom";
|
|
4342
|
+
}
|
|
4343
|
+
if (this.#canAutoFollowTop) return "top";
|
|
4344
|
+
if (this.#canAutoFollowBottom) return "bottom";
|
|
4169
4345
|
}
|
|
4170
|
-
#
|
|
4171
|
-
|
|
4172
|
-
return boundary === "top" ? this.#confirmedAutoFollowTop || this.#pendingBoundaryJumpTop || animationBoundary === "top" : this.#confirmedAutoFollowBottom || this.#pendingBoundaryJumpBottom || animationBoundary === "bottom";
|
|
4346
|
+
#readCapabilityForBoundary(capabilities, boundary) {
|
|
4347
|
+
return boundary === "top" ? capabilities.top : capabilities.bottom;
|
|
4173
4348
|
}
|
|
4174
|
-
#
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4349
|
+
#startTransitionSettleSnap(boundary) {
|
|
4350
|
+
if (this.#options.getItemCount() <= 0) return;
|
|
4351
|
+
this.#startJumpToIndex(boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
|
|
4352
|
+
block: boundary === "bottom" ? "end" : "start",
|
|
4353
|
+
duration: JumpController.TRANSITION_SETTLE_SNAP_DURATION
|
|
4354
|
+
});
|
|
4178
4355
|
}
|
|
4179
|
-
#
|
|
4180
|
-
this.#
|
|
4181
|
-
this.#pendingBoundaryJumpBottom = boundary === "bottom";
|
|
4356
|
+
#syncScrollMutationVersion() {
|
|
4357
|
+
this.#lastHandledScrollMutationVersion = this.#options.readScrollMutation().version;
|
|
4182
4358
|
}
|
|
4183
|
-
#
|
|
4184
|
-
|
|
4185
|
-
this.#
|
|
4359
|
+
#handlePendingExternalScrollMutation() {
|
|
4360
|
+
const mutation = this.#options.readScrollMutation();
|
|
4361
|
+
if (mutation.version === this.#lastHandledScrollMutationVersion) return false;
|
|
4362
|
+
this.#lastHandledScrollMutationVersion = mutation.version;
|
|
4363
|
+
if (mutation.source !== "external") return false;
|
|
4364
|
+
this.#cancelJumpAnimation();
|
|
4365
|
+
this.#clearPendingPostJumpBoundary();
|
|
4366
|
+
this.#clearPendingTransitionSettleReconcile();
|
|
4367
|
+
this.#markAutoFollowRecompute(void 0, "manual-scroll");
|
|
4368
|
+
return true;
|
|
4186
4369
|
}
|
|
4187
4370
|
};
|
|
4188
4371
|
//#endregion
|
|
@@ -4196,11 +4379,8 @@ var VisibilitySnapshot = class {
|
|
|
4196
4379
|
#previousSnapshotState;
|
|
4197
4380
|
#emptyState;
|
|
4198
4381
|
#coversShortList = false;
|
|
4199
|
-
#topGap = 0;
|
|
4200
|
-
#bottomGap = 0;
|
|
4201
4382
|
#atStartBoundary = false;
|
|
4202
4383
|
#atEndBoundary = false;
|
|
4203
|
-
#currentExtraShift = 0;
|
|
4204
4384
|
#minDrawnIndex = Number.POSITIVE_INFINITY;
|
|
4205
4385
|
#maxDrawnIndex = Number.NEGATIVE_INFINITY;
|
|
4206
4386
|
#topBoundaryItem;
|
|
@@ -4208,18 +4388,12 @@ var VisibilitySnapshot = class {
|
|
|
4208
4388
|
get coversShortList() {
|
|
4209
4389
|
return this.#hasSnapshot && this.#snapshotState != null && this.#coversShortList;
|
|
4210
4390
|
}
|
|
4211
|
-
get
|
|
4212
|
-
return this.#
|
|
4213
|
-
}
|
|
4214
|
-
get bottomGap() {
|
|
4215
|
-
return this.#bottomGap;
|
|
4391
|
+
get hasSnapshot() {
|
|
4392
|
+
return this.#hasSnapshot;
|
|
4216
4393
|
}
|
|
4217
4394
|
get previousState() {
|
|
4218
4395
|
return this.#previousSnapshotState;
|
|
4219
4396
|
}
|
|
4220
|
-
get currentExtraShift() {
|
|
4221
|
-
return this.#currentExtraShift;
|
|
4222
|
-
}
|
|
4223
4397
|
readDrawnIndexRange() {
|
|
4224
4398
|
if (!Number.isFinite(this.#minDrawnIndex) || !Number.isFinite(this.#maxDrawnIndex)) return;
|
|
4225
4399
|
return {
|
|
@@ -4230,7 +4404,7 @@ var VisibilitySnapshot = class {
|
|
|
4230
4404
|
readBoundaryItem(boundary) {
|
|
4231
4405
|
return boundary === "top" ? this.#topBoundaryItem : this.#bottomBoundaryItem;
|
|
4232
4406
|
}
|
|
4233
|
-
capture(window, _resolutionPath, items,
|
|
4407
|
+
capture(window, _resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
|
|
4234
4408
|
this.#previousVisibleItems = this.#visibleItems;
|
|
4235
4409
|
this.#previousSnapshotState = this.#snapshotState;
|
|
4236
4410
|
const nextDrawnItems = /* @__PURE__ */ new Set();
|
|
@@ -4245,18 +4419,23 @@ var VisibilitySnapshot = class {
|
|
|
4245
4419
|
let nextBottomBoundaryItem;
|
|
4246
4420
|
let nextTopBoundaryY = Number.POSITIVE_INFINITY;
|
|
4247
4421
|
let nextBottomBoundaryY = Number.NEGATIVE_INFINITY;
|
|
4248
|
-
const effectiveShift = window.shift
|
|
4422
|
+
const effectiveShift = window.shift;
|
|
4423
|
+
const contentOriginY = viewport.contentTop;
|
|
4249
4424
|
for (const { idx, offset, height } of window.drawList) {
|
|
4250
|
-
|
|
4251
|
-
maxVisibleIndex = Math.max(maxVisibleIndex, idx);
|
|
4252
|
-
nextMinDrawnIndex = Math.min(nextMinDrawnIndex, idx);
|
|
4253
|
-
nextMaxDrawnIndex = Math.max(nextMaxDrawnIndex, idx);
|
|
4254
|
-
const y = offset + effectiveShift;
|
|
4425
|
+
const y = offset + effectiveShift + contentOriginY;
|
|
4255
4426
|
topMostY = Math.min(topMostY, y);
|
|
4256
4427
|
bottomMostY = Math.max(bottomMostY, y + height);
|
|
4257
4428
|
const item = items[idx];
|
|
4258
|
-
if (item != null) {
|
|
4429
|
+
if (item != null && readOuterVisibleRange(y, height) != null) {
|
|
4259
4430
|
nextDrawnItems.add(item);
|
|
4431
|
+
nextMinDrawnIndex = Math.min(nextMinDrawnIndex, idx);
|
|
4432
|
+
nextMaxDrawnIndex = Math.max(nextMaxDrawnIndex, idx);
|
|
4433
|
+
}
|
|
4434
|
+
if (item == null) continue;
|
|
4435
|
+
if (readVisibleRange(y, height) != null) {
|
|
4436
|
+
minVisibleIndex = Math.min(minVisibleIndex, idx);
|
|
4437
|
+
maxVisibleIndex = Math.max(maxVisibleIndex, idx);
|
|
4438
|
+
nextVisibleItems.add(item);
|
|
4260
4439
|
if (y < nextTopBoundaryY) {
|
|
4261
4440
|
nextTopBoundaryY = y;
|
|
4262
4441
|
nextTopBoundaryItem = item;
|
|
@@ -4266,25 +4445,20 @@ var VisibilitySnapshot = class {
|
|
|
4266
4445
|
nextBottomBoundaryItem = item;
|
|
4267
4446
|
}
|
|
4268
4447
|
}
|
|
4269
|
-
if (item == null || readVisibleRange(y, height) == null) continue;
|
|
4270
|
-
nextVisibleItems.add(item);
|
|
4271
4448
|
}
|
|
4272
4449
|
this.#drawnItems = nextDrawnItems;
|
|
4273
4450
|
this.#visibleItems = nextVisibleItems;
|
|
4274
4451
|
this.#hasSnapshot = true;
|
|
4275
4452
|
this.#snapshotState = snapshotState;
|
|
4276
|
-
this.#currentExtraShift = extraShift;
|
|
4277
4453
|
this.#minDrawnIndex = nextMinDrawnIndex;
|
|
4278
4454
|
this.#maxDrawnIndex = nextMaxDrawnIndex;
|
|
4279
4455
|
this.#topBoundaryItem = nextTopBoundaryItem;
|
|
4280
4456
|
this.#bottomBoundaryItem = nextBottomBoundaryItem;
|
|
4281
4457
|
this.#emptyState = items.length === 0 && window.drawList.length === 0 ? snapshotState : void 0;
|
|
4282
4458
|
const contentHeight = bottomMostY - topMostY;
|
|
4283
|
-
this.#coversShortList = window.drawList.length > 0 && items.length > 0 && window.drawList.length === items.length && minVisibleIndex === 0 && maxVisibleIndex === items.length - 1 && topMostY >=
|
|
4284
|
-
this.#
|
|
4285
|
-
this.#
|
|
4286
|
-
this.#atStartBoundary = window.drawList.length > 0 && items.length > 0 && minVisibleIndex === 0 && topMostY >= -Number.EPSILON;
|
|
4287
|
-
this.#atEndBoundary = window.drawList.length > 0 && items.length > 0 && maxVisibleIndex === items.length - 1 && bottomMostY <= viewportHeight + Number.EPSILON;
|
|
4459
|
+
this.#coversShortList = window.drawList.length > 0 && items.length > 0 && window.drawList.length === items.length && minVisibleIndex === 0 && maxVisibleIndex === items.length - 1 && topMostY >= viewport.contentTop - 1e-6 && bottomMostY <= viewport.contentBottom + 1e-6 && contentHeight < viewport.contentHeight - 1e-6;
|
|
4460
|
+
this.#atStartBoundary = window.drawList.length > 0 && items.length > 0 && minVisibleIndex === 0 && topMostY >= viewport.contentTop - 1e-6;
|
|
4461
|
+
this.#atEndBoundary = window.drawList.length > 0 && items.length > 0 && maxVisibleIndex === items.length - 1 && bottomMostY <= viewport.contentBottom + 1e-6;
|
|
4288
4462
|
}
|
|
4289
4463
|
matchesCurrentState(position, offset) {
|
|
4290
4464
|
return this.#hasSnapshot && this.#snapshotState != null && sameState(this.#snapshotState, position, offset);
|
|
@@ -4324,11 +4498,8 @@ var VisibilitySnapshot = class {
|
|
|
4324
4498
|
this.#previousSnapshotState = void 0;
|
|
4325
4499
|
this.#emptyState = void 0;
|
|
4326
4500
|
this.#coversShortList = false;
|
|
4327
|
-
this.#topGap = 0;
|
|
4328
|
-
this.#bottomGap = 0;
|
|
4329
4501
|
this.#atStartBoundary = false;
|
|
4330
4502
|
this.#atEndBoundary = false;
|
|
4331
|
-
this.#currentExtraShift = 0;
|
|
4332
4503
|
this.#minDrawnIndex = Number.POSITIVE_INFINITY;
|
|
4333
4504
|
this.#maxDrawnIndex = Number.NEGATIVE_INFINITY;
|
|
4334
4505
|
this.#topBoundaryItem = void 0;
|
|
@@ -4381,7 +4552,7 @@ var TransitionStore = class {
|
|
|
4381
4552
|
}));
|
|
4382
4553
|
}
|
|
4383
4554
|
findInvisible(snapshot) {
|
|
4384
|
-
return [...this.#transitions.entries()].filter(([item, transition]) => !snapshot.tracks(item, transition.retention)).map(([item, transition]) => ({
|
|
4555
|
+
return [...this.#transitions.entries()].filter(([item, transition]) => !snapshot.tracks(item, transition.retention) && !(transition.kind === "insert" && !snapshot.wasVisible(item))).map(([item, transition]) => ({
|
|
4385
4556
|
item,
|
|
4386
4557
|
transition
|
|
4387
4558
|
}));
|
|
@@ -4395,9 +4566,6 @@ var TransitionStore = class {
|
|
|
4395
4566
|
};
|
|
4396
4567
|
//#endregion
|
|
4397
4568
|
//#region src/renderer/virtualized/transition-planner.ts
|
|
4398
|
-
function isFinitePositive(value) {
|
|
4399
|
-
return Number.isFinite(value) && value > 0;
|
|
4400
|
-
}
|
|
4401
4569
|
function normalizeDuration(duration) {
|
|
4402
4570
|
return Math.max(0, typeof duration === "number" && Number.isFinite(duration) ? duration : 0);
|
|
4403
4571
|
}
|
|
@@ -4429,13 +4597,16 @@ function isIndexVisible(index, resolveVisibleWindow, readVisibleRange) {
|
|
|
4429
4597
|
}
|
|
4430
4598
|
function resolveAnimationEligibility(params) {
|
|
4431
4599
|
if (params.index < 0) return false;
|
|
4432
|
-
if (params.snapshot.matchesCurrentState(params.position, params.offset)) return params.snapshot.
|
|
4433
|
-
return isIndexVisible(params.index, params.resolveVisibleWindow, params.
|
|
4600
|
+
if (params.snapshot.matchesCurrentState(params.position, params.offset)) return params.snapshot.tracks(params.item, "drawn");
|
|
4601
|
+
return isIndexVisible(params.index, params.resolveVisibleWindow, params.readOuterVisibleRange);
|
|
4434
4602
|
}
|
|
4435
|
-
function
|
|
4436
|
-
if (
|
|
4437
|
-
|
|
4438
|
-
|
|
4603
|
+
function hasVisibleBoundaryInsertItems(direction, count, ctx) {
|
|
4604
|
+
if (count <= 0) return false;
|
|
4605
|
+
const start = direction === "push" ? ctx.items.length - count : 0;
|
|
4606
|
+
const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
|
|
4607
|
+
if (start < 0 || end <= start) return false;
|
|
4608
|
+
const solution = ctx.resolveVisibleWindow();
|
|
4609
|
+
return solution.window.drawList.some((entry) => entry.idx >= start && entry.idx < end && ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null);
|
|
4439
4610
|
}
|
|
4440
4611
|
function sampleScalarAnimation(animation, now) {
|
|
4441
4612
|
return interpolate(animation.from, animation.to, animation.startTime, animation.duration, now);
|
|
@@ -4468,68 +4639,31 @@ function planExistingItemTransition(params) {
|
|
|
4468
4639
|
kind: "update",
|
|
4469
4640
|
layers,
|
|
4470
4641
|
height: createScalarAnimation(params.currentVisualState.height, params.nextHeight, params.now, params.duration),
|
|
4471
|
-
retention: "
|
|
4642
|
+
retention: "drawn"
|
|
4472
4643
|
};
|
|
4473
4644
|
}
|
|
4474
4645
|
return {
|
|
4475
4646
|
kind: "delete",
|
|
4476
4647
|
layers,
|
|
4477
4648
|
height: createScalarAnimation(params.currentVisualState.height, 0, params.now, params.duration),
|
|
4478
|
-
retention: "
|
|
4649
|
+
retention: "drawn"
|
|
4479
4650
|
};
|
|
4480
4651
|
}
|
|
4481
|
-
function planViewportShift(params) {
|
|
4482
|
-
if (!isFinitePositive(params.travel) || params.duration <= 0) return;
|
|
4483
|
-
return createScalarAnimation(params.direction === "positive" ? params.currentTranslateY + params.travel : params.currentTranslateY - params.travel, 0, params.now, params.duration);
|
|
4484
|
-
}
|
|
4485
|
-
function planBoundaryInsert(params) {
|
|
4486
|
-
switch (params.strategy) {
|
|
4487
|
-
case "hard-cut": return;
|
|
4488
|
-
case "item-enter": return planBoundaryInsertItems(params);
|
|
4489
|
-
case "viewport-slide": return planBoundaryInsertViewportShift(params);
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
4652
|
function planBoundaryInsertItems(params) {
|
|
4493
4653
|
const entries = [];
|
|
4494
|
-
const signedDistance = params.direction === "push" ? 1 : -1;
|
|
4495
4654
|
for (const { item, node, height } of params.measuredItems) {
|
|
4496
4655
|
if (!Number.isFinite(height) || height < 0) return;
|
|
4497
|
-
const resolvedDistance = typeof params.distance === "number" && Number.isFinite(params.distance) ? Math.max(0, params.distance) : Math.min(24, height);
|
|
4498
4656
|
entries.push({
|
|
4499
4657
|
item,
|
|
4500
4658
|
transition: {
|
|
4501
4659
|
kind: "insert",
|
|
4502
|
-
layers: [createLayerAnimation(node, 0, 1, params.now, params.duration,
|
|
4503
|
-
height: createScalarAnimation(height, height, params.now, params.duration),
|
|
4660
|
+
layers: [createLayerAnimation(node, 0, 1, params.now, params.duration, 0, 0)],
|
|
4661
|
+
height: createScalarAnimation(params.animateHeight ? 0 : height, height, params.now, params.duration),
|
|
4504
4662
|
retention: "drawn"
|
|
4505
4663
|
}
|
|
4506
4664
|
});
|
|
4507
4665
|
}
|
|
4508
|
-
return entries.length === 0 ? void 0 : {
|
|
4509
|
-
kind: "item-enter",
|
|
4510
|
-
entries
|
|
4511
|
-
};
|
|
4512
|
-
}
|
|
4513
|
-
function planBoundaryInsertViewportShift(params) {
|
|
4514
|
-
let insertedHeight = 0;
|
|
4515
|
-
for (const { height } of params.measuredItems) {
|
|
4516
|
-
if (!Number.isFinite(height) || height <= 0) return;
|
|
4517
|
-
insertedHeight += height;
|
|
4518
|
-
}
|
|
4519
|
-
if (!isFinitePositive(insertedHeight)) return;
|
|
4520
|
-
const gap = params.direction === "push" ? params.snapshot.topGap : params.snapshot.bottomGap;
|
|
4521
|
-
const travel = Math.min(insertedHeight, gap);
|
|
4522
|
-
const animation = planViewportShift({
|
|
4523
|
-
currentTranslateY: params.currentTranslateY,
|
|
4524
|
-
travel,
|
|
4525
|
-
direction: params.direction === "push" ? "positive" : "negative",
|
|
4526
|
-
now: params.now,
|
|
4527
|
-
duration: params.duration
|
|
4528
|
-
});
|
|
4529
|
-
return animation == null ? void 0 : {
|
|
4530
|
-
kind: "viewport-slide",
|
|
4531
|
-
animation
|
|
4532
|
-
};
|
|
4666
|
+
return entries.length === 0 ? void 0 : { entries };
|
|
4533
4667
|
}
|
|
4534
4668
|
function measureBoundaryInsertItems(direction, count, ctx) {
|
|
4535
4669
|
const start = direction === "push" ? ctx.items.length - count : 0;
|
|
@@ -4557,6 +4691,11 @@ function drawSampledLayers(sampled, y, adapter) {
|
|
|
4557
4691
|
if (alpha <= .001) continue;
|
|
4558
4692
|
adapter.graphics.save();
|
|
4559
4693
|
try {
|
|
4694
|
+
if (sampled.kind === "insert") {
|
|
4695
|
+
adapter.graphics.beginPath();
|
|
4696
|
+
adapter.graphics.rect(0, y, adapter.graphics.canvas.clientWidth, sampled.slotHeight);
|
|
4697
|
+
adapter.graphics.clip();
|
|
4698
|
+
}
|
|
4560
4699
|
if (typeof adapter.graphics.globalAlpha === "number") adapter.graphics.globalAlpha *= alpha;
|
|
4561
4700
|
if (adapter.drawNode(layer.node, 0, y + layer.translateY)) result = true;
|
|
4562
4701
|
} finally {
|
|
@@ -4580,7 +4719,7 @@ function planUpdateTransition(prevItem, nextItem, duration, now, currentVisualSt
|
|
|
4580
4719
|
snapshot,
|
|
4581
4720
|
hasActiveTransition: store.has(prevItem),
|
|
4582
4721
|
resolveVisibleWindow: ctx.resolveVisibleWindow,
|
|
4583
|
-
|
|
4722
|
+
readOuterVisibleRange: ctx.readOuterVisibleRange
|
|
4584
4723
|
}),
|
|
4585
4724
|
now,
|
|
4586
4725
|
currentVisualState,
|
|
@@ -4601,27 +4740,26 @@ function planDeleteTransition(item, duration, now, currentVisualState, ctx, snap
|
|
|
4601
4740
|
snapshot,
|
|
4602
4741
|
hasActiveTransition: store.has(item),
|
|
4603
4742
|
resolveVisibleWindow: ctx.resolveVisibleWindow,
|
|
4604
|
-
|
|
4743
|
+
readOuterVisibleRange: ctx.readOuterVisibleRange
|
|
4605
4744
|
}),
|
|
4606
4745
|
now,
|
|
4607
4746
|
currentVisualState
|
|
4608
4747
|
});
|
|
4609
4748
|
}
|
|
4610
|
-
function planBoundaryInsertTransition(direction, count, duration,
|
|
4749
|
+
function planBoundaryInsertTransition(direction, count, duration, now, ctx, snapshot) {
|
|
4611
4750
|
const normalizedDuration = normalizeDuration(duration);
|
|
4612
4751
|
if (count <= 0 || normalizedDuration <= 0) return;
|
|
4613
|
-
const
|
|
4614
|
-
|
|
4752
|
+
const matchesBoundaryState = snapshot.matchesBoundaryInsertState(direction, count, ctx.position, ctx.offset);
|
|
4753
|
+
const matchesFollowState = snapshot.matchesFollowBoundaryInsertState(direction, count, ctx.position, ctx.offset);
|
|
4754
|
+
const matchesEmptyState = snapshot.matchesEmptyBoundaryInsertState(direction, count, ctx.position, ctx.offset);
|
|
4755
|
+
if (!(matchesBoundaryState || matchesFollowState || matchesEmptyState || snapshot.hasSnapshot && hasVisibleBoundaryInsertItems(direction, count, ctx))) return;
|
|
4756
|
+
const animateHeight = !(direction === "unshift" && matchesFollowState && !matchesBoundaryState && !matchesEmptyState);
|
|
4615
4757
|
const measuredItems = measureBoundaryInsertItems(direction, count, ctx);
|
|
4616
4758
|
if (measuredItems == null) return;
|
|
4617
|
-
return
|
|
4618
|
-
direction,
|
|
4759
|
+
return planBoundaryInsertItems({
|
|
4619
4760
|
duration: normalizedDuration,
|
|
4620
|
-
|
|
4761
|
+
animateHeight,
|
|
4621
4762
|
now,
|
|
4622
|
-
strategy,
|
|
4623
|
-
snapshot,
|
|
4624
|
-
currentTranslateY,
|
|
4625
4763
|
measuredItems
|
|
4626
4764
|
});
|
|
4627
4765
|
}
|
|
@@ -4674,7 +4812,7 @@ function readCurrentVisualState(item, now, store, adapter) {
|
|
|
4674
4812
|
translateY: 0
|
|
4675
4813
|
};
|
|
4676
4814
|
}
|
|
4677
|
-
function handleTransitionStateChange(store, snapshot,
|
|
4815
|
+
function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
|
|
4678
4816
|
switch (change.type) {
|
|
4679
4817
|
case "update": {
|
|
4680
4818
|
const now = getNow();
|
|
@@ -4682,10 +4820,10 @@ function handleTransitionStateChange(store, snapshot, currentViewportTranslateY,
|
|
|
4682
4820
|
const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
|
|
4683
4821
|
if (transition == null) {
|
|
4684
4822
|
store.delete(change.prevItem);
|
|
4685
|
-
return
|
|
4823
|
+
return;
|
|
4686
4824
|
}
|
|
4687
4825
|
store.replace(change.prevItem, change.nextItem, transition);
|
|
4688
|
-
return
|
|
4826
|
+
return;
|
|
4689
4827
|
}
|
|
4690
4828
|
case "delete": {
|
|
4691
4829
|
const now = getNow();
|
|
@@ -4694,28 +4832,32 @@ function handleTransitionStateChange(store, snapshot, currentViewportTranslateY,
|
|
|
4694
4832
|
if (transition == null) {
|
|
4695
4833
|
store.delete(change.item);
|
|
4696
4834
|
lifecycle.onDeleteComplete(change.item);
|
|
4697
|
-
return
|
|
4835
|
+
return;
|
|
4698
4836
|
}
|
|
4699
4837
|
store.set(change.item, transition);
|
|
4700
|
-
return
|
|
4838
|
+
return;
|
|
4701
4839
|
}
|
|
4702
4840
|
case "delete-finalize":
|
|
4703
4841
|
store.delete(change.item);
|
|
4704
|
-
return
|
|
4842
|
+
return;
|
|
4705
4843
|
case "unshift":
|
|
4706
4844
|
case "push": {
|
|
4707
4845
|
const now = getNow();
|
|
4708
|
-
const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration,
|
|
4709
|
-
if (plan == null) return
|
|
4710
|
-
if (plan.kind === "viewport-slide") return { viewportAnimation: plan.animation };
|
|
4846
|
+
const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, now, ctx, snapshot);
|
|
4847
|
+
if (plan == null) return;
|
|
4711
4848
|
for (const entry of plan.entries) store.set(entry.item, entry.transition);
|
|
4712
|
-
|
|
4849
|
+
if (ctx.position == null && snapshot.coversShortList && (change.type === "push" && ctx.anchorMode === "bottom" || change.type === "unshift" && ctx.anchorMode === "top")) {
|
|
4850
|
+
const boundary = change.type === "push" ? "bottom" : "top";
|
|
4851
|
+
const boundaryItem = snapshot.readBoundaryItem(boundary);
|
|
4852
|
+
if (boundaryItem != null) lifecycle.snapItemToViewportBoundary(boundaryItem, boundary);
|
|
4853
|
+
}
|
|
4854
|
+
return;
|
|
4713
4855
|
}
|
|
4714
4856
|
case "reset":
|
|
4715
4857
|
case "set":
|
|
4716
4858
|
store.reset();
|
|
4717
4859
|
snapshot.reset();
|
|
4718
|
-
return
|
|
4860
|
+
return;
|
|
4719
4861
|
}
|
|
4720
4862
|
}
|
|
4721
4863
|
//#endregion
|
|
@@ -4736,22 +4878,15 @@ function remapAnchorAfterDeletes(anchor, deletedIndices) {
|
|
|
4736
4878
|
var TransitionController = class {
|
|
4737
4879
|
#store = new TransitionStore();
|
|
4738
4880
|
#snapshot = new VisibilitySnapshot();
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
this.#snapshot.capture(window, resolutionPath, items, viewportHeight, snapshotState, extraShift, readVisibleRange);
|
|
4881
|
+
captureVisibilitySnapshot(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
|
|
4882
|
+
this.#snapshot.capture(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange);
|
|
4742
4883
|
}
|
|
4743
4884
|
pruneInvisible(ctx, lifecycle) {
|
|
4744
4885
|
return this.pruneInvisibleAt(getNow(), ctx, lifecycle);
|
|
4745
4886
|
}
|
|
4746
4887
|
prepare(now, lifecycle) {
|
|
4747
4888
|
this.settle(now, lifecycle);
|
|
4748
|
-
this.#
|
|
4749
|
-
const keepViewportAnimating = this.#viewportTranslateAnimation != null;
|
|
4750
|
-
return this.#store.prepare(now) || keepViewportAnimating;
|
|
4751
|
-
}
|
|
4752
|
-
getViewportTranslateY(now) {
|
|
4753
|
-
this.#cleanupViewportTranslateAnimation(now);
|
|
4754
|
-
return this.#viewportTranslateAnimation == null ? 0 : sampleScalarAnimation(this.#viewportTranslateAnimation, now);
|
|
4889
|
+
return this.#store.prepare(now);
|
|
4755
4890
|
}
|
|
4756
4891
|
canAutoFollowBoundaryInsert(direction, count, position, offset) {
|
|
4757
4892
|
return this.#snapshot.matchesFollowBoundaryInsertState(direction, count, position, offset);
|
|
@@ -4765,17 +4900,10 @@ var TransitionController = class {
|
|
|
4765
4900
|
handleListStateChange(change, ctx, lifecycle) {
|
|
4766
4901
|
const now = getNow();
|
|
4767
4902
|
this.settle(now, lifecycle);
|
|
4768
|
-
|
|
4769
|
-
if (change.type === "reset" || change.type === "set") {
|
|
4770
|
-
this.#viewportTranslateAnimation = void 0;
|
|
4771
|
-
return;
|
|
4772
|
-
}
|
|
4773
|
-
if (result.viewportAnimation != null) this.#viewportTranslateAnimation = result.viewportAnimation;
|
|
4903
|
+
handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle);
|
|
4774
4904
|
}
|
|
4775
4905
|
settle(now, lifecycle) {
|
|
4776
|
-
|
|
4777
|
-
this.#cleanupViewportTranslateAnimation(now);
|
|
4778
|
-
return changed;
|
|
4906
|
+
return this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
|
|
4779
4907
|
}
|
|
4780
4908
|
pruneInvisibleAt(now, ctx, lifecycle) {
|
|
4781
4909
|
const removals = this.#store.findInvisible(this.#snapshot);
|
|
@@ -4784,16 +4912,11 @@ var TransitionController = class {
|
|
|
4784
4912
|
reset() {
|
|
4785
4913
|
this.#store.reset();
|
|
4786
4914
|
this.#snapshot.reset();
|
|
4787
|
-
this.#viewportTranslateAnimation = void 0;
|
|
4788
|
-
}
|
|
4789
|
-
#cleanupViewportTranslateAnimation(now) {
|
|
4790
|
-
const animation = this.#viewportTranslateAnimation;
|
|
4791
|
-
if (animation == null) return;
|
|
4792
|
-
if (getProgress(animation.startTime, animation.duration, now) >= 1) this.#viewportTranslateAnimation = void 0;
|
|
4793
4915
|
}
|
|
4794
4916
|
#settleTransitions(removals, now, lifecycle, boundarySnap) {
|
|
4795
4917
|
if (removals.length === 0) return false;
|
|
4796
4918
|
const anchor = lifecycle.captureVisualAnchor(now);
|
|
4919
|
+
const beforeState = lifecycle.readScrollState();
|
|
4797
4920
|
const completedDeleteIndices = [];
|
|
4798
4921
|
for (const { item, transition } of removals) {
|
|
4799
4922
|
if (transition.kind === "delete") {
|
|
@@ -4805,6 +4928,8 @@ var TransitionController = class {
|
|
|
4805
4928
|
}
|
|
4806
4929
|
if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(remapAnchorAfterDeletes(anchor, completedDeleteIndices));
|
|
4807
4930
|
if (boundarySnap != null) lifecycle.snapItemToViewportBoundary(boundarySnap.item, boundarySnap.boundary);
|
|
4931
|
+
const afterState = lifecycle.readScrollState();
|
|
4932
|
+
if (!sameState(beforeState, afterState.position, afterState.offset)) lifecycle.onTransitionSettleScrollAdjusted();
|
|
4808
4933
|
return true;
|
|
4809
4934
|
}
|
|
4810
4935
|
#resolveNaturalBoundarySnap(removals, now, ctx, lifecycle) {
|
|
@@ -4816,7 +4941,7 @@ var TransitionController = class {
|
|
|
4816
4941
|
if (transition.kind !== "update" && transition.kind !== "delete") continue;
|
|
4817
4942
|
const index = lifecycle.readItemIndex(item);
|
|
4818
4943
|
if (index < 0 || !this.#snapshot.wasVisible(item)) return;
|
|
4819
|
-
if (this.#isTransitionVisibleInState(index, previousState, now,
|
|
4944
|
+
if (this.#isTransitionVisibleInState(index, previousState, now, ctx)) return;
|
|
4820
4945
|
naturalIndices.push(index);
|
|
4821
4946
|
}
|
|
4822
4947
|
if (naturalIndices.length === 0) return;
|
|
@@ -4835,16 +4960,324 @@ var TransitionController = class {
|
|
|
4835
4960
|
};
|
|
4836
4961
|
}
|
|
4837
4962
|
}
|
|
4838
|
-
#isTransitionVisibleInState(index, state, now,
|
|
4963
|
+
#isTransitionVisibleInState(index, state, now, ctx) {
|
|
4839
4964
|
const solution = ctx.resolveVisibleWindowForState(state, now);
|
|
4840
4965
|
for (const entry of solution.window.drawList) {
|
|
4841
4966
|
if (entry.idx !== index) continue;
|
|
4842
|
-
return ctx.
|
|
4967
|
+
return ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null;
|
|
4843
4968
|
}
|
|
4844
4969
|
return false;
|
|
4845
4970
|
}
|
|
4846
4971
|
};
|
|
4847
4972
|
//#endregion
|
|
4973
|
+
//#region src/renderer/virtualized/solver.ts
|
|
4974
|
+
function clamp(value, min, max) {
|
|
4975
|
+
return Math.min(Math.max(value, min), max);
|
|
4976
|
+
}
|
|
4977
|
+
function normalizeOffset(offset) {
|
|
4978
|
+
return Number.isFinite(offset) ? offset : 0;
|
|
4979
|
+
}
|
|
4980
|
+
function normalizeListPadding(padding) {
|
|
4981
|
+
return {
|
|
4982
|
+
top: typeof padding?.top === "number" && Number.isFinite(padding.top) ? Math.max(0, padding.top) : 0,
|
|
4983
|
+
bottom: typeof padding?.bottom === "number" && Number.isFinite(padding.bottom) ? Math.max(0, padding.bottom) : 0
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4986
|
+
function resolveListViewport(outerHeight, padding) {
|
|
4987
|
+
const height = typeof outerHeight === "number" && Number.isFinite(outerHeight) ? Math.max(0, outerHeight) : 0;
|
|
4988
|
+
const resolvedPadding = normalizeListPadding(padding);
|
|
4989
|
+
const contentTop = resolvedPadding.top;
|
|
4990
|
+
const contentBottom = Math.max(contentTop, height - resolvedPadding.bottom);
|
|
4991
|
+
return {
|
|
4992
|
+
outerHeight: height,
|
|
4993
|
+
contentTop,
|
|
4994
|
+
contentBottom,
|
|
4995
|
+
contentHeight: contentBottom - contentTop,
|
|
4996
|
+
outerContentTop: -contentTop,
|
|
4997
|
+
outerContentBottom: height - contentTop
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
5000
|
+
function resolveListLayoutOptions(options = {}) {
|
|
5001
|
+
return {
|
|
5002
|
+
anchorMode: options.anchorMode ?? "top",
|
|
5003
|
+
underflowAlign: options.underflowAlign ?? "top",
|
|
5004
|
+
padding: normalizeListPadding(options.padding)
|
|
5005
|
+
};
|
|
5006
|
+
}
|
|
5007
|
+
function normalizeVisibleState(itemCount, state, layout) {
|
|
5008
|
+
if (itemCount <= 0) return {
|
|
5009
|
+
position: 0,
|
|
5010
|
+
offset: 0
|
|
5011
|
+
};
|
|
5012
|
+
const position = state.position;
|
|
5013
|
+
const fallbackPosition = layout.anchorMode === "top" ? 0 : itemCount - 1;
|
|
5014
|
+
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
5015
|
+
position: fallbackPosition,
|
|
5016
|
+
offset: normalizeOffset(state.offset)
|
|
5017
|
+
};
|
|
5018
|
+
return {
|
|
5019
|
+
position: clamp(Math.trunc(position), 0, itemCount - 1),
|
|
5020
|
+
offset: normalizeOffset(state.offset)
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout) {
|
|
5024
|
+
const viewport = typeof viewportHeight === "number" ? resolveListViewport(viewportHeight, layout.padding) : viewportHeight;
|
|
5025
|
+
const contentHeight = viewport.contentHeight;
|
|
5026
|
+
const normalizedState = normalizeVisibleState(items.length, state, layout);
|
|
5027
|
+
const resolutionPath = /* @__PURE__ */ new Set();
|
|
5028
|
+
const readResolvedItem = (item, idx) => {
|
|
5029
|
+
resolutionPath.add(idx);
|
|
5030
|
+
return resolveItem(item, idx);
|
|
5031
|
+
};
|
|
5032
|
+
if (items.length === 0) return {
|
|
5033
|
+
normalizedState,
|
|
5034
|
+
resolutionPath: [],
|
|
5035
|
+
window: {
|
|
5036
|
+
drawList: [],
|
|
5037
|
+
shift: 0
|
|
5038
|
+
}
|
|
5039
|
+
};
|
|
5040
|
+
if (layout.anchorMode === "top") {
|
|
5041
|
+
let { position, offset } = normalizedState;
|
|
5042
|
+
let drawLength = 0;
|
|
5043
|
+
if (offset > 0) if (position === 0) offset = 0;
|
|
5044
|
+
else {
|
|
5045
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5046
|
+
const { height } = readResolvedItem(items[i], i);
|
|
5047
|
+
position = i;
|
|
5048
|
+
offset -= height;
|
|
5049
|
+
if (offset <= 0) break;
|
|
5050
|
+
}
|
|
5051
|
+
if (position === 0 && offset > 0) offset = 0;
|
|
5052
|
+
}
|
|
5053
|
+
let y = offset;
|
|
5054
|
+
const drawList = [];
|
|
5055
|
+
for (let i = position; i < items.length; i += 1) {
|
|
5056
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5057
|
+
if (y + height > 0) {
|
|
5058
|
+
drawList.push({
|
|
5059
|
+
idx: i,
|
|
5060
|
+
value,
|
|
5061
|
+
offset: y,
|
|
5062
|
+
height
|
|
5063
|
+
});
|
|
5064
|
+
drawLength += height;
|
|
5065
|
+
} else {
|
|
5066
|
+
offset += height;
|
|
5067
|
+
position = i + 1;
|
|
5068
|
+
}
|
|
5069
|
+
y += height;
|
|
5070
|
+
if (y >= contentHeight) break;
|
|
5071
|
+
}
|
|
5072
|
+
let shift = 0;
|
|
5073
|
+
if (y < contentHeight) {
|
|
5074
|
+
if (drawList.length > 0 && drawList.at(-1)?.idx === items.length - 1 && !(drawList.at(-1)?.height > Number.EPSILON)) return finalizeVisibleWindowResult(items.length, viewport, layout, {
|
|
5075
|
+
position,
|
|
5076
|
+
offset
|
|
5077
|
+
}, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
|
|
5078
|
+
drawList,
|
|
5079
|
+
shift
|
|
5080
|
+
}, viewport, readResolvedItem));
|
|
5081
|
+
if (position === 0 && drawLength < contentHeight) {
|
|
5082
|
+
shift = -offset;
|
|
5083
|
+
offset = 0;
|
|
5084
|
+
} else {
|
|
5085
|
+
shift = contentHeight - y;
|
|
5086
|
+
y = offset += shift;
|
|
5087
|
+
let lastIdx = -1;
|
|
5088
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5089
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5090
|
+
drawLength += height;
|
|
5091
|
+
y -= height;
|
|
5092
|
+
drawList.push({
|
|
5093
|
+
idx: i,
|
|
5094
|
+
value,
|
|
5095
|
+
offset: y - shift,
|
|
5096
|
+
height
|
|
5097
|
+
});
|
|
5098
|
+
lastIdx = i;
|
|
5099
|
+
if (y < 0) break;
|
|
5100
|
+
}
|
|
5101
|
+
if (lastIdx === 0 && drawLength < contentHeight) {
|
|
5102
|
+
shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
|
|
5103
|
+
position = 0;
|
|
5104
|
+
offset = 0;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
return finalizeVisibleWindowResult(items.length, viewport, layout, {
|
|
5109
|
+
position,
|
|
5110
|
+
offset
|
|
5111
|
+
}, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
|
|
5112
|
+
drawList,
|
|
5113
|
+
shift
|
|
5114
|
+
}, viewport, readResolvedItem));
|
|
5115
|
+
}
|
|
5116
|
+
let { position, offset } = normalizedState;
|
|
5117
|
+
let drawLength = 0;
|
|
5118
|
+
if (offset < 0) if (position === items.length - 1) offset = 0;
|
|
5119
|
+
else for (let i = position + 1; i < items.length; i += 1) {
|
|
5120
|
+
const { height } = readResolvedItem(items[i], i);
|
|
5121
|
+
position = i;
|
|
5122
|
+
offset += height;
|
|
5123
|
+
if (offset > 0) break;
|
|
5124
|
+
}
|
|
5125
|
+
let y = contentHeight + offset;
|
|
5126
|
+
const drawList = [];
|
|
5127
|
+
for (let i = position; i >= 0; i -= 1) {
|
|
5128
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5129
|
+
y -= height;
|
|
5130
|
+
if (y <= contentHeight) {
|
|
5131
|
+
drawList.push({
|
|
5132
|
+
idx: i,
|
|
5133
|
+
value,
|
|
5134
|
+
offset: y,
|
|
5135
|
+
height
|
|
5136
|
+
});
|
|
5137
|
+
drawLength += height;
|
|
5138
|
+
} else {
|
|
5139
|
+
offset -= height;
|
|
5140
|
+
position = i - 1;
|
|
5141
|
+
}
|
|
5142
|
+
if (y < 0) break;
|
|
5143
|
+
}
|
|
5144
|
+
let shift = 0;
|
|
5145
|
+
if (y > 0) {
|
|
5146
|
+
shift = -y;
|
|
5147
|
+
if (drawLength < contentHeight) {
|
|
5148
|
+
y = drawLength;
|
|
5149
|
+
for (let i = position + 1; i < items.length; i += 1) {
|
|
5150
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5151
|
+
drawList.push({
|
|
5152
|
+
idx: i,
|
|
5153
|
+
value,
|
|
5154
|
+
offset: y - shift,
|
|
5155
|
+
height
|
|
5156
|
+
});
|
|
5157
|
+
y = drawLength += height;
|
|
5158
|
+
if (height > Number.EPSILON) position = i;
|
|
5159
|
+
if (y >= contentHeight) break;
|
|
5160
|
+
}
|
|
5161
|
+
offset = drawLength < contentHeight ? 0 : drawLength - contentHeight;
|
|
5162
|
+
} else offset = drawLength - contentHeight;
|
|
5163
|
+
}
|
|
5164
|
+
return finalizeVisibleWindowResult(items.length, viewport, layout, {
|
|
5165
|
+
position,
|
|
5166
|
+
offset
|
|
5167
|
+
}, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
|
|
5168
|
+
drawList,
|
|
5169
|
+
shift
|
|
5170
|
+
}, viewport, readResolvedItem));
|
|
5171
|
+
}
|
|
5172
|
+
function finalizeVisibleWindowResult(itemCount, viewport, layout, normalizedState, resolutionPath, window) {
|
|
5173
|
+
const viewportHeight = viewport.contentHeight;
|
|
5174
|
+
if (window.drawList.length !== itemCount || itemCount <= 0) return {
|
|
5175
|
+
normalizedState,
|
|
5176
|
+
resolutionPath,
|
|
5177
|
+
window
|
|
5178
|
+
};
|
|
5179
|
+
let minIndex = Number.POSITIVE_INFINITY;
|
|
5180
|
+
let maxIndex = Number.NEGATIVE_INFINITY;
|
|
5181
|
+
let minOffset = Number.POSITIVE_INFINITY;
|
|
5182
|
+
let maxBottom = Number.NEGATIVE_INFINITY;
|
|
5183
|
+
let hasDeferredSlots = false;
|
|
5184
|
+
for (const entry of window.drawList) {
|
|
5185
|
+
if (!(entry.height > Number.EPSILON)) hasDeferredSlots = true;
|
|
5186
|
+
else {
|
|
5187
|
+
minOffset = Math.min(minOffset, entry.offset);
|
|
5188
|
+
maxBottom = Math.max(maxBottom, entry.offset + entry.height);
|
|
5189
|
+
}
|
|
5190
|
+
minIndex = Math.min(minIndex, entry.idx);
|
|
5191
|
+
maxIndex = Math.max(maxIndex, entry.idx);
|
|
5192
|
+
}
|
|
5193
|
+
if (!Number.isFinite(minOffset) || !Number.isFinite(maxBottom)) return {
|
|
5194
|
+
normalizedState,
|
|
5195
|
+
resolutionPath,
|
|
5196
|
+
window
|
|
5197
|
+
};
|
|
5198
|
+
const contentHeight = maxBottom - minOffset;
|
|
5199
|
+
if (minIndex !== 0 || maxIndex !== itemCount - 1 || !(contentHeight < viewportHeight - Number.EPSILON)) return {
|
|
5200
|
+
normalizedState,
|
|
5201
|
+
resolutionPath,
|
|
5202
|
+
window
|
|
5203
|
+
};
|
|
5204
|
+
const desiredTop = layout.underflowAlign === "bottom" ? viewportHeight - contentHeight : 0;
|
|
5205
|
+
return {
|
|
5206
|
+
normalizedState: hasDeferredSlots ? normalizedState : layout.anchorMode === "top" ? {
|
|
5207
|
+
position: 0,
|
|
5208
|
+
offset: 0
|
|
5209
|
+
} : {
|
|
5210
|
+
position: itemCount - 1,
|
|
5211
|
+
offset: 0
|
|
5212
|
+
},
|
|
5213
|
+
resolutionPath,
|
|
5214
|
+
window: {
|
|
5215
|
+
drawList: window.drawList,
|
|
5216
|
+
shift: desiredTop - minOffset
|
|
5217
|
+
}
|
|
5218
|
+
};
|
|
5219
|
+
}
|
|
5220
|
+
function extendVisibleWindowToOuterBounds(items, window, viewport, resolveItem) {
|
|
5221
|
+
if (window.drawList.length === 0 || items.length === 0) return window;
|
|
5222
|
+
const drawList = [...window.drawList];
|
|
5223
|
+
const existingIndices = new Set(drawList.map((entry) => entry.idx));
|
|
5224
|
+
let topEntry = drawList[0];
|
|
5225
|
+
let bottomEntry = drawList[0];
|
|
5226
|
+
for (const entry of drawList) {
|
|
5227
|
+
if (entry.offset < topEntry.offset) topEntry = entry;
|
|
5228
|
+
if (entry.offset + entry.height > bottomEntry.offset + bottomEntry.height) bottomEntry = entry;
|
|
5229
|
+
}
|
|
5230
|
+
let topIdx = topEntry.idx;
|
|
5231
|
+
let topY = topEntry.offset + window.shift;
|
|
5232
|
+
while (topIdx > 0) {
|
|
5233
|
+
const prevIdx = topIdx - 1;
|
|
5234
|
+
if (existingIndices.has(prevIdx)) {
|
|
5235
|
+
const existing = drawList.find((entry) => entry.idx === prevIdx);
|
|
5236
|
+
topIdx = prevIdx;
|
|
5237
|
+
if (existing != null) topY = existing.offset + window.shift;
|
|
5238
|
+
continue;
|
|
5239
|
+
}
|
|
5240
|
+
const { value, height } = resolveItem(items[prevIdx], prevIdx);
|
|
5241
|
+
const prevY = topY - height;
|
|
5242
|
+
if (prevY + height <= viewport.outerContentTop) break;
|
|
5243
|
+
drawList.push({
|
|
5244
|
+
idx: prevIdx,
|
|
5245
|
+
value,
|
|
5246
|
+
offset: prevY - window.shift,
|
|
5247
|
+
height
|
|
5248
|
+
});
|
|
5249
|
+
existingIndices.add(prevIdx);
|
|
5250
|
+
topIdx = prevIdx;
|
|
5251
|
+
topY = prevY;
|
|
5252
|
+
}
|
|
5253
|
+
let bottomIdx = bottomEntry.idx;
|
|
5254
|
+
let bottomY = bottomEntry.offset + window.shift + bottomEntry.height;
|
|
5255
|
+
while (bottomIdx < items.length - 1) {
|
|
5256
|
+
const nextIdx = bottomIdx + 1;
|
|
5257
|
+
if (existingIndices.has(nextIdx)) {
|
|
5258
|
+
const existing = drawList.find((entry) => entry.idx === nextIdx);
|
|
5259
|
+
bottomIdx = nextIdx;
|
|
5260
|
+
if (existing != null) bottomY = Math.max(bottomY, existing.offset + window.shift + existing.height);
|
|
5261
|
+
continue;
|
|
5262
|
+
}
|
|
5263
|
+
const { value, height } = resolveItem(items[nextIdx], nextIdx);
|
|
5264
|
+
if (bottomY >= viewport.outerContentBottom) break;
|
|
5265
|
+
drawList.push({
|
|
5266
|
+
idx: nextIdx,
|
|
5267
|
+
value,
|
|
5268
|
+
offset: bottomY - window.shift,
|
|
5269
|
+
height
|
|
5270
|
+
});
|
|
5271
|
+
existingIndices.add(nextIdx);
|
|
5272
|
+
bottomIdx = nextIdx;
|
|
5273
|
+
bottomY += height;
|
|
5274
|
+
}
|
|
5275
|
+
return {
|
|
5276
|
+
drawList,
|
|
5277
|
+
shift: window.shift
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
//#endregion
|
|
4848
5281
|
//#region src/renderer/virtualized/base.ts
|
|
4849
5282
|
/**
|
|
4850
5283
|
* Shared base class for virtualized list renderers.
|
|
@@ -4863,6 +5296,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4863
5296
|
jumpDurationPerPixel: VirtualizedRenderer.JUMP_DURATION_PER_PIXEL,
|
|
4864
5297
|
getItemCount: () => this.items.length,
|
|
4865
5298
|
readListState: this._readListState.bind(this),
|
|
5299
|
+
readScrollMutation: () => readListScrollMutation(this.options.list),
|
|
4866
5300
|
normalizeListState: this._normalizeListState.bind(this),
|
|
4867
5301
|
readAnchor: (state) => this._readAnchor(state, this._getItemHeight.bind(this)),
|
|
4868
5302
|
applyAnchor: this._applyAnchor.bind(this),
|
|
@@ -4902,6 +5336,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4902
5336
|
/** Renders the current visible window. */
|
|
4903
5337
|
render(feedback) {
|
|
4904
5338
|
this.#jumpController.beforeFrame();
|
|
5339
|
+
this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
|
|
4905
5340
|
const now = getNow();
|
|
4906
5341
|
const keepAnimating = this._prepareRender(now);
|
|
4907
5342
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
@@ -4909,12 +5344,11 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4909
5344
|
const frame = prepareFrameSession({
|
|
4910
5345
|
now,
|
|
4911
5346
|
resolveVisibleWindow: (frameNow) => this._resolveVisibleWindow(frameNow),
|
|
4912
|
-
|
|
4913
|
-
captureVisibleItemSnapshot: (solution, extraShift) => this._captureVisibleItemSnapshot(solution, extraShift),
|
|
5347
|
+
captureVisibleItemSnapshot: (solution) => this._captureVisibleItemSnapshot(solution),
|
|
4914
5348
|
pruneTransitionAnimations: (window, frameNow) => this._pruneTransitionAnimations(window, frameNow)
|
|
4915
5349
|
});
|
|
4916
|
-
const autoFollowCapabilities = this.#jumpController.
|
|
4917
|
-
const requestRedraw = this._renderVisibleWindow(frame.solution.window, feedback
|
|
5350
|
+
const autoFollowCapabilities = this.#jumpController.recomputeAutoFollowCapabilities(this._readAutoFollowCapabilities(frame.solution.window));
|
|
5351
|
+
const requestRedraw = this._renderVisibleWindow(frame.solution.window, feedback);
|
|
4918
5352
|
if (feedback != null) {
|
|
4919
5353
|
feedback.canAutoFollowTop = autoFollowCapabilities.top;
|
|
4920
5354
|
feedback.canAutoFollowBottom = autoFollowCapabilities.bottom;
|
|
@@ -4925,17 +5359,17 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4925
5359
|
/** Hit-tests the current visible window. */
|
|
4926
5360
|
hittest(test) {
|
|
4927
5361
|
this.#jumpController.beforeFrame();
|
|
5362
|
+
this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
|
|
4928
5363
|
const now = getNow();
|
|
4929
5364
|
this.#transitionController.settle(now, this.#getTransitionLifecycleAdapter());
|
|
4930
5365
|
const frame = prepareFrameSession({
|
|
4931
5366
|
now,
|
|
4932
5367
|
resolveVisibleWindow: (frameNow) => this._resolveVisibleWindow(frameNow),
|
|
4933
|
-
|
|
4934
|
-
captureVisibleItemSnapshot: (solution, extraShift) => this._captureVisibleItemSnapshot(solution, extraShift),
|
|
5368
|
+
captureVisibleItemSnapshot: (solution) => this._captureVisibleItemSnapshot(solution),
|
|
4935
5369
|
pruneTransitionAnimations: (window, frameNow) => this._pruneTransitionAnimations(window, frameNow)
|
|
4936
5370
|
});
|
|
4937
|
-
this.#jumpController.
|
|
4938
|
-
return this._hittestVisibleWindow(frame.solution.window, test
|
|
5371
|
+
this.#jumpController.recomputeAutoFollowCapabilities(this._readAutoFollowCapabilities(frame.solution.window));
|
|
5372
|
+
return this._hittestVisibleWindow(frame.solution.window, test);
|
|
4939
5373
|
}
|
|
4940
5374
|
_readListState() {
|
|
4941
5375
|
return {
|
|
@@ -4947,8 +5381,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4947
5381
|
return this._resolveVisibleWindowForState(this._readListState(), now);
|
|
4948
5382
|
}
|
|
4949
5383
|
_commitListState(state) {
|
|
4950
|
-
this.
|
|
4951
|
-
this.offset = state.offset;
|
|
5384
|
+
writeInternalListScrollState(this.options.list, state);
|
|
4952
5385
|
this.#jumpController.commit(state);
|
|
4953
5386
|
}
|
|
4954
5387
|
/**
|
|
@@ -4990,20 +5423,20 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
4990
5423
|
}
|
|
4991
5424
|
_renderDrawList(list, shift, feedback) {
|
|
4992
5425
|
let result = false;
|
|
4993
|
-
const
|
|
5426
|
+
const viewport = this._getViewportMetrics();
|
|
4994
5427
|
for (const { idx, value: item, offset, height } of list) {
|
|
4995
|
-
const y = offset + shift;
|
|
5428
|
+
const y = offset + shift + viewport.contentTop;
|
|
4996
5429
|
if (feedback != null) this._accumulateRenderFeedback(feedback, idx, y, height);
|
|
4997
|
-
if (y + height < 0 || y >
|
|
5430
|
+
if (y + height < 0 || y > viewport.outerHeight) continue;
|
|
4998
5431
|
if (item.draw(y)) result = true;
|
|
4999
5432
|
}
|
|
5000
5433
|
return result;
|
|
5001
5434
|
}
|
|
5002
|
-
_renderVisibleWindow(window, feedback
|
|
5435
|
+
_renderVisibleWindow(window, feedback) {
|
|
5003
5436
|
this._resetRenderFeedback(feedback);
|
|
5004
|
-
return this._renderDrawList(window.drawList, window.shift
|
|
5437
|
+
return this._renderDrawList(window.drawList, window.shift, feedback);
|
|
5005
5438
|
}
|
|
5006
|
-
_readAutoFollowCapabilities(window
|
|
5439
|
+
_readAutoFollowCapabilities(window) {
|
|
5007
5440
|
if (window.drawList.length === 0 || this.items.length === 0) return {
|
|
5008
5441
|
top: false,
|
|
5009
5442
|
bottom: false
|
|
@@ -5012,21 +5445,31 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5012
5445
|
let maxIndex = Number.NEGATIVE_INFINITY;
|
|
5013
5446
|
let topMostY = Number.POSITIVE_INFINITY;
|
|
5014
5447
|
let bottomMostY = Number.NEGATIVE_INFINITY;
|
|
5015
|
-
const
|
|
5448
|
+
const viewport = this._getViewportMetrics();
|
|
5016
5449
|
for (const { idx, offset, height } of window.drawList) {
|
|
5017
5450
|
minIndex = Math.min(minIndex, idx);
|
|
5018
5451
|
maxIndex = Math.max(maxIndex, idx);
|
|
5019
|
-
const y = offset +
|
|
5452
|
+
const y = offset + window.shift + viewport.contentTop;
|
|
5020
5453
|
topMostY = Math.min(topMostY, y);
|
|
5021
5454
|
bottomMostY = Math.max(bottomMostY, y + height);
|
|
5022
5455
|
}
|
|
5023
|
-
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
5024
5456
|
return {
|
|
5025
|
-
top: minIndex === 0 && topMostY >= -
|
|
5026
|
-
bottom: maxIndex === this.items.length - 1 && bottomMostY <=
|
|
5457
|
+
top: minIndex === 0 && topMostY >= viewport.contentTop - 1e-6,
|
|
5458
|
+
bottom: maxIndex === this.items.length - 1 && bottomMostY <= viewport.contentBottom + 1e-6
|
|
5027
5459
|
};
|
|
5028
5460
|
}
|
|
5029
5461
|
_readVisibleRange(top, height) {
|
|
5462
|
+
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
5463
|
+
const viewport = this._getViewportMetrics();
|
|
5464
|
+
const visibleTop = clamp$1(viewport.contentTop - top, 0, height);
|
|
5465
|
+
const visibleBottom = clamp$1(viewport.contentBottom - top, 0, height);
|
|
5466
|
+
if (visibleBottom <= visibleTop) return;
|
|
5467
|
+
return {
|
|
5468
|
+
top: visibleTop,
|
|
5469
|
+
bottom: visibleBottom
|
|
5470
|
+
};
|
|
5471
|
+
}
|
|
5472
|
+
_readOuterVisibleRange(top, height) {
|
|
5030
5473
|
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
5031
5474
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
5032
5475
|
const visibleTop = clamp$1(-top, 0, height);
|
|
@@ -5040,17 +5483,19 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5040
5483
|
_pruneTransitionAnimations(_window, now) {
|
|
5041
5484
|
return this.#transitionController.pruneInvisibleAt(now, this.#getTransitionPlanningAdapter(), this.#getTransitionLifecycleAdapter());
|
|
5042
5485
|
}
|
|
5043
|
-
_hittestVisibleWindow(window, test
|
|
5486
|
+
_hittestVisibleWindow(window, test) {
|
|
5487
|
+
const viewport = this._getViewportMetrics();
|
|
5044
5488
|
for (const { value: item, offset, height } of window.drawList) {
|
|
5045
|
-
const y = offset + window.shift +
|
|
5489
|
+
const y = offset + window.shift + viewport.contentTop;
|
|
5046
5490
|
if (test.y < y || test.y >= y + height) continue;
|
|
5047
5491
|
return item.hittest(test, y);
|
|
5048
5492
|
}
|
|
5049
5493
|
return false;
|
|
5050
5494
|
}
|
|
5051
|
-
_captureVisibleItemSnapshot(solution
|
|
5495
|
+
_captureVisibleItemSnapshot(solution) {
|
|
5052
5496
|
const normalizedState = this._normalizeListState(this._readListState());
|
|
5053
|
-
|
|
5497
|
+
const viewport = this._getViewportMetrics();
|
|
5498
|
+
this.#transitionController.captureVisibilitySnapshot(solution.window, solution.resolutionPath, this.items, viewport, normalizedState, this._readVisibleRange.bind(this), this._readOuterVisibleRange.bind(this));
|
|
5054
5499
|
}
|
|
5055
5500
|
_prepareRender(now) {
|
|
5056
5501
|
const keepTransitioning = this.#transitionController.prepare(now, this.#getTransitionLifecycleAdapter());
|
|
@@ -5090,6 +5535,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5090
5535
|
_resolveItem(item, _index, now) {
|
|
5091
5536
|
return this.#transitionController.resolveItem(item, now, this.#getTransitionRenderAdapter(), this.#getTransitionLifecycleAdapter());
|
|
5092
5537
|
}
|
|
5538
|
+
_getViewportMetrics() {
|
|
5539
|
+
return resolveListViewport(this.graphics.canvas.clientHeight, this._getLayoutOptions().padding);
|
|
5540
|
+
}
|
|
5093
5541
|
#handleDeleteComplete(item) {
|
|
5094
5542
|
this.options.list.finalizeDelete(item);
|
|
5095
5543
|
}
|
|
@@ -5098,18 +5546,23 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5098
5546
|
onDeleteComplete: this.#handleDeleteComplete.bind(this),
|
|
5099
5547
|
captureVisualAnchor: this._readAnchorAt.bind(this),
|
|
5100
5548
|
restoreVisualAnchor: this._restoreAnchor.bind(this),
|
|
5549
|
+
readScrollState: this._readListState.bind(this),
|
|
5101
5550
|
readItemIndex: (item) => this.items.indexOf(item),
|
|
5102
|
-
snapItemToViewportBoundary: this.#snapItemToViewportBoundary.bind(this)
|
|
5551
|
+
snapItemToViewportBoundary: this.#snapItemToViewportBoundary.bind(this),
|
|
5552
|
+
onTransitionSettleScrollAdjusted: () => this.#jumpController.reconcileAutoFollowAfterTransitionSettle()
|
|
5103
5553
|
};
|
|
5104
5554
|
}
|
|
5105
5555
|
#getVirtualizedRuntime() {
|
|
5556
|
+
const viewport = this._getViewportMetrics();
|
|
5106
5557
|
return {
|
|
5107
5558
|
items: this.items,
|
|
5108
5559
|
position: this.position,
|
|
5109
5560
|
offset: this.offset,
|
|
5110
5561
|
renderItem: this.options.renderItem,
|
|
5111
5562
|
measureNode: this.measureRootNode.bind(this),
|
|
5563
|
+
viewport,
|
|
5112
5564
|
readVisibleRange: this._readVisibleRange.bind(this),
|
|
5565
|
+
readOuterVisibleRange: this._readOuterVisibleRange.bind(this),
|
|
5113
5566
|
resolveVisibleWindow: () => this._resolveVisibleWindow(getNow()),
|
|
5114
5567
|
resolveVisibleWindowForState: (state, now) => this._resolveVisibleWindowForState(state, now)
|
|
5115
5568
|
};
|
|
@@ -5127,7 +5580,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5127
5580
|
#getTransitionPlanningAdapter() {
|
|
5128
5581
|
return {
|
|
5129
5582
|
...this.#getVirtualizedRuntime(),
|
|
5130
|
-
|
|
5583
|
+
anchorMode: this._getLayoutOptions().anchorMode
|
|
5131
5584
|
};
|
|
5132
5585
|
}
|
|
5133
5586
|
#handleListStateChange(change) {
|
|
@@ -5204,212 +5657,6 @@ function getTargetAnchorForItem(itemCount, index, block, anchorMode, viewportHei
|
|
|
5204
5657
|
}
|
|
5205
5658
|
}
|
|
5206
5659
|
//#endregion
|
|
5207
|
-
//#region src/renderer/virtualized/solver.ts
|
|
5208
|
-
function clamp(value, min, max) {
|
|
5209
|
-
return Math.min(Math.max(value, min), max);
|
|
5210
|
-
}
|
|
5211
|
-
function normalizeOffset(offset) {
|
|
5212
|
-
return Number.isFinite(offset) ? offset : 0;
|
|
5213
|
-
}
|
|
5214
|
-
function resolveListLayoutOptions(options = {}) {
|
|
5215
|
-
return {
|
|
5216
|
-
anchorMode: options.anchorMode ?? "top",
|
|
5217
|
-
underflowAlign: options.underflowAlign ?? "top"
|
|
5218
|
-
};
|
|
5219
|
-
}
|
|
5220
|
-
function normalizeVisibleState(itemCount, state, layout) {
|
|
5221
|
-
if (itemCount <= 0) return {
|
|
5222
|
-
position: 0,
|
|
5223
|
-
offset: 0
|
|
5224
|
-
};
|
|
5225
|
-
const position = state.position;
|
|
5226
|
-
const fallbackPosition = layout.anchorMode === "top" ? 0 : itemCount - 1;
|
|
5227
|
-
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
5228
|
-
position: fallbackPosition,
|
|
5229
|
-
offset: normalizeOffset(state.offset)
|
|
5230
|
-
};
|
|
5231
|
-
return {
|
|
5232
|
-
position: clamp(Math.trunc(position), 0, itemCount - 1),
|
|
5233
|
-
offset: normalizeOffset(state.offset)
|
|
5234
|
-
};
|
|
5235
|
-
}
|
|
5236
|
-
function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout) {
|
|
5237
|
-
const normalizedState = normalizeVisibleState(items.length, state, layout);
|
|
5238
|
-
const resolutionPath = /* @__PURE__ */ new Set();
|
|
5239
|
-
const readResolvedItem = (item, idx) => {
|
|
5240
|
-
resolutionPath.add(idx);
|
|
5241
|
-
return resolveItem(item, idx);
|
|
5242
|
-
};
|
|
5243
|
-
if (items.length === 0) return {
|
|
5244
|
-
normalizedState,
|
|
5245
|
-
resolutionPath: [],
|
|
5246
|
-
window: {
|
|
5247
|
-
drawList: [],
|
|
5248
|
-
shift: 0
|
|
5249
|
-
}
|
|
5250
|
-
};
|
|
5251
|
-
if (layout.anchorMode === "top") {
|
|
5252
|
-
let { position, offset } = normalizedState;
|
|
5253
|
-
let drawLength = 0;
|
|
5254
|
-
if (offset > 0) if (position === 0) offset = 0;
|
|
5255
|
-
else {
|
|
5256
|
-
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5257
|
-
const { height } = readResolvedItem(items[i], i);
|
|
5258
|
-
position = i;
|
|
5259
|
-
offset -= height;
|
|
5260
|
-
if (offset <= 0) break;
|
|
5261
|
-
}
|
|
5262
|
-
if (position === 0 && offset > 0) offset = 0;
|
|
5263
|
-
}
|
|
5264
|
-
let y = offset;
|
|
5265
|
-
const drawList = [];
|
|
5266
|
-
for (let i = position; i < items.length; i += 1) {
|
|
5267
|
-
const { value, height } = readResolvedItem(items[i], i);
|
|
5268
|
-
if (y + height > 0) {
|
|
5269
|
-
drawList.push({
|
|
5270
|
-
idx: i,
|
|
5271
|
-
value,
|
|
5272
|
-
offset: y,
|
|
5273
|
-
height
|
|
5274
|
-
});
|
|
5275
|
-
drawLength += height;
|
|
5276
|
-
} else {
|
|
5277
|
-
offset += height;
|
|
5278
|
-
position = i + 1;
|
|
5279
|
-
}
|
|
5280
|
-
y += height;
|
|
5281
|
-
if (y >= viewportHeight) break;
|
|
5282
|
-
}
|
|
5283
|
-
let shift = 0;
|
|
5284
|
-
if (y < viewportHeight) if (position === 0 && drawLength < viewportHeight) {
|
|
5285
|
-
shift = -offset;
|
|
5286
|
-
offset = 0;
|
|
5287
|
-
} else {
|
|
5288
|
-
shift = viewportHeight - y;
|
|
5289
|
-
y = offset += shift;
|
|
5290
|
-
let lastIdx = -1;
|
|
5291
|
-
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5292
|
-
const { value, height } = readResolvedItem(items[i], i);
|
|
5293
|
-
drawLength += height;
|
|
5294
|
-
y -= height;
|
|
5295
|
-
drawList.push({
|
|
5296
|
-
idx: i,
|
|
5297
|
-
value,
|
|
5298
|
-
offset: y - shift,
|
|
5299
|
-
height
|
|
5300
|
-
});
|
|
5301
|
-
lastIdx = i;
|
|
5302
|
-
if (y < 0) break;
|
|
5303
|
-
}
|
|
5304
|
-
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
5305
|
-
shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
|
|
5306
|
-
position = 0;
|
|
5307
|
-
offset = 0;
|
|
5308
|
-
}
|
|
5309
|
-
}
|
|
5310
|
-
return finalizeVisibleWindowResult(items.length, viewportHeight, layout, {
|
|
5311
|
-
position,
|
|
5312
|
-
offset
|
|
5313
|
-
}, Array.from(resolutionPath), {
|
|
5314
|
-
drawList,
|
|
5315
|
-
shift
|
|
5316
|
-
});
|
|
5317
|
-
}
|
|
5318
|
-
let { position, offset } = normalizedState;
|
|
5319
|
-
let drawLength = 0;
|
|
5320
|
-
if (offset < 0) if (position === items.length - 1) offset = 0;
|
|
5321
|
-
else for (let i = position + 1; i < items.length; i += 1) {
|
|
5322
|
-
const { height } = readResolvedItem(items[i], i);
|
|
5323
|
-
position = i;
|
|
5324
|
-
offset += height;
|
|
5325
|
-
if (offset > 0) break;
|
|
5326
|
-
}
|
|
5327
|
-
let y = viewportHeight + offset;
|
|
5328
|
-
const drawList = [];
|
|
5329
|
-
for (let i = position; i >= 0; i -= 1) {
|
|
5330
|
-
const { value, height } = readResolvedItem(items[i], i);
|
|
5331
|
-
y -= height;
|
|
5332
|
-
if (y <= viewportHeight) {
|
|
5333
|
-
drawList.push({
|
|
5334
|
-
idx: i,
|
|
5335
|
-
value,
|
|
5336
|
-
offset: y,
|
|
5337
|
-
height
|
|
5338
|
-
});
|
|
5339
|
-
drawLength += height;
|
|
5340
|
-
} else {
|
|
5341
|
-
offset -= height;
|
|
5342
|
-
position = i - 1;
|
|
5343
|
-
}
|
|
5344
|
-
if (y < 0) break;
|
|
5345
|
-
}
|
|
5346
|
-
let shift = 0;
|
|
5347
|
-
if (y > 0) {
|
|
5348
|
-
shift = -y;
|
|
5349
|
-
if (drawLength < viewportHeight) {
|
|
5350
|
-
y = drawLength;
|
|
5351
|
-
for (let i = position + 1; i < items.length; i += 1) {
|
|
5352
|
-
const { value, height } = readResolvedItem(items[i], i);
|
|
5353
|
-
drawList.push({
|
|
5354
|
-
idx: i,
|
|
5355
|
-
value,
|
|
5356
|
-
offset: y - shift,
|
|
5357
|
-
height
|
|
5358
|
-
});
|
|
5359
|
-
y = drawLength += height;
|
|
5360
|
-
position = i;
|
|
5361
|
-
if (y >= viewportHeight) break;
|
|
5362
|
-
}
|
|
5363
|
-
offset = drawLength < viewportHeight ? 0 : drawLength - viewportHeight;
|
|
5364
|
-
} else offset = drawLength - viewportHeight;
|
|
5365
|
-
}
|
|
5366
|
-
return finalizeVisibleWindowResult(items.length, viewportHeight, layout, {
|
|
5367
|
-
position,
|
|
5368
|
-
offset
|
|
5369
|
-
}, Array.from(resolutionPath), {
|
|
5370
|
-
drawList,
|
|
5371
|
-
shift
|
|
5372
|
-
});
|
|
5373
|
-
}
|
|
5374
|
-
function finalizeVisibleWindowResult(itemCount, viewportHeight, layout, normalizedState, resolutionPath, window) {
|
|
5375
|
-
if (window.drawList.length !== itemCount || itemCount <= 0) return {
|
|
5376
|
-
normalizedState,
|
|
5377
|
-
resolutionPath,
|
|
5378
|
-
window
|
|
5379
|
-
};
|
|
5380
|
-
let minIndex = Number.POSITIVE_INFINITY;
|
|
5381
|
-
let maxIndex = Number.NEGATIVE_INFINITY;
|
|
5382
|
-
let minOffset = Number.POSITIVE_INFINITY;
|
|
5383
|
-
let maxBottom = Number.NEGATIVE_INFINITY;
|
|
5384
|
-
for (const entry of window.drawList) {
|
|
5385
|
-
minIndex = Math.min(minIndex, entry.idx);
|
|
5386
|
-
maxIndex = Math.max(maxIndex, entry.idx);
|
|
5387
|
-
minOffset = Math.min(minOffset, entry.offset);
|
|
5388
|
-
maxBottom = Math.max(maxBottom, entry.offset + entry.height);
|
|
5389
|
-
}
|
|
5390
|
-
const contentHeight = maxBottom - minOffset;
|
|
5391
|
-
if (minIndex !== 0 || maxIndex !== itemCount - 1 || !(contentHeight < viewportHeight - Number.EPSILON)) return {
|
|
5392
|
-
normalizedState,
|
|
5393
|
-
resolutionPath,
|
|
5394
|
-
window
|
|
5395
|
-
};
|
|
5396
|
-
const desiredTop = layout.underflowAlign === "bottom" ? viewportHeight - contentHeight : 0;
|
|
5397
|
-
return {
|
|
5398
|
-
normalizedState: layout.anchorMode === "top" ? {
|
|
5399
|
-
position: 0,
|
|
5400
|
-
offset: 0
|
|
5401
|
-
} : {
|
|
5402
|
-
position: itemCount - 1,
|
|
5403
|
-
offset: 0
|
|
5404
|
-
},
|
|
5405
|
-
resolutionPath,
|
|
5406
|
-
window: {
|
|
5407
|
-
drawList: window.drawList,
|
|
5408
|
-
shift: desiredTop - minOffset
|
|
5409
|
-
}
|
|
5410
|
-
};
|
|
5411
|
-
}
|
|
5412
|
-
//#endregion
|
|
5413
5660
|
//#region src/renderer/virtualized/list.ts
|
|
5414
5661
|
/**
|
|
5415
5662
|
* Virtualized list renderer with configurable anchor semantics.
|
|
@@ -5420,11 +5667,24 @@ var ListRenderer = class extends VirtualizedRenderer {
|
|
|
5420
5667
|
super(graphics, options);
|
|
5421
5668
|
this.#layout = resolveListLayoutOptions(options);
|
|
5422
5669
|
}
|
|
5670
|
+
get padding() {
|
|
5671
|
+
return { ...this.#layout.padding };
|
|
5672
|
+
}
|
|
5673
|
+
set padding(value) {
|
|
5674
|
+
const nextPadding = normalizeListPadding(value);
|
|
5675
|
+
if (nextPadding.top === this.#layout.padding.top && nextPadding.bottom === this.#layout.padding.bottom) return;
|
|
5676
|
+
const anchor = this._readAnchorAt(performance.now());
|
|
5677
|
+
this.#layout = {
|
|
5678
|
+
...this.#layout,
|
|
5679
|
+
padding: nextPadding
|
|
5680
|
+
};
|
|
5681
|
+
if (anchor != null) this._restoreAnchor(anchor);
|
|
5682
|
+
}
|
|
5423
5683
|
_getLayoutOptions() {
|
|
5424
5684
|
return this.#layout;
|
|
5425
5685
|
}
|
|
5426
5686
|
_resolveVisibleWindowForState(state, now) {
|
|
5427
|
-
return resolveVisibleWindow(this.items, state, this.graphics.canvas.clientHeight, (item, idx) => this._resolveItem(item, idx, now), this.#layout);
|
|
5687
|
+
return resolveVisibleWindow(this.items, state, resolveListViewport(this.graphics.canvas.clientHeight, this.#layout.padding), (item, idx) => this._resolveItem(item, idx, now), this.#layout);
|
|
5428
5688
|
}
|
|
5429
5689
|
_getDefaultJumpBlock() {
|
|
5430
5690
|
return this.#layout.anchorMode === "top" ? "start" : "end";
|
|
@@ -5441,7 +5701,7 @@ var ListRenderer = class extends VirtualizedRenderer {
|
|
|
5441
5701
|
this._commitListState(state);
|
|
5442
5702
|
}
|
|
5443
5703
|
_getTargetAnchor(index, block) {
|
|
5444
|
-
return getTargetAnchorForItem(this.items.length, index, block, this.#layout.anchorMode, this.graphics.canvas.clientHeight, this._getItemHeight.bind(this));
|
|
5704
|
+
return getTargetAnchorForItem(this.items.length, index, block, this.#layout.anchorMode, resolveListViewport(this.graphics.canvas.clientHeight, this.#layout.padding).contentHeight, this._getItemHeight.bind(this));
|
|
5445
5705
|
}
|
|
5446
5706
|
};
|
|
5447
5707
|
//#endregion
|