rnwind 0.0.3 → 0.0.5

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 (134) hide show
  1. package/lib/cjs/core/normalize-classname.cjs +25 -0
  2. package/lib/cjs/core/normalize-classname.cjs.map +1 -0
  3. package/lib/cjs/core/normalize-classname.d.ts +10 -0
  4. package/lib/cjs/core/style-builder/build-style.cjs +258 -58
  5. package/lib/cjs/core/style-builder/build-style.cjs.map +1 -1
  6. package/lib/cjs/core/style-builder/build-style.d.ts +6 -1
  7. package/lib/cjs/core/style-builder/union-builder.cjs +37 -3
  8. package/lib/cjs/core/style-builder/union-builder.cjs.map +1 -1
  9. package/lib/cjs/core/style-builder/union-builder.d.ts +21 -1
  10. package/lib/cjs/metro/css-imports.cjs +81 -0
  11. package/lib/cjs/metro/css-imports.cjs.map +1 -0
  12. package/lib/cjs/metro/css-imports.d.ts +8 -0
  13. package/lib/cjs/metro/dts.cjs +7 -16
  14. package/lib/cjs/metro/dts.cjs.map +1 -1
  15. package/lib/cjs/metro/dts.d.ts +2 -4
  16. package/lib/cjs/metro/state.cjs +38 -86
  17. package/lib/cjs/metro/state.cjs.map +1 -1
  18. package/lib/cjs/metro/state.d.ts +8 -25
  19. package/lib/cjs/metro/transformer.cjs +193 -34
  20. package/lib/cjs/metro/transformer.cjs.map +1 -1
  21. package/lib/cjs/metro/with-config.cjs +2 -2
  22. package/lib/cjs/metro/with-config.cjs.map +1 -1
  23. package/lib/cjs/metro/with-config.d.ts +11 -26
  24. package/lib/cjs/metro/wrap-imports.cjs +273 -0
  25. package/lib/cjs/metro/wrap-imports.cjs.map +1 -0
  26. package/lib/cjs/metro/wrap-imports.d.ts +26 -0
  27. package/lib/cjs/runtime/components/rnwind-provider.cjs +0 -17
  28. package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -1
  29. package/lib/cjs/runtime/components/rnwind-provider.d.ts +0 -14
  30. package/lib/cjs/runtime/hooks/use-css.cjs +16 -10
  31. package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -1
  32. package/lib/cjs/runtime/hooks/use-css.d.ts +15 -9
  33. package/lib/cjs/runtime/index.cjs +11 -13
  34. package/lib/cjs/runtime/index.cjs.map +1 -1
  35. package/lib/cjs/runtime/index.d.ts +4 -9
  36. package/lib/cjs/runtime/lookup-css.cjs +10 -0
  37. package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
  38. package/lib/cjs/runtime/lookup-css.d.ts +7 -0
  39. package/lib/cjs/runtime/resolve.cjs +348 -0
  40. package/lib/cjs/runtime/resolve.cjs.map +1 -0
  41. package/lib/cjs/runtime/resolve.d.ts +61 -0
  42. package/lib/cjs/runtime/wrap.cjs +254 -0
  43. package/lib/cjs/runtime/wrap.cjs.map +1 -0
  44. package/lib/cjs/runtime/wrap.d.ts +37 -0
  45. package/lib/cjs/testing/index.cjs +81 -50
  46. package/lib/cjs/testing/index.cjs.map +1 -1
  47. package/lib/esm/core/normalize-classname.d.ts +10 -0
  48. package/lib/esm/core/normalize-classname.mjs +23 -0
  49. package/lib/esm/core/normalize-classname.mjs.map +1 -0
  50. package/lib/esm/core/style-builder/build-style.d.ts +6 -1
  51. package/lib/esm/core/style-builder/build-style.mjs +258 -58
  52. package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
  53. package/lib/esm/core/style-builder/union-builder.d.ts +21 -1
  54. package/lib/esm/core/style-builder/union-builder.mjs +37 -3
  55. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
  56. package/lib/esm/metro/css-imports.d.ts +8 -0
  57. package/lib/esm/metro/css-imports.mjs +79 -0
  58. package/lib/esm/metro/css-imports.mjs.map +1 -0
  59. package/lib/esm/metro/dts.d.ts +2 -4
  60. package/lib/esm/metro/dts.mjs +7 -16
  61. package/lib/esm/metro/dts.mjs.map +1 -1
  62. package/lib/esm/metro/state.d.ts +8 -25
  63. package/lib/esm/metro/state.mjs +39 -85
  64. package/lib/esm/metro/state.mjs.map +1 -1
  65. package/lib/esm/metro/transformer.mjs +194 -35
  66. package/lib/esm/metro/transformer.mjs.map +1 -1
  67. package/lib/esm/metro/with-config.d.ts +11 -26
  68. package/lib/esm/metro/with-config.mjs +2 -2
  69. package/lib/esm/metro/with-config.mjs.map +1 -1
  70. package/lib/esm/metro/wrap-imports.d.ts +26 -0
  71. package/lib/esm/metro/wrap-imports.mjs +250 -0
  72. package/lib/esm/metro/wrap-imports.mjs.map +1 -0
  73. package/lib/esm/runtime/components/rnwind-provider.d.ts +0 -14
  74. package/lib/esm/runtime/components/rnwind-provider.mjs +1 -17
  75. package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -1
  76. package/lib/esm/runtime/hooks/use-css.d.ts +15 -9
  77. package/lib/esm/runtime/hooks/use-css.mjs +16 -10
  78. package/lib/esm/runtime/hooks/use-css.mjs.map +1 -1
  79. package/lib/esm/runtime/index.d.ts +4 -9
  80. package/lib/esm/runtime/index.mjs +4 -4
  81. package/lib/esm/runtime/index.mjs.map +1 -1
  82. package/lib/esm/runtime/lookup-css.d.ts +7 -0
  83. package/lib/esm/runtime/lookup-css.mjs +10 -1
  84. package/lib/esm/runtime/lookup-css.mjs.map +1 -1
  85. package/lib/esm/runtime/resolve.d.ts +61 -0
  86. package/lib/esm/runtime/resolve.mjs +341 -0
  87. package/lib/esm/runtime/resolve.mjs.map +1 -0
  88. package/lib/esm/runtime/wrap.d.ts +37 -0
  89. package/lib/esm/runtime/wrap.mjs +251 -0
  90. package/lib/esm/runtime/wrap.mjs.map +1 -0
  91. package/lib/esm/testing/index.mjs +84 -53
  92. package/lib/esm/testing/index.mjs.map +1 -1
  93. package/package.json +2 -1
  94. package/src/core/normalize-classname.ts +19 -0
  95. package/src/core/style-builder/build-style.ts +286 -55
  96. package/src/core/style-builder/union-builder.ts +36 -3
  97. package/src/metro/css-imports.ts +75 -0
  98. package/src/metro/dts.ts +7 -19
  99. package/src/metro/state.ts +38 -83
  100. package/src/metro/transformer.ts +190 -34
  101. package/src/metro/with-config.ts +13 -28
  102. package/src/metro/wrap-imports.ts +260 -0
  103. package/src/runtime/components/rnwind-provider.tsx +0 -17
  104. package/src/runtime/hooks/use-css.ts +17 -11
  105. package/src/runtime/index.ts +3 -26
  106. package/src/runtime/lookup-css.ts +10 -0
  107. package/src/runtime/resolve.ts +381 -0
  108. package/src/runtime/wrap.tsx +267 -0
  109. package/src/testing/index.ts +106 -56
  110. package/lib/cjs/core/parser/text-truncate.cjs +0 -78
  111. package/lib/cjs/core/parser/text-truncate.cjs.map +0 -1
  112. package/lib/cjs/metro/transform-ast.cjs +0 -1472
  113. package/lib/cjs/metro/transform-ast.cjs.map +0 -1
  114. package/lib/cjs/metro/transform-ast.d.ts +0 -88
  115. package/lib/cjs/runtime/haptics.cjs +0 -113
  116. package/lib/cjs/runtime/haptics.cjs.map +0 -1
  117. package/lib/cjs/runtime/haptics.d.ts +0 -48
  118. package/lib/cjs/runtime/interactive-box.cjs +0 -35
  119. package/lib/cjs/runtime/interactive-box.cjs.map +0 -1
  120. package/lib/cjs/runtime/interactive-box.d.ts +0 -40
  121. package/lib/esm/core/parser/text-truncate.mjs +0 -75
  122. package/lib/esm/core/parser/text-truncate.mjs.map +0 -1
  123. package/lib/esm/metro/transform-ast.d.ts +0 -88
  124. package/lib/esm/metro/transform-ast.mjs +0 -1451
  125. package/lib/esm/metro/transform-ast.mjs.map +0 -1
  126. package/lib/esm/runtime/haptics.d.ts +0 -48
  127. package/lib/esm/runtime/haptics.mjs +0 -110
  128. package/lib/esm/runtime/haptics.mjs.map +0 -1
  129. package/lib/esm/runtime/interactive-box.d.ts +0 -40
  130. package/lib/esm/runtime/interactive-box.mjs +0 -33
  131. package/lib/esm/runtime/interactive-box.mjs.map +0 -1
  132. package/src/metro/transform-ast.ts +0 -1729
  133. package/src/runtime/haptics.ts +0 -120
  134. package/src/runtime/interactive-box.tsx +0 -57
