zyzz 0.0.24 → 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 +8 -0
- 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 +32 -23
- 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/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +31 -13
- package/dist/unplugin/index.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/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 +21 -0
- 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 +31 -24
- package/src/node/internal/Contracts.ts +384 -0
- package/src/unplugin/index.ts +35 -13
- package/src/vite/index.ts +35 -36
- package/src/web/Css.ts +228 -42
- package/src/web/internal/ClassName.ts +114 -0
|
@@ -34,6 +34,10 @@ export function create(
|
|
|
34
34
|
}
|
|
35
35
|
direct: Source.Call | undefined
|
|
36
36
|
members: Map<string, Source.Call>
|
|
37
|
+
/** Namespace statement start and member identifier ends by extracted name. */
|
|
38
|
+
namespace?: Namespace | undefined
|
|
39
|
+
/** Offset after which top-level reads observe an initialized binding. */
|
|
40
|
+
ready: number
|
|
37
41
|
}[] = []
|
|
38
42
|
|
|
39
43
|
for (const statement of program.body) {
|
|
@@ -43,6 +47,7 @@ export function create(
|
|
|
43
47
|
statement.body?.type === 'TSModuleBlock'
|
|
44
48
|
) {
|
|
45
49
|
const members = new Map<string, Source.Call>()
|
|
50
|
+
const ends = new Map<string, number>()
|
|
46
51
|
let valid = true
|
|
47
52
|
|
|
48
53
|
for (const member of statement.body.body) {
|
|
@@ -71,6 +76,7 @@ export function create(
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
members.set(declaration.id.name, call)
|
|
79
|
+
ends.set(call.name, declaration.id.end)
|
|
74
80
|
memberBindings.set(declaration.id.start, call.name)
|
|
75
81
|
}
|
|
76
82
|
}
|
|
@@ -80,6 +86,8 @@ export function create(
|
|
|
80
86
|
declaration: { id: statement.id },
|
|
81
87
|
direct: undefined,
|
|
82
88
|
members,
|
|
89
|
+
namespace: { ends, start: statement.start },
|
|
90
|
+
ready: statement.end,
|
|
83
91
|
})
|
|
84
92
|
|
|
85
93
|
continue
|
|
@@ -142,6 +150,7 @@ export function create(
|
|
|
142
150
|
declaration: { ...declaration, id: declaration.id },
|
|
143
151
|
direct,
|
|
144
152
|
members,
|
|
153
|
+
ready: declaration.end,
|
|
145
154
|
})
|
|
146
155
|
}
|
|
147
156
|
}
|
|
@@ -149,6 +158,9 @@ export function create(
|
|
|
149
158
|
if (!candidates.length) return undefined
|
|
150
159
|
|
|
151
160
|
const lexical = new Set<Ast.Node>()
|
|
161
|
+
// Reads inside functions, classes, or namespaces may run before initialization.
|
|
162
|
+
const deferred = new Set<Ast.Node>()
|
|
163
|
+
const terminals = new Set<Ast.Node>()
|
|
152
164
|
const scope = new Scope.Tracker({ preserveExitedScopes: true })
|
|
153
165
|
Walker.walk(program, { scopeTracker: scope })
|
|
154
166
|
scope.freeze()
|
|
@@ -185,8 +197,23 @@ export function create(
|
|
|
185
197
|
candidates.some(
|
|
186
198
|
(candidate) => candidate.declaration.id.start === id.start,
|
|
187
199
|
)
|
|
188
|
-
)
|
|
200
|
+
) {
|
|
189
201
|
lexical.add(node)
|
|
202
|
+
if (terminal(ancestors)) terminals.add(node)
|
|
203
|
+
if (
|
|
204
|
+
ancestors.some((node) =>
|
|
205
|
+
[
|
|
206
|
+
'ArrowFunctionExpression',
|
|
207
|
+
'ClassDeclaration',
|
|
208
|
+
'ClassExpression',
|
|
209
|
+
'FunctionDeclaration',
|
|
210
|
+
'FunctionExpression',
|
|
211
|
+
'TSModuleDeclaration',
|
|
212
|
+
].includes(node.type),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
deferred.add(node)
|
|
216
|
+
}
|
|
190
217
|
},
|
|
191
218
|
leave() {
|
|
192
219
|
ancestors.pop()
|
|
@@ -200,6 +227,80 @@ export function create(
|
|
|
200
227
|
>(candidates.map(({ declaration }) => [declaration.id.name, []]))
|
|
201
228
|
let evaluation = false
|
|
202
229
|
|
|
230
|
+
// Returns undefined when any read escapes. Recipes, dynamic styles, and
|
|
231
|
+
// applications with overrides keep their runtime calls.
|
|
232
|
+
function collect(candidate: (typeof candidates)[number]):
|
|
233
|
+
| {
|
|
234
|
+
readonly applications: readonly Application[]
|
|
235
|
+
readonly called: ReadonlySet<string>
|
|
236
|
+
}
|
|
237
|
+
| undefined {
|
|
238
|
+
const { declaration, direct, members } = candidate
|
|
239
|
+
const applications: Application[] = []
|
|
240
|
+
const called = new Set<string>()
|
|
241
|
+
|
|
242
|
+
for (const reference of references.get(declaration.id.name) ?? []) {
|
|
243
|
+
if (reference === declaration.id) continue
|
|
244
|
+
|
|
245
|
+
let callee: Ast.Node = reference
|
|
246
|
+
let call = direct
|
|
247
|
+
|
|
248
|
+
if (!direct) {
|
|
249
|
+
const member = parents.get(reference)
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
member?.type !== 'MemberExpression' ||
|
|
253
|
+
member.object !== reference ||
|
|
254
|
+
member.computed ||
|
|
255
|
+
member.optional ||
|
|
256
|
+
member.property.type !== 'Identifier'
|
|
257
|
+
)
|
|
258
|
+
return undefined
|
|
259
|
+
|
|
260
|
+
call = members.get(member.property.name)
|
|
261
|
+
callee = member
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
call?.composition ||
|
|
266
|
+
call?.runtimeComposition ||
|
|
267
|
+
(!options.dynamic && (call?.slots || call?.recipe))
|
|
268
|
+
)
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
const application = parents.get(callee)
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
!call ||
|
|
275
|
+
application?.type !== 'CallExpression' ||
|
|
276
|
+
application.callee !== callee ||
|
|
277
|
+
application.optional
|
|
278
|
+
)
|
|
279
|
+
return undefined
|
|
280
|
+
|
|
281
|
+
// An override needs the runtime callable, but exposes no other definition.
|
|
282
|
+
if (!options.dynamic && application.arguments.length) {
|
|
283
|
+
called.add(call.name)
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
applications.push({
|
|
288
|
+
calleeEnd: callee.end,
|
|
289
|
+
end: application.end,
|
|
290
|
+
initialized:
|
|
291
|
+
!deferred.has(reference) &&
|
|
292
|
+
reference.start >= candidate.ready &&
|
|
293
|
+
(!candidate.namespace ||
|
|
294
|
+
namespaceCounts.get(declaration.id.name) === 1),
|
|
295
|
+
name: call.name,
|
|
296
|
+
start: application.start,
|
|
297
|
+
terminal: terminals.has(reference),
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { applications, called }
|
|
302
|
+
}
|
|
303
|
+
|
|
203
304
|
return {
|
|
204
305
|
dead() {
|
|
205
306
|
if (evaluation) return new Set<string>()
|
|
@@ -255,6 +356,58 @@ export function create(
|
|
|
255
356
|
|
|
256
357
|
return result
|
|
257
358
|
},
|
|
359
|
+
definitions() {
|
|
360
|
+
const namespaces: Namespace[] = []
|
|
361
|
+
const values = new Map<string, boolean>()
|
|
362
|
+
|
|
363
|
+
if (evaluation) return { namespaces, values }
|
|
364
|
+
|
|
365
|
+
const counts = new Map<string, number>()
|
|
366
|
+
|
|
367
|
+
for (const { declaration } of candidates)
|
|
368
|
+
counts.set(
|
|
369
|
+
declaration.id.name,
|
|
370
|
+
(counts.get(declaration.id.name) ?? 0) + 1,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
for (const candidate of candidates) {
|
|
374
|
+
const name = candidate.declaration.id.name
|
|
375
|
+
if (counts.get(name)! > 1 || (namespaceCounts.get(name) ?? 0) > 1)
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
const result = collect(candidate)
|
|
379
|
+
if (!result) continue
|
|
380
|
+
|
|
381
|
+
let erased = !!candidate.namespace
|
|
382
|
+
|
|
383
|
+
for (const call of candidate.direct
|
|
384
|
+
? [candidate.direct]
|
|
385
|
+
: candidate.members.values()) {
|
|
386
|
+
if (
|
|
387
|
+
call.composition ||
|
|
388
|
+
call.runtimeComposition ||
|
|
389
|
+
call.slots ||
|
|
390
|
+
call.recipe ||
|
|
391
|
+
result.called.has(call.name)
|
|
392
|
+
) {
|
|
393
|
+
erased = false
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const initialized = result.applications.every(
|
|
398
|
+
(application) =>
|
|
399
|
+
application.name !== call.name || application.initialized,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
values.set(call.name, initialized)
|
|
403
|
+
erased &&= initialized
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (erased) namespaces.push(candidate.namespace!)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return { namespaces, values }
|
|
410
|
+
},
|
|
258
411
|
enter(node, parent) {
|
|
259
412
|
if (
|
|
260
413
|
node.type === 'MemberExpression' &&
|
|
@@ -280,68 +433,9 @@ export function create(
|
|
|
280
433
|
find() {
|
|
281
434
|
if (evaluation) return []
|
|
282
435
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const applications: Application[] = []
|
|
287
|
-
let valid = true
|
|
288
|
-
|
|
289
|
-
for (const reference of references.get(declaration.id.name) ?? []) {
|
|
290
|
-
if (reference === declaration.id) continue
|
|
291
|
-
|
|
292
|
-
let callee: Ast.Node = reference
|
|
293
|
-
let call = direct
|
|
294
|
-
|
|
295
|
-
if (!direct) {
|
|
296
|
-
const member = parents.get(reference)
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
member?.type !== 'MemberExpression' ||
|
|
300
|
-
member.object !== reference ||
|
|
301
|
-
member.computed ||
|
|
302
|
-
member.optional ||
|
|
303
|
-
member.property.type !== 'Identifier'
|
|
304
|
-
) {
|
|
305
|
-
valid = false
|
|
306
|
-
break
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
call = members.get(member.property.name)
|
|
310
|
-
callee = member
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
call?.composition ||
|
|
315
|
-
call?.runtimeComposition ||
|
|
316
|
-
(!options.dynamic && (call?.slots || call?.recipe))
|
|
317
|
-
)
|
|
318
|
-
continue
|
|
319
|
-
|
|
320
|
-
const application = parents.get(callee)
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
!call ||
|
|
324
|
-
application?.type !== 'CallExpression' ||
|
|
325
|
-
application.callee !== callee ||
|
|
326
|
-
application.optional ||
|
|
327
|
-
(!options.dynamic && application.arguments.length)
|
|
328
|
-
) {
|
|
329
|
-
valid = false
|
|
330
|
-
break
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
applications.push({
|
|
334
|
-
calleeEnd: callee.end,
|
|
335
|
-
end: application.end,
|
|
336
|
-
name: call.name,
|
|
337
|
-
start: application.start,
|
|
338
|
-
})
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (valid) result.push(...applications)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return result
|
|
436
|
+
return candidates.flatMap(
|
|
437
|
+
(candidate) => collect(candidate)?.applications ?? [],
|
|
438
|
+
)
|
|
345
439
|
},
|
|
346
440
|
}
|
|
347
441
|
}
|
|
@@ -352,16 +446,37 @@ type Application = {
|
|
|
352
446
|
readonly calleeEnd: number
|
|
353
447
|
/** End of the application. */
|
|
354
448
|
readonly end: number
|
|
449
|
+
/** Whether the read runs at top level after its definition initializes. */
|
|
450
|
+
readonly initialized: boolean
|
|
355
451
|
/** Extracted class identity. */
|
|
356
452
|
readonly name: string
|
|
357
453
|
/** Start of the application. */
|
|
358
454
|
readonly start: number
|
|
455
|
+
/** Whether a DOM element receives the props alone, so its classes meet no other style's. */
|
|
456
|
+
readonly terminal: boolean
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** A top-level namespace whose members are all static definitions. */
|
|
460
|
+
type Namespace = {
|
|
461
|
+
/** Member identifier ends keyed by extracted name. */
|
|
462
|
+
readonly ends: ReadonlyMap<string, number>
|
|
463
|
+
/** Start of the namespace statement. */
|
|
464
|
+
readonly start: number
|
|
359
465
|
}
|
|
360
466
|
|
|
361
467
|
/** Binding references gathered during the transform's existing walk. */
|
|
362
468
|
type Collector = {
|
|
363
469
|
/** Definitions with no observable value reads, retaining escaped containers. */
|
|
364
470
|
dead: () => ReadonlySet<string>
|
|
471
|
+
/**
|
|
472
|
+
* Static definitions whose every read is a found application, keyed by
|
|
473
|
+
* extracted name with whether every read is initialized. Namespaces list
|
|
474
|
+
* those whose definitions all qualify with initialized reads.
|
|
475
|
+
*/
|
|
476
|
+
definitions: () => {
|
|
477
|
+
readonly namespaces: readonly Namespace[]
|
|
478
|
+
readonly values: ReadonlyMap<string, boolean>
|
|
479
|
+
}
|
|
365
480
|
/** Records relevant reads and their immediate parents. */
|
|
366
481
|
enter: (node: Ast.Node, parent: Ast.Node | null | undefined) => void
|
|
367
482
|
/** Resolves applications after traversal completes. */
|
|
@@ -376,3 +491,64 @@ export declare namespace create {
|
|
|
376
491
|
readonly dynamic?: boolean | undefined
|
|
377
492
|
}
|
|
378
493
|
}
|
|
494
|
+
|
|
495
|
+
// Matches a no-argument application spread onto, or read as the class of, a DOM element.
|
|
496
|
+
function terminal(path: readonly Ast.Node[]): boolean {
|
|
497
|
+
let index = path.length - 1
|
|
498
|
+
let node = path[index]
|
|
499
|
+
const member = path[index - 1]
|
|
500
|
+
if (member?.type === 'MemberExpression' && member.object === node)
|
|
501
|
+
node = path[--index]
|
|
502
|
+
|
|
503
|
+
const call = path[--index]
|
|
504
|
+
if (
|
|
505
|
+
call?.type !== 'CallExpression' ||
|
|
506
|
+
call.callee !== node ||
|
|
507
|
+
call.arguments.length
|
|
508
|
+
)
|
|
509
|
+
return false
|
|
510
|
+
|
|
511
|
+
const parent = path[--index]
|
|
512
|
+
if (parent?.type === 'JSXSpreadAttribute')
|
|
513
|
+
return parent.argument === call && alone(path[index - 1], parent)
|
|
514
|
+
|
|
515
|
+
if (
|
|
516
|
+
parent?.type !== 'MemberExpression' ||
|
|
517
|
+
parent.object !== call ||
|
|
518
|
+
parent.computed ||
|
|
519
|
+
parent.property.type !== 'Identifier' ||
|
|
520
|
+
!/^class(?:Name)?$/.test(parent.property.name)
|
|
521
|
+
)
|
|
522
|
+
return false
|
|
523
|
+
|
|
524
|
+
const container = path[--index]
|
|
525
|
+
const attribute = path[--index]
|
|
526
|
+
return (
|
|
527
|
+
container?.type === 'JSXExpressionContainer' &&
|
|
528
|
+
container.expression === parent &&
|
|
529
|
+
attribute?.type === 'JSXAttribute' &&
|
|
530
|
+
attribute.value === container &&
|
|
531
|
+
attribute.name.type === 'JSXIdentifier' &&
|
|
532
|
+
/^class(?:Name)?$/.test(attribute.name.name) &&
|
|
533
|
+
alone(path[index - 1], attribute)
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Other spreads or class attributes could add another style's classes.
|
|
538
|
+
function alone(element: Ast.Node | undefined, own: Ast.Node): boolean {
|
|
539
|
+
return (
|
|
540
|
+
element?.type === 'JSXOpeningElement' &&
|
|
541
|
+
element.name.type === 'JSXIdentifier' &&
|
|
542
|
+
/^[a-z]/.test(element.name.name) &&
|
|
543
|
+
element.attributes.every(
|
|
544
|
+
(attribute) =>
|
|
545
|
+
attribute === own ||
|
|
546
|
+
(attribute.type === 'JSXAttribute' &&
|
|
547
|
+
!(
|
|
548
|
+
attribute.name.type === 'JSXIdentifier'
|
|
549
|
+
? attribute.name.name
|
|
550
|
+
: attribute.name.namespace.name
|
|
551
|
+
).startsWith('class')),
|
|
552
|
+
)
|
|
553
|
+
)
|
|
554
|
+
}
|
|
@@ -405,7 +405,15 @@ export function collect(options: collect.Options) {
|
|
|
405
405
|
node,
|
|
406
406
|
)
|
|
407
407
|
const name = `composition-${options.identity}-${node.start}`
|
|
408
|
+
// A composition of independent styles keeps their sharing contract.
|
|
409
|
+
const independent = (calls: readonly Source.Call[]) =>
|
|
410
|
+
calls.every(
|
|
411
|
+
(call) => styles.get(call.name)!.composition === 'independent',
|
|
412
|
+
)
|
|
413
|
+
? ({ composition: 'independent' } as const)
|
|
414
|
+
: {}
|
|
408
415
|
const style: Style.NamedStyle = {
|
|
416
|
+
...independent(selected),
|
|
409
417
|
cssOutput: 'atomic',
|
|
410
418
|
name,
|
|
411
419
|
declarations: [],
|
|
@@ -580,6 +588,7 @@ export function collect(options: collect.Options) {
|
|
|
580
588
|
.join(' '),
|
|
581
589
|
},
|
|
582
590
|
style: {
|
|
591
|
+
...independent(included),
|
|
583
592
|
cssOutput: 'atomic',
|
|
584
593
|
name,
|
|
585
594
|
declarations: [],
|
|
@@ -149,6 +149,9 @@ export function collect(options: collect.Options): Themes.Link {
|
|
|
149
149
|
: {}),
|
|
150
150
|
[Token.identity]: identity,
|
|
151
151
|
...(input.id !== undefined ? { [Token.complete]: true } : {}),
|
|
152
|
+
...(input.composition !== undefined
|
|
153
|
+
? { composition: input.composition }
|
|
154
|
+
: {}),
|
|
152
155
|
cssOutput: input.cssOutput ?? 'atomic',
|
|
153
156
|
...(input.defaultLayer !== undefined
|
|
154
157
|
? { defaultLayer: input.defaultLayer }
|
|
@@ -243,6 +246,7 @@ export function collect(options: collect.Options): Themes.Link {
|
|
|
243
246
|
return {}
|
|
244
247
|
})(),
|
|
245
248
|
...(input.id !== undefined ? { id: input.id } : {}),
|
|
249
|
+
...(input.composition ? { composition: input.composition } : {}),
|
|
246
250
|
...(input.cssOutput ? { cssOutput: input.cssOutput } : {}),
|
|
247
251
|
...(input.defaultLayer !== undefined
|
|
248
252
|
? { defaultLayer: input.defaultLayer }
|
|
@@ -26,7 +26,7 @@ export function read(
|
|
|
26
26
|
if (
|
|
27
27
|
![
|
|
28
28
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
|
29
|
-
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
|
|
29
|
+
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
|
|
30
30
|
].includes(data.version as number)
|
|
31
31
|
)
|
|
32
32
|
throw new Error('Unsupported Zyzz contract version.')
|
|
@@ -149,6 +149,22 @@ export function read(
|
|
|
149
149
|
| 'grouped'
|
|
150
150
|
| undefined
|
|
151
151
|
|
|
152
|
+
if (entry.composition !== undefined) {
|
|
153
|
+
if ((data.version as number) < 34)
|
|
154
|
+
throw new Error(
|
|
155
|
+
'Composition modes require contract version 34 or later.',
|
|
156
|
+
)
|
|
157
|
+
if (
|
|
158
|
+
entry.composition !== 'independent' &&
|
|
159
|
+
entry.composition !== 'ordered'
|
|
160
|
+
)
|
|
161
|
+
throw new Error('Invalid packed composition mode.')
|
|
162
|
+
}
|
|
163
|
+
const composition = entry.composition as
|
|
164
|
+
| 'independent'
|
|
165
|
+
| 'ordered'
|
|
166
|
+
| undefined
|
|
167
|
+
|
|
152
168
|
if (entry.defaultLayer !== undefined) {
|
|
153
169
|
if ((data.version as number) < 27)
|
|
154
170
|
throw new Error('Default layers require contract version 27 or later.')
|
|
@@ -180,6 +196,14 @@ export function read(
|
|
|
180
196
|
'Conflicting packed shorthand mappings for one theme identity.',
|
|
181
197
|
)
|
|
182
198
|
|
|
199
|
+
if (
|
|
200
|
+
contract &&
|
|
201
|
+
(contract.composition ?? 'ordered') !== (composition ?? 'ordered')
|
|
202
|
+
)
|
|
203
|
+
throw new Error(
|
|
204
|
+
'Conflicting packed composition modes for one theme identity.',
|
|
205
|
+
)
|
|
206
|
+
|
|
183
207
|
if (
|
|
184
208
|
contract &&
|
|
185
209
|
(contract.cssOutput ?? 'atomic') !== (cssOutput ?? 'atomic')
|
|
@@ -204,6 +228,7 @@ export function read(
|
|
|
204
228
|
...(entry.shorthands !== undefined
|
|
205
229
|
? { shorthands: Shorthands.read(entry.shorthands) }
|
|
206
230
|
: {}),
|
|
231
|
+
...(composition ? { composition } : {}),
|
|
207
232
|
...(cssOutput ? { cssOutput } : {}),
|
|
208
233
|
...(defaultLayer !== undefined ? { defaultLayer } : {}),
|
|
209
234
|
[Token.complete]: true,
|
|
@@ -333,7 +358,7 @@ export function read(
|
|
|
333
358
|
if (
|
|
334
359
|
![
|
|
335
360
|
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
|
336
|
-
27, 28, 29, 30, 31, 32, 33,
|
|
361
|
+
27, 28, 29, 30, 31, 32, 33, 34,
|
|
337
362
|
].includes(data.version as number) ||
|
|
338
363
|
![
|
|
339
364
|
'cssFunction',
|
|
@@ -514,6 +539,14 @@ export function read(
|
|
|
514
539
|
'Configuration CSS output disagrees with linked theme metadata.',
|
|
515
540
|
)
|
|
516
541
|
|
|
542
|
+
if (
|
|
543
|
+
(options.composition ?? 'ordered') !==
|
|
544
|
+
(definition[Token.definition].contract.composition ?? 'ordered')
|
|
545
|
+
)
|
|
546
|
+
throw new Error(
|
|
547
|
+
'Configuration composition disagrees with linked theme metadata.',
|
|
548
|
+
)
|
|
549
|
+
|
|
517
550
|
if (
|
|
518
551
|
options.defaultLayer !==
|
|
519
552
|
definition[Token.definition].contract.defaultLayer
|
|
@@ -829,6 +862,9 @@ export function write(
|
|
|
829
862
|
...(theme[Token.definition].contract.defaultLayer !== undefined
|
|
830
863
|
? { defaultLayer: theme[Token.definition].contract.defaultLayer }
|
|
831
864
|
: {}),
|
|
865
|
+
...(theme[Token.definition].contract.composition
|
|
866
|
+
? { composition: theme[Token.definition].contract.composition }
|
|
867
|
+
: {}),
|
|
832
868
|
...(theme[Token.definition].contract.cssOutput
|
|
833
869
|
? { cssOutput: theme[Token.definition].contract.cssOutput }
|
|
834
870
|
: {}),
|
|
@@ -843,13 +879,29 @@ export function write(
|
|
|
843
879
|
},
|
|
844
880
|
]),
|
|
845
881
|
),
|
|
846
|
-
version:
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
882
|
+
version: (() => {
|
|
883
|
+
if (
|
|
884
|
+
Object.values(themes).some(
|
|
885
|
+
(theme) => theme[Token.definition].contract.composition,
|
|
886
|
+
) ||
|
|
887
|
+
Object.values(links).some(composed)
|
|
888
|
+
)
|
|
889
|
+
return 34
|
|
890
|
+
|
|
891
|
+
if (Object.values(links).some(nativeCatalog)) return 33
|
|
892
|
+
if (Object.values(links).some(nativeProvider)) return 32
|
|
893
|
+
|
|
894
|
+
return 31
|
|
895
|
+
})(),
|
|
851
896
|
})
|
|
852
897
|
|
|
898
|
+
function composed(link: Themes.Link): boolean {
|
|
899
|
+
return (
|
|
900
|
+
Boolean(link.style?.style.composition) ||
|
|
901
|
+
Object.values(link.members ?? {}).some(composed)
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
|
|
853
905
|
function nativeProvider(link: Themes.Link): boolean {
|
|
854
906
|
return (
|
|
855
907
|
link.call.nativeProvider === true ||
|
|
@@ -160,7 +160,16 @@ export function read(
|
|
|
160
160
|
item.cssOutput !== 'grouped'
|
|
161
161
|
)
|
|
162
162
|
throw new Error('Invalid packed style CSS output mode.')
|
|
163
|
+
if (
|
|
164
|
+
item.composition !== undefined &&
|
|
165
|
+
(version < 34 ||
|
|
166
|
+
(item.composition !== 'independent' && item.composition !== 'ordered'))
|
|
167
|
+
)
|
|
168
|
+
throw new Error('Invalid packed style composition mode.')
|
|
163
169
|
return {
|
|
170
|
+
...(item.composition
|
|
171
|
+
? { composition: item.composition as 'independent' | 'ordered' }
|
|
172
|
+
: {}),
|
|
164
173
|
...(item.cssOutput
|
|
165
174
|
? { cssOutput: item.cssOutput as 'atomic' | 'grouped' }
|
|
166
175
|
: {}),
|
|
@@ -494,13 +494,31 @@ export function collect(program: Ast.Program, options: collect.Options) {
|
|
|
494
494
|
root = root.object
|
|
495
495
|
}
|
|
496
496
|
if (root.type !== 'Identifier') return undefined
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (
|
|
503
|
-
|
|
497
|
+
const name = root.name
|
|
498
|
+
let value: unknown = (() => {
|
|
499
|
+
if (derived?.name === name) return derived.vars
|
|
500
|
+
|
|
501
|
+
const call = names.get(name)
|
|
502
|
+
if (call) {
|
|
503
|
+
if (
|
|
504
|
+
!call.variableSet &&
|
|
505
|
+
!call.directVariables &&
|
|
506
|
+
!['vars', 'tokens'].includes(path.shift()!)
|
|
507
|
+
)
|
|
508
|
+
return undefined
|
|
509
|
+
return themes[call.name]?.tokens
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Config-bound vars read the configured variables, so they follow set scoping.
|
|
513
|
+
const config = configs.get(name)
|
|
514
|
+
if (
|
|
515
|
+
!config?.call.variableConfig ||
|
|
516
|
+
(!config.call.selection && path.shift() !== 'vars')
|
|
517
|
+
)
|
|
518
|
+
return undefined
|
|
519
|
+
return themes[config.call.name]?.tokens
|
|
520
|
+
})()
|
|
521
|
+
if (value === undefined) return undefined
|
|
504
522
|
for (const key of path)
|
|
505
523
|
value =
|
|
506
524
|
value && typeof value === 'object'
|
|
@@ -150,6 +150,7 @@ export function create(
|
|
|
150
150
|
for (const key of Object.keys(input))
|
|
151
151
|
if (
|
|
152
152
|
![
|
|
153
|
+
'composition',
|
|
153
154
|
'cssOutput',
|
|
154
155
|
'id',
|
|
155
156
|
'defaultLayer',
|
|
@@ -190,6 +191,13 @@ export function create(
|
|
|
190
191
|
)
|
|
191
192
|
throw new InvalidError('cssOutput must be atomic or grouped.')
|
|
192
193
|
|
|
194
|
+
if (
|
|
195
|
+
input.composition !== undefined &&
|
|
196
|
+
input.composition !== 'independent' &&
|
|
197
|
+
input.composition !== 'ordered'
|
|
198
|
+
)
|
|
199
|
+
throw new InvalidError('composition must be independent or ordered.')
|
|
200
|
+
|
|
193
201
|
if (input.theme !== undefined && input.themes !== undefined)
|
|
194
202
|
throw new InvalidError('Use either theme or themes, not both.')
|
|
195
203
|
|
|
@@ -253,6 +261,9 @@ export function create(
|
|
|
253
261
|
|
|
254
262
|
const contract = Object.freeze({
|
|
255
263
|
...(variableMode ? { variableSet: true, mappings: variableMappings } : {}),
|
|
264
|
+
...(input.composition !== undefined
|
|
265
|
+
? { composition: input.composition as 'independent' | 'ordered' }
|
|
266
|
+
: {}),
|
|
256
267
|
...(input.defaultLayer !== undefined
|
|
257
268
|
? { defaultLayer: input.defaultLayer as string }
|
|
258
269
|
: {}),
|
|
@@ -452,6 +463,11 @@ export function create(
|
|
|
452
463
|
export declare namespace create {
|
|
453
464
|
/** Optional layer names and mutually exclusive theme modes. */
|
|
454
465
|
type Options = {
|
|
466
|
+
/**
|
|
467
|
+
* Whether bound styles' class lists may combine. Independent atomic styles
|
|
468
|
+
* share rules for repeated declarations, so combine them only with `cx`.
|
|
469
|
+
*/
|
|
470
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
455
471
|
/** CSS representation inherited by bound helpers; atomic by default. */
|
|
456
472
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
457
473
|
/** Fallback CSS layer for bound styles and variants; unlayered when omitted. */
|
|
@@ -837,6 +853,11 @@ type Validated<options> = Record<
|
|
|
837
853
|
|
|
838
854
|
/** Vars, optional named alternatives, and category-to-property mappings. */
|
|
839
855
|
export type VariableOptions = {
|
|
856
|
+
/**
|
|
857
|
+
* Whether bound styles' class lists may combine. Independent atomic styles
|
|
858
|
+
* share rules for repeated declarations, so combine them only with `cx`.
|
|
859
|
+
*/
|
|
860
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
840
861
|
/** CSS representation inherited by bound helpers; atomic by default. */
|
|
841
862
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
842
863
|
/** Fallback CSS layer for bound styles and variants; unlayered when omitted. */
|
package/src/internal/Token.ts
CHANGED
|
@@ -393,6 +393,8 @@ export type Contract = {
|
|
|
393
393
|
readonly variableSet?: boolean | undefined
|
|
394
394
|
/** Configuration-local category-to-property mappings. */
|
|
395
395
|
readonly mappings?: VariableSets.Mappings | false | undefined
|
|
396
|
+
/** Whether bound styles' class lists combine, retained by configuration-bound handles. */
|
|
397
|
+
readonly composition?: 'independent' | 'ordered' | undefined
|
|
396
398
|
/** Web emission mode retained by configuration-bound theme handles. */
|
|
397
399
|
readonly cssOutput?: 'atomic' | 'grouped' | undefined
|
|
398
400
|
/** Fallback layer for declarations without an explicit layer. */
|