@loworbitstudio/visor-theme-engine 0.18.0 → 0.20.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.
@@ -1,4 +1,4 @@
1
- import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-Cvm7vFwe.js';
1
+ import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-BG-YT4JW.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -37,6 +37,10 @@ interface NextJSAdapterOptions extends AdapterOptions {
37
37
  * `@layer visor-base` origination block that binds theme tokens to the page
38
38
  * root (default: true). Set false for consumers that ship Tailwind preflight
39
39
  * or their own reset. See VI-616.
40
+ *
41
+ * VI-638: the base block also carries `font-size: var(--font-size-base)`,
42
+ * which is how `typography.scale` reaches the page. A consumer that declines
43
+ * it must bind the base size itself — on `body`, not `:root`.
40
44
  */
41
45
  includeBaseLayer?: boolean;
42
46
  }
@@ -8,6 +8,8 @@ import {
8
8
  collectBrandPassthrough,
9
9
  fontStack,
10
10
  generateDarkCss,
11
+ generateFontSizeDecls,
12
+ generateFontWeightDecls,
11
13
  generateHairlineDecls,
12
14
  generateIntentDecls,
13
15
  generateLightCss,
@@ -17,10 +19,11 @@ import {
17
19
  generateTextScaleAliasDecls,
18
20
  header,
19
21
  parseColor,
22
+ resolveComponentBindings,
20
23
  resolveThemeBrand,
21
24
  resolveThemeFonts,
22
25
  sectionComment
23
- } from "../chunk-BUWBBUFG.js";
26
+ } from "../chunk-JDYLDGUN.js";
24
27
 
25
28
  // src/adapters/brand-passthrough.ts
26
29
  var SENTINEL_COLOR = "#ff00ff";
@@ -82,6 +85,42 @@ ${prefersInner}
82
85
  return blocks.filter(Boolean).join("\n\n");
83
86
  }
84
87
 
88
+ // src/adapters/component-tokens-css.ts
89
+ function indentBlock2(selector, decls) {
90
+ if (decls.length === 0) return "";
91
+ return [selector + " {", ...decls.map((d) => ` ${d}`), "}"].join("\n");
92
+ }
93
+ function declsFor(values) {
94
+ return Object.keys(values).sort().map((name) => `--${name}: ${values[name]};`);
95
+ }
96
+ function generateComponentTokensCss(tokens, selectors, colorScheme = "adaptive") {
97
+ const lightDecls = declsFor(tokens.light);
98
+ const darkDecls = declsFor(tokens.dark);
99
+ if (lightDecls.length === 0 && darkDecls.length === 0) return "";
100
+ const blocks = [
101
+ "/* \u2500\u2500 Component tokens (VI-625) \u2014 theme bindings for the admin-UI families \u2500\u2500 */"
102
+ ];
103
+ if (colorScheme === "dark-only") {
104
+ blocks.push(indentBlock2(selectors.light, darkDecls));
105
+ return blocks.filter(Boolean).join("\n\n");
106
+ }
107
+ if (colorScheme === "light-only") {
108
+ blocks.push(indentBlock2(selectors.light, lightDecls));
109
+ return blocks.filter(Boolean).join("\n\n");
110
+ }
111
+ if (lightDecls.length > 0) {
112
+ blocks.push(indentBlock2(selectors.light, lightDecls));
113
+ }
114
+ if (darkDecls.length > 0) {
115
+ blocks.push(indentBlock2(selectors.dark, darkDecls));
116
+ const prefersInner = indentBlock2(selectors.prefers, darkDecls).split("\n").map((l) => ` ${l}`).join("\n");
117
+ blocks.push(`@media (prefers-color-scheme: dark) {
118
+ ${prefersInner}
119
+ }`);
120
+ }
121
+ return blocks.filter(Boolean).join("\n\n");
122
+ }
123
+
85
124
  // src/adapters/layers.ts
86
125
  var LAYER_ORDER = "@layer visor-base, visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
