basalt-ui 2.6.1 → 2.7.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.
- package/AGENTS.md +15 -15
- package/CHANGELOG.md +16 -0
- package/MIGRATING.md +38 -12
- package/agent/rules/basalt-batteries.md +1 -1
- package/agent/rules/basalt-controls.md +1 -1
- package/configs/oxlint-plugin.js +48 -0
- package/configs/oxlint-plugin.test.ts +82 -0
- package/dist/controls/form-layout.d.ts +15 -6
- package/dist/controls/form-layout.d.ts.map +1 -1
- package/dist/forms/field.d.ts +42 -2
- package/dist/forms/field.d.ts.map +1 -1
- package/dist/forms/field.js +2 -2
- package/dist/forms/index.d.ts +4 -5
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/table/data-table.d.ts +33 -0
- package/dist/table/data-table.d.ts.map +1 -1
- package/dist/table/data-table.js +100 -17
- package/llms.txt +237 -4
- package/package.json +1 -1
- package/src/controls/form-layout.tsx +15 -6
- package/src/forms/field.ts +78 -3
- package/src/forms/index.ts +4 -5
- package/src/surfaces.ts +1 -1
- package/src/table/data-table.tsx +171 -17
package/AGENTS.md
CHANGED
|
@@ -13,21 +13,21 @@ Everything else is internal: `./guard`, `./theme-lab`, `./data/virtual`, `./toke
|
|
|
13
13
|
and `./package.json` are **not exports** (`llms.txt` still ships in the tarball as a file;
|
|
14
14
|
`basalt-ui tokens:css` emits the token stylesheet).
|
|
15
15
|
|
|
16
|
-
| Subpath | Layer | Purpose
|
|
17
|
-
| -------------------------- | --------------- |
|
|
18
|
-
| `basalt-ui` | mantine-coupled | Wiring (`BasaltProvider`, `createBasaltTheme`, `createBasaltRouter`, `unwrap`), shell + nav (`BasaltShell`, `PageBar`, `PageAside`, `PageTitle`, `ThemeToggle`, `defineNav`/`navGroup`/`navTarget`/`flattenNav`, `useBasaltNav`, `useSizeClass`, `useCoarsePointer`), dashboard (`Section`, `WidgetHeader`, `StatCard`, `StatGroup`, `DeltaBadge`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`). Reaches no subpath-required peer.
|
|
19
|
-
| `basalt-ui/charts` | headless | CartesianChart (the TanStack Charts rung), the Lines and Bars kinds, ChartCard and the shared chartCursor (Mantine-free; @tanstack/charts + d3-scale peers)
|
|
20
|
-
| `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free)
|
|
21
|
-
| `basalt-ui/vite` | build-tooling | basaltViteConfig(opts) — Vite preset for basalt-ui consumer apps; basaltAppPlugin(opts) — PWA head, manifest, and icon metadata derived from the token palette
|
|
22
|
-
| `basalt-ui/format` | headless | integer/money/percent/durationClock/relativeTime/weekday/km/kcal — Intl-backed, Mantine-free, React-free formatting; the chart layer formats through the same implementation
|
|
23
|
-
| `basalt-ui/forms` | mantine-coupled | Mantine form layer: useBasaltForm + inputProps/fieldKey (two calls — a spread `key` is a React 19 warning), on @mantine/form
|
|
24
|
-
| `basalt-ui/commands` | mantine-coupled | defineCommands (typed command bus), defineOverlays/overlays, setColorScheme, toggleSidebar, ShortcutsHelp, defineNotifications/emit/NotificationBell (folded in from ./notifications); BasaltProvider mounts Spotlight, Modals and Notifications
|
|
25
|
-
| `basalt-ui/table` | mantine-coupled | BasaltDataTable and createColumnHelper: a data table over TanStack Table v9, rendered with Mantine (renamed from ./data/table)
|
|
26
|
-
| `basalt-ui/agent` | mantine-coupled | Streaming-chat surface: aiSdkTransport, useAgentThreadRuns, parseAgentPart, the open part-registry types, the consumer-owned thread contracts (AgentThread/ThreadsStore/ChatMessage), and the Mantine chat UI folded in from ./agent-chat (ThreadFeed, ThreadFeedRow, Composer, ToolChip). motion is subpath-required: the chat UI imports it eagerly
|
|
27
|
-
| `basalt-ui/content` | mantine-coupled | Prose (article/chat typography), Callout, TableOfContents, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; `fenceRenderers` extends fenced-block rendering — settledOnly/FenceRenderer/FenceRenderers; `sanitizeSchema` is an additions-only SanitizeSchemaExtension merged over the default via mergeSanitizeSchema; shiki code blocks, beautiful-mermaid diagrams and the remend streaming repair load lazily from optional peers), ArticleLayout (docs-page frame), GuideLink (contextual-help link) — the content/prose surface
|
|
28
|
-
| `basalt-ui/controls` | mantine-coupled | The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter
|
|
29
|
-
| `basalt-ui/controls-dates` | mantine-coupled | DateRangePicker — the @mantine/dates implementation of RangeFilter's customPicker seam. Its own subpath because @mantine/dates is an optional peer and basaltViteConfig pre-bundles the whole @mantine scope, so a consumer without the peer (linewatch) must never resolve it: nothing under src/controls may import it, statically or lazily (docs/CONTROLS-SPEC.md §3).
|
|
30
|
-
| `basalt-ui/state` | headless | field.enum/multi/range/number/boolean/string (the store field vocabulary), createSearchStore (typed URL > localStorage > fallback store), createLocalStore (the router-free store), createPersistedState (versioned localStorage)
|
|
16
|
+
| Subpath | Layer | Purpose |
|
|
17
|
+
| -------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `basalt-ui` | mantine-coupled | Wiring (`BasaltProvider`, `createBasaltTheme`, `createBasaltRouter`, `unwrap`), shell + nav (`BasaltShell`, `PageBar`, `PageAside`, `PageTitle`, `ThemeToggle`, `defineNav`/`navGroup`/`navTarget`/`flattenNav`, `useBasaltNav`, `useSizeClass`, `useCoarsePointer`), dashboard (`Section`, `WidgetHeader`, `StatCard`, `StatGroup`, `DeltaBadge`, `EmptyState`, `SettingsSection`/`SettingsRow`/`DangerZone`). Reaches no subpath-required peer. |
|
|
19
|
+
| `basalt-ui/charts` | headless | CartesianChart (the TanStack Charts rung), the Lines and Bars kinds, ChartCard and the shared chartCursor (Mantine-free; @tanstack/charts + d3-scale peers) |
|
|
20
|
+
| `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free) |
|
|
21
|
+
| `basalt-ui/vite` | build-tooling | basaltViteConfig(opts) — Vite preset for basalt-ui consumer apps; basaltAppPlugin(opts) — PWA head, manifest, and icon metadata derived from the token palette |
|
|
22
|
+
| `basalt-ui/format` | headless | integer/money/percent/durationClock/relativeTime/weekday/km/kcal — Intl-backed, Mantine-free, React-free formatting; the chart layer formats through the same implementation |
|
|
23
|
+
| `basalt-ui/forms` | mantine-coupled | Mantine form layer: useBasaltForm + inputProps/fieldKey (two calls — a spread `key` is a React 19 warning), on @mantine/form |
|
|
24
|
+
| `basalt-ui/commands` | mantine-coupled | defineCommands (typed command bus), defineOverlays/overlays, setColorScheme, toggleSidebar, ShortcutsHelp, defineNotifications/emit/NotificationBell (folded in from ./notifications); BasaltProvider mounts Spotlight, Modals and Notifications |
|
|
25
|
+
| `basalt-ui/table` | mantine-coupled | BasaltDataTable and createColumnHelper: a data table over TanStack Table v9, rendered with Mantine (renamed from ./data/table) |
|
|
26
|
+
| `basalt-ui/agent` | mantine-coupled | Streaming-chat surface: aiSdkTransport, useAgentThreadRuns, parseAgentPart, the open part-registry types, the consumer-owned thread contracts (AgentThread/ThreadsStore/ChatMessage), and the Mantine chat UI folded in from ./agent-chat (ThreadFeed, ThreadFeedRow, Composer, ToolChip). motion is subpath-required: the chat UI imports it eagerly |
|
|
27
|
+
| `basalt-ui/content` | mantine-coupled | Prose (article/chat typography), Callout, TableOfContents, Markdown (react-markdown + remark-gfm, optional peers; `streaming` is a rendering mode ONLY — `contentTrust` is the independent security input, and any surface rendering agent/model output must pin `contentTrust="untrusted"`, the sole input to the image-origin allowlist; `fenceRenderers` extends fenced-block rendering — settledOnly/FenceRenderer/FenceRenderers; `sanitizeSchema` is an additions-only SanitizeSchemaExtension merged over the default via mergeSanitizeSchema; shiki code blocks, beautiful-mermaid diagrams and the remend streaming repair load lazily from optional peers), ArticleLayout (docs-page frame), GuideLink (contextual-help link) — the content/prose surface |
|
|
28
|
+
| `basalt-ui/controls` | mantine-coupled | The control tier (docs/CONTROLS-SPEC.md §3). Exports: `FilterSet` (nowrap row + measured +N fold + the mobile Filters (n) sheet), `RangeFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter` (each bound to a FieldHandle — no value/onChange/size, law C2/C5; `NumberFilter` is the field.number lane, a radio list over `options` or a stepper without), `ViewTabs`, `SyncButton` with the `BarAction`/`GlobalAction` types, and the form layout `FormRow`/`FormGroup` (moved here from ./forms in 1.36.0 — `FormRow` is law C1’s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop: pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3); the counts/max props on `MultiSelectFilter` render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates. |
|
|
29
|
+
| `basalt-ui/controls-dates` | mantine-coupled | DateRangePicker — the @mantine/dates implementation of RangeFilter's customPicker seam. Its own subpath because @mantine/dates is an optional peer and basaltViteConfig pre-bundles the whole @mantine scope, so a consumer without the peer (linewatch) must never resolve it: nothing under src/controls may import it, statically or lazily (docs/CONTROLS-SPEC.md §3). |
|
|
30
|
+
| `basalt-ui/state` | headless | field.enum/multi/range/number/boolean/string (the store field vocabulary), createSearchStore (typed URL > localStorage > fallback store), createLocalStore (the router-free store), createPersistedState (versioned localStorage) |
|
|
31
31
|
|
|
32
32
|
Also published, but not JS subpaths: `basalt-ui/styles.css` (the one stylesheet: `@layer mantine, basalt;`, then `@import`s of `@mantine/{core,notifications,spotlight}/styles.layer.css`, then basalt's own layer) and `basalt-ui/configs/*` (raw oxlint / tsconfig / lefthook / oxfmt presets).
|
|
33
33
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# [2.7.0](https://github.com/jkrumm/basalt-ui/compare/v2.6.1...v2.7.0) (2026-10-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* every export named in llms.txt and d.ts headers exists, with props ([7bc783f](https://github.com/jkrumm/basalt-ui/commit/7bc783f0c19330a3e2d22ae3eec5cbbfd8e75ec6))
|
|
7
|
+
* make the doc-name check fail closed and test the props block ([44c0f00](https://github.com/jkrumm/basalt-ui/commit/44c0f00f08efce122bca079358ad396dc5bbc976))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* a typed per-row action seam on BasaltDataTable ([23cc77b](https://github.com/jkrumm/basalt-ui/commit/23cc77be8b2581a8142dd1325912be7568d0b89e))
|
|
13
|
+
* checkbox and switch bindings and typed field errors in basalt-ui/forms ([0041e51](https://github.com/jkrumm/basalt-ui/commit/0041e511d8895fb6f14c856c772aad06240e8964))
|
|
14
|
+
* rowActions loading, per-row names and a sharper row-action lint ([1e0fa59](https://github.com/jkrumm/basalt-ui/commit/1e0fa59f7779dd6b58185bf7cf4451c2704d47fe))
|
|
15
|
+
* type the checkbox binding strictly and export InputProps ([a3538ab](https://github.com/jkrumm/basalt-ui/commit/a3538abeb028f8585c93c01c1d9aa50350a6eeaf))
|
|
16
|
+
|
|
1
17
|
## [2.6.1](https://github.com/jkrumm/basalt-ui/compare/v2.6.0...v2.6.1) (2026-10-15)
|
|
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 | 1.40.2 | 1.40.3 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | 2.0.3 | 2.0.4 | 2.0.5 | 2.1.0 | 2.2.0 | 2.3.0 | 2.4.0 | 2.5.0 | 2.6.0 | 2.6.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) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
|
|
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 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | 2.0.3 | 2.0.4 | 2.0.5 | 2.1.0 | 2.2.0 | 2.3.0 | 2.4.0 | 2.5.0 | 2.6.0 | 2.6.1 | 2.7.0 | 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) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
|
|
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
|
|
|
@@ -992,6 +992,32 @@ optional, subpath-required peers.
|
|
|
992
992
|
_Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
|
|
993
993
|
release renames it and opens a new one here._
|
|
994
994
|
|
|
995
|
+
## 2.7.0 — table row actions, checkbox bindings, typed field errors
|
|
996
|
+
|
|
997
|
+
- `BasaltDataTable` takes `rowActions` (argo friction row 48): typed data in `BarActionItem`'s
|
|
998
|
+
vocabulary — `{ key, label, icon?, onClick(row), danger?, disabled?(row), loading?(row) }`. The table appends a
|
|
999
|
+
trailing, end-pinned, header-less column (sr-only "Actions") and renders an icon button for one
|
|
1000
|
+
action with an `icon`, an overflow menu (`Row actions`) otherwise. A click never reaches
|
|
1001
|
+
`onRowActivate`. The column renders in the table body only; a `renderCard` card owns its own.
|
|
1002
|
+
A disabled or loading action never fires. Each trigger is named per row (`Row N actions`, or
|
|
1003
|
+
`rowLabel(row)` when given). Pass a stable `rowActions` reference (module constant or `useMemo`).
|
|
1004
|
+
`DataTableRowAction<T>` is exported beside `DataTableFacet` from the module; no value export is added.
|
|
1005
|
+
- `basalt/control-outside-home` now also flags a Mantine `Menu`/`ActionIcon` inside a column
|
|
1006
|
+
`cell`/`display` renderer, and the `basalt-controls` Homes rule names `rowActions`. Existing
|
|
1007
|
+
hand-rolled action cells will start to report: move them to `rowActions`, or waive with
|
|
1008
|
+
`theme-allow control-outside-home`.
|
|
1009
|
+
- On notice (adopt-or-delete): named consumers are argo's `strength-tracker/components/workouts-table.tsx`
|
|
1010
|
+
(Edit + Delete) and `body-composition/components/skinfold-history-table.tsx` (Delete); neither
|
|
1011
|
+
has migrated yet. Their delete icons pass `loading`, which `loading(row)` now models.
|
|
1012
|
+
- `basalt-ui/forms`: `inputProps(form, path, { type: 'checkbox' })` binds a boolean field to Mantine
|
|
1013
|
+
`Checkbox`/`Switch` (`defaultChecked` + `onChange` in the default uncontrolled mode, `checked` under
|
|
1014
|
+
`mode: 'controlled'`). It type-checks only on a literal path whose value is a boolean (dot paths,
|
|
1015
|
+
array indices, optional/nullable booleans included). Additive.
|
|
1016
|
+
- `inputProps(...).error` narrowed from `any` to `ReactNode` (the key is always present, `undefined`
|
|
1017
|
+
on a clean field). Read a field error there instead of `form.errors.x` (TS4111), and pass it to
|
|
1018
|
+
`FormRow error` for a raw control. Code that treated it as `any` (a string method call, a
|
|
1019
|
+
`Record` index) now needs a narrowing. The new `InputProps` type is exported from `basalt-ui/forms`.
|
|
1020
|
+
|
|
995
1021
|
## 2.6.1 — per-bar legend shades
|
|
996
1022
|
|
|
997
1023
|
- `Bars`: a legend swatch now wears the shade its bars draw. It samples `barOpacity(firstRow, key)`,
|
|
@@ -23,7 +23,7 @@ paths:
|
|
|
23
23
|
|
|
24
24
|
## Forms
|
|
25
25
|
|
|
26
|
-
- A field is two calls: `key={fieldKey(form, path)}` and `{...inputProps(form, path)}`, from `basalt-ui/forms`. [basalt/forms-field-key]
|
|
26
|
+
- A field is two calls: `key={fieldKey(form, path)}` and `{...inputProps(form, path)}`, from `basalt-ui/forms`. [basalt/forms-field-key] A boolean field (`Checkbox`/`Switch`) adds `{ type: 'checkbox' }`; read a field error as `inputProps(form, path).error`, never `form.errors.x` (TS4111), and hand it to `FormRow error` only for a raw control. [type inputProps]
|
|
27
27
|
- `useBasaltForm` takes a Standard Schema validator, not a `ZodSchema`. [type useBasaltForm]
|
|
28
28
|
|
|
29
29
|
## Table
|
|
@@ -21,7 +21,7 @@ A control has one home and takes its state from a store field; the home sets its
|
|
|
21
21
|
|
|
22
22
|
## Homes
|
|
23
23
|
|
|
24
|
-
- A control lives in exactly one home: a `PageBar` slot (`actions`, `sync`, `filters`, `filtersEnd`, `tabs`), a `Section` slot (`actions`, `tabs`), a `WidgetHeader`/`ChartCard`/`StatCard`/`BasaltDataTable` `actions` slot, or a form row (`SettingsRow.control`, `FormRow`, `FormGroup`). [basalt/control-outside-home]
|
|
24
|
+
- A control lives in exactly one home: a `PageBar` slot (`actions`, `sync`, `filters`, `filtersEnd`, `tabs`), a `Section` slot (`actions`, `tabs`), a `WidgetHeader`/`ChartCard`/`StatCard`/`BasaltDataTable` `actions` slot, or a form row (`SettingsRow.control`, `FormRow`, `FormGroup`). A per-row action is `BasaltDataTable rowActions` (typed data: `label`, `icon`, `onClick(row)`, `danger`, `disabled(row)`), never a `Menu`/`ActionIcon` in a column `cell`/`display` renderer. [basalt/control-outside-home]
|
|
25
25
|
- A raw Mantine selection control (`Select`, `SegmentedControl`, …) in a home slot is a filter without a `field`. [basalt/hand-rolled-filter] [check-theme raw-selection-control]
|
|
26
26
|
- A basalt filter outside a slot, `FilterSet`, `PageAside` or form row has no home. [basalt/bound-control-outside-home]
|
|
27
27
|
- `PageBar` `actions` holds at most 5 entries with exactly one `primary`. [type PageBar]
|
package/configs/oxlint-plugin.js
CHANGED
|
@@ -2527,6 +2527,41 @@ const CONTROL_OUTSIDE_HOME_MESSAGE =
|
|
|
2527
2527
|
'(Modal/Drawer/Popover/Menu) and a form (@mantine/form) are the declared non-homes and never ' +
|
|
2528
2528
|
`report. ${SHELL_LESS_HOME_HINT} (basalt/control-outside-home)`
|
|
2529
2529
|
|
|
2530
|
+
const ROW_ACTION_IN_CELL_MESSAGE =
|
|
2531
|
+
'A Mantine `Menu`/`ActionIcon` inside a column `cell`/`display` renderer is a per-row action with ' +
|
|
2532
|
+
'no home. Pass `BasaltDataTable rowActions={[{ key, label, icon, onClick: (row) => … }]}` — the ' +
|
|
2533
|
+
'table renders the trailing, pinned actions column, keyboard access and the no-row-activation ' +
|
|
2534
|
+
'guard itself. (basalt/control-outside-home)'
|
|
2535
|
+
|
|
2536
|
+
/** Tags that, in a column `cell` renderer, are a hand-rolled row action. */
|
|
2537
|
+
const ROW_ACTION_TAGS = new Set(['Menu', 'ActionIcon'])
|
|
2538
|
+
|
|
2539
|
+
/** An `ActionIcon` under a `Menu` is that menu's trigger: the `Menu` reports, once, for the pair. */
|
|
2540
|
+
const ROW_MENU_TAGS = new Set(['Menu'])
|
|
2541
|
+
|
|
2542
|
+
/**
|
|
2543
|
+
* True when `node` sits inside a function that is the value of a `cell:` property (a column def).
|
|
2544
|
+
* Only an INLINE renderer is seen: `cell: renderActions` (a reference to a function declared
|
|
2545
|
+
* elsewhere) is a cross-reference no single-file AST walk resolves — the same declared limit as
|
|
2546
|
+
* the rule's cross-file C1 gap.
|
|
2547
|
+
*/
|
|
2548
|
+
function insideCellRenderer(node) {
|
|
2549
|
+
let current = node.parent
|
|
2550
|
+
for (let depth = 0; current !== null && current !== undefined; depth++) {
|
|
2551
|
+
if (depth > ANCESTRY_MAX_DEPTH || current.type === 'Program') return false
|
|
2552
|
+
if (
|
|
2553
|
+
current.type === 'Property' &&
|
|
2554
|
+
(current.key?.name ?? current.key?.value) === 'cell' &&
|
|
2555
|
+
(current.value?.type === 'ArrowFunctionExpression' ||
|
|
2556
|
+
current.value?.type === 'FunctionExpression')
|
|
2557
|
+
) {
|
|
2558
|
+
return true
|
|
2559
|
+
}
|
|
2560
|
+
current = current.parent
|
|
2561
|
+
}
|
|
2562
|
+
return false
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2530
2565
|
/**
|
|
2531
2566
|
* The cross-file half of C1, and the one rule here that is openly a HEURISTIC: "this control has
|
|
2532
2567
|
* no home" is a claim about layout intent, which no AST can see. That is why it sat in grace from
|
|
@@ -2582,9 +2617,22 @@ const controlOutsideHome = {
|
|
|
2582
2617
|
name: node.name,
|
|
2583
2618
|
captured: slots.capture(node),
|
|
2584
2619
|
hosted: hasAncestorTag(node, CONTROL_HOST_TAGS),
|
|
2620
|
+
inCell: insideCellRenderer(node),
|
|
2621
|
+
inMenu: jsxTagName(node.name) !== 'Menu' && hasAncestorTag(node, ROW_MENU_TAGS),
|
|
2585
2622
|
})
|
|
2586
2623
|
},
|
|
2587
2624
|
'Program:exit'() {
|
|
2625
|
+
// Row actions first: a table file that imports `@mantine/form` or defines a basalt control
|
|
2626
|
+
// still hosts column cells, so neither file-level exemption applies to them. An already-homed
|
|
2627
|
+
// element (a form row, a menu dropdown, a slot) is still not a row action.
|
|
2628
|
+
for (const { node, name, captured, hosted, inCell, inMenu } of candidates) {
|
|
2629
|
+
if (!inCell || hosted || inMenu) continue
|
|
2630
|
+
const tag = resolveMantineTag(name, mantineImports)
|
|
2631
|
+
if (tag === undefined || !ROW_ACTION_TAGS.has(tag)) continue
|
|
2632
|
+
if (slots.resolve(captured) || slots.inSubtreeHome(captured)) continue
|
|
2633
|
+
if (hasThemeAllow(context, node, 'control-outside-home')) continue
|
|
2634
|
+
context.report({ node, message: ROW_ACTION_IN_CELL_MESSAGE })
|
|
2635
|
+
}
|
|
2588
2636
|
if (importsMantineForm || owner.isOwner()) return
|
|
2589
2637
|
for (const { node, name, captured, hosted } of candidates) {
|
|
2590
2638
|
const tag = resolveMantineTag(name, mantineImports)
|
|
@@ -1957,6 +1957,88 @@ describe('basalt/control-outside-home', () => {
|
|
|
1957
1957
|
expect(rules).toContain('control-outside-home')
|
|
1958
1958
|
})
|
|
1959
1959
|
|
|
1960
|
+
describe('a hand-rolled row action in a column cell', () => {
|
|
1961
|
+
const TABLE_IMPORT = `import { createColumnHelper } from 'basalt-ui/table'\n`
|
|
1962
|
+
const cell = (body: string) =>
|
|
1963
|
+
`${TABLE_IMPORT}${MANTINE_IMPORT}const col = createColumnHelper<Row>()\n` +
|
|
1964
|
+
`export const columns = [col.display({ id: 'a', cell: ({ row }) => ${body} })]\n`
|
|
1965
|
+
|
|
1966
|
+
it('flags a Mantine Menu in a col.display cell', () => {
|
|
1967
|
+
const { code, rules } = run(
|
|
1968
|
+
`import { Menu } from '@mantine/core'\n${cell('<Menu><Menu.Target><span /></Menu.Target></Menu>').replace(MANTINE_IMPORT, '')}`,
|
|
1969
|
+
)
|
|
1970
|
+
expect(code).toBe(1)
|
|
1971
|
+
expect(rules).toContain('control-outside-home')
|
|
1972
|
+
})
|
|
1973
|
+
|
|
1974
|
+
it('flags a Mantine ActionIcon in a cell renderer', () => {
|
|
1975
|
+
const { rules } = run(
|
|
1976
|
+
`import { ActionIcon } from '@mantine/core'\n${cell('<ActionIcon aria-label="x" />').replace(MANTINE_IMPORT, '')}`,
|
|
1977
|
+
)
|
|
1978
|
+
expect(rules).toContain('control-outside-home')
|
|
1979
|
+
})
|
|
1980
|
+
|
|
1981
|
+
it('does NOT flag a Menu outside a cell renderer', () => {
|
|
1982
|
+
const { rules } = run(
|
|
1983
|
+
`import { Menu } from '@mantine/core'\nexport const C = () => <Menu><Menu.Target><span /></Menu.Target></Menu>\n`,
|
|
1984
|
+
)
|
|
1985
|
+
expect(rules).not.toContain('control-outside-home')
|
|
1986
|
+
})
|
|
1987
|
+
|
|
1988
|
+
it('reports a hand-rolled Menu + ActionIcon trigger once, not twice', () => {
|
|
1989
|
+
const { output } = run(
|
|
1990
|
+
`import { ActionIcon, Menu } from '@mantine/core'\n${TABLE_IMPORT}const col = createColumnHelper<Row>()\n` +
|
|
1991
|
+
`export const columns = [col.display({ id: 'a', cell: () => (\n` +
|
|
1992
|
+
` <Menu><Menu.Target><ActionIcon aria-label="x" /></Menu.Target></Menu>\n) })]\n`,
|
|
1993
|
+
)
|
|
1994
|
+
expect(output.match(/\(basalt\/control-outside-home\)/g)).toHaveLength(1)
|
|
1995
|
+
})
|
|
1996
|
+
|
|
1997
|
+
it('does NOT flag an ActionIcon already homed in a form row inside a cell', () => {
|
|
1998
|
+
const { rules } = run(
|
|
1999
|
+
`import { ActionIcon } from '@mantine/core'\nimport { FormRow } from 'basalt-ui/controls'\n${TABLE_IMPORT}` +
|
|
2000
|
+
`const col = createColumnHelper<Row>()\nexport const columns = [col.display({ id: 'a', cell: () => (\n` +
|
|
2001
|
+
` <FormRow label="L"><ActionIcon aria-label="x" /></FormRow>\n) })]\n`,
|
|
2002
|
+
)
|
|
2003
|
+
expect(rules).not.toContain('control-outside-home')
|
|
2004
|
+
})
|
|
2005
|
+
|
|
2006
|
+
it('does NOT flag one under a Menu.Dropdown inside a cell', () => {
|
|
2007
|
+
const { rules } = run(
|
|
2008
|
+
`import { ActionIcon } from '@mantine/core'\n${TABLE_IMPORT}const col = createColumnHelper<Row>()\n` +
|
|
2009
|
+
`export const columns = [col.display({ id: 'a', cell: () => (\n` +
|
|
2010
|
+
` <Menu.Dropdown><ActionIcon aria-label="x" /></Menu.Dropdown>\n) })]\n`,
|
|
2011
|
+
)
|
|
2012
|
+
expect(rules).not.toContain('control-outside-home')
|
|
2013
|
+
})
|
|
2014
|
+
|
|
2015
|
+
it('still flags in a file that imports @mantine/form', () => {
|
|
2016
|
+
const { rules } = run(
|
|
2017
|
+
`import { useForm } from '@mantine/form'\nimport { ActionIcon } from '@mantine/core'\n${TABLE_IMPORT}` +
|
|
2018
|
+
`const col = createColumnHelper<Row>()\nexport const columns = [col.display({ id: 'a', cell: () => <ActionIcon aria-label="x" /> })]\n`,
|
|
2019
|
+
)
|
|
2020
|
+
expect(rules).toContain('control-outside-home')
|
|
2021
|
+
})
|
|
2022
|
+
|
|
2023
|
+
it('does not see a cell renderer passed by reference (declared single-file limit)', () => {
|
|
2024
|
+
const { rules } = run(
|
|
2025
|
+
`import { ActionIcon } from '@mantine/core'\n${TABLE_IMPORT}const col = createColumnHelper<Row>()\n` +
|
|
2026
|
+
`const renderActions = () => <ActionIcon aria-label="x" />\n` +
|
|
2027
|
+
`export const columns = [col.display({ id: 'a', cell: renderActions })]\n`,
|
|
2028
|
+
)
|
|
2029
|
+
expect(rules).not.toContain('control-outside-home')
|
|
2030
|
+
})
|
|
2031
|
+
|
|
2032
|
+
it('honours a theme-allow waiver', () => {
|
|
2033
|
+
const { rules } = run(
|
|
2034
|
+
`import { ActionIcon } from '@mantine/core'\n${TABLE_IMPORT}const col = createColumnHelper<Row>()\n` +
|
|
2035
|
+
`export const columns = [col.display({ id: 'a', cell: () => (\n` +
|
|
2036
|
+
` // theme-allow control-outside-home — a one-off\n <ActionIcon aria-label="x" />\n) })]\n`,
|
|
2037
|
+
)
|
|
2038
|
+
expect(rules).not.toContain('control-outside-home')
|
|
2039
|
+
})
|
|
2040
|
+
})
|
|
2041
|
+
|
|
1960
2042
|
it('does NOT flag one under a member-expression non-home (Menu.Dropdown)', () => {
|
|
1961
2043
|
const { rules } = run(
|
|
1962
2044
|
`${MANTINE_IMPORT}export const C = () => <Menu.Dropdown><Select data={[]} /></Menu.Dropdown>\n`,
|
|
@@ -42,9 +42,10 @@ export type FormRowProps = BasaltProps & SlotStylesProps<FormRowSlot> & {
|
|
|
42
42
|
hint?: string;
|
|
43
43
|
/**
|
|
44
44
|
* The field's error, rendered under the control. Falsy renders nothing. For a Mantine input
|
|
45
|
-
* wired through `inputProps(form, path)`, Mantine's own `Input.Wrapper` already renders
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* wired through `inputProps(form, path)`, Mantine's own `Input.Wrapper` already renders the
|
|
46
|
+
* field error — passing it here too would render it twice. Reserve this prop for children that
|
|
47
|
+
* render no error of their own (a raw input, a custom control) and read it typed:
|
|
48
|
+
* `error={inputProps(form, path).error}`.
|
|
48
49
|
*/
|
|
49
50
|
error?: ReactNode;
|
|
50
51
|
/** Marks the field required: an accent `*` for sighted readers, `(required)` for assistive tech. */
|
|
@@ -76,7 +77,7 @@ export type FormRowProps = BasaltProps & SlotStylesProps<FormRowSlot> & {
|
|
|
76
77
|
*
|
|
77
78
|
* @example
|
|
78
79
|
* // A raw control renders no error of its own — pass it explicitly.
|
|
79
|
-
* <FormRow label="Email" required error={form
|
|
80
|
+
* <FormRow label="Email" required error={inputProps(form, 'email').error}>
|
|
80
81
|
* <input value={form.values.email} onChange={(e) => form.setFieldValue('email', e.target.value)} />
|
|
81
82
|
* </FormRow>
|
|
82
83
|
*/
|
|
@@ -113,8 +114,16 @@ export type FormGroupProps = BasaltProps & {
|
|
|
113
114
|
*
|
|
114
115
|
* @example
|
|
115
116
|
* <FormGroup label="Notify me about" direction="row">
|
|
116
|
-
* <Checkbox
|
|
117
|
-
*
|
|
117
|
+
* <Checkbox
|
|
118
|
+
* key={fieldKey(form, 'onDeploy')}
|
|
119
|
+
* {...inputProps(form, 'onDeploy', { type: 'checkbox' })}
|
|
120
|
+
* label="Deploys"
|
|
121
|
+
* />
|
|
122
|
+
* <Switch
|
|
123
|
+
* key={fieldKey(form, 'onError')}
|
|
124
|
+
* {...inputProps(form, 'onError', { type: 'checkbox' })}
|
|
125
|
+
* label="Errors"
|
|
126
|
+
* />
|
|
118
127
|
* </FormGroup>
|
|
119
128
|
*/
|
|
120
129
|
export declare function FormGroup(props: FormGroupProps): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/controls/form-layout.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAyCxE,gEAAgE;AAChE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GACxC,eAAe,CAAC,eAAe,CAAC,GAChC,sBAAsB,GAAG;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAoBlD;AAID,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb
|
|
1
|
+
{"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/controls/form-layout.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAyCxE,gEAAgE;AAChE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GACxC,eAAe,CAAC,eAAe,CAAC,GAChC,sBAAsB,GAAG;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAoBlD;AAID,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAEtD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4FAA4F;IAC5F,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BA0B1C;AAID,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG;IACzC,qGAAqG;IACrG,KAAK,EAAE,MAAM,CAAA;IACb,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAsB9C;AAID,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C;;;;;;;;OAQG;IACH,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BAgBlD"}
|
package/dist/forms/field.d.ts
CHANGED
|
@@ -15,7 +15,28 @@
|
|
|
15
15
|
* which was the whole reason for the rename in the first place. Write
|
|
16
16
|
* `key={fieldKey(form, path)} {...inputProps(form, path)}`.
|
|
17
17
|
*/
|
|
18
|
-
import type { LooseKeys, UseFormReturnType } from '@mantine/form';
|
|
18
|
+
import type { GetInputPropsReturnType, LooseKeys, UseFormReturnType } from '@mantine/form';
|
|
19
|
+
import type { ReactNode } from 'react';
|
|
20
|
+
/**
|
|
21
|
+
* What `inputProps` returns: Mantine's shape with `error` typed (`any` upstream). `error` is always
|
|
22
|
+
* present — Mantine sets the key on every call, `undefined` on a clean field — so it is `ReactNode`,
|
|
23
|
+
* not optional. It is the one typed way to read a field error: `form.errors` is an index signature
|
|
24
|
+
* (dot access is TS4111), and a raw control inside `FormRow` takes
|
|
25
|
+
* `<FormRow error={inputProps(form, path).error}>`. A Mantine input spread with `inputProps` already
|
|
26
|
+
* renders it; passing it to `FormRow` as well renders it twice.
|
|
27
|
+
*/
|
|
28
|
+
export type InputProps = Omit<GetInputPropsReturnType, 'error'> & {
|
|
29
|
+
error: ReactNode;
|
|
30
|
+
};
|
|
31
|
+
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
32
|
+
type AllDigits<S extends string> = S extends `${Digit}${infer Rest}` ? Rest extends '' ? true : AllDigits<Rest> : false;
|
|
33
|
+
/** A canonical array index: digits only, no sign, no decimal point, no exponent, no leading zero. */
|
|
34
|
+
type IsIndex<S extends string> = S extends '0' ? true : S extends `0${string}` ? false : AllDigits<S>;
|
|
35
|
+
/** One path segment applied to a value; `never` when the segment does not exist there. */
|
|
36
|
+
type Child<T, K extends string> = NonNullable<T> extends infer N ? N extends readonly (infer V)[] ? IsIndex<K> extends true ? V : never : K extends keyof N ? N[K] : never : never;
|
|
37
|
+
type ValueAt<T, P extends string> = P extends `${infer Head}.${infer Rest}` ? ValueAt<Child<T, Head>, Rest> : Child<T, P>;
|
|
38
|
+
/** `Path` if the value at that dot path is a (possibly optional/nullable) boolean, else `never`. */
|
|
39
|
+
type BooleanAt<Values, Path extends string> = [ValueAt<Values, Path>] extends [never] ? never : [NonNullable<ValueAt<Values, Path>>] extends [boolean] ? Path : never;
|
|
19
40
|
/**
|
|
20
41
|
* The spread half: `form.getInputProps(path)`, and nothing else.
|
|
21
42
|
*
|
|
@@ -31,7 +52,25 @@ import type { LooseKeys, UseFormReturnType } from '@mantine/form';
|
|
|
31
52
|
* return <TextInput key={fieldKey(form, 'email')} {...inputProps(form, 'email')} label="Email" />
|
|
32
53
|
* }
|
|
33
54
|
*/
|
|
34
|
-
export declare function inputProps<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path):
|
|
55
|
+
export declare function inputProps<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path): InputProps;
|
|
56
|
+
/**
|
|
57
|
+
* The boolean overload: `{ type: 'checkbox' }` binds a boolean field to Mantine `Checkbox` and
|
|
58
|
+
* `Switch`. It returns `onChange` plus `defaultChecked` in `useBasaltForm`'s default `uncontrolled`
|
|
59
|
+
* mode, or `checked` under `mode: 'controlled'` (Mantine decides; there is no `value`).
|
|
60
|
+
*
|
|
61
|
+
* Only a path whose value is a boolean (optional/nullable included) type-checks; a string path is a
|
|
62
|
+
* type error, not a silently wrong binding. Two limits of that check:
|
|
63
|
+
* - The path must be a literal. A `string`-typed path is rejected here (use a literal, or the
|
|
64
|
+
* one-argument form), so the path argument does not autocomplete.
|
|
65
|
+
* - On a union parent, a key present on several members is accepted only when every member types it
|
|
66
|
+
* as boolean; array segments must be canonical indices (`flags.0`, never `flags.1.5` or `flags.01`).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* <Switch key={fieldKey(form, 'active')} {...inputProps(form, 'active', { type: 'checkbox' })} />
|
|
70
|
+
*/
|
|
71
|
+
export declare function inputProps<Values extends Record<string, unknown>, Path extends string>(form: UseFormReturnType<Values>, path: BooleanAt<Values, Path>, options: {
|
|
72
|
+
type: 'checkbox';
|
|
73
|
+
}): InputProps;
|
|
35
74
|
/**
|
|
36
75
|
* The key half: `form.key(path)`, as a JSX `key` attribute — never spread.
|
|
37
76
|
*
|
|
@@ -43,4 +82,5 @@ export declare function inputProps<Values extends Record<string, unknown>, Path
|
|
|
43
82
|
* <TextInput key={fieldKey(form, 'email')} {...inputProps(form, 'email')} />
|
|
44
83
|
*/
|
|
45
84
|
export declare function fieldKey<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path): string;
|
|
85
|
+
export {};
|
|
46
86
|
//# sourceMappingURL=field.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,uBAAuB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,uBAAuB,EAAE,OAAO,CAAC,GAAG;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAA;AAEtF,KAAK,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAEtE,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,KAAK,GAAG,MAAM,IAAI,EAAE,GAChE,IAAI,SAAS,EAAE,GACb,IAAI,GACJ,SAAS,CAAC,IAAI,CAAC,GACjB,KAAK,CAAA;AAET,qGAAqG;AACrG,KAAK,OAAO,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,GAC1C,IAAI,GACJ,CAAC,SAAS,IAAI,MAAM,EAAE,GACpB,KAAK,GACL,SAAS,CAAC,CAAC,CAAC,CAAA;AAElB,0FAA0F;AAC1F,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAC5B,WAAW,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GAC1B,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC5B,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,GACrB,CAAC,GACD,KAAK,GACP,CAAC,SAAS,MAAM,CAAC,GACf,CAAC,CAAC,CAAC,CAAC,GACJ,KAAK,GACT,KAAK,CAAA;AAEX,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,GACvE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,GAC7B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAEf,oGAAoG;AACpG,KAAK,SAAS,CAAC,MAAM,EAAE,IAAI,SAAS,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACjF,KAAK,GACL,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GACpD,IAAI,GACJ,KAAK,CAAA;AAIX;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS,SAAS,CAAC,MAAM,CAAC,EAC/F,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,IAAI,EAAE,IAAI,GACT,UAAU,CAAA;AACb;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS,MAAM,EACpF,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,EAC7B,OAAO,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAC5B,UAAU,CAAA;AAUb;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS,SAAS,CAAC,MAAM,CAAC,EAC7F,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,IAAI,EAAE,IAAI,GACT,MAAM,CAER"}
|
package/dist/forms/field.js
CHANGED
package/dist/forms/index.d.ts
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
* `basalt-ui`. Same reason `./charts` and `./tokens` stay off the root barrel, one optional peer
|
|
7
7
|
* over. Import everything below from `basalt-ui/forms`.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `basalt-ui/controls` (it needs no `@mantine/form`); `useForm`/`schemaResolver` come from
|
|
13
|
-
* `@mantine/form` directly. Both stopped being re-exported here in 1.39.0.
|
|
9
|
+
* One piece: the form (`useBasaltForm` + `inputProps`/`fieldKey`). The layout (FormRow /
|
|
10
|
+
* FormGroup) lives in `basalt-ui/controls` (it needs no `@mantine/form`); the form hook and
|
|
11
|
+
* schema resolvers come from `@mantine/form` directly.
|
|
14
12
|
*
|
|
15
13
|
* Optional peer: @mantine/form ^9.3.0 — install with: bun add @mantine/form
|
|
16
14
|
*/
|
|
17
15
|
export { useBasaltForm } from './create-form.js';
|
|
18
16
|
export { inputProps, fieldKey } from './field.js';
|
|
17
|
+
export type { InputProps } from './field.js';
|
|
19
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/surfaces.d.ts
CHANGED
|
@@ -297,7 +297,7 @@ export declare const SURFACES: {
|
|
|
297
297
|
readonly guardKinds: readonly ["raw-selection-control"];
|
|
298
298
|
readonly pluginRules: readonly ["hand-rolled-filter", "control-outside-home", "bound-control-outside-home", "control-size-literal", "responsive-twin"];
|
|
299
299
|
readonly advisoryLaws: readonly ["C1 as a cross-file law (a control placed in one file, its home declared in another)", "hand-rolled section headings (argo writes `<Text fw={600} size=\"sm\">` + children, which no AST heuristic matches without false positives)", "C11 — a table/list stating its count when it is not a BasaltDataTable", "C12 — one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"];
|
|
300
|
-
readonly description: "The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter
|
|
300
|
+
readonly description: "The control tier (docs/CONTROLS-SPEC.md §3). Exports: `FilterSet` (nowrap row + measured +N fold + the mobile Filters (n) sheet), `RangeFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter` (each bound to a FieldHandle — no value/onChange/size, law C2/C5; `NumberFilter` is the field.number lane, a radio list over `options` or a stepper without), `ViewTabs`, `SyncButton` with the `BarAction`/`GlobalAction` types, and the form layout `FormRow`/`FormGroup` (moved here from ./forms in 1.36.0 — `FormRow` is law C1’s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop: pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3); the counts/max props on `MultiSelectFilter` render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size=\"ctl\" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.";
|
|
301
301
|
readonly optionalPeers: readonly [];
|
|
302
302
|
readonly globs: {
|
|
303
303
|
readonly shipped: readonly [];
|
package/dist/surfaces.js
CHANGED
|
@@ -298,7 +298,7 @@ const SURFACES = {
|
|
|
298
298
|
"C11 \u2014 a table/list stating its count when it is not a BasaltDataTable",
|
|
299
299
|
"C12 \u2014 one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"
|
|
300
300
|
],
|
|
301
|
-
description: 'The control tier (docs/CONTROLS-SPEC.md \xA73): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter
|
|
301
|
+
description: 'The control tier (docs/CONTROLS-SPEC.md \xA73). Exports: `FilterSet` (nowrap row + measured +N fold + the mobile Filters (n) sheet), `RangeFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter` (each bound to a FieldHandle \u2014 no value/onChange/size, law C2/C5; `NumberFilter` is the field.number lane, a radio list over `options` or a stepper without), `ViewTabs`, `SyncButton` with the `BarAction`/`GlobalAction` types, and the form layout `FormRow`/`FormGroup` (moved here from ./forms in 1.36.0 \u2014 `FormRow` is law C1\u2019s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop: pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body \u2014 where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md \xA73); the counts/max props on `MultiSelectFilter` render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed \u2014 the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
|
|
302
302
|
optionalPeers: [],
|
|
303
303
|
// `@mantine/dates` is an optional peer basaltViteConfig pre-bundles the whole @mantine scope
|
|
304
304
|
// for — a consumer without the peer (linewatch) must never resolve it through this surface.
|