chat-layout 1.2.0-8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/example/chat.ts +2 -9
- package/index.d.mts +5 -1
- package/index.mjs +203 -92
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,6 +194,24 @@ Type-check:
|
|
|
194
194
|
bun run typecheck
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
Run tests:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
bun run test
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Run tests with coverage:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
bun run test:coverage
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Run the local verification bundle:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
bun run check
|
|
213
|
+
```
|
|
214
|
+
|
|
197
215
|
Build distributable files:
|
|
198
216
|
|
|
199
217
|
```bash
|
package/example/chat.ts
CHANGED
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
ShrinkWrap,
|
|
11
11
|
Text,
|
|
12
12
|
Wrapper,
|
|
13
|
+
initRenderFeedback,
|
|
13
14
|
memoRenderItem,
|
|
14
15
|
type Context,
|
|
15
16
|
type DynValue,
|
|
16
17
|
type HitTest,
|
|
17
18
|
type InlineSpan,
|
|
18
19
|
type Node,
|
|
19
|
-
type RenderFeedback,
|
|
20
20
|
} from "chat-layout";
|
|
21
21
|
|
|
22
22
|
const sampleWords = [
|
|
@@ -507,16 +507,9 @@ const renderer = new ListRenderer(ctx, {
|
|
|
507
507
|
});
|
|
508
508
|
renderer.padding = { top: 32, bottom: 32 };
|
|
509
509
|
let nextMessageId = list.items.length + 1;
|
|
510
|
+
const feedback = initRenderFeedback();
|
|
510
511
|
|
|
511
512
|
function drawFrame(): void {
|
|
512
|
-
const feedback: RenderFeedback = {
|
|
513
|
-
minIdx: Number.NaN,
|
|
514
|
-
maxIdx: Number.NaN,
|
|
515
|
-
min: Number.NaN,
|
|
516
|
-
max: Number.NaN,
|
|
517
|
-
canAutoFollowTop: false,
|
|
518
|
-
canAutoFollowBottom: false,
|
|
519
|
-
};
|
|
520
513
|
renderer.render(feedback);
|
|
521
514
|
|
|
522
515
|
ctx.save();
|
package/index.d.mts
CHANGED
|
@@ -24,6 +24,10 @@ interface RenderFeedback {
|
|
|
24
24
|
/** Whether the current viewport may auto-follow inserts at the visual bottom edge. */
|
|
25
25
|
canAutoFollowBottom: boolean;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates or resets a render feedback object to its default empty state.
|
|
29
|
+
*/
|
|
30
|
+
declare function initRenderFeedback(feedback?: Partial<RenderFeedback>): RenderFeedback;
|
|
27
31
|
/**
|
|
28
32
|
* The main axis direction used by flex containers.
|
|
29
33
|
*/
|
|
@@ -779,5 +783,5 @@ declare class ListRenderer<C extends CanvasRenderingContext2D, T extends {}> ext
|
|
|
779
783
|
protected _getTargetAnchor(index: number, block: NonNullable<JumpToOptions["block"]>): number;
|
|
780
784
|
}
|
|
781
785
|
//#endregion
|
|
782
|
-
export { Axis, BaseRenderer, Box, ChildLayoutResult, Context, CrossAxisAlignment, DebugRenderer, DeleteListItemAnimationOptions, DynValue, Fixed, Flex, FlexContainerOptions, FlexItem, FlexItemOptions, FlexLayoutResult, Group, HitTest, InlineSpan, InsertListItemsAnimationOptions, JumpToOptions, LayoutConstraints, LayoutRect, ListAnchorMode, ListLayoutOptions, ListPadding, ListRenderer, ListRendererOptions, ListState, ListUnderflowAlign, MainAxisAlignment, MainAxisSize, MultilineText, MultilineTextOptions, Node, PaddingBox, PhysicalTextAlign, Place, PushListItemsAnimationOptions, RenderFeedback, RendererOptions, ShrinkWrap, Text, TextAlign, TextEllipsisPosition, TextJustifyMode, TextJustifyOptions, TextOptions, TextOverflowMode, TextOverflowWrapMode, TextStyleOptions, TextWhiteSpaceMode, TextWordBreakMode, UnshiftListItemsAnimationOptions, UpdateListItemAnimationOptions, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
786
|
+
export { Axis, BaseRenderer, Box, ChildLayoutResult, Context, CrossAxisAlignment, DebugRenderer, DeleteListItemAnimationOptions, DynValue, Fixed, Flex, FlexContainerOptions, FlexItem, FlexItemOptions, FlexLayoutResult, Group, HitTest, InlineSpan, InsertListItemsAnimationOptions, JumpToOptions, LayoutConstraints, LayoutRect, ListAnchorMode, ListLayoutOptions, ListPadding, ListRenderer, ListRendererOptions, ListState, ListUnderflowAlign, MainAxisAlignment, MainAxisSize, MultilineText, MultilineTextOptions, Node, PaddingBox, PhysicalTextAlign, Place, PushListItemsAnimationOptions, RenderFeedback, RendererOptions, ShrinkWrap, Text, TextAlign, TextEllipsisPosition, TextJustifyMode, TextJustifyOptions, TextOptions, TextOverflowMode, TextOverflowWrapMode, TextStyleOptions, TextWhiteSpaceMode, TextWordBreakMode, UnshiftListItemsAnimationOptions, UpdateListItemAnimationOptions, VirtualizedRenderer, Wrapper, initRenderFeedback, memoRenderItem, memoRenderItemBy };
|
|
783
787
|
//# sourceMappingURL=index.d.mts.map
|
package/index.mjs
CHANGED
|
@@ -3574,31 +3574,13 @@ var DebugRenderer = class extends BaseRenderer {
|
|
|
3574
3574
|
}
|
|
3575
3575
|
};
|
|
3576
3576
|
//#endregion
|
|
3577
|
-
//#region src/renderer/weak-listeners.ts
|
|
3578
|
-
function pruneWeakListenerMap(listeners) {
|
|
3579
|
-
for (const [token, listener] of listeners) if (listener.ownerRef.deref() == null) listeners.delete(token);
|
|
3580
|
-
}
|
|
3581
|
-
function emitWeakListeners(listeners, event) {
|
|
3582
|
-
for (const [token, listener] of [...listeners]) {
|
|
3583
|
-
const owner = listener.ownerRef.deref();
|
|
3584
|
-
if (owner == null) {
|
|
3585
|
-
listeners.delete(token);
|
|
3586
|
-
continue;
|
|
3587
|
-
}
|
|
3588
|
-
listener.notify(owner, event);
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
//#endregion
|
|
3592
3577
|
//#region src/renderer/list-state.ts
|
|
3593
|
-
const
|
|
3594
|
-
const listStateListenerRegistry = typeof FinalizationRegistry === "function" ? new FinalizationRegistry(({ listRef, token }) => {
|
|
3595
|
-
const list = listRef.deref();
|
|
3596
|
-
if (list == null) return;
|
|
3597
|
-
deleteListStateListener(list, token);
|
|
3598
|
-
}) : null;
|
|
3578
|
+
const listStateChangeQueues = /* @__PURE__ */ new WeakMap();
|
|
3599
3579
|
const listScrollMutations = /* @__PURE__ */ new WeakMap();
|
|
3600
3580
|
const WRITE_LIST_SCROLL_STATE = Symbol("writeListScrollState");
|
|
3601
3581
|
const FINALIZE_LIST_DELETE = Symbol("finalizeListDelete");
|
|
3582
|
+
const LIST_STATE_CHANGE_TIME = Symbol("listStateChangeTime");
|
|
3583
|
+
const LIST_STATE_CHANGE_SNAPSHOT = Symbol("listStateChangeSnapshot");
|
|
3602
3584
|
function normalizePosition(value) {
|
|
3603
3585
|
return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : void 0;
|
|
3604
3586
|
}
|
|
@@ -3634,34 +3616,40 @@ function writeInternalListScrollState(list, state) {
|
|
|
3634
3616
|
function finalizeInternalListDelete(list, item) {
|
|
3635
3617
|
list[FINALIZE_LIST_DELETE](item);
|
|
3636
3618
|
}
|
|
3637
|
-
function
|
|
3638
|
-
const listeners = listStateListeners.get(list);
|
|
3639
|
-
if (listeners == null) return;
|
|
3640
|
-
listeners.delete(token);
|
|
3641
|
-
if (listeners.size === 0) listStateListeners.delete(list);
|
|
3642
|
-
}
|
|
3643
|
-
function emitListStateChange(list, change) {
|
|
3644
|
-
const listeners = listStateListeners.get(list);
|
|
3645
|
-
if (listeners == null) return;
|
|
3646
|
-
emitWeakListeners(listeners, change);
|
|
3647
|
-
if (listeners.size === 0) listStateListeners.delete(list);
|
|
3648
|
-
}
|
|
3649
|
-
function subscribeListState(list, owner, listener) {
|
|
3619
|
+
function enqueueListStateChange(list, change) {
|
|
3650
3620
|
const key = list;
|
|
3651
|
-
let
|
|
3652
|
-
if (
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
}
|
|
3656
|
-
const
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3621
|
+
let queue = listStateChangeQueues.get(key);
|
|
3622
|
+
if (queue == null) {
|
|
3623
|
+
queue = [];
|
|
3624
|
+
listStateChangeQueues.set(key, queue);
|
|
3625
|
+
}
|
|
3626
|
+
const timestampedChange = change;
|
|
3627
|
+
Object.defineProperty(timestampedChange, LIST_STATE_CHANGE_TIME, {
|
|
3628
|
+
value: performance.now(),
|
|
3629
|
+
configurable: true
|
|
3660
3630
|
});
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3631
|
+
Object.defineProperty(timestampedChange, LIST_STATE_CHANGE_SNAPSHOT, {
|
|
3632
|
+
value: {
|
|
3633
|
+
items: [...list.items],
|
|
3634
|
+
position: list.position,
|
|
3635
|
+
offset: list.offset
|
|
3636
|
+
},
|
|
3637
|
+
configurable: true
|
|
3664
3638
|
});
|
|
3639
|
+
queue.push(timestampedChange);
|
|
3640
|
+
}
|
|
3641
|
+
function drainInternalListStateChanges(list) {
|
|
3642
|
+
const key = list;
|
|
3643
|
+
const queue = listStateChangeQueues.get(key);
|
|
3644
|
+
if (queue == null || queue.length === 0) return [];
|
|
3645
|
+
listStateChangeQueues.delete(key);
|
|
3646
|
+
return queue;
|
|
3647
|
+
}
|
|
3648
|
+
function readInternalListStateChangeTime(change) {
|
|
3649
|
+
return change[LIST_STATE_CHANGE_TIME];
|
|
3650
|
+
}
|
|
3651
|
+
function readInternalListStateChangeSnapshot(change) {
|
|
3652
|
+
return change[LIST_STATE_CHANGE_SNAPSHOT];
|
|
3665
3653
|
}
|
|
3666
3654
|
function isObjectIdentityCandidate(value) {
|
|
3667
3655
|
return typeof value === "object" && value !== null || typeof value === "function";
|
|
@@ -3724,7 +3712,7 @@ var ListState = class {
|
|
|
3724
3712
|
assertUniqueItemReferences(nextItems);
|
|
3725
3713
|
this.#items = nextItems;
|
|
3726
3714
|
this.#pendingDeletes.clear();
|
|
3727
|
-
|
|
3715
|
+
enqueueListStateChange(this, { type: "set" });
|
|
3728
3716
|
}
|
|
3729
3717
|
/**
|
|
3730
3718
|
* @param items Initial list items.
|
|
@@ -3745,7 +3733,7 @@ var ListState = class {
|
|
|
3745
3733
|
const normalizedAnimation = normalizeInsertAnimation(animation);
|
|
3746
3734
|
if (this.position != null) this.#writeScrollState({ position: this.position + items.length }, "internal");
|
|
3747
3735
|
this.#items = items.concat(this.#items);
|
|
3748
|
-
|
|
3736
|
+
enqueueListStateChange(this, {
|
|
3749
3737
|
type: "unshift",
|
|
3750
3738
|
count: items.length,
|
|
3751
3739
|
animation: normalizedAnimation
|
|
@@ -3761,7 +3749,7 @@ var ListState = class {
|
|
|
3761
3749
|
assertUniqueItemReferences(items, this.#items);
|
|
3762
3750
|
const normalizedAnimation = normalizeInsertAnimation(animation);
|
|
3763
3751
|
this.#items.push(...items);
|
|
3764
|
-
|
|
3752
|
+
enqueueListStateChange(this, {
|
|
3765
3753
|
type: "push",
|
|
3766
3754
|
count: items.length,
|
|
3767
3755
|
animation: normalizedAnimation
|
|
@@ -3779,7 +3767,7 @@ var ListState = class {
|
|
|
3779
3767
|
if (this.#items.includes(nextItem)) throw new Error("update() nextItem is already present in the list.");
|
|
3780
3768
|
const prevItem = this.#items[index];
|
|
3781
3769
|
this.#items[index] = nextItem;
|
|
3782
|
-
|
|
3770
|
+
enqueueListStateChange(this, {
|
|
3783
3771
|
type: "update",
|
|
3784
3772
|
prevItem,
|
|
3785
3773
|
nextItem,
|
|
@@ -3800,7 +3788,7 @@ var ListState = class {
|
|
|
3800
3788
|
return;
|
|
3801
3789
|
}
|
|
3802
3790
|
this.#pendingDeletes.add(item);
|
|
3803
|
-
|
|
3791
|
+
enqueueListStateChange(this, {
|
|
3804
3792
|
type: "delete",
|
|
3805
3793
|
item,
|
|
3806
3794
|
animation: normalizedAnimation
|
|
@@ -3823,7 +3811,7 @@ var ListState = class {
|
|
|
3823
3811
|
if (this.position > index) this.#writeScrollState({ position: this.position - 1 }, "internal");
|
|
3824
3812
|
else if (this.position === index) this.#writeScrollState({ position: Math.min(index, this.#items.length - 1) }, "internal");
|
|
3825
3813
|
}
|
|
3826
|
-
|
|
3814
|
+
enqueueListStateChange(this, {
|
|
3827
3815
|
type: "delete-finalize",
|
|
3828
3816
|
item
|
|
3829
3817
|
});
|
|
@@ -3840,7 +3828,7 @@ var ListState = class {
|
|
|
3840
3828
|
position: void 0,
|
|
3841
3829
|
offset: 0
|
|
3842
3830
|
}, "internal");
|
|
3843
|
-
|
|
3831
|
+
enqueueListStateChange(this, { type: "reset" });
|
|
3844
3832
|
}
|
|
3845
3833
|
/** Applies a relative pixel scroll delta. */
|
|
3846
3834
|
applyScroll(delta) {
|
|
@@ -3930,6 +3918,21 @@ function memoRenderItemBy(keyOf, renderItem, options = {}) {
|
|
|
3930
3918
|
});
|
|
3931
3919
|
}
|
|
3932
3920
|
//#endregion
|
|
3921
|
+
//#region src/types.ts
|
|
3922
|
+
/**
|
|
3923
|
+
* Creates or resets a render feedback object to its default empty state.
|
|
3924
|
+
*/
|
|
3925
|
+
function initRenderFeedback(feedback = {}) {
|
|
3926
|
+
const target = feedback;
|
|
3927
|
+
target.minIdx = NaN;
|
|
3928
|
+
target.maxIdx = NaN;
|
|
3929
|
+
target.min = NaN;
|
|
3930
|
+
target.max = NaN;
|
|
3931
|
+
target.canAutoFollowTop = false;
|
|
3932
|
+
target.canAutoFollowBottom = false;
|
|
3933
|
+
return target;
|
|
3934
|
+
}
|
|
3935
|
+
//#endregion
|
|
3933
3936
|
//#region src/renderer/virtualized/frame-session.ts
|
|
3934
3937
|
function prepareFrameSession(params) {
|
|
3935
3938
|
let solution = params.resolveVisibleWindow(params.now);
|
|
@@ -4046,6 +4049,10 @@ var JumpController = class JumpController {
|
|
|
4046
4049
|
#pendingAutoFollowRecomputeReasonTop = "init";
|
|
4047
4050
|
#pendingAutoFollowRecomputeReasonBottom = "init";
|
|
4048
4051
|
#pendingTransitionSettleReconcile = false;
|
|
4052
|
+
#autoFollowObservationCountTop = 0;
|
|
4053
|
+
#autoFollowObservationCountBottom = 0;
|
|
4054
|
+
#pendingAutoFollowInvalidationTop = false;
|
|
4055
|
+
#pendingAutoFollowInvalidationBottom = false;
|
|
4049
4056
|
#lastArmedAutoFollowBoundary;
|
|
4050
4057
|
#lastObservedRenderedAutoFollowTop = false;
|
|
4051
4058
|
#lastObservedRenderedAutoFollowBottom = false;
|
|
@@ -4127,12 +4134,32 @@ var JumpController = class JumpController {
|
|
|
4127
4134
|
block: boundary === "bottom" ? "end" : "start"
|
|
4128
4135
|
});
|
|
4129
4136
|
}
|
|
4137
|
+
beginAutoFollowBoundaryObservation(boundary) {
|
|
4138
|
+
if (boundary === "top") {
|
|
4139
|
+
this.#autoFollowObservationCountTop += 1;
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
this.#autoFollowObservationCountBottom += 1;
|
|
4143
|
+
}
|
|
4144
|
+
endAutoFollowBoundaryObservation(boundary) {
|
|
4145
|
+
if (boundary === "top") {
|
|
4146
|
+
this.#autoFollowObservationCountTop = Math.max(0, this.#autoFollowObservationCountTop - 1);
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
this.#autoFollowObservationCountBottom = Math.max(0, this.#autoFollowObservationCountBottom - 1);
|
|
4150
|
+
}
|
|
4151
|
+
invalidateAutoFollowBoundary(boundary) {
|
|
4152
|
+
if (boundary == null || boundary === "top") this.#pendingAutoFollowInvalidationTop = true;
|
|
4153
|
+
if (boundary == null || boundary === "bottom") this.#pendingAutoFollowInvalidationBottom = true;
|
|
4154
|
+
}
|
|
4130
4155
|
recomputeAutoFollowCapabilities(capabilities) {
|
|
4131
4156
|
const previouslyObservedDualBoundary = this.#lastObservedRenderedAutoFollowTop && this.#lastObservedRenderedAutoFollowBottom;
|
|
4132
4157
|
if (capabilities.top && capabilities.bottom && !previouslyObservedDualBoundary) {
|
|
4133
4158
|
this.#setAutoFollowBoundary("top", true, "dual-boundary-promotion");
|
|
4134
4159
|
this.#setAutoFollowBoundary("bottom", true, "dual-boundary-promotion");
|
|
4135
4160
|
}
|
|
4161
|
+
this.#syncObservedOrInvalidatedBoundary("top", capabilities);
|
|
4162
|
+
this.#syncObservedOrInvalidatedBoundary("bottom", capabilities);
|
|
4136
4163
|
if (this.#pendingAutoFollowRecomputeTop) {
|
|
4137
4164
|
this.#setAutoFollowBoundary("top", capabilities.top, `strict-recompute:${this.#pendingAutoFollowRecomputeReasonTop}`);
|
|
4138
4165
|
this.#pendingAutoFollowRecomputeTop = false;
|
|
@@ -4159,22 +4186,23 @@ var JumpController = class JumpController {
|
|
|
4159
4186
|
reconcileAutoFollowAfterTransitionSettle() {
|
|
4160
4187
|
this.#pendingTransitionSettleReconcile = true;
|
|
4161
4188
|
}
|
|
4162
|
-
handleListStateChange(change) {
|
|
4189
|
+
handleListStateChange(change, now = getNow()) {
|
|
4163
4190
|
switch (change.type) {
|
|
4164
4191
|
case "reset":
|
|
4165
4192
|
case "set":
|
|
4166
4193
|
this.#cancelJumpAnimation();
|
|
4167
4194
|
this.#clearPendingPostJumpBoundary();
|
|
4168
4195
|
this.#clearPendingTransitionSettleReconcile();
|
|
4196
|
+
this.#clearAutoFollowObservationState();
|
|
4169
4197
|
this.#syncScrollMutationVersion();
|
|
4170
4198
|
this.#markAutoFollowRecompute(void 0, change.type);
|
|
4171
4199
|
return change;
|
|
4172
4200
|
case "push":
|
|
4173
|
-
case "unshift": return this.#handleBoundaryInsert(change);
|
|
4201
|
+
case "unshift": return this.#handleBoundaryInsert(change, now);
|
|
4174
4202
|
default: return change;
|
|
4175
4203
|
}
|
|
4176
4204
|
}
|
|
4177
|
-
#handleBoundaryInsert(change) {
|
|
4205
|
+
#handleBoundaryInsert(change, now) {
|
|
4178
4206
|
if (this.#handlePendingExternalScrollMutation()) return change;
|
|
4179
4207
|
this.#clearPendingTransitionSettleReconcile();
|
|
4180
4208
|
const followChange = this.#resolveAutoFollowChange(change);
|
|
@@ -4187,21 +4215,21 @@ var JumpController = class JumpController {
|
|
|
4187
4215
|
this.#lastArmedAutoFollowBoundary = followChange.boundary;
|
|
4188
4216
|
}
|
|
4189
4217
|
this.#clearPendingPostJumpBoundary();
|
|
4190
|
-
this.#materializeAnimatedAnchor(
|
|
4218
|
+
this.#materializeAnimatedAnchor(now, followChange.direction, followChange.count);
|
|
4191
4219
|
this.#startJumpToIndex(followChange.boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
|
|
4192
4220
|
block: followChange.boundary === "bottom" ? "end" : "start",
|
|
4193
4221
|
duration: followChange.animation?.duration
|
|
4194
|
-
});
|
|
4222
|
+
}, now);
|
|
4195
4223
|
return change;
|
|
4196
4224
|
}
|
|
4197
4225
|
#cancelJumpAnimation() {
|
|
4198
4226
|
this.#jumpAnimation = void 0;
|
|
4199
4227
|
}
|
|
4200
|
-
#startJumpToIndex(index, options) {
|
|
4228
|
+
#startJumpToIndex(index, options, now = getNow()) {
|
|
4201
4229
|
const targetIndex = this.#options.clampItemIndex(index);
|
|
4202
4230
|
const targetBlock = options.block ?? this.#options.getDefaultJumpBlock();
|
|
4203
4231
|
const settleBoundary = this.#resolveBoundaryLatchTarget(targetIndex, targetBlock);
|
|
4204
|
-
this.#materializeAnimatedAnchor(
|
|
4232
|
+
this.#materializeAnimatedAnchor(now);
|
|
4205
4233
|
const currentState = this.#options.normalizeListState(this.#options.readListState());
|
|
4206
4234
|
const targetAnchor = this.#options.getTargetAnchor(targetIndex, targetBlock);
|
|
4207
4235
|
if (!(options.animated ?? true)) {
|
|
@@ -4234,7 +4262,7 @@ var JumpController = class JumpController {
|
|
|
4234
4262
|
}
|
|
4235
4263
|
this.#jumpAnimation = {
|
|
4236
4264
|
path,
|
|
4237
|
-
startTime:
|
|
4265
|
+
startTime: now,
|
|
4238
4266
|
duration,
|
|
4239
4267
|
needsMoreFrames: true,
|
|
4240
4268
|
onComplete: options.onComplete
|
|
@@ -4288,6 +4316,12 @@ var JumpController = class JumpController {
|
|
|
4288
4316
|
#clearPendingTransitionSettleReconcile() {
|
|
4289
4317
|
this.#pendingTransitionSettleReconcile = false;
|
|
4290
4318
|
}
|
|
4319
|
+
#clearAutoFollowObservationState() {
|
|
4320
|
+
this.#autoFollowObservationCountTop = 0;
|
|
4321
|
+
this.#autoFollowObservationCountBottom = 0;
|
|
4322
|
+
this.#pendingAutoFollowInvalidationTop = false;
|
|
4323
|
+
this.#pendingAutoFollowInvalidationBottom = false;
|
|
4324
|
+
}
|
|
4291
4325
|
#materializeAnimatedAnchor(now, direction, count = 0) {
|
|
4292
4326
|
const animation = this.#jumpAnimation;
|
|
4293
4327
|
if (animation == null) return;
|
|
@@ -4302,6 +4336,20 @@ var JumpController = class JumpController {
|
|
|
4302
4336
|
if (boundary === "top") this.#canAutoFollowTop = value;
|
|
4303
4337
|
else this.#canAutoFollowBottom = value;
|
|
4304
4338
|
}
|
|
4339
|
+
#syncObservedOrInvalidatedBoundary(boundary, capabilities) {
|
|
4340
|
+
const isObserved = this.#readAutoFollowObservationCount(boundary) > 0;
|
|
4341
|
+
const isInvalidated = boundary === "top" ? this.#pendingAutoFollowInvalidationTop : this.#pendingAutoFollowInvalidationBottom;
|
|
4342
|
+
if (!isObserved && !isInvalidated) return;
|
|
4343
|
+
this.#setAutoFollowBoundary(boundary, this.#readCapabilityForBoundary(capabilities, boundary), "transition-observation");
|
|
4344
|
+
if (boundary === "top") {
|
|
4345
|
+
this.#pendingAutoFollowInvalidationTop = false;
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
this.#pendingAutoFollowInvalidationBottom = false;
|
|
4349
|
+
}
|
|
4350
|
+
#readAutoFollowObservationCount(boundary) {
|
|
4351
|
+
return boundary === "top" ? this.#autoFollowObservationCountTop : this.#autoFollowObservationCountBottom;
|
|
4352
|
+
}
|
|
4305
4353
|
#syncLastArmedBoundaryFromLatchedState() {
|
|
4306
4354
|
if (this.#canAutoFollowTop === this.#canAutoFollowBottom) return;
|
|
4307
4355
|
this.#lastArmedAutoFollowBoundary = this.#canAutoFollowTop ? "top" : "bottom";
|
|
@@ -4815,11 +4863,15 @@ var TransitionStore = class {
|
|
|
4815
4863
|
return this.#transitions.has(item);
|
|
4816
4864
|
}
|
|
4817
4865
|
set(item, transition) {
|
|
4866
|
+
const previous = this.#transitions.get(item);
|
|
4818
4867
|
this.#transitions.set(item, transition);
|
|
4868
|
+
return previous;
|
|
4819
4869
|
}
|
|
4820
4870
|
replace(prevItem, nextItem, transition) {
|
|
4871
|
+
const previous = this.#transitions.get(prevItem);
|
|
4821
4872
|
this.#transitions.delete(prevItem);
|
|
4822
4873
|
this.#transitions.set(nextItem, transition);
|
|
4874
|
+
return previous;
|
|
4823
4875
|
}
|
|
4824
4876
|
delete(item) {
|
|
4825
4877
|
const transition = this.#transitions.get(item);
|
|
@@ -4847,6 +4899,12 @@ var TransitionStore = class {
|
|
|
4847
4899
|
transition
|
|
4848
4900
|
}));
|
|
4849
4901
|
}
|
|
4902
|
+
entries() {
|
|
4903
|
+
return [...this.#transitions.entries()].map(([item, transition]) => ({
|
|
4904
|
+
item,
|
|
4905
|
+
transition
|
|
4906
|
+
}));
|
|
4907
|
+
}
|
|
4850
4908
|
reset() {
|
|
4851
4909
|
this.#transitions.clear();
|
|
4852
4910
|
}
|
|
@@ -4939,6 +4997,30 @@ function planExistingItemTransition(params) {
|
|
|
4939
4997
|
retention: "drawn"
|
|
4940
4998
|
};
|
|
4941
4999
|
}
|
|
5000
|
+
function resolveAutoFollowBoundaryRisk(index, ctx, snapshot) {
|
|
5001
|
+
const drawnRange = snapshot.readDrawnIndexRange();
|
|
5002
|
+
if (index < 0 || !snapshot.hasSnapshot || drawnRange == null || !Number.isFinite(drawnRange.minIndex) || !Number.isFinite(drawnRange.maxIndex)) return;
|
|
5003
|
+
if (ctx.anchorMode === "bottom") return index <= drawnRange.minIndex ? "top" : void 0;
|
|
5004
|
+
return index >= drawnRange.maxIndex ? "bottom" : void 0;
|
|
5005
|
+
}
|
|
5006
|
+
function canClassifyAutoFollowBoundaryRisk(index, snapshot) {
|
|
5007
|
+
return index >= 0 && snapshot.hasSnapshot && snapshot.readDrawnIndexRange() != null;
|
|
5008
|
+
}
|
|
5009
|
+
function beginTransitionAutoFollowObservation(transition, lifecycle) {
|
|
5010
|
+
if (transition.observedAutoFollowBoundary == null) return;
|
|
5011
|
+
lifecycle.beginAutoFollowBoundaryObservation(transition.observedAutoFollowBoundary);
|
|
5012
|
+
}
|
|
5013
|
+
function endTransitionAutoFollowObservation(transition, lifecycle) {
|
|
5014
|
+
if (transition?.observedAutoFollowBoundary == null) return;
|
|
5015
|
+
lifecycle.endAutoFollowBoundaryObservation(transition.observedAutoFollowBoundary);
|
|
5016
|
+
}
|
|
5017
|
+
function invalidateAutoFollowBoundaryRisk(boundary, canClassify, lifecycle) {
|
|
5018
|
+
if (boundary != null) {
|
|
5019
|
+
lifecycle.invalidateAutoFollowBoundary(boundary);
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
if (!canClassify) lifecycle.invalidateAutoFollowBoundary(void 0);
|
|
5023
|
+
}
|
|
4942
5024
|
function planBoundaryInsertItems(params) {
|
|
4943
5025
|
const entries = [];
|
|
4944
5026
|
for (const { item, node, height } of params.measuredItems) {
|
|
@@ -5102,40 +5184,50 @@ function readCurrentVisualState(item, now, store, adapter) {
|
|
|
5102
5184
|
translateY: 0
|
|
5103
5185
|
};
|
|
5104
5186
|
}
|
|
5105
|
-
function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
|
|
5187
|
+
function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle, now = getNow()) {
|
|
5106
5188
|
switch (change.type) {
|
|
5107
5189
|
case "update": {
|
|
5108
|
-
const
|
|
5190
|
+
const nextIndex = ctx.items.indexOf(change.nextItem);
|
|
5191
|
+
const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(nextIndex, snapshot);
|
|
5192
|
+
const observedBoundary = resolveAutoFollowBoundaryRisk(nextIndex, ctx, snapshot);
|
|
5109
5193
|
const currentVisualState = readCurrentVisualState(change.prevItem, now, store, ctx);
|
|
5110
5194
|
const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
|
|
5111
5195
|
if (transition == null) {
|
|
5112
|
-
store.delete(change.prevItem);
|
|
5196
|
+
endTransitionAutoFollowObservation(store.delete(change.prevItem), lifecycle);
|
|
5197
|
+
invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
|
|
5113
5198
|
return;
|
|
5114
5199
|
}
|
|
5115
|
-
|
|
5200
|
+
transition.observedAutoFollowBoundary = observedBoundary;
|
|
5201
|
+
endTransitionAutoFollowObservation(store.replace(change.prevItem, change.nextItem, transition), lifecycle);
|
|
5202
|
+
beginTransitionAutoFollowObservation(transition, lifecycle);
|
|
5116
5203
|
return;
|
|
5117
5204
|
}
|
|
5118
5205
|
case "delete": {
|
|
5119
|
-
const
|
|
5206
|
+
const index = ctx.items.indexOf(change.item);
|
|
5207
|
+
const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(index, snapshot);
|
|
5208
|
+
const observedBoundary = resolveAutoFollowBoundaryRisk(index, ctx, snapshot);
|
|
5120
5209
|
const currentVisualState = readCurrentVisualState(change.item, now, store, ctx);
|
|
5121
5210
|
const transition = planDeleteTransition(change.item, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
|
|
5122
5211
|
if (transition == null) {
|
|
5123
|
-
store.delete(change.item);
|
|
5212
|
+
endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
|
|
5213
|
+
invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
|
|
5124
5214
|
lifecycle.onDeleteComplete(change.item);
|
|
5125
5215
|
return;
|
|
5126
5216
|
}
|
|
5127
|
-
|
|
5217
|
+
transition.observedAutoFollowBoundary = observedBoundary;
|
|
5218
|
+
endTransitionAutoFollowObservation(store.set(change.item, transition), lifecycle);
|
|
5219
|
+
beginTransitionAutoFollowObservation(transition, lifecycle);
|
|
5128
5220
|
return;
|
|
5129
5221
|
}
|
|
5130
5222
|
case "delete-finalize":
|
|
5131
|
-
store.delete(change.item);
|
|
5223
|
+
endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
|
|
5224
|
+
lifecycle.invalidateAutoFollowBoundary(void 0);
|
|
5132
5225
|
return;
|
|
5133
5226
|
case "unshift":
|
|
5134
5227
|
case "push": {
|
|
5135
|
-
const now = getNow();
|
|
5136
5228
|
const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, now, ctx, snapshot);
|
|
5137
5229
|
if (plan == null) return;
|
|
5138
|
-
for (const entry of plan.entries) store.set(entry.item, entry.transition);
|
|
5230
|
+
for (const entry of plan.entries) endTransitionAutoFollowObservation(store.set(entry.item, entry.transition), lifecycle);
|
|
5139
5231
|
if (ctx.position == null && snapshot.coversShortList && (change.type === "push" && ctx.anchorMode === "bottom" || change.type === "unshift" && ctx.anchorMode === "top")) {
|
|
5140
5232
|
const boundary = change.type === "push" ? "bottom" : "top";
|
|
5141
5233
|
const boundaryItem = snapshot.readBoundaryItem(boundary);
|
|
@@ -5145,6 +5237,7 @@ function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
|
|
|
5145
5237
|
}
|
|
5146
5238
|
case "reset":
|
|
5147
5239
|
case "set":
|
|
5240
|
+
for (const entry of store.entries()) endTransitionAutoFollowObservation(entry.transition, lifecycle);
|
|
5148
5241
|
store.reset();
|
|
5149
5242
|
snapshot.reset();
|
|
5150
5243
|
return;
|
|
@@ -5187,10 +5280,9 @@ var TransitionController = class {
|
|
|
5187
5280
|
resolveItem(item, now, adapter, lifecycle) {
|
|
5188
5281
|
return resolveTransitionedItem(item, now, this.#store, adapter, lifecycle);
|
|
5189
5282
|
}
|
|
5190
|
-
handleListStateChange(change, ctx, lifecycle) {
|
|
5191
|
-
const now = getNow();
|
|
5283
|
+
handleListStateChange(change, ctx, lifecycle, now = getNow()) {
|
|
5192
5284
|
this.settle(now, lifecycle);
|
|
5193
|
-
handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle);
|
|
5285
|
+
handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle, now);
|
|
5194
5286
|
}
|
|
5195
5287
|
settle(now, lifecycle) {
|
|
5196
5288
|
return this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
|
|
@@ -5213,7 +5305,11 @@ var TransitionController = class {
|
|
|
5213
5305
|
const index = lifecycle.readItemIndex(item);
|
|
5214
5306
|
if (index >= 0) completedDeleteIndices.push(index);
|
|
5215
5307
|
}
|
|
5216
|
-
this.#store.delete(item);
|
|
5308
|
+
const removedTransition = this.#store.delete(item);
|
|
5309
|
+
if (removedTransition?.observedAutoFollowBoundary != null) {
|
|
5310
|
+
lifecycle.endAutoFollowBoundaryObservation(removedTransition.observedAutoFollowBoundary);
|
|
5311
|
+
lifecycle.invalidateAutoFollowBoundary(removedTransition.observedAutoFollowBoundary);
|
|
5312
|
+
}
|
|
5217
5313
|
if (transition.kind === "delete") lifecycle.onDeleteComplete(item);
|
|
5218
5314
|
}
|
|
5219
5315
|
if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(remapAnchorAfterDeletes(anchor, completedDeleteIndices));
|
|
@@ -5270,6 +5366,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5270
5366
|
static JUMP_DURATION_PER_PIXEL = .7;
|
|
5271
5367
|
#jumpController;
|
|
5272
5368
|
#transitionController = new TransitionController();
|
|
5369
|
+
#listStateOverride;
|
|
5273
5370
|
constructor(graphics, options) {
|
|
5274
5371
|
super(graphics, options);
|
|
5275
5372
|
this.#jumpController = new JumpController({
|
|
@@ -5287,21 +5384,18 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5287
5384
|
clampItemIndex: this._clampItemIndex.bind(this),
|
|
5288
5385
|
getItemHeight: this._getItemHeight.bind(this)
|
|
5289
5386
|
});
|
|
5290
|
-
subscribeListState(options.list, this, (owner, change) => {
|
|
5291
|
-
owner.#handleListStateChange(change);
|
|
5292
|
-
});
|
|
5293
5387
|
}
|
|
5294
5388
|
/** Current anchor item index. */
|
|
5295
5389
|
get position() {
|
|
5296
|
-
return this.options.list.position;
|
|
5390
|
+
return this.#listStateOverride?.position ?? this.options.list.position;
|
|
5297
5391
|
}
|
|
5298
5392
|
/** Pixel offset from the anchored item edge. */
|
|
5299
5393
|
get offset() {
|
|
5300
|
-
return this.options.list.offset;
|
|
5394
|
+
return this.#listStateOverride?.offset ?? this.options.list.offset;
|
|
5301
5395
|
}
|
|
5302
5396
|
/** Items currently available to the renderer. */
|
|
5303
5397
|
get items() {
|
|
5304
|
-
return this.options.list.items;
|
|
5398
|
+
return this.#listStateOverride?.items ?? this.options.list.items;
|
|
5305
5399
|
}
|
|
5306
5400
|
/** Replaces the current item collection. */
|
|
5307
5401
|
set items(value) {
|
|
@@ -5309,6 +5403,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5309
5403
|
}
|
|
5310
5404
|
/** Renders the current visible window. */
|
|
5311
5405
|
render(feedback) {
|
|
5406
|
+
this.#drainPendingListStateChanges();
|
|
5312
5407
|
this.#jumpController.beforeFrame();
|
|
5313
5408
|
this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
|
|
5314
5409
|
const now = getNow();
|
|
@@ -5332,6 +5427,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5332
5427
|
}
|
|
5333
5428
|
/** Hit-tests the current visible window. */
|
|
5334
5429
|
hittest(test) {
|
|
5430
|
+
this.#drainPendingListStateChanges();
|
|
5335
5431
|
this.#jumpController.beforeFrame();
|
|
5336
5432
|
this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
|
|
5337
5433
|
const now = getNow();
|
|
@@ -5378,12 +5474,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5378
5474
|
}
|
|
5379
5475
|
_resetRenderFeedback(feedback) {
|
|
5380
5476
|
if (feedback == null) return;
|
|
5381
|
-
feedback
|
|
5382
|
-
feedback.maxIdx = NaN;
|
|
5383
|
-
feedback.min = NaN;
|
|
5384
|
-
feedback.max = NaN;
|
|
5385
|
-
feedback.canAutoFollowTop = false;
|
|
5386
|
-
feedback.canAutoFollowBottom = false;
|
|
5477
|
+
initRenderFeedback(feedback);
|
|
5387
5478
|
}
|
|
5388
5479
|
_accumulateRenderFeedback(feedback, idx, top, height) {
|
|
5389
5480
|
const visibleRange = this._readVisibleRange(top, height);
|
|
@@ -5514,6 +5605,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5514
5605
|
}
|
|
5515
5606
|
#handleDeleteComplete(item) {
|
|
5516
5607
|
finalizeInternalListDelete(this.options.list, item);
|
|
5608
|
+
this.#drainPendingListStateChanges();
|
|
5517
5609
|
}
|
|
5518
5610
|
#getTransitionLifecycleAdapter() {
|
|
5519
5611
|
return {
|
|
@@ -5523,7 +5615,10 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5523
5615
|
readScrollState: this._readListState.bind(this),
|
|
5524
5616
|
readItemIndex: (item) => this.items.indexOf(item),
|
|
5525
5617
|
snapItemToViewportBoundary: this.#snapItemToViewportBoundary.bind(this),
|
|
5526
|
-
onTransitionSettleScrollAdjusted: () => this.#jumpController.reconcileAutoFollowAfterTransitionSettle()
|
|
5618
|
+
onTransitionSettleScrollAdjusted: () => this.#jumpController.reconcileAutoFollowAfterTransitionSettle(),
|
|
5619
|
+
beginAutoFollowBoundaryObservation: (boundary) => this.#jumpController.beginAutoFollowBoundaryObservation(boundary),
|
|
5620
|
+
endAutoFollowBoundaryObservation: (boundary) => this.#jumpController.endAutoFollowBoundaryObservation(boundary),
|
|
5621
|
+
invalidateAutoFollowBoundary: (boundary) => this.#jumpController.invalidateAutoFollowBoundary(boundary)
|
|
5527
5622
|
};
|
|
5528
5623
|
}
|
|
5529
5624
|
#getVirtualizedRuntime() {
|
|
@@ -5557,9 +5652,25 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
5557
5652
|
anchorMode: this._getLayoutOptions().anchorMode
|
|
5558
5653
|
};
|
|
5559
5654
|
}
|
|
5560
|
-
#handleListStateChange(change) {
|
|
5561
|
-
|
|
5562
|
-
|
|
5655
|
+
#handleListStateChange(change, now = getNow(), snapshot) {
|
|
5656
|
+
this.#listStateOverride = snapshot == null ? void 0 : {
|
|
5657
|
+
items: [...snapshot.items],
|
|
5658
|
+
position: snapshot.position,
|
|
5659
|
+
offset: snapshot.offset
|
|
5660
|
+
};
|
|
5661
|
+
try {
|
|
5662
|
+
const nextChange = this.#jumpController.handleListStateChange(change, now);
|
|
5663
|
+
this.#transitionController.handleListStateChange(nextChange, this.#getTransitionPlanningAdapter(), this.#getTransitionLifecycleAdapter(), now);
|
|
5664
|
+
} finally {
|
|
5665
|
+
this.#listStateOverride = void 0;
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
#drainPendingListStateChanges() {
|
|
5669
|
+
while (true) {
|
|
5670
|
+
const changes = drainInternalListStateChanges(this.options.list);
|
|
5671
|
+
if (changes.length === 0) return;
|
|
5672
|
+
for (const change of changes) this.#handleListStateChange(change, readInternalListStateChangeTime(change), readInternalListStateChangeSnapshot(change));
|
|
5673
|
+
}
|
|
5563
5674
|
}
|
|
5564
5675
|
};
|
|
5565
5676
|
//#endregion
|
|
@@ -5679,6 +5790,6 @@ var ListRenderer = class extends VirtualizedRenderer {
|
|
|
5679
5790
|
}
|
|
5680
5791
|
};
|
|
5681
5792
|
//#endregion
|
|
5682
|
-
export { BaseRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListRenderer, ListState, MultilineText, PaddingBox, Place, ShrinkWrap, Text, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
5793
|
+
export { BaseRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListRenderer, ListState, MultilineText, PaddingBox, Place, ShrinkWrap, Text, VirtualizedRenderer, Wrapper, initRenderFeedback, memoRenderItem, memoRenderItemBy };
|
|
5683
5794
|
|
|
5684
5795
|
//# sourceMappingURL=index.mjs.map
|