zyzz 0.0.6 → 0.0.8

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 (104) 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/babel/index.d.ts +4 -1
  7. package/dist/babel/index.d.ts.map +1 -1
  8. package/dist/babel/index.js +6 -3
  9. package/dist/babel/index.js.map +1 -1
  10. package/dist/babel/internal/Compilation.d.ts +12 -0
  11. package/dist/babel/internal/Compilation.d.ts.map +1 -0
  12. package/dist/babel/internal/Compilation.js +3 -0
  13. package/dist/babel/internal/Compilation.js.map +1 -0
  14. package/dist/compiler/Graph.d.ts +6 -1
  15. package/dist/compiler/Graph.d.ts.map +1 -1
  16. package/dist/compiler/Graph.js +160 -36
  17. package/dist/compiler/Graph.js.map +1 -1
  18. package/dist/compiler/Transform.d.ts.map +1 -1
  19. package/dist/compiler/Transform.js +2 -1
  20. package/dist/compiler/Transform.js.map +1 -1
  21. package/dist/compiler/internal/Stylesheets.d.ts +8 -0
  22. package/dist/compiler/internal/Stylesheets.d.ts.map +1 -1
  23. package/dist/compiler/internal/Stylesheets.js +4 -0
  24. package/dist/compiler/internal/Stylesheets.js.map +1 -1
  25. package/dist/internal/Authoring.d.ts.map +1 -1
  26. package/dist/internal/Authoring.js +14 -0
  27. package/dist/internal/Authoring.js.map +1 -1
  28. package/dist/internal/Binding.d.ts +7 -0
  29. package/dist/internal/Binding.d.ts.map +1 -1
  30. package/dist/internal/Binding.js.map +1 -1
  31. package/dist/internal/Configuration.d.ts +1 -1
  32. package/dist/internal/Configuration.d.ts.map +1 -1
  33. package/dist/internal/Configuration.js +7 -6
  34. package/dist/internal/Configuration.js.map +1 -1
  35. package/dist/internal/Theme.d.ts +1 -1
  36. package/dist/internal/Theme.d.ts.map +1 -1
  37. package/dist/internal/Theme.js.map +1 -1
  38. package/dist/internal/Token.d.ts +3 -1
  39. package/dist/internal/Token.d.ts.map +1 -1
  40. package/dist/internal/Token.js +28 -3
  41. package/dist/internal/Token.js.map +1 -1
  42. package/dist/internal/Value.d.ts +22 -0
  43. package/dist/internal/Value.d.ts.map +1 -1
  44. package/dist/internal/Value.js +76 -9
  45. package/dist/internal/Value.js.map +1 -1
  46. package/dist/metro/Graph.d.ts +9 -1
  47. package/dist/metro/Graph.d.ts.map +1 -1
  48. package/dist/metro/Graph.js +5 -4
  49. package/dist/metro/Graph.js.map +1 -1
  50. package/dist/metro/transformer.d.ts +0 -1
  51. package/dist/metro/transformer.d.ts.map +1 -1
  52. package/dist/metro/transformer.js +19 -1
  53. package/dist/metro/transformer.js.map +1 -1
  54. package/dist/next/index.d.ts +3 -3
  55. package/dist/next/index.d.ts.map +1 -1
  56. package/dist/next/index.js +25 -30
  57. package/dist/next/index.js.map +1 -1
  58. package/dist/next/internal/Project.d.ts +28 -0
  59. package/dist/next/internal/Project.d.ts.map +1 -0
  60. package/dist/next/internal/Project.js +295 -0
  61. package/dist/next/internal/Project.js.map +1 -0
  62. package/dist/next/loader.d.ts +5 -6
  63. package/dist/next/loader.d.ts.map +1 -1
  64. package/dist/next/loader.js +161 -247
  65. package/dist/next/loader.js.map +1 -1
  66. package/dist/node/Host.d.ts.map +1 -1
  67. package/dist/node/Host.js +11 -3
  68. package/dist/node/Host.js.map +1 -1
  69. package/dist/node/internal/Snapshot.d.ts +12 -0
  70. package/dist/node/internal/Snapshot.d.ts.map +1 -0
  71. package/dist/node/internal/Snapshot.js +74 -0
  72. package/dist/node/internal/Snapshot.js.map +1 -0
  73. package/dist/unplugin/index.d.ts.map +1 -1
  74. package/dist/unplugin/index.js +10 -5
  75. package/dist/unplugin/index.js.map +1 -1
  76. package/dist/variants.d.ts +1 -1
  77. package/dist/variants.d.ts.map +1 -1
  78. package/dist/variants.js.map +1 -1
  79. package/dist/vite/index.d.ts.map +1 -1
  80. package/dist/vite/index.js +19 -4
  81. package/dist/vite/index.js.map +1 -1
  82. package/package.json +1 -1
  83. package/src/Style.ts +42 -8
  84. package/src/babel/index.ts +10 -4
  85. package/src/babel/internal/Compilation.ts +13 -0
  86. package/src/compiler/Graph.ts +213 -47
  87. package/src/compiler/Transform.ts +2 -1
  88. package/src/compiler/internal/Stylesheets.ts +9 -0
  89. package/src/internal/Authoring.ts +21 -0
  90. package/src/internal/Binding.ts +30 -0
  91. package/src/internal/Configuration.ts +19 -13
  92. package/src/internal/Theme.ts +5 -1
  93. package/src/internal/Token.ts +34 -3
  94. package/src/internal/Value.ts +180 -10
  95. package/src/metro/Graph.ts +5 -3
  96. package/src/metro/transformer.ts +26 -1
  97. package/src/next/index.ts +26 -31
  98. package/src/next/internal/Project.ts +378 -0
  99. package/src/next/loader.ts +195 -315
  100. package/src/node/Host.ts +15 -3
  101. package/src/node/internal/Snapshot.ts +79 -0
  102. package/src/unplugin/index.ts +10 -5
  103. package/src/variants.ts +1 -0
  104. package/src/vite/index.ts +24 -4
