@loworbitstudio/visor-theme-engine 0.24.0 → 0.28.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.
@@ -9,6 +9,7 @@ import {
9
9
  collectDeclaredProperties,
10
10
  fontStack,
11
11
  generateDarkCss,
12
+ generateFontMetricDecls,
12
13
  generateFontSizeDecls,
13
14
  generateFontWeightDecls,
14
15
  generateHairlineDecls,
@@ -25,7 +26,7 @@ import {
25
26
  resolveThemeBrand,
26
27
  resolveThemeFonts,
27
28
  sectionComment
28
- } from "../chunk-D5XSLWXP.js";
29
+ } from "../chunk-X2SWJPM2.js";
29
30
 
30
31
  // src/adapters/brand-passthrough.ts
31
32
  var SENTINEL_COLOR = "#ff00ff";
@@ -559,6 +560,7 @@ function generateTypographyDecls(config, aliases) {
559
560
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
560
561
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
561
562
  decls.push(...generateFontSizeDecls(config.typography.scale));
563
+ decls.push(...generateFontMetricDecls(config.typography.body.family));
562
564
  decls.push(...generateFontWeightDecls(config.typography));
563
565
  const lineHeights = {
564
566
  none: 1,
@@ -1261,6 +1261,8 @@ 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
1263
  var INPUT = "components/ui/input/input.module.css";
1264
+ var SEPARATOR = "components/ui/separator/separator.module.css";
1265
+ var NUMBER_INPUT = "components/ui/number-input/number-input.module.css";
1264
1266
  var TEXTAREA = "components/ui/textarea/textarea.module.css";
1265
1267
  var SELECT = "components/ui/select/select.module.css";
1266
1268
  var SWITCH = "components/ui/switch/switch.module.css";
@@ -1271,7 +1273,10 @@ var FIELD = "components/ui/field/field.module.css";
1271
1273
  var TEXT = "components/ui/text/text.module.css";
1272
1274
  var INLINE_EDIT = "components/ui/inline-edit/inline-edit.module.css";
1273
1275
  var SAVE_STATUS = "components/ui/save-status/save-status.module.css";
1276
+ var SEGMENTED_CONTROL = "components/ui/segmented-control/segmented-control.module.css";
1274
1277
  var TOOLTIP = "components/ui/tooltip/tooltip.module.css";
1278
+ var ACTION_ROW = "components/ui/action-row/action-row.module.css";
1279
+ var DIALOG_FIELD = "blocks/dialog-field/dialog-field.module.css";
1275
1280
  function at(file, fallback) {
1276
1281
  return [{ file, fallback }];
1277
1282
  }
@@ -1536,6 +1541,7 @@ var emptyStateFamily = {
1536
1541
  { key: "heading-color", property: "color", description: "Heading colour.", consumers: at(EMPTY_STATE, "var(--text-primary, #111827)") },
1537
1542
  { key: "description-font-size", property: "font-size", description: "Description type size.", consumers: at(EMPTY_STATE, "var(--font-size-sm, 0.875rem)") },
1538
1543
  { key: "description-color", property: "color", description: "Description colour.", consumers: at(EMPTY_STATE, "var(--text-secondary, #6b7280)") },
1544
+ { key: "error-color", property: "color", description: 'Icon, chip glyph and heading colour for `intent="error"`.', consumers: at(EMPTY_STATE, "var(--destructive, #dc2626)") },
1539
1545
  { key: "actions-gap", property: "gap", description: "Gap between placard actions.", consumers: at(EMPTY_STATE, "var(--spacing-2, 0.5rem)") }
1540
1546
  ]
1541
1547
  };
@@ -1689,7 +1695,84 @@ var checkboxFamily = {
1689
1695
  { key: "border-checked", property: "outline-color", description: "Checked edge colour.", consumers: at(CHECKBOX, "var(--interactive-primary-bg, var(--primary, #111827))") }
1690
1696
  ]
1691
1697
  };
