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.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/lib/module/core/constants.js +21 -0
- package/lib/module/core/constants.js.map +1 -0
- package/lib/module/core/intlHelpers.js +310 -0
- package/lib/module/core/intlHelpers.js.map +1 -0
- package/lib/module/core/layout.js +71 -0
- package/lib/module/core/layout.js.map +1 -0
- package/lib/module/core/mask.js +50 -0
- package/lib/module/core/mask.js.map +1 -0
- package/lib/module/core/numerals/detection.js +105 -0
- package/lib/module/core/numerals/detection.js.map +1 -0
- package/lib/module/core/numerals/digits.js +128 -0
- package/lib/module/core/numerals/digits.js.map +1 -0
- package/lib/module/core/numerals/index.js +5 -0
- package/lib/module/core/numerals/index.js.map +1 -0
- package/lib/module/core/numerals/tables.js +114 -0
- package/lib/module/core/numerals/tables.js.map +1 -0
- package/lib/module/core/superscript.js +31 -0
- package/lib/module/core/superscript.js.map +1 -0
- package/lib/module/core/timeLayout.js +98 -0
- package/lib/module/core/timeLayout.js.map +1 -0
- package/lib/module/core/timeTypes.js +4 -0
- package/lib/module/core/timeTypes.js.map +1 -0
- package/lib/module/core/timing.js +45 -0
- package/lib/module/core/timing.js.map +1 -0
- package/lib/module/core/types.js +58 -0
- package/lib/module/core/types.js.map +1 -0
- package/lib/module/core/useAccessibilityAnnouncement.js +27 -0
- package/lib/module/core/useAccessibilityAnnouncement.js.map +1 -0
- package/lib/module/core/useAnimatedX.js +25 -0
- package/lib/module/core/useAnimatedX.js.map +1 -0
- package/lib/module/core/useAnimationLifecycle.js +37 -0
- package/lib/module/core/useAnimationLifecycle.js.map +1 -0
- package/lib/module/core/useCanAnimate.js +22 -0
- package/lib/module/core/useCanAnimate.js.map +1 -0
- package/lib/module/core/useContinuousSpin.js +89 -0
- package/lib/module/core/useContinuousSpin.js.map +1 -0
- package/lib/module/core/useDebouncedWidths.js +74 -0
- package/lib/module/core/useDebouncedWidths.js.map +1 -0
- package/lib/module/core/useDigitAnimation.js +138 -0
- package/lib/module/core/useDigitAnimation.js.map +1 -0
- package/lib/module/core/useFlowPipeline.js +85 -0
- package/lib/module/core/useFlowPipeline.js.map +1 -0
- package/lib/module/core/useFormattedValue.js +28 -0
- package/lib/module/core/useFormattedValue.js.map +1 -0
- package/lib/module/core/useLayoutDiff.js +59 -0
- package/lib/module/core/useLayoutDiff.js.map +1 -0
- package/lib/module/core/useNumberFormatting.js +158 -0
- package/lib/module/core/useNumberFormatting.js.map +1 -0
- package/lib/module/core/useSlotOpacity.js +53 -0
- package/lib/module/core/useSlotOpacity.js.map +1 -0
- package/lib/module/core/useTimeFormatting.js +74 -0
- package/lib/module/core/useTimeFormatting.js.map +1 -0
- package/lib/module/core/useTimingResolution.js +21 -0
- package/lib/module/core/useTimingResolution.js.map +1 -0
- package/lib/module/core/useWorkletFormatting.js +49 -0
- package/lib/module/core/useWorkletFormatting.js.map +1 -0
- package/lib/module/core/utils.js +132 -0
- package/lib/module/core/utils.js.map +1 -0
- package/lib/module/core/warnings.js +10 -0
- package/lib/module/core/warnings.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/DigitSlot.js +163 -0
- package/lib/module/native/DigitSlot.js.map +1 -0
- package/lib/module/native/NumberFlow.js +244 -0
- package/lib/module/native/NumberFlow.js.map +1 -0
- package/lib/module/native/SymbolSlot.js +52 -0
- package/lib/module/native/SymbolSlot.js.map +1 -0
- package/lib/module/native/TimeFlow.js +270 -0
- package/lib/module/native/TimeFlow.js.map +1 -0
- package/lib/module/native/index.js +5 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/native/renderSlots.js +108 -0
- package/lib/module/native/renderSlots.js.map +1 -0
- package/lib/module/native/types.js +4 -0
- package/lib/module/native/types.js.map +1 -0
- package/lib/module/native/useMeasuredGlyphMetrics.js +156 -0
- package/lib/module/native/useMeasuredGlyphMetrics.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/skia/DigitSlot.js +171 -0
- package/lib/module/skia/DigitSlot.js.map +1 -0
- package/lib/module/skia/SkiaNumberFlow.js +430 -0
- package/lib/module/skia/SkiaNumberFlow.js.map +1 -0
- package/lib/module/skia/SkiaTimeFlow.js +226 -0
- package/lib/module/skia/SkiaTimeFlow.js.map +1 -0
- package/lib/module/skia/SymbolSlot.js +92 -0
- package/lib/module/skia/SymbolSlot.js.map +1 -0
- package/lib/module/skia/index.js +6 -0
- package/lib/module/skia/index.js.map +1 -0
- package/lib/module/skia/renderSlots.js +131 -0
- package/lib/module/skia/renderSlots.js.map +1 -0
- package/lib/module/skia/useGlyphMetrics.js +72 -0
- package/lib/module/skia/useGlyphMetrics.js.map +1 -0
- package/lib/module/skia/useScrubbing.js +165 -0
- package/lib/module/skia/useScrubbing.js.map +1 -0
- package/lib/module/skia/useSkiaFont.js +23 -0
- package/lib/module/skia/useSkiaFont.js.map +1 -0
- package/lib/typescript/core/constants.d.ts +15 -0
- package/lib/typescript/core/constants.d.ts.map +1 -0
- package/lib/typescript/core/intlHelpers.d.ts +10 -0
- package/lib/typescript/core/intlHelpers.d.ts.map +1 -0
- package/lib/typescript/core/layout.d.ts +22 -0
- package/lib/typescript/core/layout.d.ts.map +1 -0
- package/lib/typescript/core/mask.d.ts +18 -0
- package/lib/typescript/core/mask.d.ts.map +1 -0
- package/lib/typescript/core/numerals/detection.d.ts +17 -0
- package/lib/typescript/core/numerals/detection.d.ts.map +1 -0
- package/lib/typescript/core/numerals/digits.d.ts +43 -0
- package/lib/typescript/core/numerals/digits.d.ts.map +1 -0
- package/lib/typescript/core/numerals/index.d.ts +3 -0
- package/lib/typescript/core/numerals/index.d.ts.map +1 -0
- package/lib/typescript/core/numerals/tables.d.ts +32 -0
- package/lib/typescript/core/numerals/tables.d.ts.map +1 -0
- package/lib/typescript/core/superscript.d.ts +16 -0
- package/lib/typescript/core/superscript.d.ts.map +1 -0
- package/lib/typescript/core/timeLayout.d.ts +19 -0
- package/lib/typescript/core/timeLayout.d.ts.map +1 -0
- package/lib/typescript/core/timeTypes.d.ts +80 -0
- package/lib/typescript/core/timeTypes.d.ts.map +1 -0
- package/lib/typescript/core/timing.d.ts +6 -0
- package/lib/typescript/core/timing.d.ts.map +1 -0
- package/lib/typescript/core/types.d.ts +165 -0
- package/lib/typescript/core/types.d.ts.map +1 -0
- package/lib/typescript/core/useAccessibilityAnnouncement.d.ts +10 -0
- package/lib/typescript/core/useAccessibilityAnnouncement.d.ts.map +1 -0
- package/lib/typescript/core/useAnimatedX.d.ts +9 -0
- package/lib/typescript/core/useAnimatedX.d.ts.map +1 -0
- package/lib/typescript/core/useAnimationLifecycle.d.ts +14 -0
- package/lib/typescript/core/useAnimationLifecycle.d.ts.map +1 -0
- package/lib/typescript/core/useCanAnimate.d.ts +14 -0
- package/lib/typescript/core/useCanAnimate.d.ts.map +1 -0
- package/lib/typescript/core/useContinuousSpin.d.ts +23 -0
- package/lib/typescript/core/useContinuousSpin.d.ts.map +1 -0
- package/lib/typescript/core/useDebouncedWidths.d.ts +17 -0
- package/lib/typescript/core/useDebouncedWidths.d.ts.map +1 -0
- package/lib/typescript/core/useDigitAnimation.d.ts +38 -0
- package/lib/typescript/core/useDigitAnimation.d.ts.map +1 -0
- package/lib/typescript/core/useFlowPipeline.d.ts +46 -0
- package/lib/typescript/core/useFlowPipeline.d.ts.map +1 -0
- package/lib/typescript/core/useFormattedValue.d.ts +14 -0
- package/lib/typescript/core/useFormattedValue.d.ts.map +1 -0
- package/lib/typescript/core/useLayoutDiff.d.ts +18 -0
- package/lib/typescript/core/useLayoutDiff.d.ts.map +1 -0
- package/lib/typescript/core/useNumberFormatting.d.ts +18 -0
- package/lib/typescript/core/useNumberFormatting.d.ts.map +1 -0
- package/lib/typescript/core/useSlotOpacity.d.ts +18 -0
- package/lib/typescript/core/useSlotOpacity.d.ts.map +1 -0
- package/lib/typescript/core/useTimeFormatting.d.ts +22 -0
- package/lib/typescript/core/useTimeFormatting.d.ts.map +1 -0
- package/lib/typescript/core/useTimingResolution.d.ts +13 -0
- package/lib/typescript/core/useTimingResolution.d.ts.map +1 -0
- package/lib/typescript/core/useWorkletFormatting.d.ts +14 -0
- package/lib/typescript/core/useWorkletFormatting.d.ts.map +1 -0
- package/lib/typescript/core/utils.d.ts +44 -0
- package/lib/typescript/core/utils.d.ts.map +1 -0
- package/lib/typescript/core/warnings.d.ts +2 -0
- package/lib/typescript/core/warnings.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +8 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/native/DigitSlot.d.ts +27 -0
- package/lib/typescript/native/DigitSlot.d.ts.map +1 -0
- package/lib/typescript/native/NumberFlow.d.ts +3 -0
- package/lib/typescript/native/NumberFlow.d.ts.map +1 -0
- package/lib/typescript/native/SymbolSlot.d.ts +19 -0
- package/lib/typescript/native/SymbolSlot.d.ts.map +1 -0
- package/lib/typescript/native/TimeFlow.d.ts +3 -0
- package/lib/typescript/native/TimeFlow.d.ts.map +1 -0
- package/lib/typescript/native/index.d.ts +3 -0
- package/lib/typescript/native/index.d.ts.map +1 -0
- package/lib/typescript/native/renderSlots.d.ts +31 -0
- package/lib/typescript/native/renderSlots.d.ts.map +1 -0
- package/lib/typescript/native/types.d.ts +36 -0
- package/lib/typescript/native/types.d.ts.map +1 -0
- package/lib/typescript/native/useMeasuredGlyphMetrics.d.ts +8 -0
- package/lib/typescript/native/useMeasuredGlyphMetrics.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/skia/DigitSlot.d.ts +35 -0
- package/lib/typescript/skia/DigitSlot.d.ts.map +1 -0
- package/lib/typescript/skia/SkiaNumberFlow.d.ts +3 -0
- package/lib/typescript/skia/SkiaNumberFlow.d.ts.map +1 -0
- package/lib/typescript/skia/SkiaTimeFlow.d.ts +3 -0
- package/lib/typescript/skia/SkiaTimeFlow.d.ts.map +1 -0
- package/lib/typescript/skia/SymbolSlot.d.ts +26 -0
- package/lib/typescript/skia/SymbolSlot.d.ts.map +1 -0
- package/lib/typescript/skia/index.d.ts +6 -0
- package/lib/typescript/skia/index.d.ts.map +1 -0
- package/lib/typescript/skia/renderSlots.d.ts +40 -0
- package/lib/typescript/skia/renderSlots.d.ts.map +1 -0
- package/lib/typescript/skia/useGlyphMetrics.d.ts +16 -0
- package/lib/typescript/skia/useGlyphMetrics.d.ts.map +1 -0
- package/lib/typescript/skia/useScrubbing.d.ts +59 -0
- package/lib/typescript/skia/useScrubbing.d.ts.map +1 -0
- package/lib/typescript/skia/useSkiaFont.d.ts +13 -0
- package/lib/typescript/skia/useSkiaFont.d.ts.map +1 -0
- package/package.json +104 -0
- package/src/core/constants.ts +20 -0
- package/src/core/intlHelpers.ts +351 -0
- package/src/core/layout.ts +108 -0
- package/src/core/mask.ts +72 -0
- package/src/core/numerals/detection.ts +112 -0
- package/src/core/numerals/digits.ts +102 -0
- package/src/core/numerals/index.ts +9 -0
- package/src/core/numerals/tables.ts +112 -0
- package/src/core/superscript.ts +27 -0
- package/src/core/timeLayout.ts +119 -0
- package/src/core/timeTypes.ts +88 -0
- package/src/core/timing.ts +60 -0
- package/src/core/types.ts +189 -0
- package/src/core/useAccessibilityAnnouncement.ts +27 -0
- package/src/core/useAnimatedX.ts +30 -0
- package/src/core/useAnimationLifecycle.ts +54 -0
- package/src/core/useCanAnimate.ts +21 -0
- package/src/core/useContinuousSpin.ts +112 -0
- package/src/core/useDebouncedWidths.ts +93 -0
- package/src/core/useDigitAnimation.ts +192 -0
- package/src/core/useFlowPipeline.ts +126 -0
- package/src/core/useFormattedValue.ts +32 -0
- package/src/core/useLayoutDiff.ts +71 -0
- package/src/core/useNumberFormatting.ts +164 -0
- package/src/core/useSlotOpacity.ts +66 -0
- package/src/core/useTimeFormatting.ts +95 -0
- package/src/core/useTimingResolution.ts +47 -0
- package/src/core/useWorkletFormatting.ts +59 -0
- package/src/core/utils.ts +149 -0
- package/src/core/warnings.ts +8 -0
- package/src/index.ts +15 -0
- package/src/native/DigitSlot.tsx +203 -0
- package/src/native/NumberFlow.tsx +287 -0
- package/src/native/SymbolSlot.tsx +68 -0
- package/src/native/TimeFlow.tsx +287 -0
- package/src/native/index.ts +2 -0
- package/src/native/renderSlots.tsx +150 -0
- package/src/native/types.ts +40 -0
- package/src/native/useMeasuredGlyphMetrics.tsx +205 -0
- package/src/skia/DigitSlot.tsx +221 -0
- package/src/skia/SkiaNumberFlow.tsx +506 -0
- package/src/skia/SkiaTimeFlow.tsx +257 -0
- package/src/skia/SymbolSlot.tsx +120 -0
- package/src/skia/index.ts +5 -0
- package/src/skia/renderSlots.tsx +180 -0
- package/src/skia/useGlyphMetrics.ts +79 -0
- package/src/skia/useScrubbing.ts +223 -0
- 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
|
+
}
|