basalt-ui 1.32.0 → 1.32.1

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,14 @@
1
+ ## [1.32.1](https://github.com/jkrumm/basalt-ui/compare/v1.32.0...v1.32.1) (2026-09-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * align guard lanes on FormRow/FormGroup, drop basalt-main ([17e673d](https://github.com/jkrumm/basalt-ui/commit/17e673d421c3e2f93be01c2b8d24e10e56d5b71c))
7
+ * bottom-align FormGroup's row direction so a trailing button isn't stretched ([e5ca2ab](https://github.com/jkrumm/basalt-ui/commit/e5ca2ab5e433e4e3ef15f505913c672b4912f0e9))
8
+ * export the size- and container-class tables from basalt-ui/tokens ([14fb784](https://github.com/jkrumm/basalt-ui/commit/14fb784586c3348a3647280e74ea905b2df0091b))
9
+ * point every § Unreleased reference at the section the release names ([bc17710](https://github.com/jkrumm/basalt-ui/commit/bc1771001048f2883607d6917e6f94cc397865be))
10
+ * resolve consts before unwrapping raw-breakpoint conditionals ([4f9cc79](https://github.com/jkrumm/basalt-ui/commit/4f9cc7912ae0805eb5e574f8ed8a8f9c068e0ddf))
11
+
1
12
  # [1.32.0](https://github.com/jkrumm/basalt-ui/compare/v1.31.0...v1.32.0) (2026-09-30)
2
13
 
3
14
 
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 | 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` (§ Unreleased) | — | — |
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 (§ Unreleased) | — | — |
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 (§ Unreleased) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ Unreleased) | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — |
75
- | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | — |
76
- | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | — |
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 (§ Unreleased) | — |
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 | 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) | — |
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`) | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | — |
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) | — |
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) | — |
81
81
 
82
82
  ---
83
83
 
@@ -86,6 +86,114 @@ 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.32.1 — the 1.32.0 recipe resolves, and the gaps consumers hit upgrading to it
90
+
91
+ A patch: no new API beyond restoring what 1.31.0/1.32.0 already documented. Eight consumers
92
+ upgraded to 1.32.0; this section is what they had to find out by reading source.
93
+
94
+ ### Fixes you can now delete a workaround for
95
+
96
+ | What | Delete |
97
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
98
+ | **`SIZE_CLASSES`, `CONTAINER_CLASSES`, `resolveContainerClass`, `ContainerClass` are exported from `basalt-ui/tokens`.** The 1.31.0/1.32.0 sections, the `raw-breakpoint` message and the `BasaltDataTable` JSDoc all named that import; it did not resolve. A test now fails the build when a fenced import in this file names a symbol its subpath does not export. | the local `container-classes.ts` mirror or the literal `'480px'`/`800` — import them |
99
+ | **`FormRow` / `FormGroup` are control homes in `check-theme` too.** The oxlint plugin already treated them as homes; the text lane's `raw-selection-control` did not, so a correctly migrated form row warned (and would have errored at 1.33.0). A parity test now pins both lanes' home lists to one set. | every `theme-allow raw-selection-control — … FormRow/FormGroup …` waiver (grep `WORKAROUND: homed in a FormRow`) |
100
+ | **`basalt-main` is no longer an accepted `@container` name.** Nothing ever declared it, so a query against it never matched. Declare your own container (`container: <name> / inline-size` on the element) or use `basalt-grid` on the parent. | `@container basalt-main …` — rewrite against a container you declare |
101
+ | **The `raw-media-query` message no longer over-claims.** Only `basalt-card` and unnamed `@container` queries are judged against the 0/240/480/800 table; any other declared name is accepted at any width. Same behaviour, honest text. | — |
102
+ | **`raw-breakpoint` exempts a `tests?/` directory** (e.g. a `tests/setup/dom.ts` `matchMedia` polyfill), not only `*.test.*`/`__tests__`. The message now says `cols={{ base: 3 }}` is just `cols={3}`, and a `(hover: none)` read gets a pointer-tier hint. | the polyfill's `theme-allow raw-breakpoint` |
103
+ | **`FormGroup direction="row"` no longer stretches a trailing button** (`align-items: flex-end`). | a `style={{ alignSelf: … }}` on the button |
104
+
105
+ **This patch also TIGHTENS two checks — expect NEW warnings after upgrading, still `warn` until
106
+ 1.33.0:** `raw-breakpoint` now sees a responsive object inside a conditional, logical or
107
+ const-resolved prop value (`span={wide ? { base: 12, md: 4 } : 12}` used to slip through), and
108
+ `raw-media-query` rejects `@container basalt-main`. (The `FormRow`/`FormGroup` change only removes
109
+ warnings.)
110
+
111
+ **After any guard narrows, audit your waivers:** `basalt-ui check-theme --audit-allows` lists every
112
+ `theme-allow` and whether it still suppresses anything. Dead ones (rb carried five
113
+ `in-body-page-title`, linewatch one `bound-control-outside-home`) go.
114
+
115
+ **Scope — the breakpoint law is Mantine/CSS-scoped.** `raw-breakpoint` is an oxlint rule over JSX
116
+ and `raw-media-query` a `check-theme` kind over `.css`. A tokens-only (Tailwind) consumer has only
117
+ the `check-theme` lane; nothing here asks it to adopt `type="container"` grids.
118
+
119
+ ### Container recipes 1.32.0 left implicit
120
+
121
+ **Swap two children at a container width in ONE query** — narrow default, flip both sides inside
122
+ one `min-width` rule. A `max-width: 799.9px` / `min-width: 800px` pair leaves a sub-pixel band where
123
+ both or neither render.
124
+
125
+ ```css
126
+ .root {
127
+ container: basalt-grid / inline-size;
128
+ }
129
+ .wide {
130
+ display: none;
131
+ }
132
+ @container basalt-grid (min-width: 800px) {
133
+ .narrow {
134
+ display: none;
135
+ }
136
+ .wide {
137
+ display: block;
138
+ }
139
+ }
140
+ ```
141
+
142
+ **A plain Mantine `Table` (not `BasaltDataTable`)** has no column fold or card projection; the
143
+ sanctioned remedy is the same CSS module — declare the container on the wrapper and hide columns or
144
+ swap to a list inside a `min-width` query. `BasaltDataTable` needs the optional
145
+ `@tanstack/react-table` peer; don't add it only to get the fold.
146
+
147
+ **Layouts that need a step above `wide` (800px).** There is no fifth class; one ships when two named
148
+ consumers need the same width. Until then, verified against the 1.32.0 plugin:
149
+
150
+ | Where | Off-class width (e.g. `1200px`) |
151
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
152
+ | `SimpleGrid type="container"` px key — `cols={{ base: 1, '1200px': 5 }}` | passes: `raw-breakpoint` does not judge px keys, only theme names (`sm`) |
153
+ | `Grid type="container"` `breakpoints` map value — `xl: '1200px'` | passes: the map's values are not judged |
154
+ | `@container basalt-card` or unnamed, in CSS | flagged — `theme-allow raw-media-query — <why this layout needs 1200px>` |
155
+ | `@container <your-own-name>` or `basalt-grid`, in CSS | passes today; a later minor may judge `basalt-grid` — prefer a reasoned `theme-allow` over relying on the unjudged name |
156
+
157
+ Either way, write the reason next to the width: it is the evidence the fifth class is waiting on.
158
+
159
+ **`ResponsiveChartHeight` `sm` (768px) → which key.** `wide` (800) is 32px later and keeps the old
160
+ step's meaning ("tablet or wider"); `regular` (480) opens 288px earlier. Pick by which side of the old
161
+ width the height was for.
162
+
163
+ ### `BasaltDataTable` `renderCard` (1.32.0, undocumented here until now)
164
+
165
+ The law lives in the prop's JSDoc (`src/data/data-table.tsx`, shipped in `dist/data/table.d.ts`).
166
+ The points consumers tripped on:
167
+
168
+ - Swaps at the table's OWN width `< CONTAINER_CLASSES.regular` (480px), not the viewport.
169
+ - Card order is the table's processed order — sort, filter, page. For a domain order, pass
170
+ `initialSorting` or sort `data`; there is no second sort.
171
+ - `minWidth` disables the COLUMN fold (it declares a horizontal floor) and is ignored by the card
172
+ list. `stickyHeader` alone keeps the fold; pair it with `maxHeight` to stick at every width.
173
+ - Activation is click or **Enter** — not Space (the browser's page scroll on a focused non-button).
174
+
175
+ ### Shell-less apps and the 1.31.0 breakpoint move
176
+
177
+ 1.31.0 moved `sm` from 48em to **52.5em** (840px). What its checklist missed:
178
+
179
+ - **Re-key your own literals:** grep `.css` for `47.99375em` / `48em` → `52.49375em` / `52.5em`.
180
+ - **A shell-less app's gutter / sticky-bar CSS belongs under your own `shell/` directory** at
181
+ 52.5em: any `shell/`-pathed CSS may write a width `@media` at a `SIZE_CLASSES` boundary; anywhere
182
+ else it is `raw-media-query`.
183
+ - **A consumer-owned Mantine `AppShell`** resolves `navbar.breakpoint` / `aside.breakpoint` through
184
+ `theme.breakpoints`, so `breakpoint: 'sm'` collapses at 840px now, not 768px.
185
+ - **In the `medium` class the sidebar defaults to the rail**, and the rail hides a `kind: 'custom'`
186
+ block entirely (a list block keeps a dot only with both `icon` and `count`). Anything a
187
+ `medium`-width user needs must not live only in a custom block.
188
+ - **`PageBar` without a shell:** see the shell-less example in its JSDoc — `title` leads the one
189
+ sticky bar, and `classNames.root` is where the gutter bleed goes.
190
+
191
+ ### `useSizeClass()` outside the shell — two sanctioned uses
192
+
193
+ Layout stays a container decision; an overlay's form (`Drawer` side, sheet vs dialog) and content
194
+ paired with a `ViewTabs` option `only: 'sm-down'` may read it. Law and wording:
195
+ `agent/rules/basalt-mantine.md` (a shipped rule, placed by `basalt-ui sync`).
196
+
89
197
  ## 1.32.0 — `basalt/raw-breakpoint` gets its answer
90
198
 
91
199
  ### The C1 pair (`control-outside-home` / `raw-selection-control`) — `error` at 1.33.0, the last extension
@@ -105,7 +213,7 @@ minor with a documented answer). A `theme-allow raw-breakpoint — <reason>` wai
105
213
 
106
214
  **Its CSS twin, the `raw-media-query` guard kind** (`basalt-ui check-theme`), enforces the same law
107
215
  in `.css`: a width `@media` outside `shell/**`, an `@container` name other than `basalt-card` /
108
- `basalt-form` / `basalt-main` / `basalt-grid` or at a width off a `CONTAINER_CLASSES` boundary, and
216
+ `basalt-form` / `basalt-grid` (`basalt-main` was listed here too; dropped in 1.32.1, nothing declared it) or at a width off a `CONTAINER_CLASSES` boundary, and
109
217
  a pointer query other than `coarse`/`fine`. Same `warn`, and it moved with the rule — **`error` at
110
218
  1.33.0** (was 1.32.0). `theme-allow raw-media-query — <reason>` waives one line.
111
219
 
@@ -406,7 +514,7 @@ exactly the case it was restored for. Measured: a 7-entry legend in a 92–150px
406
514
  entries at `maxRows` 3, 6 **and** 99, with five series drawn in colours nothing named.
407
515
 
408
516
  The fit now resolves an ABSENT cap only. When a caller states a number on a `fill` band, the legend
409
- takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § Unreleased) and
517
+ takes the ENTRIES it asked for (`maxRows` counts entries, not rows — see § 1.30.1) and
410
518
  **`VX.minPlotHeight` stops being a floor** — the plot yields the height instead. Both things that used to trim the number are framework DEFAULTS (the tier's cap,
411
519
  and a floor about the plot); the stated number is the only intent in the room.
412
520
 
@@ -571,7 +679,7 @@ children is invisible to every rule. Grep for `SimpleGrid` before concluding the
571
679
  ### Charts — a responsive `height`, and the legend escape 1.30.0 promised
572
680
 
573
681
  Both of these landed in 1.30.0 with no row here, and both were only partly reachable. **Read this
574
- section against 1.30.1, not 1.30.0** — the § Unreleased entries above are what makes them usable.
682
+ section against 1.30.1, not 1.30.0** — the § 1.30.1 entries above are what makes them usable.
575
683
 
576
684
  - **`height` accepts `{ base, sm?, md?, lg? }`** as well as a plain number, resolved against the
577
685
  chart's MEASURED container width (the same width the tier reads), not the viewport. In 1.30.0 the
@@ -583,7 +691,7 @@ section against 1.30.1, not 1.30.0** — the § Unreleased entries above are wha
583
691
  measured box, so a 380px inspector panel on a 1440px desktop IS "a phone" and its two-row default
584
692
  rolled a six-series legend up to `+4 more` with no way to say no. `margin` and `xLabelRotate`
585
693
  always had that escape; the legend did not. Under `fill` a measured cap still applies on top — see
586
- § Unreleased for what that actually costs, because "can still roll up FURTHER" understated it.
694
+ § 1.30.1 for what that actually costs, because "can still roll up FURTHER" understated it.
587
695
 
588
696
  ### Toolchain — the shipped lefthook preset's `check-theme`, and what it does NOT fix
589
697
 
@@ -97,8 +97,8 @@ Depth-tier law (which token for which surface, why static, why the ring is inset
97
97
  clears the iOS zoom threshold via an `!important` floor (`sub-16-input-font` flags a smaller one).
98
98
  - **Charts measure themselves** — pass `height`/`aspectRatio`/`fill` only; never `useElementSize`
99
99
  in a chart file (Mantine is banned there), never raw `@visx/responsive` outside `charts/**`.
100
- - **A page's own layout keys on its container, not the viewport** — Mantine's `type="container"`
101
- grids on `CONTAINER_CLASSES`; `useSizeClass()` is shell chrome only. Recipes: `MIGRATING.md`.
100
+ - **A page's layout keys on its container, not the viewport** (`type="container"` grids, `CONTAINER_CLASSES`).
101
+ `useSizeClass()`: shell chrome, overlay form, `ViewTabs only: 'sm-down'` partners. Recipes: `MIGRATING.md`.
102
102
 
103
103
  ## Scroll regions
104
104
 
@@ -390,8 +390,12 @@ function hasFileDeclaration(context, ruleId) {
390
390
  )
391
391
  }
392
392
 
393
- /** Test/spec files — design guidance does not apply to a fixture. Mirrors `src/cli`'s own SKIP. */
394
- const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])__tests__[\\/]/
393
+ /**
394
+ * Test/spec files — design guidance does not apply to a fixture. Mirrors `src/cli`'s own SKIP, plus
395
+ * a `test/`/`tests/` directory (1.32.1): a test-setup polyfill (`tests/setup/dom.ts` stubbing
396
+ * `window.matchMedia`) is neither a `.test` file nor under `__tests__`, and needed a waiver.
397
+ */
398
+ const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])(?:__tests__|tests?)[\\/]/
395
399
 
