chat-layout 1.2.0-7 → 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.
package/index.mjs CHANGED
@@ -2041,6 +2041,154 @@ function createRichSourceItems(spans, defaultFont) {
2041
2041
  }));
2042
2042
  }
2043
2043
  //#endregion
2044
+ //#region src/text/justify.ts
2045
+ let _justifySupported;
2046
+ function isJustifySupported(ctx) {
2047
+ if (_justifySupported !== void 0) return _justifySupported;
2048
+ _justifySupported = typeof ctx.wordSpacing === "string" && typeof ctx.letterSpacing === "string";
2049
+ return _justifySupported;
2050
+ }
2051
+ function resolveJustifyMode(justify) {
2052
+ if (justify === true) return "inter-word";
2053
+ if (justify === "inter-word" || justify === "inter-character") return justify;
2054
+ return null;
2055
+ }
2056
+ const HYBRID_WORD_SHARE_CANDIDATES = [
2057
+ .15,
2058
+ .2,
2059
+ .25,
2060
+ .3,
2061
+ .35,
2062
+ .4,
2063
+ .45,
2064
+ .5,
2065
+ .55,
2066
+ .6,
2067
+ .65,
2068
+ .7,
2069
+ .75,
2070
+ .8,
2071
+ .85,
2072
+ 1,
2073
+ 0
2074
+ ];
2075
+ const PUNCTUATION_OR_SYMBOL_PATTERN = /^[\p{P}\p{S}]$/u;
2076
+ const JUSTIFY_SCORE_EPSILON = 1e-9;
2077
+ function analyzeLineForJustify(prepared, line) {
2078
+ let wordGapCount = 0;
2079
+ let wordCount = 0;
2080
+ let renderAtomCount = 0;
2081
+ let spaceCount = 0;
2082
+ let nonSpaceCount = 0;
2083
+ let cjkCount = 0;
2084
+ let latinLikeCount = 0;
2085
+ let punctuationCount = 0;
2086
+ let nonSpaceWidth = 0;
2087
+ let insideWord = false;
2088
+ forEachAtomInRange(prepared, line.start, line.end, (atom) => {
2089
+ if (atom.kind === "space" && !atom.preservesLineEnd && atom.atomicGroupId == null) wordGapCount++;
2090
+ renderAtomCount++;
2091
+ if (atom.kind === "space") {
2092
+ spaceCount++;
2093
+ insideWord = false;
2094
+ return;
2095
+ }
2096
+ nonSpaceCount++;
2097
+ nonSpaceWidth += atom.width + atom.extraWidthAfter;
2098
+ if (!insideWord) {
2099
+ wordCount++;
2100
+ insideWord = true;
2101
+ }
2102
+ if (isCJK(atom.text)) {
2103
+ cjkCount++;
2104
+ return;
2105
+ }
2106
+ if (PUNCTUATION_OR_SYMBOL_PATTERN.test(atom.text)) {
2107
+ punctuationCount++;
2108
+ return;
2109
+ }
2110
+ latinLikeCount++;
2111
+ });
2112
+ return {
2113
+ wordGapCount,
2114
+ wordCount,
2115
+ renderAtomCount,
2116
+ letterGapCount: Math.max(renderAtomCount - 1, 0),
2117
+ spaceCount,
2118
+ nonSpaceCount,
2119
+ cjkCount,
2120
+ latinLikeCount,
2121
+ punctuationCount,
2122
+ lineWidth: line.width,
2123
+ nonSpaceWidth
2124
+ };
2125
+ }
2126
+ function getAverageWordWidth(info) {
2127
+ return info.wordCount > 0 ? info.nonSpaceWidth / info.wordCount : info.lineWidth;
2128
+ }
2129
+ function getAverageCharWidth(info) {
2130
+ return info.renderAtomCount > 0 ? info.lineWidth / info.renderAtomCount : info.lineWidth;
2131
+ }
2132
+ function resolvePerGapSpacing(totalSpace, gapCount) {
2133
+ if (totalSpace === 0) return 0;
2134
+ if (gapCount <= 0) return null;
2135
+ return totalSpace / gapCount;
2136
+ }
2137
+ function exceedsThreshold(perGap, averageWidth, threshold) {
2138
+ if (!Number.isFinite(threshold)) return false;
2139
+ return perGap > threshold * averageWidth;
2140
+ }
2141
+ function createJustifySpacing(wordSpacingPx, letterSpacingPx) {
2142
+ return {
2143
+ wordSpacing: `${wordSpacingPx}px`,
2144
+ letterSpacing: `${letterSpacingPx}px`,
2145
+ wordSpacingPx,
2146
+ letterSpacingPx
2147
+ };
2148
+ }
2149
+ function computeJustifySpacing(lineWidth, maxWidth, info, mode, threshold = Number.POSITIVE_INFINITY) {
2150
+ const extraSpace = maxWidth - lineWidth;
2151
+ if (extraSpace <= 0 || mode == null) return null;
2152
+ if (mode === "inter-word" && info.wordGapCount > 0) {
2153
+ const perGap = extraSpace / info.wordGapCount;
2154
+ if (exceedsThreshold(perGap, Math.max(getAverageWordWidth(info), Number.EPSILON), threshold)) return null;
2155
+ return createJustifySpacing(perGap, 0);
2156
+ }
2157
+ if (mode !== "inter-character" || info.renderAtomCount === 0) return null;
2158
+ const avgCharWidth = Math.max(getAverageCharWidth(info), Number.EPSILON);
2159
+ if (info.wordGapCount === 0) {
2160
+ const perGap = resolvePerGapSpacing(extraSpace, info.letterGapCount);
2161
+ if (perGap == null) return null;
2162
+ if (exceedsThreshold(perGap, avgCharWidth, threshold)) return null;
2163
+ return createJustifySpacing(0, perGap);
2164
+ }
2165
+ const avgWordWidth = Math.max(getAverageWordWidth(info), Number.EPSILON);
2166
+ const nonSpaceCount = Math.max(info.nonSpaceCount, 1);
2167
+ const cjkRatio = info.cjkCount / nonSpaceCount;
2168
+ const latinLikeRatio = info.latinLikeCount / nonSpaceCount;
2169
+ const punctuationRatio = info.punctuationCount / nonSpaceCount;
2170
+ const wordPenalty = 1 + cjkRatio;
2171
+ const letterPenalty = 1 + latinLikeRatio + .5 * punctuationRatio;
2172
+ let bestCandidate = null;
2173
+ for (const wordShare of HYBRID_WORD_SHARE_CANDIDATES) {
2174
+ const wordExtraSpace = extraSpace * wordShare;
2175
+ const letterExtraSpace = extraSpace - wordExtraSpace;
2176
+ const wordSpacingPx = resolvePerGapSpacing(wordExtraSpace, info.wordGapCount);
2177
+ const letterSpacingPx = resolvePerGapSpacing(letterExtraSpace, info.letterGapCount);
2178
+ if (wordSpacingPx == null || letterSpacingPx == null) continue;
2179
+ if (exceedsThreshold(wordSpacingPx, avgWordWidth, threshold) || exceedsThreshold(letterSpacingPx, avgCharWidth, threshold)) continue;
2180
+ const wordRatio = wordSpacingPx / avgWordWidth;
2181
+ const letterRatio = letterSpacingPx / avgCharWidth;
2182
+ const score = wordPenalty * wordRatio ** 2 + letterPenalty * letterRatio ** 2;
2183
+ if (bestCandidate == null || score < bestCandidate.score - JUSTIFY_SCORE_EPSILON || Math.abs(score - bestCandidate.score) <= JUSTIFY_SCORE_EPSILON && wordShare > bestCandidate.wordShare) bestCandidate = {
2184
+ spacing: createJustifySpacing(wordSpacingPx, letterSpacingPx),
2185
+ score,
2186
+ wordShare
2187
+ };
2188
+ }
2189
+ return bestCandidate?.spacing ?? null;
2190
+ }
2191
+ //#endregion
2044
2192
  //#region src/text/plain-core.ts
2045
2193
  function readPreparedText(text, font, whiteSpace, wordBreak) {
2046
2194
  return readPreparedInlineLayout(getPlainPreparedKey(text, font, whiteSpace, wordBreak), createPlainSourceItems(text, font), whiteSpace, wordBreak);
@@ -2759,174 +2907,26 @@ function layoutRichTextWithOverflow(ctx, spans, maxWidth, defaultFont, defaultCo
2759
2907
  };
2760
2908
  }
2761
2909
  //#endregion
