react-native-laminar 1.4.4 → 1.5.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 +36 -6
- package/lib/commonjs/hooks/use-inline-auto-width.js +24 -1
- package/lib/commonjs/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/commonjs/hooks/use-leading-animation.js +49 -0
- package/lib/commonjs/hooks/use-leading-animation.js.map +1 -0
- package/lib/commonjs/hooks/use-morph-motion.js +3 -1
- package/lib/commonjs/hooks/use-morph-motion.js.map +1 -1
- package/lib/commonjs/index.js +35 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/motion/preset-map.js +5 -0
- package/lib/commonjs/motion/preset-map.js.map +1 -1
- package/lib/commonjs/view/leading-element.js +37 -0
- package/lib/commonjs/view/leading-element.js.map +1 -0
- package/lib/commonjs/view/morph-viewport.js +26 -0
- package/lib/commonjs/view/morph-viewport.js.map +1 -1
- package/lib/commonjs/view/number-run.js +70 -22
- package/lib/commonjs/view/number-run.js.map +1 -1
- package/lib/commonjs/view/slot-shadow.js +77 -0
- package/lib/commonjs/view/slot-shadow.js.map +1 -0
- package/lib/commonjs/view/slots-run.js +111 -42
- package/lib/commonjs/view/slots-run.js.map +1 -1
- package/lib/commonjs/view/text-run.js +13 -24
- package/lib/commonjs/view/text-run.js.map +1 -1
- package/lib/commonjs/view/ticker-run.js +127 -0
- package/lib/commonjs/view/ticker-run.js.map +1 -0
- package/lib/module/hooks/use-inline-auto-width.js +24 -1
- package/lib/module/hooks/use-inline-auto-width.js.map +1 -1
- package/lib/module/hooks/use-leading-animation.js +44 -0
- package/lib/module/hooks/use-leading-animation.js.map +1 -0
- package/lib/module/hooks/use-morph-motion.js +3 -1
- package/lib/module/hooks/use-morph-motion.js.map +1 -1
- package/lib/module/index.js +35 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/motion/preset-map.js +5 -0
- package/lib/module/motion/preset-map.js.map +1 -1
- package/lib/module/view/leading-element.js +32 -0
- package/lib/module/view/leading-element.js.map +1 -0
- package/lib/module/view/morph-viewport.js +28 -2
- package/lib/module/view/morph-viewport.js.map +1 -1
- package/lib/module/view/number-run.js +70 -23
- package/lib/module/view/number-run.js.map +1 -1
- package/lib/module/view/slot-shadow.js +72 -0
- package/lib/module/view/slot-shadow.js.map +1 -0
- package/lib/module/view/slots-run.js +111 -42
- package/lib/module/view/slots-run.js.map +1 -1
- package/lib/module/view/text-run.js +14 -25
- package/lib/module/view/text-run.js.map +1 -1
- package/lib/module/view/ticker-run.js +121 -0
- package/lib/module/view/ticker-run.js.map +1 -0
- package/lib/typescript/commonjs/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/use-leading-animation.d.ts +16 -0
- package/lib/typescript/commonjs/hooks/use-leading-animation.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts +3 -2
- package/lib/typescript/commonjs/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +14 -2
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/leading-element.d.ts +14 -0
- package/lib/typescript/commonjs/view/leading-element.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/morph-viewport.d.ts +4 -1
- package/lib/typescript/commonjs/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/number-run.d.ts +6 -2
- package/lib/typescript/commonjs/view/number-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/slot-shadow.d.ts +11 -0
- package/lib/typescript/commonjs/view/slot-shadow.d.ts.map +1 -0
- package/lib/typescript/commonjs/view/slots-run.d.ts +8 -3
- package/lib/typescript/commonjs/view/slots-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/text-run.d.ts.map +1 -1
- package/lib/typescript/commonjs/view/ticker-run.d.ts +21 -0
- package/lib/typescript/commonjs/view/ticker-run.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-inline-auto-width.d.ts.map +1 -1
- package/lib/typescript/module/hooks/use-leading-animation.d.ts +16 -0
- package/lib/typescript/module/hooks/use-leading-animation.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-morph-motion.d.ts +3 -2
- package/lib/typescript/module/hooks/use-morph-motion.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/motion/preset-map.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +14 -2
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/view/leading-element.d.ts +14 -0
- package/lib/typescript/module/view/leading-element.d.ts.map +1 -0
- package/lib/typescript/module/view/morph-viewport.d.ts +4 -1
- package/lib/typescript/module/view/morph-viewport.d.ts.map +1 -1
- package/lib/typescript/module/view/number-run.d.ts +6 -2
- package/lib/typescript/module/view/number-run.d.ts.map +1 -1
- package/lib/typescript/module/view/slot-shadow.d.ts +11 -0
- package/lib/typescript/module/view/slot-shadow.d.ts.map +1 -0
- package/lib/typescript/module/view/slots-run.d.ts +8 -3
- package/lib/typescript/module/view/slots-run.d.ts.map +1 -1
- package/lib/typescript/module/view/text-run.d.ts.map +1 -1
- package/lib/typescript/module/view/ticker-run.d.ts +21 -0
- package/lib/typescript/module/view/ticker-run.d.ts.map +1 -0
- package/package.json +3 -1
- package/src/hooks/use-inline-auto-width.ts +33 -1
- package/src/hooks/use-leading-animation.ts +74 -0
- package/src/hooks/use-morph-motion.ts +10 -1
- package/src/index.tsx +44 -6
- package/src/motion/preset-map.ts +5 -0
- package/src/types.ts +17 -2
- package/src/view/leading-element.tsx +45 -0
- package/src/view/morph-viewport.tsx +39 -1
- package/src/view/number-run.tsx +94 -26
- package/src/view/slot-shadow.tsx +99 -0
- package/src/view/slots-run.tsx +163 -58
- package/src/view/text-run.tsx +14 -51
- package/src/view/ticker-run.tsx +167 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { useRef, type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type TextStyle, View } from "react-native";
|
|
3
|
+
import Animated from "react-native-reanimated";
|
|
4
|
+
import { useLeadingAnimation } from "../hooks/use-leading-animation";
|
|
5
|
+
import { useNumericLanes } from "../hooks/use-numeric-lanes";
|
|
6
|
+
import { isAsciiDigit } from "../model/display-units";
|
|
7
|
+
import type {
|
|
8
|
+
LaminarAlign,
|
|
9
|
+
LaminarShadow,
|
|
10
|
+
MotionRecipe,
|
|
11
|
+
} from "../types";
|
|
12
|
+
import { LeadingElement } from "./leading-element";
|
|
13
|
+
|
|
14
|
+
const rowStyle = {
|
|
15
|
+
flexDirection: "row",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
alignSelf: "flex-start",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
// The wrapper owns the changing layout box. Keeping the Text itself out of
|
|
21
|
+
// the layout transition prevents a newly rendered glyph from being clipped by
|
|
22
|
+
// the previous glyph's narrower bounds.
|
|
23
|
+
const glyphSlotStyle = {
|
|
24
|
+
alignSelf: "flex-start",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
const rowAlignStyles = {
|
|
28
|
+
left: { alignSelf: "flex-start" },
|
|
29
|
+
center: { alignSelf: "center" },
|
|
30
|
+
right: { alignSelf: "flex-end" },
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
type TickerRunProps = {
|
|
34
|
+
readonly value: string;
|
|
35
|
+
readonly motionRecipe: MotionRecipe;
|
|
36
|
+
readonly align: LaminarAlign;
|
|
37
|
+
readonly fontSize?: number;
|
|
38
|
+
readonly textStyle?: StyleProp<TextStyle>;
|
|
39
|
+
readonly staggerMs: number;
|
|
40
|
+
readonly className?: string;
|
|
41
|
+
readonly leading?: ReactNode;
|
|
42
|
+
readonly leadingKey?: string | number;
|
|
43
|
+
readonly leadingGap?: number;
|
|
44
|
+
readonly ready?: boolean;
|
|
45
|
+
/** accepted for API compatibility; flow values stay crisp and unmasked */
|
|
46
|
+
readonly shadow?: LaminarShadow;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Render the data-ticker behavior from the reference: stable inline lanes and
|
|
50
|
+
// crisp in-place replacements. There is deliberately no reel, vertical travel,
|
|
51
|
+
// opacity fade, or viewport mask in this renderer.
|
|
52
|
+
export const TickerRun = React.memo(
|
|
53
|
+
({
|
|
54
|
+
value,
|
|
55
|
+
motionRecipe,
|
|
56
|
+
align,
|
|
57
|
+
textStyle,
|
|
58
|
+
className,
|
|
59
|
+
leading,
|
|
60
|
+
leadingKey,
|
|
61
|
+
leadingGap = 0,
|
|
62
|
+
ready = true,
|
|
63
|
+
}: Readonly<TickerRunProps>) => {
|
|
64
|
+
const hasDisplayedRef = useRef(false);
|
|
65
|
+
const displayedValueRef = useRef(value);
|
|
66
|
+
const displayedLeadingRef = useRef(leading);
|
|
67
|
+
const displayedLeadingKeyRef = useRef(leadingKey);
|
|
68
|
+
const displayedLeadingGapRef = useRef(leadingGap);
|
|
69
|
+
const shouldDisplayTarget = ready || !hasDisplayedRef.current;
|
|
70
|
+
const visibleValue = shouldDisplayTarget
|
|
71
|
+
? value
|
|
72
|
+
: displayedValueRef.current;
|
|
73
|
+
const visibleLeading = shouldDisplayTarget
|
|
74
|
+
? leading
|
|
75
|
+
: displayedLeadingRef.current;
|
|
76
|
+
const visibleLeadingKey = shouldDisplayTarget
|
|
77
|
+
? leadingKey
|
|
78
|
+
: displayedLeadingKeyRef.current;
|
|
79
|
+
const visibleLeadingGap = shouldDisplayTarget
|
|
80
|
+
? leadingGap
|
|
81
|
+
: displayedLeadingGapRef.current;
|
|
82
|
+
|
|
83
|
+
if (shouldDisplayTarget) {
|
|
84
|
+
hasDisplayedRef.current = true;
|
|
85
|
+
displayedValueRef.current = value;
|
|
86
|
+
displayedLeadingRef.current = leading;
|
|
87
|
+
displayedLeadingKeyRef.current = leadingKey;
|
|
88
|
+
displayedLeadingGapRef.current = leadingGap;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const {
|
|
92
|
+
elementEnterTransition,
|
|
93
|
+
elementExitTransition,
|
|
94
|
+
isLeadingChange,
|
|
95
|
+
isLeadingSwap,
|
|
96
|
+
} = useLeadingAnimation({
|
|
97
|
+
leading: visibleLeading,
|
|
98
|
+
leadingKey: visibleLeadingKey,
|
|
99
|
+
leadingGap: visibleLeadingGap,
|
|
100
|
+
motionRecipe,
|
|
101
|
+
});
|
|
102
|
+
const { units, leadLength } = useNumericLanes(visibleValue);
|
|
103
|
+
const lastValueRef = useRef(visibleValue);
|
|
104
|
+
const hasAnimatedRef = useRef(false);
|
|
105
|
+
|
|
106
|
+
if (visibleValue !== lastValueRef.current || isLeadingChange) {
|
|
107
|
+
hasAnimatedRef.current = true;
|
|
108
|
+
lastValueRef.current = visibleValue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const hasAnimated = hasAnimatedRef.current;
|
|
112
|
+
const bodyLength = Math.max(units.length - leadLength, 0);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<View style={[rowStyle, rowAlignStyles[align]]}>
|
|
116
|
+
<LeadingElement
|
|
117
|
+
leading={visibleLeading}
|
|
118
|
+
leadingKey={visibleLeadingKey}
|
|
119
|
+
leadingGap={visibleLeadingGap}
|
|
120
|
+
enterTransition={
|
|
121
|
+
hasAnimated ? motionRecipe.enterTransition : undefined
|
|
122
|
+
}
|
|
123
|
+
elementEnterTransition={
|
|
124
|
+
isLeadingSwap ? elementEnterTransition : undefined
|
|
125
|
+
}
|
|
126
|
+
exitTransition={motionRecipe.exitTransition}
|
|
127
|
+
elementExitTransition={elementExitTransition}
|
|
128
|
+
/>
|
|
129
|
+
<Animated.View
|
|
130
|
+
layout={isLeadingChange ? motionRecipe.layoutTransition : undefined}
|
|
131
|
+
style={rowStyle}
|
|
132
|
+
>
|
|
133
|
+
{units.map((unit, index) => {
|
|
134
|
+
const inLead = index < leadLength;
|
|
135
|
+
const bodyIndex = index - leadLength;
|
|
136
|
+
const isDigit = !inLead && isAsciiDigit(unit);
|
|
137
|
+
const laneKey = inLead
|
|
138
|
+
? `flow:lead:${index}`
|
|
139
|
+
: `flow:${bodyLength - 1 - bodyIndex}`;
|
|
140
|
+
const unitKey = isDigit ? laneKey : `${laneKey}:${unit}`;
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Animated.View
|
|
144
|
+
// Keep each digit lane mounted across updates. Replacing the
|
|
145
|
+
// node here makes the glyph snap to its new width; a stable
|
|
146
|
+
// lane lets Reanimated interpolate that width and the
|
|
147
|
+
// positions of the following glyphs, which is the subtle
|
|
148
|
+
// horizontal movement in the reference ticker.
|
|
149
|
+
key={unitKey}
|
|
150
|
+
layout={
|
|
151
|
+
hasAnimated ? motionRecipe.layoutTransition : undefined
|
|
152
|
+
}
|
|
153
|
+
style={glyphSlotStyle}
|
|
154
|
+
>
|
|
155
|
+
<Animated.Text style={textStyle} className={className}>
|
|
156
|
+
{unit}
|
|
157
|
+
</Animated.Text>
|
|
158
|
+
</Animated.View>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</Animated.View>
|
|
162
|
+
</View>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
TickerRun.displayName = "TickerRun";
|