@loworbitstudio/visor-theme-engine 0.8.1 → 0.10.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 +1 -1
- package/dist/adapters/index.js +46 -5
- package/dist/{chunk-OJVNL7KN.js → chunk-43TVIXUS.js} +145 -30
- package/dist/index.d.ts +27 -3
- package/dist/index.js +128 -12
- package/dist/{types-CV0nmvMz.d.ts → types-Dwc1V0Nc.d.ts} +42 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +12 -0
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-Dwc1V0Nc.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adapter types for the Visor theme engine.
|
package/dist/adapters/index.js
CHANGED
|
@@ -6,14 +6,18 @@ import {
|
|
|
6
6
|
buildVisorFontUrl,
|
|
7
7
|
fontStack,
|
|
8
8
|
generateDarkCss,
|
|
9
|
+
generateHairlineDecls,
|
|
10
|
+
generateIntentDecls,
|
|
9
11
|
generateLightCss,
|
|
10
12
|
generatePrimitivesCss,
|
|
11
13
|
generateShadeScale,
|
|
14
|
+
generateSpaceAliasDecls,
|
|
15
|
+
generateTextScaleAliasDecls,
|
|
12
16
|
header,
|
|
13
17
|
parseColor,
|
|
14
18
|
resolveThemeFonts,
|
|
15
19
|
sectionComment
|
|
16
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-43TVIXUS.js";
|
|
17
21
|
|
|
18
22
|
// src/adapters/layers.ts
|
|
19
23
|
var LAYER_ORDER = "@layer visor-primitives, visor-semantic, visor-adaptive, visor-bridge;";
|
|
@@ -75,7 +79,7 @@ function nextjsAdapter(input, options) {
|
|
|
75
79
|
seenVisorFamilies.add(font.family);
|
|
76
80
|
const aliased = aliasedFamilies.get(font.family);
|
|
77
81
|
for (const weight of font.weights) {
|
|
78
|
-
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
82
|
+
const url = buildVisorFontUrl(font.org ?? "", font.family, weight, font.cdnBase);
|
|
79
83
|
lines.push(`@font-face {`);
|
|
80
84
|
lines.push(` font-family: "${aliased}";`);
|
|
81
85
|
lines.push(` src: url("${url}") format("woff2");`);
|
|
@@ -463,7 +467,7 @@ function docsAdapter(input, options) {
|
|
|
463
467
|
emittedFamilies.add(font.family);
|
|
464
468
|
const aliased = aliasedFamilies.get(font.family);
|
|
465
469
|
for (const weight of font.weights) {
|
|
466
|
-
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
470
|
+
const url = buildVisorFontUrl(font.org ?? "", font.family, weight, font.cdnBase);
|
|
467
471
|
fontLines.push("@font-face {");
|
|
468
472
|
fontLines.push(` font-family: "${aliased}";`);
|
|
469
473
|
fontLines.push(` src: url("${url}") format("woff2");`);
|
|
@@ -589,9 +593,46 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
|
589
593
|
lines.push(sectionComment2("Fumadocs bridge: light"));
|
|
590
594
|
lines.push(block(`html:not(.dark) ${scopeClass}`, generateFumadocsBridgeDecls(input.tokens, "light")));
|
|
591
595
|
lines.push("");
|
|
592
|
-
const
|
|
596
|
+
const semanticLines = [];
|
|
597
|
+
semanticLines.push("\n/* \u2500\u2500 Layer: Semantic aliases (VI-451) \u2500\u2500 */");
|
|
598
|
+
semanticLines.push(sectionComment2("Discrete: Text size aliases (--text-N)"));
|
|
599
|
+
semanticLines.push(block(scopeClass, generateTextScaleAliasDecls()));
|
|
600
|
+
semanticLines.push("");
|
|
601
|
+
semanticLines.push(sectionComment2("Discrete: Space aliases (--space-N)"));
|
|
602
|
+
semanticLines.push(block(scopeClass, generateSpaceAliasDecls(input.config)));
|
|
603
|
+
semanticLines.push("");
|
|
604
|
+
semanticLines.push(sectionComment2("Intent aliases (light)"));
|
|
605
|
+
semanticLines.push(block(`html:not(.dark) ${scopeClass}`, generateIntentDecls(input.tokens, "light")));
|
|
606
|
+
semanticLines.push("");
|
|
607
|
+
semanticLines.push(sectionComment2("Hairline aliases (light)"));
|
|
608
|
+
semanticLines.push(block(`html:not(.dark) ${scopeClass}`, generateHairlineDecls(input.tokens, "light")));
|
|
609
|
+
semanticLines.push("");
|
|
610
|
+
semanticLines.push(sectionComment2("Intent aliases (dark) \u2014 manual toggle"));
|
|
611
|
+
semanticLines.push(block(`.dark ${scopeClass}`, generateIntentDecls(input.tokens, "dark")));
|
|
612
|
+
semanticLines.push("");
|
|
613
|
+
semanticLines.push(sectionComment2("Hairline aliases (dark) \u2014 manual toggle"));
|
|
614
|
+
semanticLines.push(block(`.dark ${scopeClass}`, generateHairlineDecls(input.tokens, "dark")));
|
|
615
|
+
semanticLines.push("");
|
|
616
|
+
semanticLines.push(sectionComment2("Intent aliases (dark) \u2014 prefers-color-scheme"));
|
|
617
|
+
{
|
|
618
|
+
const inner = block(`${scopeClass}:not(.light)`, generateIntentDecls(input.tokens, "dark"));
|
|
619
|
+
semanticLines.push(`@media (prefers-color-scheme: dark) {
|
|
620
|
+
${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
621
|
+
}`);
|
|
622
|
+
}
|
|
623
|
+
semanticLines.push("");
|
|
624
|
+
semanticLines.push(sectionComment2("Hairline aliases (dark) \u2014 prefers-color-scheme"));
|
|
625
|
+
{
|
|
626
|
+
const inner = block(`${scopeClass}:not(.light)`, generateHairlineDecls(input.tokens, "dark"));
|
|
627
|
+
semanticLines.push(`@media (prefers-color-scheme: dark) {
|
|
628
|
+
${inner.split("\n").map((l) => ` ${l}`).join("\n")}
|
|
629
|
+
}`);
|
|
630
|
+
}
|
|
631
|
+
semanticLines.push("");
|
|
632
|
+
const adaptiveLayer = wrapInLayer("visor-adaptive", lines.join("\n").trim());
|
|
633
|
+
const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
|
|
593
634
|
const head = fontLines.length > 0 ? fontLines.join("\n") + "\n" : "";
|
|
594
|
-
return head + LAYER_ORDER + "\n\n" +
|
|
635
|
+
return head + LAYER_ORDER + "\n\n" + semanticLayer + "\n\n" + adaptiveLayer + "\n";
|
|
595
636
|
}
|
|
596
637
|
|
|
597
638
|
// src/flutter/color-to-dart.ts
|
|
@@ -179,11 +179,13 @@ var WEIGHT_NAMES = {
|
|
|
179
179
|
800: "ExtraBold",
|
|
180
180
|
900: "Black"
|
|
181
181
|
};
|
|
182
|
-
function buildVisorFontUrl(org, family, weight) {
|
|
182
|
+
function buildVisorFontUrl(org, family, weight, cdnBase) {
|
|
183
|
+
const base = cdnBase ?? VISOR_FONTS_CDN;
|
|
183
184
|
const slug = buildFamilySlug(family);
|
|
184
185
|
const prefix = buildFamilyPrefix(family);
|
|
185
186
|
const weightName = lookupFontWeightAlias(family, weight) ?? WEIGHT_NAMES[weight] ?? `W${weight}`;
|
|
186
|
-
|
|
187
|
+
const orgSegment = org ? `/${org}` : "";
|
|
188
|
+
return `${base}${orgSegment}/${slug}/${prefix}-${weightName}.woff2`;
|
|
187
189
|
}
|
|
188
190
|
function buildFontshareCssUrl(family, weights, italic, display) {
|
|
189
191
|
const slug = buildFamilySlug(family);
|
|
@@ -210,7 +212,8 @@ function resolveFont(family, options = {}) {
|
|
|
210
212
|
display,
|
|
211
213
|
category: options.category ?? "sans-serif",
|
|
212
214
|
guidance: null,
|
|
213
|
-
org: options.org ?? null
|
|
215
|
+
org: options.org ?? null,
|
|
216
|
+
cdnBase: options.cdnBase ?? null
|
|
214
217
|
};
|
|
215
218
|
}
|
|
216
219
|
if (explicitSource === "fontshare") {
|
|
@@ -224,7 +227,8 @@ function resolveFont(family, options = {}) {
|
|
|
224
227
|
display,
|
|
225
228
|
category: options.category ?? "sans-serif",
|
|
226
229
|
guidance: null,
|
|
227
|
-
org: null
|
|
230
|
+
org: null,
|
|
231
|
+
cdnBase: null
|
|
228
232
|
};
|
|
229
233
|
}
|
|
230
234
|
if (explicitSource === "local") {
|
|
@@ -240,7 +244,8 @@ function resolveFont(family, options = {}) {
|
|
|
240
244
|
1. Add the font files (.woff2) to your project's public/fonts/ directory
|
|
241
245
|
2. Create @font-face declarations in your theme CSS
|
|
242
246
|
3. Reference the font family in your theme's --font-display or --font-body token`,
|
|
243
|
-
org: null
|
|
247
|
+
org: null,
|
|
248
|
+
cdnBase: null
|
|
244
249
|
};
|
|
245
250
|
}
|
|
246
251
|
const catalogEntry = lookupGoogleFont(family);
|
|
@@ -267,7 +272,8 @@ function resolveFont(family, options = {}) {
|
|
|
267
272
|
display,
|
|
268
273
|
category: catalogEntry.category,
|
|
269
274
|
guidance: null,
|
|
270
|
-
org: null
|
|
275
|
+
org: null,
|
|
276
|
+
cdnBase: null
|
|
271
277
|
};
|
|
272
278
|
}
|
|
273
279
|
return {
|
|
@@ -282,7 +288,8 @@ function resolveFont(family, options = {}) {
|
|
|
282
288
|
1. Add the font files (.woff2) to your project's public/fonts/ directory
|
|
283
289
|
2. Create @font-face declarations in your theme CSS
|
|
284
290
|
3. Reference the font family in your theme's --font-display or --font-body token`,
|
|
285
|
-
org: null
|
|
291
|
+
org: null,
|
|
292
|
+
cdnBase: null
|
|
286
293
|
};
|
|
287
294
|
}
|
|
288
295
|
|
|
@@ -307,19 +314,29 @@ function generatePreloadLinks(resolutions, customFontPaths) {
|
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
315
|
-
for (const resolution of
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
317
|
+
const visorFontResolutions = resolutions.filter(
|
|
318
|
+
(r) => r.source === "visor-fonts"
|
|
319
|
+
);
|
|
320
|
+
if (visorFontResolutions.length > 0) {
|
|
321
|
+
const cdnBases = /* @__PURE__ */ new Set();
|
|
322
|
+
for (const resolution of visorFontResolutions) {
|
|
323
|
+
cdnBases.add(resolution.cdnBase ?? VISOR_FONTS_CDN);
|
|
324
|
+
}
|
|
325
|
+
for (const base of cdnBases) {
|
|
326
|
+
links.push(`<link rel="preconnect" href="${base}" crossorigin>`);
|
|
327
|
+
}
|
|
328
|
+
for (const resolution of visorFontResolutions) {
|
|
329
|
+
if (resolution.org === null && resolution.cdnBase === null) continue;
|
|
330
|
+
for (const weight of resolution.weights) {
|
|
331
|
+
const url = buildVisorFontUrl(
|
|
332
|
+
resolution.org ?? "",
|
|
333
|
+
resolution.family,
|
|
334
|
+
weight,
|
|
335
|
+
resolution.cdnBase
|
|
336
|
+
);
|
|
337
|
+
links.push(
|
|
338
|
+
`<link rel="preload" as="font" type="font/woff2" href="${url}" crossorigin>`
|
|
339
|
+
);
|
|
323
340
|
}
|
|
324
341
|
}
|
|
325
342
|
}
|
|
@@ -402,7 +419,7 @@ function generateFontCSS(heading, displayFont, body, mono, typography) {
|
|
|
402
419
|
if (seenVisorFonts.has(font.family)) continue;
|
|
403
420
|
seenVisorFonts.add(font.family);
|
|
404
421
|
for (const weight of font.weights) {
|
|
405
|
-
const url = buildVisorFontUrl(font.org ?? "", font.family, weight);
|
|
422
|
+
const url = buildVisorFontUrl(font.org ?? "", font.family, weight, font.cdnBase);
|
|
406
423
|
lines.push(`@font-face {`);
|
|
407
424
|
lines.push(` font-family: "${font.family}";`);
|
|
408
425
|
lines.push(` src: url("${url}") format("woff2");`);
|
|
@@ -542,6 +559,8 @@ function getSizeAdjust(category) {
|
|
|
542
559
|
function resolveThemeFonts(typography, options) {
|
|
543
560
|
const display = options?.display ?? "swap";
|
|
544
561
|
const warnings = [];
|
|
562
|
+
const cdnOverrides = typography["cdn-overrides"];
|
|
563
|
+
const visorFontsCdnBase = cdnOverrides?.["visor-fonts"];
|
|
545
564
|
let headingResolution = null;
|
|
546
565
|
if (typography.heading?.family) {
|
|
547
566
|
const weights = typography.heading.weights ? [...typography.heading.weights] : typography.heading.weight ? [typography.heading.weight] : [];
|
|
@@ -549,7 +568,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
549
568
|
weights: weights.length > 0 ? weights : void 0,
|
|
550
569
|
display,
|
|
551
570
|
source: typography.heading.source,
|
|
552
|
-
org: typography.heading.org
|
|
571
|
+
org: typography.heading.org,
|
|
572
|
+
cdnBase: visorFontsCdnBase
|
|
553
573
|
});
|
|
554
574
|
if (headingResolution.guidance) {
|
|
555
575
|
warnings.push(headingResolution.guidance);
|
|
@@ -574,7 +594,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
574
594
|
weights: mergedWeights,
|
|
575
595
|
display,
|
|
576
596
|
source: typography.heading.source,
|
|
577
|
-
org: typography.heading.org
|
|
597
|
+
org: typography.heading.org,
|
|
598
|
+
cdnBase: visorFontsCdnBase
|
|
578
599
|
});
|
|
579
600
|
bodyResolution = headingResolution;
|
|
580
601
|
} else {
|
|
@@ -582,7 +603,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
582
603
|
weights: bodyWeights.length > 0 ? bodyWeights : void 0,
|
|
583
604
|
display,
|
|
584
605
|
source: typography.body.source,
|
|
585
|
-
org: typography.body.org
|
|
606
|
+
org: typography.body.org,
|
|
607
|
+
cdnBase: visorFontsCdnBase
|
|
586
608
|
});
|
|
587
609
|
if (bodyResolution.guidance) {
|
|
588
610
|
warnings.push(bodyResolution.guidance);
|
|
@@ -603,7 +625,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
603
625
|
weights: mergedWeights,
|
|
604
626
|
display,
|
|
605
627
|
source: typography.heading.source,
|
|
606
|
-
org: typography.heading.org
|
|
628
|
+
org: typography.heading.org,
|
|
629
|
+
cdnBase: visorFontsCdnBase
|
|
607
630
|
});
|
|
608
631
|
displayResolution = headingResolution;
|
|
609
632
|
}
|
|
@@ -615,7 +638,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
615
638
|
weights: mergedWeights,
|
|
616
639
|
display,
|
|
617
640
|
source: typography.body.source,
|
|
618
|
-
org: typography.body.org
|
|
641
|
+
org: typography.body.org,
|
|
642
|
+
cdnBase: visorFontsCdnBase
|
|
619
643
|
});
|
|
620
644
|
displayResolution = bodyResolution;
|
|
621
645
|
} else {
|
|
@@ -623,7 +647,8 @@ function resolveThemeFonts(typography, options) {
|
|
|
623
647
|
weights: displayWeights.length > 0 ? displayWeights : void 0,
|
|
624
648
|
display,
|
|
625
649
|
source: typography.display.source,
|
|
626
|
-
org: typography.display.org
|
|
650
|
+
org: typography.display.org,
|
|
651
|
+
cdnBase: visorFontsCdnBase
|
|
627
652
|
});
|
|
628
653
|
if (displayResolution.guidance) {
|
|
629
654
|
warnings.push(displayResolution.guidance);
|
|
@@ -655,6 +680,7 @@ function resolveThemeFonts(typography, options) {
|
|
|
655
680
|
display,
|
|
656
681
|
source: monoSource,
|
|
657
682
|
org: monoOrg,
|
|
683
|
+
cdnBase: visorFontsCdnBase,
|
|
658
684
|
category: "monospace"
|
|
659
685
|
});
|
|
660
686
|
if (monoResolution.guidance) {
|
|
@@ -1298,6 +1324,9 @@ function generateMotionPrimitives(config) {
|
|
|
1298
1324
|
decls.push(
|
|
1299
1325
|
"--motion-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);"
|
|
1300
1326
|
);
|
|
1327
|
+
if (config.motion["easing-overshoot"]) {
|
|
1328
|
+
decls.push(`--motion-easing-overshoot: ${config.motion["easing-overshoot"]};`);
|
|
1329
|
+
}
|
|
1301
1330
|
return decls;
|
|
1302
1331
|
}
|
|
1303
1332
|
function generateMiscPrimitives() {
|
|
@@ -1343,6 +1372,9 @@ function generatePrimitivesCss(primitives, config, options) {
|
|
|
1343
1372
|
lines.push(block(host, generateMiscPrimitives()));
|
|
1344
1373
|
return header("Visor Theme \u2014 Primitives") + lines.join("\n");
|
|
1345
1374
|
}
|
|
1375
|
+
function hairlineDeclName(name) {
|
|
1376
|
+
return name === "default" ? "--hairline" : `--hairline-${name}`;
|
|
1377
|
+
}
|
|
1346
1378
|
function generateSemanticCss(tokens) {
|
|
1347
1379
|
const lines = [];
|
|
1348
1380
|
lines.push(sectionComment("Semantic: Text"));
|
|
@@ -1365,8 +1397,61 @@ function generateSemanticCss(tokens) {
|
|
|
1365
1397
|
([name, { light }]) => `--interactive-${name}: ${light};`
|
|
1366
1398
|
);
|
|
1367
1399
|
lines.push(block(":root", interactiveDecls));
|
|
1400
|
+
lines.push(sectionComment("Semantic: Intent (aliases)"));
|
|
1401
|
+
const intentDecls = Object.entries(tokens.intent).map(
|
|
1402
|
+
([name, { light }]) => `--${name}: ${light};`
|
|
1403
|
+
);
|
|
1404
|
+
lines.push(block(":root", intentDecls));
|
|
1405
|
+
lines.push(sectionComment("Semantic: Hairline (aliases)"));
|
|
1406
|
+
const hairlineDecls = Object.entries(tokens.hairline).map(
|
|
1407
|
+
([name, { light }]) => `${hairlineDeclName(name)}: ${light};`
|
|
1408
|
+
);
|
|
1409
|
+
lines.push(block(":root", hairlineDecls));
|
|
1368
1410
|
return header("Visor Theme \u2014 Semantic") + lines.join("\n");
|
|
1369
1411
|
}
|
|
1412
|
+
var TEXT_SCALE_ALIASES = [
|
|
1413
|
+
11,
|
|
1414
|
+
13,
|
|
1415
|
+
14,
|
|
1416
|
+
16,
|
|
1417
|
+
20,
|
|
1418
|
+
24,
|
|
1419
|
+
32,
|
|
1420
|
+
40,
|
|
1421
|
+
48
|
|
1422
|
+
];
|
|
1423
|
+
function generateTextScaleAliasDecls() {
|
|
1424
|
+
return TEXT_SCALE_ALIASES.map((px) => `--text-${px}: ${px}px;`);
|
|
1425
|
+
}
|
|
1426
|
+
var SPACE_ALIAS_MULTIPLIERS = [
|
|
1427
|
+
1,
|
|
1428
|
+
2,
|
|
1429
|
+
3,
|
|
1430
|
+
4,
|
|
1431
|
+
5,
|
|
1432
|
+
6,
|
|
1433
|
+
8,
|
|
1434
|
+
10,
|
|
1435
|
+
12,
|
|
1436
|
+
16
|
|
1437
|
+
];
|
|
1438
|
+
function generateSpaceAliasDecls(config) {
|
|
1439
|
+
const base = config.spacing.base;
|
|
1440
|
+
return SPACE_ALIAS_MULTIPLIERS.map((m) => {
|
|
1441
|
+
const px = base * m;
|
|
1442
|
+
return `--space-${m}: ${px}px;`;
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
function generateIntentDecls(tokens, mode) {
|
|
1446
|
+
return Object.entries(tokens.intent).map(
|
|
1447
|
+
([name, value]) => `--${name}: ${value[mode]};`
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
function generateHairlineDecls(tokens, mode) {
|
|
1451
|
+
return Object.entries(tokens.hairline).map(
|
|
1452
|
+
([name, value]) => `${hairlineDeclName(name)}: ${value[mode]};`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1370
1455
|
function buildAdaptiveDecls(tokens, theme) {
|
|
1371
1456
|
const textDecls = Object.entries(tokens.text).map(
|
|
1372
1457
|
([name, values]) => `--text-${name}: ${values[theme]};`
|
|
@@ -1380,11 +1465,13 @@ function buildAdaptiveDecls(tokens, theme) {
|
|
|
1380
1465
|
const interactiveDecls = Object.entries(tokens.interactive).map(
|
|
1381
1466
|
([name, values]) => `--interactive-${name}: ${values[theme]};`
|
|
1382
1467
|
);
|
|
1383
|
-
|
|
1468
|
+
const intentDecls = generateIntentDecls(tokens, theme);
|
|
1469
|
+
const hairlineDecls = generateHairlineDecls(tokens, theme);
|
|
1470
|
+
return { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls };
|
|
1384
1471
|
}
|
|
1385
1472
|
function generateLightCss(tokens, options) {
|
|
1386
1473
|
const lines = [];
|
|
1387
|
-
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1474
|
+
const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "light");
|
|
1388
1475
|
const host = options?.scopePrefix ?? ":root";
|
|
1389
1476
|
lines.push(sectionComment("Adaptive: Text (light)"));
|
|
1390
1477
|
lines.push(block(host, textDecls));
|
|
@@ -1394,11 +1481,15 @@ function generateLightCss(tokens, options) {
|
|
|
1394
1481
|
lines.push(block(host, borderDecls));
|
|
1395
1482
|
lines.push(sectionComment("Adaptive: Interactive (light)"));
|
|
1396
1483
|
lines.push(block(host, interactiveDecls));
|
|
1484
|
+
lines.push(sectionComment("Adaptive: Intent aliases (light)"));
|
|
1485
|
+
lines.push(block(host, intentDecls));
|
|
1486
|
+
lines.push(sectionComment("Adaptive: Hairline aliases (light)"));
|
|
1487
|
+
lines.push(block(host, hairlineDecls));
|
|
1397
1488
|
return header("Visor Theme \u2014 Light") + lines.join("\n");
|
|
1398
1489
|
}
|
|
1399
1490
|
function generateDarkCss(tokens, options) {
|
|
1400
1491
|
const lines = [];
|
|
1401
|
-
const { textDecls, surfaceDecls, borderDecls, interactiveDecls } = buildAdaptiveDecls(tokens, "dark");
|
|
1492
|
+
const { textDecls, surfaceDecls, borderDecls, interactiveDecls, intentDecls, hairlineDecls } = buildAdaptiveDecls(tokens, "dark");
|
|
1402
1493
|
const prefix = options?.scopePrefix;
|
|
1403
1494
|
const darkSelectors = prefix ? [`${prefix}.dark`, `${prefix}.theme-dark`, `${prefix}[data-theme="dark"]`] : [".dark", ".theme-dark", '[data-theme="dark"]'];
|
|
1404
1495
|
const darkSelector = darkSelectors.join(",\n");
|
|
@@ -1411,6 +1502,10 @@ function generateDarkCss(tokens, options) {
|
|
|
1411
1502
|
lines.push(block(darkSelector, borderDecls));
|
|
1412
1503
|
lines.push(sectionComment("Adaptive: Interactive (dark) \u2014 manual toggle"));
|
|
1413
1504
|
lines.push(block(darkSelector, interactiveDecls));
|
|
1505
|
+
lines.push(sectionComment("Adaptive: Intent aliases (dark) \u2014 manual toggle"));
|
|
1506
|
+
lines.push(block(darkSelector, intentDecls));
|
|
1507
|
+
lines.push(sectionComment("Adaptive: Hairline aliases (dark) \u2014 manual toggle"));
|
|
1508
|
+
lines.push(block(darkSelector, hairlineDecls));
|
|
1414
1509
|
lines.push(
|
|
1415
1510
|
sectionComment("Adaptive: Text (dark) \u2014 prefers-color-scheme")
|
|
1416
1511
|
);
|
|
@@ -1441,6 +1536,22 @@ ${block(prefersSelector, borderDecls)}}`
|
|
|
1441
1536
|
lines.push(
|
|
1442
1537
|
`@media (prefers-color-scheme: dark) {
|
|
1443
1538
|
${block(prefersSelector, interactiveDecls)}}`
|
|
1539
|
+
);
|
|
1540
|
+
lines.push("");
|
|
1541
|
+
lines.push(
|
|
1542
|
+
sectionComment("Adaptive: Intent aliases (dark) \u2014 prefers-color-scheme")
|
|
1543
|
+
);
|
|
1544
|
+
lines.push(
|
|
1545
|
+
`@media (prefers-color-scheme: dark) {
|
|
1546
|
+
${block(prefersSelector, intentDecls)}}`
|
|
1547
|
+
);
|
|
1548
|
+
lines.push("");
|
|
1549
|
+
lines.push(
|
|
1550
|
+
sectionComment("Adaptive: Hairline aliases (dark) \u2014 prefers-color-scheme")
|
|
1551
|
+
);
|
|
1552
|
+
lines.push(
|
|
1553
|
+
`@media (prefers-color-scheme: dark) {
|
|
1554
|
+
${block(prefersSelector, hairlineDecls)}}`
|
|
1444
1555
|
);
|
|
1445
1556
|
lines.push("");
|
|
1446
1557
|
return header("Visor Theme \u2014 Dark") + lines.join("\n");
|
|
@@ -1535,6 +1646,10 @@ export {
|
|
|
1535
1646
|
sectionComment,
|
|
1536
1647
|
generatePrimitivesCss,
|
|
1537
1648
|
generateSemanticCss,
|
|
1649
|
+
generateTextScaleAliasDecls,
|
|
1650
|
+
generateSpaceAliasDecls,
|
|
1651
|
+
generateIntentDecls,
|
|
1652
|
+
generateHairlineDecls,
|
|
1538
1653
|
generateLightCss,
|
|
1539
1654
|
generateDarkCss,
|
|
1540
1655
|
generateFullBundleCss
|
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-Dwc1V0Nc.js';
|
|
2
|
+
export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue } from './types-Dwc1V0Nc.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Font resolver — maps font family names to loadable font resources.
|
|
@@ -16,7 +16,17 @@ export { k as ColorFormat, l as FontSource, m as RGBA, n as SemanticTokenValue }
|
|
|
16
16
|
* Filename: original uploaded name (e.g., PPModelPlastic-Regular).
|
|
17
17
|
*/
|
|
18
18
|
declare const VISOR_FONTS_CDN = "https://fonts.visor.design";
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Build a visor-fonts URL.
|
|
21
|
+
*
|
|
22
|
+
* Default pattern: `https://fonts.visor.design/{org}/{slug}/{prefix}-{weight}.woff2`
|
|
23
|
+
*
|
|
24
|
+
* When `cdnBase` is provided, that base replaces `VISOR_FONTS_CDN`. When
|
|
25
|
+
* `org` is empty (theme-level CDN override already encodes the namespace,
|
|
26
|
+
* e.g. `https://fonts.knowmentum.ai`), the org segment is dropped:
|
|
27
|
+
* `{cdnBase}/{slug}/{prefix}-{weight}.woff2`.
|
|
28
|
+
*/
|
|
29
|
+
declare function buildVisorFontUrl(org: string, family: string, weight: number, cdnBase?: string | null): string;
|
|
20
30
|
/**
|
|
21
31
|
* Resolve a font family name to a FontResolution.
|
|
22
32
|
*
|
|
@@ -422,6 +432,18 @@ var properties = {
|
|
|
422
432
|
}
|
|
423
433
|
}
|
|
424
434
|
},
|
|
435
|
+
"cdn-overrides": {
|
|
436
|
+
type: "object",
|
|
437
|
+
description: "Per-source CDN base URL overrides. When set, the override CDN replaces the default for every slot whose source matches the key. The per-slot org may be empty when the override CDN already encodes the project namespace (e.g., https://fonts.knowmentum.ai).",
|
|
438
|
+
additionalProperties: false,
|
|
439
|
+
properties: {
|
|
440
|
+
"visor-fonts": {
|
|
441
|
+
type: "string",
|
|
442
|
+
minLength: 1,
|
|
443
|
+
description: "CDN base URL that replaces fonts.visor.design for source: visor-fonts slots (e.g., https://fonts.knowmentum.ai)."
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
425
447
|
slots: {
|
|
426
448
|
type: "object",
|
|
427
449
|
description: "Per-slot overrides for the generated Flutter Material TextTheme. Any subset of the 16 slots may be specified; omitted slots fall through to the Material 3 2024 defaults shipped in visor_core.",
|
|
@@ -876,6 +898,8 @@ declare const SEMANTIC_MAP: {
|
|
|
876
898
|
surface: Record<string, SemanticMapping>;
|
|
877
899
|
border: Record<string, SemanticMapping>;
|
|
878
900
|
interactive: Record<string, SemanticMapping>;
|
|
901
|
+
intent: Record<string, SemanticMapping>;
|
|
902
|
+
hairline: Record<string, SemanticMapping>;
|
|
879
903
|
};
|
|
880
904
|
|
|
881
905
|
/**
|
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-43TVIXUS.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;
|
|
@@ -381,6 +381,18 @@ var visor_theme_schema_default = {
|
|
|
381
381
|
wide: { type: "string" }
|
|
382
382
|
}
|
|
383
383
|
},
|
|
384
|
+
"cdn-overrides": {
|
|
385
|
+
type: "object",
|
|
386
|
+
description: "Per-source CDN base URL overrides. When set, the override CDN replaces the default for every slot whose source matches the key. The per-slot org may be empty when the override CDN already encodes the project namespace (e.g., https://fonts.knowmentum.ai).",
|
|
387
|
+
additionalProperties: false,
|
|
388
|
+
properties: {
|
|
389
|
+
"visor-fonts": {
|
|
390
|
+
type: "string",
|
|
391
|
+
minLength: 1,
|
|
392
|
+
description: "CDN base URL that replaces fonts.visor.design for source: visor-fonts slots (e.g., https://fonts.knowmentum.ai)."
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
384
396
|
slots: {
|
|
385
397
|
type: "object",
|
|
386
398
|
description: "Per-slot overrides for the generated Flutter Material TextTheme. Any subset of the 16 slots may be specified; omitted slots fall through to the Material 3 2024 defaults shipped in visor_core.",
|
|
@@ -580,8 +592,10 @@ var KNOWN_TYPOGRAPHY_KEYS = /* @__PURE__ */ new Set([
|
|
|
580
592
|
"mono",
|
|
581
593
|
"letter-spacing",
|
|
582
594
|
"scale",
|
|
583
|
-
"slots"
|
|
595
|
+
"slots",
|
|
596
|
+
"cdn-overrides"
|
|
584
597
|
]);
|
|
598
|
+
var KNOWN_CDN_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["visor-fonts"]);
|
|
585
599
|
var KNOWN_TYPOGRAPHY_FONT_KEYS = /* @__PURE__ */ new Set(["family", "weight", "weights", "source", "org"]);
|
|
586
600
|
var KNOWN_TYPOGRAPHY_MONO_KEYS = /* @__PURE__ */ new Set(["family", "weight", "weights", "source", "org"]);
|
|
587
601
|
var KNOWN_LETTER_SPACING_KEYS = /* @__PURE__ */ new Set(["tight", "normal", "wide"]);
|
|
@@ -591,7 +605,7 @@ var KNOWN_SPACING_KEYS = /* @__PURE__ */ new Set(["base"]);
|
|
|
591
605
|
var KNOWN_RADIUS_KEYS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "pill"]);
|
|
592
606
|
var KNOWN_SHADOW_KEYS = /* @__PURE__ */ new Set(["xs", "sm", "md", "lg", "xl"]);
|
|
593
607
|
var KNOWN_STROKE_WIDTH_KEYS = /* @__PURE__ */ new Set(["thin", "regular", "medium", "thick"]);
|
|
594
|
-
var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing"]);
|
|
608
|
+
var KNOWN_MOTION_KEYS = /* @__PURE__ */ new Set(["duration-fast", "duration-normal", "duration-slow", "easing", "easing-overshoot"]);
|
|
595
609
|
var KNOWN_OVERRIDES_KEYS = /* @__PURE__ */ new Set(["light", "dark"]);
|
|
596
610
|
function checkUnknownKeys(obj, errors) {
|
|
597
611
|
for (const key of Object.keys(obj)) {
|
|
@@ -648,6 +662,13 @@ function checkUnknownKeys(obj, errors) {
|
|
|
648
662
|
}
|
|
649
663
|
}
|
|
650
664
|
}
|
|
665
|
+
if (typeof typo["cdn-overrides"] === "object" && typo["cdn-overrides"] !== null) {
|
|
666
|
+
for (const key of Object.keys(typo["cdn-overrides"])) {
|
|
667
|
+
if (!KNOWN_CDN_OVERRIDE_KEYS.has(key)) {
|
|
668
|
+
errors.push(`Unknown key 'typography.cdn-overrides.${key}'. Valid keys: ${[...KNOWN_CDN_OVERRIDE_KEYS].join(", ")}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
651
672
|
if (typeof typo["letter-spacing"] === "object" && typo["letter-spacing"] !== null) {
|
|
652
673
|
for (const key of Object.keys(typo["letter-spacing"])) {
|
|
653
674
|
if (!KNOWN_LETTER_SPACING_KEYS.has(key)) {
|
|
@@ -798,10 +819,19 @@ function validateConfig(config) {
|
|
|
798
819
|
}
|
|
799
820
|
if (typeof obj.typography === "object" && obj.typography !== null) {
|
|
800
821
|
const typo = obj.typography;
|
|
822
|
+
const cdnOverrides = typo["cdn-overrides"];
|
|
823
|
+
const visorFontsOverride = cdnOverrides?.["visor-fonts"];
|
|
824
|
+
if (visorFontsOverride !== void 0 && typeof visorFontsOverride !== "string") {
|
|
825
|
+
errors.push(`'typography.cdn-overrides.visor-fonts' must be a string URL`);
|
|
826
|
+
}
|
|
827
|
+
if (typeof visorFontsOverride === "string" && visorFontsOverride.length === 0) {
|
|
828
|
+
errors.push(`'typography.cdn-overrides.visor-fonts' must not be empty`);
|
|
829
|
+
}
|
|
830
|
+
const orgOptional = typeof visorFontsOverride === "string" && visorFontsOverride.length > 0;
|
|
801
831
|
for (const slot of ["heading", "display", "body"]) {
|
|
802
832
|
const font = typo[slot];
|
|
803
|
-
if (font && font.source === "visor-fonts" && !font.org) {
|
|
804
|
-
errors.push(`'typography.${slot}.org' is required when source is 'visor-fonts'`);
|
|
833
|
+
if (font && font.source === "visor-fonts" && !orgOptional && !font.org) {
|
|
834
|
+
errors.push(`'typography.${slot}.org' is required when source is 'visor-fonts' (unless typography.cdn-overrides.visor-fonts is set)`);
|
|
805
835
|
}
|
|
806
836
|
if (font && font.weights !== void 0) {
|
|
807
837
|
if (!Array.isArray(font.weights) || !font.weights.every((w) => typeof w === "number" && w > 0)) {
|
|
@@ -962,6 +992,9 @@ function resolveConfig(config) {
|
|
|
962
992
|
...config.typography?.mono?.source && { source: config.typography.mono.source },
|
|
963
993
|
...config.typography?.mono?.org && { org: config.typography.mono.org }
|
|
964
994
|
},
|
|
995
|
+
...config.typography?.["cdn-overrides"] && {
|
|
996
|
+
"cdn-overrides": config.typography["cdn-overrides"]
|
|
997
|
+
},
|
|
965
998
|
slots: config.typography?.slots ?? {}
|
|
966
999
|
},
|
|
967
1000
|
spacing: {
|
|
@@ -991,7 +1024,10 @@ function resolveConfig(config) {
|
|
|
991
1024
|
"duration-fast": config.motion?.["duration-fast"] ?? DEFAULTS.motion["duration-fast"],
|
|
992
1025
|
"duration-normal": config.motion?.["duration-normal"] ?? DEFAULTS.motion["duration-normal"],
|
|
993
1026
|
"duration-slow": config.motion?.["duration-slow"] ?? DEFAULTS.motion["duration-slow"],
|
|
994
|
-
easing: config.motion?.easing ?? DEFAULTS.motion.easing
|
|
1027
|
+
easing: config.motion?.easing ?? DEFAULTS.motion.easing,
|
|
1028
|
+
// VI-451 (drive-by): pass through opt-in bouncy easing when set; absent
|
|
1029
|
+
// themes leave this undefined and fall through to --motion-easing-spring.
|
|
1030
|
+
...config.motion?.["easing-overshoot"] !== void 0 ? { "easing-overshoot": config.motion["easing-overshoot"] } : {}
|
|
995
1031
|
},
|
|
996
1032
|
overrides: config.overrides,
|
|
997
1033
|
originalColors,
|
|
@@ -1024,6 +1060,11 @@ var SEMANTIC_TEXT_MAP = {
|
|
|
1024
1060
|
light: { role: "neutral", shade: 600 },
|
|
1025
1061
|
dark: { role: "neutral", shade: 400 }
|
|
1026
1062
|
},
|
|
1063
|
+
// VI-451: deemphasized but readable — slots between tertiary and disabled.
|
|
1064
|
+
muted: {
|
|
1065
|
+
light: { role: "neutral", shade: 500 },
|
|
1066
|
+
dark: { role: "neutral", shade: 500 }
|
|
1067
|
+
},
|
|
1027
1068
|
disabled: {
|
|
1028
1069
|
light: { role: "neutral", shade: 300 },
|
|
1029
1070
|
dark: { role: "neutral", shade: 600 }
|
|
@@ -1084,6 +1125,16 @@ var SEMANTIC_SURFACE_MAP = {
|
|
|
1084
1125
|
light: { role: "neutral", shade: 100 },
|
|
1085
1126
|
dark: { role: "neutral", shade: 700 }
|
|
1086
1127
|
},
|
|
1128
|
+
// VI-451: deeper than page — used by admin-ui chrome (deepest backdrop).
|
|
1129
|
+
screen: {
|
|
1130
|
+
light: { role: "neutral", shade: 50 },
|
|
1131
|
+
dark: { role: "neutral", shade: 950 }
|
|
1132
|
+
},
|
|
1133
|
+
// VI-451: singular elevation alias, distinct from elev-0..4. Defaults to mid (elev-2).
|
|
1134
|
+
elev: {
|
|
1135
|
+
light: { role: "neutral", shade: 100 },
|
|
1136
|
+
dark: { role: "neutral", shade: 800 }
|
|
1137
|
+
},
|
|
1087
1138
|
overlay: {
|
|
1088
1139
|
light: { role: "neutral", shade: 900 },
|
|
1089
1140
|
dark: { role: "neutral", shade: 950 }
|
|
@@ -1278,11 +1329,56 @@ var SEMANTIC_INTERACTIVE_MAP = {
|
|
|
1278
1329
|
dark: { role: "neutral", shade: 700 }
|
|
1279
1330
|
}
|
|
1280
1331
|
};
|
|
1332
|
+
var SEMANTIC_INTENT_MAP = {
|
|
1333
|
+
primary: {
|
|
1334
|
+
light: { role: "primary", shade: 500 },
|
|
1335
|
+
dark: { role: "primary", shade: 500 }
|
|
1336
|
+
},
|
|
1337
|
+
// Text color paired with --primary backgrounds. Default white; themes whose
|
|
1338
|
+
// primary fails AA on white pin to a graphite via overrides (entr does this).
|
|
1339
|
+
"primary-text": {
|
|
1340
|
+
light: { constant: "#ffffff" },
|
|
1341
|
+
dark: { constant: "#ffffff" }
|
|
1342
|
+
},
|
|
1343
|
+
accent: {
|
|
1344
|
+
light: { role: "accent", shade: 500 },
|
|
1345
|
+
dark: { role: "accent", shade: 500 }
|
|
1346
|
+
},
|
|
1347
|
+
success: {
|
|
1348
|
+
light: { role: "success", shade: 500 },
|
|
1349
|
+
dark: { role: "success", shade: 500 }
|
|
1350
|
+
},
|
|
1351
|
+
warning: {
|
|
1352
|
+
light: { role: "warning", shade: 500 },
|
|
1353
|
+
dark: { role: "warning", shade: 500 }
|
|
1354
|
+
},
|
|
1355
|
+
// shadcn naming — aliases the `error` role.
|
|
1356
|
+
destructive: {
|
|
1357
|
+
light: { role: "error", shade: 500 },
|
|
1358
|
+
dark: { role: "error", shade: 500 }
|
|
1359
|
+
},
|
|
1360
|
+
info: {
|
|
1361
|
+
light: { role: "info", shade: 500 },
|
|
1362
|
+
dark: { role: "info", shade: 500 }
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
var SEMANTIC_HAIRLINE_MAP = {
|
|
1366
|
+
default: {
|
|
1367
|
+
light: { constant: "rgba(0, 0, 0, 0.06)" },
|
|
1368
|
+
dark: { constant: "rgba(255, 255, 255, 0.06)" }
|
|
1369
|
+
},
|
|
1370
|
+
strong: {
|
|
1371
|
+
light: { constant: "rgba(0, 0, 0, 0.10)" },
|
|
1372
|
+
dark: { constant: "rgba(255, 255, 255, 0.10)" }
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1281
1375
|
var SEMANTIC_MAP = {
|
|
1282
1376
|
text: SEMANTIC_TEXT_MAP,
|
|
1283
1377
|
surface: SEMANTIC_SURFACE_MAP,
|
|
1284
1378
|
border: SEMANTIC_BORDER_MAP,
|
|
1285
|
-
interactive: SEMANTIC_INTERACTIVE_MAP
|
|
1379
|
+
interactive: SEMANTIC_INTERACTIVE_MAP,
|
|
1380
|
+
intent: SEMANTIC_INTENT_MAP,
|
|
1381
|
+
hairline: SEMANTIC_HAIRLINE_MAP
|
|
1286
1382
|
};
|
|
1287
1383
|
|
|
1288
1384
|
// src/assign.ts
|
|
@@ -1324,6 +1420,8 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
|
|
|
1324
1420
|
const surface = {};
|
|
1325
1421
|
const border = {};
|
|
1326
1422
|
const interactive = {};
|
|
1423
|
+
const intent = {};
|
|
1424
|
+
const hairline = {};
|
|
1327
1425
|
for (const [name, mapping] of Object.entries(SEMANTIC_MAP.text)) {
|
|
1328
1426
|
text[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
|
|
1329
1427
|
}
|
|
@@ -1336,7 +1434,13 @@ function assignSemanticTokens(lightPrimitives, darkPrimitives, config) {
|
|
|
1336
1434
|
for (const [name, mapping] of Object.entries(SEMANTIC_MAP.interactive)) {
|
|
1337
1435
|
interactive[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
|
|
1338
1436
|
}
|
|
1339
|
-
|
|
1437
|
+
for (const [name, mapping] of Object.entries(SEMANTIC_MAP.intent)) {
|
|
1438
|
+
intent[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
|
|
1439
|
+
}
|
|
1440
|
+
for (const [name, mapping] of Object.entries(SEMANTIC_MAP.hairline)) {
|
|
1441
|
+
hairline[name] = resolveMapping(mapping, lightPrimitives, darkPrimitives, config);
|
|
1442
|
+
}
|
|
1443
|
+
return { text, surface, border, interactive, intent, hairline };
|
|
1340
1444
|
}
|
|
1341
1445
|
|
|
1342
1446
|
// src/overrides.ts
|
|
@@ -1344,9 +1448,13 @@ var TOKEN_CATEGORIES = [
|
|
|
1344
1448
|
{ prefix: "text-", key: "text" },
|
|
1345
1449
|
{ prefix: "surface-", key: "surface" },
|
|
1346
1450
|
{ prefix: "border-", key: "border" },
|
|
1347
|
-
{ prefix: "interactive-", key: "interactive" }
|
|
1451
|
+
{ prefix: "interactive-", key: "interactive" },
|
|
1452
|
+
{ prefix: "hairline-", key: "hairline" }
|
|
1348
1453
|
];
|
|
1349
1454
|
function findToken(key, tokens) {
|
|
1455
|
+
if (key === "hairline" && "default" in tokens.hairline) {
|
|
1456
|
+
return { group: tokens.hairline, name: "default" };
|
|
1457
|
+
}
|
|
1350
1458
|
for (const { prefix, key: groupKey } of TOKEN_CATEGORIES) {
|
|
1351
1459
|
if (key.startsWith(prefix)) {
|
|
1352
1460
|
const name = key.slice(prefix.length);
|
|
@@ -1355,6 +1463,9 @@ function findToken(key, tokens) {
|
|
|
1355
1463
|
}
|
|
1356
1464
|
}
|
|
1357
1465
|
}
|
|
1466
|
+
if (key in tokens.intent) {
|
|
1467
|
+
return { group: tokens.intent, name: key };
|
|
1468
|
+
}
|
|
1358
1469
|
return null;
|
|
1359
1470
|
}
|
|
1360
1471
|
function applyOverrides(tokens, overrides) {
|
|
@@ -1363,9 +1474,11 @@ function applyOverrides(tokens, overrides) {
|
|
|
1363
1474
|
text: { ...tokens.text },
|
|
1364
1475
|
surface: { ...tokens.surface },
|
|
1365
1476
|
border: { ...tokens.border },
|
|
1366
|
-
interactive: { ...tokens.interactive }
|
|
1477
|
+
interactive: { ...tokens.interactive },
|
|
1478
|
+
intent: { ...tokens.intent },
|
|
1479
|
+
hairline: { ...tokens.hairline }
|
|
1367
1480
|
};
|
|
1368
|
-
for (const group of ["text", "surface", "border", "interactive"]) {
|
|
1481
|
+
for (const group of ["text", "surface", "border", "interactive", "intent", "hairline"]) {
|
|
1369
1482
|
for (const [name, value] of Object.entries(result[group])) {
|
|
1370
1483
|
result[group][name] = { ...value };
|
|
1371
1484
|
}
|
|
@@ -1582,10 +1695,13 @@ function exportTheme(primitives, config) {
|
|
|
1582
1695
|
const motion = {};
|
|
1583
1696
|
for (const [key, defaultVal] of Object.entries(DEFAULT_MOTION)) {
|
|
1584
1697
|
const val = config.motion[key];
|
|
1585
|
-
if (val !== defaultVal) {
|
|
1698
|
+
if (val !== void 0 && val !== defaultVal) {
|
|
1586
1699
|
motion[key] = val;
|
|
1587
1700
|
}
|
|
1588
1701
|
}
|
|
1702
|
+
if (config.motion["easing-overshoot"] !== void 0) {
|
|
1703
|
+
motion["easing-overshoot"] = config.motion["easing-overshoot"];
|
|
1704
|
+
}
|
|
1589
1705
|
if (Object.keys(motion).length > 0) {
|
|
1590
1706
|
output.motion = motion;
|
|
1591
1707
|
}
|
|
@@ -25,6 +25,8 @@ interface FontResolution {
|
|
|
25
25
|
guidance: string | null;
|
|
26
26
|
/** Organization namespace (only for visor-fonts source) */
|
|
27
27
|
org: string | null;
|
|
28
|
+
/** CDN base URL override (only for visor-fonts source; null = default fonts.visor.design) */
|
|
29
|
+
cdnBase: string | null;
|
|
28
30
|
}
|
|
29
31
|
/** Options for resolving a single font */
|
|
30
32
|
interface FontResolveOptions {
|
|
@@ -38,8 +40,10 @@ interface FontResolveOptions {
|
|
|
38
40
|
category?: string;
|
|
39
41
|
/** Font source override — skips Google Fonts lookup when set */
|
|
40
42
|
source?: FontSource;
|
|
41
|
-
/** Organization namespace for visor-fonts CDN (required when source is "visor-fonts") */
|
|
43
|
+
/** Organization namespace for visor-fonts CDN (required when source is "visor-fonts" unless a cdnBase override is provided) */
|
|
42
44
|
org?: string;
|
|
45
|
+
/** CDN base URL override for visor-fonts (e.g., "https://fonts.knowmentum.ai"); defaults to VISOR_FONTS_CDN */
|
|
46
|
+
cdnBase?: string;
|
|
43
47
|
}
|
|
44
48
|
/** Typography section from a .visor.yaml file */
|
|
45
49
|
interface VisorTypography {
|
|
@@ -80,6 +84,15 @@ interface VisorTypography {
|
|
|
80
84
|
normal?: string;
|
|
81
85
|
wide?: string;
|
|
82
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Per-source CDN base URL overrides. When a slot's `source` matches a key,
|
|
89
|
+
* URL resolution uses the override base instead of the default CDN.
|
|
90
|
+
* Currently only `visor-fonts` is supported; Google/Fontshare have no
|
|
91
|
+
* analogous need.
|
|
92
|
+
*/
|
|
93
|
+
"cdn-overrides"?: {
|
|
94
|
+
"visor-fonts"?: string;
|
|
95
|
+
};
|
|
83
96
|
}
|
|
84
97
|
/** Result of resolving all fonts for a theme */
|
|
85
98
|
interface ThemeFontResult {
|
|
@@ -209,6 +222,16 @@ interface VisorThemeConfig {
|
|
|
209
222
|
normal?: string;
|
|
210
223
|
wide?: string;
|
|
211
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* Per-source CDN base URL overrides for font URL resolution. Only
|
|
227
|
+
* `visor-fonts` is currently supported. When set, the override CDN
|
|
228
|
+
* replaces `fonts.visor.design` for every slot whose `source: visor-fonts`.
|
|
229
|
+
* The per-slot `org` may be empty when the override CDN already encodes
|
|
230
|
+
* the project namespace (e.g., `fonts.knowmentum.ai`).
|
|
231
|
+
*/
|
|
232
|
+
"cdn-overrides"?: {
|
|
233
|
+
"visor-fonts"?: string;
|
|
234
|
+
};
|
|
212
235
|
/**
|
|
213
236
|
* Per-slot overrides for the generated Flutter `TextTheme`. Any subset
|
|
214
237
|
* of the 16 Material slots may be specified; omitted slots fall
|
|
@@ -245,6 +268,12 @@ interface VisorThemeConfig {
|
|
|
245
268
|
"duration-normal"?: string;
|
|
246
269
|
"duration-slow"?: string;
|
|
247
270
|
easing?: string;
|
|
271
|
+
/**
|
|
272
|
+
* VI-451 (drive-by): opt-in tier-2 easing for bouncy entrances
|
|
273
|
+
* (marker pops, scale-in entrances). Emitted as `--motion-easing-overshoot`
|
|
274
|
+
* when set; absent themes fall through to the default `--motion-easing-spring`.
|
|
275
|
+
*/
|
|
276
|
+
"easing-overshoot"?: string;
|
|
248
277
|
};
|
|
249
278
|
overrides?: {
|
|
250
279
|
light?: Record<string, string>;
|
|
@@ -314,6 +343,14 @@ interface ResolvedThemeConfig {
|
|
|
314
343
|
source?: FontSource;
|
|
315
344
|
org?: string;
|
|
316
345
|
};
|
|
346
|
+
/**
|
|
347
|
+
* Per-source CDN base URL overrides (e.g., `visor-fonts:
|
|
348
|
+
* https://fonts.knowmentum.ai`). Passed through from raw config so
|
|
349
|
+
* adapters can route font URLs to per-theme buckets.
|
|
350
|
+
*/
|
|
351
|
+
"cdn-overrides"?: {
|
|
352
|
+
"visor-fonts"?: string;
|
|
353
|
+
};
|
|
317
354
|
/**
|
|
318
355
|
* Per-slot Material `TextTheme` overrides, passed through from the
|
|
319
356
|
* raw config. Empty object when none supplied. Flutter adapter
|
|
@@ -349,6 +386,8 @@ interface ResolvedThemeConfig {
|
|
|
349
386
|
"duration-normal": string;
|
|
350
387
|
"duration-slow": string;
|
|
351
388
|
easing: string;
|
|
389
|
+
/** VI-451 (drive-by): opt-in bouncy easing for marker pops / scale-in entrances. */
|
|
390
|
+
"easing-overshoot"?: string;
|
|
352
391
|
};
|
|
353
392
|
overrides?: {
|
|
354
393
|
light?: Record<string, string>;
|
|
@@ -377,6 +416,8 @@ interface SemanticTokens {
|
|
|
377
416
|
surface: Record<string, SemanticTokenValue>;
|
|
378
417
|
border: Record<string, SemanticTokenValue>;
|
|
379
418
|
interactive: Record<string, SemanticTokenValue>;
|
|
419
|
+
intent: Record<string, SemanticTokenValue>;
|
|
420
|
+
hairline: Record<string, SemanticTokenValue>;
|
|
380
421
|
}
|
|
381
422
|
interface ThemeOutput {
|
|
382
423
|
primitivesCss: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -185,6 +185,18 @@
|
|
|
185
185
|
"wide": { "type": "string" }
|
|
186
186
|
}
|
|
187
187
|
},
|
|
188
|
+
"cdn-overrides": {
|
|
189
|
+
"type": "object",
|
|
190
|
+
"description": "Per-source CDN base URL overrides. When set, the override CDN replaces the default for every slot whose source matches the key. The per-slot org may be empty when the override CDN already encodes the project namespace (e.g., https://fonts.knowmentum.ai).",
|
|
191
|
+
"additionalProperties": false,
|
|
192
|
+
"properties": {
|
|
193
|
+
"visor-fonts": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"minLength": 1,
|
|
196
|
+
"description": "CDN base URL that replaces fonts.visor.design for source: visor-fonts slots (e.g., https://fonts.knowmentum.ai)."
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
188
200
|
"slots": {
|
|
189
201
|
"type": "object",
|
|
190
202
|
"description": "Per-slot overrides for the generated Flutter Material TextTheme. Any subset of the 16 slots may be specified; omitted slots fall through to the Material 3 2024 defaults shipped in visor_core.",
|