react-native-ai-animations 0.8.1 → 0.9.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 +1 -1
- package/lib/module/text/streaming-text.js +150 -54
- package/lib/module/text/streaming-text.js.map +1 -1
- package/lib/typescript/src/text/streaming-text.d.ts +2 -2
- package/lib/typescript/src/text/streaming-text.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/text/streaming-text.tsx +129 -53
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ prompts, on the [website](https://nami-motion-web.vercel.app/).
|
|
|
41
41
|
| Component | Entry | What it does |
|
|
42
42
|
| --- | --- | --- |
|
|
43
43
|
| Shimmer Text | `/text` | A light band sweeping across text while a response is being generated. |
|
|
44
|
-
| Streaming Text | `/text` |
|
|
44
|
+
| Streaming Text | `/text` | A streaming answer fades in chunk by chunk with native text layout and a blinking caret. |
|
|
45
45
|
| Spoken Text | `/text` | Highlights the word being spoken and dims the words already read aloud, for voice replies. |
|
|
46
46
|
| Spinning Mark | `/brand` | Spins any icon you pass (your logo) with optional breathing and entrance. |
|
|
47
47
|
| Status Row | `/chat` | Pulsing icon, shimmering label and elapsed timer for agent status. |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { isValidElement, memo, useEffect, useMemo } from "react";
|
|
3
|
+
import { isValidElement, memo, useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { StyleSheet, Text, View } from "react-native";
|
|
5
|
-
import Animated, { cancelAnimation,
|
|
5
|
+
import Animated, { cancelAnimation, useAnimatedStyle, useReducedMotion, useSharedValue, withRepeat, withSequence, withTiming } from "react-native-reanimated";
|
|
6
|
+
import { withAlpha } from "../internal/color.js";
|
|
6
7
|
import { useMotionPalette } from "../theme/provider.js";
|
|
7
8
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
9
|
const DEFAULT_TEXT_STYLE = {
|
|
@@ -10,14 +11,85 @@ const DEFAULT_TEXT_STYLE = {
|
|
|
10
11
|
lineHeight: 24
|
|
11
12
|
};
|
|
12
13
|
const DEFAULT_FADE = 360;
|
|
13
|
-
const
|
|
14
|
+
const FRAME_MS = 32;
|
|
14
15
|
const CARET_BLINK = 530;
|
|
15
|
-
function
|
|
16
|
-
const
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
function easeOut(progress) {
|
|
17
|
+
const clamped = Math.min(1, Math.max(0, progress));
|
|
18
|
+
return 1 - (1 - clamped) ** 3;
|
|
19
|
+
}
|
|
20
|
+
function commonPrefix(a, b) {
|
|
21
|
+
const limit = Math.min(a.length, b.length);
|
|
22
|
+
let index = 0;
|
|
23
|
+
while (index < limit && a.charCodeAt(index) === b.charCodeAt(index)) index += 1;
|
|
24
|
+
return index;
|
|
25
|
+
}
|
|
26
|
+
function paragraphRanges(text) {
|
|
27
|
+
const ranges = [];
|
|
28
|
+
const breaks = /\n{2,}/g;
|
|
29
|
+
let start = 0;
|
|
30
|
+
let match = breaks.exec(text);
|
|
31
|
+
while (match) {
|
|
32
|
+
ranges.push({
|
|
33
|
+
start,
|
|
34
|
+
end: match.index
|
|
35
|
+
});
|
|
36
|
+
start = match.index + match[0].length;
|
|
37
|
+
match = breaks.exec(text);
|
|
38
|
+
}
|
|
39
|
+
ranges.push({
|
|
40
|
+
start,
|
|
41
|
+
end: text.length
|
|
42
|
+
});
|
|
43
|
+
return ranges;
|
|
44
|
+
}
|
|
45
|
+
function settledReveal(text) {
|
|
46
|
+
return {
|
|
47
|
+
text,
|
|
48
|
+
settled: text.length,
|
|
49
|
+
chunks: []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function nextReveal(previous, text, born) {
|
|
53
|
+
if (text === previous.text) return previous;
|
|
54
|
+
const common = commonPrefix(previous.text, text);
|
|
55
|
+
const chunks = previous.chunks.filter(chunk => chunk.start < common).map(chunk => chunk.end > common ? {
|
|
56
|
+
...chunk,
|
|
57
|
+
end: common
|
|
58
|
+
} : chunk);
|
|
59
|
+
if (text.length > common) chunks.push({
|
|
60
|
+
start: common,
|
|
61
|
+
end: text.length,
|
|
62
|
+
born
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
text,
|
|
66
|
+
settled: Math.min(previous.settled, common),
|
|
67
|
+
chunks
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function revealPieces(reveal, length, now, fadeDuration) {
|
|
71
|
+
const pieces = [];
|
|
72
|
+
if (reveal.settled > 0) pieces.push({
|
|
73
|
+
start: 0,
|
|
74
|
+
end: reveal.settled,
|
|
75
|
+
alpha: 1
|
|
76
|
+
});
|
|
77
|
+
let cursor = reveal.settled;
|
|
78
|
+
for (const chunk of reveal.chunks) {
|
|
79
|
+
const end = Math.min(chunk.end, length);
|
|
80
|
+
if (end > cursor) pieces.push({
|
|
81
|
+
start: cursor,
|
|
82
|
+
end,
|
|
83
|
+
alpha: easeOut((now - chunk.born) / fadeDuration)
|
|
84
|
+
});
|
|
85
|
+
cursor = Math.max(cursor, end);
|
|
86
|
+
}
|
|
87
|
+
if (cursor < length) pieces.push({
|
|
88
|
+
start: cursor,
|
|
89
|
+
end: length,
|
|
90
|
+
alpha: 0
|
|
91
|
+
});
|
|
92
|
+
return pieces;
|
|
21
93
|
}
|
|
22
94
|
export function StreamingText({
|
|
23
95
|
text,
|
|
@@ -25,17 +97,53 @@ export function StreamingText({
|
|
|
25
97
|
textStyle = DEFAULT_TEXT_STYLE,
|
|
26
98
|
paragraphGap = 12,
|
|
27
99
|
fadeDuration = DEFAULT_FADE,
|
|
28
|
-
rise = DEFAULT_RISE,
|
|
29
100
|
caret = true,
|
|
101
|
+
selectable = false,
|
|
30
102
|
colors,
|
|
31
103
|
style
|
|
32
104
|
}) {
|
|
33
105
|
const palette = useMotionPalette(colors);
|
|
34
106
|
const reduceMotion = useReducedMotion();
|
|
35
|
-
const
|
|
36
|
-
const
|
|
107
|
+
const animate = !reduceMotion && fadeDuration > 0;
|
|
108
|
+
const [reveal, setReveal] = useState(() => streaming && animate ? settledReveal("") : settledReveal(text));
|
|
109
|
+
const [now, setNow] = useState(() => Date.now());
|
|
110
|
+
const fading = reveal.chunks.length > 0;
|
|
37
111
|
const color = palette.text;
|
|
38
|
-
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!streaming || !animate) {
|
|
114
|
+
setReveal(previous => previous.text === text ? previous : settledReveal(text));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const born = Date.now();
|
|
118
|
+
setNow(born);
|
|
119
|
+
setReveal(previous => nextReveal(previous, text, born));
|
|
120
|
+
}, [text, streaming, animate]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!fading) return;
|
|
123
|
+
const timer = setInterval(() => {
|
|
124
|
+
const time = Date.now();
|
|
125
|
+
setNow(time);
|
|
126
|
+
setReveal(previous => {
|
|
127
|
+
const live = previous.chunks.filter(chunk => time - chunk.born < fadeDuration);
|
|
128
|
+
if (live.length === previous.chunks.length) return previous;
|
|
129
|
+
const done = previous.chunks[previous.chunks.length - live.length - 1];
|
|
130
|
+
return {
|
|
131
|
+
...previous,
|
|
132
|
+
settled: Math.max(previous.settled, done.end),
|
|
133
|
+
chunks: live
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}, FRAME_MS);
|
|
137
|
+
return () => clearInterval(timer);
|
|
138
|
+
}, [fading, fadeDuration]);
|
|
139
|
+
const current = streaming ? nextReveal(reveal, text, Number.POSITIVE_INFINITY) : reveal.text === text ? reveal : settledReveal(text);
|
|
140
|
+
const pieces = animate ? revealPieces(current, text.length, now, fadeDuration) : [{
|
|
141
|
+
start: 0,
|
|
142
|
+
end: text.length,
|
|
143
|
+
alpha: 1
|
|
144
|
+
}];
|
|
145
|
+
const paragraphs = useMemo(() => paragraphRanges(text), [text]);
|
|
146
|
+
const caretNode = !streaming || caret === false ? null : /*#__PURE__*/isValidElement(caret) ? caret : /*#__PURE__*/_jsx(Caret, {
|
|
39
147
|
color: color,
|
|
40
148
|
blink: !reduceMotion,
|
|
41
149
|
textStyle: textStyle
|
|
@@ -44,60 +152,48 @@ export function StreamingText({
|
|
|
44
152
|
style: [{
|
|
45
153
|
gap: paragraphGap
|
|
46
154
|
}, style],
|
|
47
|
-
children: paragraphs.map((
|
|
155
|
+
children: paragraphs.map((range, index) => {
|
|
48
156
|
const last = index === paragraphs.length - 1;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
children: [paragraph, last && streaming && caretNode]
|
|
55
|
-
}, index);
|
|
56
|
-
}
|
|
57
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
58
|
-
accessible: true,
|
|
59
|
-
accessibilityRole: "text",
|
|
60
|
-
accessibilityLabel: paragraph,
|
|
61
|
-
style: {
|
|
62
|
-
flexDirection: "row",
|
|
63
|
-
flexWrap: "wrap",
|
|
64
|
-
alignItems: "flex-end"
|
|
65
|
-
},
|
|
66
|
-
children: [tokenize(paragraph).map(token => /*#__PURE__*/_jsx(StreamingWord, {
|
|
67
|
-
value: token.value,
|
|
157
|
+
const inside = pieces.filter(piece => piece.end > range.start && piece.start < range.end);
|
|
158
|
+
const plain = inside.length <= 1 && (inside[0]?.alpha ?? 1) >= 1;
|
|
159
|
+
if (plain && !last) {
|
|
160
|
+
return /*#__PURE__*/_jsx(PlainParagraph, {
|
|
161
|
+
value: text.slice(range.start, range.end),
|
|
68
162
|
textStyle: textStyle,
|
|
69
163
|
color: color,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
164
|
+
selectable: selectable
|
|
165
|
+
}, index);
|
|
166
|
+
}
|
|
167
|
+
return /*#__PURE__*/_jsxs(Text, {
|
|
168
|
+
style: [textStyle, {
|
|
169
|
+
color
|
|
170
|
+
}],
|
|
171
|
+
selectable: selectable,
|
|
172
|
+
children: [inside.map(piece => {
|
|
173
|
+
const value = text.slice(Math.max(piece.start, range.start), Math.min(piece.end, range.end));
|
|
174
|
+
if (piece.alpha >= 1) return value;
|
|
175
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
176
|
+
style: {
|
|
177
|
+
color: withAlpha(color, piece.alpha)
|
|
178
|
+
},
|
|
179
|
+
children: value
|
|
180
|
+
}, piece.start);
|
|
181
|
+
}), last && caretNode]
|
|
73
182
|
}, index);
|
|
74
183
|
})
|
|
75
184
|
});
|
|
76
185
|
}
|
|
77
|
-
const
|
|
186
|
+
const PlainParagraph = /*#__PURE__*/memo(function PlainParagraph({
|
|
78
187
|
value,
|
|
79
188
|
textStyle,
|
|
80
189
|
color,
|
|
81
|
-
|
|
82
|
-
rise
|
|
190
|
+
selectable
|
|
83
191
|
}) {
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
progress.value = withTiming(1, {
|
|
87
|
-
duration: fadeDuration,
|
|
88
|
-
easing: Easing.out(Easing.cubic)
|
|
89
|
-
});
|
|
90
|
-
}, [fadeDuration, progress]);
|
|
91
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
92
|
-
opacity: progress.value,
|
|
93
|
-
transform: [{
|
|
94
|
-
translateY: rise * (1 - progress.value)
|
|
95
|
-
}]
|
|
96
|
-
}));
|
|
97
|
-
return /*#__PURE__*/_jsx(Animated.Text, {
|
|
192
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
98
193
|
style: [textStyle, {
|
|
99
194
|
color
|
|
100
|
-
}
|
|
195
|
+
}],
|
|
196
|
+
selectable: selectable,
|
|
101
197
|
children: value
|
|
102
198
|
});
|
|
103
199
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["isValidElement","memo","useEffect","useMemo","StyleSheet","Text","View","Animated","cancelAnimation","
|
|
1
|
+
{"version":3,"names":["isValidElement","memo","useEffect","useMemo","useState","StyleSheet","Text","View","Animated","cancelAnimation","useAnimatedStyle","useReducedMotion","useSharedValue","withRepeat","withSequence","withTiming","withAlpha","useMotionPalette","jsx","_jsx","jsxs","_jsxs","DEFAULT_TEXT_STYLE","fontSize","lineHeight","DEFAULT_FADE","FRAME_MS","CARET_BLINK","easeOut","progress","clamped","Math","min","max","commonPrefix","a","b","limit","length","index","charCodeAt","paragraphRanges","text","ranges","breaks","start","match","exec","push","end","settledReveal","settled","chunks","nextReveal","previous","born","common","filter","chunk","map","revealPieces","reveal","now","fadeDuration","pieces","alpha","cursor","StreamingText","streaming","textStyle","paragraphGap","caret","selectable","colors","style","palette","reduceMotion","animate","setReveal","setNow","Date","fading","color","timer","setInterval","time","live","done","clearInterval","current","Number","POSITIVE_INFINITY","paragraphs","caretNode","Caret","blink","gap","children","range","last","inside","piece","plain","PlainParagraph","value","slice","opacity","flat","flatten","height","round","duration","animatedStyle","width","borderRadius","backgroundColor","marginLeft","marginBottom"],"sourceRoot":"../../../src","sources":["text/streaming-text.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -7,10 +7,10 @@ export type StreamingTextProps = {
|
|
|
7
7
|
textStyle?: StyleProp<TextStyle>;
|
|
8
8
|
paragraphGap?: number;
|
|
9
9
|
fadeDuration?: number;
|
|
10
|
-
rise?: number;
|
|
11
10
|
caret?: boolean | ReactNode;
|
|
11
|
+
selectable?: boolean;
|
|
12
12
|
colors?: Partial<MotionPalette>;
|
|
13
13
|
style?: StyleProp<ViewStyle>;
|
|
14
14
|
};
|
|
15
|
-
export declare function StreamingText({ text, streaming, textStyle, paragraphGap, fadeDuration,
|
|
15
|
+
export declare function StreamingText({ text, streaming, textStyle, paragraphGap, fadeDuration, caret, selectable, colors, style, }: StreamingTextProps): import("react").JSX.Element;
|
|
16
16
|
//# sourceMappingURL=streaming-text.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming-text.d.ts","sourceRoot":"","sources":["../../../../src/text/streaming-text.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"streaming-text.d.ts","sourceRoot":"","sources":["../../../../src/text/streaming-text.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3F,OAAO,EAA0B,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAWtG,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAkB,CAAC;AAQtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AA+DF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,SAAS,EACT,SAA8B,EAC9B,YAAiB,EACjB,YAA2B,EAC3B,KAAY,EACZ,UAAkB,EAClB,MAAM,EACN,KAAK,GACN,EAAE,kBAAkB,+BA0EpB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-ai-animations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Animated React Native and Expo components for AI chat apps: streaming text, shimmer loading text, thinking and status indicators, voice orbs and waveforms, image and video generation loaders, question cards and reasoning effort bars. Built on Reanimated and Skia.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ahmet Kar",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { isValidElement, memo, useEffect, useMemo, type ReactNode } from "react";
|
|
1
|
+
import { isValidElement, memo, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
2
2
|
import { StyleSheet, Text, View, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
|
3
3
|
import Animated, {
|
|
4
4
|
cancelAnimation,
|
|
5
|
-
Easing,
|
|
6
5
|
useAnimatedStyle,
|
|
7
6
|
useReducedMotion,
|
|
8
7
|
useSharedValue,
|
|
@@ -10,12 +9,13 @@ import Animated, {
|
|
|
10
9
|
withSequence,
|
|
11
10
|
withTiming,
|
|
12
11
|
} from "react-native-reanimated";
|
|
12
|
+
import { withAlpha } from "../internal/color";
|
|
13
13
|
import { type MotionPalette } from "../theme/palette";
|
|
14
14
|
import { useMotionPalette } from "../theme/provider";
|
|
15
15
|
|
|
16
16
|
const DEFAULT_TEXT_STYLE: TextStyle = { fontSize: 16, lineHeight: 24 };
|
|
17
17
|
const DEFAULT_FADE = 360;
|
|
18
|
-
const
|
|
18
|
+
const FRAME_MS = 32;
|
|
19
19
|
const CARET_BLINK = 530;
|
|
20
20
|
|
|
21
21
|
export type StreamingTextProps = {
|
|
@@ -24,17 +24,71 @@ export type StreamingTextProps = {
|
|
|
24
24
|
textStyle?: StyleProp<TextStyle>;
|
|
25
25
|
paragraphGap?: number;
|
|
26
26
|
fadeDuration?: number;
|
|
27
|
-
rise?: number;
|
|
28
27
|
caret?: boolean | ReactNode;
|
|
28
|
+
selectable?: boolean;
|
|
29
29
|
colors?: Partial<MotionPalette>;
|
|
30
30
|
style?: StyleProp<ViewStyle>;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
type
|
|
33
|
+
type Chunk = { start: number; end: number; born: number };
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
type Reveal = { text: string; settled: number; chunks: Chunk[] };
|
|
36
|
+
|
|
37
|
+
type Range = { start: number; end: number };
|
|
38
|
+
|
|
39
|
+
type Piece = Range & { alpha: number };
|
|
40
|
+
|
|
41
|
+
function easeOut(progress: number) {
|
|
42
|
+
const clamped = Math.min(1, Math.max(0, progress));
|
|
43
|
+
return 1 - (1 - clamped) ** 3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function commonPrefix(a: string, b: string) {
|
|
47
|
+
const limit = Math.min(a.length, b.length);
|
|
48
|
+
let index = 0;
|
|
49
|
+
while (index < limit && a.charCodeAt(index) === b.charCodeAt(index)) index += 1;
|
|
50
|
+
return index;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function paragraphRanges(text: string): Range[] {
|
|
54
|
+
const ranges: Range[] = [];
|
|
55
|
+
const breaks = /\n{2,}/g;
|
|
56
|
+
let start = 0;
|
|
57
|
+
let match = breaks.exec(text);
|
|
58
|
+
while (match) {
|
|
59
|
+
ranges.push({ start, end: match.index });
|
|
60
|
+
start = match.index + match[0].length;
|
|
61
|
+
match = breaks.exec(text);
|
|
62
|
+
}
|
|
63
|
+
ranges.push({ start, end: text.length });
|
|
64
|
+
return ranges;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function settledReveal(text: string): Reveal {
|
|
68
|
+
return { text, settled: text.length, chunks: [] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nextReveal(previous: Reveal, text: string, born: number): Reveal {
|
|
72
|
+
if (text === previous.text) return previous;
|
|
73
|
+
const common = commonPrefix(previous.text, text);
|
|
74
|
+
const chunks = previous.chunks
|
|
75
|
+
.filter((chunk) => chunk.start < common)
|
|
76
|
+
.map((chunk) => (chunk.end > common ? { ...chunk, end: common } : chunk));
|
|
77
|
+
if (text.length > common) chunks.push({ start: common, end: text.length, born });
|
|
78
|
+
return { text, settled: Math.min(previous.settled, common), chunks };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function revealPieces(reveal: Reveal, length: number, now: number, fadeDuration: number): Piece[] {
|
|
82
|
+
const pieces: Piece[] = [];
|
|
83
|
+
if (reveal.settled > 0) pieces.push({ start: 0, end: reveal.settled, alpha: 1 });
|
|
84
|
+
let cursor = reveal.settled;
|
|
85
|
+
for (const chunk of reveal.chunks) {
|
|
86
|
+
const end = Math.min(chunk.end, length);
|
|
87
|
+
if (end > cursor) pieces.push({ start: cursor, end, alpha: easeOut((now - chunk.born) / fadeDuration) });
|
|
88
|
+
cursor = Math.max(cursor, end);
|
|
89
|
+
}
|
|
90
|
+
if (cursor < length) pieces.push({ start: cursor, end: length, alpha: 0 });
|
|
91
|
+
return pieces;
|
|
38
92
|
}
|
|
39
93
|
|
|
40
94
|
export function StreamingText({
|
|
@@ -43,77 +97,99 @@ export function StreamingText({
|
|
|
43
97
|
textStyle = DEFAULT_TEXT_STYLE,
|
|
44
98
|
paragraphGap = 12,
|
|
45
99
|
fadeDuration = DEFAULT_FADE,
|
|
46
|
-
rise = DEFAULT_RISE,
|
|
47
100
|
caret = true,
|
|
101
|
+
selectable = false,
|
|
48
102
|
colors,
|
|
49
103
|
style,
|
|
50
104
|
}: StreamingTextProps) {
|
|
51
105
|
const palette = useMotionPalette(colors);
|
|
52
106
|
const reduceMotion = useReducedMotion();
|
|
53
|
-
const
|
|
54
|
-
const
|
|
107
|
+
const animate = !reduceMotion && fadeDuration > 0;
|
|
108
|
+
const [reveal, setReveal] = useState<Reveal>(() => (streaming && animate ? settledReveal("") : settledReveal(text)));
|
|
109
|
+
const [now, setNow] = useState(() => Date.now());
|
|
110
|
+
const fading = reveal.chunks.length > 0;
|
|
55
111
|
const color = palette.text;
|
|
56
|
-
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!streaming || !animate) {
|
|
115
|
+
setReveal((previous) => (previous.text === text ? previous : settledReveal(text)));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const born = Date.now();
|
|
119
|
+
setNow(born);
|
|
120
|
+
setReveal((previous) => nextReveal(previous, text, born));
|
|
121
|
+
}, [text, streaming, animate]);
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!fading) return;
|
|
125
|
+
const timer = setInterval(() => {
|
|
126
|
+
const time = Date.now();
|
|
127
|
+
setNow(time);
|
|
128
|
+
setReveal((previous) => {
|
|
129
|
+
const live = previous.chunks.filter((chunk) => time - chunk.born < fadeDuration);
|
|
130
|
+
if (live.length === previous.chunks.length) return previous;
|
|
131
|
+
const done = previous.chunks[previous.chunks.length - live.length - 1];
|
|
132
|
+
return { ...previous, settled: Math.max(previous.settled, done.end), chunks: live };
|
|
133
|
+
});
|
|
134
|
+
}, FRAME_MS);
|
|
135
|
+
return () => clearInterval(timer);
|
|
136
|
+
}, [fading, fadeDuration]);
|
|
137
|
+
|
|
138
|
+
const current = streaming ? nextReveal(reveal, text, Number.POSITIVE_INFINITY) : reveal.text === text ? reveal : settledReveal(text);
|
|
139
|
+
const pieces = animate ? revealPieces(current, text.length, now, fadeDuration) : [{ start: 0, end: text.length, alpha: 1 }];
|
|
140
|
+
const paragraphs = useMemo(() => paragraphRanges(text), [text]);
|
|
141
|
+
const caretNode =
|
|
142
|
+
!streaming || caret === false ? null : isValidElement(caret) ? caret : <Caret color={color} blink={!reduceMotion} textStyle={textStyle} />;
|
|
57
143
|
|
|
58
144
|
return (
|
|
59
145
|
<View style={[{ gap: paragraphGap }, style]}>
|
|
60
|
-
{paragraphs.map((
|
|
146
|
+
{paragraphs.map((range, index) => {
|
|
61
147
|
const last = index === paragraphs.length - 1;
|
|
62
|
-
|
|
148
|
+
const inside = pieces.filter((piece) => piece.end > range.start && piece.start < range.end);
|
|
149
|
+
const plain = inside.length <= 1 && (inside[0]?.alpha ?? 1) >= 1;
|
|
150
|
+
if (plain && !last) {
|
|
63
151
|
return (
|
|
64
|
-
<
|
|
65
|
-
{
|
|
66
|
-
{
|
|
67
|
-
|
|
152
|
+
<PlainParagraph
|
|
153
|
+
key={index}
|
|
154
|
+
value={text.slice(range.start, range.end)}
|
|
155
|
+
textStyle={textStyle}
|
|
156
|
+
color={color}
|
|
157
|
+
selectable={selectable}
|
|
158
|
+
/>
|
|
68
159
|
);
|
|
69
160
|
}
|
|
70
161
|
return (
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
color={color}
|
|
84
|
-
fadeDuration={fadeDuration}
|
|
85
|
-
rise={rise}
|
|
86
|
-
/>
|
|
87
|
-
))}
|
|
88
|
-
{caretNode}
|
|
89
|
-
</View>
|
|
162
|
+
<Text key={index} style={[textStyle, { color }]} selectable={selectable}>
|
|
163
|
+
{inside.map((piece) => {
|
|
164
|
+
const value = text.slice(Math.max(piece.start, range.start), Math.min(piece.end, range.end));
|
|
165
|
+
if (piece.alpha >= 1) return value;
|
|
166
|
+
return (
|
|
167
|
+
<Text key={piece.start} style={{ color: withAlpha(color, piece.alpha) }}>
|
|
168
|
+
{value}
|
|
169
|
+
</Text>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
{last && caretNode}
|
|
173
|
+
</Text>
|
|
90
174
|
);
|
|
91
175
|
})}
|
|
92
176
|
</View>
|
|
93
177
|
);
|
|
94
178
|
}
|
|
95
179
|
|
|
96
|
-
type
|
|
180
|
+
type PlainParagraphProps = {
|
|
97
181
|
value: string;
|
|
98
182
|
textStyle: StyleProp<TextStyle>;
|
|
99
183
|
color: string;
|
|
100
|
-
|
|
101
|
-
rise: number;
|
|
184
|
+
selectable: boolean;
|
|
102
185
|
};
|
|
103
186
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
112
|
-
opacity: progress.value,
|
|
113
|
-
transform: [{ translateY: rise * (1 - progress.value) }],
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
return <Animated.Text style={[textStyle, { color }, animatedStyle]}>{value}</Animated.Text>;
|
|
187
|
+
const PlainParagraph = memo(function PlainParagraph({ value, textStyle, color, selectable }: PlainParagraphProps) {
|
|
188
|
+
return (
|
|
189
|
+
<Text style={[textStyle, { color }]} selectable={selectable}>
|
|
190
|
+
{value}
|
|
191
|
+
</Text>
|
|
192
|
+
);
|
|
117
193
|
});
|
|
118
194
|
|
|
119
195
|
function Caret({ color, blink, textStyle }: { color: string; blink: boolean; textStyle: StyleProp<TextStyle> }) {
|