@loworbitstudio/visor-theme-engine 0.20.0 → 0.22.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.
@@ -1,4 +1,4 @@
1
- import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-BG-YT4JW.js';
1
+ import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-Wy5n0W9F.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -6,6 +6,7 @@ import {
6
6
  block,
7
7
  buildVisorFontUrl,
8
8
  collectBrandPassthrough,
9
+ collectDeclaredProperties,
9
10
  fontStack,
10
11
  generateDarkCss,
11
12
  generateFontSizeDecls,
@@ -14,6 +15,7 @@ import {
14
15
  generateIntentDecls,
15
16
  generateLightCss,
16
17
  generatePrimitivesCss,
18
+ generateSemanticAliasDecls,
17
19
  generateShadeScale,
18
20
  generateSpaceAliasDecls,
19
21
  generateTextScaleAliasDecls,
@@ -23,7 +25,7 @@ import {
23
25
  resolveThemeBrand,
24
26
  resolveThemeFonts,
25
27
  sectionComment
26
- } from "../chunk-JDYLDGUN.js";
28
+ } from "../chunk-L5DQDEZV.js";
27
29
 
28
30
  // src/adapters/brand-passthrough.ts
29
31
  var SENTINEL_COLOR = "#ff00ff";
@@ -301,14 +303,27 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
301
303
  }
302
304
  semanticLines.push("");
303
305
  }
306
+ const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix, colorScheme }));
307
+ const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
308
+ const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
309
+ if (scopePrefix) {
310
+ const declared = collectDeclaredProperties(
311
+ [primitivesBody, semanticLines.join("\n"), adaptiveBody].join("\n")
312
+ );
313
+ const aliasDecls = generateSemanticAliasDecls(declared);
314
+ if (aliasDecls.length > 0) {
315
+ semanticLines.push(
316
+ sectionComment("visor-core alias re-substitution at theme scope (VI-648)")
317
+ );
318
+ semanticLines.push(block(hostSelector, aliasDecls));
319
+ semanticLines.push("");
320
+ }
321
+ }
304
322
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
305
323
  if (semanticLayer) {
306
324
  lines.push(semanticLayer);
307
325
  lines.push("");
308
326
  }
309
- const lightBody = stripHeader(generateLightCss(input.tokens, { scopePrefix, colorScheme }));
310
- const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
311
- const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
312
327
  lines.push(
313
328
  wrapInLayer("visor-adaptive", adaptiveBody)
314
329
  );
@@ -1260,9 +1260,23 @@ var TABS = "components/ui/tabs/tabs.module.css";
1260
1260
  var SKELETON = "components/ui/skeleton/skeleton.module.css";
1261
1261
  var SPINNER = "components/ui/spinner/spinner.module.css";
1262
1262
  var CHECKBOX = "components/ui/checkbox/checkbox.module.css";
1263
+ var INPUT = "components/ui/input/input.module.css";
1264
+ var TEXTAREA = "components/ui/textarea/textarea.module.css";
1265
+ var SELECT = "components/ui/select/select.module.css";
1266
+ var SWITCH = "components/ui/switch/switch.module.css";
1267
+ var TAG_INPUT = "components/ui/tag-input/tag-input.module.css";
1268
+ var FILE_UPLOAD = "components/ui/file-upload/file-upload.module.css";
1269
+ var BUTTON = "components/ui/button/button.module.css";
1270
+ var FIELD = "components/ui/field/field.module.css";
1271
+ var TEXT = "components/ui/text/text.module.css";
1272
+ var INLINE_EDIT = "components/ui/inline-edit/inline-edit.module.css";
1273
+ var SAVE_STATUS = "components/ui/save-status/save-status.module.css";
1263
1274
  function at(file, fallback) {
1264
1275
  return [{ file, fallback }];
1265
1276
  }
