zyzz 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -365
- package/dist/Style.d.ts +2 -0
- package/dist/Style.d.ts.map +1 -1
- package/dist/compiler/Graph.d.ts +3 -0
- package/dist/compiler/Graph.d.ts.map +1 -1
- package/dist/compiler/Graph.js +38 -2
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/Source.d.ts.map +1 -1
- package/dist/compiler/Source.js +11 -3
- package/dist/compiler/Source.js.map +1 -1
- package/dist/compiler/Transform.d.ts +4 -0
- package/dist/compiler/Transform.d.ts.map +1 -1
- package/dist/compiler/Transform.js +79 -15
- package/dist/compiler/Transform.js.map +1 -1
- package/dist/compiler/internal/Applications.d.ts +20 -0
- package/dist/compiler/internal/Applications.d.ts.map +1 -1
- package/dist/compiler/internal/Applications.js +151 -47
- package/dist/compiler/internal/Applications.js.map +1 -1
- package/dist/compiler/internal/Compositions.d.ts.map +1 -1
- package/dist/compiler/internal/Compositions.js +6 -0
- package/dist/compiler/internal/Compositions.js.map +1 -1
- package/dist/compiler/internal/Configurations.d.ts.map +1 -1
- package/dist/compiler/internal/Configurations.js +4 -0
- package/dist/compiler/internal/Configurations.js.map +1 -1
- package/dist/compiler/internal/Contract.d.ts.map +1 -1
- package/dist/compiler/internal/Contract.js +34 -7
- package/dist/compiler/internal/Contract.js.map +1 -1
- package/dist/compiler/internal/PackedStyles.d.ts.map +1 -1
- package/dist/compiler/internal/PackedStyles.js +7 -0
- package/dist/compiler/internal/PackedStyles.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +20 -8
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/internal/Configuration.d.ts +10 -0
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +10 -1
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/Token.d.ts +2 -0
- package/dist/internal/Token.d.ts.map +1 -1
- package/dist/internal/Token.js.map +1 -1
- package/dist/next/internal/Project.d.ts.map +1 -1
- package/dist/next/internal/Project.js +30 -25
- package/dist/next/internal/Project.js.map +1 -1
- package/dist/next/loader.js +2 -1
- package/dist/next/loader.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +64 -27
- package/dist/node/Host.js.map +1 -1
- package/dist/node/internal/Contracts.d.ts +22 -0
- package/dist/node/internal/Contracts.d.ts.map +1 -0
- package/dist/node/internal/Contracts.js +287 -0
- package/dist/node/internal/Contracts.js.map +1 -0
- package/dist/react-native/internal/Device.js +90 -37
- package/dist/react-native/internal/Device.js.map +3 -3
- package/dist/react-native/internal/NativeZyzz.js.map +2 -2
- package/dist/react-native/internal/Store.d.ts +4 -0
- package/dist/react-native/internal/Store.d.ts.map +1 -1
- package/dist/react-native/internal/Store.js.map +1 -1
- package/dist/react-native/internal/Viewport.d.ts +6 -0
- package/dist/react-native/internal/Viewport.d.ts.map +1 -1
- package/dist/react-native/internal/Viewport.js +2 -0
- package/dist/react-native/internal/Viewport.js.map +1 -1
- package/dist/react-native/react.d.ts +5 -2
- package/dist/react-native/react.d.ts.map +1 -1
- package/dist/react-native/react.js +60 -8
- package/dist/react-native/react.js.map +1 -1
- package/dist/runtime/NativeContext.d.ts +6 -0
- package/dist/runtime/NativeContext.d.ts.map +1 -1
- package/dist/runtime/NativeContext.js +62 -9
- package/dist/runtime/NativeContext.js.map +1 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +35 -14
- package/dist/unplugin/index.js.map +1 -1
- package/dist/variants.d.ts +3 -2
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +30 -37
- package/dist/vite/index.js.map +1 -1
- package/dist/web/Css.d.ts +10 -5
- package/dist/web/Css.d.ts.map +1 -1
- package/dist/web/Css.js +170 -31
- package/dist/web/Css.js.map +1 -1
- package/dist/web/internal/ClassName.d.ts +49 -0
- package/dist/web/internal/ClassName.d.ts.map +1 -1
- package/dist/web/internal/ClassName.js +57 -0
- package/dist/web/internal/ClassName.js.map +1 -1
- package/native/NativeZyzz.cpp +52 -65
- package/native/NativeZyzz.h +0 -4
- package/native/ZyzzSpecJSI.h +0 -10
- package/native/android/ZyzzSpec-generated.cpp +0 -6
- package/package.json +1 -1
- package/src/Style.ts +2 -0
- package/src/compiler/Graph.ts +42 -2
- package/src/compiler/Source.ts +11 -3
- package/src/compiler/Transform.ts +130 -29
- package/src/compiler/internal/Applications.ts +239 -63
- package/src/compiler/internal/Compositions.ts +9 -0
- package/src/compiler/internal/Configurations.ts +4 -0
- package/src/compiler/internal/Contract.ts +59 -7
- package/src/compiler/internal/PackedStyles.ts +9 -0
- package/src/compiler/internal/Themes.ts +25 -7
- package/src/internal/Configuration.ts +23 -1
- package/src/internal/Token.ts +2 -0
- package/src/next/internal/Project.ts +28 -23
- package/src/next/loader.ts +2 -1
- package/src/node/Host.ts +90 -36
- package/src/node/internal/Contracts.ts +384 -0
- package/src/react-native/internal/Device.ts +129 -48
- package/src/react-native/internal/NativeZyzz.ts +4 -6
- package/src/react-native/internal/Store.ts +4 -0
- package/src/react-native/internal/Viewport.ts +7 -0
- package/src/react-native/react.ts +81 -11
- package/src/runtime/NativeContext.ts +83 -9
- package/src/unplugin/index.ts +39 -14
- package/src/variants.ts +3 -2
- package/src/vite/index.ts +35 -36
- package/src/web/Css.ts +228 -42
- package/src/web/internal/ClassName.ts +114 -0
package/src/web/Css.ts
CHANGED
|
@@ -21,7 +21,8 @@ export type Contribution = Contributions.Definition
|
|
|
21
21
|
/**
|
|
22
22
|
* Emits atomic or grouped CSS without reading files or generating runtime code.
|
|
23
23
|
* Shares only nonconflicting declaration domains; conflicting rules retain authored
|
|
24
|
-
* order by default. Independent composition deduplicates complete applications
|
|
24
|
+
* order by default. Independent composition deduplicates complete applications and
|
|
25
|
+
* shares equal atomic domain declarations under short names.
|
|
25
26
|
* Class lists are scoped to the complete compilation input. Empty styles
|
|
26
27
|
* return an empty class list and no rule.
|
|
27
28
|
* @param options - Validated, ordered definitions from Style.define.
|
|
@@ -35,6 +36,9 @@ export function compile<
|
|
|
35
36
|
options: compile.Options<name, themeName>,
|
|
36
37
|
): compile.ReturnType<name, themeName> {
|
|
37
38
|
const labels = options[ClassName.labels] ?? options.styles[ClassName.labels]
|
|
39
|
+
const units =
|
|
40
|
+
options[ClassName.units] ??
|
|
41
|
+
ClassName.registry().scope({ owner: '', qualifier: '' })
|
|
38
42
|
options = {
|
|
39
43
|
...options,
|
|
40
44
|
styles: { styles: options.styles.styles.map(Targets.web) },
|
|
@@ -92,11 +96,17 @@ export function compile<
|
|
|
92
96
|
const classes = Object.create(null) as Record<name, string>
|
|
93
97
|
const diagnostics: Diagnostic[] = []
|
|
94
98
|
const groups = new Map<string, false | string>()
|
|
95
|
-
const nestedComposition = options.styles.styles.some(
|
|
99
|
+
const nestedComposition = options.styles.styles.some(
|
|
100
|
+
(style) => style.rules && !isolated(style),
|
|
101
|
+
)
|
|
96
102
|
|
|
97
103
|
const properties = new Set<Style.Declaration['property']>()
|
|
98
|
-
|
|
104
|
+
// Conditional and composed declarations join the same conflict domains.
|
|
105
|
+
function collect(style: Style.NamedStyle): void {
|
|
99
106
|
for (const { property } of style.declarations) properties.add(property)
|
|
107
|
+
for (const rule of style.rules ?? []) collect(rule.style)
|
|
108
|
+
}
|
|
109
|
+
for (const style of analyzed) collect(style)
|
|
100
110
|
|
|
101
111
|
// Logical dimensions may alias either physical axis in inherited writing modes.
|
|
102
112
|
// Preserve physical-only factoring when no logical dimension is authored.
|
|
@@ -361,32 +371,39 @@ export function compile<
|
|
|
361
371
|
|
|
362
372
|
// Sharing is safe only when every use of a conflict domain has the same
|
|
363
373
|
// declaration sequence. Conditions conservatively retain contextual identities.
|
|
374
|
+
// Isolated class lists never combine, so atomic sharing considers ordered styles only.
|
|
375
|
+
const ordered = new Map<string, false | string>()
|
|
376
|
+
const combinable = new Set<Style.NamedStyle>()
|
|
377
|
+
for (const [index, style] of options.styles.styles.entries())
|
|
378
|
+
if (!isolated(style)) combinable.add(canonicalStyles[index]!)
|
|
379
|
+
|
|
364
380
|
for (const style of analyzed) {
|
|
365
|
-
const domains = new Map<
|
|
381
|
+
const domains = new Map<
|
|
382
|
+
string,
|
|
383
|
+
{ body: string; mixed: boolean; property: string }
|
|
384
|
+
>()
|
|
366
385
|
|
|
367
386
|
for (const item of style.declarations) {
|
|
368
387
|
const property = item.property
|
|
369
388
|
const key = conflict(property)
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
body: '',
|
|
374
|
-
property,
|
|
375
|
-
}
|
|
376
|
-
entry.body += declaration
|
|
377
|
-
if (entry.property !== property) groups.set(key, false)
|
|
389
|
+
const entry = domains.get(key) ?? { body: '', mixed: false, property }
|
|
390
|
+
entry.body += declarationBody(item)
|
|
391
|
+
entry.mixed ||= entry.property !== property
|
|
378
392
|
domains.set(key, entry)
|
|
379
393
|
}
|
|
380
394
|
|
|
381
|
-
for (const [key, entry] of domains)
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
395
|
+
for (const [key, entry] of domains)
|
|
396
|
+
for (const map of combinable.has(style) ? [groups, ordered] : [groups]) {
|
|
397
|
+
const previous = map.get(key)
|
|
398
|
+
map.set(
|
|
399
|
+
key,
|
|
400
|
+
previous === false ||
|
|
401
|
+
entry.mixed ||
|
|
402
|
+
(previous !== undefined && previous !== entry.body)
|
|
403
|
+
? false
|
|
404
|
+
: entry.body,
|
|
405
|
+
)
|
|
406
|
+
}
|
|
390
407
|
}
|
|
391
408
|
|
|
392
409
|
const defaultMode = options.cssOutput ?? 'atomic'
|
|
@@ -406,6 +423,75 @@ export function compile<
|
|
|
406
423
|
| undefined
|
|
407
424
|
}
|
|
408
425
|
|
|
426
|
+
function compositionMode(style: Style.NamedStyle) {
|
|
427
|
+
const mode = Object.getOwnPropertyDescriptor(style, 'composition')
|
|
428
|
+
?.value as 'independent' | 'ordered' | undefined
|
|
429
|
+
|
|
430
|
+
return mode ?? options.composition ?? 'ordered'
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Development keeps every style ordered so names survive value edits.
|
|
434
|
+
function isolated(style: Style.NamedStyle) {
|
|
435
|
+
return compositionMode(style) === 'independent' && !options.development
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
type Item = {
|
|
439
|
+
readonly conditions: readonly string[]
|
|
440
|
+
readonly property: string
|
|
441
|
+
readonly value: string
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Lists declaration groups in authored order; undefined marks styles whose
|
|
445
|
+
// nested grouped output or anonymous layers emit one opaque rule.
|
|
446
|
+
function flatten(
|
|
447
|
+
style: Style.NamedStyle,
|
|
448
|
+
conditions: readonly string[] = [],
|
|
449
|
+
): Item[] | undefined {
|
|
450
|
+
if (outputMode(style) === 'grouped') return undefined
|
|
451
|
+
|
|
452
|
+
if (style.rules) {
|
|
453
|
+
const items: Item[] = []
|
|
454
|
+
for (const rule of style.rules) {
|
|
455
|
+
if (rule.condition?.trim() === '@layer') return undefined
|
|
456
|
+
|
|
457
|
+
const nested = flatten(
|
|
458
|
+
rule.style,
|
|
459
|
+
rule.condition === undefined
|
|
460
|
+
? conditions
|
|
461
|
+
: [...conditions, rule.condition],
|
|
462
|
+
)
|
|
463
|
+
if (!nested) return undefined
|
|
464
|
+
|
|
465
|
+
items.push(...nested)
|
|
466
|
+
}
|
|
467
|
+
return items
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const items: Item[] = []
|
|
471
|
+
for (let index = 0; index < style.declarations.length; ) {
|
|
472
|
+
const property = style.declarations[index]!.property
|
|
473
|
+
let value = ''
|
|
474
|
+
// Keep same-property fallbacks ordered; they form one semantic value.
|
|
475
|
+
while (style.declarations[index]?.property === property) {
|
|
476
|
+
const declaration = style.declarations[index++]!
|
|
477
|
+
value += `${Literal.name(property)}:${serialize(declaration.value)}${declaration.important ? '!important' : ''};`
|
|
478
|
+
}
|
|
479
|
+
items.push({ conditions, property, value })
|
|
480
|
+
}
|
|
481
|
+
return items
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function byDomain(items: readonly Item[]) {
|
|
485
|
+
const result = new Map<string, Item[]>()
|
|
486
|
+
for (const item of items) {
|
|
487
|
+
const key = conflict(item.property)
|
|
488
|
+
const domain = result.get(key)
|
|
489
|
+
if (domain) domain.push(item)
|
|
490
|
+
else result.set(key, [item])
|
|
491
|
+
}
|
|
492
|
+
return result
|
|
493
|
+
}
|
|
494
|
+
|
|
409
495
|
function representation(style: Style.NamedStyle): unknown {
|
|
410
496
|
return [
|
|
411
497
|
outputMode(style),
|
|
@@ -444,8 +530,48 @@ export function compile<
|
|
|
444
530
|
occurrences.set(key, (occurrences.get(key) ?? 0) + 1)
|
|
445
531
|
}
|
|
446
532
|
|
|
533
|
+
// An ordered atomic style reuses the rules of the previous style that set a
|
|
534
|
+
// domain to the same sequence: no differing rule sits between them.
|
|
535
|
+
const runs = new Map<number, Map<string, number>>()
|
|
536
|
+
const owned = new Map<number, Map<string, string[]>>()
|
|
537
|
+
if (!options.development) {
|
|
538
|
+
const last = new Map<string, { key: string; owner: number }>()
|
|
539
|
+
|
|
540
|
+
for (const [index, style] of options.styles.styles.entries()) {
|
|
541
|
+
if (isolated(style)) continue
|
|
542
|
+
|
|
543
|
+
const items =
|
|
544
|
+
(outputMode(style) ?? defaultMode) === 'atomic' &&
|
|
545
|
+
options.names?.[style.name] === undefined
|
|
546
|
+
? flatten(style)
|
|
547
|
+
: undefined
|
|
548
|
+
// Grouped, explicit, and opaque rules may set any domain.
|
|
549
|
+
if (!items) {
|
|
550
|
+
last.clear()
|
|
551
|
+
continue
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
for (const [domain, list] of byDomain(items)) {
|
|
555
|
+
const key = JSON.stringify(
|
|
556
|
+
list.map((item) => [item.conditions, item.value]),
|
|
557
|
+
)
|
|
558
|
+
const previous = last.get(domain)
|
|
559
|
+
if (previous?.key !== key) {
|
|
560
|
+
last.set(domain, { key, owner: index })
|
|
561
|
+
continue
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const reuse = runs.get(index) ?? new Map<string, number>()
|
|
565
|
+
reuse.set(domain, previous.owner)
|
|
566
|
+
runs.set(index, reuse)
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
447
571
|
for (const [styleIndex, style] of options.styles.styles.entries()) {
|
|
448
572
|
const mode = outputMode(style) ?? defaultMode
|
|
573
|
+
const independent =
|
|
574
|
+
compositionMode(style) === 'independent' && !options.development
|
|
449
575
|
if (!style.name || Object.hasOwn(classes, style.name)) {
|
|
450
576
|
diagnostics.push({
|
|
451
577
|
code: 'invalid_name',
|
|
@@ -473,9 +599,7 @@ export function compile<
|
|
|
473
599
|
}
|
|
474
600
|
|
|
475
601
|
const application =
|
|
476
|
-
explicit === undefined &&
|
|
477
|
-
options.composition === 'independent' &&
|
|
478
|
-
!options.development
|
|
602
|
+
explicit === undefined && independent
|
|
479
603
|
? `${mode}:${style.rules ? JSON.stringify(representation(style)) : ''}:${nested(canonicalStyles[styleIndex]!)}`
|
|
480
604
|
: undefined
|
|
481
605
|
if (application !== undefined && applications.has(application)) {
|
|
@@ -501,19 +625,16 @@ export function compile<
|
|
|
501
625
|
shared: boolean,
|
|
502
626
|
output = mode,
|
|
503
627
|
conditional?: ConditionalRule,
|
|
504
|
-
) {
|
|
505
|
-
if (!body) return
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
output === mode &&
|
|
511
|
-
options.composition === 'independent'
|
|
512
|
-
const reusable = explicit === undefined && (shared || independent)
|
|
628
|
+
): string | undefined {
|
|
629
|
+
if (!body) return undefined
|
|
630
|
+
|
|
631
|
+
const reusable =
|
|
632
|
+
explicit === undefined &&
|
|
633
|
+
(shared || (independent && mode === 'grouped' && output === mode))
|
|
513
634
|
const previous = reusable ? sharedRules![output].get(body) : undefined
|
|
514
635
|
if (previous && !names.includes(previous)) {
|
|
515
636
|
names.push(previous)
|
|
516
|
-
return
|
|
637
|
+
return previous
|
|
517
638
|
}
|
|
518
639
|
|
|
519
640
|
if (previous) shared = false
|
|
@@ -562,6 +683,8 @@ export function compile<
|
|
|
562
683
|
if (reusable) sharedRules![output].set(body, identity)
|
|
563
684
|
names.push(identity)
|
|
564
685
|
}
|
|
686
|
+
|
|
687
|
+
return identity
|
|
565
688
|
}
|
|
566
689
|
|
|
567
690
|
function atoms(
|
|
@@ -605,6 +728,16 @@ export function compile<
|
|
|
605
728
|
while (style.declarations[index]?.property === property)
|
|
606
729
|
values.push(style.declarations[index++]!)
|
|
607
730
|
|
|
731
|
+
const domain = conflict(property)
|
|
732
|
+
const owner = runs.get(styleIndex)?.get(domain)
|
|
733
|
+
const reused =
|
|
734
|
+
owner === undefined ? undefined : owned.get(owner)?.get(domain)
|
|
735
|
+
if (reused) {
|
|
736
|
+
for (const identity of reused)
|
|
737
|
+
if (!names.includes(identity)) names.push(identity)
|
|
738
|
+
continue
|
|
739
|
+
}
|
|
740
|
+
|
|
608
741
|
const value = values
|
|
609
742
|
.map(
|
|
610
743
|
({ important, value }) =>
|
|
@@ -618,8 +751,54 @@ export function compile<
|
|
|
618
751
|
const shared =
|
|
619
752
|
!nestedComposition &&
|
|
620
753
|
!conditions.length &&
|
|
621
|
-
|
|
622
|
-
emit(body, property, shared, mode, {
|
|
754
|
+
ordered.get(domain) !== false
|
|
755
|
+
const identity = emit(body, property, shared, mode, {
|
|
756
|
+
conditions,
|
|
757
|
+
value,
|
|
758
|
+
})
|
|
759
|
+
if (identity === undefined) continue
|
|
760
|
+
|
|
761
|
+
const domains = owned.get(styleIndex) ?? new Map<string, string[]>()
|
|
762
|
+
domains.set(domain, [...(domains.get(domain) ?? []), identity])
|
|
763
|
+
owned.set(styleIndex, domains)
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Independent class lists never combine, so only declarations within one
|
|
768
|
+
// conflict domain need relative order. Equal domain bodies share one rule.
|
|
769
|
+
function share(style: Style.NamedStyle) {
|
|
770
|
+
const items = flatten(style)
|
|
771
|
+
if (!items) return atoms(style)
|
|
772
|
+
|
|
773
|
+
for (const domain of byDomain(items).values()) {
|
|
774
|
+
// Conditional rules stay contextual to keep their order against base declarations.
|
|
775
|
+
if (domain.some((item) => item.conditions.length)) {
|
|
776
|
+
for (const item of domain)
|
|
777
|
+
emit(
|
|
778
|
+
item.conditions.reduceRight(
|
|
779
|
+
(body, condition) => `${condition}{${body}}`,
|
|
780
|
+
item.value,
|
|
781
|
+
),
|
|
782
|
+
item.property,
|
|
783
|
+
false,
|
|
784
|
+
mode,
|
|
785
|
+
item,
|
|
786
|
+
)
|
|
787
|
+
continue
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const body = domain.map((item) => item.value).join('')
|
|
791
|
+
const identity = units.name(body, cssNamespace)
|
|
792
|
+
if (rules.has(identity) && rules.get(identity) !== body)
|
|
793
|
+
diagnostics.push({
|
|
794
|
+
code: 'identity_collision',
|
|
795
|
+
message:
|
|
796
|
+
'Generated class names conflict. Supply distinct config or style ids.',
|
|
797
|
+
path: [style.name],
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
rules.set(identity, body)
|
|
801
|
+
names.push(identity)
|
|
623
802
|
}
|
|
624
803
|
}
|
|
625
804
|
|
|
@@ -631,7 +810,7 @@ export function compile<
|
|
|
631
810
|
const key = conflict(declaration.property)
|
|
632
811
|
const reusable =
|
|
633
812
|
!style.rules &&
|
|
634
|
-
|
|
813
|
+
independent &&
|
|
635
814
|
groups.get(key) !== false &&
|
|
636
815
|
(occurrences.get(key) ?? 0) > 1
|
|
637
816
|
if (reusable) shared.push(declaration)
|
|
@@ -641,7 +820,8 @@ export function compile<
|
|
|
641
820
|
emit(shared.map(declarationBody).join(''), 'shared', false)
|
|
642
821
|
emit(local.map(declarationBody).join(''), 'style', false)
|
|
643
822
|
} else emit(nested(style), 'style', false)
|
|
644
|
-
} else
|
|
823
|
+
} else if (explicit === undefined && independent) share(style)
|
|
824
|
+
else atoms(style)
|
|
645
825
|
} catch (error) {
|
|
646
826
|
diagnostics.push({
|
|
647
827
|
code: 'invalid_declaration',
|
|
@@ -730,6 +910,7 @@ export function compile<
|
|
|
730
910
|
const result = {
|
|
731
911
|
...(scopes.resources.length ? { [Themes.shared]: scopes.resources } : {}),
|
|
732
912
|
...(contributionCss ? { contributionCss, scopedCss } : {}),
|
|
913
|
+
[ClassName.units]: units.used,
|
|
733
914
|
[ClassName.rules]: Object.freeze({
|
|
734
915
|
...Object.fromEntries(rules),
|
|
735
916
|
...Object.fromEntries(
|
|
@@ -744,6 +925,7 @@ export function compile<
|
|
|
744
925
|
vars: scopes.classes as Readonly<Record<themeName, string>>,
|
|
745
926
|
}
|
|
746
927
|
Object.defineProperty(result, ClassName.rules, { enumerable: false })
|
|
928
|
+
Object.defineProperty(result, ClassName.units, { enumerable: false })
|
|
747
929
|
return Object.freeze(result)
|
|
748
930
|
}
|
|
749
931
|
|
|
@@ -761,18 +943,20 @@ export declare namespace compile {
|
|
|
761
943
|
readonly [Themes.shared]?: 'all' | 'defaults' | undefined
|
|
762
944
|
/** Readable source bindings and consumer-owned configuration namespaces. */
|
|
763
945
|
readonly [ClassName.labels]?: ClassName.Labels | undefined
|
|
946
|
+
/** Names shared independent atomic rules, with one name table across a graph's modules. */
|
|
947
|
+
readonly [ClassName.units]?: ClassName.Units | undefined
|
|
764
948
|
/** Fixed class identities used by CSS-only consumers. */
|
|
765
949
|
readonly names?: Readonly<Record<string, string>> | undefined
|
|
766
950
|
/**
|
|
767
|
-
*
|
|
768
|
-
* Independent
|
|
769
|
-
*
|
|
951
|
+
* Ordered by default, preserving precedence across combined class lists.
|
|
952
|
+
* Independent shares applications and atomic declarations, so resolve
|
|
953
|
+
* composition first and never combine its lists.
|
|
770
954
|
*/
|
|
955
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
771
956
|
/** Eager module-level stylesheet contributions, supplied as static data. */
|
|
772
957
|
readonly contributions?: readonly Contribution[] | undefined
|
|
773
958
|
/** CSS representation; atomic declarations are the default. */
|
|
774
959
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
775
|
-
readonly composition?: 'independent' | 'ordered' | undefined
|
|
776
960
|
/** Retains live definitions for development updates. */
|
|
777
961
|
readonly development?: boolean | undefined
|
|
778
962
|
/**
|
|
@@ -797,6 +981,8 @@ export declare namespace compile {
|
|
|
797
981
|
> = {
|
|
798
982
|
/** Exact emitted rule bodies retained for graph-wide collision diagnostics. */
|
|
799
983
|
readonly [ClassName.rules]: Readonly<Record<string, string>>
|
|
984
|
+
/** Shared rule names chosen by this compilation, for graph cache validation. */
|
|
985
|
+
readonly [ClassName.units]: ClassName.Units['used']
|
|
800
986
|
/** Shared token resources omitted from this module's stylesheet. */
|
|
801
987
|
readonly [Themes.shared]?: readonly Themes.Resource[] | undefined
|
|
802
988
|
/** Contribution text separated for graph-wide hoisting. */
|
|
@@ -95,6 +95,120 @@ export function selector(value: string): string {
|
|
|
95
95
|
/** Carries exact emitted rule bodies for graph-wide collision checks. */
|
|
96
96
|
export const rules = Symbol('zyzz.css.rules')
|
|
97
97
|
|
|
98
|
+
/** Carries a compilation's unit naming scope in, and its naming decisions out. */
|
|
99
|
+
export const units = Symbol('zyzz.css.units')
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Marks a graph compiled once with every module, whose stylesheets load together.
|
|
103
|
+
* Only its modules may reuse names that unrelated modules introduce.
|
|
104
|
+
*/
|
|
105
|
+
export const complete = Symbol('zyzz.css.complete')
|
|
106
|
+
|
|
107
|
+
/** One compilation's unit names. */
|
|
108
|
+
export type Units = {
|
|
109
|
+
/** Names a declaration unit, reusing a visible earlier name for an equal body. */
|
|
110
|
+
readonly name: (body: string, namespace: string | undefined) => string
|
|
111
|
+
/** Names chosen so far, keyed by namespace and body, and whether each was reused. */
|
|
112
|
+
readonly used: ReadonlyMap<string, Decision>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A naming decision recorded so cached output can be validated against a later build. */
|
|
116
|
+
export type Decision = {
|
|
117
|
+
/** Whether the name was introduced by an earlier compilation. */
|
|
118
|
+
readonly borrowed: boolean
|
|
119
|
+
/** Emitted class name. */
|
|
120
|
+
readonly name: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Shares unit names across the compilations of one graph. */
|
|
124
|
+
export function registry(): registry.ReturnType {
|
|
125
|
+
const names = new Map<string, { name: string; owner: string }>()
|
|
126
|
+
|
|
127
|
+
// A borrowed name stays tied to its first owner, so later modules can resolve it.
|
|
128
|
+
function borrow(key: string, visible?: (owner: string) => boolean) {
|
|
129
|
+
const previous = names.get(key)
|
|
130
|
+
if (previous && (visible?.(previous.owner) ?? true)) return previous.name
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
restore(options) {
|
|
136
|
+
for (const [key, decision] of options.used) {
|
|
137
|
+
const name = borrow(key, options.visible)
|
|
138
|
+
if ((name !== undefined) !== decision.borrowed) return false
|
|
139
|
+
if (name !== undefined && name !== decision.name) return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const [key, decision] of options.used)
|
|
143
|
+
if (!names.has(key))
|
|
144
|
+
names.set(key, { name: decision.name, owner: options.owner })
|
|
145
|
+
|
|
146
|
+
return true
|
|
147
|
+
},
|
|
148
|
+
scope(options) {
|
|
149
|
+
const local = new Map<string, number>()
|
|
150
|
+
const used = new Map<string, Decision>()
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
name(body, namespace) {
|
|
154
|
+
const key = `${namespace ?? ''}\n${body}`
|
|
155
|
+
|
|
156
|
+
// Every distinct body consumes a slot, so own names never depend on reuse.
|
|
157
|
+
let index = local.get(key)
|
|
158
|
+
if (index === undefined) {
|
|
159
|
+
index = local.size
|
|
160
|
+
local.set(key, index)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const borrowed = borrow(key, options.visible)
|
|
164
|
+
// The underscore keeps unit names apart from readable names and anonymous scopes.
|
|
165
|
+
const name =
|
|
166
|
+
borrowed ??
|
|
167
|
+
`z_${namespace ? `${namespace}-` : ''}${options.qualifier}${index.toString(36)}`
|
|
168
|
+
|
|
169
|
+
if (!names.has(key)) names.set(key, { name, owner: options.owner })
|
|
170
|
+
used.set(key, { borrowed: borrowed !== undefined, name })
|
|
171
|
+
|
|
172
|
+
return name
|
|
173
|
+
},
|
|
174
|
+
used,
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Graph-wide unit naming contracts. */
|
|
181
|
+
export declare namespace registry {
|
|
182
|
+
/** Visibility and identity of one compilation in the shared name table. */
|
|
183
|
+
type Options = {
|
|
184
|
+
/** Compilation identity recorded on the names it introduces. */
|
|
185
|
+
readonly owner: string
|
|
186
|
+
/** Limits reuse to names introduced by these owners. Defaults to every owner. */
|
|
187
|
+
readonly visible?: ((owner: string) => boolean) | undefined
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Creates per-compilation naming scopes over one shared name table. */
|
|
191
|
+
type ReturnType = {
|
|
192
|
+
/**
|
|
193
|
+
* Registers a cached compilation's names. Returns false, registering nothing,
|
|
194
|
+
* when compiling it now would choose different names.
|
|
195
|
+
*/
|
|
196
|
+
readonly restore: (
|
|
197
|
+
options: Options & {
|
|
198
|
+
/** Decisions recorded by the cached compilation. */
|
|
199
|
+
readonly used: ReadonlyMap<string, Decision>
|
|
200
|
+
},
|
|
201
|
+
) => boolean
|
|
202
|
+
/** Names units for one compilation, whose qualifier keeps new names distinct. */
|
|
203
|
+
readonly scope: (
|
|
204
|
+
options: Options & {
|
|
205
|
+
/** Fixed prefix distinguishing this compilation's new names. */
|
|
206
|
+
readonly qualifier: string
|
|
207
|
+
},
|
|
208
|
+
) => Units
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
98
212
|
/** Carries authored names between source extraction and CSS emission. */
|
|
99
213
|
export const labels = Symbol('zyzz.css.labels')
|
|
100
214
|
|