zyzz 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/Source.d.ts.map +1 -1
- package/dist/compiler/Source.js +165 -11
- package/dist/compiler/Source.js.map +1 -1
- package/dist/compiler/Transform.d.ts.map +1 -1
- package/dist/compiler/Transform.js +3 -0
- package/dist/compiler/Transform.js.map +1 -1
- package/dist/compiler/internal/Applications.d.ts.map +1 -1
- package/dist/compiler/internal/Applications.js +42 -1
- package/dist/compiler/internal/Applications.js.map +1 -1
- package/dist/compiler/internal/Selectors.d.ts +3 -2
- package/dist/compiler/internal/Selectors.d.ts.map +1 -1
- package/dist/compiler/internal/Selectors.js +23 -11
- package/dist/compiler/internal/Selectors.js.map +1 -1
- package/dist/internal/Authoring.d.ts.map +1 -1
- package/dist/internal/Authoring.js +37 -7
- package/dist/internal/Authoring.js.map +1 -1
- package/dist/internal/Configuration.d.ts +6 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/Literal.d.ts +1 -1
- package/dist/internal/Literal.d.ts.map +1 -1
- package/dist/internal/Literal.js +3 -0
- package/dist/internal/Literal.js.map +1 -1
- package/dist/internal/Theme.d.ts +4 -0
- package/dist/internal/Theme.d.ts.map +1 -1
- package/dist/internal/Theme.js.map +1 -1
- package/dist/styleFunction.d.ts +11 -2
- package/dist/styleFunction.d.ts.map +1 -1
- package/dist/styleFunction.js +3 -1
- package/dist/styleFunction.js.map +1 -1
- package/dist/vite/index.d.ts +7 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +173 -22
- package/dist/vite/index.js.map +1 -1
- package/dist/web/internal/Themes.d.ts.map +1 -1
- package/dist/web/internal/Themes.js +38 -2
- package/dist/web/internal/Themes.js.map +1 -1
- package/package.json +13 -1
- package/src/compiler/Source.ts +237 -13
- package/src/compiler/Transform.ts +4 -1
- package/src/compiler/internal/Applications.ts +57 -1
- package/src/compiler/internal/Selectors.ts +25 -11
- package/src/internal/Authoring.ts +53 -18
- package/src/internal/Configuration.ts +38 -1
- package/src/internal/Literal.ts +3 -0
- package/src/internal/Theme.ts +15 -0
- package/src/styleFunction.ts +31 -2
- package/src/vite/index.ts +242 -27
- package/src/web/internal/Themes.ts +47 -2
package/src/compiler/Source.ts
CHANGED
|
@@ -504,11 +504,21 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
504
504
|
const opaque = new Set(variables.references.keys())
|
|
505
505
|
const normalized = new Map<number, Ast.Node | Error>()
|
|
506
506
|
const selectorKeys = new Set<number>()
|
|
507
|
+
const composed: Ast.Node[] = []
|
|
507
508
|
for (const call of pending) {
|
|
508
509
|
if (!call.arguments[0]) continue
|
|
509
510
|
try {
|
|
510
511
|
const value = staticData.normalize(call.arguments[0], staticCalls, opaque)
|
|
511
512
|
normalized.set(call.start, value)
|
|
513
|
+
if (value.type === 'ArrayExpression')
|
|
514
|
+
for (const element of value.elements) {
|
|
515
|
+
const entry = element && Expression.unwrap(element)
|
|
516
|
+
if (
|
|
517
|
+
entry?.type === 'Identifier' ||
|
|
518
|
+
entry?.type === 'MemberExpression'
|
|
519
|
+
)
|
|
520
|
+
composed.push(entry)
|
|
521
|
+
}
|
|
512
522
|
const body =
|
|
513
523
|
value.type === 'ArrowFunctionExpression'
|
|
514
524
|
? staticData.normalize(value.body, staticCalls, opaque)
|
|
@@ -564,6 +574,7 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
564
574
|
}),
|
|
565
575
|
),
|
|
566
576
|
options.moduleId,
|
|
577
|
+
composed,
|
|
567
578
|
)
|
|
568
579
|
} catch (error) {
|
|
569
580
|
if (!(error instanceof Themes.InvalidError)) throw error
|
|
@@ -573,6 +584,7 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
573
584
|
}
|
|
574
585
|
})()
|
|
575
586
|
|
|
587
|
+
const bodies = new Map<number, Ast.ObjectExpression>()
|
|
576
588
|
const cssNameCounts = new Map<string, number>()
|
|
577
589
|
for (const call of pending) {
|
|
578
590
|
const name = authoredNames.get(call.start)!
|
|
@@ -686,10 +698,20 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
686
698
|
}
|
|
687
699
|
}
|
|
688
700
|
|
|
689
|
-
|
|
701
|
+
const entries =
|
|
702
|
+
argument?.type === 'ArrayExpression' &&
|
|
703
|
+
!dynamic &&
|
|
704
|
+
!recipes.has(call.start)
|
|
705
|
+
? argument.elements
|
|
706
|
+
: undefined
|
|
707
|
+
|
|
708
|
+
if (
|
|
709
|
+
call.arguments.length > 2 ||
|
|
710
|
+
(argument?.type !== 'ObjectExpression' && !entries)
|
|
711
|
+
) {
|
|
690
712
|
report(
|
|
691
713
|
'unsupported_syntax',
|
|
692
|
-
'Expected one literal object
|
|
714
|
+
'Expected one literal object, typed callback, or array of style definitions and literal objects; spreads are not supported.',
|
|
693
715
|
call,
|
|
694
716
|
)
|
|
695
717
|
continue
|
|
@@ -700,7 +722,7 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
700
722
|
let recipeTypes: Call['recipeTypes']
|
|
701
723
|
let staticRecipe: StaticRecipe.Definition<Ast.ObjectExpression> | undefined
|
|
702
724
|
let dynamicRecipe: StaticRecipe.Definition<Ast.ObjectExpression> | undefined
|
|
703
|
-
if (recipes.has(call.start)) {
|
|
725
|
+
if (recipes.has(call.start) && argument?.type === 'ObjectExpression') {
|
|
704
726
|
try {
|
|
705
727
|
if (dynamic)
|
|
706
728
|
throw new Themes.InvalidError(
|
|
@@ -838,7 +860,18 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
838
860
|
) {
|
|
839
861
|
report(
|
|
840
862
|
'unsupported_syntax',
|
|
841
|
-
|
|
863
|
+
(() => {
|
|
864
|
+
const key =
|
|
865
|
+
property.type === 'Property' && property.computed
|
|
866
|
+
? Expression.unwrap(property.key)
|
|
867
|
+
: undefined
|
|
868
|
+
|
|
869
|
+
// Template keys with an & target are selectors outside their group.
|
|
870
|
+
return key?.type === 'TemplateLiteral' &&
|
|
871
|
+
key.quasis.some((quasi) => quasi.value.raw.includes('&'))
|
|
872
|
+
? 'Template selector keys require a selectors object.'
|
|
873
|
+
: 'Only explicit literal properties are supported; spreads, computed keys, shorthand, and methods are not evaluated.'
|
|
874
|
+
})(),
|
|
842
875
|
property,
|
|
843
876
|
)
|
|
844
877
|
continue
|
|
@@ -1231,14 +1264,175 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
1231
1264
|
return values
|
|
1232
1265
|
}
|
|
1233
1266
|
|
|
1234
|
-
|
|
1267
|
+
// Each entry becomes an ordered declaration segment, so later entries win as authored declarations do.
|
|
1268
|
+
function compose(elements: Ast.ArrayExpression['elements']) {
|
|
1269
|
+
const parts: Style.NamedStyle[] = []
|
|
1270
|
+
const properties: Ast.ObjectExpression['properties'] = []
|
|
1271
|
+
|
|
1272
|
+
for (const element of elements) {
|
|
1273
|
+
const entry =
|
|
1274
|
+
element && element.type !== 'SpreadElement'
|
|
1275
|
+
? Expression.unwrap(element)
|
|
1276
|
+
: undefined
|
|
1277
|
+
|
|
1278
|
+
if (entry?.type === 'ObjectExpression') {
|
|
1279
|
+
const values = object(entry)
|
|
1280
|
+
if (diagnostics.length !== before) continue
|
|
1281
|
+
|
|
1282
|
+
parts.push(
|
|
1283
|
+
define(
|
|
1284
|
+
{ [name]: values },
|
|
1285
|
+
{ locations, vars: themes?.styles.get(call.start)?.theme },
|
|
1286
|
+
).styles[0]!,
|
|
1287
|
+
)
|
|
1288
|
+
properties.push(...entry.properties)
|
|
1289
|
+
continue
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const target = entry && selectors.compositions.get(entry.start)
|
|
1293
|
+
const local =
|
|
1294
|
+
target && 'type' in target
|
|
1295
|
+
? calls.find((value) => value.start === target.start)
|
|
1296
|
+
: undefined
|
|
1297
|
+
const packed = target && !('type' in target) ? target.style : undefined
|
|
1298
|
+
const style = local
|
|
1299
|
+
? styles.find((style) => style.name === local.name)
|
|
1300
|
+
: packed?.style
|
|
1301
|
+
|
|
1302
|
+
if (!style) {
|
|
1303
|
+
report(
|
|
1304
|
+
'unsupported_syntax',
|
|
1305
|
+
'Composition entries require literal objects or previously declared style definitions.',
|
|
1306
|
+
element ?? call,
|
|
1307
|
+
)
|
|
1308
|
+
continue
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (
|
|
1312
|
+
local?.recipe ||
|
|
1313
|
+
local?.slots ||
|
|
1314
|
+
packed?.dynamic ||
|
|
1315
|
+
(packed?.staticRecipe &&
|
|
1316
|
+
!(
|
|
1317
|
+
packed.staticRecipe.rules.length === 1 &&
|
|
1318
|
+
packed.staticRecipe.rules[0]!.value.styles.length === 1 &&
|
|
1319
|
+
packed.staticRecipe.rules[0]!.value.styles[0]!.name ===
|
|
1320
|
+
packed.style.name
|
|
1321
|
+
))
|
|
1322
|
+
) {
|
|
1323
|
+
report(
|
|
1324
|
+
'unsupported_syntax',
|
|
1325
|
+
'Composition entries cannot be variants or dynamic styles; apply them with cx.',
|
|
1326
|
+
entry!,
|
|
1327
|
+
)
|
|
1328
|
+
continue
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Native tables select variable alternatives from one context per call.
|
|
1332
|
+
if (
|
|
1333
|
+
options.target === 'native' &&
|
|
1334
|
+
!context(
|
|
1335
|
+
local
|
|
1336
|
+
? themes?.nativeContext(local.start, local.end)
|
|
1337
|
+
: packed?.nativeContext,
|
|
1338
|
+
themes?.nativeContext(call.start, call.end),
|
|
1339
|
+
)
|
|
1340
|
+
) {
|
|
1341
|
+
report(
|
|
1342
|
+
'unsupported_syntax',
|
|
1343
|
+
'Native composition entries require the variable catalog of the composing style.',
|
|
1344
|
+
entry!,
|
|
1345
|
+
)
|
|
1346
|
+
continue
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// The composing configuration decides output for every copied entry.
|
|
1350
|
+
parts.push(
|
|
1351
|
+
Object.fromEntries(
|
|
1352
|
+
Object.entries(style).filter(
|
|
1353
|
+
([key]) => key !== 'composition' && key !== 'cssOutput',
|
|
1354
|
+
),
|
|
1355
|
+
) as Style.NamedStyle,
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
if (local) {
|
|
1359
|
+
properties.push(...(bodies.get(local.start)?.properties ?? []))
|
|
1360
|
+
for (const [start, count] of local.typography ?? [])
|
|
1361
|
+
typography.set(start, count)
|
|
1362
|
+
for (const [start, count] of local.typographyQueries ?? [])
|
|
1363
|
+
typographyQueries.set(start, count)
|
|
1364
|
+
continue
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Packed declarations have no authored source here, so they map to the entry.
|
|
1368
|
+
properties.push(
|
|
1369
|
+
...declarations(style).map(
|
|
1370
|
+
(declaration): Ast.ObjectProperty => ({
|
|
1371
|
+
type: 'Property',
|
|
1372
|
+
computed: false,
|
|
1373
|
+
kind: 'init',
|
|
1374
|
+
method: false,
|
|
1375
|
+
shorthand: false,
|
|
1376
|
+
start: entry!.start,
|
|
1377
|
+
end: entry!.end,
|
|
1378
|
+
key: {
|
|
1379
|
+
type: 'Literal',
|
|
1380
|
+
value: declaration.property,
|
|
1381
|
+
raw: JSON.stringify(declaration.property),
|
|
1382
|
+
start: entry!.start,
|
|
1383
|
+
end: entry!.end,
|
|
1384
|
+
},
|
|
1385
|
+
value: {
|
|
1386
|
+
type: 'Literal',
|
|
1387
|
+
value: '',
|
|
1388
|
+
raw: '""',
|
|
1389
|
+
start: entry!.start,
|
|
1390
|
+
end: entry!.end,
|
|
1391
|
+
},
|
|
1392
|
+
}),
|
|
1393
|
+
),
|
|
1394
|
+
)
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
body: {
|
|
1399
|
+
type: 'ObjectExpression',
|
|
1400
|
+
start: call.start,
|
|
1401
|
+
end: call.end,
|
|
1402
|
+
properties,
|
|
1403
|
+
} satisfies Ast.ObjectExpression,
|
|
1404
|
+
definition: {
|
|
1405
|
+
styles: [
|
|
1406
|
+
Object.freeze<Style.NamedStyle>({
|
|
1407
|
+
name,
|
|
1408
|
+
declarations: Object.freeze([]),
|
|
1409
|
+
rules: Object.freeze(
|
|
1410
|
+
parts.map((style) => Object.freeze({ style })),
|
|
1411
|
+
),
|
|
1412
|
+
}),
|
|
1413
|
+
],
|
|
1414
|
+
} satisfies Style.Definition as Style.Definition,
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
const values = entries
|
|
1419
|
+
? undefined
|
|
1420
|
+
: object(argument as Ast.ObjectExpression)
|
|
1235
1421
|
if (diagnostics.length !== before) continue
|
|
1236
1422
|
|
|
1237
1423
|
try {
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1424
|
+
const composite = entries && compose(entries)
|
|
1425
|
+
if (diagnostics.length !== before) continue
|
|
1426
|
+
|
|
1427
|
+
const definition =
|
|
1428
|
+
composite?.definition ??
|
|
1429
|
+
define(
|
|
1430
|
+
{ [name]: values! },
|
|
1431
|
+
{ locations, vars: themes?.styles.get(call.start)?.theme },
|
|
1432
|
+
)
|
|
1433
|
+
|
|
1434
|
+
const body = composite?.body ?? (argument as Ast.ObjectExpression)
|
|
1435
|
+
bodies.set(call.start, body)
|
|
1242
1436
|
|
|
1243
1437
|
function validate(style: Style.NamedStyle, path: readonly string[]) {
|
|
1244
1438
|
for (const declaration of style.declarations) {
|
|
@@ -1331,10 +1525,7 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
1331
1525
|
...(selectors.identities.has(call.start)
|
|
1332
1526
|
? { identity: selectors.identities.get(call.start)! }
|
|
1333
1527
|
: {}),
|
|
1334
|
-
...(
|
|
1335
|
-
argument.type === 'ObjectExpression'
|
|
1336
|
-
? { body: argument }
|
|
1337
|
-
: {}),
|
|
1528
|
+
...(body !== (dynamic?.body ?? original) ? { body } : {}),
|
|
1338
1529
|
...(shorthands ? { shorthands } : {}),
|
|
1339
1530
|
...(typography.size ? { typography } : {}),
|
|
1340
1531
|
...(typographyQueries.size ? { typographyQueries } : {}),
|
|
@@ -1357,6 +1548,7 @@ export function extract(options: extract.Options): extract.ReturnType {
|
|
|
1357
1548
|
if (
|
|
1358
1549
|
dynamic ||
|
|
1359
1550
|
recipe ||
|
|
1551
|
+
entries?.length === 0 ||
|
|
1360
1552
|
(!definition.styles[0]!.declarations.length &&
|
|
1361
1553
|
!definition.styles[0]!.rules &&
|
|
1362
1554
|
!definition.styles[0]!.targets)
|
|
@@ -1748,6 +1940,38 @@ export class ExtractError extends Error {
|
|
|
1748
1940
|
override name = 'Source.ExtractError'
|
|
1749
1941
|
}
|
|
1750
1942
|
|
|
1943
|
+
/** Accepts an entry context that the composing call selects identically. */
|
|
1944
|
+
function context(
|
|
1945
|
+
entry: Call['nativeContext'],
|
|
1946
|
+
composite: Call['nativeContext'],
|
|
1947
|
+
): boolean {
|
|
1948
|
+
if (!entry || entry === composite) return true
|
|
1949
|
+
if (!composite || entry.defaultVars !== composite.defaultVars) return false
|
|
1950
|
+
|
|
1951
|
+
const names = Object.keys(entry.vars)
|
|
1952
|
+
return (
|
|
1953
|
+
names.length === Object.keys(composite.vars).length &&
|
|
1954
|
+
names.every((name) => {
|
|
1955
|
+
const left = entry.vars[name]?.[Token.definition].contract
|
|
1956
|
+
const right = composite.vars[name]?.[Token.definition].contract
|
|
1957
|
+
return (
|
|
1958
|
+
!!left &&
|
|
1959
|
+
!!right &&
|
|
1960
|
+
(left === right ||
|
|
1961
|
+
(left[Token.identity] !== undefined &&
|
|
1962
|
+
left[Token.identity] === right[Token.identity]))
|
|
1963
|
+
)
|
|
1964
|
+
})
|
|
1965
|
+
)
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/** Lists declarations in emitted order, through conditions and segments. */
|
|
1969
|
+
function declarations(style: Style.NamedStyle): readonly Style.Declaration[] {
|
|
1970
|
+
return style.rules
|
|
1971
|
+
? style.rules.flatMap((rule) => declarations(rule.style))
|
|
1972
|
+
: style.declarations
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1751
1975
|
function identity(value: string): string {
|
|
1752
1976
|
let first = 2166136261
|
|
1753
1977
|
let second = 2246822507
|
|
@@ -1189,7 +1189,10 @@ export function compile(options: compile.Options): compile.ReturnType {
|
|
|
1189
1189
|
) ?? -1
|
|
1190
1190
|
if (start < 0) continue
|
|
1191
1191
|
|
|
1192
|
-
|
|
1192
|
+
// Composed packed entries can expand differently from the composite's shorthands.
|
|
1193
|
+
const location = authored[propertyIndex]
|
|
1194
|
+
if (!location) continue
|
|
1195
|
+
|
|
1193
1196
|
used.add(propertyIndex)
|
|
1194
1197
|
|
|
1195
1198
|
Mapping.addMapping(cssMap, {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Finds nonescaping local static callables whose applications can become props. @module */
|
|
2
2
|
import type * as Ast from '@oxc-project/types'
|
|
3
3
|
import * as Walker from 'oxc-walker'
|
|
4
|
+
import * as Expression from './Expression.js'
|
|
4
5
|
import * as Scope from './Scope.js'
|
|
5
6
|
import type * as Source from '../Source.js'
|
|
6
7
|
|
|
@@ -158,6 +159,8 @@ export function create(
|
|
|
158
159
|
if (!candidates.length) return undefined
|
|
159
160
|
|
|
160
161
|
const lexical = new Set<Ast.Node>()
|
|
162
|
+
// Extraction resolves these reads and rewrites their definitions, so none run.
|
|
163
|
+
const resolved = new Set<Ast.Node>()
|
|
161
164
|
// Reads inside functions, classes, or namespaces may run before initialization.
|
|
162
165
|
const deferred = new Set<Ast.Node>()
|
|
163
166
|
const terminals = new Set<Ast.Node>()
|
|
@@ -200,6 +203,7 @@ export function create(
|
|
|
200
203
|
) {
|
|
201
204
|
lexical.add(node)
|
|
202
205
|
if (terminal(ancestors)) terminals.add(node)
|
|
206
|
+
if (extracted(ancestors, definitions)) resolved.add(node)
|
|
203
207
|
if (
|
|
204
208
|
ancestors.some((node) =>
|
|
205
209
|
[
|
|
@@ -240,7 +244,7 @@ export function create(
|
|
|
240
244
|
const called = new Set<string>()
|
|
241
245
|
|
|
242
246
|
for (const reference of references.get(declaration.id.name) ?? []) {
|
|
243
|
-
if (reference === declaration.id) continue
|
|
247
|
+
if (reference === declaration.id || resolved.has(reference)) continue
|
|
244
248
|
|
|
245
249
|
let callee: Ast.Node = reference
|
|
246
250
|
let call = direct
|
|
@@ -492,6 +496,58 @@ export declare namespace create {
|
|
|
492
496
|
}
|
|
493
497
|
}
|
|
494
498
|
|
|
499
|
+
// Matches a selector interpolation or composition entry inside a definition, which
|
|
500
|
+
// extraction resolves statically. Computed keys elsewhere in a definition fail extraction.
|
|
501
|
+
function extracted(
|
|
502
|
+
path: readonly Ast.Node[],
|
|
503
|
+
definitions: ReadonlyMap<number, Source.Call>,
|
|
504
|
+
): boolean {
|
|
505
|
+
let index = path.length - 1
|
|
506
|
+
const member = path[index - 1]
|
|
507
|
+
if (
|
|
508
|
+
member?.type === 'MemberExpression' &&
|
|
509
|
+
member.object === path[index] &&
|
|
510
|
+
!member.computed
|
|
511
|
+
)
|
|
512
|
+
index--
|
|
513
|
+
|
|
514
|
+
// Type assertions around a node keep the same read.
|
|
515
|
+
function parent() {
|
|
516
|
+
const node = path[index]!
|
|
517
|
+
while (index > 0 && Expression.unwrap(path[index - 1]!) === node) index--
|
|
518
|
+
return path[--index]
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function definition(node: Ast.Node | undefined) {
|
|
522
|
+
return (
|
|
523
|
+
node?.type === 'CallExpression' &&
|
|
524
|
+
definitions.get(node.start)?.end === node.end
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const container = parent()
|
|
529
|
+
|
|
530
|
+
if (container?.type === 'ArrayExpression') {
|
|
531
|
+
const call = parent()
|
|
532
|
+
return (
|
|
533
|
+
call?.type === 'CallExpression' &&
|
|
534
|
+
definition(call) &&
|
|
535
|
+
!!call.arguments[0] &&
|
|
536
|
+
Expression.unwrap(call.arguments[0]) === container
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (container?.type !== 'TemplateLiteral') return false
|
|
541
|
+
|
|
542
|
+
const property = parent()
|
|
543
|
+
return (
|
|
544
|
+
property?.type === 'Property' &&
|
|
545
|
+
property.computed &&
|
|
546
|
+
Expression.unwrap(property.key) === container &&
|
|
547
|
+
path.slice(0, index).some(definition)
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
|
|
495
551
|
// Matches a no-argument application spread onto, or read as the class of, a DOM element.
|
|
496
552
|
function terminal(path: readonly Ast.Node[]): boolean {
|
|
497
553
|
let index = path.length - 1
|
|
@@ -11,7 +11,7 @@ import * as Expression from './Expression.js'
|
|
|
11
11
|
import type * as Scope from './Scope.js'
|
|
12
12
|
import * as Themes from './Themes.js'
|
|
13
13
|
|
|
14
|
-
/** Collects scoped selectors, local identities,
|
|
14
|
+
/** Collects scoped selectors, local identities, exported style references, and composed definitions. */
|
|
15
15
|
export function scan(
|
|
16
16
|
program: Ast.Program,
|
|
17
17
|
scope: Scope.Tracker,
|
|
@@ -21,6 +21,7 @@ export function scan(
|
|
|
21
21
|
selectorKeys: ReadonlySet<number> = new Set(),
|
|
22
22
|
cssNamespaces: ReadonlyMap<number, string> = new Map(),
|
|
23
23
|
moduleId = namespace,
|
|
24
|
+
composed: readonly Ast.Node[] = [],
|
|
24
25
|
) {
|
|
25
26
|
const authoredNames = Names.collect(program)
|
|
26
27
|
const conditions = new Map<number, string>()
|
|
@@ -33,6 +34,7 @@ export function scan(
|
|
|
33
34
|
const namespaces = new Map<number, Map<string, Ast.Expression>>()
|
|
34
35
|
const importedNames = new Map<string, Themes.Link>()
|
|
35
36
|
const imported = new Map<number, Themes.Link>()
|
|
37
|
+
const compositions = new Map<number, Ast.CallExpression | Themes.Link>()
|
|
36
38
|
|
|
37
39
|
const names = new Map<string, Ast.Expression>()
|
|
38
40
|
const publicNames = new Map<string, string>()
|
|
@@ -122,7 +124,7 @@ export function scan(
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
if (!calls.length && !imported.size)
|
|
125
|
-
return { conditions, exports, identities, localConditions }
|
|
127
|
+
return { compositions, conditions, exports, identities, localConditions }
|
|
126
128
|
|
|
127
129
|
const exposed = new Map<Ast.Expression, readonly (readonly string[])[]>()
|
|
128
130
|
for (const [name, local] of publicNames) {
|
|
@@ -138,8 +140,8 @@ export function scan(
|
|
|
138
140
|
for (const [member, value] of values)
|
|
139
141
|
exposed.set(value, [...(exposed.get(value) ?? []), [name, member]])
|
|
140
142
|
|
|
141
|
-
if (!selectorKeys.size && !exposed.size)
|
|
142
|
-
return { conditions, exports, identities, localConditions }
|
|
143
|
+
if (!selectorKeys.size && !exposed.size && !composed.length)
|
|
144
|
+
return { compositions, conditions, exports, identities, localConditions }
|
|
143
145
|
|
|
144
146
|
type Entry = { node: Ast.Node; parent: Ast.Node | null | undefined }
|
|
145
147
|
const nodes: Entry[] = []
|
|
@@ -179,10 +181,11 @@ export function scan(
|
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
// Composition reads the definition itself, so only selector references claim an identity.
|
|
185
|
+
function target(
|
|
183
186
|
node: Ast.Node,
|
|
184
187
|
seen = new Set<Ast.Node>(),
|
|
185
|
-
): Themes.Link | undefined {
|
|
188
|
+
): Ast.CallExpression | Themes.Link | undefined {
|
|
186
189
|
node = Expression.unwrap(node)
|
|
187
190
|
if (seen.has(node)) return undefined
|
|
188
191
|
seen.add(node)
|
|
@@ -191,7 +194,7 @@ export function scan(
|
|
|
191
194
|
node.type === 'CallExpression' &&
|
|
192
195
|
definitions.get(node.start)?.end === node.end
|
|
193
196
|
)
|
|
194
|
-
return
|
|
197
|
+
return node
|
|
195
198
|
|
|
196
199
|
if (node.type === 'Identifier') {
|
|
197
200
|
const declaration = declarations.get(node.start)
|
|
@@ -199,7 +202,7 @@ export function scan(
|
|
|
199
202
|
const external = imported.get(declaration)
|
|
200
203
|
if (external) return external
|
|
201
204
|
const value = bindings.get(declaration)
|
|
202
|
-
return value ?
|
|
205
|
+
return value ? target(value, seen) : undefined
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
if (node.type === 'MemberExpression' && !node.optional) {
|
|
@@ -221,14 +224,25 @@ export function scan(
|
|
|
221
224
|
declaration === undefined
|
|
222
225
|
? undefined
|
|
223
226
|
: namespaces.get(declaration)?.get(name)
|
|
224
|
-
if (value) return
|
|
227
|
+
if (value) return target(value, seen)
|
|
225
228
|
}
|
|
226
|
-
|
|
229
|
+
const object = target(node.object, seen)
|
|
230
|
+
return object && !('type' in object) ? object.members?.[name] : undefined
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
return undefined
|
|
230
234
|
}
|
|
231
235
|
|
|
236
|
+
function resolve(node: Ast.Node): Themes.Link | undefined {
|
|
237
|
+
const value = target(node)
|
|
238
|
+
return value && 'type' in value ? link(value) : value
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const node of composed) {
|
|
242
|
+
const value = target(node)
|
|
243
|
+
if (value) compositions.set(node.start, value)
|
|
244
|
+
}
|
|
245
|
+
|
|
232
246
|
for (const { node } of nodes) {
|
|
233
247
|
if (node.type === 'VariableDeclarator' && node.init)
|
|
234
248
|
for (const [key, member] of exposed.get(node.init) ?? []) {
|
|
@@ -335,5 +349,5 @@ export function scan(
|
|
|
335
349
|
return [...imported.values()].some(contains)
|
|
336
350
|
}
|
|
337
351
|
|
|
338
|
-
return { conditions, exports, identities, localConditions }
|
|
352
|
+
return { compositions, conditions, exports, identities, localConditions }
|
|
339
353
|
}
|
|
@@ -10,6 +10,10 @@ import type * as Theme from './Theme.js'
|
|
|
10
10
|
/** Private ownership used by uncompiled composition; never spread onto DOM props. */
|
|
11
11
|
export const metadata = Symbol.for('zyzz.authoring')
|
|
12
12
|
|
|
13
|
+
// Static definitions retain their style data so later compositions can read it,
|
|
14
|
+
// including definitions created by another bundled copy of the runtime.
|
|
15
|
+
const data = Symbol.for('zyzz.style')
|
|
16
|
+
|
|
13
17
|
/** Identity and replaceable attribute/slot ownership for an applied definition. */
|
|
14
18
|
export type Owner = {
|
|
15
19
|
readonly name: string
|
|
@@ -77,24 +81,54 @@ export function create(
|
|
|
77
81
|
if (!dynamic && !id && Object.keys(input as object).length === 0)
|
|
78
82
|
Identity.requireId(undefined, 'Empty style')
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
const define = (input: unknown) =>
|
|
85
|
+
(
|
|
86
|
+
Style.define as (
|
|
87
|
+
input: Record<string, unknown>,
|
|
88
|
+
options: Style.define.Options,
|
|
89
|
+
) => Style.Definition
|
|
90
|
+
)(
|
|
91
|
+
{ style: body(input as Record<string, unknown>) },
|
|
92
|
+
{ vars: options.theme },
|
|
93
|
+
).styles[0]!
|
|
94
|
+
|
|
95
|
+
const style = (() => {
|
|
96
|
+
if (dynamic) return undefined
|
|
97
|
+
if (!Array.isArray(input)) return define(input)
|
|
98
|
+
|
|
99
|
+
// Mirrors compiled composition: each entry is one ordered declaration segment.
|
|
100
|
+
const rules: Style.Rule[] = []
|
|
101
|
+
for (const [index, entry] of (input as readonly unknown[]).entries()) {
|
|
102
|
+
if (typeof entry !== 'function') {
|
|
103
|
+
rules.push({ style: define(entry) })
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const style = Object.getOwnPropertyDescriptor(entry, data)?.value as
|
|
108
|
+
| Style.NamedStyle
|
|
109
|
+
| undefined
|
|
110
|
+
if (style) {
|
|
111
|
+
rules.push({ style })
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Compiled callables carry only class names, so an explicit id names the composite.
|
|
116
|
+
if (id) return undefined
|
|
117
|
+
|
|
118
|
+
throw new Style.InvalidError([
|
|
119
|
+
{
|
|
120
|
+
code: 'invalid_structure',
|
|
121
|
+
message:
|
|
122
|
+
'Composition entries require literal objects or static style definitions. Compositions of compiled definitions require an explicit id, and variants and dynamic styles are applied with cx.',
|
|
123
|
+
path: [String(index)],
|
|
124
|
+
},
|
|
125
|
+
])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { name: 'style', declarations: [], rules } satisfies Style.NamedStyle
|
|
129
|
+
})()
|
|
130
|
+
|
|
131
|
+
const className = id ? `z-style-${id}` : Identity.style(style!, namespace)
|
|
98
132
|
const props = Props.create({ className })
|
|
99
133
|
const apply = (values?: style.Options & Record<string, unknown>) => {
|
|
100
134
|
let result = props(values as style.Options)
|
|
@@ -124,6 +158,7 @@ export function create(
|
|
|
124
158
|
Object.defineProperty(apply, Symbol.toPrimitive, {
|
|
125
159
|
value: () => `.${className}`,
|
|
126
160
|
})
|
|
161
|
+
if (style) Object.defineProperty(apply, data, { value: style })
|
|
127
162
|
return apply as style.ReturnType
|
|
128
163
|
}
|
|
129
164
|
|
|
@@ -625,15 +625,52 @@ export type StyleFactory<
|
|
|
625
625
|
? [options?: style.DefinitionOptions]
|
|
626
626
|
: [invalid: never, unavailable: never]
|
|
627
627
|
: [invalid: never, unavailable: never]
|
|
628
|
-
|
|
628
|
+
// TypeScript types a call that matches no overload with this signature. Other
|
|
629
|
+
// inputs leave `callback` at its constraint, so their definitions stay static.
|
|
630
|
+
): ((...args: never[]) => unknown) extends callback
|
|
631
|
+
? style.ReturnType<output>
|
|
632
|
+
: style.Dynamic<values, output>
|
|
629
633
|
<const styles extends Record<string, unknown>>(
|
|
630
634
|
styles: styles &
|
|
631
635
|
NoInfer<Body<styles, tokens, layers, mappings>> &
|
|
632
636
|
Completion.Properties<tokens>,
|
|
633
637
|
options?: style.DefinitionOptions,
|
|
634
638
|
): style.ReturnType<output>
|
|
639
|
+
<const entries>(
|
|
640
|
+
styles: entries & Entries<entries, tokens, layers, mappings>,
|
|
641
|
+
...options: entries extends readonly unknown[]
|
|
642
|
+
? [options?: style.DefinitionOptions]
|
|
643
|
+
: [invalid: never, unavailable: never]
|
|
644
|
+
): style.ReturnType<output>
|
|
635
645
|
}
|
|
636
646
|
|
|
647
|
+
// Composition entries are static definitions or checked literal objects.
|
|
648
|
+
type Entries<
|
|
649
|
+
entries,
|
|
650
|
+
tokens extends Theme.Tokens,
|
|
651
|
+
layers extends string,
|
|
652
|
+
mappings extends Shorthands.Map,
|
|
653
|
+
> = {
|
|
654
|
+
readonly [index in keyof entries]: Entry<
|
|
655
|
+
entries[index],
|
|
656
|
+
tokens,
|
|
657
|
+
layers,
|
|
658
|
+
mappings
|
|
659
|
+
>
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Distributes over mixed element unions so each member keeps its own contract.
|
|
663
|
+
type Entry<
|
|
664
|
+
entry,
|
|
665
|
+
tokens extends Theme.Tokens,
|
|
666
|
+
layers extends string,
|
|
667
|
+
mappings extends Shorthands.Map,
|
|
668
|
+
> = entry extends (...args: never) => unknown
|
|
669
|
+
? style.Composable<entry>
|
|
670
|
+
: entry &
|
|
671
|
+
NoInfer<Body<entry, tokens, layers, mappings>> &
|
|
672
|
+
Completion.Properties<tokens>
|
|
673
|
+
|
|
637
674
|
type Keys<styles> = styles extends unknown ? keyof styles : never
|
|
638
675
|
|
|
639
676
|
/** Checked declaration body shared by configured styles and recipe choices. */
|