@react-native-motion-kit/text-motion 0.0.0-stage → 0.1.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/CHANGELOG.md +11 -0
- package/LICENSE +20 -0
- package/README.ko.md +698 -0
- package/README.md +696 -2
- package/lib/module/accessibility/index.js +4 -0
- package/lib/module/accessibility/index.js.map +1 -0
- package/lib/module/accessibility/policies.js +39 -0
- package/lib/module/accessibility/policies.js.map +1 -0
- package/lib/module/controls/brand.js +4 -0
- package/lib/module/controls/brand.js.map +1 -0
- package/lib/module/controls/controls.js +28 -0
- package/lib/module/controls/controls.js.map +1 -0
- package/lib/module/controls/descriptors.js +55 -0
- package/lib/module/controls/descriptors.js.map +1 -0
- package/lib/module/controls/index.js +4 -0
- package/lib/module/controls/index.js.map +1 -0
- package/lib/module/effects/compose.js +48 -0
- package/lib/module/effects/compose.js.map +1 -0
- package/lib/module/effects/fade.js +15 -0
- package/lib/module/effects/fade.js.map +1 -0
- package/lib/module/effects/index.js +12 -0
- package/lib/module/effects/index.js.map +1 -0
- package/lib/module/effects/lineReveal.js +17 -0
- package/lib/module/effects/lineReveal.js.map +1 -0
- package/lib/module/effects/pulse.js +14 -0
- package/lib/module/effects/pulse.js.map +1 -0
- package/lib/module/effects/rise.js +14 -0
- package/lib/module/effects/rise.js.map +1 -0
- package/lib/module/effects/scale.js +15 -0
- package/lib/module/effects/scale.js.map +1 -0
- package/lib/module/effects/shake.js +14 -0
- package/lib/module/effects/shake.js.map +1 -0
- package/lib/module/effects/slide.js +15 -0
- package/lib/module/effects/slide.js.map +1 -0
- package/lib/module/effects/typewriter.js +19 -0
- package/lib/module/effects/typewriter.js.map +1 -0
- package/lib/module/effects/validation.js +10 -0
- package/lib/module/effects/validation.js.map +1 -0
- package/lib/module/index.js +10 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/presets/index.js +35 -0
- package/lib/module/presets/index.js.map +1 -0
- package/lib/module/recipe/component.js +60 -0
- package/lib/module/recipe/component.js.map +1 -0
- package/lib/module/recipe/defineTextMotion.js +9 -0
- package/lib/module/recipe/defineTextMotion.js.map +1 -0
- package/lib/module/recipe/descriptors.js +60 -0
- package/lib/module/recipe/descriptors.js.map +1 -0
- package/lib/module/recipe/index.js +5 -0
- package/lib/module/recipe/index.js.map +1 -0
- package/lib/module/recipe/recipe.js +163 -0
- package/lib/module/recipe/recipe.js.map +1 -0
- package/lib/module/renderers/index.js +5 -0
- package/lib/module/renderers/index.js.map +1 -0
- package/lib/module/renderers/nativeText.js +494 -0
- package/lib/module/renderers/nativeText.js.map +1 -0
- package/lib/module/renderers/nativeTextControls.js +96 -0
- package/lib/module/renderers/nativeTextControls.js.map +1 -0
- package/lib/module/renderers/nativeTextLineLayout.js +290 -0
- package/lib/module/renderers/nativeTextLineLayout.js.map +1 -0
- package/lib/module/renderers/nativeTextPlayback.js +91 -0
- package/lib/module/renderers/nativeTextPlayback.js.map +1 -0
- package/lib/module/renderers/overlayText.js +543 -0
- package/lib/module/renderers/overlayText.js.map +1 -0
- package/lib/module/renderers/overlayTextLayout.js +182 -0
- package/lib/module/renderers/overlayTextLayout.js.map +1 -0
- package/lib/module/renderers/rendererMotion.js +264 -0
- package/lib/module/renderers/rendererMotion.js.map +1 -0
- package/lib/module/split/createToken.js +19 -0
- package/lib/module/split/createToken.js.map +1 -0
- package/lib/module/split/custom.js +57 -0
- package/lib/module/split/custom.js.map +1 -0
- package/lib/module/split/graphemes.js +25 -0
- package/lib/module/split/graphemes.js.map +1 -0
- package/lib/module/split/index.js +7 -0
- package/lib/module/split/index.js.map +1 -0
- package/lib/module/split/lines.js +43 -0
- package/lib/module/split/lines.js.map +1 -0
- package/lib/module/split/segmenter.js +74 -0
- package/lib/module/split/segmenter.js.map +1 -0
- package/lib/module/split/words.js +28 -0
- package/lib/module/split/words.js.map +1 -0
- package/lib/module/timeline/index.js +7 -0
- package/lib/module/timeline/index.js.map +1 -0
- package/lib/module/timeline/parallel.js +21 -0
- package/lib/module/timeline/parallel.js.map +1 -0
- package/lib/module/timeline/sequence.js +18 -0
- package/lib/module/timeline/sequence.js.map +1 -0
- package/lib/module/timeline/stagger.js +43 -0
- package/lib/module/timeline/stagger.js.map +1 -0
- package/lib/module/timeline/validation.js +24 -0
- package/lib/module/timeline/validation.js.map +1 -0
- package/lib/module/timeline/wave.js +25 -0
- package/lib/module/timeline/wave.js.map +1 -0
- package/lib/module/types/index.js +4 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/types/recipe.js +4 -0
- package/lib/module/types/recipe.js.map +1 -0
- package/lib/module/types/renderer.js +27 -0
- package/lib/module/types/renderer.js.map +1 -0
- package/lib/module/types/token.js +2 -0
- package/lib/module/types/token.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/accessibility/index.d.ts +2 -0
- package/lib/typescript/src/accessibility/index.d.ts.map +1 -0
- package/lib/typescript/src/accessibility/policies.d.ts +13 -0
- package/lib/typescript/src/accessibility/policies.d.ts.map +1 -0
- package/lib/typescript/src/controls/brand.d.ts +2 -0
- package/lib/typescript/src/controls/brand.d.ts.map +1 -0
- package/lib/typescript/src/controls/controls.d.ts +28 -0
- package/lib/typescript/src/controls/controls.d.ts.map +1 -0
- package/lib/typescript/src/controls/descriptors.d.ts +14 -0
- package/lib/typescript/src/controls/descriptors.d.ts.map +1 -0
- package/lib/typescript/src/controls/index.d.ts +3 -0
- package/lib/typescript/src/controls/index.d.ts.map +1 -0
- package/lib/typescript/src/effects/compose.d.ts +11 -0
- package/lib/typescript/src/effects/compose.d.ts.map +1 -0
- package/lib/typescript/src/effects/fade.d.ts +10 -0
- package/lib/typescript/src/effects/fade.d.ts.map +1 -0
- package/lib/typescript/src/effects/index.d.ts +10 -0
- package/lib/typescript/src/effects/index.d.ts.map +1 -0
- package/lib/typescript/src/effects/lineReveal.d.ts +11 -0
- package/lib/typescript/src/effects/lineReveal.d.ts.map +1 -0
- package/lib/typescript/src/effects/pulse.d.ts +8 -0
- package/lib/typescript/src/effects/pulse.d.ts.map +1 -0
- package/lib/typescript/src/effects/rise.d.ts +8 -0
- package/lib/typescript/src/effects/rise.d.ts.map +1 -0
- package/lib/typescript/src/effects/scale.d.ts +10 -0
- package/lib/typescript/src/effects/scale.d.ts.map +1 -0
- package/lib/typescript/src/effects/shake.d.ts +8 -0
- package/lib/typescript/src/effects/shake.d.ts.map +1 -0
- package/lib/typescript/src/effects/slide.d.ts +10 -0
- package/lib/typescript/src/effects/slide.d.ts.map +1 -0
- package/lib/typescript/src/effects/typewriter.d.ts +13 -0
- package/lib/typescript/src/effects/typewriter.d.ts.map +1 -0
- package/lib/typescript/src/effects/validation.d.ts +2 -0
- package/lib/typescript/src/effects/validation.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/presets/index.d.ts +10 -0
- package/lib/typescript/src/presets/index.d.ts.map +1 -0
- package/lib/typescript/src/recipe/component.d.ts +3 -0
- package/lib/typescript/src/recipe/component.d.ts.map +1 -0
- package/lib/typescript/src/recipe/defineTextMotion.d.ts +4 -0
- package/lib/typescript/src/recipe/defineTextMotion.d.ts.map +1 -0
- package/lib/typescript/src/recipe/descriptors.d.ts +15 -0
- package/lib/typescript/src/recipe/descriptors.d.ts.map +1 -0
- package/lib/typescript/src/recipe/index.d.ts +4 -0
- package/lib/typescript/src/recipe/index.d.ts.map +1 -0
- package/lib/typescript/src/recipe/recipe.d.ts +67 -0
- package/lib/typescript/src/recipe/recipe.d.ts.map +1 -0
- package/lib/typescript/src/renderers/index.d.ts +3 -0
- package/lib/typescript/src/renderers/index.d.ts.map +1 -0
- package/lib/typescript/src/renderers/nativeText.d.ts +13 -0
- package/lib/typescript/src/renderers/nativeText.d.ts.map +1 -0
- package/lib/typescript/src/renderers/nativeTextControls.d.ts +16 -0
- package/lib/typescript/src/renderers/nativeTextControls.d.ts.map +1 -0
- package/lib/typescript/src/renderers/nativeTextLineLayout.d.ts +84 -0
- package/lib/typescript/src/renderers/nativeTextLineLayout.d.ts.map +1 -0
- package/lib/typescript/src/renderers/nativeTextPlayback.d.ts +21 -0
- package/lib/typescript/src/renderers/nativeTextPlayback.d.ts.map +1 -0
- package/lib/typescript/src/renderers/overlayText.d.ts +13 -0
- package/lib/typescript/src/renderers/overlayText.d.ts.map +1 -0
- package/lib/typescript/src/renderers/overlayTextLayout.d.ts +36 -0
- package/lib/typescript/src/renderers/overlayTextLayout.d.ts.map +1 -0
- package/lib/typescript/src/renderers/rendererMotion.d.ts +53 -0
- package/lib/typescript/src/renderers/rendererMotion.d.ts.map +1 -0
- package/lib/typescript/src/split/createToken.d.ts +11 -0
- package/lib/typescript/src/split/createToken.d.ts.map +1 -0
- package/lib/typescript/src/split/custom.d.ts +17 -0
- package/lib/typescript/src/split/custom.d.ts.map +1 -0
- package/lib/typescript/src/split/graphemes.d.ts +4 -0
- package/lib/typescript/src/split/graphemes.d.ts.map +1 -0
- package/lib/typescript/src/split/index.d.ts +5 -0
- package/lib/typescript/src/split/index.d.ts.map +1 -0
- package/lib/typescript/src/split/lines.d.ts +4 -0
- package/lib/typescript/src/split/lines.d.ts.map +1 -0
- package/lib/typescript/src/split/segmenter.d.ts +18 -0
- package/lib/typescript/src/split/segmenter.d.ts.map +1 -0
- package/lib/typescript/src/split/words.d.ts +4 -0
- package/lib/typescript/src/split/words.d.ts.map +1 -0
- package/lib/typescript/src/timeline/index.d.ts +5 -0
- package/lib/typescript/src/timeline/index.d.ts.map +1 -0
- package/lib/typescript/src/timeline/parallel.d.ts +4 -0
- package/lib/typescript/src/timeline/parallel.d.ts.map +1 -0
- package/lib/typescript/src/timeline/sequence.d.ts +4 -0
- package/lib/typescript/src/timeline/sequence.d.ts.map +1 -0
- package/lib/typescript/src/timeline/stagger.d.ts +11 -0
- package/lib/typescript/src/timeline/stagger.d.ts.map +1 -0
- package/lib/typescript/src/timeline/validation.d.ts +4 -0
- package/lib/typescript/src/timeline/validation.d.ts.map +1 -0
- package/lib/typescript/src/timeline/wave.d.ts +11 -0
- package/lib/typescript/src/timeline/wave.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +6 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- package/lib/typescript/src/types/recipe.d.ts +123 -0
- package/lib/typescript/src/types/recipe.d.ts.map +1 -0
- package/lib/typescript/src/types/renderer.d.ts +51 -0
- package/lib/typescript/src/types/renderer.d.ts.map +1 -0
- package/lib/typescript/src/types/token.d.ts +35 -0
- package/lib/typescript/src/types/token.d.ts.map +1 -0
- package/package.json +116 -3
- package/src/accessibility/index.ts +7 -0
- package/src/accessibility/policies.ts +60 -0
- package/src/controls/brand.ts +3 -0
- package/src/controls/controls.ts +41 -0
- package/src/controls/descriptors.ts +83 -0
- package/src/controls/index.ts +2 -0
- package/src/effects/compose.ts +98 -0
- package/src/effects/fade.ts +22 -0
- package/src/effects/index.ts +9 -0
- package/src/effects/lineReveal.ts +28 -0
- package/src/effects/pulse.ts +19 -0
- package/src/effects/rise.ts +19 -0
- package/src/effects/scale.ts +22 -0
- package/src/effects/shake.ts +19 -0
- package/src/effects/slide.ts +22 -0
- package/src/effects/typewriter.ts +16 -0
- package/src/effects/validation.ts +9 -0
- package/src/index.tsx +46 -0
- package/src/presets/index.ts +42 -0
- package/src/recipe/component.tsx +88 -0
- package/src/recipe/defineTextMotion.ts +6 -0
- package/src/recipe/descriptors.ts +158 -0
- package/src/recipe/index.ts +3 -0
- package/src/recipe/recipe.ts +359 -0
- package/src/renderers/index.ts +2 -0
- package/src/renderers/nativeText.tsx +813 -0
- package/src/renderers/nativeTextControls.ts +131 -0
- package/src/renderers/nativeTextLineLayout.ts +495 -0
- package/src/renderers/nativeTextPlayback.ts +204 -0
- package/src/renderers/overlayText.tsx +945 -0
- package/src/renderers/overlayTextLayout.ts +312 -0
- package/src/renderers/rendererMotion.ts +483 -0
- package/src/split/createToken.ts +26 -0
- package/src/split/custom.ts +92 -0
- package/src/split/graphemes.ts +27 -0
- package/src/split/index.ts +9 -0
- package/src/split/lines.ts +35 -0
- package/src/split/segmenter.ts +111 -0
- package/src/split/words.ts +28 -0
- package/src/timeline/index.ts +4 -0
- package/src/timeline/parallel.ts +30 -0
- package/src/timeline/sequence.ts +29 -0
- package/src/timeline/stagger.ts +60 -0
- package/src/timeline/validation.ts +29 -0
- package/src/timeline/wave.ts +34 -0
- package/src/types/index.ts +34 -0
- package/src/types/recipe.ts +195 -0
- package/src/types/renderer.ts +91 -0
- package/src/types/token.ts +39 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export type SegmenterGranularity = 'grapheme' | 'word';
|
|
2
|
+
|
|
3
|
+
export type TextMotionSegment = {
|
|
4
|
+
index: number;
|
|
5
|
+
isWordLike?: boolean;
|
|
6
|
+
segment: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type SegmenterLike = {
|
|
10
|
+
segment(input: string): Iterable<TextMotionSegment>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SegmenterRuntime = {
|
|
14
|
+
Segmenter?: SegmenterConstructor;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type SegmenterConstructor = new (
|
|
18
|
+
locale?: string | string[],
|
|
19
|
+
options?: { granularity: SegmenterGranularity },
|
|
20
|
+
) => SegmenterLike;
|
|
21
|
+
|
|
22
|
+
const WORD_CHARACTER_SOURCE =
|
|
23
|
+
'[0-9A-Za-z_\\u00C0-\\u024F\\u0370-\\u03FF\\u0400-\\u04FF\\u0590-\\u05FF\\u0600-\\u06FF\\u0900-\\u097F\\u0E00-\\u0E7F\\u1100-\\u11FF\\u3040-\\u30FF\\u3130-\\u318F\\u3400-\\u9FFF\\uAC00-\\uD7AF\\uF900-\\uFAFF]';
|
|
24
|
+
const WORD_MARK_SOURCE =
|
|
25
|
+
'[\\u0300-\\u036F\\u1AB0-\\u1AFF\\u1DC0-\\u1DFF\\u20D0-\\u20FF\\uFE20-\\uFE2F]';
|
|
26
|
+
const WORD_CONTINUATION_SOURCE = `(?:${WORD_CHARACTER_SOURCE}|${WORD_MARK_SOURCE})*`;
|
|
27
|
+
const WORD_UNIT_SOURCE = `${WORD_CHARACTER_SOURCE}${WORD_CONTINUATION_SOURCE}`;
|
|
28
|
+
const WORD_LIKE_SOURCE = `${WORD_UNIT_SOURCE}(?:[.'’]${WORD_UNIT_SOURCE})*`;
|
|
29
|
+
const WHITESPACE_SOURCE = '\\s+';
|
|
30
|
+
const WHITESPACE_PATTERN = /^\s+$/u;
|
|
31
|
+
const SURROGATE_PAIR_SOURCE = '[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]';
|
|
32
|
+
const CODE_POINT_SOURCE = `(?:\\r\\n|${SURROGATE_PAIR_SOURCE}|[\\s\\S])`;
|
|
33
|
+
const GRAPHEME_EXTENSION_SOURCE =
|
|
34
|
+
'[\\u0300-\\u036F\\u1AB0-\\u1AFF\\u1DC0-\\u1DFF\\u20D0-\\u20FF\\uFE20-\\uFE2F\\uFE00-\\uFE0F\\u{E0100}-\\u{E01EF}\\u{1F3FB}-\\u{1F3FF}]';
|
|
35
|
+
const HANGUL_LEADING_SOURCE = '[\\u1100-\\u115F\\uA960-\\uA97C]';
|
|
36
|
+
const HANGUL_VOWEL_SOURCE = '[\\u1160-\\u11A7\\uD7B0-\\uD7C6]';
|
|
37
|
+
const HANGUL_TRAILING_SOURCE = '[\\u11A8-\\u11FF\\uD7CB-\\uD7FB]';
|
|
38
|
+
// Precomposed LV syllables occur every 28 code points, starting at 가 (U+AC00).
|
|
39
|
+
const HANGUL_LV_SOURCE = `[${Array.from({ length: 399 }, (_, index) =>
|
|
40
|
+
String.fromCharCode(0xac00 + index * 28),
|
|
41
|
+
).join('')}]`;
|
|
42
|
+
const HANGUL_LVT_SOURCE = `(?!${HANGUL_LV_SOURCE})[\\uAC00-\\uD7A3]`;
|
|
43
|
+
// UAX #29 GB6–GB8: L* (V+ | LV V* | LVT) T* | L+ | T+.
|
|
44
|
+
const HANGUL_SYLLABLE_SOURCE = `${HANGUL_LEADING_SOURCE}*(?:${HANGUL_VOWEL_SOURCE}+|${HANGUL_LV_SOURCE}${HANGUL_VOWEL_SOURCE}*|${HANGUL_LVT_SOURCE})${HANGUL_TRAILING_SOURCE}*|${HANGUL_LEADING_SOURCE}+|${HANGUL_TRAILING_SOURCE}+`;
|
|
45
|
+
const BASE_GRAPHEME_SOURCE = `(?:${HANGUL_SYLLABLE_SOURCE}|${CODE_POINT_SOURCE})${GRAPHEME_EXTENSION_SOURCE}*`;
|
|
46
|
+
const JOINED_GRAPHEME_SOURCE = `${BASE_GRAPHEME_SOURCE}(?:\\u200D${BASE_GRAPHEME_SOURCE})*`;
|
|
47
|
+
const REGIONAL_INDICATOR_PAIR_SOURCE = '[\\u{1F1E6}-\\u{1F1FF}]{2}';
|
|
48
|
+
const GRAPHEME_SEGMENT_SOURCE = `${REGIONAL_INDICATOR_PAIR_SOURCE}|${JOINED_GRAPHEME_SOURCE}`;
|
|
49
|
+
const WORD_SEGMENT_SOURCE = `${WORD_LIKE_SOURCE}|${WHITESPACE_SOURCE}|${GRAPHEME_SEGMENT_SOURCE}`;
|
|
50
|
+
const WORD_LIKE_PATTERN = new RegExp(`^${WORD_LIKE_SOURCE}$`, 'u');
|
|
51
|
+
|
|
52
|
+
export function createSegmenter(
|
|
53
|
+
granularity: SegmenterGranularity,
|
|
54
|
+
locale?: string | string[],
|
|
55
|
+
runtime: SegmenterRuntime | undefined = getDefaultSegmenterRuntime(),
|
|
56
|
+
): SegmenterLike {
|
|
57
|
+
const Segmenter = runtime?.Segmenter;
|
|
58
|
+
|
|
59
|
+
if (Segmenter) {
|
|
60
|
+
return new Segmenter(locale, { granularity });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return createFallbackSegmenter(granularity);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createFallbackSegmenter(granularity: SegmenterGranularity): SegmenterLike {
|
|
67
|
+
return {
|
|
68
|
+
segment(input) {
|
|
69
|
+
return granularity === 'word'
|
|
70
|
+
? segmentWordsWithoutIntl(input)
|
|
71
|
+
: segmentGraphemesWithoutIntl(input);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function segmentWordsWithoutIntl(input: string): TextMotionSegment[] {
|
|
77
|
+
return Array.from(input.matchAll(createWordSegmentPattern()), createWordSegment);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function segmentGraphemesWithoutIntl(input: string): TextMotionSegment[] {
|
|
81
|
+
return Array.from(input.matchAll(createGraphemeSegmentPattern()), createSegment);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createWordSegment(match: RegExpMatchArray): TextMotionSegment {
|
|
85
|
+
const segment = match[0] ?? '';
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
index: match.index ?? 0,
|
|
89
|
+
isWordLike: WORD_LIKE_PATTERN.test(segment) && !WHITESPACE_PATTERN.test(segment),
|
|
90
|
+
segment,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createSegment(match: RegExpMatchArray): TextMotionSegment {
|
|
95
|
+
return {
|
|
96
|
+
index: match.index ?? 0,
|
|
97
|
+
segment: match[0] ?? '',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createGraphemeSegmentPattern(): RegExp {
|
|
102
|
+
return new RegExp(GRAPHEME_SEGMENT_SOURCE, 'gu');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createWordSegmentPattern(): RegExp {
|
|
106
|
+
return new RegExp(WORD_SEGMENT_SOURCE, 'gu');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getDefaultSegmenterRuntime(): SegmenterRuntime | undefined {
|
|
110
|
+
return globalThis.Intl as unknown as SegmenterRuntime | undefined;
|
|
111
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TextMotionSplitter } from '../types';
|
|
2
|
+
|
|
3
|
+
import { createTextMotionSplitterHandle } from '../recipe/descriptors';
|
|
4
|
+
import { createTextMotionToken } from './createToken';
|
|
5
|
+
import { createSegmenter } from './segmenter';
|
|
6
|
+
|
|
7
|
+
/** Split text into word, spacing, and punctuation tokens. */
|
|
8
|
+
export function words(): TextMotionSplitter<'word'> {
|
|
9
|
+
return createTextMotionSplitterHandle({
|
|
10
|
+
kind: 'word',
|
|
11
|
+
split(input, context) {
|
|
12
|
+
const segmenter = createSegmenter('word', context?.locale);
|
|
13
|
+
|
|
14
|
+
return Array.from(segmenter.segment(input), (segment, index) =>
|
|
15
|
+
createTextMotionToken({
|
|
16
|
+
index,
|
|
17
|
+
metadata: { isWordLike: segment.isWordLike === true },
|
|
18
|
+
sourceRange: {
|
|
19
|
+
start: segment.index,
|
|
20
|
+
end: segment.index + segment.segment.length,
|
|
21
|
+
},
|
|
22
|
+
text: segment.segment,
|
|
23
|
+
unit: 'word',
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TextMotionAnyTimeline, TextMotionTimeline } from '../types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createTextMotionTimelineHandle,
|
|
5
|
+
readTextMotionTimelineDescriptor,
|
|
6
|
+
} from '../recipe/descriptors';
|
|
7
|
+
|
|
8
|
+
type TextMotionCompositeTimelineOptions = {
|
|
9
|
+
timelines: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Combine timelines by using the earliest delay for each token. */
|
|
13
|
+
export function parallel(
|
|
14
|
+
...timelines: readonly TextMotionAnyTimeline[]
|
|
15
|
+
): TextMotionTimeline<'parallel'> {
|
|
16
|
+
const descriptors = timelines.map(readTextMotionTimelineDescriptor);
|
|
17
|
+
|
|
18
|
+
return createTextMotionTimelineHandle<TextMotionCompositeTimelineOptions, 'parallel'>({
|
|
19
|
+
kind: 'timeline',
|
|
20
|
+
name: 'parallel',
|
|
21
|
+
options: { timelines: descriptors.map((descriptor) => descriptor.name) },
|
|
22
|
+
delayFor(index, count) {
|
|
23
|
+
if (descriptors.length === 0) {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Math.min(...descriptors.map((descriptor) => descriptor.delayFor(index, count)));
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TextMotionAnyTimeline, TextMotionTimeline } from '../types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createTextMotionTimelineHandle,
|
|
5
|
+
readTextMotionTimelineDescriptor,
|
|
6
|
+
} from '../recipe/descriptors';
|
|
7
|
+
|
|
8
|
+
type TextMotionCompositeTimelineOptions = {
|
|
9
|
+
timelines: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Combine timelines by adding each timeline delay for a token. */
|
|
13
|
+
export function sequence(
|
|
14
|
+
...timelines: readonly TextMotionAnyTimeline[]
|
|
15
|
+
): TextMotionTimeline<'sequence'> {
|
|
16
|
+
const descriptors = timelines.map(readTextMotionTimelineDescriptor);
|
|
17
|
+
|
|
18
|
+
return createTextMotionTimelineHandle<TextMotionCompositeTimelineOptions, 'sequence'>({
|
|
19
|
+
kind: 'timeline',
|
|
20
|
+
name: 'sequence',
|
|
21
|
+
options: { timelines: descriptors.map((descriptor) => descriptor.name) },
|
|
22
|
+
delayFor(index, count) {
|
|
23
|
+
return descriptors.reduce(
|
|
24
|
+
(delay, descriptor) => delay + descriptor.delayFor(index, count),
|
|
25
|
+
0,
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TextMotionTimeline } from '../types';
|
|
2
|
+
|
|
3
|
+
import { createTextMotionTimelineHandle } from '../recipe/descriptors';
|
|
4
|
+
import { validateFiniteTimelineNumber, validateNonNegativeTimelineNumber } from './validation';
|
|
5
|
+
|
|
6
|
+
/** Origin used when calculating stagger delays. */
|
|
7
|
+
export type TextMotionStaggerFrom = 'start' | 'center' | 'end' | 'edges' | number;
|
|
8
|
+
|
|
9
|
+
/** Options for {@link stagger}. */
|
|
10
|
+
export type TextMotionStaggerOptions = {
|
|
11
|
+
/** Token index origin for the stagger. Defaults to `start`. */
|
|
12
|
+
from?: TextMotionStaggerFrom;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type TextMotionStaggerTimelineOptions = {
|
|
16
|
+
from: TextMotionStaggerFrom;
|
|
17
|
+
step: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function offsetFrom(index: number, count: number, from: TextMotionStaggerFrom): number {
|
|
21
|
+
if (typeof from === 'number') {
|
|
22
|
+
return Math.abs(index - from);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (from === 'center') {
|
|
26
|
+
return Math.abs(index - (count - 1) / 2);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (from === 'end') {
|
|
30
|
+
return count - 1 - index;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (from === 'edges') {
|
|
34
|
+
return Math.min(index, count - 1 - index);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return index;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Delay each token by a fixed step from the chosen origin. */
|
|
41
|
+
export function stagger(
|
|
42
|
+
step: number,
|
|
43
|
+
options: TextMotionStaggerOptions = {},
|
|
44
|
+
): TextMotionTimeline<'stagger'> {
|
|
45
|
+
const fromOption = options.from ?? 'start';
|
|
46
|
+
const from =
|
|
47
|
+
typeof fromOption === 'number'
|
|
48
|
+
? validateFiniteTimelineNumber(fromOption, 'stagger from')
|
|
49
|
+
: fromOption;
|
|
50
|
+
const safeStep = validateNonNegativeTimelineNumber(step, 'stagger step');
|
|
51
|
+
|
|
52
|
+
return createTextMotionTimelineHandle<TextMotionStaggerTimelineOptions, 'stagger'>({
|
|
53
|
+
kind: 'timeline',
|
|
54
|
+
name: 'stagger',
|
|
55
|
+
options: { from, step: safeStep },
|
|
56
|
+
delayFor(index, count) {
|
|
57
|
+
return offsetFrom(index, count, from) * safeStep;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const ERROR_PREFIX = '@react-native-motion-kit/text-motion';
|
|
2
|
+
|
|
3
|
+
export function validateFiniteTimelineNumber(value: number, label: string): number {
|
|
4
|
+
if (Number.isFinite(value)) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
throw new Error(`${ERROR_PREFIX} ${label} must be a finite number.`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function validateNonNegativeTimelineNumber(value: number, label: string): number {
|
|
12
|
+
const finiteValue = validateFiniteTimelineNumber(value, label);
|
|
13
|
+
|
|
14
|
+
if (finiteValue >= 0) {
|
|
15
|
+
return finiteValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error(`${ERROR_PREFIX} ${label} must be greater than or equal to 0.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validatePositiveTimelineNumber(value: number, label: string): number {
|
|
22
|
+
const finiteValue = validateFiniteTimelineNumber(value, label);
|
|
23
|
+
|
|
24
|
+
if (finiteValue > 0) {
|
|
25
|
+
return finiteValue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw new Error(`${ERROR_PREFIX} ${label} must be greater than 0.`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TextMotionTimeline } from '../types';
|
|
2
|
+
|
|
3
|
+
import { createTextMotionTimelineHandle } from '../recipe/descriptors';
|
|
4
|
+
import { validateNonNegativeTimelineNumber, validatePositiveTimelineNumber } from './validation';
|
|
5
|
+
|
|
6
|
+
/** Options for {@link wave}. */
|
|
7
|
+
export type TextMotionWaveOptions = {
|
|
8
|
+
/** Maximum delay in seconds. Defaults to `0.08`. */
|
|
9
|
+
amplitude?: number;
|
|
10
|
+
/** Number of tokens per sine-wave cycle. Defaults to `4`. */
|
|
11
|
+
wavelength?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type TextMotionWaveTimelineOptions = {
|
|
15
|
+
amplitude: number;
|
|
16
|
+
wavelength: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Create sine-wave token delays for soft rolling text motion. */
|
|
20
|
+
export function wave(options: TextMotionWaveOptions = {}): TextMotionTimeline<'wave'> {
|
|
21
|
+
const amplitude = validateNonNegativeTimelineNumber(options.amplitude ?? 0.08, 'wave amplitude');
|
|
22
|
+
const wavelength = validatePositiveTimelineNumber(options.wavelength ?? 4, 'wave wavelength');
|
|
23
|
+
|
|
24
|
+
return createTextMotionTimelineHandle<TextMotionWaveTimelineOptions, 'wave'>({
|
|
25
|
+
kind: 'timeline',
|
|
26
|
+
name: 'wave',
|
|
27
|
+
options: { amplitude, wavelength },
|
|
28
|
+
delayFor(index) {
|
|
29
|
+
const phase = (index / wavelength) * Math.PI * 2;
|
|
30
|
+
|
|
31
|
+
return ((Math.sin(phase) + 1) / 2) * amplitude;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
TextMotionAccessibilityPolicy,
|
|
3
|
+
TextMotionAnyEffect,
|
|
4
|
+
TextMotionAnyTimeline,
|
|
5
|
+
TextMotionComponent,
|
|
6
|
+
TextMotionComponentAccessibilityProps,
|
|
7
|
+
TextMotionComponentProps,
|
|
8
|
+
TextMotionComponentTextProps,
|
|
9
|
+
TextMotionEffect,
|
|
10
|
+
TextMotionEffectForRenderer,
|
|
11
|
+
TextMotionInternalRecipeConfig,
|
|
12
|
+
TextMotionMotionConfig,
|
|
13
|
+
TextMotionRecipeConfig,
|
|
14
|
+
TextMotionSpringOptions,
|
|
15
|
+
TextMotionTimeline,
|
|
16
|
+
TextMotionTimingOptions,
|
|
17
|
+
} from './recipe';
|
|
18
|
+
export type { TextMotionControls } from '../controls';
|
|
19
|
+
export type {
|
|
20
|
+
TextMotionCompatibleEffect,
|
|
21
|
+
TextMotionMissingCapabilities,
|
|
22
|
+
TextMotionRenderer,
|
|
23
|
+
TextMotionRendererCapability,
|
|
24
|
+
TextMotionRendererCapabilityExtension,
|
|
25
|
+
TextMotionRendererProps,
|
|
26
|
+
} from './renderer';
|
|
27
|
+
export { createTextMotionRendererCapability } from './renderer';
|
|
28
|
+
export type {
|
|
29
|
+
TextMotionSourceRange,
|
|
30
|
+
TextMotionSplitContext,
|
|
31
|
+
TextMotionSplitter,
|
|
32
|
+
TextMotionToken,
|
|
33
|
+
TextMotionTokenUnit,
|
|
34
|
+
} from './token';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { StyleProp, TextProps, TextStyle } from 'react-native';
|
|
3
|
+
import type { SharedValue, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated';
|
|
4
|
+
|
|
5
|
+
import type { TextMotionControls } from '../controls';
|
|
6
|
+
import type {
|
|
7
|
+
TextMotionCompatibleEffect,
|
|
8
|
+
TextMotionRenderer,
|
|
9
|
+
TextMotionRendererCapability,
|
|
10
|
+
} from './renderer';
|
|
11
|
+
import type { TextMotionSplitter } from './token';
|
|
12
|
+
|
|
13
|
+
type DistributiveOmit<T, Keys extends PropertyKey> = T extends unknown ? Omit<T, Keys> : never;
|
|
14
|
+
|
|
15
|
+
declare const textMotionEffectBrand: unique symbol;
|
|
16
|
+
declare const textMotionTimelineBrand: unique symbol;
|
|
17
|
+
|
|
18
|
+
/** @internal Serializable metadata attached to effect descriptors. */
|
|
19
|
+
export type TextMotionEffectDescriptorOptions = Readonly<Record<string, unknown>>;
|
|
20
|
+
|
|
21
|
+
/** @internal Serializable metadata attached to timeline descriptors. */
|
|
22
|
+
export type TextMotionTimelineDescriptorOptions = Readonly<Record<string, unknown>>;
|
|
23
|
+
|
|
24
|
+
/** @internal Timeline descriptor consumed by recipe and renderer internals. */
|
|
25
|
+
export type TextMotionTimelineDescriptor<
|
|
26
|
+
Options extends TextMotionTimelineDescriptorOptions = TextMotionTimelineDescriptorOptions,
|
|
27
|
+
Name extends string = string,
|
|
28
|
+
> = {
|
|
29
|
+
kind: 'timeline';
|
|
30
|
+
name: Name;
|
|
31
|
+
options?: Options;
|
|
32
|
+
delayFor(index: number, count: number): number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** @internal Timeline descriptor with extension-level options for recipe internals. */
|
|
36
|
+
export type TextMotionAnyTimelineDescriptor = TextMotionTimelineDescriptor;
|
|
37
|
+
|
|
38
|
+
/** Opaque timeline handle accepted by `.timeline(...)`. */
|
|
39
|
+
export type TextMotionTimeline<Name extends string = string> = {
|
|
40
|
+
readonly [textMotionTimelineBrand]: {
|
|
41
|
+
readonly name: Name;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Opaque timeline handle with any timeline name. */
|
|
46
|
+
export type TextMotionAnyTimeline = TextMotionTimeline;
|
|
47
|
+
|
|
48
|
+
/** @internal Effect descriptor consumed by renderers. */
|
|
49
|
+
export type TextMotionEffectDescriptor<
|
|
50
|
+
RequiredCapabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
51
|
+
Options extends TextMotionEffectDescriptorOptions = TextMotionEffectDescriptorOptions,
|
|
52
|
+
Name extends string = string,
|
|
53
|
+
> = {
|
|
54
|
+
kind: 'effect';
|
|
55
|
+
name: Name;
|
|
56
|
+
options?: Options;
|
|
57
|
+
requiredCapabilities: readonly RequiredCapabilities[];
|
|
58
|
+
effects?: readonly TextMotionAnyEffect[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** @internal Effect descriptor with extension-level options for renderer internals. */
|
|
62
|
+
export type TextMotionAnyEffectDescriptor = TextMotionEffectDescriptor;
|
|
63
|
+
|
|
64
|
+
/** Opaque effect handle accepted by `.effect(...)`. */
|
|
65
|
+
export type TextMotionEffect<
|
|
66
|
+
RequiredCapabilities extends TextMotionRendererCapability = 'native-text',
|
|
67
|
+
Name extends string = string,
|
|
68
|
+
> = {
|
|
69
|
+
readonly [textMotionEffectBrand]: {
|
|
70
|
+
readonly requiredCapabilities: RequiredCapabilities;
|
|
71
|
+
readonly name: Name;
|
|
72
|
+
};
|
|
73
|
+
and<NextCapabilities extends TextMotionRendererCapability, NextName extends string>(
|
|
74
|
+
effect: TextMotionEffect<NextCapabilities, NextName>,
|
|
75
|
+
): TextMotionEffect<RequiredCapabilities | NextCapabilities>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Opaque effect handle with any required capability. */
|
|
79
|
+
export type TextMotionAnyEffect = TextMotionEffect<TextMotionRendererCapability>;
|
|
80
|
+
|
|
81
|
+
/** Timing options accepted by text motion renderers. Reduced motion is controlled by accessibility policy. */
|
|
82
|
+
export type TextMotionTimingOptions = DistributiveOmit<WithTimingConfig, 'reduceMotion'>;
|
|
83
|
+
|
|
84
|
+
/** Spring options accepted by text motion renderers. Reduced motion is controlled by accessibility policy. */
|
|
85
|
+
export type TextMotionSpringOptions = DistributiveOmit<WithSpringConfig, 'reduceMotion'>;
|
|
86
|
+
|
|
87
|
+
/** Motion primitive used by Reanimated renderers. */
|
|
88
|
+
export type TextMotionMotionConfig =
|
|
89
|
+
| {
|
|
90
|
+
kind: 'timing';
|
|
91
|
+
options?: TextMotionTimingOptions;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
kind: 'spring';
|
|
95
|
+
options?: TextMotionSpringOptions;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Accessibility behavior for a text motion recipe. */
|
|
99
|
+
export type TextMotionAccessibilityPolicy = {
|
|
100
|
+
kind: string;
|
|
101
|
+
parentLabel: boolean;
|
|
102
|
+
hideTokensFromAccessibility: boolean;
|
|
103
|
+
reducedMotion: 'final-state' | 'system';
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Immutable recipe config produced by {@link TextMotionRecipeBuilder}. */
|
|
107
|
+
export type TextMotionRecipeConfig<
|
|
108
|
+
_RendererCapabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
109
|
+
Timeline extends TextMotionAnyTimeline = TextMotionTimeline,
|
|
110
|
+
Effects extends readonly TextMotionAnyEffect[] = readonly TextMotionAnyEffect[],
|
|
111
|
+
> = {
|
|
112
|
+
splitter?: TextMotionSplitter;
|
|
113
|
+
renderer?: TextMotionRenderer;
|
|
114
|
+
timeline?: Timeline;
|
|
115
|
+
effects: Effects;
|
|
116
|
+
motion?: TextMotionMotionConfig;
|
|
117
|
+
accessibility?: TextMotionAccessibilityPolicy;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** Internal recipe shape used by runtime renderers after recipe composition. */
|
|
121
|
+
export type TextMotionInternalRecipeConfig<
|
|
122
|
+
RendererCapabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
123
|
+
> = TextMotionRecipeConfig<
|
|
124
|
+
RendererCapabilities,
|
|
125
|
+
TextMotionAnyTimeline,
|
|
126
|
+
readonly TextMotionAnyEffect[]
|
|
127
|
+
>;
|
|
128
|
+
|
|
129
|
+
/** Accessibility props intentionally supported by text motion components. */
|
|
130
|
+
export type TextMotionComponentAccessibilityProps = Pick<
|
|
131
|
+
TextProps,
|
|
132
|
+
| 'accessibilityActions'
|
|
133
|
+
| 'accessibilityHint'
|
|
134
|
+
| 'accessibilityLabel'
|
|
135
|
+
| 'accessibilityLanguage'
|
|
136
|
+
| 'accessibilityRole'
|
|
137
|
+
| 'accessibilityState'
|
|
138
|
+
| 'accessibilityValue'
|
|
139
|
+
| 'onAccessibilityAction'
|
|
140
|
+
>;
|
|
141
|
+
|
|
142
|
+
/** Non-child props intentionally supported by text motion components. */
|
|
143
|
+
export type TextMotionComponentTextProps = TextMotionComponentAccessibilityProps &
|
|
144
|
+
Pick<TextProps, 'allowFontScaling' | 'maxFontSizeMultiplier' | 'nativeID' | 'testID'> & {
|
|
145
|
+
/** Text style applied to each rendered token. Layout-only text props are not supported. */
|
|
146
|
+
style?: StyleProp<TextStyle>;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
type TextMotionRawProgressPlaybackProps = {
|
|
150
|
+
/**
|
|
151
|
+
* Raw app-owned normalized progress for externally driven playback.
|
|
152
|
+
*
|
|
153
|
+
* `0` renders the initial text motion state and `1` renders the final state. Finite values
|
|
154
|
+
* outside `0..1` are clamped by the renderer; non-finite values are treated as `0`. When
|
|
155
|
+
* provided, `.motion()` does not drive playback. Use this for scroll, gesture, or synchronized
|
|
156
|
+
* shared values where the app owns the exact progress.
|
|
157
|
+
*/
|
|
158
|
+
progress?: SharedValue<number>;
|
|
159
|
+
controls?: never;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type TextMotionControlsPlaybackProps = {
|
|
163
|
+
progress?: never;
|
|
164
|
+
/**
|
|
165
|
+
* Command channel for event-driven playback.
|
|
166
|
+
*
|
|
167
|
+
* The component owns playback execution and uses its recipe `.motion()` config. Use this for
|
|
168
|
+
* buttons, screen focus, onboarding steps, and replay/reset/stop interactions.
|
|
169
|
+
*/
|
|
170
|
+
controls?: TextMotionControls;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
type TextMotionComponentPlaybackProps =
|
|
174
|
+
| TextMotionRawProgressPlaybackProps
|
|
175
|
+
| TextMotionControlsPlaybackProps;
|
|
176
|
+
|
|
177
|
+
/** Props forwarded from a text motion component to its renderer. */
|
|
178
|
+
export type TextMotionComponentRendererProps = TextMotionComponentTextProps &
|
|
179
|
+
TextMotionComponentPlaybackProps;
|
|
180
|
+
|
|
181
|
+
/** Props accepted by components created from text motion recipes. */
|
|
182
|
+
export type TextMotionComponentProps = TextMotionComponentRendererProps & {
|
|
183
|
+
/** Plain string rendered and split by the recipe splitter. */
|
|
184
|
+
children: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** React component produced by a text motion recipe. */
|
|
188
|
+
export type TextMotionComponent = ComponentType<TextMotionComponentProps>;
|
|
189
|
+
|
|
190
|
+
/** Type helper used by `.effect(...)` to enforce renderer capability compatibility. */
|
|
191
|
+
export type TextMotionEffectForRenderer<
|
|
192
|
+
RendererCapabilities extends TextMotionRendererCapability,
|
|
193
|
+
RequiredCapabilities extends TextMotionRendererCapability,
|
|
194
|
+
Effect extends TextMotionEffect<RequiredCapabilities> = TextMotionEffect<RequiredCapabilities>,
|
|
195
|
+
> = TextMotionCompatibleEffect<RendererCapabilities, RequiredCapabilities, Effect>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { TextMotionComponentRendererProps, TextMotionRecipeConfig } from './recipe';
|
|
4
|
+
import type { TextMotionToken } from './token';
|
|
5
|
+
|
|
6
|
+
declare const textMotionRendererCapabilityBrand: unique symbol;
|
|
7
|
+
declare const textMotionRendererBrand: unique symbol;
|
|
8
|
+
declare const textMotionRenderedLineRendererBrand: unique symbol;
|
|
9
|
+
|
|
10
|
+
/** Branded renderer capability name for custom renderer packages. */
|
|
11
|
+
export type TextMotionRendererCapabilityExtension<Name extends string = string> = Name & {
|
|
12
|
+
readonly [textMotionRendererCapabilityBrand]: 'TextMotionRendererCapabilityExtension';
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Capability supported by a renderer and required by effects. */
|
|
16
|
+
export type TextMotionRendererCapability =
|
|
17
|
+
| 'native-text'
|
|
18
|
+
| 'style-transform'
|
|
19
|
+
| TextMotionRendererCapabilityExtension;
|
|
20
|
+
|
|
21
|
+
/** @internal Built-in capability used by overlayText line-mask effects. */
|
|
22
|
+
export type TextMotionLineMaskCapability = TextMotionRendererCapabilityExtension<'line-mask'>;
|
|
23
|
+
|
|
24
|
+
/** Create a branded capability name for a custom renderer. */
|
|
25
|
+
export function createTextMotionRendererCapability<Name extends string>(
|
|
26
|
+
name: Name,
|
|
27
|
+
): TextMotionRendererCapabilityExtension<Name> {
|
|
28
|
+
return name as TextMotionRendererCapabilityExtension<Name>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Props passed from the recipe component into a renderer component. */
|
|
32
|
+
export type TextMotionRendererProps<Recipe = TextMotionRecipeConfig> = {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
tokens: readonly TextMotionToken[];
|
|
35
|
+
recipe: Recipe;
|
|
36
|
+
textProps?: TextMotionComponentRendererProps;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** @internal Renderer descriptor consumed by recipe internals. */
|
|
40
|
+
export type TextMotionRendererDescriptor<
|
|
41
|
+
Capabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
42
|
+
Recipe = TextMotionRecipeConfig,
|
|
43
|
+
MotionUnit extends 'source-token' | 'rendered-line' = 'source-token' | 'rendered-line',
|
|
44
|
+
> = {
|
|
45
|
+
kind: string;
|
|
46
|
+
capabilities: readonly Capabilities[];
|
|
47
|
+
motionUnit?: MotionUnit;
|
|
48
|
+
Component: ComponentType<TextMotionRendererProps<Recipe>>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Renderer handle used by `.layout(...)`. */
|
|
52
|
+
export type TextMotionRenderer<
|
|
53
|
+
Capabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
54
|
+
Recipe = unknown,
|
|
55
|
+
> = {
|
|
56
|
+
readonly [textMotionRendererBrand]: {
|
|
57
|
+
readonly capabilities: Capabilities;
|
|
58
|
+
readonly recipe: Recipe;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** @internal Renderer handle for source-token renderers accepted after `.split(...)`. */
|
|
63
|
+
export type TextMotionSourceTokenRenderer<
|
|
64
|
+
Capabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
65
|
+
Recipe = unknown,
|
|
66
|
+
> = TextMotionRenderer<Capabilities, Recipe> & {
|
|
67
|
+
readonly [textMotionRenderedLineRendererBrand]?: never;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** @internal Renderer handle for renderers that own rendered-line layout. */
|
|
71
|
+
export type TextMotionRenderedLineRenderer<
|
|
72
|
+
Capabilities extends TextMotionRendererCapability = TextMotionRendererCapability,
|
|
73
|
+
Recipe = unknown,
|
|
74
|
+
> = TextMotionRenderer<Capabilities, Recipe> & {
|
|
75
|
+
readonly [textMotionRenderedLineRendererBrand]: 'rendered-line';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Capabilities required by an effect but missing from a renderer. */
|
|
79
|
+
export type TextMotionMissingCapabilities<
|
|
80
|
+
RendererCapabilities extends TextMotionRendererCapability,
|
|
81
|
+
RequiredCapabilities extends TextMotionRendererCapability,
|
|
82
|
+
> = Exclude<RequiredCapabilities, RendererCapabilities>;
|
|
83
|
+
|
|
84
|
+
/** Effect type gate that rejects effects incompatible with the selected renderer. */
|
|
85
|
+
export type TextMotionCompatibleEffect<
|
|
86
|
+
RendererCapabilities extends TextMotionRendererCapability,
|
|
87
|
+
RequiredCapabilities extends TextMotionRendererCapability,
|
|
88
|
+
Effect,
|
|
89
|
+
> = [TextMotionMissingCapabilities<RendererCapabilities, RequiredCapabilities>] extends [never]
|
|
90
|
+
? Effect
|
|
91
|
+
: never;
|