@@ -0,0 +1,37 @@
1
+ import { type ComponentType } from 'react';
2
+ /**
3
+ * Wrap a component so its `className` prop resolves to RN `style` (plus
4
+ * gradient / truncate props and haptic dispatch) at render — no matter
5
+ * how className arrived: written directly, spread through `{...rest}`, or
6
+ * forwarded down custom wrappers. The returned component is hook-free; it
7
+ * dispatches to a plain or interactive leaf so non-interactive elements
8
+ * never pay for press/focus state. `ref` (a normal prop in React 19) and
9
+ * all other props forward untouched.
10
+ * @example
11
+ * ```tsx
12
+ * const Pressable = wrap(RNPressable)
13
+ * <Pressable className="active:bg-sky-700 px-4 haptic-light" onPress={fn} />
14
+ * ```
15
+ * @param Component Any component accepting a `style` prop.
16
+ * @returns A component accepting `className`.
17
+ */
18
+ export declare function wrap<P>(Component: ComponentType<P>): ComponentType<P & {
19
+ className?: string;
20
+ }>;
21
+ /**
22
+ * Wrap a component NAMESPACE (a default/namespace import like reanimated's
23
+ * `Animated`) so member access — `Animated.View`, `Animated.ScrollView` —
24
+ * returns a `wrap()`-ed component whose `className` resolves at render.
25
+ * Returns a Proxy: component members are wrapped lazily and memoised so
26
+ * each access yields the SAME wrapped component (stable identity — React
27
+ * would remount otherwise). Non-component members (`createAnimatedComponent`,
28
+ * config objects) pass straight through.
29
+ * @example
30
+ * ```tsx
31
+ * const Animated = wrapNamespace(RNReanimated)
32
+ * <Animated.View className="enter-fade" />
33
+ * ```
34
+ * @param namespace The imported namespace object.
35
+ * @returns A Proxy that wraps component members on access.
36
+ */
37
+ export declare function wrapNamespace<T extends object>(namespace: T): T;
@@ -0,0 +1,251 @@
1
+ import { createElement, useRef, useEffect } from 'react';
2
+ import { chainPress, chainFocus } from './chain-handlers.mjs';
3
+ import { useInteract } from './hooks/use-interact.mjs';
4
+ import { useRnwind } from './components/rnwind-provider.mjs';
5
+ import { resolve } from './resolve.mjs';
6
+
7
+ /** Matches a leading `active:` / `focus:` variant token (`\b` excludes `inactive:`). */
8
+ const INTERACTIVE_VARIANT = /\b(?:active|focus):/;
9
+ /** One-shot guard so the missing-`onHaptics` warning logs once per session. */
10
+ let warnedMissingOnHaptics = false;
11
+ /**
12
+ * Dev-only warning when a className carries a haptic utility but no
13
+ * `onHaptics` dispatcher is wired on the nearest `<RnwindProvider>` — the
14
+ * haptic would silently drop otherwise. Fires once per session.
15
+ * @param onHaptics The dispatcher from context (or undefined).
16
+ * @param haptics The resolved haptic requests (or undefined).
17
+ */
18
+ function warnIfHapticsUnwired(onHaptics, haptics) {
19
+ if (onHaptics || !haptics || haptics.length === 0)
20
+ return;
21
+ const isDevelopment = typeof __DEV__ === 'undefined' || __DEV__;
22
+ if (!isDevelopment || warnedMissingOnHaptics)
23
+ return;
24
+ warnedMissingOnHaptics = true;
25
+ // eslint-disable-next-line no-console
26
+ console.warn('rnwind: a `haptic-*` utility resolved but no `onHaptics` callback is wired on <RnwindProvider>. ' +
27
+ 'Pass `onHaptics` on the provider to forward the request to expo-haptics (or any library).');
28
+ }
29
+ /**
30
+ * Whether a className needs press/focus state tracking.
31
+ * @param className Raw className string.
32
+ * @returns True when an `active:` / `focus:` variant is present.
33
+ */
34
+ function hasInteractiveVariant(className) {
35
+ return INTERACTIVE_VARIANT.test(className);
36
+ }
37
+ /**
38
+ * Best-effort display name for the wrapped component.
39
+ * @param component Component being wrapped.
40
+ * @returns Its `displayName`, `name`, or `'Component'`.
41
+ */
42
+ function displayNameOf(component) {
43
+ const named = component;
44
+ return named.displayName ?? named.name ?? 'Component';
45
+ }
46
+ /**
47
+ * Fire the `mount`-trigger haptics once, after the element mounts. Snaps
48
+ * the resolved requests + dispatcher at mount via a `useRef` initializer
49
+ * (evaluated only on the first render), so an unstable inline `onHaptics`
50
+ * doesn't re-fire them and no ref is written during render.
51
+ * @param resolved The resolved className (carries any haptic requests).
52
+ * @param onHaptics The dispatcher from context (or undefined).
53
+ */
54
+ function useMountHaptics(resolved, onHaptics) {
55
+ const mount = useRef({ resolved, onHaptics });
56
+ useEffect(() => {
57
+ const { resolved: current, onHaptics: dispatch } = mount.current;
58
+ if (!dispatch || !current.haptics)
59
+ return;
60
+ for (const entry of current.haptics)
61
+ if (entry.trigger === 'mount')
62
+ dispatch(entry.request, 'mount');
63
+ }, []);
64
+ }
65
+ /** Suffix marking a secondary class-prop (`contentContainerClassName`, …). */
66
+ const CLASSNAME_SUFFIX = 'ClassName';
67
+ /**
68
+ * Resolve every secondary `<prefix>ClassName` prop (e.g.
69
+ * `contentContainerClassName` on a ScrollView / FlatList) into its
70
+ * matching `<prefix>Style`, in place. Any existing `<prefix>Style` is
71
+ * appended last (caller wins). The original `*ClassName` prop is deleted
72
+ * so RN never sees an unknown attribute. The primary `className` is
73
+ * handled separately by the leaf and never reaches here.
74
+ * @param props Mutable prop object being assembled for the host.
75
+ * @param state Rnwind context for resolution.
76
+ */
77
+ function applyContainerClassNames(props, state) {
78
+ for (const key of Object.keys(props)) {
79
+ if (!key.endsWith(CLASSNAME_SUFFIX))
80
+ continue;
81
+ const value = props[key];
82
+ if (typeof value !== 'string')
83
+ continue;
84
+ const styleKey = `${key.slice(0, -CLASSNAME_SUFFIX.length)}Style`;
85
+ props[styleKey] = resolve(value, state, props[styleKey]).style;
86
+ delete props[key];
87
+ }
88
+ }
89
+ /**
90
+ * Build the props for the wrapped host: resolved `style`, gradient
91
+ * (`colors`/`start`/`end`), truncate (`numberOfLines`/`ellipsizeMode`),
92
+ * secondary `<prefix>ClassName` → `<prefix>Style`, and a chained
93
+ * `onPressIn` that fires press-trigger haptics. Unknown props on a host
94
+ * are simply ignored by RN, so this stays generic.
95
+ * @param rest Forwarded props (incl. `ref`).
96
+ * @param resolved Resolved className result.
97
+ * @param state Rnwind context — used to resolve secondary class props.
98
+ * @param onHaptics Dispatcher from context.
99
+ * @param userOnPressIn Caller-supplied onPressIn to chain after the haptic.
100
+ * @returns The merged prop object for `createElement`.
101
+ */
102
+ function buildProps(rest, resolved, state, onHaptics, userOnPressIn) {
103
+ warnIfHapticsUnwired(onHaptics, resolved.haptics);
104
+ const props = { ...rest, style: resolved.style };
105
+ applyContainerClassNames(props, state);
106
+ if (resolved.colors) {
107
+ props.colors = resolved.colors;
108
+ props.start = resolved.start;
109
+ props.end = resolved.end;
110
+ }
111
+ if (resolved.numberOfLines !== undefined) {
112
+ props.numberOfLines = resolved.numberOfLines;
113
+ if (resolved.ellipsizeMode !== undefined)
114
+ props.ellipsizeMode = resolved.ellipsizeMode;
115
+ }
116
+ const pressHaptics = onHaptics && resolved.haptics?.filter((entry) => entry.trigger === 'pressIn');
117
+ if (pressHaptics && pressHaptics.length > 0) {
118
+ const previous = userOnPressIn;
119
+ props.onPressIn = (event) => {
120
+ for (const entry of pressHaptics)
121
+ onHaptics(entry.request, 'pressIn');
122
+ previous?.(event);
123
+ };
124
+ }
125
+ else if (userOnPressIn !== undefined) {
126
+ props.onPressIn = userOnPressIn;
127
+ }
128
+ return props;
129
+ }
130
+ /**
131
+ * Non-interactive leaf: resolve className → style (+ features) and
132
+ * forward. One context read, one molecule/atom resolve.
133
+ * @param props Leaf props.
134
+ * @param props.as
135
+ * @param props.className
136
+ * @param props.style
137
+ * @param props.onPressIn
138
+ * @returns The rendered `as` element.
139
+ */
140
+ function PlainLeaf({ as: As, className, style, onPressIn, ...rest }) {
141
+ const state = useRnwind();
142
+ const resolved = resolve(className, state, style);
143
+ useMountHaptics(resolved, state.onHaptics);
144
+ return createElement(As, buildProps(rest, resolved, state, state.onHaptics, onPressIn));
145
+ }
146
+ /**
147
+ * Interactive leaf: tracks press/focus via `useInteract()`, feeds it into
148
+ * `resolve` so `active:`/`focus:` atoms apply, and chains the
149
+ * press/focus handlers.
150
+ * @param props Leaf props.
151
+ * @param props.as
152
+ * @param props.className
153
+ * @param props.style
154
+ * @param props.onPressIn
155
+ * @param props.onPressOut
156
+ * @param props.onFocus
157
+ * @param props.onBlur
158
+ * @returns The rendered `as` element with interactive wiring.
159
+ */
160
+ function InteractiveLeaf({ as: As, className, style, onPressIn, onPressOut, onFocus, onBlur, ...rest }) {
161
+ const state = useRnwind();
162
+ const interact = useInteract();
163
+ const resolved = resolve(className, state, style, interact.state);
164
+ useMountHaptics(resolved, state.onHaptics);
165
+ const props = buildProps(rest, resolved, state, state.onHaptics, onPressIn);
166
+ props.onPressIn = chainPress(props.onPressIn, interact.onPressIn);
167
+ props.onPressOut = chainPress(onPressOut, interact.onPressOut);
168
+ props.onFocus = chainFocus(onFocus, interact.onFocus);
169
+ props.onBlur = chainFocus(onBlur, interact.onBlur);
170
+ return createElement(As, props);
171
+ }
172
+ /**
173
+ * Wrap a component so its `className` prop resolves to RN `style` (plus
174
+ * gradient / truncate props and haptic dispatch) at render — no matter
175
+ * how className arrived: written directly, spread through `{...rest}`, or
176
+ * forwarded down custom wrappers. The returned component is hook-free; it
177
+ * dispatches to a plain or interactive leaf so non-interactive elements
178
+ * never pay for press/focus state. `ref` (a normal prop in React 19) and
179
+ * all other props forward untouched.
180
+ * @example
181
+ * ```tsx
182
+ * const Pressable = wrap(RNPressable)
183
+ * <Pressable className="active:bg-sky-700 px-4 haptic-light" onPress={fn} />
184
+ * ```
185
+ * @param Component Any component accepting a `style` prop.
186
+ * @returns A component accepting `className`.
187
+ */
188
+ function wrap(Component) {
189
+ const as = Component;
190
+ /**
191
+ * The wrapped component — hook-free dispatcher to a leaf.
192
+ * @param props Forwarded props with `className` intercepted.
193
+ * @param props.className
194
+ * @returns The rendered leaf.
195
+ */
196
+ function RnwindWrapped({ className, ...rest }) {
197
+ if (className !== undefined && hasInteractiveVariant(className)) {
198
+ return createElement(InteractiveLeaf, { as, className, ...rest });
199
+ }
200
+ return createElement(PlainLeaf, { as, className, ...rest });
201
+ }
202
+ RnwindWrapped.displayName = `wrap(${displayNameOf(Component)})`;
203
+ return RnwindWrapped;
204
+ }
205
+ /**
206
+ * Whether a namespace member name denotes a component to wrap —
207
+ * PascalCase and not a React context (`*Context`). Lowercase utilities /
208
+ * hooks (`createAnimatedComponent`, `spring`) pass through untouched.
209
+ * @param name Member key.
210
+ * @returns True when the member should be `wrap()`-ed.
211
+ */
212
+ function isComponentMember(name) {
213
+ return /^[A-Z]/.test(name) && !name.endsWith('Context');
214
+ }
215
+ /**
216
+ * Wrap a component NAMESPACE (a default/namespace import like reanimated's
217
+ * `Animated`) so member access — `Animated.View`, `Animated.ScrollView` —
218
+ * returns a `wrap()`-ed component whose `className` resolves at render.
219
+ * Returns a Proxy: component members are wrapped lazily and memoised so
220
+ * each access yields the SAME wrapped component (stable identity — React
221
+ * would remount otherwise). Non-component members (`createAnimatedComponent`,
222
+ * config objects) pass straight through.
223
+ * @example
224
+ * ```tsx
225
+ * const Animated = wrapNamespace(RNReanimated)
226
+ * <Animated.View className="enter-fade" />
227
+ * ```
228
+ * @param namespace The imported namespace object.
229
+ * @returns A Proxy that wraps component members on access.
230
+ */
231
+ function wrapNamespace(namespace) {
232
+ const cache = new Map();
233
+ return new Proxy(namespace, {
234
+ get(target, key, receiver) {
235
+ const value = Reflect.get(target, key, receiver);
236
+ if (typeof key !== 'string' || !isComponentMember(key))
237
+ return value;
238
+ if (!value || (typeof value !== 'function' && typeof value !== 'object'))
239
+ return value;
240
+ let wrapped = cache.get(key);
241
+ if (wrapped === undefined) {
242
+ wrapped = wrap(value);
243
+ cache.set(key, wrapped);
244
+ }
245
+ return wrapped;
246
+ },
247
+ });
248
+ }
249
+
250
+ export { wrap, wrapNamespace };
251
+ //# sourceMappingURL=wrap.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.mjs","sources":["../../../../src/runtime/wrap.tsx"],"sourcesContent":["import { createElement, useEffect, useRef, type ComponentType, type ReactElement } from 'react'\nimport { chainFocus, chainPress } from './chain-handlers'\nimport { useInteract } from './hooks/use-interact'\nimport { useRnwind } from './components/rnwind-provider'\nimport type { RnwindState } from './components/rnwind-provider'\nimport { resolve, type ResolvedCss } from './resolve'\nimport type { OnHaptics } from '../core/parser/haptics'\n\n/** Matches a leading `active:` / `focus:` variant token (`\\b` excludes `inactive:`). */\nconst INTERACTIVE_VARIANT = /\\b(?:active|focus):/\n\n/** One-shot guard so the missing-`onHaptics` warning logs once per session. */\nlet warnedMissingOnHaptics = false\n\n/**\n * Dev-only warning when a className carries a haptic utility but no\n * `onHaptics` dispatcher is wired on the nearest `<RnwindProvider>` — the\n * haptic would silently drop otherwise. Fires once per session.\n * @param onHaptics The dispatcher from context (or undefined).\n * @param haptics The resolved haptic requests (or undefined).\n */\nfunction warnIfHapticsUnwired(onHaptics: OnHaptics | undefined, haptics: ResolvedCss['haptics']): void {\n if (onHaptics || !haptics || haptics.length === 0) return\n const isDevelopment = typeof __DEV__ === 'undefined' || __DEV__\n if (!isDevelopment || warnedMissingOnHaptics) return\n warnedMissingOnHaptics = true\n // eslint-disable-next-line no-console\n console.warn(\n 'rnwind: a `haptic-*` utility resolved but no `onHaptics` callback is wired on <RnwindProvider>. ' +\n 'Pass `onHaptics` on the provider to forward the request to expo-haptics (or any library).',\n )\n}\n\n/**\n * Whether a className needs press/focus state tracking.\n * @param className Raw className string.\n * @returns True when an `active:` / `focus:` variant is present.\n */\nfunction hasInteractiveVariant(className: string): boolean {\n return INTERACTIVE_VARIANT.test(className)\n}\n\n/**\n * Best-effort display name for the wrapped component.\n * @param component Component being wrapped.\n * @returns Its `displayName`, `name`, or `'Component'`.\n */\nfunction displayNameOf(component: unknown): string {\n const named = component as { displayName?: string; name?: string }\n return named.displayName ?? named.name ?? 'Component'\n}\n\n/**\n * Fire the `mount`-trigger haptics once, after the element mounts. Snaps\n * the resolved requests + dispatcher at mount via a `useRef` initializer\n * (evaluated only on the first render), so an unstable inline `onHaptics`\n * doesn't re-fire them and no ref is written during render.\n * @param resolved The resolved className (carries any haptic requests).\n * @param onHaptics The dispatcher from context (or undefined).\n */\nfunction useMountHaptics(resolved: ResolvedCss, onHaptics: OnHaptics | undefined): void {\n const mount = useRef({ resolved, onHaptics })\n useEffect(() => {\n const { resolved: current, onHaptics: dispatch } = mount.current\n if (!dispatch || !current.haptics) return\n for (const entry of current.haptics) if (entry.trigger === 'mount') dispatch(entry.request, 'mount')\n }, [])\n}\n\n/** Suffix marking a secondary class-prop (`contentContainerClassName`, …). */\nconst CLASSNAME_SUFFIX = 'ClassName'\n\n/**\n * Resolve every secondary `<prefix>ClassName` prop (e.g.\n * `contentContainerClassName` on a ScrollView / FlatList) into its\n * matching `<prefix>Style`, in place. Any existing `<prefix>Style` is\n * appended last (caller wins). The original `*ClassName` prop is deleted\n * so RN never sees an unknown attribute. The primary `className` is\n * handled separately by the leaf and never reaches here.\n * @param props Mutable prop object being assembled for the host.\n * @param state Rnwind context for resolution.\n */\nfunction applyContainerClassNames(props: Record<string, unknown>, state: RnwindState): void {\n for (const key of Object.keys(props)) {\n if (!key.endsWith(CLASSNAME_SUFFIX)) continue\n const value = props[key]\n if (typeof value !== 'string') continue\n const styleKey = `${key.slice(0, -CLASSNAME_SUFFIX.length)}Style`\n props[styleKey] = resolve(value, state, props[styleKey]).style\n delete props[key]\n }\n}\n\n/**\n * Build the props for the wrapped host: resolved `style`, gradient\n * (`colors`/`start`/`end`), truncate (`numberOfLines`/`ellipsizeMode`),\n * secondary `<prefix>ClassName` → `<prefix>Style`, and a chained\n * `onPressIn` that fires press-trigger haptics. Unknown props on a host\n * are simply ignored by RN, so this stays generic.\n * @param rest Forwarded props (incl. `ref`).\n * @param resolved Resolved className result.\n * @param state Rnwind context — used to resolve secondary class props.\n * @param onHaptics Dispatcher from context.\n * @param userOnPressIn Caller-supplied onPressIn to chain after the haptic.\n * @returns The merged prop object for `createElement`.\n */\nfunction buildProps(\n rest: Record<string, unknown>,\n resolved: ResolvedCss,\n state: RnwindState,\n onHaptics: OnHaptics | undefined,\n userOnPressIn?: unknown,\n): Record<string, unknown> {\n warnIfHapticsUnwired(onHaptics, resolved.haptics)\n const props: Record<string, unknown> = { ...rest, style: resolved.style }\n applyContainerClassNames(props, state)\n if (resolved.colors) {\n props.colors = resolved.colors\n props.start = resolved.start\n props.end = resolved.end\n }\n if (resolved.numberOfLines !== undefined) {\n props.numberOfLines = resolved.numberOfLines\n if (resolved.ellipsizeMode !== undefined) props.ellipsizeMode = resolved.ellipsizeMode\n }\n const pressHaptics = onHaptics && resolved.haptics?.filter((entry) => entry.trigger === 'pressIn')\n if (pressHaptics && pressHaptics.length > 0) {\n const previous = userOnPressIn as ((event: unknown) => void) | undefined\n props.onPressIn = (event: unknown): void => {\n for (const entry of pressHaptics) onHaptics(entry.request, 'pressIn')\n previous?.(event)\n }\n } else if (userOnPressIn !== undefined) {\n props.onPressIn = userOnPressIn\n }\n return props\n}\n\n/** Props a leaf receives — the wrapped `as` tag plus forwarded props. */\ninterface LeafProps {\n readonly as: ComponentType<Record<string, unknown>>\n readonly className?: string\n readonly style?: unknown\n readonly [key: string]: unknown\n}\n\n/**\n * Non-interactive leaf: resolve className → style (+ features) and\n * forward. One context read, one molecule/atom resolve.\n * @param props Leaf props.\n * @param props.as\n * @param props.className\n * @param props.style\n * @param props.onPressIn\n * @returns The rendered `as` element.\n */\nfunction PlainLeaf({ as: As, className, style, onPressIn, ...rest }: LeafProps): ReactElement {\n const state = useRnwind()\n const resolved = resolve(className, state, style)\n useMountHaptics(resolved, state.onHaptics)\n return createElement(As, buildProps(rest, resolved, state, state.onHaptics, onPressIn))\n}\n\n/**\n * Interactive leaf: tracks press/focus via `useInteract()`, feeds it into\n * `resolve` so `active:`/`focus:` atoms apply, and chains the\n * press/focus handlers.\n * @param props Leaf props.\n * @param props.as\n * @param props.className\n * @param props.style\n * @param props.onPressIn\n * @param props.onPressOut\n * @param props.onFocus\n * @param props.onBlur\n * @returns The rendered `as` element with interactive wiring.\n */\nfunction InteractiveLeaf({ as: As, className, style, onPressIn, onPressOut, onFocus, onBlur, ...rest }: LeafProps): ReactElement {\n const state = useRnwind()\n const interact = useInteract()\n const resolved = resolve(className, state, style, interact.state)\n useMountHaptics(resolved, state.onHaptics)\n const props = buildProps(rest, resolved, state, state.onHaptics, onPressIn)\n props.onPressIn = chainPress(props.onPressIn as Parameters<typeof chainPress>[0], interact.onPressIn)\n props.onPressOut = chainPress(onPressOut as Parameters<typeof chainPress>[0], interact.onPressOut)\n props.onFocus = chainFocus(onFocus as Parameters<typeof chainFocus>[0], interact.onFocus)\n props.onBlur = chainFocus(onBlur as Parameters<typeof chainFocus>[0], interact.onBlur)\n return createElement(As, props)\n}\n\n/**\n * Wrap a component so its `className` prop resolves to RN `style` (plus\n * gradient / truncate props and haptic dispatch) at render — no matter\n * how className arrived: written directly, spread through `{...rest}`, or\n * forwarded down custom wrappers. The returned component is hook-free; it\n * dispatches to a plain or interactive leaf so non-interactive elements\n * never pay for press/focus state. `ref` (a normal prop in React 19) and\n * all other props forward untouched.\n * @example\n * ```tsx\n * const Pressable = wrap(RNPressable)\n * <Pressable className=\"active:bg-sky-700 px-4 haptic-light\" onPress={fn} />\n * ```\n * @param Component Any component accepting a `style` prop.\n * @returns A component accepting `className`.\n */\nexport function wrap<P>(Component: ComponentType<P>): ComponentType<P & { className?: string }> {\n const as = Component as unknown as ComponentType<Record<string, unknown>>\n /**\n * The wrapped component — hook-free dispatcher to a leaf.\n * @param props Forwarded props with `className` intercepted.\n * @param props.className\n * @returns The rendered leaf.\n */\n function RnwindWrapped({ className, ...rest }: { className?: string; [key: string]: unknown }): ReactElement {\n if (className !== undefined && hasInteractiveVariant(className)) {\n return createElement(InteractiveLeaf, { as, className, ...rest })\n }\n return createElement(PlainLeaf, { as, className, ...rest })\n }\n RnwindWrapped.displayName = `wrap(${displayNameOf(Component)})`\n return RnwindWrapped as unknown as ComponentType<P & { className?: string }>\n}\n\n/**\n * Whether a namespace member name denotes a component to wrap —\n * PascalCase and not a React context (`*Context`). Lowercase utilities /\n * hooks (`createAnimatedComponent`, `spring`) pass through untouched.\n * @param name Member key.\n * @returns True when the member should be `wrap()`-ed.\n */\nfunction isComponentMember(name: string): boolean {\n return /^[A-Z]/.test(name) && !name.endsWith('Context')\n}\n\n/**\n * Wrap a component NAMESPACE (a default/namespace import like reanimated's\n * `Animated`) so member access — `Animated.View`, `Animated.ScrollView` —\n * returns a `wrap()`-ed component whose `className` resolves at render.\n * Returns a Proxy: component members are wrapped lazily and memoised so\n * each access yields the SAME wrapped component (stable identity — React\n * would remount otherwise). Non-component members (`createAnimatedComponent`,\n * config objects) pass straight through.\n * @example\n * ```tsx\n * const Animated = wrapNamespace(RNReanimated)\n * <Animated.View className=\"enter-fade\" />\n * ```\n * @param namespace The imported namespace object.\n * @returns A Proxy that wraps component members on access.\n */\nexport function wrapNamespace<T extends object>(namespace: T): T {\n const cache = new Map<string, unknown>()\n return new Proxy(namespace, {\n get(target, key, receiver): unknown {\n const value = Reflect.get(target, key, receiver)\n if (typeof key !== 'string' || !isComponentMember(key)) return value\n if (!value || (typeof value !== 'function' && typeof value !== 'object')) return value\n let wrapped = cache.get(key)\n if (wrapped === undefined) {\n wrapped = wrap(value as ComponentType<unknown>)\n cache.set(key, wrapped)\n }\n return wrapped\n },\n })\n}\n"],"names":[],"mappings":";;;;;;AAQA;AACA,MAAM,mBAAmB,GAAG,qBAAqB;AAEjD;AACA,IAAI,sBAAsB,GAAG,KAAK;AAElC;;;;;;AAMG;AACH,SAAS,oBAAoB,CAAC,SAAgC,EAAE,OAA+B,EAAA;IAC7F,IAAI,SAAS,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE;IACnD,MAAM,aAAa,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO;IAC/D,IAAI,CAAC,aAAa,IAAI,sBAAsB;QAAE;IAC9C,sBAAsB,GAAG,IAAI;;IAE7B,OAAO,CAAC,IAAI,CACV,kGAAkG;AAChG,QAAA,2FAA2F,CAC9F;AACH;AAEA;;;;AAIG;AACH,SAAS,qBAAqB,CAAC,SAAiB,EAAA;AAC9C,IAAA,OAAO,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC;AAC5C;AAEA;;;;AAIG;AACH,SAAS,aAAa,CAAC,SAAkB,EAAA;IACvC,MAAM,KAAK,GAAG,SAAoD;IAClE,OAAO,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW;AACvD;AAEA;;;;;;;AAOG;AACH,SAAS,eAAe,CAAC,QAAqB,EAAE,SAAgC,EAAA;IAC9E,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC7C,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,OAAO;AAChE,QAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE;AACnC,QAAA,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO;AAAE,YAAA,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO;AAAE,gBAAA,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;IACtG,CAAC,EAAE,EAAE,CAAC;AACR;AAEA;AACA,MAAM,gBAAgB,GAAG,WAAW;AAEpC;;;;;;;;;AASG;AACH,SAAS,wBAAwB,CAAC,KAA8B,EAAE,KAAkB,EAAA;IAClF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;AACpC,QAAA,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAAE;AACrC,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC;QACxB,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE;AAC/B,QAAA,MAAM,QAAQ,GAAG,CAAA,EAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,OAAO;AACjE,QAAA,KAAK,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;AAC9D,QAAA,OAAO,KAAK,CAAC,GAAG,CAAC;IACnB;AACF;AAEA;;;;;;;;;;;;AAYG;AACH,SAAS,UAAU,CACjB,IAA6B,EAC7B,QAAqB,EACrB,KAAkB,EAClB,SAAgC,EAChC,aAAuB,EAAA;AAEvB,IAAA,oBAAoB,CAAC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC;AACjD,IAAA,MAAM,KAAK,GAA4B,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE;AACzE,IAAA,wBAAwB,CAAC,KAAK,EAAE,KAAK,CAAC;AACtC,IAAA,IAAI,QAAQ,CAAC,MAAM,EAAE;AACnB,QAAA,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM;AAC9B,QAAA,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK;AAC5B,QAAA,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG;IAC1B;AACA,IAAA,IAAI,QAAQ,CAAC,aAAa,KAAK,SAAS,EAAE;AACxC,QAAA,KAAK,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa;AAC5C,QAAA,IAAI,QAAQ,CAAC,aAAa,KAAK,SAAS;AAAE,YAAA,KAAK,CAAC,aAAa,GAAG,QAAQ,CAAC,aAAa;IACxF;IACA,MAAM,YAAY,GAAG,SAAS,IAAI,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC;IAClG,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;QAC3C,MAAM,QAAQ,GAAG,aAAuD;AACxE,QAAA,KAAK,CAAC,SAAS,GAAG,CAAC,KAAc,KAAU;YACzC,KAAK,MAAM,KAAK,IAAI,YAAY;AAAE,gBAAA,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC;AACrE,YAAA,QAAQ,GAAG,KAAK,CAAC;AACnB,QAAA,CAAC;IACH;AAAO,SAAA,IAAI,aAAa,KAAK,SAAS,EAAE;AACtC,QAAA,KAAK,CAAC,SAAS,GAAG,aAAa;IACjC;AACA,IAAA,OAAO,KAAK;AACd;AAUA;;;;;;;;;AASG;AACH,SAAS,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,EAAa,EAAA;AAC5E,IAAA,MAAM,KAAK,GAAG,SAAS,EAAE;IACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC;AACjD,IAAA,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;AAC1C,IAAA,OAAO,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AACzF;AAEA;;;;;;;;;;;;;AAaG;AACH,SAAS,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAa,EAAA;AAC/G,IAAA,MAAM,KAAK,GAAG,SAAS,EAAE;AACzB,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE;AAC9B,IAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;AACjE,IAAA,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;AAC1C,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC;AAC3E,IAAA,KAAK,CAAC,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,SAA6C,EAAE,QAAQ,CAAC,SAAS,CAAC;IACrG,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,UAA8C,EAAE,QAAQ,CAAC,UAAU,CAAC;IAClG,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,OAA2C,EAAE,QAAQ,CAAC,OAAO,CAAC;IACzF,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,MAA0C,EAAE,QAAQ,CAAC,MAAM,CAAC;AACtF,IAAA,OAAO,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC;AACjC;AAEA;;;;;;;;;;;;;;;AAeG;AACG,SAAU,IAAI,CAAI,SAA2B,EAAA;IACjD,MAAM,EAAE,GAAG,SAA8D;AACzE;;;;;AAKG;AACH,IAAA,SAAS,aAAa,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAkD,EAAA;QAC3F,IAAI,SAAS,KAAK,SAAS,IAAI,qBAAqB,CAAC,SAAS,CAAC,EAAE;AAC/D,YAAA,OAAO,aAAa,CAAC,eAAe,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,CAAC;QACnE;AACA,QAAA,OAAO,aAAa,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7D;IACA,aAAa,CAAC,WAAW,GAAG,CAAA,KAAA,EAAQ,aAAa,CAAC,SAAS,CAAC,CAAA,CAAA,CAAG;AAC/D,IAAA,OAAO,aAAqE;AAC9E;AAEA;;;;;;AAMG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAA;AACrC,IAAA,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;AACzD;AAEA;;;;;;;;;;;;;;;AAeG;AACG,SAAU,aAAa,CAAmB,SAAY,EAAA;AAC1D,IAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB;AACxC,IAAA,OAAO,IAAI,KAAK,CAAC,SAAS,EAAE;AAC1B,QAAA,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAA;AACvB,YAAA,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC;YAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC;AAAE,gBAAA,OAAO,KAAK;AACpE,YAAA,IAAI,CAAC,KAAK,KAAK,OAAO,KAAK,KAAK,UAAU,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AAAE,gBAAA,OAAO,KAAK;YACtF,IAAI,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;AAC5B,YAAA,IAAI,OAAO,KAAK,SAAS,EAAE;AACzB,gBAAA,OAAO,GAAG,IAAI,CAAC,KAA+B,CAAC;AAC/C,gBAAA,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;YACzB;AACA,YAAA,OAAO,OAAO;QAChB,CAAC;AACF,KAAA,CAAC;AACJ;;;;"}
@@ -1,13 +1,14 @@
1
1
  import { parse } from '@babel/parser';
