@loworbitstudio/visor-theme-engine 0.19.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.
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +33 -27
- package/dist/{chunk-32DC5DAR.js → chunk-LY5LXQYE.js} +204 -21
- package/dist/index.d.ts +214 -4
- package/dist/index.js +63 -2
- package/dist/{types-DF5StphJ.d.ts → types-BG-YT4JW.d.ts} +12 -0
- package/package.json +1 -1
- package/src/visor-theme.schema.json +1 -1
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-
|
|
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
|
}
|
package/dist/adapters/index.js
CHANGED
|
@@ -6,12 +6,16 @@ import {
|
|
|
6
6
|
block,
|
|
7
7
|
buildVisorFontUrl,
|
|
8
8
|
collectBrandPassthrough,
|
|
9
|
+
collectDeclaredProperties,
|
|
9
10
|
fontStack,
|
|
10
11
|
generateDarkCss,
|
|
12
|
+
generateFontSizeDecls,
|
|
13
|
+
generateFontWeightDecls,
|
|
11
14
|
generateHairlineDecls,
|
|
12
15
|
generateIntentDecls,
|
|
13
16
|
generateLightCss,
|
|
14
17
|
generatePrimitivesCss,
|
|
18
|
+
generateSemanticAliasDecls,
|
|
15
19
|
generateShadeScale,
|
|
16
20
|
generateSpaceAliasDecls,
|
|
17
21
|
generateTextScaleAliasDecls,
|
|
@@ -21,7 +25,7 @@ import {
|
|
|
21
25
|
resolveThemeBrand,
|
|
22
26
|
resolveThemeFonts,
|
|
23
27
|
sectionComment
|
|
24
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-LY5LXQYE.js";
|
|
25
29
|
|
|
26
30
|
// src/adapters/brand-passthrough.ts
|
|
27
31
|
var SENTINEL_COLOR = "#ff00ff";
|
|
@@ -212,7 +216,11 @@ function nextjsAdapter(input, options) {
|
|
|
212
216
|
baseLines.push(
|
|
213
217
|
block(scopePrefix ?? "body", [
|
|
214
218
|
"font-family: var(--font-body);",
|
|
215
|
-
|
|
219
|
+
// VI-638: the scaled ramp is the single mechanism that sets type size,
|
|
220
|
+
// and the base step is what the page inherits. This binding lives on
|
|
221
|
+
// `body` (or the body-class scope), never `:root` — the ramp is
|
|
222
|
+
// expressed in `rem`, so a scaled root would multiply it a second time.
|
|
223
|
+
"font-size: var(--font-size-base, 1rem);",
|
|
216
224
|
"color: var(--text-primary);",
|
|
217
225
|
"background: var(--surface-page, var(--surface-background));"
|
|
218
226
|
])
|
|
@@ -295,14 +303,27 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
|
295
303
|
}
|
|
296
304
|
semanticLines.push("");
|
|
297
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
|
+
}
|
|
298
322
|
const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
|
|
299
323
|
if (semanticLayer) {
|
|
300
324
|
lines.push(semanticLayer);
|
|
301
325
|
lines.push("");
|
|
302
326
|
}
|
|
303
|
-
const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix, colorScheme }));
|
|
304
|
-
const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
|
|
305
|
-
const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
|
|
306
327
|
lines.push(
|
|
307
328
|
wrapInLayer("visor-adaptive", adaptiveBody)
|
|
308
329
|
);
|
|
@@ -537,23 +558,8 @@ function generateTypographyDecls(config, aliases) {
|
|
|
537
558
|
decls.push(`--font-heading: ${fontStack(headingFamily, aliases)};`);
|
|
538
559
|
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
539
560
|
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
sm: 14,
|
|
543
|
-
base: 16,
|
|
544
|
-
lg: 18,
|
|
545
|
-
xl: 20,
|
|
546
|
-
"2xl": 24,
|
|
547
|
-
"3xl": 30,
|
|
548
|
-
"4xl": 36
|
|
549
|
-
};
|
|
550
|
-
for (const [name, px] of Object.entries(fontSizes)) {
|
|
551
|
-
decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
|
|
552
|
-
}
|
|
553
|
-
decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
|
|
554
|
-
decls.push("--font-weight-medium: 500;");
|
|
555
|
-
decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
|
|
556
|
-
decls.push("--font-weight-bold: 700;");
|
|
561
|
+
decls.push(...generateFontSizeDecls(config.typography.scale));
|
|
562
|
+
decls.push(...generateFontWeightDecls(config.typography));
|
|
557
563
|
const lineHeights = {
|
|
558
564
|
none: 1,
|
|
559
565
|
tight: 1.25,
|
|
@@ -686,7 +692,6 @@ function docsAdapter(input, options) {
|
|
|
686
692
|
fontLines.push("");
|
|
687
693
|
}
|
|
688
694
|
}
|
|
689
|
-
const scale = input.config.typography?.scale ?? 1;
|
|
690
695
|
const emittedFamilies = /* @__PURE__ */ new Set();
|
|
691
696
|
for (const font of fontSlots) {
|
|
692
697
|
if (font && font.source === "visor-fonts" && !emittedFamilies.has(font.family)) {
|
|
@@ -700,9 +705,6 @@ function docsAdapter(input, options) {
|
|
|
700
705
|
fontLines.push(` font-weight: ${weight};`);
|
|
701
706
|
fontLines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
702
707
|
fontLines.push(` font-display: ${font.display};`);
|
|
703
|
-
if (scale !== 1) {
|
|
704
|
-
fontLines.push(` size-adjust: ${Math.round(scale * 100)}%;`);
|
|
705
|
-
}
|
|
706
708
|
fontLines.push("}");
|
|
707
709
|
fontLines.push("");
|
|
708
710
|
}
|
|
@@ -712,7 +714,11 @@ function docsAdapter(input, options) {
|
|
|
712
714
|
lines.push("\n/* \u2500\u2500 Section 1: Shared tokens (mode-independent) \u2500\u2500 */");
|
|
713
715
|
const sharedDecls = [
|
|
714
716
|
"min-height: 100vh;",
|
|
715
|
-
|
|
717
|
+
// VI-638: the scaled ramp is the single mechanism that sets type size, and
|
|
718
|
+
// the base step is what the page inherits. Safe here because the scope is a
|
|
719
|
+
// class on a wrapper, never `:root` — a scaled root would multiply the
|
|
720
|
+
// rem-expressed ramp a second time.
|
|
721
|
+
"font-size: var(--font-size-base, 1rem);",
|
|
716
722
|
// BO-56: pin UA chrome to the brand's single mode (adaptive emits nothing).
|
|
717
723
|
...singleMode ? [`color-scheme: ${singleMode};`] : [],
|
|
718
724
|
`background: var(--surface-page, var(--surface-background));`,
|
|
@@ -1928,6 +1928,99 @@ function applyOverrides(tokens, overrides) {
|
|
|
1928
1928
|
return result;
|
|
1929
1929
|
}
|
|
1930
1930
|
|
|
1931
|
+
// src/font-weights.ts
|
|
1932
|
+
var WEIGHT_RAMP_TARGETS = {
|
|
1933
|
+
medium: 500,
|
|
1934
|
+
semibold: 600,
|
|
1935
|
+
bold: 700
|
|
1936
|
+
};
|
|
1937
|
+
var SLOTS = ["heading", "display", "body", "mono"];
|
|
1938
|
+
function loadedWeights(typography) {
|
|
1939
|
+
const set = /* @__PURE__ */ new Set();
|
|
1940
|
+
for (const slot of SLOTS) {
|
|
1941
|
+
const cfg = typography[slot];
|
|
1942
|
+
if (!cfg) continue;
|
|
1943
|
+
if (cfg.weights?.length) {
|
|
1944
|
+
for (const w of cfg.weights) set.add(w);
|
|
1945
|
+
} else if (typeof cfg.weight === "number") {
|
|
1946
|
+
set.add(cfg.weight);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return [...set].sort((a, b) => a - b);
|
|
1950
|
+
}
|
|
1951
|
+
function declaresWeights(typography) {
|
|
1952
|
+
return SLOTS.some((slot) => Boolean(typography[slot]?.weights?.length));
|
|
1953
|
+
}
|
|
1954
|
+
function matchLoadedWeight(desired, loaded) {
|
|
1955
|
+
if (loaded.length === 0) return desired;
|
|
1956
|
+
if (loaded.includes(desired)) return desired;
|
|
1957
|
+
const ascending = [...loaded].sort((a, b) => a - b);
|
|
1958
|
+
const lighter = ascending.filter((w) => w < desired).reverse();
|
|
1959
|
+
const heavier = ascending.filter((w) => w > desired);
|
|
1960
|
+
if (desired >= 400 && desired <= 500) {
|
|
1961
|
+
const upToFive = heavier.filter((w) => w <= 500);
|
|
1962
|
+
const aboveFive = heavier.filter((w) => w > 500);
|
|
1963
|
+
return upToFive[0] ?? lighter[0] ?? aboveFive[0];
|
|
1964
|
+
}
|
|
1965
|
+
if (desired < 400) {
|
|
1966
|
+
return lighter[0] ?? heavier[0];
|
|
1967
|
+
}
|
|
1968
|
+
return heavier[0] ?? lighter[0];
|
|
1969
|
+
}
|
|
1970
|
+
function resolveWeightRamp(typography) {
|
|
1971
|
+
const bodyWeight = typography.body?.weight ?? 400;
|
|
1972
|
+
if (!declaresWeights(typography)) {
|
|
1973
|
+
return {
|
|
1974
|
+
normal: bodyWeight,
|
|
1975
|
+
medium: WEIGHT_RAMP_TARGETS.medium,
|
|
1976
|
+
semibold: WEIGHT_RAMP_TARGETS.semibold,
|
|
1977
|
+
bold: WEIGHT_RAMP_TARGETS.bold
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
const loaded = loadedWeights(typography);
|
|
1981
|
+
return {
|
|
1982
|
+
normal: matchLoadedWeight(bodyWeight, loaded),
|
|
1983
|
+
medium: matchLoadedWeight(WEIGHT_RAMP_TARGETS.medium, loaded),
|
|
1984
|
+
semibold: matchLoadedWeight(WEIGHT_RAMP_TARGETS.semibold, loaded),
|
|
1985
|
+
bold: matchLoadedWeight(WEIGHT_RAMP_TARGETS.bold, loaded)
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
function resolveRoleWeight(declared, typography) {
|
|
1989
|
+
if (!declaresWeights(typography)) return declared;
|
|
1990
|
+
return matchLoadedWeight(declared, loadedWeights(typography));
|
|
1991
|
+
}
|
|
1992
|
+
function generateFontWeightDecls(typography) {
|
|
1993
|
+
const ramp = resolveWeightRamp(typography);
|
|
1994
|
+
const decls = [
|
|
1995
|
+
`--font-weight-normal: ${ramp.normal};`,
|
|
1996
|
+
`--font-weight-medium: ${ramp.medium};`,
|
|
1997
|
+
`--font-weight-semibold: ${ramp.semibold};`,
|
|
1998
|
+
`--font-weight-bold: ${ramp.bold};`,
|
|
1999
|
+
// Role primitives. These are why `semibold` was pinned in the first place:
|
|
2000
|
+
// packages/tokens defined `--weight-heading: var(--font-weight-semibold)`
|
|
2001
|
+
// and the engine emitted no per-theme value, so hijacking `semibold` was
|
|
2002
|
+
// the only channel a theme's heading weight had. Each role has its own
|
|
2003
|
+
// primitive now, and the semantic `--weight-*` roles resolve through them.
|
|
2004
|
+
//
|
|
2005
|
+
// Emitted as `--font-weight-*` primitives rather than the bare `--weight-*`
|
|
2006
|
+
// names on purpose: the semantic names are defined in @layer visor-semantic,
|
|
2007
|
+
// which wins over visor-primitives, so a theme writing them directly would
|
|
2008
|
+
// be overridden by the tokens package on a `:root`-scoped consumer.
|
|
2009
|
+
`--font-weight-heading: ${resolveRoleWeight(typography.heading.weight, typography)};`,
|
|
2010
|
+
`--font-weight-body: ${resolveRoleWeight(typography.body.weight, typography)};`,
|
|
2011
|
+
`--font-weight-display: ${resolveRoleWeight(typography.display.weight, typography)};`,
|
|
2012
|
+
// Retained: `--weight-display` has no competing definition in the tokens
|
|
2013
|
+
// package and predates this change, so consumers already reference it.
|
|
2014
|
+
`--weight-display: ${resolveRoleWeight(typography.display.weight, typography)};`
|
|
2015
|
+
];
|
|
2016
|
+
if (declaresWeights(typography)) {
|
|
2017
|
+
for (const weight of loadedWeights(typography)) {
|
|
2018
|
+
decls.push(`--font-weight-${weight}: ${weight};`);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return decls;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1931
2024
|
// src/fonts/theme-alias.ts
|
|
1932
2025
|
var EMPTY_ALIASES = /* @__PURE__ */ new Map();
|
|
1933
2026
|
function aliasFamily(family, themeSlug) {
|
|
@@ -2024,33 +2117,32 @@ function generateShadowPrimitives(config) {
|
|
|
2024
2117
|
`--shadow-xl: ${config.shadows.xl};`
|
|
2025
2118
|
];
|
|
2026
2119
|
}
|
|
2120
|
+
var FONT_SIZE_RAMP_PX = {
|
|
2121
|
+
xs: 12,
|
|
2122
|
+
sm: 14,
|
|
2123
|
+
base: 16,
|
|
2124
|
+
lg: 18,
|
|
2125
|
+
xl: 20,
|
|
2126
|
+
"2xl": 24,
|
|
2127
|
+
"3xl": 30,
|
|
2128
|
+
"4xl": 36
|
|
2129
|
+
};
|
|
2130
|
+
function generateFontSizeDecls(scale) {
|
|
2131
|
+
return Object.entries(FONT_SIZE_RAMP_PX).map(([name, px]) => {
|
|
2132
|
+
const scaled = px * scale;
|
|
2133
|
+
const rem = Number((scaled / 16).toFixed(5));
|
|
2134
|
+
return `--font-size-${name}: ${rem}rem; /* ${Number(scaled.toFixed(2))}px */`;
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2027
2137
|
function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
|
|
2028
2138
|
const decls = [];
|
|
2029
|
-
const scale = config.typography.scale;
|
|
2030
|
-
decls.push(`font-size: ${scale === 1 ? "1rem" : `${scale}rem`};`);
|
|
2031
2139
|
decls.push(`--font-heading: ${fontStack(config.typography.heading.family, aliases)};`);
|
|
2032
2140
|
decls.push(`--font-display: ${fontStack(config.typography.display.family, aliases)};`);
|
|
2033
2141
|
decls.push(`--font-sans: ${fontStack(config.typography.body.family, aliases)};`);
|
|
2034
2142
|
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
2035
2143
|
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
sm: 14,
|
|
2039
|
-
base: 16,
|
|
2040
|
-
lg: 18,
|
|
2041
|
-
xl: 20,
|
|
2042
|
-
"2xl": 24,
|
|
2043
|
-
"3xl": 30,
|
|
2044
|
-
"4xl": 36
|
|
2045
|
-
};
|
|
2046
|
-
for (const [name, px] of Object.entries(fontSizes)) {
|
|
2047
|
-
decls.push(`--font-size-${name}: ${px / 16}rem; /* ${px}px */`);
|
|
2048
|
-
}
|
|
2049
|
-
decls.push(`--font-weight-normal: ${config.typography.body.weight};`);
|
|
2050
|
-
decls.push("--font-weight-medium: 500;");
|
|
2051
|
-
decls.push(`--font-weight-semibold: ${config.typography.heading.weight};`);
|
|
2052
|
-
decls.push("--font-weight-bold: 700;");
|
|
2053
|
-
decls.push(`--weight-display: ${config.typography.display.weight};`);
|
|
2144
|
+
decls.push(...generateFontSizeDecls(config.typography.scale));
|
|
2145
|
+
decls.push(...generateFontWeightDecls(config.typography));
|
|
2054
2146
|
const lineHeights = {
|
|
2055
2147
|
none: 1,
|
|
2056
2148
|
tight: 1.25,
|
|
@@ -2392,6 +2484,86 @@ function generateFullBundleCss(primitives, tokens, config) {
|
|
|
2392
2484
|
return lines.join("\n");
|
|
2393
2485
|
}
|
|
2394
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
|
+
|
|
2395
2567
|
// src/types.ts
|
|
2396
2568
|
var MATERIAL_TEXT_SLOTS = [
|
|
2397
2569
|
"displayLarge",
|
|
@@ -2464,11 +2636,19 @@ export {
|
|
|
2464
2636
|
collectBrandPassthrough,
|
|
2465
2637
|
hasBrandPassthrough,
|
|
2466
2638
|
applyOverrides,
|
|
2639
|
+
WEIGHT_RAMP_TARGETS,
|
|
2640
|
+
loadedWeights,
|
|
2641
|
+
declaresWeights,
|
|
2642
|
+
matchLoadedWeight,
|
|
2643
|
+
resolveWeightRamp,
|
|
2644
|
+
resolveRoleWeight,
|
|
2645
|
+
generateFontWeightDecls,
|
|
2467
2646
|
aliasFamily,
|
|
2468
2647
|
fontStack,
|
|
2469
2648
|
header,
|
|
2470
2649
|
sectionComment,
|
|
2471
2650
|
block2 as block,
|
|
2651
|
+
generateFontSizeDecls,
|
|
2472
2652
|
generatePrimitivesCss,
|
|
2473
2653
|
generateSemanticCss,
|
|
2474
2654
|
generateTextScaleAliasDecls,
|
|
@@ -2477,5 +2657,8 @@ export {
|
|
|
2477
2657
|
generateHairlineDecls,
|
|
2478
2658
|
generateLightCss,
|
|
2479
2659
|
generateDarkCss,
|
|
2480
|
-
generateFullBundleCss
|
|
2660
|
+
generateFullBundleCss,
|
|
2661
|
+
VISOR_CORE_SEMANTIC_ALIASES,
|
|
2662
|
+
collectDeclaredProperties,
|
|
2663
|
+
generateSemanticAliasDecls
|
|
2481
2664
|
};
|
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, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-
|
|
2
|
-
export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-
|
|
1
|
+
import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-BG-YT4JW.js';
|
|
2
|
+
export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-BG-YT4JW.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -585,7 +585,7 @@ var properties = {
|
|
|
585
585
|
},
|
|
586
586
|
scale: {
|
|
587
587
|
type: "number",
|
|
588
|
-
description: "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
588
|
+
description: "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only — the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
589
589
|
},
|
|
590
590
|
"text-on-light": {
|
|
591
591
|
type: "string",
|
|
@@ -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
|
*
|
|
@@ -1922,6 +2003,135 @@ declare function generateDarkCss(tokens: SemanticTokens, options?: {
|
|
|
1922
2003
|
}): string;
|
|
1923
2004
|
declare function generateFullBundleCss(primitives: GeneratedPrimitives, tokens: SemanticTokens, config: ResolvedThemeConfig): string;
|
|
1924
2005
|
|
|
2006
|
+
/**
|
|
2007
|
+
* Font-weight resolution (VI-639).
|
|
2008
|
+
*
|
|
2009
|
+
* The named weight ramp used to be three literals and one role leak:
|
|
2010
|
+
*
|
|
2011
|
+
* ```
|
|
2012
|
+
* --font-weight-normal: <body.weight>
|
|
2013
|
+
* --font-weight-medium: 500 // literal
|
|
2014
|
+
* --font-weight-semibold: <heading.weight> // a different concept entirely
|
|
2015
|
+
* --font-weight-bold: 700 // literal
|
|
2016
|
+
* ```
|
|
2017
|
+
*
|
|
2018
|
+
* `semibold` is not a heading. Pinning it there collapsed it onto `medium` on
|
|
2019
|
+
* every theme whose heading weight is 500 (the Blacklight family), onto
|
|
2020
|
+
* `normal` where it is 400 (knowmentum), and inverted the ramp where it is
|
|
2021
|
+
* above `bold` (strata emitted `semibold: 800` over `bold: 700`). Meanwhile the
|
|
2022
|
+
* literals ignored the `weights` array a theme actually loaded, so a theme
|
|
2023
|
+
* declaring `[200, 400, 600, 900]` got tokens pointing at faces it never
|
|
2024
|
+
* fetched — which the browser then silently substituted.
|
|
2025
|
+
*
|
|
2026
|
+
* Resolution here follows the CSS font-matching algorithm's weight-selection
|
|
2027
|
+
* order (CSS Fonts 4 §5.2) rather than naive nearest-numeric. The browser is
|
|
2028
|
+
* already performing exactly this substitution at render time, so an emitted
|
|
2029
|
+
* token becomes the weight that was *already rendering* — which is what makes
|
|
2030
|
+
* this change visually neutral everywhere except where the token was wrong.
|
|
2031
|
+
*/
|
|
2032
|
+
/** Canonical targets for the named ramp above `normal`. */
|
|
2033
|
+
declare const WEIGHT_RAMP_TARGETS: {
|
|
2034
|
+
readonly medium: 500;
|
|
2035
|
+
readonly semibold: 600;
|
|
2036
|
+
readonly bold: 700;
|
|
2037
|
+
};
|
|
2038
|
+
/** The typography slots that can declare a family, weight and weights array. */
|
|
2039
|
+
declare const SLOTS: readonly ["heading", "display", "body", "mono"];
|
|
2040
|
+
type WeightedSlot = {
|
|
2041
|
+
weight?: number;
|
|
2042
|
+
weights?: number[];
|
|
2043
|
+
} | undefined;
|
|
2044
|
+
type SlottedTypography = Partial<Record<(typeof SLOTS)[number], WeightedSlot>>;
|
|
2045
|
+
/**
|
|
2046
|
+
* The weights a theme actually loads: each slot's explicit `weights` array, or
|
|
2047
|
+
* its single `weight` when it declares none — mirroring how the font pipeline
|
|
2048
|
+
* resolves the set it fetches.
|
|
2049
|
+
*/
|
|
2050
|
+
declare function loadedWeights(typography: SlottedTypography): number[];
|
|
2051
|
+
/**
|
|
2052
|
+
* Whether any slot declares an explicit `weights` array.
|
|
2053
|
+
*
|
|
2054
|
+
* Resolution is gated on this. A theme that declares one has told us what it
|
|
2055
|
+
* loads, so we can hold every emitted token to that set. A theme that hasn't
|
|
2056
|
+
* keeps the historical emission untouched — snapping against a set inferred
|
|
2057
|
+
* from three `weight` fields would move tokens on a claim the theme never made
|
|
2058
|
+
* (and says nothing at all about a system font stack, which has whatever the OS
|
|
2059
|
+
* has). `validateFontWeights` warns about those instead.
|
|
2060
|
+
*/
|
|
2061
|
+
declare function declaresWeights(typography: SlottedTypography): boolean;
|
|
2062
|
+
/**
|
|
2063
|
+
* Pick the weight a browser would actually use for `desired`, given the faces
|
|
2064
|
+
* the theme loaded — CSS Fonts 4 §5.2 weight-matching order:
|
|
2065
|
+
*
|
|
2066
|
+
* - exact match wins;
|
|
2067
|
+
* - desired 400–500: heavier faces up to 500 ascending, then lighter descending,
|
|
2068
|
+
* then heavier than 500 ascending;
|
|
2069
|
+
* - desired below 400: lighter descending, then heavier ascending;
|
|
2070
|
+
* - desired above 500: heavier ascending, then lighter descending.
|
|
2071
|
+
*
|
|
2072
|
+
* Returns `desired` unchanged when the theme loads nothing (nothing to match
|
|
2073
|
+
* against, and the system font has its own ideas).
|
|
2074
|
+
*/
|
|
2075
|
+
declare function matchLoadedWeight(desired: number, loaded: readonly number[]): number;
|
|
2076
|
+
/** The four named ramp steps a theme emits, resolved against what it loaded. */
|
|
2077
|
+
interface ResolvedWeightRamp {
|
|
2078
|
+
normal: number;
|
|
2079
|
+
medium: number;
|
|
2080
|
+
semibold: number;
|
|
2081
|
+
bold: number;
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Resolve the named ramp.
|
|
2085
|
+
*
|
|
2086
|
+
* `normal` tracks the theme's body weight rather than a hard 400 — it is the
|
|
2087
|
+
* ramp's zero point, and Blacklight's `body.weight: 300` is a deliberate
|
|
2088
|
+
* choice, not a rounding error. It is still coerced to a loaded face (strata
|
|
2089
|
+
* declares `body.weight: 450` and loads `[400, 700, 900]`).
|
|
2090
|
+
*
|
|
2091
|
+
* `medium / semibold / bold` resolve the canonical 500 / 600 / 700 targets.
|
|
2092
|
+
* Names can still share a value on a family that cannot separate them — two
|
|
2093
|
+
* loaded faces cannot back four distinct names — but the result is now the
|
|
2094
|
+
* truth about what renders rather than a number nothing will honour.
|
|
2095
|
+
*/
|
|
2096
|
+
declare function resolveWeightRamp(typography: SlottedTypography & {
|
|
2097
|
+
body?: WeightedSlot;
|
|
2098
|
+
}): ResolvedWeightRamp;
|
|
2099
|
+
/**
|
|
2100
|
+
* Resolve a role weight (`--weight-heading`, `--weight-body`,
|
|
2101
|
+
* `--weight-display`) to a loaded face. Roles carry theme intent — what this
|
|
2102
|
+
* theme's headings weigh — and are the channel components should read when they
|
|
2103
|
+
* mean "the heading weight" rather than "a step above medium".
|
|
2104
|
+
*/
|
|
2105
|
+
declare function resolveRoleWeight(declared: number, typography: SlottedTypography): number;
|
|
2106
|
+
/**
|
|
2107
|
+
* Emit the shared weight declarations: the named ramp, the role tokens, and the
|
|
2108
|
+
* discrete ladder.
|
|
2109
|
+
*
|
|
2110
|
+
* The ladder (`--font-weight-300` … `--font-weight-800`, one per loaded face)
|
|
2111
|
+
* is what makes the `weights` array authoritative instead of decorative. Before
|
|
2112
|
+
* it, a theme could load five faces and reach four of them by name at best —
|
|
2113
|
+
* Blacklight's 800 was unreachable from CSS, so BL-987 had to write a literal.
|
|
2114
|
+
* It follows the `--text-N` / `--space-N` discrete-alias precedent from VI-451.
|
|
2115
|
+
*/
|
|
2116
|
+
declare function generateFontWeightDecls(typography: {
|
|
2117
|
+
heading: {
|
|
2118
|
+
weight: number;
|
|
2119
|
+
weights?: number[];
|
|
2120
|
+
};
|
|
2121
|
+
display: {
|
|
2122
|
+
weight: number;
|
|
2123
|
+
weights?: number[];
|
|
2124
|
+
};
|
|
2125
|
+
body: {
|
|
2126
|
+
weight: number;
|
|
2127
|
+
weights?: number[];
|
|
2128
|
+
};
|
|
2129
|
+
mono: {
|
|
2130
|
+
weight?: number;
|
|
2131
|
+
weights?: number[];
|
|
2132
|
+
};
|
|
2133
|
+
}): string[];
|
|
2134
|
+
|
|
1925
2135
|
/**
|
|
1926
2136
|
* Theme Extraction Engine
|
|
1927
2137
|
*
|
|
@@ -1997,4 +2207,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
1997
2207
|
*/
|
|
1998
2208
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
1999
2209
|
|
|
2000
|
-
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, 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, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, lookupFontWeightAlias, lookupGoogleFont, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveThemeBrand, resolveThemeFonts, 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,21 +7,27 @@ 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,
|
|
13
|
+
WEIGHT_RAMP_TARGETS,
|
|
12
14
|
allComponentTokenNames,
|
|
13
15
|
applyOverrides,
|
|
14
16
|
buildVisorBrandUrl,
|
|
15
17
|
buildVisorFontUrl,
|
|
16
18
|
clampToSrgb,
|
|
17
19
|
collectBrandPassthrough,
|
|
20
|
+
collectDeclaredProperties,
|
|
18
21
|
componentTokenName,
|
|
19
22
|
compositeOverBackground,
|
|
23
|
+
declaresWeights,
|
|
20
24
|
generateDarkCss,
|
|
25
|
+
generateFontWeightDecls,
|
|
21
26
|
generateFullBundleCss,
|
|
22
27
|
generateLightCss,
|
|
23
28
|
generatePreloadLinks,
|
|
24
29
|
generatePrimitivesCss,
|
|
30
|
+
generateSemanticAliasDecls,
|
|
25
31
|
generateSemanticCss,
|
|
26
32
|
generateShadeScale,
|
|
27
33
|
generateStylesheetLinks,
|
|
@@ -34,8 +40,10 @@ import {
|
|
|
34
40
|
hexToRgb,
|
|
35
41
|
isValidColor,
|
|
36
42
|
isValidHex,
|
|
43
|
+
loadedWeights,
|
|
37
44
|
lookupFontWeightAlias,
|
|
38
45
|
lookupGoogleFont,
|
|
46
|
+
matchLoadedWeight,
|
|
39
47
|
normalizeHex,
|
|
40
48
|
oklchToHex,
|
|
41
49
|
parseColor,
|
|
@@ -47,13 +55,15 @@ import {
|
|
|
47
55
|
resolveBrandSource,
|
|
48
56
|
resolveComponentBindings,
|
|
49
57
|
resolveFont,
|
|
58
|
+
resolveRoleWeight,
|
|
50
59
|
resolveThemeBrand,
|
|
51
60
|
resolveThemeFonts,
|
|
61
|
+
resolveWeightRamp,
|
|
52
62
|
rgbToHex,
|
|
53
63
|
rgbToOklch,
|
|
54
64
|
serializeColor,
|
|
55
65
|
validateComponentBindings
|
|
56
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-LY5LXQYE.js";
|
|
57
67
|
|
|
58
68
|
// src/fonts/validate-coverage.ts
|
|
59
69
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -793,7 +803,7 @@ var visor_theme_schema_default = {
|
|
|
793
803
|
},
|
|
794
804
|
scale: {
|
|
795
805
|
type: "number",
|
|
796
|
-
description: "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
806
|
+
description: "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only \u2014 the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
797
807
|
},
|
|
798
808
|
"text-on-light": {
|
|
799
809
|
type: "string",
|
|
@@ -3137,6 +3147,46 @@ function checkTypeScaleCoherence(config, issues) {
|
|
|
3137
3147
|
}
|
|
3138
3148
|
}
|
|
3139
3149
|
}
|
|
3150
|
+
function checkFontWeightCoverage(config, issues) {
|
|
3151
|
+
const typography = config.typography;
|
|
3152
|
+
if (!typography) return;
|
|
3153
|
+
const HOSTED = /* @__PURE__ */ new Set(["visor-fonts", "google-fonts", "fontshare"]);
|
|
3154
|
+
const slots = ["heading", "display", "body", "mono"];
|
|
3155
|
+
if (!declaresWeights(typography)) {
|
|
3156
|
+
const hosted = slots.filter((slot) => {
|
|
3157
|
+
const source = typography[slot]?.source;
|
|
3158
|
+
return typeof source === "string" && HOSTED.has(source);
|
|
3159
|
+
});
|
|
3160
|
+
if (hosted.length > 0) {
|
|
3161
|
+
issues.push(
|
|
3162
|
+
issue(
|
|
3163
|
+
"warning",
|
|
3164
|
+
"FONT_WEIGHTS_UNDECLARED",
|
|
3165
|
+
`typography.${hosted[0]} loads from '${typography[hosted[0]].source}' but no slot declares a 'weights' array. Named weight tokens fall back to the canonical 500/600/700 literals, which may not be faces this theme fetched. Declare 'weights' to make them resolve against what you actually load.`,
|
|
3166
|
+
`typography.${hosted[0]}.weights`
|
|
3167
|
+
)
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
const loaded = loadedWeights(typography);
|
|
3173
|
+
const ramp = resolveWeightRamp(typography);
|
|
3174
|
+
const collapsed = [
|
|
3175
|
+
["medium", "normal"],
|
|
3176
|
+
["semibold", "medium"],
|
|
3177
|
+
["bold", "semibold"]
|
|
3178
|
+
].filter(([heavier, lighter]) => ramp[heavier] === ramp[lighter]).map(([heavier, lighter]) => `${heavier} = ${lighter} (${ramp[heavier]})`);
|
|
3179
|
+
if (collapsed.length > 0) {
|
|
3180
|
+
issues.push(
|
|
3181
|
+
issue(
|
|
3182
|
+
"warning",
|
|
3183
|
+
"FONT_WEIGHT_RAMP_COLLAPSED",
|
|
3184
|
+
`Named weight steps share a value because this theme loads only [${loaded.join(", ")}]: ${collapsed.join(", ")}. Asking for the heavier name renders no differently from the lighter one. This is what the browser already does \u2014 load an intermediate face, or use the discrete ladder (--font-weight-${loaded.join(" / --font-weight-")}) where a real step is needed.`,
|
|
3185
|
+
"typography"
|
|
3186
|
+
)
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3140
3190
|
function checkLetterSpacing(config, issues) {
|
|
3141
3191
|
const ls = config.typography?.["letter-spacing"];
|
|
3142
3192
|
if (!ls) return;
|
|
@@ -3643,6 +3693,7 @@ function validate(config, options) {
|
|
|
3643
3693
|
const typedConfig = config;
|
|
3644
3694
|
checkCompleteness(typedConfig, errors);
|
|
3645
3695
|
checkTypeScaleCoherence(typedConfig, errors);
|
|
3696
|
+
checkFontWeightCoverage(typedConfig, warnings);
|
|
3646
3697
|
checkLetterSpacing(typedConfig, errors);
|
|
3647
3698
|
checkMotionEasing(typedConfig, errors);
|
|
3648
3699
|
const durationIssues = [];
|
|
@@ -4214,8 +4265,10 @@ export {
|
|
|
4214
4265
|
SEMANTIC_MAP,
|
|
4215
4266
|
TAILWIND_GRAY,
|
|
4216
4267
|
VISOR_BRANDS_CDN,
|
|
4268
|
+
VISOR_CORE_SEMANTIC_ALIASES,
|
|
4217
4269
|
VISOR_DEFAULT_BRAND_PATH,
|
|
4218
4270
|
VISOR_FONTS_CDN,
|
|
4271
|
+
WEIGHT_RAMP_TARGETS,
|
|
4219
4272
|
allComponentTokenNames,
|
|
4220
4273
|
applyOverrides,
|
|
4221
4274
|
assignSemanticTokens,
|
|
@@ -4226,17 +4279,21 @@ export {
|
|
|
4226
4279
|
clampToSrgb,
|
|
4227
4280
|
cleanFontValue,
|
|
4228
4281
|
collectBrandPassthrough,
|
|
4282
|
+
collectDeclaredProperties,
|
|
4229
4283
|
componentTokenName,
|
|
4230
4284
|
compositeOverBackground,
|
|
4285
|
+
declaresWeights,
|
|
4231
4286
|
exportTheme,
|
|
4232
4287
|
extractFromCSS,
|
|
4233
4288
|
formatFontCoverageError,
|
|
4234
4289
|
generateDarkCss,
|
|
4290
|
+
generateFontWeightDecls,
|
|
4235
4291
|
generateFullBundleCss,
|
|
4236
4292
|
generateLightCss,
|
|
4237
4293
|
generatePreloadLinks,
|
|
4238
4294
|
generatePrimitives,
|
|
4239
4295
|
generatePrimitivesCss,
|
|
4296
|
+
generateSemanticAliasDecls,
|
|
4240
4297
|
generateSemanticCss,
|
|
4241
4298
|
generateShadeScale,
|
|
4242
4299
|
generateStylesheetLinks,
|
|
@@ -4255,8 +4312,10 @@ export {
|
|
|
4255
4312
|
isValidColor,
|
|
4256
4313
|
isValidHex,
|
|
4257
4314
|
isVisorThemeConfig,
|
|
4315
|
+
loadedWeights,
|
|
4258
4316
|
lookupFontWeightAlias,
|
|
4259
4317
|
lookupGoogleFont,
|
|
4318
|
+
matchLoadedWeight,
|
|
4260
4319
|
normalizeHex,
|
|
4261
4320
|
oklchToHex,
|
|
4262
4321
|
parseCSSDeclarations,
|
|
@@ -4272,8 +4331,10 @@ export {
|
|
|
4272
4331
|
resolveComponentBindings,
|
|
4273
4332
|
resolveConfig,
|
|
4274
4333
|
resolveFont,
|
|
4334
|
+
resolveRoleWeight,
|
|
4275
4335
|
resolveThemeBrand,
|
|
4276
4336
|
resolveThemeFonts,
|
|
4337
|
+
resolveWeightRamp,
|
|
4277
4338
|
rgbToHex,
|
|
4278
4339
|
serializeBrandStrategy,
|
|
4279
4340
|
serializeColor,
|
|
@@ -678,6 +678,12 @@ interface VisorThemeConfig {
|
|
|
678
678
|
info?: string;
|
|
679
679
|
};
|
|
680
680
|
typography?: {
|
|
681
|
+
/**
|
|
682
|
+
* Type-scale multiplier applied to the `--font-size-*` ramp (VI-638).
|
|
683
|
+
* Every step is multiplied by it, and the page inherits the scaled
|
|
684
|
+
* `--font-size-base`. CSS adapters only — the Flutter adapter emits
|
|
685
|
+
* Material 3 slot sizes verbatim. Default: 1.
|
|
686
|
+
*/
|
|
681
687
|
scale?: number;
|
|
682
688
|
/**
|
|
683
689
|
* VI-375: text color placed on a LIGHT interactive background. Auto-picked
|
|
@@ -867,6 +873,12 @@ interface ResolvedThemeConfig {
|
|
|
867
873
|
};
|
|
868
874
|
"colors-dark"?: VisorThemeConfig["colors-dark"];
|
|
869
875
|
typography: {
|
|
876
|
+
/**
|
|
877
|
+
* Type-scale multiplier applied to the `--font-size-*` ramp (VI-638).
|
|
878
|
+
* Every step is multiplied by it, and the page inherits the scaled
|
|
879
|
+
* `--font-size-base`. CSS adapters only — the Flutter adapter emits
|
|
880
|
+
* Material 3 slot sizes verbatim. Default: 1.
|
|
881
|
+
*/
|
|
870
882
|
scale: number;
|
|
871
883
|
/**
|
|
872
884
|
* VI-375: resolved default text color for LIGHT interactive backgrounds.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "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",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
},
|
|
188
188
|
"scale": {
|
|
189
189
|
"type": "number",
|
|
190
|
-
"description": "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
190
|
+
"description": "Type scale multiplier applied to the font-size ramp: every --font-size-* step is multiplied by it, and the page inherits the scaled --font-size-base. Default: 1. CSS adapters only — the Flutter adapter emits Material 3 slot sizes verbatim and ignores this field."
|
|
191
191
|
},
|
|
192
192
|
"text-on-light": {
|
|
193
193
|
"type": "string",
|