@loworbitstudio/visor-theme-engine 0.25.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-C2DUPZVY.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,8 +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";
1275
1278
  var ACTION_ROW = "components/ui/action-row/action-row.module.css";
1279
+ var DIALOG_FIELD = "blocks/dialog-field/dialog-field.module.css";
1276
1280
  function at(file, fallback) {
1277
1281
  return [{ file, fallback }];
1278
1282
  }
@@ -1691,7 +1695,84 @@ var checkboxFamily = {
1691
1695
  { key: "border-checked", property: "outline-color", description: "Checked edge colour.", consumers: at(CHECKBOX, "var(--interactive-primary-bg, var(--primary, #111827))") }
1692
1696
  ]
1693
1697
  };
1694
- 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];
1695
1776
  var controlFamily = {
1696
1777
  family: "control",
1697
1778
  prefix: "control",
@@ -1712,7 +1793,7 @@ var controlFamily = {
1712
1793
  key: "edge-color",
1713
1794
  property: "outline-color",
1714
1795
  description: "Resting-edge colour. A component's own `--<component>-border` override still wins over it.",
1715
- 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)")
1716
1797
  },
1717
1798
  {
1718
1799
  key: "edge-style",
@@ -1724,19 +1805,40 @@ var controlFamily = {
1724
1805
  key: "drop-edge-width",
1725
1806
  property: "outline-width",
1726
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.",
1727
- 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)")
1728
1809
  },
1729
1810
  {
1730
1811
  key: "drop-edge-style",
1731
1812
  property: "outline-style",
1732
- description: "Dashed-edge style for drop targets and empty states.",
1733
- 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")
1734
1815
  },
1735
1816
  {
1736
1817
  key: "state-edge-width",
1737
1818
  property: "outline-width",
1738
1819
  description: "Focus and invalid edge weight. Independent of `edge-width`, so focus and invalid stay visible with edges off.",
1739
- 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
+ ]
1740
1842
  }
1741
1843
  ]
1742
1844
  };
@@ -1768,10 +1870,10 @@ var fieldFamily = {
1768
1870
  ]
1769
1871
  };
1770
1872
  var BUTTON_SIZES = [
1771
- { size: "sm", fontSize: ["var(--font-size-xs, 0.75rem)", "0.8125rem"], radius: "var(--radius-md, 0.375rem)" },
1772
- { size: "md", fontSize: ["var(--font-size-sm, 0.875rem)"], radius: "var(--radius-md, 0.375rem)" },
1773
- { size: "lg", fontSize: ["var(--font-size-base, 1rem)"], radius: "var(--radius-md, 0.375rem)" },
1774
- { 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)" }
1775
1877
  ];
