@loworbitstudio/visor-theme-engine 0.5.0 → 0.6.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
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-CtozYHw0.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
package/dist/adapters/index.js
CHANGED
|
@@ -2,7 +2,9 @@ import {
|
|
|
2
2
|
FULL_SHADE_STEPS,
|
|
3
3
|
MATERIAL_TEXT_SLOTS,
|
|
4
4
|
SELECTIVE_SHADE_STEPS,
|
|
5
|
+
aliasFamily,
|
|
5
6
|
buildVisorFontUrl,
|
|
7
|
+
fontStack,
|
|
6
8
|
generateDarkCss,
|
|
7
9
|
generateLightCss,
|
|
8
10
|
generatePrimitivesCss,
|
|
@@ -11,7 +13,7 @@ import {
|
|
|
11
13
|
parseColor,
|
|
12
14
|
resolveThemeFonts,
|
|
13
15
|
sectionComment
|
|
14
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-4U5L3AWY.js";
|
|
15
17
|
|
|
16
18
|
// src/adapters/layers.ts
|
|
17
19
|
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
|
@@ -24,18 +26,29 @@ ${trimmed}
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// src/adapters/nextjs.ts
|
|
29
|
+
function toKebabCase(name) {
|
|
30
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
31
|
+
}
|
|
27
32
|
function nextjsAdapter(input, options) {
|
|
28
33
|
const includeFontImports = options?.includeFontImports ?? true;
|
|
29
34
|
const includeFowt = options?.includeFowt ?? true;
|
|
30
35
|
const lines = [];
|
|
36
|
+
const slug = toKebabCase(input.config.name);
|
|
37
|
+
const aliasedFamilies = /* @__PURE__ */ new Map();
|
|
31
38
|
lines.push(header("Visor Theme \u2014 NextJS Adapter"));
|
|
32
39
|
if (includeFontImports && input.config.typography) {
|
|
33
40
|
const fontResult = resolveThemeFonts(input.config.typography);
|
|
34
|
-
const
|
|
35
|
-
|
|
41
|
+
const fontSlots = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono];
|
|
42
|
+
for (const font of fontSlots) {
|
|
43
|
+
if (font && font.source === "visor-fonts" && !aliasedFamilies.has(font.family)) {
|
|
44
|
+
aliasedFamilies.set(font.family, aliasFamily(font.family, slug));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const hostedCssFonts = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono].filter(
|
|
48
|
+
(r) => r !== null && (r.source === "google-fonts" || r.source === "fontshare")
|
|
36
49
|
);
|
|
37
50
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
38
|
-
for (const font of
|
|
51
|
+
for (const font of hostedCssFonts) {
|
|
39
52
|
if (font?.cssUrl && !seenUrls.has(font.cssUrl)) {
|
|
40
53
|
seenUrls.add(font.cssUrl);
|
|
41
54
|
lines.push(`@import url("${font.cssUrl}");`);
|
|
@@ -59,10 +72,11 @@ function nextjsAdapter(input, options) {
|
|
|
59
72
|
for (const font of visorFonts) {
|
|
60
73
|
if (seenVisorFamilies.has(font.family)) continue;
|
|
61
74
|
seenVisorFamilies.add(font.family);
|
|
75
|
+
const aliased = aliasedFamilies.get(font.family);
|
|
62
76
|
for (const weight of font.weights) {
|
|
63
77
|
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
64
78
|
lines.push(`@font-face {`);
|
|
65
|
-
lines.push(` font-family: "${
|
|
79
|
+
lines.push(` font-family: "${aliased}";`);
|
|
66
80
|
lines.push(` src: url("${url}") format("woff2");`);
|
|
67
81
|
lines.push(` font-weight: ${weight};`);
|
|
68
82
|
lines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
@@ -75,7 +89,7 @@ function nextjsAdapter(input, options) {
|
|
|
75
89
|
lines.push(LAYER_ORDER);
|
|
76
90
|
lines.push("");
|
|
77
91
|
const primitivesBody = stripHeader(
|
|
78
|
-
generatePrimitivesCss(input.primitives, input.config)
|
|
92
|
+
generatePrimitivesCss(input.primitives, input.config, { aliasedFamilies })
|
|
79
93
|
);
|
|
80
94
|
lines.push(wrapInLayer("visor-primitives", primitivesBody));
|
|
81
95
|
lines.push("");
|
|
@@ -174,7 +188,7 @@ var SELECTIVE_SCALE_ROLES = [
|
|
|
174
188
|
"error",
|
|
175
189
|
"info"
|
|
176
190
|
];
|
|
177
|
-
function
|
|
191
|
+
function toKebabCase2(name) {
|
|
178
192
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
179
193
|
}
|
|
180
194
|
function generateScopedPrimitives(primitives, config) {
|
|
@@ -226,7 +240,7 @@ function generateSemanticDecls(tokens, mode) {
|
|
|
226
240
|
return decls;
|
|
227
241
|
}
|
|
228
242
|
function deckAdapter(input, options) {
|
|
229
|
-
const scopeClass = options?.scopeClass ?? `.deck--${
|
|
243
|
+
const scopeClass = options?.scopeClass ?? `.deck--${toKebabCase2(input.config.name)}`;
|
|
230
244
|
const lines = [];
|
|
231
245
|
lines.push(header(`Visor Theme \u2014 Deck Adapter (${scopeClass})`));
|
|
232
246
|
const primDecls = generateScopedPrimitives(input.primitives, input.config);
|
|
@@ -260,7 +274,7 @@ function deckAdapter(input, options) {
|
|
|
260
274
|
// src/adapters/docs.ts
|
|
261
275
|
var FULL_SCALE_ROLES2 = ["primary", "accent", "neutral"];
|
|
262
276
|
var SELECTIVE_SCALE_ROLES2 = ["success", "warning", "error", "info"];
|
|
263
|
-
function
|
|
277
|
+
function toKebabCase3(name) {
|
|
264
278
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
265
279
|
}
|
|
266
280
|
function generateColorDecls(primitives) {
|
|
@@ -298,13 +312,14 @@ function generateRadiusDecls(config) {
|
|
|
298
312
|
`--radius-full: ${config.radius.pill}px;`
|
|
299
313
|
];
|
|
300
314
|
}
|
|
301
|
-
function generateTypographyDecls(config) {
|
|
315
|
+
function generateTypographyDecls(config, aliases) {
|
|
302
316
|
const decls = [];
|
|
303
|
-
|
|
304
|
-
decls.push(`--font-
|
|
305
|
-
decls.push(`--font-
|
|
306
|
-
decls.push(`--font-
|
|
307
|
-
decls.push(`--font-
|
|
317
|
+
const headingFamily = config.typography.heading?.family ?? config.typography.body.family;
|
|
318
|
+
decls.push(`--font-display: ${fontStack(config.typography.display.family, aliases)};`);
|
|
319
|
+
decls.push(`--font-sans: ${fontStack(config.typography.body.family, aliases)};`);
|
|
320
|
+
decls.push(`--font-heading: ${fontStack(headingFamily, aliases)};`);
|
|
321
|
+
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
322
|
+
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
308
323
|
const fontSizes = {
|
|
309
324
|
xs: 12,
|
|
310
325
|
sm: 14,
|
|
@@ -415,31 +430,38 @@ function sectionComment2(label) {
|
|
|
415
430
|
/* --- ${label} --- */`;
|
|
416
431
|
}
|
|
417
432
|
function docsAdapter(input, options) {
|
|
418
|
-
const slug =
|
|
433
|
+
const slug = toKebabCase3(input.config.name);
|
|
419
434
|
const scopeClass = `.${slug}-theme`;
|
|
420
435
|
const includeFontImports = options?.includeFontImports ?? true;
|
|
421
436
|
const fontLines = [];
|
|
422
437
|
const lines = [];
|
|
438
|
+
const aliasedFamilies = /* @__PURE__ */ new Map();
|
|
423
439
|
if (includeFontImports && input.config.typography) {
|
|
424
440
|
const fontResult = resolveThemeFonts(input.config.typography);
|
|
425
441
|
const fontSlots = [fontResult.heading, fontResult.display, fontResult.body, fontResult.mono];
|
|
442
|
+
for (const font of fontSlots) {
|
|
443
|
+
if (font && font.source === "visor-fonts" && !aliasedFamilies.has(font.family)) {
|
|
444
|
+
aliasedFamilies.set(font.family, aliasFamily(font.family, slug));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
426
447
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
427
448
|
for (const font of fontSlots) {
|
|
428
|
-
if (font && font.source === "google-fonts" && font.cssUrl && !seenUrls.has(font.cssUrl)) {
|
|
449
|
+
if (font && (font.source === "google-fonts" || font.source === "fontshare") && font.cssUrl && !seenUrls.has(font.cssUrl)) {
|
|
429
450
|
seenUrls.add(font.cssUrl);
|
|
430
451
|
fontLines.push(`@import url("${font.cssUrl}");`);
|
|
431
452
|
fontLines.push("");
|
|
432
453
|
}
|
|
433
454
|
}
|
|
434
455
|
const scale = input.config.typography?.scale ?? 1;
|
|
435
|
-
const
|
|
456
|
+
const emittedFamilies = /* @__PURE__ */ new Set();
|
|
436
457
|
for (const font of fontSlots) {
|
|
437
|
-
if (font && font.source === "visor-fonts" && !
|
|
438
|
-
|
|
458
|
+
if (font && font.source === "visor-fonts" && !emittedFamilies.has(font.family)) {
|
|
459
|
+
emittedFamilies.add(font.family);
|
|
460
|
+
const aliased = aliasedFamilies.get(font.family);
|
|
439
461
|
for (const weight of font.weights) {
|
|
440
462
|
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
441
463
|
fontLines.push("@font-face {");
|
|
442
|
-
fontLines.push(` font-family: "${
|
|
464
|
+
fontLines.push(` font-family: "${aliased}";`);
|
|
443
465
|
fontLines.push(` src: url("${url}") format("woff2");`);
|
|
444
466
|
fontLines.push(` font-weight: ${weight};`);
|
|
445
467
|
fontLines.push(` font-style: ${font.italic ? "italic" : "normal"};`);
|
|
@@ -475,7 +497,7 @@ function docsAdapter(input, options) {
|
|
|
475
497
|
lines.push(block(scopeClass, generateRadiusDecls(input.config)));
|
|
476
498
|
lines.push("");
|
|
477
499
|
lines.push(sectionComment2("Primitive: Typography"));
|
|
478
|
-
lines.push(block(scopeClass, generateTypographyDecls(input.config)));
|
|
500
|
+
lines.push(block(scopeClass, generateTypographyDecls(input.config, aliasedFamilies)));
|
|
479
501
|
lines.push("");
|
|
480
502
|
lines.push(sectionComment2("Primitive: Shadows"));
|
|
481
503
|
lines.push(block(scopeClass, generateShadowDecls(input.config)));
|
|
@@ -151,6 +151,8 @@ function buildGoogleFontsCssUrl(family, weights, italic, display) {
|
|
|
151
151
|
return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${wghtValues}&display=${display}`;
|
|
152
152
|
}
|
|
153
153
|
var VISOR_FONTS_CDN = "https://fonts.visor.design";
|
|
154
|
+
var FONTSHARE_API_ORIGIN = "https://api.fontshare.com";
|
|
155
|
+
var FONTSHARE_CDN_ORIGIN = "https://cdn.fontshare.com";
|
|
154
156
|
function buildFamilySlug(family) {
|
|
155
157
|
return family.toLowerCase().replace(/ /g, "-");
|
|
156
158
|
}
|
|
@@ -174,6 +176,16 @@ function buildVisorFontUrl(org, family, weight) {
|
|
|
174
176
|
const weightName = lookupFontWeightAlias(family, weight) ?? WEIGHT_NAMES[weight] ?? `W${weight}`;
|
|
175
177
|
return `${VISOR_FONTS_CDN}/${org}/${slug}/${prefix}-${weightName}.woff2`;
|
|
176
178
|
}
|
|
179
|
+
function buildFontshareCssUrl(family, weights, italic, display) {
|
|
180
|
+
const slug = buildFamilySlug(family);
|
|
181
|
+
const sortedWeights = [...weights].sort((a, b) => a - b);
|
|
182
|
+
const tokens = [];
|
|
183
|
+
if (italic) {
|
|
184
|
+
for (const w of sortedWeights) tokens.push(`${w}i`);
|
|
185
|
+
}
|
|
186
|
+
for (const w of sortedWeights) tokens.push(`${w}`);
|
|
187
|
+
return `${FONTSHARE_API_ORIGIN}/v2/css?f[]=${slug}@${tokens.join(",")}&display=${display}`;
|
|
188
|
+
}
|
|
177
189
|
function resolveFont(family, options = {}) {
|
|
178
190
|
const display = options.display ?? DEFAULT_DISPLAY;
|
|
179
191
|
const requestedWeights = options.weights ?? DEFAULT_WEIGHTS;
|
|
@@ -192,6 +204,20 @@ function resolveFont(family, options = {}) {
|
|
|
192
204
|
org: options.org ?? null
|
|
193
205
|
};
|
|
194
206
|
}
|
|
207
|
+
if (explicitSource === "fontshare") {
|
|
208
|
+
const cssUrl = buildFontshareCssUrl(family, requestedWeights, italic, display);
|
|
209
|
+
return {
|
|
210
|
+
family,
|
|
211
|
+
source: "fontshare",
|
|
212
|
+
cssUrl,
|
|
213
|
+
weights: requestedWeights,
|
|
214
|
+
italic,
|
|
215
|
+
display,
|
|
216
|
+
category: options.category ?? "sans-serif",
|
|
217
|
+
guidance: null,
|
|
218
|
+
org: null
|
|
219
|
+
};
|
|
220
|
+
}
|
|
195
221
|
if (explicitSource === "local") {
|
|
196
222
|
return {
|
|
197
223
|
family,
|
|
@@ -288,6 +314,20 @@ function generatePreloadLinks(resolutions, customFontPaths) {
|
|
|
288
314
|
}
|
|
289
315
|
}
|
|
290
316
|
}
|
|
317
|
+
const hasFontshare = resolutions.some((r) => r.source === "fontshare");
|
|
318
|
+
if (hasFontshare) {
|
|
319
|
+
links.push(`<link rel="preconnect" href="${FONTSHARE_API_ORIGIN}">`);
|
|
320
|
+
links.push(
|
|
321
|
+
`<link rel="preconnect" href="${FONTSHARE_CDN_ORIGIN}" crossorigin>`
|
|
322
|
+
);
|
|
323
|
+
for (const resolution of resolutions) {
|
|
324
|
+
if (resolution.source === "fontshare" && resolution.cssUrl) {
|
|
325
|
+
links.push(
|
|
326
|
+
`<link rel="preload" as="style" href="${resolution.cssUrl}">`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
291
331
|
if (customFontPaths) {
|
|
292
332
|
for (const resolution of resolutions) {
|
|
293
333
|
if (resolution.source === "local") {
|
|
@@ -307,7 +347,7 @@ function generatePreloadLinks(resolutions, customFontPaths) {
|
|
|
307
347
|
function generateStylesheetLinks(resolutions) {
|
|
308
348
|
const links = [];
|
|
309
349
|
for (const resolution of resolutions) {
|
|
310
|
-
if (resolution.source === "google-fonts" && resolution.cssUrl) {
|
|
350
|
+
if ((resolution.source === "google-fonts" || resolution.source === "fontshare") && resolution.cssUrl) {
|
|
311
351
|
links.push(
|
|
312
352
|
`<link rel="stylesheet" href="${resolution.cssUrl}">`
|
|
313
353
|
);
|
|
@@ -330,6 +370,19 @@ function generateFontCSS(heading, displayFont, body, mono, typography) {
|
|
|
330
370
|
}
|
|
331
371
|
lines.push("");
|
|
332
372
|
}
|
|
373
|
+
const fontshareFonts = allSlots.filter(
|
|
374
|
+
(r) => r !== null && r.source === "fontshare"
|
|
375
|
+
);
|
|
376
|
+
const seenFontshareUrls = /* @__PURE__ */ new Set();
|
|
377
|
+
if (fontshareFonts.length > 0) {
|
|
378
|
+
lines.push("/* Fontshare \u2014 load these stylesheets in your HTML <head> */");
|
|
379
|
+
for (const font of fontshareFonts) {
|
|
380
|
+
if (!font.cssUrl || seenFontshareUrls.has(font.cssUrl)) continue;
|
|
381
|
+
seenFontshareUrls.add(font.cssUrl);
|
|
382
|
+
lines.push(`/* ${font.cssUrl} */`);
|
|
383
|
+
}
|
|
384
|
+
lines.push("");
|
|
385
|
+
}
|
|
333
386
|
const visorFonts = allSlots.filter(
|
|
334
387
|
(r) => r !== null && r.source === "visor-fonts"
|
|
335
388
|
);
|
|
@@ -1069,6 +1122,17 @@ function generateShadeScale(color, role) {
|
|
|
1069
1122
|
return scale;
|
|
1070
1123
|
}
|
|
1071
1124
|
|
|
1125
|
+
// src/fonts/theme-alias.ts
|
|
1126
|
+
var EMPTY_ALIASES = /* @__PURE__ */ new Map();
|
|
1127
|
+
function aliasFamily(family, themeSlug) {
|
|
1128
|
+
return `${family} [${themeSlug}]`;
|
|
1129
|
+
}
|
|
1130
|
+
function fontStack(bare, aliases) {
|
|
1131
|
+
const aliased = aliases.get(bare);
|
|
1132
|
+
if (!aliased) return bare;
|
|
1133
|
+
return `"${aliased}", "${bare}"`;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1072
1136
|
// src/generate-css.ts
|
|
1073
1137
|
function header(label) {
|
|
1074
1138
|
return [
|
|
@@ -1154,15 +1218,15 @@ function generateShadowPrimitives(config) {
|
|
|
1154
1218
|
`--shadow-xl: ${config.shadows.xl};`
|
|
1155
1219
|
];
|
|
1156
1220
|
}
|
|
1157
|
-
function generateTypographyPrimitives(config) {
|
|
1221
|
+
function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
|
|
1158
1222
|
const decls = [];
|
|
1159
1223
|
const scale = config.typography.scale;
|
|
1160
1224
|
decls.push(`font-size: ${scale === 1 ? "1rem" : `${scale}rem`};`);
|
|
1161
|
-
decls.push(`--font-heading: ${config.typography.heading.family};`);
|
|
1162
|
-
decls.push(`--font-display: ${config.typography.display.family};`);
|
|
1163
|
-
decls.push(`--font-sans: ${config.typography.body.family};`);
|
|
1164
|
-
decls.push(`--font-body: ${config.typography.body.family};`);
|
|
1165
|
-
decls.push(`--font-mono: ${config.typography.mono.family};`);
|
|
1225
|
+
decls.push(`--font-heading: ${fontStack(config.typography.heading.family, aliases)};`);
|
|
1226
|
+
decls.push(`--font-display: ${fontStack(config.typography.display.family, aliases)};`);
|
|
1227
|
+
decls.push(`--font-sans: ${fontStack(config.typography.body.family, aliases)};`);
|
|
1228
|
+
decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
|
|
1229
|
+
decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
|
|
1166
1230
|
const fontSizes = {
|
|
1167
1231
|
xs: 12,
|
|
1168
1232
|
sm: 14,
|
|
@@ -1233,7 +1297,7 @@ function generateMiscPrimitives() {
|
|
|
1233
1297
|
"--focus-ring-offset: 2px;"
|
|
1234
1298
|
];
|
|
1235
1299
|
}
|
|
1236
|
-
function generatePrimitivesCss(primitives, config) {
|
|
1300
|
+
function generatePrimitivesCss(primitives, config, options) {
|
|
1237
1301
|
const lines = [];
|
|
1238
1302
|
lines.push(sectionComment("Primitive: Colors"));
|
|
1239
1303
|
lines.push(
|
|
@@ -1244,7 +1308,7 @@ function generatePrimitivesCss(primitives, config) {
|
|
|
1244
1308
|
lines.push(sectionComment("Primitive: Border Radius"));
|
|
1245
1309
|
lines.push(block(":root", generateRadiusPrimitives(config)));
|
|
1246
1310
|
lines.push(sectionComment("Primitive: Typography"));
|
|
1247
|
-
lines.push(block(":root", generateTypographyPrimitives(config)));
|
|
1311
|
+
lines.push(block(":root", generateTypographyPrimitives(config, options?.aliasedFamilies)));
|
|
1248
1312
|
lines.push(sectionComment("Primitive: Shadows"));
|
|
1249
1313
|
lines.push(block(":root", generateShadowPrimitives(config)));
|
|
1250
1314
|
lines.push(sectionComment("Primitive: Motion"));
|
|
@@ -1437,6 +1501,8 @@ export {
|
|
|
1437
1501
|
SELECTIVE_SHADE_STEPS,
|
|
1438
1502
|
TAILWIND_GRAY,
|
|
1439
1503
|
generateShadeScale,
|
|
1504
|
+
aliasFamily,
|
|
1505
|
+
fontStack,
|
|
1440
1506
|
header,
|
|
1441
1507
|
sectionComment,
|
|
1442
1508
|
generatePrimitivesCss,
|
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-CtozYHw0.js';
|
|
2
|
+
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-CtozYHw0.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -102,6 +102,34 @@ declare const googleFontsCatalog: GoogleFontEntry[];
|
|
|
102
102
|
/** Look up a font family in the Google Fonts catalog (case-insensitive) */
|
|
103
103
|
declare function lookupGoogleFont(family: string): GoogleFontEntry | undefined;
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Font coverage validator.
|
|
107
|
+
*
|
|
108
|
+
* Catches the failure mode behind VI-358: emitted theme CSS declares
|
|
109
|
+
* `--font-*: Family, ...` overrides but the same CSS contains no
|
|
110
|
+
* `@font-face` rule for that family, so the browser can never load the
|
|
111
|
+
* declared font and silently falls through to the next stack entry.
|
|
112
|
+
*
|
|
113
|
+
* The validator extracts the primary family from each `--font-*` declaration
|
|
114
|
+
* (the first comma-separated token, unquoted) and checks it against the set
|
|
115
|
+
* of @font-face families in the same emitted CSS. Generic CSS keywords
|
|
116
|
+
* (sans-serif, system-ui, etc.) and well-known platform-installed fonts
|
|
117
|
+
* (SF Mono, Helvetica, etc.) are skipped — those are intentionally part of
|
|
118
|
+
* the fallback stack and never carry their own @font-face.
|
|
119
|
+
*
|
|
120
|
+
* Size-adjusted system-fallback faces (family ends with " Fallback") are
|
|
121
|
+
* also excluded from the @font-face coverage set; they don't load a real
|
|
122
|
+
* font, they only adjust local metrics.
|
|
123
|
+
*/
|
|
124
|
+
interface FontCoverageError {
|
|
125
|
+
family: string;
|
|
126
|
+
declaredAt: string;
|
|
127
|
+
}
|
|
128
|
+
interface FontCoverageResult {
|
|
129
|
+
errors: FontCoverageError[];
|
|
130
|
+
}
|
|
131
|
+
declare function validateFontCoverage(css: string): FontCoverageResult;
|
|
132
|
+
|
|
105
133
|
/**
|
|
106
134
|
* Import Pipeline
|
|
107
135
|
*
|
|
@@ -840,6 +868,30 @@ declare const SEMANTIC_MAP: {
|
|
|
840
868
|
interactive: Record<string, SemanticMapping>;
|
|
841
869
|
};
|
|
842
870
|
|
|
871
|
+
/**
|
|
872
|
+
* Per-theme font-family aliasing — substrate fix for VI-354.
|
|
873
|
+
*
|
|
874
|
+
* `@font-face` declarations are global to the document, so co-loaded themes
|
|
875
|
+
* that share a font family with differing per-theme properties (e.g.
|
|
876
|
+
* `size-adjust`) silently overwrite each other. Aliasing each theme's
|
|
877
|
+
* `@font-face` family as `{family} [{slug}]` scopes the declaration to
|
|
878
|
+
* that theme only; the theme's `--font-*` vars then list the alias first
|
|
879
|
+
* with the bare family as a fallback for graceful degradation.
|
|
880
|
+
*
|
|
881
|
+
* Lives in `fonts/` (not `adapters/`) because every adapter that emits
|
|
882
|
+
* visor-fonts `@font-face` blocks needs the same aliasing rules. Sharing
|
|
883
|
+
* the helpers prevents drift between adapters.
|
|
884
|
+
*/
|
|
885
|
+
/**
|
|
886
|
+
* Map of `bare family name → aliased family name` for every family the
|
|
887
|
+
* theme emits as a per-theme `@font-face`. The alias applies to every
|
|
888
|
+
* `--font-*` whose family matches an entry, regardless of which slot the
|
|
889
|
+
* var represents (the bug repro in VI-354 hinges on this for --font-mono,
|
|
890
|
+
* which can resolve to the same family as heading/body but doesn't carry
|
|
891
|
+
* the visor-fonts source through `resolveConfig`).
|
|
892
|
+
*/
|
|
893
|
+
type AliasedFamilies = ReadonlyMap<string, string>;
|
|
894
|
+
|
|
843
895
|
/**
|
|
844
896
|
* CSS Generation (Stage 3 + Output)
|
|
845
897
|
*
|
|
@@ -847,7 +899,9 @@ declare const SEMANTIC_MAP: {
|
|
|
847
899
|
* packages/tokens/src/generate/generate-css.ts output format.
|
|
848
900
|
*/
|
|
849
901
|
|
|
850
|
-
declare function generatePrimitivesCss(primitives: GeneratedPrimitives, config: ResolvedThemeConfig
|
|
902
|
+
declare function generatePrimitivesCss(primitives: GeneratedPrimitives, config: ResolvedThemeConfig, options?: {
|
|
903
|
+
aliasedFamilies?: AliasedFamilies;
|
|
904
|
+
}): string;
|
|
851
905
|
declare function generateSemanticCss(tokens: SemanticTokens): string;
|
|
852
906
|
declare function generateLightCss(tokens: SemanticTokens): string;
|
|
853
907
|
declare function generateDarkCss(tokens: SemanticTokens): string;
|
|
@@ -928,4 +982,4 @@ declare function cleanFontValue(val: string): string;
|
|
|
928
982
|
*/
|
|
929
983
|
declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
|
|
930
984
|
|
|
931
|
-
export { type CSSFile, ColorRole, type Confidence, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, 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, visorTheme_schema as visorThemeSchema };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,154 @@ import {
|
|
|
34
34
|
rgbToHex,
|
|
35
35
|
rgbToOklch,
|
|
36
36
|
serializeColor
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-4U5L3AWY.js";
|
|
38
|
+
|
|
39
|
+
// src/fonts/validate-coverage.ts
|
|
40
|
+
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
41
|
+
var FONT_FACE_RE = /@font-face\s*\{[^}]*\}/g;
|
|
42
|
+
var FONT_FAMILY_DECL_RE = /font-family\s*:\s*([^;]+);/;
|
|
43
|
+
var GOOGLE_FONTS_IMPORT_RE = /@import\s+url\(["']?https:\/\/fonts\.googleapis\.com\/css2?\?family=([^:&"')]+)/g;
|
|
44
|
+
var FONTSHARE_IMPORT_RE = /@import\s+url\(["']?https:\/\/api\.fontshare\.com\/v2\/css\?f(?:\[\]|%5B%5D)=([a-z0-9-]+)/gi;
|
|
45
|
+
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
46
|
+
"serif",
|
|
47
|
+
"sans-serif",
|
|
48
|
+
"monospace",
|
|
49
|
+
"cursive",
|
|
50
|
+
"fantasy",
|
|
51
|
+
"system-ui",
|
|
52
|
+
"ui-monospace",
|
|
53
|
+
"ui-sans-serif",
|
|
54
|
+
"ui-serif",
|
|
55
|
+
"ui-rounded",
|
|
56
|
+
"emoji",
|
|
57
|
+
"math",
|
|
58
|
+
"fangsong",
|
|
59
|
+
"inherit",
|
|
60
|
+
"initial",
|
|
61
|
+
"unset",
|
|
62
|
+
"revert",
|
|
63
|
+
"none",
|
|
64
|
+
// Apple / Chromium-on-Mac system keywords. These behave like
|
|
65
|
+
// CSS-engine-level aliases, not real font families — `local()` can
|
|
66
|
+
// never resolve them, so they never need an @font-face.
|
|
67
|
+
"-apple-system",
|
|
68
|
+
"-webkit-system-font",
|
|
69
|
+
"BlinkMacSystemFont"
|
|
70
|
+
]);
|
|
71
|
+
var SYSTEM_FONTS = /* @__PURE__ */ new Set([
|
|
72
|
+
"Apple Color Emoji",
|
|
73
|
+
"Arial",
|
|
74
|
+
"Arial Black",
|
|
75
|
+
"BlinkMacSystemFont",
|
|
76
|
+
"Cambria",
|
|
77
|
+
"Comic Sans MS",
|
|
78
|
+
"Consolas",
|
|
79
|
+
"Courier",
|
|
80
|
+
"Courier New",
|
|
81
|
+
"DejaVu Sans",
|
|
82
|
+
"DejaVu Sans Mono",
|
|
83
|
+
"Fira Code",
|
|
84
|
+
"Fira Mono",
|
|
85
|
+
"Fira Sans",
|
|
86
|
+
"Georgia",
|
|
87
|
+
"Helvetica",
|
|
88
|
+
"Helvetica Neue",
|
|
89
|
+
"Impact",
|
|
90
|
+
"JetBrains Mono",
|
|
91
|
+
"Liberation Mono",
|
|
92
|
+
"Liberation Sans",
|
|
93
|
+
"Lucida Console",
|
|
94
|
+
"Lucida Grande",
|
|
95
|
+
"Menlo",
|
|
96
|
+
"Microsoft YaHei",
|
|
97
|
+
"Monaco",
|
|
98
|
+
"Noto Color Emoji",
|
|
99
|
+
"Open Sans",
|
|
100
|
+
"PingFang SC",
|
|
101
|
+
"PingFang TC",
|
|
102
|
+
"Roboto",
|
|
103
|
+
"Roboto Mono",
|
|
104
|
+
"Roboto Slab",
|
|
105
|
+
"SF Mono",
|
|
106
|
+
"SF Pro Display",
|
|
107
|
+
"SF Pro Text",
|
|
108
|
+
"Segoe UI",
|
|
109
|
+
"Segoe UI Emoji",
|
|
110
|
+
"Segoe UI Symbol",
|
|
111
|
+
"Segoe UI Variable",
|
|
112
|
+
"Source Code Pro",
|
|
113
|
+
"Source Sans Pro",
|
|
114
|
+
"Times",
|
|
115
|
+
"Times New Roman",
|
|
116
|
+
"Trebuchet MS",
|
|
117
|
+
"Verdana"
|
|
118
|
+
]);
|
|
119
|
+
function extractPrimaryFamily(value) {
|
|
120
|
+
const trimmed = value.trim();
|
|
121
|
+
if (trimmed.startsWith("var(")) return null;
|
|
122
|
+
const firstToken = trimmed.split(",")[0].trim();
|
|
123
|
+
if (!firstToken) return null;
|
|
124
|
+
const unquoted = firstToken.replace(/^["']|["']$/g, "");
|
|
125
|
+
if (GENERIC_FAMILIES.has(unquoted)) return null;
|
|
126
|
+
if (SYSTEM_FONTS.has(unquoted)) return null;
|
|
127
|
+
return unquoted;
|
|
128
|
+
}
|
|
129
|
+
function extractFontFaceFamilies(css) {
|
|
130
|
+
const families = /* @__PURE__ */ new Set();
|
|
131
|
+
const blocks = css.match(FONT_FACE_RE) ?? [];
|
|
132
|
+
for (const block of blocks) {
|
|
133
|
+
const decl = FONT_FAMILY_DECL_RE.exec(block);
|
|
134
|
+
if (!decl) continue;
|
|
135
|
+
const family = decl[1].trim().replace(/^["']|["'];?$/g, "");
|
|
136
|
+
if (family.endsWith(" Fallback")) continue;
|
|
137
|
+
families.add(family);
|
|
138
|
+
}
|
|
139
|
+
return families;
|
|
140
|
+
}
|
|
141
|
+
function extractGoogleFontsImports(css) {
|
|
142
|
+
const families = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const match of css.matchAll(GOOGLE_FONTS_IMPORT_RE)) {
|
|
144
|
+
const family = decodeURIComponent(match[1]).replace(/\+/g, " ");
|
|
145
|
+
families.add(family);
|
|
146
|
+
}
|
|
147
|
+
return families;
|
|
148
|
+
}
|
|
149
|
+
function fontshareSlugToFamily(slug) {
|
|
150
|
+
return slug.split("-").filter((p) => p.length > 0).map((p) => p[0].toUpperCase() + p.slice(1)).join(" ");
|
|
151
|
+
}
|
|
152
|
+
function extractFontshareImports(css) {
|
|
153
|
+
const families = /* @__PURE__ */ new Set();
|
|
154
|
+
for (const match of css.matchAll(FONTSHARE_IMPORT_RE)) {
|
|
155
|
+
families.add(fontshareSlugToFamily(match[1]));
|
|
156
|
+
}
|
|
157
|
+
return families;
|
|
158
|
+
}
|
|
159
|
+
function extractFontVarDeclarations(css) {
|
|
160
|
+
const decls = [];
|
|
161
|
+
for (const match of css.matchAll(FONT_VAR_RE)) {
|
|
162
|
+
const slot = `--font-${match[1]}`;
|
|
163
|
+
const family = extractPrimaryFamily(match[2]);
|
|
164
|
+
if (!family) continue;
|
|
165
|
+
decls.push({ slot, family });
|
|
166
|
+
}
|
|
167
|
+
return decls;
|
|
168
|
+
}
|
|
169
|
+
function validateFontCoverage(css) {
|
|
170
|
+
const declaredFamilies = extractFontFaceFamilies(css);
|
|
171
|
+
for (const f of extractGoogleFontsImports(css)) declaredFamilies.add(f);
|
|
172
|
+
for (const f of extractFontshareImports(css)) declaredFamilies.add(f);
|
|
173
|
+
const declarations = extractFontVarDeclarations(css);
|
|
174
|
+
const errors = [];
|
|
175
|
+
const seen = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const decl of declarations) {
|
|
177
|
+
if (declaredFamilies.has(decl.family)) continue;
|
|
178
|
+
const key = `${decl.slot}::${decl.family}`;
|
|
179
|
+
if (seen.has(key)) continue;
|
|
180
|
+
seen.add(key);
|
|
181
|
+
errors.push({ family: decl.family, declaredAt: decl.slot });
|
|
182
|
+
}
|
|
183
|
+
return { errors };
|
|
184
|
+
}
|
|
38
185
|
|
|
39
186
|
// src/pipeline.ts
|
|
40
187
|
import { parse as parseYaml } from "yaml";
|
|
@@ -429,7 +576,7 @@ var KNOWN_TYPOGRAPHY_KEYS = /* @__PURE__ */ new Set([
|
|
|
429
576
|
"slots"
|
|
430
577
|
]);
|
|
431
578
|
var KNOWN_TYPOGRAPHY_FONT_KEYS = /* @__PURE__ */ new Set(["family", "weight", "weights", "source", "org"]);
|
|
432
|
-
var KNOWN_TYPOGRAPHY_MONO_KEYS = /* @__PURE__ */ new Set(["family"]);
|
|
579
|
+
var KNOWN_TYPOGRAPHY_MONO_KEYS = /* @__PURE__ */ new Set(["family", "weight", "weights", "source", "org"]);
|
|
433
580
|
var KNOWN_LETTER_SPACING_KEYS = /* @__PURE__ */ new Set(["tight", "normal", "wide"]);
|
|
434
581
|
var KNOWN_SLOT_NAMES = new Set(MATERIAL_TEXT_SLOTS);
|
|
435
582
|
var KNOWN_SLOT_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["size", "weight", "letter-spacing"]);
|
|
@@ -801,7 +948,11 @@ function resolveConfig(config) {
|
|
|
801
948
|
...config.typography?.body?.weights && { weights: config.typography.body.weights }
|
|
802
949
|
},
|
|
803
950
|
mono: {
|
|
804
|
-
family: config.typography?.mono?.family ?? DEFAULTS.typography.mono.family
|
|
951
|
+
family: config.typography?.mono?.family ?? DEFAULTS.typography.mono.family,
|
|
952
|
+
...config.typography?.mono?.weight && { weight: config.typography.mono.weight },
|
|
953
|
+
...config.typography?.mono?.weights && { weights: config.typography.mono.weights },
|
|
954
|
+
...config.typography?.mono?.source && { source: config.typography.mono.source },
|
|
955
|
+
...config.typography?.mono?.org && { org: config.typography.mono.org }
|
|
805
956
|
},
|
|
806
957
|
slots: config.typography?.slots ?? {}
|
|
807
958
|
},
|
|
@@ -2679,5 +2830,6 @@ export {
|
|
|
2679
2830
|
serializeColor,
|
|
2680
2831
|
validate,
|
|
2681
2832
|
validateConfig,
|
|
2833
|
+
validateFontCoverage,
|
|
2682
2834
|
visor_theme_schema_default as visorThemeSchema
|
|
2683
2835
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Font resolution types for the Visor theme engine.
|
|
3
3
|
*/
|
|
4
4
|
/** Where a font is loaded from */
|
|
5
|
-
type FontSource = "google-fonts" | "visor-fonts" | "local";
|
|
5
|
+
type FontSource = "google-fonts" | "visor-fonts" | "fontshare" | "local";
|
|
6
6
|
/** CSS font-display strategy */
|
|
7
7
|
type FontDisplayStrategy = "swap" | "block" | "fallback" | "optional" | "auto";
|
|
8
8
|
/** A single resolved font */
|
|
@@ -11,7 +11,7 @@ interface FontResolution {
|
|
|
11
11
|
family: string;
|
|
12
12
|
/** Where this font comes from */
|
|
13
13
|
source: FontSource;
|
|
14
|
-
/**
|
|
14
|
+
/** Hosted CSS URL (only for google-fonts or fontshare sources) */
|
|
15
15
|
cssUrl: string | null;
|
|
16
16
|
/** Weights available/requested for this font */
|
|
17
17
|
weights: number[];
|
|
@@ -198,6 +198,7 @@ interface VisorThemeConfig {
|
|
|
198
198
|
mono?: {
|
|
199
199
|
family?: string;
|
|
200
200
|
weight?: number;
|
|
201
|
+
weights?: number[];
|
|
201
202
|
source?: FontSource;
|
|
202
203
|
org?: string;
|
|
203
204
|
};
|
|
@@ -304,6 +305,10 @@ interface ResolvedThemeConfig {
|
|
|
304
305
|
};
|
|
305
306
|
mono: {
|
|
306
307
|
family: string;
|
|
308
|
+
weight?: number;
|
|
309
|
+
weights?: number[];
|
|
310
|
+
source?: FontSource;
|
|
311
|
+
org?: string;
|
|
307
312
|
};
|
|
308
313
|
/**
|
|
309
314
|
* Per-slot Material `TextTheme` overrides, passed through from the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|