zyzz 0.0.6 → 0.0.7

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 (48) hide show
  1. package/README.md +26 -1
  2. package/dist/Style.d.ts +3 -3
  3. package/dist/Style.d.ts.map +1 -1
  4. package/dist/Style.js +12 -1
  5. package/dist/Style.js.map +1 -1
  6. package/dist/internal/Authoring.d.ts.map +1 -1
  7. package/dist/internal/Authoring.js +14 -0
  8. package/dist/internal/Authoring.js.map +1 -1
  9. package/dist/internal/Binding.d.ts +7 -0
  10. package/dist/internal/Binding.d.ts.map +1 -1
  11. package/dist/internal/Binding.js.map +1 -1
  12. package/dist/internal/Configuration.d.ts +1 -1
  13. package/dist/internal/Configuration.d.ts.map +1 -1
  14. package/dist/internal/Configuration.js +7 -6
  15. package/dist/internal/Configuration.js.map +1 -1
  16. package/dist/internal/Theme.d.ts +1 -1
  17. package/dist/internal/Theme.d.ts.map +1 -1
  18. package/dist/internal/Theme.js.map +1 -1
  19. package/dist/internal/Token.d.ts +3 -1
  20. package/dist/internal/Token.d.ts.map +1 -1
  21. package/dist/internal/Token.js +28 -3
  22. package/dist/internal/Token.js.map +1 -1
  23. package/dist/internal/Value.d.ts +22 -0
  24. package/dist/internal/Value.d.ts.map +1 -1
  25. package/dist/internal/Value.js +76 -9
  26. package/dist/internal/Value.js.map +1 -1
  27. package/dist/next/internal/Project.d.ts +28 -0
  28. package/dist/next/internal/Project.d.ts.map +1 -0
  29. package/dist/next/internal/Project.js +249 -0
  30. package/dist/next/internal/Project.js.map +1 -0
  31. package/dist/next/loader.d.ts +2 -5
  32. package/dist/next/loader.d.ts.map +1 -1
  33. package/dist/next/loader.js +11 -150
  34. package/dist/next/loader.js.map +1 -1
  35. package/dist/variants.d.ts +1 -1
  36. package/dist/variants.d.ts.map +1 -1
  37. package/dist/variants.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/Style.ts +42 -8
  40. package/src/internal/Authoring.ts +21 -0
  41. package/src/internal/Binding.ts +30 -0
  42. package/src/internal/Configuration.ts +19 -13
  43. package/src/internal/Theme.ts +5 -1
  44. package/src/internal/Token.ts +34 -3
  45. package/src/internal/Value.ts +180 -10
  46. package/src/next/internal/Project.ts +337 -0
  47. package/src/next/loader.ts +19 -196
  48. package/src/variants.ts +1 -0
package/src/Style.ts CHANGED
@@ -68,13 +68,20 @@ export type Accepted<
68
68
  ? never
69
69
  : Value.Atom<Typography.Names<tokens>>
70
70
  : key extends keyof Literal.Properties
71
- ? Value.Accepted<
72
- Pick<style, key>,
73
- literal extends true
71
+ ? (literal extends true
72
+ ? LiteralDeclarations
73
+ : keyof tokens extends never
74
74
  ? LiteralDeclarations
75
- : DeclarationProperties<tokens>
76
- >[key] &
77
- Value.Checked<Pick<style, key>, tokens>[key]
75
+ : DeclarationProperties<tokens>)[key] extends style[key]
76
+ ? style[key]
77
+ : Value.Accepted<
78
+ Pick<style, key>,
79
+ literal extends true
80
+ ? LiteralDeclarations
81
+ : DeclarationProperties<tokens>
82
+ >[key] &
83
+ Value.Checked<Pick<style, key>, tokens>[key] &
84
+ Value.Tokens<style[key], tokens, key>
78
85
  : key extends Condition.Keys<tokens, key>
79
86
  ? [style[key]] extends [undefined]
80
87
  ? never
@@ -605,10 +612,11 @@ export function define(
605
612
  )
606
613
  continue
607
614
  }
615
+ const custom = parsed?.custom
608
616
  const scalar = parsed ? parsed.value : entry
