@loworbitstudio/visor-theme-engine 0.17.1 → 0.19.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-DF5StphJ.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -32,6 +32,13 @@ interface NextJSAdapterOptions extends AdapterOptions {
32
32
  * (`:root`) for backward compatibility. See VI-368.
33
33
  */
34
34
  scopePrefix?: string;
35
+ /**
36
+ * Emit the `@import "@loworbitstudio/visor-core/reset";` line and the
37
+ * `@layer visor-base` origination block that binds theme tokens to the page
38
+ * root (default: true). Set false for consumers that ship Tailwind preflight
39
+ * or their own reset. See VI-616.
40
+ */
41
+ includeBaseLayer?: boolean;
35
42
  }
36
43
  /** Options specific to the Deck adapter. */
37
44
  interface DeckAdapterOptions extends AdapterOptions {
@@ -198,12 +205,19 @@ declare function flutterAdapter(input: AdapterInput, options?: FlutterAdapterOpt
198
205
  /**
199
206
  * Layer order declaration — must appear before any @layer blocks.
200
207
  *
208
+ * `visor-base` (VI-616) is FIRST — i.e. lowest priority. It carries the
209
+ * element-level baseline (token-to-page binding + UA reset). Lowest placement
210
+ * means a consumer's own unlayered `body {}` always wins, and component
211
+ * `.module.css` (which uses no `@layer` at all) beats the base layer
212
+ * unconditionally. Author-origin still beats the UA stylesheet regardless of
213
+ * layer, so the reset does its job.
214
+ *
201
215
  * `visor-brand` (VI-470) is ordered immediately after `visor-semantic`: brand
202
216
  * asset vars (`--brand-*`) sit above semantic tokens so brand overrides stay
203
217
  * cleanly separable, while still below `visor-adaptive` chrome and the
204
218
  * `visor-bridge` framework layer.
205
219
  */
206
- declare const LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
220
+ declare const LAYER_ORDER = "@layer visor-base, visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
207
221
  /**
208
222
  * Wrap CSS content in a named @layer block.
209
223
  */
@@ -17,10 +17,11 @@ import {
17
17
  generateTextScaleAliasDecls,
18
18
  header,
19
19
  parseColor,
20
+ resolveComponentBindings,
20
21
  resolveThemeBrand,
21
22
  resolveThemeFonts,
22
23
  sectionComment
23
- } from "../chunk-BUWBBUFG.js";
24
+ } from "../chunk-32DC5DAR.js";
24
25
 
25
26
  // src/adapters/brand-passthrough.ts
26
27
  var SENTINEL_COLOR = "#ff00ff";
@@ -82,8 +83,44 @@ ${prefersInner}
82
83
  return blocks.filter(Boolean).join("\n\n");
83
84
  }
84
85
 
86
+ // src/adapters/component-tokens-css.ts
87
+ function indentBlock2(selector, decls) {
88
+ if (decls.length === 0) return "";
89
+ return [selector + " {", ...decls.map((d) => ` ${d}`), "}"].join("\n");
90
+ }
91
+ function declsFor(values) {
92
+ return Object.keys(values).sort().map((name) => `--${name}: ${values[name]};`);
93
+ }
94
+ function generateComponentTokensCss(tokens, selectors, colorScheme = "adaptive") {
95
+ const lightDecls = declsFor(tokens.light);
96
+ const darkDecls = declsFor(tokens.dark);
97
+ if (lightDecls.length === 0 && darkDecls.length === 0) return "";
98
+ const blocks = [
99
+ "/* \u2500\u2500 Component tokens (VI-625) \u2014 theme bindings for the admin-UI families \u2500\u2500 */"
100
+ ];
101
+ if (colorScheme === "dark-only") {
102
+ blocks.push(indentBlock2(selectors.light, darkDecls));
103
+ return blocks.filter(Boolean).join("\n\n");
104
+ }
105
+ if (colorScheme === "light-only") {
106
+ blocks.push(indentBlock2(selectors.light, lightDecls));
107
+ return blocks.filter(Boolean).join("\n\n");
108
+ }
109
+ if (lightDecls.length > 0) {
110
+ blocks.push(indentBlock2(selectors.light, lightDecls));
111
+ }
112
+ if (darkDecls.length > 0) {
113
+ blocks.push(indentBlock2(selectors.dark, darkDecls));
114
+ const prefersInner = indentBlock2(selectors.prefers, darkDecls).split("\n").map((l) => ` ${l}`).join("\n");
115
+ blocks.push(`@media (prefers-color-scheme: dark) {
116
+ ${prefersInner}
117
+ }`);
118
+ }
119
+ return blocks.filter(Boolean).join("\n\n");
120
+ }
121
+
85
122
  // src/adapters/layers.ts
