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
@@ -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]),
@@ -227,6 +245,13 @@ export function create(
227
245
  ])
228
246
  }
229
247
  const style = output as StyleSheet.NativeStyle
248
+ if (cache && key !== undefined) {
249
+ Object.freeze(style)
250
+ // Bound retained selections without limiting the recipe's choice space.
251
+ if (cache.size >= 256) cache.delete(cache.keys().next().value!)
252
+ cache.set(key, style)
253
+ }
254
+
230
255
  return { style: input.style ? [style, input.style] : style }
231
256
  }
232
257
  }
@@ -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
+ }
@@ -0,0 +1,189 @@
1
+ /** Selects immutable native variable tables emitted by the compiler. @module */
2
+ import type * as Literal from '../internal/Literal.js'
3
+ import * as Media from './internal/NativeMedia.js'
4
+ import type * as StyleSheet from '../react-native/StyleSheet.js'
5
+ import type * as Vars from '../Vars.js'
6
+
7
+ const binding = Symbol('zyzz.native.vars')
8
+
9
+ /** Static values with deferred diagnostics for unsupported native leaves. */
10
+ export type EncodedTree = {
11
+ readonly [key: string]: string | number | EncodedTree | readonly [string]
12
+ }
13
+
14
+ /** Resolves portable reference domains to readonly native value types. */
15
+ export type Values<
16
+ values,
17
+ category = undefined,
18
+ root extends boolean = true,
19
+ > = {
20
+ readonly [key in Exclude<
21
+ keyof values,
22
+ root extends true ? 'breakpoint' | 'containerNames' : never
23
+ >]: values[key] extends Vars.Value
24
+ ? category extends 'fontWeight'
25
+ ? Exclude<StyleSheet.NativeStyle['fontWeight'], undefined>
26
+ : category extends 'typography'
27
+ ? key extends 'fontWeight'
28
+ ? Exclude<StyleSheet.NativeStyle['fontWeight'], undefined>
29
+ : key extends 'lineHeight'
30
+ ? number
31
+ : Scalar<Vars.Scalar<values[key]>>
32
+ : Scalar<Vars.Scalar<values[key]>>
33
+ : Values<values[key], root extends true ? key : category, false>
34
+ }
35
+
36
+ /** Native scalar values nested under their authored variable paths. */
37
+ export type Tree = { readonly [key: string]: string | number | Tree }
38
+
39
+ type Scalar<value> = value extends number | Literal.Length ? number : string
40
+
41
+ type Definition = {
42
+ readonly defaultVars: string
43
+ readonly media?:
44
+ | {
45
+ readonly profiles: Readonly<Record<string, Definition['profiles']>>
46
+ readonly queries: readonly Media.Query[]
47
+ }
48
+ | undefined
49
+ readonly profiles: Readonly<
50
+ Record<string, Readonly<Record<'dark' | 'light', Tree>>>
51
+ >
52
+ readonly unnamed: boolean
53
+ }
54
+
55
+ /** Copies and shares equal branches across the compiled profiles. */
56
+ export function create(options: create.Options): object {
57
+ const shared = new Map<string, Tree>()
58
+
59
+ function freeze(tree: EncodedTree): Tree {
60
+ const key = JSON.stringify(tree)
61
+ const previous = shared.get(key)
62
+ if (previous) return previous
63
+
64
+ const value: Tree = {}
65
+ for (const [name, entry] of Object.entries(tree)) {
66
+ const diagnostic = Array.isArray(entry) ? String(entry[0]) : undefined
67
+ Object.defineProperty(
68
+ value,
69
+ name,
70
+ diagnostic !== undefined
71
+ ? {
72
+ enumerable: true,
73
+ get() {
74
+ throw new Error(diagnostic)
75
+ },
76
+ }
77
+ : {
78
+ enumerable: true,
79
+ value:
80
+ typeof entry === 'object'
81
+ ? freeze(entry as EncodedTree)
82
+ : entry,
83
+ },
84
+ )
85
+ }
86
+ Object.freeze(value)
87
+ shared.set(key, value)
88
+ return value
89
+ }
90
+
91
+ function profiles(value: create.Options['profiles']): Definition['profiles'] {
92
+ return Object.freeze(
93
+ Object.fromEntries(
94
+ Object.entries(value).map(([name, schemes]) => [
95
+ name,
96
+ Object.freeze({
97
+ dark: freeze(schemes.dark),
98
+ light: freeze(schemes.light),
99
+ }),
100
+ ]),
101
+ ),
102
+ )
103
+ }
104
+
105
+ return Object.freeze({
106
+ [binding]: Object.freeze({
107
+ defaultVars: options.defaultVars,
108
+ ...(options.media
109
+ ? {
110
+ media: Object.freeze({
111
+ profiles: Object.freeze(
112
+ Object.fromEntries(
113
+ Object.entries(options.media.profiles).map(([key, value]) => [
114
+ key,
115
+ profiles(value),
116
+ ]),
117
+ ),
118
+ ),
119
+ queries: options.media.queries,
120
+ }),
121
+ }
122
+ : {}),
123
+ profiles: profiles(options.profiles),
124
+ unnamed: options.unnamed,
125
+ }),
126
+ })
127
+ }
128
+
129
+ /** Compiler-generated native profiles. */
130
+ export declare namespace create {
131
+ /** Static tables and the owning configuration's default name. */
132
+ type Options = {
133
+ /** Fallback when the Provider omits its selection. */
134
+ readonly defaultVars: string
135
+ /** Precompiled alternatives for supported window queries. */
136
+ readonly media?:
137
+ | {
138
+ readonly profiles: Readonly<Record<string, Options['profiles']>>
139
+ readonly queries: readonly Media.Query[]
140
+ }
141
+ | undefined
142
+ /** Native values for every configured name and scheme. */
143
+ readonly profiles: Readonly<
144
+ Record<string, Readonly<Record<'dark' | 'light', EncodedTree>>>
145
+ >
146
+ /** A standalone definition whose values do not depend on the selected name. */
147
+ readonly unnamed: boolean
148
+ }
149
+ }
150
+
151
+ /** Reads a compiled profile without evaluating authoring or accessing device state. */
152
+ export function read(value: object, selection: read.Options): Tree {
153
+ if (
154
+ !value ||
155
+ (typeof value !== 'object' && typeof value !== 'function') ||
156
+ !Object.hasOwn(value, binding)
157
+ )
158
+ throw new Error(
159
+ 'useVars requires variables compiled by the native adapter.',
160
+ )
161
+
162
+ const definition = (value as { readonly [binding]: Definition })[binding]
163
+ const profiles =
164
+ definition.media && selection.viewport
165
+ ? definition.media.profiles[
166
+ Media.select(definition.media.queries, selection.viewport)
167
+ ]!
168
+ : definition.profiles
169
+ const name = selection.set ?? definition.defaultVars
170
+ const profile =
171
+ (Object.hasOwn(profiles, name) ? profiles[name] : undefined) ??
172
+ (definition.unnamed ? profiles.default : undefined)
173
+ if (!profile) throw new Error(`Unknown native vars: ${name}.`)
174
+
175
+ return profile[selection.colorScheme]
176
+ }
177
+
178
+ /** Selection supplied by the nearest native Provider. */
179
+ export declare namespace read {
180
+ /** Resolved appearance and an optional variable name. */
181
+ type Options = {
182
+ /** Resolved native color scheme. */
183
+ readonly colorScheme: 'dark' | 'light'
184
+ /** Selected name, or the configuration default when omitted. */
185
+ readonly set?: string | undefined
186
+ /** Native window dimensions; unsupported leaves remain lazy when absent. */
187
+ readonly viewport?: Media.Viewport | undefined
188
+ }
189
+ }
@@ -29,6 +29,12 @@ export * as NativeContext from './NativeContext.js'
29
29
  /** Ordered native scalar bindings for generated dynamic callables. */