396
400
  function isTestFile(context) {
397
401
  return TEST_FILE.test(getFilename(context))
@@ -3972,26 +3976,57 @@ function isResponsiveObjectValue(node, namedOnly = false) {
3972
3976
  return !namedOnly || names.some((name) => name !== 'base')
3973
3977
  }
3974
3978
 
3979
+ /** The `const` initializers {@link reachableObjects} can resolve an Identifier to. */
3980
+ const RESOLVABLE_INIT_TYPES = new Set([
3981
+ 'ObjectExpression',
3982
+ 'ConditionalExpression',
3983
+ 'LogicalExpression',
3984
+ 'Identifier',
3985
+ ])
3986
+
3975
3987
  /**
3976
- * Does this prop value CONTAIN a responsive object — itself, nested one level down in a property
3977
- * (`footer={{ height: { base: 60, sm: 0 } }}`, Mantine's own AppShell idiom), or behind a same-file
3978
- * `const` binding at either place (`const h = { base: 60, sm: 0 }`, then `footer={{ height: h }}`)?
3979
- * `consts` is the same-file name→object map, so an Identifier resolves identically wherever it
3980
- * appears — the top-level attribute value and a nested property value alike. Matching only a nested
3981
- * `ObjectExpression` left the Identifier-in-a-property shape trivially bypassable.
3988
+ * Every object literal a value can evaluate to: an Identifier resolves through `consts` (the
3989
+ * same-file name→init map) FIRST, then a conditional contributes both branches and a logical both
3990
+ * sides — in any nesting, so `const span = wide ? { base: 12, md: 4 } : 12` then `span={span}` is
3991
+ * the same shape as writing the conditional inline (rb's hero Grid was a false negative until
3992
+ * 1.32.1). `seen` guards an Identifier cycle (`const a = b`, `const b = a`) and a shared object.
3982
3993
  */
3983
- function containsResponsiveObjectValue(node, consts, namedOnly = false, seen = new Set()) {
3984
- const resolved =
3985
- node !== null && node !== undefined && node.type === 'Identifier' ? consts.get(node.name) : node
3986
- if (resolved === null || resolved === undefined || resolved.type !== 'ObjectExpression')
3987
- return false
3988
- if (seen.has(resolved)) return false
3994
+ function reachableObjects(node, consts, seen = new Set()) {
3995
+ const resolved = node?.type === 'Identifier' ? consts.get(node.name) : node
3996
+ if (resolved === null || resolved === undefined || seen.has(resolved)) return []
3989
3997
  seen.add(resolved)
3990
- if (isResponsiveObjectValue(resolved, namedOnly)) return true
3991
- return resolved.properties.some(
3992
- (prop) =>
3993
- prop.type === 'Property' &&
3994
- containsResponsiveObjectValue(prop.value, consts, namedOnly, seen),
3998
+ switch (resolved.type) {
3999
+ case 'ObjectExpression':
4000
+ return [resolved]
4001
+ case 'Identifier':
4002
+ return reachableObjects(resolved, consts, seen)
4003
+ case 'ConditionalExpression':
4004
+ return [resolved.consequent, resolved.alternate].flatMap((branch) =>
4005
+ reachableObjects(branch, consts, seen),
4006
+ )
4007
+ case 'LogicalExpression':
4008
+ return [resolved.left, resolved.right].flatMap((side) => reachableObjects(side, consts, seen))
4009
+ default:
4010
+ return []
4011
+ }
4012
+ }
4013
+
4014
+ /**
4015
+ * Does this prop value CONTAIN a responsive object — any object it can evaluate to
4016
+ * ({@link reachableObjects}), or one nested one level down in a property of one
4017
+ * (`footer={{ height: { base: 60, sm: 0 } }}`, Mantine's own AppShell idiom), the property value
4018
+ * resolving the same way. Matching only a nested `ObjectExpression` left the Identifier-in-a-
4019
+ * property shape trivially bypassable.
4020
+ */
4021
+ function containsResponsiveObjectValue(node, consts, namedOnly = false, seen = new Set()) {
4022
+ return reachableObjects(node, consts, seen).some(
4023
+ (object) =>
4024
+ isResponsiveObjectValue(object, namedOnly) ||
4025
+ object.properties.some(
4026
+ (prop) =>
4027
+ prop.type === 'Property' &&
4028
+ containsResponsiveObjectValue(prop.value, consts, namedOnly, seen),
4029
+ ),
3995
4030
  )
3996
4031
  }
3997
4032
 
@@ -4108,6 +4143,33 @@ const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
4108
4143
  'Responsive-object prop keyed on theme breakpoints — those are viewport widths. ' +
4109
4144
  `${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4110
4145
 
4146
+ /** rb r2 #5: a `(hover: none)` read was flagged with advice that only covered widths. */
4147
+ const RAW_BREAKPOINT_POINTER_HINT =
4148
+ 'A hover/pointer capability read belongs in CSS: @media (pointer: coarse) in a module (the ' +
4149
+ 'query --vx-hit keys on) — (hover: none) is not a sanctioned query.'
4150
+
4151
+ const RAW_BREAKPOINT_BASE_ONLY_MESSAGE =
4152
+ 'Responsive-object prop with only a base key — there is no breakpoint in it at all, so drop the ' +
4153
+ 'object and write the value: cols={{ base: 3 }} → cols={3}. (basalt/raw-breakpoint)'
4154
+
4155
+ /**
4156
+ * Is every responsive object this value can evaluate to ({@link reachableObjects} — the same
4157
+ * unwrap detection uses) one carrying ONLY `base`? That is no breakpoint decision, and the viewport
4158
+ * recipe is the wrong advice for it — the fix is the bare value. Message-only: the shape still
4159
+ * reports, as it always has.
4160
+ */
4161
+ function isBaseOnlyResponsiveObject(node, consts) {
4162
+ const responsive = reachableObjects(node, consts).filter((object) =>
4163
+ isResponsiveObjectValue(object),
4164
+ )
4165
+ return (
4166
+ responsive.length > 0 &&
4167
+ responsive.every((object) =>
4168
+ object.properties.every((prop) => propertyKeyName(prop) === 'base'),
4169
+ )
4170
+ )
4171
+ }
4172
+
4111
4173
  const RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE =
4112
4174
  'Theme breakpoint name under type="container" — Mantine does not resolve it there: ' +
4113
4175
  "SimpleGrid uses each key verbatim as a (min-width: …) length, so 'sm' is a dead query, " +
@@ -4121,11 +4183,12 @@ const RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE =
4121
4183
  const RAW_BREAKPOINT_HOOK_MESSAGE =
4122
4184
  "useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's " +
4123
4185
  'one seam. Shell chrome reads useSizeClass(); a page swapping its own layout (a table for a card ' +
4124
- `list, a column count) is a container decision. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4186
+ `list, a column count) is a container decision. ${RAW_BREAKPOINT_POINTER_HINT} ` +
4187
+ `${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4125
4188
 
4126
4189
  const RAW_BREAKPOINT_GLOBAL_MESSAGE =
4127
4190
  'window.matchMedia/window.innerWidth read directly — the viewport read useSizeClass() owns ' +
4128
- `for shell chrome. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4191
+ `for shell chrome. ${RAW_BREAKPOINT_POINTER_HINT} ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
4129
4192
 
4130
4193
  /** The three viewport hooks the law names, whatever module they are imported from. */
4131
4194
  const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useViewportSize'])
@@ -4168,8 +4231,9 @@ const rawBreakpoint = {
4168
4231
  let definesShellHome = false
4169
4232
  const visibleHiddenCandidates = []
4170
4233
  const responsiveObjectCandidates = []
4171
- // Same-file `const NAME = { base, sm }` bindings, resolved when a JSX attribute or a nested
4172
- // property passes the Identifier — no scope manager here, so this is a flat name→object map.
4234
+ // Same-file `const NAME = { base, sm }` bindings (or a conditional/logical/alias init, see
4235
+ // RESOLVABLE_INIT_TYPES), resolved when a JSX attribute or a nested property passes the
4236
+ // Identifier — no scope manager here, so this is a flat name→init map.
4173
4237
  // A name declared more than once in the file is ambiguous (its two bindings cannot be told
4174
4238
  // apart), so it is dropped from the map entirely rather than guessed at last-wins.
4175
4239
  const responsiveObjectConsts = new Map()
@@ -4217,7 +4281,7 @@ const rawBreakpoint = {
4217
4281
  return
4218
4282
  }
4219
4283
  seenDeclaratorNames.add(name)
4220
- if (node.init?.type === 'ObjectExpression') responsiveObjectConsts.set(name, node.init)
4284
+ if (RESOLVABLE_INIT_TYPES.has(node.init?.type)) responsiveObjectConsts.set(name, node.init)
4221
4285
  },
4222
4286
  JSXAttribute(node) {
4223
4287
  const name = node.name?.name
@@ -4264,10 +4328,10 @@ const rawBreakpoint = {
4264
4328
  if (!containsResponsiveObjectValue(value, responsiveObjectConsts, mode !== undefined))
4265
4329
  continue
4266
4330
  if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
4267
- const message =
4268
- mode === undefined
4269
- ? RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE
4270
- : RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE
4331
+ let message = RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE
4332
+ if (mode !== undefined) message = RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE
4333
+ else if (isBaseOnlyResponsiveObject(value, responsiveObjectConsts))
4334
+ message = RAW_BREAKPOINT_BASE_ONLY_MESSAGE
4271
4335
  context.report({ node, message })
4272
4336
  }
4273
4337
  for (const node of visibleHiddenCandidates) {
@@ -7,7 +7,7 @@
7
7
  * enabling every rule this file tests. `run` shells the workspace-root `node_modules/.bin/oxlint`
8
8
  * binary and returns the parsed set of `basalt/<rule>` diagnostics it printed.
9
9
  */
10
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
10
+ import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test'
11
11
  import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
12
12
  import { tmpdir } from 'node:os'
13
13
  import { dirname, resolve } from 'node:path'
@@ -100,12 +100,37 @@ function run(
100
100
  writeFileSync(filePath, source)
101
101
  const result = Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', filename], { cwd: dir })
102
102
  const output = `${result.stdout}${result.stderr}`
103
+ // A plugin that fails to load must fail the run, not read as "no findings" — otherwise every
104
+ // `not.toContain` below is false-green.
105
+ if (/Failed to (?:load JS plugin|parse oxlint configuration)/.test(output))
106
+ throw new Error(`oxlint could not load the basalt plugin:\n${output}`)
103
107
  const rules = new Set(
104
108
  [...output.matchAll(/\(basalt\/([\w-]+)\)/g)].map((match) => match[1] as string),
105
109
  )
106
110
  return { code: result.exitCode ?? 0, rules, output }
107
111
  }
108
112
 
113
+ // The sentinel: one fixture every working plugin reports. If it does not, the plugin is not loaded
114
+ // (or not registered under `basalt/`) and nothing else in this file means anything.
115
+ beforeAll(() => {
116
+ const sentinelDir = mkdtempSync(resolve(tmpdir(), 'basalt-oxlint-sentinel-'))
117
+ try {
118
+ writeFileSync(
119
+ resolve(sentinelDir, '.oxlintrc.json'),
120
+ JSON.stringify({ jsPlugins: [PLUGIN_PATH], rules: { 'basalt/no-raw-font-size': 'error' } }),
121
+ )
122
+ writeFileSync(resolve(sentinelDir, 'sentinel.tsx'), 'export const C = () => <Text fz={10} />\n')
123
+ const result = Bun.spawnSync([OXLINT_BIN, '-c', '.oxlintrc.json', 'sentinel.tsx'], {
124
+ cwd: sentinelDir,
125
+ })
126
+ const output = `${result.stdout}${result.stderr}`
127
+ if (!output.includes('(basalt/no-raw-font-size)'))
128
+ throw new Error(`basalt oxlint plugin did not load — sentinel rule never fired:\n${output}`)
129
+ } finally {
130
+ rmSync(sentinelDir, { recursive: true, force: true })
131
+ }
132
+ })
133
+
109
134
  // ── no-raw-font-size ─────────────────────────────────────────────────────────
110
135
 
111
136
  describe('basalt/no-raw-font-size', () => {
@@ -3723,6 +3748,127 @@ describe('basalt/raw-breakpoint', () => {
3723
3748
  )
3724
3749
  expect(rules).not.toContain('raw-breakpoint')
3725
3750
  })
3751
+
3752
+ // rb r2 #11: the object sat inside a conditional, so the rule never saw it.
3753
+ it('flags a responsive object inside a conditional prop value (Grid.Col, no container)', () => {
3754
+ const { rules } = run(
3755
+ `import { Grid } from '@mantine/core'\n` +
3756
+ `export const C = ({ wide }: { wide: boolean }) => (\n` +
3757
+ ` <Grid><Grid.Col span={wide ? { base: 12, md: 4 } : 12}>x</Grid.Col></Grid>\n)\n`,
3758
+ )
3759
+ expect(rules).toContain('raw-breakpoint')
3760
+ })
3761
+
3762
+ // Review r1: the unwrap ran before const resolution, so hoisting the conditional bypassed it.
3763
+ it('flags a conditional responsive object behind a same-file const', () => {
3764
+ const { rules } = run(
3765
+ `import { Grid } from '@mantine/core'\n` +
3766
+ `export const C = ({ wide }: { wide: boolean }) => {\n` +
3767
+ ` const span = wide ? { base: 12, md: 4 } : 12\n` +
3768
+ ` return <Grid><Grid.Col span={span}>x</Grid.Col></Grid>\n}\n`,
3769
+ )
3770
+ expect(rules).toContain('raw-breakpoint')
3771
+ })
3772
+
3773
+ it('flags through a const alias chain and survives an alias cycle', () => {
3774
+ const chained = run(
3775
+ `import { SimpleGrid } from '@mantine/core'\n` +
3776
+ `const base = { base: 1, sm: 2 }\nconst cols = base\n` +
3777
+ `export const C = () => <SimpleGrid cols={cols} />\n`,
3778
+ )
3779
+ expect(chained.rules).toContain('raw-breakpoint')
3780
+ const cyclic = run(
3781
+ `import { SimpleGrid } from '@mantine/core'\n` +
3782
+ `const a = b\nconst b = a\n` +
3783
+ `export const C = () => <SimpleGrid cols={a} />\n`,
3784
+ )
3785
+ expect(cyclic.rules).not.toContain('raw-breakpoint')
3786
+ })
3787
+
3788
+ it('flags a responsive object on either side of a logical prop value', () => {
3789
+ const { rules } = run(
3790
+ `import { SimpleGrid } from '@mantine/core'\n` +
3791
+ `export const C = ({ c }: { c?: number }) => <SimpleGrid cols={c ?? { base: 1, sm: 2 }} />\n`,
3792
+ )
3793
+ expect(rules).toContain('raw-breakpoint')
3794
+ })
3795
+
3796
+ // rb r2 #13: a base-only object is no breakpoint at all — the message says to write the value.
3797
+ it('tells a base-only responsive object to write the bare value', () => {
3798
+ const { rules, output } = run(
3799
+ `import { SimpleGrid } from '@mantine/core'\n` +
3800
+ `export const C = () => <SimpleGrid cols={{ base: 3 }} />\n`,
3801
+ )
3802
+ expect(rules).toContain('raw-breakpoint')
3803
+ expect(output).toContain('cols={3}')
3804
+ expect(output).not.toContain('keyed on theme breakpoints')
3805
+ })
3806
+
3807
+ it('gives the base-only message through a const-bound conditional too', () => {
3808
+ const { output } = run(
3809
+ `import { SimpleGrid } from '@mantine/core'\n` +
3810
+ `export const C = ({ d }: { d: boolean }) => {\n` +
3811
+ ` const cols = d ? { base: 3 } : 2\n return <SimpleGrid cols={cols} />\n}\n`,
3812
+ )
3813
+ expect(output).toContain('cols={3}')
3814
+ expect(output).not.toContain('keyed on theme breakpoints')
3815
+ })
3816
+
3817
+ it('keeps the viewport message when any branch carries a real breakpoint', () => {
3818
+ const { output } = run(
3819
+ `import { SimpleGrid } from '@mantine/core'\n` +
3820
+ `export const C = ({ d }: { d: boolean }) => <SimpleGrid cols={d ? { base: 3 } : { base: 1, sm: 2 }} />\n`,
3821
+ )
3822
+ expect(output).toContain('keyed on theme breakpoints')
3823
+ })
3824
+
3825
+ // rb r2 #5.
3826
+ it('points a useMediaQuery read at the (pointer: coarse) CSS home', () => {
3827
+ const { output } = run(
3828
+ `import { useMediaQuery } from '@mantine/hooks'\n` +
3829
+ `export const useTouch = () => useMediaQuery('(hover: none)')\n`,
3830
+ )
3831
+ expect(output).toContain('@media (pointer: coarse)')
3832
+ })
3833
+
3834
+ // obsidian r2 #7: a test-setup polyfill lives in `tests/`, not in a `.test` file.
3835
+ it.each(['tests/setup/dom.ts', 'test/setup.ts', 'src/__tests__/setup.ts'])(
3836
+ 'does NOT flag a matchMedia stub under %s',
3837
+ (filename) => {
3838
+ const { rules } = run(`window.matchMedia = () => ({ matches: false })\n`, filename)
3839
+ expect(rules).not.toContain('raw-breakpoint')
3840
+ },
3841
+ )
3842
+
3843
+ it('still flags a file whose name merely contains "test" outside a test dir', () => {
3844
+ const { rules } = run(`const mq = window.matchMedia('(max-width: 600px)')\n`, 'src/latest/a.ts')
3845
+ expect(rules).toContain('raw-breakpoint')
3846
+ })
3847
+ })
3848
+
3849
+ // ── control homes: text lane ≡ plugin ────────────────────────────────────────
3850
+
3851
+ // 1.32.1 (B2): the plugin homed FormRow/FormGroup while check-theme's `raw-selection-control` did
3852
+ // not, so a correctly migrated form reported in one lane only. Both lists are module-private (the
3853
+ // guard is dependency-free, the plugin cannot import TS), so this reads them out of the source.
3854
+ describe('control homes — lane parity', () => {
3855
+ const pluginSource = readFileSync(PLUGIN_PATH, 'utf8')
3856
+ const guardSource = readFileSync(resolve(import.meta.dirname, '../src/guard/index.ts'), 'utf8')
3857
+
3858
+ const pluginSet = (name: string): string[] => {
3859
+ const body = pluginSource.match(new RegExp(`const ${name} = new Set\\(\\[([^\\]]*)\\]`))?.[1]
3860
+ if (body === undefined) throw new Error(`${name} not found in oxlint-plugin.js`)
3861
+ return [...body.matchAll(/'([^']+)'/g)].map((m) => m[1] as string)
3862
+ }
3863
+
3864
+ it('check-theme CONTROL_HOST_TAG names exactly CONTROL_HOST_TAGS ∪ BASALT_HOST_TAGS', () => {
3865
+ const alternation = guardSource.match(/const CONTROL_HOST_TAG =\s*\/<\(\?:([^)]*)\)/)?.[1]
3866
+ if (alternation === undefined) throw new Error('CONTROL_HOST_TAG not found in guard/index.ts')
3867
+ const textLane = alternation.split('|').map((tag) => tag.replaceAll('\\.', '.'))
3868
+ const pluginLane = [...pluginSet('CONTROL_HOST_TAGS'), ...pluginSet('BASALT_HOST_TAGS')]
3869
+ expect(pluginLane).toContain('FormRow')
3870
+ expect(textLane.toSorted()).toEqual(pluginLane.toSorted())
3871
+ })
3726
3872
  })
3727
3873
 
3728
3874
  // ── the deprecation ledger (B4) ──────────────────────────────────────────────
@@ -287,7 +287,8 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
287
287
  * list can never disagree with its table; a fixed card order is `initialSorting`, not a second
288
288
  * sort. Everything around the body stays: the header (title · count · toolbar), the bulk bar,
289
289
  * the pager, and `query` / `isLoading` / `emptyState`. `onRowActivate` makes each card
290
- * activatable exactly as it does a row (click, or Enter on the focused card). The selection
290
+ * activatable exactly as it does a row (click, or Enter on the focused card — never Space, the
291
+ * browser's own page scroll on a focused non-button). The selection
291
292
  * checkbox is not projected — a card list with a selection keeps its bulk bar but no ticks.
292
293
  *
293
294
  * An activatable card takes `tabIndex=0` + `data-activatable`, never `role="button"`: a card is
@@ -295,7 +296,9 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
295
296
  * descendant presentational, so those nested controls would vanish from the accessibility tree.
296
297
  * The affordance is the cursor and the focus ring, as on the row. `maxHeight` caps the card list
297
298
  * in a `ScrollArea`; `minWidth` does not apply — it is a horizontal floor for columns, and a
298
- * card list has none.
299
+ * card list has none. `stickyHeader` has nothing to stick while the cards render. A card order
300
+ * with no user sort is `data`'s own order — sort it there (or `initialSorting`) for a domain
301
+ * order.
299
302
  *
300
303
  * Reach for it when the row carries more than the columns a phone can hold (a status + label +
301
304
  * age header, a clamped title, a prose line). A table that only needs to shed a column or two
@@ -431,6 +434,10 @@ export type BasaltDataTableProps<T> = BasaltProps & SlotStylesProps<BasaltDataTa
431
434
  * its own card instead of widening the page. Reach for this only to hold a MINIMUM table width
432
435
  * — e.g. so eight columns keep their readable widths and scroll rather than compress.
433
436
  *
437
+ * Setting it also switches the column fold OFF (a declared floor is the caller saying "this
438
+ * table is at least this wide", which folding columns out would contradict), so a `minWidth`
439
+ * table scrolls where the same table without it folds. `maxHeight` alone keeps the fold.
440
+ *
434
441
  * The one table whose wrapper is MEASURED rather than declared is `stickyHeader` with neither
435
442
  * this nor `maxHeight`; see that prop and `useMeasuredContainment` in the implementation for
436
443
  * why, and pair it with `maxHeight` if the table must both scroll and stick.
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/data/data-table.tsx"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAEV,SAAS,EACT,kBAAkB,EAElB,eAAe,EAEf,OAAO,EACP,iBAAiB,EACjB,YAAY,EAGb,MAAM,uBAAuB,CAAA;AAS9B,OAAO,KAAK,EAAgD,SAAS,EAAa,MAAM,OAAO,CAAA;AAG/F,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAOnE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAYjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAc9D,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAExD;;;;;;;;GAQG;AACH,OAAO,QAAQ,uBAAuB,CAAC;IAGrC,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM;QAChD,KAAK,CAAC,EAAE,cAAc,CAAA;QACtB;;;;;WAKG;QACH,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB;CACF;AAmBD,0GAA0G;AAC1G,MAAM,MAAM,oBAAoB,GAAG;IACjC,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAErF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI;IACxC,sFAAsF;IACtF,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAA;IAC9B,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,OAAO,EAAE,oBAAoB,EAAE,CAAA;IAC/B,2GAA2G;IAC3G,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,CAAC,CAAC,GAAG,OAAO,IAAI;IAChD,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7B,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;CAC/B,CAAA;AAuLD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,WAAW,GAC/C,eAAe,CAAC,mBAAmB,CAAC,GAAG;IAGrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,sBAAsB;IACtB,IAAI,EAAE,CAAC,EAAE,CAAA;IACT;;;OAGG;IAKH,OAAO,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;IAC5B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAA;IAClC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,YAAY,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;IAIjD;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5B,6FAA6F;IAC7F,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7D;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAA;IAIrB;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAA;IAClC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAA;IAChC,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC7D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,SAAS,EAAE,CAAA;IAIxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,eAAe,CAAA;IACnC,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iHAAiH;IACjH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2HAA2H;IAC3H,SAAS,CAAC,EAAE,MAAM,CAAA;IAIlB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,oFAAoF;IACpF,oBAAoB,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAA;IAIhD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpC,2DAA2D;IAC3D,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAgKH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC,GAAG,WAAW,CAAC,MAAM,CAAC,CAiBtB;AAqRD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,+BA0pBhE"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/data/data-table.tsx"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAEV,SAAS,EACT,kBAAkB,EAElB,eAAe,EAEf,OAAO,EACP,iBAAiB,EACjB,YAAY,EAGb,MAAM,uBAAuB,CAAA;AAS9B,OAAO,KAAK,EAAgD,SAAS,EAAa,MAAM,OAAO,CAAA;AAG/F,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAOnE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAYjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAc9D,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAExD;;;;;;;;GAQG;AACH,OAAO,QAAQ,uBAAuB,CAAC;IAGrC,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM;QAChD,KAAK,CAAC,EAAE,cAAc,CAAA;QACtB;;;;;WAKG;QACH,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB;CACF;AAmBD,0GAA0G;AAC1G,MAAM,MAAM,oBAAoB,GAAG;IACjC,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAErF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI;IACxC,sFAAsF;IACtF,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAA;IAC9B,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAA;IACb,yCAAyC;IACzC,OAAO,EAAE,oBAAoB,EAAE,CAAA;IAC/B,2GAA2G;IAC3G,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,CAAC,CAAC,GAAG,OAAO,IAAI;IAChD,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7B,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAA;CAC/B,CAAA;AAuLD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,WAAW,GAC/C,eAAe,CAAC,mBAAmB,CAAC,GAAG;IAGrC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,sBAAsB;IACtB,IAAI,EAAE,CAAC,EAAE,CAAA;IACT;;;OAGG;IAKH,OAAO,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;IAC5B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAA;IAClC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,YAAY,CAAA;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;IAIjD;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9C;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5B,6FAA6F;IAC7F,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC7D;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,WAAW,CAAA;IAIrB;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAA;IAClC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAA;IAChC,6FAA6F;IAC7F,oBAAoB,CAAC,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IAC7D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,SAAS,EAAE,CAAA;IAIxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,eAAe,CAAA;IACnC,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iHAAiH;IACjH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2HAA2H;IAC3H,SAAS,CAAC,EAAE,MAAM,CAAA;IAIlB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,oFAAoF;IACpF,oBAAoB,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAA;IAIhD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpC,2DAA2D;IAC3D,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,0FAA0F;IAC1F,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAgKH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC,GAAG,WAAW,CAAC,MAAM,CAAC,CAiBtB;AAqRD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,+BA0pBhE"}
@@ -90,7 +90,12 @@ export type FormGroupProps = BasaltProps & {
90
90
  error?: ReactNode;
91
91
  /** Marks the group required — same two-channel treatment as `FormRow`. */
92
92
  required?: boolean;
93
- /** `column` stacks the options; `row` lays them out inline and wraps. @default 'column' */
93
+ /**
94
+ * `column` stacks the options; `row` lays them out inline, wraps, and bottom-aligns them, so a
95
+ * trailing button sits level with the inputs instead of stretching to the tallest child. Give a
96
+ * `FormRow` inside a `row` group (or any flex row) a definite width — it is an inline-size
97
+ * container, so its intrinsic width is zero and it collapses otherwise. @default 'column'
98
+ */
94
99
  direction?: 'row' | 'column';
95
100
  /** The cluster — checkboxes, radios, chips. */
96
101
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/forms/form-layout.tsx"],"names":[],"mappings":"AAoBA,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,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAyC1D,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;;;;;OAKG;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,2FAA2F;IAC3F,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;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"}
1
+ {"version":3,"file":"form-layout.d.ts","sourceRoot":"","sources":["../../src/forms/form-layout.tsx"],"names":[],"mappings":"AAoBA,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,qBAAqB,CAAA;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAyC1D,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;;;;;OAKG;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;;;;;;;;;;;;;;;;GAgBG;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"}
@@ -138,9 +138,12 @@
138
138
  gap: var(--vx-space-stack-xs);
139
139
  }
140
140
 
141
+ /* Bottom-aligned, not stretched: a trailing button beside a labelled FormRow keeps its own height
142
+ and lines up with the input, not with the label above it. */
141
143
  .groupControls[data-direction='row'] {
142
144
  flex-direction: row;
143
145
  flex-wrap: wrap;
146
+ align-items: flex-end;
144
147
  gap: var(--vx-space-control-gap);
145
148
  }
146
149
 
@@ -302,7 +302,7 @@ export declare const GUARD_RULES: {
302
302
  readonly kind: "raw-selection-control";
303
303
  readonly pattern: RegExp;
304
304
  readonly appliesTo: (relPath: string) => boolean;
305
- readonly message: "Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.";
305
+ readonly message: "Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.";
306
306
  };
307
307
  readonly 'inline-font-size': {
308
308
  readonly kind: "inline-font-size";
@@ -313,7 +313,7 @@ export declare const GUARD_RULES: {
313
313
  readonly kind: "raw-media-query";
314
314
  readonly pattern: RegExp;
315
315
  readonly appliesTo: (relPath: string) => boolean;
316
- readonly message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${string} at a boundary in ${string}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`;
316
+ readonly message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${string}, and a basalt-card or unnamed @container query must sit at a boundary in ${string} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`;
317
317
  };
318
318
  };
319
319
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
@@ -22,12 +22,7 @@ const CSS_WIDTH_MEDIA_QUERY = new RegExp(
22
22
  const CSS_CONTAINER_QUERY = /@container(?:\s+([\w-]+))?\s*((?:\([^)]*\)\s*(?:and\s*)?)+)/g;
23
23
  const CSS_CONTAINER_LITERAL = /(?:min-width|max-width|width)\s*(?:[<>]=?|:)\s*([\d.]+)(px|em)?/g;
24
24
  const CSS_NON_COARSE_POINTER_QUERY = /\(\s*(?:any-)?pointer\s*:\s*(?!(?:coarse|fine)\b)[a-z-]+\s*\)/g;
25
- const DECLARED_CONTAINER_NAMES = /* @__PURE__ */ new Set([
26
- "basalt-card",
27
- "basalt-form",
28
- "basalt-main",
29
- "basalt-grid"
30
- ]);
25
+ const DECLARED_CONTAINER_NAMES = /* @__PURE__ */ new Set(["basalt-card", "basalt-form", "basalt-grid"]);
31
26
  const CONTAINER_CLASS_LITERALS_PX = /* @__PURE__ */ new Set([0, 240, 239.9, 480, 479.9, 800, 799.9]);
32
27
  const SIZE_CLASS_LITERALS = /* @__PURE__ */ new Set(["52.5em", "75em", "52.49375em", "74.99375em"]);
33
28
  const SHELL_CSS_PATH = /(?:^|\/)shell\//;
@@ -193,7 +188,7 @@ function titleBodyNamesThePage(body) {
193
188
  }
194
189
  const RAW_SELECTION_CONTROL_TAG = /<(SegmentedControl|Select|MultiSelect|NativeSelect|DatePickerInput|DateInput|TagsInput|Chip\.Group)(?![\w])(?:=>|[^>])*?>/g;
195
190
  const PROSE_CONTEXT_TAG = /<(?:Prose|ArticleLayout|Modal|Drawer)(?![\w])/;
196
- const CONTROL_HOST_TAG = /<(?:SettingsRow|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/;
191
+ const CONTROL_HOST_TAG = /<(?:SettingsRow|FormRow|FormGroup|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/;
197
192
  const CONTROL_HOST_WINDOW_LINES = 12;
198
193
  const OVERLAY_CONVENTION_KEBAB = /^[^/]*-(?:modal|drawer|popover|panel|form)\.[jt]sx$/;
199
194
  const OVERLAY_CONVENTION_PASCAL = /^[A-Z]\w*(?:Modal|Drawer|Popover|Panel|Form)\.[jt]sx$/;
@@ -923,7 +918,7 @@ const GUARD_RULES = {
923
918
  // handled inline (full-text tag-scoped scan + host window)
924
919
  // JSX-tag-shaped (`<Select …>`) — never appears in CSS text.
925
920
  appliesTo: (relPath) => !relPath.endsWith(".css"),
926
- message: "Raw selection control with no home \u2014 a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1\u2013C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title \u2014 so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST \u2014 where the two disagree, the plugin is right."
921
+ message: "Raw selection control with no home \u2014 a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1\u2013C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title \u2014 so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST \u2014 where the two disagree, the plugin is right."
927
922
  },
928
923
  "inline-font-size": {
929
924
  kind: "inline-font-size",
@@ -937,7 +932,7 @@ const GUARD_RULES = {
937
932
  // CSS-only: a width @media, an @container name/literal or a non-coarse pointer query has no JSX
938
933
  // spelling — the AST-side twin of this law is the oxlint plugin's basalt/raw-breakpoint.
939
934
  appliesTo: (relPath) => relPath.endsWith(".css"),
940
- message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(" / ")} at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(" / ")}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`
935
+ message: `Raw viewport/container breakpoint in CSS \u2014 a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(" / ")}, and a basalt-card or unnamed @container query must sit at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(" / ")} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`
941
936
  }
942
937
  };