1776
1878
  var buttonFamily = {
1777
1879
  family: "button",
@@ -1790,6 +1892,12 @@ var buttonFamily = {
1790
1892
  description: "Tracking for every size.",
1791
1893
  consumers: BUTTON_SIZES.map(() => ({ file: BUTTON, fallback: "revert-layer" }))
1792
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
+ },
1793
1901
  {
1794
1902
  key: "radius",
1795
1903
  property: "border-radius",
@@ -1799,13 +1907,19 @@ var buttonFamily = {
1799
1907
  { file: BUTTON, fallback: "var(--radius-md, 0.375rem)" }
1800
1908
  ]
1801
1909
  },
1802
- ...BUTTON_SIZES.flatMap(({ size, fontSize, radius }) => [
1910
+ ...BUTTON_SIZES.flatMap(({ size, fontSize, fontWeight, radius }) => [
1803
1911
  {
1804
1912
  key: `${size}-font-size`,
1805
1913
  property: "font-size",
1806
1914
  description: fontSize.length > 1 ? `\`${size}\` type size. Drives both the standard and editorial-density steps.` : `\`${size}\` type size.`,
1807
1915
  consumers: fontSize.map((fallback) => ({ file: BUTTON, fallback }))
1808
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
+ },
1809
1923
  {
1810
1924
  key: `${size}-text-transform`,
1811
1925
  property: "text-transform",
@@ -1905,7 +2019,22 @@ var saveStatusFamily = {
1905
2019
  tokens: [
1906
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") },
1907
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)") },
1908
- { key: "dot-size", property: "width", description: 'Diameter of the dot that draws Unsaved when `unsavedAs="dot"`. The slot width does not change.', consumers: at(SAVE_STATUS, "var(--spacing-2, 0.5rem)") },
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
+ },
1909
2038
  { key: "font-family", property: "font-family", description: "Readout family (e.g. the theme's mono).", consumers: at(SAVE_STATUS, "revert-layer") },
1910
2039
  { key: "text-transform", property: "text-transform", description: "Readout casing.", consumers: at(SAVE_STATUS, "revert-layer") },
1911
2040
  { key: "letter-spacing", property: "letter-spacing", description: "Readout tracking.", consumers: at(SAVE_STATUS, "revert-layer") }
@@ -1941,6 +2070,38 @@ var actionRowFamily = {
1941
2070
  { key: "trailing-color", property: "color", description: "Trailing value or caret colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") }
1942
2071
  ]
1943
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
+ };
1944
2105
  var HAIRLINE_PX = [
1945
2106
  "components/ui/bulk-action-bar/bulk-action-bar.module.css",
1946
2107
  "components/ui/dialog/dialog.module.css",
@@ -1980,6 +2141,29 @@ var hairlineFamily = {
1980
2141
  }
1981
2142
  ]
1982
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
+ };
1983
2167
  var adminUiFamily = {
1984
2168
  family: "admin-ui",
1985
2169
  prefix: "admin-ui",
@@ -2008,6 +2192,10 @@ var COMPONENT_TOKEN_FAMILIES = [
2008
2192
  skeletonFamily,
2009
2193
  spinnerFamily,
2010
2194
  checkboxFamily,
2195
+ inputFamily,
2196
+ textareaFamily,
2197
+ selectFamily,
2198
+ tagInputFamily,
2011
2199
  controlFamily,
2012
2200
  hairlineFamily,
2013
2201
  fieldFamily,
@@ -2015,8 +2203,11 @@ var COMPONENT_TOKEN_FAMILIES = [
2015
2203
  textFamily,
2016
2204
  switchFamily,
2017
2205
  saveStatusFamily,
2206
+ segmentedControlFamily,
2018
2207
  tooltipFamily,
2019
2208
  actionRowFamily,
2209
+ inputAffixFamily,
2210
+ separatorFamily,
2020
2211
  adminUiFamily
2021
2212
  ];
2022
2213
  var COMPONENT_TOKEN_FAMILY_BY_NAME = new Map(
@@ -2306,6 +2497,23 @@ function applyOverrides(tokens, overrides) {
2306
2497
  return result;
2307
2498
  }
2308
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
+
2309
2517
  // src/font-weights.ts
2310
2518
  var WEIGHT_RAMP_TARGETS = {
2311
2519
  medium: 500,
@@ -2520,6 +2728,7 @@ function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
2520
2728
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
2521
2729
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
2522
2730
  decls.push(...generateFontSizeDecls(config.typography.scale));
2731
+ decls.push(...generateFontMetricDecls(config.typography.body.family));
2523
2732
  decls.push(...generateFontWeightDecls(config.typography));
2524
2733
  const lineHeights = {
2525
2734
  none: 1,
@@ -3014,6 +3223,8 @@ export {
3014
3223
  collectBrandPassthrough,
3015
3224
  hasBrandPassthrough,
3016
3225
  applyOverrides,
3226
+ FONT_VERTICAL_METRICS,
3227
+ generateFontMetricDecls,
3017
3228
  WEIGHT_RAMP_TARGETS,
3018
3229
  loadedWeights,
3019
3230
  declaresWeights,
package/dist/index.d.ts CHANGED
@@ -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, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, 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
  }
@@ -1214,6 +1249,20 @@ var properties = {
1214
1249
  $ref: "#/$defs/componentTokenBinding"
1215
1250
  }
1216
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
+ },
1217
1266
  "admin-ui": {
1218
1267
  type: "object",
1219
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.",
@@ -2286,4 +2335,36 @@ declare function cleanFontValue(val: string): string;
2286
2335
  */
2287
2336
  declare function extractFromCSS(files: CSSFile[], name?: string): ExtractionResult;
2288
2337
 
2289
- 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-C2DUPZVY.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;
@@ -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, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, 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
  }
@@ -1250,6 +1287,20 @@ var visor_theme_schema_default = {
1250
1287
  $ref: "#/$defs/componentTokenBinding"
1251
1288
  }
1252
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
+ },
1253
1304
  "admin-ui": {
1254
1305
  type: "object",
1255
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.",
@@ -4353,6 +4404,7 @@ export {
4353
4404
  DEFAULT_BRAND_STRATEGY_SURFACES,
4354
4405
  DEFAULT_BRAND_STRATEGY_TONE_STATES,
4355
4406
  DEFAULT_VISOR_BRAND,
4407
+ FONT_VERTICAL_METRICS,
4356
4408
  FONT_WEIGHT_ALIASES,
4357
4409
  GOVERNS_WILDCARD,
4358
4410
  SEMANTIC_MAP,
@@ -4380,6 +4432,7 @@ export {
4380
4432
  extractFromCSS,
4381
4433
  formatFontCoverageError,
4382
4434
  generateDarkCss,
4435
+ generateFontMetricDecls,
4383
4436
  generateFontWeightDecls,
4384
4437
  generateFullBundleCss,
4385
4438
  generateLightCss,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.25.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",
@@ -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, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, 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
  }
@@ -634,6 +669,20 @@
634
669
  "$ref": "#/$defs/componentTokenBinding"
635
670
  }
636
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
+ },
637
686
  "admin-ui": {
638
687
  "type": "object",
639
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.",