react-native-laminar 1.4.1 → 1.4.3
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 +66 -0
- package/lib/commonjs/hooks/use-inline-auto-width.js +105 -18
- package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/commonjs/index.js +5 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/view/morph-viewport.js +2 -0
- package/lib/commonjs/view/morph-viewport.js.map +1 -1
- package/lib/commonjs/view/text-run.js +30 -12
- package/lib/commonjs/view/text-run.js.map +1 -1
- package/lib/module/hooks/use-inline-auto-width.js +106 -19
- package/lib/module/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/view/morph-viewport.js +2 -0
- package/lib/module/view/morph-viewport.js.map +1 -1
- package/lib/module/view/text-run.js +30 -12
- package/lib/module/view/text-run.js.map +1 -1
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts +2 -0
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/morph-viewport.d.ts +3 -2
- package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/text-run.d.ts +2 -1
- package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts +2 -0
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/view/morph-viewport.d.ts +3 -2
- package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/module/view/text-run.d.ts +2 -1
- package/lib/typescript/module/view/text-run.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hooks/use-inline-auto-width.ts +160 -18
- package/src/index.tsx +10 -1
- package/src/view/morph-viewport.tsx +7 -2
- package/src/view/text-run.tsx +49 -15
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import type { LayoutChangeEvent } from "react-native";
|
|
3
9
|
import { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
|
4
10
|
|
|
@@ -8,6 +14,11 @@ type Params = {
|
|
|
8
14
|
measurementKey: string;
|
|
9
15
|
};
|
|
10
16
|
|
|
17
|
+
type PendingProbe = {
|
|
18
|
+
key: string;
|
|
19
|
+
width: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
11
22
|
const WIDTH_CACHE_LIMIT = 64;
|
|
12
23
|
|
|
13
24
|
// measure each content signature once and drive later changes through one width value
|
|
@@ -17,10 +28,24 @@ export const useInlineAutoWidth = ({
|
|
|
17
28
|
measurementKey,
|
|
18
29
|
}: Params) => {
|
|
19
30
|
const widthValue = useSharedValue(0);
|
|
31
|
+
const hasBootstrappedWidth = useSharedValue(false);
|
|
20
32
|
const measuredWidthRef = useRef<number | null>(null);
|
|
21
33
|
const bootstrappedRef = useRef(false);
|
|
22
34
|
const widthCacheRef = useRef(new Map<string, number>());
|
|
23
|
-
const
|
|
35
|
+
const hasCapturedVisibleLayoutRef = useRef(false);
|
|
36
|
+
const initialMeasurementKeyRef = useRef<string | null>(null);
|
|
37
|
+
const firstProbeRef = useRef<PendingProbe | null>(null);
|
|
38
|
+
const latestProbeRef = useRef<PendingProbe | null>(null);
|
|
39
|
+
const bootstrapTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
40
|
+
const [readyMeasurementKey, setReadyMeasurementKey] = useState<string | null>(
|
|
41
|
+
null
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const markMeasurementReady = useCallback((key: string) => {
|
|
45
|
+
setReadyMeasurementKey((currentKey) =>
|
|
46
|
+
currentKey === key ? currentKey : key
|
|
47
|
+
);
|
|
48
|
+
}, []);
|
|
24
49
|
|
|
25
50
|
// the first measurement establishes the shell without animating from zero
|
|
26
51
|
const applyWidth = useCallback(
|
|
@@ -34,7 +59,7 @@ export const useInlineAutoWidth = ({
|
|
|
34
59
|
if (!bootstrappedRef.current) {
|
|
35
60
|
bootstrappedRef.current = true;
|
|
36
61
|
widthValue.value = nextWidth;
|
|
37
|
-
|
|
62
|
+
hasBootstrappedWidth.value = true;
|
|
38
63
|
return;
|
|
39
64
|
}
|
|
40
65
|
|
|
@@ -43,6 +68,68 @@ export const useInlineAutoWidth = ({
|
|
|
43
68
|
[driveToWidth, widthValue]
|
|
44
69
|
);
|
|
45
70
|
|
|
71
|
+
const bootstrapFromProbe = useCallback(() => {
|
|
72
|
+
bootstrapTimerRef.current = null;
|
|
73
|
+
|
|
74
|
+
if (hasCapturedVisibleLayoutRef.current) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const firstProbe = firstProbeRef.current;
|
|
79
|
+
if (!firstProbe) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The visible layout did not arrive in time, so use the first probe as a
|
|
84
|
+
// safe fallback. A later probe can still animate from this baseline.
|
|
85
|
+
hasCapturedVisibleLayoutRef.current = true;
|
|
86
|
+
initialMeasurementKeyRef.current = firstProbe.key;
|
|
87
|
+
applyWidth(firstProbe.width);
|
|
88
|
+
|
|
89
|
+
const latestProbe = latestProbeRef.current;
|
|
90
|
+
if (latestProbe && latestProbe.key !== firstProbe.key) {
|
|
91
|
+
// The target width is already cached. Defer its animation until the
|
|
92
|
+
// ready render so the shell and the new glyph row start together.
|
|
93
|
+
markMeasurementReady(latestProbe.key);
|
|
94
|
+
} else {
|
|
95
|
+
markMeasurementReady(firstProbe.key);
|
|
96
|
+
}
|
|
97
|
+
}, [applyWidth, markMeasurementReady]);
|
|
98
|
+
|
|
99
|
+
const captureVisibleLayout = useCallback(
|
|
100
|
+
(event: LayoutChangeEvent) => {
|
|
101
|
+
if (!enabled || hasCapturedVisibleLayoutRef.current) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (bootstrapTimerRef.current !== null) {
|
|
106
|
+
clearTimeout(bootstrapTimerRef.current);
|
|
107
|
+
bootstrapTimerRef.current = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
hasCapturedVisibleLayoutRef.current = true;
|
|
111
|
+
// bootstrap from the visible tree so the first explicit width matches
|
|
112
|
+
// the geometry that was already painted to the user
|
|
113
|
+
const nextWidth = Math.max(0, event.nativeEvent.layout.width);
|
|
114
|
+
// retain the visible width for the initial key so a later cache hit does
|
|
115
|
+
// not reintroduce the probe-to-visible mismatch
|
|
116
|
+
widthCacheRef.current.delete(measurementKey);
|
|
117
|
+
widthCacheRef.current.set(measurementKey, nextWidth);
|
|
118
|
+
const firstProbe = firstProbeRef.current;
|
|
119
|
+
if (firstProbe && firstProbe.key !== measurementKey) {
|
|
120
|
+
initialMeasurementKeyRef.current = firstProbe.key;
|
|
121
|
+
applyWidth(firstProbe.width);
|
|
122
|
+
markMeasurementReady(measurementKey);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
initialMeasurementKeyRef.current = measurementKey;
|
|
127
|
+
applyWidth(nextWidth);
|
|
128
|
+
markMeasurementReady(measurementKey);
|
|
129
|
+
},
|
|
130
|
+
[applyWidth, enabled, markMeasurementReady, measurementKey]
|
|
131
|
+
);
|
|
132
|
+
|
|
46
133
|
const captureLayout = useCallback(
|
|
47
134
|
(event: LayoutChangeEvent) => {
|
|
48
135
|
if (!enabled) {
|
|
@@ -52,49 +139,104 @@ export const useInlineAutoWidth = ({
|
|
|
52
139
|
// round up so fractional native measurements cannot clip the final glyph
|
|
53
140
|
const nextWidth = Math.max(0, Math.ceil(event.nativeEvent.layout.width));
|
|
54
141
|
|
|
55
|
-
|
|
56
|
-
|
|
142
|
+
const isInitialMeasurement =
|
|
143
|
+
initialMeasurementKeyRef.current === measurementKey;
|
|
144
|
+
|
|
145
|
+
if (!isInitialMeasurement) {
|
|
146
|
+
// delete before setting so a repeated key moves to the most recent position
|
|
147
|
+
widthCacheRef.current.delete(measurementKey);
|
|
57
148
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
149
|
+
if (widthCacheRef.current.size >= WIDTH_CACHE_LIMIT) {
|
|
150
|
+
// map insertion order gives the lru entry at the front
|
|
151
|
+
const oldestKey = widthCacheRef.current.keys().next().value;
|
|
61
152
|
|
|
62
|
-
|
|
63
|
-
|
|
153
|
+
if (oldestKey !== undefined) {
|
|
154
|
+
widthCacheRef.current.delete(oldestKey);
|
|
155
|
+
}
|
|
64
156
|
}
|
|
157
|
+
|
|
158
|
+
widthCacheRef.current.set(measurementKey, nextWidth);
|
|
65
159
|
}
|
|
66
160
|
|
|
67
|
-
|
|
68
|
-
|
|
161
|
+
const probe = { key: measurementKey, width: nextWidth };
|
|
162
|
+
if (!firstProbeRef.current) {
|
|
163
|
+
firstProbeRef.current = probe;
|
|
164
|
+
}
|
|
165
|
+
latestProbeRef.current = probe;
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
!hasCapturedVisibleLayoutRef.current &&
|
|
169
|
+
bootstrapTimerRef.current === null
|
|
170
|
+
) {
|
|
171
|
+
bootstrapTimerRef.current = setTimeout(bootstrapFromProbe, 0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Do not let the initial probe override the visible first frame.
|
|
175
|
+
if (hasCapturedVisibleLayoutRef.current && !isInitialMeasurement) {
|
|
176
|
+
// captureLayout only records the target. The cache effect applies the
|
|
177
|
+
// animation during the render that releases the new glyph row.
|
|
178
|
+
markMeasurementReady(measurementKey);
|
|
179
|
+
}
|
|
69
180
|
},
|
|
70
|
-
[
|
|
181
|
+
[
|
|
182
|
+
applyWidth,
|
|
183
|
+
bootstrapFromProbe,
|
|
184
|
+
enabled,
|
|
185
|
+
markMeasurementReady,
|
|
186
|
+
measurementKey,
|
|
187
|
+
]
|
|
71
188
|
);
|
|
72
189
|
|
|
73
190
|
const cachedWidth = widthCacheRef.current.get(measurementKey);
|
|
74
191
|
|
|
75
192
|
// a cached width can restore the shell before the hidden probe renders again
|
|
76
|
-
|
|
77
|
-
if (
|
|
193
|
+
useLayoutEffect(() => {
|
|
194
|
+
if (
|
|
195
|
+
enabled &&
|
|
196
|
+
cachedWidth !== undefined &&
|
|
197
|
+
hasCapturedVisibleLayoutRef.current
|
|
198
|
+
) {
|
|
78
199
|
// promote cache hits so active states stay available longer
|
|
79
200
|
widthCacheRef.current.delete(measurementKey);
|
|
80
201
|
widthCacheRef.current.set(measurementKey, cachedWidth);
|
|
81
202
|
applyWidth(cachedWidth);
|
|
203
|
+
markMeasurementReady(measurementKey);
|
|
82
204
|
}
|
|
83
|
-
}, [
|
|
205
|
+
}, [
|
|
206
|
+
applyWidth,
|
|
207
|
+
cachedWidth,
|
|
208
|
+
enabled,
|
|
209
|
+
markMeasurementReady,
|
|
210
|
+
measurementKey,
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
useEffect(
|
|
214
|
+
() => () => {
|
|
215
|
+
if (bootstrapTimerRef.current !== null) {
|
|
216
|
+
clearTimeout(bootstrapTimerRef.current);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
[]
|
|
220
|
+
);
|
|
84
221
|
|
|
85
222
|
const animatedWidthStyle = useAnimatedStyle(
|
|
86
223
|
() =>
|
|
87
|
-
enabled && hasBootstrappedWidth
|
|
224
|
+
enabled && hasBootstrappedWidth.value
|
|
88
225
|
? {
|
|
89
226
|
width: widthValue.value,
|
|
90
227
|
}
|
|
91
228
|
: {},
|
|
92
|
-
[enabled
|
|
229
|
+
[enabled]
|
|
93
230
|
);
|
|
94
231
|
|
|
95
232
|
return {
|
|
96
233
|
captureLayout,
|
|
234
|
+
captureVisibleLayout,
|
|
97
235
|
animatedWidthStyle,
|
|
236
|
+
isReady:
|
|
237
|
+
!enabled ||
|
|
238
|
+
cachedWidth !== undefined ||
|
|
239
|
+
readyMeasurementKey === measurementKey,
|
|
98
240
|
shouldMeasure: enabled && cachedWidth === undefined,
|
|
99
241
|
};
|
|
100
242
|
};
|
package/src/index.tsx
CHANGED
|
@@ -79,7 +79,13 @@ export const Laminar = React.memo(function Laminar({
|
|
|
79
79
|
textStyle,
|
|
80
80
|
variant,
|
|
81
81
|
]);
|
|
82
|
-
const {
|
|
82
|
+
const {
|
|
83
|
+
captureLayout,
|
|
84
|
+
captureVisibleLayout,
|
|
85
|
+
animatedWidthStyle,
|
|
86
|
+
isReady: isAutoSizeReady,
|
|
87
|
+
shouldMeasure,
|
|
88
|
+
} =
|
|
83
89
|
useInlineAutoWidth({
|
|
84
90
|
enabled: autoSize,
|
|
85
91
|
driveToWidth: motionRecipe.driveNumber,
|
|
@@ -103,9 +109,11 @@ export const Laminar = React.memo(function Laminar({
|
|
|
103
109
|
align={align}
|
|
104
110
|
containerStyle={containerStyle}
|
|
105
111
|
animatedWidthStyle={animatedWidthStyle}
|
|
112
|
+
onVisibleLayout={autoSize ? captureVisibleLayout : undefined}
|
|
106
113
|
measurement={
|
|
107
114
|
shouldMeasure ? (
|
|
108
115
|
<View
|
|
116
|
+
key={measurementKey}
|
|
109
117
|
onLayout={captureLayout}
|
|
110
118
|
style={{ flexDirection: "row", alignSelf: "flex-start" }}
|
|
111
119
|
>
|
|
@@ -157,6 +165,7 @@ export const Laminar = React.memo(function Laminar({
|
|
|
157
165
|
leading={resolvedLeading}
|
|
158
166
|
leadingKey={resolvedLeadingKey}
|
|
159
167
|
leadingGap={leadingGap}
|
|
168
|
+
ready={!autoSize || isAutoSizeReady}
|
|
160
169
|
textStyle={textStyle}
|
|
161
170
|
className={className}
|
|
162
171
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import type { StyleProp, ViewStyle } from "react-native";
|
|
2
|
+
import type { LayoutChangeEvent, StyleProp, ViewStyle } from "react-native";
|
|
3
3
|
import { View } from "react-native";
|
|
4
4
|
import Animated from "react-native-reanimated";
|
|
5
5
|
import type { LaminarAlign } from "../types";
|
|
@@ -52,6 +52,7 @@ type MorphViewportProps = {
|
|
|
52
52
|
readonly align: LaminarAlign;
|
|
53
53
|
readonly containerStyle?: StyleProp<ViewStyle>;
|
|
54
54
|
readonly animatedWidthStyle?: React.ComponentProps<typeof Animated.View>["style"];
|
|
55
|
+
readonly onVisibleLayout?: (event: LayoutChangeEvent) => void;
|
|
55
56
|
readonly measurement?: React.ReactNode;
|
|
56
57
|
readonly children: React.ReactNode;
|
|
57
58
|
};
|
|
@@ -64,6 +65,7 @@ export const MorphViewport = React.memo(
|
|
|
64
65
|
align,
|
|
65
66
|
containerStyle,
|
|
66
67
|
animatedWidthStyle,
|
|
68
|
+
onVisibleLayout,
|
|
67
69
|
measurement,
|
|
68
70
|
children,
|
|
69
71
|
}: MorphViewportProps) => {
|
|
@@ -91,7 +93,10 @@ export const MorphViewport = React.memo(
|
|
|
91
93
|
>
|
|
92
94
|
{measurement}
|
|
93
95
|
</View>
|
|
94
|
-
<Animated.View
|
|
96
|
+
<Animated.View
|
|
97
|
+
onLayout={onVisibleLayout}
|
|
98
|
+
style={[resolvedViewportStyle, animatedWidthStyle]}
|
|
99
|
+
>
|
|
95
100
|
{children}
|
|
96
101
|
</Animated.View>
|
|
97
102
|
</>
|
package/src/view/text-run.tsx
CHANGED
|
@@ -17,6 +17,7 @@ type TextRunProps = {
|
|
|
17
17
|
readonly leading?: ReactNode;
|
|
18
18
|
readonly leadingKey?: string | number;
|
|
19
19
|
readonly leadingGap?: number;
|
|
20
|
+
readonly ready?: boolean;
|
|
20
21
|
readonly textStyle?: StyleProp<TextStyle>;
|
|
21
22
|
readonly className?: string;
|
|
22
23
|
};
|
|
@@ -30,25 +31,58 @@ export const TextRun = React.memo(
|
|
|
30
31
|
leading,
|
|
31
32
|
leadingKey,
|
|
32
33
|
leadingGap = 0,
|
|
34
|
+
ready = true,
|
|
33
35
|
textStyle,
|
|
34
36
|
className,
|
|
35
37
|
}: TextRunProps) => {
|
|
36
38
|
// namespace ids per instance so repeated strings do not collide
|
|
37
39
|
const scopeId = useId();
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
40
|
+
const hasDisplayedRef = useRef(false);
|
|
41
|
+
const displayedValueRef = useRef(value);
|
|
42
|
+
const displayedLeadingRef = useRef(leading);
|
|
43
|
+
const displayedLeadingKeyRef = useRef(leadingKey);
|
|
44
|
+
const displayedLeadingGapRef = useRef(leadingGap);
|
|
45
|
+
const shouldDisplayTarget = ready || !hasDisplayedRef.current;
|
|
46
|
+
const visibleValue = shouldDisplayTarget
|
|
47
|
+
? value
|
|
48
|
+
: displayedValueRef.current;
|
|
49
|
+
const visibleLeading = shouldDisplayTarget
|
|
50
|
+
? leading
|
|
51
|
+
: displayedLeadingRef.current;
|
|
52
|
+
const visibleLeadingKey = shouldDisplayTarget
|
|
53
|
+
? leadingKey
|
|
54
|
+
: displayedLeadingKeyRef.current;
|
|
55
|
+
const visibleLeadingGap = shouldDisplayTarget
|
|
56
|
+
? leadingGap
|
|
57
|
+
: displayedLeadingGapRef.current;
|
|
58
|
+
|
|
59
|
+
if (shouldDisplayTarget) {
|
|
60
|
+
hasDisplayedRef.current = true;
|
|
61
|
+
displayedValueRef.current = value;
|
|
62
|
+
displayedLeadingRef.current = leading;
|
|
63
|
+
displayedLeadingKeyRef.current = leadingKey;
|
|
64
|
+
displayedLeadingGapRef.current = leadingGap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lastValueRef = useRef(visibleValue);
|
|
68
|
+
const lastLeadingPresenceRef = useRef(Boolean(visibleLeading));
|
|
69
|
+
const lastLeadingKeyRef = useRef(visibleLeadingKey);
|
|
41
70
|
const hasAnimatedRef = useRef(false);
|
|
42
71
|
const isLeadingSwap =
|
|
43
|
-
Boolean(
|
|
72
|
+
Boolean(visibleLeading) &&
|
|
44
73
|
lastLeadingPresenceRef.current &&
|
|
45
|
-
|
|
74
|
+
visibleLeadingKey !== lastLeadingKeyRef.current;
|
|
46
75
|
// update the worklet flag after commit so render never writes a shared value
|
|
47
76
|
const leadingSwapProgress = useSharedValue(0);
|
|
48
77
|
useLayoutEffect(() => {
|
|
49
78
|
leadingSwapProgress.value = isLeadingSwap ? 1 : 0;
|
|
50
79
|
}, [isLeadingSwap, leadingSwapProgress]);
|
|
51
|
-
const glyphs = useTextGlyphs(
|
|
80
|
+
const glyphs = useTextGlyphs(
|
|
81
|
+
visibleValue,
|
|
82
|
+
scopeId,
|
|
83
|
+
visibleLeading,
|
|
84
|
+
visibleLeadingKey
|
|
85
|
+
);
|
|
52
86
|
const elementEnterTransition = useMemo(
|
|
53
87
|
() =>
|
|
54
88
|
createLeadingEnterTransition({
|
|
@@ -62,11 +96,11 @@ export const TextRun = React.memo(
|
|
|
62
96
|
createLeadingExitTransition({
|
|
63
97
|
durationMs: motionRecipe.durationMs,
|
|
64
98
|
easing: motionRecipe.easing,
|
|
65
|
-
leadingGap,
|
|
99
|
+
leadingGap: visibleLeadingGap,
|
|
66
100
|
scale: leadingSwapProgress,
|
|
67
101
|
}),
|
|
68
102
|
[
|
|
69
|
-
|
|
103
|
+
visibleLeadingGap,
|
|
70
104
|
leadingSwapProgress,
|
|
71
105
|
motionRecipe.durationMs,
|
|
72
106
|
motionRecipe.easing,
|
|
@@ -75,14 +109,14 @@ export const TextRun = React.memo(
|
|
|
75
109
|
|
|
76
110
|
// mark the first value as settled and later changes as eligible for motion
|
|
77
111
|
if (
|
|
78
|
-
|
|
79
|
-
Boolean(
|
|
80
|
-
|
|
112
|
+
visibleValue !== lastValueRef.current ||
|
|
113
|
+
Boolean(visibleLeading) !== lastLeadingPresenceRef.current ||
|
|
114
|
+
visibleLeadingKey !== lastLeadingKeyRef.current
|
|
81
115
|
) {
|
|
82
116
|
hasAnimatedRef.current = true;
|
|
83
|
-
lastValueRef.current =
|
|
84
|
-
lastLeadingPresenceRef.current = Boolean(
|
|
85
|
-
lastLeadingKeyRef.current =
|
|
117
|
+
lastValueRef.current = visibleValue;
|
|
118
|
+
lastLeadingPresenceRef.current = Boolean(visibleLeading);
|
|
119
|
+
lastLeadingKeyRef.current = visibleLeadingKey;
|
|
86
120
|
}
|
|
87
121
|
|
|
88
122
|
const hasAnimated = hasAnimatedRef.current;
|
|
@@ -103,7 +137,7 @@ export const TextRun = React.memo(
|
|
|
103
137
|
}
|
|
104
138
|
exitTransition={motionRecipe.exitTransition}
|
|
105
139
|
elementExitTransition={elementExitTransition}
|
|
106
|
-
leadingGap={
|
|
140
|
+
leadingGap={visibleLeadingGap}
|
|
107
141
|
align={align}
|
|
108
142
|
textStyle={textStyle}
|
|
109
143
|
className={className}
|