2
2
  import generate$1 from '@babel/generator';
3
- import { mkdtempSync, writeFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
3
+ import { mkdtempSync, writeFileSync, existsSync, readdirSync, rmSync, readFileSync } from 'node:fs';
4
4
  import { createRequire } from 'node:module';
5
5
  import { tmpdir } from 'node:os';
6
6
  import path from 'node:path';
7
7
  import * as React from 'react';
8
- import { lookupCss, registerAtoms, __resetLookupCssState } from '../runtime/lookup-css.mjs';
9
- import { RnwindProvider, useR_, useRnwind } from '../runtime/components/rnwind-provider.mjs';
10
- import { triggerHaptic, useMountHaptic } from '../runtime/haptics.mjs';
8
+ import { __resetLookupCssState, registerAtoms, registerBreakpoints, registerSchemeLoader } from '../runtime/lookup-css.mjs';
9
+ import { __resetResolveState, registerMolecules, registerGradients, registerHaptics } from '../runtime/resolve.mjs';
10
+ import * as runtime_index from '../runtime/index.mjs';
11
+ import { RnwindProvider } from '../runtime/components/rnwind-provider.mjs';
11
12
  import { configureRnwindState, resetRnwindState } from '../metro/state.mjs';
12
13
  import { transform } from '../metro/transformer.mjs';
13
14
 
@@ -29,26 +30,6 @@ const DEFAULT_THEME_CSS = `@import 'tailwindcss';
29
30
  `;
30
31
  /** `StyleSheet.create` stub the evaluated bundle calls — identity at test time. */
31
32
  const BUNDLE_STYLE_SHEET = { create: (styles) => styles, hairlineWidth: 1 };
32
- /** Stubbed interactive-state hook return matching `useInteract()`. */
33
- const NOOP_INTERACT = {
34
- state: undefined,
35
- onPressIn: () => undefined,
36
- onPressOut: () => undefined,
37
- onFocus: () => undefined,
38
- onBlur: () => undefined,
39
- };
40
- /**
41
- * Identity chain helper matching `chainPress` / `chainFocus`. Returns a
42
- * single callback that invokes every supplied handler in turn.
43
- * @param handlers Handlers to chain.
44
- * @returns Combined callback.
45
- */
46
- const NOOP_CHAIN = (...handlers) => (...args) => {
47
- for (const handler of handlers) {
48
- if (typeof handler === 'function')
49
- handler(...args);
50
- }
51
- };
52
33
  // Synthesize a require rooted at the consumer's cwd so optional peer
53
34
  // lookups (`@testing-library/react-native`, `esbuild`) resolve from THEIR
54
35
  // node_modules, not rnwind's. A workspace-local rnwind install resolves
@@ -106,28 +87,87 @@ function compileToJs(source) {
106
87
  }
107
88
  }
