react-native-laminar 1.2.0 → 1.4.1
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 +9 -2
- package/lib/commonjs/hooks/use-display-units.js +1 -0
- package/lib/commonjs/hooks/use-display-units.js.map +1 -1
- package/lib/commonjs/hooks/use-inline-auto-width.js +43 -10
- package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/commonjs/hooks/use-morph-motion.js +2 -1
- package/lib/commonjs/hooks/use-morph-motion.js.map +1 -1
- package/lib/commonjs/hooks/use-morph-text-style.js +4 -0
- package/lib/commonjs/hooks/use-morph-text-style.js.map +1 -1
- package/lib/commonjs/hooks/use-numeric-lanes.js +1 -0
- package/lib/commonjs/hooks/use-numeric-lanes.js.map +1 -1
- package/lib/commonjs/hooks/use-text-glyphs.js +28 -7
- package/lib/commonjs/hooks/use-text-glyphs.js.map +1 -1
- package/lib/commonjs/index.js +54 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/model/display-units.js +7 -1
- package/lib/commonjs/model/display-units.js.map +1 -1
- package/lib/commonjs/model/numeric-lanes.js +4 -0
- package/lib/commonjs/model/numeric-lanes.js.map +1 -1
- package/lib/commonjs/model/text-keys.js +4 -0
- package/lib/commonjs/model/text-keys.js.map +1 -1
- package/lib/commonjs/motion/entry-exit-builders.js +78 -1
- package/lib/commonjs/motion/entry-exit-builders.js.map +1 -1
- package/lib/commonjs/motion/preset-map.js +7 -0
- package/lib/commonjs/motion/preset-map.js.map +1 -1
- package/lib/commonjs/view/glyph-run.js +21 -2
- package/lib/commonjs/view/glyph-run.js.map +1 -1
- package/lib/commonjs/view/morph-viewport.js +4 -0
- package/lib/commonjs/view/morph-viewport.js.map +1 -1
- package/lib/commonjs/view/number-lane.js +5 -1
- package/lib/commonjs/view/number-lane.js.map +1 -1
- package/lib/commonjs/view/number-run.js +4 -0
- package/lib/commonjs/view/number-run.js.map +1 -1
- package/lib/commonjs/view/slots-run.js +258 -0
- package/lib/commonjs/view/slots-run.js.map +1 -0
- package/lib/commonjs/view/text-run.js +35 -3
- package/lib/commonjs/view/text-run.js.map +1 -1
- package/lib/module/hooks/use-display-units.js +2 -0
- package/lib/module/hooks/use-display-units.js.map +1 -1
- package/lib/module/hooks/use-inline-auto-width.js +44 -11
- package/lib/module/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/module/hooks/use-morph-motion.js +2 -1
- package/lib/module/hooks/use-morph-motion.js.map +1 -1
- package/lib/module/hooks/use-morph-text-style.js +4 -0
- package/lib/module/hooks/use-morph-text-style.js.map +1 -1
- package/lib/module/hooks/use-numeric-lanes.js +1 -0
- package/lib/module/hooks/use-numeric-lanes.js.map +1 -1
- package/lib/module/hooks/use-text-glyphs.js +28 -7
- package/lib/module/hooks/use-text-glyphs.js.map +1 -1
- package/lib/module/index.js +56 -12
- package/lib/module/index.js.map +1 -1
- package/lib/module/model/display-units.js +7 -1
- package/lib/module/model/display-units.js.map +1 -1
- package/lib/module/model/numeric-lanes.js +5 -0
- package/lib/module/model/numeric-lanes.js.map +1 -1
- package/lib/module/model/text-keys.js +4 -0
- package/lib/module/model/text-keys.js.map +1 -1
- package/lib/module/motion/entry-exit-builders.js +75 -0
- package/lib/module/motion/entry-exit-builders.js.map +1 -1
- package/lib/module/motion/preset-map.js +7 -0
- package/lib/module/motion/preset-map.js.map +1 -1
- package/lib/module/view/glyph-run.js +21 -2
- package/lib/module/view/glyph-run.js.map +1 -1
- package/lib/module/view/morph-viewport.js +4 -0
- package/lib/module/view/morph-viewport.js.map +1 -1
- package/lib/module/view/number-lane.js +5 -1
- package/lib/module/view/number-lane.js.map +1 -1
- package/lib/module/view/number-run.js +4 -0
- package/lib/module/view/number-run.js.map +1 -1
- package/lib/module/view/slots-run.js +253 -0
- package/lib/module/view/slots-run.js.map +1 -0
- package/lib/module/view/text-run.js +36 -4
- package/lib/module/view/text-run.js.map +1 -1
- package/lib/typescript/commonjs/hooks/use-display-units.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts +3 -1
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-morph-text-style.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts +2 -1
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/display-units.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/commonjs/model/text-keys.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts +13 -0
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +12 -2
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/glyph-run.d.ts +5 -2
- package/lib/typescript/commonjs/view/glyph-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/number-lane.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/number-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/slots-run.d.ts +15 -0
- package/lib/typescript/commonjs/view/slots-run.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/text-run.d.ts +5 -1
- package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-display-units.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts +3 -1
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-morph-text-style.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts +2 -1
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/model/display-units.d.ts.map +1 -1
- package/lib/typescript/module/model/numeric-lanes.d.ts.map +1 -1
- package/lib/typescript/module/model/text-keys.d.ts.map +1 -1
- package/lib/typescript/module/motion/entry-exit-builders.d.ts +13 -0
- package/lib/typescript/module/motion/entry-exit-builders.d.ts.map +1 -1
- package/lib/typescript/module/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +12 -2
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/view/glyph-run.d.ts +5 -2
- package/lib/typescript/module/view/glyph-run.d.ts.map +1 -1
- package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/module/view/number-lane.d.ts.map +1 -1
- package/lib/typescript/module/view/number-run.d.ts.map +1 -1
- package/lib/typescript/module/view/slots-run.d.ts +15 -0
- package/lib/typescript/module/view/slots-run.d.ts.map +1 -0
- package/lib/typescript/module/view/text-run.d.ts +5 -1
- package/lib/typescript/module/view/text-run.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/hooks/use-display-units.ts +1 -0
- package/src/hooks/use-inline-auto-width.ts +55 -12
- package/src/hooks/use-morph-motion.ts +2 -1
- package/src/hooks/use-morph-text-style.ts +3 -0
- package/src/hooks/use-numeric-lanes.ts +1 -0
- package/src/hooks/use-text-glyphs.ts +48 -10
- package/src/index.tsx +98 -17
- package/src/model/display-units.ts +4 -1
- package/src/model/numeric-lanes.ts +3 -0
- package/src/model/text-keys.ts +3 -0
- package/src/motion/entry-exit-builders.ts +73 -0
- package/src/motion/preset-map.ts +4 -0
- package/src/types.ts +14 -2
- package/src/view/glyph-run.tsx +38 -13
- package/src/view/morph-viewport.tsx +3 -0
- package/src/view/number-lane.tsx +7 -2
- package/src/view/number-run.tsx +3 -0
- package/src/view/slots-run.tsx +385 -0
- package/src/view/text-run.tsx +77 -6
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type EntryExitAnimationFunction,
|
|
3
|
+
type ExitAnimationsValues,
|
|
3
4
|
type WithTimingConfig,
|
|
4
5
|
withDelay,
|
|
5
6
|
withTiming,
|
|
6
7
|
} from "react-native-reanimated";
|
|
8
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
7
9
|
|
|
8
10
|
type TransitionParams = {
|
|
9
11
|
readonly delayMs?: number;
|
|
@@ -17,6 +19,40 @@ type TransitionParams = {
|
|
|
17
19
|
readonly toScale: number;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
type LeadingExitTransitionParams = {
|
|
23
|
+
readonly durationMs: number;
|
|
24
|
+
readonly easing: NonNullable<WithTimingConfig["easing"]>;
|
|
25
|
+
readonly leadingGap: number;
|
|
26
|
+
readonly scale?: boolean | SharedValue<number>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type LeadingEnterTransitionParams = {
|
|
30
|
+
readonly durationMs: number;
|
|
31
|
+
readonly easing: NonNullable<WithTimingConfig["easing"]>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// leading replacements use scale while first appearance uses the regular fade path
|
|
35
|
+
export const createLeadingEnterTransition = ({
|
|
36
|
+
durationMs,
|
|
37
|
+
easing,
|
|
38
|
+
}: LeadingEnterTransitionParams): EntryExitAnimationFunction => {
|
|
39
|
+
return () => {
|
|
40
|
+
"worklet";
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
initialValues: {
|
|
44
|
+
opacity: 0,
|
|
45
|
+
transform: [{ scale: 0.5 }],
|
|
46
|
+
},
|
|
47
|
+
animations: {
|
|
48
|
+
opacity: withTiming(1, { duration: durationMs, easing }),
|
|
49
|
+
transform: [{ scale: withTiming(1, { duration: durationMs, easing }) }],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// one builder covers glyph opacity, travel, scale, and optional stagger timing
|
|
20
56
|
export const createShiftTransition = ({
|
|
21
57
|
delayMs = 0,
|
|
22
58
|
durationMs,
|
|
@@ -72,3 +108,40 @@ export const createShiftTransition = ({
|
|
|
72
108
|
};
|
|
73
109
|
};
|
|
74
110
|
};
|
|
111
|
+
|
|
112
|
+
// the shared swap flag lets an exiting worklet read the current transition type
|
|
113
|
+
export const createLeadingExitTransition = ({
|
|
114
|
+
durationMs,
|
|
115
|
+
easing,
|
|
116
|
+
leadingGap,
|
|
117
|
+
scale = false,
|
|
118
|
+
}: LeadingExitTransitionParams): EntryExitAnimationFunction => {
|
|
119
|
+
return (values: ExitAnimationsValues) => {
|
|
120
|
+
"worklet";
|
|
121
|
+
|
|
122
|
+
const shouldScale =
|
|
123
|
+
typeof scale === "object" ? scale.value > 0.5 : scale;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
initialValues: {
|
|
127
|
+
opacity: 1,
|
|
128
|
+
originX: values.currentOriginX,
|
|
129
|
+
...(shouldScale ? { transform: [{ scale: 1 }] } : {}),
|
|
130
|
+
},
|
|
131
|
+
animations: {
|
|
132
|
+
opacity: withTiming(0, { duration: durationMs, easing }),
|
|
133
|
+
originX: withTiming(
|
|
134
|
+
values.currentOriginX - values.currentWidth - leadingGap,
|
|
135
|
+
{ duration: durationMs, easing }
|
|
136
|
+
),
|
|
137
|
+
...(shouldScale
|
|
138
|
+
? {
|
|
139
|
+
transform: [
|
|
140
|
+
{ scale: withTiming(0.5, { duration: durationMs, easing }) },
|
|
141
|
+
],
|
|
142
|
+
}
|
|
143
|
+
: {}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
package/src/motion/preset-map.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
MorphAnimationPresetName,
|
|
14
14
|
} from "../types";
|
|
15
15
|
|
|
16
|
+
// keep all presets on one curve family so layout and glyph motion agree
|
|
16
17
|
const DEFAULT_CURVE = [0.19, 1, 0.22, 1] as const;
|
|
17
18
|
|
|
18
19
|
export const MOTION_PRESETS: Record<
|
|
@@ -40,11 +41,13 @@ export const MOTION_PRESETS: Record<
|
|
|
40
41
|
},
|
|
41
42
|
} as const;
|
|
42
43
|
|
|
44
|
+
// the public preset table uses seconds while reanimated timing uses milliseconds
|
|
43
45
|
const toMilliseconds = (seconds: number) => Math.round(seconds * 1000);
|
|
44
46
|
|
|
45
47
|
const toDampingRatio = (bounce: number) =>
|
|
46
48
|
Math.max(0.55, Math.min(1, 1 - bounce));
|
|
47
49
|
|
|
50
|
+
// spring and timing presets share the same opacity contract
|
|
48
51
|
const createOpacityTransition = (
|
|
49
52
|
fromOpacity: number,
|
|
50
53
|
toOpacity: number,
|
|
@@ -68,6 +71,7 @@ const createOpacityTransition = (
|
|
|
68
71
|
};
|
|
69
72
|
};
|
|
70
73
|
|
|
74
|
+
// convert a named preset into the builders consumed by every rendering variant
|
|
71
75
|
export const resolveMotionRecipe = (
|
|
72
76
|
presetName: MorphAnimationPresetName = "default",
|
|
73
77
|
durationOverride?: number
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
3
|
import type {
|
|
3
4
|
ComplexAnimationBuilder,
|
|
@@ -11,8 +12,10 @@ export type MorphAnimationPresetName =
|
|
|
11
12
|
| "snappy"
|
|
12
13
|
| "bouncy";
|
|
13
14
|
|
|
14
|
-
export type MorphContentVariant = "text" | "number";
|
|
15
|
+
export type MorphContentVariant = "text" | "number" | "slots";
|
|
15
16
|
export type LaminarAlign = "left" | "center" | "right";
|
|
17
|
+
// text keys let leading content follow the same state as the displayed value
|
|
18
|
+
export type LaminarLeadingMap = Readonly<Record<string, ReactNode>>;
|
|
16
19
|
|
|
17
20
|
type CubicBezierTuple = readonly [number, number, number, number];
|
|
18
21
|
|
|
@@ -29,12 +32,15 @@ export type MorphAnimationPreset =
|
|
|
29
32
|
|
|
30
33
|
export type GlyphToken = {
|
|
31
34
|
readonly id: string;
|
|
32
|
-
readonly
|
|
35
|
+
readonly kind: "text" | "element";
|
|
36
|
+
readonly value?: string;
|
|
37
|
+
readonly element?: ReactNode;
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
export type NumericFlowDirection = -1 | 0 | 1;
|
|
36
41
|
|
|
37
42
|
export type MotionRecipe = {
|
|
43
|
+
// every variant consumes the same timing and layout contract
|
|
38
44
|
readonly durationMs: number;
|
|
39
45
|
readonly easing: NonNullable<WithTimingConfig["easing"]>;
|
|
40
46
|
readonly layoutTransition: ComplexAnimationBuilder;
|
|
@@ -50,6 +56,12 @@ export type MorphingTextProps = {
|
|
|
50
56
|
readonly color?: string;
|
|
51
57
|
readonly align?: LaminarAlign;
|
|
52
58
|
readonly className?: string;
|
|
59
|
+
/** optional inline element reconciled and animated as the leading token */
|
|
60
|
+
readonly leading?: ReactNode | LaminarLeadingMap;
|
|
61
|
+
/** identity for swapping one leading element for another */
|
|
62
|
+
readonly leadingKey?: string | number;
|
|
63
|
+
/** spacing between the leading element and the first text glyph */
|
|
64
|
+
readonly leadingGap?: number;
|
|
53
65
|
readonly style?: StyleProp<TextStyle>;
|
|
54
66
|
readonly containerStyle?: StyleProp<ViewStyle>;
|
|
55
67
|
readonly fontStyle?: StyleProp<TextStyle>;
|
package/src/view/glyph-run.tsx
CHANGED
|
@@ -19,38 +19,63 @@ const rowAlignStyles = {
|
|
|
19
19
|
right: { alignSelf: "flex-end" },
|
|
20
20
|
} as const;
|
|
21
21
|
|
|
22
|
+
// render text and inline elements through one animated token row
|
|
22
23
|
export const GlyphRun = React.memo(
|
|
23
24
|
({
|
|
24
25
|
glyphs,
|
|
25
26
|
layoutTransition,
|
|
26
27
|
enterTransition,
|
|
28
|
+
elementEnterTransition,
|
|
27
29
|
exitTransition,
|
|
30
|
+
elementExitTransition,
|
|
31
|
+
leadingGap,
|
|
28
32
|
align,
|
|
29
33
|
textStyle,
|
|
30
34
|
className,
|
|
31
35
|
}: Readonly<{
|
|
32
36
|
glyphs: readonly GlyphToken[];
|
|
33
|
-
layoutTransition
|
|
37
|
+
layoutTransition?: ComplexAnimationBuilder;
|
|
34
38
|
enterTransition?: EntryExitAnimationFunction;
|
|
39
|
+
elementEnterTransition?: EntryExitAnimationFunction;
|
|
35
40
|
exitTransition?: EntryExitAnimationFunction;
|
|
41
|
+
elementExitTransition?: EntryExitAnimationFunction;
|
|
42
|
+
leadingGap?: number;
|
|
36
43
|
align: LaminarAlign;
|
|
37
44
|
textStyle?: StyleProp<TextStyle>;
|
|
38
45
|
className?: string;
|
|
39
46
|
}>) => (
|
|
47
|
+
// each token keeps its own view so reanimated can animate only changed content
|
|
40
48
|
<View style={[rowStyle, rowAlignStyles[align]]}>
|
|
41
49
|
{/* glyph ids decide what swaps, layout handles the row reflow */}
|
|
42
|
-
{glyphs.map((glyph) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
{glyphs.map((glyph, glyphIndex) =>
|
|
51
|
+
glyph.kind === "element" ? (
|
|
52
|
+
<Animated.View
|
|
53
|
+
key={glyph.id}
|
|
54
|
+
layout={layoutTransition}
|
|
55
|
+
entering={elementEnterTransition ?? enterTransition}
|
|
56
|
+
exiting={elementExitTransition ?? exitTransition}
|
|
57
|
+
style={[
|
|
58
|
+
{ alignItems: "center", justifyContent: "center" },
|
|
59
|
+
glyphIndex === 0 && (leadingGap ?? 0) > 0
|
|
60
|
+
? { marginRight: leadingGap }
|
|
61
|
+
: undefined,
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
{glyph.element}
|
|
65
|
+
</Animated.View>
|
|
66
|
+
) : (
|
|
67
|
+
<Animated.Text
|
|
68
|
+
key={glyph.id}
|
|
69
|
+
layout={layoutTransition}
|
|
70
|
+
entering={enterTransition}
|
|
71
|
+
exiting={exitTransition}
|
|
72
|
+
style={textStyle}
|
|
73
|
+
className={className}
|
|
74
|
+
>
|
|
75
|
+
{glyph.value}
|
|
76
|
+
</Animated.Text>
|
|
77
|
+
)
|
|
78
|
+
)}
|
|
54
79
|
</View>
|
|
55
80
|
)
|
|
56
81
|
);
|
|
@@ -56,6 +56,7 @@ type MorphViewportProps = {
|
|
|
56
56
|
readonly children: React.ReactNode;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// separate hidden measurement from visible content while sharing the shell alignment
|
|
59
60
|
export const MorphViewport = React.memo(
|
|
60
61
|
({
|
|
61
62
|
autoSize,
|
|
@@ -66,6 +67,7 @@ export const MorphViewport = React.memo(
|
|
|
66
67
|
measurement,
|
|
67
68
|
children,
|
|
68
69
|
}: MorphViewportProps) => {
|
|
70
|
+
// compute alignment once so the animated shell and its child share one layout contract
|
|
69
71
|
const resolvedViewportStyle = useMemo(
|
|
70
72
|
() => [
|
|
71
73
|
viewportStyle,
|
|
@@ -75,6 +77,7 @@ export const MorphViewport = React.memo(
|
|
|
75
77
|
[align, clipToBounds]
|
|
76
78
|
);
|
|
77
79
|
|
|
80
|
+
// keep measurement outside the visible viewport so it cannot affect accessibility or layout
|
|
78
81
|
return (
|
|
79
82
|
<View style={[shellStyle, shellAlignStyles[align], containerStyle]}>
|
|
80
83
|
{autoSize ? (
|
package/src/view/number-lane.tsx
CHANGED
|
@@ -33,6 +33,7 @@ type NumberLaneProps = {
|
|
|
33
33
|
readonly className?: string;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
// render one place-value lane with a hidden layout probe and an animated token
|
|
36
37
|
export const NumberLane = React.memo(
|
|
37
38
|
({
|
|
38
39
|
unit,
|
|
@@ -46,6 +47,7 @@ export const NumberLane = React.memo(
|
|
|
46
47
|
textStyle,
|
|
47
48
|
className,
|
|
48
49
|
}: NumberLaneProps) => {
|
|
50
|
+
// punctuation stays in place while digits travel by the shared numeric direction
|
|
49
51
|
const usesDigitTravel = isAsciiDigit(unit);
|
|
50
52
|
const verticalOffset =
|
|
51
53
|
direction > 0
|
|
@@ -98,14 +100,17 @@ export const NumberLane = React.memo(
|
|
|
98
100
|
]
|
|
99
101
|
);
|
|
100
102
|
|
|
103
|
+
// prefix units do not need an absolute lane or a duplicate probe
|
|
101
104
|
if (isLead) {
|
|
102
105
|
return <Text style={textStyle} className={className}>{unit}</Text>;
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
return (
|
|
106
109
|
<Animated.View
|
|
107
|
-
layout={motionRecipe.layoutTransition}
|
|
108
|
-
entering={
|
|
110
|
+
layout={hasAnimated ? motionRecipe.layoutTransition : undefined}
|
|
111
|
+
entering={
|
|
112
|
+
hasAnimated ? motionRecipe.enterTransition : undefined
|
|
113
|
+
}
|
|
109
114
|
exiting={motionRecipe.exitTransition}
|
|
110
115
|
style={laneStyle}
|
|
111
116
|
>
|
package/src/view/number-run.tsx
CHANGED
|
@@ -26,6 +26,7 @@ type NumberRunProps = {
|
|
|
26
26
|
readonly className?: string;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// arrange numeric lanes from left to right while the hook preserves their identities
|
|
29
30
|
export const NumberRun = React.memo(
|
|
30
31
|
({
|
|
31
32
|
value,
|
|
@@ -36,6 +37,7 @@ export const NumberRun = React.memo(
|
|
|
36
37
|
staggerMs,
|
|
37
38
|
className,
|
|
38
39
|
}: Readonly<NumberRunProps>) => {
|
|
40
|
+
// the hook owns lane identity while this component owns row placement and timing
|
|
39
41
|
const { units, laneKeys, direction, leadLength } = useNumericLanes(value);
|
|
40
42
|
const lastValueRef = useRef(value);
|
|
41
43
|
const hasAnimatedRef = useRef(false);
|
|
@@ -46,6 +48,7 @@ export const NumberRun = React.memo(
|
|
|
46
48
|
lastValueRef.current = value;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// scale travel from the font while keeping a minimum visible movement
|
|
49
52
|
const travelDistance = useMemo(
|
|
50
53
|
() => Math.max(8, Math.round((fontSize ?? 16) * 0.4)),
|
|
51
54
|
[fontSize]
|