zyzz 0.0.22 → 0.0.24
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/README.md +6 -6
- package/dist/babel/NativeJsx.d.ts.map +1 -1
- package/dist/babel/NativeJsx.js +14 -4
- package/dist/babel/NativeJsx.js.map +1 -1
- package/dist/compiler/Graph.js +78 -35
- package/dist/compiler/Graph.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.map +1 -1
- package/dist/compiler/internal/NativeBindings.js +1 -0
- package/dist/compiler/internal/NativeBindings.js.map +1 -1
- package/dist/compiler/internal/NativeVars.d.ts.map +1 -1
- package/dist/compiler/internal/NativeVars.js +3 -0
- package/dist/compiler/internal/NativeVars.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts +2 -0
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +24 -22
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +2 -1
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/metro/Graph.d.ts.map +1 -1
- package/dist/metro/Graph.js +11 -2
- package/dist/metro/Graph.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +40 -12
- package/dist/node/Host.js.map +1 -1
- package/dist/react-native/StyleSheet.d.ts +7 -3
- package/dist/react-native/StyleSheet.d.ts.map +1 -1
- package/dist/react-native/StyleSheet.js +8 -0
- package/dist/react-native/StyleSheet.js.map +1 -1
- package/dist/react-native/internal/Device.js +118 -38
- package/dist/react-native/internal/Device.js.map +3 -3
- package/dist/react-native/internal/NativeZyzz.js.map +2 -2
- 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 +53 -2
- package/dist/react-native/internal/Scalar.js.map +1 -1
- package/dist/react-native/internal/Store.d.ts +4 -0
- package/dist/react-native/internal/Store.d.ts.map +1 -1
- package/dist/react-native/internal/Store.js.map +1 -1
- 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/Viewport.d.ts +6 -0
- package/dist/react-native/internal/Viewport.d.ts.map +1 -1
- package/dist/react-native/internal/Viewport.js +2 -0
- package/dist/react-native/internal/Viewport.js.map +1 -1
- package/dist/react-native/react.d.ts +44 -16
- package/dist/react-native/react.d.ts.map +1 -1
- package/dist/react-native/react.js +68 -47
- package/dist/react-native/react.js.map +1 -1
- package/dist/runtime/NativeContext.d.ts +6 -0
- package/dist/runtime/NativeContext.d.ts.map +1 -1
- package/dist/runtime/NativeContext.js +62 -9
- package/dist/runtime/NativeContext.js.map +1 -1
- package/dist/runtime/NativeDynamic.d.ts.map +1 -1
- package/dist/runtime/NativeDynamic.js +5 -0
- package/dist/runtime/NativeDynamic.js.map +1 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +4 -1
- package/dist/unplugin/index.js.map +1 -1
- package/dist/variants.d.ts +3 -2
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.js.map +1 -1
- package/native/NativeZyzz.cpp +52 -65
- package/native/NativeZyzz.h +0 -4
- package/native/ZyzzSpecJSI.h +0 -10
- package/native/android/ZyzzSpec-generated.cpp +0 -6
- package/package.json +1 -1
- package/src/babel/NativeJsx.ts +17 -8
- package/src/compiler/Graph.ts +89 -38
- package/src/compiler/internal/Dynamic.ts +4 -0
- package/src/compiler/internal/Expression.ts +2 -2
- package/src/compiler/internal/NativeBindings.ts +1 -0
- package/src/compiler/internal/NativeVars.ts +3 -0
- package/src/compiler/internal/Themes.ts +29 -23
- package/src/internal/Configuration.ts +2 -1
- package/src/metro/Graph.ts +17 -2
- package/src/node/Host.ts +64 -17
- package/src/react-native/StyleSheet.ts +32 -9
- package/src/react-native/internal/Device.ts +196 -49
- package/src/react-native/internal/NativeZyzz.ts +4 -6
- package/src/react-native/internal/Scalar.ts +73 -2
- package/src/react-native/internal/Store.ts +4 -0
- package/src/react-native/internal/Styles.ts +77 -0
- package/src/react-native/internal/Viewport.ts +7 -0
- package/src/react-native/react.ts +142 -72
- package/src/runtime/NativeContext.ts +83 -9
- package/src/runtime/NativeDynamic.ts +7 -0
- package/src/unplugin/index.ts +4 -1
- package/src/variants.ts +3 -2
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/** React subscriptions for compiled native styles and variable values. @module */
|
|
2
2
|
import * as Config from '../Config.js'
|
|
3
|
-
import * as NativeContext from '../runtime/NativeContext.js'
|
|
3
|
+
import type * as NativeContext from '../runtime/NativeContext.js'
|
|
4
4
|
import * as NativeVars from '../runtime/NativeVars.js'
|
|
5
5
|
import * as React from 'react'
|
|
6
6
|
import type * as Vars from '../Vars.js'
|
|
7
7
|
import * as Store from './internal/Store.js'
|
|
8
|
+
import * as Styles from './internal/Styles.js'
|
|
8
9
|
import * as Viewport from './internal/Viewport.js'
|
|
9
10
|
|
|
10
|
-
const unbound = {
|
|
11
|
-
getSnapshot: () => undefined,
|
|
12
|
-
subscribe: () => () => {},
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
/** Creates native authoring helpers and a Provider typed to the configured variables. */
|
|
16
12
|
export function defineConfig<const options extends Config.create.Options = {}>(
|
|
17
13
|
options: options & Parameters<typeof Config.create<options>>[0] = {} as never,
|
|
18
14
|
): defineConfig.ReturnType<options> {
|
|
19
|
-
const config = Config.create<options>(options)
|
|
20
15
|
const defaultVars = (options as Config.VariableOptions).defaultVars
|
|
21
16
|
const names =
|
|
22
17
|
defaultVars === undefined
|
|
@@ -33,10 +28,71 @@ export function defineConfig<const options extends Config.create.Options = {}>(
|
|
|
33
28
|
})
|
|
34
29
|
}
|
|
35
30
|
|
|
31
|
+
if ((options as Config.VariableOptions).vars === undefined)
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
...Config.create<options>(options),
|
|
34
|
+
Provider: BoundProvider,
|
|
35
|
+
}) as defineConfig.ReturnType<options>
|
|
36
|
+
|
|
37
|
+
// Compiled native styles and `useVars` read precompiled tables, so variable
|
|
38
|
+
// configurations build their authoring helpers on first runtime use.
|
|
39
|
+
let config: Helpers | undefined
|
|
40
|
+
const create = () =>
|
|
41
|
+
(config ??= (Config.create as (options: object) => Helpers)(options))
|
|
36
42
|
return Object.freeze({
|
|
37
|
-
|
|
43
|
+
get appearance() {
|
|
44
|
+
return create().appearance
|
|
45
|
+
},
|
|
38
46
|
Provider: BoundProvider,
|
|
39
|
-
|
|
47
|
+
get script() {
|
|
48
|
+
return create().script
|
|
49
|
+
},
|
|
50
|
+
style: (...args: never[]) => create().style(...args),
|
|
51
|
+
variants: (...args: never[]) => create().variants(...args),
|
|
52
|
+
vars: deferred(() => create().vars),
|
|
53
|
+
}) as unknown as defineConfig.ReturnType<options>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Untyped view of a variable configuration's helpers. */
|
|
57
|
+
type Helpers = {
|
|
58
|
+
readonly appearance: unknown
|
|
59
|
+
readonly script: unknown
|
|
60
|
+
readonly style: (...args: never[]) => unknown
|
|
61
|
+
readonly variants: (...args: never[]) => unknown
|
|
62
|
+
readonly vars: object
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Forwards every operation to a value built on first use, mirroring it onto the proxy target so invariants hold. */
|
|
66
|
+
function deferred<value extends object>(create: () => value): value {
|
|
67
|
+
const target = (() => {}) as unknown as value
|
|
68
|
+
let ready = false
|
|
69
|
+
function resolve() {
|
|
70
|
+
if (ready) return target
|
|
71
|
+
const value = create()
|
|
72
|
+
Object.defineProperties(target, Object.getOwnPropertyDescriptors(value))
|
|
73
|
+
Object.setPrototypeOf(target, Object.getPrototypeOf(value))
|
|
74
|
+
if (Object.isFrozen(value)) Object.freeze(target)
|
|
75
|
+
ready = true
|
|
76
|
+
return target
|
|
77
|
+
}
|
|
78
|
+
return new Proxy(target, {
|
|
79
|
+
apply: (_, self, args) =>
|
|
80
|
+
Reflect.apply(create() as (...args: unknown[]) => unknown, self, args),
|
|
81
|
+
defineProperty: (_, key, descriptor) =>
|
|
82
|
+
Reflect.defineProperty(resolve(), key, descriptor),
|
|
83
|
+
deleteProperty: (_, key) => Reflect.deleteProperty(resolve(), key),
|
|
84
|
+
get: (_, key) => Reflect.get(resolve(), key),
|
|
85
|
+
getOwnPropertyDescriptor: (_, key) =>
|
|
86
|
+
Reflect.getOwnPropertyDescriptor(resolve(), key),
|
|
87
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(resolve()),
|
|
88
|
+
has: (_, key) => Reflect.has(resolve(), key),
|
|
89
|
+
isExtensible: () => Reflect.isExtensible(resolve()),
|
|
90
|
+
ownKeys: () => Reflect.ownKeys(resolve()),
|
|
91
|
+
preventExtensions: () => Reflect.preventExtensions(resolve()),
|
|
92
|
+
set: (_, key, value) => Reflect.set(resolve(), key, value),
|
|
93
|
+
setPrototypeOf: (_, prototype) =>
|
|
94
|
+
Reflect.setPrototypeOf(resolve(), prototype),
|
|
95
|
+
})
|
|
40
96
|
}
|
|
41
97
|
|
|
42
98
|
/** Native configuration helpers and inferred Provider props. */
|
|
@@ -66,23 +122,30 @@ export function Provider(props: Provider.Props) {
|
|
|
66
122
|
if (Object.hasOwn(props, 'set'))
|
|
67
123
|
throw new Error('Provider uses vars instead of set.')
|
|
68
124
|
if (
|
|
69
|
-
(props.colorScheme !== 'light' &&
|
|
125
|
+
(props.colorScheme !== 'light' &&
|
|
126
|
+
props.colorScheme !== 'dark' &&
|
|
127
|
+
props.colorScheme !== 'system') ||
|
|
70
128
|
(props.vars !== undefined &&
|
|
71
129
|
(typeof props.vars !== 'string' || !props.vars.trim()))
|
|
72
130
|
)
|
|
73
131
|
throw new Error(
|
|
74
|
-
'Native appearance requires a
|
|
132
|
+
'Native appearance requires a light, dark, or system scheme and a nonempty vars name.',
|
|
75
133
|
)
|
|
76
134
|
|
|
77
135
|
const viewport = React.useContext(Viewport.context)
|
|
136
|
+
const system = React.useContext(Viewport.appearance)
|
|
137
|
+
const colorScheme =
|
|
138
|
+
props.colorScheme === 'system' ? system.colorScheme : props.colorScheme
|
|
139
|
+
const adaptive = props.colorScheme === 'system' ? system.adaptive : undefined
|
|
78
140
|
const [store] = React.useState(() =>
|
|
79
|
-
Store.create({
|
|
141
|
+
Store.create({ adaptive, colorScheme, set: props.vars, viewport }),
|
|
80
142
|
)
|
|
81
143
|
const [, publish] = React.useReducer((version: number) => version + 1, 0)
|
|
82
144
|
const committed = React.useRef(props.children)
|
|
83
145
|
const snapshot = store.getSnapshot()
|
|
84
146
|
const pending =
|
|
85
|
-
snapshot.
|
|
147
|
+
snapshot.adaptive !== adaptive ||
|
|
148
|
+
snapshot.colorScheme !== colorScheme ||
|
|
86
149
|
snapshot.set !== props.vars ||
|
|
87
150
|
snapshot.viewport?.width !== viewport?.width ||
|
|
88
151
|
snapshot.viewport?.height !== viewport?.height
|
|
@@ -93,9 +156,9 @@ export function Provider(props: Provider.Props) {
|
|
|
93
156
|
// Keep the committed subtree until its selection is published. This avoids
|
|
94
157
|
// rendering new application props with the previous store snapshot, without
|
|
95
158
|
// mutating an external store during a potentially abandoned React render.
|
|
96
|
-
store.update({
|
|
159
|
+
store.update({ adaptive, colorScheme, set: props.vars, viewport })
|
|
97
160
|
publish()
|
|
98
|
-
}, [
|
|
161
|
+
}, [adaptive, children, colorScheme, pending, props.vars, store, viewport])
|
|
99
162
|
|
|
100
163
|
return React.createElement(Store.context.Provider, { value: store }, children)
|
|
101
164
|
}
|
|
@@ -106,22 +169,49 @@ export declare namespace Provider {
|
|
|
106
169
|
type Props = {
|
|
107
170
|
/** Components that consume compiled native styling or values. */
|
|
108
171
|
readonly children?: React.ReactNode | undefined
|
|
109
|
-
/**
|
|
110
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Resolved device scheme, an application override, or `system` to follow the device.
|
|
174
|
+
* On iOS, `system` styles whose schemes differ only in color switch without updates.
|
|
175
|
+
*/
|
|
176
|
+
readonly colorScheme: 'dark' | 'light' | 'system'
|
|
111
177
|
/** Selected variables. Omission uses each configuration's default. */
|
|
112
178
|
readonly vars?: string | undefined
|
|
113
179
|
}
|
|
114
180
|
}
|
|
115
181
|
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Compiler-inserted subscription for resolving native style applications.
|
|
184
|
+
* Outside a Provider, selections use default variables and the light scheme.
|
|
185
|
+
*/
|
|
186
|
+
export function useStyles(): useStyles.ReturnType {
|
|
187
|
+
const [fallback] = React.useState(() =>
|
|
188
|
+
Store.create({ colorScheme: 'light' }),
|
|
123
189
|
)
|
|
124
|
-
return React.
|
|
190
|
+
return Styles.use(React.useContext(Store.context) ?? fallback)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Resolver types for Provider-selected native styles. */
|
|
194
|
+
export declare namespace useStyles {
|
|
195
|
+
/** Resolvers bound to the nearest Provider selection. */
|
|
196
|
+
type ReturnType = {
|
|
197
|
+
/** Resolves the `style` prop of applied props and preserves other props. */
|
|
198
|
+
readonly props: <
|
|
199
|
+
props extends Readonly<Record<string, unknown>> | null | undefined,
|
|
200
|
+
>(
|
|
201
|
+
props: props,
|
|
202
|
+
) => props
|
|
203
|
+
/** Resolves an applied style, native style, style array, or style callback. */
|
|
204
|
+
readonly style: <style>(style: style, input?: unknown) => Style<style>
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Resolved style type. Caller-owned values keep their types. Native applications
|
|
208
|
+
* always carry `style`, so an applied style drops the `undefined` allowed by the shared props type.
|
|
209
|
+
*/
|
|
210
|
+
type Style<style> = [
|
|
211
|
+
style extends object ? (string extends keyof style ? style : never) : never,
|
|
212
|
+
] extends [never]
|
|
213
|
+
? style
|
|
214
|
+
: Exclude<style, undefined>
|
|
125
215
|
}
|
|
126
216
|
|
|
127
217
|
/** Compiler fallback for native view bindings when no native adapter is installed. */
|
|
@@ -171,46 +261,37 @@ export function useVars(
|
|
|
171
261
|
}
|
|
172
262
|
|
|
173
263
|
/**
|
|
174
|
-
* Resolves compiled style props for components using React updates.
|
|
175
|
-
* @param Component - Function,
|
|
264
|
+
* Resolves compiled style props for function components using React updates.
|
|
265
|
+
* @param Component - Function, memo, or ref-forwarding component receiving native styles.
|
|
176
266
|
* @param options - Additional style-bearing prop names.
|
|
177
|
-
* @returns A component
|
|
178
|
-
* @throws For invalid style prop names
|
|
267
|
+
* @returns A component with the original call signature and ref. Type parameters remain when `styleProps` is omitted.
|
|
268
|
+
* @throws For invalid style prop names.
|
|
269
|
+
*/
|
|
270
|
+
export function withStyles<
|
|
271
|
+
props extends object,
|
|
272
|
+
result extends React.ReactNode,
|
|
273
|
+
>(
|
|
274
|
+
Component: (props: props) => result,
|
|
275
|
+
options?: NoInfer<withStyles.Options<(props: props) => result>>,
|
|
276
|
+
): (props: props) => result
|
|
277
|
+
/**
|
|
278
|
+
* Resolves compiled style props for class and host components using React updates.
|
|
279
|
+
* @param Component - Class component or host element type receiving native styles.
|
|
280
|
+
* @param options - Additional style-bearing prop names.
|
|
281
|
+
* @returns A component preserving the original props and instance ref.
|
|
282
|
+
* @throws For invalid style prop names.
|
|
179
283
|
*/
|
|
180
284
|
export function withStyles<const component extends React.ElementType>(
|
|
181
285
|
Component: component,
|
|
182
|
-
options
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)
|
|
192
|
-
throw new Error(
|
|
193
|
-
'withStyles requires style prop names excluding key and ref.',
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
const names = [...new Set(['style', 'contentContainerStyle', ...additional])]
|
|
197
|
-
const Wrapped = React.forwardRef<
|
|
198
|
-
React.ComponentRef<component>,
|
|
199
|
-
React.ComponentPropsWithoutRef<component>
|
|
200
|
-
>((props, ref) => {
|
|
201
|
-
const selected = useStyles()
|
|
202
|
-
const resolved: Record<string, unknown> = { ...props, ref }
|
|
203
|
-
for (const name of names)
|
|
204
|
-
if (Object.hasOwn(props, name))
|
|
205
|
-
resolved[name] = selected.style(
|
|
206
|
-
(props as Record<string, unknown>)[name],
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
return React.createElement(Component, resolved)
|
|
210
|
-
})
|
|
211
|
-
Wrapped.displayName = `withStyles(${typeof Component === 'string' ? Component : Component.displayName || Component.name || 'Component'})`
|
|
212
|
-
|
|
213
|
-
return Wrapped
|
|
286
|
+
options?: withStyles.Options<component>,
|
|
287
|
+
): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<component>>
|
|
288
|
+
export function withStyles(
|
|
289
|
+
Component: React.ElementType,
|
|
290
|
+
options: withStyles.Options<
|
|
291
|
+
(props: Readonly<Record<string, unknown>>) => React.ReactNode
|
|
292
|
+
> = {},
|
|
293
|
+
): React.ElementType {
|
|
294
|
+
return Styles.wrap(Component, options, useStyles)
|
|
214
295
|
}
|
|
215
296
|
|
|
216
297
|
/** Style-bearing component properties resolved by the wrapper. */
|
|
@@ -226,14 +307,3 @@ export declare namespace withStyles {
|
|
|
226
307
|
| undefined
|
|
227
308
|
}
|
|
228
309
|
}
|
|
229
|
-
|
|
230
|
-
function styles(value: NativeContext.Context | undefined) {
|
|
231
|
-
return {
|
|
232
|
-
props: (props: Record<string, unknown> | null | undefined) =>
|
|
233
|
-
props && Object.hasOwn(props, 'style')
|
|
234
|
-
? { ...props, style: NativeContext.resolve(props.style, value) }
|
|
235
|
-
: props,
|
|
236
|
-
style: (style: unknown, input?: unknown) =>
|
|
237
|
-
NativeContext.resolve(style, value, input),
|
|
238
|
-
}
|
|
239
|
-
}
|
|
@@ -7,6 +7,14 @@ const dependency = Symbol('zyzz.native.dependency')
|
|
|
7
7
|
|
|
8
8
|
/** Render-local set and resolved appearance. */
|
|
9
9
|
export type Context = {
|
|
10
|
+
/**
|
|
11
|
+
* Builds a color that follows the platform appearance. When present, styles whose
|
|
12
|
+
* light and dark selections differ only in color use it and need no scheme update.
|
|
13
|
+
* Must be stable, since adapted styles are cached per selection pair.
|
|
14
|
+
*/
|
|
15
|
+
readonly adaptive?:
|
|
16
|
+
| ((light: string | number, dark: string | number) => unknown)
|
|
17
|
+
| undefined
|
|
10
18
|
/** Resolved system appearance or an explicit override. */
|
|
11
19
|
readonly colorScheme: 'dark' | 'light'
|
|
12
20
|
/** Named compiled set; omission uses each configuration default. */
|
|
@@ -37,7 +45,15 @@ export function create<const tables extends Tables>(
|
|
|
37
45
|
const set = context.set ?? defaultVars
|
|
38
46
|
const table = tables[set] ?? fallback
|
|
39
47
|
if (!table) throw new Error(`Unknown native set: ${set}.`)
|
|
40
|
-
|
|
48
|
+
const style = table[context.colorScheme](input!).style
|
|
49
|
+
if (!context.adaptive) return style
|
|
50
|
+
return (
|
|
51
|
+
adapt(
|
|
52
|
+
table.light(input!).style,
|
|
53
|
+
table.dark(input!).style,
|
|
54
|
+
context.adaptive,
|
|
55
|
+
) ?? style
|
|
56
|
+
)
|
|
41
57
|
}
|
|
42
58
|
return bind(select, (context) => {
|
|
43
59
|
const table = tables[context.set ?? defaultVars] ?? fallback
|
|
@@ -94,10 +110,10 @@ export function responsive<
|
|
|
94
110
|
export function application(value: unknown, input?: unknown): unknown {
|
|
95
111
|
return {
|
|
96
112
|
[binding]: (context: Context) => resolve(value, context, input),
|
|
97
|
-
[dependency]: (context: Context) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
[dependency]: (context: Context) =>
|
|
114
|
+
input === undefined
|
|
115
|
+
? key(value, context)
|
|
116
|
+
: [...key(value, context), ...key(input, context)],
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
|
|
@@ -117,6 +133,64 @@ export function key(value: unknown, context: Context): readonly unknown[] {
|
|
|
117
133
|
return []
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
const adapted = new WeakMap<object, WeakMap<object, object | null>>()
|
|
137
|
+
|
|
138
|
+
/** Merges selections that differ only in color, or returns undefined. Results keep their identity across schemes. */
|
|
139
|
+
function adapt(
|
|
140
|
+
light: unknown,
|
|
141
|
+
dark: unknown,
|
|
142
|
+
color: NonNullable<Context['adaptive']>,
|
|
143
|
+
): unknown {
|
|
144
|
+
if (light === dark) return light
|
|
145
|
+
if (!record(light) || !record(dark)) return undefined
|
|
146
|
+
let pairs = adapted.get(light)
|
|
147
|
+
if (!pairs) adapted.set(light, (pairs = new WeakMap()))
|
|
148
|
+
if (pairs.has(dark)) return pairs.get(dark) ?? undefined
|
|
149
|
+
const result: Record<string, unknown> | null = (() => {
|
|
150
|
+
const keys = Object.keys(light)
|
|
151
|
+
if (keys.length !== Object.keys(dark).length) return null
|
|
152
|
+
const merged: Record<string, unknown> = {}
|
|
153
|
+
for (const key of keys) {
|
|
154
|
+
if (!Object.hasOwn(dark, key)) return null
|
|
155
|
+
const [first, second] = [light[key], dark[key]]
|
|
156
|
+
if (equal(first, second)) merged[key] = first
|
|
157
|
+
else if (
|
|
158
|
+
(key === 'color' || key.endsWith('Color')) &&
|
|
159
|
+
(typeof first === 'string' || typeof first === 'number') &&
|
|
160
|
+
(typeof second === 'string' || typeof second === 'number')
|
|
161
|
+
)
|
|
162
|
+
merged[key] = color(first, second)
|
|
163
|
+
else return null
|
|
164
|
+
}
|
|
165
|
+
return merged
|
|
166
|
+
})()
|
|
167
|
+
const value = result && Object.freeze(result)
|
|
168
|
+
pairs.set(dark, value)
|
|
169
|
+
return value ?? undefined
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function equal(left: unknown, right: unknown): boolean {
|
|
173
|
+
if (Object.is(left, right)) return true
|
|
174
|
+
if (Array.isArray(left))
|
|
175
|
+
return (
|
|
176
|
+
Array.isArray(right) &&
|
|
177
|
+
left.length === right.length &&
|
|
178
|
+
left.every((value, index) => equal(value, right[index]))
|
|
179
|
+
)
|
|
180
|
+
if (!record(left) || !record(right)) return false
|
|
181
|
+
const keys = Object.keys(left)
|
|
182
|
+
return (
|
|
183
|
+
keys.length === Object.keys(right).length &&
|
|
184
|
+
keys.every(
|
|
185
|
+
(key) => Object.hasOwn(right, key) && equal(left[key], right[key]),
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function record(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
191
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
192
|
+
}
|
|
193
|
+
|
|
120
194
|
function bind(
|
|
121
195
|
select: (context: Context, input?: never) => unknown,
|
|
122
196
|
read: (context: Context) => readonly unknown[],
|
|
@@ -125,10 +199,10 @@ function bind(
|
|
|
125
199
|
return {
|
|
126
200
|
style: {
|
|
127
201
|
[binding]: (context: Context) => select(context, input),
|
|
128
|
-
[dependency]: (context: Context) =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
202
|
+
[dependency]: (context: Context) =>
|
|
203
|
+
input === undefined
|
|
204
|
+
? read(context)
|
|
205
|
+
: [...read(context), ...key(input, context)],
|
|
132
206
|
},
|
|
133
207
|
}
|
|
134
208
|
}
|
|
@@ -205,6 +205,13 @@ export function create(
|
|
|
205
205
|
[`${property}Bottom`]: bottom,
|
|
206
206
|
[`${property}Left`]: left,
|
|
207
207
|
})
|
|
208
|
+
} else if (property === 'inset') {
|
|
209
|
+
const parts =
|
|
210
|
+
typeof value === 'string' ? Calculation.parts(value) : [value]
|
|
211
|
+
const [top, right = top, bottom = top, left = right] = parts.map(
|
|
212
|
+
(part) => Scalar.convert('offset', part, options, [property]),
|
|
213
|
+
)
|
|
214
|
+
Object.assign(output, { top, right, bottom, left })
|
|
208
215
|
} else if (property === 'borderColor' || property === 'borderWidth') {
|
|
209
216
|
for (const side of ['Top', 'Right', 'Bottom', 'Left'])
|
|
210
217
|
output[
|
package/src/unplugin/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as AtRules from '../compiler/internal/AtRules.js'
|
|
|
13
13
|
import * as Catalogs from '../compiler/internal/Catalogs.js'
|
|
14
14
|
import * as Contract from '../compiler/internal/Contract.js'
|
|
15
15
|
import * as Vite from '../vite/index.js'
|
|
16
|
+
import * as ThemeRules from '../web/internal/Themes.js'
|
|
16
17
|
|
|
17
18
|
/** Portable build configuration. Vite uses the options from `zyzz/vite` instead. */
|
|
18
19
|
export type Options = {
|
|
@@ -284,7 +285,9 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
// Only the combined stylesheet is emitted, so generated theme rules are shared once.
|
|
287
289
|
const result = compiler.compile({
|
|
290
|
+
[ThemeRules.shared]: 'all',
|
|
288
291
|
[Syntax.cache]: snapshot.programs(modules),
|
|
289
292
|
compiler: options.compiler,
|
|
290
293
|
reset: options.reset ? Reset.read() : undefined,
|
|
@@ -460,7 +463,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
460
463
|
let line = 0
|
|
461
464
|
css = ''
|
|
462
465
|
for (const chunk of chunks) {
|
|
463
|
-
if (!chunk.code) continue
|
|
466
|
+
if (!chunk.code.trim()) continue
|
|
464
467
|
const traced = Mapping.fromMap(chunk.map)
|
|
465
468
|
for (const mapping of Mapping.allMappings(traced)) {
|
|
466
469
|
const generated = {
|
package/src/variants.ts
CHANGED
|
@@ -26,8 +26,9 @@ type ChoiceName<choices> = keyof choices extends infer key
|
|
|
26
26
|
? key
|
|
27
27
|
: never
|
|
28
28
|
: never
|
|
29
|
+
/** `void` targets skip dynamic choice return types, which resolve through `Checked` and cycle when selections are read first. */
|
|
29
30
|
type DynamicKeys<choices> = {
|
|
30
|
-
[key in keyof choices]: choices[key] extends (...args: never[]) =>
|
|
31
|
+
[key in keyof choices]: choices[key] extends (...args: never[]) => void
|
|
31
32
|
? key
|
|
32
33
|
: never
|
|
33
34
|
}[keyof choices]
|
|
@@ -36,7 +37,7 @@ type Choice<choices> =
|
|
|
36
37
|
| {
|
|
37
38
|
[key in DynamicKeys<choices>]: choices[key] extends (
|
|
38
39
|
values: infer values,
|
|
39
|
-
) =>
|
|
40
|
+
) => void
|
|
40
41
|
? { readonly [name in key]: values } & {
|
|
41
42
|
readonly [name in Exclude<keyof choices, key>]?: never
|
|
42
43
|
}
|