Skip to content

Commit 2c75fc0

Browse files
authored
fix(📝): normalize text color in ReText (#342)
1 parent aca8970 commit 2c75fc0

File tree

3 files changed

+198
-2
lines changed

3 files changed

+198
-2
lines changed

src/Paths.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { interpolate } from "react-native-reanimated";
2+
3+
import { Vector } from "./Vectors";
4+
5+
export enum SVGCommand {
6+
MOVE,
7+
CURVE,
8+
CLOSE,
9+
}
10+
11+
interface Move extends Vector {
12+
type: SVGCommand.MOVE;
13+
}
14+
15+
interface Curve {
16+
type: SVGCommand.CURVE;
17+
to: Vector;
18+
c1: Vector;
19+
c2: Vector;
20+
}
21+
22+
interface Close {
23+
type: SVGCommand.CLOSE;
24+
}
25+
26+
type SVGSegment = Close | Curve | Move;
27+
28+
export const exhaustiveCheck = (command: never): never => {
29+
"worklet";
30+
throw new TypeError(`Unknown SVG Command: ${command}`);
31+
};
32+
33+
const serializeMove = (c: Move) => {
34+
"worklet";
35+
return `M${c.x},${c.y} `;
36+
};
37+
38+
const serializeClose = () => {
39+
"worklet";
40+
return "Z";
41+
};
42+
43+
const serializeCurve = (c: Curve) => {
44+
"worklet";
45+
return `C${c.c1.x},${c.c1.y} ${c.c2.x},${c.c2.y} ${c.to.x},${c.to.y} `;
46+
};
47+
48+
const isMove = (command: SVGSegment): command is Move => {
49+
"worklet";
50+
return command.type === SVGCommand.MOVE;
51+
};
52+
53+
const isCurve = (command: SVGSegment): command is Curve => {
54+
"worklet";
55+
return command.type === SVGCommand.CURVE;
56+
};
57+
58+
const isClose = (command: SVGSegment): command is Close => {
59+
"worklet";
60+
return command.type === SVGCommand.CLOSE;
61+
};
62+
63+
export const serialize = (path: SVGSegment[]) => {
64+
"worklet";
65+
return path
66+
.map((segment) => {
67+
if (isMove(segment)) {
68+
return serializeMove(segment);
69+
}
70+
if (isCurve(segment)) {
71+
return serializeCurve(segment);
72+
}
73+
if (isClose(segment)) {
74+
return serializeClose();
75+
}
76+
return exhaustiveCheck(segment);
77+
})
78+
.reduce((acc, c) => acc + c);
79+
};
80+
81+
interface PathInterpolation<T extends number[]> {
82+
inputRange: T;
83+
outputRange: { [K in keyof T]: SVGSegment[] };
84+
}
85+
86+
export const interpolatePath = <T extends number[]>(
87+
value: number,
88+
config: PathInterpolation<T>
89+
) => {
90+
"worklet";
91+
const { inputRange, outputRange } = config;
92+
const path = outputRange[0].map((segment, index) => {
93+
if (isMove(segment)) {
94+
const points = outputRange.map((p) => {
95+
const s = p[index];
96+
if (isMove(s)) {
97+
return {
98+
x: s.x,
99+
y: s.y,
100+
};
101+
}
102+
throw new Error("Paths to interpolate are not symetrical");
103+
});
104+
return {
105+
type: SVGCommand.MOVE,
106+
x: interpolate(
107+
value,
108+
inputRange,
109+
points.map((p) => p.x)
110+
),
111+
y: interpolate(
112+
value,
113+
inputRange,
114+
points.map((p) => p.y)
115+
),
116+
} as Move;
117+
}
118+
if (isCurve(segment)) {
119+
const curves = outputRange.map((p) => {
120+
const s = p[index];
121+
if (isCurve(s)) {
122+
return {
123+
to: s.to,
124+
c1: s.c1,
125+
c2: s.c2,
126+
};
127+
}
128+
throw new Error("Paths to interpolate are not symetrical");
129+
});
130+
return {
131+
type: SVGCommand.CURVE,
132+
to: {
133+
x: interpolate(
134+
value,
135+
inputRange,
136+
curves.map((c) => c.to.x)
137+
),
138+
y: interpolate(
139+
value,
140+
inputRange,
141+
curves.map((c) => c.to.y)
142+
),
143+
},
144+
c1: {
145+
x: interpolate(
146+
value,
147+
inputRange,
148+
curves.map((c) => c.c1.x)
149+
),
150+
y: interpolate(
151+
value,
152+
inputRange,
153+
curves.map((c) => c.c1.y)
154+
),
155+
},
156+
c2: {
157+
x: interpolate(
158+
value,
159+
inputRange,
160+
curves.map((c) => c.c2.x)
161+
),
162+
y: interpolate(
163+
value,
164+
inputRange,
165+
curves.map((c) => c.c2.y)
166+
),
167+
},
168+
} as Curve;
169+
}
170+
return segment;
171+
});
172+
return serialize(path);
173+
};
174+
175+
export const move = (x: number, y: number) => {
176+
"worklet";
177+
return { type: SVGCommand.MOVE, x, y };
178+
};
179+
180+
export const curve = (c: Omit<Curve, "type">) => {
181+
"worklet";
182+
return { type: SVGCommand.CURVE, c1: c.c1, c2: c.c2, to: c.to };
183+
};
184+
185+
export const close = () => {
186+
"worklet";
187+
return { type: SVGCommand.CLOSE };
188+
};

src/ReText.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import * as React from "react";
2-
import { TextStyle, TextProps as RNTextProps } from "react-native";
2+
import { TextStyle, TextProps as RNTextProps, StyleSheet } from "react-native";
33
import Animated, { useAnimatedProps } from "react-native-reanimated";
44
import { TextInput } from "react-native-gesture-handler";
55

6+
const styles = StyleSheet.create({
7+
baseStyle: {
8+
color: "black",
9+
},
10+
});
11+
612
interface TextProps {
713
text: Animated.SharedValue<string>;
814
style?: Animated.AnimateProps<TextStyle, RNTextProps>["style"];
@@ -21,7 +27,8 @@ const ReText = (props: TextProps) => {
2127
underlineColorAndroid="transparent"
2228
editable={false}
2329
value={text.value}
24-
{...{ style, animatedProps }}
30+
style={[styles.baseStyle, style]}
31+
{...{ animatedProps }}
2532
/>
2633
);
2734
};

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export * from "./Transitions";
44
export * from "./Math";
55
export * from "./Vectors";
66
export * from "./Colors";
7+
export * from "./Paths";
78
export { default as ReText } from "./ReText";

0 commit comments

Comments
 (0)