react-native-laminar 1.0.0
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 +11 -0
- package/lib/commonjs/hooks/use-display-units.js +13 -0
- package/lib/commonjs/hooks/use-display-units.js.map +1 -0
- package/lib/commonjs/hooks/use-inline-auto-width.js +45 -0
- package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -0
- package/lib/commonjs/hooks/use-morph-motion.js +24 -0
- package/lib/commonjs/hooks/use-morph-motion.js.map +1 -0
- package/lib/commonjs/hooks/use-morph-text-style.js +32 -0
- package/lib/commonjs/hooks/use-morph-text-style.js.map +1 -0
- package/lib/commonjs/hooks/use-numeric-lanes.js +37 -0
- package/lib/commonjs/hooks/use-numeric-lanes.js.map +1 -0
- package/lib/commonjs/hooks/use-text-glyphs.js +35 -0
- package/lib/commonjs/hooks/use-text-glyphs.js.map +1 -0
- package/lib/commonjs/index.js +89 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/model/display-units.js +29 -0
- package/lib/commonjs/model/display-units.js.map +1 -0
- package/lib/commonjs/model/numeric-lanes.js +45 -0
- package/lib/commonjs/model/numeric-lanes.js.map +1 -0
- package/lib/commonjs/model/text-keys.js +80 -0
- package/lib/commonjs/model/text-keys.js.map +1 -0
- package/lib/commonjs/motion/entry-exit-builders.js +57 -0
- package/lib/commonjs/motion/entry-exit-builders.js.map +1 -0
- package/lib/commonjs/motion/preset-map.js +91 -0
- package/lib/commonjs/motion/preset-map.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/view/glyph-run.js +34 -0
- package/lib/commonjs/view/glyph-run.js.map +1 -0
- package/lib/commonjs/view/morph-viewport.js +64 -0
- package/lib/commonjs/view/morph-viewport.js.map +1 -0
- package/lib/commonjs/view/number-lane.js +85 -0
- package/lib/commonjs/view/number-lane.js.map +1 -0
- package/lib/commonjs/view/number-run.js +61 -0
- package/lib/commonjs/view/number-run.js.map +1 -0
- package/lib/commonjs/view/text-run.js +35 -0
- package/lib/commonjs/view/text-run.js.map +1 -0
- package/lib/module/hooks/use-display-units.js +8 -0
- package/lib/module/hooks/use-display-units.js.map +1 -0
- package/lib/module/hooks/use-inline-auto-width.js +40 -0
- package/lib/module/hooks/use-inline-auto-width.js.map +1 -0
- package/lib/module/hooks/use-morph-motion.js +19 -0
- package/lib/module/hooks/use-morph-motion.js.map +1 -0
- package/lib/module/hooks/use-morph-text-style.js +27 -0
- package/lib/module/hooks/use-morph-text-style.js.map +1 -0
- package/lib/module/hooks/use-numeric-lanes.js +32 -0
- package/lib/module/hooks/use-numeric-lanes.js.map +1 -0
- package/lib/module/hooks/use-text-glyphs.js +30 -0
- package/lib/module/hooks/use-text-glyphs.js.map +1 -0
- package/lib/module/index.js +84 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/model/display-units.js +21 -0
- package/lib/module/model/display-units.js.map +1 -0
- package/lib/module/model/numeric-lanes.js +40 -0
- package/lib/module/model/numeric-lanes.js.map +1 -0
- package/lib/module/model/text-keys.js +75 -0
- package/lib/module/model/text-keys.js.map +1 -0
- package/lib/module/motion/entry-exit-builders.js +52 -0
- package/lib/module/motion/entry-exit-builders.js.map +1 -0
- package/lib/module/motion/preset-map.js +86 -0
- package/lib/module/motion/preset-map.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/view/glyph-run.js +29 -0
- package/lib/module/view/glyph-run.js.map +1 -0
- package/lib/module/view/morph-viewport.js +58 -0
- package/lib/module/view/morph-viewport.js.map +1 -0
- package/lib/module/view/number-lane.js +79 -0
- package/lib/module/view/number-lane.js.map +1 -0
- package/lib/module/view/number-run.js +56 -0
- package/lib/module/view/number-run.js.map +1 -0
- package/lib/module/view/text-run.js +30 -0
- package/lib/module/view/text-run.js.map +1 -0
- package/lib/typescript/commonjs/hooks/use-display-units.d.ts +2 -0
- package/lib/typescript/commonjs/hooks/use-display-units.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts +15 -0
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts +14 -0
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-morph-text-style.d.ts +13 -0
- package/lib/typescript/commonjs/hooks/use-morph-text-style.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-numeric-lanes.d.ts +10 -0
- package/lib/typescript/commonjs/hooks/use-numeric-lanes.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts +3 -0
- package/lib/typescript/commonjs/hooks/use-text-glyphs.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +7 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/model/display-units.d.ts +5 -0
- package/lib/typescript/commonjs/model/display-units.d.ts.map +1 -0
- package/lib/typescript/commonjs/model/numeric-lanes.d.ts +9 -0
- package/lib/typescript/commonjs/model/numeric-lanes.d.ts.map +1 -0
- package/lib/typescript/commonjs/model/text-keys.d.ts +7 -0
- package/lib/typescript/commonjs/model/text-keys.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts +15 -0
- package/lib/typescript/commonjs/motion/entry-exit-builders.d.ts.map +1 -0
- package/lib/typescript/commonjs/motion/preset-map.d.ts +4 -0
- package/lib/typescript/commonjs/motion/preset-map.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/types.d.ts +43 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/glyph-run.d.ts +12 -0
- package/lib/typescript/commonjs/view/glyph-run.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/morph-viewport.d.ts +14 -0
- package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/number-lane.d.ts +17 -0
- package/lib/typescript/commonjs/view/number-lane.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/number-run.d.ts +13 -0
- package/lib/typescript/commonjs/view/number-run.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/text-run.d.ts +11 -0
- package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-display-units.d.ts +2 -0
- package/lib/typescript/module/hooks/use-display-units.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts +15 -0
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-morph-motion.d.ts +14 -0
- package/lib/typescript/module/hooks/use-morph-motion.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-morph-text-style.d.ts +13 -0
- package/lib/typescript/module/hooks/use-morph-text-style.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-numeric-lanes.d.ts +10 -0
- package/lib/typescript/module/hooks/use-numeric-lanes.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts +3 -0
- package/lib/typescript/module/hooks/use-text-glyphs.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +7 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/model/display-units.d.ts +5 -0
- package/lib/typescript/module/model/display-units.d.ts.map +1 -0
- package/lib/typescript/module/model/numeric-lanes.d.ts +9 -0
- package/lib/typescript/module/model/numeric-lanes.d.ts.map +1 -0
- package/lib/typescript/module/model/text-keys.d.ts +7 -0
- package/lib/typescript/module/model/text-keys.d.ts.map +1 -0
- package/lib/typescript/module/motion/entry-exit-builders.d.ts +15 -0
- package/lib/typescript/module/motion/entry-exit-builders.d.ts.map +1 -0
- package/lib/typescript/module/motion/preset-map.d.ts +4 -0
- package/lib/typescript/module/motion/preset-map.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/types.d.ts +43 -0
- package/lib/typescript/module/types.d.ts.map +1 -0
- package/lib/typescript/module/view/glyph-run.d.ts +12 -0
- package/lib/typescript/module/view/glyph-run.d.ts.map +1 -0
- package/lib/typescript/module/view/morph-viewport.d.ts +14 -0
- package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -0
- package/lib/typescript/module/view/number-lane.d.ts +17 -0
- package/lib/typescript/module/view/number-lane.d.ts.map +1 -0
- package/lib/typescript/module/view/number-run.d.ts +13 -0
- package/lib/typescript/module/view/number-run.d.ts.map +1 -0
- package/lib/typescript/module/view/text-run.d.ts +11 -0
- package/lib/typescript/module/view/text-run.d.ts.map +1 -0
- package/package.json +61 -0
- package/src/hooks/use-display-units.ts +18 -0
- package/src/hooks/use-inline-auto-width.ts +57 -0
- package/src/hooks/use-morph-motion.ts +40 -0
- package/src/hooks/use-morph-text-style.ts +45 -0
- package/src/hooks/use-numeric-lanes.ts +55 -0
- package/src/hooks/use-text-glyphs.ts +56 -0
- package/src/index.tsx +98 -0
- package/src/model/display-units.ts +28 -0
- package/src/model/numeric-lanes.ts +80 -0
- package/src/model/text-keys.ts +123 -0
- package/src/motion/entry-exit-builders.ts +74 -0
- package/src/motion/preset-map.ts +127 -0
- package/src/types.ts +60 -0
- package/src/view/glyph-run.tsx +47 -0
- package/src/view/morph-viewport.tsx +83 -0
- package/src/view/number-lane.tsx +128 -0
- package/src/view/number-run.tsx +73 -0
- package/src/view/text-run.tsx +40 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Text, type StyleProp, type TextStyle } from "react-native";
|
|
3
|
+
import Animated from "react-native-reanimated";
|
|
4
|
+
import { isAsciiDigit } from "../model/display-units";
|
|
5
|
+
import { createShiftTransition } from "../motion/entry-exit-builders";
|
|
6
|
+
import type { MotionRecipe, NumericFlowDirection } from "../types";
|
|
7
|
+
|
|
8
|
+
const laneStyle = {
|
|
9
|
+
position: "relative",
|
|
10
|
+
alignSelf: "flex-start",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
const laneProbeStyle = {
|
|
14
|
+
opacity: 0,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
const laneTokenStyle = {
|
|
18
|
+
position: "absolute",
|
|
19
|
+
top: 0,
|
|
20
|
+
left: 0,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
type NumberLaneProps = {
|
|
24
|
+
readonly unit: string;
|
|
25
|
+
readonly tokenKey: number;
|
|
26
|
+
readonly isLead: boolean;
|
|
27
|
+
readonly hasAnimated: boolean;
|
|
28
|
+
readonly delayMs: number;
|
|
29
|
+
readonly direction: NumericFlowDirection;
|
|
30
|
+
readonly travelDistance: number;
|
|
31
|
+
readonly motionRecipe: MotionRecipe;
|
|
32
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const NumberLane = React.memo(
|
|
36
|
+
({
|
|
37
|
+
unit,
|
|
38
|
+
tokenKey,
|
|
39
|
+
isLead,
|
|
40
|
+
hasAnimated,
|
|
41
|
+
delayMs,
|
|
42
|
+
direction,
|
|
43
|
+
travelDistance,
|
|
44
|
+
motionRecipe,
|
|
45
|
+
textStyle,
|
|
46
|
+
}: NumberLaneProps) => {
|
|
47
|
+
const usesDigitTravel = isAsciiDigit(unit);
|
|
48
|
+
const verticalOffset =
|
|
49
|
+
direction > 0
|
|
50
|
+
? travelDistance
|
|
51
|
+
: direction < 0
|
|
52
|
+
? -travelDistance
|
|
53
|
+
: 0;
|
|
54
|
+
|
|
55
|
+
const enterTransition = useMemo(
|
|
56
|
+
() =>
|
|
57
|
+
createShiftTransition({
|
|
58
|
+
delayMs,
|
|
59
|
+
durationMs: motionRecipe.durationMs,
|
|
60
|
+
easing: motionRecipe.easing,
|
|
61
|
+
fromOpacity: 0,
|
|
62
|
+
toOpacity: 1,
|
|
63
|
+
fromTranslateY: usesDigitTravel ? verticalOffset : 0,
|
|
64
|
+
toTranslateY: 0,
|
|
65
|
+
fromScale: 0.6,
|
|
66
|
+
toScale: 1,
|
|
67
|
+
}),
|
|
68
|
+
[
|
|
69
|
+
delayMs,
|
|
70
|
+
motionRecipe.durationMs,
|
|
71
|
+
motionRecipe.easing,
|
|
72
|
+
usesDigitTravel,
|
|
73
|
+
verticalOffset,
|
|
74
|
+
]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const exitTransition = useMemo(
|
|
78
|
+
() =>
|
|
79
|
+
createShiftTransition({
|
|
80
|
+
delayMs,
|
|
81
|
+
durationMs: motionRecipe.durationMs,
|
|
82
|
+
easing: motionRecipe.easing,
|
|
83
|
+
fromOpacity: 1,
|
|
84
|
+
toOpacity: 0,
|
|
85
|
+
fromTranslateY: 0,
|
|
86
|
+
toTranslateY: usesDigitTravel ? -verticalOffset : 0,
|
|
87
|
+
fromScale: 1,
|
|
88
|
+
toScale: 0.6,
|
|
89
|
+
}),
|
|
90
|
+
[
|
|
91
|
+
delayMs,
|
|
92
|
+
motionRecipe.durationMs,
|
|
93
|
+
motionRecipe.easing,
|
|
94
|
+
usesDigitTravel,
|
|
95
|
+
verticalOffset,
|
|
96
|
+
]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (isLead) {
|
|
100
|
+
return <Text style={textStyle}>{unit}</Text>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Animated.View
|
|
105
|
+
layout={motionRecipe.layoutTransition}
|
|
106
|
+
entering={hasAnimated ? motionRecipe.enterTransition : undefined}
|
|
107
|
+
exiting={motionRecipe.exitTransition}
|
|
108
|
+
style={laneStyle}
|
|
109
|
+
>
|
|
110
|
+
{/* this hidden copy owns layout while the animated token swaps on top */}
|
|
111
|
+
<Text style={[textStyle, laneProbeStyle]}>
|
|
112
|
+
{unit}
|
|
113
|
+
</Text>
|
|
114
|
+
|
|
115
|
+
<Animated.Text
|
|
116
|
+
key={`token:${tokenKey}`}
|
|
117
|
+
entering={hasAnimated ? enterTransition : undefined}
|
|
118
|
+
exiting={exitTransition}
|
|
119
|
+
style={[textStyle, laneTokenStyle]}
|
|
120
|
+
>
|
|
121
|
+
{unit}
|
|
122
|
+
</Animated.Text>
|
|
123
|
+
</Animated.View>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
NumberLane.displayName = "NumberLane";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { useMemo, useRef } from "react";
|
|
2
|
+
import { type StyleProp, type TextStyle, View } from "react-native";
|
|
3
|
+
import { useNumericLanes } from "../hooks/use-numeric-lanes";
|
|
4
|
+
import type { MotionRecipe } from "../types";
|
|
5
|
+
import { NumberLane } from "./number-lane";
|
|
6
|
+
|
|
7
|
+
const rowStyle = {
|
|
8
|
+
flexDirection: "row",
|
|
9
|
+
alignItems: "center",
|
|
10
|
+
alignSelf: "flex-start",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
type NumberRunProps = {
|
|
14
|
+
readonly value: string;
|
|
15
|
+
readonly motionRecipe: MotionRecipe;
|
|
16
|
+
readonly fontSize?: number;
|
|
17
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
18
|
+
readonly staggerMs: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const NumberRun = React.memo(
|
|
22
|
+
({
|
|
23
|
+
value,
|
|
24
|
+
motionRecipe,
|
|
25
|
+
fontSize,
|
|
26
|
+
textStyle,
|
|
27
|
+
staggerMs,
|
|
28
|
+
}: Readonly<NumberRunProps>) => {
|
|
29
|
+
const { units, laneKeys, direction, leadLength } = useNumericLanes(value);
|
|
30
|
+
const lastValueRef = useRef(value);
|
|
31
|
+
const hasAnimatedRef = useRef(false);
|
|
32
|
+
|
|
33
|
+
// skip enter animations on first paint so the number feels settled
|
|
34
|
+
if (value !== lastValueRef.current) {
|
|
35
|
+
hasAnimatedRef.current = true;
|
|
36
|
+
lastValueRef.current = value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const travelDistance = useMemo(
|
|
40
|
+
() => Math.max(8, Math.round((fontSize ?? 16) * 0.4)),
|
|
41
|
+
[fontSize]
|
|
42
|
+
);
|
|
43
|
+
const hasAnimated = hasAnimatedRef.current;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={rowStyle}>
|
|
47
|
+
{units.map((unit, index) => {
|
|
48
|
+
const inLead = index < leadLength;
|
|
49
|
+
const laneKey = inLead
|
|
50
|
+
? `lead:${index}`
|
|
51
|
+
: `lane:${units.length - 1 - index}`;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<NumberLane
|
|
55
|
+
key={laneKey}
|
|
56
|
+
unit={unit}
|
|
57
|
+
tokenKey={laneKeys[index]}
|
|
58
|
+
isLead={inLead}
|
|
59
|
+
hasAnimated={hasAnimated}
|
|
60
|
+
delayMs={index * staggerMs}
|
|
61
|
+
direction={direction}
|
|
62
|
+
travelDistance={travelDistance}
|
|
63
|
+
motionRecipe={motionRecipe}
|
|
64
|
+
textStyle={textStyle}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
NumberRun.displayName = "NumberRun";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useId, useRef } from "react";
|
|
2
|
+
import type { StyleProp, TextStyle } from "react-native";
|
|
3
|
+
import { useTextGlyphs } from "../hooks/use-text-glyphs";
|
|
4
|
+
import type { MotionRecipe } from "../types";
|
|
5
|
+
import { GlyphRun } from "./glyph-run";
|
|
6
|
+
|
|
7
|
+
type TextRunProps = {
|
|
8
|
+
readonly value: string;
|
|
9
|
+
readonly motionRecipe: MotionRecipe;
|
|
10
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TextRun = React.memo(
|
|
14
|
+
({ value, motionRecipe, textStyle }: TextRunProps) => {
|
|
15
|
+
// namespace ids per instance so repeated strings do not collide
|
|
16
|
+
const scopeId = useId();
|
|
17
|
+
const glyphs = useTextGlyphs(value, scopeId);
|
|
18
|
+
const lastValueRef = useRef(value);
|
|
19
|
+
const hasAnimatedRef = useRef(false);
|
|
20
|
+
|
|
21
|
+
if (value !== lastValueRef.current) {
|
|
22
|
+
hasAnimatedRef.current = true;
|
|
23
|
+
lastValueRef.current = value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<GlyphRun
|
|
28
|
+
glyphs={glyphs}
|
|
29
|
+
layoutTransition={motionRecipe.layoutTransition}
|
|
30
|
+
enterTransition={
|
|
31
|
+
hasAnimatedRef.current ? motionRecipe.enterTransition : undefined
|
|
32
|
+
}
|
|
33
|
+
exitTransition={motionRecipe.exitTransition}
|
|
34
|
+
textStyle={textStyle}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
TextRun.displayName = "TextRun";
|