108
89
  /**
109
- * Evaluate the union `style.js` the ledger wrote during the transform
110
- * so its `registerAtoms` call lands in the process-global registry.
111
- * Strips the `react-native` + `rnwind` imports from the source and
112
- * forwards local bindings into the evaluated closure.
113
- * @param cacheDir The rnwind cache dir where the ledger writes bundles.
90
+ * Evaluate one generated registry file (`common.style.js`,
91
+ * `<variant>.style.js`, or `schemes.js`) so its `registerAtoms` /
92
+ * `registerBreakpoints` / `registerGradients` / `registerHaptics` /
93
+ * `registerSchemeLoader` calls land in the process-global registries the
94
+ * runtime resolver reads. Imports (`'rnwind'`, `'react-native'`, relative
95
+ * `./common.style`), `require(...)` loaders, and `export {...}` are
96
+ * stripped — every scheme file is evaluated directly, so lazy loaders are
97
+ * inert.
98
+ * @param filePath Absolute path to the generated file.
114
99
  */
115
- function evaluateStyleBundle(cacheDir) {
116
- const filePath = path.join(cacheDir, 'style.js');
100
+ function evaluateGeneratedFile(filePath) {
117
101
  if (!existsSync(filePath))
118
102
  return;
119
103
  const body = readFileSync(filePath, 'utf8')
120
- .replaceAll(/import \{ StyleSheet \} from 'react-native'\s*\n/g, '')
121
- .replaceAll(/import \{[^}]+\} from 'rnwind'\s*\n/g, '');
122
- // Bundle body is generated by rnwind itself — not user-controlled.
104
+ .replaceAll(/import\s+\{[^}]*\}\s+from\s+['"][^'"]+['"];?\s*\n?/g, '')
105
+ .replaceAll(/import\s+['"][^'"]+['"];?\s*\n?/g, '')
106
+ .replaceAll(/export\s+\{[^}]*\}\s*;?\s*\n?/g, '');
107
+ // Generated body is produced by rnwind itself — not user-controlled.
108
+ // `require` is neutralised: each variant file is evaluated directly,
109
+ // so the manifest's lazy `require('./x.style')` loaders are no-ops.
123
110
  // eslint-disable-next-line sonarjs/code-eval
