zyzz 0.0.21 → 0.0.22

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 (220) 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 +204 -12
  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 +64 -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/NativeBindings.d.ts +24 -26
  37. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  38. package/dist/compiler/internal/NativeBindings.js +61 -8
  39. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  40. package/dist/compiler/internal/NativeMedia.d.ts +14 -0
  41. package/dist/compiler/internal/NativeMedia.d.ts.map +1 -0
  42. package/dist/compiler/internal/NativeMedia.js +287 -0
  43. package/dist/compiler/internal/NativeMedia.js.map +1 -0
  44. package/dist/compiler/internal/NativeVars.d.ts +28 -0
  45. package/dist/compiler/internal/NativeVars.d.ts.map +1 -0
  46. package/dist/compiler/internal/NativeVars.js +157 -0
  47. package/dist/compiler/internal/NativeVars.js.map +1 -0
  48. package/dist/compiler/internal/PackedStyles.d.ts +2 -0
  49. package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
  50. package/dist/compiler/internal/PackedStyles.js +2 -0
  51. package/dist/compiler/internal/PackedStyles.js.map +1 -1
  52. package/dist/compiler/internal/Themes.d.ts +22 -1
  53. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  54. package/dist/compiler/internal/Themes.js +118 -3
  55. package/dist/compiler/internal/Themes.js.map +1 -1
  56. package/dist/compiler/internal/Vars.d.ts +1 -1
  57. package/dist/compiler/internal/Vars.d.ts.map +1 -1
  58. package/dist/compiler/internal/Vars.js +7 -2
  59. package/dist/compiler/internal/Vars.js.map +1 -1
  60. package/dist/default.js.zyzz.json +1 -1
  61. package/dist/internal/Completion.d.ts +1 -1
  62. package/dist/internal/Completion.d.ts.map +1 -1
  63. package/dist/internal/Targets.d.ts.map +1 -1
  64. package/dist/internal/Targets.js +2 -0
  65. package/dist/internal/Targets.js.map +1 -1
  66. package/dist/internal/Token.d.ts +0 -2
  67. package/dist/internal/Token.d.ts.map +1 -1
  68. package/dist/internal/Token.js.map +1 -1
  69. package/dist/internal/Value.d.ts +7 -0
  70. package/dist/internal/Value.d.ts.map +1 -1
  71. package/dist/internal/Value.js.map +1 -1
  72. package/dist/metro/Graph.d.ts +22 -7
  73. package/dist/metro/Graph.d.ts.map +1 -1
  74. package/dist/metro/Graph.js +179 -42
  75. package/dist/metro/Graph.js.map +1 -1
  76. package/dist/metro/Watch.d.ts +28 -4
  77. package/dist/metro/Watch.d.ts.map +1 -1
  78. package/dist/metro/Watch.js +39 -7
  79. package/dist/metro/Watch.js.map +1 -1
  80. package/dist/metro/index.d.ts +15 -4
  81. package/dist/metro/index.d.ts.map +1 -1
  82. package/dist/metro/index.js +27 -0
  83. package/dist/metro/index.js.map +1 -1
  84. package/dist/metro/transformer.d.ts +5 -1
  85. package/dist/metro/transformer.d.ts.map +1 -1
  86. package/dist/metro/transformer.js +5 -3
  87. package/dist/metro/transformer.js.map +1 -1
  88. package/dist/react-native/StyleSheet.d.ts +3 -2
  89. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  90. package/dist/react-native/StyleSheet.js +17 -34
  91. package/dist/react-native/StyleSheet.js.map +1 -1
  92. package/dist/react-native/index.d.ts +1 -1
  93. package/dist/react-native/index.d.ts.map +1 -1
  94. package/dist/react-native/index.js +1 -1
  95. package/dist/react-native/index.js.map +1 -1
  96. package/dist/react-native/index.native.d.ts +5 -0
  97. package/dist/react-native/index.native.js +20 -0
  98. package/dist/react-native/index.native.js.map +7 -0
  99. package/dist/react-native/internal/Calculation.d.ts +7 -0
  100. package/dist/react-native/internal/Calculation.d.ts.map +1 -0
  101. package/dist/react-native/internal/Calculation.js +134 -0
  102. package/dist/react-native/internal/Calculation.js.map +1 -0
  103. package/dist/react-native/internal/Device.js +191 -0
  104. package/dist/react-native/internal/Device.js.map +7 -0
  105. package/dist/react-native/internal/NativeZyzz.js +7 -0
  106. package/dist/react-native/internal/NativeZyzz.js.map +7 -0
  107. package/dist/react-native/internal/Scalar.d.ts.map +1 -1
  108. package/dist/react-native/internal/Scalar.js +9 -2
  109. package/dist/react-native/internal/Scalar.js.map +1 -1
  110. package/dist/react-native/internal/Store.d.ts +32 -0
  111. package/dist/react-native/internal/Store.d.ts.map +1 -0
  112. package/dist/react-native/internal/Store.js +57 -0
  113. package/dist/react-native/internal/Store.js.map +1 -0
  114. package/dist/react-native/internal/Tokens.d.ts +21 -0
  115. package/dist/react-native/internal/Tokens.d.ts.map +1 -0
  116. package/dist/react-native/internal/Tokens.js +58 -0
  117. package/dist/react-native/internal/Tokens.js.map +1 -0
  118. package/dist/react-native/internal/Viewport.d.ts +6 -0
  119. package/dist/react-native/internal/Viewport.d.ts.map +1 -0
  120. package/dist/react-native/internal/Viewport.js +4 -0
  121. package/dist/react-native/internal/Viewport.js.map +1 -0
  122. package/dist/react-native/react.d.ts +74 -11
  123. package/dist/react-native/react.d.ts.map +1 -1
  124. package/dist/react-native/react.js +111 -12
  125. package/dist/react-native/react.js.map +1 -1
  126. package/dist/react-native/reanimated.d.ts +39 -0
  127. package/dist/react-native/reanimated.d.ts.map +1 -0
  128. package/dist/react-native/reanimated.js +76 -0
  129. package/dist/react-native/reanimated.js.map +1 -0
  130. package/dist/runtime/NativeContext.d.ts +10 -1
  131. package/dist/runtime/NativeContext.d.ts.map +1 -1
  132. package/dist/runtime/NativeContext.js +67 -14
  133. package/dist/runtime/NativeContext.js.map +1 -1
  134. package/dist/runtime/NativeDynamic.d.ts +3 -2
  135. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  136. package/dist/runtime/NativeDynamic.js +32 -7
  137. package/dist/runtime/NativeDynamic.js.map +1 -1
  138. package/dist/runtime/NativeStatic.d.ts +23 -0
  139. package/dist/runtime/NativeStatic.d.ts.map +1 -0
  140. package/dist/runtime/NativeStatic.js +106 -0
  141. package/dist/runtime/NativeStatic.js.map +1 -0
  142. package/dist/runtime/NativeVars.d.ts +53 -0
  143. package/dist/runtime/NativeVars.d.ts.map +1 -0
  144. package/dist/runtime/NativeVars.js +77 -0
  145. package/dist/runtime/NativeVars.js.map +1 -0
  146. package/dist/runtime/index.d.ts +4 -0
  147. package/dist/runtime/index.d.ts.map +1 -1
  148. package/dist/runtime/index.js +4 -0
  149. package/dist/runtime/index.js.map +1 -1
  150. package/dist/runtime/internal/NativeMedia.d.ts +25 -0
  151. package/dist/runtime/internal/NativeMedia.d.ts.map +1 -0
  152. package/dist/runtime/internal/NativeMedia.js +36 -0
  153. package/dist/runtime/internal/NativeMedia.js.map +1 -0
  154. package/dist/vite/index.d.ts.map +1 -1
  155. package/dist/vite/index.js +7 -35
  156. package/dist/vite/index.js.map +1 -1
  157. package/dist/web/Css.d.ts.map +1 -1
  158. package/dist/web/Css.js +31 -19
  159. package/dist/web/Css.js.map +1 -1
  160. package/dist/web/internal/Themes.d.ts +0 -2
  161. package/dist/web/internal/Themes.d.ts.map +1 -1
  162. package/dist/web/internal/Themes.js +30 -28
  163. package/dist/web/internal/Themes.js.map +1 -1
  164. package/dist/web/page.d.ts +2 -0
  165. package/dist/web/page.d.ts.map +1 -1
  166. package/native/NativeZyzz.cpp +215 -0
  167. package/native/NativeZyzz.h +52 -0
  168. package/native/ZyzzSpecJSI.h +65 -0
  169. package/native/android/CMakeLists.txt +8 -0
  170. package/native/android/ZyzzSpec-generated.cpp +50 -0
  171. package/native/android/ZyzzSpec.h +31 -0
  172. package/native/ios/ZyzzModuleProvider.mm +13 -0
  173. package/package.json +39 -1
  174. package/react-native.config.ts +14 -0
  175. package/src/Style.ts +11 -8
  176. package/src/babel/NativeJsx.ts +305 -28
  177. package/src/babel/index.ts +21 -3
  178. package/src/compiler/Graph.ts +75 -0
  179. package/src/compiler/Native.ts +158 -26
  180. package/src/compiler/Source.ts +32 -5
  181. package/src/compiler/internal/Configurations.ts +25 -3
  182. package/src/compiler/internal/Contract.ts +98 -50
  183. package/src/compiler/internal/Contributions.ts +7 -1
  184. package/src/compiler/internal/Descriptors.ts +12 -8
  185. package/src/compiler/internal/NativeBindings.ts +98 -15
  186. package/src/compiler/internal/NativeMedia.ts +342 -0
  187. package/src/compiler/internal/NativeVars.ts +228 -0
  188. package/src/compiler/internal/PackedStyles.ts +4 -0
  189. package/src/compiler/internal/Themes.ts +187 -2
  190. package/src/compiler/internal/Vars.ts +12 -1
  191. package/src/internal/Completion.ts +6 -3
  192. package/src/internal/Targets.ts +2 -0
  193. package/src/internal/Token.ts +0 -2
  194. package/src/internal/Value.ts +20 -0
  195. package/src/metro/Graph.ts +235 -50
  196. package/src/metro/Watch.ts +95 -10
  197. package/src/metro/index.ts +62 -4
  198. package/src/metro/transformer.ts +21 -11
  199. package/src/react-native/StyleSheet.ts +28 -44
  200. package/src/react-native/index.native.ts +10 -0
  201. package/src/react-native/index.ts +1 -1
  202. package/src/react-native/internal/Calculation.ts +159 -0
  203. package/src/react-native/internal/Device.ts +231 -0
  204. package/src/react-native/internal/NativeZyzz.ts +20 -0
  205. package/src/react-native/internal/Scalar.ts +13 -2
  206. package/src/react-native/internal/Store.ts +78 -0
  207. package/src/react-native/internal/Tokens.ts +85 -0
  208. package/src/react-native/internal/Viewport.ts +8 -0
  209. package/src/react-native/react.ts +211 -19
  210. package/src/react-native/reanimated.ts +162 -0
  211. package/src/runtime/NativeContext.ts +88 -17
  212. package/src/runtime/NativeDynamic.ts +35 -10
  213. package/src/runtime/NativeStatic.ts +145 -0
  214. package/src/runtime/NativeVars.ts +189 -0
  215. package/src/runtime/index.ts +6 -0
  216. package/src/runtime/internal/NativeMedia.ts +63 -0
  217. package/src/vite/index.ts +9 -42
  218. package/src/web/Css.ts +42 -27
  219. package/src/web/internal/Themes.ts +34 -57
  220. package/src/web/page.ts +2 -0