87
126
  function wrapInLayer(layerName, css) {
@@ -175,7 +214,11 @@ function nextjsAdapter(input, options) {
175
214
  baseLines.push(
176
215
  block(scopePrefix ?? "body", [
177
216
  "font-family: var(--font-body);",
178
- "font-size: 1rem;",
217
+ // VI-638: the scaled ramp is the single mechanism that sets type size,
218
+ // and the base step is what the page inherits. This binding lives on
219
+ // `body` (or the body-class scope), never `:root` — the ramp is
220
+ // expressed in `rem`, so a scaled root would multiply it a second time.
221
+ "font-size: var(--font-size-base, 1rem);",
179
222
  "color: var(--text-primary);",
180
223
  "background: var(--surface-page, var(--surface-background));"
181
224
  ])
@@ -269,6 +312,15 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
269
312
  lines.push(
270
313
  wrapInLayer("visor-adaptive", adaptiveBody)
271
314
  );
315
+ const componentTokensCss = generateComponentTokensCss(
316
+ resolveComponentBindings(input.config.components),
317
+ { light: hostSelector, dark: darkSelectors.join(",\n"), prefers: prefersSelector },
318
+ colorScheme
319
+ );
320
+ if (componentTokensCss) {
321
+ lines.push("");
322
+ lines.push(wrapInLayer("visor-adaptive", componentTokensCss));
323
+ }
272
324
  if (includeFowt) {
273
325
  lines.push("");
274
326
  lines.push(
@@ -491,23 +543,8 @@ function generateTypographyDecls(config, aliases) {
491
543
  decls.push(`--font-heading: ${fontStack(headingFamily, aliases)};`);
492
544
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
493
545
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
494
- const fontSizes = {
495
- xs: 12,
496
- sm: 14,
497
- base: 16,
498
- lg: 18,
499
- xl: 20,
500
- "2xl": 24,
501
- "3xl": 30,
502
- "4xl": 36
503
- };
504
- for (const [name, px] of Object.entries(fontSizes)) {
505
- decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
506
- }
507
- decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
508
- decls.push("--font-weight-medium: 500;");
509
- decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
510
- decls.push("--font-weight-bold: 700;");
546
+ decls.push(...generateFontSizeDecls(config.typography.scale));
547
+ decls.push(...generateFontWeightDecls(config.typography));
511
548
  const lineHeights = {
512
549
  none: 1,
513
550
  tight: 1.25,
@@ -640,7 +677,6 @@ function docsAdapter(input, options) {
640
677
  fontLines.push("");
641
678
  }
642
679
  }
643
- const scale = input.config.typography?.scale ?? 1;
644
680
  const emittedFamilies = /* @__PURE__ */ new Set();
645
681
  for (const font of fontSlots) {
646
682
  if (font && font.source === "visor-fonts" && !emittedFamilies.has(font.family)) {
@@ -654,9 +690,6 @@ function docsAdapter(input, options) {
654
690
  fontLines.push(` font-weight: ${weight};`);
655
691
  fontLines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
656
692
  fontLines.push(` font-display: ${font.display};`);
657
- if (scale !== 1) {
658
- fontLines.push(` size-adjust: ${Math.round(scale * 100)}%;`);
659
- }
660
693
  fontLines.push("}");
661
694
  fontLines.push("");
662
695
  }
@@ -666,7 +699,11 @@ function docsAdapter(input, options) {
666
699
  lines.push("\n/* \u2500\u2500 Section 1: Shared tokens (mode-independent) \u2500\u2500 */");
667
700
  const sharedDecls = [
668
701
  "min-height: 100vh;",
669
- "font-size: 1rem;",
702
+ // VI-638: the scaled ramp is the single mechanism that sets type size, and
703
+ // the base step is what the page inherits. Safe here because the scope is a
704
+ // class on a wrapper, never `:root` — a scaled root would multiply the
705
+ // rem-expressed ramp a second time.
706
+ "font-size: var(--font-size-base, 1rem);",
670
707
  // BO-56: pin UA chrome to the brand's single mode (adaptive emits nothing).
671
708
  ...singleMode ? [`color-scheme: ${singleMode};`] : [],
672
709
  `background: var(--surface-page, var(--surface-background));`,
@@ -837,7 +874,17 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
837
874
  },
838
875
  colorScheme
839
876
  );
840
- const adaptiveLayer = wrapInLayer("visor-adaptive", lines.join("\n").trim());
877
+ const componentTokensCss = generateComponentTokensCss(
878
+ resolveComponentBindings(input.config.components),
879
+ {
880
+ light: lightSel,
881
+ dark: darkSel,
882
+ prefers: `${scopeClass}:not(.light):not(.theme-light):not([data-theme="light"])`
883
+ },
884
+ colorScheme
885
+ );
886
+ const adaptiveBody = [lines.join("\n").trim(), componentTokensCss].filter(Boolean).join("\n\n");
887
+ const adaptiveLayer = wrapInLayer("visor-adaptive", adaptiveBody);
841
888
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
842
889
  const brandLayerBody = [brandResult.css, passthroughCss].filter(Boolean).join("\n\n");
843
890
  const brandLayer = wrapInLayer("visor-brand", brandLayerBody);