zyzz 0.0.21 → 0.0.23

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 (233) hide show
  1. package/Zyzz.podspec +16 -0
  2. package/dist/Style.d.ts +3 -1
  3. package/dist/Style.d.ts.map +1 -1
  4. package/dist/Style.js.map +1 -1
  5. package/dist/babel/NativeJsx.d.ts.map +1 -1
  6. package/dist/babel/NativeJsx.js +217 -15
  7. package/dist/babel/NativeJsx.js.map +1 -1
  8. package/dist/babel/index.d.ts +6 -2
  9. package/dist/babel/index.d.ts.map +1 -1
  10. package/dist/babel/index.js +10 -1
  11. package/dist/babel/index.js.map +1 -1
  12. package/dist/compiler/Graph.d.ts.map +1 -1
  13. package/dist/compiler/Graph.js +107 -0
  14. package/dist/compiler/Graph.js.map +1 -1
  15. package/dist/compiler/Native.d.ts +6 -3
  16. package/dist/compiler/Native.d.ts.map +1 -1
  17. package/dist/compiler/Native.js +142 -24
  18. package/dist/compiler/Native.js.map +1 -1
  19. package/dist/compiler/Source.d.ts +2 -0
  20. package/dist/compiler/Source.d.ts.map +1 -1
  21. package/dist/compiler/Source.js +17 -5
  22. package/dist/compiler/Source.js.map +1 -1
  23. package/dist/compiler/internal/Configurations.d.ts +2 -0
  24. package/dist/compiler/internal/Configurations.d.ts.map +1 -1
  25. package/dist/compiler/internal/Configurations.js +21 -3
  26. package/dist/compiler/internal/Configurations.js.map +1 -1
  27. package/dist/compiler/internal/Contract.d.ts.map +1 -1
  28. package/dist/compiler/internal/Contract.js +75 -38
  29. package/dist/compiler/internal/Contract.js.map +1 -1
  30. package/dist/compiler/internal/Contributions.d.ts.map +1 -1
  31. package/dist/compiler/internal/Contributions.js +7 -1
  32. package/dist/compiler/internal/Contributions.js.map +1 -1
  33. package/dist/compiler/internal/Descriptors.d.ts.map +1 -1
  34. package/dist/compiler/internal/Descriptors.js +11 -2
  35. package/dist/compiler/internal/Descriptors.js.map +1 -1
  36. package/dist/compiler/internal/Dynamic.d.ts.map +1 -1
  37. package/dist/compiler/internal/Dynamic.js +3 -0
  38. package/dist/compiler/internal/Dynamic.js.map +1 -1
  39. package/dist/compiler/internal/Expression.js +2 -2
  40. package/dist/compiler/internal/Expression.js.map +1 -1
  41. package/dist/compiler/internal/NativeBindings.d.ts +24 -26
  42. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  43. package/dist/compiler/internal/NativeBindings.js +62 -8
  44. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  45. package/dist/compiler/internal/NativeMedia.d.ts +14 -0
  46. package/dist/compiler/internal/NativeMedia.d.ts.map +1 -0
  47. package/dist/compiler/internal/NativeMedia.js +287 -0
  48. package/dist/compiler/internal/NativeMedia.js.map +1 -0
  49. package/dist/compiler/internal/NativeVars.d.ts +28 -0
  50. package/dist/compiler/internal/NativeVars.d.ts.map +1 -0
  51. package/dist/compiler/internal/NativeVars.js +160 -0
  52. package/dist/compiler/internal/NativeVars.js.map +1 -0
  53. package/dist/compiler/internal/PackedStyles.d.ts +2 -0
  54. package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
  55. package/dist/compiler/internal/PackedStyles.js +2 -0
  56. package/dist/compiler/internal/PackedStyles.js.map +1 -1
  57. package/dist/compiler/internal/Themes.d.ts +24 -1
  58. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  59. package/dist/compiler/internal/Themes.js +120 -3
  60. package/dist/compiler/internal/Themes.js.map +1 -1
  61. package/dist/compiler/internal/Vars.d.ts +1 -1
  62. package/dist/compiler/internal/Vars.d.ts.map +1 -1
  63. package/dist/compiler/internal/Vars.js +7 -2
  64. package/dist/compiler/internal/Vars.js.map +1 -1
  65. package/dist/default.js.zyzz.json +1 -1
  66. package/dist/internal/Completion.d.ts +1 -1
  67. package/dist/internal/Completion.d.ts.map +1 -1
  68. package/dist/internal/Targets.d.ts.map +1 -1
  69. package/dist/internal/Targets.js +2 -0
  70. package/dist/internal/Targets.js.map +1 -1
  71. package/dist/internal/Token.d.ts +0 -2
  72. package/dist/internal/Token.d.ts.map +1 -1
  73. package/dist/internal/Token.js.map +1 -1
  74. package/dist/internal/Value.d.ts +7 -0
  75. package/dist/internal/Value.d.ts.map +1 -1
  76. package/dist/internal/Value.js.map +1 -1
  77. package/dist/metro/Graph.d.ts +22 -7
  78. package/dist/metro/Graph.d.ts.map +1 -1
  79. package/dist/metro/Graph.js +188 -42
  80. package/dist/metro/Graph.js.map +1 -1
  81. package/dist/metro/Watch.d.ts +28 -4
  82. package/dist/metro/Watch.d.ts.map +1 -1
  83. package/dist/metro/Watch.js +39 -7
  84. package/dist/metro/Watch.js.map +1 -1
  85. package/dist/metro/index.d.ts +15 -4
  86. package/dist/metro/index.d.ts.map +1 -1
  87. package/dist/metro/index.js +27 -0
  88. package/dist/metro/index.js.map +1 -1
  89. package/dist/metro/transformer.d.ts +5 -1
  90. package/dist/metro/transformer.d.ts.map +1 -1
  91. package/dist/metro/transformer.js +5 -3
  92. package/dist/metro/transformer.js.map +1 -1
  93. package/dist/react-native/StyleSheet.d.ts +8 -3
  94. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  95. package/dist/react-native/StyleSheet.js +25 -34
  96. package/dist/react-native/StyleSheet.js.map +1 -1
  97. package/dist/react-native/index.d.ts +1 -1
  98. package/dist/react-native/index.d.ts.map +1 -1
  99. package/dist/react-native/index.js +1 -1
  100. package/dist/react-native/index.js.map +1 -1
  101. package/dist/react-native/index.native.d.ts +5 -0
  102. package/dist/react-native/index.native.js +20 -0
  103. package/dist/react-native/index.native.js.map +7 -0
  104. package/dist/react-native/internal/Calculation.d.ts +7 -0
  105. package/dist/react-native/internal/Calculation.d.ts.map +1 -0
  106. package/dist/react-native/internal/Calculation.js +134 -0
  107. package/dist/react-native/internal/Calculation.js.map +1 -0
  108. package/dist/react-native/internal/Device.js +218 -0
  109. package/dist/react-native/internal/Device.js.map +7 -0
  110. package/dist/react-native/internal/NativeZyzz.js +7 -0
  111. package/dist/react-native/internal/NativeZyzz.js.map +7 -0
  112. package/dist/react-native/internal/Scalar.d.ts +16 -0
  113. package/dist/react-native/internal/Scalar.d.ts.map +1 -1
  114. package/dist/react-native/internal/Scalar.js +62 -4
  115. package/dist/react-native/internal/Scalar.js.map +1 -1
  116. package/dist/react-native/internal/Store.d.ts +32 -0
  117. package/dist/react-native/internal/Store.d.ts.map +1 -0
  118. package/dist/react-native/internal/Store.js +57 -0
  119. package/dist/react-native/internal/Store.js.map +1 -0
  120. package/dist/react-native/internal/Styles.d.ts +19 -0
  121. package/dist/react-native/internal/Styles.d.ts.map +1 -0
  122. package/dist/react-native/internal/Styles.js +35 -0
  123. package/dist/react-native/internal/Styles.js.map +1 -0
  124. package/dist/react-native/internal/Tokens.d.ts +21 -0
  125. package/dist/react-native/internal/Tokens.d.ts.map +1 -0
  126. package/dist/react-native/internal/Tokens.js +58 -0
  127. package/dist/react-native/internal/Tokens.js.map +1 -0
  128. package/dist/react-native/internal/Viewport.d.ts +6 -0
  129. package/dist/react-native/internal/Viewport.d.ts.map +1 -0
  130. package/dist/react-native/internal/Viewport.js +4 -0
  131. package/dist/react-native/internal/Viewport.js.map +1 -0
  132. package/dist/react-native/react.d.ts +102 -14
  133. package/dist/react-native/react.d.ts.map +1 -1
  134. package/dist/react-native/react.js +86 -18
  135. package/dist/react-native/react.js.map +1 -1
  136. package/dist/react-native/reanimated.d.ts +39 -0
  137. package/dist/react-native/reanimated.d.ts.map +1 -0
  138. package/dist/react-native/reanimated.js +76 -0
  139. package/dist/react-native/reanimated.js.map +1 -0
  140. package/dist/runtime/NativeContext.d.ts +10 -1
  141. package/dist/runtime/NativeContext.d.ts.map +1 -1
  142. package/dist/runtime/NativeContext.js +67 -14
  143. package/dist/runtime/NativeContext.js.map +1 -1
  144. package/dist/runtime/NativeDynamic.d.ts +3 -2
  145. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  146. package/dist/runtime/NativeDynamic.js +37 -7
  147. package/dist/runtime/NativeDynamic.js.map +1 -1
  148. package/dist/runtime/NativeStatic.d.ts +23 -0
  149. package/dist/runtime/NativeStatic.d.ts.map +1 -0
  150. package/dist/runtime/NativeStatic.js +106 -0
  151. package/dist/runtime/NativeStatic.js.map +1 -0
  152. package/dist/runtime/NativeVars.d.ts +53 -0
  153. package/dist/runtime/NativeVars.d.ts.map +1 -0
  154. package/dist/runtime/NativeVars.js +77 -0
  155. package/dist/runtime/NativeVars.js.map +1 -0
  156. package/dist/runtime/index.d.ts +4 -0
  157. package/dist/runtime/index.d.ts.map +1 -1
  158. package/dist/runtime/index.js +4 -0
  159. package/dist/runtime/index.js.map +1 -1
  160. package/dist/runtime/internal/NativeMedia.d.ts +25 -0
  161. package/dist/runtime/internal/NativeMedia.d.ts.map +1 -0
  162. package/dist/runtime/internal/NativeMedia.js +36 -0
  163. package/dist/runtime/internal/NativeMedia.js.map +1 -0
  164. package/dist/vite/index.d.ts.map +1 -1
  165. package/dist/vite/index.js +7 -35
  166. package/dist/vite/index.js.map +1 -1
  167. package/dist/web/Css.d.ts.map +1 -1
  168. package/dist/web/Css.js +31 -19
  169. package/dist/web/Css.js.map +1 -1
  170. package/dist/web/internal/Themes.d.ts +0 -2
  171. package/dist/web/internal/Themes.d.ts.map +1 -1
  172. package/dist/web/internal/Themes.js +30 -28
  173. package/dist/web/internal/Themes.js.map +1 -1
  174. package/dist/web/page.d.ts +2 -0
  175. package/dist/web/page.d.ts.map +1 -1
  176. package/native/NativeZyzz.cpp +215 -0
  177. package/native/NativeZyzz.h +52 -0
  178. package/native/ZyzzSpecJSI.h +65 -0
  179. package/native/android/CMakeLists.txt +8 -0
  180. package/native/android/ZyzzSpec-generated.cpp +50 -0
  181. package/native/android/ZyzzSpec.h +31 -0
  182. package/native/ios/ZyzzModuleProvider.mm +13 -0
  183. package/package.json +39 -1
  184. package/react-native.config.ts +14 -0
  185. package/src/Style.ts +11 -8
  186. package/src/babel/NativeJsx.ts +319 -33
  187. package/src/babel/index.ts +21 -3
  188. package/src/compiler/Graph.ts +126 -0
  189. package/src/compiler/Native.ts +158 -26
  190. package/src/compiler/Source.ts +32 -5
  191. package/src/compiler/internal/Configurations.ts +25 -3
  192. package/src/compiler/internal/Contract.ts +98 -50
  193. package/src/compiler/internal/Contributions.ts +7 -1
  194. package/src/compiler/internal/Descriptors.ts +12 -8
  195. package/src/compiler/internal/Dynamic.ts +4 -0
  196. package/src/compiler/internal/Expression.ts +2 -2
  197. package/src/compiler/internal/NativeBindings.ts +99 -15
  198. package/src/compiler/internal/NativeMedia.ts +342 -0
  199. package/src/compiler/internal/NativeVars.ts +231 -0
  200. package/src/compiler/internal/PackedStyles.ts +4 -0
  201. package/src/compiler/internal/Themes.ts +193 -2
  202. package/src/compiler/internal/Vars.ts +12 -1
  203. package/src/internal/Completion.ts +6 -3
  204. package/src/internal/Targets.ts +2 -0
  205. package/src/internal/Token.ts +0 -2
  206. package/src/internal/Value.ts +20 -0
  207. package/src/metro/Graph.ts +250 -50
  208. package/src/metro/Watch.ts +95 -10
  209. package/src/metro/index.ts +62 -4
  210. package/src/metro/transformer.ts +21 -11
  211. package/src/react-native/StyleSheet.ts +56 -49
  212. package/src/react-native/index.native.ts +10 -0
  213. package/src/react-native/index.ts +1 -1
  214. package/src/react-native/internal/Calculation.ts +159 -0
  215. package/src/react-native/internal/Device.ts +297 -0
  216. package/src/react-native/internal/NativeZyzz.ts +20 -0
  217. package/src/react-native/internal/Scalar.ts +86 -4
  218. package/src/react-native/internal/Store.ts +78 -0
  219. package/src/react-native/internal/Styles.ts +77 -0
  220. package/src/react-native/internal/Tokens.ts +85 -0
  221. package/src/react-native/internal/Viewport.ts +8 -0
  222. package/src/react-native/react.ts +218 -26
  223. package/src/react-native/reanimated.ts +162 -0
  224. package/src/runtime/NativeContext.ts +88 -17
  225. package/src/runtime/NativeDynamic.ts +42 -10
  226. package/src/runtime/NativeStatic.ts +145 -0
  227. package/src/runtime/NativeVars.ts +189 -0
  228. package/src/runtime/index.ts +6 -0
  229. package/src/runtime/internal/NativeMedia.ts +63 -0
  230. package/src/vite/index.ts +9 -42
  231. package/src/web/Css.ts +42 -27
  232. package/src/web/internal/Themes.ts +34 -57
  233. package/src/web/page.ts +2 -0
