@loworbitstudio/visor-theme-engine 0.25.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-ZPTjTL_-.js';
1
+ import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-C7nWZ5fB.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -2,6 +2,8 @@ import {
2
2
  FULL_SHADE_STEPS,
3
3
  MATERIAL_TEXT_SLOTS,
4
4
  SELECTIVE_SHADE_STEPS,
5
+ VISOR_CORE_DARK_SEMANTIC_ALIASES,
6
+ VISOR_CORE_SEMANTIC_ALIASES_DARK_HOST,
5
7
  aliasFamily,
6
8
  block,
7
9
  buildVisorFontUrl,
@@ -9,7 +11,9 @@ import {
9
11
  collectDeclaredProperties,
10
12
  fontStack,
11
13
  generateDarkCss,
14
+ generateFontMetricDecls,
12
15
  generateFontSizeDecls,
16
+ generateFontTextTransformDecls,
13
17
  generateFontWeightDecls,
14
18
  generateHairlineDecls,
15
19
  generateIntentDecls,
@@ -25,7 +29,7 @@ import {
25
29
  resolveThemeBrand,
26
30
  resolveThemeFonts,
27
31
  sectionComment
28
- } from "../chunk-C2DUPZVY.js";
32
+ } from "../chunk-YMCDUC7N.js";
29
33
 
30
34
  // src/adapters/brand-passthrough.ts
31
35
  var SENTINEL_COLOR = "#ff00ff";
@@ -307,10 +311,11 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
307
311
  const darkBody = stripHeader(generateDarkCss(input.tokens, { scopePrefix, colorScheme }));
308
312
  const adaptiveBody = [lightBody, darkBody].filter(Boolean).join("\n\n");
309
313
  if (scopePrefix) {
310
- const declared = collectDeclaredProperties(
311
- [primitivesBody, semanticLines.join("\n"), adaptiveBody].join("\n")
312
- );
313
- const aliasDecls = generateSemanticAliasDecls(declared);
314
+ const themeCss = [primitivesBody, semanticLines.join("\n"), adaptiveBody].join("\n");
315
+ const declared = collectDeclaredProperties(themeCss);
316
+ const aliasDecls = generateSemanticAliasDecls(declared, {
317
+ table: colorScheme === "dark-only" ? VISOR_CORE_SEMANTIC_ALIASES_DARK_HOST : void 0
318
+ });
314
319
  if (aliasDecls.length > 0) {
315
320
  semanticLines.push(
316
321
  sectionComment("visor-core alias re-substitution at theme scope (VI-648)")
@@ -318,6 +323,27 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
318
323
  semanticLines.push(block(hostSelector, aliasDecls));
319
324
  semanticLines.push("");
320
325
  }
326
+ if (colorScheme === "adaptive") {
327
+ const darkAliasDecls = generateSemanticAliasDecls(
328
+ collectDeclaredProperties(themeCss, { exclude: [lightModeSelector] }),
329
+ { table: VISOR_CORE_DARK_SEMANTIC_ALIASES }
330
+ );
331
+ if (darkAliasDecls.length > 0) {
332
+ semanticLines.push(
333
+ sectionComment("visor-core alias re-substitution (dark) \u2014 manual toggle (VI-696)")
334
+ );
335
+ semanticLines.push(block(darkSelectors.join(",\n"), darkAliasDecls));
336
+ semanticLines.push("");
337
+ semanticLines.push(
338
+ sectionComment("visor-core alias re-substitution (dark) \u2014 prefers-color-scheme (VI-696)")
339
+ );
340
+ const inner = block(prefersSelector, darkAliasDecls);
341
+ semanticLines.push(`@media (prefers-color-scheme: dark) {
342
+ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
343
+ }`);
344
+ semanticLines.push("");
345
+ }
346
+ }
321
347
  }
322
348
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
323
349
  if (semanticLayer) {
@@ -559,7 +585,9 @@ function generateTypographyDecls(config, aliases) {
559
585
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
560
586
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
561
587
  decls.push(...generateFontSizeDecls(config.typography.scale));
588
+ decls.push(...generateFontMetricDecls(config.typography.body.family));
562
589
  decls.push(...generateFontWeightDecls(config.typography));
590
+ decls.push(...generateFontTextTransformDecls(config.typography));
563
591
  const lineHeights = {
564
592
  none: 1,
565
593
  tight: 1.25,
@@ -898,6 +926,33 @@ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
898
926
  },
899
927
  colorScheme
900
928
  );
929
+ const themeCss = [lines.join("\n"), semanticLines.join("\n"), componentTokensCss, brandResult.css, passthroughCss].join("\n");
930
+ const declared = collectDeclaredProperties(themeCss);
931
+ const aliasDecls = generateSemanticAliasDecls(declared, {
932
+ table: colorScheme === "dark-only" ? VISOR_CORE_SEMANTIC_ALIASES_DARK_HOST : void 0
933
+ });
934
+ if (aliasDecls.length > 0) {
935
+ semanticLines.push(sectionComment2("visor-core alias re-substitution at theme scope (VI-695)"));
936
+ semanticLines.push(block2(scopeClass, aliasDecls));
937
+ semanticLines.push("");
938
+ }
939
+ if (emitPrefers) {
940
+ const darkAliasDecls = generateSemanticAliasDecls(
941
+ collectDeclaredProperties(themeCss, { exclude: [lightSel] }),
942
+ { table: VISOR_CORE_DARK_SEMANTIC_ALIASES }
943
+ );
944
+ if (darkAliasDecls.length > 0) {
945
+ semanticLines.push(sectionComment2("visor-core alias re-substitution (dark) \u2014 manual toggle (VI-696)"));
946
+ semanticLines.push(block2(darkSel, darkAliasDecls));
947
+ semanticLines.push("");
948
+ semanticLines.push(sectionComment2("visor-core alias re-substitution (dark) \u2014 prefers-color-scheme (VI-696)"));
949
+ const inner = block2(`${scopeClass}:not(.light):not(.theme-light):not([data-theme="light"])`, darkAliasDecls);
950
+ semanticLines.push(`@media (prefers-color-scheme: dark) {
951
+ ${inner.split("\n").map((l) => ` ${l}`).join("\n")}
952
+ }`);
953
+ semanticLines.push("");
954
+ }
955
+ }
901
956
  const adaptiveBody = [lines.join("\n").trim(), componentTokensCss].filter(Boolean).join("\n\n");
902
957
  const adaptiveLayer = wrapInLayer("visor-adaptive", adaptiveBody);
903
958
  const semanticLayer = wrapInLayer("visor-semantic", semanticLines.join("\n").trim());
@@ -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",
@@ -1874,7 +1988,7 @@ var textFamily = {
1874
1988
  var switchFamily = {
1875
1989
  family: "switch",
1876
1990
  prefix: "switch",
1877
- description: 'Switch track and knob dimensions (default size; `size="sm"` keeps its own). Set `knob-inset` to size the knob from the track: it sits that far from the track\'s outer edge on every side.',
1991
+ description: 'Switch track and knob dimensions (default size; `size="sm"` keeps its own) and colours. Set `knob-inset` to size the knob from the track: it sits that far from the track\'s outer edge on every side.',
1878
1992
  tokens: [
1879
1993
  { key: "track-width", property: "width", description: "Track width.", consumers: at(SWITCH, "2rem") },
1880
1994
  {
@@ -1895,6 +2009,30 @@ var switchFamily = {
1895
2009
  property: "outline-color",
1896
2010
  description: "Track edge colour \u2014 transparent by default. Width and style come from the shared `control` edge, so `control.edge-width: 0` turns it off.",
1897
2011
  consumers: at(SWITCH, "transparent")
2012
+ },
2013
+ {
2014
+ key: "track-hover-bg",
2015
+ property: "background-color (hover)",
2016
+ description: "Unchecked track fill under the pointer. Bind it in a borderless theme, where `--border-strong` is transparent.",
2017
+ consumers: at(SWITCH, "var(--border-strong, #d1d5db)")
2018
+ },
2019
+ {
2020
+ key: "track-hover-bg-checked",
2021
+ property: "background-color (hover)",
2022
+ description: "Checked track fill under the pointer. Unbound it is `--border-strong`, as it always was; bind it to keep the checked fill under the pointer.",
2023
+ consumers: at(SWITCH, "var(--border-strong, #d1d5db)")
2024
+ },
2025
+ {
2026
+ key: "knob-bg",
2027
+ property: "background-color (knob)",
2028
+ description: "Unchecked knob fill. Bind it in a dark theme, where the page colour is a black knob on a dark track.",
2029
+ consumers: at(SWITCH, "var(--surface-page, #ffffff)")
2030
+ },
2031
+ {
2032
+ key: "knob-bg-checked",
2033
+ property: "background-color (knob)",
2034
+ description: "Checked knob fill. Independent of `knob-bg`, so a light unchecked knob can sit beside a dark knob on a bright checked track.",
2035
+ consumers: at(SWITCH, "var(--surface-page, #ffffff)")
1898
2036
  }
1899
2037
  ]
1900
2038
  };
@@ -1905,7 +2043,22 @@ var saveStatusFamily = {
1905
2043
  tokens: [
1906
2044
  { 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
2045
  { 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)") },
2046
+ { key: "font-size", property: "font-size", description: "Readout type size.", consumers: at(SAVE_STATUS, "var(--font-size-xs, 0.75rem)") },
2047
+ {
2048
+ key: "dot-size",
2049
+ property: "width / height",
2050
+ 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.",
2051
+ consumers: [
2052
+ { file: SAVE_STATUS, fallback: "var(--spacing-2, 0.5rem)" },
2053
+ { file: SAVE_STATUS, fallback: "var(--spacing-2, 0.5rem)" }
2054
+ ]
2055
+ },
2056
+ {
2057
+ key: "unsaved-dot-size",
2058
+ property: "width / height",
2059
+ description: 'Diameter of the dot that draws Unsaved \u2014 the status dot while Unsaved, and the dot alone of `unsavedAs="dot"`. Follows `dot-size`.',
2060
+ consumers: each([SAVE_STATUS, SAVE_STATUS], "var(--save-status-dot-size, var(--spacing-2, 0.5rem))")
2061
+ },
1909
2062
  { key: "font-family", property: "font-family", description: "Readout family (e.g. the theme's mono).", consumers: at(SAVE_STATUS, "revert-layer") },
1910
2063
  { key: "text-transform", property: "text-transform", description: "Readout casing.", consumers: at(SAVE_STATUS, "revert-layer") },
1911
2064
  { key: "letter-spacing", property: "letter-spacing", description: "Readout tracking.", consumers: at(SAVE_STATUS, "revert-layer") }
@@ -1941,6 +2094,38 @@ var actionRowFamily = {
1941
2094
  { key: "trailing-color", property: "color", description: "Trailing value or caret colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") }
1942
2095
  ]
1943
2096
  };
2097
+ var inputAffixFamily = {
2098
+ family: "input-affix",
2099
+ prefix: "input-affix",
2100
+ description: "Input and NumberInput prefix and suffix: the gap between a fixed affix and the value, and the affix's ink.",
2101
+ tokens: [
2102
+ {
2103
+ key: "gap",
2104
+ property: "gap",
2105
+ description: "Space between a prefix or suffix and the value.",
2106
+ consumers: [...at(INPUT, "var(--spacing-2, 0.5rem)"), ...at(NUMBER_INPUT, "var(--spacing-2, 0.5rem)")]
2107
+ },
2108
+ {
2109
+ key: "color",
2110
+ property: "color",
2111
+ description: "Prefix and suffix ink.",
2112
+ consumers: [...at(INPUT, "var(--text-secondary, #6b7280)"), ...at(NUMBER_INPUT, "var(--text-secondary, #6b7280)")]
2113
+ }
2114
+ ]
2115
+ };
2116
+ var separatorFamily = {
2117
+ family: "separator",
2118
+ prefix: "separator",
2119
+ description: "Separator rule: the colour of the line, bare or on both sides of a label.",
2120
+ tokens: [
2121
+ {
2122
+ key: "color",
2123
+ property: "background-color",
2124
+ description: "Rule colour. Falls back to the default border colour.",
2125
+ consumers: at(SEPARATOR, "var(--border-default, #e5e7eb)")
2126
+ }
2127
+ ]
2128
+ };
1944
2129
  var HAIRLINE_PX = [
1945
2130
  "components/ui/bulk-action-bar/bulk-action-bar.module.css",
1946
2131
  "components/ui/dialog/dialog.module.css",
@@ -1980,6 +2165,29 @@ var hairlineFamily = {
1980
2165
  }
1981
2166
  ]
1982
2167
  };
2168
+ var segmentedControlFamily = {
2169
+ family: "segmented-control",
2170
+ prefix: "segmented-control",
2171
+ 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.",
2172
+ tokens: [
2173
+ { 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))") },
2174
+ {
2175
+ key: "radius",
2176
+ property: "border-radius",
2177
+ description: "Track corner radius. Unset, each size keeps its ToggleGroup radius (md and sm `--radius-lg`, xs `--radius-md`, lg `--radius-xl`).",
2178
+ consumers: [
2179
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-md, 0.375rem)" },
2180
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-lg, 0.5rem)" },
2181
+ { file: SEGMENTED_CONTROL, fallback: "var(--radius-xl, 0.75rem)" }
2182
+ ]
2183
+ },
2184
+ { 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))") },
2185
+ { key: "indicator-text", property: "color", description: "Active segment label ink, on the pill.", consumers: at(SEGMENTED_CONTROL, "var(--interactive-primary-text, #f9fafb)") },
2186
+ { 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)") },
2187
+ { key: "inactive-text", property: "color", description: "Inactive segment label colour.", consumers: at(SEGMENTED_CONTROL, "var(--text-primary, #111827)") },
2188
+ { key: "active-weight", property: "font-weight", description: "Active segment label weight.", consumers: at(SEGMENTED_CONTROL, "var(--font-weight-medium, 500)") }
2189
+ ]
2190
+ };
1983
2191
  var adminUiFamily = {
1984
2192
  family: "admin-ui",
1985
2193
  prefix: "admin-ui",
@@ -2008,6 +2216,10 @@ var COMPONENT_TOKEN_FAMILIES = [
2008
2216
  skeletonFamily,
2009
2217
  spinnerFamily,
2010
2218
  checkboxFamily,
2219
+ inputFamily,
2220
+ textareaFamily,
2221
+ selectFamily,
2222
+ tagInputFamily,
2011
2223
  controlFamily,
2012
2224
  hairlineFamily,
2013
2225
  fieldFamily,
@@ -2015,8 +2227,11 @@ var COMPONENT_TOKEN_FAMILIES = [
2015
2227
  textFamily,
2016
2228
  switchFamily,
2017
2229
  saveStatusFamily,
2230
+ segmentedControlFamily,
2018
2231
  tooltipFamily,
2019
2232
  actionRowFamily,
2233
+ inputAffixFamily,
2234
+ separatorFamily,
2020
2235
  adminUiFamily
2021
2236
  ];
2022
2237
  var COMPONENT_TOKEN_FAMILY_BY_NAME = new Map(
@@ -2306,6 +2521,23 @@ function applyOverrides(tokens, overrides) {
2306
2521
  return result;
2307
2522
  }
2308
2523
 
2524
+ // src/font-metrics.ts
2525
+ var FONT_VERTICAL_METRICS = {
2526
+ arial: { ascent: 0.905, descent: 0.212 },
2527
+ inter: { ascent: 0.969, descent: 0.241 },
2528
+ outfit: { ascent: 1, descent: 0.26 },
2529
+ "pitch sans": { ascent: 0.9, descent: 0.32 },
2530
+ "pp model mono": { ascent: 1.02, descent: 0.288 },
2531
+ "product sans": { ascent: 0.96, descent: 0.253 },
2532
+ satoshi: { ascent: 1.01, descent: 0.24 },
2533
+ "system-ui": { ascent: 0.967, descent: 0.211 }
2534
+ };
2535
+ function generateFontMetricDecls(family) {
2536
+ const key = family.trim().replace(/^["']|["']$/g, "").toLowerCase();
2537
+ const m = FONT_VERTICAL_METRICS[key];
2538
+ return m ? [`--font-ascent: ${m.ascent};`, `--font-descent: ${m.descent};`] : [];
2539
+ }
2540
+
2309
2541
  // src/font-weights.ts
2310
2542
  var WEIGHT_RAMP_TARGETS = {
2311
2543
  medium: 500,
@@ -2512,6 +2744,14 @@ function generateFontSizeDecls(scale) {
2512
2744
  return `--font-size-${name}: ${rem}rem; /* ${Number(scaled.toFixed(2))}px */`;
2513
2745
  });
2514
2746
  }
2747
+ function generateFontTextTransformDecls(typography) {
2748
+ const decls = [];
2749
+ for (const slot of ["heading", "display", "body", "mono"]) {
2750
+ const value = typography[slot]["text-transform"];
2751
+ if (value) decls.push(`--font-${slot}-text-transform: ${value};`);
2752
+ }
2753
+ return decls;
2754
+ }
2515
2755
  function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
2516
2756
  const decls = [];
2517
2757
  decls.push(`--font-heading: ${fontStack(config.typography.heading.family, aliases)};`);
@@ -2520,7 +2760,9 @@ function generateTypographyPrimitives(config, aliases = EMPTY_ALIASES) {
2520
2760
  decls.push(`--font-body: ${fontStack(config.typography.body.family, aliases)};`);
2521
2761
  decls.push(`--font-mono: ${fontStack(config.typography.mono.family, aliases)};`);
2522
2762
  decls.push(...generateFontSizeDecls(config.typography.scale));
2763
+ decls.push(...generateFontMetricDecls(config.typography.body.family));
2523
2764
  decls.push(...generateFontWeightDecls(config.typography));
2765
+ decls.push(...generateFontTextTransformDecls(config.typography));
2524
2766
  const lineHeights = {
2525
2767
  none: 1,
2526
2768
  tight: 1.25,
@@ -2925,16 +3167,46 @@ var VISOR_CORE_SEMANTIC_ALIASES = {
2925
3167
  // Field-attached floating panels (VI-497).
2926
3168
  "field-menu-bg": "surface-popover"
2927
3169
  };
2928
- function collectDeclaredProperties(css) {
3170
+ var VISOR_CORE_DARK_SEMANTIC_ALIASES = {
3171
+ "border-input": "color-neutral-700",
3172
+ "skeleton-from": "color-neutral-800",
3173
+ "skeleton-to": "color-neutral-700",
3174
+ "chart-1": "color-primary-400",
3175
+ "sidebar-bg": "color-neutral-900",
3176
+ "sidebar-text": "color-neutral-300",
3177
+ "sidebar-primary-bg": "color-primary-500",
3178
+ "sidebar-accent-bg": "color-neutral-800",
3179
+ "sidebar-accent-text": "color-neutral-50",
3180
+ "sidebar-border": "color-neutral-700",
3181
+ "sidebar-ring": "color-primary-400",
3182
+ "sidebar-text-muted": "color-neutral-400"
3183
+ };
3184
+ var MODE_DEPENDENT_SEMANTIC_ALIASES = new Set(
3185
+ Object.keys(VISOR_CORE_DARK_SEMANTIC_ALIASES)
3186
+ );
3187
+ var VISOR_CORE_SEMANTIC_ALIASES_DARK_HOST = {
3188
+ ...VISOR_CORE_SEMANTIC_ALIASES,
3189
+ ...VISOR_CORE_DARK_SEMANTIC_ALIASES
3190
+ };
3191
+ function collectDeclaredProperties(css, options = {}) {
2929
3192
  const declared = /* @__PURE__ */ new Set();
2930
- for (const match of css.matchAll(/(^|[{;\s])--([a-zA-Z0-9-]+)\s*:/g)) {
2931
- declared.add(match[2]);
3193
+ if (!options.exclude?.length) {
3194
+ for (const match of css.matchAll(/(^|[{;\s])--([a-zA-Z0-9-]+)\s*:/g)) {
3195
+ declared.add(match[2]);
3196
+ }
3197
+ return declared;
3198
+ }
3199
+ const exclude = new Set(options.exclude);
3200
+ for (const rule of css.replace(/\/\*[\s\S]*?\*\//g, "").matchAll(/([^{}]*)\{([^{}]*)\}/g)) {
3201
+ if (exclude.has(rule[1].trim())) continue;
3202
+ for (const prop of collectDeclaredProperties(rule[2])) declared.add(prop);
2932
3203
  }
2933
3204
  return declared;
2934
3205
  }
2935
- function generateSemanticAliasDecls(declared) {
3206
+ function generateSemanticAliasDecls(declared, options = {}) {
2936
3207
  const decls = [];
2937
- for (const [alias, referent] of Object.entries(VISOR_CORE_SEMANTIC_ALIASES)) {
3208
+ for (const [alias, referent] of Object.entries(options.table ?? VISOR_CORE_SEMANTIC_ALIASES)) {
3209
+ if (options.skip?.has(alias)) continue;
2938
3210
  if (declared.has(alias)) continue;
2939
3211
  if (!declared.has(referent)) continue;
2940
3212
  decls.push(`--${alias}: var(--${referent});`);
@@ -2943,6 +3215,7 @@ function generateSemanticAliasDecls(declared) {
2943
3215
  }
2944
3216
 
2945
3217
  // src/types.ts
3218
+ var TEXT_TRANSFORMS = ["none", "uppercase", "lowercase", "capitalize"];
2946
3219
  var MATERIAL_TEXT_SLOTS = [
2947
3220
  "displayLarge",
2948
3221
  "displayMedium",
@@ -2999,6 +3272,7 @@ export {
2999
3272
  isValidColor,
3000
3273
  compositeOverBackground,
3001
3274
  serializeColor,
3275
+ TEXT_TRANSFORMS,
3002
3276
  MATERIAL_TEXT_SLOTS,
3003
3277
  componentTokenName,
3004
3278
  COMPONENT_TOKEN_FAMILIES,
@@ -3014,6 +3288,8 @@ export {
3014
3288
  collectBrandPassthrough,
3015
3289
  hasBrandPassthrough,
3016
3290
  applyOverrides,
3291
+ FONT_VERTICAL_METRICS,
3292
+ generateFontMetricDecls,
3017
3293
  WEIGHT_RAMP_TARGETS,
3018
3294
  loadedWeights,
3019
3295
  declaresWeights,
@@ -3027,6 +3303,7 @@ export {
3027
3303
  sectionComment,
3028
3304
  block2 as block,
3029
3305
  generateFontSizeDecls,
3306
+ generateFontTextTransformDecls,
3030
3307
  generatePrimitivesCss,
3031
3308
  generateSemanticCss,
3032
3309
  generateTextScaleAliasDecls,
@@ -3037,6 +3314,9 @@ export {
3037
3314
  generateDarkCss,
3038
3315
  generateFullBundleCss,
3039
3316
  VISOR_CORE_SEMANTIC_ALIASES,
3317
+ VISOR_CORE_DARK_SEMANTIC_ALIASES,
3318
+ MODE_DEPENDENT_SEMANTIC_ALIASES,
3319
+ VISOR_CORE_SEMANTIC_ALIASES_DARK_HOST,
3040
3320
  collectDeclaredProperties,
3041
3321
  generateSemanticAliasDecls
3042
3322
  };