@loworbitstudio/visor-theme-engine 0.17.1 → 0.19.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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-Cvm7vFwe.js';
2
- export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as ColorFormat, Z as DEFAULT_BRAND_STRATEGY_SURFACES, _ as DEFAULT_BRAND_STRATEGY_TONE_STATES, $ as FontSource, a0 as GOVERNS_WILDCARD, a1 as RGBA, a2 as SemanticTokenValue } from './types-Cvm7vFwe.js';
1
+ import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-DF5StphJ.js';
2
+ export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-DF5StphJ.js';
3
3
 
4
4
  /**
5
5
  * Font resolver — maps font family names to loadable font resources.
@@ -1025,6 +1025,125 @@ var properties = {
1025
1025
  }
1026
1026
  }
1027
1027
  },
1028
+ components: {
1029
+ type: "object",
1030
+ 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.",
1031
+ additionalProperties: false,
1032
+ properties: {
1033
+ table: {
1034
+ type: "object",
1035
+ 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.",
1036
+ additionalProperties: {
1037
+ $ref: "#/$defs/componentTokenBinding"
1038
+ }
1039
+ },
1040
+ "data-table": {
1041
+ type: "object",
1042
+ 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.",
1043
+ additionalProperties: {
1044
+ $ref: "#/$defs/componentTokenBinding"
1045
+ }
1046
+ },
1047
+ chip: {
1048
+ type: "object",
1049
+ description: "Chip / filter-pill treatment. The retro found the same mono status chip at 8.5 / 9 / 9.5 / 10 / 11px with tracking .03–.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.",
1050
+ additionalProperties: {
1051
+ $ref: "#/$defs/componentTokenBinding"
1052
+ }
1053
+ },
1054
+ badge: {
1055
+ type: "object",
1056
+ description: "Badge treatment — 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.",
1057
+ additionalProperties: {
1058
+ $ref: "#/$defs/componentTokenBinding"
1059
+ }
1060
+ },
1061
+ "status-badge": {
1062
+ type: "object",
1063
+ 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.",
1064
+ additionalProperties: {
1065
+ $ref: "#/$defs/componentTokenBinding"
1066
+ }
1067
+ },
1068
+ "filter-bar": {
1069
+ type: "object",
1070
+ 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.",
1071
+ additionalProperties: {
1072
+ $ref: "#/$defs/componentTokenBinding"
1073
+ }
1074
+ },
1075
+ "page-header": {
1076
+ type: "object",
1077
+ 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.",
1078
+ additionalProperties: {
1079
+ $ref: "#/$defs/componentTokenBinding"
1080
+ }
1081
+ },
1082
+ "empty-state": {
1083
+ type: "object",
1084
+ description: "Empty-state placard — 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.",
1085
+ additionalProperties: {
1086
+ $ref: "#/$defs/componentTokenBinding"
1087
+ }
1088
+ },
1089
+ banner: {
1090
+ type: "object",
1091
+ 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.",
1092
+ additionalProperties: {
1093
+ $ref: "#/$defs/componentTokenBinding"
1094
+ }
1095
+ },
1096
+ sidebar: {
1097
+ type: "object",
1098
+ 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.",
1099
+ additionalProperties: {
1100
+ $ref: "#/$defs/componentTokenBinding"
1101
+ }
1102
+ },
1103
+ tabs: {
1104
+ type: "object",
1105
+ 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.",
1106
+ additionalProperties: {
1107
+ $ref: "#/$defs/componentTokenBinding"
1108
+ }
1109
+ },
1110
+ skeleton: {
1111
+ type: "object",
1112
+ 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.",
1113
+ additionalProperties: {
1114
+ $ref: "#/$defs/componentTokenBinding"
1115
+ }
1116
+ },
1117
+ spinner: {
1118
+ type: "object",
1119
+ description: "Inline loading ring — 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.",
1120
+ additionalProperties: {
1121
+ $ref: "#/$defs/componentTokenBinding"
1122
+ }
1123
+ },
1124
+ checkbox: {
1125
+ type: "object",
1126
+ description: "Control sizing — 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.",
1127
+ additionalProperties: {
1128
+ $ref: "#/$defs/componentTokenBinding"
1129
+ }
1130
+ },
1131
+ surface: {
1132
+ type: "object",
1133
+ 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.",
1134
+ additionalProperties: {
1135
+ $ref: "#/$defs/componentTokenBinding"
1136
+ }
1137
+ },
1138
+ "admin-ui": {
1139
+ type: "object",
1140
+ description: "Structural roles the admin-ui pattern owns. `marquee-family` is the display face for KPI hero figures and marquee page titles — the role the admin-ui portability audit named as the one piece of ENTR brand coupling a new theme must rebind. Keys: marquee-family.",
1141
+ additionalProperties: {
1142
+ $ref: "#/$defs/componentTokenBinding"
1143
+ }
1144
+ }
1145
+ }
1146
+ },
1028
1147
  migrate: {
1029
1148
  type: "object",
1030
1149
  description: "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
@@ -1041,6 +1160,26 @@ var properties = {
1041
1160
  }
1042
1161
  };
1043
1162
  var $defs = {
1163
+ componentTokenBinding: {
1164
+ description: "A component-token binding: one string for both modes, or per-mode values.",
1165
+ oneOf: [
1166
+ {
1167
+ type: "string"
1168
+ },
1169
+ {
1170
+ type: "object",
1171
+ additionalProperties: false,
1172
+ properties: {
1173
+ light: {
1174
+ type: "string"
1175
+ },
1176
+ dark: {
1177
+ type: "string"
1178
+ }
1179
+ }
1180
+ }
1181
+ ]
1182
+ },
1044
1183
  cssColor: {
1045
1184
  type: "string",
1046
1185
  description: "CSS color value. Accepts hex (#RGB, #RRGGBB, #RRGGBBAA), rgb()/rgba(), hsl()/hsla(), or oklch() formats.",
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,11 +9,13 @@ import {
7
9
  VISOR_BRANDS_CDN,
8
10
  VISOR_DEFAULT_BRAND_PATH,
9
11
  VISOR_FONTS_CDN,
12
+ allComponentTokenNames,
10
13
  applyOverrides,
11
14
  buildVisorBrandUrl,
12
15
  buildVisorFontUrl,
13
16
  clampToSrgb,
14
17
  collectBrandPassthrough,
18
+ componentTokenName,
15
19
  compositeOverBackground,
16
20
  generateDarkCss,
17
21
  generateFullBundleCss,
@@ -25,6 +29,7 @@ import {
25
29
  getLuminance,
26
30
  googleFontsCatalog,
27
31
  hasBrandPassthrough,
32
+ hasComponentBindings,
28
33
  hexToOklch,
29
34
  hexToRgb,
30
35
  isValidColor,
@@ -40,13 +45,15 @@ import {
40
45
  parseRgba,
41
46
  resolveBrandSlot,
42
47
  resolveBrandSource,
48
+ resolveComponentBindings,
43
49
  resolveFont,
44
50
  resolveThemeBrand,
45
51
  resolveThemeFonts,
46
52
  rgbToHex,
47
53
  rgbToOklch,
48
- serializeColor
49
- } from "./chunk-BUWBBUFG.js";
54
+ serializeColor,
55
+ validateComponentBindings
56
+ } from "./chunk-32DC5DAR.js";
50
57
 
51
58
  // src/fonts/validate-coverage.ts
52
59
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -1050,6 +1057,125 @@ var visor_theme_schema_default = {
1050
1057
  }
1051
1058
  }
1052
1059
  },
1060
+ components: {
1061
+ type: "object",
1062
+ 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.",
1063
+ additionalProperties: false,
1064
+ properties: {
1065
+ table: {
1066
+ type: "object",
1067
+ 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.",
1068
+ additionalProperties: {
1069
+ $ref: "#/$defs/componentTokenBinding"
1070
+ }
1071
+ },
1072
+ "data-table": {
1073
+ type: "object",
1074
+ 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.",
1075
+ additionalProperties: {
1076
+ $ref: "#/$defs/componentTokenBinding"
1077
+ }
1078
+ },
1079
+ chip: {
1080
+ type: "object",
1081
+ 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.",
1082
+ additionalProperties: {
1083
+ $ref: "#/$defs/componentTokenBinding"
1084
+ }
1085
+ },
1086
+ badge: {
1087
+ type: "object",
1088
+ 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.",
1089
+ additionalProperties: {
1090
+ $ref: "#/$defs/componentTokenBinding"
1091
+ }
1092
+ },
1093
+ "status-badge": {
1094
+ type: "object",
1095
+ 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.",
1096
+ additionalProperties: {
1097
+ $ref: "#/$defs/componentTokenBinding"
1098
+ }
1099
+ },
1100
+ "filter-bar": {
1101
+ type: "object",
1102
+ 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.`,
1103
+ additionalProperties: {
1104
+ $ref: "#/$defs/componentTokenBinding"
1105
+ }
1106
+ },
1107
+ "page-header": {
1108
+ type: "object",
1109
+ 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.",
1110
+ additionalProperties: {
1111
+ $ref: "#/$defs/componentTokenBinding"
1112
+ }
1113
+ },
1114
+ "empty-state": {
1115
+ type: "object",
1116
+ 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.",
1117
+ additionalProperties: {
1118
+ $ref: "#/$defs/componentTokenBinding"
1119
+ }
1120
+ },
1121
+ banner: {
1122
+ type: "object",
1123
+ 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.",
1124
+ additionalProperties: {
1125
+ $ref: "#/$defs/componentTokenBinding"
1126
+ }
1127
+ },
1128
+ sidebar: {
1129
+ type: "object",
1130
+ 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.",
1131
+ additionalProperties: {
1132
+ $ref: "#/$defs/componentTokenBinding"
1133
+ }
1134
+ },
1135
+ tabs: {
1136
+ type: "object",
1137
+ 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.",
1138
+ additionalProperties: {
1139
+ $ref: "#/$defs/componentTokenBinding"
1140
+ }
1141
+ },
1142
+ skeleton: {
1143
+ type: "object",
1144
+ 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.",
1145
+ additionalProperties: {
1146
+ $ref: "#/$defs/componentTokenBinding"
1147
+ }
1148
+ },
1149
+ spinner: {
1150
+ type: "object",
1151
+ 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.",
1152
+ additionalProperties: {
1153
+ $ref: "#/$defs/componentTokenBinding"
1154
+ }
1155
+ },
1156
+ checkbox: {
1157
+ type: "object",
1158
+ 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.",
1159
+ additionalProperties: {
1160
+ $ref: "#/$defs/componentTokenBinding"
1161
+ }
1162
+ },
1163
+ surface: {
1164
+ type: "object",
1165
+ 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.",
1166
+ additionalProperties: {
1167
+ $ref: "#/$defs/componentTokenBinding"
1168
+ }
1169
+ },
1170
+ "admin-ui": {
1171
+ type: "object",
1172
+ 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.",
1173
+ additionalProperties: {
1174
+ $ref: "#/$defs/componentTokenBinding"
1175
+ }
1176
+ }
1177
+ }
1178
+ },
1053
1179
  migrate: {
1054
1180
  type: "object",
1055
1181
  description: "Migration metadata consumed by `visor migrate` commands. Does not affect CSS generation or theme application.",
@@ -1064,6 +1190,20 @@ var visor_theme_schema_default = {
1064
1190
  }
1065
1191
  },
1066
1192
  $defs: {
1193
+ componentTokenBinding: {
1194
+ description: "A component-token binding: one string for both modes, or per-mode values.",
1195
+ oneOf: [
1196
+ { type: "string" },
1197
+ {
1198
+ type: "object",
1199
+ additionalProperties: false,
1200
+ properties: {
1201
+ light: { type: "string" },
1202
+ dark: { type: "string" }
1203
+ }
1204
+ }
1205
+ ]
1206
+ },
1067
1207
  cssColor: {
1068
1208
  type: "string",
1069
1209
  description: "CSS color value. Accepts hex (#RGB, #RRGGBB, #RRGGBBAA), rgb()/rgba(), hsl()/hsla(), or oklch() formats.",
@@ -1310,7 +1450,8 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
1310
1450
  "shadows",
1311
1451
  "strokeWidths",
1312
1452
  "motion",
1313
- "overrides"
1453
+ "overrides",
1454
+ "components"
1314
1455
  ]);
1315
1456
  var KNOWN_COLOR_KEYS = /* @__PURE__ */ new Set([
1316
1457
  "primary",
@@ -1557,6 +1698,9 @@ function checkUnknownKeys(obj, errors) {
1557
1698
  }
1558
1699
  }
1559
1700
  }
