Skip to content

Commit 11aaffe

Browse files
committed
fix: isolate Skia type imports from non-Skia entry points
src/core/types.ts and src/core/timeTypes.ts imported SkFont from @shopify/react-native-skia at the type level, leaking Skia as a transitive type dependency of the root and /native entry points. Consumers using only NumberFlow or TimeFlow hit TS2307 from tsc when Skia was not installed. Move SkiaNumberFlowProps and SkiaTimeFlowProps to src/skia/types.ts so core/ stays renderer-agnostic. Public exports are unchanged: SkiaNumberFlowProps and SkiaTimeFlowProps remain available from number-flow-react-native/skia with identical shape. Closes #12
1 parent e34062e commit 11aaffe

9 files changed

Lines changed: 149 additions & 147 deletions

File tree

.changeset/isolate-skia-types.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"number-flow-react-native": patch
3+
---
4+
5+
Stop leaking `@shopify/react-native-skia` type imports into the `/native` and root entry points. `SkiaNumberFlowProps` and `SkiaTimeFlowProps` now live in `src/skia/types.ts` instead of `src/core/`, so consumers of `NumberFlow` and `TimeFlow` no longer need Skia installed to pass `tsc`. (#12)
Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import type { SkFont } from "@shopify/react-native-skia";
21
import type { ViewStyle } from "react-native";
3-
import type { SharedValue } from "react-native-reanimated";
42
import type { NumberFlowStyle } from "../native/types";
5-
import type { AnimationBehaviorProps, Direction, TextAlign } from "./types";
3+
import type { AnimationBehaviorProps, Direction } from "./types";
64

75
interface TimeFlowBaseProps extends AnimationBehaviorProps {
86
/** Use 24-hour format. Default: true. Only applies when hours are shown. */
@@ -35,64 +33,3 @@ interface TimeFlowValueProps {
3533

3634
/** Props for TimeFlow. Accessibility is built-in: accessibilityRole="text" and accessibilityLabel are set automatically. */
3735
export type TimeFlowProps = TimeFlowBaseProps & TimeFlowValueProps;
38-
39-
interface SkiaTimeFlowBaseProps extends AnimationBehaviorProps {
40-
/** Use 24-hour format. Default: true. Only applies when hours are shown. */
41-
is24Hour?: boolean;
42-
/** Pad hours with leading zero. Default: true. "09:30" vs "9:30". */
43-
padHours?: boolean;
44-
45-
/** Force equal-width digits using percentile-based interpolation between min and max digit widths. Equivalent to `fontVariant: ['tabular-nums']` on native components. */
46-
tabularNums?: boolean;
47-
48-
/** SkFont instance from useFont(). Required; renders empty until font loads. */
49-
font: SkFont | null;
50-
/** Text color. Accepts a static string or a SharedValue for animated color transitions. Defaults to "#000000". */
51-
color?: string | SharedValue<string>;
52-
/** X position within the Canvas. Defaults to 0. */
53-
x?: number;
54-
/** Y position within the Canvas (baseline). Defaults to 0. */
55-
y?: number;
56-
/** Available width for alignment calculations. Defaults to 0. */
57-
width?: number;
58-
/** Text alignment within the available width. Defaults to "start" (left in LTR, right in RTL). Accepts "start"/"end" for direction-aware alignment. */
59-
textAlign?: TextAlign;
60-
/** Overrides automatic RTL detection from I18nManager.isRTL. Omit to follow the system setting. Controls text alignment only; bidi visual reordering of characters applies to NumberFlow/SkiaNumberFlow, not SkiaTimeFlow. */
61-
direction?: Direction;
62-
/** Parent opacity (SharedValue for animation coordination) */
63-
opacity?: SharedValue<number>;
64-
}
65-
66-
type SkiaTimeFlowValueProps =
67-
| {
68-
/** Hours value (0-23). Omit to hide the hours segment. */
69-
hours?: number;
70-
/** Minutes value (0-59). Required when using direct time values. */
71-
minutes: number;
72-
/** Seconds value (0-59). Omit to hide the seconds segment. */
73-
seconds?: number;
74-
/** Centiseconds value (0-99). Omit to hide. Requires seconds to be set. Displayed as ".CC" after seconds. */
75-
centiseconds?: number;
76-
/** Unix timestamp in ms. Extracts hours/minutes/seconds automatically. */
77-
timestamp?: number;
78-
/** Timezone offset in ms for timestamp mode. */
79-
timezoneOffset?: number;
80-
sharedValue?: never;
81-
}
82-
| {
83-
hours?: never;
84-
minutes?: never;
85-
seconds?: never;
86-
centiseconds?: never;
87-
timestamp?: never;
88-
timezoneOffset?: never;
89-
/** Worklet-driven pre-formatted time string (e.g. "14:30", "2:30 PM"). */
90-
sharedValue: SharedValue<string>;
91-
};
92-
93-
/**
94-
* Props for SkiaTimeFlow.
95-
* Value changes are auto-announced for screen reader users.
96-
* For VoiceOver/TalkBack focus-based reading, set `accessibilityLabel` on the parent Canvas.
97-
*/
98-
export type SkiaTimeFlowProps = SkiaTimeFlowBaseProps & SkiaTimeFlowValueProps;

packages/number-flow-react-native/src/core/types.ts

Lines changed: 1 addition & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { SkFont } from "@shopify/react-native-skia";
2-
import type { EasingFunction, SharedValue } from "react-native-reanimated";
1+
import type { EasingFunction } from "react-native-reanimated";
32

43
export interface TimingConfig {
54
/** Duration in milliseconds */
@@ -133,73 +132,3 @@ export interface DigitConstraint {
133132
* Positions not listed default to { max: 9 } (standard 0-9 wheel).
134133
*/
135134
export type DigitsProp = Record<number, DigitConstraint>;
136-
137-
/** Value props, mutually exclusive: provide `value` (JS-driven) or `sharedValue` (worklet-driven), not both. */
138-
type SkiaNumberFlowValueProps =
139-
| {
140-
/** JS-thread numeric value. Mutually exclusive with sharedValue. */
141-
value: number;
142-
/** Intl.NumberFormatOptions for value formatting */
143-
format?: Intl.NumberFormatOptions;
144-
/** Locale(s) for Intl.NumberFormat */
145-
locales?: Intl.LocalesArgument;
146-
sharedValue?: never;
147-
}
148-
| {
149-
value?: never;
150-
format?: never;
151-
locales?: never;
152-
/** Worklet-driven pre-formatted string. Mutually exclusive with value. */
153-
sharedValue: SharedValue<string>;
154-
};
155-
156-
interface SkiaNumberFlowBaseProps extends AnimationBehaviorProps {
157-
/** Force equal-width digits using percentile-based interpolation between min and max digit widths. Equivalent to `fontVariant: ['tabular-nums']` on native components. */
158-
tabularNums?: boolean;
159-
160-
/** SkFont instance from useFont(). Required; renders empty until font loads. */
161-
font: SkFont | null;
162-
/** Text color. Accepts a static string or a SharedValue for animated color transitions. Defaults to "#000000". */
163-
color?: string | SharedValue<string>;
164-
/** X position within the Canvas. Defaults to 0. */
165-
x?: number;
166-
/** Y position within the Canvas (baseline). Defaults to 0. */
167-
y?: number;
168-
/** Available width for alignment calculations. Defaults to 0. */
169-
width?: number;
170-
/** Text alignment within the available width. Defaults to "start" (left in LTR, right in RTL). Accepts "start"/"end" for direction-aware alignment. */
171-
textAlign?: TextAlign;
172-
/** Overrides automatic RTL detection from I18nManager.isRTL. Omit to follow the system setting. In sharedValue/scrubbing mode, controls alignment only (bidi visual reordering requires value mode, since the SharedValue string lacks the bidi marks that Intl.NumberFormat embeds). */
173-
direction?: Direction;
174-
/** Static string prepended before the number */
175-
prefix?: string;
176-
/** Static string appended after the number */
177-
suffix?: string;
178-
/** Parent opacity (SharedValue for animation coordination) */
179-
opacity?: SharedValue<number>;
180-
181-
/**
182-
* Per-position digit constraints. Maps integer position (0=ones, 1=tens, ...)
183-
* to { max: N } where N is the highest digit value (inclusive).
184-
* Example: { 1: { max: 5 } } for a wheel where tens go 0-5.
185-
*/
186-
digits?: DigitsProp;
187-
188-
/**
189-
* Controls digit width during worklet-driven scrubbing (when sharedValue is active).
190-
* Value between 0 and 1 representing the percentile between min and max digit width.
191-
* - 0: use narrowest digit width (tightest, may clip wide digits)
192-
* - 0.5: use average digit width
193-
* - 1: use widest digit width (no clipping, but wider spacing)
194-
* Defaults to 0.75 (75th percentile) for a good balance.
195-
* Only affects digits; symbols like "." keep their natural width.
196-
*/
197-
scrubDigitWidthPercentile?: number;
198-
}
199-
200-
/**
201-
* Props for SkiaNumberFlow.
202-
* Value changes are auto-announced for screen reader users.
203-
* For VoiceOver/TalkBack focus-based reading, set `accessibilityLabel` on the parent Canvas.
204-
*/
205-
export type SkiaNumberFlowProps = SkiaNumberFlowBaseProps & SkiaNumberFlowValueProps;

packages/number-flow-react-native/src/skia/DigitSlot.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import {
88
} from "react-native-reanimated";
99
import { DIGIT_COUNT, SUPERSCRIPT_SCALE } from "../core/constants";
1010
import { getSuperscriptTransform } from "../core/superscript";
11-
import type { GlyphMetrics, SkiaNumberFlowProps, TimingConfig, Trend } from "../core/types";
11+
import type { GlyphMetrics, TimingConfig, Trend } from "../core/types";
1212
import { useAnimatedX } from "../core/useAnimatedX";
1313
import { useDigitAnimation } from "../core/useDigitAnimation";
1414
import { signedDigitOffset } from "../core/utils";
15+
import type { SkiaNumberFlowProps } from "./types";
1516

1617
interface DigitSlotProps {
1718
metrics: GlyphMetrics;

packages/number-flow-react-native/src/skia/SkiaNumberFlow.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,14 @@ import { resolveDirection, resolveTextAlign } from "../core/direction";
66
import { getFormatCharacters, parseFormattedNumber } from "../core/intlHelpers";
77
import { type CharLayout, computeKeyedLayout } from "../core/layout";
88
import { detectNumberingSystem, getDigitStrings, getZeroCodePoint } from "../core/numerals";
9-
import type {
10-
GlyphMetrics,
11-
KeyedPart,
12-
ResolvedTextAlign,
13-
SkiaNumberFlowProps,
14-
} from "../core/types";
9+
import type { GlyphMetrics, KeyedPart, ResolvedTextAlign } from "../core/types";
1510
import { useAccessibilityAnnouncement } from "../core/useAccessibilityAnnouncement";
1611
import { useFlowPipeline } from "../core/useFlowPipeline";
1712
import { rawPartsToKeyedParts, useNumberFormatting } from "../core/useNumberFormatting";
1813
import { getDigitCount } from "../core/utils";
1914
import { warnOnce } from "../core/warnings";
2015
import { renderSlots } from "./renderSlots";
16+
import type { SkiaNumberFlowProps } from "./types";
2117
import { useGlyphMetrics } from "./useGlyphMetrics";
2218
import { useScrubbingBridge } from "./useScrubbingBridge";
2319
import { useScrubbingLayout } from "./useScrubbingLayout";

packages/number-flow-react-native/src/skia/SkiaTimeFlow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { MASK_WIDTH_RATIO } from "../core/constants";
44
import { resolveDirection, resolveTextAlign } from "../core/direction";
55
import { computeKeyedLayout } from "../core/layout";
66
import { computeTimeStringLayout } from "../core/timeLayout";
7-
import type { SkiaTimeFlowProps } from "../core/timeTypes";
87
import { useAccessibilityAnnouncement } from "../core/useAccessibilityAnnouncement";
98
import { useFlowPipeline } from "../core/useFlowPipeline";
109
import { useTimeFormatting } from "../core/useTimeFormatting";
1110
import { useWorkletFormatting } from "../core/useWorkletFormatting";
1211
import { TIME_DIGIT_COUNTS } from "../core/utils";
1312
import { warnOnce } from "../core/warnings";
1413
import { renderSlots } from "./renderSlots";
14+
import type { SkiaTimeFlowProps } from "./types";
1515
import { useGlyphMetrics } from "./useGlyphMetrics";
1616

1717
export const SkiaTimeFlow = ({

packages/number-flow-react-native/src/skia/SymbolSlot.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
withTiming,
1010
} from "react-native-reanimated";
1111
import { getSuperscriptTransform } from "../core/superscript";
12-
import type { SkiaNumberFlowProps, TimingConfig } from "../core/types";
12+
import type { TimingConfig } from "../core/types";
1313
import { useAnimatedX } from "../core/useAnimatedX";
1414
import { useSlotOpacity } from "../core/useSlotOpacity";
15+
import type { SkiaNumberFlowProps } from "./types";
1516

1617
// Timing for smooth prefix/suffix animation during scrubbing
1718
const WORKLET_X_ANIMATION_MS = 150;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
export type { SkiaTimeFlowProps } from "../core/timeTypes";
2-
export type { SkiaNumberFlowProps } from "../core/types";
31
export { SkiaNumberFlow } from "./SkiaNumberFlow";
42
export { SkiaTimeFlow } from "./SkiaTimeFlow";
3+
export type { SkiaNumberFlowProps, SkiaTimeFlowProps } from "./types";
54
export { useSkiaFont } from "./useSkiaFont";
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import type { SkFont } from "@shopify/react-native-skia";
2+
import type { SharedValue } from "react-native-reanimated";
3+
import type { AnimationBehaviorProps, DigitsProp, Direction, TextAlign } from "../core/types";
4+
5+
/** Value props, mutually exclusive: provide `value` (JS-driven) or `sharedValue` (worklet-driven), not both. */
6+
type SkiaNumberFlowValueProps =
7+
| {
8+
/** JS-thread numeric value. Mutually exclusive with sharedValue. */
9+
value: number;
10+
/** Intl.NumberFormatOptions for value formatting */
11+
format?: Intl.NumberFormatOptions;
12+
/** Locale(s) for Intl.NumberFormat */
13+
locales?: Intl.LocalesArgument;
14+
sharedValue?: never;
15+
}
16+
| {
17+
value?: never;
18+
format?: never;
19+
locales?: never;
20+
/** Worklet-driven pre-formatted string. Mutually exclusive with value. */
21+
sharedValue: SharedValue<string>;
22+
};
23+
24+
interface SkiaNumberFlowBaseProps extends AnimationBehaviorProps {
25+
/** Force equal-width digits using percentile-based interpolation between min and max digit widths. Equivalent to `fontVariant: ['tabular-nums']` on native components. */
26+
tabularNums?: boolean;
27+
28+
/** SkFont instance from useFont(). Required; renders empty until font loads. */
29+
font: SkFont | null;
30+
/** Text color. Accepts a static string or a SharedValue for animated color transitions. Defaults to "#000000". */
31+
color?: string | SharedValue<string>;
32+
/** X position within the Canvas. Defaults to 0. */
33+
x?: number;
34+
/** Y position within the Canvas (baseline). Defaults to 0. */
35+
y?: number;
36+
/** Available width for alignment calculations. Defaults to 0. */
37+
width?: number;
38+
/** Text alignment within the available width. Defaults to "start" (left in LTR, right in RTL). Accepts "start"/"end" for direction-aware alignment. */
39+
textAlign?: TextAlign;
40+
/** Overrides automatic RTL detection from I18nManager.isRTL. Omit to follow the system setting. In sharedValue/scrubbing mode, controls alignment only (bidi visual reordering requires value mode, since the SharedValue string lacks the bidi marks that Intl.NumberFormat embeds). */
41+
direction?: Direction;
42+
/** Static string prepended before the number */
43+
prefix?: string;
44+
/** Static string appended after the number */
45+
suffix?: string;
46+
/** Parent opacity (SharedValue for animation coordination) */
47+
opacity?: SharedValue<number>;
48+
49+
/**
50+
* Per-position digit constraints. Maps integer position (0=ones, 1=tens, ...)
51+
* to { max: N } where N is the highest digit value (inclusive).
52+
* Example: { 1: { max: 5 } } for a wheel where tens go 0-5.
53+
*/
54+
digits?: DigitsProp;
55+
56+
/**
57+
* Controls digit width during worklet-driven scrubbing (when sharedValue is active).
58+
* Value between 0 and 1 representing the percentile between min and max digit width.
59+
* - 0: use narrowest digit width (tightest, may clip wide digits)
60+
* - 0.5: use average digit width
61+
* - 1: use widest digit width (no clipping, but wider spacing)
62+
* Defaults to 0.75 (75th percentile) for a good balance.
63+
* Only affects digits; symbols like "." keep their natural width.
64+
*/
65+
scrubDigitWidthPercentile?: number;
66+
}
67+
68+
/**
69+
* Props for SkiaNumberFlow.
70+
* Value changes are auto-announced for screen reader users.
71+
* For VoiceOver/TalkBack focus-based reading, set `accessibilityLabel` on the parent Canvas.
72+
*/
73+
export type SkiaNumberFlowProps = SkiaNumberFlowBaseProps & SkiaNumberFlowValueProps;
74+
75+
interface SkiaTimeFlowBaseProps extends AnimationBehaviorProps {
76+
/** Use 24-hour format. Default: true. Only applies when hours are shown. */
77+
is24Hour?: boolean;
78+
/** Pad hours with leading zero. Default: true. "09:30" vs "9:30". */
79+
padHours?: boolean;
80+
81+
/** Force equal-width digits using percentile-based interpolation between min and max digit widths. Equivalent to `fontVariant: ['tabular-nums']` on native components. */
82+
tabularNums?: boolean;
83+
84+
/** SkFont instance from useFont(). Required; renders empty until font loads. */
85+
font: SkFont | null;
86+
/** Text color. Accepts a static string or a SharedValue for animated color transitions. Defaults to "#000000". */
87+
color?: string | SharedValue<string>;
88+
/** X position within the Canvas. Defaults to 0. */
89+
x?: number;
90+
/** Y position within the Canvas (baseline). Defaults to 0. */
91+
y?: number;
92+
/** Available width for alignment calculations. Defaults to 0. */
93+
width?: number;
94+
/** Text alignment within the available width. Defaults to "start" (left in LTR, right in RTL). Accepts "start"/"end" for direction-aware alignment. */
95+
textAlign?: TextAlign;
96+
/** Overrides automatic RTL detection from I18nManager.isRTL. Omit to follow the system setting. Controls text alignment only; bidi visual reordering of characters applies to NumberFlow/SkiaNumberFlow, not SkiaTimeFlow. */
97+
direction?: Direction;
98+
/** Parent opacity (SharedValue for animation coordination) */
99+
opacity?: SharedValue<number>;
100+
}
101+
102+
type SkiaTimeFlowValueProps =
103+
| {
104+
/** Hours value (0-23). Omit to hide the hours segment. */
105+
hours?: number;
106+
/** Minutes value (0-59). Required when using direct time values. */
107+
minutes: number;
108+
/** Seconds value (0-59). Omit to hide the seconds segment. */
109+
seconds?: number;
110+
/** Centiseconds value (0-99). Omit to hide. Requires seconds to be set. Displayed as ".CC" after seconds. */
111+
centiseconds?: number;
112+
/** Unix timestamp in ms. Extracts hours/minutes/seconds automatically. */
113+
timestamp?: number;
114+
/** Timezone offset in ms for timestamp mode. */
115+
timezoneOffset?: number;
116+
sharedValue?: never;
117+
}
118+
| {
119+
hours?: never;
120+
minutes?: never;
121+
seconds?: never;
122+
centiseconds?: never;
123+
timestamp?: never;
124+
timezoneOffset?: never;
125+
/** Worklet-driven pre-formatted time string (e.g. "14:30", "2:30 PM"). */
126+
sharedValue: SharedValue<string>;
127+
};
128+
129+
/**
130+
* Props for SkiaTimeFlow.
131+
* Value changes are auto-announced for screen reader users.
132+
* For VoiceOver/TalkBack focus-based reading, set `accessibilityLabel` on the parent Canvas.
133+
*/
134+
export type SkiaTimeFlowProps = SkiaTimeFlowBaseProps & SkiaTimeFlowValueProps;

0 commit comments

Comments
 (0)