1692
- var EDGE_CONTROLS = [INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT, BUTTON];
1698
+ var INPUT_MD_PADDING = "var(--spacing-3_5, 0.875rem) var(--spacing-4, 1rem)";
1699
+ var INPUT_MD_PADDING_NUDGED = "calc(var(--spacing-3_5, 0.875rem) + var(--input-nudge, 0px)) var(--spacing-4, 1rem) calc(var(--spacing-3_5, 0.875rem) - var(--input-nudge, 0px))";
1700
+ var inputFamily = {
1701
+ family: "input",
1702
+ prefix: "input",
1703
+ description: "Input shape at the default `md` size. Each key falls back to the matching `--input-*-md` variable, so a scope that sets one still applies while the key is unbound; a bound key wins.",
1704
+ tokens: [
1705
+ {
1706
+ key: "md-height",
1707
+ property: "height",
1708
+ description: "`md` height. Unbound, the height follows the padding and the line. Bind `md-padding` with it.",
1709
+ consumers: at(INPUT, "var(--input-height-md, auto)")
1710
+ },
1711
+ {
1712
+ key: "md-padding",
1713
+ property: "padding",
1714
+ description: "`md` padding. Also the padding of DialogField's medium well, so the well tracks the field.",
1715
+ consumers: [
1716
+ { file: INPUT, fallback: `var(--input-padding-md, ${INPUT_MD_PADDING})` },
1717
+ { file: INPUT, fallback: `var(--input-padding-md, ${INPUT_MD_PADDING_NUDGED})` },
1718
+ { file: DIALOG_FIELD, fallback: `var(--input-padding-md, ${INPUT_MD_PADDING})` }
1719
+ ]
1720
+ },
1721
+ {
1722
+ key: "md-radius",
1723
+ property: "border-radius",
1724
+ description: "`md` corner rounding. Also the rounding of DialogField's well.",
1725
+ consumers: each([INPUT, DIALOG_FIELD], "var(--input-radius-md, var(--radius-sm, 0.5rem))")
1726
+ }
1727
+ ]
1728
+ };
1729
+ var textareaFamily = {
1730
+ family: "textarea",
1731
+ prefix: "textarea",
1732
+ description: "Textarea shape at the default `md` size.",
1733
+ tokens: [
1734
+ { key: "padding", property: "padding", description: "`md` padding.", consumers: at(TEXTAREA, INPUT_MD_PADDING) },
1735
+ { key: "radius", property: "border-radius", description: "`md` corner rounding.", consumers: at(TEXTAREA, "var(--radius-sm, 0.5rem)") }
1736
+ ]
1737
+ };
1738
+ var selectFamily = {
1739
+ family: "select",
1740
+ prefix: "select",
1741
+ description: "Select trigger shape at the default `md` size.",
1742
+ tokens: [
1743
+ {
1744
+ key: "height",
1745
+ property: "height",
1746
+ description: "`md` trigger height. Unbound, the height follows the padding and the line. Bind `padding` with it.",
1747
+ consumers: [
1748
+ { file: SELECT, fallback: "auto" },
1749
+ { file: SELECT, fallback: "calc(1lh + var(--spacing-3_5, 0.875rem) * 2 + 2px)" }
1750
+ ]
1751
+ },
1752
+ { key: "padding", property: "padding", description: "`md` trigger padding.", consumers: at(SELECT, INPUT_MD_PADDING) },
1753
+ { key: "radius", property: "border-radius", description: "`md` trigger corner rounding.", consumers: at(SELECT, "var(--radius-sm, 0.5rem)") }
1754
+ ]
1755
+ };
1756
+ var tagInputFamily = {
1757
+ family: "tag-input",
1758
+ prefix: "tag-input",
1759
+ description: 'TagInput \u2014 the field around the tags, each held tag, and the opt-in typing slot (`entryAs="slot"`). The slot follows the tag\'s shape unless its own keys are set.',
1760
+ tokens: [
1761
+ { key: "min-height", property: "min-height", description: "Field minimum height. The field grows as tags wrap.", consumers: at(TAG_INPUT, "2.25rem") },
1762
+ { key: "padding", property: "padding", description: "Field padding around the tags.", consumers: at(TAG_INPUT, "var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem)") },
1763
+ { key: "radius", property: "border-radius", description: "Field corner rounding.", consumers: at(TAG_INPUT, "var(--radius-md, 0.375rem)") },
1764
+ { key: "tag-height", property: "height", description: "Held-tag height. Unbound, the tag is as tall as its line.", consumers: at(TAG_INPUT, "auto") },
1765
+ { key: "tag-padding-x", property: "padding-left / padding-right", description: "Held-tag horizontal padding.", consumers: at(TAG_INPUT, "var(--spacing-2, 0.5rem)") },
1766
+ { key: "tag-font-size", property: "font-size", description: "Held-tag type size. The typing slot types at it too.", consumers: each([TAG_INPUT, TAG_INPUT], "var(--font-size-xs, 0.75rem)") },
1767
+ { key: "tag-font-weight", property: "font-weight", description: "Held-tag weight. Unbound, the tag inherits its row's weight.", consumers: at(TAG_INPUT, "revert-layer") },
1768
+ { key: "tag-radius", property: "border-radius", description: "Held-tag corner rounding (a pill today).", consumers: at(TAG_INPUT, "var(--radius-full, 9999px)") },
1769
+ { key: "slot-height", property: "height", description: "Typing-slot height. Follows `tag-height`.", consumers: at(TAG_INPUT, "var(--tag-input-tag-height, 1.375rem)") },
1770
+ { key: "slot-padding-x", property: "padding-left / padding-right", description: "Typing-slot horizontal padding. Follows `tag-padding-x`.", consumers: at(TAG_INPUT, "var(--tag-input-tag-padding-x, var(--spacing-2, 0.5rem))") },
1771
+ { key: "slot-radius", property: "border-radius", description: "Typing-slot corner rounding. Follows `tag-radius`.", consumers: at(TAG_INPUT, "var(--tag-input-tag-radius, var(--radius-full, 9999px))") },
1772
+ { key: "slot-min-width", property: "min-width", description: "Typing-slot minimum width. The slot grows with what is typed.", consumers: at(TAG_INPUT, "4rem") }
1773
+ ]
1774
+ };
1775
+ var EDGE_CONTROLS = [INPUT, NUMBER_INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT, BUTTON];
1693
1776
  var controlFamily = {
1694
1777
  family: "control",
1695
1778
  prefix: "control",
@@ -1710,7 +1793,7 @@ var controlFamily = {
1710
1793
  key: "edge-color",
1711
1794
  property: "outline-color",
1712
1795
  description: "Resting-edge colour. A component's own `--<component>-border` override still wins over it.",
1713
- consumers: each([INPUT, TEXTAREA, SELECT, CHECKBOX, TAG_INPUT, CHIP, FILE_UPLOAD, EMPTY_STATE, BUTTON], "var(--border-default, #e5e7eb)")
1796
+ consumers: each([INPUT, NUMBER_INPUT, TEXTAREA, SELECT, CHECKBOX, TAG_INPUT, CHIP, FILE_UPLOAD, EMPTY_STATE, BUTTON], "var(--border-default, #e5e7eb)")
1714
1797
  },
1715
1798
  {
1716
1799
  key: "edge-style",
@@ -1722,19 +1805,40 @@ var controlFamily = {
1722
1805
  key: "drop-edge-width",
1723
1806
  property: "outline-width",
1724
1807
  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.",
1725
- consumers: each([FILE_UPLOAD, EMPTY_STATE], "var(--control-edge-width, 1px)")
1808
+ consumers: each([FILE_UPLOAD, EMPTY_STATE, TAG_INPUT], "var(--control-edge-width, 1px)")
1726
1809
  },
1727
1810
  {
1728
1811
  key: "drop-edge-style",
1729
1812
  property: "outline-style",
1730
- description: "Dashed-edge style for drop targets and empty states.",
1731
- consumers: each([FILE_UPLOAD, EMPTY_STATE], "dashed")
1813
+ description: "Dashed-edge style for drop targets, empty states and the TagInput typing slot.",
1814
+ consumers: each([FILE_UPLOAD, EMPTY_STATE, TAG_INPUT], "dashed")
1732
1815
  },
1733
1816
  {
1734
1817
  key: "state-edge-width",
1735
1818
  property: "outline-width",
1736
1819
  description: "Focus and invalid edge weight. Independent of `edge-width`, so focus and invalid stay visible with edges off.",
1737
- consumers: each([INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT], "1px")
1820
+ consumers: each([INPUT, NUMBER_INPUT, TEXTAREA, SELECT, CHECKBOX, SWITCH, TAG_INPUT], "1px")
1821
+ },
1822
+ // VI-663: the refused-value look on Input, Textarea and Select — a ring in
1823
+ // the error colour plus a halo around it. Neither reads `edge-width`, so
1824
+ // invalid stays visible with edges off.
1825
+ {
1826
+ key: "invalid-ring-width",
1827
+ property: "box-shadow (Input) / outline-width",
1828
+ description: "Width of the error-colour ring on a refused value. Unbound, Input draws a 1.5px inset ring and Textarea and Select draw it at `state-edge-width`.",
1829
+ consumers: [
1830
+ ...each([INPUT, INPUT], "1.5px"),
1831
+ ...each([TEXTAREA, TEXTAREA, SELECT, SELECT], "var(--control-state-edge-width, 1px)")
1832
+ ]
1833
+ },
1834
+ {
1835
+ key: "invalid-halo-width",
1836
+ property: "box-shadow",
1837
+ description: "Width of the soft error-colour halo outside the ring. Unbound, Input draws none and Textarea and Select draw it at `--focus-ring-width`.",
1838
+ consumers: [
1839
+ { file: INPUT, fallback: "0px" },
1840
+ ...each([TEXTAREA, SELECT], "var(--focus-ring-width, 2px)")
1841
+ ]
1738
1842
  }
1739
1843
  ]
1740
1844
  };
