basalt-ui 1.40.1 → 1.40.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [1.40.3](https://github.com/jkrumm/basalt-ui/compare/v1.40.2...v1.40.3) (2026-10-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * chart cursor slider always carries aria-valuenow ([e1bce15](https://github.com/jkrumm/basalt-ui/commit/e1bce15e7b280a4f203bb05173d4478c0a64c7f3))
7
+ * light-scheme text, links, errors and badges clear WCAG AA ([bfa4c0e](https://github.com/jkrumm/basalt-ui/commit/bfa4c0e329138daa0f6ffe7d6a9678644a1b0440))
8
+ * shortcut display uses platform glyphs and Title-case words for named keys ([77733e9](https://github.com/jkrumm/basalt-ui/commit/77733e916892138b42b398188f85dae234877181))
9
+
10
+ ## [1.40.2](https://github.com/jkrumm/basalt-ui/compare/v1.40.1...v1.40.2) (2026-10-05)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * sidebar footer rows sit on the nav row's height and 1px pitch ([bbd5c66](https://github.com/jkrumm/basalt-ui/commit/bbd5c66a223af61888df28cca837b89ef95181b1))
16
+
1
17
  ## [1.40.1](https://github.com/jkrumm/basalt-ui/compare/v1.40.0...v1.40.1) (2026-10-05)
2
18
 
3
19
 
package/MIGRATING.md CHANGED
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
66
66
  shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
67
67
  consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
68
68
 
69
- | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | Unreleased |
70
- | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ---------- | --- |
71
- | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — |
72
- | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
- | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — |
75
- | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — |
76
- | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
- | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
- | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
- | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
80
- | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — |
69
+ | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | 1.40.2 | 1.40.3 | Unreleased |
70
+ | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ------ | ------ | ---------- | --- |
71
+ | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — | — | — |
72
+ | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
+ | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — | — | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — |
75
+ | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — |
76
+ | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
+ | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
+ | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
+ | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
80
+ | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — | — | — |
81
81
 
82
82
  ---
83
83
 
@@ -86,6 +86,14 @@ consumer read this index, found nothing, and diffed a KPI row that had silently
86
86
  _Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
87
87
  release renames it and opens a new one here._
88
88
 
89
+ ## 1.40.3
90
+
91
+ _Nothing to migrate in this release._
92
+
93
+ ## 1.40.2
94
+
95
+ _Nothing to migrate in this release._
96
+
89
97
  ## 1.40.1
90
98
 
91
99
  _Nothing to migrate in this release._
@@ -1 +1 @@
1
- {"version":3,"file":"HoverOverlay.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/HoverOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAA;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAG1D,qCAAqC;AACrC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC3C;2FACuF;IACvF,MAAM,CAAC,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC5C,wEAAwE;IACxE,IAAI,CAAC,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC1C,OAAO,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC5C,4DAA4D;IAC5D,SAAS,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAA;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACJ,OAAO,EACP,SAAS,EACT,MAAM,EACN,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BAiCnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE;IAC1C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IACtB,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAc9C"}
1
+ {"version":3,"file":"HoverOverlay.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/HoverOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAA;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAG1D,qCAAqC;AACrC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC3C;2FACuF;IACvF,MAAM,CAAC,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC5C,wEAAwE;IACxE,IAAI,CAAC,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC1C,OAAO,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC5C,4DAA4D;IAC5D,SAAS,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAA;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,MAAM,EACN,MAAM,EACN,IAAI,EACJ,OAAO,EACP,SAAS,EACT,MAAM,EACN,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BAkCnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE;IAC1C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IACtB,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAChC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,QAAQ,CAAC,CAc9C"}
@@ -36,7 +36,8 @@ function HoverOverlay({
36
36
  "aria-orientation": "horizontal",
37
37
  "aria-valuemin": 0,
38
38
  ...valueMax !== void 0 && { "aria-valuemax": valueMax },
39
- ...valueNow !== void 0 && { "aria-valuenow": valueNow },
39
+ // `aria-valuenow` is required on a slider: 0 (the first point) until the cursor lands on one.
40
+ "aria-valuenow": valueNow ?? 0,
40
41
  ...valueText !== void 0 && { "aria-valuetext": valueText },
41
42
  onKeyDown,
42
43
  ...onBlur !== void 0 && { onBlur }
@@ -9,7 +9,7 @@
9
9
  * parseShortcut('Mod+S', false) // ['Ctrl', 'S']
10
10
  *
11
11
  * // Single display string for Spotlight description:
12
- * formatShortcutDisplay('Mod+S', true) // '⌘ S' on mac
12
+ * formatShortcutDisplay('Mod+S', true) // '⌘S' on mac
13
13
  * formatShortcutDisplay('Mod+S', false) // 'Ctrl+S' on windows
14
14
  */
15
15
  /**
@@ -22,16 +22,20 @@ export declare function detectMac(): boolean;
22
22
  * Convert a shortcut string to a list of Kbd-friendly tokens.
23
23
  * 'Mod+S' → ['⌘', 'S'] (mac) or ['Ctrl', 'S'] (other).
24
24
  * 'Shift+Mod+P' → ['⇧', '⌘', 'P'] (mac) or ['Shift', 'Ctrl', 'P'] (other).
25
+ * Named keys read as glyphs on mac and Title-case words elsewhere:
26
+ * 'Mod+Backspace' → ['⌘', '⌫'] (mac) or ['Ctrl', 'Backspace'] (other).
25
27
  * isMac is the post-mount platform flag; defaults false on SSR / first render.
26
28
  *
27
29
  * @example
28
30
  * parseShortcut('Mod+S', true) // ['⌘', 'S']
29
31
  * parseShortcut('Shift+Mod+P', false) // ['Shift', 'Ctrl', 'P']
32
+ * parseShortcut('ArrowLeft', true) // ['←']
30
33
  */
31
34
  export declare function parseShortcut(shortcut: string, isMac: boolean): string[];
32
35
  /**
33
36
  * Format a shortcut string for inline display (e.g. Spotlight action description).
34
- * Returns a compact string like '⌘ S' (mac) or 'Ctrl+S' (windows).
37
+ * Returns a compact string like '⌘S' / '⌘⌫' (mac, glyphs with no separator) or 'Ctrl+S' /
38
+ * 'Ctrl+Backspace' (elsewhere, Title-case words joined with '+').
35
39
  *
36
40
  * isMac is the post-mount platform flag. Pass false on SSR / first client render.
37
41
  *
@@ -40,9 +44,10 @@ export declare function parseShortcut(shortcut: string, isMac: boolean): string[
40
44
  * always-on and produces the same tokens via parseShortcut.
41
45
  *
42
46
  * @example
43
- * formatShortcutDisplay('Mod+S', true) // '⌘ S'
47
+ * formatShortcutDisplay('Mod+S', true) // '⌘S'
48
+ * formatShortcutDisplay('Mod+Backspace', true) // '⌘⌫'
44
49
  * formatShortcutDisplay('Mod+S', false) // 'Ctrl+S'
45
- * formatShortcutDisplay('Shift+Mod+P', true) // '⇧ ⌘ P'
50
+ * formatShortcutDisplay('Shift+Mod+P', true) // '⇧⌘P'
46
51
  */
47
52
  export declare function formatShortcutDisplay(shortcut: string, isMac: boolean): string;
48
53
  //# sourceMappingURL=shortcut-format.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shortcut-format.d.ts","sourceRoot":"","sources":["../../src/commands/shortcut-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAMnC;AAID;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAiBxE;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAI9E"}
1
+ {"version":3,"file":"shortcut-format.d.ts","sourceRoot":"","sources":["../../src/commands/shortcut-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAMnC;AAmCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAqBxE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAG9E"}
@@ -4,9 +4,36 @@ function detectMac() {
4
4
  if (uadPlatform !== void 0) return /mac|iphone|ipad/i.test(uadPlatform);
5
5
  return /Mac|iPhone|iPad/.test(navigator.userAgent);
6
6
  }
7
+ const NAMED_KEYS = {
8
+ backspace: ["\u232B", "Backspace"],
9
+ delete: ["\u2326", "Delete"],
10
+ del: ["\u2326", "Delete"],
11
+ arrowleft: ["\u2190", "Left"],
12
+ arrowright: ["\u2192", "Right"],
13
+ arrowup: ["\u2191", "Up"],
14
+ arrowdown: ["\u2193", "Down"],
15
+ left: ["\u2190", "Left"],
16
+ right: ["\u2192", "Right"],
17
+ up: ["\u2191", "Up"],
18
+ down: ["\u2193", "Down"],
19
+ escape: ["\u238B", "Esc"],
20
+ esc: ["\u238B", "Esc"],
21
+ enter: ["\u21A9", "Enter"],
22
+ return: ["\u21A9", "Enter"],
23
+ tab: ["\u21E5", "Tab"],
24
+ space: ["\u2423", "Space"],
25
+ home: ["\u2196", "Home"],
26
+ end: ["\u2198", "End"],
27
+ pageup: ["\u21DE", "PgUp"],
28
+ pagedown: ["\u21DF", "PgDn"]
29
+ };
30
+ function plainKey(key) {
31
+ return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);
32
+ }
7
33
  function parseShortcut(shortcut, isMac) {
8
34
  return shortcut.split("+").map((key) => {
9
- switch (key.toLowerCase()) {
35
+ const lower = key.toLowerCase();
36
+ switch (lower) {
10
37
  case "mod":
11
38
  return isMac ? "\u2318" : "Ctrl";
12
39
  case "shift":
@@ -16,15 +43,18 @@ function parseShortcut(shortcut, isMac) {
16
43
  case "meta":
17
44
  return isMac ? "\u2318" : "Meta";
18
45
  case "ctrl":
19
- return isMac ? "^" : "Ctrl";
20
- default:
21
- return key.toUpperCase();
46
+ return isMac ? "\u2303" : "Ctrl";
47
+ default: {
48
+ const named = NAMED_KEYS[lower];
49
+ if (named !== void 0) return isMac ? named[0] : named[1];
50
+ return plainKey(key);
51
+ }
22
52
  }
23
53
  });
24
54
  }
25
55
  function formatShortcutDisplay(shortcut, isMac) {
26
56
  const tokens = parseShortcut(shortcut, isMac);
27
- return isMac ? tokens.join(" ") : tokens.join("+");
57
+ return tokens.join(isMac ? "" : "+");
28
58
  }
29
59
  export {
30
60
  detectMac,
@@ -205,9 +205,8 @@
205
205
  * bottom-placed block (a `kind: 'progress'` block defaults to `'bottom'`) stacked FLUSH on the
206
206
  * settings and account rows and on the scroll region above, which is the "cramped" footer.
207
207
  *
208
- * `region-gap` (12) above the cluster, `stack-sm` (8) between its parts — deliberately tighter
209
- * than the nav's `section-gap` (16): the footer is ONE pinned cluster whose rows belong together,
210
- * not a list of peer sections.
208
+ * `region-gap` (12) above the cluster, `stack-sm` (8) between BLOCKS, and the settings/account rows
209
+ * on the nav's own 1px pitch (see `.footerLinks + *` below): the footer rows are one list with the nav.
211
210
  */
212
211
  .footer {
213
212
  flex-shrink: 0;
@@ -223,6 +222,12 @@
223
222
  margin-top: var(--vx-space-stack-sm);
224
223
  }
225
224
 
225
+ /* Footer rows ARE a list like the nav's (`Stack gap={1}`): the settings rows and the account row
226
+ * sit on the nav's 1px pitch, not the cluster gap that separates blocks from rows. */
227
+ .footer > .footerLinks + * {
228
+ margin-top: 1px;
229
+ }
230
+
226
231
  /*
227
232
  * Footer settings row — IDENTICAL geometry to a nav row (padding 6px 10px, radius 6, gap 10,
228
233
  * 15px): muted text + faint icon at rest, ink-6% tint + ink on hover, exactly like an inactive
@@ -237,6 +242,9 @@
237
242
  padding: var(--vx-space-row-inset-y) var(--vx-space-row-inset-x);
238
243
  border-radius: var(--vx-radius-ctrl);
239
244
  font-size: var(--vx-text-md);
245
+ /* The nav row's line-height, so a footer row is exactly as tall as a nav row (Mantine's `Text`
246
+ * would otherwise bring its own 1.55 and add ~3px). */
247
+ line-height: var(--vx-space-row-line-height);
240
248
  color: var(--vx-muted);
241
249
  }
242
250
 
@@ -272,6 +280,7 @@
272
280
  .footerText {
273
281
  flex: 1;
274
282
  text-align: left;
283
+ line-height: var(--vx-space-row-line-height);
275
284
  }
276
285
 
277
286
  /*
@@ -296,6 +305,7 @@
296
305
  flex: 1;
297
306
  min-width: 0;
298
307
  text-align: left;
308
+ line-height: var(--vx-space-row-line-height);
299
309
  }
300
310
 
301
311
  /*
@@ -584,6 +594,7 @@
584
594
  .footerLinks {
585
595
  display: flex;
586
596
  flex-direction: column;
597
+ gap: 1px;
587
598
  }
588
599
 
589
600
  /* Fixed 18px icon column for a flat footer row — an icon-less row keeps the label aligned with
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAiEA,OAAO,KAAK,EACV,oBAAoB,EAGpB,oBAAoB,EAErB,MAAM,eAAe,CAAA;AAStB,OAAO,KAAK,EAAe,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAQlD;;;;;GAKG;AACH,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAIlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAMvD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAgBnE,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,iBAAiB;QACzB,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,WAAW,CAAC,EAAE,iBAAiB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,aAAa,CAAC,EAAE,WAAW,CAAA;KAC5B;CACF;AAgiCD;+EAC+E;AAC/E,eAAO,MAAM,SAAS,EAAE,oBAAuD,CAAA;AAyT/E;;8FAE8F;AAC9F,eAAO,MAAM,oBAAoB,EAAE,oBACc,CAAA;AAEjD,kEAAkE;AAClE,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAA;IAEzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC7B,CAAA;AA0BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,CAAC,EACrD,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CAmCtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAiEA,OAAO,KAAK,EACV,oBAAoB,EAGpB,oBAAoB,EAErB,MAAM,eAAe,CAAA;AAStB,OAAO,KAAK,EAAe,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAQlD;;;;;GAKG;AACH,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAIlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAMvD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAgBnE,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,iBAAiB;QACzB,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,WAAW,CAAC,EAAE,iBAAiB,CAAA;QAC/B,YAAY,CAAC,EAAE,YAAY,CAAA;QAC3B,aAAa,CAAC,EAAE,WAAW,CAAA;KAC5B;CACF;AAuiCD;+EAC+E;AAC/E,eAAO,MAAM,SAAS,EAAE,oBAAuD,CAAA;AAkU/E;;8FAE8F;AAC9F,eAAO,MAAM,oBAAoB,EAAE,oBACc,CAAA;AAEjD,kEAAkE;AAClE,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;IAE9B;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAA;IAEzB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC7B,CAAA;AA0BD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,CAAC,EACrD,OAAO,CAAC,EAAE,wBAAwB,GACjC,oBAAoB,CAmCtB"}
@@ -156,13 +156,17 @@ const basaltVariantColorResolver = (input) => {
156
156
  const isDefaultColor = input.color === void 0 || input.color === input.theme.primaryColor;
157
157
  return isDefaultColor ? { ...resolved, hover: `color-mix(in srgb, var(--vx-ink) 6%, transparent)` } : resolved;
158
158
  }
159
+ if (input.variant === "outline") {
160
+ return { ...resolved, color: `color-mix(in srgb, ${resolved.color} 70%, var(--vx-ink))` };
161
+ }
159
162
  if (input.variant !== "light") return resolved;
160
163
  const ink = `var(--mantine-color-${colorName}-light-color)`;
161
164
  return {
162
165
  ...resolved,
163
166
  background: `color-mix(in srgb, ${ink} 13%, transparent)`,
164
167
  hover: `color-mix(in srgb, ${ink} 20%, transparent)`,
165
- color: ink,
168
+ // Same pull toward ink as `outline`: the saturated tone text sat at 3.2–4.4:1 on its own tint.
169
+ color: `color-mix(in srgb, ${ink} 70%, var(--vx-ink))`,
166
170
  border: "none"
167
171
  };
168
172
  };
@@ -1003,6 +1007,12 @@ function buildCssVariablesResolver(data) {
1003
1007
  // secondary / muted text
1004
1008
  "--mantine-color-text": `var(--vx-ink, ${INK.ink.light})`,
1005
1009
  // primary body/heading text
1010
+ // Field errors: Mantine's own error red measures 1.98:1 (dark) / 4.37:1 (light). The solid status red
1011
+ // pulled toward ink clears AA on every text surface in both schemes.
1012
+ "--mantine-color-error": "color-mix(in srgb, var(--vx-bad-solid) 85%, var(--vx-ink))",
1013
+ // Links: Mantine reads the accent FILL shade (4.38:1 on the light page, under WCAG AA); the hover
1014
+ // accent is the next step darker and clears 4.5:1 on every light text surface.
1015
+ "--mantine-color-anchor": "var(--vx-accent-hover)",
1006
1016
  // `--app-shell-border-color` is set by `components.AppShell.extend({ vars })` above — this
1007
1017
  // entry was dead (an element-level declaration always beats an inherited `:root` value).
1008
1018
  // THE strict-surface lever. Mantine components do NOT chain border/surface colors through
@@ -1031,6 +1041,9 @@ function buildCssVariablesResolver(data) {
1031
1041
  "--mantine-color-default-border": "transparent",
1032
1042
  "--mantine-color-dimmed": `var(--vx-neutral, ${NEUTRAL.neutral.dark})`,
1033
1043
  "--mantine-color-text": `var(--vx-ink, ${INK.ink.dark})`,
1044
+ // Field errors: Mantine's own error red measures 1.98:1 (dark) / 4.37:1 (light). The solid status red
1045
+ // pulled toward ink clears AA on every text surface in both schemes.
1046
+ "--mantine-color-error": "color-mix(in srgb, var(--vx-bad-solid) 85%, var(--vx-ink))",
1034
1047
  // See the light block above — `--app-shell-border-color` is set by `AppShell.extend` now.
1035
1048
  // Dark components read `--mantine-color-dark-4` (border) raw. `buildDarkTuple` already sets
1036
1049
  // dark-4, but to a slightly lighter step than the hairline token, so layout borders read
@@ -13,6 +13,14 @@
13
13
  font-weight: 600;
14
14
  }
15
15
 
16
+ /*
17
+ * Inactive labels read ink2, not Mantine's gray-7 (4.0:1 on the light track): the active segment is
18
+ * told apart by the raised pill and weight 600, so the inactive text can stay at full AA contrast.
19
+ */
20
+ .label:not([data-active], [data-disabled]) {
21
+ color: var(--vx-ink2);
22
+ }
23
+
16
24
  /*
17
25
  * Mantine's own `.control` ships `flex: 1` with no `min-width` override, so its initial value
18
26
  * (`auto`) floors every option at its min-content width — a `fullWidth` track therefore still
@@ -1 +1 @@
1
- {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../src/tokens/derive.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;IAC/C;kGAC8F;IAC9F,UAAU,EAAE,MAAM,CAAA;IAClB;mDAC+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAA;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAEnE;AAuHD,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,KAAK,GACL,MAAM,GACN,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,QAAQ,CAAA;AA6DZ,MAAM,MAAM,UAAU,GAClB,IAAI,GACJ,OAAO,GACP,YAAY,GACZ,UAAU,GACV,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,UAAU,CAAA;AA2Bd,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAQvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAS/C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAA;IACvB,6EAA6E;IAC7E,iBAAiB,EAAE,MAAM,CAAA;IACzB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;8BAC8B;AAC9B,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,SAAS,CAAA;IACjB,WAAW,EAAE,SAAS,CAAA;IACtB,UAAU,EAAE,SAAS,CAAA;IACrB,eAAe,EAAE,SAAS,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IACnC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CACrC,CAAA;AAED;;8DAE8D;AAC9D,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAQjE;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,SAAS,CAAC,EAChD,MAAM,EAAE,YAAY,GACnB,cAAc,CA+GhB"}
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../src/tokens/derive.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;;;OAQG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;IAC/C;kGAC8F;IAC9F,UAAU,EAAE,MAAM,CAAA;IAClB;mDAC+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAA;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAEnE;AAuHD,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,KAAK,GACL,MAAM,GACN,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,QAAQ,GACR,QAAQ,CAAA;AA6DZ,MAAM,MAAM,UAAU,GAClB,IAAI,GACJ,OAAO,GACP,YAAY,GACZ,UAAU,GACV,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,UAAU,CAAA;AA2Bd,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;AAUvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAS/C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAA;IAClB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAA;IACvB,6EAA6E;IAC7E,iBAAiB,EAAE,MAAM,CAAA;IACzB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;8BAC8B;AAC9B,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,SAAS,CAAA;IACjB,WAAW,EAAE,SAAS,CAAA;IACtB,UAAU,EAAE,SAAS,CAAA;IACrB,eAAe,EAAE,SAAS,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IACnC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;IACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CACrC,CAAA;AAED;;8DAE8D;AAC9D,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAQjE;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,SAAS,CAAC,EAChD,MAAM,EAAE,YAAY,GACnB,cAAc,CA+GhB"}
@@ -127,7 +127,9 @@ const SURFACE_TONE = {
127
127
  }
128
128
  };
129
129
  const INK_TONE = {
130
- light: { ink: 15.16, ink2: 27.09, muted: 34.88, faint: 48.44 },
130
+ // light `faint` is tone 44.5 so tertiary text clears WCAG AA (4.5:1) on every text surface
131
+ // (bg / panel / subtle / field); 48.44 landed at 4.23–4.31:1 on bg and subtle.
132
+ light: { ink: 15.16, ink2: 27.09, muted: 34.88, faint: 44.5 },
131
133
  dark: { ink: 90.94, ink2: 88.12, muted: 84.91, faint: 66.24 }
132
134
  };
133
135
  const STATUS_HUE = { good: 151.975, warn: 76.64, bad: 29.905 };
package/dist/tokens.css CHANGED
@@ -291,7 +291,7 @@ html[data-mantine-color-scheme='light'] {
291
291
  --vx-ink: #262629;
292
292
  --vx-ink2: #404043;
293
293
  --vx-muted: #525255;
294
- --vx-faint: #737376;
294
+ --vx-faint: #69696c;
295
295
  --vx-accent: #4374a6;
296
296
  --vx-accent-hover: #3c6895;
297
297
  --vx-accent-fill: #4374a6;
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.40.1
1
+ # basalt-ui v1.40.3
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
3
3
  #
4
4
  # basalt-ui is an opinionated framework for Mantine-based React apps.
@@ -19,7 +19,7 @@
19
19
  # ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
20
20
 
21
21
  Package: basalt-ui
22
- Version: 1.40.1
22
+ Version: 1.40.3
23
23
 
24
24
  ## basalt-ui
25
25
  Import: import { ... } from 'basalt-ui'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "1.40.1",
3
+ "version": "1.40.3",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -66,7 +66,8 @@ export function HoverOverlay({
66
66
  'aria-orientation': 'horizontal' as const,
67
67
  'aria-valuemin': 0,
68
68
  ...(valueMax !== undefined && { 'aria-valuemax': valueMax }),
69
- ...(valueNow !== undefined && { 'aria-valuenow': valueNow }),
69
+ // `aria-valuenow` is required on a slider: 0 (the first point) until the cursor lands on one.
70
+ 'aria-valuenow': valueNow ?? 0,
70
71
  ...(valueText !== undefined && { 'aria-valuetext': valueText }),
71
72
  onKeyDown,
72
73
  ...(onBlur !== undefined && { onBlur }),
@@ -9,7 +9,7 @@
9
9
  * parseShortcut('Mod+S', false) // ['Ctrl', 'S']
10
10
  *
11
11
  * // Single display string for Spotlight description:
12
- * formatShortcutDisplay('Mod+S', true) // '⌘ S' on mac
12
+ * formatShortcutDisplay('Mod+S', true) // '⌘S' on mac
13
13
  * formatShortcutDisplay('Mod+S', false) // 'Ctrl+S' on windows
14
14
  */
15
15
 
@@ -30,19 +30,54 @@ export function detectMac(): boolean {
30
30
 
31
31
  // ── parseShortcut ─────────────────────────────────────────────────────────────
32
32
 
33
+ /** Named keys → [mac glyph, other-platform word]. Keys are the lower-cased shortcut token. */
34
+ const NAMED_KEYS: Record<string, readonly [mac: string, other: string]> = {
35
+ backspace: ['⌫', 'Backspace'],
36
+ delete: ['⌦', 'Delete'],
37
+ del: ['⌦', 'Delete'],
38
+ arrowleft: ['←', 'Left'],
39
+ arrowright: ['→', 'Right'],
40
+ arrowup: ['↑', 'Up'],
41
+ arrowdown: ['↓', 'Down'],
42
+ left: ['←', 'Left'],
43
+ right: ['→', 'Right'],
44
+ up: ['↑', 'Up'],
45
+ down: ['↓', 'Down'],
46
+ escape: ['⎋', 'Esc'],
47
+ esc: ['⎋', 'Esc'],
48
+ enter: ['↩', 'Enter'],
49
+ return: ['↩', 'Enter'],
50
+ tab: ['⇥', 'Tab'],
51
+ space: ['␣', 'Space'],
52
+ home: ['↖', 'Home'],
53
+ end: ['↘', 'End'],
54
+ pageup: ['⇞', 'PgUp'],
55
+ pagedown: ['⇟', 'PgDn'],
56
+ }
57
+
58
+ /** A key that is not a modifier or a named key: a single character upper-cases (`s` → `S`), any
59
+ * other word title-cases (`f5` → `F5`, `insert` → `Insert`). */
60
+ function plainKey(key: string): string {
61
+ return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)
62
+ }
63
+
33
64
  /**
34
65
  * Convert a shortcut string to a list of Kbd-friendly tokens.
35
66
  * 'Mod+S' → ['⌘', 'S'] (mac) or ['Ctrl', 'S'] (other).
36
67
  * 'Shift+Mod+P' → ['⇧', '⌘', 'P'] (mac) or ['Shift', 'Ctrl', 'P'] (other).
68
+ * Named keys read as glyphs on mac and Title-case words elsewhere:
69
+ * 'Mod+Backspace' → ['⌘', '⌫'] (mac) or ['Ctrl', 'Backspace'] (other).
37
70
  * isMac is the post-mount platform flag; defaults false on SSR / first render.
38
71
  *
39
72
  * @example
40
73
  * parseShortcut('Mod+S', true) // ['⌘', 'S']
41
74
  * parseShortcut('Shift+Mod+P', false) // ['Shift', 'Ctrl', 'P']
75
+ * parseShortcut('ArrowLeft', true) // ['←']
42
76
  */
43
77
  export function parseShortcut(shortcut: string, isMac: boolean): string[] {
44
78
  return shortcut.split('+').map((key) => {
45
- switch (key.toLowerCase()) {
79
+ const lower = key.toLowerCase()
80
+ switch (lower) {
46
81
  case 'mod':
47
82
  return isMac ? '⌘' : 'Ctrl'
48
83
  case 'shift':
@@ -52,9 +87,12 @@ export function parseShortcut(shortcut: string, isMac: boolean): string[] {
52
87
  case 'meta':
53
88
  return isMac ? '⌘' : 'Meta'
54
89
  case 'ctrl':
55
- return isMac ? '^' : 'Ctrl'
56
- default:
57
- return key.toUpperCase()
90
+ return isMac ? '⌃' : 'Ctrl'
91
+ default: {
92
+ const named = NAMED_KEYS[lower]
93
+ if (named !== undefined) return isMac ? named[0] : named[1]
94
+ return plainKey(key)
95
+ }
58
96
  }
59
97
  })
60
98
  }
@@ -63,7 +101,8 @@ export function parseShortcut(shortcut: string, isMac: boolean): string[] {
63
101
 
64
102
  /**
65
103
  * Format a shortcut string for inline display (e.g. Spotlight action description).
66
- * Returns a compact string like '⌘ S' (mac) or 'Ctrl+S' (windows).
104
+ * Returns a compact string like '⌘S' / '⌘⌫' (mac, glyphs with no separator) or 'Ctrl+S' /
105
+ * 'Ctrl+Backspace' (elsewhere, Title-case words joined with '+').
67
106
  *
68
107
  * isMac is the post-mount platform flag. Pass false on SSR / first client render.
69
108
  *
@@ -72,12 +111,12 @@ export function parseShortcut(shortcut: string, isMac: boolean): string[] {
72
111
  * always-on and produces the same tokens via parseShortcut.
73
112
  *
74
113
  * @example
75
- * formatShortcutDisplay('Mod+S', true) // '⌘ S'
114
+ * formatShortcutDisplay('Mod+S', true) // '⌘S'
115
+ * formatShortcutDisplay('Mod+Backspace', true) // '⌘⌫'
76
116
  * formatShortcutDisplay('Mod+S', false) // 'Ctrl+S'
77
- * formatShortcutDisplay('Shift+Mod+P', true) // '⇧ ⌘ P'
117
+ * formatShortcutDisplay('Shift+Mod+P', true) // '⇧⌘P'
78
118
  */
79
119
  export function formatShortcutDisplay(shortcut: string, isMac: boolean): string {
80
120
  const tokens = parseShortcut(shortcut, isMac)
81
- // Mac uses space-separated symbols (⌘ S); windows uses + (Ctrl+S)
82
- return isMac ? tokens.join(' ') : tokens.join('+')
121
+ return tokens.join(isMac ? '' : '+')
83
122
  }
@@ -205,9 +205,8 @@
205
205
  * bottom-placed block (a `kind: 'progress'` block defaults to `'bottom'`) stacked FLUSH on the
206
206
  * settings and account rows and on the scroll region above, which is the "cramped" footer.
207
207
  *
208
- * `region-gap` (12) above the cluster, `stack-sm` (8) between its parts — deliberately tighter
209
- * than the nav's `section-gap` (16): the footer is ONE pinned cluster whose rows belong together,
210
- * not a list of peer sections.
208
+ * `region-gap` (12) above the cluster, `stack-sm` (8) between BLOCKS, and the settings/account rows
209
+ * on the nav's own 1px pitch (see `.footerLinks + *` below): the footer rows are one list with the nav.
211
210
  */
212
211
  .footer {
213
212
  flex-shrink: 0;
@@ -223,6 +222,12 @@
223
222
  margin-top: var(--vx-space-stack-sm);
224
223
  }
225
224
 
225
+ /* Footer rows ARE a list like the nav's (`Stack gap={1}`): the settings rows and the account row
226
+ * sit on the nav's 1px pitch, not the cluster gap that separates blocks from rows. */
227
+ .footer > .footerLinks + * {
228
+ margin-top: 1px;
229
+ }
230
+
226
231
  /*
227
232
  * Footer settings row — IDENTICAL geometry to a nav row (padding 6px 10px, radius 6, gap 10,
228
233
  * 15px): muted text + faint icon at rest, ink-6% tint + ink on hover, exactly like an inactive
@@ -237,6 +242,9 @@
237
242
  padding: var(--vx-space-row-inset-y) var(--vx-space-row-inset-x);
238
243
  border-radius: var(--vx-radius-ctrl);
239
244
  font-size: var(--vx-text-md);
245
+ /* The nav row's line-height, so a footer row is exactly as tall as a nav row (Mantine's `Text`
246
+ * would otherwise bring its own 1.55 and add ~3px). */
247
+ line-height: var(--vx-space-row-line-height);
240
248
  color: var(--vx-muted);
241
249
  }
242
250
 
@@ -272,6 +280,7 @@
272
280
  .footerText {
273
281
  flex: 1;
274
282
  text-align: left;
283
+ line-height: var(--vx-space-row-line-height);
275
284
  }
276
285
 
277
286
  /*
@@ -296,6 +305,7 @@
296
305
  flex: 1;
297
306
  min-width: 0;
298
307
  text-align: left;
308
+ line-height: var(--vx-space-row-line-height);
299
309
  }
300
310
 
301
311
  /*
@@ -584,6 +594,7 @@
584
594
  .footerLinks {
585
595
  display: flex;
586
596
  flex-direction: column;
597
+ gap: 1px;
587
598
  }
588
599
 
589
600
  /* Fixed 18px icon column for a flat footer row — an icon-less row keeps the label aligned with
@@ -383,13 +383,20 @@ const basaltVariantColorResolver: VariantColorsResolver = (input) => {
383
383
  : resolved
384
384
  }
385
385
 
386
+ // `outline`: the tone's own text colour, pulled toward ink so the label keeps its hue at AA
387
+ // (grey outline badges measured 3.9:1 on the light page).
388
+ if (input.variant === 'outline') {
389
+ return { ...resolved, color: `color-mix(in srgb, ${resolved.color} 70%, var(--vx-ink))` }
390
+ }
391
+
386
392
  if (input.variant !== 'light') return resolved
387
393
  const ink = `var(--mantine-color-${colorName}-light-color)`
388
394
  return {
389
395
  ...resolved,
390
396
  background: `color-mix(in srgb, ${ink} 13%, transparent)`,
391
397
  hover: `color-mix(in srgb, ${ink} 20%, transparent)`,
392
- color: ink,
398
+ // Same pull toward ink as `outline`: the saturated tone text sat at 3.2–4.4:1 on its own tint.
399
+ color: `color-mix(in srgb, ${ink} 70%, var(--vx-ink))`,
393
400
  border: 'none',
394
401
  }
395
402
  }
@@ -1464,6 +1471,12 @@ function buildCssVariablesResolver(data: PaletteData): CSSVariablesResolver {
1464
1471
  '--mantine-color-default-border': 'transparent',
1465
1472
  '--mantine-color-dimmed': `var(--vx-neutral, ${NEUTRAL.neutral.light})`, // secondary / muted text
1466
1473
  '--mantine-color-text': `var(--vx-ink, ${INK.ink.light})`, // primary body/heading text
1474
+ // Field errors: Mantine's own error red measures 1.98:1 (dark) / 4.37:1 (light). The solid status red
1475
+ // pulled toward ink clears AA on every text surface in both schemes.
1476
+ '--mantine-color-error': 'color-mix(in srgb, var(--vx-bad-solid) 85%, var(--vx-ink))',
1477
+ // Links: Mantine reads the accent FILL shade (4.38:1 on the light page, under WCAG AA); the hover
1478
+ // accent is the next step darker and clears 4.5:1 on every light text surface.
1479
+ '--mantine-color-anchor': 'var(--vx-accent-hover)',
1467
1480
  // `--app-shell-border-color` is set by `components.AppShell.extend({ vars })` above — this
1468
1481
  // entry was dead (an element-level declaration always beats an inherited `:root` value).
1469
1482
  // THE strict-surface lever. Mantine components do NOT chain border/surface colors through
@@ -1492,6 +1505,9 @@ function buildCssVariablesResolver(data: PaletteData): CSSVariablesResolver {
1492
1505
  '--mantine-color-default-border': 'transparent',
1493
1506
  '--mantine-color-dimmed': `var(--vx-neutral, ${NEUTRAL.neutral.dark})`,
1494
1507
  '--mantine-color-text': `var(--vx-ink, ${INK.ink.dark})`,
1508
+ // Field errors: Mantine's own error red measures 1.98:1 (dark) / 4.37:1 (light). The solid status red
1509
+ // pulled toward ink clears AA on every text surface in both schemes.
1510
+ '--mantine-color-error': 'color-mix(in srgb, var(--vx-bad-solid) 85%, var(--vx-ink))',
1495
1511
  // See the light block above — `--app-shell-border-color` is set by `AppShell.extend` now.
1496
1512
  // Dark components read `--mantine-color-dark-4` (border) raw. `buildDarkTuple` already sets
1497
1513
  // dark-4, but to a slightly lighter step than the hairline token, so layout borders read
@@ -13,6 +13,14 @@
13
13
  font-weight: 600;
14
14
  }
15
15
 
16
+ /*
17
+ * Inactive labels read ink2, not Mantine's gray-7 (4.0:1 on the light track): the active segment is
18
+ * told apart by the raised pill and weight 600, so the inactive text can stay at full AA contrast.
19
+ */
20
+ .label:not([data-active], [data-disabled]) {
21
+ color: var(--vx-ink2);
22
+ }
23
+
16
24
  /*
17
25
  * Mantine's own `.control` ships `flex: 1` with no `min-width` override, so its initial value
18
26
  * (`auto`) floors every option at its min-content width — a `fullWidth` track therefore still
@@ -307,7 +307,9 @@ const SURFACE_TONE: Record<Side, Record<SurfaceKey, number>> = {
307
307
  export type InkKey = 'ink' | 'ink2' | 'muted' | 'faint'
308
308
 
309
309
  const INK_TONE: Record<Side, Record<InkKey, number>> = {
310
- light: { ink: 15.16, ink2: 27.09, muted: 34.88, faint: 48.44 },
310
+ // light `faint` is tone 44.5 so tertiary text clears WCAG AA (4.5:1) on every text surface
311
+ // (bg / panel / subtle / field); 48.44 landed at 4.23–4.31:1 on bg and subtle.
312
+ light: { ink: 15.16, ink2: 27.09, muted: 34.88, faint: 44.5 },
311
313
  dark: { ink: 90.94, ink2: 88.12, muted: 84.91, faint: 66.24 },
312
314
  }
313
315