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
@@ -7,6 +7,8 @@ import type * as Native from '../internal/NativeProperties.js'
7
7
  import * as Values from './internal/Values.js'
8
8
  import * as Color from './internal/Color.js'
9
9
  import * as Scalar from './internal/Scalar.js'
10
+ import * as Calculation from './internal/Calculation.js'
11
+ import * as Tokens from './internal/Tokens.js'
10
12
 
11
13
  const properties = Scalar.properties
12
14
 
@@ -207,6 +209,7 @@ export function compile<
207
209
  scheme,
208
210
  path,
209
211
  resolution,
212
+ options,
210
213
  )
211
214
  if (property === 'lineHeight') {
212
215
  lineHeight = value
@@ -266,9 +269,7 @@ export function compile<
266
269
  const converted = convert(kind, value, options, path)
267
270
  if (property === 'padding' || property === 'margin') {
268
271
  const values =
269
- typeof value === 'string'
270
- ? value.trim().split(/\s+/)
271
- : [value]
272
+ typeof value === 'string' ? Calculation.parts(value) : [value]
272
273
  const [top, right = top, bottom = top, left = right] =
273
274
  values.map((value) =>
274
275
  length(value, options, property === 'margin', path),
@@ -606,12 +607,21 @@ type Merged<style> = {
606
607
  type StyleArray<style extends object> = Array<
607
608
  style | Falsy | readonly (style | Falsy)[] | StyleArray<style>
608
609
  >
609
- type Length = 0 | '0' | `${number}px` | `${number}rem`
610
+ type Length =
611
+ | 0
612
+ | '0'
613
+ | `${number}px`
614
+ | `${number}rem`
615
+ | `${'c' | 'C'}${'a' | 'A'}${'l' | 'L'}${'c' | 'C'}(${string})`
610
616
  type Box =
611
617
  | Length
612
618
  | `${Length} ${Length}`
613
619
  | `${Length} ${Length} ${Length}`
614
- | `${Length} ${Length} ${Length} ${Length}`
620
+ | Box4
621
+ // Distribute the first item to keep case-insensitive calculations below the template expansion limit.
622
+ type Box4<first extends string | number = Length> = first extends unknown
623
+ ? `${first} ${Length} ${Length} ${Length}`
624
+ : never
615
625
  type Weight =
616
626
  | 'bold'
617
627
  | 'normal'
@@ -757,50 +767,24 @@ function resolve(
757
767
  scheme: ColorScheme,
758
768
  path: readonly string[],
759
769
  resolution: { schemeIndependent: boolean },
770
+ options: compile.Options,
760
771
  ): number | string {
761
- if (typeof value === 'number' || typeof value === 'string') return value
762
- if (
763
- !Token.is(value) ||
764
- Object.getOwnPropertyDescriptor(value, Token.web)?.value
765
- )
772
+ try {
773
+ return Tokens.resolve(value, {
774
+ ...options,
775
+ metadata,
776
+ colorScheme: scheme,
777
+ resolution,
778
+ })
779
+ } catch (error) {
766
780
  fail(
767
- 'unsupported_feature',
768
- 'Web variables, expressions, and dynamic bindings are not native scalar tokens.',
781
+ error instanceof Tokens.UnsupportedError
782
+ ? 'unsupported_feature'
783
+ : 'unsupported_value',
784
+ (error as Error).message,
769
785
  path,
770
786
  )
771
- const shared =
772
- metadata &&
773
- (metadata.contract === value.contract ||
774
- (metadata.contract[Token.identity] !== undefined &&
775
- metadata.contract[Token.identity] === value.contract[Token.identity]))
776
- let resolved = shared
777
- ? Object.hasOwn(metadata.values, value.path)
778
- ? metadata.values[value.path]
779
- : undefined
780
- : value.value
781
- if (resolved === undefined)
782
- fail('unsupported_value', 'Theme is missing a live token.', path)
783
- while (typeof resolved === 'object') {
784
- if (Token.is(resolved)) {
785
- resolved = resolved.value
786
- continue
787
- }
788
- if (Token.isExpression(resolved))
789
- fail(
790
- 'unsupported_feature',
791
- 'Composed variables require a web target.',
792
- path,
793
- )
794
- if ('default' in resolved)
795
- fail(
796
- 'unsupported_feature',
797
- 'Media-conditioned variables require a web target.',
798
- path,
799
- )
800
- if (resolved.light !== resolved.dark) resolution.schemeIndependent = false
801
- resolved = resolved[scheme]
802
787
  }
803
- return resolved
804
788
  }
805
789
 
806
790
  type TransformValues = {
@@ -0,0 +1,10 @@
1
+ /** Native authoring and React subscriptions with automatic device dimensions. @module */
2
+ export * as Host from './Host.js'
3
+ export * as StyleSheet from './StyleSheet.js'
4
+ export * as Variants from './Variants.js'
5
+ export {
6
+ defineConfig,
7
+ useStyles,
8
+ useVars,
9
+ withStyles,
10
+ } from './internal/Device.js'
@@ -1,4 +1,4 @@
1
- /** Native compilation, precompiled selection, and explicit host lifecycle. @module */
1
+ /** Native compilation and explicit host lifecycle. @module */
2
2
  /** Explicit native context and adapter lifecycle. */
3
3
  export * as Host from './Host.js'
4
4
  /** Native compilation and table selection. */
@@ -0,0 +1,159 @@
1
+ /** Evaluates finite calc lengths using explicit native unit mappings. @module */
2
+ import type * as Scalar from './Scalar.js'
3
+
4
+ type Value = { amount: number; length: boolean }
5
+
6
+ /** Resolves number/length arithmetic, rejecting relative units and incompatible dimensions. */
7
+ export function length(input: string, options: Scalar.Options): number {
8
+ const pattern =
9
+ /\s*(?:(\d+(?:\.\d+)?|\.\d+)(?:[eE]([+-]?\d+))?(px|rem)?|(calc)|([()+*/-]))/giy
10
+ const positions: number[] = []
11
+ const tokens: (string | Value)[] = []
12
+ let offset = 0
13
+ while (offset < input.trimEnd().length) {
14
+ pattern.lastIndex = offset
15
+ const match = pattern.exec(input)
16
+ if (!match) throw new Error('Unsupported native calc expression.')
17
+
18
+ offset = pattern.lastIndex
19
+ positions.push(match.index + match[0].search(/\S/))
20
+ if (positions.length > 512)
21
+ throw new Error('Native calc exceeds 512 tokens.')
22
+ if (match[1] === undefined) {
23
+ tokens.push(match[4]?.toLowerCase() ?? match[5]!)
24
+ continue
25
+ }
26
+
27
+ const unit = match[3]?.toLowerCase()
28
+ const scale =
29
+ unit === 'rem'
30
+ ? options.units?.rem
31
+ : unit === 'px'
32
+ ? (options.units?.px ?? 1)
33
+ : 1
34
+ if (scale === undefined)
35
+ throw new Error('Provide units.rem for rem lengths.')
36
+
37
+ tokens.push({
38
+ amount:
39
+ Number(`${match[1]}${match[2] === undefined ? '' : `e${match[2]}`}`) *
40
+ scale,
41
+ length: unit !== undefined,
42
+ })
43
+ }
44
+
45
+ let cursor = 0
46
+ let depth = 0
47
+ function atom(): Value {
48
+ if (++depth > 64)
49
+ throw new Error('Native calc exceeds 64 nested operations.')
50
+ try {
51
+ return readAtom()
52
+ } finally {
53
+ depth--
54
+ }
55
+ }
56
+ function readAtom(): Value {
57
+ const token = tokens[cursor++]
58
+ if (token === '+' || token === '-') {
59
+ const value = atom()
60
+ return { ...value, amount: token === '-' ? -value.amount : value.amount }
61
+ }
62
+ if (token === 'calc' || token === '(') {
63
+ if (token === 'calc' && tokens[cursor++] !== '(')
64
+ throw new Error('Unsupported native calc expression.')
65
+ const value = sum()
66
+ if (tokens[cursor++] !== ')')
67
+ throw new Error('Unsupported native calc expression.')
68
+ return value
69
+ }
70
+ if (!token || typeof token === 'string')
71
+ throw new Error('Unsupported native calc expression.')
72
+
73
+ return token
74
+ }
75
+
76
+ function product(): Value {
77
+ let value = atom()
78
+ while (tokens[cursor] === '*' || tokens[cursor] === '/') {
79
+ const operator = tokens[cursor++]
80
+ const right = atom()
81
+ if (
82
+ (operator === '*' && value.length && right.length) ||
83
+ (operator === '/' && (right.length || right.amount === 0))
84
+ )
85
+ throw new Error(
86
+ 'Native calc requires compatible number and length operands.',
87
+ )
88
+
89
+ value = {
90
+ amount:
91
+ operator === '*'
92
+ ? value.amount * right.amount
93
+ : value.amount / right.amount,
94
+ length: value.length || right.length,
95
+ }
96
+ }
97
+ return value
98
+ }
99
+
100
+ function sum(): Value {
101
+ let value = product()
102
+ while (tokens[cursor] === '+' || tokens[cursor] === '-') {
103
+ const position = positions[cursor]!
104
+ if (
105
+ !/\s/.test(input[position - 1] ?? '') ||
106
+ !/\s/.test(input[position + 1] ?? '')
107
+ )
108
+ throw new Error('Unsupported native calc expression.')
109
+
110
+ const operator = tokens[cursor++]
111
+ const right = product()
112
+ if (value.length !== right.length)
113
+ throw new Error(
114
+ 'Native calc requires compatible number and length operands.',
115
+ )
116
+
117
+ value = {
118
+ amount:
119
+ operator === '+'
120
+ ? value.amount + right.amount
121
+ : value.amount - right.amount,
122
+ length: value.length,
123
+ }
124
+ }
125
+ return value
126
+ }
127
+
128
+ const value = atom()
129
+ if (
130
+ cursor !== tokens.length ||
131
+ !value.length ||
132
+ !Number.isFinite(value.amount)
133
+ )
134
+ throw new Error('Native calc must resolve to one finite px or rem length.')
135
+
136
+ return value.amount
137
+ }
138
+
139
+ /** Splits shorthand lengths while preserving spaces within calculations. */
140
+ export function parts(input: string): readonly string[] {
141
+ const result: string[] = []
142
+ let depth = 0
143
+ let start = 0
144
+ const text = input.trim()
145
+ for (let index = 0; index < text.length; index++) {
146
+ const character = text[index]
147
+ if (character === '(') depth++
148
+ if (character === ')' && --depth < 0)
149
+ throw new Error('Unbalanced native calculation.')
150
+ if (depth > 64) throw new Error('Native calc exceeds 64 nested operations.')
151
+ if (depth === 0 && /\s/.test(character!)) {
152
+ if (index > start) result.push(text.slice(start, index))
153
+ start = index + 1
154
+ }
155
+ }
156
+ if (depth !== 0) throw new Error('Unbalanced native calculation.')
157
+ if (start < text.length) result.push(text.slice(start))
158
+ return result
159
+ }
@@ -0,0 +1,231 @@
1
+ /** Supplies automatic window measurements for native React consumers. @module */
2
+ import type * as Config from '../../Config.js'
3
+ import * as NativeContext from '../../runtime/NativeContext.js'
4
+ import NativeZyzz from './NativeZyzz.js'
5
+ import * as React from 'react'
6
+ import * as ReactNative from 'react-native'
7
+ import * as Store from './Store.js'
8
+ import * as Subscription from '../react.js'
9
+ import * as Viewport from './Viewport.js'
10
+
11
+ /** Creates native authoring helpers and a Provider with automatic window subscriptions. */
12
+ export function defineConfig<const options extends Config.create.Options = {}>(
13
+ options: options & Parameters<typeof Config.create<options>>[0] = {} as never,
14
+ ): Subscription.defineConfig.ReturnType<options> {
15
+ const config = Subscription.defineConfig<options>(options)
16
+ return Object.freeze({
17
+ ...config,
18
+ Provider: dimensions(config.Provider),
19
+ }) as Subscription.defineConfig.ReturnType<options>
20
+ }
21
+
22
+ /** Provides selected variables, appearance, and automatic native dimensions. */
23
+ export const Provider = dimensions(Subscription.Provider)
24
+
25
+ export { useStyles, useVars, withStyles } from '../react.js'
26
+
27
+ function dimensions<props extends Subscription.Provider.Props>(
28
+ Component: React.FunctionComponent<props>,
29
+ ): React.FunctionComponent<props> {
30
+ return function WindowProvider(props: props) {
31
+ const window = ReactNative.useWindowDimensions()
32
+ const viewport = React.useMemo(
33
+ () => ({ height: window.height, width: window.width }),
34
+ [window.height, window.width],
35
+ )
36
+ return React.createElement(
37
+ Viewport.context.Provider,
38
+ { value: viewport },
39
+ React.createElement(Component, props),
40
+ )
41
+ }
42
+ }
43
+
44
+ /** Compiler-only binding path, with React subscriptions for Expo Go. */
45
+ export const useNativeStyles = useBindings
46
+
47
+ type Instance = {
48
+ readonly __internalInstanceHandle?:
49
+ | {
50
+ readonly stateNode?: { readonly node?: object | undefined } | undefined
51
+ }
52
+ | undefined
53
+ readonly getNativeScrollRef?: (() => Instance) | undefined
54
+ }
55
+
56
+ function useBindings() {
57
+ const owner = React.useContext(Store.context)
58
+ const [fallback] = React.useState(() =>
59
+ Store.create({
60
+ colorScheme: 'light',
61
+ viewport: ReactNative.Dimensions.get('window'),
62
+ }),
63
+ )
64
+ const store = owner ?? fallback
65
+ const [, render] = React.useReducer((count: number) => count + 1, 0)
66
+ const [refs] = React.useState(
67
+ () =>
68
+ new Map<
69
+ Instance,
70
+ {
71
+ active: boolean
72
+ ref: React.Ref<Instance> | undefined
73
+ release: () => void
74
+ }
75
+ >(),
76
+ )
77
+ React.useLayoutEffect(() => {
78
+ for (const [instance, entry] of refs) {
79
+ if (entry.active) continue
80
+ entry.release()
81
+ refs.delete(instance)
82
+ }
83
+ })
84
+ React.useLayoutEffect(
85
+ () => () => {
86
+ for (const entry of refs.values()) entry.release()
87
+ refs.clear()
88
+ },
89
+ [refs],
90
+ )
91
+ return {
92
+ style: NativeContext.application,
93
+ view: (props: Record<string, unknown>) => {
94
+ const snapshot = store.getSnapshot()
95
+ const selected = NativeContext.resolve(props.style, snapshot)
96
+ const initial = NativeContext.key(props.style, snapshot)
97
+
98
+ return {
99
+ ...props,
100
+ ref: (value: Instance | null) => {
101
+ const ref = props.ref as React.Ref<Instance> | undefined
102
+ if (!value) return
103
+ let entry = refs.get(value)
104
+ if (entry && entry.ref !== ref) {
105
+ entry.release()
106
+ refs.delete(value)
107
+ entry = undefined
108
+ }
109
+ if (!entry) {
110
+ const release = typeof ref === 'function' ? ref(value) : undefined
111
+ if (ref && typeof ref !== 'function') ref.current = value
112
+ entry = {
113
+ active: true,
114
+ ref,
115
+ release: () => {
116
+ if (typeof release === 'function') release()
117
+ else if (typeof ref === 'function') ref(null)
118
+ else if (ref) ref.current = null
119
+ },
120
+ }
121
+ refs.set(value, entry)
122
+ }
123
+ entry.active = true
124
+ const attached = entry
125
+
126
+ const instance = value.getNativeScrollRef?.() ?? value
127
+ const node = instance.__internalInstanceHandle?.stateNode?.node
128
+ const detach = (() => {
129
+ if (!initial.length && typeof selected !== 'function')
130
+ return () => {}
131
+ if (!NativeZyzz || !node || !selective(selected)) {
132
+ const unsubscribe = store.subscribe(render)
133
+ if (store.getSnapshot() !== snapshot) render()
134
+ return unsubscribe
135
+ }
136
+ const id = NativeZyzz!.attach(node, nativeProps(selected))
137
+ let previous = initial
138
+ const read = (context: NativeContext.Context) => {
139
+ const keys = NativeContext.key(props.style, context)
140
+ if (
141
+ keys.length === previous.length &&
142
+ keys.every((key, index) => Object.is(key, previous[index]))
143
+ )
144
+ return { commit: () => {} }
145
+ const selected = NativeContext.resolve(props.style, context)
146
+ if (!selective(selected)) return { commit: render }
147
+ return {
148
+ commit: () => {
149
+ previous = keys
150
+ },
151
+ patch: {
152
+ id,
153
+ props: nativeProps(selected),
154
+ },
155
+ }
156
+ }
157
+ // Context can change between render and committing the host ref.
158
+ const current = read(store.getSnapshot())
159
+ if (current.patch) write([current.patch])
160
+ current.commit()
161
+ const unbind = store.bind(read, write)
162
+ return () => {
163
+ unbind()
164
+ NativeZyzz!.detach(id)
165
+ }
166
+ })()
167
+ const update = () =>
168
+ fallback.update({
169
+ colorScheme: 'light',
170
+ viewport: ReactNative.Dimensions.get('window'),
171
+ })
172
+ const contextual =
173
+ initial.length > 0 || typeof selected === 'function'
174
+ const subscription =
175
+ owner || !contextual
176
+ ? undefined
177
+ : ReactNative.Dimensions.addEventListener('change', update)
178
+ if (!owner && contextual) update()
179
+ return () => {
180
+ detach()
181
+ subscription?.remove()
182
+ attached.active = false
183
+ }
184
+ },
185
+ style: selected,
186
+ }
187
+ },
188
+ }
189
+ }
190
+
191
+ function selective(style: unknown): boolean {
192
+ if (typeof style === 'function') return false
193
+ const value = ReactNative.StyleSheet.flatten(
194
+ style as ReactNative.StyleProp<ReactNative.ViewStyle>,
195
+ )
196
+ // Let React Native apply its processors to caller-owned representations.
197
+ // Only color properties are processed by the selective writer below.
198
+ return (
199
+ !value ||
200
+ ![
201
+ 'aspectRatio',
202
+ 'backgroundImage',
203
+ 'backgroundPosition',
204
+ 'backgroundRepeat',
205
+ 'backgroundSize',
206
+ 'boxShadow',
207
+ 'experimental_backgroundImage',
208
+ 'filter',
209
+ 'fontVariant',
210
+ 'transform',
211
+ 'transformOrigin',
212
+ ].some((property) => property in value)
213
+ )
214
+ }
215
+
216
+ function nativeProps(style: unknown): Record<string, unknown> {
217
+ const flattened =
218
+ ReactNative.StyleSheet.flatten(
219
+ style as ReactNative.StyleProp<ReactNative.ViewStyle>,
220
+ ) ?? {}
221
+ const result: Record<string, unknown> = { ...flattened }
222
+ for (const [name, value] of Object.entries(result)) {
223
+ if (name === 'color' || name.endsWith('Color'))
224
+ result[name] = ReactNative.processColor(value as ReactNative.ColorValue)
225
+ }
226
+ return result
227
+ }
228
+
229
+ function write(patches: readonly Store.Patch[]) {
230
+ NativeZyzz!.update(patches)
231
+ }
@@ -0,0 +1,20 @@
1
+ /** Private Fabric bridge for committed view bindings. @module */
2
+ import type { CodegenTypes, TurboModule } from 'react-native'
3
+ import { TurboModuleRegistry } from 'react-native'
4
+
5
+ /** Codegen contract, consumed only by the native adapter. */
6
+ export interface Spec extends TurboModule {
7
+ /** Links a mounted Fabric node and its rendered style. */
8
+ readonly attach: (
9
+ node: CodegenTypes.UnsafeObject,
10
+ props: CodegenTypes.UnsafeObject,
11
+ ) => number
12
+ /** Releases a view binding. */
13
+ readonly detach: (id: number) => void
14
+ /** Reports live bindings and applied update batches. */
15
+ readonly inspect: () => CodegenTypes.UnsafeObject
16
+ /** Applies one batch of changed native styles. */
17
+ readonly update: (updates: ReadonlyArray<CodegenTypes.UnsafeObject>) => void
18
+ }
19
+
20
+ export default TurboModuleRegistry.get<Spec>('NativeZyzz')
@@ -1,5 +1,6 @@
1
1
  /** Converts portable scalar values without source parsing or device state. @module */
2
2
  import * as Color from './Color.js'
3
+ import * as Calculation from './Calculation.js'
3
4
 
4
5
  /** Portable property domains shared by static and dynamic native conversion. */
5
6
  export const properties = {
@@ -207,7 +208,7 @@ export function convert(
207
208
  }
208
209
  if (kind === 'box' || kind === 'boxSigned') {
209
210
  const values =
210
- typeof value === 'string' ? value.trim().split(/\s+/) : [value]
211
+ typeof value === 'string' ? Calculation.parts(value) : [value]
211
212
  if (!values.length || values.length > 4)
212
213
  fail('unsupported_value', 'Use one to four scalar lengths.', path)
213
214
  for (const value of values)
@@ -225,9 +226,19 @@ export function length(
225
226
  path: readonly string[],
226
227
  ): number {
227
228
  if (value === 0 || value === '0') return 0
229
+ if (typeof value === 'string' && /^\s*calc\(/i.test(value)) {
230
+ const result = Calculation.length(value.trim(), options)
231
+ if (!signed && result < 0)
232
+ fail(
233
+ 'unsupported_value',
234
+ 'Converted length is outside the native property domain.',
235
+ path,
236
+ )
237
+ return result
238
+ }
228
239
  const match =
229
240
  typeof value === 'string' &&
230
- /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(px|rem)$/.exec(value)
241
+ /^([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?)(px|rem)$/.exec(value)
231
242
  if (!match)
232
243
  fail(
233
244
  'unsupported_value',
@@ -0,0 +1,78 @@
1
+ /** Owns scoped React subscriptions and committed native style readers. @module */
2
+ import type * as NativeContext from '../../runtime/NativeContext.js'
3
+ import * as React from 'react'
4
+
5
+ /** Internal view update payload shared with the native bridge. */
6
+ export type Patch = {
7
+ /** Runtime-local mounted view identifier. */
8
+ readonly id: number
9
+ /** Processed native style properties. */
10
+ readonly props: Record<string, unknown>
11
+ }
12
+
13
+ type Selection = {
14
+ readonly commit: () => void
15
+ readonly patch?: Patch | undefined
16
+ }
17
+ type Reader = (value: NativeContext.Context) => Selection
18
+ type Writer = (patches: readonly Patch[]) => void
19
+
20
+ /** Nearest configuration-owned selection. */
21
+ export const context = React.createContext<
22
+ ReturnType<typeof create> | undefined
23
+ >(undefined)
24
+
25
+ /** Creates subscriptions owned by a Provider or a provider-free style consumer. */
26
+ export function create(initial: NativeContext.Context) {
27
+ let snapshot = Object.freeze(initial)
28
+ const bindings = new Map<Reader, Writer>()
29
+ const listeners = new Set<() => void>()
30
+
31
+ return {
32
+ bind(read: Reader, write: Writer) {
33
+ bindings.set(read, write)
34
+ return () => {
35
+ bindings.delete(read)
36
+ }
37
+ },
38
+ getSnapshot: () => snapshot,
39
+ subscribe(listener: () => void) {
40
+ const entry = () => listener()
41
+ listeners.add(entry)
42
+ return () => {
43
+ listeners.delete(entry)
44
+ }
45
+ },
46
+ update(value: NativeContext.Context) {
47
+ if (
48
+ snapshot.colorScheme === value.colorScheme &&
49
+ snapshot.set === value.set &&
50
+ snapshot.viewport?.height === value.viewport?.height &&
51
+ snapshot.viewport?.width === value.viewport?.width
52
+ )
53
+ return
54
+
55
+ // Resolve every binding before applying a scope change that can fail.
56
+ const selections: Selection[] = []
57
+ const batches = new Map<Writer, Patch[]>()
58
+ for (const [read, write] of bindings) {
59
+ const selection = read(value)
60
+ selections.push(selection)
61
+ if (!selection.patch) continue
62
+ let batch = batches.get(write)
63
+ if (!batch) {
64
+ batch = []
65
+ batches.set(write, batch)
66
+ }
67
+ batch.push(selection.patch)
68
+ }
69
+ for (const [write, patches] of batches) write(patches)
70
+
71
+ snapshot = Object.freeze(value)
72
+ for (const selection of selections) selection.commit()
73
+ // Subscriptions added during notification wait for the next change.
74
+ for (const listener of Array.from(listeners))
75
+ if (listeners.has(listener)) listener()
76
+ },
77
+ }
78
+ }