@loworbitstudio/visor-theme-engine 0.20.0 → 0.21.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.
@@ -6,6 +6,7 @@ import {
6
6
  block,
7
7
  buildVisorFontUrl,
8
8
  collectBrandPassthrough,
9
+ collectDeclaredProperties,
9
10
  fontStack,
10
11
  generateDarkCss,
11
12
  generateFontSizeDecls,
@@ -14,6 +15,7 @@ import {
14
15
  generateIntentDecls,
15
16
  generateLightCss,
16
17
  generatePrimitivesCss,
18
+ generateSemanticAliasDecls,
17
19
  generateShadeScale,
18
20
  generateSpaceAliasDecls,
19
21
  generateTextScaleAliasDecls,
@@ -23,7 +25,7 @@ import {
23
25
  resolveThemeBrand,
24
26
  resolveThemeFonts,
25
27
  sectionComment
26
- } from "../chunk-JDYLDGUN.js";
28
+ } from "../chunk-LY5LXQYE.js";
27
29
 
28
30
  // src/adapters/brand-passthrough.ts
29
31
  var SENTINEL_COLOR = "#ff00ff";
@@ -301,14 +303,27 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
301
303
  }
302
304
  semanticLines.push("");
303
305
  }
306
+ const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix, colorScheme }));
307
+ const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
308
+ const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
309
+ if (scopePrefix) {
310
+ const declared = collectDeclaredProperties(
311
+ [primitivesBody, semanticLines.join("\n"), adaptiveBody].join("\n")
312
+ );
313
+ const aliasDecls = generateSemanticAliasDecls(declared);
314
+ if (aliasDecls.length > 0) {
315
+ semanticLines.push(
316
+ sectionComment("visor-core alias re-substitution at theme scope (VI-648)")
317
+ );
318
+ semanticLines.push(block(hostSelector, aliasDecls));
319
+ semanticLines.push("");
320
+ }
321
+ }
304
322
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
305
323
  if (semanticLayer) {
306
324
  lines.push(semanticLayer);
307
325
  lines.push("");
308
326
  }
309
- const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix, colorScheme }));
310
- const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
311
- const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
312
327
  lines.push(
313
328
  wrapInLayer("visor-adaptive", adaptiveBody)
314
329
  );
@@ -2484,6 +2484,86 @@ function generateFullBundleCss(primitives, tokens, config) {
2484
2484
  return lines.join("\n");
2485
2485
  }
2486
2486
 