609
617
 
610
618
  const resolved = (() => {
611
- if (!theme) return scalar
619
+ if (!theme || custom) return scalar
612
620
 
613
621
  if (typeof scalar !== 'string' && typeof scalar !== 'number')
614
622
  return scalar
@@ -645,6 +653,25 @@ export function define(
645
653
  continue
646
654
  }
647
655
 
656
+ if (
657
+ theme &&
658
+ !custom &&
659
+ Token.mapped(theme, key) &&
660
+ !Token.is(resolved) &&
661
+ !(Binding.is(resolved) && !resolved.name.startsWith('--z-d')) &&
662
+ !(
663
+ Token.isExpression(resolved) &&
664
+ resolved.parts.every((part) => Token.is(part) || part === '')
665
+ )
666
+ ) {
667
+ report(
668
+ 'invalid_value',
669
+ [name, authoredProperty],
670
+ 'Expected a configured token or a bracketed CSS value.',
671
+ )
672
+ continue
673
+ }
674
+
648
675
  const value = parsed && resolved === '0' ? 0 : resolved
649
676
 
650
677
  declarations.push(
@@ -774,6 +801,7 @@ export class InvalidError extends Error {
774
801
  type LiteralAtoms = {
775
802
  readonly [property in keyof Literal.Properties]-?: Value.Atom<
776
803
  | Exclude<Literal.Properties[property], undefined>
804
+ | `[${string}]`
777
805
  | Binding.Reference<'*'>
778
806
  | {
779
807
  [kind in Binding.Kind]: property extends Binding.Property<
@@ -812,7 +840,13 @@ export type NamedStyle<name extends string = string> = {
812
840
  /** Supported literal and token declarations. Unknown properties and undefined values are rejected. */
813
841
  export type DeclarationProperties<tokens extends Theme.Tokens = {}> = {
814
842
  readonly [property in keyof Literal.Properties]: Value.Fallbacks<
815
- | LiteralAtoms[property]
843
+ | (property extends `--${string}`
844
+ ? LiteralAtoms[property]
845
+ : [Token.Names<tokens, property>] extends [never]
846
+ ? LiteralAtoms[property]
847
+ :
848
+ | Value.Atom<`[${string}]`>
849
+ | Extract<LiteralAtoms[property], Binding.Reference>)
816
850
  | Value.Atom<Token.Names<tokens, property>>
817
851
  | {
818
852
  [group in Token.Group]: property extends Token.Properties<group>
@@ -74,6 +74,10 @@ export function create(
74
74
  if (!dynamic && !id && Object.keys(input as object).length === 0)
75
75
  Identity.requireId(undefined, 'Empty style')
76
76
 
77
+ if (id && !dynamic && options.theme)
78
+ Style.define({ style: body(input as Record<string, unknown>) } as never, {
79
+ vars: options.theme,
80
+ })
77
81
  const className = id
78
82
  ? `z-style-${id}`
79
83
  : Identity.style(
@@ -125,6 +129,23 @@ export function variants(
125
129
  options: Options = {},
126
130
  ): unknown {
127
131
  const id = Identity.requireId(options.id, 'variants')
132
+ if (options.theme) {
133
+ const styles = [
134
+ input.base,
135
+ ...Object.values(
136
+ (input.variants ?? {}) as Record<string, Record<string, unknown>>,
137
+ ).flatMap(Object.values),
138
+ ...((input.compoundVariants ?? []) as readonly { style: unknown }[]).map(
139
+ (entry) => entry.style,
140
+ ),
141
+ ]
142
+ for (const input of styles)
143
+ if (input && typeof input === 'object')
144
+ Style.define(
145
+ { style: body(input as Record<string, unknown>) } as never,
146
+ { vars: options.theme },
147
+ )
148
+ }
128
149
  const className = `z-style-${id}`
129
150
  const axes = Object.entries(
130
151
  (input.variants ?? {}) as Record<string, Record<string, unknown>>,
@@ -127,6 +127,36 @@ export type Checked<style> = {
127
127
  : unknown
128
128
  }
129
129
 
130
+ /** Excludes token-name slots, which cannot resolve token names during application. */
131
+ export type TokenSlots<style, values, tokens, mappings = {}> = {
132
+ [property in keyof style]: property extends keyof Literal.Properties
133
+ ? SlotValue<
134
+ style[property],
135
+ values[keyof values],
136
+ Token.Names<tokens, property>
137
+ >
138
+ : property extends keyof mappings
139
+ ? mappings[property] extends readonly (infer target extends
140
+ keyof Literal.Properties)[]
141
+ ? SlotValue<
142
+ style[property],
143
+ values[keyof values],
144
+ Token.Names<tokens, target>
145
+ >
146
+ : unknown
147
+ : property extends 'vars'
148
+ ? unknown
149
+ : style[property] extends Record<string, unknown>
150
+ ? TokenSlots<style[property], values, tokens, mappings>
151
+ : unknown
152
+ }
153
+
154
+ type SlotValue<value, inputs, names> = value extends readonly unknown[]
155
+ ? { [key in keyof value]: SlotValue<value[key], inputs, names> }
156
+ : Extract<Extract<value, inputs>, names> extends never
157
+ ? unknown
158
+ : never
159
+
130
160
  /** Rejects reserved callback field names and importance-bearing value domains. */
131
161
  export type Inputs<values> = {
132
162
  [key in keyof values]: key extends
@@ -277,14 +277,16 @@ export function create(
277
277
  })
278
278
  }
279
279
  }
280
- function boundVariants(
281
- definition: Record<string, unknown>,
282
- options: style.DefinitionOptions = {},
283
- ) {
284
- return Authoring.variants(definition, {
285
- ...options,
286
- output: input.output as style.Output | undefined,
287
- })
280
+ function boundVariants(theme?: Theme.Definition) {
281
+ return (
282
+ definition: Record<string, unknown>,
283
+ options: style.DefinitionOptions = {},
284
+ ) =>
285
+ Authoring.variants(definition, {
286
+ ...options,
287
+ output: input.output as style.Output | undefined,
288
+ theme,
289
+ })
288
290
  }
289
291
  function handle(theme: Theme.Definition, name: string) {
290
292
  const original = Token.bind(theme, contract)
@@ -313,7 +315,7 @@ export function create(
313
315
  })
314
316
  Object.assign(select, {
315
317
  style: boundStyle(original),
316
- variants: boundVariants,
318
+ variants: boundVariants(original),
317
319
  })
318
320
  return select
319
321
  }
@@ -410,7 +412,9 @@ export function create(
410
412
  ),
411
413
  theme: bound[input.defaultTheme],
412
414
  themes: Object.freeze(select),
413
- variants: boundVariants,
415
+ variants: boundVariants(
416
+ bound[input.defaultTheme] as unknown as Theme.Definition,
417
+ ),
414
418
  })
415
419
  }
416
420
 
@@ -424,7 +428,7 @@ export function create(
424
428
  script: Appearance.create([], { storageKey }),
425
429
  style: boundStyle(theme as unknown as Theme.Definition),
426
430
  theme,
427
- variants: boundVariants,
431
+ variants: boundVariants(theme as unknown as Theme.Definition),
428
432
  })
429
433
  }
430
434
 
@@ -436,7 +440,7 @@ export function create(
436
440
  appearance: Appearance.root([], { storageKey }),
437
441
  script: Appearance.create([], { storageKey }),
438
442
  style: boundStyle(theme),
439
- variants: boundVariants,
443
+ variants: boundVariants(theme),
440
444
  })
441
445
  }
442
446
 
@@ -590,7 +594,9 @@ export type StyleFactory<
590
594
  values: values,
591
595
  ) => styles &
592
596
  NoInfer<
593
- Body<styles, tokens, layers, mappings> & Binding.Checked<styles>
597
+ Body<styles, tokens, layers, mappings> &
598
+ Binding.Checked<styles> &
599
+ Binding.TokenSlots<styles, values, tokens, mappings>
594
600
  >) &
595
601
  Completion.Properties<tokens, NoInfer<styles>>,
596
602
  ...options: Parameters<callback> extends [Record<string, string | number>]
@@ -30,7 +30,11 @@ export type StyleFactory<tokens extends Tokens> = {
30
30
  styles: ((
31
31
  values: values,
32
32
  ) => styles &
33
- NoInfer<Style.Accepted<styles, tokens> & Binding.Checked<styles>>) &
33
+ NoInfer<
34
+ Style.Accepted<styles, tokens> &
35
+ Binding.Checked<styles> &
36
+ Binding.TokenSlots<styles, values, tokens>
37
+ >) &
34
38
  (values extends Binding.Inputs<values> ? unknown : never),
35
39
  options?: style.DefinitionOptions,
36
40
  ): style.Dynamic<values>
@@ -444,11 +444,11 @@ export type Reference<group extends Group = Group> = {
444
444
 
445
445
  const reference = Symbol('zyzz.token')
446
446
 
447
- /** Resolves shorthand tokens with literal precedence, with specific colors first. */
447
+ /** Resolves configured token names, with specific color groups first. */
448
448
  export function resolve(value: unknown, options: resolve.Options): unknown {
449
449
  if (
450
- (typeof value !== 'string' && typeof value !== 'number') ||
451
- Literal.isLiteral(options.property, value)
450
+ options.property.startsWith('--') ||
451
+ (typeof value !== 'string' && typeof value !== 'number')
452
452
  )
453
453
  return value
454
454
 
@@ -529,6 +529,37 @@ export function resolve(value: unknown, options: resolve.Options): unknown {
529
529
  return value
530
530
  }
531
531
 
532
+ /** Whether the configured catalog supplies tokens for a property. */
533
+ export function mapped(
534
+ theme: object,
535
+ property: keyof Literal.Properties,
536
+ ): boolean {
537
+ if (property.startsWith('--')) return false
538
+ const data = Object.getOwnPropertyDescriptor(theme, definition)
539
+ ?.value as Metadata
540
+ return Object.keys(data.values).some((path) => {
541
+ const category = path.split('.')[0]!
542
+ const targets =
543
+ data.contract.mappings === false
544
+ ? undefined
545
+ : data.contract.mappings?.[category]
546
+ if (
547
+ data.contract.mappings !== false &&
548
+ !(targets
549
+ ? targets.includes(property)
550
+ : accepts(category as Group, property))
551
+ )
552
+ return false
553
+ let leaf: unknown = Object.getOwnPropertyDescriptor(theme, 'tokens')?.value
554
+ for (const key of path.split('.'))
555
+ leaf =
556
+ leaf && typeof leaf === 'object'
557
+ ? Object.getOwnPropertyDescriptor(leaf, key)?.value
558
+ : undefined
559
+ return is(leaf) && acceptsReference(leaf, property)
560
+ })
561
+ }
562
+
532
563
  /** Input contract for theme token resolution. */
533
564
  export declare namespace resolve {
534
565
  /** Property domain and immutable theme metadata supplied by style validation. */
@@ -21,6 +21,60 @@ export type Atom<value> =
21
21
  * Checks authored properties and retains validated input types to avoid expanding whole-property fallback unions.
22
22
  */
23
23
  export type Accepted<style, properties> = {
24
+ [property in keyof style]: AcceptedValue<
25
+ style[property],
26
+ property,
27
+ properties
28
+ >
29
+ }
30
+
31
+ type AcceptedValue<value, property extends PropertyKey, properties> =
32
+ Extract<value, `[${string}]` | `[${string}] !important`> extends never
33
+ ? AcceptedMember<Record<property, value>, properties>[property]
34
+ : value extends unknown
35
+ ? AcceptedMember<Record<property, value>, properties>[property]
36
+ : never
37
+
38
+ type AcceptedMember<style, properties> = {
39
+ [property in keyof style]: property extends keyof properties
40
+ ? style[property] extends `[${infer value}]` | `[${infer value}] !important`
41
+ ?
42
+ | (property extends 'gridTemplateColumns' | 'gridTemplateRows'
43
+ ? style[property] extends LiteralAccepted<
44
+ Pick<style, property>,
45
+ Literal.Properties
46
+ >[property]
47
+ ? style[property]
48
+ : never
49
+ : never)
50
+ | (ScalarValue<value, property> extends LiteralAccepted<
51
+ Record<property, ScalarValue<value, property>>,
52
+ Literal.Properties
53
+ >[property]
54
+ ? style[property]
55
+ : never)
56
+ : style[property] extends readonly [unknown, ...unknown[]]
57
+ ? AcceptedArray<style[property], property, properties>
58
+ : LiteralAccepted<Pick<style, property>, properties>[property]
59
+ : never
60
+ }
61
+
62
+ type ScalarValue<value, property> =
63
+ value extends `${infer numeric extends number}`
64
+ ? property extends keyof Literal.Properties
65
+ ? number extends Literal.Properties[property]
66
+ ? numeric
67
+ : value
68
+ : value
69
+ : value
70
+
71
+ type AcceptedArray<values, property extends PropertyKey, properties> = {
72
+ [index in keyof values]: values[index] extends readonly unknown[]
73
+ ? never
74
+ : Accepted<Record<property, values[index]>, properties>[property]
75
+ }
76
+
77
+ type LiteralAccepted<style, properties> = {
24
78
  [property in keyof style]: property extends keyof properties
25
79
  ? style[property] extends { readonly [Token.scalar]: infer scalar }
26
80
  ? property extends keyof Literal.Properties
@@ -80,6 +134,22 @@ type Fold<value> = value extends string
80
134
 
81
135
  /** Refines concrete scalar spellings; already-broad property contracts need no literal refinement. */
82
136
  export type Checked<style, tokens = {}> = {
137
+ [property in keyof style]: style[property] extends
138
+ | `[${infer value}]`
139
+ | `[${infer value}] !important`
140
+ ?
141
+ | (property extends 'gridTemplateColumns' | 'gridTemplateRows'
142
+ ? LiteralChecked<Pick<style, property>>[property]
143
+ : never)
144
+ | LiteralChecked<
145
+ Record<property, ScalarValue<value, property>>
146
+ >[property]
147
+ : style[property] extends readonly [unknown, ...unknown[]]
148
+ ? CheckedArray<style[property], property, tokens>
149
+ : LiteralChecked<Pick<style, property>, tokens>[property]
150
+ }
151
+
152
+ type LiteralChecked<style, tokens = {}> = {
83
153
  [property in keyof style]: style[property] extends string & Binding.Reference
84
154
  ? style[property] extends Binding.Reference<'*'>
85
155
  ? unknown
@@ -121,6 +191,40 @@ export type Checked<style, tokens = {}> = {
121
191
  : unknown)
122
192
  }
123
193
 
194
+ type CheckedArray<values, property extends PropertyKey, tokens> = {
195
+ [index in keyof values]: Checked<
196
+ Record<property, values[index]>,
197
+ tokens
198
+ >[property]
199
+ }
200
+
201
+ /** Requires tokens for properties with configured values. */
202
+ export type Tokens<
203
+ value,
204
+ tokens,
205
+ property extends keyof Literal.Properties,
206
+ > = property extends `--${string}`
207
+ ? unknown
208
+ : [Token.Names<tokens, property>] extends [never]
209
+ ? unknown
210
+ : value extends readonly unknown[]
211
+ ? { [index in keyof value]: Tokens<value[index], tokens, property> }
212
+ : value extends
213
+ | Binding.Reference
214
+ | Token.Reference
215
+ | Token.Variable
216
+ | `${Token.Variable} !important`
217
+ | `[${string}]`
218
+ | `[${string}] !important`
219
+ ? unknown
220
+ : value extends Atom<Token.Names<tokens, property>>
221
+ ? unknown
222
+ : value extends number
223
+ ? `${value}` extends Token.Names<tokens, property>
224
+ ? unknown
225
+ : never
226
+ : never
227
+
124
228
  type Importance<value> = value extends readonly unknown[]
125
229
  ? { [key in keyof value]: Importance<value[key]> }
126
230
  : value extends `${infer body}!${infer suffix}`
@@ -263,7 +367,11 @@ export function parse(
263
367
  property: keyof Literal.Properties,
264
368
  ):
265
369
  | { invalid: true }
266
- | { important: boolean; value: number | string | Token.Expression }
370
+ | {
371
+ custom?: true
372
+ important: boolean
373
+ value: number | string | Token.Expression
374
+ }
267
375
  | undefined {
268
376
  if (Token.isExpression(input)) {
269
377
  const text = input.parts
@@ -275,6 +383,16 @@ export function parse(
275
383
 
276
384
  const parts = [...input.parts]
277
385
  let remaining = text.length - String(parsed.value).length
386
+ if (parsed.custom) {
387
+ for (let index = 0; index < parts.length; index++) {
388
+ const part = parts[index]
389
+ if (part === '') continue
390
+ if (typeof part !== 'string') return undefined
391
+ parts[index] = part.slice(1)
392
+ break
393
+ }
394
+ remaining--
395
+ }
278
396
 
279
397
  for (let index = parts.length - 1; remaining > 0 && index >= 0; index--) {
280
398
  const part = parts[index]
@@ -286,7 +404,11 @@ export function parse(
286
404
  remaining -= count
287
405
  }
288
406
 
289
- return { important: parsed.important, value: Token.compose(parts) }
407
+ return {
408
+ ...(parsed.custom ? { custom: true } : {}),
409
+ important: parsed.important,
410
+ value: Token.compose(parts.filter((part) => part !== '')),
411
+ }
290
412
  }
291
413
 
292
414
  if (typeof input !== 'string') return undefined
@@ -317,7 +439,15 @@ export function parse(
317
439
  marker = marker === 0 ? -1 : input.lastIndexOf('!', marker - 1)
318
440
  }
319
441
 
320
- if (marker < 0) return undefined
442
+ if (marker < 0) {
443
+ if (bracketed(input))
444
+ return {
445
+ custom: true,
446
+ important: false,
447
+ value: scalar(input.slice(1, -1)),
448
+ }
449
+ return undefined
450
+ }
321
451
  if (input.slice(marker - 1) !== ' !important' || escaped(marker - 1))
322
452
  return { invalid: true }
323
453
 
@@ -326,14 +456,54 @@ export function parse(
326
456
  while (end > 0 && /[ \t\n\r\f]/.test(input[end - 1]!) && !escaped(end - 1))
327
457
  end--
328
458
 
329
- const text = input.slice(0, end)
330
- const numeric =
331
- Literal.rule(property)?.kind === 'number' ||
332
- Literal.rule(property)?.kind === 'grid-line'
333
- const value =
334
- numeric && /^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(text)
459
+ const body = input.slice(0, end)
460
+ const custom = bracketed(body)
461
+ const text = custom ? body.slice(1, -1) : body
462
+ const value = scalar(text)
463
+
464
+ return {
465
+ ...(custom ? { custom: true } : {}),
466
+ important: true as const,
467
+ value,
468
+ }
469
+
470
+ function scalar(text: string): number | string {
471
+ const numeric =
472
+ Literal.rule(property)?.kind === 'number' ||
473
+ Literal.rule(property)?.kind === 'grid-line'
474
+ return numeric && /^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(text)
335
475
  ? Number(text)
336
476
  : text
477
+ }
478
+ }
337
479
 
338
- return { important: true as const, value }
480
+ /** Recognizes one enclosing pair without consuming native grid line lists. */
481
+ function bracketed(value: string): boolean {
482
+ if (!value.startsWith('[') || !value.endsWith(']')) return false
483
+ let depth = 0
484
+ let quote = ''
485
+ for (let index = 0; index < value.length; index++) {
486
+ const character = value[index]!
487
+ if (character === '\\') {
488
+ index++
489
+ continue
490
+ }
491
+ if (quote) {
492
+ if (character === quote) quote = ''
493
+ continue
494
+ }
495
+ if (character === '/' && value[index + 1] === '*') {
496
+ const end = value.indexOf('*/', index + 2)
497
+ if (end < 0) return false
498
+ index = end + 1
499
+ continue
500
+ }
501
+ if (character === '"' || character === "'") {
502
+ quote = character
503
+ continue
504
+ }
505
+ if (character === '[') depth++
506
+ if (character === ']' && --depth === 0) return index === value.length - 1
507
+ }
508
+ return false
339
509
  }