124
- new Function('StyleSheet', 'registerAtoms', body)(BUNDLE_STYLE_SHEET, registerAtoms);
111
+ new Function('StyleSheet', 'registerAtoms', 'registerMolecules', 'registerBreakpoints', 'registerGradients', 'registerHaptics', 'registerSchemeLoader', 'require', body)(BUNDLE_STYLE_SHEET, registerAtoms, registerMolecules, registerBreakpoints, registerGradients, registerHaptics, registerSchemeLoader, () => { });
112
+ }
113
+ /**
114
+ * Sort comparator that floats `common.style.js` to the front, rest alpha.
115
+ * @param a
116
+ * @param b
117
+ */
118
+ function commonFirst(a, b) {
119
+ if (a === 'common.style.js')
120
+ return -1;
121
+ if (b === 'common.style.js')
122
+ return 1;
123
+ return a.localeCompare(b);
124
+ }
125
+ /**
126
+ * Evaluate every generated registry the transform wrote (`common` +
127
+ * every variant scheme + the manifest) so the runtime resolver sees the
128
+ * full atom / molecule / gradient / haptic registries — same state a
129
+ * production bundle would hold once all scheme files load.
130
+ * @param cacheDir The rnwind cache dir holding the generated files.
131
+ */
132
+ function evaluateGeneratedRegistries(cacheDir) {
133
+ if (!existsSync(cacheDir))
134
+ return;
135
+ const files = readdirSync(cacheDir).filter((name) => name.endsWith('.style.js'));
136
+ // common first so variants layer their diffs on top of it.
137
+ for (const name of files.toSorted(commonFirst)) {
138
+ evaluateGeneratedFile(path.join(cacheDir, name));
139
+ }
140
+ evaluateGeneratedFile(path.join(cacheDir, 'schemes.js'));
141
+ }
142
+ /**
143
+ * Move every `const {…} = __rnwind;` / `const {…} = __reactNative;`
144
+ * binding line to the top of the source, preserving order, so they
145
+ * initialise before the transformer's `const View = _rnwWrap(_rnw0)`
146
+ * wrap declarations reference them. Mirrors ESM import hoisting.
147
+ * @param source Source with imports already converted to const-destructures.
148
+ * @returns Source with binding consts hoisted to the front.
149
+ */
150
+ function hoistBindingConsts(source) {
151
+ const hoisted = [];
152
+ const rest = [];
153
+ for (const line of source.split('\n')) {
154
+ if (/=\s*__(?:rnwind|reactNative);\s*$/.test(line))
155
+ hoisted.push(line);
156
+ else
157
+ rest.push(line);
158
+ }
159
+ return [...hoisted, ...rest].join('\n');
125
160
  }