2762
- //#region src/text/justify.ts
2763
- let _justifySupported;
2764
- function isJustifySupported(ctx) {
2765
- if (_justifySupported !== void 0) return _justifySupported;
2766
- _justifySupported = typeof ctx.wordSpacing === "string" && typeof ctx.letterSpacing === "string";
2767
- return _justifySupported;
2768
- }
2769
- function resolveJustifyMode(justify) {
2770
- if (justify === true) return "inter-word";
2771
- if (justify === "inter-word" || justify === "inter-character") return justify;
2772
- return null;
2773
- }
2774
- const HYBRID_WORD_SHARE_CANDIDATES = [
2775
- .15,
2776
- .2,
2777
- .25,
2778
- .3,
2779
- .35,
2780
- .4,
2781
- .45,
2782
- .5,
2783
- .55,
2784
- .6,
2785
- .65,
2786
- .7,
2787
- .75,
2788
- .8,
2789
- .85,
2790
- 1,
2791
- 0
2792
- ];
2793
- const PUNCTUATION_OR_SYMBOL_PATTERN = /^[\p{P}\p{S}]$/u;
2794
- const JUSTIFY_SCORE_EPSILON = 1e-9;
2795
- function analyzeLineForJustify(prepared, line) {
2796
- let wordGapCount = 0;
2797
- let wordCount = 0;
2798
- let renderAtomCount = 0;
2799
- let spaceCount = 0;
2800
- let nonSpaceCount = 0;
2801
- let cjkCount = 0;
2802
- let latinLikeCount = 0;
2803
- let punctuationCount = 0;
2804
- let nonSpaceWidth = 0;
2805
- let insideWord = false;
2806
- forEachAtomInRange(prepared, line.start, line.end, (atom) => {
2807
- if (atom.kind === "space" && !atom.preservesLineEnd && atom.atomicGroupId == null) wordGapCount++;
2808
- renderAtomCount++;
2809
- if (atom.kind === "space") {
2810
- spaceCount++;
2811
- insideWord = false;
2812
- return;
2813
- }
2814
- nonSpaceCount++;
2815
- nonSpaceWidth += atom.width + atom.extraWidthAfter;
2816
- if (!insideWord) {
2817
- wordCount++;
2818
- insideWord = true;
2819
- }
2820
- if (isCJK(atom.text)) {
2821
- cjkCount++;
2822
- return;
2823
- }
2824
- if (PUNCTUATION_OR_SYMBOL_PATTERN.test(atom.text)) {
2825
- punctuationCount++;
2826
- return;
2827
- }
2828
- latinLikeCount++;
2829
- });
2830
- return {
2831
- wordGapCount,
2832
- wordCount,
2833
- renderAtomCount,
2834
- letterGapCount: Math.max(renderAtomCount - 1, 0),
2835
- spaceCount,
2836
- nonSpaceCount,
2837
- cjkCount,
2838
- latinLikeCount,
2839
- punctuationCount,
2840
- lineWidth: line.width,
2841
- nonSpaceWidth
2842
- };
2843
- }
2844
- function getAverageWordWidth(info) {
2845
- return info.wordCount > 0 ? info.nonSpaceWidth / info.wordCount : info.lineWidth;
2910
+ //#region src/nodes/text.ts
2911
+ function resolvePhysicalTextAlign(options) {
2912
+ if (options.physicalAlign != null) return options.physicalAlign;
2913
+ if (options.align != null) switch (options.align) {
2914
+ case "start": return "left";
2915
+ case "center": return "center";
2916
+ case "end": return "right";
2917
+ }
2918
+ return "left";
2846
2919
  }
2847
- function getAverageCharWidth(info) {
2848
- return info.renderAtomCount > 0 ? info.lineWidth / info.renderAtomCount : info.lineWidth;
2920
+ function normalizeTextMaxWidth(maxWidth) {
2921
+ if (maxWidth == null) return;
2922
+ return Math.max(0, maxWidth);
2849
2923
  }
2850
- function resolvePerGapSpacing(totalSpace, gapCount) {
2851
- if (totalSpace === 0) return 0;
2852
- if (gapCount <= 0) return null;
2853
- return totalSpace / gapCount;
2854
- }
2855
- function exceedsThreshold(perGap, averageWidth, threshold) {
2856
- if (!Number.isFinite(threshold)) return false;
2857
- return perGap > threshold * averageWidth;
2858
- }
2859
- function createJustifySpacing(wordSpacingPx, letterSpacingPx) {
2860
- return {
2861
- wordSpacing: `${wordSpacingPx}px`,
2862
- letterSpacing: `${letterSpacingPx}px`,
2863
- wordSpacingPx,
2864
- letterSpacingPx
2865
- };
2866
- }
2867
- function computeJustifySpacing(lineWidth, maxWidth, info, mode, threshold = Number.POSITIVE_INFINITY) {
2868
- const extraSpace = maxWidth - lineWidth;
2869
- if (extraSpace <= 0 || mode == null) return null;
2870
- if (mode === "inter-word" && info.wordGapCount > 0) {
2871
- const perGap = extraSpace / info.wordGapCount;
2872
- if (exceedsThreshold(perGap, Math.max(getAverageWordWidth(info), Number.EPSILON), threshold)) return null;
2873
- return createJustifySpacing(perGap, 0);
2874
- }
2875
- if (mode !== "inter-character" || info.renderAtomCount === 0) return null;
2876
- const avgCharWidth = Math.max(getAverageCharWidth(info), Number.EPSILON);
2877
- if (info.wordGapCount === 0) {
2878
- const perGap = resolvePerGapSpacing(extraSpace, info.letterGapCount);
2879
- if (perGap == null) return null;
2880
- if (exceedsThreshold(perGap, avgCharWidth, threshold)) return null;
2881
- return createJustifySpacing(0, perGap);
2882
- }
2883
- const avgWordWidth = Math.max(getAverageWordWidth(info), Number.EPSILON);
2884
- const nonSpaceCount = Math.max(info.nonSpaceCount, 1);
2885
- const cjkRatio = info.cjkCount / nonSpaceCount;
2886
- const latinLikeRatio = info.latinLikeCount / nonSpaceCount;
2887
- const punctuationRatio = info.punctuationCount / nonSpaceCount;
2888
- const wordPenalty = 1 + cjkRatio;
2889
- const letterPenalty = 1 + latinLikeRatio + .5 * punctuationRatio;
2890
- let bestCandidate = null;
2891
- for (const wordShare of HYBRID_WORD_SHARE_CANDIDATES) {
2892
- const wordExtraSpace = extraSpace * wordShare;
2893
- const letterExtraSpace = extraSpace - wordExtraSpace;
2894
- const wordSpacingPx = resolvePerGapSpacing(wordExtraSpace, info.wordGapCount);
2895
- const letterSpacingPx = resolvePerGapSpacing(letterExtraSpace, info.letterGapCount);
2896
- if (wordSpacingPx == null || letterSpacingPx == null) continue;
2897
- if (exceedsThreshold(wordSpacingPx, avgWordWidth, threshold) || exceedsThreshold(letterSpacingPx, avgCharWidth, threshold)) continue;
2898
- const wordRatio = wordSpacingPx / avgWordWidth;
2899
- const letterRatio = letterSpacingPx / avgCharWidth;
2900
- const score = wordPenalty * wordRatio ** 2 + letterPenalty * letterRatio ** 2;
2901
- if (bestCandidate == null || score < bestCandidate.score - JUSTIFY_SCORE_EPSILON || Math.abs(score - bestCandidate.score) <= JUSTIFY_SCORE_EPSILON && wordShare > bestCandidate.wordShare) bestCandidate = {
2902
- spacing: createJustifySpacing(wordSpacingPx, letterSpacingPx),
2903
- score,
2904
- wordShare
2905
- };
2906
- }
2907
- return bestCandidate?.spacing ?? null;
2908
- }
2909
- //#endregion
2910
- //#region src/nodes/text.ts
2911
- function resolvePhysicalTextAlign(options) {
2912
- if (options.physicalAlign != null) return options.physicalAlign;
2913
- if (options.align != null) switch (options.align) {
2914
- case "start": return "left";
2915
- case "center": return "center";
2916
- case "end": return "right";
2917
- }
2918
- return "left";
2919
- }
2920
- function normalizeTextMaxWidth(maxWidth) {
2921
- if (maxWidth == null) return;
2922
- return Math.max(0, maxWidth);
2923
- }
2924
- const DEFAULT_TEXT_SPACING = {
2925
- wordSpacing: "0px",
2926
- letterSpacing: "0px"
2927
- };
2928
- function supportsTextSpacing(g) {
2929
- return typeof g.wordSpacing === "string" && typeof g.letterSpacing === "string";
2924
+ const DEFAULT_TEXT_SPACING = {
2925
+ wordSpacing: "0px",
2926
+ letterSpacing: "0px"
2927
+ };
2928
+ function supportsTextSpacing(g) {
2929
+ return typeof g.wordSpacing === "string" && typeof g.letterSpacing === "string";
2930
2930
  }
2931
2931
  function withTextSpacing(g, spacing, cb) {
2932
2932
  if (!supportsTextSpacing(g)) return cb();
@@ -3574,30 +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");
3581
+ const FINALIZE_LIST_DELETE = Symbol("finalizeListDelete");
3582
+ const LIST_STATE_CHANGE_TIME = Symbol("listStateChangeTime");
3583
+ const LIST_STATE_CHANGE_SNAPSHOT = Symbol("listStateChangeSnapshot");
3601
3584
  function normalizePosition(value) {
3602
3585
  return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : void 0;
3603
3586
  }
@@ -3630,34 +3613,43 @@ function readListScrollMutation(list) {
3630
3613
  function writeInternalListScrollState(list, state) {
3631
3614
  list[WRITE_LIST_SCROLL_STATE](state, "internal");
3632
3615
  }
3633
- function deleteListStateListener(list, token) {
3634
- const listeners = listStateListeners.get(list);
3635
- if (listeners == null) return;
3636
- listeners.delete(token);
3637
- if (listeners.size === 0) listStateListeners.delete(list);
3638
- }
3639
- function emitListStateChange(list, change) {
3640
- const listeners = listStateListeners.get(list);
3641
- if (listeners == null) return;
3642
- emitWeakListeners(listeners, change);
3643
- if (listeners.size === 0) listStateListeners.delete(list);
3616
+ function finalizeInternalListDelete(list, item) {
3617
+ list[FINALIZE_LIST_DELETE](item);
3644
3618
  }
3645
- function subscribeListState(list, owner, listener) {
3619
+ function enqueueListStateChange(list, change) {
3646
3620
  const key = list;
3647
- let listeners = listStateListeners.get(key);
3648
- if (listeners == null) {
3649
- listeners = /* @__PURE__ */ new Map();
3650
- listStateListeners.set(key, listeners);
3651
- } else pruneWeakListenerMap(listeners);
3652
- const token = Symbol();
3653
- listeners.set(token, {
3654
- ownerRef: new WeakRef(owner),
3655
- 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
3656
3630
  });
3657
- listStateListenerRegistry?.register(owner, {
3658
- listRef: new WeakRef(key),
3659
- 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
3660
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];
3661
3653
  }
3662
3654
  function isObjectIdentityCandidate(value) {
3663
3655
  return typeof value === "object" && value !== null || typeof value === "function";
@@ -3706,16 +3698,10 @@ var ListState = class {
3706
3698
  get offset() {
3707
3699
  return this.#offset;
3708
3700
  }
3709
- set offset(value) {
3710
- this.#writeScrollState({ offset: normalizeOffset$1(value) }, "external");
3711
- }
3712
3701
  /** Anchor item index, or `undefined` to use the renderer default. */
3713
3702
  get position() {
3714
3703
  return this.#position;
3715
3704
  }
3716
- set position(value) {
3717
- this.#writeScrollState({ position: normalizePosition(value) }, "external");
3718
- }
3719
3705
  /** Items currently managed by the renderer. */
3720
3706
  get items() {
3721
3707
  return this.#items;
@@ -3726,7 +3712,7 @@ var ListState = class {
3726
3712
  assertUniqueItemReferences(nextItems);
3727
3713
  this.#items = nextItems;
3728
3714
  this.#pendingDeletes.clear();
3729
- emitListStateChange(this, { type: "set" });
3715
+ enqueueListStateChange(this, { type: "set" });
3730
3716
  }
3731
3717
  /**
3732
3718
  * @param items Initial list items.
@@ -3747,7 +3733,7 @@ var ListState = class {
3747
3733
  const normalizedAnimation = normalizeInsertAnimation(animation);
3748
3734
  if (this.position != null) this.#writeScrollState({ position: this.position + items.length }, "internal");
3749
3735
  this.#items = items.concat(this.#items);
3750
- emitListStateChange(this, {
3736
+ enqueueListStateChange(this, {
3751
3737
  type: "unshift",
3752
3738
  count: items.length,
3753
3739
  animation: normalizedAnimation
@@ -3763,7 +3749,7 @@ var ListState = class {
3763
3749
  assertUniqueItemReferences(items, this.#items);
3764
3750
  const normalizedAnimation = normalizeInsertAnimation(animation);
3765
3751
  this.#items.push(...items);
3766
- emitListStateChange(this, {
3752
+ enqueueListStateChange(this, {
3767
3753
  type: "push",
3768
3754
  count: items.length,
3769
3755
  animation: normalizedAnimation
@@ -3781,7 +3767,7 @@ var ListState = class {
3781
3767
  if (this.#items.includes(nextItem)) throw new Error("update() nextItem is already present in the list.");
3782
3768
  const prevItem = this.#items[index];
3783
3769
  this.#items[index] = nextItem;
3784
- emitListStateChange(this, {
3770
+ enqueueListStateChange(this, {
3785
3771
  type: "update",
3786
3772
  prevItem,
3787
3773
  nextItem,
@@ -3798,11 +3784,11 @@ var ListState = class {
3798
3784
  const normalizedAnimation = normalizeDeleteAnimation(animation);
3799
3785
  if (!((normalizedAnimation?.duration ?? 0) > 0)) {
3800
3786
  this.#pendingDeletes.add(item);
3801
- this.finalizeDelete(item);
3787
+ this[FINALIZE_LIST_DELETE](item);
3802
3788
  return;
3803
3789
  }
3804
3790
  this.#pendingDeletes.add(item);
3805
- emitListStateChange(this, {
3791
+ enqueueListStateChange(this, {
3806
3792
  type: "delete",
3807
3793
  item,
3808
3794
  animation: normalizedAnimation
@@ -3811,7 +3797,7 @@ var ListState = class {
3811
3797
  /**
3812
3798
  * Finalizes a pending delete by removing the item from the list.
3813
3799
  */
3814
- finalizeDelete(item) {
3800
+ [FINALIZE_LIST_DELETE](item) {
3815
3801
  if (!this.#pendingDeletes.has(item)) return;
3816
3802
  const index = this.#items.indexOf(item);
3817
3803
  this.#pendingDeletes.delete(item);
@@ -3825,21 +3811,12 @@ var ListState = class {
3825
3811
  if (this.position > index) this.#writeScrollState({ position: this.position - 1 }, "internal");
3826
3812
  else if (this.position === index) this.#writeScrollState({ position: Math.min(index, this.#items.length - 1) }, "internal");
3827
3813
  }
3828
- emitListStateChange(this, {
3814
+ enqueueListStateChange(this, {
3829
3815
  type: "delete-finalize",
3830
3816
  item
3831
3817
  });
3832
3818
  }
3833
3819
  /**
3834
- * Sets the current anchor item and pixel offset.
3835
- */
3836
- setAnchor(position, offset = 0) {
3837
- this.#writeScrollState({
3838
- position: normalizePosition(position),
3839
- offset: normalizeOffset$1(offset)
3840
- }, "external");
3841
- }
3842
- /**
3843
3820
  * Replaces all items and clears scroll state.
3844
3821
  */
3845
3822
  reset(items = []) {
@@ -3851,14 +3828,7 @@ var ListState = class {
3851
3828
  position: void 0,
3852
3829
  offset: 0
3853
3830
  }, "internal");
3854
- emitListStateChange(this, { type: "reset" });
3855
- }
3856
- /** Clears the current scroll anchor while keeping the items. */
3857
- resetScroll() {
3858
- this.#writeScrollState({
3859
- position: void 0,
3860
- offset: 0
3861
- }, "external");
3831
+ enqueueListStateChange(this, { type: "reset" });
3862
3832
  }
3863
3833
  /** Applies a relative pixel scroll delta. */
3864
3834
  applyScroll(delta) {
@@ -3948,7 +3918,22 @@ function memoRenderItemBy(keyOf, renderItem, options = {}) {
3948
3918
  });
3949
3919
  }
3950
3920
  //#endregion
3951
- //#region src/renderer/virtualized/base-animation.ts
3921
+ //#region src/renderer/virtualized/frame-session.ts
3922
+ function prepareFrameSession(params) {
3923
+ let solution = params.resolveVisibleWindow(params.now);
3924
+ params.captureVisibleItemSnapshot(solution);
3925
+ const requestSettleRedraw = params.pruneTransitionAnimations(solution.window, params.now);
3926
+ if (requestSettleRedraw) {
3927
+ solution = params.resolveVisibleWindow(params.now);
3928
+ params.captureVisibleItemSnapshot(solution);
3929
+ }
3930
+ return {
3931
+ solution,
3932
+ requestSettleRedraw
3933
+ };
3934
+ }
3935
+ //#endregion
3936
+ //#region src/renderer/virtualized/virtualized-animation.ts
3952
3937
  const CONTROLLED_STATE_OFFSET_EPSILON = 1e-9;
3953
3938
  function clamp$1(value, min, max) {
3954
3939
  return Math.min(Math.max(value, min), max);
@@ -4039,21 +4024,6 @@ function getNow() {
4039
4024
  return globalThis.performance?.now() ?? Date.now();
4040
4025
  }
4041
4026
  //#endregion
4042
- //#region src/renderer/virtualized/frame-session.ts
4043
- function prepareFrameSession(params) {
4044
- let solution = params.resolveVisibleWindow(params.now);
4045
- params.captureVisibleItemSnapshot(solution);
4046
- const requestSettleRedraw = params.pruneTransitionAnimations(solution.window, params.now);
4047
- if (requestSettleRedraw) {
4048
- solution = params.resolveVisibleWindow(params.now);
4049
- params.captureVisibleItemSnapshot(solution);
4050
- }
4051
- return {
4052
- solution,
4053
- requestSettleRedraw
4054
- };
4055
- }
4056
- //#endregion
4057
4027
  //#region src/renderer/virtualized/jump-controller.ts
4058
4028
  var JumpController = class JumpController {
4059
4029
  static TRANSITION_SETTLE_SNAP_DURATION = 120;
@@ -4064,6 +4034,10 @@ var JumpController = class JumpController {
4064
4034
  #pendingAutoFollowRecomputeReasonTop = "init";
4065
4035
  #pendingAutoFollowRecomputeReasonBottom = "init";
4066
4036
  #pendingTransitionSettleReconcile = false;
4037
+ #autoFollowObservationCountTop = 0;
4038
+ #autoFollowObservationCountBottom = 0;
4039
+ #pendingAutoFollowInvalidationTop = false;
4040
+ #pendingAutoFollowInvalidationBottom = false;
4067
4041
  #lastArmedAutoFollowBoundary;
4068
4042
  #lastObservedRenderedAutoFollowTop = false;
4069
4043
  #lastObservedRenderedAutoFollowBottom = false;
@@ -4145,12 +4119,32 @@ var JumpController = class JumpController {
4145
4119
  block: boundary === "bottom" ? "end" : "start"
4146
4120
  });
4147
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
+ }
4148
4140
  recomputeAutoFollowCapabilities(capabilities) {
4149
4141
  const previouslyObservedDualBoundary = this.#lastObservedRenderedAutoFollowTop && this.#lastObservedRenderedAutoFollowBottom;
4150
4142
  if (capabilities.top && capabilities.bottom && !previouslyObservedDualBoundary) {
4151
4143
  this.#setAutoFollowBoundary("top", true, "dual-boundary-promotion");
4152
4144
  this.#setAutoFollowBoundary("bottom", true, "dual-boundary-promotion");
4153
4145
  }
4146
+ this.#syncObservedOrInvalidatedBoundary("top", capabilities);
4147
+ this.#syncObservedOrInvalidatedBoundary("bottom", capabilities);
4154
4148
  if (this.#pendingAutoFollowRecomputeTop) {
4155
4149
  this.#setAutoFollowBoundary("top", capabilities.top, `strict-recompute:${this.#pendingAutoFollowRecomputeReasonTop}`);
4156
4150
  this.#pendingAutoFollowRecomputeTop = false;
@@ -4177,22 +4171,23 @@ var JumpController = class JumpController {
4177
4171
  reconcileAutoFollowAfterTransitionSettle() {
4178
4172
  this.#pendingTransitionSettleReconcile = true;
4179
4173
  }
4180
- handleListStateChange(change) {
4174
+ handleListStateChange(change, now = getNow()) {
4181
4175
  switch (change.type) {
4182
4176
  case "reset":
4183
4177
  case "set":
4184
4178
  this.#cancelJumpAnimation();
4185
4179
  this.#clearPendingPostJumpBoundary();
4186
4180
  this.#clearPendingTransitionSettleReconcile();
4181
+ this.#clearAutoFollowObservationState();
4187
4182
  this.#syncScrollMutationVersion();
4188
4183
  this.#markAutoFollowRecompute(void 0, change.type);
4189
4184
  return change;
4190
4185
  case "push":
4191
- case "unshift": return this.#handleBoundaryInsert(change);
4186
+ case "unshift": return this.#handleBoundaryInsert(change, now);
4192
4187
  default: return change;
4193
4188
  }
4194
4189
  }
4195
- #handleBoundaryInsert(change) {
4190
+ #handleBoundaryInsert(change, now) {
4196
4191
  if (this.#handlePendingExternalScrollMutation()) return change;
4197
4192
  this.#clearPendingTransitionSettleReconcile();
4198
4193
  const followChange = this.#resolveAutoFollowChange(change);
@@ -4205,21 +4200,21 @@ var JumpController = class JumpController {
4205
4200
  this.#lastArmedAutoFollowBoundary = followChange.boundary;
4206
4201
  }
4207
4202
  this.#clearPendingPostJumpBoundary();
4208
- this.#materializeAnimatedAnchor(getNow(), followChange.direction, followChange.count);
4203
+ this.#materializeAnimatedAnchor(now, followChange.direction, followChange.count);
4209
4204
  this.#startJumpToIndex(followChange.boundary === "bottom" ? this.#options.getItemCount() - 1 : 0, {
4210
4205
  block: followChange.boundary === "bottom" ? "end" : "start",
4211
4206
  duration: followChange.animation?.duration
4212
- });
4207
+ }, now);
4213
4208
  return change;
4214
4209
  }
4215
4210
  #cancelJumpAnimation() {
4216
4211
  this.#jumpAnimation = void 0;
4217
4212
  }
4218
- #startJumpToIndex(index, options) {
4213
+ #startJumpToIndex(index, options, now = getNow()) {
4219
4214
  const targetIndex = this.#options.clampItemIndex(index);
4220
4215
  const targetBlock = options.block ?? this.#options.getDefaultJumpBlock();
4221
4216
  const settleBoundary = this.#resolveBoundaryLatchTarget(targetIndex, targetBlock);
4222
- this.#materializeAnimatedAnchor(getNow());
4217
+ this.#materializeAnimatedAnchor(now);
4223
4218
  const currentState = this.#options.normalizeListState(this.#options.readListState());
4224
4219
  const targetAnchor = this.#options.getTargetAnchor(targetIndex, targetBlock);
4225
4220
  if (!(options.animated ?? true)) {
@@ -4252,7 +4247,7 @@ var JumpController = class JumpController {
4252
4247
  }
4253
4248
  this.#jumpAnimation = {
4254
4249
  path,
4255
- startTime: getNow(),
4250
+ startTime: now,
4256
4251
  duration,
4257
4252
  needsMoreFrames: true,
4258
4253
  onComplete: options.onComplete
@@ -4306,6 +4301,12 @@ var JumpController = class JumpController {
4306
4301
  #clearPendingTransitionSettleReconcile() {
4307
4302
  this.#pendingTransitionSettleReconcile = false;
4308
4303
  }
4304
+ #clearAutoFollowObservationState() {
4305
+ this.#autoFollowObservationCountTop = 0;
4306
+ this.#autoFollowObservationCountBottom = 0;
4307
+ this.#pendingAutoFollowInvalidationTop = false;
4308
+ this.#pendingAutoFollowInvalidationBottom = false;
4309
+ }
4309
4310
  #materializeAnimatedAnchor(now, direction, count = 0) {
4310
4311
  const animation = this.#jumpAnimation;
4311
4312
  if (animation == null) return;
@@ -4320,6 +4321,20 @@ var JumpController = class JumpController {
4320
4321
  if (boundary === "top") this.#canAutoFollowTop = value;
4321
4322
  else this.#canAutoFollowBottom = value;
4322
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
+ }
4323
4338
  #syncLastArmedBoundaryFromLatchedState() {
4324
4339
  if (this.#canAutoFollowTop === this.#canAutoFollowBottom) return;
4325
4340
  this.#lastArmedAutoFollowBoundary = this.#canAutoFollowTop ? "top" : "bottom";
@@ -4369,777 +4384,176 @@ var JumpController = class JumpController {
4369
4384
  }
4370
4385
  };
4371
4386
  //#endregion
4372
- //#region src/renderer/virtualized/transition-snapshot.ts
4373
- var VisibilitySnapshot = class {
4374
- #drawnItems = /* @__PURE__ */ new Set();
4375
- #visibleItems = /* @__PURE__ */ new Set();
4376
- #previousVisibleItems = /* @__PURE__ */ new Set();
4377
- #hasSnapshot = false;
4378
- #snapshotState;
4379
- #previousSnapshotState;
4380
- #emptyState;
4381
- #coversShortList = false;
4382
- #atStartBoundary = false;
4383
- #atEndBoundary = false;
4384
- #minDrawnIndex = Number.POSITIVE_INFINITY;
4385
- #maxDrawnIndex = Number.NEGATIVE_INFINITY;
4386
- #topBoundaryItem;
4387
- #bottomBoundaryItem;
4388
- get coversShortList() {
4389
- return this.#hasSnapshot && this.#snapshotState != null && this.#coversShortList;
4390
- }
4391
- get hasSnapshot() {
4392
- return this.#hasSnapshot;
4393
- }
4394
- get previousState() {
4395
- return this.#previousSnapshotState;
4396
- }
4397
- readDrawnIndexRange() {
4398
- if (!Number.isFinite(this.#minDrawnIndex) || !Number.isFinite(this.#maxDrawnIndex)) return;
4399
- return {
4400
- minIndex: this.#minDrawnIndex,
4401
- maxIndex: this.#maxDrawnIndex
4402
- };
4403
- }
4404
- readBoundaryItem(boundary) {
4405
- return boundary === "top" ? this.#topBoundaryItem : this.#bottomBoundaryItem;
4406
- }
4407
- capture(window, _resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
4408
- this.#previousVisibleItems = this.#visibleItems;
4409
- this.#previousSnapshotState = this.#snapshotState;
4410
- const nextDrawnItems = /* @__PURE__ */ new Set();
4411
- const nextVisibleItems = /* @__PURE__ */ new Set();
4412
- let minVisibleIndex = Number.POSITIVE_INFINITY;
4413
- let maxVisibleIndex = Number.NEGATIVE_INFINITY;
4414
- let topMostY = Number.POSITIVE_INFINITY;
4415
- let bottomMostY = Number.NEGATIVE_INFINITY;
4416
- let nextMinDrawnIndex = Number.POSITIVE_INFINITY;
4417
- let nextMaxDrawnIndex = Number.NEGATIVE_INFINITY;
4418
- let nextTopBoundaryItem;
4419
- let nextBottomBoundaryItem;
4420
- let nextTopBoundaryY = Number.POSITIVE_INFINITY;
4421
- let nextBottomBoundaryY = Number.NEGATIVE_INFINITY;
4422
- const effectiveShift = window.shift;
4423
- const contentOriginY = viewport.contentTop;
4424
- for (const { idx, offset, height } of window.drawList) {
4425
- const y = offset + effectiveShift + contentOriginY;
4426
- topMostY = Math.min(topMostY, y);
4427
- bottomMostY = Math.max(bottomMostY, y + height);
4428
- const item = items[idx];
4429
- if (item != null && readOuterVisibleRange(y, height) != null) {
4430
- nextDrawnItems.add(item);
4431
- nextMinDrawnIndex = Math.min(nextMinDrawnIndex, idx);
4432
- nextMaxDrawnIndex = Math.max(nextMaxDrawnIndex, idx);
4433
- }
4434
- if (item == null) continue;
4435
- if (readVisibleRange(y, height) != null) {
4436
- minVisibleIndex = Math.min(minVisibleIndex, idx);
4437
- maxVisibleIndex = Math.max(maxVisibleIndex, idx);
4438
- nextVisibleItems.add(item);
4439
- if (y < nextTopBoundaryY) {
4440
- nextTopBoundaryY = y;
4441
- nextTopBoundaryItem = item;
4442
- }
4443
- if (y + height > nextBottomBoundaryY) {
4444
- nextBottomBoundaryY = y + height;
4445
- nextBottomBoundaryItem = item;
4446
- }
4447
- }
4448
- }
4449
- this.#drawnItems = nextDrawnItems;
4450
- this.#visibleItems = nextVisibleItems;
4451
- this.#hasSnapshot = true;
4452
- this.#snapshotState = snapshotState;
4453
- this.#minDrawnIndex = nextMinDrawnIndex;
4454
- this.#maxDrawnIndex = nextMaxDrawnIndex;
4455
- this.#topBoundaryItem = nextTopBoundaryItem;
4456
- this.#bottomBoundaryItem = nextBottomBoundaryItem;
4457
- this.#emptyState = items.length === 0 && window.drawList.length === 0 ? snapshotState : void 0;
4458
- const contentHeight = bottomMostY - topMostY;
4459
- this.#coversShortList = window.drawList.length > 0 && items.length > 0 && window.drawList.length === items.length && minVisibleIndex === 0 && maxVisibleIndex === items.length - 1 && topMostY >= viewport.contentTop - 1e-6 && bottomMostY <= viewport.contentBottom + 1e-6 && contentHeight < viewport.contentHeight - 1e-6;
4460
- this.#atStartBoundary = window.drawList.length > 0 && items.length > 0 && minVisibleIndex === 0 && topMostY >= viewport.contentTop - 1e-6;
4461
- this.#atEndBoundary = window.drawList.length > 0 && items.length > 0 && maxVisibleIndex === items.length - 1 && bottomMostY <= viewport.contentBottom + 1e-6;
4462
- }
4463
- matchesCurrentState(position, offset) {
4464
- return this.#hasSnapshot && this.#snapshotState != null && sameState(this.#snapshotState, position, offset);
4465
- }
4466
- matchesBoundaryInsertState(direction, count, position, offset) {
4467
- if (!this.coversShortList || this.#snapshotState == null) return false;
4468
- return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
4469
- }
4470
- matchesFollowBoundaryInsertState(direction, count, position, offset) {
4471
- if (!this.#hasSnapshot || this.#snapshotState == null) return false;
4472
- if (direction === "push" ? !this.#atEndBoundary : !this.#atStartBoundary) return false;
4473
- return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
4474
- }
4475
- matchesEmptyBoundaryInsertState(direction, count, position, offset) {
4476
- const emptyState = this.#emptyState;
4477
- if (!this.#hasSnapshot || emptyState == null) return false;
4478
- return sameState({
4479
- position: direction === "unshift" && emptyState.position != null ? emptyState.position + count : emptyState.position,
4480
- offset: emptyState.offset
4481
- }, position, offset);
4482
- }
4483
- isVisible(item) {
4484
- return this.#visibleItems.has(item);
4485
- }
4486
- wasVisible(item) {
4487
- return this.#previousVisibleItems.has(item);
4488
- }
4489
- tracks(item, retention) {
4490
- return retention === "drawn" ? this.#drawnItems.has(item) : this.#visibleItems.has(item);
4491
- }
4492
- reset() {
4493
- this.#drawnItems.clear();
4494
- this.#visibleItems.clear();
4495
- this.#previousVisibleItems.clear();
4496
- this.#hasSnapshot = false;
4497
- this.#snapshotState = void 0;
4498
- this.#previousSnapshotState = void 0;
4499
- this.#emptyState = void 0;
4500
- this.#coversShortList = false;
4501
- this.#atStartBoundary = false;
4502
- this.#atEndBoundary = false;
4503
- this.#minDrawnIndex = Number.POSITIVE_INFINITY;
4504
- this.#maxDrawnIndex = Number.NEGATIVE_INFINITY;
4505
- this.#topBoundaryItem = void 0;
4506
- this.#bottomBoundaryItem = void 0;
4507
- }
4508
- #matchesStateAfterBoundaryInsert(direction, count, position, offset) {
4509
- const snapshotState = this.#snapshotState;
4510
- if (snapshotState == null) return false;
4511
- return sameState({
4512
- position: direction === "unshift" && snapshotState.position != null ? snapshotState.position + count : snapshotState.position,
4513
- offset: snapshotState.offset
4514
- }, position, offset);
4515
- }
4516
- };
4517
- //#endregion
4518
- //#region src/renderer/virtualized/transition-store.ts
4519
- var TransitionStore = class {
4520
- #transitions = /* @__PURE__ */ new Map();
4521
- get size() {
4522
- return this.#transitions.size;
4523
- }
4524
- has(item) {
4525
- return this.#transitions.has(item);
4526
- }
4527
- set(item, transition) {
4528
- this.#transitions.set(item, transition);
4529
- }
4530
- replace(prevItem, nextItem, transition) {
4531
- this.#transitions.delete(prevItem);
4532
- this.#transitions.set(nextItem, transition);
4533
- }
4534
- delete(item) {
4535
- const transition = this.#transitions.get(item);
4536
- if (transition != null) this.#transitions.delete(item);
4537
- return transition;
4538
- }
4539
- readActive(item, now) {
4540
- const transition = this.#transitions.get(item);
4541
- if (transition == null) return;
4542
- return this.#isComplete(transition, now) ? void 0 : transition;
4543
- }
4544
- prepare(now) {
4545
- for (const transition of this.#transitions.values()) if (!this.#isComplete(transition, now)) return true;
4546
- return false;
4547
- }
4548
- findCompleted(now) {
4549
- return [...this.#transitions.entries()].filter(([, transition]) => this.#isComplete(transition, now)).map(([item, transition]) => ({
4550
- item,
4551
- transition
4552
- }));
4553
- }
4554
- findInvisible(snapshot) {
4555
- return [...this.#transitions.entries()].filter(([item, transition]) => !snapshot.tracks(item, transition.retention) && !(transition.kind === "insert" && !snapshot.wasVisible(item))).map(([item, transition]) => ({
4556
- item,
4557
- transition
4558
- }));
4559
- }
4560
- reset() {
4561
- this.#transitions.clear();
4562
- }
4563
- #isComplete(transition, now) {
4564
- return getProgress(transition.height.startTime, transition.height.duration, now) >= 1;
4565
- }
4566
- };
4567
- //#endregion
4568
- //#region src/renderer/virtualized/transition-planner.ts
4569
- function normalizeDuration(duration) {
4570
- return Math.max(0, typeof duration === "number" && Number.isFinite(duration) ? duration : 0);
4387
+ //#region src/renderer/virtualized/solver.ts
4388
+ function clamp(value, min, max) {
4389
+ return Math.min(Math.max(value, min), max);
4571
4390
  }
4572
- function createScalarAnimation(from, to, startTime, duration) {
4573
- return {
4574
- from,
4575
- to,
4576
- startTime,
4577
- duration
4578
- };
4391
+ function normalizeOffset(offset) {
4392
+ return Number.isFinite(offset) ? offset : 0;
4579
4393
  }
4580
- function createLayerAnimation(node, fromAlpha, toAlpha, startTime, duration, fromTranslateY, toTranslateY) {
4394
+ function normalizeListPadding(padding) {
4581
4395
  return {
4582
- node,
4583
- alpha: createScalarAnimation(fromAlpha, toAlpha, startTime, duration),
4584
- translateY: createScalarAnimation(fromTranslateY, toTranslateY, startTime, duration)
4396
+ top: typeof padding?.top === "number" && Number.isFinite(padding.top) ? Math.max(0, padding.top) : 0,
4397
+ bottom: typeof padding?.bottom === "number" && Number.isFinite(padding.bottom) ? Math.max(0, padding.bottom) : 0
4585
4398
  };
4586
4399
  }
4587
- function findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) {
4588
- if (index < 0) return;
4589
- const solution = resolveVisibleWindow();
4590
- for (const entry of solution.window.drawList) {
4591
- if (entry.idx !== index) continue;
4592
- if (readVisibleRange(entry.offset + solution.window.shift, entry.height) != null) return entry;
4593
- }
4594
- }
4595
- function isIndexVisible(index, resolveVisibleWindow, readVisibleRange) {
4596
- return findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) != null;
4597
- }
4598
- function resolveAnimationEligibility(params) {
4599
- if (params.index < 0) return false;
4600
- if (params.snapshot.matchesCurrentState(params.position, params.offset)) return params.snapshot.tracks(params.item, "drawn");
4601
- return isIndexVisible(params.index, params.resolveVisibleWindow, params.readOuterVisibleRange);
4602
- }
4603
- function hasVisibleBoundaryInsertItems(direction, count, ctx) {
4604
- if (count <= 0) return false;
4605
- const start = direction === "push" ? ctx.items.length - count : 0;
4606
- const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
4607
- if (start < 0 || end <= start) return false;
4608
- const solution = ctx.resolveVisibleWindow();
4609
- return solution.window.drawList.some((entry) => entry.idx >= start && entry.idx < end && ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null);
4610
- }
4611
- function sampleScalarAnimation(animation, now) {
4612
- return interpolate(animation.from, animation.to, animation.startTime, animation.duration, now);
4613
- }
4614
- function sampleLayerAnimation(layer, now) {
4615
- const alpha = sampleScalarAnimation(layer.alpha, now);
4616
- if (alpha <= .001) return;
4400
+ function resolveListViewport(outerHeight, padding) {
4401
+ const height = typeof outerHeight === "number" && Number.isFinite(outerHeight) ? Math.max(0, outerHeight) : 0;
4402
+ const resolvedPadding = normalizeListPadding(padding);
4403
+ const contentTop = resolvedPadding.top;
4404
+ const contentBottom = Math.max(contentTop, height - resolvedPadding.bottom);
4617
4405
  return {
4618
- alpha,
4619
- node: layer.node,
4620
- translateY: sampleScalarAnimation(layer.translateY, now)
4406
+ outerHeight: height,
4407
+ contentTop,
4408
+ contentBottom,
4409
+ contentHeight: contentBottom - contentTop,
4410
+ outerContentTop: -contentTop,
4411
+ outerContentBottom: height - contentTop
4621
4412
  };
4622
4413
  }
4623
- function sampleTransition(transition, now) {
4414
+ function resolveListLayoutOptions(options = {}) {
4624
4415
  return {
4625
- kind: transition.kind,
4626
- slotHeight: sampleScalarAnimation(transition.height, now),
4627
- layers: transition.layers.map((layer) => sampleLayerAnimation(layer, now)).filter((layer) => layer != null),
4628
- retention: transition.retention
4416
+ anchorMode: options.anchorMode ?? "top",
4417
+ underflowAlign: options.underflowAlign ?? "top",
4418
+ padding: normalizeListPadding(options.padding)
4629
4419
  };
4630
4420
  }
4631
- function planExistingItemTransition(params) {
4632
- if (!params.canAnimate || params.duration <= 0) return;
4633
- if (params.kind === "update" && !Number.isFinite(params.nextHeight)) return;
4634
- const layers = [];
4635
- if (params.currentVisualState.alpha > .001) layers.push(createLayerAnimation(params.currentVisualState.node, params.currentVisualState.alpha, 0, params.now, params.duration, params.currentVisualState.translateY, 0));
4636
- if (params.kind === "update") {
4637
- layers.push(createLayerAnimation(params.nextNode, 0, 1, params.now, params.duration, params.currentVisualState.translateY, 0));
4638
- return {
4639
- kind: "update",
4640
- layers,
4641
- height: createScalarAnimation(params.currentVisualState.height, params.nextHeight, params.now, params.duration),
4642
- retention: "drawn"
4643
- };
4644
- }
4421
+ function normalizeVisibleState(itemCount, state, layout) {
4422
+ if (itemCount <= 0) return {
4423
+ position: 0,
4424
+ offset: 0
4425
+ };
4426
+ const position = state.position;
4427
+ const fallbackPosition = layout.anchorMode === "top" ? 0 : itemCount - 1;
4428
+ if (typeof position !== "number" || !Number.isFinite(position)) return {
4429
+ position: fallbackPosition,
4430
+ offset: normalizeOffset(state.offset)
4431
+ };
4645
4432
  return {
4646
- kind: "delete",
4647
- layers,
4648
- height: createScalarAnimation(params.currentVisualState.height, 0, params.now, params.duration),
4649
- retention: "drawn"
4433
+ position: clamp(Math.trunc(position), 0, itemCount - 1),
4434
+ offset: normalizeOffset(state.offset)
4650
4435
  };
4651
4436
  }
4652
- function planBoundaryInsertItems(params) {
4653
- const entries = [];
4654
- for (const { item, node, height } of params.measuredItems) {
4655
- if (!Number.isFinite(height) || height < 0) return;
4656
- entries.push({
4657
- item,
4658
- transition: {
4659
- kind: "insert",
4660
- layers: [createLayerAnimation(node, 0, 1, params.now, params.duration, 0, 0)],
4661
- height: createScalarAnimation(params.animateHeight ? 0 : height, height, params.now, params.duration),
4662
- retention: "drawn"
4663
- }
4664
- });
4665
- }
4666
- return entries.length === 0 ? void 0 : { entries };
4667
- }
4668
- function measureBoundaryInsertItems(direction, count, ctx) {
4669
- const start = direction === "push" ? ctx.items.length - count : 0;
4670
- const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
4671
- if (start < 0 || end < start) return;
4672
- const measured = [];
4673
- for (let index = start; index < end; index += 1) {
4674
- const item = ctx.items[index];
4675
- if (item == null) continue;
4676
- const node = ctx.renderItem(item);
4677
- const height = ctx.measureNode(node).height;
4678
- measured.push({
4679
- item,
4680
- node,
4681
- height
4682
- });
4683
- }
4684
- return measured;
4685
- }
4686
- function drawSampledLayers(sampled, y, adapter) {
4687
- if (sampled.slotHeight <= 0) return false;
4688
- let result = false;
4689
- for (const layer of sampled.layers) {
4690
- const alpha = clamp$1(layer.alpha, 0, 1);
4691
- if (alpha <= .001) continue;
4692
- adapter.graphics.save();
4693
- try {
4694
- if (sampled.kind === "insert") {
4695
- adapter.graphics.beginPath();
4696
- adapter.graphics.rect(0, y, adapter.graphics.canvas.clientWidth, sampled.slotHeight);
4697
- adapter.graphics.clip();
4698
- }
4699
- if (typeof adapter.graphics.globalAlpha === "number") adapter.graphics.globalAlpha *= alpha;
4700
- if (adapter.drawNode(layer.node, 0, y + layer.translateY)) result = true;
4701
- } finally {
4702
- adapter.graphics.restore();
4437
+ function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout) {
4438
+ const viewport = typeof viewportHeight === "number" ? resolveListViewport(viewportHeight, layout.padding) : viewportHeight;
4439
+ const contentHeight = viewport.contentHeight;
4440
+ const normalizedState = normalizeVisibleState(items.length, state, layout);
4441
+ const resolutionPath = /* @__PURE__ */ new Set();
4442
+ const readResolvedItem = (item, idx) => {
4443
+ resolutionPath.add(idx);
4444
+ return resolveItem(item, idx);
4445
+ };
4446
+ if (items.length === 0) return {
4447
+ normalizedState,
4448
+ resolutionPath: [],
4449
+ window: {
4450
+ drawList: [],
4451
+ shift: 0
4703
4452
  }
4704
- }
4705
- return result;
4706
- }
4707
- function planUpdateTransition(prevItem, nextItem, duration, now, currentVisualState, ctx, snapshot, store) {
4708
- const nextIndex = ctx.items.indexOf(nextItem);
4709
- const nextNode = ctx.renderItem(nextItem);
4710
- const nextHeight = ctx.measureNode(nextNode).height;
4711
- return planExistingItemTransition({
4712
- kind: "update",
4713
- duration: normalizeDuration(duration),
4714
- canAnimate: resolveAnimationEligibility({
4715
- index: nextIndex,
4716
- item: prevItem,
4717
- position: ctx.position,
4718
- offset: ctx.offset,
4719
- snapshot,
4720
- hasActiveTransition: store.has(prevItem),
4721
- resolveVisibleWindow: ctx.resolveVisibleWindow,
4722
- readOuterVisibleRange: ctx.readOuterVisibleRange
4723
- }),
4724
- now,
4725
- currentVisualState,
4726
- nextNode,
4727
- nextHeight
4728
- });
4729
- }
4730
- function planDeleteTransition(item, duration, now, currentVisualState, ctx, snapshot, store) {
4731
- const index = ctx.items.indexOf(item);
4732
- return planExistingItemTransition({
4733
- kind: "delete",
4734
- duration: normalizeDuration(duration),
4735
- canAnimate: resolveAnimationEligibility({
4736
- index,
4737
- item,
4738
- position: ctx.position,
4739
- offset: ctx.offset,
4740
- snapshot,
4741
- hasActiveTransition: store.has(item),
4742
- resolveVisibleWindow: ctx.resolveVisibleWindow,
4743
- readOuterVisibleRange: ctx.readOuterVisibleRange
4744
- }),
4745
- now,
4746
- currentVisualState
4747
- });
4748
- }
4749
- function planBoundaryInsertTransition(direction, count, duration, now, ctx, snapshot) {
4750
- const normalizedDuration = normalizeDuration(duration);
4751
- if (count <= 0 || normalizedDuration <= 0) return;
4752
- const matchesBoundaryState = snapshot.matchesBoundaryInsertState(direction, count, ctx.position, ctx.offset);
4753
- const matchesFollowState = snapshot.matchesFollowBoundaryInsertState(direction, count, ctx.position, ctx.offset);
4754
- const matchesEmptyState = snapshot.matchesEmptyBoundaryInsertState(direction, count, ctx.position, ctx.offset);
4755
- if (!(matchesBoundaryState || matchesFollowState || matchesEmptyState || snapshot.hasSnapshot && hasVisibleBoundaryInsertItems(direction, count, ctx))) return;
4756
- const animateHeight = !(direction === "unshift" && matchesFollowState && !matchesBoundaryState && !matchesEmptyState);
4757
- const measuredItems = measureBoundaryInsertItems(direction, count, ctx);
4758
- if (measuredItems == null) return;
4759
- return planBoundaryInsertItems({
4760
- duration: normalizedDuration,
4761
- animateHeight,
4762
- now,
4763
- measuredItems
4764
- });
4765
- }
4766
- function getTransitionedItemHeight(item, now, store, adapter) {
4767
- const transition = store.readActive(item, now);
4768
- if (transition != null) return sampleTransition(transition, now).slotHeight;
4769
- const node = adapter.renderItem(item);
4770
- return adapter.measureNode(node).height;
4771
- }
4772
- function resolveTransitionedItem(item, now, store, adapter, lifecycle) {
4773
- const transition = store.readActive(item, now);
4774
- if (transition == null) {
4775
- const node = adapter.renderItem(item);
4776
- return {
4777
- value: {
4778
- draw: (y) => adapter.drawNode(node, 0, y),
4779
- hittest: (test, y) => node.hittest(adapter.getRootContext(), {
4780
- ...test,
4781
- y: test.y - y
4782
- })
4783
- },
4784
- height: adapter.measureNode(node).height
4785
- };
4786
- }
4787
- const sampled = sampleTransition(transition, now);
4788
- return {
4789
- value: {
4790
- draw: (y) => drawSampledLayers(sampled, y, adapter),
4791
- hittest: () => false
4792
- },
4793
- height: sampled.slotHeight
4794
- };
4795
- }
4796
- function readCurrentVisualState(item, now, store, adapter) {
4797
- const transition = store.readActive(item, now);
4798
- if (transition != null && transition.layers.length > 0) {
4799
- const primaryLayer = transition.layers[transition.layers.length - 1];
4800
- return {
4801
- node: primaryLayer.node,
4802
- alpha: sampleScalarAnimation(primaryLayer.alpha, now),
4803
- height: sampleScalarAnimation(transition.height, now),
4804
- translateY: sampleScalarAnimation(primaryLayer.translateY, now)
4805
- };
4806
- }
4807
- const node = adapter.renderItem(item);
4808
- return {
4809
- node,
4810
- alpha: 1,
4811
- height: adapter.measureNode(node).height,
4812
- translateY: 0
4813
4453
  };
4814
- }
4815
- function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle) {
4816
- switch (change.type) {
4817
- case "update": {
4818
- const now = getNow();
4819
- const currentVisualState = readCurrentVisualState(change.prevItem, now, store, ctx);
4820
- const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
4821
- if (transition == null) {
4822
- store.delete(change.prevItem);
4823
- return;
4454
+ if (layout.anchorMode === "top") {
4455
+ let { position, offset } = normalizedState;
4456
+ let drawLength = 0;
4457
+ if (offset > 0) if (position === 0) offset = 0;
4458
+ else {
4459
+ for (let i = position - 1; i >= 0; i -= 1) {
4460
+ const { height } = readResolvedItem(items[i], i);
4461
+ position = i;
4462
+ offset -= height;
4463
+ if (offset <= 0) break;
4824
4464
  }
4825
- store.replace(change.prevItem, change.nextItem, transition);
4826
- return;
4465
+ if (position === 0 && offset > 0) offset = 0;
4827
4466
  }
4828
- case "delete": {
4829
- const now = getNow();
4830
- const currentVisualState = readCurrentVisualState(change.item, now, store, ctx);
4831
- const transition = planDeleteTransition(change.item, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
4832
- if (transition == null) {
4833
- store.delete(change.item);
4834
- lifecycle.onDeleteComplete(change.item);
4835
- return;
4467
+ let y = offset;
4468
+ const drawList = [];
4469
+ for (let i = position; i < items.length; i += 1) {
4470
+ const { value, height } = readResolvedItem(items[i], i);
4471
+ if (y + height > 0) {
4472
+ drawList.push({
4473
+ index: i,
4474
+ value,
4475
+ offset: y,
4476
+ height
4477
+ });
4478
+ drawLength += height;
4479
+ } else {
4480
+ offset += height;
4481
+ position = i + 1;
4836
4482
  }
4837
- store.set(change.item, transition);
4838
- return;
4483
+ y += height;
4484
+ if (y >= contentHeight) break;
4839
4485
  }
4840
- case "delete-finalize":
4841
- store.delete(change.item);
4842
- return;
4843
- case "unshift":
4844
- case "push": {
4845
- const now = getNow();
4846
- const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, now, ctx, snapshot);
4847
- if (plan == null) return;
4848
- for (const entry of plan.entries) store.set(entry.item, entry.transition);
4849
- if (ctx.position == null && snapshot.coversShortList && (change.type === "push" && ctx.anchorMode === "bottom" || change.type === "unshift" && ctx.anchorMode === "top")) {
4850
- const boundary = change.type === "push" ? "bottom" : "top";
4851
- const boundaryItem = snapshot.readBoundaryItem(boundary);
4852
- if (boundaryItem != null) lifecycle.snapItemToViewportBoundary(boundaryItem, boundary);
4486
+ let shift = 0;
4487
+ if (y < contentHeight) {
4488
+ if (drawList.length > 0 && drawList.at(-1)?.index === items.length - 1 && !(drawList.at(-1)?.height > Number.EPSILON)) return finalizeVisibleWindowResult(items.length, viewport, layout, {
4489
+ position,
4490
+ offset
4491
+ }, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
4492
+ drawList,
4493
+ shift
4494
+ }, viewport, readResolvedItem));
4495
+ if (position === 0 && drawLength < contentHeight) {
4496
+ shift = -offset;
4497
+ offset = 0;
4498
+ } else {
4499
+ shift = contentHeight - y;
4500
+ y = offset += shift;
4501
+ let lastIdx = -1;
4502
+ for (let i = position - 1; i >= 0; i -= 1) {
4503
+ const { value, height } = readResolvedItem(items[i], i);
4504
+ drawLength += height;
4505
+ y -= height;
4506
+ drawList.push({
4507
+ index: i,
4508
+ value,
4509
+ offset: y - shift,
4510
+ height
4511
+ });
4512
+ lastIdx = i;
4513
+ if (y < 0) break;
4514
+ }
4515
+ if (lastIdx === 0 && drawLength < contentHeight) {
4516
+ shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
4517
+ position = 0;
4518
+ offset = 0;
4519
+ }
4853
4520
  }
4854
- return;
4855
- }
4856
- case "reset":
4857
- case "set":
4858
- store.reset();
4859
- snapshot.reset();
4860
- return;
4861
- }
4862
- }
4863
- //#endregion
4864
- //#region src/renderer/virtualized/base-transition.ts
4865
- function remapAnchorAfterDeletes(anchor, deletedIndices) {
4866
- if (!Number.isFinite(anchor) || deletedIndices.length === 0) return anchor;
4867
- const sortedIndices = [...deletedIndices].filter((index) => Number.isFinite(index) && index >= 0).sort((a, b) => a - b);
4868
- let removedBeforeAnchor = 0;
4869
- for (const index of sortedIndices) {
4870
- if (anchor > index + 1) {
4871
- removedBeforeAnchor += 1;
4872
- continue;
4873
4521
  }
4874
- if (anchor >= index) return index - removedBeforeAnchor;
4875
- }
4876
- return anchor - removedBeforeAnchor;
4877
- }
4878
- var TransitionController = class {
4879
- #store = new TransitionStore();
4880
- #snapshot = new VisibilitySnapshot();
4881
- captureVisibilitySnapshot(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
4882
- this.#snapshot.capture(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange);
4883
- }
4884
- pruneInvisible(ctx, lifecycle) {
4885
- return this.pruneInvisibleAt(getNow(), ctx, lifecycle);
4886
- }
4887
- prepare(now, lifecycle) {
4888
- this.settle(now, lifecycle);
4889
- return this.#store.prepare(now);
4522
+ return finalizeVisibleWindowResult(items.length, viewport, layout, {
4523
+ position,
4524
+ offset
4525
+ }, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
4526
+ drawList,
4527
+ shift
4528
+ }, viewport, readResolvedItem));
4890
4529
  }
4891
- canAutoFollowBoundaryInsert(direction, count, position, offset) {
4892
- return this.#snapshot.matchesFollowBoundaryInsertState(direction, count, position, offset);
4530
+ let { position, offset } = normalizedState;
4531
+ let drawLength = 0;
4532
+ if (offset < 0) if (position === items.length - 1) offset = 0;
4533
+ else for (let i = position + 1; i < items.length; i += 1) {
4534
+ const { height } = readResolvedItem(items[i], i);
4535
+ position = i;
4536
+ offset += height;
4537
+ if (offset > 0) break;
4893
4538
  }
4894
- getItemHeight(item, now, adapter) {
4895
- return getTransitionedItemHeight(item, now, this.#store, adapter);
4896
- }
4897
- resolveItem(item, now, adapter, lifecycle) {
4898
- return resolveTransitionedItem(item, now, this.#store, adapter, lifecycle);
4899
- }
4900
- handleListStateChange(change, ctx, lifecycle) {
4901
- const now = getNow();
4902
- this.settle(now, lifecycle);
4903
- handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle);
4904
- }
4905
- settle(now, lifecycle) {
4906
- return this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
4907
- }
4908
- pruneInvisibleAt(now, ctx, lifecycle) {
4909
- const removals = this.#store.findInvisible(this.#snapshot);
4910
- return this.#settleTransitions(removals, now, lifecycle, this.#resolveNaturalBoundarySnap(removals, now, ctx, lifecycle));
4911
- }
4912
- reset() {
4913
- this.#store.reset();
4914
- this.#snapshot.reset();
4915
- }
4916
- #settleTransitions(removals, now, lifecycle, boundarySnap) {
4917
- if (removals.length === 0) return false;
4918
- const anchor = lifecycle.captureVisualAnchor(now);
4919
- const beforeState = lifecycle.readScrollState();
4920
- const completedDeleteIndices = [];
4921
- for (const { item, transition } of removals) {
4922
- if (transition.kind === "delete") {
4923
- const index = lifecycle.readItemIndex(item);
4924
- if (index >= 0) completedDeleteIndices.push(index);
4925
- }
4926
- this.#store.delete(item);
4927
- if (transition.kind === "delete") lifecycle.onDeleteComplete(item);
4928
- }
4929
- if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(remapAnchorAfterDeletes(anchor, completedDeleteIndices));
4930
- if (boundarySnap != null) lifecycle.snapItemToViewportBoundary(boundarySnap.item, boundarySnap.boundary);
4931
- const afterState = lifecycle.readScrollState();
4932
- if (!sameState(beforeState, afterState.position, afterState.offset)) lifecycle.onTransitionSettleScrollAdjusted();
4933
- return true;
4934
- }
4935
- #resolveNaturalBoundarySnap(removals, now, ctx, lifecycle) {
4936
- const previousState = this.#snapshot.previousState;
4937
- const drawnRange = this.#snapshot.readDrawnIndexRange();
4938
- if (previousState == null || drawnRange == null) return;
4939
- const naturalIndices = [];
4940
- for (const { item, transition } of removals) {
4941
- if (transition.kind !== "update" && transition.kind !== "delete") continue;
4942
- const index = lifecycle.readItemIndex(item);
4943
- if (index < 0 || !this.#snapshot.wasVisible(item)) return;
4944
- if (this.#isTransitionVisibleInState(index, previousState, now, ctx)) return;
4945
- naturalIndices.push(index);
4946
- }
4947
- if (naturalIndices.length === 0) return;
4948
- if (naturalIndices.every((index) => index < drawnRange.minIndex)) {
4949
- const item = this.#snapshot.readBoundaryItem("top");
4950
- return item == null ? void 0 : {
4951
- item,
4952
- boundary: "top"
4953
- };
4954
- }
4955
- if (naturalIndices.every((index) => index > drawnRange.maxIndex)) {
4956
- const item = this.#snapshot.readBoundaryItem("bottom");
4957
- return item == null ? void 0 : {
4958
- item,
4959
- boundary: "bottom"
4960
- };
4961
- }
4962
- }
4963
- #isTransitionVisibleInState(index, state, now, ctx) {
4964
- const solution = ctx.resolveVisibleWindowForState(state, now);
4965
- for (const entry of solution.window.drawList) {
4966
- if (entry.idx !== index) continue;
4967
- return ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null;
4968
- }
4969
- return false;
4970
- }
4971
- };
4972
- //#endregion
4973
- //#region src/renderer/virtualized/solver.ts
4974
- function clamp(value, min, max) {
4975
- return Math.min(Math.max(value, min), max);
4976
- }
4977
- function normalizeOffset(offset) {
4978
- return Number.isFinite(offset) ? offset : 0;
4979
- }
4980
- function normalizeListPadding(padding) {
4981
- return {
4982
- top: typeof padding?.top === "number" && Number.isFinite(padding.top) ? Math.max(0, padding.top) : 0,
4983
- bottom: typeof padding?.bottom === "number" && Number.isFinite(padding.bottom) ? Math.max(0, padding.bottom) : 0
4984
- };
4985
- }
4986
- function resolveListViewport(outerHeight, padding) {
4987
- const height = typeof outerHeight === "number" && Number.isFinite(outerHeight) ? Math.max(0, outerHeight) : 0;
4988
- const resolvedPadding = normalizeListPadding(padding);
4989
- const contentTop = resolvedPadding.top;
4990
- const contentBottom = Math.max(contentTop, height - resolvedPadding.bottom);
4991
- return {
4992
- outerHeight: height,
4993
- contentTop,
4994
- contentBottom,
4995
- contentHeight: contentBottom - contentTop,
4996
- outerContentTop: -contentTop,
4997
- outerContentBottom: height - contentTop
4998
- };
4999
- }
5000
- function resolveListLayoutOptions(options = {}) {
5001
- return {
5002
- anchorMode: options.anchorMode ?? "top",
5003
- underflowAlign: options.underflowAlign ?? "top",
5004
- padding: normalizeListPadding(options.padding)
5005
- };
5006
- }
5007
- function normalizeVisibleState(itemCount, state, layout) {
5008
- if (itemCount <= 0) return {
5009
- position: 0,
5010
- offset: 0
5011
- };
5012
- const position = state.position;
5013
- const fallbackPosition = layout.anchorMode === "top" ? 0 : itemCount - 1;
5014
- if (typeof position !== "number" || !Number.isFinite(position)) return {
5015
- position: fallbackPosition,
5016
- offset: normalizeOffset(state.offset)
5017
- };
5018
- return {
5019
- position: clamp(Math.trunc(position), 0, itemCount - 1),
5020
- offset: normalizeOffset(state.offset)
5021
- };
5022
- }
5023
- function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout) {
5024
- const viewport = typeof viewportHeight === "number" ? resolveListViewport(viewportHeight, layout.padding) : viewportHeight;
5025
- const contentHeight = viewport.contentHeight;
5026
- const normalizedState = normalizeVisibleState(items.length, state, layout);
5027
- const resolutionPath = /* @__PURE__ */ new Set();
5028
- const readResolvedItem = (item, idx) => {
5029
- resolutionPath.add(idx);
5030
- return resolveItem(item, idx);
5031
- };
5032
- if (items.length === 0) return {
5033
- normalizedState,
5034
- resolutionPath: [],
5035
- window: {
5036
- drawList: [],
5037
- shift: 0
5038
- }
5039
- };
5040
- if (layout.anchorMode === "top") {
5041
- let { position, offset } = normalizedState;
5042
- let drawLength = 0;
5043
- if (offset > 0) if (position === 0) offset = 0;
5044
- else {
5045
- for (let i = position - 1; i >= 0; i -= 1) {
5046
- const { height } = readResolvedItem(items[i], i);
5047
- position = i;
5048
- offset -= height;
5049
- if (offset <= 0) break;
5050
- }
5051
- if (position === 0 && offset > 0) offset = 0;
5052
- }
5053
- let y = offset;
5054
- const drawList = [];
5055
- for (let i = position; i < items.length; i += 1) {
5056
- const { value, height } = readResolvedItem(items[i], i);
5057
- if (y + height > 0) {
5058
- drawList.push({
5059
- idx: i,
5060
- value,
5061
- offset: y,
5062
- height
5063
- });
5064
- drawLength += height;
5065
- } else {
5066
- offset += height;
5067
- position = i + 1;
5068
- }
5069
- y += height;
5070
- if (y >= contentHeight) break;
5071
- }
5072
- let shift = 0;
5073
- if (y < contentHeight) {
5074
- if (drawList.length > 0 && drawList.at(-1)?.idx === items.length - 1 && !(drawList.at(-1)?.height > Number.EPSILON)) return finalizeVisibleWindowResult(items.length, viewport, layout, {
5075
- position,
5076
- offset
5077
- }, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
5078
- drawList,
5079
- shift
5080
- }, viewport, readResolvedItem));
5081
- if (position === 0 && drawLength < contentHeight) {
5082
- shift = -offset;
5083
- offset = 0;
5084
- } else {
5085
- shift = contentHeight - y;
5086
- y = offset += shift;
5087
- let lastIdx = -1;
5088
- for (let i = position - 1; i >= 0; i -= 1) {
5089
- const { value, height } = readResolvedItem(items[i], i);
5090
- drawLength += height;
5091
- y -= height;
5092
- drawList.push({
5093
- idx: i,
5094
- value,
5095
- offset: y - shift,
5096
- height
5097
- });
5098
- lastIdx = i;
5099
- if (y < 0) break;
5100
- }
5101
- if (lastIdx === 0 && drawLength < contentHeight) {
5102
- shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
5103
- position = 0;
5104
- offset = 0;
5105
- }
5106
- }
5107
- }
5108
- return finalizeVisibleWindowResult(items.length, viewport, layout, {
5109
- position,
5110
- offset
5111
- }, Array.from(resolutionPath), extendVisibleWindowToOuterBounds(items, {
5112
- drawList,
5113
- shift
5114
- }, viewport, readResolvedItem));
5115
- }
5116
- let { position, offset } = normalizedState;
5117
- let drawLength = 0;
5118
- if (offset < 0) if (position === items.length - 1) offset = 0;
5119
- else for (let i = position + 1; i < items.length; i += 1) {
5120
- const { height } = readResolvedItem(items[i], i);
5121
- position = i;
5122
- offset += height;
5123
- if (offset > 0) break;
5124
- }
5125
- let y = contentHeight + offset;
5126
- const drawList = [];
5127
- for (let i = position; i >= 0; i -= 1) {
5128
- const { value, height } = readResolvedItem(items[i], i);
5129
- y -= height;
5130
- if (y <= contentHeight) {
5131
- drawList.push({
5132
- idx: i,
5133
- value,
5134
- offset: y,
5135
- height
5136
- });
5137
- drawLength += height;
5138
- } else {
5139
- offset -= height;
5140
- position = i - 1;
5141
- }
5142
- if (y < 0) break;
4539
+ let y = contentHeight + offset;
4540
+ const drawList = [];
4541
+ for (let i = position; i >= 0; i -= 1) {
4542
+ const { value, height } = readResolvedItem(items[i], i);
4543
+ y -= height;
4544
+ if (y <= contentHeight) {
4545
+ drawList.push({
4546
+ index: i,
4547
+ value,
4548
+ offset: y,
4549
+ height
4550
+ });
4551
+ drawLength += height;
4552
+ } else {
4553
+ offset -= height;
4554
+ position = i - 1;
4555
+ }
4556
+ if (y < 0) break;
5143
4557
  }
5144
4558
  let shift = 0;
5145
4559
  if (y > 0) {
@@ -5149,7 +4563,7 @@ function resolveVisibleWindow(items, state, viewportHeight, resolveItem, layout)
5149
4563
  for (let i = position + 1; i < items.length; i += 1) {
5150
4564
  const { value, height } = readResolvedItem(items[i], i);
5151
4565
  drawList.push({
5152
- idx: i,
4566
+ index: i,
5153
4567
  value,
5154
4568
  offset: y - shift,
5155
4569
  height
@@ -5187,96 +4601,745 @@ function finalizeVisibleWindowResult(itemCount, viewport, layout, normalizedStat
5187
4601
  minOffset = Math.min(minOffset, entry.offset);
5188
4602
  maxBottom = Math.max(maxBottom, entry.offset + entry.height);
5189
4603
  }
5190
- minIndex = Math.min(minIndex, entry.idx);
5191
- maxIndex = Math.max(maxIndex, entry.idx);
4604
+ minIndex = Math.min(minIndex, entry.index);
4605
+ maxIndex = Math.max(maxIndex, entry.index);
4606
+ }
4607
+ if (!Number.isFinite(minOffset) || !Number.isFinite(maxBottom)) return {
4608
+ normalizedState,
4609
+ resolutionPath,
4610
+ window
4611
+ };
4612
+ const contentHeight = maxBottom - minOffset;
4613
+ if (minIndex !== 0 || maxIndex !== itemCount - 1 || !(contentHeight < viewportHeight - Number.EPSILON)) return {
4614
+ normalizedState,
4615
+ resolutionPath,
4616
+ window
4617
+ };
4618
+ const desiredTop = layout.underflowAlign === "bottom" ? viewportHeight - contentHeight : 0;
4619
+ return {
4620
+ normalizedState: hasDeferredSlots ? normalizedState : layout.anchorMode === "top" ? {
4621
+ position: 0,
4622
+ offset: 0
4623
+ } : {
4624
+ position: itemCount - 1,
4625
+ offset: 0
4626
+ },
4627
+ resolutionPath,
4628
+ window: {
4629
+ drawList: window.drawList,
4630
+ shift: desiredTop - minOffset
4631
+ }
4632
+ };
4633
+ }
4634
+ function extendVisibleWindowToOuterBounds(items, window, viewport, resolveItem) {
4635
+ if (window.drawList.length === 0 || items.length === 0) return window;
4636
+ const drawList = [...window.drawList];
4637
+ const existingIndices = new Set(drawList.map((entry) => entry.index));
4638
+ let topEntry = drawList[0];
4639
+ let bottomEntry = drawList[0];
4640
+ for (const entry of drawList) {
4641
+ if (entry.offset < topEntry.offset) topEntry = entry;
4642
+ if (entry.offset + entry.height > bottomEntry.offset + bottomEntry.height) bottomEntry = entry;
4643
+ }
4644
+ let topIdx = topEntry.index;
4645
+ let topY = topEntry.offset + window.shift;
4646
+ while (topIdx > 0) {
4647
+ const prevIdx = topIdx - 1;
4648
+ if (existingIndices.has(prevIdx)) {
4649
+ const existing = drawList.find((entry) => entry.index === prevIdx);
4650
+ topIdx = prevIdx;
4651
+ if (existing != null) topY = existing.offset + window.shift;
4652
+ continue;
4653
+ }
4654
+ const { value, height } = resolveItem(items[prevIdx], prevIdx);
4655
+ const prevY = topY - height;
4656
+ if (prevY + height <= viewport.outerContentTop) break;
4657
+ drawList.push({
4658
+ index: prevIdx,
4659
+ value,
4660
+ offset: prevY - window.shift,
4661
+ height
4662
+ });
4663
+ existingIndices.add(prevIdx);
4664
+ topIdx = prevIdx;
4665
+ topY = prevY;
4666
+ }
4667
+ let bottomIdx = bottomEntry.index;
4668
+ let bottomY = bottomEntry.offset + window.shift + bottomEntry.height;
4669
+ while (bottomIdx < items.length - 1) {
4670
+ const nextIdx = bottomIdx + 1;
4671
+ if (existingIndices.has(nextIdx)) {
4672
+ const existing = drawList.find((entry) => entry.index === nextIdx);
4673
+ bottomIdx = nextIdx;
4674
+ if (existing != null) bottomY = Math.max(bottomY, existing.offset + window.shift + existing.height);
4675
+ continue;
4676
+ }
4677
+ const { value, height } = resolveItem(items[nextIdx], nextIdx);
4678
+ if (bottomY >= viewport.outerContentBottom) break;
4679
+ drawList.push({
4680
+ index: nextIdx,
4681
+ value,
4682
+ offset: bottomY - window.shift,
4683
+ height
4684
+ });
4685
+ existingIndices.add(nextIdx);
4686
+ bottomIdx = nextIdx;
4687
+ bottomY += height;
4688
+ }
4689
+ return {
4690
+ drawList,
4691
+ shift: window.shift
4692
+ };
4693
+ }
4694
+ //#endregion
4695
+ //#region src/renderer/virtualized/transition-snapshot.ts
4696
+ var VisibilitySnapshot = class {
4697
+ #drawnItems = /* @__PURE__ */ new Set();
4698
+ #visibleItems = /* @__PURE__ */ new Set();
4699
+ #previousVisibleItems = /* @__PURE__ */ new Set();
4700
+ #hasSnapshot = false;
4701
+ #snapshotState;
4702
+ #previousSnapshotState;
4703
+ #emptyState;
4704
+ #coversShortList = false;
4705
+ #atStartBoundary = false;
4706
+ #atEndBoundary = false;
4707
+ #minDrawnIndex = Number.POSITIVE_INFINITY;
4708
+ #maxDrawnIndex = Number.NEGATIVE_INFINITY;
4709
+ #topBoundaryItem;
4710
+ #bottomBoundaryItem;
4711
+ get coversShortList() {
4712
+ return this.#hasSnapshot && this.#snapshotState != null && this.#coversShortList;
4713
+ }
4714
+ get hasSnapshot() {
4715
+ return this.#hasSnapshot;
4716
+ }
4717
+ get previousState() {
4718
+ return this.#previousSnapshotState;
4719
+ }
4720
+ readDrawnIndexRange() {
4721
+ if (!Number.isFinite(this.#minDrawnIndex) || !Number.isFinite(this.#maxDrawnIndex)) return;
4722
+ return {
4723
+ minIndex: this.#minDrawnIndex,
4724
+ maxIndex: this.#maxDrawnIndex
4725
+ };
4726
+ }
4727
+ readBoundaryItem(boundary) {
4728
+ return boundary === "top" ? this.#topBoundaryItem : this.#bottomBoundaryItem;
4729
+ }
4730
+ capture(window, _resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
4731
+ this.#previousVisibleItems = this.#visibleItems;
4732
+ this.#previousSnapshotState = this.#snapshotState;
4733
+ const nextDrawnItems = /* @__PURE__ */ new Set();
4734
+ const nextVisibleItems = /* @__PURE__ */ new Set();
4735
+ let minVisibleIndex = Number.POSITIVE_INFINITY;
4736
+ let maxVisibleIndex = Number.NEGATIVE_INFINITY;
4737
+ let topMostY = Number.POSITIVE_INFINITY;
4738
+ let bottomMostY = Number.NEGATIVE_INFINITY;
4739
+ let nextMinDrawnIndex = Number.POSITIVE_INFINITY;
4740
+ let nextMaxDrawnIndex = Number.NEGATIVE_INFINITY;
4741
+ let nextTopBoundaryItem;
4742
+ let nextBottomBoundaryItem;
4743
+ let nextTopBoundaryY = Number.POSITIVE_INFINITY;
4744
+ let nextBottomBoundaryY = Number.NEGATIVE_INFINITY;
4745
+ const effectiveShift = window.shift;
4746
+ const contentOriginY = viewport.contentTop;
4747
+ for (const { index, offset, height } of window.drawList) {
4748
+ const y = offset + effectiveShift + contentOriginY;
4749
+ topMostY = Math.min(topMostY, y);
4750
+ bottomMostY = Math.max(bottomMostY, y + height);
4751
+ const item = items[index];
4752
+ if (item != null && readOuterVisibleRange(y, height) != null) {
4753
+ nextDrawnItems.add(item);
4754
+ nextMinDrawnIndex = Math.min(nextMinDrawnIndex, index);
4755
+ nextMaxDrawnIndex = Math.max(nextMaxDrawnIndex, index);
4756
+ }
4757
+ if (item == null) continue;
4758
+ if (readVisibleRange(y, height) != null) {
4759
+ minVisibleIndex = Math.min(minVisibleIndex, index);
4760
+ maxVisibleIndex = Math.max(maxVisibleIndex, index);
4761
+ nextVisibleItems.add(item);
4762
+ if (y < nextTopBoundaryY) {
4763
+ nextTopBoundaryY = y;
4764
+ nextTopBoundaryItem = item;
4765
+ }
4766
+ if (y + height > nextBottomBoundaryY) {
4767
+ nextBottomBoundaryY = y + height;
4768
+ nextBottomBoundaryItem = item;
4769
+ }
4770
+ }
4771
+ }
4772
+ this.#drawnItems = nextDrawnItems;
4773
+ this.#visibleItems = nextVisibleItems;
4774
+ this.#hasSnapshot = true;
4775
+ this.#snapshotState = snapshotState;
4776
+ this.#minDrawnIndex = nextMinDrawnIndex;
4777
+ this.#maxDrawnIndex = nextMaxDrawnIndex;
4778
+ this.#topBoundaryItem = nextTopBoundaryItem;
4779
+ this.#bottomBoundaryItem = nextBottomBoundaryItem;
4780
+ this.#emptyState = items.length === 0 && window.drawList.length === 0 ? snapshotState : void 0;
4781
+ const contentHeight = bottomMostY - topMostY;
4782
+ this.#coversShortList = window.drawList.length > 0 && items.length > 0 && window.drawList.length === items.length && minVisibleIndex === 0 && maxVisibleIndex === items.length - 1 && topMostY >= viewport.contentTop - 1e-6 && bottomMostY <= viewport.contentBottom + 1e-6 && contentHeight < viewport.contentHeight - 1e-6;
4783
+ this.#atStartBoundary = window.drawList.length > 0 && items.length > 0 && minVisibleIndex === 0 && topMostY >= viewport.contentTop - 1e-6;
4784
+ this.#atEndBoundary = window.drawList.length > 0 && items.length > 0 && maxVisibleIndex === items.length - 1 && bottomMostY <= viewport.contentBottom + 1e-6;
4785
+ }
4786
+ matchesCurrentState(position, offset) {
4787
+ return this.#hasSnapshot && this.#snapshotState != null && sameState(this.#snapshotState, position, offset);
4788
+ }
4789
+ matchesBoundaryInsertState(direction, count, position, offset) {
4790
+ if (!this.coversShortList || this.#snapshotState == null) return false;
4791
+ return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
4792
+ }
4793
+ matchesFollowBoundaryInsertState(direction, count, position, offset) {
4794
+ if (!this.#hasSnapshot || this.#snapshotState == null) return false;
4795
+ if (direction === "push" ? !this.#atEndBoundary : !this.#atStartBoundary) return false;
4796
+ return this.#matchesStateAfterBoundaryInsert(direction, count, position, offset);
4797
+ }
4798
+ matchesEmptyBoundaryInsertState(direction, count, position, offset) {
4799
+ const emptyState = this.#emptyState;
4800
+ if (!this.#hasSnapshot || emptyState == null) return false;
4801
+ return sameState({
4802
+ position: direction === "unshift" && emptyState.position != null ? emptyState.position + count : emptyState.position,
4803
+ offset: emptyState.offset
4804
+ }, position, offset);
4805
+ }
4806
+ isVisible(item) {
4807
+ return this.#visibleItems.has(item);
4808
+ }
4809
+ wasVisible(item) {
4810
+ return this.#previousVisibleItems.has(item);
4811
+ }
4812
+ tracks(item, retention) {
4813
+ return retention === "drawn" ? this.#drawnItems.has(item) : this.#visibleItems.has(item);
4814
+ }
4815
+ reset() {
4816
+ this.#drawnItems.clear();
4817
+ this.#visibleItems.clear();
4818
+ this.#previousVisibleItems.clear();
4819
+ this.#hasSnapshot = false;
4820
+ this.#snapshotState = void 0;
4821
+ this.#previousSnapshotState = void 0;
4822
+ this.#emptyState = void 0;
4823
+ this.#coversShortList = false;
4824
+ this.#atStartBoundary = false;
4825
+ this.#atEndBoundary = false;
4826
+ this.#minDrawnIndex = Number.POSITIVE_INFINITY;
4827
+ this.#maxDrawnIndex = Number.NEGATIVE_INFINITY;
4828
+ this.#topBoundaryItem = void 0;
4829
+ this.#bottomBoundaryItem = void 0;
4830
+ }
4831
+ #matchesStateAfterBoundaryInsert(direction, count, position, offset) {
4832
+ const snapshotState = this.#snapshotState;
4833
+ if (snapshotState == null) return false;
4834
+ return sameState({
4835
+ position: direction === "unshift" && snapshotState.position != null ? snapshotState.position + count : snapshotState.position,
4836
+ offset: snapshotState.offset
4837
+ }, position, offset);
4838
+ }
4839
+ };
4840
+ //#endregion
4841
+ //#region src/renderer/virtualized/transition-store.ts
4842
+ var TransitionStore = class {
4843
+ #transitions = /* @__PURE__ */ new Map();
4844
+ get size() {
4845
+ return this.#transitions.size;
4846
+ }
4847
+ has(item) {
4848
+ return this.#transitions.has(item);
4849
+ }
4850
+ set(item, transition) {
4851
+ const previous = this.#transitions.get(item);
4852
+ this.#transitions.set(item, transition);
4853
+ return previous;
4854
+ }
4855
+ replace(prevItem, nextItem, transition) {
4856
+ const previous = this.#transitions.get(prevItem);
4857
+ this.#transitions.delete(prevItem);
4858
+ this.#transitions.set(nextItem, transition);
4859
+ return previous;
4860
+ }
4861
+ delete(item) {
4862
+ const transition = this.#transitions.get(item);
4863
+ if (transition != null) this.#transitions.delete(item);
4864
+ return transition;
4865
+ }
4866
+ readActive(item, now) {
4867
+ const transition = this.#transitions.get(item);
4868
+ if (transition == null) return;
4869
+ return this.#isComplete(transition, now) ? void 0 : transition;
4870
+ }
4871
+ prepare(now) {
4872
+ for (const transition of this.#transitions.values()) if (!this.#isComplete(transition, now)) return true;
4873
+ return false;
4874
+ }
4875
+ findCompleted(now) {
4876
+ return [...this.#transitions.entries()].filter(([, transition]) => this.#isComplete(transition, now)).map(([item, transition]) => ({
4877
+ item,
4878
+ transition
4879
+ }));
4880
+ }
4881
+ findInvisible(snapshot) {
4882
+ return [...this.#transitions.entries()].filter(([item, transition]) => !snapshot.tracks(item, transition.retention) && !(transition.kind === "insert" && !snapshot.wasVisible(item))).map(([item, transition]) => ({
4883
+ item,
4884
+ transition
4885
+ }));
4886
+ }
4887
+ entries() {
4888
+ return [...this.#transitions.entries()].map(([item, transition]) => ({
4889
+ item,
4890
+ transition
4891
+ }));
4892
+ }
4893
+ reset() {
4894
+ this.#transitions.clear();
4895
+ }
4896
+ #isComplete(transition, now) {
4897
+ return getProgress(transition.height.startTime, transition.height.duration, now) >= 1;
4898
+ }
4899
+ };
4900
+ //#endregion
4901
+ //#region src/renderer/virtualized/transition-planner.ts
4902
+ function normalizeDuration(duration) {
4903
+ return Math.max(0, typeof duration === "number" && Number.isFinite(duration) ? duration : 0);
4904
+ }
4905
+ function createScalarAnimation(from, to, startTime, duration) {
4906
+ return {
4907
+ from,
4908
+ to,
4909
+ startTime,
4910
+ duration
4911
+ };
4912
+ }
4913
+ function createLayerAnimation(node, fromAlpha, toAlpha, startTime, duration, fromTranslateY, toTranslateY) {
4914
+ return {
4915
+ node,
4916
+ alpha: createScalarAnimation(fromAlpha, toAlpha, startTime, duration),
4917
+ translateY: createScalarAnimation(fromTranslateY, toTranslateY, startTime, duration)
4918
+ };
4919
+ }
4920
+ function findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) {
4921
+ if (index < 0) return;
4922
+ const solution = resolveVisibleWindow();
4923
+ for (const entry of solution.window.drawList) {
4924
+ if (entry.index !== index) continue;
4925
+ if (readVisibleRange(entry.offset + solution.window.shift, entry.height) != null) return entry;
4926
+ }
4927
+ }
4928
+ function isIndexVisible(index, resolveVisibleWindow, readVisibleRange) {
4929
+ return findVisibleEntry(index, resolveVisibleWindow, readVisibleRange) != null;
4930
+ }
4931
+ function resolveAnimationEligibility(params) {
4932
+ if (params.index < 0) return false;
4933
+ if (params.snapshot.matchesCurrentState(params.position, params.offset)) return params.snapshot.tracks(params.item, "drawn");
4934
+ return isIndexVisible(params.index, params.resolveVisibleWindow, params.readOuterVisibleRange);
4935
+ }
4936
+ function hasVisibleBoundaryInsertItems(direction, count, ctx) {
4937
+ if (count <= 0) return false;
4938
+ const start = direction === "push" ? ctx.items.length - count : 0;
4939
+ const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
4940
+ if (start < 0 || end <= start) return false;
4941
+ const solution = ctx.resolveVisibleWindow();
4942
+ return solution.window.drawList.some((entry) => entry.index >= start && entry.index < end && ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null);
4943
+ }
4944
+ function sampleScalarAnimation(animation, now) {
4945
+ return interpolate(animation.from, animation.to, animation.startTime, animation.duration, now);
4946
+ }
4947
+ function sampleLayerAnimation(layer, now) {
4948
+ const alpha = sampleScalarAnimation(layer.alpha, now);
4949
+ if (alpha <= .001) return;
4950
+ return {
4951
+ alpha,
4952
+ node: layer.node,
4953
+ translateY: sampleScalarAnimation(layer.translateY, now)
4954
+ };
4955
+ }
4956
+ function sampleTransition(transition, now) {
4957
+ return {
4958
+ kind: transition.kind,
4959
+ slotHeight: sampleScalarAnimation(transition.height, now),
4960
+ layers: transition.layers.map((layer) => sampleLayerAnimation(layer, now)).filter((layer) => layer != null),
4961
+ retention: transition.retention
4962
+ };
4963
+ }
4964
+ function planExistingItemTransition(params) {
4965
+ if (!params.canAnimate || params.duration <= 0) return;
4966
+ if (params.kind === "update" && !Number.isFinite(params.nextHeight)) return;
4967
+ const layers = [];
4968
+ if (params.currentVisualState.alpha > .001) layers.push(createLayerAnimation(params.currentVisualState.node, params.currentVisualState.alpha, 0, params.now, params.duration, params.currentVisualState.translateY, 0));
4969
+ if (params.kind === "update") {
4970
+ layers.push(createLayerAnimation(params.nextNode, 0, 1, params.now, params.duration, params.currentVisualState.translateY, 0));
4971
+ return {
4972
+ kind: "update",
4973
+ layers,
4974
+ height: createScalarAnimation(params.currentVisualState.height, params.nextHeight, params.now, params.duration),
4975
+ retention: "drawn"
4976
+ };
4977
+ }
4978
+ return {
4979
+ kind: "delete",
4980
+ layers,
4981
+ height: createScalarAnimation(params.currentVisualState.height, 0, params.now, params.duration),
4982
+ retention: "drawn"
4983
+ };
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
+ }
5009
+ function planBoundaryInsertItems(params) {
5010
+ const entries = [];
5011
+ for (const { item, node, height } of params.measuredItems) {
5012
+ if (!Number.isFinite(height) || height < 0) return;
5013
+ entries.push({
5014
+ item,
5015
+ transition: {
5016
+ kind: "insert",
5017
+ layers: [createLayerAnimation(node, 0, 1, params.now, params.duration, 0, 0)],
5018
+ height: createScalarAnimation(params.animateHeight ? 0 : height, height, params.now, params.duration),
5019
+ retention: "drawn"
5020
+ }
5021
+ });
5022
+ }
5023
+ return entries.length === 0 ? void 0 : { entries };
5024
+ }
5025
+ function measureBoundaryInsertItems(direction, count, ctx) {
5026
+ const start = direction === "push" ? ctx.items.length - count : 0;
5027
+ const end = direction === "push" ? ctx.items.length : Math.min(count, ctx.items.length);
5028
+ if (start < 0 || end < start) return;
5029
+ const measured = [];
5030
+ for (let index = start; index < end; index += 1) {
5031
+ const item = ctx.items[index];
5032
+ if (item == null) continue;
5033
+ const node = ctx.renderItem(item);
5034
+ const height = ctx.measureNode(node).height;
5035
+ measured.push({
5036
+ item,
5037
+ node,
5038
+ height
5039
+ });
5040
+ }
5041
+ return measured;
5042
+ }
5043
+ function drawSampledLayers(sampled, y, adapter) {
5044
+ if (sampled.slotHeight <= 0) return false;
5045
+ let result = false;
5046
+ for (const layer of sampled.layers) {
5047
+ const alpha = clamp$1(layer.alpha, 0, 1);
5048
+ if (alpha <= .001) continue;
5049
+ adapter.graphics.save();
5050
+ try {
5051
+ if (sampled.kind === "insert") {
5052
+ adapter.graphics.beginPath();
5053
+ adapter.graphics.rect(0, y, adapter.graphics.canvas.clientWidth, sampled.slotHeight);
5054
+ adapter.graphics.clip();
5055
+ }
5056
+ if (typeof adapter.graphics.globalAlpha === "number") adapter.graphics.globalAlpha *= alpha;
5057
+ if (adapter.drawNode(layer.node, 0, y + layer.translateY)) result = true;
5058
+ } finally {
5059
+ adapter.graphics.restore();
5060
+ }
5061
+ }
5062
+ return result;
5063
+ }
5064
+ function planUpdateTransition(prevItem, nextItem, duration, now, currentVisualState, ctx, snapshot, store) {
5065
+ const nextIndex = ctx.items.indexOf(nextItem);
5066
+ const nextNode = ctx.renderItem(nextItem);
5067
+ const nextHeight = ctx.measureNode(nextNode).height;
5068
+ return planExistingItemTransition({
5069
+ kind: "update",
5070
+ duration: normalizeDuration(duration),
5071
+ canAnimate: resolveAnimationEligibility({
5072
+ index: nextIndex,
5073
+ item: prevItem,
5074
+ position: ctx.position,
5075
+ offset: ctx.offset,
5076
+ snapshot,
5077
+ hasActiveTransition: store.has(prevItem),
5078
+ resolveVisibleWindow: ctx.resolveVisibleWindow,
5079
+ readOuterVisibleRange: ctx.readOuterVisibleRange
5080
+ }),
5081
+ now,
5082
+ currentVisualState,
5083
+ nextNode,
5084
+ nextHeight
5085
+ });
5086
+ }
5087
+ function planDeleteTransition(item, duration, now, currentVisualState, ctx, snapshot, store) {
5088
+ const index = ctx.items.indexOf(item);
5089
+ return planExistingItemTransition({
5090
+ kind: "delete",
5091
+ duration: normalizeDuration(duration),
5092
+ canAnimate: resolveAnimationEligibility({
5093
+ index,
5094
+ item,
5095
+ position: ctx.position,
5096
+ offset: ctx.offset,
5097
+ snapshot,
5098
+ hasActiveTransition: store.has(item),
5099
+ resolveVisibleWindow: ctx.resolveVisibleWindow,
5100
+ readOuterVisibleRange: ctx.readOuterVisibleRange
5101
+ }),
5102
+ now,
5103
+ currentVisualState
5104
+ });
5105
+ }
5106
+ function planBoundaryInsertTransition(direction, count, duration, now, ctx, snapshot) {
5107
+ const normalizedDuration = normalizeDuration(duration);
5108
+ if (count <= 0 || normalizedDuration <= 0) return;
5109
+ const matchesBoundaryState = snapshot.matchesBoundaryInsertState(direction, count, ctx.position, ctx.offset);
5110
+ const matchesFollowState = snapshot.matchesFollowBoundaryInsertState(direction, count, ctx.position, ctx.offset);
5111
+ const matchesEmptyState = snapshot.matchesEmptyBoundaryInsertState(direction, count, ctx.position, ctx.offset);
5112
+ if (!(matchesBoundaryState || matchesFollowState || matchesEmptyState || snapshot.hasSnapshot && hasVisibleBoundaryInsertItems(direction, count, ctx))) return;
5113
+ const animateHeight = !(direction === "unshift" && matchesFollowState && !matchesBoundaryState && !matchesEmptyState);
5114
+ const measuredItems = measureBoundaryInsertItems(direction, count, ctx);
5115
+ if (measuredItems == null) return;
5116
+ return planBoundaryInsertItems({
5117
+ duration: normalizedDuration,
5118
+ animateHeight,
5119
+ now,
5120
+ measuredItems
5121
+ });
5122
+ }
5123
+ function getTransitionedItemHeight(item, now, store, adapter) {
5124
+ const transition = store.readActive(item, now);
5125
+ if (transition != null) return sampleTransition(transition, now).slotHeight;
5126
+ const node = adapter.renderItem(item);
5127
+ return adapter.measureNode(node).height;
5128
+ }
5129
+ function resolveTransitionedItem(item, now, store, adapter, lifecycle) {
5130
+ const transition = store.readActive(item, now);
5131
+ if (transition == null) {
5132
+ const node = adapter.renderItem(item);
5133
+ return {
5134
+ value: {
5135
+ draw: (y) => adapter.drawNode(node, 0, y),
5136
+ hittest: (test, y) => node.hittest(adapter.getRootContext(), {
5137
+ ...test,
5138
+ y: test.y - y
5139
+ })
5140
+ },
5141
+ height: adapter.measureNode(node).height
5142
+ };
5192
5143
  }
5193
- if (!Number.isFinite(minOffset) || !Number.isFinite(maxBottom)) return {
5194
- normalizedState,
5195
- resolutionPath,
5196
- window
5197
- };
5198
- const contentHeight = maxBottom - minOffset;
5199
- if (minIndex !== 0 || maxIndex !== itemCount - 1 || !(contentHeight < viewportHeight - Number.EPSILON)) return {
5200
- normalizedState,
5201
- resolutionPath,
5202
- window
5203
- };
5204
- const desiredTop = layout.underflowAlign === "bottom" ? viewportHeight - contentHeight : 0;
5144
+ const sampled = sampleTransition(transition, now);
5205
5145
  return {
5206
- normalizedState: hasDeferredSlots ? normalizedState : layout.anchorMode === "top" ? {
5207
- position: 0,
5208
- offset: 0
5209
- } : {
5210
- position: itemCount - 1,
5211
- offset: 0
5146
+ value: {
5147
+ draw: (y) => drawSampledLayers(sampled, y, adapter),
5148
+ hittest: () => false
5212
5149
  },
5213
- resolutionPath,
5214
- window: {
5215
- drawList: window.drawList,
5216
- shift: desiredTop - minOffset
5217
- }
5150
+ height: sampled.slotHeight
5218
5151
  };
5219
5152
  }
5220
- function extendVisibleWindowToOuterBounds(items, window, viewport, resolveItem) {
5221
- if (window.drawList.length === 0 || items.length === 0) return window;
5222
- const drawList = [...window.drawList];
5223
- const existingIndices = new Set(drawList.map((entry) => entry.idx));
5224
- let topEntry = drawList[0];
5225
- let bottomEntry = drawList[0];
5226
- for (const entry of drawList) {
5227
- if (entry.offset < topEntry.offset) topEntry = entry;
5228
- if (entry.offset + entry.height > bottomEntry.offset + bottomEntry.height) bottomEntry = entry;
5153
+ function readCurrentVisualState(item, now, store, adapter) {
5154
+ const transition = store.readActive(item, now);
5155
+ if (transition != null && transition.layers.length > 0) {
5156
+ const primaryLayer = transition.layers[transition.layers.length - 1];
5157
+ return {
5158
+ node: primaryLayer.node,
5159
+ alpha: sampleScalarAnimation(primaryLayer.alpha, now),
5160
+ height: sampleScalarAnimation(transition.height, now),
5161
+ translateY: sampleScalarAnimation(primaryLayer.translateY, now)
5162
+ };
5229
5163
  }
5230
- let topIdx = topEntry.idx;
5231
- let topY = topEntry.offset + window.shift;
5232
- while (topIdx > 0) {
5233
- const prevIdx = topIdx - 1;
5234
- if (existingIndices.has(prevIdx)) {
5235
- const existing = drawList.find((entry) => entry.idx === prevIdx);
5236
- topIdx = prevIdx;
5237
- if (existing != null) topY = existing.offset + window.shift;
5238
- continue;
5164
+ const node = adapter.renderItem(item);
5165
+ return {
5166
+ node,
5167
+ alpha: 1,
5168
+ height: adapter.measureNode(node).height,
5169
+ translateY: 0
5170
+ };
5171
+ }
5172
+ function handleTransitionStateChange(store, snapshot, change, ctx, lifecycle, now = getNow()) {
5173
+ switch (change.type) {
5174
+ case "update": {
5175
+ const nextIndex = ctx.items.indexOf(change.nextItem);
5176
+ const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(nextIndex, snapshot);
5177
+ const observedBoundary = resolveAutoFollowBoundaryRisk(nextIndex, ctx, snapshot);
5178
+ const currentVisualState = readCurrentVisualState(change.prevItem, now, store, ctx);
5179
+ const transition = planUpdateTransition(change.prevItem, change.nextItem, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
5180
+ if (transition == null) {
5181
+ endTransitionAutoFollowObservation(store.delete(change.prevItem), lifecycle);
5182
+ invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
5183
+ return;
5184
+ }
5185
+ transition.observedAutoFollowBoundary = observedBoundary;
5186
+ endTransitionAutoFollowObservation(store.replace(change.prevItem, change.nextItem, transition), lifecycle);
5187
+ beginTransitionAutoFollowObservation(transition, lifecycle);
5188
+ return;
5239
5189
  }
5240
- const { value, height } = resolveItem(items[prevIdx], prevIdx);
5241
- const prevY = topY - height;
5242
- if (prevY + height <= viewport.outerContentTop) break;
5243
- drawList.push({
5244
- idx: prevIdx,
5245
- value,
5246
- offset: prevY - window.shift,
5247
- height
5248
- });
5249
- existingIndices.add(prevIdx);
5250
- topIdx = prevIdx;
5251
- topY = prevY;
5190
+ case "delete": {
5191
+ const index = ctx.items.indexOf(change.item);
5192
+ const canClassifyRisk = canClassifyAutoFollowBoundaryRisk(index, snapshot);
5193
+ const observedBoundary = resolveAutoFollowBoundaryRisk(index, ctx, snapshot);
5194
+ const currentVisualState = readCurrentVisualState(change.item, now, store, ctx);
5195
+ const transition = planDeleteTransition(change.item, change.animation?.duration, now, currentVisualState, ctx, snapshot, store);
5196
+ if (transition == null) {
5197
+ endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
5198
+ invalidateAutoFollowBoundaryRisk(observedBoundary, canClassifyRisk, lifecycle);
5199
+ lifecycle.onDeleteComplete(change.item);
5200
+ return;
5201
+ }
5202
+ transition.observedAutoFollowBoundary = observedBoundary;
5203
+ endTransitionAutoFollowObservation(store.set(change.item, transition), lifecycle);
5204
+ beginTransitionAutoFollowObservation(transition, lifecycle);
5205
+ return;
5206
+ }
5207
+ case "delete-finalize":
5208
+ endTransitionAutoFollowObservation(store.delete(change.item), lifecycle);
5209
+ lifecycle.invalidateAutoFollowBoundary(void 0);
5210
+ return;
5211
+ case "unshift":
5212
+ case "push": {
5213
+ const plan = planBoundaryInsertTransition(change.type, change.count, change.animation?.duration, now, ctx, snapshot);
5214
+ if (plan == null) return;
5215
+ for (const entry of plan.entries) endTransitionAutoFollowObservation(store.set(entry.item, entry.transition), lifecycle);
5216
+ if (ctx.position == null && snapshot.coversShortList && (change.type === "push" && ctx.anchorMode === "bottom" || change.type === "unshift" && ctx.anchorMode === "top")) {
5217
+ const boundary = change.type === "push" ? "bottom" : "top";
5218
+ const boundaryItem = snapshot.readBoundaryItem(boundary);
5219
+ if (boundaryItem != null) lifecycle.snapItemToViewportBoundary(boundaryItem, boundary);
5220
+ }
5221
+ return;
5222
+ }
5223
+ case "reset":
5224
+ case "set":
5225
+ for (const entry of store.entries()) endTransitionAutoFollowObservation(entry.transition, lifecycle);
5226
+ store.reset();
5227
+ snapshot.reset();
5228
+ return;
5252
5229
  }
5253
- let bottomIdx = bottomEntry.idx;
5254
- let bottomY = bottomEntry.offset + window.shift + bottomEntry.height;
5255
- while (bottomIdx < items.length - 1) {
5256
- const nextIdx = bottomIdx + 1;
5257
- if (existingIndices.has(nextIdx)) {
5258
- const existing = drawList.find((entry) => entry.idx === nextIdx);
5259
- bottomIdx = nextIdx;
5260
- if (existing != null) bottomY = Math.max(bottomY, existing.offset + window.shift + existing.height);
5230
+ }
5231
+ //#endregion
5232
+ //#region src/renderer/virtualized/transition-controller.ts
5233
+ function remapAnchorAfterDeletes(anchor, deletedIndices) {
5234
+ if (!Number.isFinite(anchor) || deletedIndices.length === 0) return anchor;
5235
+ const sortedIndices = [...deletedIndices].filter((index) => Number.isFinite(index) && index >= 0).sort((a, b) => a - b);
5236
+ let removedBeforeAnchor = 0;
5237
+ for (const index of sortedIndices) {
5238
+ if (anchor > index + 1) {
5239
+ removedBeforeAnchor += 1;
5261
5240
  continue;
5262
5241
  }
5263
- const { value, height } = resolveItem(items[nextIdx], nextIdx);
5264
- if (bottomY >= viewport.outerContentBottom) break;
5265
- drawList.push({
5266
- idx: nextIdx,
5267
- value,
5268
- offset: bottomY - window.shift,
5269
- height
5270
- });
5271
- existingIndices.add(nextIdx);
5272
- bottomIdx = nextIdx;
5273
- bottomY += height;
5242
+ if (anchor >= index) return index - removedBeforeAnchor;
5274
5243
  }
5275
- return {
5276
- drawList,
5277
- shift: window.shift
5278
- };
5244
+ return anchor - removedBeforeAnchor;
5279
5245
  }
5246
+ var TransitionController = class {
5247
+ #store = new TransitionStore();
5248
+ #snapshot = new VisibilitySnapshot();
5249
+ captureVisibilitySnapshot(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange) {
5250
+ this.#snapshot.capture(window, resolutionPath, items, viewport, snapshotState, readVisibleRange, readOuterVisibleRange);
5251
+ }
5252
+ pruneInvisible(ctx, lifecycle) {
5253
+ return this.pruneInvisibleAt(getNow(), ctx, lifecycle);
5254
+ }
5255
+ prepare(now, lifecycle) {
5256
+ this.settle(now, lifecycle);
5257
+ return this.#store.prepare(now);
5258
+ }
5259
+ canAutoFollowBoundaryInsert(direction, count, position, offset) {
5260
+ return this.#snapshot.matchesFollowBoundaryInsertState(direction, count, position, offset);
5261
+ }
5262
+ getItemHeight(item, now, adapter) {
5263
+ return getTransitionedItemHeight(item, now, this.#store, adapter);
5264
+ }
5265
+ resolveItem(item, now, adapter, lifecycle) {
5266
+ return resolveTransitionedItem(item, now, this.#store, adapter, lifecycle);
5267
+ }
5268
+ handleListStateChange(change, ctx, lifecycle, now = getNow()) {
5269
+ this.settle(now, lifecycle);
5270
+ handleTransitionStateChange(this.#store, this.#snapshot, change, ctx, lifecycle, now);
5271
+ }
5272
+ settle(now, lifecycle) {
5273
+ return this.#settleTransitions(this.#store.findCompleted(now), now, lifecycle);
5274
+ }
5275
+ pruneInvisibleAt(now, ctx, lifecycle) {
5276
+ const removals = this.#store.findInvisible(this.#snapshot);
5277
+ return this.#settleTransitions(removals, now, lifecycle, this.#resolveNaturalBoundarySnap(removals, now, ctx, lifecycle));
5278
+ }
5279
+ reset() {
5280
+ this.#store.reset();
5281
+ this.#snapshot.reset();
5282
+ }
5283
+ #settleTransitions(removals, now, lifecycle, boundarySnap) {
5284
+ if (removals.length === 0) return false;
5285
+ const anchor = lifecycle.captureVisualAnchor(now);
5286
+ const beforeState = lifecycle.readScrollState();
5287
+ const completedDeleteIndices = [];
5288
+ for (const { item, transition } of removals) {
5289
+ if (transition.kind === "delete") {
5290
+ const index = lifecycle.readItemIndex(item);
5291
+ if (index >= 0) completedDeleteIndices.push(index);
5292
+ }
5293
+ const removedTransition = this.#store.delete(item);
5294
+ if (removedTransition?.observedAutoFollowBoundary != null) {
5295
+ lifecycle.endAutoFollowBoundaryObservation(removedTransition.observedAutoFollowBoundary);
5296
+ lifecycle.invalidateAutoFollowBoundary(removedTransition.observedAutoFollowBoundary);
5297
+ }
5298
+ if (transition.kind === "delete") lifecycle.onDeleteComplete(item);
5299
+ }
5300
+ if (anchor != null && Number.isFinite(anchor)) lifecycle.restoreVisualAnchor(remapAnchorAfterDeletes(anchor, completedDeleteIndices));
5301
+ if (boundarySnap != null) lifecycle.snapItemToViewportBoundary(boundarySnap.item, boundarySnap.boundary);
5302
+ const afterState = lifecycle.readScrollState();
5303
+ if (!sameState(beforeState, afterState.position, afterState.offset)) lifecycle.onTransitionSettleScrollAdjusted();
5304
+ return true;
5305
+ }
5306
+ #resolveNaturalBoundarySnap(removals, now, ctx, lifecycle) {
5307
+ const previousState = this.#snapshot.previousState;
5308
+ const drawnRange = this.#snapshot.readDrawnIndexRange();
5309
+ if (previousState == null || drawnRange == null) return;
5310
+ const naturalIndices = [];
5311
+ for (const { item, transition } of removals) {
5312
+ if (transition.kind !== "update" && transition.kind !== "delete") continue;
5313
+ const index = lifecycle.readItemIndex(item);
5314
+ if (index < 0 || !this.#snapshot.wasVisible(item)) return;
5315
+ if (this.#isTransitionVisibleInState(index, previousState, now, ctx)) return;
5316
+ naturalIndices.push(index);
5317
+ }
5318
+ if (naturalIndices.length === 0) return;
5319
+ if (naturalIndices.every((index) => index < drawnRange.minIndex)) {
5320
+ const item = this.#snapshot.readBoundaryItem("top");
5321
+ return item == null ? void 0 : {
5322
+ item,
5323
+ boundary: "top"
5324
+ };
5325
+ }
5326
+ if (naturalIndices.every((index) => index > drawnRange.maxIndex)) {
5327
+ const item = this.#snapshot.readBoundaryItem("bottom");
5328
+ return item == null ? void 0 : {
5329
+ item,
5330
+ boundary: "bottom"
5331
+ };
5332
+ }
5333
+ }
5334
+ #isTransitionVisibleInState(index, state, now, ctx) {
5335
+ const solution = ctx.resolveVisibleWindowForState(state, now);
5336
+ for (const entry of solution.window.drawList) {
5337
+ if (entry.index !== index) continue;
5338
+ return ctx.readOuterVisibleRange(entry.offset + solution.window.shift, entry.height) != null;
5339
+ }
5340
+ return false;
5341
+ }
5342
+ };
5280
5343
  //#endregion
5281
5344
  //#region src/renderer/virtualized/base.ts
5282
5345
  /**
@@ -5288,6 +5351,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5288
5351
  static JUMP_DURATION_PER_PIXEL = .7;
5289
5352
  #jumpController;
5290
5353
  #transitionController = new TransitionController();
5354
+ #listStateOverride;
5291
5355
  constructor(graphics, options) {
5292
5356
  super(graphics, options);
5293
5357
  this.#jumpController = new JumpController({
@@ -5305,29 +5369,18 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5305
5369
  clampItemIndex: this._clampItemIndex.bind(this),
5306
5370
  getItemHeight: this._getItemHeight.bind(this)
5307
5371
  });
5308
- subscribeListState(options.list, this, (owner, change) => {
5309
- owner.#handleListStateChange(change);
5310
- });
5311
5372
  }
5312
5373
  /** Current anchor item index. */
5313
5374
  get position() {
5314
- return this.options.list.position;
5315
- }
5316
- /** Updates the current anchor item index. */
5317
- set position(value) {
5318
- this.options.list.position = value;
5375
+ return this.#listStateOverride?.position ?? this.options.list.position;
5319
5376
  }
5320
5377
  /** Pixel offset from the anchored item edge. */
5321
5378
  get offset() {
5322
- return this.options.list.offset;
5323
- }
5324
- /** Updates the pixel offset from the anchored item edge. */
5325
- set offset(value) {
5326
- this.options.list.offset = value;
5379
+ return this.#listStateOverride?.offset ?? this.options.list.offset;
5327
5380
  }
5328
5381
  /** Items currently available to the renderer. */
5329
5382
  get items() {
5330
- return this.options.list.items;
5383
+ return this.#listStateOverride?.items ?? this.options.list.items;
5331
5384
  }
5332
5385
  /** Replaces the current item collection. */
5333
5386
  set items(value) {
@@ -5335,6 +5388,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5335
5388
  }
5336
5389
  /** Renders the current visible window. */
5337
5390
  render(feedback) {
5391
+ this.#drainPendingListStateChanges();
5338
5392
  this.#jumpController.beforeFrame();
5339
5393
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5340
5394
  const now = getNow();
@@ -5358,6 +5412,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5358
5412
  }
5359
5413
  /** Hit-tests the current visible window. */
5360
5414
  hittest(test) {
5415
+ this.#drainPendingListStateChanges();
5361
5416
  this.#jumpController.beforeFrame();
5362
5417
  this.#jumpController.noteViewportWidth(this.graphics.canvas.clientWidth);
5363
5418
  const now = getNow();
@@ -5424,9 +5479,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5424
5479
  _renderDrawList(list, shift, feedback) {
5425
5480
  let result = false;
5426
5481
  const viewport = this._getViewportMetrics();
5427
- for (const { idx, value: item, offset, height } of list) {
5482
+ for (const { index, value: item, offset, height } of list) {
5428
5483
  const y = offset + shift + viewport.contentTop;
5429
- if (feedback != null) this._accumulateRenderFeedback(feedback, idx, y, height);
5484
+ if (feedback != null) this._accumulateRenderFeedback(feedback, index, y, height);
5430
5485
  if (y + height < 0 || y > viewport.outerHeight) continue;
5431
5486
  if (item.draw(y)) result = true;
5432
5487
  }
@@ -5446,9 +5501,9 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5446
5501
  let topMostY = Number.POSITIVE_INFINITY;
5447
5502
  let bottomMostY = Number.NEGATIVE_INFINITY;
5448
5503
  const viewport = this._getViewportMetrics();
5449
- for (const { idx, offset, height } of window.drawList) {
5450
- minIndex = Math.min(minIndex, idx);
5451
- maxIndex = Math.max(maxIndex, idx);
5504
+ for (const { index, offset, height } of window.drawList) {
5505
+ minIndex = Math.min(minIndex, index);
5506
+ maxIndex = Math.max(maxIndex, index);
5452
5507
  const y = offset + window.shift + viewport.contentTop;
5453
5508
  topMostY = Math.min(topMostY, y);
5454
5509
  bottomMostY = Math.max(bottomMostY, y + height);
@@ -5539,7 +5594,8 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5539
5594
  return resolveListViewport(this.graphics.canvas.clientHeight, this._getLayoutOptions().padding);
5540
5595
  }
5541
5596
  #handleDeleteComplete(item) {
5542
- this.options.list.finalizeDelete(item);
5597
+ finalizeInternalListDelete(this.options.list, item);
5598
+ this.#drainPendingListStateChanges();
5543
5599
  }
5544
5600
  #getTransitionLifecycleAdapter() {
5545
5601
  return {
@@ -5549,7 +5605,10 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5549
5605
  readScrollState: this._readListState.bind(this),
5550
5606
  readItemIndex: (item) => this.items.indexOf(item),
5551
5607
  snapItemToViewportBoundary: this.#snapItemToViewportBoundary.bind(this),
5552
- 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)
5553
5612
  };
5554
5613
  }
5555
5614
  #getVirtualizedRuntime() {
@@ -5583,9 +5642,25 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
5583
5642
  anchorMode: this._getLayoutOptions().anchorMode
5584
5643
  };
5585
5644
  }
5586
- #handleListStateChange(change) {
5587
- const nextChange = this.#jumpController.handleListStateChange(change);
5588
- 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
+ }
5589
5664
  }
5590
5665
  };
5591
5666
  //#endregion