2487
+ // src/semantic-aliases.ts
2488
+ var VISOR_CORE_SEMANTIC_ALIASES = {
2489
+ // Border — the one entry the engine's own `SemanticTokens.border` set lacks.
2490
+ "border-input": "color-neutral-200",
2491
+ // Component + layout spacing (4px grid multiples).
2492
+ "component-xs": "spacing-1",
2493
+ "component-sm": "spacing-2",
2494
+ "component-md": "spacing-4",
2495
+ "component-lg": "spacing-6",
2496
+ "component-xl": "spacing-8",
2497
+ "layout-sm": "spacing-4",
2498
+ "layout-md": "spacing-8",
2499
+ "layout-lg": "spacing-12",
2500
+ "layout-xl": "spacing-16",
2501
+ "layout-2xl": "spacing-24",
2502
+ "layout-section-padding": "spacing-24",
2503
+ "layout-section-padding-mobile": "spacing-16",
2504
+ // Typography size roles — track the theme's `typography.scale` ramp.
2505
+ "size-body": "font-size-base",
2506
+ "size-body-sm": "font-size-sm",
2507
+ "size-label": "font-size-sm",
2508
+ "size-caption": "font-size-xs",
2509
+ "size-heading-sm": "font-size-lg",
2510
+ "size-heading-md": "font-size-xl",
2511
+ "size-heading-lg": "font-size-2xl",
2512
+ "size-heading-xl": "font-size-3xl",
2513
+ // Typography weight roles — `--weight-heading` is the token VI-648 was filed on.
2514
+ "weight-body": "font-weight-body",
2515
+ "weight-label": "font-weight-medium",
2516
+ "weight-heading": "font-weight-heading",
2517
+ "weight-strong": "font-weight-bold",
2518
+ // Motion. `--motion-easing-spring` is absent because the engine emits it
2519
+ // under its own name, so it is already correct on the scope.
2520
+ "motion-duration-fast": "motion-duration-100",
2521
+ "motion-duration-normal": "motion-duration-200",
2522
+ "motion-duration-slow": "motion-duration-500",
2523
+ "motion-easing-default": "motion-easing-ease-in-out",
2524
+ "motion-easing-enter": "motion-easing-ease-out",
2525
+ "motion-easing-exit": "motion-easing-ease-in",
2526
+ // Skeleton shimmer. Resolves through *semantic* surfaces, not primitives —
2527
+ // the engine emits those in `visor-adaptive`, a later layer, which does not
2528
+ // affect substitution: `var()` reads the computed value on the element.
2529
+ "skeleton-from": "surface-muted",
2530
+ "skeleton-to": "surface-subtle",
2531
+ // Chart series.
2532
+ "chart-1": "color-primary-500",
2533
+ "chart-2": "color-success-500",
2534
+ "chart-3": "color-warning-500",
2535
+ "chart-4": "color-info-500",
2536
+ "chart-5": "color-error-500",
2537
+ // Sidebar chrome.
2538
+ "sidebar-bg": "color-neutral-50",
2539
+ "sidebar-text": "color-neutral-700",
2540
+ "sidebar-primary-bg": "color-primary-600",
2541
+ "sidebar-primary-text": "color-white",
2542
+ "sidebar-accent-bg": "color-neutral-100",
2543
+ "sidebar-accent-text": "color-neutral-900",
2544
+ "sidebar-border": "color-neutral-200",
2545
+ "sidebar-ring": "color-primary-500",
2546
+ "sidebar-text-muted": "color-neutral-500",
2547
+ // Field-attached floating panels (VI-497).
2548
+ "field-menu-bg": "surface-popover"
2549
+ };
2550
+ function collectDeclaredProperties(css) {
2551
+ const declared = /* @__PURE__ */ new Set();
2552
+ for (const match of css.matchAll(/(^|[{;\s])--([a-zA-Z0-9-]+)\s*:/g)) {
2553
+ declared.add(match[2]);
2554
+ }
2555
+ return declared;
2556
+ }
2557
+ function generateSemanticAliasDecls(declared) {
2558
+ const decls = [];
2559
+ for (const [alias, referent] of Object.entries(VISOR_CORE_SEMANTIC_ALIASES)) {
2560
+ if (declared.has(alias)) continue;
2561
+ if (!declared.has(referent)) continue;
2562
+ decls.push(`--${alias}: var(--${referent});`);
2563
+ }
2564
+ return decls;
2565
+ }
2566
+
2487
2567
  // src/types.ts
2488
2568
  var MATERIAL_TEXT_SLOTS = [
2489
2569
  "displayLarge",
@@ -2577,5 +2657,8 @@ export {
2577
2657
  generateHairlineDecls,
2578
2658
  generateLightCss,
2579
2659
  generateDarkCss,
2580
- generateFullBundleCss
2660
+ generateFullBundleCss,
2661
+ VISOR_CORE_SEMANTIC_ALIASES,
2662
+ collectDeclaredProperties,
2663
+ generateSemanticAliasDecls
2581
2664
  };
package/dist/index.d.ts CHANGED
@@ -1876,6 +1876,87 @@ declare const SEMANTIC_MAP: {
1876
1876
  */
1877
1877
  declare function getKnownTokenRefs(): Set<string>;
1878
1878
 
1879
+ /**
1880
+ * visor-core semantic aliases that need re-substituting at a theme scope (VI-648).
1881
+ *
1882
+ * `packages/tokens` emits its semantic layer as indirections — `--chart-1:
1883
+ * var(--color-primary-500)`, `--weight-heading: var(--font-weight-heading)` —
1884
+ * declared on `:root`.
1885
+ *
1886
+ * Custom-property substitution resolves **where the property is declared**. On
1887
+ * a `scopePrefix` theme (the VI-368 body-class repaint pattern that every
1888
+ * Blacklight theme uses) the engine emits the theme's primitives on the scope
1889
+ * selector, e.g. `body.blacklight-pro-theme`. From `:root`, a descendant's
1890
+ * value is invisible, so `var(--color-primary-500)` sees only visor-core's
1891
+ * untuned default. `body` then *inherits* the already-substituted default, and
1892
+ * the alias silently ignores the theme:
1893
+ *
1894
+ * ```
1895
+ * --font-weight-heading : 500 <- correct, emitted by the engine on the scope
1896
+ * --weight-heading : 600 <- visor-core's :root default
1897
+ * --chart-1 : #3b82f6 <- stock blue on a monochrome theme
1898
+ * --skeleton-from : #f3f4f6 <- light gray on a dark theme
1899
+ * ```
1900
+ *
1901
+ * The engine independently resolves 66 of visor-core's 114 aliases to concrete
1902
+ * values (`--text-*`, `--surface-*`, `--border-*`, `--interactive-*`, `--font-*`)
1903
+ * and emits those on the scope already, so they are correct. The 48 below are
1904
+ * the ones only visor-core knows how to derive. Re-declaring them on the scope
1905
+ * selector makes the substitution happen there, against the theme's own
1906
+ * primitives.
1907
+ *
1908
+ * ## Why this table lives in the engine, not in `packages/tokens`
1909
+ *
1910
+ * `packages/tokens` is the natural owner — these are its aliases. But
1911
+ * `packages/tokens` already depends on this package (its build calls
1912
+ * `generateThemeData` + `docsAdapter` to emit `dist/themes/<slug>.css`), so an
1913
+ * engine → tokens import would be a build cycle. The table therefore sits on
1914
+ * the importable side of that edge.
1915
+ *
1916
+ * It is not a hand-maintained copy. `packages/tokens` owns the authoring
1917
+ * tables in `src/tokens/semantic.ts` and this list is asserted against the
1918
+ * *emitted* `tokens.css` by `semantic-alias-coverage` — a stronger check than
1919
+ * comparing two constants, because it compares against what actually ships.
1920
+ * Adding an alias in `packages/tokens` without adding it here fails that test
1921
+ * with the missing names.
1922
+ */
1923
+ /**
1924
+ * Alias name → the token it resolves through. Both written without the leading
1925
+ * `--`. Order mirrors visor-core's emission order in `tokens.css`, which keeps
1926
+ * a diff against that file readable.
1927
+ */
1928
+ declare const VISOR_CORE_SEMANTIC_ALIASES: Readonly<Record<string, string>>;
1929
+ /**
1930
+ * Every custom property declared anywhere in `css`, without the leading `--`.
1931
+ *
1932
+ * Read off the generated CSS rather than the token data so the filter in
1933
+ * `generateSemanticAliasDecls` reflects what this theme *actually* emits —
1934
+ * including conditional output such as the `--font-weight-<n>` ladder, which
1935
+ * only appears when a theme declares an explicit `weights` array.
1936
+ */
1937
+ declare function collectDeclaredProperties(css: string): Set<string>;
1938
+ /**
1939
+ * The `--alias: var(--referent);` declarations to re-emit on a theme's scope
1940
+ * selector, given everything the theme has emitted so far.
1941
+ *
1942
+ * Filtered on both sides, so the table can never make output worse:
1943
+ *
1944
+ * - **Skip an alias the theme already declares.** The theme's own value is
1945
+ * authoritative, and re-declaring it in `visor-semantic` would override a
1946
+ * `visor-primitives` declaration of the same name. `--font-body` is the live
1947
+ * case: the engine emits it as a concrete family, and re-aliasing it to
1948
+ * `var(--font-sans)` would win the layer contest and resolve to nothing.
1949
+ * - **Skip an alias whose referent the theme does not declare.** There is then
1950
+ * nothing to re-resolve against, and inheriting visor-core's `:root` value is
1951
+ * the correct outcome — re-emitting would replace a working value with an
1952
+ * invalid one.
1953
+ *
1954
+ * Returns `[]` for a `:root`-scoped theme's caller, which does not need this at
1955
+ * all: there, visor-core's aliases and the theme's primitives are declared on
1956
+ * the same element, so substitution already sees the theme's values.
1957
+ */
1958
+ declare function generateSemanticAliasDecls(declared: ReadonlySet<string>): string[];
1959
+
1879
1960
  /**
1880
1961
  * Per-theme font-family aliasing — substrate fix for VI-354.
1881
1962
  *
@@ -2126,4 +2207,4 @@ declare function cleanFontValue(val: string): string;
2126
2207
  */
2127
2208
  declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
2128
2209
 
2129
- export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, type ResolvedWeightRamp, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, WEIGHT_RAMP_TARGETS, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, declaresWeights, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFontWeightDecls, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, loadedWeights, lookupFontWeightAlias, lookupGoogleFont, matchLoadedWeight, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveRoleWeight, resolveThemeBrand, resolveThemeFonts, resolveWeightRamp, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
2210
+ export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, type ResolvedWeightRamp, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_CORE_SEMANTIC_ALIASES, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, WEIGHT_RAMP_TARGETS, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, collectDeclaredProperties, compositeOverBackground, declaresWeights, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFontWeightDecls, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticAliasDecls, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, loadedWeights, lookupFontWeightAlias, lookupGoogleFont, matchLoadedWeight, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveRoleWeight, resolveThemeBrand, resolveThemeFonts, resolveWeightRamp, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  MATERIAL_TEXT_SLOTS,
8
8
  TAILWIND_GRAY,
9
9
  VISOR_BRANDS_CDN,
10
+ VISOR_CORE_SEMANTIC_ALIASES,
10
11
  VISOR_DEFAULT_BRAND_PATH,
11
12
  VISOR_FONTS_CDN,
12
13
  WEIGHT_RAMP_TARGETS,
@@ -16,6 +17,7 @@ import {
16
17
  buildVisorFontUrl,
17
18
  clampToSrgb,
18
19
  collectBrandPassthrough,
20
+ collectDeclaredProperties,
19
21
  componentTokenName,
20
22
  compositeOverBackground,
21
23
  declaresWeights,
@@ -25,6 +27,7 @@ import {
25
27
  generateLightCss,
26
28
  generatePreloadLinks,
27
29
  generatePrimitivesCss,
30
+ generateSemanticAliasDecls,
28
31
  generateSemanticCss,
29
32
  generateShadeScale,
30
33
  generateStylesheetLinks,
@@ -60,7 +63,7 @@ import {
60
63
  rgbToOklch,
61
64
  serializeColor,
62
65
  validateComponentBindings
63
- } from "./chunk-JDYLDGUN.js";
66
+ } from "./chunk-LY5LXQYE.js";
64
67
 
65
68
  // src/fonts/validate-coverage.ts
66
69
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -4262,6 +4265,7 @@ export {
4262
4265
  SEMANTIC_MAP,
4263
4266
  TAILWIND_GRAY,
4264
4267
  VISOR_BRANDS_CDN,
4268
+ VISOR_CORE_SEMANTIC_ALIASES,
4265
4269
  VISOR_DEFAULT_BRAND_PATH,
4266
4270
  VISOR_FONTS_CDN,
4267
4271
  WEIGHT_RAMP_TARGETS,
@@ -4275,6 +4279,7 @@ export {
4275
4279
  clampToSrgb,
4276
4280
  cleanFontValue,
4277
4281
  collectBrandPassthrough,
4282
+ collectDeclaredProperties,
4278
4283
  componentTokenName,
4279
4284
  compositeOverBackground,
4280
4285
  declaresWeights,
@@ -4288,6 +4293,7 @@ export {
4288
4293
  generatePreloadLinks,
4289
4294
  generatePrimitives,
4290
4295
  generatePrimitivesCss,
4296
+ generateSemanticAliasDecls,
4291
4297
  generateSemanticCss,
4292
4298
  generateShadeScale,
4293
4299
  generateStylesheetLinks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Theme engine for the Visor design system — shade generation, token mapping, font resolution, and import/export for .visor.yaml themes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",