@loworbitstudio/visor-theme-engine 0.18.0 → 0.20.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 +72 -25
- package/dist/{chunk-BUWBBUFG.js → chunk-JDYLDGUN.js} +610 -20
- package/dist/index.d.ts +272 -4
- package/dist/index.js +216 -4
- package/dist/{types-Cvm7vFwe.d.ts → types-BG-YT4JW.d.ts} +176 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +134 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BRAND_VARIANTS,
|
|
3
|
+
COMPONENT_TOKEN_FAMILIES,
|
|
4
|
+
COMPONENT_TOKEN_FAMILY_BY_NAME,
|
|
3
5
|
DEFAULT_VISOR_BRAND,
|
|
4
6
|
FONT_WEIGHT_ALIASES,
|
|
5
7
|
MATERIAL_TEXT_SLOTS,
|
|
@@ -7,13 +9,18 @@ import {
|
|
|
7
9
|
VISOR_BRANDS_CDN,
|
|
8
10
|
VISOR_DEFAULT_BRAND_PATH,
|
|
9
11
|
VISOR_FONTS_CDN,
|
|
12
|
+
WEIGHT_RAMP_TARGETS,
|
|
13
|
+
allComponentTokenNames,
|
|
10
14
|
applyOverrides,
|
|
11
15
|
buildVisorBrandUrl,
|
|
12
16
|
buildVisorFontUrl,
|
|
13
17
|
clampToSrgb,
|
|
14
18
|
collectBrandPassthrough,
|
|
19
|
+
componentTokenName,
|
|
15
20
|
compositeOverBackground,
|
|
21
|
+
declaresWeights,
|
|
16
22
|
generateDarkCss,
|
|
23
|
+
generateFontWeightDecls,
|
|
17
24
|
generateFullBundleCss,
|
|
18
25
|
generateLightCss,
|
|
19
26
|
generatePreloadLinks,
|
|
@@ -25,12 +32,15 @@ import {
|
|
|
25
32
|
getLuminance,
|
|
26
33
|
googleFontsCatalog,
|
|
27
34
|
hasBrandPassthrough,
|
|
35
|
+
hasComponentBindings,
|
|
28
36
|
hexToOklch,
|
|
29
37
|
hexToRgb,
|
|
30
38
|
isValidColor,
|
|
31
39
|
isValidHex,
|
|
40
|
+
loadedWeights,
|
|
32
41
|
lookupFontWeightAlias,
|
|
33
42
|
lookupGoogleFont,
|
|
43
|
+
matchLoadedWeight,
|
|
34
44
|
normalizeHex,
|
|
35
45
|
oklchToHex,
|
|
36
46
|
parseColor,
|
|
@@ -40,13 +50,17 @@ import {
|
|
|
40
50
|
parseRgba,
|
|
41
51
|
resolveBrandSlot,
|
|
42
52
|
resolveBrandSource,
|
|
53
|
+
resolveComponentBindings,
|
|
43
54
|
resolveFont,
|
|
55
|
+
resolveRoleWeight,
|
|
44
56
|
resolveThemeBrand,
|
|
45
57
|
resolveThemeFonts,
|
|
58
|
+
resolveWeightRamp,
|
|
46
59
|
rgbToHex,
|
|
47
60
|
rgbToOklch,
|
|
48
|
-
serializeColor
|
|
49
|
-
|
|
61
|
+
serializeColor,
|
|
62
|
+
validateComponentBindings
|
|
63
|
+
} from "./chunk-JDYLDGUN.js";
|
|
50
64
|
|
|
51
65
|
// src/fonts/validate-coverage.ts
|
|
52
66
|
var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
|
|
@@ -786,7 +800,7 @@ var visor_theme_schema_default = {
|
|
|
786
800
|
},
|
|
787
801
|
scale: {
|
|
788
802
|
type: "number",
|
|
789
|
-
description: "Type scale multiplier applied to the font-size ramp. Default: 1."
|
|
803
|
+
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."
|
|
790
804
|
},
|
|
791
805
|
"text-on-light": {
|
|
792
806
|
type: "string",
|
|
@@ -1050,6 +1064,125 @@ var visor_theme_schema_default = {
|
|
|
1050
1064
|
}
|
|
1051
1065
|
}
|
|
1052
1066
|
},
|
|
1067
|
+
components: {
|
|
1068
|
+
type: "object",
|
|
1069
|
+
description: "Component-scoped theme bindings for the admin-UI families (VI-625). Keyed by family, then by token key; the value is a string (both modes) or { light, dark }. Every token falls back to its Tier-1 expression, so omitting this block renders exactly as before. Key names are validated against the contract in component-tokens.ts.",
|
|
1070
|
+
additionalProperties: false,
|
|
1071
|
+
properties: {
|
|
1072
|
+
table: {
|
|
1073
|
+
type: "object",
|
|
1074
|
+
description: "Table head, row and cell treatment. The retro measured the same header cell rendered four ways across one admin app; binding this family pins it once. Keys: font-size, text-color, head-height, head-padding-x, head-font-family, head-font-size, head-font-weight, head-letter-spacing, head-text-transform, head-color, head-bg, row-border, row-hover-bg, row-selected-bg, cell-padding, cell-font-size, cell-color, cell-bg, cell-border-top, footer-bg, caption-color.",
|
|
1075
|
+
additionalProperties: {
|
|
1076
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
"data-table": {
|
|
1080
|
+
type: "object",
|
|
1081
|
+
description: "The admin-ui Tier-2 data-table surface stack (D3). Shared between the Table primitive and the DataTable shell so a list page and its table read as one surface. Keys: header-bg, row-bg, container-radius, container-shadow, row-py, cell-px, header-font-size.",
|
|
1082
|
+
additionalProperties: {
|
|
1083
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
chip: {
|
|
1087
|
+
type: "object",
|
|
1088
|
+
description: "Chip / filter-pill treatment. The retro found the same mono status chip at 8.5 / 9 / 9.5 / 10 / 11px with tracking .03\u2013.14em across nine implementations; this family is the single dial. Keys: radius, border, bg, text-color, font-family, font-weight, letter-spacing, text-transform, sm-height, sm-padding-x, sm-font-size, md-height, md-padding-x, md-font-size, lg-height, lg-padding-x, lg-font-size, selected-bg, selected-border-color, selected-text-color.",
|
|
1089
|
+
additionalProperties: {
|
|
1090
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
badge: {
|
|
1094
|
+
type: "object",
|
|
1095
|
+
description: "Badge treatment \u2014 the D3 Tier-2 `--badge-text-transform` / `-letter-spacing` / `-font-size` / `-font-weight` set, promoted from the admin-ui audit to a bindable contract. Keys: radius, border, font-family, font-weight, text-transform, letter-spacing, font-size, md-padding, md-gap.",
|
|
1096
|
+
additionalProperties: {
|
|
1097
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
"status-badge": {
|
|
1101
|
+
type: "object",
|
|
1102
|
+
description: "Status-badge indicator dot and the mono readout label. Chrome is inherited from the Badge family. Keys: dot-size, dot-radius, mono-font-family, mono-font-size, mono-letter-spacing.",
|
|
1103
|
+
additionalProperties: {
|
|
1104
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
"filter-bar": {
|
|
1108
|
+
type: "object",
|
|
1109
|
+
description: `The operator's named case: "the filter bar above the table in many admin pages was pretty different from one to the next; that should be streamlined." Keys: bg, border, radius, padding, gap, text-color, dense-padding, dense-gap, control-radius, results-font-size, results-color.`,
|
|
1110
|
+
additionalProperties: {
|
|
1111
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
"page-header": {
|
|
1115
|
+
type: "object",
|
|
1116
|
+
description: "Page-header lockup. The retro found the page title rendered five ways across one app. Keys: gap, text-color, leading-gap, eyebrow-font-size, eyebrow-font-weight, eyebrow-letter-spacing, eyebrow-text-transform, eyebrow-color, title-family, title-size, title-leading, title-font-size, title-font-weight, title-letter-spacing, title-color, description-font-size, description-color, actions-gap.",
|
|
1117
|
+
additionalProperties: {
|
|
1118
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
"empty-state": {
|
|
1122
|
+
type: "object",
|
|
1123
|
+
description: "Empty-state placard \u2014 surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, actions-gap.",
|
|
1124
|
+
additionalProperties: {
|
|
1125
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
banner: {
|
|
1129
|
+
type: "object",
|
|
1130
|
+
description: "Full-width notice bar. The retro found six separate banner systems across ~180 instances in one app; binding this family collapses them to one. Keys: padding, gap, font-size, radius, border-width, shadow, title-font-weight, description-font-size, info-bg, info-text-color, info-border-color, warning-bg, warning-text-color, warning-border-color, error-bg, error-text-color, error-border-color, success-bg, success-text-color, success-border-color.",
|
|
1131
|
+
additionalProperties: {
|
|
1132
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
sidebar: {
|
|
1136
|
+
type: "object",
|
|
1137
|
+
description: "Sidebar chrome and nav-item treatment. The palette roles (`bg`, `text`, `border`, `accent-*`) already ship defaults from visor-core's `visor-semantic` layer and are read bare; the structural roles below are new in VI-625. Keys: bg, text, text-muted, border, accent-bg, accent-text, header-padding, footer-padding, content-padding, menu-gap, group-label-height, group-label-font-size, group-label-font-weight, group-label-letter-spacing, group-label-text-transform, item-radius, item-gap, item-font-size, item-font-weight, item-letter-spacing, item-text-transform, item-height, item-padding, item-active-font-weight.",
|
|
1138
|
+
additionalProperties: {
|
|
1139
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1140
|
+
}
|
|
1141
|
+
},
|
|
1142
|
+
tabs: {
|
|
1143
|
+
type: "object",
|
|
1144
|
+
description: "Tab rail and trigger treatment, for both the segmented (`default`) and underlined (`line`) variants. Keys: list-radius, list-height, list-padding, list-color, list-bg, line-border-bottom, trigger-radius, trigger-padding, trigger-font-size, trigger-font-weight, trigger-letter-spacing, trigger-text-transform, trigger-color, trigger-active-bg, trigger-active-color, trigger-active-shadow, indicator-height, indicator-color.",
|
|
1145
|
+
additionalProperties: {
|
|
1146
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
skeleton: {
|
|
1150
|
+
type: "object",
|
|
1151
|
+
description: "Loading-placeholder shimmer and the content-shape geometry the admin list/table/detail skeletons use. Keys: from, to, radius, duration, logo-width, logo-height, pill-width, pill-height, line-h1-height, line-heading-height, line-body-height, avatar-size, avatar-lg-size, badge-width, badge-height, row-gap, row-padding, row-border-bottom.",
|
|
1152
|
+
additionalProperties: {
|
|
1153
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
spinner: {
|
|
1157
|
+
type: "object",
|
|
1158
|
+
description: "Inline loading ring \u2014 track, leading edge, per-size geometry and cycle length. Keys: track-color, edge-color, primary-edge-color, radius, duration, xs-size, xs-border-width, sm-size, sm-border-width, md-size, md-border-width.",
|
|
1159
|
+
additionalProperties: {
|
|
1160
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
checkbox: {
|
|
1164
|
+
type: "object",
|
|
1165
|
+
description: "Control sizing \u2014 the D3 Tier-2 `--checkbox-size` / `-radius` / `-bg` / `-border` set, promoted from the admin-ui audit to a bindable contract. Keys: size, radius, bg, border, bg-checked, border-checked.",
|
|
1166
|
+
additionalProperties: {
|
|
1167
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
surface: {
|
|
1171
|
+
type: "object",
|
|
1172
|
+
description: "The D3 Tier-2 surface-scale extremes. `elev` is the elevated control tier the chip family already reads; `screen` is the deepest chrome tier consumed by admin pattern shells (`design-prototypes/admin-ui/tokens.css`) rather than by a Visor component. Keys: elev, screen.",
|
|
1173
|
+
additionalProperties: {
|
|
1174
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
"admin-ui": {
|
|
1178
|
+
type: "object",
|
|
1179
|
+
description: "Structural roles the admin-ui pattern owns. `marquee-family` is the display face for KPI hero figures and marquee page titles \u2014 the role the admin-ui portability audit named as the one piece of ENTR brand coupling a new theme must rebind. Keys: marquee-family.",
|
|
1180
|
+
additionalProperties: {
|
|
1181
|
+
$ref: "#/$defs/componentTokenBinding"
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1053
1186
|
migrate: {
|
|
1054
1187
|
type: "object",
|
|
1055
1188
|
description: "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
|
|
@@ -1064,6 +1197,20 @@ var visor_theme_schema_default = {
|
|
|
1064
1197
|
}
|
|
1065
1198
|
},
|
|
1066
1199
|
$defs: {
|
|
1200
|
+
componentTokenBinding: {
|
|
1201
|
+
description: "A component-token binding: one string for both modes, or per-mode values.",
|
|
1202
|
+
oneOf: [
|
|
1203
|
+
{ type: "string" },
|
|
1204
|
+
{
|
|
1205
|
+
type: "object",
|
|
1206
|
+
additionalProperties: false,
|
|
1207
|
+
properties: {
|
|
1208
|
+
light: { type: "string" },
|
|
1209
|
+
dark: { type: "string" }
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
]
|
|
1213
|
+
},
|
|
1067
1214
|
cssColor: {
|
|
1068
1215
|
type: "string",
|
|
1069
1216
|
description: "CSS color value. Accepts hex (#RGB, #RRGGBB, #RRGGBBAA), rgb()/rgba(), hsl()/hsla(), or oklch() formats.",
|
|
@@ -1310,7 +1457,8 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
1310
1457
|
"shadows",
|
|
1311
1458
|
"strokeWidths",
|
|
1312
1459
|
"motion",
|
|
1313
|
-
"overrides"
|
|
1460
|
+
"overrides",
|
|
1461
|
+
"components"
|
|
1314
1462
|
]);
|
|
1315
1463
|
var KNOWN_COLOR_KEYS = /* @__PURE__ */ new Set([
|
|
1316
1464
|
"primary",
|
|
@@ -1557,6 +1705,9 @@ function checkUnknownKeys(obj, errors) {
|
|
|
1557
1705
|
}
|
|
1558
1706
|
}
|
|
1559
1707
|
}
|
|
1708
|
+
if (obj.components !== void 0) {
|
|
1709
|
+
errors.push(...validateComponentBindings(obj.components));
|
|
1710
|
+
}
|
|
1560
1711
|
}
|
|
1561
1712
|
function validateConfig(config) {
|
|
1562
1713
|
const errors = [];
|
|
@@ -1964,6 +2115,9 @@ function resolveConfig(config) {
|
|
|
1964
2115
|
...config.motion?.["easing-overshoot"] !== void 0 ? { "easing-overshoot": config.motion["easing-overshoot"] } : {}
|
|
1965
2116
|
},
|
|
1966
2117
|
overrides: config.overrides,
|
|
2118
|
+
// VI-625: component bindings pass through untouched — the contract in
|
|
2119
|
+
// component-tokens.ts owns their shape, there is nothing to default.
|
|
2120
|
+
components: config.components,
|
|
1967
2121
|
originalColors,
|
|
1968
2122
|
colorFormats
|
|
1969
2123
|
};
|
|
@@ -2717,6 +2871,9 @@ function exportTheme(primitives, config) {
|
|
|
2717
2871
|
output.overrides = overrides;
|
|
2718
2872
|
}
|
|
2719
2873
|
}
|
|
2874
|
+
if (config.components && Object.keys(config.components).length > 0) {
|
|
2875
|
+
output.components = config.components;
|
|
2876
|
+
}
|
|
2720
2877
|
return stringifyYaml(output, { lineWidth: 0 });
|
|
2721
2878
|
}
|
|
2722
2879
|
|
|
@@ -2987,6 +3144,46 @@ function checkTypeScaleCoherence(config, issues) {
|
|
|
2987
3144
|
}
|
|
2988
3145
|
}
|
|
2989
3146
|
}
|
|
3147
|
+
function checkFontWeightCoverage(config, issues) {
|
|
3148
|
+
const typography = config.typography;
|
|
3149
|
+
if (!typography) return;
|
|
3150
|
+
const HOSTED = /* @__PURE__ */ new Set(["visor-fonts", "google-fonts", "fontshare"]);
|
|
3151
|
+
const slots = ["heading", "display", "body", "mono"];
|
|
3152
|
+
if (!declaresWeights(typography)) {
|
|
3153
|
+
const hosted = slots.filter((slot) => {
|
|
3154
|
+
const source = typography[slot]?.source;
|
|
3155
|
+
return typeof source === "string" && HOSTED.has(source);
|
|
3156
|
+
});
|
|
3157
|
+
if (hosted.length > 0) {
|
|
3158
|
+
issues.push(
|
|
3159
|
+
issue(
|
|
3160
|
+
"warning",
|
|
3161
|
+
"FONT_WEIGHTS_UNDECLARED",
|
|
3162
|
+
`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.`,
|
|
3163
|
+
`typography.${hosted[0]}.weights`
|
|
3164
|
+
)
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
const loaded = loadedWeights(typography);
|
|
3170
|
+
const ramp = resolveWeightRamp(typography);
|
|
3171
|
+
const collapsed = [
|
|
3172
|
+
["medium", "normal"],
|
|
3173
|
+
["semibold", "medium"],
|
|
3174
|
+
["bold", "semibold"]
|
|
3175
|
+
].filter(([heavier, lighter]) => ramp[heavier] === ramp[lighter]).map(([heavier, lighter]) => `${heavier} = ${lighter} (${ramp[heavier]})`);
|
|
3176
|
+
if (collapsed.length > 0) {
|
|
3177
|
+
issues.push(
|
|
3178
|
+
issue(
|
|
3179
|
+
"warning",
|
|
3180
|
+
"FONT_WEIGHT_RAMP_COLLAPSED",
|
|
3181
|
+
`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.`,
|
|
3182
|
+
"typography"
|
|
3183
|
+
)
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
2990
3187
|
function checkLetterSpacing(config, issues) {
|
|
2991
3188
|
const ls = config.typography?.["letter-spacing"];
|
|
2992
3189
|
if (!ls) return;
|
|
@@ -3493,6 +3690,7 @@ function validate(config, options) {
|
|
|
3493
3690
|
const typedConfig = config;
|
|
3494
3691
|
checkCompleteness(typedConfig, errors);
|
|
3495
3692
|
checkTypeScaleCoherence(typedConfig, errors);
|
|
3693
|
+
checkFontWeightCoverage(typedConfig, warnings);
|
|
3496
3694
|
checkLetterSpacing(typedConfig, errors);
|
|
3497
3695
|
checkMotionEasing(typedConfig, errors);
|
|
3498
3696
|
const durationIssues = [];
|
|
@@ -4054,6 +4252,8 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
4054
4252
|
export {
|
|
4055
4253
|
BRAND_VARIANTS,
|
|
4056
4254
|
BRAND_VISIBILITIES,
|
|
4255
|
+
COMPONENT_TOKEN_FAMILIES,
|
|
4256
|
+
COMPONENT_TOKEN_FAMILY_BY_NAME,
|
|
4057
4257
|
DEFAULT_BRAND_STRATEGY_SURFACES,
|
|
4058
4258
|
DEFAULT_BRAND_STRATEGY_TONE_STATES,
|
|
4059
4259
|
DEFAULT_VISOR_BRAND,
|
|
@@ -4064,6 +4264,8 @@ export {
|
|
|
4064
4264
|
VISOR_BRANDS_CDN,
|
|
4065
4265
|
VISOR_DEFAULT_BRAND_PATH,
|
|
4066
4266
|
VISOR_FONTS_CDN,
|
|
4267
|
+
WEIGHT_RAMP_TARGETS,
|
|
4268
|
+
allComponentTokenNames,
|
|
4067
4269
|
applyOverrides,
|
|
4068
4270
|
assignSemanticTokens,
|
|
4069
4271
|
buildVisorBrandUrl,
|
|
@@ -4073,11 +4275,14 @@ export {
|
|
|
4073
4275
|
clampToSrgb,
|
|
4074
4276
|
cleanFontValue,
|
|
4075
4277
|
collectBrandPassthrough,
|
|
4278
|
+
componentTokenName,
|
|
4076
4279
|
compositeOverBackground,
|
|
4280
|
+
declaresWeights,
|
|
4077
4281
|
exportTheme,
|
|
4078
4282
|
extractFromCSS,
|
|
4079
4283
|
formatFontCoverageError,
|
|
4080
4284
|
generateDarkCss,
|
|
4285
|
+
generateFontWeightDecls,
|
|
4081
4286
|
generateFullBundleCss,
|
|
4082
4287
|
generateLightCss,
|
|
4083
4288
|
generatePreloadLinks,
|
|
@@ -4095,13 +4300,16 @@ export {
|
|
|
4095
4300
|
getLuminance,
|
|
4096
4301
|
googleFontsCatalog,
|
|
4097
4302
|
hasBrandPassthrough,
|
|
4303
|
+
hasComponentBindings,
|
|
4098
4304
|
hexToOklch,
|
|
4099
4305
|
hexToRgb,
|
|
4100
4306
|
isValidColor,
|
|
4101
4307
|
isValidHex,
|
|
4102
4308
|
isVisorThemeConfig,
|
|
4309
|
+
loadedWeights,
|
|
4103
4310
|
lookupFontWeightAlias,
|
|
4104
4311
|
lookupGoogleFont,
|
|
4312
|
+
matchLoadedWeight,
|
|
4105
4313
|
normalizeHex,
|
|
4106
4314
|
oklchToHex,
|
|
4107
4315
|
parseCSSDeclarations,
|
|
@@ -4114,15 +4322,19 @@ export {
|
|
|
4114
4322
|
parseRgba,
|
|
4115
4323
|
resolveBrandSlot,
|
|
4116
4324
|
resolveBrandSource,
|
|
4325
|
+
resolveComponentBindings,
|
|
4117
4326
|
resolveConfig,
|
|
4118
4327
|
resolveFont,
|
|
4328
|
+
resolveRoleWeight,
|
|
4119
4329
|
resolveThemeBrand,
|
|
4120
4330
|
resolveThemeFonts,
|
|
4331
|
+
resolveWeightRamp,
|
|
4121
4332
|
rgbToHex,
|
|
4122
4333
|
serializeBrandStrategy,
|
|
4123
4334
|
serializeColor,
|
|
4124
4335
|
validate,
|
|
4125
4336
|
validateBrandStrategy,
|
|
4337
|
+
validateComponentBindings,
|
|
4126
4338
|
validateConfig,
|
|
4127
4339
|
validateFontCoverage,
|
|
4128
4340
|
visor_theme_schema_default as visorThemeSchema
|
|
@@ -446,6 +446,157 @@ interface BrandStrategyContext {
|
|
|
446
446
|
states?: ReadonlySet<string>;
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Component-scoped theme-bindable token contract (VI-625).
|
|
451
|
+
*
|
|
452
|
+
* Visor themes have always been able to repaint the Tier-1 semantic surface
|
|
453
|
+
* (`--surface-*`, `--text-*`, `--border-*`, `--interactive-*`). That is enough
|
|
454
|
+
* to make an admin UI *functional*, but not enough to make it *look like one
|
|
455
|
+
* thing*: nothing stopped a page inventing its own table-header treatment, its
|
|
456
|
+
* own chip tracking, its own filter-bar padding. The AN-366 Fidelity-Mirror
|
|
457
|
+
* retro measured the result — 247 element categories across 13 families whose
|
|
458
|
+
* inconsistency traced to the substrate, not to any one component.
|
|
459
|
+
*
|
|
460
|
+
* This module is the answer: a **contract**, not new components. For every
|
|
461
|
+
* family in the admin kit it names the component-scoped custom properties a
|
|
462
|
+
* theme may bind, and — critically — the Tier-1 expression each one falls back
|
|
463
|
+
* to when the theme is silent.
|
|
464
|
+
*
|
|
465
|
+
* ## The two invariants
|
|
466
|
+
*
|
|
467
|
+
* 1. **Unbound renders identically.** Every token is consumed by its component
|
|
468
|
+
* as `var(--<token>, <fallback>)` where `<fallback>` is byte-for-byte the
|
|
469
|
+
* expression that shipped before the token existed. A theme that binds
|
|
470
|
+
* nothing is pixel-identical to a theme from before this contract.
|
|
471
|
+
* 2. **Bound changes everything at once.** A theme that binds a token retunes
|
|
472
|
+
* every consuming surface in one place, instead of each page re-deriving the
|
|
473
|
+
* look in local CSS.
|
|
474
|
+
*
|
|
475
|
+
* Both are enforced by tests, not prose — see
|
|
476
|
+
* `components/ui/__tests__/component-token-contract.test.ts`.
|
|
477
|
+
*
|
|
478
|
+
* ## Authoring
|
|
479
|
+
*
|
|
480
|
+
* ```yaml
|
|
481
|
+
* # my-theme.visor.yaml
|
|
482
|
+
* components:
|
|
483
|
+
* table:
|
|
484
|
+
* head-height: "2.5rem"
|
|
485
|
+
* head-text-transform: uppercase
|
|
486
|
+
* head-letter-spacing: "0.06em"
|
|
487
|
+
* head-bg:
|
|
488
|
+
* light: "#f7f7f8"
|
|
489
|
+
* dark: "#141418"
|
|
490
|
+
* chip:
|
|
491
|
+
* md-font-size: "11px"
|
|
492
|
+
* letter-spacing: "0.04em"
|
|
493
|
+
* ```
|
|
494
|
+
*
|
|
495
|
+
* A bare string binds both modes; `{ light, dark }` binds them independently.
|
|
496
|
+
*
|
|
497
|
+
* ## Prior art
|
|
498
|
+
*
|
|
499
|
+
* VI-620 / VI-621 did exactly this for the dialog substrate
|
|
500
|
+
* (`--dialog-form-panel-border`, `--field-control-bg`, `--input-*`) and the
|
|
501
|
+
* field language stayed consistent across all twelve modals. This extends the
|
|
502
|
+
* same move to the page-level families. D3 additionally promotes the Playbook's
|
|
503
|
+
* admin-ui Tier-2 treatment layer
|
|
504
|
+
* (`design-prototypes/admin-ui/audits/admin-ui-theme-portability.md`) from prose
|
|
505
|
+
* in an audit doc to a first-class bindable contract.
|
|
506
|
+
*
|
|
507
|
+
* NOTE: this module is intentionally dependency-free (pure data + types) so it
|
|
508
|
+
* can be imported from the engine, from repo-root component tests, and from
|
|
509
|
+
* docs tooling without pulling in the pipeline.
|
|
510
|
+
*/
|
|
511
|
+
/** A single consuming declaration of a component-scoped token. */
|
|
512
|
+
interface ComponentTokenConsumer {
|
|
513
|
+
/** Repo-relative path of the CSS module that reads the token. */
|
|
514
|
+
file: string;
|
|
515
|
+
/**
|
|
516
|
+
* The exact fallback expression the component uses, i.e. the `X` in
|
|
517
|
+
* `var(--token, X)`. `null` means the token is read bare (`var(--token)`)
|
|
518
|
+
* because a default is supplied elsewhere — visor-core's `visor-semantic`
|
|
519
|
+
* layer for the `--sidebar-*` palette roles.
|
|
520
|
+
*/
|
|
521
|
+
fallback: string | null;
|
|
522
|
+
}
|
|
523
|
+
/** One theme-bindable, component-scoped custom property. */
|
|
524
|
+
interface ComponentTokenSpec {
|
|
525
|
+
/** Key under `components.<family>` in `.visor.yaml`. */
|
|
526
|
+
key: string;
|
|
527
|
+
/** The CSS property (or properties) the token drives — documentation only. */
|
|
528
|
+
property: string;
|
|
529
|
+
/** What binding this token achieves, in one line. */
|
|
530
|
+
description: string;
|
|
531
|
+
/**
|
|
532
|
+
* Every place a Visor component reads this token. An empty array marks an
|
|
533
|
+
* **emit-only role**: the engine can emit it so a generated theme carries it,
|
|
534
|
+
* but the consumer is a pattern shell (the admin-ui prototype layer), not a
|
|
535
|
+
* Visor component.
|
|
536
|
+
*/
|
|
537
|
+
consumers: ComponentTokenConsumer[];
|
|
538
|
+
/** Recommended value for an emit-only role, for the docs table. */
|
|
539
|
+
recommended?: string;
|
|
540
|
+
}
|
|
541
|
+
/** A family of component-scoped tokens a theme can bind as a unit. */
|
|
542
|
+
interface ComponentTokenFamily {
|
|
543
|
+
/** Key under `components:` in `.visor.yaml`. */
|
|
544
|
+
family: string;
|
|
545
|
+
/**
|
|
546
|
+
* Custom-property prefix. The emitted name is `--<prefix>-<key>`.
|
|
547
|
+
* Every family declares one — there are no verbatim-key families.
|
|
548
|
+
*/
|
|
549
|
+
prefix: string;
|
|
550
|
+
/** One-line summary for the docs page. */
|
|
551
|
+
description: string;
|
|
552
|
+
tokens: ComponentTokenSpec[];
|
|
553
|
+
}
|
|
554
|
+
/** Emitted custom-property name (without the leading `--`) for a family key. */
|
|
555
|
+
declare function componentTokenName(family: ComponentTokenFamily, key: string): string;
|
|
556
|
+
/**
|
|
557
|
+
* The full contract, in the D2 priority order the retro measured.
|
|
558
|
+
*
|
|
559
|
+
* Adding a family here is the ONLY registration step: the schema validator, the
|
|
560
|
+
* CSS emitter, the docs page test and the fallback-parity test all read this
|
|
561
|
+
* array.
|
|
562
|
+
*/
|
|
563
|
+
declare const COMPONENT_TOKEN_FAMILIES: readonly ComponentTokenFamily[];
|
|
564
|
+
/** Family lookup by `.visor.yaml` key. */
|
|
565
|
+
declare const COMPONENT_TOKEN_FAMILY_BY_NAME: ReadonlyMap<string, ComponentTokenFamily>;
|
|
566
|
+
/** Every emitted custom-property name in the contract, without the `--`. */
|
|
567
|
+
declare function allComponentTokenNames(): string[];
|
|
568
|
+
/** A binding value: one string for both modes, or per-mode values. */
|
|
569
|
+
type ComponentTokenBinding = string | {
|
|
570
|
+
light?: string;
|
|
571
|
+
dark?: string;
|
|
572
|
+
};
|
|
573
|
+
/** The `components:` block of a `.visor.yaml`. */
|
|
574
|
+
type ComponentTokenBindings = Record<string, Record<string, ComponentTokenBinding>>;
|
|
575
|
+
/** Resolved per-mode custom properties, keyed by name without the `--`. */
|
|
576
|
+
interface ResolvedComponentTokens {
|
|
577
|
+
light: Record<string, string>;
|
|
578
|
+
dark: Record<string, string>;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Flatten a `components:` block into per-mode `--<name>: <value>` pairs.
|
|
582
|
+
*
|
|
583
|
+
* Unknown families / keys are skipped — `validateComponentBindings` is the place
|
|
584
|
+
* that reports them, so this stays a pure projection.
|
|
585
|
+
*/
|
|
586
|
+
declare function resolveComponentBindings(bindings: ComponentTokenBindings | undefined): ResolvedComponentTokens;
|
|
587
|
+
/** True when any component token is bound. */
|
|
588
|
+
declare function hasComponentBindings(bindings: ComponentTokenBindings | undefined): boolean;
|
|
589
|
+
/**
|
|
590
|
+
* Validate a `components:` block against the contract.
|
|
591
|
+
*
|
|
592
|
+
* Returns human-readable error strings — unknown family, unknown key, or a value
|
|
593
|
+
* that is neither a string nor a `{ light, dark }` pair. An unknown key is an
|
|
594
|
+
* ERROR rather than a warning on purpose: a typo'd component token is silently
|
|
595
|
+
* inert at runtime (the component keeps resolving its fallback), which is
|
|
596
|
+
* exactly the failure mode this contract exists to end.
|
|
597
|
+
*/
|
|
598
|
+
declare function validateComponentBindings(bindings: unknown): string[];
|
|
599
|
+
|
|
449
600
|
/**
|
|
450
601
|
* Types for the Visor Theme Engine
|
|
451
602
|
*
|
|
@@ -527,6 +678,12 @@ interface VisorThemeConfig {
|
|
|
527
678
|
info?: string;
|
|
528
679
|
};
|
|
529
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
|
+
*/
|
|
530
687
|
scale?: number;
|
|
531
688
|
/**
|
|
532
689
|
* VI-375: text color placed on a LIGHT interactive background. Auto-picked
|
|
@@ -663,6 +820,16 @@ interface VisorThemeConfig {
|
|
|
663
820
|
light?: Record<string, string>;
|
|
664
821
|
dark?: Record<string, string>;
|
|
665
822
|
};
|
|
823
|
+
/**
|
|
824
|
+
* VI-625: component-scoped theme bindings for the admin-UI families.
|
|
825
|
+
*
|
|
826
|
+
* Keyed by family (`table`, `chip`, `filter-bar`, …) then by token key; the
|
|
827
|
+
* value is a string (both modes) or `{ light, dark }`. Every key is declared
|
|
828
|
+
* in `component-tokens.ts`, and every consuming component falls back to its
|
|
829
|
+
* Tier-1 expression, so a theme that omits this block renders exactly as it
|
|
830
|
+
* did before the contract existed.
|
|
831
|
+
*/
|
|
832
|
+
components?: ComponentTokenBindings;
|
|
666
833
|
/**
|
|
667
834
|
* Migration metadata — optional, additive, consumed by `visor migrate` commands.
|
|
668
835
|
* Does not affect CSS generation or theme application.
|
|
@@ -706,6 +873,12 @@ interface ResolvedThemeConfig {
|
|
|
706
873
|
};
|
|
707
874
|
"colors-dark"?: VisorThemeConfig["colors-dark"];
|
|
708
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
|
+
*/
|
|
709
882
|
scale: number;
|
|
710
883
|
/**
|
|
711
884
|
* VI-375: resolved default text color for LIGHT interactive backgrounds.
|
|
@@ -813,6 +986,8 @@ interface ResolvedThemeConfig {
|
|
|
813
986
|
light?: Record<string, string>;
|
|
814
987
|
dark?: Record<string, string>;
|
|
815
988
|
};
|
|
989
|
+
/** VI-625: component-scoped theme bindings, carried through unchanged. */
|
|
990
|
+
components?: ComponentTokenBindings;
|
|
816
991
|
/** Original color strings from user input, for round-trip export. */
|
|
817
992
|
originalColors?: Record<string, string>;
|
|
818
993
|
/** Color format of each user-provided color, keyed by field name. */
|
|
@@ -854,4 +1029,4 @@ interface ThemeData {
|
|
|
854
1029
|
output: ThemeOutput;
|
|
855
1030
|
}
|
|
856
1031
|
|
|
857
|
-
export { type
|
|
1032
|
+
export { type ComponentTokenBinding as $, type BrandColorUsage as A, type BrandSlot as B, type ColorScheme as C, type BrandContrastTarget as D, type BrandGoverns as E, type FontResolveOptions as F, type GoogleFontEntry as G, type BrandLexiconEntry as H, type BrandMessaging as I, type BrandPersonalityTrait as J, type BrandPillar as K, type BrandPositioning as L, type BrandStrategyIssueSeverity as M, type BrandToneEntry as N, type OKLCH as O, type ParsedColor as P, type BrandVariant as Q, type ResolvedThemeConfig as R, type SerializedBrandStrategy as S, type ThemeFontResult as T, type BrandVisibility as U, type VisorTypography as V, type BrandVoice as W, type BrandVoiceTrait as X, COMPONENT_TOKEN_FAMILIES as Y, COMPONENT_TOKEN_FAMILY_BY_NAME as Z, type ColorFormat as _, type FontResolution as a, type ComponentTokenBindings as a0, type ComponentTokenConsumer as a1, type ComponentTokenFamily as a2, type ComponentTokenSpec as a3, DEFAULT_BRAND_STRATEGY_SURFACES as a4, DEFAULT_BRAND_STRATEGY_TONE_STATES as a5, type FontSource as a6, GOVERNS_WILDCARD as a7, type RGBA as a8, type ResolvedComponentTokens as a9, type SemanticTokenValue as aa, allComponentTokenNames as ab, componentTokenName as ac, hasComponentBindings as ad, resolveComponentBindings as ae, validateComponentBindings as af, type FontDisplayStrategy as b, type VisorBrand as c, type BrandSource as d, type BrandResolution as e, type ThemeBrandResult as f, type BrandStrategy as g, type BrandStrategyContext as h, type BrandStrategyIssue as i, type BrandStrategyValidationResult as j, type GeneratedPrimitives as k, type ThemeOutput as l, type ThemeData as m, type VisorThemeConfig as n, type FullShadeScale as o, type ColorRole as p, type SelectiveShadeScale as q, type RGB as r, type SemanticTokens as s, type ShadeStep as t, BRAND_VARIANTS as u, BRAND_VISIBILITIES as v, type BrandAccessibility as w, type BrandArchetype as x, type BrandBoilerplate as y, type BrandColorPairing as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loworbitstudio/visor-theme-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|