30
30
  export * as NativeDynamic from './NativeDynamic.js'
31
31
 
32
+ /** Finite native selection without dynamic value conversion. */
33
+ export * as NativeStatic from './NativeStatic.js'
34
+
35
+ /** Readonly native variable selection for compiled hooks. */
36
+ export * as NativeVars from './NativeVars.js'
37
+
32
38
  /** Scoped recipe inputs bound to fixed variable slots. */
33
39
  export * as PayloadRecipe from './PayloadRecipe.js'
34
40
 
@@ -0,0 +1,63 @@
1
+ /** Selects precompiled media alternatives using native logical dimensions. @module */
2
+
3
+ /** Comparison data emitted by the compiler, with no CSS parsing at application time. */
4
+ export type Query =
5
+ | { readonly kind: 'and'; readonly queries: readonly Query[] }
6
+ | {
7
+ readonly kind: 'compare'
8
+ readonly left: number | 'height' | 'width'
9
+ readonly operator: '<' | '<=' | '=' | '>' | '>='
10
+ readonly right: number | 'height' | 'width'
11
+ }
12
+ | { readonly kind: 'not'; readonly query: Query }
13
+ | { readonly kind: 'or'; readonly queries: readonly Query[] }
14
+
15
+ /** Window measurements supplied by the native React adapter. */
16
+ export type Viewport = {
17
+ readonly height: number
18
+ readonly width: number
19
+ }
20
+
21
+ /** Computes the selected alternative without interpreting authoring code. */
22
+ export function select(
23
+ queries: readonly Query[],
24
+ viewport: Viewport | undefined,
25
+ ): string {
26
+ if (!viewport)
27
+ throw new Error(
28
+ 'Native media queries require the native Provider window dimensions.',
29
+ )
30
+ if (
31
+ !Number.isFinite(viewport.height) ||
32
+ viewport.height < 0 ||
33
+ !Number.isFinite(viewport.width) ||
34
+ viewport.width < 0
35
+ )
36
+ throw new Error('Native window dimensions must be finite and nonnegative.')
37
+
38
+ const dimensions = viewport
39
+ function matches(query: Query): boolean {
40
+ if (query.kind === 'and') return query.queries.every(matches)
41
+ if (query.kind === 'or') return query.queries.some(matches)
42
+ if (query.kind === 'not') return !matches(query.query)
43
+
44
+ const left =
45
+ typeof query.left === 'number' ? query.left : dimensions[query.left]
46
+ const right =
47
+ typeof query.right === 'number' ? query.right : dimensions[query.right]
48
+ switch (query.operator) {
49
+ case '<':
50
+ return left < right
51
+ case '<=':
52
+ return left <= right
53
+ case '=':
54
+ return left === right
55
+ case '>':
56
+ return left > right
57
+ case '>=':
58
+ return left >= right
59
+ }
60
+ }
61
+
62
+ return queries.map((query) => (matches(query) ? '1' : '0')).join('')
63
+ }
package/src/vite/index.ts CHANGED
@@ -20,6 +20,7 @@ import * as Scope from '../compiler/internal/Scope.js'
20
20
  import type { Environment, Plugin, Rollup, ViteDevServer } from 'vite'
