zyzz 0.0.25 → 0.0.27
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/Vars.d.ts +18 -2
- package/dist/Vars.d.ts.map +1 -1
- package/dist/Vars.js +21 -3
- package/dist/Vars.js.map +1 -1
- package/dist/compiler/internal/Configurations.d.ts +3 -0
- package/dist/compiler/internal/Configurations.d.ts.map +1 -1
- package/dist/compiler/internal/Configurations.js +2 -1
- package/dist/compiler/internal/Configurations.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +103 -27
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +5 -1
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/VariableSets.d.ts +20 -4
- package/dist/internal/VariableSets.d.ts.map +1 -1
- package/dist/internal/VariableSets.js +70 -19
- package/dist/internal/VariableSets.js.map +1 -1
- package/dist/react-native/internal/Tokens.d.ts.map +1 -1
- package/dist/react-native/internal/Tokens.js +3 -6
- package/dist/react-native/internal/Tokens.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +3 -7
- package/dist/vite/index.js.map +1 -1
- package/dist/web/internal/Themes.d.ts.map +1 -1
- package/dist/web/internal/Themes.js +42 -4
- package/dist/web/internal/Themes.js.map +1 -1
- package/package.json +13 -1
- package/src/Vars.ts +104 -12
- package/src/compiler/internal/Configurations.ts +4 -1
- package/src/compiler/internal/Themes.ts +149 -45
- package/src/internal/Configuration.ts +5 -4
- package/src/internal/VariableSets.ts +104 -24
- package/src/react-native/internal/Tokens.ts +5 -6
- package/src/vite/index.ts +3 -9
- package/src/web/internal/Themes.ts +59 -13
|
@@ -55,19 +55,30 @@ export function from(metadata: Token.Metadata): Vars.Definition {
|
|
|
55
55
|
) as Vars.Definition
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Validates and freezes a complete set, or grows a base set with compatible
|
|
60
|
+
* overrides and new leaves.
|
|
61
|
+
*/
|
|
59
62
|
export function build(
|
|
60
63
|
input: unknown,
|
|
61
64
|
contract: Token.Contract,
|
|
62
|
-
|
|
63
|
-
baseQueries?: Query.Metadata,
|
|
64
|
-
basePaths?: Token.Metadata['paths'],
|
|
65
|
+
options: build.Options = {},
|
|
65
66
|
): Vars.Definition {
|
|
67
|
+
const base = options.base
|
|
66
68
|
const values: Record<string, Token.Value> = Object.assign(
|
|
67
69
|
Object.create(null),
|
|
68
|
-
base,
|
|
70
|
+
base?.values,
|
|
69
71
|
)
|
|
70
|
-
const paths = { ...
|
|
72
|
+
const paths = { ...base?.paths }
|
|
73
|
+
// Segment keys keep dotted typography conditions distinct from nested paths.
|
|
74
|
+
const leaves = new Set<string>()
|
|
75
|
+
const categories = new Set<string>()
|
|
76
|
+
for (const path of Object.keys(base?.values ?? {})) {
|
|
77
|
+
const parts = base!.paths?.[path] ?? path.split('.')
|
|
78
|
+
leaves.add(JSON.stringify(parts))
|
|
79
|
+
for (let index = 1; index < parts.length; index++)
|
|
80
|
+
categories.add(JSON.stringify(parts.slice(0, index)))
|
|
81
|
+
}
|
|
71
82
|
const conditions: string[][] = []
|
|
72
83
|
const active = new Set<object>()
|
|
73
84
|
function visit(input: unknown, path: string[]) {
|
|
@@ -86,14 +97,31 @@ export function build(
|
|
|
86
97
|
) {
|
|
87
98
|
if (!path.length)
|
|
88
99
|
throw new Vars.InvalidError(path, 'Expected a variable record.')
|
|
89
|
-
|
|
100
|
+
if (categories.has(JSON.stringify(path)))
|
|
101
|
+
throw new Vars.InvalidError(
|
|
102
|
+
path,
|
|
103
|
+
'Extensions cannot replace a variable category with a leaf.',
|
|
104
|
+
)
|
|
90
105
|
const name = path.join('.')
|
|
91
|
-
|
|
106
|
+
const previous =
|
|
107
|
+
base && Object.hasOwn(base.values, name) ? base.values[name] : undefined
|
|
108
|
+
if (previous !== undefined && options.derived)
|
|
92
109
|
throw new Vars.InvalidError(
|
|
93
110
|
path,
|
|
94
|
-
'
|
|
111
|
+
'Derived variables cannot replace existing paths.',
|
|
95
112
|
)
|
|
96
|
-
|
|
113
|
+
// Restating the base reference would make the variable reference itself.
|
|
114
|
+
if (
|
|
115
|
+
previous !== undefined &&
|
|
116
|
+
Token.is(input) &&
|
|
117
|
+
input.path === name &&
|
|
118
|
+
(input.contract === contract ||
|
|
119
|
+
(contract[Token.identity] !== undefined &&
|
|
120
|
+
input.contract[Token.identity] === contract[Token.identity]))
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
const value = read(input, path, queryData)
|
|
124
|
+
if (previous !== undefined && domain(previous) !== domain(value))
|
|
97
125
|
throw new Vars.InvalidError(
|
|
98
126
|
path,
|
|
99
127
|
'Variable overrides must preserve their domain.',
|
|
@@ -103,11 +131,19 @@ export function build(
|
|
|
103
131
|
values[name] = value
|
|
104
132
|
return
|
|
105
133
|
}
|
|
134
|
+
if (leaves.has(JSON.stringify(path)))
|
|
135
|
+
throw new Vars.InvalidError(
|
|
136
|
+
path,
|
|
137
|
+
'Extensions cannot replace a variable leaf with a category.',
|
|
138
|
+
)
|
|
106
139
|
const entries = record(input, path)
|
|
107
140
|
if (active.has(input as object))
|
|
108
141
|
throw new Vars.InvalidError(path, 'Cyclic variables are not supported.')
|
|
109
142
|
active.add(input as object)
|
|
110
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
!entries.length &&
|
|
145
|
+
(!base || (path.length > 0 && !categories.has(JSON.stringify(path))))
|
|
146
|
+
)
|
|
111
147
|
throw new Vars.InvalidError(path, 'Variable records cannot be empty.')
|
|
112
148
|
for (const [key, value] of entries) {
|
|
113
149
|
const query = path[0] === 'typography' && Typography.condition(key)
|
|
@@ -122,34 +158,29 @@ export function build(
|
|
|
122
158
|
active.delete(input as object)
|
|
123
159
|
}
|
|
124
160
|
const fields = Object.fromEntries(record(input, []))
|
|
125
|
-
const queries = { ...
|
|
161
|
+
const queries = { ...base?.queries }
|
|
126
162
|
for (const key of ['breakpoint', 'container', 'containerNames'] as const) {
|
|
127
163
|
if (Object.hasOwn(fields, key)) {
|
|
128
164
|
const value = fields[key]
|
|
129
165
|
if (base) {
|
|
130
166
|
if (key === 'containerNames') {
|
|
131
167
|
if (
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
!Array.isArray(value) ||
|
|
169
|
+
(base.queries?.containerNames ?? []).some(
|
|
170
|
+
(name) => !value.includes(name),
|
|
171
|
+
)
|
|
134
172
|
)
|
|
135
173
|
throw new Vars.InvalidError(
|
|
136
174
|
[key],
|
|
137
|
-
'Extensions cannot
|
|
175
|
+
'Extensions cannot remove container identities.',
|
|
138
176
|
)
|
|
139
|
-
} else
|
|
140
|
-
for (const [name] of record(value, [key]))
|
|
141
|
-
if (!Object.hasOwn(baseQueries?.[key] ?? {}, name))
|
|
142
|
-
throw new Vars.InvalidError(
|
|
143
|
-
[key, name],
|
|
144
|
-
'Extensions cannot add query thresholds.',
|
|
145
|
-
)
|
|
146
|
-
}
|
|
177
|
+
} else record(value, [key])
|
|
147
178
|
}
|
|
148
179
|
Object.assign(queries, {
|
|
149
180
|
[key]:
|
|
150
181
|
key === 'containerNames'
|
|
151
182
|
? value
|
|
152
|
-
: { ...
|
|
183
|
+
: { ...base?.queries?.[key], ...value },
|
|
153
184
|
})
|
|
154
185
|
if (key !== 'container' || Object.keys(value as object).length === 0)
|
|
155
186
|
delete fields[key]
|
|
@@ -178,6 +209,55 @@ export function build(
|
|
|
178
209
|
) as Vars.Definition
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
/** Set construction inputs. */
|
|
213
|
+
export declare namespace build {
|
|
214
|
+
/** Base set metadata and how its existing paths may change. */
|
|
215
|
+
type Options = {
|
|
216
|
+
/** Set whose leaves the input overrides or extends. */
|
|
217
|
+
readonly base?:
|
|
218
|
+
| Pick<Token.Metadata, 'paths' | 'queries' | 'values'>
|
|
219
|
+
| undefined
|
|
220
|
+
/** Rejects existing paths, as derived leaves only add to the set. */
|
|
221
|
+
readonly derived?: boolean | undefined
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Reads a set's references as values for a named extension. Typography presets
|
|
227
|
+
* keep their base values, since presets expand at each use.
|
|
228
|
+
*/
|
|
229
|
+
export function references(
|
|
230
|
+
variables: object,
|
|
231
|
+
metadata: Pick<Token.Metadata, 'paths' | 'queries' | 'values'>,
|
|
232
|
+
): Vars.Values {
|
|
233
|
+
const tree: Record<string, unknown> = Object.create(null)
|
|
234
|
+
|
|
235
|
+
for (const [path, value] of Object.entries(metadata.values)) {
|
|
236
|
+
const parts = metadata.paths?.[path] ?? path.split('.')
|
|
237
|
+
let target = tree
|
|
238
|
+
let source: unknown = variables
|
|
239
|
+
|
|
240
|
+
for (const part of parts.slice(0, -1)) {
|
|
241
|
+
target = (target[part] ??= Object.create(null)) as Record<string, unknown>
|
|
242
|
+
source = (source as Record<string, unknown>)[part]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const name = parts.at(-1)!
|
|
246
|
+
target[name] =
|
|
247
|
+
parts[0] === 'typography'
|
|
248
|
+
? value
|
|
249
|
+
: (source as Record<string, unknown>)[name]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const key of ['breakpoint', 'container'] as const)
|
|
253
|
+
if (Object.keys(metadata.queries?.[key] ?? {}).length)
|
|
254
|
+
tree[key] = metadata.queries![key]
|
|
255
|
+
if (metadata.queries?.containerNames?.length)
|
|
256
|
+
tree.containerNames = metadata.queries.containerNames
|
|
257
|
+
|
|
258
|
+
return tree as Vars.Values
|
|
259
|
+
}
|
|
260
|
+
|
|
181
261
|
function buildTree(
|
|
182
262
|
values: Token.Metadata['values'],
|
|
183
263
|
contract: Token.Contract,
|
|
@@ -32,12 +32,11 @@ export function resolve(
|
|
|
32
32
|
(metadata.contract[Token.identity] !== undefined &&
|
|
33
33
|
metadata.contract[Token.identity] ===
|
|
34
34
|
value.contract[Token.identity]))
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (next === undefined) throw new Error('Theme is missing a live token.')
|
|
35
|
+
// A set sharing its contract with an extension lacks the extension's added paths.
|
|
36
|
+
const next =
|
|
37
|
+
shared && Object.hasOwn(metadata.values, value.path)
|
|
38
|
+
? metadata.values[value.path]!
|
|
39
|
+
: value.value
|
|
41
40
|
active.add(value)
|
|
42
41
|
const result = scalar(next)
|
|
43
42
|
active.delete(value)
|
package/src/vite/index.ts
CHANGED
|
@@ -907,15 +907,9 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
|
|
|
907
907
|
url += target.slice(raw.length)
|
|
908
908
|
}
|
|
909
909
|
} else {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
.map((part, index) =>
|
|
914
|
-
index === 0 && /^[a-z]:$/i.test(part)
|
|
915
|
-
? part
|
|
916
|
-
: encodeURIComponent(part),
|
|
917
|
-
)
|
|
918
|
-
.join('/')
|
|
910
|
+
// Vite reads `/@fs/` URLs with `decodeURI`, which keeps reserved escapes
|
|
911
|
+
// such as `%40` in scoped package paths, so those characters stay raw.
|
|
912
|
+
const pathname = encodeURI(file.replaceAll('\\', '/'))
|
|
919
913
|
|
|
920
914
|
url = `/@fs/${pathname}${target.slice(raw.length)}`
|
|
921
915
|
}
|
|
@@ -70,6 +70,7 @@ export function create() {
|
|
|
70
70
|
const completeDefaults = new Set<Token.Contract>()
|
|
71
71
|
const empty: string[] = []
|
|
72
72
|
const rules: Rule[] = []
|
|
73
|
+
const varied = variedPaths(themes)
|
|
73
74
|
|
|
74
75
|
for (const [name, theme] of Object.entries(themes)) {
|
|
75
76
|
if (!name) throw new Error('Theme names must be nonempty.')
|
|
@@ -130,8 +131,14 @@ export function create() {
|
|
|
130
131
|
const paths = [...contract.paths]
|
|
131
132
|
for (const [path, name] of paths) {
|
|
132
133
|
const value = data.values[path]
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
// A set sharing its contract with an extension lacks the extension's added paths.
|
|
135
|
+
if (value === undefined) continue
|
|
136
|
+
// An inherited base reference resolves through its fallback, unless a sibling set overrides it.
|
|
137
|
+
if (
|
|
138
|
+
inherited(value, path, data.contract) &&
|
|
139
|
+
!varied.get(data.contract)?.has(path)
|
|
140
|
+
)
|
|
141
|
+
continue
|
|
135
142
|
|
|
136
143
|
const label = path
|
|
137
144
|
declarations.push({
|
|
@@ -144,15 +151,16 @@ export function create() {
|
|
|
144
151
|
|
|
145
152
|
rules.push(...declarations)
|
|
146
153
|
for (const [path, name] of contract.paths)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
if (Object.hasOwn(data.values, path))
|
|
155
|
+
conditional(
|
|
156
|
+
data.values[path]!,
|
|
157
|
+
name,
|
|
158
|
+
`.${className}`,
|
|
159
|
+
rules,
|
|
160
|
+
path,
|
|
161
|
+
[],
|
|
162
|
+
data.contract,
|
|
163
|
+
)
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
for (const rule of rules) {
|
|
@@ -257,9 +265,9 @@ export function create() {
|
|
|
257
265
|
if ('default' in value) {
|
|
258
266
|
// Separate fallback properties preserve extensions and resolve references within each scope.
|
|
259
267
|
const base = literal(value.default, label, owner)
|
|
260
|
-
// Rule consolidation must not change existing variable identities.
|
|
268
|
+
// Rule consolidation must not change existing variable identities, so the name hashes the fallback's CSS.
|
|
261
269
|
const css = `:where(*){--fallback:${base};}${conditionalCss(value, '--fallback', ':where(*)', label, owner)}`
|
|
262
|
-
const name = `${owner ? property(owner, label ?? 'value') : variable(undefined, label ?? 'value')}-fallback-${Identity.
|
|
270
|
+
const name = `${owner ? property(owner, label ?? 'value') : variable(undefined, label ?? 'value')}-fallback-${Identity.hash(css)}`
|
|
263
271
|
const emitted: Rule[] = [
|
|
264
272
|
{ conditions: [], property: name, selector: ':where(*)', value: base },
|
|
265
273
|
]
|
|
@@ -409,6 +417,44 @@ export function scope(value: string, identity?: string): string {
|
|
|
409
417
|
return `z-theme-${namespace}${Identity.name(value)}`
|
|
410
418
|
}
|
|
411
419
|
|
|
420
|
+
/** Whether a set's value is its base's own reference at the same path, as named extensions inherit. */
|
|
421
|
+
function inherited(value: Token.Value, path: string, contract: Token.Contract) {
|
|
422
|
+
return Token.is(value) && value.path === path && value.contract !== contract
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Paths whose values differ between sets that share a contract. */
|
|
426
|
+
function variedPaths(
|
|
427
|
+
themes: Readonly<Record<string, Theme.Definition | Vars.Definition>>,
|
|
428
|
+
) {
|
|
429
|
+
const seen = new Map<Token.Contract, Map<string, string>>()
|
|
430
|
+
const varied = new Map<Token.Contract, Set<string>>()
|
|
431
|
+
|
|
432
|
+
for (const theme of Object.values(themes)) {
|
|
433
|
+
const data = Object.getOwnPropertyDescriptor(theme, Token.definition)
|
|
434
|
+
?.value as Token.Metadata | undefined
|
|
435
|
+
if (!data) continue
|
|
436
|
+
|
|
437
|
+
const values = seen.get(data.contract) ?? new Map<string, string>()
|
|
438
|
+
seen.set(data.contract, values)
|
|
439
|
+
|
|
440
|
+
for (const [path, value] of Object.entries(data.values)) {
|
|
441
|
+
const key = Token.is(value)
|
|
442
|
+
? JSON.stringify([value.contract[Token.identity], value.path])
|
|
443
|
+
: JSON.stringify(value)
|
|
444
|
+
const previous = values.get(path)
|
|
445
|
+
|
|
446
|
+
if (previous === undefined) values.set(path, key)
|
|
447
|
+
else if (previous !== key) {
|
|
448
|
+
const paths = varied.get(data.contract) ?? new Set<string>()
|
|
449
|
+
paths.add(path)
|
|
450
|
+
varied.set(data.contract, paths)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return varied
|
|
456
|
+
}
|
|
457
|
+
|
|
412
458
|
function variable(identity: string | undefined, path: string): string {
|
|
413
459
|
const namespace = identity?.startsWith('id-') ? `${identity.slice(3)}-` : ''
|
|
414
460
|
return `--z-${namespace}${path.split('.').map(Identity.name).join('-')}`
|