126
161
  /**
127
162
  * Evaluate the transformer's rewritten source as a standalone module:
128
- * strip synthetic imports, forward `import ... from 'rnwind'` +
129
- * `'react-native'` to local bindings, compile JSX via the compiler, and
130
- * capture the default export.
163
+ * strip synthetic generated imports, forward `import ... from 'rnwind'`
164
+ * (incl. the injected `wrap as _rnwWrap`) and `'react-native'` to local
165
+ * bindings, compile JSX via the compiler, and capture the default export.
166
+ * Aliased named imports (`{ View as _rnw0 }`) are converted to valid
167
+ * destructuring (`{ View: _rnw0 }`). The import-derived `const`s are then
168
+ * hoisted above the body: the transformer injects `const View =
169
+ * _rnwWrap(_rnw0)` ahead of the (real-ESM-hoisted) `import … as _rnw0`,
170
+ * so without re-hoisting the binding the eval would hit a TDZ on `_rnw0`.
131
171
  * @param transformedSource Post-transformer source.
132
172
  * @param reactNative `react-native` namespace bindings to forward.
133
173
  * @returns The default-exported component.
@@ -135,24 +175,14 @@ function evaluateStyleBundle(cacheDir) {
135
175
  function evaluateRewrittenModule(transformedSource, reactNative) {
136
176
  const prepared = transformedSource
137
177
  .replaceAll(/import\s+["']rnwind\/__generated\/[^"']+["'];?\s*\n?/g, '')
138
- .replaceAll(/import\s+\{([^}]+)\}\s+from\s+["']rnwind["'];?/g, 'const {$1} = __rnwind;')
139
- .replaceAll(/import\s+\{([^}]+)\}\s+from\s+["']react-native["'];?/g, 'const {$1} = __reactNative;')
178
+ .replaceAll(/import\s+\{([^}]+)\}\s+from\s+["']rnwind["'];?/g, (_m, spec) => `const {${spec.replaceAll(/\bas\b/g, ':')}} = __rnwind;`)
179
+ .replaceAll(/import\s+\{([^}]+)\}\s+from\s+["']react-native["'];?/g, (_m, spec) => `const {${spec.replaceAll(/\bas\b/g, ':')}} = __reactNative;`)
140
180
  .replace(/export\s+default\s+/, 'module.exports.default = ');
141
- const compiled = compileToJs(prepared);
142
- const rnwindEnv = {
143
- lookupCss,
144
- useRnwind,
145
- useR_,
146
- useMountHaptic,
147
- triggerHaptic,
148
- useInteract: () => NOOP_INTERACT,
149
- chainPress: NOOP_CHAIN,
150
- chainFocus: NOOP_CHAIN,
151
- };
181
+ const compiled = compileToJs(hoistBindingConsts(prepared));
152
182
  const moduleObject = { exports: {} };
153
183
  // Compiled source originates from rnwind's own transformer + the JSX compiler.
154
184
  // eslint-disable-next-line sonarjs/code-eval
155
- new Function('React', '__rnwind', '__reactNative', 'module', compiled)(React, rnwindEnv, reactNative, moduleObject);
185
+ new Function('React', '__rnwind', '__reactNative', 'module', compiled)(React, runtime_index, reactNative, moduleObject);
156
186
  if (!moduleObject.exports.default) {
157
187
  throw new Error('rnwind/testing: evaluated module did not export a default component.');
158
188
  }
@@ -228,10 +258,11 @@ async function bootstrapRnwindRuntime(options) {
228
258
  const ast = parse(options.source, { sourceType: 'module', plugins: ['typescript', 'jsx'] });
229
259
  const result = await transform({ filename, src: options.source, options: { projectRoot }, ast });
230
260
  transformedSource = generate(result.ast).code;
231
- evaluateStyleBundle(cacheDir);
261
+ evaluateGeneratedRegistries(cacheDir);
232
262
  }
233
263
  const cleanup = () => {
234
264
  __resetLookupCssState();
265
+ __resetResolveState();
235
266
  resetRnwindState();
236
267
  if (existsSync(projectRoot))
237
268
  rmSync(projectRoot, { recursive: true, force: true });