@@ -0,0 +1,162 @@
1
+ /** Publishes scoped native selections to Reanimated shared values. @module */
2
+ import * as NativeContext from '../runtime/NativeContext.js'
3
+ import * as NativeVars from '../runtime/NativeVars.js'
4
+ import * as React from 'react'
5
+ import * as ReactNative from 'react-native'
6
+ import * as Reanimated from 'react-native-reanimated'
7
+ import * as Store from './internal/Store.js'
8
+ import type * as StyleSheet from './StyleSheet.js'
9
+ import type * as Vars from '../Vars.js'
10
+
11
+ /**
12
+ * Reads a numeric or color property from a selected, composed native style.
13
+ * @param appliedStyle - Applied native style, including arrays and caller overrides.
14
+ * @param property - Numeric or color property to publish.
15
+ * @returns A shared value updated on style input and Provider selection changes.
16
+ * @throws For unresolved bindings, missing properties, or unsupported property values.
17
+ */
18
+ export function useAnimatedStyleValue<
19
+ const property extends useAnimatedStyleValue.Property,
20
+ >(
21
+ appliedStyle: StyleSheet.StyleProp<object>,
22
+ property: property,
23
+ ): Reanimated.SharedValue<useAnimatedStyleValue.Value<property>> {
24
+ const store = React.useContext(Store.context)
25
+ const read = React.useCallback(() => {
26
+ const resolved = NativeContext.resolve(appliedStyle, store?.getSnapshot())
27
+ const style = ReactNative.StyleSheet.flatten(
28
+ resolved as ReactNative.StyleProp<ReactNative.ViewStyle>,
29
+ )
30
+ const value = style?.[property as keyof ReactNative.ViewStyle]
31
+ const color = property === 'color' || property.endsWith('Color')
32
+ if (
33
+ color
34
+ ? typeof value !== 'string' || ReactNative.processColor(value) == null
35
+ : typeof value !== 'number' || !Number.isFinite(value)
36
+ )
37
+ throw new Error(
38
+ `useAnimatedStyleValue requires a numeric or color value for ${property}.`,
39
+ )
40
+
41
+ return value as useAnimatedStyleValue.Value<property>
42
+ }, [appliedStyle, property, store])
43
+
44
+ return useSelection(read, store)
45
+ }
46
+
47
+ /** Properties supported by scalar style animation bindings. */
48
+ export declare namespace useAnimatedStyleValue {
49
+ /** Numeric native properties and native color properties. */
50
+ type Property = {
51
+ [key in keyof StyleSheet.NativeStyle]-?: key extends
52
+ | 'color'
53
+ | `${string}Color`
54
+ ? key
55
+ : Extract<StyleSheet.NativeStyle[key], number> extends never
56
+ ? never
57
+ : key
58
+ }[keyof StyleSheet.NativeStyle] &
59
+ string
60
+ /** Color strings or finite numeric selections, excluding layout strings. */
61
+ type Value<property extends Property> = property extends
62
+ | 'color'
63
+ | `${string}Color`
64
+ ? string
65
+ : number
66
+ }
67
+
68
+ /**
69
+ * Publishes compiled native variables without subscribing the component to React updates.
70
+ * @param variables - Statically linked variables from the owning configuration.
71
+ * @param select - Optional JS selector, with results compared using Object.is.
72
+ * @returns A shared value containing the selected serializable native values.
73
+ * @throws For a missing Provider, unsupported variable reads, or nonserializable selections.
74
+ */
75
+ export function useAnimatedVars<const variables extends Vars.Definition>(
76
+ variables: variables,
77
+ select?: undefined,
78
+ ): Reanimated.SharedValue<NativeVars.Values<Vars.Extract<variables>>>
79
+ /** Publishes a selected variable value to the UI thread. */
80
+ export function useAnimatedVars<
81
+ const variables extends Vars.Definition,
82
+ selected extends useAnimatedVars.Value,
83
+ >(
84
+ variables: variables,
85
+ select: (values: NativeVars.Values<Vars.Extract<variables>>) => selected,
86
+ ): Reanimated.SharedValue<selected>
87
+ export function useAnimatedVars(
88
+ variables: Vars.Definition,
89
+ select: (values: never) => unknown = (values) => values,
90
+ ) {
91
+ const store = React.useContext(Store.context)
92
+ if (!store) throw new Error('useAnimatedVars requires a Zyzz Provider.')
93
+
94
+ const read = React.useCallback(
95
+ () => select(NativeVars.read(variables, store.getSnapshot()) as never),
96
+ [select, store, variables],
97
+ )
98
+
99
+ return useSelection(read, store)
100
+ }
101
+
102
+ /** Serializable selections shared with animation worklets. */
103
+ export declare namespace useAnimatedVars {
104
+ /** Plain native values, arrays, and objects without functions or class instances. */
105
+ type Value =
106
+ | boolean
107
+ | null
108
+ | number
109
+ | readonly Value[]
110
+ | string
111
+ | undefined
112
+ | { readonly [key: string]: Value }
113
+ }
114
+
115
+ function useSelection<value>(
116
+ read: () => value,
117
+ store: ReturnType<typeof Store.create> | undefined,
118
+ ): Reanimated.SharedValue<value> {
119
+ const initial = read()
120
+ serializable(initial)
121
+ const previous = React.useRef(initial)
122
+ const shared = Reanimated.useSharedValue(initial)
123
+
124
+ React.useLayoutEffect(() => {
125
+ const update = () => {
126
+ const next = read()
127
+ if (Object.is(previous.current, next)) return
128
+ serializable(next)
129
+ shared.set(next)
130
+ previous.current = next
131
+ }
132
+ update()
133
+ return store?.subscribe(update)
134
+ }, [read, shared, store])
135
+
136
+ return shared
137
+ }
138
+
139
+ function serializable(value: unknown, ancestors = new Set<object>()): void {
140
+ if (value == null || typeof value === 'string' || typeof value === 'boolean')
141
+ return
142
+ if (typeof value === 'number' && Number.isFinite(value)) return
143
+ if (
144
+ typeof value !== 'object' ||
145
+ (Array.isArray(value)
146
+ ? Object.getPrototypeOf(value) !== Array.prototype
147
+ : Object.getPrototypeOf(value) !== Object.prototype &&
148
+ Object.getPrototypeOf(value) !== null)
149
+ )
150
+ throw new Error(
151
+ 'Animated selections require finite, serializable native values.',
152
+ )
153
+
154
+ if (ancestors.has(value))
155
+ throw new Error('Animated selections cannot contain cycles.')
156
+ if (Object.getOwnPropertySymbols(value).length)
157
+ throw new Error('Animated selections cannot contain symbol properties.')
158
+
159
+ ancestors.add(value)
160
+ for (const entry of Object.values(value)) serializable(entry, ancestors)
161
+ ancestors.delete(value)
162
+ }
@@ -1,7 +1,9 @@
1
- /** Defers precompiled native selection until a React render supplies its context. @module */
1
+ /** Defers precompiled native selection until a consumer supplies its scoped selection. @module */
2
2
  import type * as Native from './Native.js'
