number-flow-react-native 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.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/lib/module/core/constants.js +21 -0
  4. package/lib/module/core/constants.js.map +1 -0
  5. package/lib/module/core/intlHelpers.js +310 -0
  6. package/lib/module/core/intlHelpers.js.map +1 -0
  7. package/lib/module/core/layout.js +71 -0
  8. package/lib/module/core/layout.js.map +1 -0
  9. package/lib/module/core/mask.js +50 -0
  10. package/lib/module/core/mask.js.map +1 -0
  11. package/lib/module/core/numerals/detection.js +105 -0
  12. package/lib/module/core/numerals/detection.js.map +1 -0
  13. package/lib/module/core/numerals/digits.js +128 -0
  14. package/lib/module/core/numerals/digits.js.map +1 -0
  15. package/lib/module/core/numerals/index.js +5 -0
  16. package/lib/module/core/numerals/index.js.map +1 -0
  17. package/lib/module/core/numerals/tables.js +114 -0
  18. package/lib/module/core/numerals/tables.js.map +1 -0
  19. package/lib/module/core/superscript.js +31 -0
  20. package/lib/module/core/superscript.js.map +1 -0
  21. package/lib/module/core/timeLayout.js +98 -0
  22. package/lib/module/core/timeLayout.js.map +1 -0
  23. package/lib/module/core/timeTypes.js +4 -0
  24. package/lib/module/core/timeTypes.js.map +1 -0
  25. package/lib/module/core/timing.js +45 -0
  26. package/lib/module/core/timing.js.map +1 -0
  27. package/lib/module/core/types.js +58 -0
  28. package/lib/module/core/types.js.map +1 -0
  29. package/lib/module/core/useAccessibilityAnnouncement.js +27 -0
  30. package/lib/module/core/useAccessibilityAnnouncement.js.map +1 -0
  31. package/lib/module/core/useAnimatedX.js +25 -0
  32. package/lib/module/core/useAnimatedX.js.map +1 -0
  33. package/lib/module/core/useAnimationLifecycle.js +37 -0
  34. package/lib/module/core/useAnimationLifecycle.js.map +1 -0
  35. package/lib/module/core/useCanAnimate.js +22 -0
  36. package/lib/module/core/useCanAnimate.js.map +1 -0
  37. package/lib/module/core/useContinuousSpin.js +89 -0
  38. package/lib/module/core/useContinuousSpin.js.map +1 -0
  39. package/lib/module/core/useDebouncedWidths.js +74 -0
  40. package/lib/module/core/useDebouncedWidths.js.map +1 -0
  41. package/lib/module/core/useDigitAnimation.js +138 -0
  42. package/lib/module/core/useDigitAnimation.js.map +1 -0
  43. package/lib/module/core/useFlowPipeline.js +85 -0
  44. package/lib/module/core/useFlowPipeline.js.map +1 -0
  45. package/lib/module/core/useFormattedValue.js +28 -0
  46. package/lib/module/core/useFormattedValue.js.map +1 -0
  47. package/lib/module/core/useLayoutDiff.js +59 -0
  48. package/lib/module/core/useLayoutDiff.js.map +1 -0
  49. package/lib/module/core/useNumberFormatting.js +158 -0
  50. package/lib/module/core/useNumberFormatting.js.map +1 -0
  51. package/lib/module/core/useSlotOpacity.js +53 -0
  52. package/lib/module/core/useSlotOpacity.js.map +1 -0
  53. package/lib/module/core/useTimeFormatting.js +74 -0
  54. package/lib/module/core/useTimeFormatting.js.map +1 -0
  55. package/lib/module/core/useTimingResolution.js +21 -0
  56. package/lib/module/core/useTimingResolution.js.map +1 -0
  57. package/lib/module/core/useWorkletFormatting.js +49 -0
  58. package/lib/module/core/useWorkletFormatting.js.map +1 -0
  59. package/lib/module/core/utils.js +132 -0
  60. package/lib/module/core/utils.js.map +1 -0
  61. package/lib/module/core/warnings.js +10 -0
  62. package/lib/module/core/warnings.js.map +1 -0
  63. package/lib/module/index.js +7 -0
  64. package/lib/module/index.js.map +1 -0
  65. package/lib/module/native/DigitSlot.js +163 -0
  66. package/lib/module/native/DigitSlot.js.map +1 -0
  67. package/lib/module/native/NumberFlow.js +244 -0
  68. package/lib/module/native/NumberFlow.js.map +1 -0
  69. package/lib/module/native/SymbolSlot.js +52 -0
  70. package/lib/module/native/SymbolSlot.js.map +1 -0
  71. package/lib/module/native/TimeFlow.js +270 -0
  72. package/lib/module/native/TimeFlow.js.map +1 -0
  73. package/lib/module/native/index.js +5 -0
  74. package/lib/module/native/index.js.map +1 -0
  75. package/lib/module/native/renderSlots.js +108 -0
  76. package/lib/module/native/renderSlots.js.map +1 -0
  77. package/lib/module/native/types.js +4 -0
  78. package/lib/module/native/types.js.map +1 -0
  79. package/lib/module/native/useMeasuredGlyphMetrics.js +156 -0
  80. package/lib/module/native/useMeasuredGlyphMetrics.js.map +1 -0
  81. package/lib/module/package.json +1 -0
  82. package/lib/module/skia/DigitSlot.js +171 -0
  83. package/lib/module/skia/DigitSlot.js.map +1 -0
  84. package/lib/module/skia/SkiaNumberFlow.js +430 -0
  85. package/lib/module/skia/SkiaNumberFlow.js.map +1 -0
  86. package/lib/module/skia/SkiaTimeFlow.js +226 -0
  87. package/lib/module/skia/SkiaTimeFlow.js.map +1 -0
  88. package/lib/module/skia/SymbolSlot.js +92 -0
  89. package/lib/module/skia/SymbolSlot.js.map +1 -0
  90. package/lib/module/skia/index.js +6 -0
  91. package/lib/module/skia/index.js.map +1 -0
  92. package/lib/module/skia/renderSlots.js +131 -0
  93. package/lib/module/skia/renderSlots.js.map +1 -0
  94. package/lib/module/skia/useGlyphMetrics.js +72 -0
  95. package/lib/module/skia/useGlyphMetrics.js.map +1 -0
  96. package/lib/module/skia/useScrubbing.js +165 -0
  97. package/lib/module/skia/useScrubbing.js.map +1 -0
  98. package/lib/module/skia/useSkiaFont.js +23 -0
  99. package/lib/module/skia/useSkiaFont.js.map +1 -0
  100. package/lib/typescript/core/constants.d.ts +15 -0
  101. package/lib/typescript/core/constants.d.ts.map +1 -0
  102. package/lib/typescript/core/intlHelpers.d.ts +10 -0
  103. package/lib/typescript/core/intlHelpers.d.ts.map +1 -0
  104. package/lib/typescript/core/layout.d.ts +22 -0
  105. package/lib/typescript/core/layout.d.ts.map +1 -0
  106. package/lib/typescript/core/mask.d.ts +18 -0
  107. package/lib/typescript/core/mask.d.ts.map +1 -0
  108. package/lib/typescript/core/numerals/detection.d.ts +17 -0
  109. package/lib/typescript/core/numerals/detection.d.ts.map +1 -0
  110. package/lib/typescript/core/numerals/digits.d.ts +43 -0
  111. package/lib/typescript/core/numerals/digits.d.ts.map +1 -0
  112. package/lib/typescript/core/numerals/index.d.ts +3 -0
  113. package/lib/typescript/core/numerals/index.d.ts.map +1 -0
  114. package/lib/typescript/core/numerals/tables.d.ts +32 -0
  115. package/lib/typescript/core/numerals/tables.d.ts.map +1 -0
  116. package/lib/typescript/core/superscript.d.ts +16 -0
  117. package/lib/typescript/core/superscript.d.ts.map +1 -0
  118. package/lib/typescript/core/timeLayout.d.ts +19 -0
  119. package/lib/typescript/core/timeLayout.d.ts.map +1 -0
  120. package/lib/typescript/core/timeTypes.d.ts +80 -0
  121. package/lib/typescript/core/timeTypes.d.ts.map +1 -0
  122. package/lib/typescript/core/timing.d.ts +6 -0
  123. package/lib/typescript/core/timing.d.ts.map +1 -0
  124. package/lib/typescript/core/types.d.ts +165 -0
  125. package/lib/typescript/core/types.d.ts.map +1 -0
  126. package/lib/typescript/core/useAccessibilityAnnouncement.d.ts +10 -0
  127. package/lib/typescript/core/useAccessibilityAnnouncement.d.ts.map +1 -0
  128. package/lib/typescript/core/useAnimatedX.d.ts +9 -0
  129. package/lib/typescript/core/useAnimatedX.d.ts.map +1 -0
  130. package/lib/typescript/core/useAnimationLifecycle.d.ts +14 -0
  131. package/lib/typescript/core/useAnimationLifecycle.d.ts.map +1 -0
  132. package/lib/typescript/core/useCanAnimate.d.ts +14 -0
  133. package/lib/typescript/core/useCanAnimate.d.ts.map +1 -0
  134. package/lib/typescript/core/useContinuousSpin.d.ts +23 -0
  135. package/lib/typescript/core/useContinuousSpin.d.ts.map +1 -0
  136. package/lib/typescript/core/useDebouncedWidths.d.ts +17 -0
  137. package/lib/typescript/core/useDebouncedWidths.d.ts.map +1 -0
  138. package/lib/typescript/core/useDigitAnimation.d.ts +38 -0
  139. package/lib/typescript/core/useDigitAnimation.d.ts.map +1 -0
  140. package/lib/typescript/core/useFlowPipeline.d.ts +46 -0
  141. package/lib/typescript/core/useFlowPipeline.d.ts.map +1 -0
  142. package/lib/typescript/core/useFormattedValue.d.ts +14 -0
  143. package/lib/typescript/core/useFormattedValue.d.ts.map +1 -0
  144. package/lib/typescript/core/useLayoutDiff.d.ts +18 -0
  145. package/lib/typescript/core/useLayoutDiff.d.ts.map +1 -0
  146. package/lib/typescript/core/useNumberFormatting.d.ts +18 -0
  147. package/lib/typescript/core/useNumberFormatting.d.ts.map +1 -0
  148. package/lib/typescript/core/useSlotOpacity.d.ts +18 -0
  149. package/lib/typescript/core/useSlotOpacity.d.ts.map +1 -0
  150. package/lib/typescript/core/useTimeFormatting.d.ts +22 -0
  151. package/lib/typescript/core/useTimeFormatting.d.ts.map +1 -0
  152. package/lib/typescript/core/useTimingResolution.d.ts +13 -0
  153. package/lib/typescript/core/useTimingResolution.d.ts.map +1 -0
  154. package/lib/typescript/core/useWorkletFormatting.d.ts +14 -0
  155. package/lib/typescript/core/useWorkletFormatting.d.ts.map +1 -0
  156. package/lib/typescript/core/utils.d.ts +44 -0
  157. package/lib/typescript/core/utils.d.ts.map +1 -0
  158. package/lib/typescript/core/warnings.d.ts +2 -0
  159. package/lib/typescript/core/warnings.d.ts.map +1 -0
  160. package/lib/typescript/index.d.ts +8 -0
  161. package/lib/typescript/index.d.ts.map +1 -0
  162. package/lib/typescript/native/DigitSlot.d.ts +27 -0
  163. package/lib/typescript/native/DigitSlot.d.ts.map +1 -0
  164. package/lib/typescript/native/NumberFlow.d.ts +3 -0
  165. package/lib/typescript/native/NumberFlow.d.ts.map +1 -0
  166. package/lib/typescript/native/SymbolSlot.d.ts +19 -0
  167. package/lib/typescript/native/SymbolSlot.d.ts.map +1 -0
  168. package/lib/typescript/native/TimeFlow.d.ts +3 -0
  169. package/lib/typescript/native/TimeFlow.d.ts.map +1 -0
  170. package/lib/typescript/native/index.d.ts +3 -0
  171. package/lib/typescript/native/index.d.ts.map +1 -0
  172. package/lib/typescript/native/renderSlots.d.ts +31 -0
  173. package/lib/typescript/native/renderSlots.d.ts.map +1 -0
  174. package/lib/typescript/native/types.d.ts +36 -0
  175. package/lib/typescript/native/types.d.ts.map +1 -0
  176. package/lib/typescript/native/useMeasuredGlyphMetrics.d.ts +8 -0
  177. package/lib/typescript/native/useMeasuredGlyphMetrics.d.ts.map +1 -0
  178. package/lib/typescript/package.json +1 -0
  179. package/lib/typescript/skia/DigitSlot.d.ts +35 -0
  180. package/lib/typescript/skia/DigitSlot.d.ts.map +1 -0
  181. package/lib/typescript/skia/SkiaNumberFlow.d.ts +3 -0
  182. package/lib/typescript/skia/SkiaNumberFlow.d.ts.map +1 -0
  183. package/lib/typescript/skia/SkiaTimeFlow.d.ts +3 -0
  184. package/lib/typescript/skia/SkiaTimeFlow.d.ts.map +1 -0
  185. package/lib/typescript/skia/SymbolSlot.d.ts +26 -0
  186. package/lib/typescript/skia/SymbolSlot.d.ts.map +1 -0
  187. package/lib/typescript/skia/index.d.ts +6 -0
  188. package/lib/typescript/skia/index.d.ts.map +1 -0
  189. package/lib/typescript/skia/renderSlots.d.ts +40 -0
  190. package/lib/typescript/skia/renderSlots.d.ts.map +1 -0
  191. package/lib/typescript/skia/useGlyphMetrics.d.ts +16 -0
  192. package/lib/typescript/skia/useGlyphMetrics.d.ts.map +1 -0
  193. package/lib/typescript/skia/useScrubbing.d.ts +59 -0
  194. package/lib/typescript/skia/useScrubbing.d.ts.map +1 -0
  195. package/lib/typescript/skia/useSkiaFont.d.ts +13 -0
  196. package/lib/typescript/skia/useSkiaFont.d.ts.map +1 -0
  197. package/package.json +104 -0
  198. package/src/core/constants.ts +20 -0
  199. package/src/core/intlHelpers.ts +351 -0
  200. package/src/core/layout.ts +108 -0
  201. package/src/core/mask.ts +72 -0
  202. package/src/core/numerals/detection.ts +112 -0
  203. package/src/core/numerals/digits.ts +102 -0
  204. package/src/core/numerals/index.ts +9 -0
  205. package/src/core/numerals/tables.ts +112 -0
  206. package/src/core/superscript.ts +27 -0
  207. package/src/core/timeLayout.ts +119 -0
  208. package/src/core/timeTypes.ts +88 -0
  209. package/src/core/timing.ts +60 -0
  210. package/src/core/types.ts +189 -0
  211. package/src/core/useAccessibilityAnnouncement.ts +27 -0
  212. package/src/core/useAnimatedX.ts +30 -0
  213. package/src/core/useAnimationLifecycle.ts +54 -0
  214. package/src/core/useCanAnimate.ts +21 -0
  215. package/src/core/useContinuousSpin.ts +112 -0
  216. package/src/core/useDebouncedWidths.ts +93 -0
  217. package/src/core/useDigitAnimation.ts +192 -0
  218. package/src/core/useFlowPipeline.ts +126 -0
  219. package/src/core/useFormattedValue.ts +32 -0
  220. package/src/core/useLayoutDiff.ts +71 -0
  221. package/src/core/useNumberFormatting.ts +164 -0
  222. package/src/core/useSlotOpacity.ts +66 -0
  223. package/src/core/useTimeFormatting.ts +95 -0
  224. package/src/core/useTimingResolution.ts +47 -0
  225. package/src/core/useWorkletFormatting.ts +59 -0
  226. package/src/core/utils.ts +149 -0
  227. package/src/core/warnings.ts +8 -0
  228. package/src/index.ts +15 -0
  229. package/src/native/DigitSlot.tsx +203 -0
  230. package/src/native/NumberFlow.tsx +287 -0
  231. package/src/native/SymbolSlot.tsx +68 -0
  232. package/src/native/TimeFlow.tsx +287 -0
  233. package/src/native/index.ts +2 -0
  234. package/src/native/renderSlots.tsx +150 -0
  235. package/src/native/types.ts +40 -0
  236. package/src/native/useMeasuredGlyphMetrics.tsx +205 -0
  237. package/src/skia/DigitSlot.tsx +221 -0
  238. package/src/skia/SkiaNumberFlow.tsx +506 -0
  239. package/src/skia/SkiaTimeFlow.tsx +257 -0
  240. package/src/skia/SymbolSlot.tsx +120 -0
  241. package/src/skia/index.ts +5 -0
  242. package/src/skia/renderSlots.tsx +180 -0
  243. package/src/skia/useGlyphMetrics.ts +79 -0
  244. package/src/skia/useScrubbing.ts +223 -0
  245. package/src/skia/useSkiaFont.ts +25 -0
