chat-layout 1.2.0-8 → 1.2.0-9

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.
Files changed (3) hide show
  1. package/index.mjs +186 -85
  2. package/index.mjs.map +1 -1
  3. package/package.json +1 -1
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 listStateListeners = /* @__PURE__ */ new WeakMap();
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 deleteListStateListener(list, token) {
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 listeners = listStateListeners.get(key);
3652
- if (listeners == null) {
3653
- listeners = /* @__PURE__ */ new Map();
3654
- listStateListeners.set(key, listeners);
3655
- } else pruneWeakListenerMap(listeners);
3656
- const token = Symbol();
3657
- listeners.set(token, {
3658
- ownerRef: new WeakRef(owner),
3659
- notify: listener
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
- listStateListenerRegistry?.register(owner, {
3662
- listRef: new WeakRef(key),
3663
- token
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
- emitListStateChange(this, { type: "set" });
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
- emitListStateChange(this, {
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
- emitListStateChange(this, {
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
- emitListStateChange(this, {
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
- emitListStateChange(this, {
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
- emitListStateChange(this, {
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
- emitListStateChange(this, { type: "reset" });
3831
+ enqueueListStateChange(this, { type: "reset" });
3844
3832
  }
3845
3833
  /** Applies a relative pixel scroll delta. */
3846
3834
  applyScroll(delta) {
@@ -4046,6 +4034,10 @@ var JumpController = class JumpController {
4046
4034
  #pendingAutoFollowRecomputeReasonTop = "init";
4047
4035
  #pendingAutoFollowRecomputeReasonBottom = "init";
4048
4036
  #pendingTransitionSettleReconcile = false;
4037
+ #autoFollowObservationCountTop = 0;
4038
+ #autoFollowObservationCountBottom = 0;
4039
+ #pendingAutoFollowInvalidationTop = false;
4040
+ #pendingAutoFollowInvalidationBottom = false;
4049
4041
  #lastArmedAutoFollowBoundary;
4050
4042
  #lastObservedRenderedAutoFollowTop = false;
4051
4043
  #lastObservedRenderedAutoFollowBottom = false;
@@ -4127,12 +4119,32 @@ var JumpController = class JumpController {
4127
4119
  block: boundary === "bottom" ? "end" : "start"
4128
4120
  });
4129
4121
  }
4122
+ beginAutoFollowBoundaryObservation(boundary) {
4123
+ if (boundary === "top") {
4124
+ this.#autoFollowObservationCountTop += 1;
4125
+ return;
4126
+ }
4127
+ this.#autoFollowObservationCountBottom += 1;
4128
+ }
4129
+ endAutoFollowBoundaryObservation(boundary) {
4130
+ if (boundary === "top") {
4131
+ this.#autoFollowObservationCountTop = Math.max(0, this.#autoFollowObservationCountTop - 1);
4132
+ return;
4133
+ }
4134
+ this.#autoFollowObservationCountBottom = Math.max(0, this.#autoFollowObservationCountBottom - 1);
4135
+ }
4136
+ invalidateAutoFollowBoundary(boundary) {
4137
+ if (boundary == null || boundary === "top") this.#pendingAutoFollowInvalidationTop = true;
4138
+ if (boundary == null || boundary === "bottom") this.#pendingAutoFollowInvalidationBottom = true;
4139
+ }
4130
4140
  recomputeAutoFollowCapabilities(capabilities) {
4131
4141
  const previouslyObservedDualBoundary = this.#lastObservedRenderedAutoFollowTop && this.#lastObservedRenderedAutoFollowBottom;
4132
4142
  if (capabilities.top && capabilities.bottom && !previouslyObservedDualBoundary) {
4133
4143
  this.#setAutoFollowBoundary("top", true, "dual-boundary-promotion");
4134
4144
  this.#setAutoFollowBoundary("bottom", true, "dual-boundary-promotion");
4135
4145
  }
4146
+ this.#syncObservedOrInvalidatedBoundary("top", capabilities);
4147
+ this.#syncObservedOrInvalidatedBoundary("bottom", capabilities);
4136
4148
  if (this.#pendingAutoFollowRecomputeTop) {
4137
4149
  this.#setAutoFollowBoundary("top", capabilities.top, `strict-recompute:${this.#pendingAutoFollowRecomputeReasonTop}`);
4138
4150
  this.#pendingAutoFollowRecomputeTop = false;
@@ -4159,22 +4171,23 @@ var JumpController = class JumpController {
4159
4171
  reconcileAutoFollowAfterTransitionSettle() {
4160
4172
  this.#pendingTransitionSettleReconcile = true;
4161
4173
  }
4162
- handleListStateChange(change) {
4174
+ handleListStateChange(change, now = getNow()) {
4163
4175
  switch (change.type) {
4164
4176
  case "reset":
4165
4177
  case "set":
4166
4178
  this.#cancelJumpAnimation();
4167
4179
  this.#clearPendingPostJumpBoundary();
4168
4180
  this.#clearPendingTransitionSettleReconcile();
4181
+ this.#clearAutoFollowObservationState();
4169
4182
  this.#syncScrollMutationVersion();
4170
4183
  this.#markAutoFollowRecompute(void 0, change.type);
4171
4184
  return change;
4172
4185
  case "push":
4173
- case "unshift": return this.#handleBoundaryInsert(change);
4186
+ case "unshift": return this.#handleBoundaryInsert(change, now);
4174
4187
  default: return change;
4175
4188
  }
4176
4189
  }
4177
- #handleBoundaryInsert(change) {
4190
+ #handleBoundaryInsert(change, now) {
4178
4191
  if (this.#handlePendingExternalScrollMutation()) return change;
4179
4192
  this.#clearPendingTransitionSettleReconcile();
4180
4193
  const followChange = this.#resolveAutoFollowChange(change);
@@ -4187,21 +4200,21 @@ var JumpController = class JumpController {
4187
4200
  this.#lastArmedAutoFollowBoundary = followChange.boundary;
4188
4201
  }
4189
4202
  this.#clearPendingPostJumpBoundary();
4190
- this.#materializeAnimatedAnchor(getNow(), followChange.direction, followChange.count);
4203
+ this.#materializeAnimatedAnchor(now, followChange.direction, followChange.count);
4191
4204
  this.#startJumpToIndex(followChange.boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
4192
4205
  block: followChange.boundary === "bottom" ? "end" : "start",
4193
4206
  duration: followChange.animation?.duration
4194
- });
4207
+ }, now);
4195
4208
  return change;
4196
4209
  }
4197
4210
  #cancelJumpAnimation() {
4198
4211
  this.#jumpAnimation = void 0;
4199
4212
  }
4200
- #startJumpToIndex(index, options) {
4213
+ #startJumpToIndex(index, options, now = getNow()) {
4201
4214
  const targetIndex = this.#options.clampItemIndex(index);
4202
4215
  const targetBlock = options.block ?? this.#options.getDefaultJumpBlock();
4203
4216
  const settleBoundary = this.#resolveBoundaryLatchTarget(targetIndex, targetBlock);
4204
- this.#materializeAnimatedAnchor(getNow());
4217
+ this.#materializeAnimatedAnchor(now);
4205
4218
  const currentState = this.#options.normalizeListState(this.#options.readListState());
4206
4219
  const targetAnchor = this.#options.getTargetAnchor(targetIndex, targetBlock);
4207
4220
  if (!(options.animated ?? true)) {
@@ -4234,7 +4247,7 @@ var JumpController = class JumpController {
4234
4247
  }
4235
4248
  this.#jumpAnimation = {
4236
4249
  path,
4237
- startTime: getNow(),
4250
+ startTime: now,
4238
4251
  duration,
4239
4252
  needsMoreFrames: true,
4240
4253
  onComplete: options.onComplete
@@ -4288,6 +4301,12 @@ var JumpController = class JumpController {
4288
4301
  #clearPendingTransitionSettleReconcile() {
4289
4302
  this.#pendingTransitionSettleReconcile = false;
4290
4303
  }
4304
+ #clearAutoFollowObservationState() {
4305
+ this.#autoFollowObservationCountTop = 0;
4306
+ this.#autoFollowObservationCountBottom = 0;
4307
+ this.#pendingAutoFollowInvalidationTop = false;
4308
+ this.#pendingAutoFollowInvalidationBottom = false;
4309
+ }
4291
4310
  #materializeAnimatedAnchor(now, direction, count = 0) {
4292
4311
  const animation = this.#jumpAnimation;
4293
4312
  if (animation == null) return;
@@ -4302,6 +4321,20 @@ var JumpController = class JumpController {
4302
4321
  if (boundary === "top") this.#canAutoFollowTop = value;
4303
4322
  else this.#canAutoFollowBottom = value;
4304
4323
  }
4324
+ #syncObservedOrInvalidatedBoundary(boundary, capabilities) {
4325
+ const isObserved = this.#readAutoFollowObservationCount(boundary) > 0;
4326
+ const isInvalidated = boundary === "top" ? this.#pendingAutoFollowInvalidationTop : this.#pendingAutoFollowInvalidationBottom;
4327
+ if (!isObserved && !isInvalidated) return;
4328
+ this.#setAutoFollowBoundary(boundary, this.#readCapabilityForBoundary(capabilities, boundary), "transition-observation");
4329
+ if (boundary === "top") {
4330
+ this.#pendingAutoFollowInvalidationTop = false;
4331
+ return;
4332
+ }
4333
+ this.#pendingAutoFollowInvalidationBottom = false;
4334
+ }
4335
+ #readAutoFollowObservationCount(boundary) {
4336
+ return boundary === "top" ? this.#autoFollowObservationCountTop : this.#autoFollowObservationCountBottom;
4337
+ }
4305
4338
  #syncLastArmedBoundaryFromLatchedState() {
4306
4339
  if (this.#canAutoFollowTop === this.#canAutoFollowBottom) return;
4307
4340
  this.#lastArmedAutoFollowBoundary = this.#canAutoFollowTop ? "top" : "bottom";
@@ -4815,11 +4848,15 @@ var TransitionStore = class {
4815
4848
  return this.#transitions.has(item);
4816
4849
  }
4817
4850
  set(item, transition) {
4851
+ const previous = this.#transitions.get(item);
4818
4852
  this.#transitions.set(item, transition);
4853
+ return previous;
4819
4854
  }
4820
4855
  replace(prevItem, nextItem, transition) {
4856
+ const previous = this.#transitions.get(prevItem);
4821
4857
  this.#transitions.delete(prevItem);
4822
4858
  this.#transitions.set(nextItem, transition);
4859
+ return previous;
4823
4860
  }
4824
4861
  delete(item) {
4825
4862
  const transition = this.#transitions.get(item);
@@ -4847,6 +4884,12 @@ var TransitionStore = class {
4847
4884
  transition
4848
4885
  }));
4849
4886
  }
4887
+ entries() {
4888
+ return [...this.#transitions.entries()].map(([item, transition]) => ({
4889
+ item,
4890
+ transition
4891
+ }));
4892
+ }
4850
4893
  reset() {
4851
4894
  this.#transitions.clear();
4852
4895
  }
@@ -4939,6 +4982,30 @@ function planExistingItemTransition(params) {
4939
4982
  retention: "drawn"
4940
4983
  };
4941
4984
  }
4985
+ function resolveAutoFollowBoundaryRisk(index, ctx, snapshot) {
4986
+ const drawnRange = snapshot.readDrawnIndexRange();
4987
+ if (index < 0 || !snapshot.hasSnapshot || drawnRange == null || !Number.isFinite(drawnRange.minIndex) || !Number.isFinite(drawnRange.maxIndex)) return;
4988
+ if (ctx.anchorMode === "bottom") return index <= drawnRange.minIndex ? "top" : void 0;
4989
+ return index >= drawnRange.maxIndex ? "bottom" : void 0;
4990
+ }
4991
+ function canClassifyAutoFollowBoundaryRisk(index, snapshot) {
4992
+ return index >= 0 && snapshot.hasSnapshot && snapshot.readDrawnIndexRange() != null;
4993
+ }
4994
+ function beginTransitionAutoFollowObservation(transition, lifecycle) {
4995
+ if (transition.observedAutoFollowBoundary == null) return;
4996
+ lifecycle.beginAutoFollowBoundaryObservation(transition.observedAutoFollowBoundary);
4997
+ }
4998
+ function endTransitionAutoFollowObservation(transition, lifecycle) {
4999
+ if (transition?.observedAutoFollowBoundary == null) return;
5000
+ lifecycle.endAutoFollowBoundaryObservation(transition.observedAutoFollowBoundary);
5001
+ }
5002
+ function invalidateAutoFollowBoundaryRisk(boundary, canClassify, lifecycle) {
5003
+ if (boundary != null) {
5004
+ lifecycle.invalidateAutoFollowBoundary(boundary);
5005
+ return;
5006
+ }
5007
+ if (!canClassify) lifecycle.invalidateAutoFollowBoundary(void 0);
5008
+ }
4942
5009
  function planBoundaryInsertItems(params) {
4943
5010
  const entries = [];
4944
5011
  for (const { item, node, height } of params.measuredItems) {
@@ -5102,40 +5169,50 @@ function readCurrentVisualState(item, now, store, adapter) {
5102
5169
  translateY: 0
5103
5170
  };
5104
5171
  }
5105
- function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
5172
+ function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle, now = getNow()) {
5106
5173
  switch (change.type) {
5107
5174
  case "update": {
5108
- const now = getNow();
5175
+ const nextIndex = ctx.items.indexOf(change.nextItem);
5176
+ const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(nextIndex, snapshot);
5177
+ const observedBoundary = resolveAutoFollowBoundaryRisk(nextIndex, ctx, snapshot);
5109
5178
  const currentVisualState = readCurrentVisualState(change.prevItem, now, store, ctx);
5110
5179
  const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
5111
5180
  if (transition == null) {
5112
- store.delete(change.prevItem);
5181
+ endTransitionAutoFollowObservation(store.delete(change.prevItem), lifecycle);
5182
+ invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
5113
5183
  return;
5114
5184
  }
5115
- store.replace(change.prevItem, change.nextItem, transition);
5185
+ transition.observedAutoFollowBoundary = observedBoundary;
5186
+ endTransitionAutoFollowObservation(store.replace(change.prevItem, change.nextItem, transition), lifecycle);
5187
+ beginTransitionAutoFollowObservation(transition, lifecycle);
5116
5188
  return;
5117
5189
  }
5118
5190
  case "delete": {
5119
- const now = getNow();
5191
+ const index = ctx.items.indexOf(change.item);
5192
+ const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(index, snapshot);
5193
+ const observedBoundary = resolveAutoFollowBoundaryRisk(index, ctx, snapshot);
5120
5194
  const currentVisualState = readCurrentVisualState(change.item, now, store, ctx);
5121
5195
  const transition = planDeleteTransition(change.item, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
5122
5196
  if (transition == null) {
5123
- store.delete(change.item);
5197
+ endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
5198
+ invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
5124
5199
  lifecycle.onDeleteComplete(change.item);
5125
5200
  return;
5126
5201
  }
5127
- store.set(change.item, transition);
5202
+ transition.observedAutoFollowBoundary = observedBoundary;
5203
+ endTransitionAutoFollowObservation(store.set(change.item, transition), lifecycle);
5204
+ beginTransitionAutoFollowObservation(transition, lifecycle);
5128
5205
  return;
5129
5206
  }
5130
5207
  case "delete-finalize":
5131
- store.delete(change.item);
5208
+ endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
5209
+ lifecycle.invalidateAutoFollowBoundary(void 0);
5132
5210
  return;
5133
5211
  case "unshift":
5134
5212
  case "push": {
5135
- const now = getNow();
5136
5213
  const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, now, ctx, snapshot);
5137
5214
  if (plan == null) return;
5138
- for (const entry of plan.entries) store.set(entry.item, entry.transition);
5215
+ for (const entry of plan.entries) endTransitionAutoFollowObservation(store.set(entry.item, entry.transition), lifecycle);
5139
5216
  if (ctx.position == null && snapshot.coversShortList && (change.type === "push" && ctx.anchorMode === "bottom" || change.type === "unshift" && ctx.anchorMode === "top")) {
5140
5217
  const boundary = change.type === "push" ? "bottom" : "top";
5141
5218
  const boundaryItem = snapshot.readBoundaryItem(boundary);
@@ -5145,6 +5222,7 @@ function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
5145
5222
  }
5146
5223
  case "reset":
5147
5224
  case "set":
5225
+ for (const entry of store.entries()) endTransitionAutoFollowObservation(entry.transition, lifecycle);
5148
5226
  store.reset();
5149
5227
  snapshot.reset();
5150
5228
  return;
@@ -5187,10 +5265,9 @@ var TransitionController = class {
5187
5265
  resolveItem(item, now, adapter, lifecycle) {
5188
5266
  return resolveTransitionedItem(item, now, this.#store, adapter, lifecycle);
5189
5267
  }
5190
- handleListStateChange(change, ctx, lifecycle) {
5191
- const now = getNow();
5268
+ handleListStateChange(change, ctx, lifecycle, now = getNow()) {
5192
5269
  this.settle(now, lifecycle);
5193
- handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle);
5270
+ handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle, now);
5194
5271
  }
5195
5272
  settle(now, lifecycle) {
5196
5273
  return this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
@@ -5213,7 +5290,11 @@ var TransitionController = class {
5213
5290
  const index = lifecycle.readItemIndex(item);
5214
5291
  if (index >= 0) completedDeleteIndices.push(index);
5215
5292
  }
5216
- this.#store.delete(item);
5293
+ const removedTransition = this.#store.delete(item);
5294
+ if (removedTransition?.observedAutoFollowBoundary != null) {
5295
+ lifecycle.endAutoFollowBoundaryObservation(removedTransition.observedAutoFollowBoundary);
5296
+ lifecycle.invalidateAutoFollowBoundary(removedTransition.observedAutoFollowBoundary);
5297
+ }
5217
5298
  if (transition.kind === "delete") lifecycle.onDeleteComplete(item);
5218
5299
  }
5219
5300
  if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(remapAnchorAfterDeletes(anchor, completedDeleteIndices));
@@ -5270,6 +5351,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5270
5351
  static JUMP_DURATION_PER_PIXEL = .7;
5271
5352
  #jumpController;
5272
5353
  #transitionController = new TransitionController();
5354
+ #listStateOverride;
5273
5355
  constructor(graphics, options) {
5274
5356
  super(graphics, options);
5275
5357
  this.#jumpController = new JumpController({
@@ -5287,21 +5369,18 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5287
5369
  clampItemIndex: this._clampItemIndex.bind(this),
5288
5370
  getItemHeight: this._getItemHeight.bind(this)
5289
5371
  });
5290
- subscribeListState(options.list, this, (owner, change) => {
5291
- owner.#handleListStateChange(change);
5292
- });
5293
5372
  }
5294
5373
  /** Current anchor item index. */
5295
5374
  get position() {
5296
- return this.options.list.position;
5375
+ return this.#listStateOverride?.position ?? this.options.list.position;
5297
5376
  }
5298
5377
  /** Pixel offset from the anchored item edge. */
5299
5378
  get offset() {
5300
- return this.options.list.offset;
5379
+ return this.#listStateOverride?.offset ?? this.options.list.offset;
5301
5380
  }
5302
5381
  /** Items currently available to the renderer. */
5303
5382
  get items() {
5304
- return this.options.list.items;
5383
+ return this.#listStateOverride?.items ?? this.options.list.items;
5305
5384
  }
5306
5385
  /** Replaces the current item collection. */
5307
5386
  set items(value) {
@@ -5309,6 +5388,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5309
5388
  }
5310
5389
  /** Renders the current visible window. */
5311
5390
  render(feedback) {
5391
+ this.#drainPendingListStateChanges();
5312
5392
  this.#jumpController.beforeFrame();
5313
5393
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5314
5394
  const now = getNow();
@@ -5332,6 +5412,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5332
5412
  }
5333
5413
  /** Hit-tests the current visible window. */
5334
5414
  hittest(test) {
5415
+ this.#drainPendingListStateChanges();
5335
5416
  this.#jumpController.beforeFrame();
5336
5417
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5337
5418
  const now = getNow();
@@ -5514,6 +5595,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5514
5595
  }
5515
5596
  #handleDeleteComplete(item) {
5516
5597
  finalizeInternalListDelete(this.options.list, item);
5598
+ this.#drainPendingListStateChanges();
5517
5599
  }
5518
5600
  #getTransitionLifecycleAdapter() {
5519
5601
  return {
@@ -5523,7 +5605,10 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5523
5605
  readScrollState: this._readListState.bind(this),
5524
5606
  readItemIndex: (item) => this.items.indexOf(item),
5525
5607
  snapItemToViewportBoundary: this.#snapItemToViewportBoundary.bind(this),
5526
- onTransitionSettleScrollAdjusted: () => this.#jumpController.reconcileAutoFollowAfterTransitionSettle()
5608
+ onTransitionSettleScrollAdjusted: () => this.#jumpController.reconcileAutoFollowAfterTransitionSettle(),
5609
+ beginAutoFollowBoundaryObservation: (boundary) => this.#jumpController.beginAutoFollowBoundaryObservation(boundary),
5610
+ endAutoFollowBoundaryObservation: (boundary) => this.#jumpController.endAutoFollowBoundaryObservation(boundary),
5611
+ invalidateAutoFollowBoundary: (boundary) => this.#jumpController.invalidateAutoFollowBoundary(boundary)
5527
5612
  };
5528
5613
  }
5529
5614
  #getVirtualizedRuntime() {
@@ -5557,9 +5642,25 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5557
5642
  anchorMode: this._getLayoutOptions().anchorMode
5558
5643
  };
5559
5644
  }
5560
- #handleListStateChange(change) {
5561
- const nextChange = this.#jumpController.handleListStateChange(change);
5562
- this.#transitionController.handleListStateChange(nextChange, this.#getTransitionPlanningAdapter(), this.#getTransitionLifecycleAdapter());
5645
+ #handleListStateChange(change, now = getNow(), snapshot) {
5646
+ this.#listStateOverride = snapshot == null ? void 0 : {
5647
+ items: [...snapshot.items],
5648
+ position: snapshot.position,
5649
+ offset: snapshot.offset
5650
+ };
5651
+ try {
5652
+ const nextChange = this.#jumpController.handleListStateChange(change, now);
5653
+ this.#transitionController.handleListStateChange(nextChange, this.#getTransitionPlanningAdapter(), this.#getTransitionLifecycleAdapter(), now);
5654
+ } finally {
5655
+ this.#listStateOverride = void 0;
5656
+ }
5657
+ }
5658
+ #drainPendingListStateChanges() {
5659
+ while (true) {
5660
+ const changes = drainInternalListStateChanges(this.options.list);
5661
+ if (changes.length === 0) return;
5662
+ for (const change of changes) this.#handleListStateChange(change, readInternalListStateChangeTime(change), readInternalListStateChangeSnapshot(change));
5663
+ }
5563
5664
  }
5564
5665
  };
5565
5666
  //#endregion