943
938
  function guardKindRemedy(kind) {
@@ -100,5 +100,29 @@ export type PageBarProps = BasaltProps & SlotStylesProps<PageBarSlot> & {
100
100
  /** One `ViewTabs`. */
101
101
  tabs?: ReactNode;
102
102
  };
103
+ /**
104
+ * The page's control bar — row 1 (title/icon, `actions`, `sync`) and row 2 (`tabs`, `filters`,
105
+ * `filtersEnd`). Inside `BasaltShell` both rows portal into shell chrome; with no shell it renders
106
+ * ONE sticky bar in flow, `title` leading, and needs no `PageBarProvider`.
107
+ *
108
+ * @example
109
+ * // Shell-less (linewatch-shaped): the store is local, the bar is the page's first child.
110
+ * const filters = createLocalStore({ key: 'outages', fields: { q: field.string({ max: 60 }) } })
111
+ *
112
+ * function OutagesPage() {
113
+ * const [q] = filters.field.q.use()
114
+ * return (
115
+ * <>
116
+ * <PageBar
117
+ * title="Outages"
118
+ * actions={{ primary: { key: 'export', label: 'Export', onClick: exportCsv } }}
119
+ * filters={<FilterSet><SearchFilter field={filters.field.q} placeholder="Find a line" /></FilterSet>}
120
+ * classNames={{ root: classes.bleed }} // your gutter bleed — see `PageBarSlot`
121
+ * />
122
+ * <OutageTable query={q} />
123
+ * </>
124
+ * )
125
+ * }
126
+ */
103
127
  export declare function PageBar({ title, icon, actions, sync, filters, filtersEnd, tabs, className, style, classNames, }: PageBarProps): ReactNode;
104
128
  //# sourceMappingURL=page-bar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAWnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAK9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAKxD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CACzB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CAsDZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGtE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IAC7C,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAC3B,CAaA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAmEH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,YAAY,GAAG,SAAS,CAkM1B"}
1
+ {"version":3,"file":"page-bar.d.ts","sourceRoot":"","sources":["../../src/shell/page-bar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAWnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAK9D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAKxD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CACzB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IACtC,QAAQ,EAAE,SAAS,CAAA;CACpB,GAAG,SAAS,CAsDZ;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAGtE;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IAC7C,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAC3B,CAaA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,WAAW,GACpC,eAAe,CAAC,WAAW,CAAC,GAAG;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,qGAAqG;IACrG,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IACrC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;IACxB,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB,CAAA;AAmEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,YAAY,GAAG,SAAS,CAkM1B"}
@@ -16,6 +16,8 @@ export { deriveRadius } from './palette.js';
16
16
  export type { RadiusValues } from './palette.js';
17
17
  export { deriveSpacing } from './palette.js';
18
18
  export type { SpaceValues } from './palette.js';
19
+ export { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES } from './size-classes.js';
20
+ export type { ContainerClass } from './size-classes.js';
19
21
  /** A per-theme color pair: a hue keeps its identity but shifts shade across schemes. */
20
22
  export type ColorPair = {
21
23
  light: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import { BP, p } from "./palette.js";
14
14
  import { DEFAULT_DERIVE_CONFIG, deriveTokens, resolveDeriveConfig } from "./derive.js";
15
15
  import { deriveRadius } from "./palette.js";
16
16
  import { deriveSpacing } from "./palette.js";
17
+ import { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES as SIZE_CLASSES2 } from "./size-classes.js";
17
18
  const alpha = (token, a) => `color-mix(in srgb, ${token} ${Math.round(a * 100)}%, transparent)`;
18
19
  const TEXT = {
19
20
  nano: 10,
@@ -671,7 +672,9 @@ ${mobileLadderBlock(space)}`;
671
672
  export {
672
673
  BP,
673
674
  BREAKPOINTS,
675
+ CONTAINER_CLASSES,
674
676
  DEFAULT_DERIVE_CONFIG,
677
+ SIZE_CLASSES2 as SIZE_CLASSES,
675
678
  VX,
676
679
  alpha,
677
680
  buildDensityCss,
@@ -686,6 +689,7 @@ export {
686
689
  groupTokens,
687
690
  p,
688
691
  pxRem,
692
+ resolveContainerClass,
689
693
  resolveDeriveConfig,
690
694
  seriesTokens
691
695
  };
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.32.0
1
+ # basalt-ui v1.32.1
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.32.0
22
+ Version: 1.32.1
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.32.0",
3
+ "version": "1.32.1",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -608,7 +608,8 @@ export type BasaltDataTableProps<T> = BasaltProps &
608
608
  * list can never disagree with its table; a fixed card order is `initialSorting`, not a second
609
609
  * sort. Everything around the body stays: the header (title · count · toolbar), the bulk bar,
610
610
  * the pager, and `query` / `isLoading` / `emptyState`. `onRowActivate` makes each card
611
- * activatable exactly as it does a row (click, or Enter on the focused card). The selection
611
+ * activatable exactly as it does a row (click, or Enter on the focused card — never Space, the
612
+ * browser's own page scroll on a focused non-button). The selection
612
613
  * checkbox is not projected — a card list with a selection keeps its bulk bar but no ticks.
613
614
  *
614
615
  * An activatable card takes `tabIndex=0` + `data-activatable`, never `role="button"`: a card is
@@ -616,7 +617,9 @@ export type BasaltDataTableProps<T> = BasaltProps &
616
617
  * descendant presentational, so those nested controls would vanish from the accessibility tree.
617
618
  * The affordance is the cursor and the focus ring, as on the row. `maxHeight` caps the card list
618
619
  * in a `ScrollArea`; `minWidth` does not apply — it is a horizontal floor for columns, and a
619
- * card list has none.
620
+ * card list has none. `stickyHeader` has nothing to stick while the cards render. A card order
621
+ * with no user sort is `data`'s own order — sort it there (or `initialSorting`) for a domain
622
+ * order.
620
623
  *
621
624
  * Reach for it when the row carries more than the columns a phone can hold (a status + label +
622
625
  * age header, a clamped title, a prose line). A table that only needs to shed a column or two
@@ -761,6 +764,10 @@ export type BasaltDataTableProps<T> = BasaltProps &
761
764
  * its own card instead of widening the page. Reach for this only to hold a MINIMUM table width
762
765
  * — e.g. so eight columns keep their readable widths and scroll rather than compress.
763
766
  *
767
+ * Setting it also switches the column fold OFF (a declared floor is the caller saying "this
768
+ * table is at least this wide", which folding columns out would contradict), so a `minWidth`
769
+ * table scrolls where the same table without it folds. `maxHeight` alone keeps the fold.
770
+ *
764
771
  * The one table whose wrapper is MEASURED rather than declared is `stickyHeader` with neither
765
772
  * this nor `maxHeight`; see that prop and `useMeasuredContainment` in the implementation for
766
773
  * why, and pair it with `maxHeight` if the table must both scroll and stick.
@@ -138,9 +138,12 @@
138
138
  gap: var(--vx-space-stack-xs);
139
139
  }
140
140
 
141
+ /* Bottom-aligned, not stretched: a trailing button beside a labelled FormRow keeps its own height
142
+ and lines up with the input, not with the label above it. */
141
143
  .groupControls[data-direction='row'] {
142
144
  flex-direction: row;
143
145
  flex-wrap: wrap;
146
+ align-items: flex-end;
144
147
  gap: var(--vx-space-control-gap);
145
148
  }
146
149
 
@@ -213,7 +213,12 @@ export type FormGroupProps = BasaltProps & {
213
213
  error?: ReactNode
214
214
  /** Marks the group required — same two-channel treatment as `FormRow`. */
215
215
  required?: boolean
216
- /** `column` stacks the options; `row` lays them out inline and wraps. @default 'column' */
216
+ /**
217
+ * `column` stacks the options; `row` lays them out inline, wraps, and bottom-aligns them, so a
218
+ * trailing button sits level with the inputs instead of stretching to the tallest child. Give a
219
+ * `FormRow` inside a `row` group (or any flex row) a definite width — it is an inline-size
220
+ * container, so its intrinsic width is zero and it collapses otherwise. @default 'column'
221
+ */
217
222
  direction?: 'row' | 'column'
218
223
  /** The cluster — checkboxes, radios, chips. */
219
224
  children: ReactNode
@@ -177,27 +177,23 @@ const CSS_NON_COARSE_POINTER_QUERY =
177
177
  * The `@container` names the container-class axis declares — a consumer container carrying any
178
178
  * other name has invented its own container-query breakpoint instead of using the framework's.
179
179
  * `basalt-card` is the one shipped today (`src/dashboard/section.module.css`);
180
- * `basalt-form`/`basalt-main`/`basalt-grid` are the declared remaining three, reserved here so a
181
- * consumer adopting one ahead of the framework does not immediately trip this guard.
180
+ * `basalt-form`/`basalt-grid` are the declared remaining two, reserved here so a consumer adopting
181
+ * one ahead of the framework does not immediately trip this guard. `basalt-main` was a third until
182
+ * 1.32.1 dropped it: nothing ever declared it, so the guard accepted a query that never matched.
182
183
  *
183
184
  * `basalt-grid` is also what `StatGroup`/`WidgetGrid` declare (`src/dashboard/*.module.css`) —
184
185
  * they shipped as `basalt-stat-group`/`basalt-widget-grid` until 1.32.0 moved them onto this table.
185
186
  */
186
- const DECLARED_CONTAINER_NAMES = new Set([
187
- 'basalt-card',
188
- 'basalt-form',
189
- 'basalt-main',
190
- 'basalt-grid',
191
- ])
187
+ const DECLARED_CONTAINER_NAMES = new Set(['basalt-card', 'basalt-form', 'basalt-grid'])
192
188
 
193
189
  /**
194
190
  * `CONTAINER_CLASSES` (`src/tokens/size-classes.ts`) boundaries, duplicated per the `CTL_THEME_TAGS`
195
191
  * convention in the oxlint plugin — this module is dependency-free by contract (file header) and
196
192
  * must not import `../tokens`. Pinned against the real table by `check-source.test.ts`. Judged only
197
- * against `basalt-card`: the other three declared names have no shipped numeric table yet. `0` is
198
- * the container's own floor, not a class boundary, but `(width >= 0px)` is a legitimate idiom for
199
- * "scope this rule to the container, whatever its width" (`widget-header.module.css`'s `cqi` clamp)
200
- * — allowed for that reason, not because it names a real `CONTAINER_CLASSES` step.
193
+ * against `basalt-card` and unnamed queries: the other declared names have no shipped numeric table
194
+ * yet. `0` is the container's own floor, not a class boundary, but `(width >= 0px)` is a legitimate
195
+ * idiom for "scope this rule to the container, whatever its width" (`widget-header.module.css`'s
196
+ * `cqi` clamp) — allowed for that reason, not because it names a real `CONTAINER_CLASSES` step.
201
197
  */
202
198
  const CONTAINER_CLASS_LITERALS_PX = new Set([0, 240, 239.9, 480, 479.9, 800, 799.9])
203
199
 
@@ -691,9 +687,12 @@ const PROSE_CONTEXT_TAG = /<(?:Prose|ArticleLayout|Modal|Drawer)(?![\w])/
691
687
  * read differently in the two lanes. The plugin gates them on the tag's basalt IMPORT; a 12-line
692
688
  * regex window has no import graph, so this lane matches by name and accepts the missed warn on a
693
689
  * consumer's own `PanelRow` — the same direction every approximation in this scan already leans.
690
+ *
691
+ * The name set is the plugin's `CONTROL_HOST_TAGS` ∪ `BASALT_HOST_TAGS`; `oxlint-plugin.test.ts` ›
692
+ * lane parity asserts the two agree (1.32.1 — `FormRow`/`FormGroup` had drifted out of this lane).
694
693
  */
695
694
  const CONTROL_HOST_TAG =
696
- /<(?:SettingsRow|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/
695
+ /<(?:SettingsRow|FormRow|FormGroup|Modal|Drawer|Popover\.Dropdown|Menu\.Dropdown|Composer|FilterSet|PageAside|PanelRow)(?![\w])/
697
696
 
698
697
  /**
699
698
  * How far ABOVE a control the host-tag window reaches, in lines.
@@ -2200,7 +2199,7 @@ export const GUARD_RULES = {
2200
2199
  // JSX-tag-shaped (`<Select …>`) — never appears in CSS text.
2201
2200
  appliesTo: (relPath) => !relPath.endsWith('.css'),
2202
2201
  message:
2203
- 'Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.',
2202
+ 'Raw selection control with no home — a filter or tab belongs in a PageBar / Section / WidgetHeader slot and takes a `field` (a FieldHandle), so it owns the URL write and the localStorage mirror instead of carrying value/onChange (docs/CONTROLS-SPEC.md laws C1–C3). Use the bound control from basalt-ui/controls. A settings row, a FormRow / FormGroup, an overlay and an @mantine/form file are the declared non-homes and never report. No shell? PageBar needs no BasaltShell: outside one it renders in flow, sticky, with its own title — so its slots are a reachable home for a provider-only app. This is the TEXT lane of basalt/control-outside-home, which answers the same question against the real AST — where the two disagree, the plugin is right.',
2204
2203
  },
2205
2204
  'inline-font-size': {
2206
2205
  kind: 'inline-font-size',
@@ -2214,7 +2213,7 @@ export const GUARD_RULES = {
2214
2213
  // CSS-only: a width @media, an @container name/literal or a non-coarse pointer query has no JSX
2215
2214
  // spelling — the AST-side twin of this law is the oxlint plugin's basalt/raw-breakpoint.
2216
2215
  appliesTo: (relPath) => relPath.endsWith('.css'),
2217
- message: `Raw viewport/container breakpoint in CSS — a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(' / ')} at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(' / ')}; and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`,
2216
+ message: `Raw viewport/container breakpoint in CSS — a width @media belongs to shell/** against SIZE_CLASSES (useSizeClass() is the JS seam); an @container name must be one of ${[...DECLARED_CONTAINER_NAMES].join(' / ')}, and a basalt-card or unnamed @container query must sit at a boundary in ${[...CONTAINER_CLASS_LITERALS_PX].map((px) => `${px}px`).join(' / ')} (the other declared names have no numeric table yet, so their literals are not judged); and a pointer query other than (pointer: coarse) or (pointer: fine) has no framework meaning. Same law the oxlint plugin enforces as basalt/raw-breakpoint over JSX; this is the CSS half.`,
2218
2217
  },
2219
2218
  } as const satisfies Record<GuardKind, GuardRule>
2220
2219
 
@@ -2481,7 +2480,7 @@ export function checkSource(text: string, relPath: string, cfg: GuardConfig): Fi
2481
2480
  }
2482
2481
  // Literal boundaries are judged against `basalt-card`'s own table (see
2483
2482
  // `CONTAINER_CLASS_LITERALS_PX`'s doc) — an unnamed query has no OTHER table to judge
2484
- // against, so it is judged against this one too; the other three declared names stay
2483
+ // against, so it is judged against this one too; the other declared names stay
2485
2484
  // unjudged until they ship their own.
2486
2485
  if (name !== '' && name !== 'basalt-card') continue
2487
2486
  for (const lit of body.matchAll(CSS_CONTAINER_LITERAL)) {
@@ -346,6 +346,30 @@ function useMeasuredHeightVar(active: boolean): (el: HTMLDivElement | null) => v
346
346
  return attach
347
347
  }
348
348
 
349
+ /**
350
+ * The page's control bar — row 1 (title/icon, `actions`, `sync`) and row 2 (`tabs`, `filters`,
351
+ * `filtersEnd`). Inside `BasaltShell` both rows portal into shell chrome; with no shell it renders
352
+ * ONE sticky bar in flow, `title` leading, and needs no `PageBarProvider`.
353
+ *
354
+ * @example
355
+ * // Shell-less (linewatch-shaped): the store is local, the bar is the page's first child.
356
+ * const filters = createLocalStore({ key: 'outages', fields: { q: field.string({ max: 60 }) } })
357
+ *
358
+ * function OutagesPage() {
359
+ * const [q] = filters.field.q.use()
360
+ * return (
361
+ * <>
362
+ * <PageBar
363
+ * title="Outages"
364
+ * actions={{ primary: { key: 'export', label: 'Export', onClick: exportCsv } }}
365
+ * filters={<FilterSet><SearchFilter field={filters.field.q} placeholder="Find a line" /></FilterSet>}
366
+ * classNames={{ root: classes.bleed }} // your gutter bleed — see `PageBarSlot`
367
+ * />
368
+ * <OutageTable query={q} />
369
+ * </>
370
+ * )
371
+ * }
372
+ */
349
373
  export function PageBar({
350
374
  title,
351
375
  icon,
@@ -52,6 +52,11 @@ export type { RadiusValues } from './palette'
52
52
  export { deriveSpacing } from './palette'
53
53
  export type { SpaceValues } from './palette'
54
54
 
55
+ // The two responsive tables — MIGRATING recipes and the raw-breakpoint guard messages send
56
+ // consumers here for them; `migrating-imports.test.ts` pins that every documented import resolves.
57
+ export { CONTAINER_CLASSES, resolveContainerClass, SIZE_CLASSES } from './size-classes'
58
+ export type { ContainerClass } from './size-classes'
59
+
55
60
  /** A per-theme color pair: a hue keeps its identity but shifts shade across schemes. */
56
61
  export type ColorPair = { light: string; dark: string }
57
62