@@ -0,0 +1,342 @@
1
+ /** Lowers native media rules and conditional tokens to bounded static alternatives. @module */
2
+ import * as CssTree from 'css-tree'
3
+ import type * as Media from '../../runtime/internal/NativeMedia.js'
4
+ import * as Scalar from '../../react-native/internal/Scalar.js'
5
+ import type * as Style from '../../Style.js'
6
+ import type * as StyleSheet from '../../react-native/StyleSheet.js'
7
+ import * as Token from '../../internal/Token.js'
8
+
9
+ /** Retains authored precedence while selecting each possible media outcome. */
10
+ export function prepare<variables extends StyleSheet.compile.Options['vars']>(
11
+ styles: readonly Style.NamedStyle[] | undefined,
12
+ vars: variables,
13
+ options: Scalar.Options,
14
+ ) {
15
+ const catalog: StyleSheet.compile.Options['vars'] = vars
16
+ const expressions = new Map<string, number>()
17
+ const names = new Map<string, number>()
18
+ const queries: Media.Query[] = []
19
+ const visited = new Set<object>()
20
+
21
+ function add(name: string) {
22
+ if (names.has(name)) return
23
+
24
+ const query = parse(name, options)
25
+ const expression = JSON.stringify(query)
26
+ let index = expressions.get(expression)
27
+ if (index === undefined) {
28
+ index = queries.length
29
+ if (index === 8)
30
+ throw new Error(
31
+ 'Native media supports at most eight distinct conditions per definition.',
32
+ )
33
+
34
+ queries.push(query)
35
+ expressions.set(expression, index)
36
+ }
37
+
38
+ names.set(name, index)
39
+ }
40
+
41
+ function values(value: unknown) {
42
+ if (!value || typeof value !== 'object' || visited.has(value)) return
43
+
44
+ visited.add(value)
45
+ if (Token.is(value)) {
46
+ values(value.value)
47
+ for (const theme of Object.values(catalog ?? {})) {
48
+ const metadata = theme[Token.definition]
49
+ if (
50
+ metadata.contract === value.contract ||
51
+ (metadata.contract[Token.identity] !== undefined &&
52
+ metadata.contract[Token.identity] ===
53
+ value.contract[Token.identity])
54
+ )
55
+ values(metadata.values[value.path])
56
+ }
57
+ } else if (Token.isExpression(value)) value.parts.forEach(values)
58
+ else if ('default' in value) {
59
+ for (const [name, entry] of Object.entries(value)) {
60
+ if (name !== 'default') {
61
+ // Unsupported variable leaves keep their deferred read diagnostic.
62
+ try {
63
+ add(name)
64
+ } catch (error) {
65
+ if (!(error instanceof UnsupportedError)) throw error
66
+ }
67
+ }
68
+ values(entry)
69
+ }
70
+ } else if ('light' in value && 'dark' in value) {
71
+ values(value.light)
72
+ values(value.dark)
73
+ }
74
+ }
75
+
76
+ function visit(style: Style.NamedStyle) {
77
+ for (const declaration of style.declarations) values(declaration.value)
78
+ for (const rule of style.rules ?? []) {
79
+ if (rule.condition?.startsWith('@media')) add(rule.condition)
80
+ visit(rule.style)
81
+ }
82
+ }
83
+
84
+ styles?.forEach(visit)
85
+ if (!styles)
86
+ for (const theme of Object.values(catalog ?? {})) {
87
+ const metadata = theme[Token.definition]
88
+ Object.values(metadata.values).forEach(values)
89
+ }
90
+ if (!queries.length) return
91
+
92
+ return {
93
+ queries,
94
+ select(selection: string) {
95
+ const active = new Set<object>()
96
+ function value<entry>(entry: entry): entry {
97
+ if (!entry || typeof entry !== 'object') return entry
98
+ if (active.has(entry))
99
+ throw new Error('Native token aliases must not form a cycle.')
100
+
101
+ active.add(entry)
102
+ try {
103
+ if (Token.is(entry))
104
+ return Token.create({
105
+ ...entry,
106
+ value: value(entry.value),
107
+ }) as entry
108
+ if (Token.isExpression(entry))
109
+ return { ...entry, parts: entry.parts.map(value) } as entry
110
+ if ('default' in entry) {
111
+ const branches = Object.entries(entry)
112
+ if (
113
+ branches.some(([name]) => name !== 'default' && !names.has(name))
114
+ )
115
+ return Object.fromEntries(
116
+ branches
117
+ .filter(([name]) => name === 'default' || !names.has(name))
118
+ .map(([name, branch]) => [name, value(branch)]),
119
+ ) as entry
120
+
121
+ let selected = entry.default
122
+ for (const [name, branch] of branches)
123
+ if (name !== 'default' && selection[names.get(name)!] === '1')
124
+ selected = branch
125
+
126
+ return value(selected) as entry
127
+ }
128
+ if ('light' in entry && 'dark' in entry)
129
+ return {
130
+ ...entry,
131
+ dark: value(entry.dark),
132
+ light: value(entry.light),
133
+ }
134
+ return entry
135
+ } finally {
136
+ active.delete(entry)
137
+ }
138
+ }
139
+
140
+ function style(entry: Style.NamedStyle): Style.NamedStyle {
141
+ return {
142
+ ...entry,
143
+ declarations: entry.declarations.map((declaration) => ({
144
+ ...declaration,
145
+ value: value(declaration.value),
146
+ })),
147
+ ...(entry.rules
148
+ ? {
149
+ rules: entry.rules.flatMap((rule) => {
150
+ const index =
151
+ rule.condition === undefined
152
+ ? undefined
153
+ : names.get(rule.condition)
154
+ if (index !== undefined && selection[index] !== '1') return []
155
+
156
+ return [
157
+ {
158
+ ...(index === undefined ? rule : {}),
159
+ style: style(rule.style),
160
+ },
161
+ ]
162
+ }),
163
+ }
164
+ : {}),
165
+ }
166
+ }
167
+
168
+ return {
169
+ style,
170
+ vars:
171
+ catalog &&
172
+ (Object.fromEntries(
173
+ Object.entries(catalog).map(([name, theme]) => {
174
+ const metadata = theme[Token.definition]
175
+ return [
176
+ name,
177
+ Object.defineProperty({}, Token.definition, {
178
+ value: {
179
+ ...metadata,
180
+ values: Object.fromEntries(
181
+ Object.entries(metadata.values).map(([path, entry]) => [
182
+ path,
183
+ value(entry),
184
+ ]),
185
+ ),
186
+ },
187
+ }) as typeof theme,
188
+ ]
189
+ }),
190
+ ) as variables),
191
+ }
192
+ },
193
+ selections: Array.from({ length: 2 ** queries.length }, (_entry, index) =>
194
+ index.toString(2).padStart(queries.length, '0'),
195
+ ),
196
+ }
197
+ }
198
+
199
+ class UnsupportedError extends Error {
200
+ override name = 'NativeMedia.UnsupportedError'
201
+ }
202
+
203
+ function parse(input: string, options: Scalar.Options): Media.Query {
204
+ if (!input.startsWith('@media'))
205
+ throw new UnsupportedError(
206
+ 'Native conditional variables require media queries.',
207
+ )
208
+ const parsed = CssTree.parse(input.slice(6), { context: 'mediaQueryList' })
209
+ let count = 0
210
+ function read(node: CssTree.CssNode): Media.Query {
211
+ if (++count > 128)
212
+ throw new Error('Native media queries exceed 128 syntax nodes.')
213
+ if (node.type === 'MediaQueryList') {
214
+ const queries = node.children.toArray().map(read)
215
+ return queries.length === 1 ? queries[0]! : { kind: 'or', queries }
216
+ }
217
+ if (node.type === 'MediaQuery') {
218
+ if (
219
+ (node.mediaType &&
220
+ !['all', 'screen'].includes(node.mediaType.toLowerCase())) ||
221
+ node.modifier?.toLowerCase() === 'not'
222
+ )
223
+ throw new UnsupportedError(
224
+ 'Native media types support all or screen without negation.',
225
+ )
226
+ return node.condition
227
+ ? read(node.condition)
228
+ : { kind: 'compare', left: 0, operator: '=', right: 0 }
229
+ }
230
+ if (node.type === 'Condition') {
231
+ const parts = node.children.toArray()
232
+ if (parts.length === 1) return read(parts[0]!)
233
+ if (
234
+ parts.length === 2 &&
235
+ parts[0]?.type === 'Identifier' &&
236
+ parts[0].name.toLowerCase() === 'not'
237
+ )
238
+ return { kind: 'not', query: read(parts[1]!) }
239
+ const operator =
240
+ parts[1]?.type === 'Identifier'
241
+ ? parts[1].name.toLowerCase()
242
+ : undefined
243
+ if (
244
+ (operator !== 'and' && operator !== 'or') ||
245
+ parts.length % 2 !== 1 ||
246
+ parts.some(
247
+ (part, index) =>
248
+ index % 2 === 1 &&
249
+ (part.type !== 'Identifier' ||
250
+ part.name.toLowerCase() !== operator),
251
+ )
252
+ )
253
+ throw new UnsupportedError('Unsupported native media condition.')
254
+ return {
255
+ kind: operator,
256
+ queries: parts.filter((_part, index) => index % 2 === 0).map(read),
257
+ }
258
+ }
259
+ function operand(entry: CssTree.CssNode): number | 'height' | 'width' {
260
+ if (
261
+ entry.type === 'Identifier' &&
262
+ (entry.name.toLowerCase() === 'height' ||
263
+ entry.name.toLowerCase() === 'width')
264
+ )
265
+ return entry.name.toLowerCase() as 'height' | 'width'
266
+ if (entry.type === 'Dimension') {
267
+ try {
268
+ return Scalar.length(
269
+ `${entry.value}${entry.unit.toLowerCase()}`,
270
+ options,
271
+ false,
272
+ [],
273
+ )
274
+ } catch (error) {
275
+ throw new UnsupportedError((error as Error).message)
276
+ }
277
+ }
278
+ if (entry.type === 'Number' && Number(entry.value) === 0) return 0
279
+ throw new UnsupportedError(
280
+ 'Native media ranges require width/height and px/rem thresholds.',
281
+ )
282
+ }
283
+ function comparison(
284
+ left: CssTree.CssNode,
285
+ operator: string | null,
286
+ right: CssTree.CssNode,
287
+ ): Media.Query {
288
+ if (!operator || !['<', '<=', '=', '>', '>='].includes(operator))
289
+ throw new UnsupportedError('Unsupported native media comparison.')
290
+ return {
291
+ kind: 'compare',
292
+ left: operand(left),
293
+ operator: operator as '<' | '<=' | '=' | '>' | '>=',
294
+ right: operand(right),
295
+ }
296
+ }
297
+ if (node.type === 'FeatureRange') {
298
+ const first = comparison(node.left, node.leftComparison, node.middle)
299
+ return node.right
300
+ ? {
301
+ kind: 'and',
302
+ queries: [
303
+ first,
304
+ comparison(node.middle, node.rightComparison, node.right),
305
+ ],
306
+ }
307
+ : first
308
+ }
309
+ if (node.type === 'Feature') {
310
+ const name = node.name.toLowerCase()
311
+ if (
312
+ name === 'orientation' &&
313
+ node.value?.type === 'Identifier' &&
314
+ ['landscape', 'portrait'].includes(node.value.name.toLowerCase())
315
+ )
316
+ return {
317
+ kind: 'compare',
318
+ left: 'width',
319
+ operator: node.value.name.toLowerCase() === 'landscape' ? '>' : '<=',
320
+ right: 'height',
321
+ }
322
+ const match = /^(?:(min|max)-)?(height|width)$/.exec(name)
323
+ if (match)
324
+ return {
325
+ kind: 'compare',
326
+ left: match[2] as 'height' | 'width',
327
+ operator:
328
+ match[1] === 'min'
329
+ ? '>='
330
+ : match[1] === 'max'
331
+ ? '<='
332
+ : node.value
333
+ ? '='
334
+ : '>',
335
+ right: node.value ? operand(node.value) : 0,
336
+ }
337
+ }
338
+ throw new UnsupportedError('Unsupported native media feature.')
339
+ }
340
+
341
+ return read(parsed)
342
+ }
@@ -0,0 +1,228 @@
1
+ /** Converts authored variables to static native value profiles. @module */
2
+ import * as Calculation from '../../react-native/internal/Calculation.js'
3
+ import * as Literal from '../../internal/Literal.js'
4
+ import * as Media from './NativeMedia.js'
5
+ import type * as Runtime from '../../runtime/NativeVars.js'
6
+ import * as Scalar from '../../react-native/internal/Scalar.js'
7
+ import type * as Theme from '../../internal/Theme.js'
8
+ import * as Token from '../../internal/Token.js'
9
+ import * as VariableSets from '../../internal/VariableSets.js'
10
+
11
+ /** Resolves native domains using the style compiler's unit and font mappings. */
12
+ export function compile(options: compile.Options): Runtime.create.Options {
13
+ const media = Media.prepare(undefined, options.vars, options)
14
+ return {
15
+ defaultVars: options.defaultVars,
16
+ ...(media
17
+ ? {
18
+ media: {
19
+ profiles: Object.fromEntries(
20
+ media.selections.map((selection) => [
21
+ selection,
22
+ compile({ ...options, vars: media.select(selection).vars! })
23
+ .profiles,
24
+ ]),
25
+ ),
26
+ queries: media.queries,
27
+ },
28
+ }
29
+ : {}),
30
+ profiles: Object.fromEntries(
31
+ Object.entries(options.vars).map((entry) => {
32
+ const [name, theme] = entry
33
+ return [
34
+ name,
35
+ { dark: profile(theme, 'dark'), light: profile(theme, 'light') },
36
+ ]
37
+ }),
38
+ ),
39
+ unnamed: options.unnamed,
40
+ }
41
+
42
+ function profile(theme: Theme.Definition, scheme: 'dark' | 'light') {
43
+ const metadata = theme[Token.definition]
44
+ const active = new Set<Token.Reference>()
45
+ type Tree = {
46
+ [key: string]: string | number | Tree | readonly [string]
47
+ }
48
+ const tree: Tree = Object.create(null)
49
+
50
+ function resolve(value: Token.Value): string | number {
51
+ if (typeof value === 'string' || typeof value === 'number') return value
52
+ if (Token.is(value)) {
53
+ if (active.has(value))
54
+ throw new Error('Native token aliases must not form a cycle.')
55
+ const shared =
56
+ metadata.contract === value.contract ||
57
+ (metadata.contract[Token.identity] !== undefined &&
58
+ metadata.contract[Token.identity] ===
59
+ value.contract[Token.identity])
60
+ const next = shared ? metadata.values[value.path] : value.value
61
+ if (next === undefined)
62
+ throw new Error(`Missing native token: ${value.path}.`)
63
+ active.add(value)
64
+ try {
65
+ return resolve(next)
66
+ } finally {
67
+ active.delete(value)
68
+ }
69
+ }
70
+ if (Token.isExpression(value) && 'group' in value) {
71
+ const text = value.parts.map(resolve).join('')
72
+ return value.group === 'spacing' && /^\s*calc\(/i.test(text)
73
+ ? `${Calculation.length(text, options) / (options.units?.px ?? 1)}px`
74
+ : text
75
+ }
76
+ if ('light' in value && 'dark' in value) return resolve(value[scheme])
77
+ throw new Error(
78
+ media
79
+ ? 'Native media-conditioned variables require the native Provider window dimensions.'
80
+ : 'Media-conditioned variables require a web target.',
81
+ )
82
+ }
83
+
84
+ for (const [path, source] of Object.entries(metadata.values)) {
85
+ const parts = metadata.paths?.[path] ?? path.split('.')
86
+ const property = parts.at(-1)
87
+ const domain = VariableSets.domain(source)
88
+ const mappings = metadata.contract.mappings
89
+ const properties = mappings === false ? undefined : mappings?.[parts[0]!]
90
+ const category = parts[0] === 'typography' ? property! : parts[0]!
91
+ const target = category === 'radius' ? 'borderRadius' : category
92
+ const targets = properties?.length
93
+ ? properties
94
+ : Object.hasOwn(Scalar.properties, target)
95
+ ? [target]
96
+ : []
97
+
98
+ const converted = (() => {
99
+ try {
100
+ if (parts.some((part) => /^@(media|container)\s/.test(part)))
101
+ throw new Error('Media-conditioned variables require a web target.')
102
+ const value = resolve(source)
103
+
104
+ function convert(property: string): string | number {
105
+ property =
106
+ property in Literal.aliases
107
+ ? Literal.aliases[property as keyof typeof Literal.aliases]
108
+ : property
109
+ if (!Object.hasOwn(Scalar.properties, property))
110
+ throw new Error(`Unsupported native property: ${property}.`)
111
+
112
+ if (property === 'lineHeight') {
113
+ if (
114
+ typeof value === 'string' &&
115
+ (/^\s*calc\(/i.test(value) ||
116
+ /^\s*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?(?:px|rem)\s*$/i.test(
117
+ value,
118
+ ))
119
+ )
120
+ return Scalar.length(
121
+ value.trim().toLowerCase(),
122
+ options,
123
+ false,
124
+ [path],
125
+ )
126
+
127
+ const multiplier =
128
+ typeof value === 'number' ? value : Number(value)
129
+ if (
130
+ !Number.isFinite(multiplier) ||
131
+ (typeof value === 'string' && !value.trim())
132
+ )
133
+ throw new Error(`Unsupported native line height: ${value}.`)
134
+ const size =
135
+ parts[0] === 'typography'
136
+ ? metadata.values[
137
+ [...parts.slice(0, -1), 'fontSize'].join('.')
138
+ ]
139
+ : undefined
140
+ if (size === undefined)
141
+ throw new Error(
142
+ `Native line height requires fontSize: ${path}.`,
143
+ )
144
+ return Scalar.convert(
145
+ 'number',
146
+ multiplier *
147
+ Scalar.length(resolve(size), options, false, [path]),
148
+ options,
149
+ [path],
150
+ )
151
+ }
152
+
153
+ const kind =
154
+ Scalar.properties[property as keyof typeof Scalar.properties]
155
+ if (
156
+ typeof kind === 'string' &&
157
+ [
158
+ 'fontVariant',
159
+ 'origin',
160
+ 'shadow',
161
+ 'textShadow',
162
+ 'transform',
163
+ ].includes(kind)
164
+ )
165
+ throw new Error(
166
+ `Native property ${property} does not have a scalar variable value.`,
167
+ )
168
+ if (kind === 'ratio' && typeof value !== 'number')
169
+ throw new Error(
170
+ 'Native aspect ratios require numeric variable values.',
171
+ )
172
+ if (kind === 'box' || kind === 'boxSigned')
173
+ return Scalar.length(value, options, kind === 'boxSigned', [path])
174
+ return Scalar.convert(kind, value, options, [path])
175
+ }
176
+
177
+ if (targets.length) {
178
+ const values = targets.map(convert)
179
+ if (values.some((value) => value !== values[0]))
180
+ throw new Error(
181
+ 'Mapped native properties require different converted values.',
182
+ )
183
+ return values[0]!
184
+ }
185
+ if (domain === 'spacing')
186
+ return Scalar.length(value, options, true, [path])
187
+ if (domain === 'color')
188
+ return Scalar.convert('color', value, options, [path])
189
+ return value
190
+ } catch (error) {
191
+ return [
192
+ `Native variable ${path}: ${error instanceof Error ? error.message : String(error)}`,
193
+ ] as const
194
+ }
195
+ })()
196
+ let parent = tree
197
+ for (const key of parts.slice(0, -1))
198
+ parent = (parent[key] ??= Object.create(null)) as typeof tree
199
+ parent[parts.at(-1)!] = converted
200
+ }
201
+ return tree
202
+ }
203
+ }
204
+
205
+ /** Compiler inputs for resolved native variables. */
206
+ export declare namespace compile {
207
+ /** Complete variable catalog and explicit target mappings. */
208
+ type Options = Scalar.Options & {
209
+ /** Configuration fallback. */
210
+ readonly defaultVars: string
211
+ /** Whether the input is a standalone definition instead of a named catalog. */
212
+ readonly unnamed: boolean
213
+ /** Compatible authored definitions. */
214
+ readonly vars: Readonly<Record<string, Theme.Definition>>
215
+ }
216
+ }
217
+
218
+ /** Graph-owned profile exports and imports, excluded from public adapter options. */
219
+ export const shared = Symbol('zyzz.compiler.native.vars')
220
+
221
+ /** Static profile artifacts belonging to one source module. */
222
+ export type Shared = {
223
+ readonly definitions: ReadonlyMap<string, string>
224
+ readonly reads: ReadonlyMap<
225
+ number,
226
+ { readonly name: string; readonly source?: string | undefined }
227
+ >
228
+ }
@@ -26,6 +26,8 @@ export type Definition = {
26
26
  readonly className?: string | undefined
27
27
  /** Attributes owned by the callable, including conditional selections. */
28
28
  readonly attributes: readonly string[]
29
+ /** Named Provider selections retained by separately compiled styles. */
30
+ readonly nativeContext?: Source.Call['nativeContext']
29
31
  /** Native HTML output; React props are the default. */
30
32
  readonly output?: 'html' | undefined
31
33
  /** Private dynamic custom properties owned by the callable. */
@@ -39,6 +41,7 @@ export type Definition = {
39
41
  /** Captures ownership without retaining source ASTs or executable expressions. */
40
42
  export function create(call: Source.Call, style: Style.NamedStyle): Definition {
41
43
  return {
44
+ ...(call.nativeContext ? { nativeContext: call.nativeContext } : {}),
42
45
  ...(call.slots || call.dynamicRecipe
43
46
  ? {
44
47
  dynamic: {
@@ -419,6 +422,7 @@ export function write(definition: Definition): unknown {
419
422
  JSON.stringify(
420
423
  {
421
424
  ...definition,
425
+ nativeContext: undefined,
422
426
  style: {
423
427
  ...definition.style,
424
428
  cssOutput: definition.style.cssOutput ?? 'atomic',