86
- var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
123
+ var LAYER_ORDER = "@layer visor-base, visor-primitives, visor-semantic, visor-brand, visor-adaptive, visor-bridge;";
87
124
  function wrapInLayer(layerName, css) {
88
125
  const trimmed = css.trim();
89
126
  if (!trimmed) return "";
@@ -99,12 +136,17 @@ function toKebabCase(name) {
99
136
  function nextjsAdapter(input, options) {
100
137
  const includeFontImports = options?.includeFontImports ?? true;
101
138
  const includeFowt = options?.includeFowt ?? true;
139
+ const includeBaseLayer = options?.includeBaseLayer ?? true;
102
140
  const scopePrefix = options?.scopePrefix;
103
141
  const colorScheme = input.config["color-scheme"] ?? "adaptive";
104
142
  const lines = [];
105
143
  const slug = toKebabCase(input.config.name);
106
144
  const aliasedFamilies = /* @__PURE__ */ new Map();
107
145
  lines.push(header("Visor Theme \u2014 NextJS Adapter"));
146
+ if (includeBaseLayer) {
147
+ lines.push('@import "@loworbitstudio/visor-core/reset";');
148
+ lines.push("");
149
+ }
108
150
  if (includeFontImports && input.config.typography) {
109
151
  const fontResult = resolveThemeFonts(input.config.typography);
110
152
  const fontSlots = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono];
@@ -157,6 +199,27 @@ function nextjsAdapter(input, options) {
157
199
  }
158
200
  lines.push(LAYER_ORDER);
159
201
  lines.push("");
202
+ if (includeBaseLayer) {
203
+ const baseLines = [];
204
+ if (colorScheme === "dark-only" || colorScheme === "light-only") {
205
+ baseLines.push(sectionComment("Base: UA color-scheme"));
206
+ baseLines.push(
207
+ block("html", [`color-scheme: ${colorScheme === "dark-only" ? "dark" : "light"};`])
208
+ );
209
+ baseLines.push("");
210
+ }
211
+ baseLines.push(sectionComment("Base: token-to-page binding"));
212
+ baseLines.push(
213
+ block(scopePrefix ?? "body", [
214
+ "font-family: var(--font-body);",
215
+ "font-size: 1rem;",
216
+ "color: var(--text-primary);",
217
+ "background: var(--surface-page, var(--surface-background));"
218
+ ])
219
+ );
220
+ lines.push(wrapInLayer("visor-base", baseLines.join("\n").trim()));
221
+ lines.push("");
222
+ }
160
223
  const primitivesBody = stripHeader(
161
224
  generatePrimitivesCss(input.primitives, input.config, {
162
225
  aliasedFamilies,
@@ -243,6 +306,15 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
243
306
  lines.push(
244
307
  wrapInLayer("visor-adaptive", adaptiveBody)
245
308
  );
309
+ const componentTokensCss = generateComponentTokensCss(
310
+ resolveComponentBindings(input.config.components),
311
+ { light: hostSelector, dark: darkSelectors.join(",\n"), prefers: prefersSelector },
312
+ colorScheme
313
+ );
314
+ if (componentTokensCss) {
315
+ lines.push("");
316
+ lines.push(wrapInLayer("visor-adaptive", componentTokensCss));
317
+ }
246
318
  if (includeFowt) {
247
319
  lines.push("");
248
320
  lines.push(
@@ -811,7 +883,17 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
811
883
  },
812
884
  colorScheme
813
885
  );
814
- const adaptiveLayer = wrapInLayer("visor-adaptive", lines.join("\n").trim());
886
+ const componentTokensCss = generateComponentTokensCss(
887
+ resolveComponentBindings(input.config.components),
888
+ {
889
+ light: lightSel,
890
+ dark: darkSel,
891
+ prefers: `${scopeClass}:not(.light):not(.theme-light):not([data-theme="light"])`
892
+ },
893
+ colorScheme
894
+ );
895
+ const adaptiveBody = [lines.join("\n").trim(), componentTokensCss].filter(Boolean).join("\n\n");
896
+ const adaptiveLayer = wrapInLayer("visor-adaptive", adaptiveBody);
815
897
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
816
898
  const brandLayerBody = [brandResult.css, passthroughCss].filter(Boolean).join("\n\n");
817
899
  const brandLayer = wrapInLayer("visor-brand", brandLayerBody);