@@ -1766,10 +1870,10 @@ var fieldFamily = {
1766
1870
  ]
1767
1871
  };
1768
1872
  var BUTTON_SIZES = [
1769
- { size: "sm", fontSize: ["var(--font-size-xs, 0.75rem)", "0.8125rem"], radius: "var(--radius-md, 0.375rem)" },
1770
- { size: "md", fontSize: ["var(--font-size-sm, 0.875rem)"], radius: "var(--radius-md, 0.375rem)" },
1771
- { size: "lg", fontSize: ["var(--font-size-base, 1rem)"], radius: "var(--radius-md, 0.375rem)" },
1772
- { size: "dlg", fontSize: ["var(--font-size-2xs, 0.6875rem)"], radius: "var(--radius-lg, 0.5rem)" }
1873
+ { size: "sm", fontSize: ["var(--font-size-xs, 0.75rem)", "0.8125rem"], fontWeight: "var(--font-weight-medium, 500)", radius: "var(--radius-md, 0.375rem)" },
1874
+ { size: "md", fontSize: ["var(--font-size-sm, 0.875rem)"], fontWeight: "var(--font-weight-medium, 500)", radius: "var(--radius-md, 0.375rem)" },
1875
+ { size: "lg", fontSize: ["var(--font-size-base, 1rem)"], fontWeight: "var(--font-weight-medium, 500)", radius: "var(--radius-md, 0.375rem)" },
1876
+ { size: "dlg", fontSize: ["var(--font-size-2xs, 0.6875rem)"], fontWeight: "var(--font-weight-semibold, 600)", radius: "var(--radius-lg, 0.5rem)" }
1773
1877
  ];
