chat-layout 1.2.0-3 → 1.2.0-5
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 +24 -1
- package/example/chat.ts +53 -14
- package/index.d.mts +78 -40
- package/index.mjs +1223 -467
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -3639,12 +3639,34 @@ function assertUniqueItemReferences(items, existingItems) {
|
|
|
3639
3639
|
seen.add(item);
|
|
3640
3640
|
}
|
|
3641
3641
|
}
|
|
3642
|
+
function normalizeAnimationDuration(duration) {
|
|
3643
|
+
if (duration == null) return;
|
|
3644
|
+
return Number.isFinite(duration) ? { duration } : {};
|
|
3645
|
+
}
|
|
3642
3646
|
function normalizeUpdateAnimation(animation) {
|
|
3643
|
-
|
|
3644
|
-
|
|
3647
|
+
return normalizeAnimationDuration(animation?.duration);
|
|
3648
|
+
}
|
|
3649
|
+
function normalizeDeleteAnimation(animation) {
|
|
3650
|
+
return normalizeAnimationDuration(animation?.duration);
|
|
3651
|
+
}
|
|
3652
|
+
const DEFAULT_INSERT_ALL_ANIMATION_DURATION = 220;
|
|
3653
|
+
function normalizeInsertAnimationDuration(duration, hasAnimationOptions) {
|
|
3654
|
+
if (!hasAnimationOptions) return;
|
|
3655
|
+
const resolvedDuration = duration == null ? DEFAULT_INSERT_ALL_ANIMATION_DURATION : duration;
|
|
3656
|
+
if (!Number.isFinite(resolvedDuration) || resolvedDuration <= 0) return;
|
|
3657
|
+
return resolvedDuration;
|
|
3658
|
+
}
|
|
3659
|
+
function normalizeInsertAnimation(animation) {
|
|
3660
|
+
const duration = normalizeInsertAnimationDuration(animation?.duration, animation != null);
|
|
3661
|
+
if (duration == null) return;
|
|
3662
|
+
const normalizedAnimation = { duration };
|
|
3663
|
+
if (typeof animation?.distance === "number" && Number.isFinite(animation.distance)) normalizedAnimation.distance = Math.max(0, animation.distance);
|
|
3664
|
+
if (animation?.followIfAtBoundary === true) normalizedAnimation.followIfAtBoundary = true;
|
|
3665
|
+
return normalizedAnimation;
|
|
3645
3666
|
}
|
|
3646
3667
|
var ListState = class {
|
|
3647
3668
|
#items;
|
|
3669
|
+
#pendingDeletes = /* @__PURE__ */ new Set();
|
|
3648
3670
|
/** Pixel offset from the anchored item edge. */
|
|
3649
3671
|
offset = 0;
|
|
3650
3672
|
/** Anchor item index, or `undefined` to use the renderer default. */
|
|
@@ -3658,6 +3680,7 @@ var ListState = class {
|
|
|
3658
3680
|
const nextItems = [...value];
|
|
3659
3681
|
assertUniqueItemReferences(nextItems);
|
|
3660
3682
|
this.#items = nextItems;
|
|
3683
|
+
this.#pendingDeletes.clear();
|
|
3661
3684
|
emitListStateChange(this, { type: "set" });
|
|
3662
3685
|
}
|
|
3663
3686
|
/**
|
|
@@ -3673,14 +3696,16 @@ var ListState = class {
|
|
|
3673
3696
|
this.unshiftAll(items);
|
|
3674
3697
|
}
|
|
3675
3698
|
/** Prepends an array of items. */
|
|
3676
|
-
unshiftAll(items) {
|
|
3699
|
+
unshiftAll(items, animation) {
|
|
3677
3700
|
if (items.length === 0) return;
|
|
3678
3701
|
assertUniqueItemReferences(items, this.#items);
|
|
3702
|
+
const normalizedAnimation = normalizeInsertAnimation(animation);
|
|
3679
3703
|
if (this.position != null) this.position += items.length;
|
|
3680
3704
|
this.#items = items.concat(this.#items);
|
|
3681
3705
|
emitListStateChange(this, {
|
|
3682
3706
|
type: "unshift",
|
|
3683
|
-
count: items.length
|
|
3707
|
+
count: items.length,
|
|
3708
|
+
animation: normalizedAnimation
|
|
3684
3709
|
});
|
|
3685
3710
|
}
|
|
3686
3711
|
/** Appends one or more items. */
|
|
@@ -3688,13 +3713,15 @@ var ListState = class {
|
|
|
3688
3713
|
this.pushAll(items);
|
|
3689
3714
|
}
|
|
3690
3715
|
/** Appends an array of items. */
|
|
3691
|
-
pushAll(items) {
|
|
3716
|
+
pushAll(items, animation) {
|
|
3692
3717
|
if (items.length === 0) return;
|
|
3693
3718
|
assertUniqueItemReferences(items, this.#items);
|
|
3719
|
+
const normalizedAnimation = normalizeInsertAnimation(animation);
|
|
3694
3720
|
this.#items.push(...items);
|
|
3695
3721
|
emitListStateChange(this, {
|
|
3696
3722
|
type: "push",
|
|
3697
|
-
count: items.length
|
|
3723
|
+
count: items.length,
|
|
3724
|
+
animation: normalizedAnimation
|
|
3698
3725
|
});
|
|
3699
3726
|
}
|
|
3700
3727
|
/**
|
|
@@ -3705,6 +3732,7 @@ var ListState = class {
|
|
|
3705
3732
|
if (targetItem === nextItem) throw new Error("update() requires nextItem to be a new object reference.");
|
|
3706
3733
|
const index = this.#items.indexOf(targetItem);
|
|
3707
3734
|
if (index < 0) throw new Error("update() targetItem is not present in the list.");
|
|
3735
|
+
if (this.#pendingDeletes.has(targetItem)) throw new Error("update() targetItem is pending deletion.");
|
|
3708
3736
|
if (this.#items.includes(nextItem)) throw new Error("update() nextItem is already present in the list.");
|
|
3709
3737
|
const prevItem = this.#items[index];
|
|
3710
3738
|
this.#items[index] = nextItem;
|
|
@@ -3716,6 +3744,47 @@ var ListState = class {
|
|
|
3716
3744
|
});
|
|
3717
3745
|
}
|
|
3718
3746
|
/**
|
|
3747
|
+
* Starts deleting an existing item by object identity.
|
|
3748
|
+
*/
|
|
3749
|
+
delete(item, animation) {
|
|
3750
|
+
if (!isObjectIdentityCandidate(item)) throw new TypeError("delete() only supports object items.");
|
|
3751
|
+
if (this.#items.indexOf(item) < 0) throw new Error("delete() item is not present in the list.");
|
|
3752
|
+
if (this.#pendingDeletes.has(item)) return;
|
|
3753
|
+
const normalizedAnimation = normalizeDeleteAnimation(animation);
|
|
3754
|
+
if (!((normalizedAnimation?.duration ?? 0) > 0)) {
|
|
3755
|
+
this.#pendingDeletes.add(item);
|
|
3756
|
+
this.finalizeDelete(item);
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
this.#pendingDeletes.add(item);
|
|
3760
|
+
emitListStateChange(this, {
|
|
3761
|
+
type: "delete",
|
|
3762
|
+
item,
|
|
3763
|
+
animation: normalizedAnimation
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
/**
|
|
3767
|
+
* Finalizes a pending delete by removing the item from the list.
|
|
3768
|
+
*/
|
|
3769
|
+
finalizeDelete(item) {
|
|
3770
|
+
if (!this.#pendingDeletes.has(item)) return;
|
|
3771
|
+
const index = this.#items.indexOf(item);
|
|
3772
|
+
this.#pendingDeletes.delete(item);
|
|
3773
|
+
if (index < 0) return;
|
|
3774
|
+
this.#items.splice(index, 1);
|
|
3775
|
+
if (this.#items.length === 0) {
|
|
3776
|
+
this.position = void 0;
|
|
3777
|
+
this.offset = 0;
|
|
3778
|
+
} else if (this.position != null) {
|
|
3779
|
+
if (this.position > index) this.position -= 1;
|
|
3780
|
+
else if (this.position === index) this.position = Math.min(index, this.#items.length - 1);
|
|
3781
|
+
}
|
|
3782
|
+
emitListStateChange(this, {
|
|
3783
|
+
type: "delete-finalize",
|
|
3784
|
+
item
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3787
|
+
/**
|
|
3719
3788
|
* Sets the current anchor item and pixel offset.
|
|
3720
3789
|
*/
|
|
3721
3790
|
setAnchor(position, offset = 0) {
|
|
@@ -3729,6 +3798,7 @@ var ListState = class {
|
|
|
3729
3798
|
const nextItems = [...items];
|
|
3730
3799
|
assertUniqueItemReferences(nextItems);
|
|
3731
3800
|
this.#items = nextItems;
|
|
3801
|
+
this.#pendingDeletes.clear();
|
|
3732
3802
|
this.offset = 0;
|
|
3733
3803
|
this.position = void 0;
|
|
3734
3804
|
emitListStateChange(this, { type: "reset" });
|
|
@@ -3804,43 +3874,857 @@ function memoRenderItemBy(keyOf, renderItem, options = {}) {
|
|
|
3804
3874
|
});
|
|
3805
3875
|
}
|
|
3806
3876
|
//#endregion
|
|
3807
|
-
//#region src/renderer/virtualized/base.ts
|
|
3808
|
-
|
|
3809
|
-
function clamp$3(value, min, max) {
|
|
3877
|
+
//#region src/renderer/virtualized/base-animation.ts
|
|
3878
|
+
function clamp$1(value, min, max) {
|
|
3810
3879
|
return Math.min(Math.max(value, min), max);
|
|
3811
3880
|
}
|
|
3812
3881
|
function sameState(state, position, offset) {
|
|
3813
3882
|
return Object.is(state.position, position) && Object.is(state.offset, offset);
|
|
3814
3883
|
}
|
|
3884
|
+
function resolveJumpSegmentIndex(anchor, direction, itemCount) {
|
|
3885
|
+
if (itemCount <= 0) return;
|
|
3886
|
+
if (direction > 0) {
|
|
3887
|
+
if (anchor >= itemCount) return;
|
|
3888
|
+
return clamp$1(Math.floor(anchor), 0, itemCount - 1);
|
|
3889
|
+
}
|
|
3890
|
+
if (anchor <= 0) return;
|
|
3891
|
+
return clamp$1(Math.ceil(anchor) - 1, 0, itemCount - 1);
|
|
3892
|
+
}
|
|
3893
|
+
function buildJumpPath(itemCount, readItemHeight, startAnchor, targetAnchor) {
|
|
3894
|
+
const clampedStartAnchor = clamp$1(startAnchor, 0, itemCount);
|
|
3895
|
+
const clampedTargetAnchor = clamp$1(targetAnchor, 0, itemCount);
|
|
3896
|
+
if (itemCount <= 0 || !Number.isFinite(clampedStartAnchor) || !Number.isFinite(clampedTargetAnchor) || Math.abs(clampedTargetAnchor - clampedStartAnchor) <= Number.EPSILON) return {
|
|
3897
|
+
startAnchor: clampedStartAnchor,
|
|
3898
|
+
targetAnchor: clampedTargetAnchor,
|
|
3899
|
+
totalDistance: 0,
|
|
3900
|
+
segments: []
|
|
3901
|
+
};
|
|
3902
|
+
const direction = clampedTargetAnchor > clampedStartAnchor ? 1 : -1;
|
|
3903
|
+
const segments = [];
|
|
3904
|
+
let cursor = clampedStartAnchor;
|
|
3905
|
+
let totalDistance = 0;
|
|
3906
|
+
while (direction > 0 ? cursor < clampedTargetAnchor : cursor > clampedTargetAnchor) {
|
|
3907
|
+
const index = resolveJumpSegmentIndex(cursor, direction, itemCount);
|
|
3908
|
+
if (index == null) break;
|
|
3909
|
+
const nextCursor = direction > 0 ? Math.min(clampedTargetAnchor, index + 1) : Math.max(clampedTargetAnchor, index);
|
|
3910
|
+
if (Math.abs(nextCursor - cursor) <= Number.EPSILON) {
|
|
3911
|
+
cursor = nextCursor;
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3914
|
+
const height = readItemHeight(index);
|
|
3915
|
+
const distance = height > 0 ? Math.abs(nextCursor - cursor) * height : 0;
|
|
3916
|
+
if (distance > 0) {
|
|
3917
|
+
segments.push({
|
|
3918
|
+
anchorStart: cursor,
|
|
3919
|
+
anchorEnd: nextCursor,
|
|
3920
|
+
distanceStart: totalDistance,
|
|
3921
|
+
distanceEnd: totalDistance + distance
|
|
3922
|
+
});
|
|
3923
|
+
totalDistance += distance;
|
|
3924
|
+
}
|
|
3925
|
+
cursor = nextCursor;
|
|
3926
|
+
}
|
|
3927
|
+
return {
|
|
3928
|
+
startAnchor: clampedStartAnchor,
|
|
3929
|
+
targetAnchor: clampedTargetAnchor,
|
|
3930
|
+
totalDistance,
|
|
3931
|
+
segments
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3815
3934
|
function smoothstep(value) {
|
|
3816
3935
|
return value * value * (3 - 2 * value);
|
|
3817
3936
|
}
|
|
3818
3937
|
function getProgress(startTime, duration, now) {
|
|
3819
3938
|
if (!(duration > 0)) return 1;
|
|
3820
|
-
return clamp$
|
|
3939
|
+
return clamp$1((now - startTime) / duration, 0, 1);
|
|
3821
3940
|
}
|
|
3822
3941
|
function interpolate(from, to, startTime, duration, now) {
|
|
3823
3942
|
const progress = getProgress(startTime, duration, now);
|
|
3824
3943
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
3825
3944
|
return from + (to - from) * eased;
|
|
3826
3945
|
}
|
|
3946
|
+
function getAnchorAtDistance(path, distance) {
|
|
3947
|
+
if (!(path.totalDistance > 0) || path.segments.length === 0) return path.targetAnchor;
|
|
3948
|
+
const clampedDistance = clamp$1(distance, 0, path.totalDistance);
|
|
3949
|
+
if (clampedDistance <= 0) return path.startAnchor;
|
|
3950
|
+
if (clampedDistance >= path.totalDistance) return path.targetAnchor;
|
|
3951
|
+
for (const segment of path.segments) {
|
|
3952
|
+
if (clampedDistance >= segment.distanceEnd) continue;
|
|
3953
|
+
const span = segment.distanceEnd - segment.distanceStart;
|
|
3954
|
+
if (!(span > 0)) continue;
|
|
3955
|
+
const ratio = (clampedDistance - segment.distanceStart) / span;
|
|
3956
|
+
return segment.anchorStart + (segment.anchorEnd - segment.anchorStart) * ratio;
|
|
3957
|
+
}
|
|
3958
|
+
return path.targetAnchor;
|
|
3959
|
+
}
|
|
3827
3960
|
function getNow() {
|
|
3828
3961
|
return globalThis.performance?.now() ?? Date.now();
|
|
3829
3962
|
}
|
|
3963
|
+
//#endregion
|
|
3964
|
+
//#region src/renderer/virtualized/frame-session.ts
|
|
3965
|
+
function prepareFrameSession(params) {
|
|
3966
|
+
let solution = params.resolveVisibleWindow(params.now);
|
|
3967
|
+
let viewportTranslateY = params.getViewportTranslateY(params.now);
|
|
3968
|
+
params.captureVisibleItemSnapshot(solution, viewportTranslateY);
|
|
3969
|
+
const requestSettleRedraw = params.pruneTransitionAnimations(solution.window, params.now);
|
|
3970
|
+
if (requestSettleRedraw) {
|
|
3971
|
+
solution = params.resolveVisibleWindow(params.now);
|
|
3972
|
+
viewportTranslateY = params.getViewportTranslateY(params.now);
|
|
3973
|
+
params.captureVisibleItemSnapshot(solution, viewportTranslateY);
|
|
3974
|
+
}
|
|
3975
|
+
return {
|
|
3976
|
+
solution,
|
|
3977
|
+
viewportTranslateY,
|
|
3978
|
+
requestSettleRedraw
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
//#endregion
|
|
3982
|
+
//#region src/renderer/virtualized/jump-controller.ts
|
|
3983
|
+
var JumpController = class {
|
|
3984
|
+
#autoFollowLatch;
|
|
3985
|
+
#controlledState;
|
|
3986
|
+
#jumpAnimation;
|
|
3987
|
+
#lastCommittedState;
|
|
3988
|
+
#hasPendingListChange = false;
|
|
3989
|
+
#options;
|
|
3990
|
+
constructor(options) {
|
|
3991
|
+
this.#options = options;
|
|
3992
|
+
}
|
|
3993
|
+
beforeFrame() {
|
|
3994
|
+
const currentState = this.#options.readListState();
|
|
3995
|
+
if (!this.#hasPendingListChange && this.#jumpAnimation == null && this.#lastCommittedState != null && !sameState(this.#lastCommittedState, currentState.position, currentState.offset)) this.#clearAutoFollowLatch();
|
|
3996
|
+
this.#hasPendingListChange = false;
|
|
3997
|
+
}
|
|
3998
|
+
prepare(now) {
|
|
3999
|
+
const animation = this.#jumpAnimation;
|
|
4000
|
+
if (animation == null) return false;
|
|
4001
|
+
if (this.#options.getItemCount() === 0) {
|
|
4002
|
+
this.#cancelJumpAnimation();
|
|
4003
|
+
return false;
|
|
4004
|
+
}
|
|
4005
|
+
if (this.#controlledState != null && !sameState(this.#controlledState, this.#options.readListState().position, this.#options.readListState().offset)) {
|
|
4006
|
+
this.#clearAutoFollowLatch();
|
|
4007
|
+
this.#cancelJumpAnimation();
|
|
4008
|
+
return false;
|
|
4009
|
+
}
|
|
4010
|
+
const progress = getProgress(animation.startTime, animation.duration, now);
|
|
4011
|
+
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
4012
|
+
const anchor = getAnchorAtDistance(animation.path, animation.path.totalDistance * eased);
|
|
4013
|
+
this.#options.applyAnchor(anchor);
|
|
4014
|
+
animation.needsMoreFrames = progress < 1;
|
|
4015
|
+
return animation.needsMoreFrames;
|
|
4016
|
+
}
|
|
4017
|
+
finishFrame(requestRedraw) {
|
|
4018
|
+
const animation = this.#jumpAnimation;
|
|
4019
|
+
if (animation == null) return requestRedraw;
|
|
4020
|
+
if (animation.needsMoreFrames) {
|
|
4021
|
+
this.#controlledState = this.#options.readListState();
|
|
4022
|
+
return true;
|
|
4023
|
+
}
|
|
4024
|
+
const onComplete = animation.onComplete;
|
|
4025
|
+
this.#cancelJumpAnimation();
|
|
4026
|
+
onComplete?.();
|
|
4027
|
+
return requestRedraw || this.#jumpAnimation != null;
|
|
4028
|
+
}
|
|
4029
|
+
commit(state) {
|
|
4030
|
+
this.#lastCommittedState = {
|
|
4031
|
+
position: state.position,
|
|
4032
|
+
offset: state.offset
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
jumpTo(index, options = {}) {
|
|
4036
|
+
this.#clearAutoFollowLatch();
|
|
4037
|
+
if (this.#options.getItemCount() === 0) {
|
|
4038
|
+
this.#cancelJumpAnimation();
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4041
|
+
this.#startJumpToIndex(index, options, { kind: "manual" });
|
|
4042
|
+
}
|
|
4043
|
+
handleListStateChange(change) {
|
|
4044
|
+
this.#hasPendingListChange = true;
|
|
4045
|
+
const followChange = this.#resolveAutoFollowChange(change);
|
|
4046
|
+
const canChainAutoFollow = followChange != null ? this.#shouldChainAutoFollow(followChange.direction, followChange.animation) : false;
|
|
4047
|
+
const canLatchAutoFollow = followChange != null ? this.#shouldLatchAutoFollow(followChange.direction, followChange.count, followChange.animation) : false;
|
|
4048
|
+
const canSnapshotAutoFollow = followChange != null ? this.#shouldAutoFollowFromSnapshot(followChange.direction, followChange.count, followChange.animation) : false;
|
|
4049
|
+
if (followChange != null && (canSnapshotAutoFollow || canChainAutoFollow || canLatchAutoFollow)) {
|
|
4050
|
+
if (canChainAutoFollow) this.#rebaseJumpAnchorForBoundaryInsert(followChange.direction, followChange.count, getNow());
|
|
4051
|
+
this.#autoFollowLatch = followChange.direction;
|
|
4052
|
+
this.#startJumpToIndex(followChange.direction === "push" ? this.#options.getItemCount() - 1 : 0, {
|
|
4053
|
+
block: followChange.direction === "push" ? "end" : "start",
|
|
4054
|
+
duration: followChange.animation?.duration
|
|
4055
|
+
}, {
|
|
4056
|
+
kind: "auto-follow",
|
|
4057
|
+
direction: followChange.direction
|
|
4058
|
+
});
|
|
4059
|
+
return {
|
|
4060
|
+
...followChange.change,
|
|
4061
|
+
animation: void 0
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
return change;
|
|
4065
|
+
}
|
|
4066
|
+
#cancelJumpAnimation() {
|
|
4067
|
+
this.#jumpAnimation = void 0;
|
|
4068
|
+
this.#controlledState = void 0;
|
|
4069
|
+
}
|
|
4070
|
+
#startJumpToIndex(index, options, source) {
|
|
4071
|
+
const targetIndex = this.#options.clampItemIndex(index);
|
|
4072
|
+
const currentState = this.#options.normalizeListState(this.#options.readListState());
|
|
4073
|
+
const targetBlock = options.block ?? this.#options.getDefaultJumpBlock();
|
|
4074
|
+
const targetAnchor = this.#options.getTargetAnchor(targetIndex, targetBlock);
|
|
4075
|
+
if (!(options.animated ?? true)) {
|
|
4076
|
+
this.#cancelJumpAnimation();
|
|
4077
|
+
this.#options.applyAnchor(targetAnchor);
|
|
4078
|
+
options.onComplete?.();
|
|
4079
|
+
return;
|
|
4080
|
+
}
|
|
4081
|
+
const startAnchor = this.#options.readAnchor(currentState);
|
|
4082
|
+
if (!Number.isFinite(startAnchor)) {
|
|
4083
|
+
this.#cancelJumpAnimation();
|
|
4084
|
+
this.#options.applyAnchor(targetAnchor);
|
|
4085
|
+
options.onComplete?.();
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
const path = buildJumpPath(this.#options.getItemCount(), this.#options.getItemHeight, startAnchor, targetAnchor);
|
|
4089
|
+
const duration = clamp$1(options.duration ?? this.#options.minJumpDuration + path.totalDistance * this.#options.jumpDurationPerPixel, 0, this.#options.maxJumpDuration);
|
|
4090
|
+
if (duration <= 0 || path.totalDistance <= Number.EPSILON) {
|
|
4091
|
+
this.#cancelJumpAnimation();
|
|
4092
|
+
this.#options.applyAnchor(targetAnchor);
|
|
4093
|
+
options.onComplete?.();
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
this.#jumpAnimation = {
|
|
4097
|
+
path,
|
|
4098
|
+
startTime: getNow(),
|
|
4099
|
+
duration,
|
|
4100
|
+
needsMoreFrames: true,
|
|
4101
|
+
onComplete: options.onComplete,
|
|
4102
|
+
source
|
|
4103
|
+
};
|
|
4104
|
+
this.#controlledState = this.#options.readListState();
|
|
4105
|
+
}
|
|
4106
|
+
#resolveAutoFollowChange(change) {
|
|
4107
|
+
switch (change.type) {
|
|
4108
|
+
case "push":
|
|
4109
|
+
case "unshift": return change.animation?.followIfAtBoundary === true ? {
|
|
4110
|
+
change,
|
|
4111
|
+
direction: change.type,
|
|
4112
|
+
count: change.count,
|
|
4113
|
+
animation: change.animation
|
|
4114
|
+
} : void 0;
|
|
4115
|
+
default: return;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
#shouldAutoFollowFromSnapshot(direction, count, animation) {
|
|
4119
|
+
if (animation?.followIfAtBoundary !== true) return false;
|
|
4120
|
+
return this.#options.canAutoFollowBoundaryInsert(direction, count, this.#options.readListState().position, this.#options.readListState().offset);
|
|
4121
|
+
}
|
|
4122
|
+
#shouldLatchAutoFollow(direction, count, animation) {
|
|
4123
|
+
if (animation?.followIfAtBoundary !== true) return false;
|
|
4124
|
+
if (!this.#matchesLastCommittedStateAfterBoundaryInsert(direction, count)) {
|
|
4125
|
+
this.#clearAutoFollowLatch();
|
|
4126
|
+
return false;
|
|
4127
|
+
}
|
|
4128
|
+
return this.#autoFollowLatch === direction;
|
|
4129
|
+
}
|
|
4130
|
+
#shouldChainAutoFollow(direction, animation) {
|
|
4131
|
+
if (animation?.followIfAtBoundary !== true) return false;
|
|
4132
|
+
return this.#jumpAnimation?.source.kind === "auto-follow" ? this.#jumpAnimation.source.direction === direction : false;
|
|
4133
|
+
}
|
|
4134
|
+
#rebaseJumpAnchorForBoundaryInsert(direction, count, now) {
|
|
4135
|
+
const animation = this.#jumpAnimation;
|
|
4136
|
+
if (animation == null) return;
|
|
4137
|
+
const progress = getProgress(animation.startTime, animation.duration, now);
|
|
4138
|
+
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
4139
|
+
const anchorAtNow = getAnchorAtDistance(animation.path, animation.path.totalDistance * eased);
|
|
4140
|
+
this.#cancelJumpAnimation();
|
|
4141
|
+
this.#options.applyAnchor(direction === "unshift" ? anchorAtNow + count : anchorAtNow);
|
|
4142
|
+
}
|
|
4143
|
+
#matchesLastCommittedStateAfterBoundaryInsert(direction, count) {
|
|
4144
|
+
const state = this.#lastCommittedState;
|
|
4145
|
+
if (state == null) return false;
|
|
4146
|
+
return sameState({
|
|
4147
|
+
position: direction === "unshift" && state.position != null ? state.position + count : state.position,
|
|
4148
|
+
offset: state.offset
|
|
4149
|
+
}, this.#options.readListState().position, this.#options.readListState().offset);
|
|
4150
|
+
}
|
|
4151
|
+
#clearAutoFollowLatch() {
|
|
4152
|
+
this.#autoFollowLatch = void 0;
|
|
4153
|
+
}
|
|
4154
|
+
};
|
|
4155
|
+
//#endregion
|
|
4156
|
+
//#region src/renderer/virtualized/transition-snapshot.ts
|
|
4157
|
+
var VisibilitySnapshot = class {
|
|
4158
|
+
#drawnItems = /* @__PURE__ */ new Set();
|
|
4159
|
+
#visibleItems = /* @__PURE__ */ new Set();
|
|
4160
|
+
#hasSnapshot = false;
|
|
4161
|
+
#snapshotState;
|
|
4162
|
+
#emptyState;
|
|
4163
|
+
#coversShortList = false;
|
|
4164
|
+
#topGap = 0;
|
|
4165
|
+
#bottomGap = 0;
|
|
4166
|
+
#atStartBoundary = false;
|
|
4167
|
+
#atEndBoundary = false;
|
|
4168
|
+
get coversShortList() {
|
|
4169
|
+
return this.#hasSnapshot && this.#snapshotState != null && this.#coversShortList;
|
|
4170
|
+
}
|
|
4171
|
+
get topGap() {
|
|
4172
|
+
return this.#topGap;
|
|
4173
|
+
}
|
|
4174
|
+
get bottomGap() {
|
|
4175
|
+
return this.#bottomGap;
|
|
4176
|
+
}
|
|
4177
|
+
capture(window, _resolutionPath, items, viewportHeight, snapshotState, extraShift, readVisibleRange) {
|
|
4178
|
+
const nextDrawnItems = /* @__PURE__ */ new Set();
|
|
4179
|
+
const nextVisibleItems = /* @__PURE__ */ new Set();
|
|
4180
|
+
let minVisibleIndex = Number.POSITIVE_INFINITY;
|
|
4181
|
+
let maxVisibleIndex = Number.NEGATIVE_INFINITY;
|
|
4182
|
+
let topMostY = Number.POSITIVE_INFINITY;
|
|
4183
|
+
let bottomMostY = Number.NEGATIVE_INFINITY;
|
|
4184
|
+
const effectiveShift = window.shift + extraShift;
|
|
4185
|
+
for (const { idx, offset, height } of window.drawList) {
|
|
4186
|
+
minVisibleIndex = Math.min(minVisibleIndex, idx);
|
|
4187
|
+
maxVisibleIndex = Math.max(maxVisibleIndex, idx);
|
|
4188
|
+
const y = offset + effectiveShift;
|
|
4189
|
+
topMostY = Math.min(topMostY, y);
|
|
4190
|
+
bottomMostY = Math.max(bottomMostY, y + height);
|
|
4191
|
+
const item = items[idx];
|
|
4192
|
+
if (item != null) nextDrawnItems.add(item);
|
|
4193
|
+
if (item == null || readVisibleRange(y, height) == null) continue;
|
|
4194
|
+
nextVisibleItems.add(item);
|
|
4195
|
+
}
|
|
4196
|
+
this.#drawnItems = nextDrawnItems;
|
|
4197
|
+
this.#visibleItems = nextVisibleItems;
|
|
4198
|
+
this.#hasSnapshot = true;
|
|
4199
|
+
this.#snapshotState = snapshotState;
|
|
4200
|
+
this.#emptyState = items.length === 0 && window.drawList.length === 0 ? snapshotState : void 0;
|
|
4201
|
+
const contentHeight = bottomMostY - topMostY;
|
|
4202
|
+
this.#coversShortList = window.drawList.length > 0 && items.length > 0 && window.drawList.length === items.length && minVisibleIndex === 0 && maxVisibleIndex === items.length - 1 && topMostY >= -Number.EPSILON && bottomMostY <= viewportHeight + Number.EPSILON && contentHeight < viewportHeight - Number.EPSILON;
|
|
4203
|
+
this.#topGap = this.#coversShortList ? Math.max(0, topMostY) : 0;
|
|
4204
|
+
this.#bottomGap = this.#coversShortList ? Math.max(0, viewportHeight - bottomMostY) : 0;
|
|
4205
|
+
this.#atStartBoundary = window.drawList.length > 0 && items.length > 0 && minVisibleIndex === 0 && topMostY >= -Number.EPSILON;
|
|
4206
|
+
this.#atEndBoundary = window.drawList.length > 0 && items.length > 0 && maxVisibleIndex === items.length - 1 && bottomMostY <= viewportHeight + Number.EPSILON;
|
|
4207
|
+
}
|
|
4208
|
+
matchesCurrentState(position, offset) {
|
|
4209
|
+
return this.#hasSnapshot && this.#snapshotState != null && sameState(this.#snapshotState, position, offset);
|
|
4210
|
+
}
|
|
4211
|
+
matchesBoundaryInsertState(direction, count, position, offset) {
|
|
4212
|
+
if (!this.coversShortList || this.#snapshotState == null) return false;
|
|
4213
|
+
return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
|
|
4214
|
+
}
|
|
4215
|
+
matchesFollowBoundaryInsertState(direction, count, position, offset) {
|
|
4216
|
+
if (!this.#hasSnapshot || this.#snapshotState == null) return false;
|
|
4217
|
+
if (direction === "push" ? !this.#atEndBoundary : !this.#atStartBoundary) return false;
|
|
4218
|
+
return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
|
|
4219
|
+
}
|
|
4220
|
+
matchesEmptyBoundaryInsertState(direction, count, position, offset) {
|
|
4221
|
+
const emptyState = this.#emptyState;
|
|
4222
|
+
if (!this.#hasSnapshot || emptyState == null) return false;
|
|
4223
|
+
return sameState({
|
|
4224
|
+
position: direction === "unshift" && emptyState.position != null ? emptyState.position + count : emptyState.position,
|
|
4225
|
+
offset: emptyState.offset
|
|
4226
|
+
}, position, offset);
|
|
4227
|
+
}
|
|
4228
|
+
isVisible(item) {
|
|
4229
|
+
return this.#visibleItems.has(item);
|
|
4230
|
+
}
|
|
4231
|
+
tracks(item, retention) {
|
|
4232
|
+
return retention === "drawn" ? this.#drawnItems.has(item) : this.#visibleItems.has(item);
|
|
4233
|
+
}
|
|
4234
|
+
reset() {
|
|
4235
|
+
this.#drawnItems.clear();
|
|
4236
|
+
this.#visibleItems.clear();
|
|
4237
|
+
this.#hasSnapshot = false;
|
|
4238
|
+
this.#snapshotState = void 0;
|
|
4239
|
+
this.#emptyState = void 0;
|
|
4240
|
+
this.#coversShortList = false;
|
|
4241
|
+
this.#topGap = 0;
|
|
4242
|
+
this.#bottomGap = 0;
|
|
4243
|
+
this.#atStartBoundary = false;
|
|
4244
|
+
this.#atEndBoundary = false;
|
|
4245
|
+
}
|
|
4246
|
+
#matchesStateAfterBoundaryInsert(direction, count, position, offset) {
|
|
4247
|
+
const snapshotState = this.#snapshotState;
|
|
4248
|
+
if (snapshotState == null) return false;
|
|
4249
|
+
return sameState({
|
|
4250
|
+
position: direction === "unshift" && snapshotState.position != null ? snapshotState.position + count : snapshotState.position,
|
|
4251
|
+
offset: snapshotState.offset
|
|
4252
|
+
}, position, offset);
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
4255
|
+
//#endregion
|
|
4256
|
+
//#region src/renderer/virtualized/transition-store.ts
|
|
4257
|
+
var TransitionStore = class {
|
|
4258
|
+
#transitions = /* @__PURE__ */ new Map();
|
|
4259
|
+
get size() {
|
|
4260
|
+
return this.#transitions.size;
|
|
4261
|
+
}
|
|
4262
|
+
has(item) {
|
|
4263
|
+
return this.#transitions.has(item);
|
|
4264
|
+
}
|
|
4265
|
+
set(item, transition) {
|
|
4266
|
+
this.#transitions.set(item, transition);
|
|
4267
|
+
}
|
|
4268
|
+
replace(prevItem, nextItem, transition) {
|
|
4269
|
+
this.#transitions.delete(prevItem);
|
|
4270
|
+
this.#transitions.set(nextItem, transition);
|
|
4271
|
+
}
|
|
4272
|
+
delete(item) {
|
|
4273
|
+
const transition = this.#transitions.get(item);
|
|
4274
|
+
if (transition != null) this.#transitions.delete(item);
|
|
4275
|
+
return transition;
|
|
4276
|
+
}
|
|
4277
|
+
readActive(item, now) {
|
|
4278
|
+
const transition = this.#transitions.get(item);
|
|
4279
|
+
if (transition == null) return;
|
|
4280
|
+
return this.#isComplete(transition, now) ? void 0 : transition;
|
|
4281
|
+
}
|
|
4282
|
+
prepare(now) {
|
|
4283
|
+
for (const transition of this.#transitions.values()) if (!this.#isComplete(transition, now)) return true;
|
|
4284
|
+
return false;
|
|
4285
|
+
}
|
|
4286
|
+
findCompleted(now) {
|
|
4287
|
+
return [...this.#transitions.entries()].filter(([, transition]) => this.#isComplete(transition, now)).map(([item, transition]) => ({
|
|
4288
|
+
item,
|
|
4289
|
+
transition
|
|
4290
|
+
}));
|
|
4291
|
+
}
|
|
4292
|
+
findInvisible(snapshot) {
|
|
4293
|
+
return [...this.#transitions.entries()].filter(([item, transition]) => !snapshot.tracks(item, transition.retention)).map(([item, transition]) => ({
|
|
4294
|
+
item,
|
|
4295
|
+
transition
|
|
4296
|
+
}));
|
|
4297
|
+
}
|
|
4298
|
+
reset() {
|
|
4299
|
+
this.#transitions.clear();
|
|
4300
|
+
}
|
|
4301
|
+
#isComplete(transition, now) {
|
|
4302
|
+
return getProgress(transition.height.startTime, transition.height.duration, now) >= 1;
|
|
4303
|
+
}
|
|
4304
|
+
};
|
|
4305
|
+
//#endregion
|
|
4306
|
+
//#region src/renderer/virtualized/transition-planner.ts
|
|
4307
|
+
function isFinitePositive(value) {
|
|
4308
|
+
return Number.isFinite(value) && value > 0;
|
|
4309
|
+
}
|
|
4310
|
+
function normalizeDuration(duration) {
|
|
4311
|
+
return Math.max(0, typeof duration === "number" && Number.isFinite(duration) ? duration : 0);
|
|
4312
|
+
}
|
|
4313
|
+
function createScalarAnimation(from, to, startTime, duration) {
|
|
4314
|
+
return {
|
|
4315
|
+
from,
|
|
4316
|
+
to,
|
|
4317
|
+
startTime,
|
|
4318
|
+
duration
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
function createLayerAnimation(node, fromAlpha, toAlpha, startTime, duration, fromTranslateY, toTranslateY) {
|
|
4322
|
+
return {
|
|
4323
|
+
node,
|
|
4324
|
+
alpha: createScalarAnimation(fromAlpha, toAlpha, startTime, duration),
|
|
4325
|
+
translateY: createScalarAnimation(fromTranslateY, toTranslateY, startTime, duration)
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
function findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) {
|
|
4329
|
+
if (index < 0) return;
|
|
4330
|
+
const solution = resolveVisibleWindow();
|
|
4331
|
+
for (const entry of solution.window.drawList) {
|
|
4332
|
+
if (entry.idx !== index) continue;
|
|
4333
|
+
if (readVisibleRange(entry.offset + solution.window.shift, entry.height) != null) return entry;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
function isIndexVisible(index, resolveVisibleWindow, readVisibleRange) {
|
|
4337
|
+
return findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) != null;
|
|
4338
|
+
}
|
|
4339
|
+
function resolveAnimationEligibility(params) {
|
|
4340
|
+
if (params.index < 0) return false;
|
|
4341
|
+
if (params.snapshot.matchesCurrentState(params.position, params.offset)) return params.snapshot.isVisible(params.item);
|
|
4342
|
+
return isIndexVisible(params.index, params.resolveVisibleWindow, params.readVisibleRange);
|
|
4343
|
+
}
|
|
4344
|
+
function resolveBoundaryInsertStrategy(direction, underflowAlign, coversShortListSnapshot) {
|
|
4345
|
+
if (!coversShortListSnapshot) return "hard-cut";
|
|
4346
|
+
if (direction === "push" && underflowAlign === "bottom" || direction === "unshift" && underflowAlign === "top") return "viewport-slide";
|
|
4347
|
+
return "item-enter";
|
|
4348
|
+
}
|
|
4349
|
+
function sampleScalarAnimation(animation, now) {
|
|
4350
|
+
return interpolate(animation.from, animation.to, animation.startTime, animation.duration, now);
|
|
4351
|
+
}
|
|
4352
|
+
function sampleLayerAnimation(layer, now) {
|
|
4353
|
+
const alpha = sampleScalarAnimation(layer.alpha, now);
|
|
4354
|
+
if (alpha <= .001) return;
|
|
4355
|
+
return {
|
|
4356
|
+
alpha,
|
|
4357
|
+
node: layer.node,
|
|
4358
|
+
translateY: sampleScalarAnimation(layer.translateY, now)
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
function sampleTransition(transition, now) {
|
|
4362
|
+
return {
|
|
4363
|
+
kind: transition.kind,
|
|
4364
|
+
slotHeight: sampleScalarAnimation(transition.height, now),
|
|
4365
|
+
layers: transition.layers.map((layer) => sampleLayerAnimation(layer, now)).filter((layer) => layer != null),
|
|
4366
|
+
retention: transition.retention
|
|
4367
|
+
};
|
|
4368
|
+
}
|
|
4369
|
+
function planExistingItemTransition(params) {
|
|
4370
|
+
if (!params.canAnimate || params.duration <= 0) return;
|
|
4371
|
+
if (params.kind === "update" && !Number.isFinite(params.nextHeight)) return;
|
|
4372
|
+
const layers = [];
|
|
4373
|
+
if (params.currentVisualState.alpha > .001) layers.push(createLayerAnimation(params.currentVisualState.node, params.currentVisualState.alpha, 0, params.now, params.duration, params.currentVisualState.translateY, 0));
|
|
4374
|
+
if (params.kind === "update") {
|
|
4375
|
+
layers.push(createLayerAnimation(params.nextNode, 0, 1, params.now, params.duration, params.currentVisualState.translateY, 0));
|
|
4376
|
+
return {
|
|
4377
|
+
kind: "update",
|
|
4378
|
+
layers,
|
|
4379
|
+
height: createScalarAnimation(params.currentVisualState.height, params.nextHeight, params.now, params.duration),
|
|
4380
|
+
retention: "visible"
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
return {
|
|
4384
|
+
kind: "delete",
|
|
4385
|
+
layers,
|
|
4386
|
+
height: createScalarAnimation(params.currentVisualState.height, 0, params.now, params.duration),
|
|
4387
|
+
retention: "visible"
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
function planViewportShift(params) {
|
|
4391
|
+
if (!isFinitePositive(params.travel) || params.duration <= 0) return;
|
|
4392
|
+
return createScalarAnimation(params.direction === "positive" ? params.currentTranslateY + params.travel : params.currentTranslateY - params.travel, 0, params.now, params.duration);
|
|
4393
|
+
}
|
|
4394
|
+
function planBoundaryInsert(params) {
|
|
4395
|
+
switch (params.strategy) {
|
|
4396
|
+
case "hard-cut": return;
|
|
4397
|
+
case "item-enter": return planBoundaryInsertItems(params);
|
|
4398
|
+
case "viewport-slide": return planBoundaryInsertViewportShift(params);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
function planBoundaryInsertItems(params) {
|
|
4402
|
+
const entries = [];
|
|
4403
|
+
const signedDistance = params.direction === "push" ? 1 : -1;
|
|
4404
|
+
for (const { item, node, height } of params.measuredItems) {
|
|
4405
|
+
if (!Number.isFinite(height) || height < 0) return;
|
|
4406
|
+
const resolvedDistance = typeof params.distance === "number" && Number.isFinite(params.distance) ? Math.max(0, params.distance) : Math.min(24, height);
|
|
4407
|
+
entries.push({
|
|
4408
|
+
item,
|
|
4409
|
+
transition: {
|
|
4410
|
+
kind: "insert",
|
|
4411
|
+
layers: [createLayerAnimation(node, 0, 1, params.now, params.duration, signedDistance * resolvedDistance, 0)],
|
|
4412
|
+
height: createScalarAnimation(height, height, params.now, params.duration),
|
|
4413
|
+
retention: "drawn"
|
|
4414
|
+
}
|
|
4415
|
+
});
|
|
4416
|
+
}
|
|
4417
|
+
return entries.length === 0 ? void 0 : {
|
|
4418
|
+
kind: "item-enter",
|
|
4419
|
+
entries
|
|
4420
|
+
};
|
|
4421
|
+
}
|
|
4422
|
+
function planBoundaryInsertViewportShift(params) {
|
|
4423
|
+
let insertedHeight = 0;
|
|
4424
|
+
for (const { height } of params.measuredItems) {
|
|
4425
|
+
if (!Number.isFinite(height) || height <= 0) return;
|
|
4426
|
+
insertedHeight += height;
|
|
4427
|
+
}
|
|
4428
|
+
if (!isFinitePositive(insertedHeight)) return;
|
|
4429
|
+
const gap = params.direction === "push" ? params.snapshot.topGap : params.snapshot.bottomGap;
|
|
4430
|
+
const travel = Math.min(insertedHeight, gap);
|
|
4431
|
+
const animation = planViewportShift({
|
|
4432
|
+
currentTranslateY: params.currentTranslateY,
|
|
4433
|
+
travel,
|
|
4434
|
+
direction: params.direction === "push" ? "positive" : "negative",
|
|
4435
|
+
now: params.now,
|
|
4436
|
+
duration: params.duration
|
|
4437
|
+
});
|
|
4438
|
+
return animation == null ? void 0 : {
|
|
4439
|
+
kind: "viewport-slide",
|
|
4440
|
+
animation
|
|
4441
|
+
};
|
|
4442
|
+
}
|
|
4443
|
+
function measureBoundaryInsertItems(direction, count, ctx) {
|
|
4444
|
+
const start = direction === "push" ? ctx.items.length - count : 0;
|
|
4445
|
+
const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
|
|
4446
|
+
if (start < 0 || end < start) return;
|
|
4447
|
+
const measured = [];
|
|
4448
|
+
for (let index = start; index < end; index += 1) {
|
|
4449
|
+
const item = ctx.items[index];
|
|
4450
|
+
if (item == null) continue;
|
|
4451
|
+
const node = ctx.renderItem(item);
|
|
4452
|
+
const height = ctx.measureNode(node).height;
|
|
4453
|
+
measured.push({
|
|
4454
|
+
item,
|
|
4455
|
+
node,
|
|
4456
|
+
height
|
|
4457
|
+
});
|
|
4458
|
+
}
|
|
4459
|
+
return measured;
|
|
4460
|
+
}
|
|
4461
|
+
function drawSampledLayers(sampled, y, adapter) {
|
|
4462
|
+
if (sampled.slotHeight <= 0) return false;
|
|
4463
|
+
let result = false;
|
|
4464
|
+
for (const layer of sampled.layers) {
|
|
4465
|
+
const alpha = clamp$1(layer.alpha, 0, 1);
|
|
4466
|
+
if (alpha <= .001) continue;
|
|
4467
|
+
adapter.graphics.save();
|
|
4468
|
+
try {
|
|
4469
|
+
if (typeof adapter.graphics.globalAlpha === "number") adapter.graphics.globalAlpha *= alpha;
|
|
4470
|
+
if (adapter.drawNode(layer.node, 0, y + layer.translateY)) result = true;
|
|
4471
|
+
} finally {
|
|
4472
|
+
adapter.graphics.restore();
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
return result;
|
|
4476
|
+
}
|
|
4477
|
+
function planUpdateTransition(prevItem, nextItem, duration, now, currentVisualState, ctx, snapshot, store) {
|
|
4478
|
+
const nextIndex = ctx.items.indexOf(nextItem);
|
|
4479
|
+
const nextNode = ctx.renderItem(nextItem);
|
|
4480
|
+
const nextHeight = ctx.measureNode(nextNode).height;
|
|
4481
|
+
return planExistingItemTransition({
|
|
4482
|
+
kind: "update",
|
|
4483
|
+
duration: normalizeDuration(duration),
|
|
4484
|
+
canAnimate: resolveAnimationEligibility({
|
|
4485
|
+
index: nextIndex,
|
|
4486
|
+
item: prevItem,
|
|
4487
|
+
position: ctx.position,
|
|
4488
|
+
offset: ctx.offset,
|
|
4489
|
+
snapshot,
|
|
4490
|
+
hasActiveTransition: store.has(prevItem),
|
|
4491
|
+
resolveVisibleWindow: ctx.resolveVisibleWindow,
|
|
4492
|
+
readVisibleRange: ctx.readVisibleRange
|
|
4493
|
+
}),
|
|
4494
|
+
now,
|
|
4495
|
+
currentVisualState,
|
|
4496
|
+
nextNode,
|
|
4497
|
+
nextHeight
|
|
4498
|
+
});
|
|
4499
|
+
}
|
|
4500
|
+
function planDeleteTransition(item, duration, now, currentVisualState, ctx, snapshot, store) {
|
|
4501
|
+
const index = ctx.items.indexOf(item);
|
|
4502
|
+
return planExistingItemTransition({
|
|
4503
|
+
kind: "delete",
|
|
4504
|
+
duration: normalizeDuration(duration),
|
|
4505
|
+
canAnimate: resolveAnimationEligibility({
|
|
4506
|
+
index,
|
|
4507
|
+
item,
|
|
4508
|
+
position: ctx.position,
|
|
4509
|
+
offset: ctx.offset,
|
|
4510
|
+
snapshot,
|
|
4511
|
+
hasActiveTransition: store.has(item),
|
|
4512
|
+
resolveVisibleWindow: ctx.resolveVisibleWindow,
|
|
4513
|
+
readVisibleRange: ctx.readVisibleRange
|
|
4514
|
+
}),
|
|
4515
|
+
now,
|
|
4516
|
+
currentVisualState
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
function planBoundaryInsertTransition(direction, count, duration, distance, now, currentTranslateY, ctx, snapshot) {
|
|
4520
|
+
const normalizedDuration = normalizeDuration(duration);
|
|
4521
|
+
if (count <= 0 || normalizedDuration <= 0) return;
|
|
4522
|
+
const strategy = snapshot.matchesBoundaryInsertState(direction, count, ctx.position, ctx.offset) ? resolveBoundaryInsertStrategy(direction, ctx.underflowAlign, true) : snapshot.matchesEmptyBoundaryInsertState(direction, count, ctx.position, ctx.offset) ? "item-enter" : "hard-cut";
|
|
4523
|
+
if (strategy === "hard-cut") return;
|
|
4524
|
+
const measuredItems = measureBoundaryInsertItems(direction, count, ctx);
|
|
4525
|
+
if (measuredItems == null) return;
|
|
4526
|
+
return planBoundaryInsert({
|
|
4527
|
+
direction,
|
|
4528
|
+
duration: normalizedDuration,
|
|
4529
|
+
distance,
|
|
4530
|
+
now,
|
|
4531
|
+
strategy,
|
|
4532
|
+
snapshot,
|
|
4533
|
+
currentTranslateY,
|
|
4534
|
+
measuredItems
|
|
4535
|
+
});
|
|
4536
|
+
}
|
|
4537
|
+
function getTransitionedItemHeight(item, now, store, adapter) {
|
|
4538
|
+
const transition = store.readActive(item, now);
|
|
4539
|
+
if (transition != null) return sampleTransition(transition, now).slotHeight;
|
|
4540
|
+
const node = adapter.renderItem(item);
|
|
4541
|
+
return adapter.measureNode(node).height;
|
|
4542
|
+
}
|
|
4543
|
+
function resolveTransitionedItem(item, now, store, adapter, lifecycle) {
|
|
4544
|
+
const transition = store.readActive(item, now);
|
|
4545
|
+
if (transition == null) {
|
|
4546
|
+
const node = adapter.renderItem(item);
|
|
4547
|
+
return {
|
|
4548
|
+
value: {
|
|
4549
|
+
draw: (y) => adapter.drawNode(node, 0, y),
|
|
4550
|
+
hittest: (test, y) => node.hittest(adapter.getRootContext(), {
|
|
4551
|
+
...test,
|
|
4552
|
+
y: test.y - y
|
|
4553
|
+
})
|
|
4554
|
+
},
|
|
4555
|
+
height: adapter.measureNode(node).height
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
const sampled = sampleTransition(transition, now);
|
|
4559
|
+
return {
|
|
4560
|
+
value: {
|
|
4561
|
+
draw: (y) => drawSampledLayers(sampled, y, adapter),
|
|
4562
|
+
hittest: () => false
|
|
4563
|
+
},
|
|
4564
|
+
height: sampled.slotHeight
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
function readCurrentVisualState(item, now, store, adapter) {
|
|
4568
|
+
const transition = store.readActive(item, now);
|
|
4569
|
+
if (transition != null && transition.layers.length > 0) {
|
|
4570
|
+
const primaryLayer = transition.layers[transition.layers.length - 1];
|
|
4571
|
+
return {
|
|
4572
|
+
node: primaryLayer.node,
|
|
4573
|
+
alpha: sampleScalarAnimation(primaryLayer.alpha, now),
|
|
4574
|
+
height: sampleScalarAnimation(transition.height, now),
|
|
4575
|
+
translateY: sampleScalarAnimation(primaryLayer.translateY, now)
|
|
4576
|
+
};
|
|
4577
|
+
}
|
|
4578
|
+
const node = adapter.renderItem(item);
|
|
4579
|
+
return {
|
|
4580
|
+
node,
|
|
4581
|
+
alpha: 1,
|
|
4582
|
+
height: adapter.measureNode(node).height,
|
|
4583
|
+
translateY: 0
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
function handleTransitionStateChange(store, snapshot, currentViewportTranslateY, change, ctx, lifecycle) {
|
|
4587
|
+
switch (change.type) {
|
|
4588
|
+
case "update": {
|
|
4589
|
+
const now = getNow();
|
|
4590
|
+
const currentVisualState = readCurrentVisualState(change.prevItem, now, store, ctx);
|
|
4591
|
+
const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
|
|
4592
|
+
if (transition == null) {
|
|
4593
|
+
store.delete(change.prevItem);
|
|
4594
|
+
return {};
|
|
4595
|
+
}
|
|
4596
|
+
store.replace(change.prevItem, change.nextItem, transition);
|
|
4597
|
+
return {};
|
|
4598
|
+
}
|
|
4599
|
+
case "delete": {
|
|
4600
|
+
const now = getNow();
|
|
4601
|
+
const currentVisualState = readCurrentVisualState(change.item, now, store, ctx);
|
|
4602
|
+
const transition = planDeleteTransition(change.item, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
|
|
4603
|
+
if (transition == null) {
|
|
4604
|
+
store.delete(change.item);
|
|
4605
|
+
lifecycle.onDeleteComplete(change.item);
|
|
4606
|
+
return {};
|
|
4607
|
+
}
|
|
4608
|
+
store.set(change.item, transition);
|
|
4609
|
+
return {};
|
|
4610
|
+
}
|
|
4611
|
+
case "delete-finalize":
|
|
4612
|
+
store.delete(change.item);
|
|
4613
|
+
return {};
|
|
4614
|
+
case "unshift":
|
|
4615
|
+
case "push": {
|
|
4616
|
+
const now = getNow();
|
|
4617
|
+
const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, change.animation?.distance, now, currentViewportTranslateY, ctx, snapshot);
|
|
4618
|
+
if (plan == null) return {};
|
|
4619
|
+
if (plan.kind === "viewport-slide") return { viewportAnimation: plan.animation };
|
|
4620
|
+
for (const entry of plan.entries) store.set(entry.item, entry.transition);
|
|
4621
|
+
return {};
|
|
4622
|
+
}
|
|
4623
|
+
case "reset":
|
|
4624
|
+
case "set":
|
|
4625
|
+
store.reset();
|
|
4626
|
+
snapshot.reset();
|
|
4627
|
+
return {};
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
//#endregion
|
|
4631
|
+
//#region src/renderer/virtualized/base-transition.ts
|
|
4632
|
+
var TransitionController = class {
|
|
4633
|
+
#store = new TransitionStore();
|
|
4634
|
+
#snapshot = new VisibilitySnapshot();
|
|
4635
|
+
#viewportTranslateAnimation;
|
|
4636
|
+
captureVisibilitySnapshot(window, resolutionPath, items, viewportHeight, snapshotState, extraShift, readVisibleRange) {
|
|
4637
|
+
this.#snapshot.capture(window, resolutionPath, items, viewportHeight, snapshotState, extraShift, readVisibleRange);
|
|
4638
|
+
}
|
|
4639
|
+
pruneInvisible(lifecycle) {
|
|
4640
|
+
return this.pruneInvisibleAt(getNow(), lifecycle);
|
|
4641
|
+
}
|
|
4642
|
+
prepare(now, lifecycle) {
|
|
4643
|
+
this.settle(now, lifecycle);
|
|
4644
|
+
this.#cleanupViewportTranslateAnimation(now);
|
|
4645
|
+
const keepViewportAnimating = this.#viewportTranslateAnimation != null;
|
|
4646
|
+
return this.#store.prepare(now) || keepViewportAnimating;
|
|
4647
|
+
}
|
|
4648
|
+
getViewportTranslateY(now) {
|
|
4649
|
+
this.#cleanupViewportTranslateAnimation(now);
|
|
4650
|
+
return this.#viewportTranslateAnimation == null ? 0 : sampleScalarAnimation(this.#viewportTranslateAnimation, now);
|
|
4651
|
+
}
|
|
4652
|
+
canAutoFollowBoundaryInsert(direction, count, position, offset) {
|
|
4653
|
+
return this.#snapshot.matchesFollowBoundaryInsertState(direction, count, position, offset);
|
|
4654
|
+
}
|
|
4655
|
+
getItemHeight(item, now, adapter) {
|
|
4656
|
+
return getTransitionedItemHeight(item, now, this.#store, adapter);
|
|
4657
|
+
}
|
|
4658
|
+
resolveItem(item, now, adapter, lifecycle) {
|
|
4659
|
+
return resolveTransitionedItem(item, now, this.#store, adapter, lifecycle);
|
|
4660
|
+
}
|
|
4661
|
+
handleListStateChange(change, ctx, lifecycle) {
|
|
4662
|
+
const now = getNow();
|
|
4663
|
+
this.settle(now, lifecycle);
|
|
4664
|
+
const result = handleTransitionStateChange(this.#store, this.#snapshot, this.getViewportTranslateY(now), change, ctx, lifecycle);
|
|
4665
|
+
if (change.type === "reset" || change.type === "set") {
|
|
4666
|
+
this.#viewportTranslateAnimation = void 0;
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4669
|
+
if (result.viewportAnimation != null) this.#viewportTranslateAnimation = result.viewportAnimation;
|
|
4670
|
+
}
|
|
4671
|
+
settle(now, lifecycle) {
|
|
4672
|
+
const changed = this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
|
|
4673
|
+
this.#cleanupViewportTranslateAnimation(now);
|
|
4674
|
+
return changed;
|
|
4675
|
+
}
|
|
4676
|
+
pruneInvisibleAt(now, lifecycle) {
|
|
4677
|
+
return this.#settleTransitions(this.#store.findInvisible(this.#snapshot), now, lifecycle);
|
|
4678
|
+
}
|
|
4679
|
+
reset() {
|
|
4680
|
+
this.#store.reset();
|
|
4681
|
+
this.#snapshot.reset();
|
|
4682
|
+
this.#viewportTranslateAnimation = void 0;
|
|
4683
|
+
}
|
|
4684
|
+
#cleanupViewportTranslateAnimation(now) {
|
|
4685
|
+
const animation = this.#viewportTranslateAnimation;
|
|
4686
|
+
if (animation == null) return;
|
|
4687
|
+
if (getProgress(animation.startTime, animation.duration, now) >= 1) this.#viewportTranslateAnimation = void 0;
|
|
4688
|
+
}
|
|
4689
|
+
#settleTransitions(removals, now, lifecycle) {
|
|
4690
|
+
if (removals.length === 0) return false;
|
|
4691
|
+
const anchor = lifecycle.captureVisualAnchor(now);
|
|
4692
|
+
for (const { item, transition } of removals) {
|
|
4693
|
+
this.#store.delete(item);
|
|
4694
|
+
if (transition.kind === "delete") lifecycle.onDeleteComplete(item);
|
|
4695
|
+
}
|
|
4696
|
+
if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(anchor);
|
|
4697
|
+
return true;
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
//#endregion
|
|
4701
|
+
//#region src/renderer/virtualized/base.ts
|
|
3830
4702
|
/**
|
|
3831
4703
|
* Shared base class for virtualized list renderers.
|
|
3832
4704
|
*/
|
|
3833
4705
|
var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
3834
4706
|
static MIN_JUMP_DURATION = 160;
|
|
3835
4707
|
static MAX_JUMP_DURATION = 420;
|
|
3836
|
-
static
|
|
3837
|
-
#
|
|
3838
|
-
#
|
|
3839
|
-
#replacementAnimations = /* @__PURE__ */ new WeakMap();
|
|
3840
|
-
#activeReplacementItems = /* @__PURE__ */ new Set();
|
|
3841
|
-
#nextReplacementLayerKey = 0;
|
|
4708
|
+
static JUMP_DURATION_PER_PIXEL = .7;
|
|
4709
|
+
#jumpController;
|
|
4710
|
+
#transitionController = new TransitionController();
|
|
3842
4711
|
constructor(graphics, options) {
|
|
3843
4712
|
super(graphics, options);
|
|
4713
|
+
this.#jumpController = new JumpController({
|
|
4714
|
+
minJumpDuration: VirtualizedRenderer.MIN_JUMP_DURATION,
|
|
4715
|
+
maxJumpDuration: VirtualizedRenderer.MAX_JUMP_DURATION,
|
|
4716
|
+
jumpDurationPerPixel: VirtualizedRenderer.JUMP_DURATION_PER_PIXEL,
|
|
4717
|
+
getItemCount: () => this.items.length,
|
|
4718
|
+
readListState: this._readListState.bind(this),
|
|
4719
|
+
normalizeListState: this._normalizeListState.bind(this),
|
|
4720
|
+
readAnchor: (state) => this._readAnchor(state, this._getItemHeight.bind(this)),
|
|
4721
|
+
applyAnchor: this._applyAnchor.bind(this),
|
|
4722
|
+
getDefaultJumpBlock: this._getDefaultJumpBlock.bind(this),
|
|
4723
|
+
getTargetAnchor: this._getTargetAnchor.bind(this),
|
|
4724
|
+
clampItemIndex: this._clampItemIndex.bind(this),
|
|
4725
|
+
getItemHeight: this._getItemHeight.bind(this),
|
|
4726
|
+
canAutoFollowBoundaryInsert: (direction, count, position, offset) => this.#transitionController.canAutoFollowBoundaryInsert(direction, count, position, offset)
|
|
4727
|
+
});
|
|
3844
4728
|
subscribeListState(options.list, this, (owner, change) => {
|
|
3845
4729
|
owner.#handleListStateChange(change);
|
|
3846
4730
|
});
|
|
@@ -3869,57 +4753,57 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
3869
4753
|
set items(value) {
|
|
3870
4754
|
this.options.list.items = value;
|
|
3871
4755
|
}
|
|
4756
|
+
/** Renders the current visible window. */
|
|
4757
|
+
render(feedback) {
|
|
4758
|
+
this.#jumpController.beforeFrame();
|
|
4759
|
+
const now = getNow();
|
|
4760
|
+
const keepAnimating = this._prepareRender(now);
|
|
4761
|
+
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
4762
|
+
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
4763
|
+
const frame = prepareFrameSession({
|
|
4764
|
+
now,
|
|
4765
|
+
resolveVisibleWindow: (frameNow) => this._resolveVisibleWindow(frameNow),
|
|
4766
|
+
getViewportTranslateY: (frameNow) => this.#transitionController.getViewportTranslateY(frameNow),
|
|
4767
|
+
captureVisibleItemSnapshot: (solution, extraShift) => this._captureVisibleItemSnapshot(solution, extraShift),
|
|
4768
|
+
pruneTransitionAnimations: (window, frameNow) => this._pruneTransitionAnimations(window, frameNow)
|
|
4769
|
+
});
|
|
4770
|
+
const requestRedraw = this._renderVisibleWindow(frame.solution.window, feedback, frame.viewportTranslateY);
|
|
4771
|
+
this._commitListState(frame.solution.normalizedState);
|
|
4772
|
+
return this._finishRender(keepAnimating || requestRedraw || frame.requestSettleRedraw);
|
|
4773
|
+
}
|
|
4774
|
+
/** Hit-tests the current visible window. */
|
|
4775
|
+
hittest(test) {
|
|
4776
|
+
this.#jumpController.beforeFrame();
|
|
4777
|
+
const now = getNow();
|
|
4778
|
+
this.#transitionController.settle(now, this.#getTransitionLifecycleAdapter());
|
|
4779
|
+
const frame = prepareFrameSession({
|
|
4780
|
+
now,
|
|
4781
|
+
resolveVisibleWindow: (frameNow) => this._resolveVisibleWindow(frameNow),
|
|
4782
|
+
getViewportTranslateY: (frameNow) => this.#transitionController.getViewportTranslateY(frameNow),
|
|
4783
|
+
captureVisibleItemSnapshot: (solution, extraShift) => this._captureVisibleItemSnapshot(solution, extraShift),
|
|
4784
|
+
pruneTransitionAnimations: (window, frameNow) => this._pruneTransitionAnimations(window, frameNow)
|
|
4785
|
+
});
|
|
4786
|
+
return this._hittestVisibleWindow(frame.solution.window, test, frame.viewportTranslateY);
|
|
4787
|
+
}
|
|
3872
4788
|
_readListState() {
|
|
3873
4789
|
return {
|
|
3874
4790
|
position: this.position,
|
|
3875
4791
|
offset: this.offset
|
|
3876
4792
|
};
|
|
3877
4793
|
}
|
|
4794
|
+
_resolveVisibleWindow(now) {
|
|
4795
|
+
return this._resolveVisibleWindowForState(this._readListState(), now);
|
|
4796
|
+
}
|
|
3878
4797
|
_commitListState(state) {
|
|
3879
4798
|
this.position = state.position;
|
|
3880
4799
|
this.offset = state.offset;
|
|
4800
|
+
this.#jumpController.commit(state);
|
|
3881
4801
|
}
|
|
3882
4802
|
/**
|
|
3883
4803
|
* Scrolls the viewport to the requested item index.
|
|
3884
4804
|
*/
|
|
3885
4805
|
jumpTo(index, options = {}) {
|
|
3886
|
-
|
|
3887
|
-
this.#cancelJumpAnimation();
|
|
3888
|
-
return;
|
|
3889
|
-
}
|
|
3890
|
-
const targetIndex = this._clampItemIndex(index);
|
|
3891
|
-
const currentState = this._normalizeListState(this._readListState());
|
|
3892
|
-
const targetBlock = options.block ?? this._getDefaultJumpBlock();
|
|
3893
|
-
const targetAnchor = this._getTargetAnchor(targetIndex, targetBlock);
|
|
3894
|
-
if (!(options.animated ?? true)) {
|
|
3895
|
-
this.#cancelJumpAnimation();
|
|
3896
|
-
this._applyAnchor(targetAnchor);
|
|
3897
|
-
options.onComplete?.();
|
|
3898
|
-
return;
|
|
3899
|
-
}
|
|
3900
|
-
const startAnchor = this._readAnchor(currentState);
|
|
3901
|
-
if (!Number.isFinite(startAnchor)) {
|
|
3902
|
-
this.#cancelJumpAnimation();
|
|
3903
|
-
this._applyAnchor(targetAnchor);
|
|
3904
|
-
options.onComplete?.();
|
|
3905
|
-
return;
|
|
3906
|
-
}
|
|
3907
|
-
const duration = clamp$3(options.duration ?? VirtualizedRenderer.MIN_JUMP_DURATION + Math.abs(targetAnchor - startAnchor) * VirtualizedRenderer.JUMP_DURATION_PER_ITEM, 0, VirtualizedRenderer.MAX_JUMP_DURATION);
|
|
3908
|
-
if (duration <= 0 || Math.abs(targetAnchor - startAnchor) <= Number.EPSILON) {
|
|
3909
|
-
this.#cancelJumpAnimation();
|
|
3910
|
-
this._applyAnchor(targetAnchor);
|
|
3911
|
-
options.onComplete?.();
|
|
3912
|
-
return;
|
|
3913
|
-
}
|
|
3914
|
-
this.#jumpAnimation = {
|
|
3915
|
-
startAnchor,
|
|
3916
|
-
targetAnchor,
|
|
3917
|
-
startTime: getNow(),
|
|
3918
|
-
duration,
|
|
3919
|
-
needsMoreFrames: true,
|
|
3920
|
-
onComplete: options.onComplete
|
|
3921
|
-
};
|
|
3922
|
-
this.#controlledState = this._readListState();
|
|
4806
|
+
this.#jumpController.jumpTo(index, options);
|
|
3923
4807
|
}
|
|
3924
4808
|
_resetRenderFeedback(feedback) {
|
|
3925
4809
|
if (feedback == null) return;
|
|
@@ -3929,13 +4813,10 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
3929
4813
|
feedback.max = NaN;
|
|
3930
4814
|
}
|
|
3931
4815
|
_accumulateRenderFeedback(feedback, idx, top, height) {
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
const
|
|
3935
|
-
const
|
|
3936
|
-
if (visibleBottom <= visibleTop) return;
|
|
3937
|
-
const itemMin = idx + visibleTop / height;
|
|
3938
|
-
const itemMax = idx + visibleBottom / height;
|
|
4816
|
+
const visibleRange = this._readVisibleRange(top, height);
|
|
4817
|
+
if (visibleRange == null) return;
|
|
4818
|
+
const itemMin = idx + visibleRange.top / height;
|
|
4819
|
+
const itemMax = idx + visibleRange.bottom / height;
|
|
3939
4820
|
feedback.minIdx = Number.isNaN(feedback.minIdx) ? idx : Math.min(idx, feedback.minIdx);
|
|
3940
4821
|
feedback.maxIdx = Number.isNaN(feedback.maxIdx) ? idx : Math.max(idx, feedback.maxIdx);
|
|
3941
4822
|
feedback.min = Number.isNaN(feedback.min) ? itemMin : Math.min(itemMin, feedback.min);
|
|
@@ -3952,367 +4833,296 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
3952
4833
|
}
|
|
3953
4834
|
return result;
|
|
3954
4835
|
}
|
|
3955
|
-
_renderVisibleWindow(window, feedback) {
|
|
4836
|
+
_renderVisibleWindow(window, feedback, extraShift = 0) {
|
|
3956
4837
|
this._resetRenderFeedback(feedback);
|
|
3957
|
-
return this._renderDrawList(window.drawList, window.shift, feedback);
|
|
4838
|
+
return this._renderDrawList(window.drawList, window.shift + extraShift, feedback);
|
|
3958
4839
|
}
|
|
3959
|
-
|
|
4840
|
+
_readVisibleRange(top, height) {
|
|
4841
|
+
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
4842
|
+
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
4843
|
+
const visibleTop = clamp$1(-top, 0, height);
|
|
4844
|
+
const visibleBottom = clamp$1(viewportHeight - top, 0, height);
|
|
4845
|
+
if (visibleBottom <= visibleTop) return;
|
|
4846
|
+
return {
|
|
4847
|
+
top: visibleTop,
|
|
4848
|
+
bottom: visibleBottom
|
|
4849
|
+
};
|
|
4850
|
+
}
|
|
4851
|
+
_pruneTransitionAnimations(_window, now) {
|
|
4852
|
+
return this.#transitionController.pruneInvisibleAt(now, this.#getTransitionLifecycleAdapter());
|
|
4853
|
+
}
|
|
4854
|
+
_hittestVisibleWindow(window, test, extraShift = 0) {
|
|
3960
4855
|
for (const { value: item, offset, height } of window.drawList) {
|
|
3961
|
-
const y = offset + window.shift;
|
|
4856
|
+
const y = offset + window.shift + extraShift;
|
|
3962
4857
|
if (test.y < y || test.y >= y + height) continue;
|
|
3963
4858
|
return item.hittest(test, y);
|
|
3964
4859
|
}
|
|
3965
4860
|
return false;
|
|
3966
4861
|
}
|
|
3967
|
-
|
|
3968
|
-
const
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
}
|
|
3976
|
-
if (this.#controlledState != null && !sameState(this.#controlledState, this.position, this.offset)) {
|
|
3977
|
-
this.#cancelJumpAnimation();
|
|
3978
|
-
return keepReplacing;
|
|
3979
|
-
}
|
|
3980
|
-
const anchor = interpolate(animation.startAnchor, animation.targetAnchor, animation.startTime, animation.duration, now);
|
|
3981
|
-
const progress = getProgress(animation.startTime, animation.duration, now);
|
|
3982
|
-
this._applyAnchor(anchor);
|
|
3983
|
-
animation.needsMoreFrames = progress < 1;
|
|
3984
|
-
return keepReplacing || animation.needsMoreFrames;
|
|
4862
|
+
_captureVisibleItemSnapshot(solution, extraShift = 0) {
|
|
4863
|
+
const normalizedState = this._normalizeListState(this._readListState());
|
|
4864
|
+
this.#transitionController.captureVisibilitySnapshot(solution.window, solution.resolutionPath, this.items, this.graphics.canvas.clientHeight, normalizedState, extraShift, this._readVisibleRange.bind(this));
|
|
4865
|
+
}
|
|
4866
|
+
_prepareRender(now) {
|
|
4867
|
+
const keepTransitioning = this.#transitionController.prepare(now, this.#getTransitionLifecycleAdapter());
|
|
4868
|
+
const keepJumping = this.#jumpController.prepare(now);
|
|
4869
|
+
return keepTransitioning || keepJumping;
|
|
3985
4870
|
}
|
|
3986
4871
|
_finishRender(requestRedraw) {
|
|
3987
|
-
|
|
3988
|
-
if (animation == null) return requestRedraw;
|
|
3989
|
-
if (animation.needsMoreFrames) {
|
|
3990
|
-
this.#controlledState = this._readListState();
|
|
3991
|
-
return true;
|
|
3992
|
-
}
|
|
3993
|
-
const onComplete = animation.onComplete;
|
|
3994
|
-
this.#cancelJumpAnimation();
|
|
3995
|
-
onComplete?.();
|
|
3996
|
-
return requestRedraw || this.#jumpAnimation != null;
|
|
4872
|
+
return this.#jumpController.finishFrame(requestRedraw);
|
|
3997
4873
|
}
|
|
3998
4874
|
_clampItemIndex(index) {
|
|
3999
|
-
return clamp$
|
|
4875
|
+
return clamp$1(Number.isFinite(index) ? Math.trunc(index) : 0, 0, this.items.length - 1);
|
|
4000
4876
|
}
|
|
4001
4877
|
_getItemHeight(index) {
|
|
4002
|
-
|
|
4878
|
+
return this._getItemHeightAt(index, getNow());
|
|
4879
|
+
}
|
|
4880
|
+
_getItemHeightAt(index, now) {
|
|
4003
4881
|
const item = this.items[index];
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4882
|
+
return this.#transitionController.getItemHeight(item, now, {
|
|
4883
|
+
renderItem: this.options.renderItem,
|
|
4884
|
+
measureNode: this.measureRootNode.bind(this)
|
|
4885
|
+
});
|
|
4008
4886
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
return {
|
|
4014
|
-
value: {
|
|
4015
|
-
draw: (y) => this.drawRootNode(node, 0, y),
|
|
4016
|
-
hittest: (test, y) => node.hittest(this.getRootContext(), {
|
|
4017
|
-
...test,
|
|
4018
|
-
y: test.y - y
|
|
4019
|
-
})
|
|
4020
|
-
},
|
|
4021
|
-
height: this.measureRootNode(node).height
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
const slotHeight = this.#sampleReplacementHeight(replacement, now);
|
|
4025
|
-
const layers = replacement.layers.map((layer) => ({
|
|
4026
|
-
alpha: this.#sampleLayerAlpha(layer, now),
|
|
4027
|
-
node: layer.node,
|
|
4028
|
-
nodeHeight: this.measureRootNode(layer.node).height
|
|
4029
|
-
})).filter((layer) => layer.alpha > ALPHA_EPSILON);
|
|
4030
|
-
return {
|
|
4031
|
-
value: {
|
|
4032
|
-
draw: (y) => this.#drawReplacementLayers(layers, slotHeight, y),
|
|
4033
|
-
hittest: () => false
|
|
4034
|
-
},
|
|
4035
|
-
height: slotHeight
|
|
4036
|
-
};
|
|
4887
|
+
_readAnchorAt(now) {
|
|
4888
|
+
if (this.items.length <= 0) return;
|
|
4889
|
+
const state = this._normalizeListState(this._readListState());
|
|
4890
|
+
return this._readAnchor(state, (index) => this._getItemHeightAt(index, now));
|
|
4037
4891
|
}
|
|
4038
|
-
|
|
4039
|
-
if (this.items.length
|
|
4040
|
-
|
|
4041
|
-
let remaining = Number.isFinite(offset) ? offset : 0;
|
|
4042
|
-
while (true) {
|
|
4043
|
-
if (remaining < 0) {
|
|
4044
|
-
if (currentIndex === 0) return 0;
|
|
4045
|
-
currentIndex -= 1;
|
|
4046
|
-
const height = this._getItemHeight(currentIndex);
|
|
4047
|
-
if (height > 0) remaining += height;
|
|
4048
|
-
continue;
|
|
4049
|
-
}
|
|
4050
|
-
const height = this._getItemHeight(currentIndex);
|
|
4051
|
-
if (height > 0) {
|
|
4052
|
-
if (remaining <= height) return currentIndex + remaining / height;
|
|
4053
|
-
remaining -= height;
|
|
4054
|
-
} else if (remaining === 0) return currentIndex;
|
|
4055
|
-
if (currentIndex === this.items.length - 1) return this.items.length;
|
|
4056
|
-
currentIndex += 1;
|
|
4057
|
-
}
|
|
4892
|
+
_restoreAnchor(anchor) {
|
|
4893
|
+
if (!Number.isFinite(anchor) || this.items.length <= 0) return;
|
|
4894
|
+
this._applyAnchor(anchor);
|
|
4058
4895
|
}
|
|
4059
|
-
|
|
4060
|
-
this.#
|
|
4061
|
-
this.#controlledState = void 0;
|
|
4896
|
+
_resolveItem(item, _index, now) {
|
|
4897
|
+
return this.#transitionController.resolveItem(item, now, this.#getTransitionRenderAdapter(), this.#getTransitionLifecycleAdapter());
|
|
4062
4898
|
}
|
|
4063
|
-
#
|
|
4899
|
+
#handleDeleteComplete(item) {
|
|
4900
|
+
this.options.list.finalizeDelete(item);
|
|
4901
|
+
}
|
|
4902
|
+
#getTransitionLifecycleAdapter() {
|
|
4064
4903
|
return {
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
toAlpha,
|
|
4069
|
-
startTime,
|
|
4070
|
-
duration
|
|
4904
|
+
onDeleteComplete: this.#handleDeleteComplete.bind(this),
|
|
4905
|
+
captureVisualAnchor: this._readAnchorAt.bind(this),
|
|
4906
|
+
restoreVisualAnchor: this._restoreAnchor.bind(this)
|
|
4071
4907
|
};
|
|
4072
4908
|
}
|
|
4073
|
-
#
|
|
4074
|
-
return
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
const animation = this.#replacementAnimations.get(item);
|
|
4084
|
-
if (animation == null) return;
|
|
4085
|
-
const currentLayer = animation.layers.find((layer) => layer.key === animation.currentLayerKey);
|
|
4086
|
-
if (currentLayer == null) {
|
|
4087
|
-
this.#replacementAnimations.delete(item);
|
|
4088
|
-
this.#activeReplacementItems.delete(item);
|
|
4089
|
-
return;
|
|
4090
|
-
}
|
|
4091
|
-
animation.layers = animation.layers.filter((layer) => layer.key === animation.currentLayerKey || !this.#isLayerComplete(layer, now));
|
|
4092
|
-
if (getProgress(animation.startTime, animation.duration, now) >= 1 && this.#isLayerComplete(currentLayer, now) && animation.layers.length === 1) {
|
|
4093
|
-
this.#replacementAnimations.delete(item);
|
|
4094
|
-
this.#activeReplacementItems.delete(item);
|
|
4095
|
-
return;
|
|
4096
|
-
}
|
|
4097
|
-
return animation;
|
|
4909
|
+
#getVirtualizedRuntime() {
|
|
4910
|
+
return {
|
|
4911
|
+
items: this.items,
|
|
4912
|
+
position: this.position,
|
|
4913
|
+
offset: this.offset,
|
|
4914
|
+
renderItem: this.options.renderItem,
|
|
4915
|
+
measureNode: this.measureRootNode.bind(this),
|
|
4916
|
+
readVisibleRange: this._readVisibleRange.bind(this),
|
|
4917
|
+
resolveVisibleWindow: () => this._resolveVisibleWindow(getNow())
|
|
4918
|
+
};
|
|
4098
4919
|
}
|
|
4099
|
-
#
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4920
|
+
#getTransitionRenderAdapter() {
|
|
4921
|
+
const runtime = this.#getVirtualizedRuntime();
|
|
4922
|
+
return {
|
|
4923
|
+
renderItem: runtime.renderItem,
|
|
4924
|
+
measureNode: runtime.measureNode,
|
|
4925
|
+
drawNode: this.drawRootNode.bind(this),
|
|
4926
|
+
getRootContext: this.getRootContext.bind(this),
|
|
4927
|
+
graphics: this.graphics
|
|
4928
|
+
};
|
|
4103
4929
|
}
|
|
4104
|
-
#
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
const alpha = clamp$3(layer.alpha, 0, 1);
|
|
4110
|
-
if (alpha <= ALPHA_EPSILON) continue;
|
|
4111
|
-
this.graphics.save();
|
|
4112
|
-
try {
|
|
4113
|
-
this.graphics.beginPath?.();
|
|
4114
|
-
this.graphics.rect?.(0, y, width, slotHeight);
|
|
4115
|
-
this.graphics.clip?.();
|
|
4116
|
-
if (typeof this.graphics.globalAlpha === "number") this.graphics.globalAlpha *= alpha;
|
|
4117
|
-
const layerY = y + this._getAnimatedLayerOffset(slotHeight, layer.nodeHeight);
|
|
4118
|
-
if (this.drawRootNode(layer.node, 0, layerY)) result = true;
|
|
4119
|
-
} finally {
|
|
4120
|
-
this.graphics.restore();
|
|
4121
|
-
}
|
|
4122
|
-
}
|
|
4123
|
-
return result;
|
|
4930
|
+
#getTransitionPlanningAdapter() {
|
|
4931
|
+
return {
|
|
4932
|
+
...this.#getVirtualizedRuntime(),
|
|
4933
|
+
underflowAlign: this._getLayoutOptions().underflowAlign
|
|
4934
|
+
};
|
|
4124
4935
|
}
|
|
4125
4936
|
#handleListStateChange(change) {
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
this.#handleUpdate(change.prevItem, change.nextItem, change.animation?.duration);
|
|
4129
|
-
break;
|
|
4130
|
-
case "unshift":
|
|
4131
|
-
case "push": break;
|
|
4132
|
-
case "reset":
|
|
4133
|
-
case "set":
|
|
4134
|
-
this.#replacementAnimations = /* @__PURE__ */ new WeakMap();
|
|
4135
|
-
this.#activeReplacementItems.clear();
|
|
4136
|
-
break;
|
|
4137
|
-
}
|
|
4937
|
+
const nextChange = this.#jumpController.handleListStateChange(change);
|
|
4938
|
+
this.#transitionController.handleListStateChange(nextChange, this.#getTransitionPlanningAdapter(), this.#getTransitionLifecycleAdapter());
|
|
4138
4939
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4940
|
+
};
|
|
4941
|
+
//#endregion
|
|
4942
|
+
//#region src/renderer/virtualized/anchor-model.ts
|
|
4943
|
+
function clampItemIndex(index, itemCount) {
|
|
4944
|
+
if (itemCount <= 0) return 0;
|
|
4945
|
+
return clamp$1(Number.isFinite(index) ? Math.trunc(index) : 0, 0, itemCount - 1);
|
|
4946
|
+
}
|
|
4947
|
+
function readAnchorFromState(itemCount, state, anchorMode, readItemHeight) {
|
|
4948
|
+
if (itemCount <= 0) return 0;
|
|
4949
|
+
const height = readItemHeight(state.position);
|
|
4950
|
+
if (anchorMode === "top") return height > 0 ? state.position - state.offset / height : state.position;
|
|
4951
|
+
return height > 0 ? state.position + 1 - state.offset / height : state.position + 1;
|
|
4952
|
+
}
|
|
4953
|
+
function applyAnchorToState(itemCount, anchor, anchorMode, readItemHeight) {
|
|
4954
|
+
if (itemCount <= 0) return;
|
|
4955
|
+
const clampedAnchor = clamp$1(anchor, 0, itemCount);
|
|
4956
|
+
if (anchorMode === "top") {
|
|
4957
|
+
const position = clamp$1(Math.floor(clampedAnchor), 0, itemCount - 1);
|
|
4958
|
+
const height = readItemHeight(position);
|
|
4959
|
+
const offset = height > 0 ? -(clampedAnchor - position) * height : 0;
|
|
4960
|
+
return {
|
|
4961
|
+
position,
|
|
4962
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
const position = clamp$1(Math.ceil(clampedAnchor) - 1, 0, itemCount - 1);
|
|
4966
|
+
const height = readItemHeight(position);
|
|
4967
|
+
const offset = height > 0 ? (position + 1 - clampedAnchor) * height : 0;
|
|
4968
|
+
return {
|
|
4969
|
+
position,
|
|
4970
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
4971
|
+
};
|
|
4972
|
+
}
|
|
4973
|
+
function getAnchorAtOffset(itemCount, index, offset, readItemHeight) {
|
|
4974
|
+
if (itemCount <= 0) return 0;
|
|
4975
|
+
let currentIndex = clampItemIndex(index, itemCount);
|
|
4976
|
+
let remaining = Number.isFinite(offset) ? offset : 0;
|
|
4977
|
+
while (true) {
|
|
4978
|
+
if (remaining < 0) {
|
|
4979
|
+
if (currentIndex === 0) return 0;
|
|
4980
|
+
currentIndex -= 1;
|
|
4981
|
+
const height = readItemHeight(currentIndex);
|
|
4982
|
+
if (height > 0) remaining += height;
|
|
4983
|
+
continue;
|
|
4165
4984
|
}
|
|
4166
|
-
const
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4985
|
+
const height = readItemHeight(currentIndex);
|
|
4986
|
+
if (height > 0) {
|
|
4987
|
+
if (remaining <= height) return currentIndex + remaining / height;
|
|
4988
|
+
remaining -= height;
|
|
4989
|
+
} else if (remaining === 0) return currentIndex;
|
|
4990
|
+
if (currentIndex === itemCount - 1) return itemCount;
|
|
4991
|
+
currentIndex += 1;
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
function getTargetAnchorForItem(itemCount, index, block, anchorMode, viewportHeight, readItemHeight) {
|
|
4995
|
+
if (itemCount <= 0) return 0;
|
|
4996
|
+
const targetIndex = clampItemIndex(index, itemCount);
|
|
4997
|
+
const height = readItemHeight(targetIndex);
|
|
4998
|
+
if (anchorMode === "top") switch (block) {
|
|
4999
|
+
case "start": return getAnchorAtOffset(itemCount, targetIndex, 0, readItemHeight);
|
|
5000
|
+
case "center": return getAnchorAtOffset(itemCount, targetIndex, height / 2 - viewportHeight / 2, readItemHeight);
|
|
5001
|
+
case "end": return getAnchorAtOffset(itemCount, targetIndex, height - viewportHeight, readItemHeight);
|
|
5002
|
+
}
|
|
5003
|
+
switch (block) {
|
|
5004
|
+
case "start": return getAnchorAtOffset(itemCount, targetIndex, viewportHeight, readItemHeight);
|
|
5005
|
+
case "center": return getAnchorAtOffset(itemCount, targetIndex, height / 2 + viewportHeight / 2, readItemHeight);
|
|
5006
|
+
case "end": return getAnchorAtOffset(itemCount, targetIndex, height, readItemHeight);
|
|
4184
5007
|
}
|
|
4185
|
-
}
|
|
5008
|
+
}
|
|
4186
5009
|
//#endregion
|
|
4187
5010
|
//#region src/renderer/virtualized/solver.ts
|
|
4188
|
-
function clamp
|
|
5011
|
+
function clamp(value, min, max) {
|
|
4189
5012
|
return Math.min(Math.max(value, min), max);
|
|
4190
5013
|
}
|
|
4191
5014
|
function normalizeOffset(offset) {
|
|
4192
5015
|
return Number.isFinite(offset) ? offset : 0;
|
|
4193
5016
|
}
|
|
4194
|
-
function
|
|
4195
|
-
if (itemCount <= 0) return {
|
|
4196
|
-
position: 0,
|
|
4197
|
-
offset: 0
|
|
4198
|
-
};
|
|
4199
|
-
const position = state.position;
|
|
4200
|
-
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
4201
|
-
position: 0,
|
|
4202
|
-
offset: normalizeOffset(state.offset)
|
|
4203
|
-
};
|
|
5017
|
+
function resolveListLayoutOptions(options = {}) {
|
|
4204
5018
|
return {
|
|
4205
|
-
|
|
4206
|
-
|
|
5019
|
+
anchorMode: options.anchorMode ?? "top",
|
|
5020
|
+
underflowAlign: options.underflowAlign ?? "top"
|
|
4207
5021
|
};
|
|
4208
5022
|
}
|
|
4209
|
-
function
|
|
5023
|
+
function normalizeVisibleState(itemCount, state, layout) {
|
|
4210
5024
|
if (itemCount <= 0) return {
|
|
4211
5025
|
position: 0,
|
|
4212
5026
|
offset: 0
|
|
4213
5027
|
};
|
|
4214
5028
|
const position = state.position;
|
|
5029
|
+
const fallbackPosition = layout.anchorMode === "top" ? 0 : itemCount - 1;
|
|
4215
5030
|
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
4216
|
-
position:
|
|
5031
|
+
position: fallbackPosition,
|
|
4217
5032
|
offset: normalizeOffset(state.offset)
|
|
4218
5033
|
};
|
|
4219
5034
|
return {
|
|
4220
|
-
position: clamp
|
|
5035
|
+
position: clamp(Math.trunc(position), 0, itemCount - 1),
|
|
4221
5036
|
offset: normalizeOffset(state.offset)
|
|
4222
5037
|
};
|
|
4223
5038
|
}
|
|
4224
|
-
function
|
|
4225
|
-
const normalizedState =
|
|
5039
|
+
function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout) {
|
|
5040
|
+
const normalizedState = normalizeVisibleState(items.length, state, layout);
|
|
5041
|
+
const resolutionPath = /* @__PURE__ */ new Set();
|
|
5042
|
+
const readResolvedItem = (item, idx) => {
|
|
5043
|
+
resolutionPath.add(idx);
|
|
5044
|
+
return resolveItem(item, idx);
|
|
5045
|
+
};
|
|
4226
5046
|
if (items.length === 0) return {
|
|
4227
5047
|
normalizedState,
|
|
5048
|
+
resolutionPath: [],
|
|
4228
5049
|
window: {
|
|
4229
5050
|
drawList: [],
|
|
4230
5051
|
shift: 0
|
|
4231
5052
|
}
|
|
4232
5053
|
};
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
let y = offset;
|
|
4246
|
-
const drawList = [];
|
|
4247
|
-
for (let i = position; i < items.length; i += 1) {
|
|
4248
|
-
const { value, height } = resolveItem(items[i], i);
|
|
4249
|
-
if (y + height > 0) {
|
|
4250
|
-
drawList.push({
|
|
4251
|
-
idx: i,
|
|
4252
|
-
value,
|
|
4253
|
-
offset: y,
|
|
4254
|
-
height
|
|
4255
|
-
});
|
|
4256
|
-
drawLength += height;
|
|
4257
|
-
} else {
|
|
4258
|
-
offset += height;
|
|
4259
|
-
position = i + 1;
|
|
5054
|
+
if (layout.anchorMode === "top") {
|
|
5055
|
+
let { position, offset } = normalizedState;
|
|
5056
|
+
let drawLength = 0;
|
|
5057
|
+
if (offset > 0) if (position === 0) offset = 0;
|
|
5058
|
+
else {
|
|
5059
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5060
|
+
const { height } = readResolvedItem(items[i], i);
|
|
5061
|
+
position = i;
|
|
5062
|
+
offset -= height;
|
|
5063
|
+
if (offset <= 0) break;
|
|
5064
|
+
}
|
|
5065
|
+
if (position === 0 && offset > 0) offset = 0;
|
|
4260
5066
|
}
|
|
4261
|
-
y
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
offset: y - shift,
|
|
4280
|
-
height
|
|
4281
|
-
});
|
|
4282
|
-
lastIdx = i;
|
|
4283
|
-
if (y < 0) break;
|
|
5067
|
+
let y = offset;
|
|
5068
|
+
const drawList = [];
|
|
5069
|
+
for (let i = position; i < items.length; i += 1) {
|
|
5070
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5071
|
+
if (y + height > 0) {
|
|
5072
|
+
drawList.push({
|
|
5073
|
+
idx: i,
|
|
5074
|
+
value,
|
|
5075
|
+
offset: y,
|
|
5076
|
+
height
|
|
5077
|
+
});
|
|
5078
|
+
drawLength += height;
|
|
5079
|
+
} else {
|
|
5080
|
+
offset += height;
|
|
5081
|
+
position = i + 1;
|
|
5082
|
+
}
|
|
5083
|
+
y += height;
|
|
5084
|
+
if (y >= viewportHeight) break;
|
|
4284
5085
|
}
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
5086
|
+
let shift = 0;
|
|
5087
|
+
if (y < viewportHeight) if (position === 0 && drawLength < viewportHeight) {
|
|
5088
|
+
shift = -offset;
|
|
4288
5089
|
offset = 0;
|
|
5090
|
+
} else {
|
|
5091
|
+
shift = viewportHeight - y;
|
|
5092
|
+
y = offset += shift;
|
|
5093
|
+
let lastIdx = -1;
|
|
5094
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
5095
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
5096
|
+
drawLength += height;
|
|
5097
|
+
y -= height;
|
|
5098
|
+
drawList.push({
|
|
5099
|
+
idx: i,
|
|
5100
|
+
value,
|
|
5101
|
+
offset: y - shift,
|
|
5102
|
+
height
|
|
5103
|
+
});
|
|
5104
|
+
lastIdx = i;
|
|
5105
|
+
if (y < 0) break;
|
|
5106
|
+
}
|
|
5107
|
+
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
5108
|
+
shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
|
|
5109
|
+
position = 0;
|
|
5110
|
+
offset = 0;
|
|
5111
|
+
}
|
|
4289
5112
|
}
|
|
4290
|
-
|
|
4291
|
-
return {
|
|
4292
|
-
normalizedState: {
|
|
5113
|
+
return finalizeVisibleWindowResult(items.length, viewportHeight, layout, {
|
|
4293
5114
|
position,
|
|
4294
5115
|
offset
|
|
4295
|
-
},
|
|
4296
|
-
window: {
|
|
5116
|
+
}, Array.from(resolutionPath), {
|
|
4297
5117
|
drawList,
|
|
4298
5118
|
shift
|
|
4299
|
-
}
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
4302
|
-
function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
4303
|
-
const normalizedState = normalizeChatState(items.length, state);
|
|
4304
|
-
if (items.length === 0) return {
|
|
4305
|
-
normalizedState,
|
|
4306
|
-
window: {
|
|
4307
|
-
drawList: [],
|
|
4308
|
-
shift: 0
|
|
4309
|
-
}
|
|
4310
|
-
};
|
|
5119
|
+
});
|
|
5120
|
+
}
|
|
4311
5121
|
let { position, offset } = normalizedState;
|
|
4312
5122
|
let drawLength = 0;
|
|
4313
5123
|
if (offset < 0) if (position === items.length - 1) offset = 0;
|
|
4314
5124
|
else for (let i = position + 1; i < items.length; i += 1) {
|
|
4315
|
-
const { height } =
|
|
5125
|
+
const { height } = readResolvedItem(items[i], i);
|
|
4316
5126
|
position = i;
|
|
4317
5127
|
offset += height;
|
|
4318
5128
|
if (offset > 0) break;
|
|
@@ -4320,7 +5130,7 @@ function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
|
4320
5130
|
let y = viewportHeight + offset;
|
|
4321
5131
|
const drawList = [];
|
|
4322
5132
|
for (let i = position; i >= 0; i -= 1) {
|
|
4323
|
-
const { value, height } =
|
|
5133
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
4324
5134
|
y -= height;
|
|
4325
5135
|
if (y <= viewportHeight) {
|
|
4326
5136
|
drawList.push({
|
|
@@ -4342,7 +5152,7 @@ function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
|
4342
5152
|
if (drawLength < viewportHeight) {
|
|
4343
5153
|
y = drawLength;
|
|
4344
5154
|
for (let i = position + 1; i < items.length; i += 1) {
|
|
4345
|
-
const { value, height } =
|
|
5155
|
+
const { value, height } = readResolvedItem(items[i], i);
|
|
4346
5156
|
drawList.push({
|
|
4347
5157
|
idx: i,
|
|
4348
5158
|
value,
|
|
@@ -4356,142 +5166,88 @@ function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
|
4356
5166
|
offset = drawLength < viewportHeight ? 0 : drawLength - viewportHeight;
|
|
4357
5167
|
} else offset = drawLength - viewportHeight;
|
|
4358
5168
|
}
|
|
5169
|
+
return finalizeVisibleWindowResult(items.length, viewportHeight, layout, {
|
|
5170
|
+
position,
|
|
5171
|
+
offset
|
|
5172
|
+
}, Array.from(resolutionPath), {
|
|
5173
|
+
drawList,
|
|
5174
|
+
shift
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5177
|
+
function finalizeVisibleWindowResult(itemCount, viewportHeight, layout, normalizedState, resolutionPath, window) {
|
|
5178
|
+
if (window.drawList.length !== itemCount || itemCount <= 0) return {
|
|
5179
|
+
normalizedState,
|
|
5180
|
+
resolutionPath,
|
|
5181
|
+
window
|
|
5182
|
+
};
|
|
5183
|
+
let minIndex = Number.POSITIVE_INFINITY;
|
|
5184
|
+
let maxIndex = Number.NEGATIVE_INFINITY;
|
|
5185
|
+
let minOffset = Number.POSITIVE_INFINITY;
|
|
5186
|
+
let maxBottom = Number.NEGATIVE_INFINITY;
|
|
5187
|
+
for (const entry of window.drawList) {
|
|
5188
|
+
minIndex = Math.min(minIndex, entry.idx);
|
|
5189
|
+
maxIndex = Math.max(maxIndex, entry.idx);
|
|
5190
|
+
minOffset = Math.min(minOffset, entry.offset);
|
|
5191
|
+
maxBottom = Math.max(maxBottom, entry.offset + entry.height);
|
|
5192
|
+
}
|
|
5193
|
+
const contentHeight = maxBottom - minOffset;
|
|
5194
|
+
if (minIndex !== 0 || maxIndex !== itemCount - 1 || !(contentHeight < viewportHeight - Number.EPSILON)) return {
|
|
5195
|
+
normalizedState,
|
|
5196
|
+
resolutionPath,
|
|
5197
|
+
window
|
|
5198
|
+
};
|
|
5199
|
+
const desiredTop = layout.underflowAlign === "bottom" ? viewportHeight - contentHeight : 0;
|
|
4359
5200
|
return {
|
|
4360
|
-
normalizedState: {
|
|
4361
|
-
position,
|
|
4362
|
-
offset
|
|
5201
|
+
normalizedState: layout.anchorMode === "top" ? {
|
|
5202
|
+
position: 0,
|
|
5203
|
+
offset: 0
|
|
5204
|
+
} : {
|
|
5205
|
+
position: itemCount - 1,
|
|
5206
|
+
offset: 0
|
|
4363
5207
|
},
|
|
5208
|
+
resolutionPath,
|
|
4364
5209
|
window: {
|
|
4365
|
-
drawList,
|
|
4366
|
-
shift
|
|
5210
|
+
drawList: window.drawList,
|
|
5211
|
+
shift: desiredTop - minOffset
|
|
4367
5212
|
}
|
|
4368
5213
|
};
|
|
4369
5214
|
}
|
|
4370
5215
|
//#endregion
|
|
4371
|
-
//#region src/renderer/virtualized/
|
|
4372
|
-
function clamp$1(value, min, max) {
|
|
4373
|
-
return Math.min(Math.max(value, min), max);
|
|
4374
|
-
}
|
|
5216
|
+
//#region src/renderer/virtualized/list.ts
|
|
4375
5217
|
/**
|
|
4376
|
-
* Virtualized renderer
|
|
5218
|
+
* Virtualized list renderer with configurable anchor semantics.
|
|
4377
5219
|
*/
|
|
4378
|
-
var
|
|
4379
|
-
#
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
});
|
|
4384
|
-
}
|
|
4385
|
-
_getDefaultJumpBlock() {
|
|
4386
|
-
return "end";
|
|
4387
|
-
}
|
|
4388
|
-
_normalizeListState(state) {
|
|
4389
|
-
return normalizeChatState(this.items.length, state);
|
|
4390
|
-
}
|
|
4391
|
-
_readAnchor(state) {
|
|
4392
|
-
if (this.items.length === 0) return 0;
|
|
4393
|
-
const height = this._getItemHeight(state.position);
|
|
4394
|
-
return height > 0 ? state.position + 1 - state.offset / height : state.position + 1;
|
|
4395
|
-
}
|
|
4396
|
-
_applyAnchor(anchor) {
|
|
4397
|
-
if (this.items.length === 0) return;
|
|
4398
|
-
const clampedAnchor = clamp$1(anchor, 0, this.items.length);
|
|
4399
|
-
const position = clamp$1(Math.ceil(clampedAnchor) - 1, 0, this.items.length - 1);
|
|
4400
|
-
const height = this._getItemHeight(position);
|
|
4401
|
-
const offset = height > 0 ? (position + 1 - clampedAnchor) * height : 0;
|
|
4402
|
-
this._commitListState({
|
|
4403
|
-
position,
|
|
4404
|
-
offset: Object.is(offset, -0) ? 0 : offset
|
|
4405
|
-
});
|
|
4406
|
-
}
|
|
4407
|
-
_getTargetAnchor(index, block) {
|
|
4408
|
-
const height = this._getItemHeight(index);
|
|
4409
|
-
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
4410
|
-
switch (block) {
|
|
4411
|
-
case "start": return this._getAnchorAtOffset(index, viewportHeight);
|
|
4412
|
-
case "center": return this._getAnchorAtOffset(index, height / 2 + viewportHeight / 2);
|
|
4413
|
-
case "end": return this._getAnchorAtOffset(index, height);
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
_getAnimatedLayerOffset(slotHeight, nodeHeight) {
|
|
4417
|
-
return slotHeight - nodeHeight;
|
|
5220
|
+
var ListRenderer = class extends VirtualizedRenderer {
|
|
5221
|
+
#layout;
|
|
5222
|
+
constructor(graphics, options) {
|
|
5223
|
+
super(graphics, options);
|
|
5224
|
+
this.#layout = resolveListLayoutOptions(options);
|
|
4418
5225
|
}
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
4422
|
-
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
4423
|
-
const solution = this.#resolveVisibleWindow();
|
|
4424
|
-
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
4425
|
-
this._commitListState(solution.normalizedState);
|
|
4426
|
-
return this._finishRender(keepAnimating || requestRedraw);
|
|
5226
|
+
_getLayoutOptions() {
|
|
5227
|
+
return this.#layout;
|
|
4427
5228
|
}
|
|
4428
|
-
|
|
4429
|
-
return this.
|
|
4430
|
-
}
|
|
4431
|
-
};
|
|
4432
|
-
//#endregion
|
|
4433
|
-
//#region src/renderer/virtualized/timeline.ts
|
|
4434
|
-
function clamp(value, min, max) {
|
|
4435
|
-
return Math.min(Math.max(value, min), max);
|
|
4436
|
-
}
|
|
4437
|
-
/**
|
|
4438
|
-
* Virtualized renderer anchored to the top, suitable for timeline-style UIs.
|
|
4439
|
-
*/
|
|
4440
|
-
var TimelineRenderer = class extends VirtualizedRenderer {
|
|
4441
|
-
#resolveVisibleWindow() {
|
|
4442
|
-
const now = globalThis.performance?.now() ?? Date.now();
|
|
4443
|
-
return resolveTimelineVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item, idx) => {
|
|
4444
|
-
return this._resolveItem(item, idx, now);
|
|
4445
|
-
});
|
|
5229
|
+
_resolveVisibleWindowForState(state, now) {
|
|
5230
|
+
return resolveVisibleWindow(this.items, state, this.graphics.canvas.clientHeight, (item, idx) => this._resolveItem(item, idx, now), this.#layout);
|
|
4446
5231
|
}
|
|
4447
5232
|
_getDefaultJumpBlock() {
|
|
4448
|
-
return "start";
|
|
5233
|
+
return this.#layout.anchorMode === "top" ? "start" : "end";
|
|
4449
5234
|
}
|
|
4450
5235
|
_normalizeListState(state) {
|
|
4451
|
-
return
|
|
5236
|
+
return normalizeVisibleState(this.items.length, state, this.#layout);
|
|
4452
5237
|
}
|
|
4453
|
-
_readAnchor(state) {
|
|
4454
|
-
|
|
4455
|
-
const height = this._getItemHeight(state.position);
|
|
4456
|
-
return height > 0 ? state.position - state.offset / height : state.position;
|
|
5238
|
+
_readAnchor(state, readItemHeight) {
|
|
5239
|
+
return readAnchorFromState(this.items.length, state, this.#layout.anchorMode, readItemHeight);
|
|
4457
5240
|
}
|
|
4458
5241
|
_applyAnchor(anchor) {
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
const height = this._getItemHeight(position);
|
|
4463
|
-
const offset = height > 0 ? -(clampedAnchor - position) * height : 0;
|
|
4464
|
-
this._commitListState({
|
|
4465
|
-
position,
|
|
4466
|
-
offset: Object.is(offset, -0) ? 0 : offset
|
|
4467
|
-
});
|
|
5242
|
+
const state = applyAnchorToState(this.items.length, anchor, this.#layout.anchorMode, this._getItemHeight.bind(this));
|
|
5243
|
+
if (state == null) return;
|
|
5244
|
+
this._commitListState(state);
|
|
4468
5245
|
}
|
|
4469
5246
|
_getTargetAnchor(index, block) {
|
|
4470
|
-
|
|
4471
|
-
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
4472
|
-
switch (block) {
|
|
4473
|
-
case "start": return this._getAnchorAtOffset(index, 0);
|
|
4474
|
-
case "center": return this._getAnchorAtOffset(index, height / 2 - viewportHeight / 2);
|
|
4475
|
-
case "end": return this._getAnchorAtOffset(index, height - viewportHeight);
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
_getAnimatedLayerOffset(_slotHeight, _nodeHeight) {
|
|
4479
|
-
return 0;
|
|
4480
|
-
}
|
|
4481
|
-
render(feedback) {
|
|
4482
|
-
const keepAnimating = this._prepareRender();
|
|
4483
|
-
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
4484
|
-
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
4485
|
-
const solution = this.#resolveVisibleWindow();
|
|
4486
|
-
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
4487
|
-
this._commitListState(solution.normalizedState);
|
|
4488
|
-
return this._finishRender(keepAnimating || requestRedraw);
|
|
4489
|
-
}
|
|
4490
|
-
hittest(test) {
|
|
4491
|
-
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
5247
|
+
return getTargetAnchorForItem(this.items.length, index, block, this.#layout.anchorMode, this.graphics.canvas.clientHeight, this._getItemHeight.bind(this));
|
|
4492
5248
|
}
|
|
4493
5249
|
};
|
|
4494
5250
|
//#endregion
|
|
4495
|
-
export { BaseRenderer,
|
|
5251
|
+
export { BaseRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListRenderer, ListState, MultilineText, PaddingBox, Place, ShrinkWrap, Text, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
4496
5252
|
|
|
4497
5253
|
//# sourceMappingURL=index.mjs.map
|