@animus-ui/system 0.1.1 → 0.1.2
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 +27 -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 +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +645 -23
- 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,27 +622,28 @@ 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
648
|
const createGlobalStyles = ((styles) => ({
|
|
479
649
|
__brand: "GlobalStyleBlock",
|
|
@@ -487,7 +657,7 @@ var SystemBuilder = class SystemBuilder {
|
|
|
487
657
|
};
|
|
488
658
|
}
|
|
489
659
|
};
|
|
490
|
-
function serializeInstance(propRegistry, groupRegistry, selectorRegistry) {
|
|
660
|
+
function serializeInstance(propRegistry, groupRegistry, selectorRegistry, conditionRegistry) {
|
|
491
661
|
const serialized = {};
|
|
492
662
|
const transforms = {};
|
|
493
663
|
for (const [propName, entry] of Object.entries(propRegistry)) {
|
|
@@ -509,11 +679,13 @@ function serializeInstance(propRegistry, groupRegistry, selectorRegistry) {
|
|
|
509
679
|
serialized[propName] = s;
|
|
510
680
|
}
|
|
511
681
|
const { selectors } = serializeSelectorMap(selectorRegistry);
|
|
682
|
+
const conditions = serializeConditionMap(conditionRegistry);
|
|
512
683
|
return {
|
|
513
684
|
propConfig: JSON.stringify(serialized),
|
|
514
685
|
groupRegistry: JSON.stringify(groupRegistry),
|
|
515
686
|
transforms,
|
|
516
|
-
selectorAliases: JSON.stringify(selectors)
|
|
687
|
+
selectorAliases: JSON.stringify(selectors),
|
|
688
|
+
conditionAliases: JSON.stringify(conditions)
|
|
517
689
|
};
|
|
518
690
|
}
|
|
519
691
|
function createSystem(config) {
|
|
@@ -618,6 +790,109 @@ function validateModeAliases(modeName, aliases, nestedColors, flatColorKeys, pre
|
|
|
618
790
|
} else if (isObject(value)) validateModeAliases(modeName, value, nestedColors, flatColorKeys, aliasPath);
|
|
619
791
|
}
|
|
620
792
|
}
|
|
793
|
+
/**
|
|
794
|
+
* Reserved mode name (D4). "System" is modeled as the ABSENCE of
|
|
795
|
+
* `data-color-mode`, never as a mode: an attribute value `system` would defeat
|
|
796
|
+
* the `:root:not([data-color-mode])` guard while matching no mode block.
|
|
797
|
+
*/
|
|
798
|
+
const RESERVED_MODE_NAME = "system";
|
|
799
|
+
/**
|
|
800
|
+
* Theme keys owned by the color-mode options. A scale may not claim them —
|
|
801
|
+
* they are skipped by `flattenTheme` and read back as option objects in
|
|
802
|
+
* `build()`, so a same-named scale would emit zero tokens AND fabricate a
|
|
803
|
+
* manifest field from its values.
|
|
804
|
+
*
|
|
805
|
+
* NOTE: the pre-existing `mode` / `modes` / `breakpoints` structural keys have
|
|
806
|
+
* the same hole and are deliberately NOT covered here (out of scope).
|
|
807
|
+
*/
|
|
808
|
+
const RESERVED_THEME_KEYS = new Set(["systemPreference", "browserColorScheme"]);
|
|
809
|
+
const COLOR_SCHEME_VALUES = new Set([
|
|
810
|
+
"light",
|
|
811
|
+
"dark",
|
|
812
|
+
"normal"
|
|
813
|
+
]);
|
|
814
|
+
/**
|
|
815
|
+
* The axis → scheme pairs the mapping forces: the mode named for the OS light
|
|
816
|
+
* preference must classify `light`, the dark one `dark`. Single source for
|
|
817
|
+
* BOTH the default fill and the conflict check in
|
|
818
|
+
* {@link resolveColorModeOptions} — one rule, one table.
|
|
819
|
+
*/
|
|
820
|
+
const MAPPING_FORCED_SCHEMES = [["light", "light"], ["dark", "dark"]];
|
|
821
|
+
/**
|
|
822
|
+
* Merge an incoming option object over the one already on the theme, mirroring
|
|
823
|
+
* `merge`'s per-key override. Returns `undefined` when neither side supplied
|
|
824
|
+
* one, so an unconfigured theme never gains the key (byte parity, G4).
|
|
825
|
+
*/
|
|
826
|
+
function mergeOptionObject(existing, incoming) {
|
|
827
|
+
const hasExisting = isObject(existing);
|
|
828
|
+
const hasIncoming = isObject(incoming);
|
|
829
|
+
if (!hasExisting && !hasIncoming) return void 0;
|
|
830
|
+
return {
|
|
831
|
+
...hasExisting ? existing : {},
|
|
832
|
+
...hasIncoming ? incoming : {}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/** Reject the reserved mode name (D4) wherever a mode set is declared or merged. */
|
|
836
|
+
function validateReservedModeNames(modeNames) {
|
|
837
|
+
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(", ")}`);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Normalize AND validate the optional system-participation options against the
|
|
841
|
+
* declared modes, returning the normalized classification. One entry point on
|
|
842
|
+
* purpose: the D3-amendment fill (mapping-named modes default to their forced
|
|
843
|
+
* schemes) must always run before totality is checked, so exposing fill and
|
|
844
|
+
* validation separately would make "validate an unfilled map" representable.
|
|
845
|
+
* An explicit entry survives the fill's spread and is still conflict-checked,
|
|
846
|
+
* so a wrong value errors rather than being silently corrected. `undefined`
|
|
847
|
+
* passes through untouched — the classification stays opt-in, and a theme with
|
|
848
|
+
* only `systemPreference` emits no `color-scheme` (byte parity, G4).
|
|
849
|
+
*
|
|
850
|
+
* ALWAYS called with MERGED state — `theme.modes` unions across `addColorModes`
|
|
851
|
+
* calls and `from()` composition, so a per-call view both misses invalidation
|
|
852
|
+
* (a later mode declaration un-totals a carried classification) and invents
|
|
853
|
+
* false rejections (a mapping naming a mode declared by an earlier call).
|
|
854
|
+
* Run at both gates: `addColorModes` (fail fast) and `build()` (authoritative —
|
|
855
|
+
* `from()` composition never passes through `addColorModes`; only build()'s
|
|
856
|
+
* resolved map feeds emission and the manifest).
|
|
857
|
+
*
|
|
858
|
+
* Error tone mirrors `validateModeAliases`: `addColorModes:` prefix plus the
|
|
859
|
+
* available names.
|
|
860
|
+
*/
|
|
861
|
+
function resolveColorModeOptions(modeNames, systemPreference, suppliedBrowserColorScheme) {
|
|
862
|
+
let browserColorScheme = suppliedBrowserColorScheme;
|
|
863
|
+
if (browserColorScheme && systemPreference) {
|
|
864
|
+
const forced = {};
|
|
865
|
+
for (const [axis, scheme] of MAPPING_FORCED_SCHEMES) {
|
|
866
|
+
const modeName = systemPreference[axis];
|
|
867
|
+
if (typeof modeName === "string") forced[modeName] = scheme;
|
|
868
|
+
}
|
|
869
|
+
browserColorScheme = {
|
|
870
|
+
...forced,
|
|
871
|
+
...suppliedBrowserColorScheme
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
const available = `Available modes: ${modeNames.join(", ")}`;
|
|
875
|
+
const declared = new Set(modeNames);
|
|
876
|
+
if (systemPreference) for (const axis of ["light", "dark"]) {
|
|
877
|
+
const modeName = systemPreference[axis];
|
|
878
|
+
if (typeof modeName !== "string" || modeName === "") throw new Error(`addColorModes: systemPreference requires both 'light' and 'dark' to name a declared mode — '${axis}' is missing. ${available}`);
|
|
879
|
+
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}`);
|
|
880
|
+
if (!declared.has(modeName)) throw new Error(`addColorModes: systemPreference.${axis} references unknown mode '${modeName}'. ${available}`);
|
|
881
|
+
}
|
|
882
|
+
if (!browserColorScheme) return browserColorScheme;
|
|
883
|
+
for (const [modeName, value] of Object.entries(browserColorScheme)) {
|
|
884
|
+
if (!declared.has(modeName)) throw new Error(`addColorModes: browserColorScheme references unknown mode '${modeName}'. ${available}`);
|
|
885
|
+
if (!COLOR_SCHEME_VALUES.has(value)) throw new Error(`addColorModes: browserColorScheme['${modeName}'] must be 'light', 'dark', or 'normal', got ${JSON.stringify(value)}.`);
|
|
886
|
+
}
|
|
887
|
+
for (const modeName of modeNames) if (!(modeName in browserColorScheme)) throw new Error(`addColorModes: browserColorScheme must classify every declared mode — mode '${modeName}' is unclassified. ${available}`);
|
|
888
|
+
if (!systemPreference) return browserColorScheme;
|
|
889
|
+
for (const [axis, expected] of MAPPING_FORCED_SCHEMES) {
|
|
890
|
+
const modeName = systemPreference[axis];
|
|
891
|
+
const classification = browserColorScheme[modeName];
|
|
892
|
+
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}'.`);
|
|
893
|
+
}
|
|
894
|
+
return browserColorScheme;
|
|
895
|
+
}
|
|
621
896
|
/** Validate all color entries, throwing on invalid values. */
|
|
622
897
|
function validateColors(colors) {
|
|
623
898
|
for (const [key, value] of Object.entries(colors)) if (isObject(value)) validateColors(value);
|
|
@@ -627,14 +902,19 @@ function createState(theme) {
|
|
|
627
902
|
return {
|
|
628
903
|
theme: theme || { breakpoints: {} },
|
|
629
904
|
emittedScales: /* @__PURE__ */ new Set(),
|
|
630
|
-
contextualVars: /* @__PURE__ */ new Map()
|
|
905
|
+
contextualVars: /* @__PURE__ */ new Map(),
|
|
906
|
+
contextualVarRegistrations: /* @__PURE__ */ new Map(),
|
|
907
|
+
hasLegacyManifestSource: false
|
|
631
908
|
};
|
|
632
909
|
}
|
|
633
910
|
function copyState(state, nextTheme) {
|
|
634
911
|
const next = {
|
|
635
912
|
theme: nextTheme,
|
|
636
913
|
emittedScales: new Set(state.emittedScales),
|
|
637
|
-
contextualVars: /* @__PURE__ */ new Map()
|
|
914
|
+
contextualVars: /* @__PURE__ */ new Map(),
|
|
915
|
+
contextualVarRegistrations: new Map(state.contextualVarRegistrations),
|
|
916
|
+
...state.carriedManifestV2 ? { carriedManifestV2: { ...state.carriedManifestV2 } } : {},
|
|
917
|
+
hasLegacyManifestSource: state.hasLegacyManifestSource
|
|
638
918
|
};
|
|
639
919
|
for (const [scale, vars] of state.contextualVars) next.contextualVars.set(scale, [...vars]);
|
|
640
920
|
return next;
|
|
@@ -667,6 +947,17 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
667
947
|
next._state.emittedScales.add(scale === "colors" ? "colors" : scale);
|
|
668
948
|
}
|
|
669
949
|
if (manifest?.contextualVars) for (const [scale, vars] of Object.entries(manifest.contextualVars)) next._state.contextualVars.set(scale, [...vars]);
|
|
950
|
+
if (manifest) if (manifest.manifestVersion === 2) {
|
|
951
|
+
next._state.carriedManifestV2 = {
|
|
952
|
+
tokenDefinitions: manifest.tokenDefinitions,
|
|
953
|
+
modeAliasDefinitions: manifest.modeAliasDefinitions,
|
|
954
|
+
registrations: manifest.registrations,
|
|
955
|
+
emitterVersion: manifest.emitterVersion,
|
|
956
|
+
contractHash: manifest.contractHash,
|
|
957
|
+
cssFragments: manifest.cssFragments
|
|
958
|
+
};
|
|
959
|
+
if (manifest.registrations) for (const [name, registration] of Object.entries(manifest.registrations)) next._state.contextualVarRegistrations.set(name, registration);
|
|
960
|
+
} else next._state.hasLegacyManifestSource = true;
|
|
670
961
|
return next;
|
|
671
962
|
}
|
|
672
963
|
addColors(colors) {
|
|
@@ -676,31 +967,46 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
676
967
|
next._state.emittedScales.add("colors");
|
|
677
968
|
return next;
|
|
678
969
|
}
|
|
679
|
-
addColorModes(initialMode, modeConfig) {
|
|
970
|
+
addColorModes(initialMode, modeConfig, options) {
|
|
680
971
|
const nestedColors = this._state.theme.colors || {};
|
|
681
972
|
const flatColors = flattenToDotPaths(nestedColors);
|
|
682
973
|
const flatColorKeys = Object.keys(flatColors);
|
|
974
|
+
const existingModes = isObject(this._state.theme.modes) ? this._state.theme.modes : {};
|
|
975
|
+
const modeNames = Object.keys({
|
|
976
|
+
...existingModes,
|
|
977
|
+
...modeConfig
|
|
978
|
+
});
|
|
979
|
+
validateReservedModeNames(modeNames);
|
|
683
980
|
for (const [modeName, modeAliases] of Object.entries(modeConfig)) validateModeAliases(modeName, modeAliases, nestedColors, flatColorKeys, "");
|
|
981
|
+
const systemPreference = mergeOptionObject(this._state.theme.systemPreference, options?.systemPreference);
|
|
982
|
+
const browserColorScheme = mergeOptionObject(this._state.theme.browserColorScheme, options?.browserColorScheme);
|
|
983
|
+
resolveColorModeOptions(modeNames, systemPreference, browserColorScheme);
|
|
684
984
|
const nextTheme = merge({}, this._state.theme, {
|
|
685
985
|
modes: modeConfig,
|
|
686
|
-
mode: initialMode
|
|
986
|
+
mode: initialMode,
|
|
987
|
+
...systemPreference ? { systemPreference } : {},
|
|
988
|
+
...browserColorScheme ? { browserColorScheme } : {}
|
|
687
989
|
});
|
|
688
990
|
return new ThemeBuilder(copyState(this._state, nextTheme));
|
|
689
991
|
}
|
|
690
992
|
addScale(config) {
|
|
691
993
|
const { name, values, emit } = config;
|
|
994
|
+
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
995
|
const nextTheme = merge({}, this._state.theme, { [name]: values });
|
|
693
996
|
const next = new ThemeBuilder(copyState(this._state, nextTheme));
|
|
694
997
|
if (emit) next._state.emittedScales.add(name);
|
|
695
998
|
return next;
|
|
696
999
|
}
|
|
697
|
-
declareContextualVars(vars) {
|
|
1000
|
+
declareContextualVars(vars, registrations) {
|
|
698
1001
|
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
1002
|
const next = new ThemeBuilder(copyState(this._state, this._state.theme));
|
|
700
1003
|
for (const [scale, names] of Object.entries(vars)) {
|
|
701
1004
|
const existing = next._state.contextualVars.get(scale) || [];
|
|
702
1005
|
next._state.contextualVars.set(scale, [...existing, ...names]);
|
|
703
1006
|
}
|
|
1007
|
+
if (registrations) {
|
|
1008
|
+
for (const [name, registration] of Object.entries(registrations)) if (registration) next._state.contextualVarRegistrations.set(name, registration);
|
|
1009
|
+
}
|
|
704
1010
|
return next;
|
|
705
1011
|
}
|
|
706
1012
|
extendScale(key, updateFn) {
|
|
@@ -715,7 +1021,11 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
715
1021
|
const theme = merge({}, this._state.theme);
|
|
716
1022
|
const emittedScales = this._state.emittedScales;
|
|
717
1023
|
const contextualVars = this._state.contextualVars;
|
|
718
|
-
const
|
|
1024
|
+
const systemPreference = isObject(theme.systemPreference) ? theme.systemPreference : void 0;
|
|
1025
|
+
const mergedModeNames = isObject(theme.modes) ? Object.keys(theme.modes) : [];
|
|
1026
|
+
validateReservedModeNames(mergedModeNames);
|
|
1027
|
+
const browserColorScheme = resolveColorModeOptions(mergedModeNames, systemPreference, isObject(theme.browserColorScheme) ? theme.browserColorScheme : void 0);
|
|
1028
|
+
const { tokenMap, variableMap, variables, modeVariables, modeTokens, tokenDefinitions, modeAliasDefinitions } = flattenTheme(theme, emittedScales);
|
|
719
1029
|
resolveTokenRefs(tokenMap, variableMap, emittedScales);
|
|
720
1030
|
const bpVariables = {};
|
|
721
1031
|
if (theme.breakpoints && isObject(theme.breakpoints)) for (const [key, value] of Object.entries(theme.breakpoints)) bpVariables[`--breakpoint-${key}`] = `${value}px`;
|
|
@@ -724,7 +1034,44 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
724
1034
|
contextualVarsSerialized = {};
|
|
725
1035
|
for (const [scale, vars] of contextualVars) contextualVarsSerialized[scale] = vars;
|
|
726
1036
|
}
|
|
727
|
-
const
|
|
1037
|
+
const propertyCss = buildPropertyRegistrationCss(contextualVars, this._state.contextualVarRegistrations);
|
|
1038
|
+
const baseVariableCss = buildVariableCss(variables, bpVariables, modeVariables, {
|
|
1039
|
+
initialMode: typeof theme.mode === "string" ? theme.mode : void 0,
|
|
1040
|
+
systemPreference,
|
|
1041
|
+
browserColorScheme
|
|
1042
|
+
});
|
|
1043
|
+
const variableCss = propertyCss ? baseVariableCss ? `${propertyCss}\n\n${baseVariableCss}` : propertyCss : baseVariableCss;
|
|
1044
|
+
let manifestV2Fields = {};
|
|
1045
|
+
if (!this._state.hasLegacyManifestSource) {
|
|
1046
|
+
const registrations = Object.fromEntries(this._state.contextualVarRegistrations);
|
|
1047
|
+
const cssFragments = [];
|
|
1048
|
+
if (propertyCss) cssFragments.push({
|
|
1049
|
+
id: "registrations",
|
|
1050
|
+
kind: "registrations",
|
|
1051
|
+
cssText: propertyCss
|
|
1052
|
+
});
|
|
1053
|
+
if (baseVariableCss) cssFragments.push({
|
|
1054
|
+
id: "base",
|
|
1055
|
+
kind: "base",
|
|
1056
|
+
cssText: baseVariableCss
|
|
1057
|
+
});
|
|
1058
|
+
manifestV2Fields = {
|
|
1059
|
+
manifestVersion: 2,
|
|
1060
|
+
tokenDefinitions,
|
|
1061
|
+
modeAliasDefinitions,
|
|
1062
|
+
registrations,
|
|
1063
|
+
emitterVersion: EMITTER_VERSION,
|
|
1064
|
+
contractHash: computeContractHash({
|
|
1065
|
+
tokenDefinitions,
|
|
1066
|
+
modeAliasDefinitions,
|
|
1067
|
+
initialMode: typeof theme.mode === "string" ? theme.mode : void 0,
|
|
1068
|
+
registrations,
|
|
1069
|
+
systemPreference,
|
|
1070
|
+
browserColorScheme
|
|
1071
|
+
}),
|
|
1072
|
+
cssFragments
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
728
1075
|
const manifest = {
|
|
729
1076
|
tokenMap: {
|
|
730
1077
|
...tokenMap,
|
|
@@ -733,7 +1080,10 @@ var ThemeBuilder = class ThemeBuilder {
|
|
|
733
1080
|
variableMap,
|
|
734
1081
|
modes: modeTokens,
|
|
735
1082
|
variableCss,
|
|
736
|
-
...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {}
|
|
1083
|
+
...contextualVarsSerialized ? { contextualVars: contextualVarsSerialized } : {},
|
|
1084
|
+
...systemPreference ? { systemPreference } : {},
|
|
1085
|
+
...browserColorScheme ? { browserColorScheme } : {},
|
|
1086
|
+
...manifestV2Fields
|
|
737
1087
|
};
|
|
738
1088
|
Object.defineProperty(theme, "manifest", {
|
|
739
1089
|
value: manifest,
|
|
@@ -787,9 +1137,11 @@ function flattenTheme(theme, emittedScales) {
|
|
|
787
1137
|
const variables = {};
|
|
788
1138
|
const modeVariables = {};
|
|
789
1139
|
const modeTokens = {};
|
|
1140
|
+
const tokenDefinitions = {};
|
|
1141
|
+
const modeAliasDefinitions = {};
|
|
790
1142
|
for (const [scaleName, scaleValue] of Object.entries(theme)) {
|
|
791
1143
|
if (scaleName.startsWith("_")) continue;
|
|
792
|
-
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes") continue;
|
|
1144
|
+
if (scaleName === "breakpoints" || scaleName === "mode" || scaleName === "modes" || scaleName === "systemPreference" || scaleName === "browserColorScheme") continue;
|
|
793
1145
|
if (typeof scaleValue === "function") continue;
|
|
794
1146
|
if (!isObject(scaleValue)) continue;
|
|
795
1147
|
const flat = flattenToDotPaths(scaleValue);
|
|
@@ -798,6 +1150,7 @@ function flattenTheme(theme, emittedScales) {
|
|
|
798
1150
|
const tokenPath = `${scaleName}.${dotKey}`;
|
|
799
1151
|
const dashKey = dotToDash(dotKey);
|
|
800
1152
|
const varName = `--${scaleName === "colors" ? "color" : scaleName}-${dashKey}`;
|
|
1153
|
+
tokenDefinitions[tokenPath] = parseTokenDefinition(String(rawValue));
|
|
801
1154
|
if (isEmitted) {
|
|
802
1155
|
tokenMap[tokenPath] = `var(${varName})`;
|
|
803
1156
|
variableMap[tokenPath] = varName;
|
|
@@ -812,15 +1165,18 @@ function flattenTheme(theme, emittedScales) {
|
|
|
812
1165
|
const flatAliases = flattenToDotPaths(modeAliases);
|
|
813
1166
|
const modeVars = {};
|
|
814
1167
|
const modeVals = {};
|
|
1168
|
+
const modeAliasDefs = {};
|
|
815
1169
|
for (const [aliasDotKey, colorRef] of Object.entries(flatAliases)) {
|
|
816
1170
|
if (typeof colorRef !== "string") continue;
|
|
817
1171
|
const varName = `--color-${dotToDash(aliasDotKey)}`;
|
|
1172
|
+
modeAliasDefs[aliasDotKey] = colorRef;
|
|
818
1173
|
const rawValue = flatColors[colorRef];
|
|
819
1174
|
modeVals[`colors.${aliasDotKey}`] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
820
1175
|
modeVars[varName] = rawValue !== void 0 ? String(rawValue) : String(colorRef);
|
|
821
1176
|
}
|
|
822
1177
|
modeVariables[modeName] = modeVars;
|
|
823
1178
|
modeTokens[modeName] = modeVals;
|
|
1179
|
+
modeAliasDefinitions[modeName] = modeAliasDefs;
|
|
824
1180
|
}
|
|
825
1181
|
const initialMode = theme.mode;
|
|
826
1182
|
if (initialMode && modeVariables[initialMode]) {
|
|
@@ -842,10 +1198,239 @@ function flattenTheme(theme, emittedScales) {
|
|
|
842
1198
|
variableMap,
|
|
843
1199
|
variables,
|
|
844
1200
|
modeVariables,
|
|
845
|
-
modeTokens
|
|
1201
|
+
modeTokens,
|
|
1202
|
+
tokenDefinitions,
|
|
1203
|
+
modeAliasDefinitions
|
|
846
1204
|
};
|
|
847
1205
|
}
|
|
848
1206
|
/**
|
|
1207
|
+
* Classify a RAW token value into its authored form (manifest v2, D6). MUST
|
|
1208
|
+
* run before `resolveTokenRefs` — resolution rewrites the string, and the
|
|
1209
|
+
* authored graph cannot be reconstructed from resolved CSS (D8). Uses
|
|
1210
|
+
* `matchAll` so the shared global {@link TOKEN_REF_RE} never carries a stale
|
|
1211
|
+
* `lastIndex` between callers.
|
|
1212
|
+
*/
|
|
1213
|
+
function parseTokenDefinition(rawValue) {
|
|
1214
|
+
const references = [];
|
|
1215
|
+
for (const match of rawValue.matchAll(TOKEN_REF_RE)) {
|
|
1216
|
+
const ref = match[1];
|
|
1217
|
+
const slashIdx = ref.indexOf("/");
|
|
1218
|
+
references.push(slashIdx === -1 ? { path: ref } : {
|
|
1219
|
+
path: ref.slice(0, slashIdx),
|
|
1220
|
+
opacity: ref.slice(slashIdx + 1)
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
if (references.length === 0) return {
|
|
1224
|
+
kind: "literal",
|
|
1225
|
+
value: rawValue
|
|
1226
|
+
};
|
|
1227
|
+
return {
|
|
1228
|
+
kind: "reference",
|
|
1229
|
+
value: rawValue,
|
|
1230
|
+
references
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Version of the CSS emitter that composed a manifest's fragments. Bump when
|
|
1235
|
+
* ANY emitted byte changes for the same authored input — composition (D8)
|
|
1236
|
+
* regenerates dirty sections "under the pinned emitter version", and this is
|
|
1237
|
+
* that pin.
|
|
1238
|
+
*/
|
|
1239
|
+
const EMITTER_VERSION = 1;
|
|
1240
|
+
/**
|
|
1241
|
+
* Sorted-key canonical form for hashing: object keys are emitted in sorted
|
|
1242
|
+
* order at every depth so the digest is independent of insertion order;
|
|
1243
|
+
* arrays keep authored order (reference order is contractual).
|
|
1244
|
+
*/
|
|
1245
|
+
function canonicalize(value) {
|
|
1246
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1247
|
+
if (isObject(value)) {
|
|
1248
|
+
const record = value;
|
|
1249
|
+
const sorted = {};
|
|
1250
|
+
for (const key of Object.keys(record).sort()) sorted[key] = canonicalize(record[key]);
|
|
1251
|
+
return sorted;
|
|
1252
|
+
}
|
|
1253
|
+
return value;
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* sha256 hex digest. Primary path is `node:crypto` `createHash('sha256')`,
|
|
1257
|
+
* reached via `process.getBuiltinModule` so this module gains NO static
|
|
1258
|
+
* `node:crypto` import edge: built themes execute inside client bundles today
|
|
1259
|
+
* (e2e fixtures inline `createTheme(…).build()` at module init), and a static
|
|
1260
|
+
* builtin import is exactly what `./bootstrap` documents as forbidden in app
|
|
1261
|
+
* bundles. Non-Node runtimes use the pure fallback below, which produces
|
|
1262
|
+
* identical hex for identical input — determinism holds across environments.
|
|
1263
|
+
*/
|
|
1264
|
+
function sha256Hex(input) {
|
|
1265
|
+
const nodeCrypto = globalThis.process?.getBuiltinModule?.("node:crypto");
|
|
1266
|
+
if (nodeCrypto?.createHash) return nodeCrypto.createHash("sha256").update(input).digest("hex");
|
|
1267
|
+
return sha256HexFallback(input);
|
|
1268
|
+
}
|
|
1269
|
+
/** SHA-256 round constants (FIPS 180-4 §4.2.2). */
|
|
1270
|
+
const SHA256_K = new Uint32Array([
|
|
1271
|
+
1116352408,
|
|
1272
|
+
1899447441,
|
|
1273
|
+
3049323471,
|
|
1274
|
+
3921009573,
|
|
1275
|
+
961987163,
|
|
1276
|
+
1508970993,
|
|
1277
|
+
2453635748,
|
|
1278
|
+
2870763221,
|
|
1279
|
+
3624381080,
|
|
1280
|
+
310598401,
|
|
1281
|
+
607225278,
|
|
1282
|
+
1426881987,
|
|
1283
|
+
1925078388,
|
|
1284
|
+
2162078206,
|
|
1285
|
+
2614888103,
|
|
1286
|
+
3248222580,
|
|
1287
|
+
3835390401,
|
|
1288
|
+
4022224774,
|
|
1289
|
+
264347078,
|
|
1290
|
+
604807628,
|
|
1291
|
+
770255983,
|
|
1292
|
+
1249150122,
|
|
1293
|
+
1555081692,
|
|
1294
|
+
1996064986,
|
|
1295
|
+
2554220882,
|
|
1296
|
+
2821834349,
|
|
1297
|
+
2952996808,
|
|
1298
|
+
3210313671,
|
|
1299
|
+
3336571891,
|
|
1300
|
+
3584528711,
|
|
1301
|
+
113926993,
|
|
1302
|
+
338241895,
|
|
1303
|
+
666307205,
|
|
1304
|
+
773529912,
|
|
1305
|
+
1294757372,
|
|
1306
|
+
1396182291,
|
|
1307
|
+
1695183700,
|
|
1308
|
+
1986661051,
|
|
1309
|
+
2177026350,
|
|
1310
|
+
2456956037,
|
|
1311
|
+
2730485921,
|
|
1312
|
+
2820302411,
|
|
1313
|
+
3259730800,
|
|
1314
|
+
3345764771,
|
|
1315
|
+
3516065817,
|
|
1316
|
+
3600352804,
|
|
1317
|
+
4094571909,
|
|
1318
|
+
275423344,
|
|
1319
|
+
430227734,
|
|
1320
|
+
506948616,
|
|
1321
|
+
659060556,
|
|
1322
|
+
883997877,
|
|
1323
|
+
958139571,
|
|
1324
|
+
1322822218,
|
|
1325
|
+
1537002063,
|
|
1326
|
+
1747873779,
|
|
1327
|
+
1955562222,
|
|
1328
|
+
2024104815,
|
|
1329
|
+
2227730452,
|
|
1330
|
+
2361852424,
|
|
1331
|
+
2428436474,
|
|
1332
|
+
2756734187,
|
|
1333
|
+
3204031479,
|
|
1334
|
+
3329325298
|
|
1335
|
+
]);
|
|
1336
|
+
function rotr(x, n) {
|
|
1337
|
+
return (x >>> n | x << 32 - n) >>> 0;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* UTF-8 bytes of `input` without WHATWG `TextEncoder`: the Rust system-loader
|
|
1341
|
+
* evaluates the system bundle in QuickJS, which provides ES built-ins only —
|
|
1342
|
+
* no Node globals and no WHATWG APIs. Iterating by code point handles
|
|
1343
|
+
* surrogate pairs; lone surrogates cannot reach this path because the input
|
|
1344
|
+
* is well-formed `JSON.stringify` output (ES2019 escapes them as `\uXXXX`).
|
|
1345
|
+
*/
|
|
1346
|
+
function utf8Bytes(input) {
|
|
1347
|
+
const bytes = [];
|
|
1348
|
+
for (const char of input) {
|
|
1349
|
+
const code = char.codePointAt(0);
|
|
1350
|
+
if (code < 128) bytes.push(code);
|
|
1351
|
+
else if (code < 2048) bytes.push(192 | code >> 6, 128 | code & 63);
|
|
1352
|
+
else if (code < 65536) bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
1353
|
+
else bytes.push(240 | code >> 18, 128 | code >> 12 & 63, 128 | code >> 6 & 63, 128 | code & 63);
|
|
1354
|
+
}
|
|
1355
|
+
return new Uint8Array(bytes);
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Pure SHA-256 (FIPS 180-4) over the UTF-8 bytes of `input` — the non-Node
|
|
1359
|
+
* fallback for {@link sha256Hex}. Not a security surface: the contract hash
|
|
1360
|
+
* is a content digest for composition-identity comparison only.
|
|
1361
|
+
*/
|
|
1362
|
+
function sha256HexFallback(input) {
|
|
1363
|
+
const bytes = utf8Bytes(input);
|
|
1364
|
+
const bitLength = bytes.length * 8;
|
|
1365
|
+
const paddedLength = (bytes.length + 8 >> 6) + 1 << 6 >>> 0;
|
|
1366
|
+
const padded = new Uint8Array(paddedLength);
|
|
1367
|
+
padded.set(bytes);
|
|
1368
|
+
padded[bytes.length] = 128;
|
|
1369
|
+
const view = new DataView(padded.buffer);
|
|
1370
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
|
|
1371
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
1372
|
+
const state = new Uint32Array([
|
|
1373
|
+
1779033703,
|
|
1374
|
+
3144134277,
|
|
1375
|
+
1013904242,
|
|
1376
|
+
2773480762,
|
|
1377
|
+
1359893119,
|
|
1378
|
+
2600822924,
|
|
1379
|
+
528734635,
|
|
1380
|
+
1541459225
|
|
1381
|
+
]);
|
|
1382
|
+
const w = new Uint32Array(64);
|
|
1383
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
1384
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);
|
|
1385
|
+
for (let i = 16; i < 64; i++) {
|
|
1386
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
1387
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
1388
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
|
|
1389
|
+
}
|
|
1390
|
+
let a = state[0];
|
|
1391
|
+
let b = state[1];
|
|
1392
|
+
let c = state[2];
|
|
1393
|
+
let d = state[3];
|
|
1394
|
+
let e = state[4];
|
|
1395
|
+
let f = state[5];
|
|
1396
|
+
let g = state[6];
|
|
1397
|
+
let h = state[7];
|
|
1398
|
+
for (let i = 0; i < 64; i++) {
|
|
1399
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1400
|
+
const ch = e & f ^ ~e & g;
|
|
1401
|
+
const temp1 = h + S1 + ch + SHA256_K[i] + w[i] >>> 0;
|
|
1402
|
+
const temp2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
1403
|
+
h = g;
|
|
1404
|
+
g = f;
|
|
1405
|
+
f = e;
|
|
1406
|
+
e = d + temp1 >>> 0;
|
|
1407
|
+
d = c;
|
|
1408
|
+
c = b;
|
|
1409
|
+
b = a;
|
|
1410
|
+
a = temp1 + temp2 >>> 0;
|
|
1411
|
+
}
|
|
1412
|
+
state[0] += a;
|
|
1413
|
+
state[1] += b;
|
|
1414
|
+
state[2] += c;
|
|
1415
|
+
state[3] += d;
|
|
1416
|
+
state[4] += e;
|
|
1417
|
+
state[5] += f;
|
|
1418
|
+
state[6] += g;
|
|
1419
|
+
state[7] += h;
|
|
1420
|
+
}
|
|
1421
|
+
let hex = "";
|
|
1422
|
+
for (const word of state) hex += word.toString(16).padStart(8, "0");
|
|
1423
|
+
return hex;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Stable digest over the canonical authored inputs. Identical authored input
|
|
1427
|
+
* ⇒ identical hash, across processes: keys sort at every depth and
|
|
1428
|
+
* `JSON.stringify` drops `undefined`-valued members deterministically.
|
|
1429
|
+
*/
|
|
1430
|
+
function computeContractHash(input) {
|
|
1431
|
+
return sha256Hex(JSON.stringify(canonicalize(input)));
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
849
1434
|
* Resolve token refs ({scale.key}) in all flattened token values.
|
|
850
1435
|
* Operates on the flattened tokenMap — does NOT mutate the nested theme.
|
|
851
1436
|
*/
|
|
@@ -882,19 +1467,56 @@ function resolveTokenRefs(tokenMap, _variableMap, _emittedScales) {
|
|
|
882
1467
|
if (resolved !== value) tokenMap[tokenPath] = resolved;
|
|
883
1468
|
}
|
|
884
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Build `@property` registration rules for registered contextual vars.
|
|
1472
|
+
* The emitted custom property is `--${name}` — the same name the Rust resolver
|
|
1473
|
+
* maps a bare contextual var value to. Rules emit in declaration order and only
|
|
1474
|
+
* for names that are genuinely declared contextual vars. Returns `''` when no
|
|
1475
|
+
* registration metadata was supplied (opt-in / byte-identical guarantee).
|
|
1476
|
+
*/
|
|
1477
|
+
function buildPropertyRegistrationCss(contextualVars, registrations) {
|
|
1478
|
+
if (registrations.size === 0) return "";
|
|
1479
|
+
const declaredNames = /* @__PURE__ */ new Set();
|
|
1480
|
+
for (const names of contextualVars.values()) for (const name of names) declaredNames.add(name);
|
|
1481
|
+
const blocks = [];
|
|
1482
|
+
for (const [name, registration] of registrations) {
|
|
1483
|
+
if (!declaredNames.has(name)) continue;
|
|
1484
|
+
const descriptors = [`syntax: "${registration.syntax}";`, `inherits: ${registration.inherits};`];
|
|
1485
|
+
if (registration.initialValue !== void 0) descriptors.push(`initial-value: ${registration.initialValue};`);
|
|
1486
|
+
blocks.push(`@property --${name} { ${descriptors.join(" ")} }`);
|
|
1487
|
+
}
|
|
1488
|
+
return blocks.join("\n");
|
|
1489
|
+
}
|
|
885
1490
|
/** Build CSS variable blocks from flattened data. */
|
|
886
|
-
function buildVariableCss(rootVariables, breakpointVariables, modeVariables) {
|
|
1491
|
+
function buildVariableCss(rootVariables, breakpointVariables, modeVariables, systemEmission = {}) {
|
|
1492
|
+
const { initialMode, systemPreference, browserColorScheme } = systemEmission;
|
|
887
1493
|
const parts = [];
|
|
888
1494
|
const rootLines = [];
|
|
889
1495
|
for (const [varName, value] of Object.entries(rootVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
890
1496
|
for (const [varName, value] of Object.entries(breakpointVariables)) rootLines.push(` ${varName}: ${value};`);
|
|
1497
|
+
if (browserColorScheme && initialMode) {
|
|
1498
|
+
const initialScheme = browserColorScheme[initialMode];
|
|
1499
|
+
if (initialScheme) rootLines.push(` color-scheme: ${initialScheme};`);
|
|
1500
|
+
}
|
|
891
1501
|
if (rootLines.length > 0) parts.push(`:root {\n${rootLines.join("\n")}\n}`);
|
|
1502
|
+
if (systemPreference) for (const scheme of ["light", "dark"]) {
|
|
1503
|
+
const modeName = systemPreference[scheme];
|
|
1504
|
+
const mediaLines = [];
|
|
1505
|
+
const modeVars = modeVariables[modeName];
|
|
1506
|
+
if (modeVars) for (const [varName, value] of Object.entries(modeVars)) mediaLines.push(` ${varName}: ${value};`);
|
|
1507
|
+
const mediaScheme = browserColorScheme?.[modeName];
|
|
1508
|
+
if (mediaScheme) mediaLines.push(` color-scheme: ${mediaScheme};`);
|
|
1509
|
+
if (mediaLines.length === 0) continue;
|
|
1510
|
+
parts.push(`@media (prefers-color-scheme: ${scheme}) {\n :root:not([data-color-mode]) {\n${mediaLines.join("\n")}\n }\n}`);
|
|
1511
|
+
}
|
|
892
1512
|
for (const [modeName, modeVars] of Object.entries(modeVariables)) {
|
|
893
1513
|
const modeLines = [];
|
|
894
1514
|
for (const [varName, value] of Object.entries(modeVars)) modeLines.push(` ${varName}: ${value};`);
|
|
1515
|
+
const modeScheme = browserColorScheme?.[modeName];
|
|
1516
|
+
if (modeScheme) modeLines.push(` color-scheme: ${modeScheme};`);
|
|
895
1517
|
if (modeLines.length > 0) parts.push(`[data-color-mode="${modeName}"] {\n${modeLines.join("\n")}\n}`);
|
|
896
1518
|
}
|
|
897
1519
|
return parts.join("\n\n");
|
|
898
1520
|
}
|
|
899
1521
|
//#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 };
|
|
1522
|
+
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 };
|