1774
1878
  var buttonFamily = {
1775
1879
  family: "button",
@@ -1788,6 +1892,12 @@ var buttonFamily = {
1788
1892
  description: "Tracking for every size.",
1789
1893
  consumers: BUTTON_SIZES.map(() => ({ file: BUTTON, fallback: "revert-layer" }))
1790
1894
  },
1895
+ {
1896
+ key: "font-weight",
1897
+ property: "font-weight",
1898
+ description: "Weight for every size.",
1899
+ consumers: BUTTON_SIZES.map(({ fontWeight }) => ({ file: BUTTON, fallback: fontWeight }))
1900
+ },
1791
1901
  {
1792
1902
  key: "radius",
1793
1903
  property: "border-radius",
@@ -1797,13 +1907,19 @@ var buttonFamily = {
1797
1907
  { file: BUTTON, fallback: "var(--radius-md, 0.375rem)" }
1798
1908
  ]
1799
1909
  },
1800
- ...BUTTON_SIZES.flatMap(({ size, fontSize, radius }) => [
1910
+ ...BUTTON_SIZES.flatMap(({ size, fontSize, fontWeight, radius }) => [
1801
1911
  {
1802
1912
  key: `${size}-font-size`,
1803
1913
  property: "font-size",
1804
1914
  description: fontSize.length > 1 ? `\`${size}\` type size. Drives both the standard and editorial-density steps.` : `\`${size}\` type size.`,
1805
1915
  consumers: fontSize.map((fallback) => ({ file: BUTTON, fallback }))
1806
1916
  },
1917
+ {
1918
+ key: `${size}-font-weight`,
1919
+ property: "font-weight",
1920
+ description: `\`${size}\` weight.`,
1921
+ consumers: at(BUTTON, `var(--button-font-weight, ${fontWeight})`)
1922
+ },
1807
1923
  {
1808
1924
  key: `${size}-text-transform`,
1809
1925
  property: "text-transform",
@@ -1899,9 +2015,26 @@ var switchFamily = {
1899
2015
  var saveStatusFamily = {
1900
2016
  family: "save-status",
1901
2017
  prefix: "save-status",
1902
- description: "SaveStatus readout \u2014 the fixed-width slot and its type, so the readout never reflows its row.",
2018
+ description: "SaveStatus readout \u2014 the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row.",
1903
2019
  tokens: [
1904
- { key: "width", property: "width", description: "Slot width. Fixed, so the row never reflows between states.", consumers: at(SAVE_STATUS, "7.5rem") },
2020
+ { key: "width", property: "width", description: "Slot width. Unbound, the slot is as wide as the longest label in the current font plus the retry mark, the same in every state. Bind it to pin a width; a longer label then truncates inside it.", consumers: at(SAVE_STATUS, "auto") },
2021
+ { key: "syncing-color", property: "color", description: "Ink of the Syncing state (a first sync from an outside source). Unbound it reads `--text-secondary`.", consumers: at(SAVE_STATUS, "var(--text-secondary, #6b7280)") },
2022
+ { key: "font-size", property: "font-size", description: "Readout type size.", consumers: at(SAVE_STATUS, "var(--font-size-xs, 0.75rem)") },
2023
+ {
2024
+ key: "dot-size",
2025
+ property: "width / height",
2026
+ description: "Diameter of the status dot (`dot`), in every state but Unsaved. Also the Unsaved dot's, until `unsaved-dot-size` is set. The slot width does not change.",
2027
+ consumers: [
2028
+ { file: SAVE_STATUS, fallback: "var(--spacing-2, 0.5rem)" },
2029
+ { file: SAVE_STATUS, fallback: "var(--spacing-2, 0.5rem)" }
2030
+ ]
2031
+ },
2032
+ {
2033
+ key: "unsaved-dot-size",
2034
+ property: "width / height",
2035
+ description: 'Diameter of the dot that draws Unsaved \u2014 the status dot while Unsaved, and the dot alone of `unsavedAs="dot"`. Follows `dot-size`.',
2036
+ consumers: each([SAVE_STATUS, SAVE_STATUS], "var(--save-status-dot-size, var(--spacing-2, 0.5rem))")
2037
+ },
1905
2038
  { key: "font-family", property: "font-family", description: "Readout family (e.g. the theme's mono).", consumers: at(SAVE_STATUS, "revert-layer") },
1906
2039
  { key: "text-transform", property: "text-transform", description: "Readout casing.", consumers: at(SAVE_STATUS, "revert-layer") },
1907
2040
  { key: "letter-spacing", property: "letter-spacing", description: "Readout tracking.", consumers: at(SAVE_STATUS, "revert-layer") }
@@ -1916,6 +2049,59 @@ var tooltipFamily = {
1916
2049
  { key: "text", property: "color", description: "Tooltip label colour.", consumers: at(TOOLTIP, "var(--text-inverse, #ffffff)") }
1917
2050
  ]
1918
2051
  };
2052
+ var actionRowFamily = {
2053
+ family: "action-row",
2054
+ prefix: "action-row",
2055
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token.",
2056
+ tokens: [
2057
+ { key: "min-height", property: "min-height", description: "Row floor height; the whole row is the target.", consumers: at(ACTION_ROW, "2.75rem") },
2058
+ { key: "padding", property: "padding", description: "Row inset, kept inside the hover and selected fills.", consumers: at(ACTION_ROW, "var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem)") },
2059
+ { key: "gap", property: "gap", description: "Gap between the leading slot, the title block and the trailing slot.", consumers: at(ACTION_ROW, "var(--spacing-3, 0.75rem)") },
2060
+ { key: "radius", property: "border-radius", description: "Row corner rounding (the hover and selected fill).", consumers: at(ACTION_ROW, "var(--radius-md, 0.375rem)") },
2061
+ { key: "hover-bg", property: "background-color", description: "Hover fill.", consumers: at(ACTION_ROW, "var(--surface-interactive-hover, #f3f4f6)") },
2062
+ { key: "open-bg", property: "background-color", description: "Fill of a row that has opened a picker or popover (`aria-expanded`).", consumers: at(ACTION_ROW, "var(--surface-interactive-hover, #f3f4f6)") },
2063
+ { key: "selected-bg", property: "background-color", description: "Selected fill. A lifted fill, never white.", consumers: at(ACTION_ROW, "var(--surface-interactive-active, #e5e7eb)") },
2064
+ { key: "selected-edge-color", property: "outline-color", description: "Selected row's inset edge colour. Its width is `--control-state-edge-width`, so `edges: off` keeps it.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
2065
+ { key: "disabled-opacity", property: "opacity", description: "Disabled row opacity.", consumers: at(ACTION_ROW, "var(--opacity-50, 0.5)") },
2066
+ { key: "title-weight", property: "font-weight", description: "Title weight.", consumers: at(ACTION_ROW, "var(--font-weight-medium, 500)") },
2067
+ { key: "line-color", property: "color", description: "Second-line colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
2068
+ { key: "leading-color", property: "color", description: "Leading icon colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
2069
+ { key: "leading-size", property: "font-size", description: "Leading icon size.", consumers: at(ACTION_ROW, "1.25rem") },
2070
+ { key: "trailing-color", property: "color", description: "Trailing value or caret colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") }
2071
+ ]
2072
+ };
2073
+ var inputAffixFamily = {
2074
+ family: "input-affix",
2075
+ prefix: "input-affix",
2076
+ description: "Input and NumberInput prefix and suffix: the gap between a fixed affix and the value, and the affix's ink.",
2077
+ tokens: [
2078
+ {
2079
+ key: "gap",
2080
+ property: "gap",
2081
+ description: "Space between a prefix or suffix and the value.",
2082
+ consumers: [...at(INPUT, "var(--spacing-2, 0.5rem)"), ...at(NUMBER_INPUT, "var(--spacing-2, 0.5rem)")]
2083
+ },
2084
+ {
2085
+ key: "color",
2086
+ property: "color",
2087
+ description: "Prefix and suffix ink.",
2088
+ consumers: [...at(INPUT, "var(--text-secondary, #6b7280)"), ...at(NUMBER_INPUT, "var(--text-secondary, #6b7280)")]
2089
+ }
2090
+ ]
2091
+ };
2092
+ var separatorFamily = {
2093
+ family: "separator",
2094
+ prefix: "separator",
2095
+ description: "Separator rule: the colour of the line, bare or on both sides of a label.",
2096
+ tokens: [
2097
+ {
2098
+ key: "color",
2099
+ property: "background-color",
2100
+ description: "Rule colour. Falls back to the default border colour.",
2101
+ consumers: at(SEPARATOR, "var(--border-default, #e5e7eb)")
2102
+ }
2103
+ ]
2104
+ };
1919
2105
  var HAIRLINE_PX = [
1920
2106
  "components/ui/bulk-action-bar/bulk-action-bar.module.css",
1921
2107
  "components/ui/dialog/dialog.module.css",
@@ -1925,6 +2111,7 @@ var HAIRLINE_PX = [
1925
2111
  TABS
1926
2112
  ];
1927
2113
  var HAIRLINE_THIN = [
2114
+ ACTION_ROW,
1928
2115
  "blocks/dialog-form/dialog-form.module.css",
1929
2116
  "components/ui/coherence-check/coherence-check.module.css",
1930
2117
  "components/ui/doc-frame/doc-frame.module.css",
@@ -1954,6 +2141,29 @@ var hairlineFamily = {
1954
2141
  }
1955
2142
  ]
1956
2143
  };
2144
+ var segmentedControlFamily = {
2145
+ family: "segmented-control",
2146
+ prefix: "segmented-control",
2147
+ description: "SegmentedControl \u2014 the well, its corner radius, the sliding pill, the ink and the state edge on it. The resting edge is the shared `control` edge.",
2148
+ tokens: [
2149
+ { key: "track-bg", property: "background-color", description: "The well the pill slides in. Default is the page nudged 12% toward the ink, so it reads as a well in light and dark.", consumers: at(SEGMENTED_CONTROL, "color-mix(in srgb, var(--text-primary, #111827) 12%, var(--surface-page, #ffffff))") },
2150
+ {
2151
+ key: "radius",
2152
+ property: "border-radius",
2153
+ description: "Track corner radius. Unset, each size keeps its ToggleGroup radius (md and sm `--radius-lg`, xs `--radius-md`, lg `--radius-xl`).",
2154
+ consumers: [
2155
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-md, 0.375rem)" },
2156
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-lg, 0.5rem)" },
2157
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-xl, 0.75rem)" }
2158
+ ]
2159
+ },
2160
+ { key: "indicator-bg", property: "background-color", description: "Sliding active pill. Bind a lifted dark fill where the theme wants one.", consumers: at(SEGMENTED_CONTROL, "var(--interactive-primary-bg, var(--primary, #111827))") },
2161
+ { key: "indicator-text", property: "color", description: "Active segment label ink, on the pill.", consumers: at(SEGMENTED_CONTROL, "var(--interactive-primary-text, #f9fafb)") },
2162
+ { key: "indicator-edge", property: "box-shadow (inset, at --control-state-edge-width)", description: "Colour of the 1px state edge on the active pill. Set `transparent` where the fill already clears 3:1 against the well.", consumers: at(SEGMENTED_CONTROL, "var(--text-primary, #111827)") },
2163
+ { key: "inactive-text", property: "color", description: "Inactive segment label colour.", consumers: at(SEGMENTED_CONTROL, "var(--text-primary, #111827)") },
2164
+ { key: "active-weight", property: "font-weight", description: "Active segment label weight.", consumers: at(SEGMENTED_CONTROL, "var(--font-weight-medium, 500)") }
2165
+ ]
2166
+ };
1957
2167
  var adminUiFamily = {
1958
2168
  family: "admin-ui",
1959
2169
  prefix: "admin-ui",
@@ -1982,6 +2192,10 @@ var COMPONENT_TOKEN_FAMILIES = [
1982
2192
  skeletonFamily,
1983
2193
  spinnerFamily,
1984
2194
  checkboxFamily,
2195
+ inputFamily,
2196
+ textareaFamily,
2197
+ selectFamily,
2198
+ tagInputFamily,
1985
2199
  controlFamily,
1986
2200
  hairlineFamily,
1987
2201
  fieldFamily,
@@ -1989,7 +2203,11 @@ var COMPONENT_TOKEN_FAMILIES = [
1989
2203
  textFamily,
1990
2204
  switchFamily,
1991
2205
  saveStatusFamily,
2206
+ segmentedControlFamily,
1992
2207
  tooltipFamily,
2208
+ actionRowFamily,
2209
+ inputAffixFamily,
2210
+ separatorFamily,
1993
2211
  adminUiFamily
1994
2212
  ];
1995
2213
  var COMPONENT_TOKEN_FAMILY_BY_NAME = new Map(
@@ -2279,6 +2497,23 @@ function applyOverrides(tokens, overrides) {
2279
2497
  return result;
2280
2498
  }
2281
2499
 
2500
+ // src/font-metrics.ts
2501
+ var FONT_VERTICAL_METRICS = {
2502
+ arial: { ascent: 0.905, descent: 0.212 },
2503
+ inter: { ascent: 0.969, descent: 0.241 },
2504
+ outfit: { ascent: 1, descent: 0.26 },
2505
+ "pitch sans": { ascent: 0.9, descent: 0.32 },
2506
+ "pp model mono": { ascent: 1.02, descent: 0.288 },
2507
+ "product sans": { ascent: 0.96, descent: 0.253 },
2508
+ satoshi: { ascent: 1.01, descent: 0.24 },
2509
+ "system-ui": { ascent: 0.967, descent: 0.211 }
2510
+ };
2511
+ function generateFontMetricDecls(family) {
2512
+ const key = family.trim().replace(/^["']|["']$/g, "").toLowerCase();
2513
+ const m = FONT_VERTICAL_METRICS[key];
2514
+ return m ? [`--font-ascent: ${m.ascent};`, `--font-descent: ${m.descent};`] : [];
2515
+ }
2516
+
2282
2517
  // src/font-weights.ts
2283
2518
  var WEIGHT_RAMP_TARGETS = {
2284
2519
  medium: 500,
@@ -2493,6 +2728,7 @@ function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
2493
2728
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
2494
2729
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
2495
2730
  decls.push(...generateFontSizeDecls(config.typography.scale));
2731
+ decls.push(...generateFontMetricDecls(config.typography.body.family));
2496
2732
  decls.push(...generateFontWeightDecls(config.typography));
2497
2733
  const lineHeights = {
2498
2734
  none: 1,
@@ -2987,6 +3223,8 @@ export {
2987
3223
  collectBrandPassthrough,
2988
3224
  hasBrandPassthrough,
2989
3225
  applyOverrides,
3226
+ FONT_VERTICAL_METRICS,
3227
+ generateFontMetricDecls,
2990
3228
  WEIGHT_RAMP_TARGETS,
2991
3229
  loadedWeights,
2992
3230
  declaresWeights,
package/dist/index.d.ts CHANGED
@@ -1097,7 +1097,7 @@ var properties = {
1097
1097
  },
1098
1098
  "empty-state": {
1099
1099
  type: "object",
1100
- 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.",
1100
+ 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, error-color, actions-gap.",
1101
1101
  additionalProperties: {
1102
1102
  $ref: "#/$defs/componentTokenBinding"
1103
1103
  }
@@ -1144,6 +1144,34 @@ var properties = {
1144
1144
  $ref: "#/$defs/componentTokenBinding"
1145
1145
  }
1146
1146
  },
1147
+ input: {
1148
+ type: "object",
1149
+ description: "Input shape at the default `md` size. Each key falls back to the matching `--input-*-md` variable, so a scope that sets one still applies while the key is unbound; a bound key wins. Keys: md-height, md-padding, md-radius.",
1150
+ additionalProperties: {
1151
+ $ref: "#/$defs/componentTokenBinding"
1152
+ }
1153
+ },
1154
+ textarea: {
1155
+ type: "object",
1156
+ description: "Textarea shape at the default `md` size. Keys: padding, radius.",
1157
+ additionalProperties: {
1158
+ $ref: "#/$defs/componentTokenBinding"
1159
+ }
1160
+ },
1161
+ select: {
1162
+ type: "object",
1163
+ description: "Select trigger shape at the default `md` size. Keys: height, padding, radius.",
1164
+ additionalProperties: {
1165
+ $ref: "#/$defs/componentTokenBinding"
1166
+ }
1167
+ },
1168
+ "tag-input": {
1169
+ type: "object",
1170
+ description: "TagInput — the field around the tags, each held tag, and the opt-in typing slot (`entryAs=\"slot\"`). The slot follows the tag's shape unless its own keys are set. Keys: min-height, padding, radius, tag-height, tag-padding-x, tag-font-size, tag-font-weight, tag-radius, slot-height, slot-padding-x, slot-radius, slot-min-width.",
1171
+ additionalProperties: {
1172
+ $ref: "#/$defs/componentTokenBinding"
1173
+ }
1174
+ },
1147
1175
  hairline: {
1148
1176
  type: "object",
1149
1177
  description: "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
@@ -1153,7 +1181,7 @@ var properties = {
1153
1181
  },
1154
1182
  control: {
1155
1183
  type: "object",
1156
- 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.",
1184
+ 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, invalid-ring-width, invalid-halo-width.",
1157
1185
  additionalProperties: {
1158
1186
  $ref: "#/$defs/componentTokenBinding"
1159
1187
  }
@@ -1174,7 +1202,7 @@ var properties = {
1174
1202
  },
1175
1203
  button: {
1176
1204
  type: "object",
1177
- 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.",
1205
+ 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, font-weight, radius, sm-font-size, sm-font-weight, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-font-weight, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-font-weight, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-font-weight, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
1178
1206
  additionalProperties: {
1179
1207
  $ref: "#/$defs/componentTokenBinding"
1180
1208
  }
@@ -1195,7 +1223,14 @@ var properties = {
1195
1223
  },
1196
1224
  "save-status": {
1197
1225
  type: "object",
1198
- 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.",
1226
+ description: "SaveStatus readout — the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, font-size, dot-size, unsaved-dot-size, font-family, text-transform, letter-spacing.",
1227
+ additionalProperties: {
1228
+ $ref: "#/$defs/componentTokenBinding"
1229
+ }
1230
+ },
1231
+ "segmented-control": {
1232
+ type: "object",
1233
+ description: "SegmentedControl — the well, its corner radius, the sliding pill, the ink and the state edge on it. The resting edge is the shared `control` edge. Keys: track-bg, radius, indicator-bg, indicator-text, indicator-edge, inactive-text, active-weight.",
1199
1234
  additionalProperties: {
1200
1235
  $ref: "#/$defs/componentTokenBinding"
1201
1236
  }
@@ -1207,6 +1242,27 @@ var properties = {
1207
1242
  $ref: "#/$defs/componentTokenBinding"
1208
1243
  }
1209
1244
  },
1245
+ "action-row": {
1246
+ type: "object",
1247
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
1248
+ additionalProperties: {
1249
+ $ref: "#/$defs/componentTokenBinding"
1250
+ }
1251
+ },
1252
+ "input-affix": {
1253
+ type: "object",
1254
+ description: "Input and NumberInput prefix and suffix: the gap between a fixed affix and the value, and the affix's ink. Keys: gap, color.",
1255
+ additionalProperties: {
1256
+ $ref: "#/$defs/componentTokenBinding"
1257
+ }
1258
+ },
1259
+ separator: {
1260
+ type: "object",
1261
+ description: "Separator rule colour, bare or either side of a label. Keys: color.",
1262
+ additionalProperties: {
1263
+ $ref: "#/$defs/componentTokenBinding"
1264
+ }
1265
+ },
1210
1266
  "admin-ui": {
1211
1267
  type: "object",
1212
1268
  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.",
@@ -2279,4 +2335,36 @@ declare function cleanFontValue(val: string): string;
2279
2335
  */
2280
2336
  declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
2281
2337
 
2282
- 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 };
2338
+ /**
2339
+ * Vertical font metrics for centring a native <input>'s text (VI-684).
2340
+ *
2341
+ * A native input cannot be trimmed to its capital band without clipping
2342
+ * descenders and accents, so its capitals sit wherever the font's line box puts
2343
+ * them: half a line above the box centre plus (ascent - descent) / 2, each
2344
+ * rounded to a whole px. That depends on the face, so input.module.css reads
2345
+ * `--font-ascent` / `--font-descent` (unitless em ratios) to work out the
2346
+ * padding that lands the baseline on the right pixel.
2347
+ *
2348
+ * The ratios are Chromium's own `fontBoundingBoxAscent` / `Descent` at 1000px
2349
+ * (what its line layout rounds), measured once from the real faces by
2350
+ * scripts/measure-font-metrics.mjs and committed here. Measuring at build time
2351
+ * would put a network fetch and a font parser on every theme build.
2352
+ *
2353
+ * A face with no entry emits no tokens, and the input takes no nudge: it renders
2354
+ * as it did before VI-684 (within about 1px of centre), never worse.
2355
+ *
2356
+ * `system-ui` is platform-dependent. The entry is what Chromium resolves on
2357
+ * macOS (San Francisco). Windows and Linux resolve another face, so the nudge
2358
+ * there is a best effort; override it with --font-ascent / --font-descent.
2359
+ */
2360
+ interface FontVerticalMetrics {
2361
+ /** Ascent as a fraction of the font size. */
2362
+ ascent: number;
2363
+ /** Descent as a fraction of the font size. */
2364
+ descent: number;
2365
+ }
2366
+ declare const FONT_VERTICAL_METRICS: Record<string, FontVerticalMetrics>;
2367
+ /** The `--font-ascent` / `--font-descent` declarations for a body family, or none when the face is unmeasured. */
2368
+ declare function generateFontMetricDecls(family: string): string[];
2369
+
2370
+ export { type BrandPassthrough, BrandResolution, BrandSlot, BrandSource, BrandStrategy, BrandStrategyContext, BrandStrategyIssue, BrandStrategyValidationResult, type CSSFile, ColorRole, type Confidence, DEFAULT_VISOR_BRAND, type ExtractedToken, type ExtractionResult, FONT_VERTICAL_METRICS, FONT_WEIGHT_ALIASES, type FontCoverageError, type FontCoverageResult, FontDisplayStrategy, type FontFaceDeclaration, FontResolution, FontResolveOptions, type FontVerticalMetrics, 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, generateFontMetricDecls, 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
@@ -3,6 +3,7 @@ import {
3
3
  COMPONENT_TOKEN_FAMILIES,
4
4
  COMPONENT_TOKEN_FAMILY_BY_NAME,
5
5
  DEFAULT_VISOR_BRAND,
6
+ FONT_VERTICAL_METRICS,
6
7
  FONT_WEIGHT_ALIASES,
7
8
  MATERIAL_TEXT_SLOTS,
8
9
  TAILWIND_GRAY,
@@ -22,6 +23,7 @@ import {
22
23
  compositeOverBackground,
23
24
  declaresWeights,
24
25
  generateDarkCss,
26
+ generateFontMetricDecls,
25
27
  generateFontWeightDecls,
26
28
  generateFullBundleCss,
27
29
  generateLightCss,
@@ -63,7 +65,7 @@ import {
63
65
  rgbToOklch,
64
66
  serializeColor,
65
67
  validateComponentBindings
66
- } from "./chunk-D5XSLWXP.js";
68
+ } from "./chunk-X2SWJPM2.js";
67
69
 
68
70
  // src/fonts/validate-coverage.ts
69
71
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -1133,7 +1135,7 @@ var visor_theme_schema_default = {
1133
1135
  },
1134
1136
  "empty-state": {
1135
1137
  type: "object",
1136
- 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.",
1138
+ 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, error-color, actions-gap.",
1137
1139
  additionalProperties: {
1138
1140
  $ref: "#/$defs/componentTokenBinding"
1139
1141
  }
@@ -1180,6 +1182,34 @@ var visor_theme_schema_default = {
1180
1182
  $ref: "#/$defs/componentTokenBinding"
1181
1183
  }
1182
1184
  },
1185
+ input: {
1186
+ type: "object",
1187
+ description: "Input shape at the default `md` size. Each key falls back to the matching `--input-*-md` variable, so a scope that sets one still applies while the key is unbound; a bound key wins. Keys: md-height, md-padding, md-radius.",
1188
+ additionalProperties: {
1189
+ $ref: "#/$defs/componentTokenBinding"
1190
+ }
1191
+ },
1192
+ textarea: {
1193
+ type: "object",
1194
+ description: "Textarea shape at the default `md` size. Keys: padding, radius.",
1195
+ additionalProperties: {
1196
+ $ref: "#/$defs/componentTokenBinding"
1197
+ }
1198
+ },
1199
+ select: {
1200
+ type: "object",
1201
+ description: "Select trigger shape at the default `md` size. Keys: height, padding, radius.",
1202
+ additionalProperties: {
1203
+ $ref: "#/$defs/componentTokenBinding"
1204
+ }
1205
+ },
1206
+ "tag-input": {
1207
+ type: "object",
1208
+ description: 'TagInput \u2014 the field around the tags, each held tag, and the opt-in typing slot (`entryAs="slot"`). The slot follows the tag\'s shape unless its own keys are set. Keys: min-height, padding, radius, tag-height, tag-padding-x, tag-font-size, tag-font-weight, tag-radius, slot-height, slot-padding-x, slot-radius, slot-min-width.',
1209
+ additionalProperties: {
1210
+ $ref: "#/$defs/componentTokenBinding"
1211
+ }
1212
+ },
1183
1213
  hairline: {
1184
1214
  type: "object",
1185
1215
  description: "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
@@ -1189,7 +1219,7 @@ var visor_theme_schema_default = {
1189
1219
  },
1190
1220
  control: {
1191
1221
  type: "object",
1192
- 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.",
1222
+ 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, invalid-ring-width, invalid-halo-width.",
1193
1223
  additionalProperties: {
1194
1224
  $ref: "#/$defs/componentTokenBinding"
1195
1225
  }
@@ -1210,7 +1240,7 @@ var visor_theme_schema_default = {
1210
1240
  },
1211
1241
  button: {
1212
1242
  type: "object",
1213
- 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.",
1243
+ 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, font-weight, radius, sm-font-size, sm-font-weight, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-font-weight, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-font-weight, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-font-weight, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
1214
1244
  additionalProperties: {
1215
1245
  $ref: "#/$defs/componentTokenBinding"
1216
1246
  }
@@ -1231,7 +1261,14 @@ var visor_theme_schema_default = {
1231
1261
  },
1232
1262
  "save-status": {
1233
1263
  type: "object",
1234
- 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.",
1264
+ description: "SaveStatus readout \u2014 the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, font-size, dot-size, unsaved-dot-size, font-family, text-transform, letter-spacing.",
1265
+ additionalProperties: {
1266
+ $ref: "#/$defs/componentTokenBinding"
1267
+ }
1268
+ },
1269
+ "segmented-control": {
1270
+ type: "object",
1271
+ description: "SegmentedControl \u2014 the well, its corner radius, the sliding pill, the ink and the state edge on it. The resting edge is the shared `control` edge. Keys: track-bg, radius, indicator-bg, indicator-text, indicator-edge, inactive-text, active-weight.",
1235
1272
  additionalProperties: {
1236
1273
  $ref: "#/$defs/componentTokenBinding"
1237
1274
  }
@@ -1243,6 +1280,27 @@ var visor_theme_schema_default = {
1243
1280
  $ref: "#/$defs/componentTokenBinding"
1244
1281
  }
1245
1282
  },
1283
+ "action-row": {
1284
+ type: "object",
1285
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
1286
+ additionalProperties: {
1287
+ $ref: "#/$defs/componentTokenBinding"
1288
+ }
1289
+ },
1290
+ "input-affix": {
1291
+ type: "object",
1292
+ description: "Input and NumberInput prefix and suffix: the gap between a fixed affix and the value, and the affix's ink. Keys: gap, color.",
1293
+ additionalProperties: {
1294
+ $ref: "#/$defs/componentTokenBinding"
1295
+ }
1296
+ },
1297
+ separator: {
1298
+ type: "object",
1299
+ description: "Separator rule colour, bare or either side of a label. Keys: color.",
1300
+ additionalProperties: {
1301
+ $ref: "#/$defs/componentTokenBinding"
1302
+ }
1303
+ },
1246
1304
  "admin-ui": {
1247
1305
  type: "object",
1248
1306
  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.",
@@ -4346,6 +4404,7 @@ export {
4346
4404
  DEFAULT_BRAND_STRATEGY_SURFACES,
4347
4405
  DEFAULT_BRAND_STRATEGY_TONE_STATES,
4348
4406
  DEFAULT_VISOR_BRAND,
4407
+ FONT_VERTICAL_METRICS,
4349
4408
  FONT_WEIGHT_ALIASES,
4350
4409
  GOVERNS_WILDCARD,
4351
4410
  SEMANTIC_MAP,
@@ -4373,6 +4432,7 @@ export {
4373
4432
  extractFromCSS,
4374
4433
  formatFontCoverageError,
4375
4434
  generateDarkCss,
4435
+ generateFontMetricDecls,
4376
4436
  generateFontWeightDecls,
4377
4437
  generateFullBundleCss,
4378
4438
  generateLightCss,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.24.0",
3
+ "version": "0.28.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",
@@ -517,7 +517,7 @@
517
517
  },
518
518
  "empty-state": {
519
519
  "type": "object",
520
- "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.",
520
+ "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, error-color, actions-gap.",
521
521
  "additionalProperties": {
522
522
  "$ref": "#/$defs/componentTokenBinding"
523
523
  }
@@ -564,6 +564,34 @@
564
564
  "$ref": "#/$defs/componentTokenBinding"
565
565
  }
566
566
  },
567
+ "input": {
568
+ "type": "object",
569
+ "description": "Input shape at the default `md` size. Each key falls back to the matching `--input-*-md` variable, so a scope that sets one still applies while the key is unbound; a bound key wins. Keys: md-height, md-padding, md-radius.",
570
+ "additionalProperties": {
571
+ "$ref": "#/$defs/componentTokenBinding"
572
+ }
573
+ },
574
+ "textarea": {
575
+ "type": "object",
576
+ "description": "Textarea shape at the default `md` size. Keys: padding, radius.",
577
+ "additionalProperties": {
578
+ "$ref": "#/$defs/componentTokenBinding"
579
+ }
580
+ },
581
+ "select": {
582
+ "type": "object",
583
+ "description": "Select trigger shape at the default `md` size. Keys: height, padding, radius.",
584
+ "additionalProperties": {
585
+ "$ref": "#/$defs/componentTokenBinding"
586
+ }
587
+ },
588
+ "tag-input": {
589
+ "type": "object",
590
+ "description": "TagInput — the field around the tags, each held tag, and the opt-in typing slot (`entryAs=\"slot\"`). The slot follows the tag's shape unless its own keys are set. Keys: min-height, padding, radius, tag-height, tag-padding-x, tag-font-size, tag-font-weight, tag-radius, slot-height, slot-padding-x, slot-radius, slot-min-width.",
591
+ "additionalProperties": {
592
+ "$ref": "#/$defs/componentTokenBinding"
593
+ }
594
+ },
567
595
  "hairline": {
568
596
  "type": "object",
569
597
  "description": "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
@@ -573,7 +601,7 @@
573
601
  },
574
602
  "control": {
575
603
  "type": "object",
576
- "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.",
604
+ "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, invalid-ring-width, invalid-halo-width.",
577
605
  "additionalProperties": {
578
606
  "$ref": "#/$defs/componentTokenBinding"
579
607
  }
@@ -594,7 +622,7 @@
594
622
  },
595
623
  "button": {
596
624
  "type": "object",
597
- "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.",
625
+ "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, font-weight, radius, sm-font-size, sm-font-weight, sm-text-transform, sm-letter-spacing, sm-radius, md-font-size, md-font-weight, md-text-transform, md-letter-spacing, md-radius, lg-font-size, lg-font-weight, lg-text-transform, lg-letter-spacing, lg-radius, dlg-font-size, dlg-font-weight, dlg-text-transform, dlg-letter-spacing, dlg-radius, icon-size, icon-pad, icon-radius, icon-ghost-color.",
598
626
  "additionalProperties": {
599
627
  "$ref": "#/$defs/componentTokenBinding"
600
628
  }
@@ -615,7 +643,14 @@
615
643
  },
616
644
  "save-status": {
617
645
  "type": "object",
618
- "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.",
646
+ "description": "SaveStatus readout — the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, font-size, dot-size, unsaved-dot-size, font-family, text-transform, letter-spacing.",
647
+ "additionalProperties": {
648
+ "$ref": "#/$defs/componentTokenBinding"
649
+ }
650
+ },
651
+ "segmented-control": {
652
+ "type": "object",
653
+ "description": "SegmentedControl \u2014 the well, its corner radius, the sliding pill, the ink and the state edge on it. The resting edge is the shared `control` edge. Keys: track-bg, radius, indicator-bg, indicator-text, indicator-edge, inactive-text, active-weight.",
619
654
  "additionalProperties": {
620
655
  "$ref": "#/$defs/componentTokenBinding"
621
656
  }
@@ -627,6 +662,27 @@
627
662
  "$ref": "#/$defs/componentTokenBinding"
628
663
  }
629
664
  },
665
+ "action-row": {
666
+ "type": "object",
667
+ "description": "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
668
+ "additionalProperties": {
669
+ "$ref": "#/$defs/componentTokenBinding"
670
+ }
671
+ },
672
+ "input-affix": {
673
+ "type": "object",
674
+ "description": "Input and NumberInput prefix and suffix: the gap between a fixed affix and the value, and the affix's ink. Keys: gap, color.",
675
+ "additionalProperties": {
676
+ "$ref": "#/$defs/componentTokenBinding"
677
+ }
678
+ },
679
+ "separator": {
680
+ "type": "object",
681
+ "description": "Separator rule colour, bare or either side of a label. Keys: color.",
682
+ "additionalProperties": {
683
+ "$ref": "#/$defs/componentTokenBinding"
684
+ }
685
+ },
630
686
  "admin-ui": {
631
687
  "type": "object",
632
688
  "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.",