@loworbitstudio/visor-theme-engine 0.6.0 → 0.8.1
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 +40 -0
- package/dist/adapters/index.d.ts +11 -1
- package/dist/adapters/index.js +8 -4
- package/dist/{chunk-4U5L3AWY.js → chunk-OJVNL7KN.js} +47 -19
- package/dist/index.d.ts +20 -5
- package/dist/index.js +70 -1
- package/dist/{types-CtozYHw0.d.ts → types-CV0nmvMz.d.ts} +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,46 @@ Themes are typically managed via the Visor CLI (`visor theme sync`). Direct API
|
|
|
24
24
|
import { generateTheme } from '@loworbitstudio/visor-theme-engine'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Migration
|
|
28
|
+
|
|
29
|
+
### Themes pinned to `^0.4.x` with a custom mono font
|
|
30
|
+
|
|
31
|
+
Engine 0.5 expanded `typography.mono` to accept `weight | weights | source | org` (previously only `family`). Engine 0.6 added `validate-coverage`, which errors when any `--font-*` declaration names a family with no matching `@font-face`. The combination created a trap: themes pinned to `^0.4.x` could only write `mono: { family: X }` (the only thing 0.4 allowed) and could not express the source/org fix the 0.6 error message points to.
|
|
32
|
+
|
|
33
|
+
To migrate:
|
|
34
|
+
|
|
35
|
+
1. **Bump both** `@loworbitstudio/visor` (the CLI) to `≥ 0.10` and `@loworbitstudio/visor-theme-engine` to `≥ 0.6` together. The CLI transitively pins its own engine copy (CLI 0.10 → engine `^0.6.0`), so `visor theme sync` runs against the CLI-bundled engine, not the hoisted one — bumping the engine alone is silently insufficient.
|
|
36
|
+
|
|
37
|
+
2. **Decide between inheritance and explicit declaration:**
|
|
38
|
+
|
|
39
|
+
- **Inheritance (preferred when applicable).** If your mono slot's family matches another slot (heading, display, or body) with `source`/`org` set, leave `typography.mono.source` and `typography.mono.org` unset. The engine will inherit `source`/`org` from the matching slot. Match precedence: heading → display → body, case-insensitive.
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
typography:
|
|
43
|
+
body:
|
|
44
|
+
family: PP Model Mono
|
|
45
|
+
weight: 400
|
|
46
|
+
source: visor-fonts
|
|
47
|
+
org: low-orbit-studio
|
|
48
|
+
mono:
|
|
49
|
+
family: PP Model Mono
|
|
50
|
+
weight: 400
|
|
51
|
+
# source/org inherited from body
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **Explicit declaration.** Otherwise, add `source` (and `org` for `visor-fonts`) directly:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
typography:
|
|
58
|
+
mono:
|
|
59
|
+
family: PP Model Mono
|
|
60
|
+
weight: 400
|
|
61
|
+
source: visor-fonts # or google-fonts, fontshare, local
|
|
62
|
+
org: low-orbit-studio # required for visor-fonts only
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
System mono fonts (`SF Mono`, `JetBrains Mono`, `Source Code Pro`, `Menlo`, etc.) are already on the validator's `SYSTEM_FONTS` list and never need `source`/`org`.
|
|
66
|
+
|
|
27
67
|
## Documentation
|
|
28
68
|
|
|
29
69
|
Full docs at [visor.loworbit.studio](https://visor.loworbit.studio).
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-
|
|
1
|
+
import { c as GeneratedPrimitives, i as SemanticTokens, R as ResolvedThemeConfig } from '../types-CV0nmvMz.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
|
@@ -22,6 +22,16 @@ interface AdapterOptions {
|
|
|
22
22
|
interface NextJSAdapterOptions extends AdapterOptions {
|
|
23
23
|
/** Include Google Fonts @import statements (default: true) */
|
|
24
24
|
includeFontImports?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Optional CSS selector that replaces `:root` in the generated output,
|
|
27
|
+
* enabling the body-class repaint pattern (e.g. `body.blacklight-theme`).
|
|
28
|
+
* When set, the dark-mode block scopes to `<scopePrefix>.dark`,
|
|
29
|
+
* `<scopePrefix>.theme-dark`, and `<scopePrefix>[data-theme="dark"]`;
|
|
30
|
+
* the `prefers-color-scheme: dark` media query composes the prefix with
|
|
31
|
+
* the existing `:not(.light)` guards. When omitted, output is unchanged
|
|
32
|
+
* (`:root`) for backward compatibility. See VI-368.
|
|
33
|
+
*/
|
|
34
|
+
scopePrefix?: string;
|
|
25
35
|
}
|
|
26
36
|
/** Options specific to the Deck adapter. */
|
|
27
37
|
interface DeckAdapterOptions extends AdapterOptions {
|
package/dist/adapters/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
parseColor,
|
|
14
14
|
resolveThemeFonts,
|
|
15
15
|
sectionComment
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-OJVNL7KN.js";
|
|
17
17
|
|
|
18
18
|
// src/adapters/layers.ts
|
|
19
19
|
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
|
@@ -32,6 +32,7 @@ function toKebabCase(name) {
|
|
|
32
32
|
function nextjsAdapter(input, options) {
|
|
33
33
|
const includeFontImports = options?.includeFontImports ?? true;
|
|
34
34
|
const includeFowt = options?.includeFowt ?? true;
|
|
35
|
+
const scopePrefix = options?.scopePrefix;
|
|
35
36
|
const lines = [];
|
|
36
37
|
const slug = toKebabCase(input.config.name);
|
|
37
38
|
const aliasedFamilies = /* @__PURE__ */ new Map();
|
|
@@ -89,12 +90,15 @@ function nextjsAdapter(input, options) {
|
|
|
89
90
|
lines.push(LAYER_ORDER);
|
|
90
91
|
lines.push("");
|
|
91
92
|
const primitivesBody = stripHeader(
|
|
92
|
-
generatePrimitivesCss(input.primitives, input.config, {
|
|
93
|
+
generatePrimitivesCss(input.primitives, input.config, {
|
|
94
|
+
aliasedFamilies,
|
|
95
|
+
scopePrefix
|
|
96
|
+
})
|
|
93
97
|
);
|
|
94
98
|
lines.push(wrapInLayer("visor-primitives", primitivesBody));
|
|
95
99
|
lines.push("");
|
|
96
|
-
const lightBody = stripHeader(generateLightCss(input.tokens));
|
|
97
|
-
const darkBody = stripHeader(generateDarkCss(input.tokens));
|
|
100
|
+
const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix }));
|
|
101
|
+
const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix }));
|
|
98
102
|
lines.push(
|
|
99
103
|
wrapInLayer("visor-adaptive", lightBody + "\n\n" + darkBody)
|
|
100
104
|
);
|
|
@@ -122,9 +122,18 @@ var FONT_WEIGHT_ALIASES = {
|
|
|
122
122
|
400: "Book",
|
|
123
123
|
800: "Super"
|
|
124
124
|
},
|
|
125
|
+
"PP Model Sans": {
|
|
126
|
+
400: "Book",
|
|
127
|
+
800: "Super"
|
|
128
|
+
},
|
|
125
129
|
"PP Model Plastic": {
|
|
126
130
|
400: "Book",
|
|
127
131
|
800: "Super"
|
|
132
|
+
},
|
|
133
|
+
// Hoefler's Gotham uses "Book" instead of "Regular" at weight 400.
|
|
134
|
+
// Light (300) and Medium (500) match WEIGHT_NAMES defaults.
|
|
135
|
+
Gotham: {
|
|
136
|
+
400: "Book"
|
|
128
137
|
}
|
|
129
138
|
};
|
|
130
139
|
function lookupFontWeightAlias(family, weight) {
|
|
@@ -623,13 +632,29 @@ function resolveThemeFonts(typography, options) {
|
|
|
623
632
|
}
|
|
624
633
|
let monoResolution = null;
|
|
625
634
|
if (typography.mono?.family) {
|
|
626
|
-
const monoWeights = [];
|
|
627
|
-
|
|
635
|
+
const monoWeights = typography.mono.weights ? [...typography.mono.weights] : typography.mono.weight ? [typography.mono.weight] : [];
|
|
636
|
+
let monoSource = typography.mono.source;
|
|
637
|
+
let monoOrg = typography.mono.org;
|
|
638
|
+
if (!monoSource) {
|
|
639
|
+
const monoFamilyLower = typography.mono.family.toLowerCase();
|
|
640
|
+
const candidates = [
|
|
641
|
+
{ resolution: headingResolution, configSource: typography.heading?.source, configOrg: typography.heading?.org },
|
|
642
|
+
{ resolution: displayResolution, configSource: typography.display?.source, configOrg: typography.display?.org },
|
|
643
|
+
{ resolution: bodyResolution, configSource: typography.body?.source, configOrg: typography.body?.org }
|
|
644
|
+
];
|
|
645
|
+
for (const candidate of candidates) {
|
|
646
|
+
if (candidate.resolution && candidate.configSource && candidate.resolution.family.toLowerCase() === monoFamilyLower) {
|
|
647
|
+
monoSource = candidate.configSource;
|
|
648
|
+
monoOrg = candidate.configOrg;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
628
653
|
monoResolution = resolveFont(typography.mono.family, {
|
|
629
654
|
weights: monoWeights.length > 0 ? monoWeights : void 0,
|
|
630
655
|
display,
|
|
631
|
-
source:
|
|
632
|
-
org:
|
|
656
|
+
source: monoSource,
|
|
657
|
+
org: monoOrg,
|
|
633
658
|
category: "monospace"
|
|
634
659
|
});
|
|
635
660
|
if (monoResolution.guidance) {
|
|
@@ -1299,22 +1324,23 @@ function generateMiscPrimitives() {
|
|
|
1299
1324
|
}
|
|
1300
1325
|
function generatePrimitivesCss(primitives, config, options) {
|
|
1301
1326
|
const lines = [];
|
|
1327
|
+
const host = options?.scopePrefix ?? ":root";
|
|
1302
1328
|
lines.push(sectionComment("Primitive: Colors"));
|
|
1303
1329
|
lines.push(
|
|
1304
|
-
block(
|
|
1330
|
+
block(host, [generateColorPrimitives(primitives)])
|
|
1305
1331
|
);
|
|
1306
1332
|
lines.push(sectionComment("Primitive: Spacing"));
|
|
1307
|
-
lines.push(block(
|
|
1333
|
+
lines.push(block(host, generateSpacingPrimitives(config)));
|
|
1308
1334
|
lines.push(sectionComment("Primitive: Border Radius"));
|
|
1309
|
-
lines.push(block(
|
|
1335
|
+
lines.push(block(host, generateRadiusPrimitives(config)));
|
|
1310
1336
|
lines.push(sectionComment("Primitive: Typography"));
|
|
1311
|
-
lines.push(block(
|
|
1337
|
+
lines.push(block(host, generateTypographyPrimitives(config, options?.aliasedFamilies)));
|
|
1312
1338
|
lines.push(sectionComment("Primitive: Shadows"));
|
|
1313
|
-
lines.push(block(
|
|
1339
|
+
lines.push(block(host, generateShadowPrimitives(config)));
|
|
1314
1340
|
lines.push(sectionComment("Primitive: Motion"));
|
|
1315
|
-
lines.push(block(
|
|
1341
|
+
lines.push(block(host, generateMotionPrimitives(config)));
|
|
1316
1342
|
lines.push(sectionComment("Primitive: Miscellaneous"));
|
|
1317
|
-
lines.push(block(
|
|
1343
|
+
lines.push(block(host, generateMiscPrimitives()));
|
|
1318
1344
|
return header("Visor Theme \u2014 Primitives") + lines.join("\n");
|
|
1319
1345
|
}
|
|
1320
1346
|
function generateSemanticCss(tokens) {
|
|
@@ -1356,25 +1382,27 @@ function buildAdaptiveDecls(tokens, theme) {
|
|
|
1356
1382
|
);
|
|
1357
1383
|
return { textDecls, surfaceDecls, borderDecls, interactiveDecls };
|
|
1358
1384
|
}
|
|
1359
|
-
function generateLightCss(tokens) {
|
|
1385
|
+
function generateLightCss(tokens, options) {
|
|
1360
1386
|
const lines = [];
|
|
1361
1387
|
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1388
|
+
const host = options?.scopePrefix ?? ":root";
|
|
1362
1389
|
lines.push(sectionComment("Adaptive: Text (light)"));
|
|
1363
|
-
lines.push(block(
|
|
1390
|
+
lines.push(block(host, textDecls));
|
|
1364
1391
|
lines.push(sectionComment("Adaptive: Surface (light)"));
|
|
1365
|
-
lines.push(block(
|
|
1392
|
+
lines.push(block(host, surfaceDecls));
|
|
1366
1393
|
lines.push(sectionComment("Adaptive: Border (light)"));
|
|
1367
|
-
lines.push(block(
|
|
1394
|
+
lines.push(block(host, borderDecls));
|
|
1368
1395
|
lines.push(sectionComment("Adaptive: Interactive (light)"));
|
|
1369
|
-
lines.push(block(
|
|
1396
|
+
lines.push(block(host, interactiveDecls));
|
|
1370
1397
|
return header("Visor Theme \u2014 Light") + lines.join("\n");
|
|
1371
1398
|
}
|
|
1372
|
-
function generateDarkCss(tokens) {
|
|
1399
|
+
function generateDarkCss(tokens, options) {
|
|
1373
1400
|
const lines = [];
|
|
1374
1401
|
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "dark");
|
|
1375
|
-
const
|
|
1402
|
+
const prefix = options?.scopePrefix;
|
|
1403
|
+
const darkSelectors = prefix ? [`${prefix}.dark`, `${prefix}.theme-dark`, `${prefix}[data-theme="dark"]`] : [".dark", ".theme-dark", '[data-theme="dark"]'];
|
|
1376
1404
|
const darkSelector = darkSelectors.join(",\n");
|
|
1377
|
-
const prefersSelector = ':root:not(.light):not(.theme-light):not([data-theme="light"])';
|
|
1405
|
+
const prefersSelector = prefix ? `${prefix}:not(.light):not(.theme-light):not([data-theme="light"])` : ':root:not(.light):not(.theme-light):not([data-theme="light"])';
|
|
1378
1406
|
lines.push(sectionComment("Adaptive: Text (dark) \u2014 manual toggle"));
|
|
1379
1407
|
lines.push(block(darkSelector, textDecls));
|
|
1380
1408
|
lines.push(sectionComment("Adaptive: Surface (dark) \u2014 manual toggle"));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-
|
|
2
|
-
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-
|
|
1
|
+
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, R as ResolvedThemeConfig, c as GeneratedPrimitives, d as ThemeOutput, e as ThemeData, f as VisorThemeConfig, g as FullShadeScale, C as ColorRole, S as SelectiveShadeScale, h as RGB, P as ParsedColor, O as OKLCH, i as SemanticTokens, j as ShadeStep } from './types-CV0nmvMz.js';
|
|
2
|
+
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-CV0nmvMz.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -128,6 +128,16 @@ interface FontCoverageError {
|
|
|
128
128
|
interface FontCoverageResult {
|
|
129
129
|
errors: FontCoverageError[];
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Format a font coverage error for surfacing through the CLI / private-themes
|
|
133
|
+
* generator. The mono slot gets an additional sentence calling out the engine
|
|
134
|
+
* version requirement and the CLI/engine version coupling (VI-367 / BO-37):
|
|
135
|
+
* bumping the engine alone is silently insufficient because the visor CLI
|
|
136
|
+
* transitively pins its own engine copy.
|
|
137
|
+
*
|
|
138
|
+
* Filename is included so multi-theme runs surface which theme is failing.
|
|
139
|
+
*/
|
|
140
|
+
declare function formatFontCoverageError(filename: string, declaredAt: string, family: string): string;
|
|
131
141
|
declare function validateFontCoverage(css: string): FontCoverageResult;
|
|
132
142
|
|
|
133
143
|
/**
|
|
@@ -901,10 +911,15 @@ type AliasedFamilies = ReadonlyMap<string, string>;
|
|
|
901
911
|
|
|
902
912
|
declare function generatePrimitivesCss(primitives: GeneratedPrimitives, config: ResolvedThemeConfig, options?: {
|
|
903
913
|
aliasedFamilies?: AliasedFamilies;
|
|
914
|
+
scopePrefix?: string;
|
|
904
915
|
}): string;
|
|
905
916
|
declare function generateSemanticCss(tokens: SemanticTokens): string;
|
|
906
|
-
declare function generateLightCss(tokens: SemanticTokens
|
|
907
|
-
|
|
917
|
+
declare function generateLightCss(tokens: SemanticTokens, options?: {
|
|
918
|
+
scopePrefix?: string;
|
|
919
|
+
}): string;
|
|
920
|
+
declare function generateDarkCss(tokens: SemanticTokens, options?: {
|
|
921
|
+
scopePrefix?: string;
|
|
922
|
+
}): string;
|
|
908
923
|
declare function generateFullBundleCss(primitives: GeneratedPrimitives, tokens: SemanticTokens, config: ResolvedThemeConfig): string;
|
|
909
924
|
|
|
910
925
|
/**
|
|
@@ -982,4 +997,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
982
997
|
*/
|
|
983
998
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
984
999
|
|
|
985
|
-
export { type CSSFile, ColorRole, type Confidence, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, ShadeStep, TAILWIND_GRAY, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_FONTS_CDN, type ValidationIssue, type ValidationSeverity, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorFontUrl, clampToSrgb, cleanFontValue, compositeOverBackground, exportTheme, extractFromCSS, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, googleFontsCatalog, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveConfig, resolveFont, resolveThemeFonts, rgbToHex, serializeColor, validate, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
|
1000
|
+
export { type CSSFile, ColorRole, type Confidence, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, ShadeStep, TAILWIND_GRAY, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_FONTS_CDN, type ValidationIssue, type ValidationSeverity, VisorThemeConfig, VisorTypography, applyOverrides, assignSemanticTokens, buildVisorFontUrl, clampToSrgb, cleanFontValue, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, googleFontsCatalog, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveConfig, resolveFont, resolveThemeFonts, rgbToHex, serializeColor, validate, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
rgbToHex,
|
|
35
35
|
rgbToOklch,
|
|
36
36
|
serializeColor
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-OJVNL7KN.js";
|
|
38
38
|
|
|
39
39
|
// src/fonts/validate-coverage.ts
|
|
40
40
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -166,6 +166,13 @@ function extractFontVarDeclarations(css) {
|
|
|
166
166
|
}
|
|
167
167
|
return decls;
|
|
168
168
|
}
|
|
169
|
+
function formatFontCoverageError(filename, declaredAt, family) {
|
|
170
|
+
const base = `${filename}: ${declaredAt} declares "${family}" with no matching @font-face. `;
|
|
171
|
+
if (declaredAt === "--font-mono") {
|
|
172
|
+
return base + `Set typography.mono.source: visor-fonts (with org:), google-fonts, or fontshare; or pick a system mono font. The mono slot's source/org keys require @loworbitstudio/visor-theme-engine \u2265 0.5.0 and @loworbitstudio/visor \u2265 0.10.0 \u2014 bump both, since the CLI bundles its own engine copy.`;
|
|
173
|
+
}
|
|
174
|
+
return base + `Set typography.<slot>.source: visor-fonts (with org:), google-fonts, or fontshare; or pick a system font.`;
|
|
175
|
+
}
|
|
169
176
|
function validateFontCoverage(css) {
|
|
170
177
|
const declaredFamilies = extractFontFaceFamilies(css);
|
|
171
178
|
for (const f of extractGoogleFontsImports(css)) declaredFamilies.add(f);
|
|
@@ -910,6 +917,7 @@ function resolveConfig(config) {
|
|
|
910
917
|
}
|
|
911
918
|
return {
|
|
912
919
|
name: config.name,
|
|
920
|
+
...config.label !== void 0 && { label: config.label },
|
|
913
921
|
version: 1,
|
|
914
922
|
colors: {
|
|
915
923
|
primary: colors.primary,
|
|
@@ -1616,6 +1624,41 @@ var KNOWN_SEMANTIC_TOKENS = /* @__PURE__ */ new Set([
|
|
|
1616
1624
|
...Object.keys(SEMANTIC_BORDER_MAP).map((k) => `border-${k}`),
|
|
1617
1625
|
...Object.keys(SEMANTIC_INTERACTIVE_MAP).map((k) => `interactive-${k}`)
|
|
1618
1626
|
]);
|
|
1627
|
+
var REQUIRED_OVERRIDE_TOKENS = {
|
|
1628
|
+
text: ["primary", "secondary", "tertiary", "disabled"],
|
|
1629
|
+
surface: [
|
|
1630
|
+
"page",
|
|
1631
|
+
"card",
|
|
1632
|
+
"popover",
|
|
1633
|
+
"subtle",
|
|
1634
|
+
"muted",
|
|
1635
|
+
"interactive-default",
|
|
1636
|
+
"interactive-hover",
|
|
1637
|
+
"interactive-active",
|
|
1638
|
+
"interactive-disabled",
|
|
1639
|
+
"selected",
|
|
1640
|
+
"accent-subtle",
|
|
1641
|
+
"success-subtle",
|
|
1642
|
+
"warning-subtle",
|
|
1643
|
+
"error-subtle",
|
|
1644
|
+
"info-subtle",
|
|
1645
|
+
"elev-0",
|
|
1646
|
+
"elev-1",
|
|
1647
|
+
"elev-2",
|
|
1648
|
+
"elev-3",
|
|
1649
|
+
"elev-4"
|
|
1650
|
+
],
|
|
1651
|
+
border: ["default", "muted", "strong", "disabled"],
|
|
1652
|
+
interactive: [
|
|
1653
|
+
"secondary-bg",
|
|
1654
|
+
"secondary-bg-hover",
|
|
1655
|
+
"secondary-bg-active",
|
|
1656
|
+
"secondary-text",
|
|
1657
|
+
"secondary-border",
|
|
1658
|
+
"ghost-bg",
|
|
1659
|
+
"ghost-bg-hover"
|
|
1660
|
+
]
|
|
1661
|
+
};
|
|
1619
1662
|
function issue(severity, code, message, path) {
|
|
1620
1663
|
const result = { severity, code, message };
|
|
1621
1664
|
if (path !== void 0) {
|
|
@@ -1958,6 +2001,30 @@ function checkOverrides(config, issues) {
|
|
|
1958
2001
|
}
|
|
1959
2002
|
}
|
|
1960
2003
|
}
|
|
2004
|
+
function checkOverrideCompleteness(config, issues) {
|
|
2005
|
+
const lightOverrides = config.overrides?.light;
|
|
2006
|
+
if (!lightOverrides) return;
|
|
2007
|
+
const presentKeys = Object.keys(lightOverrides);
|
|
2008
|
+
if (presentKeys.length === 0) return;
|
|
2009
|
+
const hasTriggerKey = "surface-page" in lightOverrides || "surface-card" in lightOverrides;
|
|
2010
|
+
if (!hasTriggerKey) return;
|
|
2011
|
+
const present = new Set(presentKeys);
|
|
2012
|
+
for (const [family, tokens] of Object.entries(REQUIRED_OVERRIDE_TOKENS)) {
|
|
2013
|
+
for (const token of tokens) {
|
|
2014
|
+
const fullKey = `${family}-${token}`;
|
|
2015
|
+
if (!present.has(fullKey)) {
|
|
2016
|
+
issues.push(
|
|
2017
|
+
issue(
|
|
2018
|
+
"warning",
|
|
2019
|
+
"INCOMPLETE_OVERRIDE",
|
|
2020
|
+
`'overrides.light' inverts the light-mode surface (overrides 'surface-page' or 'surface-card') but is missing '${fullKey}'. The engine's light-mode default for this token may leak (bright/light-natural values on inverted always-dark themes like Blackout).`,
|
|
2021
|
+
`overrides.light.${fullKey}`
|
|
2022
|
+
)
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1961
2028
|
function checkResolvedCompleteness(resolved, issues) {
|
|
1962
2029
|
const requiredColors = [
|
|
1963
2030
|
"primary",
|
|
@@ -2274,6 +2341,7 @@ function validate(config) {
|
|
|
2274
2341
|
for (const iss of overrideIssues) {
|
|
2275
2342
|
(iss.severity === "error" ? errors : warnings).push(iss);
|
|
2276
2343
|
}
|
|
2344
|
+
checkOverrideCompleteness(typedConfig, warnings);
|
|
2277
2345
|
if (errors.length === 0) {
|
|
2278
2346
|
const resolved = resolveConfig(typedConfig);
|
|
2279
2347
|
checkResolvedCompleteness(resolved, errors);
|
|
@@ -2791,6 +2859,7 @@ export {
|
|
|
2791
2859
|
compositeOverBackground,
|
|
2792
2860
|
exportTheme,
|
|
2793
2861
|
extractFromCSS,
|
|
2862
|
+
formatFontCoverageError,
|
|
2794
2863
|
generateDarkCss,
|
|
2795
2864
|
generateFullBundleCss,
|
|
2796
2865
|
generateLightCss,
|
|
@@ -70,6 +70,8 @@ interface VisorTypography {
|
|
|
70
70
|
mono?: {
|
|
71
71
|
family: string;
|
|
72
72
|
weight?: number;
|
|
73
|
+
/** Explicit list of font weights to load (overrides engine defaults) */
|
|
74
|
+
weights?: number[];
|
|
73
75
|
source?: FontSource;
|
|
74
76
|
org?: string;
|
|
75
77
|
};
|
|
@@ -267,6 +269,8 @@ interface VisorThemeConfig {
|
|
|
267
269
|
/** Config with all defaults resolved */
|
|
268
270
|
interface ResolvedThemeConfig {
|
|
269
271
|
name: string;
|
|
272
|
+
/** Optional display label override forwarded from VisorThemeConfig.label. */
|
|
273
|
+
label?: string;
|
|
270
274
|
version: 1;
|
|
271
275
|
colors: {
|
|
272
276
|
primary: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|