3
+ import * as Media from './internal/NativeMedia.js'
3
4
 
4
5
  const binding = Symbol('zyzz.native.context')
6
+ const dependency = Symbol('zyzz.native.dependency')
5
7
 
6
8
  /** Render-local set and resolved appearance. */
7
9
  export type Context = {
@@ -9,6 +11,8 @@ export type Context = {
9
11
  readonly colorScheme: 'dark' | 'light'
10
12
  /** Named compiled set; omission uses each configuration default. */
11
13
  readonly set?: string | undefined
14
+ /** Logical window dimensions supplied by the native adapter. */
15
+ readonly viewport?: Media.Viewport | undefined
12
16
  }
13
17
 
14
18
  type Callable = (input: never) => Native.Props<object>
@@ -25,6 +29,9 @@ export function create<const tables extends Tables>(
25
29
  Object.keys(tables).length === 1 && defaultVars === 'default'
26
30
  ? tables.default
27
31
  : undefined
32
+ const selections = new Set(
33
+ Object.values(tables).flatMap((table) => [table.dark, table.light]),
34
+ )
28
35
 
29
36
  function select(context: Context, input?: never) {
30
37
  const set = context.set ?? defaultVars
@@ -32,22 +39,12 @@ export function create<const tables extends Tables>(
32
39
  if (!table) throw new Error(`Unknown native set: ${set}.`)
33
40
  return table[context.colorScheme](input!).style
34
41
  }
35
- function props(input?: never) {
36
- return {
37
- style: {
38
- [binding]: (context: Context) => select(context, input),
39
- },
40
- }
41
- }
42
- const defaults = props()
43
- Object.freeze(defaults.style)
44
- Object.freeze(defaults)
45
- const callable = (input?: never) =>
46
- input === undefined ? defaults : props(input)
47
- Object.defineProperty(callable, binding, {
48
- value: select,
49
- })
50
- return callable as tables[keyof tables]['light']
42
+ return bind(select, (context) => {
43
+ const table = tables[context.set ?? defaultVars] ?? fallback
44
+ if (!table)
45
+ throw new Error(`Unknown native set: ${context.set ?? defaultVars}.`)
46
+ return selections.size === 1 ? [] : [table[context.colorScheme]]
47
+ }) as tables[keyof tables]['light']
51
48
  }
52
49
 
53
50
  /** Resolves generated bindings and arrays while preserving caller-owned native objects. */
@@ -72,3 +69,77 @@ export function resolve(
72
69
  return (...args: unknown[]) => resolve(value(...args), context)
73
70
  return value
74
71
  }
72
+
73
+ /** Selects a precompiled media alternative before applying its scoped styling. */
74
+ export function responsive<
75
+ const profiles extends Readonly<Record<string, Callable>>,
76
+ >(
77
+ queries: readonly Media.Query[],
78
+ profiles: profiles,
79
+ ): profiles[keyof profiles] {
80
+ return bind(
81
+ (context, input) => {
82
+ const profile = profiles[Media.select(queries, context.viewport)]
83
+ if (!profile) throw new Error('Native media alternative is missing.')
84
+ return resolve(profile, context, input)
85
+ },
86
+ (context) => {
87
+ const profile = profiles[Media.select(queries, context.viewport)]
88
+ return [profile, ...key(profile, context)]
89
+ },
90
+ ) as unknown as profiles[keyof profiles]
91
+ }
92
+
93
+ /** Retains a compiled application and its input until a view selects its context. */
94
+ export function application(value: unknown, input?: unknown): unknown {
95
+ return {
96
+ [binding]: (context: Context) => resolve(value, context, input),
97
+ [dependency]: (context: Context) => [
98
+ ...key(value, context),
99
+ ...key(input, context),
100
+ ],
101
+ }
102
+ }
103
+
104
+ /** Returns compiler-owned alternatives consumed by a native style application. */
105
+ export function key(value: unknown, context: Context): readonly unknown[] {
106
+ if (Array.isArray(value)) return value.flatMap((entry) => key(entry, context))
107
+ if (
108
+ value &&
109
+ (typeof value === 'object' || typeof value === 'function') &&
110
+ dependency in value
111
+ ) {
112
+ const read = value[dependency]
113
+ if (typeof read === 'function') return read(context)
114
+ }
115
+ if (value && typeof value === 'object' && 'style' in value)
116
+ return key(value.style, context)
117
+ return []
118
+ }
119
+
120
+ function bind(
121
+ select: (context: Context, input?: never) => unknown,
122
+ read: (context: Context) => readonly unknown[],
123
+ ) {
124
+ function props(input?: never) {
125
+ return {
126
+ style: {
127
+ [binding]: (context: Context) => select(context, input),
128
+ [dependency]: (context: Context) => [
129
+ ...read(context),
130
+ ...key(input, context),
131
+ ],
132
+ },
133
+ }
134
+ }
135
+ const defaults = props()
136
+ Object.freeze(defaults.style)
137
+ Object.freeze(defaults)
138
+ const callable = (input?: never) =>
139
+ input === undefined ? defaults : props(input)
140
+ Object.defineProperties(callable, {
141
+ [binding]: { value: select },
142
+ [dependency]: { value: read },
143
+ })
144
+ return callable
145
+ }
@@ -1,5 +1,6 @@
1
- /** Applies compiled native binding instructions to runtime scalar payloads. @module */
1
+ /** Selects ordered native fragments and applies runtime scalar payloads. @module */
2
2
  import * as Scalar from '../react-native/internal/Scalar.js'
3
+ import * as Calculation from '../react-native/internal/Calculation.js'
3
4
  import type * as StyleSheet from '../react-native/StyleSheet.js'
4
5
  import * as Native from './Native.js'
5
6
  import type * as Recipe from './Recipe.js'
@@ -45,14 +46,14 @@ export type Program = {
45
46
  readonly parts: readonly (
46
47
  | string
47
48
  | number
48
- | { readonly slot: string }
49
+ | { readonly slot: string; readonly number?: true }
49
50
  )[]
50
51
  }
51
52
  )[]
52
53
  }[]
