@animus-ui/system 0.1.1 → 0.1.3
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 +125 -4
- package/dist/Animus.d.ts +2 -3
- package/dist/Animus.d.ts.map +1 -1
- package/dist/AnimusExtended.d.ts +2 -3
- package/dist/AnimusExtended.d.ts.map +1 -1
- package/dist/SystemBuilder.d.ts +55 -8
- package/dist/SystemBuilder.d.ts.map +1 -1
- package/dist/appearance/index.d.ts +75 -0
- package/dist/appearance/index.d.ts.map +1 -0
- package/dist/appearance/index.js +179 -0
- package/dist/bootstrap/createAppearanceBootstrap.d.ts +71 -0
- package/dist/bootstrap/createAppearanceBootstrap.d.ts.map +1 -0
- package/dist/bootstrap/index.d.ts +2 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +98 -0
- package/dist/composeWithContext.d.ts.map +1 -1
- package/dist/composeWithContext.js +43 -22
- package/dist/conditions.d.ts +163 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/{createComposedFamily-DSE2ZuRE.js → createComposedFamily-DJ2aqfqN.js} +141 -67
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +648 -25
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/resolveClasses.d.ts.map +1 -1
- package/dist/runtime-entry.js +1 -1
- package/dist/selectors.d.ts +22 -0
- package/dist/selectors.d.ts.map +1 -1
- package/dist/theme/createTheme.d.ts +47 -4
- package/dist/theme/createTheme.d.ts.map +1 -1
- package/dist/types/component.d.ts +27 -10
- package/dist/types/component.d.ts.map +1 -1
- package/dist/types/config.d.ts +124 -11
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/props.d.ts +0 -4
- package/dist/types/props.d.ts.map +1 -1
- package/dist/types/theme.d.ts +132 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/dist/utils/deepMerge.d.ts +0 -3
- package/dist/utils/deepMerge.d.ts.map +1 -1
- package/package.json +12 -4
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { n as createClassResolver, r as createComponent, t as createComposedFamily } from "./createComposedFamily-
|
|
1
|
+
import { n as createClassResolver, r as createComponent, t as createComposedFamily } from "./createComposedFamily-DJ2aqfqN.js";
|
|
2
2
|
import { compose } from "./compose.js";
|
|
3
3
|
import { a as borderShorthand, c as numericOrStringScale, i as gridItemRatio, l as numericScale, n as size, o as createTransform, r as gridItem, s as createScale, t as percentageOrAbsolute, u as stringScale } from "./size-CusLCguT.js";
|
|
4
4
|
//#region src/utils/deepMerge.ts
|
|
5
5
|
/**
|
|
6
6
|
* Deep merge utility — replaces lodash.merge for variant accumulation.
|
|
7
7
|
*/
|
|
8
|
+
const isMergeable = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8
9
|
function deepMerge(target, source) {
|
|
9
10
|
const result = { ...target };
|
|
10
|
-
for (const key of Object.keys(source)) if (
|
|
11
|
+
for (const key of Object.keys(source)) if (isMergeable(source[key]) && isMergeable(target[key])) result[key] = deepMerge(target[key], source[key]);
|
|
11
12
|
else result[key] = source[key];
|
|
12
13
|
return result;
|
|
13
14
|
}
|
|
@@ -228,6 +229,149 @@ var Animus = class extends AnimusWithBase {
|
|
|
228
229
|
}
|
|
229
230
|
};
|
|
230
231
|
//#endregion
|
|
232
|
+
//#region src/conditions.ts
|
|
233
|
+
/**
|
|
234
|
+
* Built-in condition aliases (design D8, increment 06).
|
|
235
|
+
*
|
|
236
|
+
* The Panda-compatible media-feature set: motion, print, orientation, contrast,
|
|
237
|
+
* and OS color-scheme preferences. Every alias is a pure `@media` feature query
|
|
238
|
+
* (`kind: 'media'`). Color-mode aliases (`_dark` / `_light`) are DELIBERATELY
|
|
239
|
+
* excluded — they are selector-kind (`[data-color-mode]`) surface owned by the
|
|
240
|
+
* `system-color-scheme` change, not media features.
|
|
241
|
+
*
|
|
242
|
+
* ORDER BAND (inc-03 full-pass): built-ins occupy a reserved band BELOW the user
|
|
243
|
+
* band — one slot of 10 per alias in table row order, 300–380. The user band
|
|
244
|
+
* starts at 500 (`mergeConditions` floors allocation at 490 → first user alias
|
|
245
|
+
* is 500), so built-ins and user registrations never interleave on collision.
|
|
246
|
+
* This mirrors the selector registry's real layout (built-ins 10–440, users
|
|
247
|
+
* 500+).
|
|
248
|
+
*
|
|
249
|
+
* The static type-level mirror is `BuiltInConditionAlias` in `types/config.ts`
|
|
250
|
+
* (added to `KnownUnderscoreKey`'s VALIDATING branch, exactly like
|
|
251
|
+
* `BuiltInSelectorAlias`). Built-ins are NOT members of the augmentable
|
|
252
|
+
* `Conditions` interface: interface members would make publication permanently
|
|
253
|
+
* non-empty, killing inc-04's graceful-degradation contract. The
|
|
254
|
+
* `types.test-d.tsx` drift positives keep the two in sync.
|
|
255
|
+
*/
|
|
256
|
+
const BUILT_IN_CONDITIONS = {
|
|
257
|
+
_motionReduce: {
|
|
258
|
+
value: "@media (prefers-reduced-motion: reduce)",
|
|
259
|
+
order: 300,
|
|
260
|
+
kind: "media"
|
|
261
|
+
},
|
|
262
|
+
_motionSafe: {
|
|
263
|
+
value: "@media (prefers-reduced-motion: no-preference)",
|
|
264
|
+
order: 310,
|
|
265
|
+
kind: "media"
|
|
266
|
+
},
|
|
267
|
+
_print: {
|
|
268
|
+
value: "@media print",
|
|
269
|
+
order: 320,
|
|
270
|
+
kind: "media"
|
|
271
|
+
},
|
|
272
|
+
_portrait: {
|
|
273
|
+
value: "@media (orientation: portrait)",
|
|
274
|
+
order: 330,
|
|
275
|
+
kind: "media"
|
|
276
|
+
},
|
|
277
|
+
_landscape: {
|
|
278
|
+
value: "@media (orientation: landscape)",
|
|
279
|
+
order: 340,
|
|
280
|
+
kind: "media"
|
|
281
|
+
},
|
|
282
|
+
_moreContrast: {
|
|
283
|
+
value: "@media (prefers-contrast: more)",
|
|
284
|
+
order: 350,
|
|
285
|
+
kind: "media"
|
|
286
|
+
},
|
|
287
|
+
_lessContrast: {
|
|
288
|
+
value: "@media (prefers-contrast: less)",
|
|
289
|
+
order: 360,
|
|
290
|
+
kind: "media"
|
|
291
|
+
},
|
|
292
|
+
_osDark: {
|
|
293
|
+
value: "@media (prefers-color-scheme: dark)",
|
|
294
|
+
order: 370,
|
|
295
|
+
kind: "media"
|
|
296
|
+
},
|
|
297
|
+
_osLight: {
|
|
298
|
+
value: "@media (prefers-color-scheme: light)",
|
|
299
|
+
order: 380,
|
|
300
|
+
kind: "media"
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
/** The supported at-rule prefixes, longest-first so `@media`/`@container`/
|
|
304
|
+
* `@supports` match unambiguously. */
|
|
305
|
+
const CONDITION_PREFIXES = [
|
|
306
|
+
["@media", "media"],
|
|
307
|
+
["@container", "container"],
|
|
308
|
+
["@supports", "supports"]
|
|
309
|
+
];
|
|
310
|
+
/**
|
|
311
|
+
* Infer the condition kind from an at-rule value's prefix (design D3).
|
|
312
|
+
* Throws (fail-loud, build time) when the value does not begin with a
|
|
313
|
+
* supported at-rule name. The compile-time complement is `addConditions()`'s
|
|
314
|
+
* value constraint (`@media`/`@container`/`@supports`-prefixed template
|
|
315
|
+
* literals); this runtime throw remains the backstop for untyped callers.
|
|
316
|
+
*/
|
|
317
|
+
function inferConditionKind(value) {
|
|
318
|
+
for (const [prefix, kind] of CONDITION_PREFIXES) if (value.startsWith(prefix)) return kind;
|
|
319
|
+
throw new Error(`addConditions: value "${value}" must begin with @media, @container, or @supports`);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Merge user-provided condition aliases with a base map.
|
|
323
|
+
*
|
|
324
|
+
* - User aliases override built-ins of the same name, preserving the existing
|
|
325
|
+
* order (mirrors `mergeSelectors`).
|
|
326
|
+
* - New aliases allocate orders CONTINUING from the highest existing order
|
|
327
|
+
* (floored at 490, so the first user alias lands at 500) rather than
|
|
328
|
+
* restarting at 500 each call — chained `.addConditions()` calls must not
|
|
329
|
+
* collide on order 500.
|
|
330
|
+
* - Fails loud when a condition alias name is already reserved by the SELECTOR
|
|
331
|
+
* alias registry (`reservedSelectorNames`): the two registries share the `_`
|
|
332
|
+
* namespace at the call site and their names MUST be disjoint, else a block
|
|
333
|
+
* key would be ambiguous. The error names the offending alias and both
|
|
334
|
+
* registries.
|
|
335
|
+
*/
|
|
336
|
+
function mergeConditions(base, custom, reservedSelectorNames = /* @__PURE__ */ new Set()) {
|
|
337
|
+
const merged = { ...base };
|
|
338
|
+
let nextOrder = Math.max(490, ...Object.values(merged).map((c) => c.order)) + 10;
|
|
339
|
+
for (const [key, value] of Object.entries(custom)) {
|
|
340
|
+
if (reservedSelectorNames.has(key)) throw new Error(`addConditions: alias "${key}" is already registered in the selector alias registry — condition and selector alias names must be disjoint (rename the condition alias or the selector alias).`);
|
|
341
|
+
const kind = inferConditionKind(value);
|
|
342
|
+
if (key in merged) merged[key] = {
|
|
343
|
+
value,
|
|
344
|
+
order: merged[key].order,
|
|
345
|
+
kind
|
|
346
|
+
};
|
|
347
|
+
else {
|
|
348
|
+
merged[key] = {
|
|
349
|
+
value,
|
|
350
|
+
order: nextOrder,
|
|
351
|
+
kind
|
|
352
|
+
};
|
|
353
|
+
nextOrder += 10;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return merged;
|
|
357
|
+
}
|
|
358
|
+
/** Get the sorted alias keys for deterministic cascade ordering. */
|
|
359
|
+
function getSortedConditionKeys(map) {
|
|
360
|
+
return Object.keys(map).sort((a, b) => map[a].order - map[b].order);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Serialize the condition map for the extraction pipeline.
|
|
364
|
+
* Emits `alias → { value, order, kind }` in registry (cascade) order so the
|
|
365
|
+
* JSON is deterministic. Distinct in SHAPE from the selector map (which
|
|
366
|
+
* flattens to `alias → selector`) — the object entry is forward-compatible
|
|
367
|
+
* with a future multi-valued `values: string[]` property (design D3).
|
|
368
|
+
*/
|
|
369
|
+
function serializeConditionMap(map) {
|
|
370
|
+
const conditions = {};
|
|
371
|
+
for (const key of getSortedConditionKeys(map)) conditions[key] = map[key];
|
|
372
|
+
return conditions;
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
231
375
|
//#region src/keyframes.ts
|
|
232
376
|
const fnv1a = (input) => {
|
|
233
377
|
let hash = 2166136261;
|
|
@@ -423,27 +567,52 @@ function serializeSelectorMap(map) {
|
|
|
423
567
|
}
|
|
424
568
|
//#endregion
|
|
425
569
|
//#region src/SystemBuilder.ts
|
|
570
|
+
function orderedPropertiesEqual(existing, incoming) {
|
|
571
|
+
if (existing === incoming) return true;
|
|
572
|
+
if (!existing || !incoming || existing.length !== incoming.length) return false;
|
|
573
|
+
return existing.every((property, index) => property === incoming[index]);
|
|
574
|
+
}
|
|
575
|
+
function arePropDefinitionsEqual(existing, incoming) {
|
|
576
|
+
return existing.property === incoming.property && orderedPropertiesEqual(existing.properties, incoming.properties) && existing.scale === incoming.scale && existing.variable === incoming.variable && existing.negative === incoming.negative && existing.strict === incoming.strict && existing.currentVar === incoming.currentVar && existing.transform === incoming.transform;
|
|
577
|
+
}
|
|
426
578
|
var SystemBuilder = class SystemBuilder {
|
|
427
579
|
#propRegistry;
|
|
428
580
|
#groupRegistry;
|
|
429
581
|
#selectorRegistry;
|
|
430
582
|
#includesRegistry;
|
|
431
|
-
|
|
583
|
+
#conditionRegistry;
|
|
584
|
+
constructor(propRegistry, groupRegistry, selectorRegistry, includesRegistry, conditionRegistry) {
|
|
432
585
|
this.#propRegistry = propRegistry || {};
|
|
433
586
|
this.#groupRegistry = groupRegistry || {};
|
|
434
587
|
this.#selectorRegistry = selectorRegistry || { ...BUILT_IN_SELECTORS };
|
|
435
588
|
this.#includesRegistry = includesRegistry || [];
|
|
589
|
+
this.#conditionRegistry = conditionRegistry || { ...BUILT_IN_CONDITIONS };
|
|
436
590
|
}
|
|
437
591
|
addSelectors(selectors) {
|
|
592
|
+
for (const name of Object.keys(selectors)) if (name in this.#conditionRegistry) throw new Error(`addSelectors: "${name}" is already registered as a condition alias; a name resolves through exactly one registry. Pick a distinct name.`);
|
|
438
593
|
const merged = mergeSelectors(this.#selectorRegistry, selectors);
|
|
439
|
-
return new SystemBuilder(this.#propRegistry, this.#groupRegistry, merged, this.#includesRegistry);
|
|
594
|
+
return new SystemBuilder(this.#propRegistry, this.#groupRegistry, merged, this.#includesRegistry, this.#conditionRegistry);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Register condition aliases (`_motionReduce`, `_cardSm`, …) → at-rule
|
|
598
|
+
* condition strings (`@media …` / `@container …` / `@supports …`).
|
|
599
|
+
* Recognized as block keys in style objects; user aliases override built-ins
|
|
600
|
+
* of the same name (design D3). Keys are constrained to `_`-prefixed aliases
|
|
601
|
+
* and values to `@`-prefixed at-rule strings — a value that does not begin
|
|
602
|
+
* with an at-rule name is a compile-time type error (design D9; the runtime
|
|
603
|
+
* `inferConditionKind` throw is defense-in-depth). The registered keys are
|
|
604
|
+
* accumulated into the phantom `Conds` union and surfaced on `build()`.
|
|
605
|
+
*/
|
|
606
|
+
addConditions(conditions) {
|
|
607
|
+
const merged = mergeConditions(this.#conditionRegistry, conditions, new Set(Object.keys(this.#selectorRegistry)));
|
|
608
|
+
return new SystemBuilder(this.#propRegistry, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, merged);
|
|
440
609
|
}
|
|
441
610
|
addGroup(name, config) {
|
|
442
611
|
if (name in this.#propRegistry) throw new Error(`Group name "${name}" collides with an existing prop name. Group names and prop names must be disjoint.`);
|
|
443
612
|
for (const key of Object.keys(config)) if (key in this.#propRegistry) {
|
|
444
613
|
const existing = this.#propRegistry[key];
|
|
445
614
|
const incoming = config[key];
|
|
446
|
-
if (existing
|
|
615
|
+
if (!arePropDefinitionsEqual(existing, incoming)) throw new Error(`Prop "${key}" already registered with a different definition. Existing: property="${existing.property}", scale="${String(existing.scale)}". Incoming: property="${incoming.property}", scale="${String(incoming.scale)}".`);
|
|
447
616
|
}
|
|
448
617
|
const nextProps = {
|
|
449
618
|
...this.#propRegistry,
|
|
@@ -453,31 +622,33 @@ var SystemBuilder = class SystemBuilder {
|
|
|
453
622
|
return new SystemBuilder(nextProps, {
|
|
454
623
|
...this.#groupRegistry,
|
|
455
624
|
...newGroup
|
|
456
|
-
}, this.#selectorRegistry, this.#includesRegistry);
|
|
625
|
+
}, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry);
|
|
457
626
|
}
|
|
458
627
|
addProps(config) {
|
|
459
628
|
for (const key of Object.keys(config)) if (key in this.#groupRegistry) throw new Error(`Prop name "${key}" collides with an existing group name. Group names and prop names must be disjoint.`);
|
|
460
629
|
for (const key of Object.keys(config)) if (key in this.#propRegistry) {
|
|
461
630
|
const existing = this.#propRegistry[key];
|
|
462
631
|
const incoming = config[key];
|
|
463
|
-
if (existing
|
|
632
|
+
if (!arePropDefinitionsEqual(existing, incoming)) throw new Error(`Prop "${key}" already registered with a different definition.`);
|
|
464
633
|
}
|
|
465
634
|
return new SystemBuilder({
|
|
466
635
|
...this.#propRegistry,
|
|
467
636
|
...config
|
|
468
|
-
}, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry);
|
|
637
|
+
}, this.#groupRegistry, this.#selectorRegistry, this.#includesRegistry, this.#conditionRegistry);
|
|
469
638
|
}
|
|
470
639
|
build() {
|
|
471
640
|
const animus = new Animus(this.#propRegistry, this.#groupRegistry);
|
|
472
641
|
const propRegistry = this.#propRegistry;
|
|
473
642
|
const groupRegistry = this.#groupRegistry;
|
|
474
643
|
const selectorRegistry = this.#selectorRegistry;
|
|
644
|
+
const conditionRegistry = this.#conditionRegistry;
|
|
475
645
|
const system = Object.assign(animus, { toConfig: () => {
|
|
476
|
-
return serializeInstance(propRegistry, groupRegistry, selectorRegistry);
|
|
646
|
+
return serializeInstance(propRegistry, groupRegistry, selectorRegistry, conditionRegistry);
|
|
477
647
|
} });
|
|
478
|
-
const createGlobalStyles = ((styles) => ({
|
|
648
|
+
const createGlobalStyles = ((styles, options) => ({
|
|
479
649
|
__brand: "GlobalStyleBlock",
|
|
480
|
-
styles
|
|
650
|
+
styles,
|
|
651
|
+
...options?.fontFaces?.length ? { fontFaces: [...options.fontFaces] } : {}
|
|
481
652
|
}));
|
|
482
653
|
const createKeyframes = ((frames) => keyframes(frames));
|
|
483
654
|
return {
|
|
@@ -487,7 +658,7 @@ var SystemBuilder = class SystemBuilder {
|
|
|
487
658
|
};
|
|
488
659
|
}
|
|
489
660
|
};
|
|
490
|
-
function serializeInstance(propRegistry, groupRegistry, selectorRegistry) {
|
|
661
|
+
function serializeInstance(propRegistry, groupRegistry, selectorRegistry, conditionRegistry) {
|
|
491
662
|
const serialized = {};
|
|
492
663
|
const transforms = {};
|
|
493
664
|
for (const [propName, entry] of Object.entries(propRegistry)) {
|
|
@@ -509,11 +680,13 @@ function serializeInstance(propRegistry, groupRegistry, selectorRegistry) {
|
|
|
509
680
|
serialized[propName] = s;
|
|
510
681
|
}
|
|
511
682
|
const { selectors } = serializeSelectorMap(selectorRegistry);
|
|
683
|
+
const conditions = serializeConditionMap(conditionRegistry);
|
|
512
684
|
return {
|
|
513
685
|
propConfig: JSON.stringify(serialized),
|
|
514
686
|
groupRegistry: JSON.stringify(groupRegistry),
|
|
515
687
|
transforms,
|
|
516
|
-
selectorAliases: JSON.stringify(selectors)
|
|
688
|
+
selectorAliases: JSON.stringify(selectors),
|
|
689
|
+
conditionAliases: JSON.stringify(conditions)
|
|
517
690
|
};
|
|
518
691
|
}
|
|
519
692
|
function createSystem(config) {
|
|
@@ -618,6 +791,109 @@ function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, pre
|
|
|
618
791
|
} else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, aliasPath);
|
|
619
792
|
}
|
|
620
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Reserved mode name (D4). "System" is modeled as the ABSENCE of
|
|
796
|
+
* `data-color-mode`, never as a mode: an attribute value `system` would defeat
|
|
797
|
+
* the `:root:not([data-color-mode])` guard while matching no mode block.
|
|
798
|
+
*/
|
|
799
|
+
const RESERVED_MODE_NAME = "system";
|
|
800
|
+
/**
|
|
801
|
+
* Theme keys owned by the color-mode options. A scale may not claim them —
|
|
802
|
+
* they are skipped by `flattenTheme` and read back as option objects in
|
|
803
|
+
* `build()`, so a same-named scale would emit zero tokens AND fabricate a
|
|
804
|
+
* manifest field from its values.
|
|
805
|
+
*
|
|
806
|
+
* NOTE: the pre-existing `mode` / `modes` / `breakpoints` structural keys have
|
|
807
|
+
* the same hole and are deliberately NOT covered here (out of scope).
|
|
808
|
+
*/
|
|
809
|
+
const RESERVED_THEME_KEYS = new Set(["systemPreference", "browserColorScheme"]);
|
|
810
|
+
const COLOR_SCHEME_VALUES = new Set([
|
|
811
|
+
"light",
|
|
812
|
+
"dark",
|
|
813
|
+
"normal"
|
|
814
|
+
]);
|
|
815
|
+
/**
|
|
816
|
+
* The axis → scheme pairs the mapping forces: the mode named for the OS light
|
|
817
|
+
* preference must classify `light`, the dark one `dark`. Single source for
|
|
818
|
+
* BOTH the default fill and the conflict check in
|
|
819
|
+
* {@link resolveColorModeOptions} — one rule, one table.
|
|
820
|
+
*/
|
|
821
|
+
const MAPPING_FORCED_SCHEMES = [["light", "light"], ["dark", "dark"]];
|
|
822
|
+
/**
|
|
823
|
+
* Merge an incoming option object over the one already on the theme, mirroring
|
|
824
|
+
* `merge`'s per-key override. Returns `undefined` when neither side supplied
|
|
825
|
+
* one, so an unconfigured theme never gains the key (byte parity, G4).
|
|
826
|
+
*/
|
|
827
|
+
function mergeOptionObject(existing, incoming) {
|
|
828
|
+
const hasExisting = isObject(existing);
|
|
829
|
+
const hasIncoming = isObject(incoming);
|
|
830
|
+
if (!hasExisting && !hasIncoming) return void 0;
|
|
831
|
+
return {
|
|
832
|
+
...hasExisting ? existing : {},
|
|
833
|
+
...hasIncoming ? incoming : {}
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/** Reject the reserved mode name (D4) wherever a mode set is declared or merged. */
|
|
837
|
+
function validateReservedModeNames(modeNames) {
|
|
838
|
+
for (const modeName of modeNames) if (modeName === RESERVED_MODE_NAME) throw new Error(`addColorModes: '${RESERVED_MODE_NAME}' is a reserved mode name — the system preference is represented by the absence of the data-color-mode attribute, never by a declared mode. Rename it. Declared modes: ${modeNames.join(", ")}`);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Normalize AND validate the optional system-participation options against the
|
|
842
|
+
* declared modes, returning the normalized classification. One entry point on
|
|
843
|
+
* purpose: the D3-amendment fill (mapping-named modes default to their forced
|
|
844
|
+
* schemes) must always run before totality is checked, so exposing fill and
|
|
845
|
+
* validation separately would make "validate an unfilled map" representable.
|
|
846
|
+
* An explicit entry survives the fill's spread and is still conflict-checked,
|
|
847
|
+
* so a wrong value errors rather than being silently corrected. `undefined`
|
|
848
|
+
* passes through untouched — the classification stays opt-in, and a theme with
|
|
849
|
+
* only `systemPreference` emits no `color-scheme` (byte parity, G4).
|
|
850
|
+
*
|
|
851
|
+
* ALWAYS called with MERGED state — `theme.modes` unions across `addColorModes`
|
|
852
|
+
* calls and `from()` composition, so a per-call view both misses invalidation
|
|
853
|
+
* (a later mode declaration un-totals a carried classification) and invents
|
|
854
|
+
* false rejections (a mapping naming a mode declared by an earlier call).
|
|
855
|
+
* Run at both gates: `addColorModes` (fail fast) and `build()` (authoritative —
|
|
856
|
+
* `from()` composition never passes through `addColorModes`; only build()'s
|
|
857
|
+
* resolved map feeds emission and the manifest).
|
|
858
|
+
*
|
|
859
|
+
* Error tone mirrors `validateModeAliases`: `addColorModes:` prefix plus the
|
|
860
|
+
* available names.
|
|
861
|
+
*/
|
|
862
|
+
function resolveColorModeOptions(modeNames, systemPreference, suppliedBrowserColorScheme) {
|
|
863
|
+
let browserColorScheme = suppliedBrowserColorScheme;
|
|
864
|
+
if (browserColorScheme && systemPreference) {
|
|
865
|
+
const forced = {};
|
|
866
|
+
for (const [axis, scheme] of MAPPING_FORCED_SCHEMES) {
|
|
867
|
+
const modeName = systemPreference[axis];
|
|
868
|
+
if (typeof modeName === "string") forced[modeName] = scheme;
|
|
869
|
+
}
|
|
870
|
+
browserColorScheme = {
|
|
871
|
+
...forced,
|
|
872
|
+
...suppliedBrowserColorScheme
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
const available = `Available modes: ${modeNames.join(", ")}`;
|
|
876
|
+
const declared = new Set(modeNames);
|
|
877
|
+
if (systemPreference) for (const axis of ["light", "dark"]) {
|
|
878
|
+
const modeName = systemPreference[axis];
|
|
879
|
+
if (typeof modeName !== "string" || modeName === "") throw new Error(`addColorModes: systemPreference requires both 'light' and 'dark' to name a declared mode — '${axis}' is missing. ${available}`);
|
|
880
|
+
if (modeName === RESERVED_MODE_NAME) throw new Error(`addColorModes: '${RESERVED_MODE_NAME}' is reserved and cannot be used as systemPreference.${axis} — the system preference is represented by the absence of the data-color-mode attribute. ${available}`);
|
|
881
|
+
if (!declared.has(modeName)) throw new Error(`addColorModes: systemPreference.${axis} references unknown mode '${modeName}'. ${available}`);
|
|
882
|
+
}
|
|
883
|
+
if (!browserColorScheme) return browserColorScheme;
|
|
884
|
+
for (const [modeName, value] of Object.entries(browserColorScheme)) {
|
|
885
|
+
if (!declared.has(modeName)) throw new Error(`addColorModes: browserColorScheme references unknown mode '${modeName}'. ${available}`);
|
|
886
|
+
if (!COLOR_SCHEME_VALUES.has(value)) throw new Error(`addColorModes: browserColorScheme['${modeName}'] must be 'light', 'dark', or 'normal', got ${JSON.stringify(value)}.`);
|
|
887
|
+
}
|
|
888
|
+
for (const modeName of modeNames) if (!(modeName in browserColorScheme)) throw new Error(`addColorModes: browserColorScheme must classify every declared mode — mode '${modeName}' is unclassified. ${available}`);
|
|
889
|
+
if (!systemPreference) return browserColorScheme;
|
|
890
|
+
for (const [axis, expected] of MAPPING_FORCED_SCHEMES) {
|
|
891
|
+
const modeName = systemPreference[axis];
|
|
892
|
+
const classification = browserColorScheme[modeName];
|
|
893
|
+
if (classification !== expected) throw new Error(`addColorModes: browserColorScheme conflicts with systemPreference — mode '${modeName}' is mapped to the OS ${axis} preference but classified '${classification}'; expected '${expected}'.`);
|
|
894
|
+
}
|
|
895
|
+
return browserColorScheme;
|
|
896
|
+
}
|
|
621
897
|
/** Validate all color entries, throwing on invalid values. */
|
|
622
898
|
function validateColors(colors) {
|
|
623
899
|
for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
|
|
@@ -627,14 +903,19 @@ function createState(theme) {
|
|
|
627
903
|
return {
|
|
628
904
|
theme: theme || { breakpoints: {} },
|
|
629
905
|
emittedScales: /* @__PURE__ */ new Set(),
|
|
630
|
-
contextualVars: /* @__PURE__ */ new Map()
|
|
906
|
+
contextualVars: /* @__PURE__ */ new Map(),
|
|
907
|
+
contextualVarRegistrations: /* @__PURE__ */ new Map(),
|
|
908
|
+
hasLegacyManifestSource: false
|
|
631
909
|
};
|
|
632
910
|
}
|
|
633
911
|
function copyState(state, nextTheme) {
|
|
634
912
|
const next = {
|
|
635
913
|
theme: nextTheme,
|
|
636
914
|
emittedScales: new Set(state.emittedScales),
|
|
637
|
-
contextualVars: /* @__PURE__ */ new Map()
|
|
915
|
+
contextualVars: /* @__PURE__ */ new Map(),
|
|
916
|
+
contextualVarRegistrations: new Map(state.contextualVarRegistrations),
|
|
917
|
+
...state.carriedManifestV2 ? { carriedManifestV2: { ...state.carriedManifestV2 } } : {},
|
|
918
|
+
hasLegacyManifestSource: state.hasLegacyManifestSource
|
|
638
919
|
};
|
|
639
920
|
for (const [scale, vars] of state.contextualVars) next.contextualVars.set(scale, [...vars]);
|
|
640
921
|
return next;
|
|
@@ -667,6 +948,17 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
667
948
|
next._state.emittedScales.add(scale === "colors" ? "colors" : scale);
|
|
668
949
|
}
|
|
669
950
|
if (manifest?.contextualVars) for (const [scale, vars] of Object.entries(manifest.contextualVars)) next._state.contextualVars.set(scale, [...vars]);
|
|
951
|
+
if (manifest) if (manifest.manifestVersion === 2) {
|
|
952
|
+
next._state.carriedManifestV2 = {
|
|
953
|
+
tokenDefinitions: manifest.tokenDefinitions,
|
|
954
|
+
modeAliasDefinitions: manifest.modeAliasDefinitions,
|
|
955
|
+
registrations: manifest.registrations,
|
|
956
|
+
emitterVersion: manifest.emitterVersion,
|
|
957
|
+
contractHash: manifest.contractHash,
|
|
958
|
+
cssFragments: manifest.cssFragments
|
|
959
|
+
};
|
|
960
|
+
if (manifest.registrations) for (const [name, registration] of Object.entries(manifest.registrations)) next._state.contextualVarRegistrations.set(name, registration);
|
|
961
|
+
} else next._state.hasLegacyManifestSource = true;
|
|
670
962
|
return next;
|
|
671
963
|
}
|
|
672
964
|
addColors(colors) {
|
|
@@ -676,31 +968,46 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
676
968
|
next._state.emittedScales.add("colors");
|
|
677
969
|
return next;
|
|
678
970
|
}
|
|
679
|
-
addColorModes(initialMode, modeConfig) {
|
|
971
|
+
addColorModes(initialMode, modeConfig, options) {
|
|
680
972
|
const nestedColors = this._state.theme.colors || {};
|
|
681
973
|
const flatColors = flattenToDotPaths(nestedColors);
|
|
682
974
|
const flatColorKeys = Object.keys(flatColors);
|
|
975
|
+
const existingModes = isObject(this._state.theme.modes) ? this._state.theme.modes : {};
|
|
976
|
+
const modeNames = Object.keys({
|
|
977
|
+
...existingModes,
|
|
978
|
+
...modeConfig
|
|
979
|
+
});
|
|
980
|
+
validateReservedModeNames(modeNames);
|
|
683
981
|
for (const [modeName, modeAliases] of Object.entries(modeConfig)) validateModeAliases(modeName, modeAliases, nestedColors, flatColorKeys, "");
|
|
982
|
+
const systemPreference = mergeOptionObject(this._state.theme.systemPreference, options?.systemPreference);
|
|
983
|
+
const browserColorScheme = mergeOptionObject(this._state.theme.browserColorScheme, options?.browserColorScheme);
|
|
984
|
+
resolveColorModeOptions(modeNames, systemPreference, browserColorScheme);
|
|
684
985
|
const nextTheme = merge({}, this._state.theme, {
|
|
685
986
|
modes: modeConfig,
|
|
686
|
-
mode: initialMode
|
|
987
|
+
mode: initialMode,
|
|
988
|
+
...systemPreference ? { systemPreference } : {},
|
|
989
|
+
...browserColorScheme ? { browserColorScheme } : {}
|
|
687
990
|
});
|
|
688
991
|
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
689
992
|
}
|
|
690
993
|
addScale(config) {
|
|
691
994
|
const { name, values, emit } = config;
|
|
995
|
+
if (RESERVED_THEME_KEYS.has(name)) throw new Error(`addScale: '${name}' is a reserved theme key owned by addColorModes options — it is skipped by the token flatten pass and read back as an option object, so a scale by this name would emit no tokens. Choose another scale name.`);
|
|
692
996
|
const nextTheme = merge({}, this._state.theme, { [name]: values });
|
|
693
997
|
const next = new ThemeBuilder(copyState(this._state, nextTheme));
|
|
694
998
|
if (emit) next._state.emittedScales.add(name);
|
|
695
999
|
return next;
|
|
696
1000
|
}
|
|
697
|
-
declareContextualVars(vars) {
|
|
1001
|
+
declareContextualVars(vars, registrations) {
|
|
698
1002
|
for (const scale of Object.keys(vars)) if (!(scale in this._state.theme)) throw new Error(`declareContextualVars: scale '${scale}' not found — call addColors or addScale first`);
|
|
699
1003
|
const next = new ThemeBuilder(copyState(this._state, this._state.theme));
|
|
700
1004
|
for (const [scale, names] of Object.entries(vars)) {
|
|
701
1005
|
const existing = next._state.contextualVars.get(scale) || [];
|
|
702
1006
|
next._state.contextualVars.set(scale, [...existing, ...names]);
|
|
703
1007
|
}
|
|
1008
|
+
if (registrations) {
|
|
1009
|
+
for (const [name, registration] of Object.entries(registrations)) if (registration) next._state.contextualVarRegistrations.set(name, registration);
|
|
1010
|
+
}
|
|
704
1011
|
return next;
|
|
705
1012
|
}
|
|
706
1013
|
extendScale(key, updateFn) {
|
|
@@ -715,7 +1022,11 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
715
1022
|
const theme = merge({}, this._state.theme);
|
|
716
1023
|
const emittedScales = this._state.emittedScales;
|
|
717
1024
|
const contextualVars = this._state.contextualVars;
|
|
718
|
-
const
|
|
1025
|
+
const systemPreference = isObject(theme.systemPreference) ? theme.systemPreference : void 0;
|
|
1026
|
+
const mergedModeNames = isObject(theme.modes) ? Object.keys(theme.modes) : [];
|
|
1027
|
+
validateReservedModeNames(mergedModeNames);
|
|
1028
|
+
const browserColorScheme = resolveColorModeOptions(mergedModeNames, systemPreference, isObject(theme.browserColorScheme) ? theme.browserColorScheme : void 0);
|
|
1029
|
+
const { tokenMap, variableMap, variables, modeVariables, modeTokens, tokenDefinitions, modeAliasDefinitions } = flattenTheme(theme, emittedScales);
|
|
719
1030
|
resolveTokenRefs(tokenMap, variableMap, emittedScales);
|
|
720
1031
|
const bpVariables = {};
|
|
721
1032
|
if (theme.breakpoints && isObject(theme.breakpoints)) for (const [key, value] of Object.entries(theme.breakpoints)) bpVariables[`--breakpoint-${key}`] = `${value}px`;
|
|
@@ -724,7 +1035,44 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
724
1035
|
contextualVarsSerialized = {};
|
|
725
1036
|
for (const [scale, vars] of contextualVars) contextualVarsSerialized[scale] = vars;
|
|
726
1037
|
}
|
|
727
|
-
const
|
|
1038
|
+
const propertyCss = buildPropertyRegistrationCss(contextualVars, this._state.contextualVarRegistrations);
|
|
1039
|
+
const baseVariableCss = buildVariableCss(variables, bpVariables, modeVariables, {
|
|
1040
|
+
initialMode: typeof theme.mode === "string" ? theme.mode : void 0,
|
|
1041
|
+
systemPreference,
|
|
1042
|
+
browserColorScheme
|
|
1043
|
+
});
|
|
1044
|
+
const variableCss = propertyCss ? baseVariableCss ? `${propertyCss}\n\n${baseVariableCss}` : propertyCss : baseVariableCss;
|
|
1045
|
+
let manifestV2Fields = {};
|
|
1046
|
+
if (!this._state.hasLegacyManifestSource) {
|
|
1047
|
+
const registrations = Object.fromEntries(this._state.contextualVarRegistrations);
|
|
1048
|
+
const cssFragments = [];
|
|
1049
|
+
if (propertyCss) cssFragments.push({
|
|
1050
|
+
id: "registrations",
|
|
1051
|
+
kind: "registrations",
|
|
1052
|
+
cssText: propertyCss
|
|
1053
|
+
});
|
|
1054
|
+
if (baseVariableCss) cssFragments.push({
|
|
1055
|
+
id: "base",
|
|
1056
|
+
kind: "base",
|
|
1057
|
+
cssText: baseVariableCss
|
|
1058
|
+
});
|
|
1059
|
+
manifestV2Fields = {
|
|
1060
|
+
manifestVersion: 2,
|
|
1061
|
+
tokenDefinitions,
|
|
1062
|
+
modeAliasDefinitions,
|
|
1063
|
+
registrations,
|
|
1064
|
+
emitterVersion: EMITTER_VERSION,
|
|
1065
|
+
contractHash: computeContractHash({
|
|
1066
|
+
tokenDefinitions,
|
|
1067
|
+
modeAliasDefinitions,
|
|
1068
|
+
initialMode: typeof theme.mode === "string" ? theme.mode : void 0,
|
|
1069
|
+
registrations,
|
|
1070
|
+
systemPreference,
|
|
1071
|
+
browserColorScheme
|
|
1072
|
+
}),
|
|
1073
|
+
cssFragments
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
728
1076
|
const manifest = {
|
|
729
1077
|
tokenMap: {
|
|
730
1078
|
...tokenMap,
|
|
@@ -733,7 +1081,10 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
733
1081
|
variableMap,
|
|
734
1082
|
modes: modeTokens,
|
|
735
1083
|
variableCss,
|
|
736
|
-
...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {}
|
|
1084
|
+
...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {},
|
|
1085
|
+
...systemPreference ? { systemPreference } : {},
|
|
1086
|
+
...browserColorScheme ? { browserColorScheme } : {},
|
|
1087
|
+
...manifestV2Fields
|
|
737
1088
|
};
|
|
738
1089
|
Object.defineProperty(theme, "manifest", {
|
|
739
1090
|
value: manifest,
|
|
@@ -787,9 +1138,11 @@ function flattenTheme(theme, emittedScales) {
|
|
|
787
1138
|
const variables = {};
|
|
788
1139
|
const modeVariables = {};
|
|
789
1140
|
const modeTokens = {};
|
|
1141
|
+
const tokenDefinitions = {};
|
|
1142
|
+
const modeAliasDefinitions = {};
|
|
790
1143
|
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
791
1144
|
if (scaleName.startsWith("_")) continue;
|
|
792
|
-
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
|
|
1145
|
+
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes" || scaleName === "systemPreference" || scaleName === "browserColorScheme") continue;
|
|
793
1146
|
if (typeof scaleValue === "function") continue;
|
|
794
1147
|
if (!isObject(scaleValue)) continue;
|
|
795
1148
|
const flat = flattenToDotPaths(scaleValue);
|
|
@@ -798,6 +1151,7 @@ function flattenTheme(theme, emittedScales) {
|
|
|
798
1151
|
const tokenPath = `${scaleName}.${dotKey}`;
|
|
799
1152
|
const dashKey = dotToDash(dotKey);
|
|
800
1153
|
const varName = `--${scaleName === "colors" ? "color" : scaleName}-${dashKey}`;
|
|
1154
|
+
tokenDefinitions[tokenPath] = parseTokenDefinition(String(rawValue));
|
|
801
1155
|
if (isEmitted) {
|
|
802
1156
|
tokenMap[tokenPath] = `var(${varName})`;
|
|
803
1157
|
variableMap[tokenPath] = varName;
|
|
@@ -812,15 +1166,18 @@ function flattenTheme(theme, emittedScales) {
|
|
|
812
1166
|
const flatAliases = flattenToDotPaths(modeAliases);
|
|
813
1167
|
const modeVars = {};
|
|
814
1168
|
const modeVals = {};
|
|
1169
|
+
const modeAliasDefs = {};
|
|
815
1170
|
for (const [aliasDotKey, colorRef] of Object.entries(flatAliases)) {
|
|
816
1171
|
if (typeof colorRef !== "string") continue;
|
|
817
1172
|
const varName = `--color-${dotToDash(aliasDotKey)}`;
|
|
1173
|
+
modeAliasDefs[aliasDotKey] = colorRef;
|
|
818
1174
|
const rawValue = flatColors[colorRef];
|
|
819
1175
|
modeVals[`colors.${aliasDotKey}`] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
820
1176
|
modeVars[varName] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
821
1177
|
}
|
|
822
1178
|
modeVariables[modeName] = modeVars;
|
|
823
1179
|
modeTokens[modeName] = modeVals;
|
|
1180
|
+
modeAliasDefinitions[modeName] = modeAliasDefs;
|
|
824
1181
|
}
|
|
825
1182
|
const initialMode = theme.mode;
|
|
826
1183
|
if (initialMode && modeVariables[initialMode]) {
|
|
@@ -842,10 +1199,239 @@ function flattenTheme(theme, emittedScales) {
|
|
|
842
1199
|
variableMap,
|
|
843
1200
|
variables,
|
|
844
1201
|
modeVariables,
|
|
845
|
-
modeTokens
|
|
1202
|
+
modeTokens,
|
|
1203
|
+
tokenDefinitions,
|
|
1204
|
+
modeAliasDefinitions
|
|
846
1205
|
};
|
|
847
1206
|
}
|
|
848
1207
|
/**
|
|
1208
|
+
* Classify a RAW token value into its authored form (manifest v2, D6). MUST
|
|
1209
|
+
* run before `resolveTokenRefs` — resolution rewrites the string, and the
|
|
1210
|
+
* authored graph cannot be reconstructed from resolved CSS (D8). Uses
|
|
1211
|
+
* `matchAll` so the shared global {@link TOKEN_REF_RE} never carries a stale
|
|
1212
|
+
* `lastIndex` between callers.
|
|
1213
|
+
*/
|
|
1214
|
+
function parseTokenDefinition(rawValue) {
|
|
1215
|
+
const references = [];
|
|
1216
|
+
for (const match of rawValue.matchAll(TOKEN_REF_RE)) {
|
|
1217
|
+
const ref = match[1];
|
|
1218
|
+
const slashIdx = ref.indexOf("/");
|
|
1219
|
+
references.push(slashIdx === -1 ? { path: ref } : {
|
|
1220
|
+
path: ref.slice(0, slashIdx),
|
|
1221
|
+
opacity: ref.slice(slashIdx + 1)
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
if (references.length === 0) return {
|
|
1225
|
+
kind: "literal",
|
|
1226
|
+
value: rawValue
|
|
1227
|
+
};
|
|
1228
|
+
return {
|
|
1229
|
+
kind: "reference",
|
|
1230
|
+
value: rawValue,
|
|
1231
|
+
references
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Version of the CSS emitter that composed a manifest's fragments. Bump when
|
|
1236
|
+
* ANY emitted byte changes for the same authored input — composition (D8)
|
|
1237
|
+
* regenerates dirty sections "under the pinned emitter version", and this is
|
|
1238
|
+
* that pin.
|
|
1239
|
+
*/
|
|
1240
|
+
const EMITTER_VERSION = 1;
|
|
1241
|
+
/**
|
|
1242
|
+
* Sorted-key canonical form for hashing: object keys are emitted in sorted
|
|
1243
|
+
* order at every depth so the digest is independent of insertion order;
|
|
1244
|
+
* arrays keep authored order (reference order is contractual).
|
|
1245
|
+
*/
|
|
1246
|
+
function canonicalize(value) {
|
|
1247
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1248
|
+
if (isObject(value)) {
|
|
1249
|
+
const record = value;
|
|
1250
|
+
const sorted = {};
|
|
1251
|
+
for (const key of Object.keys(record).sort()) sorted[key] = canonicalize(record[key]);
|
|
1252
|
+
return sorted;
|
|
1253
|
+
}
|
|
1254
|
+
return value;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* sha256 hex digest. Primary path is `node:crypto` `createHash('sha256')`,
|
|
1258
|
+
* reached via `process.getBuiltinModule` so this module gains NO static
|
|
1259
|
+
* `node:crypto` import edge: built themes execute inside client bundles today
|
|
1260
|
+
* (e2e fixtures inline `createTheme(…).build()` at module init), and a static
|
|
1261
|
+
* builtin import is exactly what `./bootstrap` documents as forbidden in app
|
|
1262
|
+
* bundles. Non-Node runtimes use the pure fallback below, which produces
|
|
1263
|
+
* identical hex for identical input — determinism holds across environments.
|
|
1264
|
+
*/
|
|
1265
|
+
function sha256Hex(input) {
|
|
1266
|
+
const nodeCrypto = globalThis.process?.getBuiltinModule?.("node:crypto");
|
|
1267
|
+
if (nodeCrypto?.createHash) return nodeCrypto.createHash("sha256").update(input).digest("hex");
|
|
1268
|
+
return sha256HexFallback(input);
|
|
1269
|
+
}
|
|
1270
|
+
/** SHA-256 round constants (FIPS 180-4 §4.2.2). */
|
|
1271
|
+
const SHA256_K = new Uint32Array([
|
|
1272
|
+
1116352408,
|
|
1273
|
+
1899447441,
|
|
1274
|
+
3049323471,
|
|
1275
|
+
3921009573,
|
|
1276
|
+
961987163,
|
|
1277
|
+
1508970993,
|
|
1278
|
+
2453635748,
|
|
1279
|
+
2870763221,
|
|
1280
|
+
3624381080,
|
|
1281
|
+
310598401,
|
|
1282
|
+
607225278,
|
|
1283
|
+
1426881987,
|
|
1284
|
+
1925078388,
|
|
1285
|
+
2162078206,
|
|
1286
|
+
2614888103,
|
|
1287
|
+
3248222580,
|
|
1288
|
+
3835390401,
|
|
1289
|
+
4022224774,
|
|
1290
|
+
264347078,
|
|
1291
|
+
604807628,
|
|
1292
|
+
770255983,
|
|
1293
|
+
1249150122,
|
|
1294
|
+
1555081692,
|
|
1295
|
+
1996064986,
|
|
1296
|
+
2554220882,
|
|
1297
|
+
2821834349,
|
|
1298
|
+
2952996808,
|
|
1299
|
+
3210313671,
|
|
1300
|
+
3336571891,
|
|
1301
|
+
3584528711,
|
|
1302
|
+
113926993,
|
|
1303
|
+
338241895,
|
|
1304
|
+
666307205,
|
|
1305
|
+
773529912,
|
|
1306
|
+
1294757372,
|
|
1307
|
+
1396182291,
|
|
1308
|
+
1695183700,
|
|
1309
|
+
1986661051,
|
|
1310
|
+
2177026350,
|
|
1311
|
+
2456956037,
|
|
1312
|
+
2730485921,
|
|
1313
|
+
2820302411,
|
|
1314
|
+
3259730800,
|
|
1315
|
+
3345764771,
|
|
1316
|
+
3516065817,
|
|
1317
|
+
3600352804,
|
|
1318
|
+
4094571909,
|
|
1319
|
+
275423344,
|
|
1320
|
+
430227734,
|
|
1321
|
+
506948616,
|
|
1322
|
+
659060556,
|
|
1323
|
+
883997877,
|
|
1324
|
+
958139571,
|
|
1325
|
+
1322822218,
|
|
1326
|
+
1537002063,
|
|
1327
|
+
1747873779,
|
|
1328
|
+
1955562222,
|
|
1329
|
+
2024104815,
|
|
1330
|
+
2227730452,
|
|
1331
|
+
2361852424,
|
|
1332
|
+
2428436474,
|
|
1333
|
+
2756734187,
|
|
1334
|
+
3204031479,
|
|
1335
|
+
3329325298
|
|
1336
|
+
]);
|
|
1337
|
+
function rotr(x, n) {
|
|
1338
|
+
return (x >>> n | x << 32 - n) >>> 0;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* UTF-8 bytes of `input` without WHATWG `TextEncoder`: the Rust system-loader
|
|
1342
|
+
* evaluates the system bundle in QuickJS, which provides ES built-ins only —
|
|
1343
|
+
* no Node globals and no WHATWG APIs. Iterating by code point handles
|
|
1344
|
+
* surrogate pairs; lone surrogates cannot reach this path because the input
|
|
1345
|
+
* is well-formed `JSON.stringify` output (ES2019 escapes them as `\uXXXX`).
|
|
1346
|
+
*/
|
|
1347
|
+
function utf8Bytes(input) {
|
|
1348
|
+
const bytes = [];
|
|
1349
|
+
for (const char of input) {
|
|
1350
|
+
const code = char.codePointAt(0);
|
|
1351
|
+
if (code < 128) bytes.push(code);
|
|
1352
|
+
else if (code < 2048) bytes.push(192 | code >> 6, 128 | code & 63);
|
|
1353
|
+
else if (code < 65536) bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
1354
|
+
else bytes.push(240 | code >> 18, 128 | code >> 12 & 63, 128 | code >> 6 & 63, 128 | code & 63);
|
|
1355
|
+
}
|
|
1356
|
+
return new Uint8Array(bytes);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Pure SHA-256 (FIPS 180-4) over the UTF-8 bytes of `input` — the non-Node
|
|
1360
|
+
* fallback for {@link sha256Hex}. Not a security surface: the contract hash
|
|
1361
|
+
* is a content digest for composition-identity comparison only.
|
|
1362
|
+
*/
|
|
1363
|
+
function sha256HexFallback(input) {
|
|
1364
|
+
const bytes = utf8Bytes(input);
|
|
1365
|
+
const bitLength = bytes.length * 8;
|
|
1366
|
+
const paddedLength = (bytes.length + 8 >> 6) + 1 << 6 >>> 0;
|
|
1367
|
+
const padded = new Uint8Array(paddedLength);
|
|
1368
|
+
padded.set(bytes);
|
|
1369
|
+
padded[bytes.length] = 128;
|
|
1370
|
+
const view = new DataView(padded.buffer);
|
|
1371
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
|
|
1372
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
1373
|
+
const state = new Uint32Array([
|
|
1374
|
+
1779033703,
|
|
1375
|
+
3144134277,
|
|
1376
|
+
1013904242,
|
|
1377
|
+
2773480762,
|
|
1378
|
+
1359893119,
|
|
1379
|
+
2600822924,
|
|
1380
|
+
528734635,
|
|
1381
|
+
1541459225
|
|
1382
|
+
]);
|
|
1383
|
+
const w = new Uint32Array(64);
|
|
1384
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
1385
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);
|
|
1386
|
+
for (let i = 16; i < 64; i++) {
|
|
1387
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
1388
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
1389
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
|
1390
|
+
}
|
|
1391
|
+
let a = state[0];
|
|
1392
|
+
let b = state[1];
|
|
1393
|
+
let c = state[2];
|
|
1394
|
+
let d = state[3];
|
|
1395
|
+
let e = state[4];
|
|
1396
|
+
let f = state[5];
|
|
1397
|
+
let g = state[6];
|
|
1398
|
+
let h = state[7];
|
|
1399
|
+
for (let i = 0; i < 64; i++) {
|
|
1400
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1401
|
+
const ch = e & f ^ ~e & g;
|
|
1402
|
+
const temp1 = h + S1 + ch + SHA256_K[i] + w[i] >>> 0;
|
|
1403
|
+
const temp2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
1404
|
+
h = g;
|
|
1405
|
+
g = f;
|
|
1406
|
+
f = e;
|
|
1407
|
+
e = d + temp1 >>> 0;
|
|
1408
|
+
d = c;
|
|
1409
|
+
c = b;
|
|
1410
|
+
b = a;
|
|
1411
|
+
a = temp1 + temp2 >>> 0;
|
|
1412
|
+
}
|
|
1413
|
+
state[0] += a;
|
|
1414
|
+
state[1] += b;
|
|
1415
|
+
state[2] += c;
|
|
1416
|
+
state[3] += d;
|
|
1417
|
+
state[4] += e;
|
|
1418
|
+
state[5] += f;
|
|
1419
|
+
state[6] += g;
|
|
1420
|
+
state[7] += h;
|
|
1421
|
+
}
|
|
1422
|
+
let hex = "";
|
|
1423
|
+
for (const word of state) hex += word.toString(16).padStart(8, "0");
|
|
1424
|
+
return hex;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Stable digest over the canonical authored inputs. Identical authored input
|
|
1428
|
+
* ⇒ identical hash, across processes: keys sort at every depth and
|
|
1429
|
+
* `JSON.stringify` drops `undefined`-valued members deterministically.
|
|
1430
|
+
*/
|
|
1431
|
+
function computeContractHash(input) {
|
|
1432
|
+
return sha256Hex(JSON.stringify(canonicalize(input)));
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
849
1435
|
* Resolve token refs ({scale.key}) in all flattened token values.
|
|
850
1436
|
* Operates on the flattened tokenMap — does NOT mutate the nested theme.
|
|
851
1437
|
*/
|
|
@@ -882,19 +1468,56 @@ function resolveTokenRefs(tokenMap, _variableMap, _emittedScales) {
|
|
|
882
1468
|
if (resolved !== value) tokenMap[tokenPath] = resolved;
|
|
883
1469
|
}
|
|
884
1470
|
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Build `@property` registration rules for registered contextual vars.
|
|
1473
|
+
* The emitted custom property is `--${name}` — the same name the Rust resolver
|
|
1474
|
+
* maps a bare contextual var value to. Rules emit in declaration order and only
|
|
1475
|
+
* for names that are genuinely declared contextual vars. Returns `''` when no
|
|
1476
|
+
* registration metadata was supplied (opt-in / byte-identical guarantee).
|
|
1477
|
+
*/
|
|
1478
|
+
function buildPropertyRegistrationCss(contextualVars, registrations) {
|
|
1479
|
+
if (registrations.size === 0) return "";
|
|
1480
|
+
const declaredNames = /* @__PURE__ */ new Set();
|
|
1481
|
+
for (const names of contextualVars.values()) for (const name of names) declaredNames.add(name);
|
|
1482
|
+
const blocks = [];
|
|
1483
|
+
for (const [name, registration] of registrations) {
|
|
1484
|
+
if (!declaredNames.has(name)) continue;
|
|
1485
|
+
const descriptors = [`syntax: "${registration.syntax}";`, `inherits: ${registration.inherits};`];
|
|
1486
|
+
if (registration.initialValue !== void 0) descriptors.push(`initial-value: ${registration.initialValue};`);
|
|
1487
|
+
blocks.push(`@property --${name} { ${descriptors.join(" ")} }`);
|
|
1488
|
+
}
|
|
1489
|
+
return blocks.join("\n");
|
|
1490
|
+
}
|
|
885
1491
|
/** Build CSS variable blocks from flattened data. */
|
|
886
|
-
function buildVariableCss(rootVariables, breakpointVariables, modeVariables) {
|
|
1492
|
+
function buildVariableCss(rootVariables, breakpointVariables, modeVariables, systemEmission = {}) {
|
|
1493
|
+
const { initialMode, systemPreference, browserColorScheme } = systemEmission;
|
|
887
1494
|
const parts = [];
|
|
888
1495
|
const rootLines = [];
|
|
889
1496
|
for (const [varName, value] of Object.entries(rootVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
890
1497
|
for (const [varName, value] of Object.entries(breakpointVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
1498
|
+
if (browserColorScheme && initialMode) {
|
|
1499
|
+
const initialScheme = browserColorScheme[initialMode];
|
|
1500
|
+
if (initialScheme) rootLines.push(` color-scheme: ${initialScheme};`);
|
|
1501
|
+
}
|
|
891
1502
|
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
1503
|
+
if (systemPreference) for (const scheme of ["light", "dark"]) {
|
|
1504
|
+
const modeName = systemPreference[scheme];
|
|
1505
|
+
const mediaLines = [];
|
|
1506
|
+
const modeVars = modeVariables[modeName];
|
|
1507
|
+
if (modeVars) for (const [varName, value] of Object.entries(modeVars)) mediaLines.push(` ${varName}: ${value};`);
|
|
1508
|
+
const mediaScheme = browserColorScheme?.[modeName];
|
|
1509
|
+
if (mediaScheme) mediaLines.push(` color-scheme: ${mediaScheme};`);
|
|
1510
|
+
if (mediaLines.length === 0) continue;
|
|
1511
|
+
parts.push(`@media (prefers-color-scheme: ${scheme}) {\n :root:not([data-color-mode]) {\n${mediaLines.join("\n")}\n }\n}`);
|
|
1512
|
+
}
|
|
892
1513
|
for (const [modeName, modeVars] of Object.entries(modeVariables)) {
|
|
893
1514
|
const modeLines = [];
|
|
894
1515
|
for (const [varName, value] of Object.entries(modeVars)) modeLines.push(` ${varName}: ${value};`);
|
|
1516
|
+
const modeScheme = browserColorScheme?.[modeName];
|
|
1517
|
+
if (modeScheme) modeLines.push(` color-scheme: ${modeScheme};`);
|
|
895
1518
|
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
896
1519
|
}
|
|
897
1520
|
return parts.join("\n\n");
|
|
898
1521
|
}
|
|
899
1522
|
//#endregion
|
|
900
|
-
export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, BUILT_IN_SELECTORS, SystemBuilder, ThemeBuilder, borderShorthand, compose, createClassResolver, createComponent, createComposedFamily, createScale, createSystem, createTheme, createTransform, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, size, stringScale };
|
|
1523
|
+
export { Animus, AnimusExtended, AnimusExtendedWithAll, AnimusWithAll, BUILT_IN_CONDITIONS, BUILT_IN_SELECTORS, SystemBuilder, ThemeBuilder, borderShorthand, compose, createClassResolver, createComponent, createComposedFamily, createScale, createSystem, createTheme, createTransform, gridItem, gridItemRatio, numericOrStringScale, numericScale, percentageOrAbsolute, size, stringScale };
|