1701
+ if (obj.components !== void 0) {
1702
+ errors.push(...validateComponentBindings(obj.components));
1703
+ }
1560
1704
  }
1561
1705
  function validateConfig(config) {
1562
1706
  const errors = [];
@@ -1964,6 +2108,9 @@ function resolveConfig(config) {
1964
2108
  ...config.motion?.["easing-overshoot"] !== void 0 ? { "easing-overshoot": config.motion["easing-overshoot"] } : {}
1965
2109
  },
1966
2110
  overrides: config.overrides,
2111
+ // VI-625: component bindings pass through untouched — the contract in
2112
+ // component-tokens.ts owns their shape, there is nothing to default.
2113
+ components: config.components,
1967
2114
  originalColors,
1968
2115
  colorFormats
1969
2116
  };
@@ -2717,6 +2864,9 @@ function exportTheme(primitives, config) {
2717
2864
  output.overrides = overrides;
2718
2865
  }
2719
2866
  }
2867
+ if (config.components && Object.keys(config.components).length > 0) {
2868
+ output.components = config.components;
2869
+ }
2720
2870
  return stringifyYaml(output, { lineWidth: 0 });
2721
2871
  }
2722
2872
 
@@ -4054,6 +4204,8 @@ function extractFromCSS(files, name = "extracted-theme") {
4054
4204
  export {
4055
4205
  BRAND_VARIANTS,
4056
4206
  BRAND_VISIBILITIES,
4207
+ COMPONENT_TOKEN_FAMILIES,
4208
+ COMPONENT_TOKEN_FAMILY_BY_NAME,
4057
4209
  DEFAULT_BRAND_STRATEGY_SURFACES,
4058
4210
  DEFAULT_BRAND_STRATEGY_TONE_STATES,
4059
4211
  DEFAULT_VISOR_BRAND,
@@ -4064,6 +4216,7 @@ export {
4064
4216
  VISOR_BRANDS_CDN,
4065
4217
  VISOR_DEFAULT_BRAND_PATH,
4066
4218
  VISOR_FONTS_CDN,
4219
+ allComponentTokenNames,
4067
4220
  applyOverrides,
4068
4221
  assignSemanticTokens,
4069
4222
  buildVisorBrandUrl,
@@ -4073,6 +4226,7 @@ export {
4073
4226
  clampToSrgb,
4074
4227
  cleanFontValue,
4075
4228
  collectBrandPassthrough,
4229
+ componentTokenName,
4076
4230
  compositeOverBackground,
4077
4231
  exportTheme,
4078
4232
  extractFromCSS,
@@ -4095,6 +4249,7 @@ export {
4095
4249
  getLuminance,
4096
4250
  googleFontsCatalog,
4097
4251
  hasBrandPassthrough,
4252
+ hasComponentBindings,
4098
4253
  hexToOklch,
4099
4254
  hexToRgb,
4100
4255
  isValidColor,
@@ -4114,6 +4269,7 @@ export {
4114
4269
  parseRgba,
4115
4270
  resolveBrandSlot,
4116
4271
  resolveBrandSource,
4272
+ resolveComponentBindings,
4117
4273
  resolveConfig,
4118
4274
  resolveFont,
4119
4275
  resolveThemeBrand,
@@ -4123,6 +4279,7 @@ export {
4123
4279
  serializeColor,
4124
4280
  validate,
4125
4281
  validateBrandStrategy,
4282
+ validateComponentBindings,
4126
4283
  validateConfig,
4127
4284
  validateFontCoverage,
4128
4285
  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
  *
@@ -663,6 +814,16 @@ interface VisorThemeConfig {
663
814
  light?: Record<string, string>;
664
815
  dark?: Record<string, string>;
665
816
  };
817
+ /**
818
+ * VI-625: component-scoped theme bindings for the admin-UI families.
819
+ *
820
+ * Keyed by family (`table`, `chip`, `filter-bar`, …) then by token key; the
821
+ * value is a string (both modes) or `{ light, dark }`. Every key is declared
822
+ * in `component-tokens.ts`, and every consuming component falls back to its
823
+ * Tier-1 expression, so a theme that omits this block renders exactly as it
824
+ * did before the contract existed.
825
+ */
826
+ components?: ComponentTokenBindings;
666
827
  /**
667
828
  * Migration metadata — optional, additive, consumed by `visor migrate` commands.
668
829
  * Does not affect CSS generation or theme application.
@@ -813,6 +974,8 @@ interface ResolvedThemeConfig {
813
974
  light?: Record<string, string>;
814
975
  dark?: Record<string, string>;
815
976
  };
977
+ /** VI-625: component-scoped theme bindings, carried through unchanged. */
978
+ components?: ComponentTokenBindings;
816
979
  /** Original color strings from user input, for round-trip export. */
817
980
  originalColors?: Record<string, string>;
818
981
  /** Color format of each user-provided color, keyed by field name. */
@@ -854,4 +1017,4 @@ interface ThemeData {
854
1017
  output: ThemeOutput;
855
1018
  }
856
1019
 
857
- export { type FontSource 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, type ColorFormat as Y, DEFAULT_BRAND_STRATEGY_SURFACES as Z, DEFAULT_BRAND_STRATEGY_TONE_STATES as _, type FontResolution as a, GOVERNS_WILDCARD as a0, type RGBA as a1, type SemanticTokenValue as a2, 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 };
1020
+ 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.17.1",
3
+ "version": "0.19.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",