53
54
  }
54
55
 
55
- /** Binds generated instructions without running authoring callbacks or generating CSS. */
56
+ /** Applies generated rules with bounded static caching and fresh scalar results. */
56
57
  export function create(
57
58
  options: create.Options,
58
59
  ): Callable<Record<string, unknown>> {
@@ -63,6 +64,10 @@ export function create(
63
64
  Object.freeze(value)
64
65
  }
65
66
  for (const style of Object.values(options.styles)) freeze(style)
67
+ const cache =
68
+ Object.keys(options.program.slots).length || options.payloads?.length
69
+ ? undefined
70
+ : new Map<string, StyleSheet.NativeStyle>()
66
71
 
67
72
  return (input = {}) => {
68
73
  const bindings: Record<string, string | number> = Object.create(null)
@@ -98,7 +103,9 @@ export function create(
98
103
  for (const [axis, choices] of Object.entries(options.axes)) {
99
104
  let value = Object.hasOwn(input, axis) ? input[axis] : undefined
100
105
  if (value === undefined) {
101
- value = options.defaults[axis]
106
+ value = Object.hasOwn(options.defaults, axis)
107
+ ? options.defaults[axis]
108
+ : undefined
102
109
  if (value != null && Object.hasOwn(options.defaultPayloads ?? {}, axis))
103
110
  value = { [String(value)]: options.defaultPayloads![axis] }
104
111
  }
@@ -107,7 +114,8 @@ export function create(
107
114
  continue
108
115
  }
109
116
  let payload: unknown
110
- if (typeof value === 'object' && !Array.isArray(value)) {
117
+ const scoped = typeof value === 'object' && !Array.isArray(value)
118
+ if (scoped) {
111
119
  const entries = Object.entries(value)
112
120
  if (entries.length !== 1)
113
121
  throw new Native.SelectionError(
@@ -128,12 +136,16 @@ export function create(
128
136
  (entry) => entry.axis === axis && entry.choice === choice,
129
137
  )
130
138
  if (definition) bind(definition.slots[0]!, payload)
131
- else if (payload !== undefined)
139
+ else if (scoped)
132
140
  throw new Native.SelectionError(
133
141
  `Static native choice does not accept a payload: ${axis}.`,
134
142
  )
135
143
  }
136
144
 
145
+ const key = cache ? JSON.stringify(Object.values(selected)) : undefined
146
+ const cached = key === undefined ? undefined : cache?.get(key)
147
+ if (cached) return { style: input.style ? [cached, input.style] : cached }
148
+
137
149
  const output: Record<string, unknown> = {}
138
150
  let lineHeight: string | number | undefined
139
151
  for (const rule of options.program.rules) {
@@ -155,9 +167,15 @@ export function create(
155
167
  if (style.lineHeight !== undefined) lineHeight = undefined
156
168
  continue
157
169
  }
158
- const values = step.parts.map((part) =>
159
- typeof part === 'object' ? bindings[part.slot] : part,
160
- )
170
+ const values = step.parts.map((part) => {
171
+ if (typeof part !== 'object') return part
172
+ const value = bindings[part.slot]
173
+ if (value !== undefined && part.number && typeof value !== 'number')
174
+ throw new Native.SelectionError(
175
+ 'Native numeric bindings require numbers.',
176
+ )
177
+ return value
178
+ })
161
179
  if (values.some((value) => value === undefined))
162
180
  throw new Native.SelectionError(
163
181
  'Native binding program references an unbound slot.',
@@ -176,7 +194,7 @@ export function create(
176
194
  )
177
195
  if (property === 'padding' || property === 'margin') {
178
196
  const parts =
179
- typeof value === 'string' ? value.trim().split(/\s+/) : [value]
197
+ typeof value === 'string' ? Calculation.parts(value) : [value]
180
198
  const [top, right = top, bottom = top, left = right] = parts.map(
181
199
  (part) =>
182
200
  Scalar.length(part, options, property === 'margin', [property]),
@@ -187,6 +205,13 @@ export function create(
187
205
  [`${property}Bottom`]: bottom,
188
206
  [`${property}Left`]: left,
189
207
  })
208
+ } else if (property === 'inset') {
209
+ const parts =
210
+ typeof value === 'string' ? Calculation.parts(value) : [value]
211
+ const [top, right = top, bottom = top, left = right] = parts.map(
212
+ (part) => Scalar.convert('offset', part, options, [property]),
213
+ )
214
+ Object.assign(output, { top, right, bottom, left })
190
215
  } else if (property === 'borderColor' || property === 'borderWidth') {
191
216
  for (const side of ['Top', 'Right', 'Bottom', 'Left'])
192
217
  output[
@@ -227,6 +252,13 @@ export function create(
227
252
  ])
228
253
  }
229
254
  const style = output as StyleSheet.NativeStyle
255
+ if (cache && key !== undefined) {
256
+ Object.freeze(style)
257
+ // Bound retained selections without limiting the recipe's choice space.
258
+ if (cache.size >= 256) cache.delete(cache.keys().next().value!)
259
+ cache.set(key, style)
260
+ }
261
+
230
262
  return { style: input.style ? [style, input.style] : style }
231
263
  }
232
264
  }
@@ -0,0 +1,145 @@
1
+ /** Selects finite native variants from precompiled fragments. @module */
2
+ import * as Native from './Native.js'
3
+ import type * as StyleSheet from '../react-native/StyleSheet.js'
4
+
5
+ /**
6
+ * Binds finite selection data without including dynamic value converters.
7
+ * @param options - Compiler-owned axes, defaults, ordered rules, and native fragments.
8
+ * @returns A callable preserving finite choices and caller-owned native overrides.
9
+ * @throws {Native.SelectionError} For invalid selections, missing fragments, or invalid line heights.
10
+ */
11
+ export function create<
12
+ const axes extends Readonly<Record<string, readonly string[]>>,
13
+ >(options: create.Options<axes>): Native.Callable<axes> {
14
+ const axes = Object.entries(options.axes)
15
+ const rules = options.rules.map((rule) => ({
16
+ matches: rule.matches.map(([name, choices]) => {
17
+ const axis = axes.findIndex(([key]) => key === name)
18
+ return [
19
+ axis,
20
+ choices.map((choice) => axes[axis]![1].indexOf(choice)),
21
+ ] as const
22
+ }),
23
+ steps: rule.steps.map((step) => {
24
+ if (typeof step === 'number') return step
25
+ const style = options.styles[step]
26
+ if (!style)
27
+ throw new Native.SelectionError(
28
+ 'Native binding program is missing a static style.',
29
+ )
30
+ freeze(style)
31
+ return style
32
+ }),
33
+ }))
34
+ const cache = new Map<string, StyleSheet.NativeStyle>()
35
+
36
+ return (input = {}) => {
37
+ for (const key of Object.keys(input))
38
+ if (key !== 'style' && !Object.hasOwn(options.axes, key))
39
+ throw new Native.SelectionError(`Unknown native recipe input: ${key}.`)
40
+ const selected: number[] = []
41
+ let key = ''
42
+ const values: Readonly<Record<string, unknown>> = input
43
+ for (const [axis, choices] of axes) {
44
+ const supplied = Object.hasOwn(values, axis) ? values[axis] : undefined
45
+ const value =
46
+ supplied === undefined
47
+ ? Object.hasOwn(options.defaults, axis)
48
+ ? options.defaults[axis]
49
+ : undefined
50
+ : supplied
51
+ if (
52
+ value !== null &&
53
+ typeof value === 'object' &&
54
+ !Array.isArray(value)
55
+ ) {
56
+ const entries = Object.keys(value)
57
+ if (entries.length !== 1)
58
+ throw new Native.SelectionError(
59
+ 'Native payloads require exactly one choice.',
60
+ )
61
+ if (choices.includes(entries[0]!))
62
+ throw new Native.SelectionError(
63
+ `Static native choice does not accept a payload: ${axis}.`,
64
+ )
65
+ }
66
+ const choice =
67
+ value == null
68
+ ? choices.length
69
+ : typeof value === 'string' || typeof value === 'boolean'
70
+ ? choices.indexOf(String(value))
71
+ : -1
72
+ if (choice < 0)
73
+ throw new Native.SelectionError(
74
+ `Unknown native recipe choice for ${axis}.`,
75
+ )
76
+ selected.push(choice)
77
+ key += `${choice},`
78
+ }
79
+ let style = cache.get(key)
80
+ if (!style) {
81
+ const output: Record<string, unknown> = {}
82
+ let lineHeight: number | undefined
83
+ for (const rule of rules) {
84
+ if (
85
+ !rule.matches.every(([axis, choices]) =>
86
+ choices.includes(selected[axis]!),
87
+ )
88
+ )
89
+ continue
90
+ for (const step of rule.steps) {
91
+ if (typeof step === 'number') lineHeight = step
92
+ else {
93
+ Object.assign(output, step)
94
+ if (step.lineHeight !== undefined) lineHeight = undefined
95
+ }
96
+ }
97
+ }
98
+ if (lineHeight !== undefined) {
99
+ if (
100
+ !Number.isFinite(lineHeight) ||
101
+ lineHeight < 0 ||
102
+ typeof output.fontSize !== 'number'
103
+ )
104
+ throw new Native.SelectionError(
105
+ 'Numeric lineHeight requires an explicit fontSize and a nonnegative finite multiplier.',
106
+ )
107
+ output.lineHeight = lineHeight * output.fontSize
108
+ if (!Number.isFinite(output.lineHeight))
109
+ throw new Native.SelectionError(
110
+ 'Converted lineHeight must be finite.',
111
+ )
112
+ }
113
+ style = Object.freeze(output) as StyleSheet.NativeStyle
114
+ // Retain a bounded working set even for recipes with many independent axes.
115
+ if (cache.size >= 256) cache.delete(cache.keys().next().value!)
116
+ cache.set(key, style)
117
+ }
118
+ return { style: input.style ? [style, input.style] : style }
119
+ }
120
+ }
121
+
122
+ /** Compiler-owned finite metadata and converted native fragments. */
123
+ export declare namespace create {
124
+ /** Numeric steps are line-height multipliers resolved against the final font size. */
125
+ type Options<
126
+ axes extends Readonly<Record<string, readonly string[]>> = Readonly<
127
+ Record<string, readonly string[]>
128
+ >,
129
+ > = Native.create.Options<axes> & {
130
+ /** Ordered rules containing static fragment names and line-height multipliers. */
131
+ readonly rules: readonly {
132
+ /** Axis names and allowed choices, all of which must match. */
133
+ readonly matches: readonly (readonly [string, readonly string[]])[]
134
+ /** Fragment names or line-height multipliers applied in order. */
135
+ readonly steps: readonly (string | number)[]
136
+ }[]
137
+ }
138
+ }
139
+
140
+ function freeze(value: object): void {
141
+ if (Object.isFrozen(value)) return
142
+ for (const child of Object.values(value))
143
+ if (child && typeof child === 'object') freeze(child)
144
+ Object.freeze(value)
145
+ }