rnwind 0.0.4 → 0.0.6

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 (127) 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/dts.cjs +7 -16
  11. package/lib/cjs/metro/dts.cjs.map +1 -1
  12. package/lib/cjs/metro/dts.d.ts +2 -4
  13. package/lib/cjs/metro/state.cjs +30 -78
  14. package/lib/cjs/metro/state.cjs.map +1 -1
  15. package/lib/cjs/metro/state.d.ts +8 -25
  16. package/lib/cjs/metro/transformer.cjs +193 -34
  17. package/lib/cjs/metro/transformer.cjs.map +1 -1
  18. package/lib/cjs/metro/with-config.cjs +2 -2
  19. package/lib/cjs/metro/with-config.cjs.map +1 -1
  20. package/lib/cjs/metro/with-config.d.ts +11 -26
  21. package/lib/cjs/metro/wrap-imports.cjs +273 -0
  22. package/lib/cjs/metro/wrap-imports.cjs.map +1 -0
  23. package/lib/cjs/metro/wrap-imports.d.ts +26 -0
  24. package/lib/cjs/runtime/components/rnwind-provider.cjs +0 -17
  25. package/lib/cjs/runtime/components/rnwind-provider.cjs.map +1 -1
  26. package/lib/cjs/runtime/components/rnwind-provider.d.ts +0 -14
  27. package/lib/cjs/runtime/hooks/use-css.cjs +16 -10
  28. package/lib/cjs/runtime/hooks/use-css.cjs.map +1 -1
  29. package/lib/cjs/runtime/hooks/use-css.d.ts +15 -9
  30. package/lib/cjs/runtime/index.cjs +11 -13
  31. package/lib/cjs/runtime/index.cjs.map +1 -1
  32. package/lib/cjs/runtime/index.d.ts +4 -9
  33. package/lib/cjs/runtime/lookup-css.cjs +10 -0
  34. package/lib/cjs/runtime/lookup-css.cjs.map +1 -1
  35. package/lib/cjs/runtime/lookup-css.d.ts +7 -0
  36. package/lib/cjs/runtime/resolve.cjs +400 -0
  37. package/lib/cjs/runtime/resolve.cjs.map +1 -0
  38. package/lib/cjs/runtime/resolve.d.ts +66 -0
  39. package/lib/cjs/runtime/wrap.cjs +254 -0
  40. package/lib/cjs/runtime/wrap.cjs.map +1 -0
  41. package/lib/cjs/runtime/wrap.d.ts +37 -0
  42. package/lib/cjs/testing/index.cjs +81 -50
  43. package/lib/cjs/testing/index.cjs.map +1 -1
  44. package/lib/esm/core/normalize-classname.d.ts +10 -0
  45. package/lib/esm/core/normalize-classname.mjs +23 -0
  46. package/lib/esm/core/normalize-classname.mjs.map +1 -0
  47. package/lib/esm/core/style-builder/build-style.d.ts +6 -1
  48. package/lib/esm/core/style-builder/build-style.mjs +258 -58
  49. package/lib/esm/core/style-builder/build-style.mjs.map +1 -1
  50. package/lib/esm/core/style-builder/union-builder.d.ts +21 -1
  51. package/lib/esm/core/style-builder/union-builder.mjs +37 -3
  52. package/lib/esm/core/style-builder/union-builder.mjs.map +1 -1
  53. package/lib/esm/metro/dts.d.ts +2 -4
  54. package/lib/esm/metro/dts.mjs +7 -16
  55. package/lib/esm/metro/dts.mjs.map +1 -1
  56. package/lib/esm/metro/state.d.ts +8 -25
  57. package/lib/esm/metro/state.mjs +30 -76
  58. package/lib/esm/metro/state.mjs.map +1 -1
  59. package/lib/esm/metro/transformer.mjs +194 -35
  60. package/lib/esm/metro/transformer.mjs.map +1 -1
  61. package/lib/esm/metro/with-config.d.ts +11 -26
  62. package/lib/esm/metro/with-config.mjs +2 -2
  63. package/lib/esm/metro/with-config.mjs.map +1 -1
  64. package/lib/esm/metro/wrap-imports.d.ts +26 -0
  65. package/lib/esm/metro/wrap-imports.mjs +250 -0
  66. package/lib/esm/metro/wrap-imports.mjs.map +1 -0
  67. package/lib/esm/runtime/components/rnwind-provider.d.ts +0 -14
  68. package/lib/esm/runtime/components/rnwind-provider.mjs +1 -17
  69. package/lib/esm/runtime/components/rnwind-provider.mjs.map +1 -1
  70. package/lib/esm/runtime/hooks/use-css.d.ts +15 -9
  71. package/lib/esm/runtime/hooks/use-css.mjs +16 -10
  72. package/lib/esm/runtime/hooks/use-css.mjs.map +1 -1
  73. package/lib/esm/runtime/index.d.ts +4 -9
  74. package/lib/esm/runtime/index.mjs +4 -4
  75. package/lib/esm/runtime/index.mjs.map +1 -1
  76. package/lib/esm/runtime/lookup-css.d.ts +7 -0
  77. package/lib/esm/runtime/lookup-css.mjs +10 -1
  78. package/lib/esm/runtime/lookup-css.mjs.map +1 -1
  79. package/lib/esm/runtime/resolve.d.ts +66 -0
  80. package/lib/esm/runtime/resolve.mjs +393 -0
  81. package/lib/esm/runtime/resolve.mjs.map +1 -0
  82. package/lib/esm/runtime/wrap.d.ts +37 -0
  83. package/lib/esm/runtime/wrap.mjs +251 -0
  84. package/lib/esm/runtime/wrap.mjs.map +1 -0
  85. package/lib/esm/testing/index.mjs +84 -53
  86. package/lib/esm/testing/index.mjs.map +1 -1
  87. package/package.json +2 -1
  88. package/src/core/normalize-classname.ts +19 -0
  89. package/src/core/style-builder/build-style.ts +286 -55
  90. package/src/core/style-builder/union-builder.ts +36 -3
  91. package/src/metro/dts.ts +7 -19
  92. package/src/metro/state.ts +29 -74
  93. package/src/metro/transformer.ts +190 -34
  94. package/src/metro/with-config.ts +13 -28
  95. package/src/metro/wrap-imports.ts +260 -0
  96. package/src/runtime/components/rnwind-provider.tsx +0 -17
  97. package/src/runtime/hooks/use-css.ts +17 -11
  98. package/src/runtime/index.ts +3 -26
  99. package/src/runtime/lookup-css.ts +10 -0
  100. package/src/runtime/resolve.ts +438 -0
  101. package/src/runtime/wrap.tsx +267 -0
  102. package/src/testing/index.ts +106 -56
  103. package/lib/cjs/core/parser/text-truncate.cjs +0 -78
  104. package/lib/cjs/core/parser/text-truncate.cjs.map +0 -1
  105. package/lib/cjs/metro/transform-ast.cjs +0 -1472
  106. package/lib/cjs/metro/transform-ast.cjs.map +0 -1
  107. package/lib/cjs/metro/transform-ast.d.ts +0 -88
  108. package/lib/cjs/runtime/haptics.cjs +0 -113
  109. package/lib/cjs/runtime/haptics.cjs.map +0 -1
  110. package/lib/cjs/runtime/haptics.d.ts +0 -48
  111. package/lib/cjs/runtime/interactive-box.cjs +0 -35
  112. package/lib/cjs/runtime/interactive-box.cjs.map +0 -1
  113. package/lib/cjs/runtime/interactive-box.d.ts +0 -40
  114. package/lib/esm/core/parser/text-truncate.mjs +0 -75
  115. package/lib/esm/core/parser/text-truncate.mjs.map +0 -1
  116. package/lib/esm/metro/transform-ast.d.ts +0 -88
  117. package/lib/esm/metro/transform-ast.mjs +0 -1451
  118. package/lib/esm/metro/transform-ast.mjs.map +0 -1
  119. package/lib/esm/runtime/haptics.d.ts +0 -48
  120. package/lib/esm/runtime/haptics.mjs +0 -110
  121. package/lib/esm/runtime/haptics.mjs.map +0 -1
  122. package/lib/esm/runtime/interactive-box.d.ts +0 -40
  123. package/lib/esm/runtime/interactive-box.mjs +0 -33
  124. package/lib/esm/runtime/interactive-box.mjs.map +0 -1
  125. package/src/metro/transform-ast.ts +0 -1729
  126. package/src/runtime/haptics.ts +0 -120
  127. package/src/runtime/interactive-box.tsx +0 -57
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../../src/testing/index.ts"],"sourcesContent":["/* eslint-disable sonarjs/no-unused-vars */\nimport type { File as BabelFile } from '@babel/types'\nimport { parse } from '@babel/parser'\nimport generateImport from '@babel/generator'\nimport { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport path from 'node:path'\nimport * as React from 'react'\nimport type {\n render as testingLibraryRender,\n renderHook as testingLibraryRenderHook,\n RenderHookOptions,\n} from '@testing-library/react-native'\nimport { configureRnwindState, resetRnwindState, transform as metroTransform } from '../metro'\nimport { __resetLookupCssState, lookupCss, registerAtoms } from '../runtime/lookup-css'\nimport type { Insets } from '../runtime/components/rnwind-provider'\nimport { RnwindProvider, useRnwind, useR_ } from '../runtime/components/rnwind-provider'\nimport { triggerHaptic, useMountHaptic } from '../runtime/haptics'\nimport type { OnHaptics } from '../core/parser/haptics'\nimport type { Scheme } from '../runtime/types'\n\n// ────────────────────────────────────────────────────────────────────────\n// Private constants\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal theme every `renderWithCss` call defaults to. Ships the two\n * conventional schemes so atoms carrying `dark:` / `light:` prefixes\n * resolve without the caller having to craft a theme CSS string.\n */\nconst DEFAULT_THEME_CSS = `@import 'tailwindcss';\n@layer theme {\n :root {\n @variant light { --color-bg: #ffffff; --color-fg: #0a0a0a; }\n @variant dark { --color-bg: #0a0a0a; --color-fg: #ffffff; }\n }\n}\n`\n\n/** `StyleSheet.create` stub the evaluated bundle calls — identity at test time. */\nconst BUNDLE_STYLE_SHEET = { create: <T>(styles: T): T => styles, hairlineWidth: 1 }\n\n/** Stubbed interactive-state hook return matching `useInteract()`. */\nconst NOOP_INTERACT = {\n state: undefined,\n onPressIn: (): void => undefined,\n onPressOut: (): void => undefined,\n onFocus: (): void => undefined,\n onBlur: (): void => undefined,\n}\n\n/**\n * Identity chain helper matching `chainPress` / `chainFocus`. Returns a\n * single callback that invokes every supplied handler in turn.\n * @param handlers Handlers to chain.\n * @returns Combined callback.\n */\nconst NOOP_CHAIN =\n (...handlers: ReadonlyArray<((...args: unknown[]) => unknown) | undefined>) =>\n (...args: unknown[]): void => {\n for (const handler of handlers) {\n if (typeof handler === 'function') handler(...args)\n }\n }\n\n// Synthesize a require rooted at the consumer's cwd so optional peer\n// lookups (`@testing-library/react-native`, `esbuild`) resolve from THEIR\n// node_modules, not rnwind's. A workspace-local rnwind install resolves\n// through a different node_modules chain than the test cwd — we want the\n// test's chain.\nconst localRequire: NodeRequire = createRequire(path.join(process.cwd(), 'package.json'))\n\nconst generate: typeof generateImport = (generateImport as { default?: typeof generateImport }).default ?? generateImport\n\n/**\n * Pre-loaded `@testing-library/react-native`. Resolved once at module\n * init (not lazily per-call) because the testing library registers\n * `beforeAll`/`afterEach` cleanup hooks on import — and Bun's test\n * runner refuses to register lifecycle hooks from inside a running\n * test body, which is where the first `renderWithCss` call lands.\n */\nconst TESTING_LIBRARY: {\n render: typeof testingLibraryRender\n renderHook: typeof testingLibraryRenderHook\n} = (() => {\n try {\n return localRequire('@testing-library/react-native') as {\n render: typeof testingLibraryRender\n renderHook: typeof testingLibraryRenderHook\n }\n } catch (error) {\n throw new Error(\n 'rnwind/testing: cannot load `@testing-library/react-native`. Add it to your dev dependencies. Underlying error: ' +\n (error instanceof Error ? error.message : String(error)),\n )\n }\n})()\n\n// ────────────────────────────────────────────────────────────────────────\n// Private helpers\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Scheme string typed to match what `<RnwindProvider>` accepts. Users\n * who declare their own scheme names via `rnwind-types.d.ts` get them\n * through `Scheme`; everyone else gets `'light' | 'dark' | string`.\n */\ntype SchemeName = Scheme\n\n/**\n * Default lookup for the test process's `react-native` module. Users\n * who run with a mocked `react-native` (via Bun's `mock.module` or\n * Jest's `jest.mock`) get the mock here automatically.\n * @returns React Native namespace bindings.\n */\nfunction resolveReactNative(): Record<string, unknown> {\n return localRequire('react-native') as Record<string, unknown>\n}\n\n/**\n * Compile JSX + TypeScript source to plain JS. Prefers Bun's built-in\n * transpiler (fast, zero-dep); falls back to `esbuild` if running under\n * Node (Jest users). Throws with an install hint if neither is available.\n * @param source Source text to compile.\n * @returns JavaScript suitable for `new Function(...)` evaluation.\n */\nfunction compileToJs(source: string): string {\n const globalBun = (\n globalThis as { Bun?: { Transpiler: new (options: unknown) => { transformSync: (text: string) => string } } }\n ).Bun\n if (globalBun?.Transpiler) {\n return new globalBun.Transpiler({\n loader: 'tsx',\n tsconfig: JSON.stringify({ compilerOptions: { jsx: 'react', target: 'esnext' } }),\n }).transformSync(source)\n }\n try {\n const esbuild = localRequire('esbuild') as {\n transformSync: (text: string, options: { loader: string; jsx: string; target?: string }) => { code: string }\n }\n return esbuild.transformSync(source, { loader: 'tsx', jsx: 'transform', target: 'esnext' }).code\n } catch {\n throw new Error(\n 'rnwind/testing: cannot compile JSX. Run tests under Bun (which has a built-in transpiler) ' +\n 'or install `esbuild` as a dev dependency for Node / Jest setups.',\n )\n }\n}\n\n/**\n * Evaluate the union `style.js` the ledger wrote during the transform\n * so its `registerAtoms` call lands in the process-global registry.\n * Strips the `react-native` + `rnwind` imports from the source and\n * forwards local bindings into the evaluated closure.\n * @param cacheDir The rnwind cache dir where the ledger writes bundles.\n */\nfunction evaluateStyleBundle(cacheDir: string): void {\n const filePath = path.join(cacheDir, 'style.js')\n if (!existsSync(filePath)) return\n const body = readFileSync(filePath, 'utf8')\n .replaceAll(/import \\{ StyleSheet \\} from 'react-native'\\s*\\n/g, '')\n .replaceAll(/import \\{[^}]+\\} from 'rnwind'\\s*\\n/g, '')\n // Bundle body is generated by rnwind itself — not user-controlled.\n // eslint-disable-next-line sonarjs/code-eval\n new Function('StyleSheet', 'registerAtoms', body)(BUNDLE_STYLE_SHEET, registerAtoms)\n}\n\n/**\n * Evaluate the transformer's rewritten source as a standalone module:\n * strip synthetic imports, forward `import ... from 'rnwind'` +\n * `'react-native'` to local bindings, compile JSX via the compiler, and\n * capture the default export.\n * @param transformedSource Post-transformer source.\n * @param reactNative `react-native` namespace bindings to forward.\n * @returns The default-exported component.\n */\nfunction evaluateRewrittenModule(\n transformedSource: string,\n reactNative: Record<string, unknown>,\n): React.ComponentType<Record<string, unknown>> {\n const prepared = transformedSource\n .replaceAll(/import\\s+[\"']rnwind\\/__generated\\/[^\"']+[\"'];?\\s*\\n?/g, '')\n .replaceAll(/import\\s+\\{([^}]+)\\}\\s+from\\s+[\"']rnwind[\"'];?/g, 'const {$1} = __rnwind;')\n .replaceAll(/import\\s+\\{([^}]+)\\}\\s+from\\s+[\"']react-native[\"'];?/g, 'const {$1} = __reactNative;')\n .replace(/export\\s+default\\s+/, 'module.exports.default = ')\n\n const compiled = compileToJs(prepared)\n const rnwindEnv = {\n lookupCss,\n useRnwind,\n useR_,\n useMountHaptic,\n triggerHaptic,\n useInteract: () => NOOP_INTERACT,\n chainPress: NOOP_CHAIN,\n chainFocus: NOOP_CHAIN,\n }\n const moduleObject: { exports: { default?: React.ComponentType<Record<string, unknown>> } } = { exports: {} }\n // Compiled source originates from rnwind's own transformer + the JSX compiler.\n // eslint-disable-next-line sonarjs/code-eval\n new Function('React', '__rnwind', '__reactNative', 'module', compiled)(React, rnwindEnv, reactNative, moduleObject)\n if (!moduleObject.exports.default) {\n throw new Error('rnwind/testing: evaluated module did not export a default component.')\n }\n return moduleObject.exports.default\n}\n\n/**\n * Build the root element `@testing-library/react-native`'s `render`\n * receives. When a scheme is specified, wrap the component in a live\n * `<RnwindProvider>` so hooks like `useScheme()` and `useCss()` return\n * the requested scheme. Otherwise render bare (context falls back to the\n * default `'light'`).\n * @param Component Component to render.\n * @param scheme Optional active scheme override.\n * @param insets\n * @param onHaptics\n * @returns The React element to hand to `render`.\n */\nfunction buildRootElement(\n Component: React.ComponentType<Record<string, unknown>>,\n scheme: string | undefined,\n insets: Partial<Insets> | undefined,\n onHaptics: OnHaptics | undefined,\n): React.ReactElement {\n const child = React.createElement(Component)\n if (scheme === undefined && !insets && !onHaptics) return child\n const providerProps: { scheme: SchemeName; insets?: Partial<Insets>; onHaptics?: OnHaptics } = {\n scheme: (scheme ?? 'light') as SchemeName,\n }\n if (insets) providerProps.insets = insets\n if (onHaptics) providerProps.onHaptics = onHaptics\n return React.createElement(RnwindProvider, providerProps, child)\n}\n\n/**\n * Compose the `wrapper` option `renderHook` accepts. When a scheme is\n * set, wrap the hook's execution context in `<RnwindProvider>` so hooks\n * that read `useScheme()` see the requested scheme. If the user also\n * supplies their own wrapper, nest it inside the provider so both apply.\n * @param scheme Optional active scheme override.\n * @param userWrapper Optional user-supplied wrapper component.\n * @returns A wrapper component suitable for `renderHook`'s `wrapper` option.\n */\nfunction composeHookWrapper(\n scheme: string | undefined,\n userWrapper: React.ComponentType<{ children?: React.ReactNode }> | undefined,\n): React.ComponentType<{ children?: React.ReactNode }> | undefined {\n if (scheme === undefined && !userWrapper) return undefined\n return function RnwindTestWrapper({ children }: { children?: React.ReactNode }): React.ReactElement {\n const inner = userWrapper ? React.createElement(userWrapper, null, children) : (children as React.ReactElement)\n if (scheme === undefined) return inner\n return React.createElement(RnwindProvider, { scheme: scheme as SchemeName }, inner)\n }\n}\n\n/**\n * Spin up an ephemeral rnwind project and register the atoms produced\n * by `source` (when provided) into the runtime. Returns the project\n * state and the post-transform source so callers can render the\n * rewritten module or just rely on the registered atoms.\n *\n * Shared between `renderWithCss` (needs the rewritten component) and\n * `renderHookWithCss` (only needs the atoms in the registry).\n * @param options Options controlling the theme and pre-registered source.\n * @param options.themeCss Theme CSS override.\n * @param options.source Source whose Tailwind candidates should be registered.\n * @returns Paths plus the transformed source and a `cleanup` closure.\n */\nasync function bootstrapRnwindRuntime(options: { themeCss?: string; source?: string }): Promise<{\n projectRoot: string\n cacheDir: string\n transformedSource: string\n cleanup: () => void\n}> {\n const projectRoot = mkdtempSync(path.join(tmpdir(), 'rnwind-test-'))\n const cacheDir = path.join(projectRoot, '.rnwind-cache')\n const cssPath = path.join(projectRoot, 'theme.css')\n writeFileSync(cssPath, options.themeCss ?? DEFAULT_THEME_CSS)\n configureRnwindState(cssPath, cacheDir)\n\n let transformedSource = ''\n if (options.source !== undefined) {\n const filename = path.join(projectRoot, 'App.tsx')\n writeFileSync(filename, options.source)\n const ast: BabelFile = parse(options.source, { sourceType: 'module', plugins: ['typescript', 'jsx'] })\n const result = await metroTransform({ filename, src: options.source, options: { projectRoot }, ast })\n transformedSource = generate(result.ast).code\n evaluateStyleBundle(cacheDir)\n }\n\n const cleanup = (): void => {\n __resetLookupCssState()\n resetRnwindState()\n if (existsSync(projectRoot)) rmSync(projectRoot, { recursive: true, force: true })\n }\n\n return { projectRoot, cacheDir, transformedSource, cleanup }\n}\n\n/**\n * Synthesize a throwaway source file that mentions each className in a\n * JSX `className=\"...\"` attribute so the Metro transformer's scanner\n * picks them up and records their resolved styles into the union\n * `style.js`. Used by `renderHookWithCss` to pre-register atoms the\n * hook under test will look up.\n * @param classNames Class names to include.\n * @returns Source string suitable for `bootstrapRnwindRuntime`.\n */\nfunction sourceFromClassNames(classNames: readonly string[]): string {\n const joined = classNames.join(' ').replaceAll('\"', String.raw`\\\"`)\n return `const V: any = () => null\\nexport default () => <V className=\"${joined}\" />\\n`\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// Public exports — types\n// ────────────────────────────────────────────────────────────────────────\n\n/** Exact signature of `@testing-library/react-native`'s `render`. */\nexport type Render = typeof testingLibraryRender\n\n/** Exact signature of `@testing-library/react-native`'s `renderHook`. */\nexport type RenderHook = typeof testingLibraryRenderHook\n\n/**\n * Exact return type of `@testing-library/react-native`'s `render` — we\n * re-export the upstream type so tests get every query (`getByTestId`,\n * `queryByText`, `rerender`, `unmount`, `debug`, `UNSAFE_root`, …) with\n * its real signature.\n */\nexport type TestingLibraryRenderAPI = ReturnType<typeof testingLibraryRender>\n\n/** Options accepted by {@link renderWithCss}. */\nexport interface RenderWithCssOptions {\n /** Theme CSS fed to rnwind's parser. Defaults to {@link DEFAULT_THEME_CSS}. */\n themeCss?: string\n /**\n * Active scheme the runtime resolver uses. Defaults to `'light'`.\n * Flip to `'dark'` / `'brand'` / ... to exercise scheme-variant atoms.\n */\n scheme?: string\n /**\n * Safe-area insets to inject. When provided, the component tree is\n * rendered under a `<RnwindProvider insets={...}>` so `*-safe` atoms\n * resolve to real numbers instead of the `0` fallback. Partial is\n * accepted — missing sides default to zero.\n */\n insets?: Partial<Insets>\n /**\n * Haptic dispatcher forwarded onto the implicit `<RnwindProvider>`.\n * Tests pass a spy callback to assert which haptic atoms fired.\n */\n onHaptics?: OnHaptics\n /**\n * `react-native` namespace bindings forwarded into the rewritten\n * module. Defaults to importing the test process's `react-native`\n * (typically a mocked stub). Override to inject custom host elements.\n */\n reactNative?: Record<string, unknown>\n}\n\n/**\n * Result of a {@link renderWithCss} call. Every key from\n * `@testing-library/react-native`'s `render` return value is spread onto\n * this object — `getByTestId`, `queryByText`, `rerender`, `unmount`,\n * `debug`, etc. — so tests use the testing-library API directly.\n *\n * Two rnwind-specific handles are added:\n * - `transformedSource` — the exact code the rnwind Metro transformer\n * emitted for your input. Log it to confirm the rewrite.\n * - `cleanup` — tears down the ephemeral rnwind state + chunk cache dir.\n * Call from `afterEach` so successive tests don't share atoms.\n */\nexport type RenderWithCssResult = TestingLibraryRenderAPI & {\n /** The post-transformer source text. Same code Metro would ship. */\n transformedSource: string\n /** Tear down the ephemeral rnwind state + cache dir. */\n cleanup: () => void\n}\n\n/** Options accepted by {@link renderHookWithCss}. */\nexport interface RenderHookWithCssOptions<Props> extends RenderHookOptions<Props> {\n /** Theme CSS fed to rnwind's parser. Defaults to the built-in minimal theme. */\n themeCss?: string\n /**\n * Active scheme the runtime resolver uses. Defaults to `'light'`. When\n * set, the hook is wrapped in `<RnwindProvider scheme={scheme}>` so\n * `useScheme()` / `useCss()` observe the override.\n */\n scheme?: string\n /**\n * Class names to pre-register into the runtime atom registry before\n * the hook runs. rnwind synthesizes a throwaway source file mentioning\n * these classes, feeds it through the real Metro transformer, and\n * evaluates the generated `style.js` bundle — so the hook sees\n * exactly the atoms the production bundle would register.\n */\n classNames?: readonly string[]\n}\n\n/**\n * Result of a {@link renderHookWithCss} call. Mirrors\n * `@testing-library/react-native`'s `renderHook` return type — `result`,\n * `rerender`, `unmount` — and adds a `cleanup` that tears down the\n * ephemeral rnwind state.\n */\nexport type RenderHookWithCssResult<Result, Props> = ReturnType<typeof testingLibraryRenderHook<Result, Props>> & {\n /** Tear down the ephemeral rnwind state + cache dir. */\n cleanup: () => void\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// Public exports — functions\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Feed a source string through rnwind's Metro transformer, evaluate\n * the generated `style.js` bundle, evaluate the rewritten module, and\n * render the default-exported component with\n * `@testing-library/react-native`.\n *\n * The rendered RN element's `style` prop is the EXACT array your\n * production bundle would attach — same transformer, same runtime\n * resolver, same atoms. Assert on `flatten(node.props.style)` to verify\n * the resolved values.\n * @example\n * ```tsx\n * const handle = await renderWithCss(\n * `import { View } from 'react-native'\n * export default () => <View className=\"bg-primary p-4\" testID=\"box\" />`,\n * { themeCss: `@import 'tailwindcss'; @theme { --color-primary: #6366f1; }` },\n * )\n * const flat = flatten(handle.getByTestId('box').props.style)\n * expect(flat.backgroundColor).toBe('#6366f1')\n * ```\n * @param source User source (one file) to transform + render.\n * @param options Optional theme override, scheme, and `react-native` bindings.\n * @returns Render queries + diagnostic handles.\n */\nexport async function renderWithCss(source: string, options: RenderWithCssOptions = {}): Promise<RenderWithCssResult> {\n const reactNative = options.reactNative ?? resolveReactNative()\n const {\n projectRoot: _projectRoot,\n transformedSource,\n cleanup,\n } = await bootstrapRnwindRuntime({\n themeCss: options.themeCss,\n source,\n })\n const Component = evaluateRewrittenModule(transformedSource, reactNative)\n const rendered = TESTING_LIBRARY.render(buildRootElement(Component, options.scheme, options.insets, options.onHaptics))\n return Object.assign({}, rendered, { transformedSource, cleanup })\n}\n\n/**\n * Render-side counterpart to {@link renderWithCss} for testing hooks in\n * isolation. Pre-registers atoms for the supplied `classNames`, wraps\n * the hook in an optional `<RnwindProvider>`, and forwards to\n * `@testing-library/react-native`'s `renderHook`.\n * @example\n * ```ts\n * const { result, cleanup } = await renderHookWithCss(\n * () => useCss('bg-primary'),\n * {\n * themeCss: `@import 'tailwindcss'; @theme { --color-primary: #6366f1; }`,\n * classNames: ['bg-primary'],\n * },\n * )\n * expect(flatten(result.current).backgroundColor).toBe('#6366f1')\n * cleanup()\n * ```\n * @param callback Hook body — same shape as `renderHook(callback)`.\n * @param options Theme, scheme, classNames to pre-register, plus\n * everything `renderHook` itself accepts (`initialProps`, `wrapper`).\n * @returns `renderHook`'s return value plus a `cleanup` function.\n */\nexport async function renderHookWithCss<Result, Props = unknown>(\n callback: (props: Props) => Result,\n options: RenderHookWithCssOptions<Props> = {},\n): Promise<RenderHookWithCssResult<Result, Props>> {\n const { cleanup } = await bootstrapRnwindRuntime({\n themeCss: options.themeCss,\n source: options.classNames && options.classNames.length > 0 ? sourceFromClassNames(options.classNames) : undefined,\n })\n const wrapper = composeHookWrapper(options.scheme, options.wrapper as RenderHookWithCssOptions<Props>['wrapper'])\n const { themeCss: _themeCss, scheme: _scheme, classNames: _classNames, ...hookOptions } = options\n const rendered = TESTING_LIBRARY.renderHook<Result, Props>(callback, {\n ...(hookOptions as RenderHookOptions<Props>),\n wrapper,\n })\n return Object.assign({}, rendered, { cleanup })\n}\n\n/**\n * Flatten a React Native style array (or single style object) into one\n * merged record. RN flattens left-to-right (later wins), so the returned\n * record is what the native view manager actually applies.\n * @param styles Style array, single object, or null/undefined.\n * @returns Flat style record.\n */\nexport function flatten(styles: unknown): Record<string, unknown> {\n if (styles == null) return {}\n if (Array.isArray(styles)) return Object.assign({}, ...styles.map((entry) => flatten(entry)))\n if (typeof styles === 'object') return styles as Record<string, unknown>\n return {}\n}\n"],"names":["createRequire","generateImport","existsSync","readFileSync","registerAtoms","lookupCss","useRnwind","useR_","useMountHaptic","triggerHaptic","React","RnwindProvider","mkdtempSync","tmpdir","writeFileSync","configureRnwindState","parse","metroTransform","__resetLookupCssState","resetRnwindState","rmSync"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../../src/testing/index.ts"],"sourcesContent":["/* eslint-disable sonarjs/no-unused-vars */\nimport type { File as BabelFile } from '@babel/types'\nimport { parse } from '@babel/parser'\nimport generateImport from '@babel/generator'\nimport { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport path from 'node:path'\nimport * as React from 'react'\nimport type {\n render as testingLibraryRender,\n renderHook as testingLibraryRenderHook,\n RenderHookOptions,\n} from '@testing-library/react-native'\nimport { configureRnwindState, resetRnwindState, transform as metroTransform } from '../metro'\nimport {\n __resetLookupCssState,\n registerAtoms,\n registerBreakpoints,\n registerSchemeLoader,\n} from '../runtime/lookup-css'\nimport { __resetResolveState, registerGradients, registerHaptics, registerMolecules } from '../runtime/resolve'\nimport * as rnwindRuntime from '../runtime'\nimport type { Insets } from '../runtime/components/rnwind-provider'\nimport { RnwindProvider } from '../runtime/components/rnwind-provider'\nimport type { OnHaptics } from '../core/parser/haptics'\nimport type { Scheme } from '../runtime/types'\n\n// ────────────────────────────────────────────────────────────────────────\n// Private constants\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal theme every `renderWithCss` call defaults to. Ships the two\n * conventional schemes so atoms carrying `dark:` / `light:` prefixes\n * resolve without the caller having to craft a theme CSS string.\n */\nconst DEFAULT_THEME_CSS = `@import 'tailwindcss';\n@layer theme {\n :root {\n @variant light { --color-bg: #ffffff; --color-fg: #0a0a0a; }\n @variant dark { --color-bg: #0a0a0a; --color-fg: #ffffff; }\n }\n}\n`\n\n/** `StyleSheet.create` stub the evaluated bundle calls — identity at test time. */\nconst BUNDLE_STYLE_SHEET = { create: <T>(styles: T): T => styles, hairlineWidth: 1 }\n\n// Synthesize a require rooted at the consumer's cwd so optional peer\n// lookups (`@testing-library/react-native`, `esbuild`) resolve from THEIR\n// node_modules, not rnwind's. A workspace-local rnwind install resolves\n// through a different node_modules chain than the test cwd — we want the\n// test's chain.\nconst localRequire: NodeRequire = createRequire(path.join(process.cwd(), 'package.json'))\n\nconst generate: typeof generateImport = (generateImport as { default?: typeof generateImport }).default ?? generateImport\n\n/**\n * Pre-loaded `@testing-library/react-native`. Resolved once at module\n * init (not lazily per-call) because the testing library registers\n * `beforeAll`/`afterEach` cleanup hooks on import — and Bun's test\n * runner refuses to register lifecycle hooks from inside a running\n * test body, which is where the first `renderWithCss` call lands.\n */\nconst TESTING_LIBRARY: {\n render: typeof testingLibraryRender\n renderHook: typeof testingLibraryRenderHook\n} = (() => {\n try {\n return localRequire('@testing-library/react-native') as {\n render: typeof testingLibraryRender\n renderHook: typeof testingLibraryRenderHook\n }\n } catch (error) {\n throw new Error(\n 'rnwind/testing: cannot load `@testing-library/react-native`. Add it to your dev dependencies. Underlying error: ' +\n (error instanceof Error ? error.message : String(error)),\n )\n }\n})()\n\n// ────────────────────────────────────────────────────────────────────────\n// Private helpers\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Scheme string typed to match what `<RnwindProvider>` accepts. Users\n * who declare their own scheme names via `rnwind-types.d.ts` get them\n * through `Scheme`; everyone else gets `'light' | 'dark' | string`.\n */\ntype SchemeName = Scheme\n\n/**\n * Default lookup for the test process's `react-native` module. Users\n * who run with a mocked `react-native` (via Bun's `mock.module` or\n * Jest's `jest.mock`) get the mock here automatically.\n * @returns React Native namespace bindings.\n */\nfunction resolveReactNative(): Record<string, unknown> {\n return localRequire('react-native') as Record<string, unknown>\n}\n\n/**\n * Compile JSX + TypeScript source to plain JS. Prefers Bun's built-in\n * transpiler (fast, zero-dep); falls back to `esbuild` if running under\n * Node (Jest users). Throws with an install hint if neither is available.\n * @param source Source text to compile.\n * @returns JavaScript suitable for `new Function(...)` evaluation.\n */\nfunction compileToJs(source: string): string {\n const globalBun = (\n globalThis as { Bun?: { Transpiler: new (options: unknown) => { transformSync: (text: string) => string } } }\n ).Bun\n if (globalBun?.Transpiler) {\n return new globalBun.Transpiler({\n loader: 'tsx',\n tsconfig: JSON.stringify({ compilerOptions: { jsx: 'react', target: 'esnext' } }),\n }).transformSync(source)\n }\n try {\n const esbuild = localRequire('esbuild') as {\n transformSync: (text: string, options: { loader: string; jsx: string; target?: string }) => { code: string }\n }\n return esbuild.transformSync(source, { loader: 'tsx', jsx: 'transform', target: 'esnext' }).code\n } catch {\n throw new Error(\n 'rnwind/testing: cannot compile JSX. Run tests under Bun (which has a built-in transpiler) ' +\n 'or install `esbuild` as a dev dependency for Node / Jest setups.',\n )\n }\n}\n\n/**\n * Evaluate one generated registry file (`common.style.js`,\n * `<variant>.style.js`, or `schemes.js`) so its `registerAtoms` /\n * `registerBreakpoints` / `registerGradients` / `registerHaptics` /\n * `registerSchemeLoader` calls land in the process-global registries the\n * runtime resolver reads. Imports (`'rnwind'`, `'react-native'`, relative\n * `./common.style`), `require(...)` loaders, and `export {...}` are\n * stripped — every scheme file is evaluated directly, so lazy loaders are\n * inert.\n * @param filePath Absolute path to the generated file.\n */\nfunction evaluateGeneratedFile(filePath: string): void {\n if (!existsSync(filePath)) return\n const body = readFileSync(filePath, 'utf8')\n .replaceAll(/import\\s+\\{[^}]*\\}\\s+from\\s+['\"][^'\"]+['\"];?\\s*\\n?/g, '')\n .replaceAll(/import\\s+['\"][^'\"]+['\"];?\\s*\\n?/g, '')\n .replaceAll(/export\\s+\\{[^}]*\\}\\s*;?\\s*\\n?/g, '')\n // Generated body is produced by rnwind itself — not user-controlled.\n // `require` is neutralised: each variant file is evaluated directly,\n // so the manifest's lazy `require('./x.style')` loaders are no-ops.\n // eslint-disable-next-line sonarjs/code-eval\n new Function(\n 'StyleSheet',\n 'registerAtoms',\n 'registerMolecules',\n 'registerBreakpoints',\n 'registerGradients',\n 'registerHaptics',\n 'registerSchemeLoader',\n 'require',\n body,\n )(\n BUNDLE_STYLE_SHEET,\n registerAtoms,\n registerMolecules,\n registerBreakpoints,\n registerGradients,\n registerHaptics,\n registerSchemeLoader,\n () => {},\n )\n}\n\n/**\n * Sort comparator that floats `common.style.js` to the front, rest alpha.\n * @param a\n * @param b\n */\nfunction commonFirst(a: string, b: string): number {\n if (a === 'common.style.js') return -1\n if (b === 'common.style.js') return 1\n return a.localeCompare(b)\n}\n\n/**\n * Evaluate every generated registry the transform wrote (`common` +\n * every variant scheme + the manifest) so the runtime resolver sees the\n * full atom / molecule / gradient / haptic registries — same state a\n * production bundle would hold once all scheme files load.\n * @param cacheDir The rnwind cache dir holding the generated files.\n */\nfunction evaluateGeneratedRegistries(cacheDir: string): void {\n if (!existsSync(cacheDir)) return\n const files = readdirSync(cacheDir).filter((name) => name.endsWith('.style.js'))\n // common first so variants layer their diffs on top of it.\n for (const name of files.toSorted(commonFirst)) {\n evaluateGeneratedFile(path.join(cacheDir, name))\n }\n evaluateGeneratedFile(path.join(cacheDir, 'schemes.js'))\n}\n\n/**\n * Move every `const {…} = __rnwind;` / `const {…} = __reactNative;`\n * binding line to the top of the source, preserving order, so they\n * initialise before the transformer's `const View = _rnwWrap(_rnw0)`\n * wrap declarations reference them. Mirrors ESM import hoisting.\n * @param source Source with imports already converted to const-destructures.\n * @returns Source with binding consts hoisted to the front.\n */\nfunction hoistBindingConsts(source: string): string {\n const hoisted: string[] = []\n const rest: string[] = []\n for (const line of source.split('\\n')) {\n if (/=\\s*__(?:rnwind|reactNative);\\s*$/.test(line)) hoisted.push(line)\n else rest.push(line)\n }\n return [...hoisted, ...rest].join('\\n')\n}\n\n/**\n * Evaluate the transformer's rewritten source as a standalone module:\n * strip synthetic generated imports, forward `import ... from 'rnwind'`\n * (incl. the injected `wrap as _rnwWrap`) and `'react-native'` to local\n * bindings, compile JSX via the compiler, and capture the default export.\n * Aliased named imports (`{ View as _rnw0 }`) are converted to valid\n * destructuring (`{ View: _rnw0 }`). The import-derived `const`s are then\n * hoisted above the body: the transformer injects `const View =\n * _rnwWrap(_rnw0)` ahead of the (real-ESM-hoisted) `import … as _rnw0`,\n * so without re-hoisting the binding the eval would hit a TDZ on `_rnw0`.\n * @param transformedSource Post-transformer source.\n * @param reactNative `react-native` namespace bindings to forward.\n * @returns The default-exported component.\n */\nfunction evaluateRewrittenModule(\n transformedSource: string,\n reactNative: Record<string, unknown>,\n): React.ComponentType<Record<string, unknown>> {\n const prepared = transformedSource\n .replaceAll(/import\\s+[\"']rnwind\\/__generated\\/[^\"']+[\"'];?\\s*\\n?/g, '')\n .replaceAll(/import\\s+\\{([^}]+)\\}\\s+from\\s+[\"']rnwind[\"'];?/g, (_m, spec: string) => `const {${spec.replaceAll(/\\bas\\b/g, ':')}} = __rnwind;`)\n .replaceAll(/import\\s+\\{([^}]+)\\}\\s+from\\s+[\"']react-native[\"'];?/g, (_m, spec: string) => `const {${spec.replaceAll(/\\bas\\b/g, ':')}} = __reactNative;`)\n .replace(/export\\s+default\\s+/, 'module.exports.default = ')\n\n const compiled = compileToJs(hoistBindingConsts(prepared))\n const moduleObject: { exports: { default?: React.ComponentType<Record<string, unknown>> } } = { exports: {} }\n // Compiled source originates from rnwind's own transformer + the JSX compiler.\n // eslint-disable-next-line sonarjs/code-eval\n new Function('React', '__rnwind', '__reactNative', 'module', compiled)(React, rnwindRuntime, reactNative, moduleObject)\n if (!moduleObject.exports.default) {\n throw new Error('rnwind/testing: evaluated module did not export a default component.')\n }\n return moduleObject.exports.default\n}\n\n/**\n * Build the root element `@testing-library/react-native`'s `render`\n * receives. When a scheme is specified, wrap the component in a live\n * `<RnwindProvider>` so hooks like `useScheme()` and `useCss()` return\n * the requested scheme. Otherwise render bare (context falls back to the\n * default `'light'`).\n * @param Component Component to render.\n * @param scheme Optional active scheme override.\n * @param insets\n * @param onHaptics\n * @returns The React element to hand to `render`.\n */\nfunction buildRootElement(\n Component: React.ComponentType<Record<string, unknown>>,\n scheme: string | undefined,\n insets: Partial<Insets> | undefined,\n onHaptics: OnHaptics | undefined,\n): React.ReactElement {\n const child = React.createElement(Component)\n if (scheme === undefined && !insets && !onHaptics) return child\n const providerProps: { scheme: SchemeName; insets?: Partial<Insets>; onHaptics?: OnHaptics } = {\n scheme: (scheme ?? 'light') as SchemeName,\n }\n if (insets) providerProps.insets = insets\n if (onHaptics) providerProps.onHaptics = onHaptics\n return React.createElement(RnwindProvider, providerProps, child)\n}\n\n/**\n * Compose the `wrapper` option `renderHook` accepts. When a scheme is\n * set, wrap the hook's execution context in `<RnwindProvider>` so hooks\n * that read `useScheme()` see the requested scheme. If the user also\n * supplies their own wrapper, nest it inside the provider so both apply.\n * @param scheme Optional active scheme override.\n * @param userWrapper Optional user-supplied wrapper component.\n * @returns A wrapper component suitable for `renderHook`'s `wrapper` option.\n */\nfunction composeHookWrapper(\n scheme: string | undefined,\n userWrapper: React.ComponentType<{ children?: React.ReactNode }> | undefined,\n): React.ComponentType<{ children?: React.ReactNode }> | undefined {\n if (scheme === undefined && !userWrapper) return undefined\n return function RnwindTestWrapper({ children }: { children?: React.ReactNode }): React.ReactElement {\n const inner = userWrapper ? React.createElement(userWrapper, null, children) : (children as React.ReactElement)\n if (scheme === undefined) return inner\n return React.createElement(RnwindProvider, { scheme: scheme as SchemeName }, inner)\n }\n}\n\n/**\n * Spin up an ephemeral rnwind project and register the atoms produced\n * by `source` (when provided) into the runtime. Returns the project\n * state and the post-transform source so callers can render the\n * rewritten module or just rely on the registered atoms.\n *\n * Shared between `renderWithCss` (needs the rewritten component) and\n * `renderHookWithCss` (only needs the atoms in the registry).\n * @param options Options controlling the theme and pre-registered source.\n * @param options.themeCss Theme CSS override.\n * @param options.source Source whose Tailwind candidates should be registered.\n * @returns Paths plus the transformed source and a `cleanup` closure.\n */\nasync function bootstrapRnwindRuntime(options: { themeCss?: string; source?: string }): Promise<{\n projectRoot: string\n cacheDir: string\n transformedSource: string\n cleanup: () => void\n}> {\n const projectRoot = mkdtempSync(path.join(tmpdir(), 'rnwind-test-'))\n const cacheDir = path.join(projectRoot, '.rnwind-cache')\n const cssPath = path.join(projectRoot, 'theme.css')\n writeFileSync(cssPath, options.themeCss ?? DEFAULT_THEME_CSS)\n configureRnwindState(cssPath, cacheDir)\n\n let transformedSource = ''\n if (options.source !== undefined) {\n const filename = path.join(projectRoot, 'App.tsx')\n writeFileSync(filename, options.source)\n const ast: BabelFile = parse(options.source, { sourceType: 'module', plugins: ['typescript', 'jsx'] })\n const result = await metroTransform({ filename, src: options.source, options: { projectRoot }, ast })\n transformedSource = generate(result.ast).code\n evaluateGeneratedRegistries(cacheDir)\n }\n\n const cleanup = (): void => {\n __resetLookupCssState()\n __resetResolveState()\n resetRnwindState()\n if (existsSync(projectRoot)) rmSync(projectRoot, { recursive: true, force: true })\n }\n\n return { projectRoot, cacheDir, transformedSource, cleanup }\n}\n\n/**\n * Synthesize a throwaway source file that mentions each className in a\n * JSX `className=\"...\"` attribute so the Metro transformer's scanner\n * picks them up and records their resolved styles into the union\n * `style.js`. Used by `renderHookWithCss` to pre-register atoms the\n * hook under test will look up.\n * @param classNames Class names to include.\n * @returns Source string suitable for `bootstrapRnwindRuntime`.\n */\nfunction sourceFromClassNames(classNames: readonly string[]): string {\n const joined = classNames.join(' ').replaceAll('\"', String.raw`\\\"`)\n return `const V: any = () => null\\nexport default () => <V className=\"${joined}\" />\\n`\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// Public exports — types\n// ────────────────────────────────────────────────────────────────────────\n\n/** Exact signature of `@testing-library/react-native`'s `render`. */\nexport type Render = typeof testingLibraryRender\n\n/** Exact signature of `@testing-library/react-native`'s `renderHook`. */\nexport type RenderHook = typeof testingLibraryRenderHook\n\n/**\n * Exact return type of `@testing-library/react-native`'s `render` — we\n * re-export the upstream type so tests get every query (`getByTestId`,\n * `queryByText`, `rerender`, `unmount`, `debug`, `UNSAFE_root`, …) with\n * its real signature.\n */\nexport type TestingLibraryRenderAPI = ReturnType<typeof testingLibraryRender>\n\n/** Options accepted by {@link renderWithCss}. */\nexport interface RenderWithCssOptions {\n /** Theme CSS fed to rnwind's parser. Defaults to {@link DEFAULT_THEME_CSS}. */\n themeCss?: string\n /**\n * Active scheme the runtime resolver uses. Defaults to `'light'`.\n * Flip to `'dark'` / `'brand'` / ... to exercise scheme-variant atoms.\n */\n scheme?: string\n /**\n * Safe-area insets to inject. When provided, the component tree is\n * rendered under a `<RnwindProvider insets={...}>` so `*-safe` atoms\n * resolve to real numbers instead of the `0` fallback. Partial is\n * accepted — missing sides default to zero.\n */\n insets?: Partial<Insets>\n /**\n * Haptic dispatcher forwarded onto the implicit `<RnwindProvider>`.\n * Tests pass a spy callback to assert which haptic atoms fired.\n */\n onHaptics?: OnHaptics\n /**\n * `react-native` namespace bindings forwarded into the rewritten\n * module. Defaults to importing the test process's `react-native`\n * (typically a mocked stub). Override to inject custom host elements.\n */\n reactNative?: Record<string, unknown>\n}\n\n/**\n * Result of a {@link renderWithCss} call. Every key from\n * `@testing-library/react-native`'s `render` return value is spread onto\n * this object — `getByTestId`, `queryByText`, `rerender`, `unmount`,\n * `debug`, etc. — so tests use the testing-library API directly.\n *\n * Two rnwind-specific handles are added:\n * - `transformedSource` — the exact code the rnwind Metro transformer\n * emitted for your input. Log it to confirm the rewrite.\n * - `cleanup` — tears down the ephemeral rnwind state + chunk cache dir.\n * Call from `afterEach` so successive tests don't share atoms.\n */\nexport type RenderWithCssResult = TestingLibraryRenderAPI & {\n /** The post-transformer source text. Same code Metro would ship. */\n transformedSource: string\n /** Tear down the ephemeral rnwind state + cache dir. */\n cleanup: () => void\n}\n\n/** Options accepted by {@link renderHookWithCss}. */\nexport interface RenderHookWithCssOptions<Props> extends RenderHookOptions<Props> {\n /** Theme CSS fed to rnwind's parser. Defaults to the built-in minimal theme. */\n themeCss?: string\n /**\n * Active scheme the runtime resolver uses. Defaults to `'light'`. When\n * set, the hook is wrapped in `<RnwindProvider scheme={scheme}>` so\n * `useScheme()` / `useCss()` observe the override.\n */\n scheme?: string\n /**\n * Class names to pre-register into the runtime atom registry before\n * the hook runs. rnwind synthesizes a throwaway source file mentioning\n * these classes, feeds it through the real Metro transformer, and\n * evaluates the generated `style.js` bundle — so the hook sees\n * exactly the atoms the production bundle would register.\n */\n classNames?: readonly string[]\n}\n\n/**\n * Result of a {@link renderHookWithCss} call. Mirrors\n * `@testing-library/react-native`'s `renderHook` return type — `result`,\n * `rerender`, `unmount` — and adds a `cleanup` that tears down the\n * ephemeral rnwind state.\n */\nexport type RenderHookWithCssResult<Result, Props> = ReturnType<typeof testingLibraryRenderHook<Result, Props>> & {\n /** Tear down the ephemeral rnwind state + cache dir. */\n cleanup: () => void\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// Public exports — functions\n// ────────────────────────────────────────────────────────────────────────\n\n/**\n * Feed a source string through rnwind's Metro transformer, evaluate\n * the generated `style.js` bundle, evaluate the rewritten module, and\n * render the default-exported component with\n * `@testing-library/react-native`.\n *\n * The rendered RN element's `style` prop is the EXACT array your\n * production bundle would attach — same transformer, same runtime\n * resolver, same atoms. Assert on `flatten(node.props.style)` to verify\n * the resolved values.\n * @example\n * ```tsx\n * const handle = await renderWithCss(\n * `import { View } from 'react-native'\n * export default () => <View className=\"bg-primary p-4\" testID=\"box\" />`,\n * { themeCss: `@import 'tailwindcss'; @theme { --color-primary: #6366f1; }` },\n * )\n * const flat = flatten(handle.getByTestId('box').props.style)\n * expect(flat.backgroundColor).toBe('#6366f1')\n * ```\n * @param source User source (one file) to transform + render.\n * @param options Optional theme override, scheme, and `react-native` bindings.\n * @returns Render queries + diagnostic handles.\n */\nexport async function renderWithCss(source: string, options: RenderWithCssOptions = {}): Promise<RenderWithCssResult> {\n const reactNative = options.reactNative ?? resolveReactNative()\n const {\n projectRoot: _projectRoot,\n transformedSource,\n cleanup,\n } = await bootstrapRnwindRuntime({\n themeCss: options.themeCss,\n source,\n })\n const Component = evaluateRewrittenModule(transformedSource, reactNative)\n const rendered = TESTING_LIBRARY.render(buildRootElement(Component, options.scheme, options.insets, options.onHaptics))\n return Object.assign({}, rendered, { transformedSource, cleanup })\n}\n\n/**\n * Render-side counterpart to {@link renderWithCss} for testing hooks in\n * isolation. Pre-registers atoms for the supplied `classNames`, wraps\n * the hook in an optional `<RnwindProvider>`, and forwards to\n * `@testing-library/react-native`'s `renderHook`.\n * @example\n * ```ts\n * const { result, cleanup } = await renderHookWithCss(\n * () => useCss('bg-primary'),\n * {\n * themeCss: `@import 'tailwindcss'; @theme { --color-primary: #6366f1; }`,\n * classNames: ['bg-primary'],\n * },\n * )\n * expect(flatten(result.current).backgroundColor).toBe('#6366f1')\n * cleanup()\n * ```\n * @param callback Hook body — same shape as `renderHook(callback)`.\n * @param options Theme, scheme, classNames to pre-register, plus\n * everything `renderHook` itself accepts (`initialProps`, `wrapper`).\n * @returns `renderHook`'s return value plus a `cleanup` function.\n */\nexport async function renderHookWithCss<Result, Props = unknown>(\n callback: (props: Props) => Result,\n options: RenderHookWithCssOptions<Props> = {},\n): Promise<RenderHookWithCssResult<Result, Props>> {\n const { cleanup } = await bootstrapRnwindRuntime({\n themeCss: options.themeCss,\n source: options.classNames && options.classNames.length > 0 ? sourceFromClassNames(options.classNames) : undefined,\n })\n const wrapper = composeHookWrapper(options.scheme, options.wrapper as RenderHookWithCssOptions<Props>['wrapper'])\n const { themeCss: _themeCss, scheme: _scheme, classNames: _classNames, ...hookOptions } = options\n const rendered = TESTING_LIBRARY.renderHook<Result, Props>(callback, {\n ...(hookOptions as RenderHookOptions<Props>),\n wrapper,\n })\n return Object.assign({}, rendered, { cleanup })\n}\n\n/**\n * Flatten a React Native style array (or single style object) into one\n * merged record. RN flattens left-to-right (later wins), so the returned\n * record is what the native view manager actually applies.\n * @param styles Style array, single object, or null/undefined.\n * @returns Flat style record.\n */\nexport function flatten(styles: unknown): Record<string, unknown> {\n if (styles == null) return {}\n if (Array.isArray(styles)) return Object.assign({}, ...styles.map((entry) => flatten(entry)))\n if (typeof styles === 'object') return styles as Record<string, unknown>\n return {}\n}\n"],"names":["createRequire","generateImport","existsSync","readFileSync","registerAtoms","registerMolecules","registerBreakpoints","registerGradients","registerHaptics","registerSchemeLoader","readdirSync","React","rnwindRuntime","RnwindProvider","mkdtempSync","tmpdir","writeFileSync","configureRnwindState","parse","metroTransform","__resetLookupCssState","__resetResolveState","resetRnwindState","rmSync"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Normalize a className for molecule keying: trim, collapse runs of
3
+ * whitespace, and drop exact-duplicate tokens — but PRESERVE ORDER.
4
+ * Tailwind is last-wins for conflicting utilities (`p-4 p-2` ≠ `p-2 p-4`),
5
+ * so sorting would corrupt the merge. Build-time (molecule keys) and
6
+ * runtime (lookup) call the identical function so their keys always match.
7
+ * @param className Raw className string.
8
+ * @returns Normalized, order-preserving className.
9
+ */
10
+ export declare function normalizeClassName(className: string): string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Normalize a className for molecule keying: trim, collapse runs of
3
+ * whitespace, and drop exact-duplicate tokens — but PRESERVE ORDER.
4
+ * Tailwind is last-wins for conflicting utilities (`p-4 p-2` ≠ `p-2 p-4`),
5
+ * so sorting would corrupt the merge. Build-time (molecule keys) and
6
+ * runtime (lookup) call the identical function so their keys always match.
7
+ * @param className Raw className string.
8
+ * @returns Normalized, order-preserving className.
9
+ */
10
+ function normalizeClassName(className) {
11
+ const seen = new Set();
12
+ const out = [];
13
+ for (const token of className.trim().split(/\s+/)) {
14
+ if (token.length === 0 || seen.has(token))
15
+ continue;
16
+ seen.add(token);
17
+ out.push(token);
18
+ }
19
+ return out.join(' ');
20
+ }
21
+
22
+ export { normalizeClassName };
23
+ //# sourceMappingURL=normalize-classname.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-classname.mjs","sources":["../../../../src/core/normalize-classname.ts"],"sourcesContent":["/**\n * Normalize a className for molecule keying: trim, collapse runs of\n * whitespace, and drop exact-duplicate tokens — but PRESERVE ORDER.\n * Tailwind is last-wins for conflicting utilities (`p-4 p-2` ≠ `p-2 p-4`),\n * so sorting would corrupt the merge. Build-time (molecule keys) and\n * runtime (lookup) call the identical function so their keys always match.\n * @param className Raw className string.\n * @returns Normalized, order-preserving className.\n */\nexport function normalizeClassName(className: string): string {\n const seen = new Set<string>()\n const out: string[] = []\n for (const token of className.trim().split(/\\s+/)) {\n if (token.length === 0 || seen.has(token)) continue\n seen.add(token)\n out.push(token)\n }\n return out.join(' ')\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;AAQG;AACG,SAAU,kBAAkB,CAAC,SAAiB,EAAA;AAClD,IAAA,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU;IAC9B,MAAM,GAAG,GAAa,EAAE;AACxB,IAAA,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE;AAC3C,QAAA,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;AACf,QAAA,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;IACjB;AACA,IAAA,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;AACtB;;;;"}
@@ -47,8 +47,13 @@ export type AtomSerializedCache = Map<string, AtomSerializedEntry>;
47
47
  * manifest emits `registerBreakpoints({...})` so the runtime can gate
48
48
  * `md:*` / `lg:*` atoms on `windowWidth`. Optional — empty when the
49
49
  * theme declares no breakpoints (legacy/test callers).
50
+ * @param gradients Gradient feature map (atom → role/colour) for the manifest + molecule eligibility.
51
+ * @param haptics Haptic feature map (atom → request) for the manifest + molecule eligibility.
52
+ * @param literals Distinct literal className strings — pre-merged into
53
+ * per-scheme molecules so the runtime resolver's O(1) molecule-first
54
+ * path is populated. Empty for legacy/test callers (atom path only).
50
55
  * @returns Per-scheme sources, manifest source, variant list.
51
56
  */
52
- export declare function buildSchemeSources(atomNames: readonly string[], resolved: ReadonlyMap<string, SchemedStyle>, keyframes: ReadonlyMap<string, KeyframeBlock>, cache?: AtomSerializedCache, breakpoints?: ReadonlyMap<string, number>): BuildSchemeSourcesOutput;
57
+ export declare function buildSchemeSources(atomNames: readonly string[], resolved: ReadonlyMap<string, SchemedStyle>, keyframes: ReadonlyMap<string, KeyframeBlock>, cache?: AtomSerializedCache, breakpoints?: ReadonlyMap<string, number>, gradients?: ReadonlyMap<string, unknown>, haptics?: ReadonlyMap<string, unknown>, literals?: readonly string[]): BuildSchemeSourcesOutput;
53
58
  /** Registry key the runtime uses for the always-loaded fallback. */
54
59
  export declare const COMMON_SCHEME_NAME: string;
@@ -1,3 +1,5 @@
1
+ import { normalizeClassName } from '../normalize-classname.mjs';
2
+
1
3
  /** Match atom names like `border-hairline`, `h-hairline`, `border-t-hairline`, etc. */
2
4
  const HAIRLINE_ATOM = /-hairline$/;
3
5
  /** Parser's synthetic "no variant" scheme — provides the canonical fallback. */
@@ -176,59 +178,92 @@ function prepareAtomValue(atomName, style, keyframes) {
176
178
  return serializeAtomValue(atomName, enveloped);
177
179
  }
178
180
  /**
179
- * Per-file value deduplicator interns each unique serialized atom
180
- * value once and emits `const _s<N> = <value>` at module scope. Scheme
181
- * entries reference the const instead of inlining the literal.
182
- *
183
- * Wins: (1) smaller bundle bytes for themes with many atoms sharing a
184
- * style shape; (2) stable `===` inside one scheme so two atoms
185
- * resolving to the same value yield the same object reference.
181
+ * Decide which serialized atom values get hoisted to a shared `const`.
182
+ * A value is hoisted ONLY when ≥2 atoms share it then one
183
+ * `const _s<N> = <value>` saves the repeated bytes AND gives those atoms
184
+ * one shared object (reference identity). A value used once is inlined
185
+ * directly at its atom (`"-m-2": {"margin":-8}`) hoisting a singleton
186
+ * would only add bytes. First-seen order keeps the const indices stable
187
+ * across workers.
188
+ * @param entries `[atomName, serializedValue]` pairs (atom-sorted).
189
+ * @returns `{ constFor }` value→const-name map + `decls` source lines.
186
190
  */
187
- class ValueDeduper {
188
- byText = new Map();
189
- decls = [];
190
- intern(serialized) {
191
- const existing = this.byText.get(serialized);
192
- if (existing)
193
- return existing;
194
- const name = `_s${this.decls.length}`;
195
- this.decls.push(`const ${name} = ${serialized}`);
196
- this.byText.set(serialized, name);
197
- return name;
198
- }
199
- get declarations() {
200
- return this.decls;
191
+ function planValueConsts(entries) {
192
+ const counts = new Map();
193
+ for (const [, value] of entries)
194
+ counts.set(value, (counts.get(value) ?? 0) + 1);
195
+ const constFor = new Map();
196
+ const decls = [];
197
+ for (const [value, count] of counts) {
198
+ if (count < 2)
199
+ continue;
200
+ const name = `_s${decls.length}`;
201
+ constFor.set(value, name);
202
+ decls.push(`const ${name} = ${value}`);
201
203
  }
204
+ return { constFor, decls };
205
+ }
206
+ /**
207
+ * Serialize a scheme's molecule map into a `registerMolecules(...)` object
208
+ * literal, sorted by className for byte-deterministic output.
209
+ * @param molecules normalized className → pre-merged style object.
210
+ * @returns Object-literal source (`null` when empty).
211
+ */
212
+ function serializeMolecules(molecules) {
213
+ if (!molecules)
214
+ return null;
215
+ const keys = Object.keys(molecules).toSorted((a, b) => a.localeCompare(b));
216
+ if (keys.length === 0)
217
+ return null;
218
+ const body = keys.map((cn) => ` ${JSON.stringify(cn)}: ${JSON.stringify(molecules[cn])},`);
219
+ return ['{', ...body, '}'].join('\n');
202
220
  }
203
221
  /**
204
222
  * Render one scheme file's source. `entries` is the list of atoms this
205
223
  * scheme contributes — for `common` every atom's canonical value; for
206
224
  * a variant only atoms whose value differs from canonical. Hairline
207
- * atoms in this file trigger the `StyleSheet` import.
225
+ * atoms in this file trigger the `StyleSheet` import. Pre-merged
226
+ * molecules (when present) are registered alongside the atoms so the
227
+ * runtime resolver's molecule-first path is populated.
208
228
  * @param schemeName Registry key (`'common'` or the variant name).
209
229
  * @param entries `[atomName, serializedValue]` pairs to emit.
230
+ * @param molecules Pre-merged className → style map for this scheme.
210
231
  * @returns JS source text.
211
232
  */
212
- function renderSchemeFile(schemeName, entries) {
233
+ function renderSchemeFile(schemeName, entries, molecules) {
213
234
  const needsStyleSheet = entries.some(([atom]) => isHairlineAtom(atom));
214
- const deduper = new ValueDeduper();
215
- const recordLines = [];
216
- for (const [atom, value] of entries) {
217
- const ref = deduper.intern(value);
218
- recordLines.push(` ${JSON.stringify(atom)}: ${ref},`);
219
- }
235
+ const { constFor, decls } = planValueConsts(entries);
236
+ const recordLines = entries.map(([atom, value]) => ` ${JSON.stringify(atom)}: ${constFor.get(value) ?? value},`);
237
+ const moleculeLiteral = serializeMolecules(molecules);
238
+ const imports = ['registerAtoms'];
239
+ if (moleculeLiteral)
240
+ imports.push('registerMolecules');
220
241
  const lines = [];
221
242
  if (needsStyleSheet)
222
243
  lines.push(`import { StyleSheet } from 'react-native'`);
223
- lines.push(`import { registerAtoms } from 'rnwind'`, ``);
224
- if (deduper.declarations.length > 0) {
225
- for (const decl of deduper.declarations)
244
+ lines.push(`import { ${imports.join(', ')} } from 'rnwind'`, ``);
245
+ if (decls.length > 0) {
246
+ for (const decl of decls)
226
247
  lines.push(decl);
227
248
  lines.push(``);
228
249
  }
229
250
  lines.push(`registerAtoms(${JSON.stringify(schemeName)}, {`, ...recordLines, `})`, ``);
251
+ if (moleculeLiteral)
252
+ lines.push(`registerMolecules(${JSON.stringify(schemeName)}, ${moleculeLiteral})`, ``);
230
253
  return lines.join('\n');
231
254
  }
255
+ /**
256
+ * Serialize a feature map (atom name → JSON-able value: gradient info or
257
+ * haptic request) into a stable JS object literal for the manifest.
258
+ * Sorted by key so the output is byte-deterministic across workers.
259
+ * @param map Atom name → feature value.
260
+ * @returns Object-literal source.
261
+ */
262
+ function serializeFeatureMap(map) {
263
+ const entries = [...map.entries()].toSorted((a, b) => a[0].localeCompare(b[0]));
264
+ const body = entries.map(([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)}`).join(', ');
265
+ return `{ ${body} }`;
266
+ }
232
267
  /**
233
268
  * Render the JS-object literal for the responsive-breakpoint table the
234
269
  * runtime registers at manifest-load time. Sorted by ascending px
@@ -245,35 +280,190 @@ function serializeBreakpoints(breakpoints) {
245
280
  return `{ ${inner} }`;
246
281
  }
247
282
  /**
248
- * Render the manifest module. Eager-imports `common.style.js` (every
249
- * rewritten source file pulls this via a transitive side-effect
250
- * import), registers the responsive-breakpoint table once, and lazy-
251
- * requires every variant scheme's file through an inline require —
252
- * first call in `ensureSchemeLoaded(name)` triggers the scheme
253
- * module's evaluation; Metro's module cache makes subsequent calls
254
- * no-ops.
283
+ * Render the manifest module. EAGER-imports `common.style.js` AND every
284
+ * variant scheme file so every scheme's atoms register the moment the
285
+ * manifest evaluates no lazy `require`. Lazy loading raced the cold
286
+ * start: `RnwindProvider` calls `loadScheme(scheme)` on its first render,
287
+ * but on a cold boot the manifest (hence `registerSchemeLoader`) may not
288
+ * have evaluated yet, so that call no-ops and the active variant's atoms
289
+ * never load — scheme-dependent styles fall back to `common` (the light
290
+ * default) until a reload. Eager imports remove the race entirely; the
291
+ * variant files are small diffs, so the upfront cost is negligible.
292
+ * `ensureSchemeLoaded` stays exported as a no-op for API compatibility.
255
293
  * @param variants Variant scheme names (no `base`, no `common`).
256
294
  * @param breakpoints Responsive breakpoint name → px-threshold map.
295
+ * @param gradients Atom → gradient info for `registerGradients`.
296
+ * @param haptics Atom → haptic request for `registerHaptics`.
257
297
  * @returns JS source text.
258
298
  */
259
- function renderManifest(variants, breakpoints) {
260
- const lines = [
261
- `import { registerSchemeLoader, registerBreakpoints } from 'rnwind'`,
262
- `import './common.style'`,
263
- ``,
264
- `registerBreakpoints(${serializeBreakpoints(breakpoints)})`,
265
- ``,
266
- ];
267
- if (variants.length === 0) {
268
- lines.push(`function ensureSchemeLoaded(_name) {}`, ``, `registerSchemeLoader(ensureSchemeLoaded)`, ``, `export { ensureSchemeLoaded }`, ``);
269
- return lines.join('\n');
299
+ function renderManifest(variants, breakpoints, gradients, haptics) {
300
+ const imports = ['registerSchemeLoader', 'registerBreakpoints'];
301
+ if (gradients.size > 0)
302
+ imports.push('registerGradients');
303
+ if (haptics.size > 0)
304
+ imports.push('registerHaptics');
305
+ const lines = [`import { ${imports.join(', ')} } from 'rnwind'`, `import './common.style'`];
306
+ for (const variant of variants)
307
+ lines.push(`import ${JSON.stringify(`./${variant}.style`)}`);
308
+ lines.push(``, `registerBreakpoints(${serializeBreakpoints(breakpoints)})`);
309
+ if (gradients.size > 0)
310
+ lines.push(`registerGradients(${serializeFeatureMap(gradients)})`);
311
+ if (haptics.size > 0)
312
+ lines.push(`registerHaptics(${serializeFeatureMap(haptics)})`);
313
+ lines.push(``, `function ensureSchemeLoaded(_name) {}`, ``, `registerSchemeLoader(ensureSchemeLoaded)`, ``, `export { ensureSchemeLoaded }`, ``);
314
+ return lines.join('\n');
315
+ }
316
+ /**
317
+ * Whether a resolved style carries a nested safe-area marker — molecules
318
+ * can't pre-bake these because the inset value is per-render.
319
+ * @param style Raw resolved RN style (pre-envelope).
320
+ * @returns True when any value is a `{__safe: ...}` marker.
321
+ */
322
+ function hasSafeMarker(style) {
323
+ for (const key of Object.keys(style)) {
324
+ const value = style[key];
325
+ if (typeof value !== 'object' || !value)
326
+ continue;
327
+ if ('__safe' in value)
328
+ return true;
329
+ }
330
+ return false;
331
+ }
332
+ /**
333
+ * Whether a resolved style has font-scale-sensitive props. Molecules
334
+ * can't pre-bake these because `fontSize`/`lineHeight` scale per-render
335
+ * with `useWindowDimensions().fontScale`.
336
+ * @param style Resolved RN style.
337
+ * @returns True when `fontSize` or `lineHeight` is present.
338
+ */
339
+ function hasFontScaleProperty(style) {
340
+ return 'fontSize' in style || 'lineHeight' in style;
341
+ }
342
+ /**
343
+ * Whether a token is a feature-only utility (gradient stop/direction,
344
+ * haptic, or text-truncate) that contributes NO RN `style` — the runtime
345
+ * resolver folds these in via `attachFeatures`, so they don't disqualify
346
+ * a molecule, they just merge nothing.
347
+ * @param token Atom name.
348
+ * @param gradients Gradient feature map.
349
+ * @param haptics Haptic feature map.
350
+ * @returns True when the token is a non-style feature.
351
+ */
352
+ function isFeatureToken(token, gradients, haptics) {
353
+ if (gradients.has(token) || haptics.has(token))
354
+ return true;
355
+ return token === 'truncate' || token === 'text-ellipsis' || token === 'text-clip' || token.startsWith('line-clamp-');
356
+ }
357
+ /**
358
+ * Resolve one atom's value under a scheme: the scheme's own non-empty
359
+ * bucket, falling back to canonical. `common` always reads canonical.
360
+ * @param schemed Parser-produced per-scheme bucket.
361
+ * @param scheme Scheme key (`'common'` or a variant name).
362
+ * @returns The atom's RN style for that scheme, or undefined.
363
+ */
364
+ function schemeValueOf(schemed, scheme) {
365
+ if (scheme === COMMON_SCHEME)
366
+ return canonicalValue(schemed);
367
+ const own = schemed[scheme];
368
+ return isNonEmptyStyle(own) ? own : canonicalValue(schemed);
369
+ }
370
+ /**
371
+ * Pre-merge a normalized className's atoms into ONE RN style object for a
372
+ * scheme, or null when the className is NOT molecule-eligible. A
373
+ * className is eligible only when every token is context-independent:
374
+ * - no variant prefix (`active:` / `focus:` / `md:` / `dark:` — anything
375
+ * with a `:`), so scheme/state/breakpoint gating never applies,
376
+ * - no `*-hairline`, `*-safe`, or font-scale (`fontSize`/`lineHeight`)
377
+ * atom, whose value is resolved per-render.
378
+ * Feature-only tokens (gradient / haptic / truncate) are skipped, not
379
+ * disqualifying — the runtime folds them in via `attachFeatures`. Unknown
380
+ * tokens disqualify so the atom path still surfaces the dev warning.
381
+ * @param tokens Normalized className tokens (order preserved).
382
+ * @param scheme Scheme key to resolve each atom under.
383
+ * @param resolved Per-atom schemed styles.
384
+ * @param keyframes Keyframes to inline into `animationName`.
385
+ * @param gradients Gradient feature map.
386
+ * @param haptics Haptic feature map.
387
+ * @returns Merged style object, or null when not eligible.
388
+ */
389
+ function mergeMolecule(tokens, scheme, resolved, keyframes, gradients, haptics) {
390
+ const merged = {};
391
+ for (const token of tokens) {
392
+ if (token.includes(':'))
393
+ return null;
394
+ if (isFeatureToken(token, gradients, haptics))
395
+ continue;
396
+ if (isHairlineAtom(token))
397
+ return null;
398
+ const schemed = resolved.get(token);
399
+ if (!schemed)
400
+ return null;
401
+ const raw = schemeValueOf(schemed, scheme);
402
+ if (!raw)
403
+ continue;
404
+ if (hasSafeMarker(raw) || hasFontScaleProperty(raw))
405
+ return null;
406
+ Object.assign(merged, inlineAnimationName(raw, keyframes));
407
+ }
408
+ return merged;
409
+ }
410
+ /**
411
+ * Emit each variant's molecule for one className — but only when the
412
+ * variant's merge DIFFERS from common (runtime falls back to common).
413
+ * @param normalized Normalized className key.
414
+ * @param tokens Normalized className tokens.
415
+ * @param commonText Serialized common-scheme merge for the diff check.
416
+ * @param variants Variant scheme names.
417
+ * @param variantMaps Mutable per-variant molecule collectors.
418
+ * @param resolved Per-atom schemed styles.
419
+ * @param keyframes Keyframes to inline.
420
+ * @param gradients Gradient feature map.
421
+ * @param haptics Haptic feature map.
422
+ */
423
+ function addVariantMolecules(normalized, tokens, commonText, variants, variantMaps, resolved, keyframes, gradients, haptics) {
424
+ for (const variant of variants) {
425
+ const variantMerged = mergeMolecule(tokens, variant, resolved, keyframes, gradients, haptics);
426
+ if (variantMerged === null)
427
+ continue;
428
+ if (JSON.stringify(variantMerged) !== commonText)
429
+ variantMaps[variant][normalized] = variantMerged;
270
430
  }
271
- lines.push(`const LOADERS = {`);
431
+ }
432
+ /**
433
+ * Build per-scheme molecules for every literal className the project
434
+ * uses. Each eligible className gets a pre-merged style object under
435
+ * `common`; a variant only carries an entry when its merge DIFFERS from
436
+ * common (runtime falls back `molecules[scheme] ?? molecules.common`).
437
+ * @param literals Distinct literal className strings (raw).
438
+ * @param resolved Per-atom schemed styles.
439
+ * @param keyframes Keyframes to inline.
440
+ * @param variants Variant scheme names.
441
+ * @param gradients Gradient feature map.
442
+ * @param haptics Haptic feature map.
443
+ * @returns scheme → (normalized className → merged style).
444
+ */
445
+ function buildMolecules(literals, resolved, keyframes, variants, gradients, haptics) {
446
+ const common = {};
447
+ const variantMaps = {};
448
+ for (const variant of variants)
449
+ variantMaps[variant] = {};
450
+ for (const literal of literals) {
451
+ const normalized = normalizeClassName(literal);
452
+ if (normalized.length === 0)
453
+ continue;
454
+ const tokens = normalized.split(' ');
455
+ const commonMerged = mergeMolecule(tokens, COMMON_SCHEME, resolved, keyframes, gradients, haptics);
456
+ if (commonMerged === null)
457
+ continue;
458
+ common[normalized] = commonMerged;
459
+ addVariantMolecules(normalized, tokens, JSON.stringify(commonMerged), variants, variantMaps, resolved, keyframes, gradients, haptics);
460
+ }
461
+ const out = { [COMMON_SCHEME]: common };
272
462
  for (const variant of variants) {
273
- lines.push(` ${JSON.stringify(variant)}: () => require(${JSON.stringify(`./${variant}.style`)}),`);
463
+ if (Object.keys(variantMaps[variant]).length > 0)
464
+ out[variant] = variantMaps[variant];
274
465
  }
275
- lines.push(`}`, ``, `function ensureSchemeLoaded(name) {`, ` const loader = LOADERS[name]`, ` if (loader) loader()`, `}`, ``, `registerSchemeLoader(ensureSchemeLoaded)`, ``, `export { ensureSchemeLoaded }`, ``);
276
- return lines.join('\n');
466
+ return out;
277
467
  }
278
468
  /**
279
469
  * Pre-serialize every non-empty variant value, reusing the per-atom
@@ -406,9 +596,14 @@ const EMPTY_BREAKPOINTS = new Map();
406
596
  * manifest emits `registerBreakpoints({...})` so the runtime can gate
407
597
  * `md:*` / `lg:*` atoms on `windowWidth`. Optional — empty when the
408
598
  * theme declares no breakpoints (legacy/test callers).
599
+ * @param gradients Gradient feature map (atom → role/colour) for the manifest + molecule eligibility.
600
+ * @param haptics Haptic feature map (atom → request) for the manifest + molecule eligibility.
601
+ * @param literals Distinct literal className strings — pre-merged into
602
+ * per-scheme molecules so the runtime resolver's O(1) molecule-first
603
+ * path is populated. Empty for legacy/test callers (atom path only).
409
604
  * @returns Per-scheme sources, manifest source, variant list.
410
605
  */
411
- function buildSchemeSources(atomNames, resolved, keyframes, cache, breakpoints = EMPTY_BREAKPOINTS) {
606
+ function buildSchemeSources(atomNames, resolved, keyframes, cache, breakpoints = EMPTY_BREAKPOINTS, gradients = EMPTY_FEATURE_MAP, haptics = EMPTY_FEATURE_MAP, literals = EMPTY_LITERALS) {
412
607
  const variants = collectVariantSchemes(resolved);
413
608
  const commonEntries = [];
414
609
  const variantEntries = {};
@@ -424,19 +619,24 @@ function buildSchemeSources(atomNames, resolved, keyframes, cache, breakpoints =
424
619
  continue;
425
620
  misses += collectAtomEntries(atom, schemed, canonical, variants, keyframes, commonEntries, variantEntries, cache);
426
621
  }
622
+ const molecules = buildMolecules(literals, resolved, keyframes, variants, gradients, haptics);
427
623
  const schemeSources = {
428
- [COMMON_SCHEME]: renderSchemeFile(COMMON_SCHEME, commonEntries),
624
+ [COMMON_SCHEME]: renderSchemeFile(COMMON_SCHEME, commonEntries, molecules[COMMON_SCHEME]),
429
625
  };
430
626
  for (const variant of variants) {
431
- schemeSources[variant] = renderSchemeFile(variant, variantEntries[variant]);
627
+ schemeSources[variant] = renderSchemeFile(variant, variantEntries[variant], molecules[variant]);
432
628
  }
433
629
  return {
434
630
  schemeSources,
435
- manifestSource: renderManifest(variants, breakpoints),
631
+ manifestSource: renderManifest(variants, breakpoints, gradients, haptics),
436
632
  variants,
437
633
  serializedMisses: misses,
438
634
  };
439
635
  }
636
+ /** Shared empty feature map default. */
637
+ const EMPTY_FEATURE_MAP = new Map();
638
+ /** Shared empty literal-list default (atom-only callers). */
639
+ const EMPTY_LITERALS = [];
440
640
 
441
641
  export { buildSchemeSources };
442
642
  //# sourceMappingURL=build-style.mjs.map