zyzz 0.0.27 → 0.0.28

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 (45) hide show
  1. package/dist/compiler/Source.d.ts.map +1 -1
  2. package/dist/compiler/Source.js +165 -11
  3. package/dist/compiler/Source.js.map +1 -1
  4. package/dist/compiler/Transform.d.ts.map +1 -1
  5. package/dist/compiler/Transform.js +3 -0
  6. package/dist/compiler/Transform.js.map +1 -1
  7. package/dist/compiler/internal/Applications.d.ts.map +1 -1
  8. package/dist/compiler/internal/Applications.js +42 -1
  9. package/dist/compiler/internal/Applications.js.map +1 -1
  10. package/dist/compiler/internal/Selectors.d.ts +3 -2
  11. package/dist/compiler/internal/Selectors.d.ts.map +1 -1
  12. package/dist/compiler/internal/Selectors.js +23 -11
  13. package/dist/compiler/internal/Selectors.js.map +1 -1
  14. package/dist/internal/Authoring.d.ts.map +1 -1
  15. package/dist/internal/Authoring.js +37 -7
  16. package/dist/internal/Authoring.js.map +1 -1
  17. package/dist/internal/Configuration.d.ts +6 -1
  18. package/dist/internal/Configuration.d.ts.map +1 -1
  19. package/dist/internal/Configuration.js.map +1 -1
  20. package/dist/internal/Literal.d.ts +1 -1
  21. package/dist/internal/Literal.d.ts.map +1 -1
  22. package/dist/internal/Literal.js +3 -0
  23. package/dist/internal/Literal.js.map +1 -1
  24. package/dist/internal/Theme.d.ts +4 -0
  25. package/dist/internal/Theme.d.ts.map +1 -1
  26. package/dist/internal/Theme.js.map +1 -1
  27. package/dist/styleFunction.d.ts +11 -2
  28. package/dist/styleFunction.d.ts.map +1 -1
  29. package/dist/styleFunction.js +3 -1
  30. package/dist/styleFunction.js.map +1 -1
  31. package/dist/vite/index.d.ts +7 -0
  32. package/dist/vite/index.d.ts.map +1 -1
  33. package/dist/vite/index.js +173 -22
  34. package/dist/vite/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/compiler/Source.ts +237 -13
  37. package/src/compiler/Transform.ts +4 -1
  38. package/src/compiler/internal/Applications.ts +57 -1
  39. package/src/compiler/internal/Selectors.ts +25 -11
  40. package/src/internal/Authoring.ts +53 -18
  41. package/src/internal/Configuration.ts +38 -1
  42. package/src/internal/Literal.ts +3 -0
  43. package/src/internal/Theme.ts +15 -0
  44. package/src/styleFunction.ts +31 -2
  45. package/src/vite/index.ts +242 -27
@@ -1,6 +1,7 @@
1
1
  /** Finds nonescaping local static callables whose applications can become props. @module */
2
2
  import type * as Ast from '@oxc-project/types'
3
3
  import * as Walker from 'oxc-walker'
4
+ import * as Expression from './Expression.js'
4
5
  import * as Scope from './Scope.js'
5
6
  import type * as Source from '../Source.js'
6
7
 
