@tenphi/glaze 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -234,9 +234,10 @@ interface GlazeOutputModes {
234
234
  /**
235
235
  * Include high-contrast variants (both light-HC and dark-HC). Default: false.
236
236
  *
237
- * Inert while a global `contrastLevel` is set: a manual level has no separate
238
- * high-contrast tier, so this reads as "emit high-contrast variants when
239
- * contrast is automatic".
237
+ * Independent of `contrastLevel` — the level positions the normal variants
238
+ * while these stay the true high-contrast resolution, so the two compose. The
239
+ * exception is a global level of 100, where the normal variants already *are*
240
+ * the high-contrast ones: the tier would duplicate them and is dropped.
240
241
  * @see GlazeConfig.contrastLevel
241
242
  */
242
243
  highContrast?: boolean;
@@ -572,15 +573,15 @@ interface GlazeConfig {
572
573
  shadowTuning?: ShadowTuning;
573
574
  /**
574
575
  * Manual contrast level — a 0–100 slider from normal contrast (`0`) to high
575
- * contrast (`100`), or `'auto'` (the default) to keep the two-tier
576
- * normal + high-contrast model.
576
+ * contrast (`100`), or `'auto'` (the default) to take the authored normal
577
+ * entries as-is.
577
578
  *
578
- * With a number, Glaze resolves the `light` / `dark` variants *at* that
579
- * level and stops emitting a separate high-contrast tier:
580
- * `lightContrast` / `darkContrast` mirror their normal counterparts and
581
- * `modes.highContrast` defaults to `false`. Level `0` is therefore "normal
582
- * contrast, no high-contrast tier"; `100` is "the high-contrast scheme as
583
- * the only scheme".
579
+ * With a number, Glaze resolves the `light` / `dark` variants *at* that level.
580
+ * That is all it does: `lightContrast` / `darkContrast` stay the true
581
+ * high-contrast resolution at every level, and `modes.highContrast` alone
582
+ * decides whether they are emitted. Level `0` therefore reproduces `'auto'`
583
+ * bit for bit. At `100` the normal variants *are* the high-contrast ones, so a
584
+ * separate tier would duplicate them and a global level of 100 drops it.
584
585
  *
585
586
  * The level interpolates all three things that make high contrast differ:
586
587
  * authored `[normal, highContrast]` pairs, the tone-window widening, and
@@ -630,8 +631,8 @@ interface GlazeConfigResolved {
630
631
  shadowTuning?: ShadowTuning;
631
632
  autoFlip: boolean;
632
633
  /**
633
- * Manual contrast level (0–100), or `'auto'` for the two-tier
634
- * normal + high-contrast model.
634
+ * Manual contrast level (0–100) for the normal variants, or `'auto'` to take
635
+ * the authored normal entries as-is.
635
636
  * @see GlazeConfig.contrastLevel
636
637
  */
637
638
  contrastLevel: number | 'auto';
@@ -661,8 +662,10 @@ interface GlazeConfigOverride {
661
662
  /** Whether to auto-flip tone when contrast can't be met. */
662
663
  autoFlip?: boolean;
663
664
  /**
664
- * Manual contrast level (0–100) for this instance, or `'auto'` to opt out
665
- * of a global level and keep the two-tier normal + high-contrast model.
665
+ * Manual contrast level (0–100) for this instance, or `'auto'` to opt out of a
666
+ * global level and take the authored normal entries as-is. Either way the
667
+ * instance keeps its high-contrast tier; a per-instance level never changes
668
+ * which modes are emitted.
666
669
  *
667
670
  * Only an instance-authored level is frozen into `.export()` snapshots — a
668
671
  * level inherited from the global config is treated as a live preference and
package/dist/index.d.mts CHANGED
@@ -234,9 +234,10 @@ interface GlazeOutputModes {
234
234
  /**
235
235
  * Include high-contrast variants (both light-HC and dark-HC). Default: false.
236
236
  *
237
- * Inert while a global `contrastLevel` is set: a manual level has no separate
238
- * high-contrast tier, so this reads as "emit high-contrast variants when
239
- * contrast is automatic".
237
+ * Independent of `contrastLevel` — the level positions the normal variants
238
+ * while these stay the true high-contrast resolution, so the two compose. The
239
+ * exception is a global level of 100, where the normal variants already *are*
240
+ * the high-contrast ones: the tier would duplicate them and is dropped.
240
241
  * @see GlazeConfig.contrastLevel
241
242
  */
242
243
  highContrast?: boolean;
@@ -572,15 +573,15 @@ interface GlazeConfig {
572
573
  shadowTuning?: ShadowTuning;
573
574
  /**
574
575
  * Manual contrast level — a 0–100 slider from normal contrast (`0`) to high
575
- * contrast (`100`), or `'auto'` (the default) to keep the two-tier
576
- * normal + high-contrast model.
576
+ * contrast (`100`), or `'auto'` (the default) to take the authored normal
577
+ * entries as-is.
577
578
  *
578
- * With a number, Glaze resolves the `light` / `dark` variants *at* that
579
- * level and stops emitting a separate high-contrast tier:
580
- * `lightContrast` / `darkContrast` mirror their normal counterparts and
581
- * `modes.highContrast` defaults to `false`. Level `0` is therefore "normal
582
- * contrast, no high-contrast tier"; `100` is "the high-contrast scheme as
583
- * the only scheme".
579
+ * With a number, Glaze resolves the `light` / `dark` variants *at* that level.
580
+ * That is all it does: `lightContrast` / `darkContrast` stay the true
581
+ * high-contrast resolution at every level, and `modes.highContrast` alone
582
+ * decides whether they are emitted. Level `0` therefore reproduces `'auto'`
583
+ * bit for bit. At `100` the normal variants *are* the high-contrast ones, so a
584
+ * separate tier would duplicate them and a global level of 100 drops it.
584
585
  *
585
586
  * The level interpolates all three things that make high contrast differ:
586
587
  * authored `[normal, highContrast]` pairs, the tone-window widening, and
@@ -630,8 +631,8 @@ interface GlazeConfigResolved {
630
631
  shadowTuning?: ShadowTuning;
631
632
  autoFlip: boolean;
632
633
  /**
633
- * Manual contrast level (0–100), or `'auto'` for the two-tier
634
- * normal + high-contrast model.
634
+ * Manual contrast level (0–100) for the normal variants, or `'auto'` to take
635
+ * the authored normal entries as-is.
635
636
  * @see GlazeConfig.contrastLevel
636
637
  */
637
638
  contrastLevel: number | 'auto';
@@ -661,8 +662,10 @@ interface GlazeConfigOverride {
661
662
  /** Whether to auto-flip tone when contrast can't be met. */
662
663
  autoFlip?: boolean;
663
664
  /**
664
- * Manual contrast level (0–100) for this instance, or `'auto'` to opt out
665
- * of a global level and keep the two-tier normal + high-contrast model.
665
+ * Manual contrast level (0–100) for this instance, or `'auto'` to opt out of a
666
+ * global level and take the authored normal entries as-is. Either way the
667
+ * instance keeps its high-contrast tier; a per-instance level never changes
668
+ * which modes are emitted.
666
669
  *
667
670
  * Only an instance-authored level is frozen into `.export()` snapshots — a
668
671
  * level inherited from the global config is treated as a live preference and
package/dist/index.mjs CHANGED
@@ -904,10 +904,14 @@ function levelFraction(level) {
904
904
  }
905
905
  /**
906
906
  * Manual-contrast blend fraction (0–1) for a resolved config, or `undefined`
907
- * in `'auto'` mode (the two-tier normal + high-contrast model).
907
+ * in `'auto'` mode, where the normal variants take the authored normal entries
908
+ * as-is.
908
909
  *
909
- * Note `contrastLevel: 0` yields `0`, not `undefined`: "pinned to normal
910
- * contrast with no high-contrast tier" is a distinct state from `'auto'`.
910
+ * Note `contrastLevel: 0` yields `0`, not `undefined`. The two select different
911
+ * branches, and an authored `0` is a slider position that `.export()` freezes
912
+ * where an absent level is not — but they are not an *output* distinction: at
913
+ * fraction 0 every interpolation returns its normal entry verbatim, so level 0
914
+ * reproduces `'auto'` bit for bit.
911
915
  */
912
916
  function contrastFraction(config) {
913
917
  const level = config.contrastLevel;
@@ -915,6 +919,20 @@ function contrastFraction(config) {
915
919
  return levelFraction(level);
916
920
  }
917
921
  /**
922
+ * Whether the high-contrast variants are exact duplicates of the normal ones —
923
+ * true only at `contrastLevel: 100`, where the normal passes already resolve at
924
+ * full contrast.
925
+ *
926
+ * The level otherwise says nothing about high contrast: it positions the normal
927
+ * variants, while `lightContrast` / `darkContrast` stay the true high-contrast
928
+ * resolution at every level. This predicate marks the one level where that tier
929
+ * carries no new information, so callers can skip the two high-contrast passes,
930
+ * their drift checks, and the redundant output tier.
931
+ */
932
+ function hcMirrorsNormal(config) {
933
+ return contrastFraction(config) === 1;
934
+ }
935
+ /**
918
936
  * A numeric HC pair at a blend fraction (shadow `intensity`, mix `value`).
919
937
  *
920
938
  * The endpoints return the authored entry *by identity* rather than through
@@ -2274,12 +2292,14 @@ function warnContrastDrift(name, isDark, isHighContrast, contrast, yColor, yBase
2274
2292
  * Color resolution engine.
2275
2293
  *
2276
2294
  * Runs the four-pass solver (light → light-HC → dark → dark-HC) that
2277
- * turns a `ColorMap` into a fully resolved `ResolvedColor` per name — or,
2278
- * under a manual `contrastLevel`, a two-pass solver (light → dark) whose
2279
- * authored HC pairs, tone window, and contrast targets are interpolated at
2280
- * that level and whose high-contrast slots mirror the normal ones.
2281
- * Owns the per-scheme resolve helpers for regular, shadow, and mix
2282
- * color defs.
2295
+ * turns a `ColorMap` into a fully resolved `ResolvedColor` per name. A manual
2296
+ * `contrastLevel` interpolates the authored HC pairs, tone window, and contrast
2297
+ * targets at that level for the two *normal* passes only; the high-contrast
2298
+ * passes bypass the level entirely, so the high-contrast slots are the same
2299
+ * values `'auto'` resolves. At level 100 the normal passes already are the
2300
+ * high-contrast ones, so the solver drops to two passes (light → dark) and
2301
+ * mirrors the slots. Owns the per-scheme resolve helpers for regular, shadow,
2302
+ * and mix color defs.
2283
2303
  *
2284
2304
  * Variants are stored in OKHST: `h` / `s` are OKHSL hue/saturation and
2285
2305
  * `t` is the canonical contrast-uniform tone (0–1, reference eps). The
@@ -2536,7 +2556,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, channels,
2536
2556
  flip,
2537
2557
  pastel
2538
2558
  };
2539
- const level = contrastFraction(ctx.config);
2559
+ const level = isHighContrast ? void 0 : contrastFraction(config);
2540
2560
  let preferInitial = false;
2541
2561
  if (level !== void 0 && level > 0 && level < 1) {
2542
2562
  initialDirection = findToneForContrast({
@@ -2725,10 +2745,11 @@ function seedField(order, ctx, field, source) {
2725
2745
  * resolved with a `base` + `contrast` may land slightly under the contrast
2726
2746
  * its tone implies because chromatic luminance drifts from the gray tone.
2727
2747
  *
2728
- * Under a manual `contrastLevel` only the two emitted variants are checked
2729
- * (the high-contrast slots are mirrors), and the spec is resolved at the level
2730
- * so the check measures the output against the target it was actually solved
2731
- * for.
2748
+ * Under a manual `contrastLevel` the spec is resolved at the level, so the check
2749
+ * measures each normal variant against the target it was actually solved for. At
2750
+ * level 100 the high-contrast slots are mirrors and are skipped: the normal
2751
+ * schemes already carry that check, and repeating it would emit a byte-identical
2752
+ * advisory under a different scheme label, which dedupes as a separate key.
2732
2753
  */
2733
2754
  function verifyContrastDrift(order, defs, result, config) {
2734
2755
  const roles = /* @__PURE__ */ new Map();
@@ -2763,9 +2784,9 @@ function verifyContrastDrift(order, defs, result, config) {
2763
2784
  field: "darkContrast"
2764
2785
  }
2765
2786
  ];
2766
- const manual = contrastFraction(config) !== void 0;
2787
+ const mirrored = hcMirrorsNormal(config);
2767
2788
  for (const s of schemes) {
2768
- if (manual && s.isHighContrast) continue;
2789
+ if (mirrored && s.isHighContrast) continue;
2769
2790
  const spec = resolveContrastSpec(regDef.contrast, s.isHighContrast, config, polarity);
2770
2791
  const cVariant = color[s.field];
2771
2792
  const bVariant = base[s.field];
@@ -2790,10 +2811,10 @@ function resolveAllColors(seed, defs, config, externalBases) {
2790
2811
  roles: /* @__PURE__ */ new Map()
2791
2812
  };
2792
2813
  if (externalBases) for (const [name, color] of externalBases) ctx.resolved.set(name, color);
2793
- const manual = contrastFraction(config) !== void 0;
2814
+ const mirrored = hcMirrorsNormal(config);
2794
2815
  const lightMap = runPass(order, defs, ctx, false, false, "light");
2795
2816
  let lightHCMap = lightMap;
2796
- if (!manual) {
2817
+ if (!mirrored) {
2797
2818
  seedField(order, ctx, "lightContrast", lightMap);
2798
2819
  lightHCMap = runPass(order, defs, ctx, false, true, "lightContrast");
2799
2820
  }
@@ -2801,7 +2822,7 @@ function resolveAllColors(seed, defs, config, externalBases) {
2801
2822
  seedField(order, ctx, "darkContrast", lightHCMap);
2802
2823
  const darkMap = runPass(order, defs, ctx, true, false, "dark");
2803
2824
  let darkHCMap = darkMap;
2804
- if (!manual) {
2825
+ if (!mirrored) {
2805
2826
  seedField(order, ctx, "darkContrast", darkMap);
2806
2827
  darkHCMap = runPass(order, defs, ctx, true, true, "darkContrast");
2807
2828
  }
@@ -3035,23 +3056,28 @@ function formatColorValue(v, format, pastel, huePlan) {
3035
3056
  /**
3036
3057
  * Resolve the effective output modes for an export.
3037
3058
  *
3038
- * A global manual `contrastLevel` turns off high-contrast output outright: the
3039
- * level *is* the contrast preference, so a separate high-contrast tier has no
3040
- * meaning alongside it. `modes.highContrast` therefore reads as "emit a separate
3041
- * high-contrast set **when** contrast is automatic" — it goes inert under a
3042
- * manual level rather than fighting it, and silently, since switching a
3043
- * preference from auto to manual is normal use and not a mistake to report.
3059
+ * `modes.highContrast` and `contrastLevel` are independent: the level positions
3060
+ * the normal variants, the high-contrast tier stays the true high-contrast
3061
+ * resolution, and the two compose — a slider raises the baseline while a
3062
+ * `prefers-contrast: more` block still escalates on top of it.
3063
+ *
3064
+ * The one exception is a **global** level of 100, where the normal variants
3065
+ * already *are* the high-contrast ones. The tier would be an exact duplicate, so
3066
+ * it is dropped and a single light/dark set is emitted — including against an
3067
+ * explicit `highContrast: true` override, which has nothing different left to
3068
+ * ask for.
3044
3069
  *
3045
3070
  * A level set on a single theme or token does not change which modes are
3046
- * emitted: sibling themes in a palette may still have a real high-contrast
3047
- * tier, and the manual one correctly reports its own resolved values there —
3048
- * the alternative would drop its colors from that tier entirely.
3071
+ * emitted: `modes` is global-only, and a palette must not have one sibling
3072
+ * collapse the shared token structure. A per-instance level of 100 therefore
3073
+ * still reports into the tier, with values equal to its own normal ones.
3049
3074
  */
3050
3075
  function resolveModes(override) {
3051
3076
  const cfg = getConfig();
3077
+ const highContrast = override?.highContrast ?? cfg.modes.highContrast;
3052
3078
  return {
3053
3079
  dark: override?.dark ?? cfg.modes.dark,
3054
- highContrast: contrastFraction(cfg) !== void 0 ? false : override?.highContrast ?? cfg.modes.highContrast
3080
+ highContrast: highContrast && !hcMirrorsNormal(cfg)
3055
3081
  };
3056
3082
  }
3057
3083
  function buildTokenMap(resolved, prefix, states, modes, format = "oklch", pastel = false, channelCtx) {