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