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.
Files changed (96) hide show
  1. package/README.md +6 -6
  2. package/dist/babel/NativeJsx.d.ts.map +1 -1
  3. package/dist/babel/NativeJsx.js +14 -4
  4. package/dist/babel/NativeJsx.js.map +1 -1
  5. package/dist/compiler/Graph.js +78 -35
  6. package/dist/compiler/Graph.js.map +1 -1
  7. package/dist/compiler/internal/Dynamic.d.ts.map +1 -1
  8. package/dist/compiler/internal/Dynamic.js +3 -0
  9. package/dist/compiler/internal/Dynamic.js.map +1 -1
  10. package/dist/compiler/internal/Expression.js +2 -2
  11. package/dist/compiler/internal/Expression.js.map +1 -1
  12. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  13. package/dist/compiler/internal/NativeBindings.js +1 -0
  14. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  15. package/dist/compiler/internal/NativeVars.d.ts.map +1 -1
  16. package/dist/compiler/internal/NativeVars.js +3 -0
  17. package/dist/compiler/internal/NativeVars.js.map +1 -1
  18. package/dist/compiler/internal/Themes.d.ts +2 -0
  19. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  20. package/dist/compiler/internal/Themes.js +24 -22
  21. package/dist/compiler/internal/Themes.js.map +1 -1
  22. package/dist/internal/Configuration.d.ts.map +1 -1
  23. package/dist/internal/Configuration.js +2 -1
  24. package/dist/internal/Configuration.js.map +1 -1
  25. package/dist/metro/Graph.d.ts.map +1 -1
  26. package/dist/metro/Graph.js +11 -2
  27. package/dist/metro/Graph.js.map +1 -1
  28. package/dist/node/Host.d.ts.map +1 -1
  29. package/dist/node/Host.js +40 -12
  30. package/dist/node/Host.js.map +1 -1
  31. package/dist/react-native/StyleSheet.d.ts +7 -3
  32. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  33. package/dist/react-native/StyleSheet.js +8 -0
  34. package/dist/react-native/StyleSheet.js.map +1 -1
  35. package/dist/react-native/internal/Device.js +118 -38
  36. package/dist/react-native/internal/Device.js.map +3 -3
  37. package/dist/react-native/internal/NativeZyzz.js.map +2 -2
  38. package/dist/react-native/internal/Scalar.d.ts +16 -0
  39. package/dist/react-native/internal/Scalar.d.ts.map +1 -1
  40. package/dist/react-native/internal/Scalar.js +53 -2
  41. package/dist/react-native/internal/Scalar.js.map +1 -1
  42. package/dist/react-native/internal/Store.d.ts +4 -0
  43. package/dist/react-native/internal/Store.d.ts.map +1 -1
  44. package/dist/react-native/internal/Store.js.map +1 -1
  45. package/dist/react-native/internal/Styles.d.ts +19 -0
  46. package/dist/react-native/internal/Styles.d.ts.map +1 -0
  47. package/dist/react-native/internal/Styles.js +35 -0
  48. package/dist/react-native/internal/Styles.js.map +1 -0
  49. package/dist/react-native/internal/Viewport.d.ts +6 -0
  50. package/dist/react-native/internal/Viewport.d.ts.map +1 -1
  51. package/dist/react-native/internal/Viewport.js +2 -0
  52. package/dist/react-native/internal/Viewport.js.map +1 -1
  53. package/dist/react-native/react.d.ts +44 -16
  54. package/dist/react-native/react.d.ts.map +1 -1
  55. package/dist/react-native/react.js +68 -47
  56. package/dist/react-native/react.js.map +1 -1
  57. package/dist/runtime/NativeContext.d.ts +6 -0
  58. package/dist/runtime/NativeContext.d.ts.map +1 -1
  59. package/dist/runtime/NativeContext.js +62 -9
  60. package/dist/runtime/NativeContext.js.map +1 -1
  61. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  62. package/dist/runtime/NativeDynamic.js +5 -0
  63. package/dist/runtime/NativeDynamic.js.map +1 -1
  64. package/dist/unplugin/index.d.ts.map +1 -1
  65. package/dist/unplugin/index.js +4 -1
  66. package/dist/unplugin/index.js.map +1 -1
  67. package/dist/variants.d.ts +3 -2
  68. package/dist/variants.d.ts.map +1 -1
  69. package/dist/variants.js.map +1 -1
  70. package/native/NativeZyzz.cpp +52 -65
  71. package/native/NativeZyzz.h +0 -4
  72. package/native/ZyzzSpecJSI.h +0 -10
  73. package/native/android/ZyzzSpec-generated.cpp +0 -6
  74. package/package.json +1 -1
  75. package/src/babel/NativeJsx.ts +17 -8
  76. package/src/compiler/Graph.ts +89 -38
  77. package/src/compiler/internal/Dynamic.ts +4 -0
  78. package/src/compiler/internal/Expression.ts +2 -2
  79. package/src/compiler/internal/NativeBindings.ts +1 -0
  80. package/src/compiler/internal/NativeVars.ts +3 -0
  81. package/src/compiler/internal/Themes.ts +29 -23
  82. package/src/internal/Configuration.ts +2 -1
  83. package/src/metro/Graph.ts +17 -2
  84. package/src/node/Host.ts +64 -17
  85. package/src/react-native/StyleSheet.ts +32 -9
  86. package/src/react-native/internal/Device.ts +196 -49
  87. package/src/react-native/internal/NativeZyzz.ts +4 -6
  88. package/src/react-native/internal/Scalar.ts +73 -2
  89. package/src/react-native/internal/Store.ts +4 -0
  90. package/src/react-native/internal/Styles.ts +77 -0
  91. package/src/react-native/internal/Viewport.ts +7 -0
  92. package/src/react-native/react.ts +142 -72
  93. package/src/runtime/NativeContext.ts +83 -9
  94. package/src/runtime/NativeDynamic.ts +7 -0
  95. package/src/unplugin/index.ts +4 -1
  96. 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
