zyzz 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Zyzz.podspec +16 -0
- package/dist/Style.d.ts +3 -1
- package/dist/Style.d.ts.map +1 -1
- package/dist/Style.js.map +1 -1
- package/dist/babel/NativeJsx.d.ts.map +1 -1
- package/dist/babel/NativeJsx.js +217 -15
- package/dist/babel/NativeJsx.js.map +1 -1
- package/dist/babel/index.d.ts +6 -2
- package/dist/babel/index.d.ts.map +1 -1
- package/dist/babel/index.js +10 -1
- package/dist/babel/index.js.map +1 -1
- package/dist/compiler/Graph.d.ts.map +1 -1
- package/dist/compiler/Graph.js +107 -0
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/Native.d.ts +6 -3
- package/dist/compiler/Native.d.ts.map +1 -1
- package/dist/compiler/Native.js +142 -24
- package/dist/compiler/Native.js.map +1 -1
- package/dist/compiler/Source.d.ts +2 -0
- package/dist/compiler/Source.d.ts.map +1 -1
- package/dist/compiler/Source.js +17 -5
- package/dist/compiler/Source.js.map +1 -1
- package/dist/compiler/internal/Configurations.d.ts +2 -0
- package/dist/compiler/internal/Configurations.d.ts.map +1 -1
- package/dist/compiler/internal/Configurations.js +21 -3
- package/dist/compiler/internal/Configurations.js.map +1 -1
- package/dist/compiler/internal/Contract.d.ts.map +1 -1
- package/dist/compiler/internal/Contract.js +75 -38
- package/dist/compiler/internal/Contract.js.map +1 -1
- package/dist/compiler/internal/Contributions.d.ts.map +1 -1
- package/dist/compiler/internal/Contributions.js +7 -1
- package/dist/compiler/internal/Contributions.js.map +1 -1
- package/dist/compiler/internal/Descriptors.d.ts.map +1 -1
- package/dist/compiler/internal/Descriptors.js +11 -2
- package/dist/compiler/internal/Descriptors.js.map +1 -1
- package/dist/compiler/internal/Dynamic.d.ts.map +1 -1
- package/dist/compiler/internal/Dynamic.js +3 -0
- package/dist/compiler/internal/Dynamic.js.map +1 -1
- package/dist/compiler/internal/Expression.js +2 -2
- package/dist/compiler/internal/Expression.js.map +1 -1
- package/dist/compiler/internal/NativeBindings.d.ts +24 -26
- package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
- package/dist/compiler/internal/NativeBindings.js +62 -8
- package/dist/compiler/internal/NativeBindings.js.map +1 -1
- package/dist/compiler/internal/NativeMedia.d.ts +14 -0
- package/dist/compiler/internal/NativeMedia.d.ts.map +1 -0
- package/dist/compiler/internal/NativeMedia.js +287 -0
- package/dist/compiler/internal/NativeMedia.js.map +1 -0
- package/dist/compiler/internal/NativeVars.d.ts +28 -0
- package/dist/compiler/internal/NativeVars.d.ts.map +1 -0
- package/dist/compiler/internal/NativeVars.js +160 -0
- package/dist/compiler/internal/NativeVars.js.map +1 -0
- package/dist/compiler/internal/PackedStyles.d.ts +2 -0
- package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
- package/dist/compiler/internal/PackedStyles.js +2 -0
- package/dist/compiler/internal/PackedStyles.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts +24 -1
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +120 -3
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/compiler/internal/Vars.d.ts +1 -1
- package/dist/compiler/internal/Vars.d.ts.map +1 -1
- package/dist/compiler/internal/Vars.js +7 -2
- package/dist/compiler/internal/Vars.js.map +1 -1
- package/dist/default.js.zyzz.json +1 -1
- package/dist/internal/Completion.d.ts +1 -1
- package/dist/internal/Completion.d.ts.map +1 -1
- package/dist/internal/Targets.d.ts.map +1 -1
- package/dist/internal/Targets.js +2 -0
- package/dist/internal/Targets.js.map +1 -1
- package/dist/internal/Token.d.ts +0 -2
- package/dist/internal/Token.d.ts.map +1 -1
- package/dist/internal/Token.js.map +1 -1
- package/dist/internal/Value.d.ts +7 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/metro/Graph.d.ts +22 -7
- package/dist/metro/Graph.d.ts.map +1 -1
- package/dist/metro/Graph.js +188 -42
- package/dist/metro/Graph.js.map +1 -1
- package/dist/metro/Watch.d.ts +28 -4
- package/dist/metro/Watch.d.ts.map +1 -1
- package/dist/metro/Watch.js +39 -7
- package/dist/metro/Watch.js.map +1 -1
- package/dist/metro/index.d.ts +15 -4
- package/dist/metro/index.d.ts.map +1 -1
- package/dist/metro/index.js +27 -0
- package/dist/metro/index.js.map +1 -1
- package/dist/metro/transformer.d.ts +5 -1
- package/dist/metro/transformer.d.ts.map +1 -1
- package/dist/metro/transformer.js +5 -3
- package/dist/metro/transformer.js.map +1 -1
- package/dist/react-native/StyleSheet.d.ts +8 -3
- package/dist/react-native/StyleSheet.d.ts.map +1 -1
- package/dist/react-native/StyleSheet.js +25 -34
- package/dist/react-native/StyleSheet.js.map +1 -1
- package/dist/react-native/index.d.ts +1 -1
- package/dist/react-native/index.d.ts.map +1 -1
- package/dist/react-native/index.js +1 -1
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native/index.native.d.ts +5 -0
- package/dist/react-native/index.native.js +20 -0
- package/dist/react-native/index.native.js.map +7 -0
- package/dist/react-native/internal/Calculation.d.ts +7 -0
- package/dist/react-native/internal/Calculation.d.ts.map +1 -0
- package/dist/react-native/internal/Calculation.js +134 -0
- package/dist/react-native/internal/Calculation.js.map +1 -0
- package/dist/react-native/internal/Device.js +218 -0
- package/dist/react-native/internal/Device.js.map +7 -0
- package/dist/react-native/internal/NativeZyzz.js +7 -0
- package/dist/react-native/internal/NativeZyzz.js.map +7 -0
- package/dist/react-native/internal/Scalar.d.ts +16 -0
- package/dist/react-native/internal/Scalar.d.ts.map +1 -1
- package/dist/react-native/internal/Scalar.js +62 -4
- package/dist/react-native/internal/Scalar.js.map +1 -1
- package/dist/react-native/internal/Store.d.ts +32 -0
- package/dist/react-native/internal/Store.d.ts.map +1 -0
- package/dist/react-native/internal/Store.js +57 -0
- package/dist/react-native/internal/Store.js.map +1 -0
- package/dist/react-native/internal/Styles.d.ts +19 -0
- package/dist/react-native/internal/Styles.d.ts.map +1 -0
- package/dist/react-native/internal/Styles.js +35 -0
- package/dist/react-native/internal/Styles.js.map +1 -0
- package/dist/react-native/internal/Tokens.d.ts +21 -0
- package/dist/react-native/internal/Tokens.d.ts.map +1 -0
- package/dist/react-native/internal/Tokens.js +58 -0
- package/dist/react-native/internal/Tokens.js.map +1 -0
- package/dist/react-native/internal/Viewport.d.ts +6 -0
- package/dist/react-native/internal/Viewport.d.ts.map +1 -0
- package/dist/react-native/internal/Viewport.js +4 -0
- package/dist/react-native/internal/Viewport.js.map +1 -0
- package/dist/react-native/react.d.ts +102 -14
- package/dist/react-native/react.d.ts.map +1 -1
- package/dist/react-native/react.js +86 -18
- package/dist/react-native/react.js.map +1 -1
- package/dist/react-native/reanimated.d.ts +39 -0
- package/dist/react-native/reanimated.d.ts.map +1 -0
- package/dist/react-native/reanimated.js +76 -0
- package/dist/react-native/reanimated.js.map +1 -0
- package/dist/runtime/NativeContext.d.ts +10 -1
- package/dist/runtime/NativeContext.d.ts.map +1 -1
- package/dist/runtime/NativeContext.js +67 -14
- package/dist/runtime/NativeContext.js.map +1 -1
- package/dist/runtime/NativeDynamic.d.ts +3 -2
- package/dist/runtime/NativeDynamic.d.ts.map +1 -1
- package/dist/runtime/NativeDynamic.js +37 -7
- package/dist/runtime/NativeDynamic.js.map +1 -1
- package/dist/runtime/NativeStatic.d.ts +23 -0
- package/dist/runtime/NativeStatic.d.ts.map +1 -0
- package/dist/runtime/NativeStatic.js +106 -0
- package/dist/runtime/NativeStatic.js.map +1 -0
- package/dist/runtime/NativeVars.d.ts +53 -0
- package/dist/runtime/NativeVars.d.ts.map +1 -0
- package/dist/runtime/NativeVars.js +77 -0
- package/dist/runtime/NativeVars.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/internal/NativeMedia.d.ts +25 -0
- package/dist/runtime/internal/NativeMedia.d.ts.map +1 -0
- package/dist/runtime/internal/NativeMedia.js +36 -0
- package/dist/runtime/internal/NativeMedia.js.map +1 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +7 -35
- package/dist/vite/index.js.map +1 -1
- package/dist/web/Css.d.ts.map +1 -1
- package/dist/web/Css.js +31 -19
- package/dist/web/Css.js.map +1 -1
- package/dist/web/internal/Themes.d.ts +0 -2
- package/dist/web/internal/Themes.d.ts.map +1 -1
- package/dist/web/internal/Themes.js +30 -28
- package/dist/web/internal/Themes.js.map +1 -1
- package/dist/web/page.d.ts +2 -0
- package/dist/web/page.d.ts.map +1 -1
- package/native/NativeZyzz.cpp +215 -0
- package/native/NativeZyzz.h +52 -0
- package/native/ZyzzSpecJSI.h +65 -0
- package/native/android/CMakeLists.txt +8 -0
- package/native/android/ZyzzSpec-generated.cpp +50 -0
- package/native/android/ZyzzSpec.h +31 -0
- package/native/ios/ZyzzModuleProvider.mm +13 -0
- package/package.json +39 -1
- package/react-native.config.ts +14 -0
- package/src/Style.ts +11 -8
- package/src/babel/NativeJsx.ts +319 -33
- package/src/babel/index.ts +21 -3
- package/src/compiler/Graph.ts +126 -0
- package/src/compiler/Native.ts +158 -26
- package/src/compiler/Source.ts +32 -5
- package/src/compiler/internal/Configurations.ts +25 -3
- package/src/compiler/internal/Contract.ts +98 -50
- package/src/compiler/internal/Contributions.ts +7 -1
- package/src/compiler/internal/Descriptors.ts +12 -8
- package/src/compiler/internal/Dynamic.ts +4 -0
- package/src/compiler/internal/Expression.ts +2 -2
- package/src/compiler/internal/NativeBindings.ts +99 -15
- package/src/compiler/internal/NativeMedia.ts +342 -0
- package/src/compiler/internal/NativeVars.ts +231 -0
- package/src/compiler/internal/PackedStyles.ts +4 -0
- package/src/compiler/internal/Themes.ts +193 -2
- package/src/compiler/internal/Vars.ts +12 -1
- package/src/internal/Completion.ts +6 -3
- package/src/internal/Targets.ts +2 -0
- package/src/internal/Token.ts +0 -2
- package/src/internal/Value.ts +20 -0
- package/src/metro/Graph.ts +250 -50
- package/src/metro/Watch.ts +95 -10
- package/src/metro/index.ts +62 -4
- package/src/metro/transformer.ts +21 -11
- package/src/react-native/StyleSheet.ts +56 -49
- package/src/react-native/index.native.ts +10 -0
- package/src/react-native/index.ts +1 -1
- package/src/react-native/internal/Calculation.ts +159 -0
- package/src/react-native/internal/Device.ts +297 -0
- package/src/react-native/internal/NativeZyzz.ts +20 -0
- package/src/react-native/internal/Scalar.ts +86 -4
- package/src/react-native/internal/Store.ts +78 -0
- package/src/react-native/internal/Styles.ts +77 -0
- package/src/react-native/internal/Tokens.ts +85 -0
- package/src/react-native/internal/Viewport.ts +8 -0
- package/src/react-native/react.ts +218 -26
- package/src/react-native/reanimated.ts +162 -0
- package/src/runtime/NativeContext.ts +88 -17
- package/src/runtime/NativeDynamic.ts +42 -10
- package/src/runtime/NativeStatic.ts +145 -0
- package/src/runtime/NativeVars.ts +189 -0
- package/src/runtime/index.ts +6 -0
- package/src/runtime/internal/NativeMedia.ts +63 -0
- package/src/vite/index.ts +9 -42
- package/src/web/Css.ts +42 -27
- package/src/web/internal/Themes.ts +34 -57
- 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,231 @@
|
|
|
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
|
+
'flex',
|
|
159
|
+
'fontVariant',
|
|
160
|
+
'origin',
|
|
161
|
+
'shadow',
|
|
162
|
+
'textShadow',
|
|
163
|
+
'transform',
|
|
164
|
+
].includes(kind)
|
|
165
|
+
)
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Native property ${property} does not have a scalar variable value.`,
|
|
168
|
+
)
|
|
169
|
+
if (kind === 'ratio' && typeof value !== 'number')
|
|
170
|
+
throw new Error(
|
|
171
|
+
'Native aspect ratios require numeric variable values.',
|
|
172
|
+
)
|
|
173
|
+
if (kind === 'box' || kind === 'boxSigned')
|
|
174
|
+
return Scalar.length(value, options, kind === 'boxSigned', [path])
|
|
175
|
+
if (kind === 'boxOffset')
|
|
176
|
+
return Scalar.convert('offset', value, options, [path])
|
|
177
|
+
return Scalar.convert(kind, value, options, [path])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (targets.length) {
|
|
181
|
+
const values = targets.map(convert)
|
|
182
|
+
if (values.some((value) => value !== values[0]))
|
|
183
|
+
throw new Error(
|
|
184
|
+
'Mapped native properties require different converted values.',
|
|
185
|
+
)
|
|
186
|
+
return values[0]!
|
|
187
|
+
}
|
|
188
|
+
if (domain === 'spacing')
|
|
189
|
+
return Scalar.length(value, options, true, [path])
|
|
190
|
+
if (domain === 'color')
|
|
191
|
+
return Scalar.convert('color', value, options, [path])
|
|
192
|
+
return value
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return [
|
|
195
|
+
`Native variable ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
196
|
+
] as const
|
|
197
|
+
}
|
|
198
|
+
})()
|
|
199
|
+
let parent = tree
|
|
200
|
+
for (const key of parts.slice(0, -1))
|
|
201
|
+
parent = (parent[key] ??= Object.create(null)) as typeof tree
|
|
202
|
+
parent[parts.at(-1)!] = converted
|
|
203
|
+
}
|
|
204
|
+
return tree
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Compiler inputs for resolved native variables. */
|
|
209
|
+
export declare namespace compile {
|
|
210
|
+
/** Complete variable catalog and explicit target mappings. */
|
|
211
|
+
type Options = Scalar.Options & {
|
|
212
|
+
/** Configuration fallback. */
|
|
213
|
+
readonly defaultVars: string
|
|
214
|
+
/** Whether the input is a standalone definition instead of a named catalog. */
|
|
215
|
+
readonly unnamed: boolean
|
|
216
|
+
/** Compatible authored definitions. */
|
|
217
|
+
readonly vars: Readonly<Record<string, Theme.Definition>>
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Graph-owned profile exports and imports, excluded from public adapter options. */
|
|
222
|
+
export const shared = Symbol('zyzz.compiler.native.vars')
|
|
223
|
+
|
|
224
|
+
/** Static profile artifacts belonging to one source module. */
|
|
225
|
+
export type Shared = {
|
|
226
|
+
readonly definitions: ReadonlyMap<string, string>
|
|
227
|
+
readonly reads: ReadonlyMap<
|
|
228
|
+
number,
|
|
229
|
+
{ readonly name: string; readonly source?: string | undefined }
|
|
230
|
+
>
|
|
231
|
+
}
|
|
@@ -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',
|