@@ -0,0 +1,223 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import {
3
+ makeMutable,
4
+ runOnJS,
5
+ type SharedValue,
6
+ useAnimatedReaction,
7
+ useDerivedValue,
8
+ } from "react-native-reanimated";
9
+ import { assignXPositions, type CharLayout } from "../core/layout";
10
+ import type { GlyphMetrics, TextAlign } from "../core/types";
11
+ import { useDebouncedWidths } from "../core/useDebouncedWidths";
12
+ import { useWorkletFormatting } from "../core/useWorkletFormatting";
13
+ import { countDigits } from "../core/numerals";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // useScrubbingBridge
17
+ // ---------------------------------------------------------------------------
18
+
19
+ interface UseScrubbingBridgeParams {
20
+ sharedValue: SharedValue<string> | undefined;
21
+ value: number | undefined;
22
+ prefix: string;
23
+ suffix: string;
24
+ zeroCodePoint: number;
25
+ }
26
+
27
+ interface UseScrubbingBridgeResult {
28
+ effectiveValue: number | undefined;
29
+ }
30
+
31
+ /**
32
+ * Digit-count bridging for worklet-driven scrubbing.
33
+ *
34
+ * When the worklet-driven sharedValue crosses a digit boundary (e.g.
35
+ * 99.9 → 100.0), the React-side layout must re-render with the correct
36
+ * number of digit slots. This hook watches the worklet's digit count and
37
+ * schedules a JS-side state update when it changes.
38
+ *
39
+ * Must be called **before** `useNumberFormatting` so the returned
40
+ * `effectiveValue` can feed into the formatter.
41
+ */
42
+ export function useScrubbingBridge({
43
+ sharedValue,
44
+ value,
45
+ prefix,
46
+ suffix,
47
+ zeroCodePoint,
48
+ }: UseScrubbingBridgeParams): UseScrubbingBridgeResult {
49
+ const [scrubbingValue, setScrubbingValue] = useState<number | undefined>(undefined);
50
+
51
+ const handleScrubbingValueUpdate = useCallback((numericValue: number) => {
52
+ if (numericValue < 0) {
53
+ setScrubbingValue(undefined);
54
+ } else {
55
+ setScrubbingValue(numericValue);
56
+ }
57
+ }, []);
58
+
59
+ const effectiveValue = scrubbingValue !== undefined ? scrubbingValue : value;
60
+
61
+ const [prevWorkletDigitCount] = useState(() => makeMutable(-1));
62
+
63
+ useAnimatedReaction(
64
+ () => sharedValue?.value ?? "",
65
+ (current, previous) => {
66
+ if (current === previous) return;
67
+
68
+ if (!current) {
69
+ if (prevWorkletDigitCount.value !== -1) {
70
+ prevWorkletDigitCount.value = -1;
71
+ runOnJS(handleScrubbingValueUpdate)(-1);
72
+ }
73
+ return;
74
+ }
75
+
76
+ const fullText = prefix + current + suffix;
77
+ const digitCount = countDigits(fullText, zeroCodePoint);
78
+
79
+ if (digitCount !== prevWorkletDigitCount.value) {
80
+ prevWorkletDigitCount.value = digitCount;
81
+ const numericValue = parseFloat(current);
82
+ if (!Number.isNaN(numericValue)) {
83
+ runOnJS(handleScrubbingValueUpdate)(numericValue);
84
+ }
85
+ }
86
+ },
87
+ [prefix, suffix, handleScrubbingValueUpdate],
88
+ );
89
+
90
+ return { effectiveValue };
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // useScrubbingLayout
95
+ // ---------------------------------------------------------------------------
96
+
97
+ interface UseScrubbingLayoutParams {
98
+ sharedValue: SharedValue<string> | undefined;
99
+ prefix: string;
100
+ suffix: string;
101
+ zeroCodePoint: number;
102
+ metrics: GlyphMetrics | null;
103
+ digitStringsArr: string[];
104
+ scrubDigitWidthPercentile: number;
105
+ layout: CharLayout[];
106
+ layoutDigitCount: number;
107
+ width: number;
108
+ textAlign: TextAlign;
109
+ }
110
+
111
+ interface UseScrubbingLayoutResult {
112
+ workletDigitValues: SharedValue<number>[] | null;
113
+ workletLayout: SharedValue<{ x: number; width: number }[]>;
114
+ }
115
+
116
+ /**
117
+ * Worklet-driven layout and digit extraction for scrubbing mode.
118
+ *
119
+ * Encapsulates:
120
+ * - `useWorkletFormatting`: per-digit SharedValues from the sharedValue string
121
+ * - Tabular digit widths: fixed-width digits during scrubbing to prevent jitter
122
+ * - `useDebouncedWidths`: animated transition between tabular and proportional
123
+ * - `workletLayout`: UI-thread-computed per-slot x/width using debounced widths
124
+ *
125
+ * Must be called **after** the layout is computed from `computeKeyedLayout`.
126
+ */
127
+ export function useScrubbingLayout({
128
+ sharedValue,
129
+ prefix,
130
+ suffix,
131
+ zeroCodePoint,
132
+ metrics,
133
+ digitStringsArr,
134
+ scrubDigitWidthPercentile,
135
+ layout,
136
+ layoutDigitCount,
137
+ width,
138
+ textAlign,
139
+ }: UseScrubbingLayoutParams): UseScrubbingLayoutResult {
140
+ const workletDigitValues = useWorkletFormatting(sharedValue, prefix, suffix, zeroCodePoint);
141
+
142
+ const digitWidths = useMemo(() => {
143
+ if (!metrics) return null;
144
+ return Array.from(
145
+ { length: 10 },
146
+ (_, d) => metrics.charWidths[digitStringsArr[d]] ?? metrics.maxDigitWidth,
147
+ );
148
+ }, [metrics, digitStringsArr]);
149
+
150
+ /**
151
+ * Tabular digit width for scrubbing mode.
152
+ * Interpolates between min and max digit width using the percentile.
153
+ * 0 = narrowest, 0.5 = midpoint, 1 = widest.
154
+ */
155
+ const scrubDigitWidth = useMemo(() => {
156
+ if (!digitWidths) return 0;
157
+
158
+ let minWidth = Infinity;
159
+ let maxWidth = 0;
160
+ for (let i = 0; i < 10; i++) {
161
+ if (digitWidths[i] < minWidth) minWidth = digitWidths[i];
162
+ if (digitWidths[i] > maxWidth) maxWidth = digitWidths[i];
163
+ }
164
+
165
+ return minWidth + (maxWidth - minWidth) * scrubDigitWidthPercentile;
166
+ }, [digitWidths, scrubDigitWidthPercentile]);
167
+
168
+ const debouncedWidths = useDebouncedWidths(
169
+ digitWidths,
170
+ scrubDigitWidth,
171
+ sharedValue,
172
+ prefix,
173
+ suffix,
174
+ zeroCodePoint,
175
+ );
176
+
177
+ /**
178
+ * Worklet-computed layout for proportional per-digit widths during scrubbing.
179
+ * Uses the prop layout's slot structure but substitutes digit widths based on
180
+ * the actual workletDigitValues. This ensures the worklet layout always has
181
+ * the same number of entries as the prop layout (no slotIndex mismatch).
182
+ */
183
+ const workletLayout = useDerivedValue((): { x: number; width: number }[] => {
184
+ if (!sharedValue || !digitWidths || layout.length === 0) return [];
185
+ if (!sharedValue.value) return [];
186
+
187
+ /**
188
+ * Verify digit count alignment — during the 1-frame gap between
189
+ * a worklet digit-count change and the React re-render, the layout
190
+ * slot count doesn't match the worklet's digits. Fall back to prop
191
+ * layout positions to avoid index misalignment artifacts.
192
+ */
193
+ const fullText = prefix + sharedValue.value + suffix;
194
+ const workletDigitCount = countDigits(fullText, zeroCodePoint);
195
+ if (workletDigitCount !== layoutDigitCount) return [];
196
+
197
+ const entries: { x: number; width: number }[] = [];
198
+ let contentWidth = 0;
199
+ let digitIdx = 0;
200
+
201
+ for (let i = 0; i < layout.length; i++) {
202
+ const slot = layout[i];
203
+ let slotWidth: number;
204
+
205
+ if (slot.isDigit) {
206
+ const dw = debouncedWidths[digitIdx].value;
207
+ slotWidth = dw > 0 ? dw : slot.width;
208
+ digitIdx++;
209
+ } else {
210
+ slotWidth = slot.width;
211
+ }
212
+
213
+ contentWidth += slotWidth;
214
+ entries.push({ x: 0, width: slotWidth });
215
+ }
216
+
217
+ assignXPositions(entries, width, textAlign, contentWidth);
218
+
219
+ return entries;
220
+ });
221
+
222
+ return { workletDigitValues, workletLayout };
223
+ }
@@ -0,0 +1,25 @@
1
+ import { matchFont, useFont } from "@shopify/react-native-skia";
2
+ import type { DataSourceParam, SkFont } from "@shopify/react-native-skia";
3
+ import { useMemo } from "react";
4
+
5
+ /**
6
+ * Loads a custom Skia font asynchronously while providing a synchronous
7
+ * system-font fallback via `matchFont`. This guarantees a non-null `SkFont`
8
+ * from the very first render, so components can run the full animated pipeline
9
+ * immediately instead of showing a blank canvas or static placeholder.
10
+ *
11
+ * Once the custom font finishes loading, the returned value swaps to the
12
+ * custom font — triggering a smooth animated transition in SkiaNumberFlow /
13
+ * SkiaTimeFlow (the value re-renders with new glyph metrics).
14
+ */
15
+ export function useSkiaFont(
16
+ source: DataSourceParam,
17
+ size: number,
18
+ onError?: (err: Error) => void,
19
+ ): SkFont {
20
+ const font = useFont(source, size, onError);
21
+
22
+ const fallback = useMemo(() => matchFont({ fontSize: size }), [size]);
23
+
24
+ return font ?? fallback;
25
+ }