@@ -158,6 +159,8 @@ export function create(
158
159
  if (!candidates.length) return undefined
159
160
 
160
161
  const lexical = new Set<Ast.Node>()
162
+ // Extraction resolves these reads and rewrites their definitions, so none run.
163
+ const resolved = new Set<Ast.Node>()
161
164
  // Reads inside functions, classes, or namespaces may run before initialization.
162
165
  const deferred = new Set<Ast.Node>()
163
166
  const terminals = new Set<Ast.Node>()
@@ -200,6 +203,7 @@ export function create(
200
203
  ) {
201
204
  lexical.add(node)
202
205
  if (terminal(ancestors)) terminals.add(node)
206
+ if (extracted(ancestors, definitions)) resolved.add(node)
203
207
  if (
204
208
  ancestors.some((node) =>
205
209
  [
@@ -240,7 +244,7 @@ export function create(
240
244
  const called = new Set<string>()
241
245
 
242
246
  for (const reference of references.get(declaration.id.name) ?? []) {
243
- if (reference === declaration.id) continue
247
+ if (reference === declaration.id || resolved.has(reference)) continue
244
248
 
245
249
  let callee: Ast.Node = reference
246
250
  let call = direct
@@ -492,6 +496,58 @@ export declare namespace create {
492
496
  }
493
497
  }
494
498
 
499
+ // Matches a selector interpolation or composition entry inside a definition, which
500
+ // extraction resolves statically. Computed keys elsewhere in a definition fail extraction.
501
+ function extracted(
502
+ path: readonly Ast.Node[],
503
+ definitions: ReadonlyMap<number, Source.Call>,
504
+ ): boolean {
505
+ let index = path.length - 1
506
+ const member = path[index - 1]
507
+ if (
508
+ member?.type === 'MemberExpression' &&
509
+ member.object === path[index] &&
510
+ !member.computed
511
+ )
512
+ index--
513
+
514
+ // Type assertions around a node keep the same read.
515
+ function parent() {
516
+ const node = path[index]!
517
+ while (index > 0 && Expression.unwrap(path[index - 1]!) === node) index--
518
+ return path[--index]
519
+ }
520
+
521
+ function definition(node: Ast.Node | undefined) {
522
+ return (
523
+ node?.type === 'CallExpression' &&
524
+ definitions.get(node.start)?.end === node.end
525
+ )
526
+ }
527
+
528
+ const container = parent()
529
+
530
+ if (container?.type === 'ArrayExpression') {
531
+ const call = parent()
532
+ return (
533
+ call?.type === 'CallExpression' &&
534
+ definition(call) &&
535
+ !!call.arguments[0] &&
536
+ Expression.unwrap(call.arguments[0]) === container
537
+ )
538
+ }
539
+
540
+ if (container?.type !== 'TemplateLiteral') return false
541
+
542
+ const property = parent()
543
+ return (
544
+ property?.type === 'Property' &&
545
+ property.computed &&
546
+ Expression.unwrap(property.key) === container &&
547
+ path.slice(0, index).some(definition)
548
+ )
549
+ }
550
+
495
551
  // Matches a no-argument application spread onto, or read as the class of, a DOM element.
496
552
  function terminal(path: readonly Ast.Node[]): boolean {
497
553
  let index = path.length - 1
@@ -11,7 +11,7 @@ import * as Expression from './Expression.js'
11
11
  import type * as Scope from './Scope.js'
12
12
  import * as Themes from './Themes.js'
13
13
 
14
- /** Collects scoped selectors, local identities, and exported style references. */
14
+ /** Collects scoped selectors, local identities, exported style references, and composed definitions. */
15
15
  export function scan(
16
16
  program: Ast.Program,
17
17
  scope: Scope.Tracker,
@@ -21,6 +21,7 @@ export function scan(
21
21
  selectorKeys: ReadonlySet<number> = new Set(),
22
22
  cssNamespaces: ReadonlyMap<number, string> = new Map(),
23
23
  moduleId = namespace,
24
+ composed: readonly Ast.Node[] = [],
24
25
  ) {
25
26
  const authoredNames = Names.collect(program)
26
27
  const conditions = new Map<number, string>()
@@ -33,6 +34,7 @@ export function scan(
33
34
  const namespaces = new Map<number, Map<string, Ast.Expression>>()
34
35
  const importedNames = new Map<string, Themes.Link>()
35
36
  const imported = new Map<number, Themes.Link>()
37
+ const compositions = new Map<number, Ast.CallExpression | Themes.Link>()
36
38
 
37
39
  const names = new Map<string, Ast.Expression>()
38
40
  const publicNames = new Map<string, string>()
@@ -122,7 +124,7 @@ export function scan(
122
124
  }
123
125
 
124
126
  if (!calls.length && !imported.size)
125
- return { conditions, exports, identities, localConditions }
127
+ return { compositions, conditions, exports, identities, localConditions }
126
128
 
127
129
  const exposed = new Map<Ast.Expression, readonly (readonly string[])[]>()
128
130
  for (const [name, local] of publicNames) {
@@ -138,8 +140,8 @@ export function scan(
138
140
  for (const [member, value] of values)
139
141
  exposed.set(value, [...(exposed.get(value) ?? []), [name, member]])
140
142
 
141
- if (!selectorKeys.size && !exposed.size)
142
- return { conditions, exports, identities, localConditions }
143
+ if (!selectorKeys.size && !exposed.size && !composed.length)
144
+ return { compositions, conditions, exports, identities, localConditions }
143
145
 
144
146
  type Entry = { node: Ast.Node; parent: Ast.Node | null | undefined }
145
147
  const nodes: Entry[] = []
@@ -179,10 +181,11 @@ export function scan(
179
181
  }
180
182
  }
181
183
 
182
- function resolve(
184
+ // Composition reads the definition itself, so only selector references claim an identity.
185
+ function target(
183
186
  node: Ast.Node,
184
187
  seen = new Set<Ast.Node>(),
185
- ): Themes.Link | undefined {
188
+ ): Ast.CallExpression | Themes.Link | undefined {
186
189
  node = Expression.unwrap(node)
187
190
  if (seen.has(node)) return undefined
188
191
  seen.add(node)
@@ -191,7 +194,7 @@ export function scan(
191
194
  node.type === 'CallExpression' &&
192
195
  definitions.get(node.start)?.end === node.end
193
196
  )
194
- return link(node)
197
+ return node
195
198
 
196
199
  if (node.type === 'Identifier') {
197
200
  const declaration = declarations.get(node.start)
@@ -199,7 +202,7 @@ export function scan(
199
202
  const external = imported.get(declaration)
200
203
  if (external) return external
201
204
  const value = bindings.get(declaration)
202
- return value ? resolve(value, seen) : undefined
205
+ return value ? target(value, seen) : undefined
203
206
  }
204
207
 
205
208
  if (node.type === 'MemberExpression' && !node.optional) {
@@ -221,14 +224,25 @@ export function scan(
221
224
  declaration === undefined
222
225
  ? undefined
223
226
  : namespaces.get(declaration)?.get(name)
224
- if (value) return resolve(value, seen)
227
+ if (value) return target(value, seen)
225
228
  }
226
- return resolve(node.object, seen)?.members?.[name]
229
+ const object = target(node.object, seen)
230
+ return object && !('type' in object) ? object.members?.[name] : undefined
227
231
  }
228
232
 
229
233
  return undefined
230
234
  }
231
235
 
236
+ function resolve(node: Ast.Node): Themes.Link | undefined {
237
+ const value = target(node)
238
+ return value && 'type' in value ? link(value) : value
239
+ }
240
+
241
+ for (const node of composed) {
242
+ const value = target(node)
243
+ if (value) compositions.set(node.start, value)
244
+ }
245
+
232
246
  for (const { node } of nodes) {
233
247
  if (node.type === 'VariableDeclarator' && node.init)
234
248
  for (const [key, member] of exposed.get(node.init) ?? []) {
@@ -335,5 +349,5 @@ export function scan(
335
349
  return [...imported.values()].some(contains)
336
350
  }
337
351
 
338
- return { conditions, exports, identities, localConditions }
352
+ return { compositions, conditions, exports, identities, localConditions }
339
353
  }
@@ -10,6 +10,10 @@ import type * as Theme from './Theme.js'
10
10
  /** Private ownership used by uncompiled composition; never spread onto DOM props. */
11
11
  export const metadata = Symbol.for('zyzz.authoring')
12
12
 
13
+ // Static definitions retain their style data so later compositions can read it,
14
+ // including definitions created by another bundled copy of the runtime.
15
+ const data = Symbol.for('zyzz.style')
16
+
13
17
  /** Identity and replaceable attribute/slot ownership for an applied definition. */
14
18
  export type Owner = {
15
19
  readonly name: string
@@ -77,24 +81,54 @@ export function create(
77
81
  if (!dynamic && !id && Object.keys(input as object).length === 0)
78
82
  Identity.requireId(undefined, 'Empty style')
79
83
 
80
- if (id && !dynamic && options.theme)
81
- Style.define({ style: body(input as Record<string, unknown>) } as never, {
82
- vars: options.theme,
83
- })
84
- const className = id
85
- ? `z-style-${id}`
86
- : Identity.style(
87
- (
88
- Style.define as (
89
- input: Record<string, unknown>,
90
- options: Style.define.Options,
91
- ) => Style.Definition
92
- )(
93
- { style: body(input as Record<string, unknown>) },
94
- { vars: options.theme },
95
- ).styles[0]!,
96
- namespace,
97
- )
84
+ const define = (input: unknown) =>
85
+ (
86
+ Style.define as (
87
+ input: Record<string, unknown>,
88
+ options: Style.define.Options,
89
+ ) => Style.Definition
90
+ )(
91
+ { style: body(input as Record<string, unknown>) },
92
+ { vars: options.theme },
93
+ ).styles[0]!
94
+
95
+ const style = (() => {
96
+ if (dynamic) return undefined
97
+ if (!Array.isArray(input)) return define(input)
98
+
99
+ // Mirrors compiled composition: each entry is one ordered declaration segment.
100
+ const rules: Style.Rule[] = []
101
+ for (const [index, entry] of (input as readonly unknown[]).entries()) {
102
+ if (typeof entry !== 'function') {
103
+ rules.push({ style: define(entry) })
104
+ continue
105
+ }
106
+
107
+ const style = Object.getOwnPropertyDescriptor(entry, data)?.value as
108
+ | Style.NamedStyle
109
+ | undefined
110
+ if (style) {
111
+ rules.push({ style })
112
+ continue
113
+ }
114
+
115
+ // Compiled callables carry only class names, so an explicit id names the composite.
116
+ if (id) return undefined
117
+
118
+ throw new Style.InvalidError([
119
+ {
120
+ code: 'invalid_structure',
121
+ message:
122
+ 'Composition entries require literal objects or static style definitions. Compositions of compiled definitions require an explicit id, and variants and dynamic styles are applied with cx.',
123
+ path: [String(index)],
124
+ },
125
+ ])
126
+ }
127
+
128
+ return { name: 'style', declarations: [], rules } satisfies Style.NamedStyle
129
+ })()
130
+
131
+ const className = id ? `z-style-${id}` : Identity.style(style!, namespace)
98
132
  const props = Props.create({ className })
99
133
  const apply = (values?: style.Options & Record<string, unknown>) => {
100
134
  let result = props(values as style.Options)
@@ -124,6 +158,7 @@ export function create(
124
158
  Object.defineProperty(apply, Symbol.toPrimitive, {
125
159
  value: () => `.${className}`,
126
160
  })
161
+ if (style) Object.defineProperty(apply, data, { value: style })
127
162
  return apply as style.ReturnType
128
163
  }
129
164
 
@@ -625,15 +625,52 @@ export type StyleFactory<
625
625
  ? [options?: style.DefinitionOptions]
626
626
  : [invalid: never, unavailable: never]
627
627
  : [invalid: never, unavailable: never]
628
- ): style.Dynamic<values, output>
628
+ // TypeScript types a call that matches no overload with this signature. Other
629
+ // inputs leave `callback` at its constraint, so their definitions stay static.
630
+ ): ((...args: never[]) => unknown) extends callback
631
+ ? style.ReturnType<output>
632
+ : style.Dynamic<values, output>
629
633
  <const styles extends Record<string, unknown>>(
630
634
  styles: styles &
631
635
  NoInfer<Body<styles, tokens, layers, mappings>> &
632
636
  Completion.Properties<tokens>,
633
637
  options?: style.DefinitionOptions,
634
638
  ): style.ReturnType<output>
639
+ <const entries>(
640
+ styles: entries & Entries<entries, tokens, layers, mappings>,
641
+ ...options: entries extends readonly unknown[]
642
+ ? [options?: style.DefinitionOptions]
643
+ : [invalid: never, unavailable: never]
644
+ ): style.ReturnType<output>
635
645
  }
636
646
 
647
+ // Composition entries are static definitions or checked literal objects.
648
+ type Entries<
649
+ entries,
650
+ tokens extends Theme.Tokens,
651
+ layers extends string,
652
+ mappings extends Shorthands.Map,
653
+ > = {
654
+ readonly [index in keyof entries]: Entry<
655
+ entries[index],
656
+ tokens,
657
+ layers,
658
+ mappings
659
+ >
660
+ }
661
+
662
+ // Distributes over mixed element unions so each member keeps its own contract.
663
+ type Entry<
664
+ entry,
665
+ tokens extends Theme.Tokens,
666
+ layers extends string,
667
+ mappings extends Shorthands.Map,
668
+ > = entry extends (...args: never) => unknown
669
+ ? style.Composable<entry>
670
+ : entry &
671
+ NoInfer<Body<entry, tokens, layers, mappings>> &
672
+ Completion.Properties<tokens>
673
+
637
674
  type Keys<styles> = styles extends unknown ? keyof styles : never
638
675
 
639
676
  /** Checked declaration body shared by configured styles and recipe choices. */
@@ -1588,7 +1588,10 @@ const properties = {
1588
1588
  direction: { kind: 'enum', values: ['ltr', 'rtl'] },
1589
1589
  display: {
1590
1590
  kind: 'enum',
1591
+ // The Compat Standard keeps the legacy box keywords that -webkit-line-clamp requires.
1591
1592
  values: [
1593
+ '-webkit-box',
1594
+ '-webkit-inline-box',
1592
1595
  'block',
1593
1596
  'block flex',
1594
1597
  'block flow',
@@ -38,6 +38,14 @@ export type StyleFactory<tokens extends Tokens> = {
38
38
  (values extends Binding.Inputs<values> ? unknown : never),
39
39
  options?: style.DefinitionOptions,
40
40
  ): style.Dynamic<values>
41
+ <const entries>(
42
+ styles: entries & {
43
+ readonly [index in keyof entries]: Entry<entries[index], tokens>
44
+ },
45
+ ...options: entries extends readonly unknown[]
46
+ ? [options?: style.DefinitionOptions]
47
+ : [invalid: never, unavailable: never]
48
+ ): style.ReturnType
41
49
  <const styles extends Record<string, unknown>>(
42
50
  styles: styles & NoInfer<Style.Accepted<styles, tokens>>,
43
51
  options?: style.DefinitionOptions,
@@ -56,6 +64,13 @@ export type StyleFactory<tokens extends Tokens> = {
56
64
  ): style.ReturnType
57
65
  }
58
66
 
67
+ // Distributes over mixed element unions so each member keeps its own contract.
68
+ type Entry<entry, tokens extends Tokens> = entry extends (
69
+ ...args: never
70
+ ) => unknown
71
+ ? style.Composable<entry>
72
+ : entry & NoInfer<Style.Accepted<entry, tokens>>
73
+
59
74
  /**
60
75
  * Defines scalar tokens and typography sets without defaults or environment access.
61
76
  * Colors currently use the supported literal color grammar; spacing and radius
@@ -29,9 +29,21 @@ type VariableOptions<input> = input extends {
29
29
  }
30
30
  : unknown
31
31
 
32
+ // Composition entries are static definitions or exact literal objects.
33
+ type Entries<entries> = {
34
+ readonly [index in keyof entries]: Entry<entries[index]>
35
+ }
36
+
37
+ // Distributes over mixed element unions so each member keeps its own contract.
38
+ type Entry<entry> = entry extends (...args: never) => unknown
39
+ ? style.Composable<entry>
40
+ : entry & NoInfer<Style.Accepted<entry, {}, true>> & Completion.Properties
41
+
32
42
  /**
33
43
  * Declares literal styles for source extraction. Requires a compile-time transform.
34
- * @param styles - Token-free literal CSS properties. Omit for an empty definition.
44
+ * An array composes static definitions and literal objects in order, so later entries
45
+ * win as later declarations do.
46
+ * @param styles - Token-free literal CSS properties, or an array of static definitions and literal objects. Omit for an empty definition.
35
47
  * @returns A callable style definition after source rewriting.
36
48
  * @throws {Error} When a definition needs an explicit identity and none is supplied.
37
49
  */
@@ -53,16 +65,29 @@ export function style<
53
65
  ? [options?: style.DefinitionOptions]
54
66
  : [invalid: never, unavailable: never]
55
67
  : [invalid: never, unavailable: never]
56
- ): style.Dynamic<values>
68
+ // TypeScript types a call that matches no overload with this signature. Other
69
+ // inputs leave `callback` at its constraint, so their definitions stay static.
70
+ ): ((...args: never[]) => unknown) extends callback
71
+ ? style.ReturnType
72
+ : style.Dynamic<values>
57
73
  export function style<const styles extends Record<string, unknown>>(
58
74
  styles: styles &
59
75
  NoInfer<Style.Accepted<styles, {}, true>> &
60
76
  Completion.Properties,
61
77
  options?: style.DefinitionOptions,
62
78
  ): style.ReturnType
79
+ export function style<const entries>(
80
+ styles: entries & Entries<entries>,
81
+ // Exclude non-array inputs by arity so they cannot mask object-property diagnostics.
82
+ ...options: entries extends readonly unknown[]
83
+ ? [options?: style.DefinitionOptions]
84
+ : [invalid: never, unavailable: never]
85
+ ): style.ReturnType
63
86
  export function style(
64
87
  styles?: unknown,
65
88
  options: style.DefinitionOptions = {},
89
+ // Matches the arity-gated overloads, whose invalid branches add parameters.
90
+ ..._: readonly never[]
66
91
  ): style.ReturnType {
67
92
  return Authoring.create(styles, options)
68
93
  }
@@ -73,6 +98,10 @@ type Reference = { readonly [identity]: true }
73
98
 
74
99
  /** Contracts for the literal authoring boundary. */
75
100
  export declare namespace style {
101
+ /** Accepts a static definition as a composition entry. Dynamic definitions and variants require cx. */
102
+ type Composable<entry> = entry extends Reference & (() => unknown)
103
+ ? entry
104
+ : ReturnType<Output>
76
105
  /** Stable identity for selectors and definitions that cannot be content-addressed. */
77
106
  type DefinitionOptions = { readonly id?: string | undefined }
78
107
  /** Callable compiled bindings with required scalar inputs and styling overrides. */