@@ -38,6 +38,8 @@ export declare namespace compile {
38
38
 
39
39
  /** Source modules available for relative import resolution. */
40
40
  type Options = {
41
+ /** Host entrypoints emit owned stylesheet effects without packed contracts. */
42
+ readonly [Stylesheets.entry]?: string | undefined
41
43
  /** Host syntax from the same immutable source snapshot. */
42
44
  readonly [Syntax.cache]?:
43
45
  | ReadonlyMap<string, Parser.ParseResult>
@@ -71,6 +73,8 @@ export declare namespace compile {
71
73
 
72
74
  /** Compiled modules and their direct source dependencies. */
73
75
  type ReturnType = {
76
+ /** Packed resources deduplicated by the host across scoped source outputs. */
77
+ readonly [Stylesheets.packed]?: readonly Stylesheets.Resource[]
74
78
  /** Versioned compiler-only JSON per module; publish beside the compiled entrypoint as <entry>.zyzz.json. */
75
79
  readonly contracts: Readonly<Record<string, string>>
76
80
  /** Direct static runtime source and library-contract dependencies, keyed by module identity. */
@@ -88,7 +92,7 @@ export declare namespace compile {
88
92
  }
89
93
  }
90
94
 
91
- /** Creates an isolated compiler that retains only the last successful graph. */
95
+ /** Creates an isolated compiler with bounded extraction reuse across source graphs. */
92
96
  export function create(): create.ReturnType {
93
97
  let previous: Cache | undefined
94
98
 
@@ -112,6 +116,26 @@ export declare namespace create {
112
116
  }
113
117
  }
114
118
 
119
+ type Extraction = {
120
+ dependencies: readonly string[]
121
+ extracted: Source.extract.ReturnType
122
+ inputs: ReadonlyMap<
123
+ string,
124
+ Source.extract.ReturnType | ReturnType<typeof Contract.read>
125
+ >
126
+ resolution: string
127
+ source: string
128
+ }
129
+
130
+ type Transformed = {
131
+ classes: Readonly<Record<string, string | undefined>>
132
+ extracted: Source.extract.ReturnType
133
+ owners: string
134
+ output: Transform.compile.ReturnType
135
+ schemes: boolean
136
+ themes: Readonly<Record<string, Theme.Definition>>
137
+ }
138
+
115
139
  type Cache = {
116
140
  compiler: boolean
117
141
  composition: Css.compile.Options['composition']
@@ -119,9 +143,12 @@ type Cache = {
119
143
 
120
144
  contracts: string
121
145
  development: boolean
146
+ entry: string | undefined
122
147
  extracted: ReadonlyMap<string, Source.extract.ReturnType>
123
148
  libraries: Readonly<Record<string, ReturnType<typeof Contract.read>>>
124
149
  reset: string | undefined
150
+ retained: ReadonlyMap<string, Extraction>
151
+ transformed: ReadonlyMap<string, Transformed>
125
152
  resolutions: Readonly<Record<string, string>>
126
153
  native: compile.Options['native']
127
154
  result: compile.ReturnType
@@ -195,14 +222,15 @@ function build(options: compile.Options, cache?: Cache): Cache {
195
222
  ]),
196
223
  )
197
224
 
198
- // File-set changes can alter extensionless resolution even without source edits.
225
+ // Host resolutions remain explicit when generated modules enter or leave the graph.
199
226
  const previous = (() => {
200
227
  if (
201
228
  cache &&
202
229
  cache.contracts === contracts &&
203
230
  cache.development === !!options.development &&
204
- ids.length === Object.keys(cache.sources).length &&
205
- ids.every((id) => Object.hasOwn(cache.sources, id))
231
+ (options.imports !== undefined ||
232
+ (ids.length === Object.keys(cache.sources).length &&
233
+ ids.every((id) => Object.hasOwn(cache.sources, id))))
206
234
  ) {
207
235
  return cache
208
236
  }
@@ -211,6 +239,8 @@ function build(options: compile.Options, cache?: Cache): Cache {
211
239
  })()
212
240
  if (
213
241
  previous &&
242
+ previous.entry === options[Stylesheets.entry] &&
243
+ ids.length === Object.keys(previous.sources).length &&
214
244
  Object.keys(resolutions).length ===
215
245
  Object.keys(previous.resolutions).length &&
216
246
  Object.entries(resolutions).every(
@@ -224,6 +254,8 @@ function build(options: compile.Options, cache?: Cache): Cache {
224
254
  )
225
255
  return previous
226
256
 
257
+ const retained = new Map(previous?.retained)
258
+ const transformed = new Map(previous?.transformed)
227
259
  const dependencies: Record<string, readonly string[]> = Object.create(null)
228
260
  const extracted = new Map<string, Source.extract.ReturnType>()
229
261
  const identifiers = new Map<string, Set<string>>()
@@ -365,29 +397,6 @@ function build(options: compile.Options, cache?: Cache): Cache {
365
397
  let direct: Ast.Node | undefined
366
398
  const stars: string[] = []
367
399
  for (const statement of program.body) {
368
- if (
369
- statement.type === 'ImportDeclaration' &&
370
- statement.importKind !== 'type'
371
- ) {
372
- const target = resolve(moduleId, statement.source.value, statement)
373
- if (!target) continue
374
- for (const specifier of statement.specifiers) {
375
- if (
376
- specifier.type === 'ImportNamespaceSpecifier' ||
377
- (specifier.type === 'ImportSpecifier' &&
378
- specifier.importKind === 'type')
379
- )
380
- continue
381
- const imported =
382
- specifier.type === 'ImportDefaultSpecifier'
383
- ? 'default'
384
- : specifier.imported.type === 'Identifier'
385
- ? specifier.imported.name
386
- : specifier.imported.value
387
- const value = constant(target, imported, next)
388
- if (value) imports[specifier.local.name] = relocate(value, specifier)
389
- }
390
- }
391
400
  if (statement.type === 'ExportDefaultDeclaration' && name === 'default') {
392
401
  direct = statement.declaration
393
402
  exported = true
@@ -436,7 +445,47 @@ function build(options: compile.Options, cache?: Cache): Cache {
436
445
  .filter((value) => value !== undefined),
437
446
  ),
438
447
  ]
439
- return candidates.length === 1 ? candidates[0] : undefined
448
+ const value = candidates.length === 1 ? candidates[0] : undefined
449
+ if (value || !stars.length) constants.set(key, value)
450
+ return value
451
+ }
452
+ if (
453
+ direct &&
454
+ [
455
+ 'FunctionDeclaration',
456
+ 'FunctionExpression',
457
+ 'ArrowFunctionExpression',
458
+ 'ClassDeclaration',
459
+ 'ClassExpression',
460
+ ].includes(direct.type)
461
+ ) {
462
+ constants.set(key, undefined)
463
+ return undefined
464
+ }
465
+ for (const statement of program.body) {
466
+ if (
467
+ statement.type === 'ImportDeclaration' &&
468
+ statement.importKind !== 'type'
469
+ ) {
470
+ const target = resolve(moduleId, statement.source.value, statement)
471
+ if (!target) continue
472
+ for (const specifier of statement.specifiers) {
473
+ if (
474
+ specifier.type === 'ImportNamespaceSpecifier' ||
475
+ (specifier.type === 'ImportSpecifier' &&
476
+ specifier.importKind === 'type')
477
+ )
478
+ continue
479
+ const imported =
480
+ specifier.type === 'ImportDefaultSpecifier'
481
+ ? 'default'
482
+ : specifier.imported.type === 'Identifier'
483
+ ? specifier.imported.name
484
+ : specifier.imported.value
485
+ const value = constant(target, imported, next)
486
+ if (value) imports[specifier.local.name] = relocate(value, specifier)
487
+ }
488
+ }
440
489
  }
441
490
  const scope = new Scope.Tracker({ preserveExitedScopes: true })
442
491
  Walker.walk(program, { scopeTracker: scope })
@@ -602,21 +651,34 @@ function build(options: compile.Options, cache?: Cache): Cache {
602
651
 
603
652
  const source = options.modules[moduleId]!
604
653
 
654
+ const snapshot =
655
+ previous?.retained.get(moduleId) ??
656
+ (previous && Object.hasOwn(previous.sources, moduleId)
657
+ ? {
658
+ dependencies: previous.result.dependencies[moduleId]!,
659
+ extracted: previous.extracted.get(moduleId)!,
660
+ inputs: new Map(
661
+ previous.result.dependencies[moduleId]!.map((target) => [
662
+ target,
663
+ previous.libraries[target] ?? previous.extracted.get(target)!,
664
+ ]),
665
+ ),
666
+ resolution: previous.resolutions[moduleId]!,
667
+ source: previous.sources[moduleId]!,
668
+ }
669
+ : undefined)
605
670
  if (
606
- previous &&
607
- source === previous.sources[moduleId] &&
608
- resolutions[moduleId] === previous.resolutions[moduleId] &&
609
- previous.result.dependencies[moduleId]!.every(
610
- (target) =>
611
- Object.hasOwn(libraries, target) ||
612
- visit(target) === previous.extracted.get(target),
671
+ snapshot &&
672
+ source === snapshot.source &&
673
+ resolutions[moduleId] === snapshot.resolution &&
674
+ snapshot.dependencies.every((target) =>
675
+ Object.hasOwn(libraries, target)
676
+ ? libraries[target] === snapshot.inputs.get(target)
677
+ : Object.hasOwn(options.modules, target) &&
678
+ visit(target) === snapshot.inputs.get(target),
613
679
  )
614
680
  ) {
615
- return retain(
616
- moduleId,
617
- previous.extracted.get(moduleId)!,
618
- previous.result.dependencies[moduleId]!,
619
- )
681
+ return retain(moduleId, snapshot.extracted, snapshot.dependencies)
620
682
  }
621
683
 
622
684
  // Validate identity and syntax through the public source boundary before linking.
@@ -957,6 +1019,20 @@ function build(options: compile.Options, cache?: Cache): Cache {
957
1019
 
958
1020
  extracted.set(moduleId, result)
959
1021
  dependencies[moduleId] = imports
1022
+ retained.delete(moduleId)
1023
+ retained.set(moduleId, {
1024
+ dependencies: imports,
1025
+ extracted: result,
1026
+ inputs: new Map(
1027
+ imports.map((target) => [
1028
+ target,
1029
+ libraries[target] ?? extracted.get(target)!,
1030
+ ]),
1031
+ ),
1032
+ resolution: resolutions[moduleId]!,
1033
+ source: options.modules[moduleId]!,
1034
+ })
1035
+ if (retained.size > 256) retained.delete(retained.keys().next().value!)
960
1036
 
961
1037
  for (const call of result.themeCalls)
962
1038
  for (const name of new Set([
@@ -1072,6 +1148,9 @@ function build(options: compile.Options, cache?: Cache): Cache {
1072
1148
  ...(options.native.units && { units: { ...options.native.units } }),
1073
1149
  },
1074
1150
  reset: options.reset,
1151
+ entry: options[Stylesheets.entry],
1152
+ retained,
1153
+ transformed,
1075
1154
  resolutions: Object.freeze(resolutions),
1076
1155
  result: Object.freeze({
1077
1156
  contracts: Object.freeze(
@@ -1248,16 +1327,58 @@ function build(options: compile.Options, cache?: Cache): Cache {
1248
1327
  }
1249
1328
 
1250
1329
  const sharedVisited = new Set<string>()
1330
+ const packed: Stylesheets.Resource[] = []
1251
1331
 
1252
1332
  const shared = (() => {
1253
1333
  try {
1254
- const sharedSections = ids.flatMap((id) => reachable(id, sharedVisited))
1334
+ const entry = options[Stylesheets.entry]
1335
+ const packedSections =
1336
+ entry === undefined
1337
+ ? []
1338
+ : (dependencies[entry] ?? [])
1339
+ .filter((id) => Object.hasOwn(libraries, id))
1340
+ .flatMap((id) => reachable(id, sharedVisited))
1341
+ const sharedSections =
1342
+ entry === undefined
1343
+ ? ids.flatMap((id) => reachable(id, sharedVisited))
1344
+ : (sections.get(entry) ?? [])
1345
+ if (packedSections.length) {
1346
+ // Validate combined ownership and ordering before splitting independently loaded resources.
1347
+ Stylesheets.render([...packedSections, ...sharedSections])
1348
+ for (const section of packedSections) {
1349
+ packed.push({
1350
+ id: JSON.stringify([section.owner, section.source, section.key]),
1351
+ ...Stylesheets.render([section]),
1352
+ })
1353
+ }
1354
+ }
1255
1355
  const resetSource =
1256
1356
  (options.reset === undefined ? undefined : 'zyzz/reset.css') ??
1257
1357
  resetOwners[0] ??
1258
1358
  sharedSections.find((section) => section.key === 'optional-reset-order')
1259
1359
  ?.source
1260
1360
 
1361
+ if (packed.length) {
1362
+ const order = Stylesheets.render([
1363
+ ...(resetSource === undefined
1364
+ ? []
1365
+ : [
1366
+ {
1367
+ source: resetSource,
1368
+ css: '',
1369
+ layers: layerNames.length
1370
+ ? layerNames.map((name) => ['reset', name])
1371
+ : [['reset']],
1372
+ },
1373
+ ]),
1374
+ ...[...packedSections, ...sharedSections].map((section) => ({
1375
+ ...section,
1376
+ css: '',
1377
+ })),
1378
+ ])
1379
+ if (order.css) packed.unshift({ ...order, id: order.css })
1380
+ }
1381
+
1261
1382
  return Stylesheets.render([
1262
1383
  ...(resetSource === undefined
1263
1384
  ? []
@@ -1406,12 +1527,26 @@ function build(options: compile.Options, cache?: Cache): Cache {
1406
1527
  ),
1407
1528
  )
1408
1529
 
1530
+ const ownerSignature = JSON.stringify(owners)
1531
+
1409
1532
  // Extraction visits dependencies first; their emitted classes must precede consumers.
1410
1533
  for (const moduleId of extracted.keys()) {
1411
- modules[moduleId] =
1412
- sameThemes &&
1413
- previous!.schemes === schemes &&
1414
- extracted.get(moduleId) === previous!.extracted.get(moduleId)
1534
+ const cached = transformed.get(moduleId)
1535
+ const reusable =
1536
+ cached &&
1537
+ cached.extracted === extracted.get(moduleId) &&
1538
+ cached.schemes === schemes &&
1539
+ Object.keys(cached.themes).length === names.length &&
1540
+ names.every((name) => cached.themes[name] === themes[name]) &&
1541
+ Object.entries(cached.classes).every(
1542
+ ([name, value]) => styleClasses[name] === value,
1543
+ ) &&
1544
+ cached.owners === ownerSignature
1545
+ modules[moduleId] = reusable
1546
+ ? cached.output
1547
+ : sameThemes &&
1548
+ previous!.schemes === schemes &&
1549
+ extracted.get(moduleId) === previous!.extracted.get(moduleId)
1415
1550
  ? previous!.result.modules[moduleId]!
1416
1551
  : Transform.compile({
1417
1552
  compiler: options.compiler,
@@ -1422,6 +1557,7 @@ function build(options: compile.Options, cache?: Cache): Cache {
1422
1557
  schemes,
1423
1558
  source: options.modules[moduleId]!,
1424
1559
  [Themes.context]: {
1560
+ parsed: parse({ moduleId, source: options.modules[moduleId]! }),
1425
1561
  extracted: Object.freeze({
1426
1562
  ...extracted.get(moduleId)!,
1427
1563
  vars: sharedThemes,
@@ -1432,13 +1568,39 @@ function build(options: compile.Options, cache?: Cache): Cache {
1432
1568
  styleClasses,
1433
1569
  },
1434
1570
  })
1571
+ transformed.delete(moduleId)
1572
+ transformed.set(moduleId, {
1573
+ classes: Object.fromEntries(
1574
+ extracted
1575
+ .get(moduleId)!
1576
+ .calls.flatMap((call) =>
1577
+ (call.runtimeComposition ?? []).map((input) => [
1578
+ input.name,
1579
+ styleClasses[input.name],
1580
+ ]),
1581
+ ),
1582
+ ),
1583
+ extracted: extracted.get(moduleId)!,
1584
+ owners: ownerSignature,
1585
+ output: modules[moduleId]!,
1586
+ schemes,
1587
+ themes: sharedThemes,
1588
+ })
1589
+ if (transformed.size > 256)
1590
+ transformed.delete(transformed.keys().next().value!)
1435
1591
  // Module-local checks cannot detect truncated ownership hashes colliding across files.
1592
+ const emitted = new Set(
1593
+ Array.from(
1594
+ modules[moduleId]!.css.matchAll(/\.(z-[\w-]+)\{/g),
1595
+ (match) => match[1],
1596
+ ),
1597
+ )
1436
1598
  for (const value of Object.values(modules[moduleId]!.classes))
1437
1599
  for (const name of value.split(' ')) {
1438
1600
  if (
1439
1601
  options.compiler === false ||
1440
1602
  !name.startsWith('z-') ||
1441
- !modules[moduleId]!.css.includes(`.${name}{`)
1603
+ !emitted.has(name)
1442
1604
  )
1443
1605
  continue
1444
1606
 
@@ -1531,8 +1693,12 @@ function build(options: compile.Options, cache?: Cache): Cache {
1531
1693
  libraries: Object.freeze(libraries),
1532
1694
  native: undefined,
1533
1695
  reset: options.reset,
1696
+ entry: options[Stylesheets.entry],
1697
+ retained,
1698
+ transformed,
1534
1699
  resolutions: Object.freeze(resolutions),
1535
1700
  result: Object.freeze({
1701
+ ...(packed.length ? { [Stylesheets.packed]: packed } : {}),
1536
1702
  ...(sharedCss
1537
1703
  ? {
1538
1704
  sharedCss,
@@ -1543,7 +1709,7 @@ function build(options: compile.Options, cache?: Cache): Cache {
1543
1709
  : {}),
1544
1710
  contracts: Object.freeze(
1545
1711
  Object.fromEntries(
1546
- ids
1712
+ (options[Stylesheets.entry] === undefined ? ids : [])
1547
1713
  .filter(
1548
1714
  (id) =>
1549
1715
  Object.keys(extracted.get(id)!.themeExports ?? {}).length ||
@@ -96,7 +96,8 @@ export function compile(options: compile.Options): compile.ReturnType {
96
96
  }
97
97
 
98
98
  const module = new MagicString(options.source)
99
- const program = Syntax.parse(options).program
99
+ const program = (options[Themes.context]?.parsed ?? Syntax.parse(options))
100
+ .program
100
101
 
101
102
  for (const call of extracted.contributionCalls ?? [])
102
103
  module.overwrite(
@@ -17,6 +17,15 @@ export class ConflictError extends Error {
17
17
  override name = 'Stylesheets.ConflictError'
18
18
  }
19
19
 
20
+ /** Selects module-owned effects when the host loads source dependencies separately. */
21
+ export const entry = Symbol('stylesheet entry')
22
+
23
+ /** Independently shared packed sections for hosts that load source modules separately. */
24
+ export const packed = Symbol('packed stylesheets')
25
+
26
+ /** One packed resource with stable source ownership for development updates. */
27
+ export type Resource = ReturnType<typeof render> & { readonly id: string }
28
+
20
29
  /** Ordered CSS owned by one portable source module. */
21
30
  export type Section = {
22
31
  /** Isolated selector namespaces belonging to the source owner. */
@@ -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. */