1277
+ function each(files, fallback) {
1278
+ return files.map((file) => ({ file, fallback }));
1279
+ }
1266
1280
  var tableFamily = {
1267
1281
  family: "table",
1268
1282
  prefix: "table",
@@ -1343,7 +1357,12 @@ var chipFamily = {
1343
1357
  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.",
1344
1358
  tokens: [
1345
1359
  { key: "radius", property: "border-radius", description: "Chip corner rounding.", consumers: at(CHIP, "var(--radius-full, 9999px)") },
1346
- { key: "border", property: "border", description: "Chip outline.", consumers: at(CHIP, "var(--stroke-width-thin, 1px) solid var(--border-default, #e5e7eb)") },
1360
+ {
1361
+ key: "border",
1362
+ property: "outline-color (outlined variant, on ::after)",
1363
+ description: "Outlined-chip edge colour. Width and style come from the shared `control` edge, so `control.edge-width: 0` turns it off.",
1364
+ consumers: at(CHIP, "var(--control-edge-color, var(--border-default, #e5e7eb))")
1365
+ },
1347
1366
  { key: "bg", property: "background-color", description: "Resting chip fill.", consumers: at(CHIP, "var(--surface-card, #ffffff)") },
1348
1367
  { key: "text-color", property: "color", description: "Chip label colour.", consumers: at(CHIP, "var(--text-primary, #111827)") },
1349
1368
  { key: "font-family", property: "font-family", description: "Chip label family \u2014 bind a mono face for token/ID chips.", consumers: at(CHIP, "var(--font-body, inherit)") },
@@ -1360,7 +1379,7 @@ var chipFamily = {
1360
1379
  { key: "lg-padding-x", property: "padding", description: "Large chip horizontal padding.", consumers: at(CHIP, "var(--spacing-4, 1rem)") },
1361
1380
  { key: "lg-font-size", property: "font-size", description: "Large chip type size.", consumers: at(CHIP, "var(--font-size-base, 1rem)") },
1362
1381
  { key: "selected-bg", property: "background-color", description: "Selected chip fill.", consumers: at(CHIP, "var(--surface-accent-subtle, #eff6ff)") },
1363
- { key: "selected-border-color", property: "border-color", description: "Selected chip outline colour.", consumers: at(CHIP, "var(--surface-accent-default, #3b82f6)") },
1382
+ { key: "selected-border-color", property: "outline-color (on ::after)", description: "Selected chip edge colour.", consumers: at(CHIP, "var(--surface-accent-default, #3b82f6)") },
1364
1383
  { key: "selected-text-color", property: "color", description: "Selected chip label colour.", consumers: at(CHIP, "var(--text-link, #2563eb)") }
1365
1384
  ]
1366
1385
  };
@@ -1458,7 +1477,12 @@ var emptyStateFamily = {
1458
1477
  { key: "gap", property: "gap", description: "Placard stack rhythm.", consumers: at(EMPTY_STATE, "var(--spacing-2, 0.5rem)") },
1459
1478
  { key: "padding", property: "padding", description: "Default-size placard inset.", consumers: at(EMPTY_STATE, "var(--spacing-8, 2rem) var(--spacing-5, 1.25rem)") },
1460
1479
  { key: "bg", property: "background-color", description: "Default-tone fill.", consumers: at(EMPTY_STATE, "var(--surface-muted, #f9fafb)") },
1461
- { key: "border", property: "border", description: "Default-tone frame (dashed by default).", consumers: at(EMPTY_STATE, "1px dashed var(--border-default, #e5e7eb)") },
1480
+ {
1481
+ key: "border",
1482
+ property: "outline-color",
1483
+ description: "Default-tone dashed-edge colour. Width and style come from the shared `control` drop edge, so `control.edge-width: 0` turns it off.",
1484
+ consumers: at(EMPTY_STATE, "var(--control-edge-color, var(--border-default, #e5e7eb))")
1485
+ },
1462
1486
  { key: "icon-size", property: "width / height", description: "Icon-chip diameter.", consumers: at(EMPTY_STATE, "72px") },
1463
1487
  { key: "icon-radius", property: "border-radius", description: "Icon-chip rounding.", consumers: at(EMPTY_STATE, "var(--radius-full, 9999px)") },
1464
1488
  { key: "icon-bg", property: "background-color", description: "Icon-chip fill.", consumers: at(EMPTY_STATE, "var(--surface-subtle, #f5f5f6)") },
@@ -1606,9 +1630,238 @@ var checkboxFamily = {
1606
1630
  { key: "size", property: "width / height", description: "Checkbox box size.", consumers: at(CHECKBOX, "1rem") },
1607
1631
  { key: "radius", property: "border-radius", description: "Checkbox corner rounding \u2014 square it for the editorial admin look.", consumers: at(CHECKBOX, "var(--radius-sm, 0.25rem)") },
1608
1632
  { key: "bg", property: "background-color", description: "Unchecked fill.", consumers: at(CHECKBOX, "transparent") },
1609
- { key: "border", property: "border", description: "Unchecked outline.", consumers: at(CHECKBOX, "1px solid var(--border-default, #e5e7eb)") },
1633
+ {
1634
+ key: "border",
1635
+ property: "outline-color",
1636
+ description: "Unchecked edge colour. Width and style come from the shared `control` edge, so `control.edge-width: 0` turns it off.",
1637
+ consumers: at(CHECKBOX, "var(--control-edge-color, var(--border-default, #e5e7eb))")
1638
+ },
1639
+ {
1640
+ key: "edgeless-bg",
1641
+ property: "background-image",
1642
+ description: "Unchecked fill shown only while the shared edge is off (`control.edge-width: 0`), so the box still reads as a control.",
1643
+ consumers: at(CHECKBOX, "var(--control-edge-color, var(--border-default, #e5e7eb))")
1644
+ },
1610
1645
  { key: "bg-checked", property: "background-color", description: "Checked fill.", consumers: at(CHECKBOX, "var(--interactive-primary-bg, var(--primary, #111827))") },
1611
- { key: "border-checked", property: "border-color", description: "Checked outline colour.", consumers: at(CHECKBOX, "var(--interactive-primary-bg, var(--primary, #111827))") }
1646
+ { key: "border-checked", property: "outline-color", description: "Checked edge colour.", consumers: at(CHECKBOX, "var(--interactive-primary-bg, var(--primary, #111827))") }
1647
+ ]
1648
+ };
1649
+ var EDGE_CONTROLS = [INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT, BUTTON];
1650
+ var controlFamily = {
1651
+ family: "control",
1652
+ prefix: "control",
1653
+ description: "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift.",
1654
+ tokens: [
1655
+ {
1656
+ key: "edge-width",
1657
+ property: "outline-width",
1658
+ description: "THE switch. Resting-edge weight for every form control; `0` turns every resting edge off.",
1659
+ consumers: [
1660
+ ...each(EDGE_CONTROLS, "1px"),
1661
+ { file: CHIP, fallback: "var(--stroke-width-thin, 1px)" },
1662
+ { file: FILE_UPLOAD, fallback: "1px" },
1663
+ { file: EMPTY_STATE, fallback: "1px" }
1664
+ ]
1665
+ },
1666
+ {
1667
+ key: "edge-color",
1668
+ property: "outline-color",
1669
+ description: "Resting-edge colour. A component's own `--<component>-border` override still wins over it.",
1670
+ consumers: each([INPUT, TEXTAREA, SELECT, CHECKBOX, TAG_INPUT, CHIP, FILE_UPLOAD, EMPTY_STATE, BUTTON], "var(--border-default, #e5e7eb)")
1671
+ },
1672
+ {
1673
+ key: "edge-style",
1674
+ property: "outline-style",
1675
+ description: "Resting-edge style for solid edges.",
1676
+ consumers: each([...EDGE_CONTROLS, CHIP], "solid")
1677
+ },
1678
+ {
1679
+ key: "drop-edge-width",
1680
+ property: "outline-width",
1681
+ description: "Dashed-edge weight (file-upload draws it at 2\xD7). Follows `edge-width` unless set \u2014 set it alone to keep dashed edges while solid edges are off.",
1682
+ consumers: each([FILE_UPLOAD, EMPTY_STATE], "var(--control-edge-width, 1px)")
1683
+ },
1684
+ {
1685
+ key: "drop-edge-style",
1686
+ property: "outline-style",
1687
+ description: "Dashed-edge style for drop targets and empty states.",
1688
+ consumers: each([FILE_UPLOAD, EMPTY_STATE], "dashed")
1689
+ },
1690
+ {
1691
+ key: "state-edge-width",
1692
+ property: "outline-width",
1693
+ description: "Focus and invalid edge weight. Independent of `edge-width`, so focus and invalid stay visible with edges off.",
1694
+ consumers: each([INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT], "1px")
1695
+ }
1696
+ ]
1697
+ };
1698
+ var fieldFamily = {
1699
+ family: "field",
1700
+ prefix: "field",
1701
+ description: "Field label treatment \u2014 size, case, tracking and colour of `FieldLabel`, in both densities.",
1702
+ tokens: [
1703
+ {
1704
+ key: "label-font-size",
1705
+ property: "font-size",
1706
+ description: "Label type size. Drives both the standard and editorial-density steps.",
1707
+ consumers: [
1708
+ { file: FIELD, fallback: "var(--font-size-sm, 0.875rem)" },
1709
+ { file: FIELD, fallback: "13px" }
1710
+ ]
1711
+ },
1712
+ { key: "label-text-transform", property: "text-transform", description: "Label casing.", consumers: at(FIELD, "revert-layer") },
1713
+ { key: "label-letter-spacing", property: "letter-spacing", description: "Label tracking.", consumers: at(FIELD, "revert-layer") },
1714
+ {
1715
+ key: "label-color",
1716
+ property: "color",
1717
+ description: "Label colour. Drives both the standard and editorial-density steps.",
1718
+ consumers: [
1719
+ { file: FIELD, fallback: "var(--text-primary, currentColor)" },
1720
+ { file: FIELD, fallback: "var(--text-secondary)" }
1721
+ ]
1722
+ }
1723
+ ]
1724
+ };
1725
+ var BUTTON_SIZES = [
1726
+ { size: "sm", fontSize: ["var(--font-size-xs, 0.75rem)", "0.8125rem"], radius: "var(--radius-md, 0.375rem)" },
1727
+ { size: "md", fontSize: ["var(--font-size-sm, 0.875rem)"], radius: "var(--radius-md, 0.375rem)" },
1728
+ { size: "lg", fontSize: ["var(--font-size-base, 1rem)"], radius: "var(--radius-md, 0.375rem)" },
1729
+ { size: "dlg", fontSize: ["var(--font-size-2xs, 0.6875rem)"], radius: "var(--radius-lg, 0.5rem)" }
1730
+ ];
1731
+ var buttonFamily = {
1732
+ family: "button",
1733
+ prefix: "button",
1734
+ description: "Button type treatment and rounding. The unprefixed keys set every size at once; a size key (`dlg-radius`) overrides one size.",
1735
+ tokens: [
1736
+ {
1737
+ key: "text-transform",
1738
+ property: "text-transform",
1739
+ description: "Casing for every size.",
1740
+ consumers: BUTTON_SIZES.map(() => ({ file: BUTTON, fallback: "revert-layer" }))
1741
+ },
1742
+ {
1743
+ key: "letter-spacing",
1744
+ property: "letter-spacing",
1745
+ description: "Tracking for every size.",
1746
+ consumers: BUTTON_SIZES.map(() => ({ file: BUTTON, fallback: "revert-layer" }))
1747
+ },
1748
+ {
1749
+ key: "radius",
1750
+ property: "border-radius",
1751
+ description: "Corner rounding for every size (`999px` for a pill).",
1752
+ consumers: [
1753
+ ...BUTTON_SIZES.map(({ radius }) => ({ file: BUTTON, fallback: radius })),
1754
+ { file: BUTTON, fallback: "var(--radius-md, 0.375rem)" }
1755
+ ]
1756
+ },
1757
+ ...BUTTON_SIZES.flatMap(({ size, fontSize, radius }) => [
1758
+ {
1759
+ key: `${size}-font-size`,
1760
+ property: "font-size",
1761
+ description: fontSize.length > 1 ? `\`${size}\` type size. Drives both the standard and editorial-density steps.` : `\`${size}\` type size.`,
1762
+ consumers: fontSize.map((fallback) => ({ file: BUTTON, fallback }))
1763
+ },
1764
+ {
1765
+ key: `${size}-text-transform`,
1766
+ property: "text-transform",
1767
+ description: `\`${size}\` casing.`,
1768
+ consumers: at(BUTTON, "var(--button-text-transform, revert-layer)")
1769
+ },
1770
+ {
1771
+ key: `${size}-letter-spacing`,
1772
+ property: "letter-spacing",
1773
+ description: `\`${size}\` tracking.`,
1774
+ consumers: at(BUTTON, "var(--button-letter-spacing, revert-layer)")
1775
+ },
1776
+ {
1777
+ key: `${size}-radius`,
1778
+ property: "border-radius",
1779
+ description: `\`${size}\` corner rounding.`,
1780
+ consumers: at(BUTTON, `var(--button-radius, ${radius})`)
1781
+ }
1782
+ ]),
1783
+ // VI-659: the icon-only size. A square box of glyph + 2 × pad.
1784
+ // VI-658: InlineEdit's pencil is the same mark, so it reads the same size,
1785
+ // pad and ink.
1786
+ {
1787
+ key: "icon-size",
1788
+ property: "width / height / font-size",
1789
+ description: 'Icon-only (`size="icon"`) glyph size. The square box is this plus `icon-pad` on every side. Also sizes the InlineEdit pencil and the SaveStatus retry mark.',
1790
+ consumers: [...each(Array(5).fill(BUTTON), "1rem"), ...each(Array(3).fill(INLINE_EDIT), "1rem"), ...each(Array(3).fill(SAVE_STATUS), "1rem")]
1791
+ },
1792
+ {
1793
+ key: "icon-pad",
1794
+ property: "width / height",
1795
+ description: "Icon-only hit-target pad around the glyph, on every side. Also the hit target of the InlineEdit pencil and the SaveStatus retry mark.",
1796
+ consumers: [...each([BUTTON, BUTTON], "var(--spacing-2, 0.5rem)"), ...at(INLINE_EDIT, "var(--spacing-2, 0.5rem)"), ...at(SAVE_STATUS, "var(--spacing-2, 0.5rem)")]
1797
+ },
1798
+ {
1799
+ key: "icon-radius",
1800
+ property: "border-radius",
1801
+ description: "Icon-only corner rounding (`999px` for a round mark).",
1802
+ consumers: at(BUTTON, "var(--button-radius, var(--radius-md, 0.375rem))")
1803
+ },
1804
+ {
1805
+ key: "icon-ghost-color",
1806
+ property: "color",
1807
+ description: "Icon-only ghost ink at rest. Hover comes up to `--text-primary`. Also the ink of the InlineEdit pencil and the SaveStatus retry mark.",
1808
+ consumers: [...at(BUTTON, "var(--text-secondary, #6b7280)"), ...at(INLINE_EDIT, "var(--text-secondary, #6b7280)"), ...at(SAVE_STATUS, "var(--text-secondary, #6b7280)")]
1809
+ }
1810
+ ]
1811
+ };
1812
+ var TEXT_SIZES = [
1813
+ { size: "xs", fontSize: "var(--font-size-xs, 0.75rem)" },
1814
+ { size: "sm", fontSize: "var(--font-size-sm, 0.875rem)" },
1815
+ { size: "md", fontSize: "var(--font-size-base, 1rem)" },
1816
+ { size: "lg", fontSize: "var(--font-size-lg, 1.125rem)" },
1817
+ { size: "xl", fontSize: "var(--font-size-xl, 1.25rem)" }
1818
+ ];
1819
+ var textFamily = {
1820
+ family: "text",
1821
+ prefix: "text",
1822
+ description: "Text type treatment, per size \u2014 set `sm-font-size: 13px` for a 13px step, or caps and tracking on `xs` for a state tag. Every key begins with a size.",
1823
+ tokens: TEXT_SIZES.flatMap(({ size, fontSize }) => [
1824
+ { key: `${size}-font-size`, property: "font-size", description: `\`${size}\` type size.`, consumers: at(TEXT, fontSize) },
1825
+ { key: `${size}-text-transform`, property: "text-transform", description: `\`${size}\` casing.`, consumers: at(TEXT, "revert-layer") },
1826
+ { key: `${size}-letter-spacing`, property: "letter-spacing", description: `\`${size}\` tracking.`, consumers: at(TEXT, "revert-layer") }
1827
+ ])
1828
+ };
1829
+ var switchFamily = {
1830
+ family: "switch",
1831
+ prefix: "switch",
1832
+ description: 'Switch track and knob dimensions (default size; `size="sm"` keeps its own). Set `knob-inset` to size the knob from the track: it sits that far from the track\'s outer edge on every side.',
1833
+ tokens: [
1834
+ { key: "track-width", property: "width", description: "Track width.", consumers: at(SWITCH, "2rem") },
1835
+ {
1836
+ key: "track-height",
1837
+ property: "height",
1838
+ description: "Track height. With `knob-inset` set, the knob is the track height less the inset on both sides.",
1839
+ consumers: at(SWITCH, "1.15rem")
1840
+ },
1841
+ {
1842
+ key: "knob-inset",
1843
+ property: "width / height / transform (knob)",
1844
+ description: "Gap between the knob and the track's outer edge, on every side. Unset, the knob keeps its shipped 1rem size and position.",
1845
+ consumers: at(SWITCH, null)
1846
+ },
1847
+ { key: "track-bg", property: "background-color", description: "Unchecked track fill.", consumers: at(SWITCH, "var(--border-default, #e5e7eb)") },
1848
+ {
1849
+ key: "edge-color",
1850
+ property: "outline-color",
1851
+ description: "Track edge colour \u2014 transparent by default. Width and style come from the shared `control` edge, so `control.edge-width: 0` turns it off.",
1852
+ consumers: at(SWITCH, "transparent")
1853
+ }
1854
+ ]
1855
+ };
1856
+ var saveStatusFamily = {
1857
+ family: "save-status",
1858
+ prefix: "save-status",
1859
+ description: "SaveStatus readout \u2014 the fixed-width slot and its type, so the readout never reflows its row.",
1860
+ tokens: [
1861
+ { key: "width", property: "width", description: "Slot width. Fixed, so the row never reflows between states.", consumers: at(SAVE_STATUS, "7.5rem") },
1862
+ { key: "font-family", property: "font-family", description: "Readout family (e.g. the theme's mono).", consumers: at(SAVE_STATUS, "revert-layer") },
1863
+ { key: "text-transform", property: "text-transform", description: "Readout casing.", consumers: at(SAVE_STATUS, "revert-layer") },
1864
+ { key: "letter-spacing", property: "letter-spacing", description: "Readout tracking.", consumers: at(SAVE_STATUS, "revert-layer") }
1612
1865
  ]
1613
1866
  };
1614
1867
  var adminUiFamily = {
@@ -1639,6 +1892,12 @@ var COMPONENT_TOKEN_FAMILIES = [
1639
1892
  skeletonFamily,
1640
1893
  spinnerFamily,
1641
1894
  checkboxFamily,
1895
+ controlFamily,
1896
+ fieldFamily,
1897
+ buttonFamily,
1898
+ textFamily,
1899
+ switchFamily,
1900
+ saveStatusFamily,
1642
1901
  adminUiFamily
1643
1902
  ];
1644
1903
  var COMPONENT_TOKEN_FAMILY_BY_NAME = new Map(
@@ -2484,6 +2743,86 @@ function generateFullBundleCss(primitives, tokens, config) {
2484
2743
  return lines.join("\n");
2485
2744
  }
2486
2745
 
2746
+ // src/semantic-aliases.ts
2747
+ var VISOR_CORE_SEMANTIC_ALIASES = {
2748
+ // Border — the one entry the engine's own `SemanticTokens.border` set lacks.
2749
+ "border-input": "color-neutral-200",
2750
+ // Component + layout spacing (4px grid multiples).
2751
+ "component-xs": "spacing-1",
2752
+ "component-sm": "spacing-2",
2753
+ "component-md": "spacing-4",
2754
+ "component-lg": "spacing-6",
2755
+ "component-xl": "spacing-8",
2756
+ "layout-sm": "spacing-4",
2757
+ "layout-md": "spacing-8",
2758
+ "layout-lg": "spacing-12",
2759
+ "layout-xl": "spacing-16",
2760
+ "layout-2xl": "spacing-24",
2761
+ "layout-section-padding": "spacing-24",
2762
+ "layout-section-padding-mobile": "spacing-16",
2763
+ // Typography size roles — track the theme's `typography.scale` ramp.
2764
+ "size-body": "font-size-base",
2765
+ "size-body-sm": "font-size-sm",
2766
+ "size-label": "font-size-sm",
2767
+ "size-caption": "font-size-xs",
2768
+ "size-heading-sm": "font-size-lg",
2769
+ "size-heading-md": "font-size-xl",
2770
+ "size-heading-lg": "font-size-2xl",
2771
+ "size-heading-xl": "font-size-3xl",
2772
+ // Typography weight roles — `--weight-heading` is the token VI-648 was filed on.
2773
+ "weight-body": "font-weight-body",
2774
+ "weight-label": "font-weight-medium",
2775
+ "weight-heading": "font-weight-heading",
2776
+ "weight-strong": "font-weight-bold",
2777
+ // Motion. `--motion-easing-spring` is absent because the engine emits it
2778
+ // under its own name, so it is already correct on the scope.
2779
+ "motion-duration-fast": "motion-duration-100",
2780
+ "motion-duration-normal": "motion-duration-200",
2781
+ "motion-duration-slow": "motion-duration-500",
2782
+ "motion-easing-default": "motion-easing-ease-in-out",
2783
+ "motion-easing-enter": "motion-easing-ease-out",
2784
+ "motion-easing-exit": "motion-easing-ease-in",
2785
+ // Skeleton shimmer. Resolves through *semantic* surfaces, not primitives —
2786
+ // the engine emits those in `visor-adaptive`, a later layer, which does not
2787
+ // affect substitution: `var()` reads the computed value on the element.
2788
+ "skeleton-from": "surface-muted",
2789
+ "skeleton-to": "surface-subtle",
2790
+ // Chart series.
2791
+ "chart-1": "color-primary-500",
2792
+ "chart-2": "color-success-500",
2793
+ "chart-3": "color-warning-500",
2794
+ "chart-4": "color-info-500",
2795
+ "chart-5": "color-error-500",
2796
+ // Sidebar chrome.
2797
+ "sidebar-bg": "color-neutral-50",
2798
+ "sidebar-text": "color-neutral-700",
2799
+ "sidebar-primary-bg": "color-primary-600",
2800
+ "sidebar-primary-text": "color-white",
2801
+ "sidebar-accent-bg": "color-neutral-100",
2802
+ "sidebar-accent-text": "color-neutral-900",
2803
+ "sidebar-border": "color-neutral-200",
2804
+ "sidebar-ring": "color-primary-500",
2805
+ "sidebar-text-muted": "color-neutral-500",
2806
+ // Field-attached floating panels (VI-497).
2807
+ "field-menu-bg": "surface-popover"
2808
+ };
2809
+ function collectDeclaredProperties(css) {
2810
+ const declared = /* @__PURE__ */ new Set();
2811
+ for (const match of css.matchAll(/(^|[{;\s])--([a-zA-Z0-9-]+)\s*:/g)) {
2812
+ declared.add(match[2]);
2813
+ }
2814
+ return declared;
2815
+ }
2816
+ function generateSemanticAliasDecls(declared) {
2817
+ const decls = [];
2818
+ for (const [alias, referent] of Object.entries(VISOR_CORE_SEMANTIC_ALIASES)) {
2819
+ if (declared.has(alias)) continue;
2820
+ if (!declared.has(referent)) continue;
2821
+ decls.push(`--${alias}: var(--${referent});`);
2822
+ }
2823
+ return decls;
2824
+ }
2825
+
2487
2826
  // src/types.ts
2488
2827
  var MATERIAL_TEXT_SLOTS = [
2489
2828
  "displayLarge",
@@ -2577,5 +2916,8 @@ export {
2577
2916
  generateHairlineDecls,
2578
2917
  generateLightCss,
2579
2918
  generateDarkCss,
2580
- generateFullBundleCss
2919
+ generateFullBundleCss,
2920
+ VISOR_CORE_SEMANTIC_ALIASES,
2921
+ collectDeclaredProperties,
2922
+ generateSemanticAliasDecls
2581
2923
  };
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-BG-YT4JW.js';
2
- export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-BG-YT4JW.js';
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-Wy5n0W9F.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-Wy5n0W9F.js';
3
3
 
4
4
  /**
5
5
  * Font resolver — maps font family names to loadable font resources.
@@ -400,6 +400,14 @@ var properties = {
400
400
  ],
401
401
  description: "Brand constraint declaring which color modes the theme supports. 'dark-only'/'light-only' lock the brand to a single mode; 'adaptive' supports both. Complements 'default-mode' (the runtime default) — 'color-scheme' is authoritative for the brand-lock. Defaults to 'adaptive' when omitted. Optional."
402
402
  },
403
+ edges: {
404
+ type: "string",
405
+ "enum": [
406
+ "on",
407
+ "off"
408
+ ],
409
+ description: "The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: \"0\" (an explicit binding there wins). Defaults to 'on'. Optional."
410
+ },
403
411
  colors: {
404
412
  type: "object",
405
413
  description: "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
@@ -1123,7 +1131,14 @@ var properties = {
1123
1131
  },
1124
1132
  checkbox: {
1125
1133
  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.",
1134
+ 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, edgeless-bg, bg-checked, border-checked.",
1135
+ additionalProperties: {
1136
+ $ref: "#/$defs/componentTokenBinding"
1137
+ }
1138
+ },
1139
+ control: {
1140
+ type: "object",
1141
+ description: "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
1127
1142
  additionalProperties: {
1128
1143
  $ref: "#/$defs/componentTokenBinding"
1129
1144
  }
@@ -1135,6 +1150,41 @@ var properties = {
1135
1150
  $ref: "#/$defs/componentTokenBinding"
1136
1151
  }
1137
1152
  },
1153
+ field: {
1154
+ type: "object",
1155
+ description: "Field label treatment — size, case, tracking and colour of `FieldLabel`, in both densities. Keys: label-font-size, label-text-transform, label-letter-spacing, label-color.",
1156
+ additionalProperties: {
1157
+ $ref: "#/$defs/componentTokenBinding"
1158
+ }
1159
+ },
1160
+ button: {
1161
+ type: "object",
1162
+ description: "Button type treatment and rounding. The unprefixed keys set every size at once; a size key (`dlg-radius`) overrides one size. Keys: text-transform, letter-spacing, radius, sm-font-size, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
1163
+ additionalProperties: {
1164
+ $ref: "#/$defs/componentTokenBinding"
1165
+ }
1166
+ },
1167
+ text: {
1168
+ type: "object",
1169
+ description: "Text type treatment, per size — set `sm-font-size: 13px` for a 13px step, or caps and tracking on `xs` for a state tag. Every key begins with a size. Keys: xs-font-size, xs-text-transform, xs-letter-spacing, sm-font-size, sm-text-transform, sm-letter-spacing, md-font-size, md-text-transform, md-letter-spacing, lg-font-size, lg-text-transform, lg-letter-spacing, xl-font-size, xl-text-transform, xl-letter-spacing.",
1170
+ additionalProperties: {
1171
+ $ref: "#/$defs/componentTokenBinding"
1172
+ }
1173
+ },
1174
+ "switch": {
1175
+ type: "object",
1176
+ description: "Switch track and knob dimensions (default size; `size=\"sm\"` keeps its own). Set `knob-inset` to size the knob from the track: it sits that far from the track's outer edge on every side. Keys: track-width, track-height, knob-inset, track-bg, edge-color.",
1177
+ additionalProperties: {
1178
+ $ref: "#/$defs/componentTokenBinding"
1179
+ }
1180
+ },
1181
+ "save-status": {
1182
+ type: "object",
1183
+ description: "SaveStatus readout — the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
1184
+ additionalProperties: {
1185
+ $ref: "#/$defs/componentTokenBinding"
1186
+ }
1187
+ },
1138
1188
  "admin-ui": {
1139
1189
  type: "object",
1140
1190
  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.",
@@ -1876,6 +1926,87 @@ declare const SEMANTIC_MAP: {
1876
1926
  */
1877
1927
  declare function getKnownTokenRefs(): Set<string>;
1878
1928
 
1929
+ /**
1930
+ * visor-core semantic aliases that need re-substituting at a theme scope (VI-648).
1931
+ *
1932
+ * `packages/tokens` emits its semantic layer as indirections — `--chart-1:
1933
+ * var(--color-primary-500)`, `--weight-heading: var(--font-weight-heading)` —
1934
+ * declared on `:root`.
1935
+ *
1936
+ * Custom-property substitution resolves **where the property is declared**. On
1937
+ * a `scopePrefix` theme (the VI-368 body-class repaint pattern that every
1938
+ * Blacklight theme uses) the engine emits the theme's primitives on the scope
1939
+ * selector, e.g. `body.blacklight-pro-theme`. From `:root`, a descendant's
1940
+ * value is invisible, so `var(--color-primary-500)` sees only visor-core's
1941
+ * untuned default. `body` then *inherits* the already-substituted default, and
1942
+ * the alias silently ignores the theme:
1943
+ *
1944
+ * ```
1945
+ * --font-weight-heading : 500 <- correct, emitted by the engine on the scope
1946
+ * --weight-heading : 600 <- visor-core's :root default
1947
+ * --chart-1 : #3b82f6 <- stock blue on a monochrome theme
1948
+ * --skeleton-from : #f3f4f6 <- light gray on a dark theme
1949
+ * ```
1950
+ *
1951
+ * The engine independently resolves 66 of visor-core's 114 aliases to concrete
1952
+ * values (`--text-*`, `--surface-*`, `--border-*`, `--interactive-*`, `--font-*`)
1953
+ * and emits those on the scope already, so they are correct. The 48 below are
1954
+ * the ones only visor-core knows how to derive. Re-declaring them on the scope
1955
+ * selector makes the substitution happen there, against the theme's own
1956
+ * primitives.
1957
+ *
1958
+ * ## Why this table lives in the engine, not in `packages/tokens`
1959
+ *
1960
+ * `packages/tokens` is the natural owner — these are its aliases. But
1961
+ * `packages/tokens` already depends on this package (its build calls
1962
+ * `generateThemeData` + `docsAdapter` to emit `dist/themes/<slug>.css`), so an
1963
+ * engine → tokens import would be a build cycle. The table therefore sits on
1964
+ * the importable side of that edge.
1965
+ *
1966
+ * It is not a hand-maintained copy. `packages/tokens` owns the authoring
1967
+ * tables in `src/tokens/semantic.ts` and this list is asserted against the
1968
+ * *emitted* `tokens.css` by `semantic-alias-coverage` — a stronger check than
1969
+ * comparing two constants, because it compares against what actually ships.
1970
+ * Adding an alias in `packages/tokens` without adding it here fails that test
1971
+ * with the missing names.
1972
+ */
1973
+ /**
1974
+ * Alias name → the token it resolves through. Both written without the leading
1975
+ * `--`. Order mirrors visor-core's emission order in `tokens.css`, which keeps
1976
+ * a diff against that file readable.
1977
+ */
1978
+ declare const VISOR_CORE_SEMANTIC_ALIASES: Readonly<Record<string, string>>;
1979
+ /**
1980
+ * Every custom property declared anywhere in `css`, without the leading `--`.
1981
+ *
1982
+ * Read off the generated CSS rather than the token data so the filter in
1983
+ * `generateSemanticAliasDecls` reflects what this theme *actually* emits —
1984
+ * including conditional output such as the `--font-weight-<n>` ladder, which
1985
+ * only appears when a theme declares an explicit `weights` array.
1986
+ */
1987
+ declare function collectDeclaredProperties(css: string): Set<string>;
1988
+ /**
1989
+ * The `--alias: var(--referent);` declarations to re-emit on a theme's scope
1990
+ * selector, given everything the theme has emitted so far.
1991
+ *
1992
+ * Filtered on both sides, so the table can never make output worse:
1993
+ *
1994
+ * - **Skip an alias the theme already declares.** The theme's own value is
1995
+ * authoritative, and re-declaring it in `visor-semantic` would override a
1996
+ * `visor-primitives` declaration of the same name. `--font-body` is the live
1997
+ * case: the engine emits it as a concrete family, and re-aliasing it to
1998
+ * `var(--font-sans)` would win the layer contest and resolve to nothing.
1999
+ * - **Skip an alias whose referent the theme does not declare.** There is then
2000
+ * nothing to re-resolve against, and inheriting visor-core's `:root` value is
2001
+ * the correct outcome — re-emitting would replace a working value with an
2002
+ * invalid one.
2003
+ *
2004
+ * Returns `[]` for a `:root`-scoped theme's caller, which does not need this at
2005
+ * all: there, visor-core's aliases and the theme's primitives are declared on
2006
+ * the same element, so substitution already sees the theme's values.
2007
+ */
2008
+ declare function generateSemanticAliasDecls(declared: ReadonlySet<string>): string[];
2009
+
1879
2010
  /**
1880
2011
  * Per-theme font-family aliasing — substrate fix for VI-354.
1881
2012
  *
@@ -2126,4 +2257,4 @@ declare function cleanFontValue(val: string): string;
2126
2257
  */
2127
2258
  declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
2128
2259
 
2129
- export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, type ResolvedWeightRamp, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, WEIGHT_RAMP_TARGETS, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, compositeOverBackground, declaresWeights, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFontWeightDecls, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, loadedWeights, lookupFontWeightAlias, lookupGoogleFont, matchLoadedWeight, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveRoleWeight, resolveThemeBrand, resolveThemeFonts, resolveWeightRamp, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
2260
+ export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, FullShadeScale, GeneratedPrimitives, GoogleFontEntry, OKLCH, ParsedColor, RGB, ResolvedThemeConfig, type ResolvedWeightRamp, SEMANTIC_MAP, SelectiveShadeScale, SemanticTokens, SerializedBrandStrategy, ShadeStep, TAILWIND_GRAY, ThemeBrandResult, ThemeData, ThemeFontResult, ThemeOutput, type ThemeValidationResult, VISOR_BRANDS_CDN, VISOR_CORE_SEMANTIC_ALIASES, VISOR_DEFAULT_BRAND_PATH, VISOR_FONTS_CDN, type ValidateOptions, type ValidationIssue, type ValidationSeverity, VisorBrand, VisorThemeConfig, VisorTypography, WEIGHT_RAMP_TARGETS, applyOverrides, assignSemanticTokens, buildVisorBrandUrl, buildVisorFontUrl, checkBrandStrategyCoherence, checkBrandStrategyStructure, clampToSrgb, cleanFontValue, collectBrandPassthrough, collectDeclaredProperties, compositeOverBackground, declaresWeights, exportTheme, extractFromCSS, formatFontCoverageError, generateDarkCss, generateFontWeightDecls, generateFullBundleCss, generateLightCss, generatePreloadLinks, generatePrimitives, generatePrimitivesCss, generateSemanticAliasDecls, generateSemanticCss, generateShadeScale, generateStylesheetLinks, generateTheme, generateThemeData, generateThemeDataFromConfig, generateThemeFromConfig, getContrastRatio, getKnownTokenRefs, getLuminance, googleFontsCatalog, hasBrandPassthrough, hexToOklch, hexToRgb, isValidColor, isValidHex, isVisorThemeConfig, loadedWeights, lookupFontWeightAlias, lookupGoogleFont, matchLoadedWeight, normalizeHex, oklchToHex, parseCSSDeclarations, parseColor, parseConfig, parseFontFaceDeclarations, parseHex, parseHsla, parseOklch, parseRgba, resolveBrandSlot, resolveBrandSource, resolveConfig, resolveFont, resolveRoleWeight, resolveThemeBrand, resolveThemeFonts, resolveWeightRamp, rgbToHex, serializeBrandStrategy, serializeColor, validate, validateBrandStrategy, validateConfig, validateFontCoverage, visorTheme_schema as visorThemeSchema };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  MATERIAL_TEXT_SLOTS,
8
8
  TAILWIND_GRAY,
9
9
  VISOR_BRANDS_CDN,
10
+ VISOR_CORE_SEMANTIC_ALIASES,
10
11
  VISOR_DEFAULT_BRAND_PATH,
11
12
  VISOR_FONTS_CDN,
12
13
  WEIGHT_RAMP_TARGETS,
@@ -16,6 +17,7 @@ import {
16
17
  buildVisorFontUrl,
17
18
  clampToSrgb,
18
19
  collectBrandPassthrough,
20
+ collectDeclaredProperties,
19
21
  componentTokenName,
20
22
  compositeOverBackground,
21
23
  declaresWeights,
@@ -25,6 +27,7 @@ import {
25
27
  generateLightCss,
26
28
  generatePreloadLinks,
27
29
  generatePrimitivesCss,
30
+ generateSemanticAliasDecls,
28
31
  generateSemanticCss,
29
32
  generateShadeScale,
30
33
  generateStylesheetLinks,
@@ -60,7 +63,7 @@ import {
60
63
  rgbToOklch,
61
64
  serializeColor,
62
65
  validateComponentBindings
63
- } from "./chunk-JDYLDGUN.js";
66
+ } from "./chunk-L5DQDEZV.js";
64
67
 
65
68
  // src/fonts/validate-coverage.ts
66
69
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -647,6 +650,11 @@ var visor_theme_schema_default = {
647
650
  enum: ["dark-only", "light-only", "adaptive"],
648
651
  description: "Brand constraint declaring which color modes the theme supports. 'dark-only'/'light-only' lock the brand to a single mode; 'adaptive' supports both. Complements 'default-mode' (the runtime default) \u2014 'color-scheme' is authoritative for the brand-lock. Defaults to 'adaptive' when omitted. Optional."
649
652
  },
653
+ edges: {
654
+ type: "string",
655
+ enum: ["on", "off"],
656
+ description: `The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: "0" (an explicit binding there wins). Defaults to 'on'. Optional.`
657
+ },
650
658
  colors: {
651
659
  type: "object",
652
660
  description: "Color definitions for light mode. Only primary is required \u2014 all others have sensible defaults.",
@@ -1162,7 +1170,14 @@ var visor_theme_schema_default = {
1162
1170
  },
1163
1171
  checkbox: {
1164
1172
  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.",
1173
+ 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, edgeless-bg, bg-checked, border-checked.",
1174
+ additionalProperties: {
1175
+ $ref: "#/$defs/componentTokenBinding"
1176
+ }
1177
+ },
1178
+ control: {
1179
+ type: "object",
1180
+ description: "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
1166
1181
  additionalProperties: {
1167
1182
  $ref: "#/$defs/componentTokenBinding"
1168
1183
  }
@@ -1174,6 +1189,41 @@ var visor_theme_schema_default = {
1174
1189
  $ref: "#/$defs/componentTokenBinding"
1175
1190
  }
1176
1191
  },
1192
+ field: {
1193
+ type: "object",
1194
+ description: "Field label treatment \u2014 size, case, tracking and colour of `FieldLabel`, in both densities. Keys: label-font-size, label-text-transform, label-letter-spacing, label-color.",
1195
+ additionalProperties: {
1196
+ $ref: "#/$defs/componentTokenBinding"
1197
+ }
1198
+ },
1199
+ button: {
1200
+ type: "object",
1201
+ description: "Button type treatment and rounding. The unprefixed keys set every size at once; a size key (`dlg-radius`) overrides one size. Keys: text-transform, letter-spacing, radius, sm-font-size, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
1202
+ additionalProperties: {
1203
+ $ref: "#/$defs/componentTokenBinding"
1204
+ }
1205
+ },
1206
+ text: {
1207
+ type: "object",
1208
+ description: "Text type treatment, per size \u2014 set `sm-font-size: 13px` for a 13px step, or caps and tracking on `xs` for a state tag. Every key begins with a size. Keys: xs-font-size, xs-text-transform, xs-letter-spacing, sm-font-size, sm-text-transform, sm-letter-spacing, md-font-size, md-text-transform, md-letter-spacing, lg-font-size, lg-text-transform, lg-letter-spacing, xl-font-size, xl-text-transform, xl-letter-spacing.",
1209
+ additionalProperties: {
1210
+ $ref: "#/$defs/componentTokenBinding"
1211
+ }
1212
+ },
1213
+ switch: {
1214
+ type: "object",
1215
+ description: 'Switch track and knob dimensions (default size; `size="sm"` keeps its own). Set `knob-inset` to size the knob from the track: it sits that far from the track\'s outer edge on every side. Keys: track-width, track-height, knob-inset, track-bg, edge-color.',
1216
+ additionalProperties: {
1217
+ $ref: "#/$defs/componentTokenBinding"
1218
+ }
1219
+ },
1220
+ "save-status": {
1221
+ type: "object",
1222
+ description: "SaveStatus readout \u2014 the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
1223
+ additionalProperties: {
1224
+ $ref: "#/$defs/componentTokenBinding"
1225
+ }
1226
+ },
1177
1227
  "admin-ui": {
1178
1228
  type: "object",
1179
1229
  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.",
@@ -1458,7 +1508,8 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
1458
1508
  "strokeWidths",
1459
1509
  "motion",
1460
1510
  "overrides",
1461
- "components"
1511
+ "components",
1512
+ "edges"
1462
1513
  ]);
1463
1514
  var KNOWN_COLOR_KEYS = /* @__PURE__ */ new Set([
1464
1515
  "primary",
@@ -1737,6 +1788,9 @@ function validateConfig(config) {
1737
1788
  errors.push("'color-scheme' must be one of: dark-only, light-only, adaptive");
1738
1789
  }
1739
1790
  }
1791
+ if (obj.edges !== void 0 && obj.edges !== "on" && obj.edges !== "off") {
1792
+ errors.push("'edges' must be either 'on' or 'off'");
1793
+ }
1740
1794
  if (typeof obj.colors !== "object" || obj.colors === null) {
1741
1795
  errors.push("'colors' is required and must be an object");
1742
1796
  return { valid: false, errors };
@@ -2117,7 +2171,12 @@ function resolveConfig(config) {
2117
2171
  overrides: config.overrides,
2118
2172
  // VI-625: component bindings pass through untouched — the contract in
2119
2173
  // component-tokens.ts owns their shape, there is nothing to default.
2120
- components: config.components,
2174
+ // VI-655: `edges: off` folds in here as `control.edge-width: 0`, so every
2175
+ // adapter emits the switch through the same component-token path.
2176
+ components: config.edges === "off" ? {
2177
+ ...config.components,
2178
+ control: { "edge-width": "0", ...config.components?.control }
2179
+ } : config.components,
2121
2180
  originalColors,
2122
2181
  colorFormats
2123
2182
  };
@@ -4262,6 +4321,7 @@ export {
4262
4321
  SEMANTIC_MAP,
4263
4322
  TAILWIND_GRAY,
4264
4323
  VISOR_BRANDS_CDN,
4324
+ VISOR_CORE_SEMANTIC_ALIASES,
4265
4325
  VISOR_DEFAULT_BRAND_PATH,
4266
4326
  VISOR_FONTS_CDN,
4267
4327
  WEIGHT_RAMP_TARGETS,
@@ -4275,6 +4335,7 @@ export {
4275
4335
  clampToSrgb,
4276
4336
  cleanFontValue,
4277
4337
  collectBrandPassthrough,
4338
+ collectDeclaredProperties,
4278
4339
  componentTokenName,
4279
4340
  compositeOverBackground,
4280
4341
  declaresWeights,
@@ -4288,6 +4349,7 @@ export {
4288
4349
  generatePreloadLinks,
4289
4350
  generatePrimitives,
4290
4351
  generatePrimitivesCss,
4352
+ generateSemanticAliasDecls,
4291
4353
  generateSemanticCss,
4292
4354
  generateShadeScale,
4293
4355
  generateStylesheetLinks,
@@ -830,6 +830,14 @@ interface VisorThemeConfig {
830
830
  * did before the contract existed.
831
831
  */
832
832
  components?: ComponentTokenBindings;
833
+ /**
834
+ * VI-655: the one switch for form-control edges. `off` removes the resting
835
+ * edge of every form control — dashed drop-target and empty-state edges
836
+ * included — with no layout shift; focus and invalid stay visible. It is
837
+ * shorthand for `components.control.edge-width: "0"`, and an explicit
838
+ * `components.control.edge-width` wins over it. Default: `on`.
839
+ */
840
+ edges?: "on" | "off";
833
841
  /**
834
842
  * Migration metadata — optional, additive, consumed by `visor migrate` commands.
835
843
  * Does not affect CSS generation or theme application.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.20.0",
3
+ "version": "0.22.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",
@@ -34,6 +34,11 @@
34
34
  "enum": ["dark-only", "light-only", "adaptive"],
35
35
  "description": "Brand constraint declaring which color modes the theme supports. 'dark-only'/'light-only' lock the brand to a single mode; 'adaptive' supports both. Complements 'default-mode' (the runtime default) — 'color-scheme' is authoritative for the brand-lock. Defaults to 'adaptive' when omitted. Optional."
36
36
  },
37
+ "edges": {
38
+ "type": "string",
39
+ "enum": ["on", "off"],
40
+ "description": "The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: \"0\" (an explicit binding there wins). Defaults to 'on'. Optional."
41
+ },
37
42
  "colors": {
38
43
  "type": "object",
39
44
  "description": "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
@@ -549,7 +554,14 @@
549
554
  },
550
555
  "checkbox": {
551
556
  "type": "object",
552
- "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.",
557
+ "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, edgeless-bg, bg-checked, border-checked.",
558
+ "additionalProperties": {
559
+ "$ref": "#/$defs/componentTokenBinding"
560
+ }
561
+ },
562
+ "control": {
563
+ "type": "object",
564
+ "description": "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
553
565
  "additionalProperties": {
554
566
  "$ref": "#/$defs/componentTokenBinding"
555
567
  }
@@ -561,6 +573,41 @@
561
573
  "$ref": "#/$defs/componentTokenBinding"
562
574
  }
563
575
  },
576
+ "field": {
577
+ "type": "object",
578
+ "description": "Field label treatment — size, case, tracking and colour of `FieldLabel`, in both densities. Keys: label-font-size, label-text-transform, label-letter-spacing, label-color.",
579
+ "additionalProperties": {
580
+ "$ref": "#/$defs/componentTokenBinding"
581
+ }
582
+ },
583
+ "button": {
584
+ "type": "object",
585
+ "description": "Button type treatment and rounding. The unprefixed keys set every size at once; a size key (`dlg-radius`) overrides one size. Keys: text-transform, letter-spacing, radius, sm-font-size, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
586
+ "additionalProperties": {
587
+ "$ref": "#/$defs/componentTokenBinding"
588
+ }
589
+ },
590
+ "text": {
591
+ "type": "object",
592
+ "description": "Text type treatment, per size — set `sm-font-size: 13px` for a 13px step, or caps and tracking on `xs` for a state tag. Every key begins with a size. Keys: xs-font-size, xs-text-transform, xs-letter-spacing, sm-font-size, sm-text-transform, sm-letter-spacing, md-font-size, md-text-transform, md-letter-spacing, lg-font-size, lg-text-transform, lg-letter-spacing, xl-font-size, xl-text-transform, xl-letter-spacing.",
593
+ "additionalProperties": {
594
+ "$ref": "#/$defs/componentTokenBinding"
595
+ }
596
+ },
597
+ "switch": {
598
+ "type": "object",
599
+ "description": "Switch track and knob dimensions (default size; `size=\"sm\"` keeps its own). Set `knob-inset` to size the knob from the track: it sits that far from the track's outer edge on every side. Keys: track-width, track-height, knob-inset, track-bg, edge-color.",
600
+ "additionalProperties": {
601
+ "$ref": "#/$defs/componentTokenBinding"
602
+ }
603
+ },
604
+ "save-status": {
605
+ "type": "object",
606
+ "description": "SaveStatus readout — the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
607
+ "additionalProperties": {
608
+ "$ref": "#/$defs/componentTokenBinding"
609
+ }
610
+ },
564
611
  "admin-ui": {
565
612
  "type": "object",
566
613
  "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.",