basalt-ui 1.40.2 → 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,12 @@
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
+
1
10
  ## [1.40.2](https://github.com/jkrumm/basalt-ui/compare/v1.40.1...v1.40.2) (2026-10-05)
2
11
 
3
12
 
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 | 1.40.2 | 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,10 @@ 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
+
89
93
  ## 1.40.2
90
94
 
91
95
  _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,
@@ -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.2
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.2
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.2",
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
  }
@@ -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