basalt-ui 1.32.1 → 1.33.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/CHANGELOG.md +39 -0
- package/MIGRATING.md +124 -24
- package/agent/rules/basalt-state.md +2 -2
- package/agent/rules/basalt-tokens.md +1 -1
- package/configs/oxlint-plugin.js +68 -61
- package/configs/oxlint-plugin.test.ts +126 -9
- package/configs/oxlint.json +2 -2
- package/dist/agent/coalesce.d.ts.map +1 -1
- package/dist/agent/coalesce.js +1 -2
- package/dist/agent/merge.d.ts.map +1 -1
- package/dist/agent/merge.js +1 -2
- package/dist/agent-chat/relative-time.d.ts +9 -0
- package/dist/agent-chat/relative-time.d.ts.map +1 -1
- package/dist/agent-chat/relative-time.js +1 -2
- package/dist/agent-chat/thread-message.d.ts.map +1 -1
- package/dist/agent-chat/thread-message.js +1 -2
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +3 -3
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +1 -2
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +1 -2
- package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -1
- package/dist/cli/audit-allows.d.ts +1 -1
- package/dist/cli/audit-allows.d.ts.map +1 -1
- package/dist/cli/audit-allows.js +4 -4
- package/dist/cli/check-theme.d.ts.map +1 -1
- package/dist/cli/check-theme.js +1 -2
- package/dist/cli/config.d.ts +197 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +20 -0
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/guard-hook.d.ts.map +1 -1
- package/dist/cli/guard-hook.js +2 -8
- package/dist/cli/index.d.ts +2 -196
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -18
- package/dist/commands/define-commands.d.ts.map +1 -1
- package/dist/commands/define-commands.js +2 -2
- package/dist/commands/define-overlays.d.ts.map +1 -1
- package/dist/commands/define-overlays.js +1 -2
- package/dist/common/index.d.ts +0 -1
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +0 -2
- package/dist/common/validate.d.ts +2 -2
- package/dist/common/validate.d.ts.map +1 -1
- package/dist/common/validate.js +2 -3
- package/dist/content/article-card.d.ts.map +1 -1
- package/dist/content/article-card.js +4 -1
- package/dist/controls/enum-filter.d.ts.map +1 -1
- package/dist/controls/enum-filter.js +3 -13
- package/dist/controls/filter-pill.d.ts +8 -0
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +15 -0
- package/dist/controls/multi-select-filter.d.ts.map +1 -1
- package/dist/controls/multi-select-filter.js +3 -13
- package/dist/controls/number-filter.d.ts +3 -0
- package/dist/controls/number-filter.d.ts.map +1 -1
- package/dist/controls/number-filter.js +12 -4
- package/dist/controls/range-filter.d.ts.map +1 -1
- package/dist/controls/range-filter.js +1 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +2 -3
- package/dist/guard/index.d.ts +16 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +63 -22
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/guard/vx-names.d.ts +3 -0
- package/dist/guard/vx-names.d.ts.map +1 -0
- package/dist/guard/vx-names.js +257 -0
- package/dist/notifications/mount-guard.d.ts.map +1 -1
- package/dist/notifications/mount-guard.js +1 -2
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +2 -3
- package/dist/provider/lab-theme.d.ts +5 -4
- package/dist/provider/lab-theme.d.ts.map +1 -1
- package/dist/provider/lab-theme.js +1 -3
- package/dist/router-tanstack/search-store.d.ts +18 -0
- package/dist/router-tanstack/search-store.d.ts.map +1 -1
- package/dist/router-tanstack/search-store.js +33 -1
- package/dist/shell/mobile-nav-model.d.ts.map +1 -1
- package/dist/shell/mobile-nav-model.js +3 -4
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +1 -2
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +2 -0
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +7 -1
- package/dist/tokens/size-classes.d.ts +13 -0
- package/dist/tokens/size-classes.d.ts.map +1 -1
- package/dist/tokens/size-classes.js +8 -0
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/agent/coalesce.ts +1 -2
- package/src/agent/merge.ts +1 -2
- package/src/agent-chat/relative-time.ts +1 -2
- package/src/agent-chat/thread-message.tsx +2 -3
- package/src/charts/kinds/BandStrip.tsx +7 -3
- package/src/charts/kinds/Bars.tsx +1 -2
- package/src/charts/kinds/StackedArea.tsx +4 -2
- package/src/charts/primitives/legend-disclosure.tsx +1 -0
- package/src/cli/audit-allows.ts +10 -9
- package/src/cli/check-theme.ts +1 -2
- package/src/cli/config.ts +209 -0
- package/src/cli/doctor.ts +2 -2
- package/src/cli/guard-hook.ts +2 -8
- package/src/cli/index.ts +2 -206
- package/src/commands/define-commands.ts +2 -2
- package/src/commands/define-overlays.ts +1 -2
- package/src/common/index.ts +0 -1
- package/src/common/validate.ts +4 -5
- package/src/content/article-card.tsx +1 -0
- package/src/controls/enum-filter.tsx +3 -13
- package/src/controls/filter-pill.tsx +20 -0
- package/src/controls/multi-select-filter.tsx +3 -13
- package/src/controls/number-filter.tsx +24 -3
- package/src/controls/range-filter.tsx +1 -2
- package/src/data/data-table.tsx +2 -3
- package/src/guard/index.ts +136 -46
- package/src/guard/types.ts +3 -2
- package/src/guard/vx-names.ts +257 -0
- package/src/notifications/mount-guard.ts +1 -2
- package/src/provider/index.tsx +2 -3
- package/src/provider/lab-theme.ts +28 -21
- package/src/router-tanstack/search-store.ts +73 -1
- package/src/shell/mobile-nav-model.ts +3 -5
- package/src/shell/page-bar.tsx +1 -4
- package/src/surfaces.ts +2 -0
- package/src/tokens/index.ts +6 -1
- package/src/tokens/size-classes.ts +14 -0
- package/dist/common/is-dev.d.ts +0 -3
- package/dist/common/is-dev.d.ts.map +0 -1
- package/dist/common/is-dev.js +0 -6
- package/src/common/is-dev.ts +0 -4
|
@@ -43,6 +43,24 @@ export type CreateSearchStoreOptions<S extends Record<string, AnyField>> = {
|
|
|
43
43
|
fields: S
|
|
44
44
|
/** Envelope version — bump when the field set changes to discard stale localStorage. */
|
|
45
45
|
version?: number
|
|
46
|
+
/**
|
|
47
|
+
* Search params this store does NOT own that a URL-lane write drops, in the SAME navigate —
|
|
48
|
+
* `['page']` on a paginated list, so narrowing the set on page 5 lands on page 1 and Back restores
|
|
49
|
+
* both together. Dropped, not written: the route's own `validateSearch` resolves the default
|
|
50
|
+
* (`page` → 1), so the store never has to know it. Applies to a URL-lane field's setter and
|
|
51
|
+
* `clear()` when the value moves, and to `useReset()` when any field moves; an explicit `patch`
|
|
52
|
+
* naming the same key wins.
|
|
53
|
+
*
|
|
54
|
+
* **URL-lane writes only.** A `url: false` field (mirror-only or memory) makes no navigate, so it
|
|
55
|
+
* has none to ride — and a `useReset()` on a store with no URL-lane field keeps the page too.
|
|
56
|
+
*
|
|
57
|
+
* Store-wide by design: every filter of a list narrows the same list. A param the store owns is
|
|
58
|
+
* refused at definition — reset a field with `useReset()` or its own `clear()`.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* createSearchStore({ key: 'shares', fields: { q: field.string({}), sort: … }, resets: ['page'] })
|
|
62
|
+
*/
|
|
63
|
+
resets?: readonly string[]
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
export type SearchStore<S extends Record<string, AnyField>> = {
|
|
@@ -205,6 +223,28 @@ function assertNoLazyUrlFallback(key: string, fields: Record<string, AnyField>):
|
|
|
205
223
|
}
|
|
206
224
|
}
|
|
207
225
|
|
|
226
|
+
/**
|
|
227
|
+
* `resets` exists for keys the store does NOT own; one it owns would be dropped from the URL while
|
|
228
|
+
* the mirror kept its value — the same URL/mirror split `patch` refuses. Static, so thrown at
|
|
229
|
+
* definition.
|
|
230
|
+
*/
|
|
231
|
+
function assertResetsNotOwned(input: {
|
|
232
|
+
key: string
|
|
233
|
+
resets: readonly string[]
|
|
234
|
+
paramOwners: ReadonlyMap<string, string>
|
|
235
|
+
}): void {
|
|
236
|
+
const { key, resets, paramOwners } = input
|
|
237
|
+
for (const param of resets) {
|
|
238
|
+
const owner = paramOwners.get(param)
|
|
239
|
+
if (owner === undefined) continue
|
|
240
|
+
throw new Error(
|
|
241
|
+
`basalt-ui: createSearchStore('${key}'): \`resets\` names \`${param}\`, which this store's ` +
|
|
242
|
+
`field '${owner}' owns. \`resets\` is for sibling params the store does not own (a ` +
|
|
243
|
+
'`page`); a field of the store is reset through `useReset()` or its own `clear()`.',
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
208
248
|
/** The memory-only lane: neither the URL nor the mirror, so the value lives in the store's own
|
|
209
249
|
* session-scoped external store (`StoreCore.memoryUse`) — the lane `createLocalStore` also uses. */
|
|
210
250
|
function isMemoryLane(entry: StoreEntry): boolean {
|
|
@@ -240,6 +280,25 @@ export function buildSearchStore<const S extends Record<string, AnyField>>(
|
|
|
240
280
|
for (const entry of core.urlEntries) {
|
|
241
281
|
for (const param of entry.codec.params) paramOwners.set(param, entry.name)
|
|
242
282
|
}
|
|
283
|
+
const resets = o.resets ?? []
|
|
284
|
+
assertResetsNotOwned({ key: o.key, resets, paramOwners })
|
|
285
|
+
/** Spread right after `prev` in every store navigate, so a `patch` or the field itself wins. */
|
|
286
|
+
const resetPatch: Record<string, undefined> = Object.fromEntries(
|
|
287
|
+
resets.map((param) => [param, undefined]),
|
|
288
|
+
)
|
|
289
|
+
/**
|
|
290
|
+
* `resetPatch` when `next` moves the field off what the URL holds NOW — read from the navigate's
|
|
291
|
+
* own `prev`, never the render-captured value, which a second `set()` in the same tick would
|
|
292
|
+
* misjudge against a value the first one already replaced.
|
|
293
|
+
*/
|
|
294
|
+
const resetsFor = (
|
|
295
|
+
entry: StoreEntry,
|
|
296
|
+
prev: Record<string, unknown>,
|
|
297
|
+
next: unknown,
|
|
298
|
+
): Record<string, undefined> | undefined => {
|
|
299
|
+
const current = core.resolve(entry, prev, core.readRecord())
|
|
300
|
+
return entry.codec.equals(next, current) ? undefined : resetPatch
|
|
301
|
+
}
|
|
243
302
|
const persistedNames = new Set(
|
|
244
303
|
core.entries.filter((entry) => entry.codec.lane.persist).map((entry) => entry.name),
|
|
245
304
|
)
|
|
@@ -326,8 +385,10 @@ export function buildSearchStore<const S extends Record<string, AnyField>>(
|
|
|
326
385
|
if (!validated) return
|
|
327
386
|
navigate({
|
|
328
387
|
to: '.',
|
|
388
|
+
// Gated like the setter: clearing a field already at its fallback keeps the page.
|
|
329
389
|
search: (prev: Record<string, unknown>) => ({
|
|
330
390
|
...prev,
|
|
391
|
+
...resetsFor(entry, prev, entry.codec.fallback),
|
|
331
392
|
...entry.codec.toSearch(entry.codec.fallback),
|
|
332
393
|
}),
|
|
333
394
|
replace: true,
|
|
@@ -356,8 +417,11 @@ export function buildSearchStore<const S extends Record<string, AnyField>>(
|
|
|
356
417
|
// `patch` FIRST, the field's own params last: a patch is for keys the store does not
|
|
357
418
|
// own (clearing a sibling `detailDate` with `undefined`), so it must never be able to
|
|
358
419
|
// overwrite the value this very call is setting.
|
|
420
|
+
// `resets` only when the value moves: a no-op write (a debounced box settling back on
|
|
421
|
+
// what it held) must not throw the reader off the page they are on.
|
|
359
422
|
search: (prev: Record<string, unknown>) => ({
|
|
360
423
|
...prev,
|
|
424
|
+
...resetsFor(entry, prev, next),
|
|
361
425
|
...opts?.patch,
|
|
362
426
|
...entry.codec.toSearch(next),
|
|
363
427
|
}),
|
|
@@ -464,7 +528,15 @@ export function buildSearchStore<const S extends Record<string, AnyField>>(
|
|
|
464
528
|
}
|
|
465
529
|
navigate({
|
|
466
530
|
to: '.',
|
|
467
|
-
|
|
531
|
+
// `resets` only when some field actually moves — the same gate as the setter and
|
|
532
|
+
// `clear()`. A `Reset all` with everything already at its fallback must keep the page:
|
|
533
|
+
// this navigate REPLACES, so a page dropped here could not be got back with Back.
|
|
534
|
+
search: (prev: Record<string, unknown>) => {
|
|
535
|
+
const moved = validatedEntries.some(
|
|
536
|
+
(entry) => resetsFor(entry, prev, entry.codec.fallback) !== undefined,
|
|
537
|
+
)
|
|
538
|
+
return { ...prev, ...(moved ? resetPatch : undefined), ...patch }
|
|
539
|
+
},
|
|
468
540
|
replace: true,
|
|
469
541
|
// Same reason as a single field's write above — a `Reset all` pressed from the mobile
|
|
470
542
|
// sheet must not also scroll the page it was pressed on.
|
|
@@ -87,8 +87,6 @@ export function blockRowCount(blocks: readonly SidebarBlock[] | undefined): numb
|
|
|
87
87
|
}).length
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const DEV = process.env['NODE_ENV'] !== 'production'
|
|
91
|
-
|
|
92
90
|
/**
|
|
93
91
|
* §2.3 rule 1. A section's `mobile: false` wins over every item inside it; otherwise the item's own
|
|
94
92
|
* field decides, with the shorthand booleans normalized and `undefined` meaning "reachable, but not
|
|
@@ -351,7 +349,7 @@ function buildCandidates(
|
|
|
351
349
|
/** Rule 11 — `mobile: 'tab'` on a disabled destination is a configuration mistake, not a slot. */
|
|
352
350
|
const tabbable = (item: SidebarItem): boolean => {
|
|
353
351
|
if (!item.disabled) return true
|
|
354
|
-
if (
|
|
352
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
355
353
|
console.warn(
|
|
356
354
|
`[basalt] MobileNav: "${item.key}" asks for a bar slot but is disabled — a disabled destination never becomes a tab. It still renders, disabled, in the More surface.`,
|
|
357
355
|
)
|
|
@@ -373,7 +371,7 @@ function buildCandidates(
|
|
|
373
371
|
candidates.push(sectionCandidate(section, menuMax))
|
|
374
372
|
continue
|
|
375
373
|
}
|
|
376
|
-
if (
|
|
374
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
377
375
|
console.warn(
|
|
378
376
|
`[basalt] MobileNav: mobileNav.tabs names "${id}", which is neither a destination key nor a visible section label — ignoring it.`,
|
|
379
377
|
)
|
|
@@ -464,7 +462,7 @@ export function projectMobileNav(
|
|
|
464
462
|
const limit = needsMore ? maxTabs - 1 : maxTabs
|
|
465
463
|
const kept = candidates.slice(0, limit)
|
|
466
464
|
const dropped = candidates.slice(limit)
|
|
467
|
-
if (
|
|
465
|
+
if (process.env.NODE_ENV !== 'production' && dropped.length > 0) {
|
|
468
466
|
// Rule 13 wants the NAMES, not a tally: "2 candidates dropped" tells a consumer their bar is
|
|
469
467
|
// wrong, while the keys tell them which `mobile: 'tab'` to move or which section to reorder.
|
|
470
468
|
console.warn(
|
package/src/shell/page-bar.tsx
CHANGED
|
@@ -46,9 +46,6 @@ import asideClasses from './page-aside.module.css'
|
|
|
46
46
|
/** The custom property row 2's measured height is published on. */
|
|
47
47
|
export const PAGE_BAR_HEIGHT_VAR = '--basalt-page-bar-h'
|
|
48
48
|
|
|
49
|
-
/** Dev-only diagnostics, constant-folded out of a production bundle — see {@link useAsidePanelSlot}. */
|
|
50
|
-
const DEV = process.env['NODE_ENV'] !== 'production'
|
|
51
|
-
|
|
52
49
|
/**
|
|
53
50
|
* What a `PageAside` publishes when it projects itself into row 2 below `sm`
|
|
54
51
|
* (`docs/ASIDE-SPEC.md` §0 "Desktop and mobile are one declaration"). Metadata only — the aside's
|
|
@@ -236,7 +233,7 @@ export function useAsidePanelSlot(): {
|
|
|
236
233
|
setSettled(true)
|
|
237
234
|
}, [])
|
|
238
235
|
useEffect(() => {
|
|
239
|
-
if (
|
|
236
|
+
if (process.env.NODE_ENV === 'production' || !settled || !inShell || panelHost) return
|
|
240
237
|
console.warn(
|
|
241
238
|
"[basalt] PageAside: this page's PageBar renders no row 2 (no `tabs`, no `filters`, no `filtersEnd`), so below `sm` there is no pill to open the aside — it renders in flow at the bottom of the page instead. Give the bar a row 2, or expect the in-flow form on a phone.",
|
|
242
239
|
)
|
package/src/surfaces.ts
CHANGED
|
@@ -355,6 +355,8 @@ export const SURFACES = {
|
|
|
355
355
|
'inline-font-size',
|
|
356
356
|
'css-raw-surface',
|
|
357
357
|
'surface-shadow-override',
|
|
358
|
+
// A `var(--vx-*)` naming a token basalt no longer (or never) emits.
|
|
359
|
+
'unknown-vx-token',
|
|
358
360
|
// Not a token kind — the kind that judges the ANNOTATION rather than the code. It lands here
|
|
359
361
|
// because `agent/rules/basalt-tokens.md` is where the `theme-allow` grammar is documented
|
|
360
362
|
// (said once, on purpose), so this is the header that must name its enforcement.
|
package/src/tokens/index.ts
CHANGED
|
@@ -54,7 +54,12 @@ export type { SpaceValues } from './palette'
|
|
|
54
54
|
|
|
55
55
|
// The two responsive tables — MIGRATING recipes and the raw-breakpoint guard messages send
|
|
56
56
|
// consumers here for them; `migrating-imports.test.ts` pins that every documented import resolves.
|
|
57
|
-
export {
|
|
57
|
+
export {
|
|
58
|
+
CONTAINER_CLASSES,
|
|
59
|
+
CONTAINER_GRID_BREAKPOINTS,
|
|
60
|
+
resolveContainerClass,
|
|
61
|
+
SIZE_CLASSES,
|
|
62
|
+
} from './size-classes'
|
|
58
63
|
export type { ContainerClass } from './size-classes'
|
|
59
64
|
|
|
60
65
|
/** A per-theme color pair: a hue keeps its identity but shifts shade across schemes. */
|
|
@@ -14,6 +14,20 @@ export const CONTAINER_CLASSES = { micro: 0, compact: 240, regular: 480, wide: 8
|
|
|
14
14
|
|
|
15
15
|
export type ContainerClass = keyof typeof CONTAINER_CLASSES
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* `CONTAINER_CLASSES` as the five-key map `<Grid type="container" breakpoints={…}>` takes (Mantine
|
|
19
|
+
* types it `Record<MantineSize, string>`), so `Grid.Col` keys resolve onto the grid's own width:
|
|
20
|
+
* `xs` opens compact, `sm` regular, `md`/`lg`/`xl` wide. `basalt/raw-breakpoint` trusts this import
|
|
21
|
+
* by name (from `basalt-ui/tokens`) — any other imported map is unreadable to it and flagged.
|
|
22
|
+
*/
|
|
23
|
+
export const CONTAINER_GRID_BREAKPOINTS = {
|
|
24
|
+
xs: `${CONTAINER_CLASSES.compact}px`,
|
|
25
|
+
sm: `${CONTAINER_CLASSES.regular}px`,
|
|
26
|
+
md: `${CONTAINER_CLASSES.wide}px`,
|
|
27
|
+
lg: `${CONTAINER_CLASSES.wide}px`,
|
|
28
|
+
xl: `${CONTAINER_CLASSES.wide}px`,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
17
31
|
/** The class a MEASURED inline size falls in (`px` is a min-width boundary, so 240 is `compact`). */
|
|
18
32
|
export function resolveContainerClass(px: number): ContainerClass {
|
|
19
33
|
if (px >= CONTAINER_CLASSES.wide) return 'wide'
|
package/dist/common/is-dev.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"is-dev.d.ts","sourceRoot":"","sources":["../../src/common/is-dev.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,wBAAgB,KAAK,IAAI,OAAO,CAE/B"}
|
package/dist/common/is-dev.js
DELETED
package/src/common/is-dev.ts
DELETED