- ...config,
43
+ get appearance() {
44
+ return create().appearance
45
+ },
38
46
  Provider: BoundProvider,
39
- }) as defineConfig.ReturnType<options>
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' && props.colorScheme !== 'dark') ||
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 resolved light/dark scheme and a nonempty vars name.',
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({ colorScheme: props.colorScheme, set: props.vars, viewport }),
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.colorScheme !== props.colorScheme ||
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({ colorScheme: props.colorScheme, set: props.vars, viewport })
159
+ store.update({ adaptive, colorScheme, set: props.vars, viewport })
97
160
  publish()
98
- }, [children, pending, props.colorScheme, props.vars, store, viewport])
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
- /** Resolved device scheme or an application override. */
110
- readonly colorScheme: 'dark' | 'light'
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
- /** Compiler-inserted subscription for resolving native style applications. */
117
- export function useStyles() {
118
- const store = React.useContext(Store.context) ?? unbound
119
- const value = React.useSyncExternalStore(
120
- store.subscribe,
121
- store.getSnapshot,
122
- store.getSnapshot,
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.useMemo(() => styles(value), [value])
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, class, or ref-forwarding component receiving native styles.
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 preserving the original props and ref.
178
- * @throws For invalid style prop names or unresolved native selections.
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: withStyles.Options<component> = {},
183
- ) {
184
- const additional = options.styleProps ?? []
185
- if (
186
- !Array.isArray(additional) ||
187
- additional.some(
188
- (name) =>
189
- typeof name !== 'string' || !name || name === 'key' || name === 'ref',
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
- return table[context.colorScheme](input!).style
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
- ...key(value, context),
99
- ...key(input, context),
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
- ...read(context),
130
- ...key(input, context),
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[
@@ -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[]) => unknown
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
- ) => unknown
40
+ ) => void
40
41
  ? { readonly [name in key]: values } & {
41
42
  readonly [name in Exclude<keyof choices, key>]?: never
42
43
  }