21
21
  import * as Graph from '../compiler/Graph.js'
22
22
  import * as Source from '../compiler/Source.js'
23
+ import * as ThemeRules from '../web/internal/Themes.js'
23
24
 
24
25
  /**
25
26
  * Compiles physical project source without executing authoring code.
@@ -749,6 +750,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
749
750
  )
750
751
 
751
752
  const result = entry.compiler.compile({
753
+ [ThemeRules.shared]: native ? undefined : 'all',
752
754
  [Syntax.cache]: snapshot(entry.environment).programs(modules),
753
755
  compiler: options.compiler,
754
756
  reset: options.reset ? Reset.read() : undefined,
@@ -812,46 +814,6 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
812
814
  configurations.set(key, configuration)
813
815
  }
814
816
 
815
- const map = new Mapping.GenMapping()
816
- const styles: string[] = []
817
- let line = 0
818
-
819
- for (const [id, output] of Object.entries(result.modules)) {
820
- if (!connected.has(sourceFile(id)!)) continue
821
- if (!output.css) continue
822
-
823
- for (const mapping of Mapping.allMappings(
824
- Mapping.fromMap(JSON.stringify(output.cssMap)),
825
- )) {
826
- const generated = {
827
- column: mapping.generated.column,
828
- line: mapping.generated.line + line,
829
- }
830
-
831
- if (mapping.source !== undefined && mapping.original !== undefined) {
832
- const location = {
833
- generated,
834
- original: mapping.original,
835
- source: sourceFile(mapping.source) ?? mapping.source,
836
- }
837
-
838
- if (mapping.name === undefined) Mapping.addMapping(map, location)
839
- else Mapping.addMapping(map, { ...location, name: mapping.name })
840
- } else Mapping.addMapping(map, { generated })
841
- }
842
-
843
- for (const [index, source] of output.cssMap.sources.entries())
844
- if (source !== null)
845
- Mapping.setSourceContent(
846
- map,
847
- sourceFile(source) ?? source,
848
- output.cssMap.sourcesContent?.[index] ?? null,
849
- )
850
-
851
- styles.push(output.css)
852
- line += output.css.split('\n').length
853
- }
854
-
855
817
  const owners = new Map<string, string>()
856
818
 
857
819
  for (const id of new Set(Object.values(result.sharedAssetOwners ?? {}))) {
@@ -1018,10 +980,15 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
1018
980
  contractIds: Object.keys(compiled),
1019
981
  configurations,
1020
982
  connections: staged,
1021
- css: styles.join('\n'),
983
+ css: output.css,
1022
984
  sharedCss: new TextDecoder().decode(shared.code),
1023
985
  sharedCssMap: sharedMap,
1024
- cssMap: Mapping.toEncodedMap(map),
986
+ cssMap: {
987
+ ...output.cssMap,
988
+ sources: output.cssMap.sources.map((source) =>
989
+ source === null ? null : (sourceFile(source) ?? source),
990
+ ),
991
+ },
1025
992
  map: {
1026
993
  ...output.map,
1027
994
  sources: output.map.sources.map((source) =>