basalt-ui 1.31.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 +34 -0
- package/MIGRATING.md +212 -20
- package/agent/rules/basalt-mantine.md +4 -4
- package/configs/oxlint-plugin.js +240 -59
- package/configs/oxlint-plugin.test.ts +329 -1
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +18 -66
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +5 -26
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +10 -68
- package/dist/charts/kinds/band-chrome.d.ts +31 -0
- package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
- package/dist/charts/kinds/band-chrome.js +71 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +49 -76
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +5 -23
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +23 -40
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +5 -133
- package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +16 -1
- package/dist/charts/primitives/HoverOverlay.d.ts +24 -9
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +17 -1
- package/dist/charts/primitives/chart-card-context.d.ts +17 -13
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -1
- package/dist/charts/primitives/chart-card-context.js +10 -5
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +25 -31
- package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
- package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
- package/dist/charts/primitives/legend-disclosure.js +153 -0
- package/dist/controls/controls.module.css +5 -0
- package/dist/controls/sync-button.module.css +1 -0
- package/dist/dashboard/stat-group.module.css +19 -64
- package/dist/dashboard/widget-grid.module.css +11 -38
- package/dist/data/data-table-state.d.ts +83 -0
- package/dist/data/data-table-state.d.ts.map +1 -0
- package/dist/data/data-table-state.js +139 -0
- package/dist/data/data-table.d.ts +40 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +179 -277
- package/dist/forms/form-layout.d.ts +6 -1
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.module.css +6 -13
- package/dist/guard/index.d.ts +2 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +10 -14
- package/dist/notifications/center.d.ts.map +1 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +12 -10
- package/dist/shell/aside-docking.d.ts +31 -0
- package/dist/shell/aside-docking.d.ts.map +1 -0
- package/dist/shell/aside-docking.js +19 -0
- package/dist/shell/index.d.ts +0 -11
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +13 -14
- package/dist/shell/page-bar.d.ts +24 -0
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/use-breakpoint.d.ts +1 -1
- package/dist/theme/floating.module.css +1 -0
- package/dist/theme-lab/derive-controls.d.ts.map +1 -1
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +4 -0
- package/llms.txt +2 -2
- package/package.json +2 -1
- package/src/charts/kinds/BandStrip.tsx +18 -64
- package/src/charts/kinds/DualPanel.tsx +4 -24
- package/src/charts/kinds/MirroredBars.tsx +12 -66
- package/src/charts/kinds/band-chrome.tsx +91 -0
- package/src/charts/primitives/CartesianChart.tsx +79 -122
- package/src/charts/primitives/ChartCard.tsx +5 -27
- package/src/charts/primitives/ChartFrame.tsx +37 -76
- package/src/charts/primitives/ChartLegend.tsx +7 -154
- package/src/charts/primitives/ChartTooltip.tsx +16 -0
- package/src/charts/primitives/HoverOverlay.tsx +52 -21
- package/src/charts/primitives/chart-card-context.ts +25 -16
- package/src/charts/primitives/chart-frame-layout.ts +44 -56
- package/src/charts/primitives/legend-disclosure.tsx +177 -0
- package/src/controls/controls.module.css +5 -0
- package/src/controls/sync-button.module.css +1 -0
- package/src/dashboard/stat-group.module.css +19 -64
- package/src/dashboard/widget-grid.module.css +11 -38
- package/src/data/data-table-state.ts +218 -0
- package/src/data/data-table.tsx +134 -218
- package/src/forms/form-layout.module.css +6 -13
- package/src/forms/form-layout.tsx +6 -1
- package/src/guard/index.ts +38 -34
- package/src/notifications/center.tsx +3 -0
- package/src/provider/index.tsx +18 -17
- package/src/shell/aside-docking.ts +54 -0
- package/src/shell/index.tsx +15 -37
- package/src/shell/page-bar.tsx +24 -0
- package/src/shell/use-breakpoint.ts +2 -2
- package/src/theme/floating.module.css +1 -0
- package/src/theme-lab/derive-controls.tsx +2 -0
- package/src/tokens/index.ts +5 -0
package/configs/oxlint-plugin.js
CHANGED
|
@@ -390,8 +390,12 @@ function hasFileDeclaration(context, ruleId) {
|
|
|
390
390
|
)
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/**
|
|
394
|
-
|
|
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))
|
|
@@ -2708,7 +2712,7 @@ const CONTROL_OUTSIDE_HOME_MESSAGE =
|
|
|
2708
2712
|
* `@mantine/form` (a form is the third home and its inputs are not filters), and the owner
|
|
2709
2713
|
* exemption — a file DEFINING a basalt control cannot be told to use one.
|
|
2710
2714
|
*/
|
|
2711
|
-
// Ships: warn (grace → 1.
|
|
2715
|
+
// Ships: warn (grace → 1.33.0)
|
|
2712
2716
|
const controlOutsideHome = {
|
|
2713
2717
|
meta: {
|
|
2714
2718
|
type: 'suggestion',
|
|
@@ -3656,8 +3660,9 @@ export const DEPRECATED_EXPORTS = [
|
|
|
3656
3660
|
{
|
|
3657
3661
|
subpath: 'basalt-ui',
|
|
3658
3662
|
name: 'useBreakpoint',
|
|
3659
|
-
replacement:
|
|
3660
|
-
|
|
3663
|
+
replacement:
|
|
3664
|
+
'useSizeClass() for shell chrome; a page’s own layout swap is a container decision — a type="container" SimpleGrid/Grid or an @container rule',
|
|
3665
|
+
removeIn: '1.33.0',
|
|
3661
3666
|
fix: false,
|
|
3662
3667
|
},
|
|
3663
3668
|
]
|
|
@@ -3959,37 +3964,144 @@ const RESPONSIVE_OBJECT_KEYS = new Set(['base', 'xs', 'sm', 'md', 'lg', 'xl'])
|
|
|
3959
3964
|
* object that merely happens to have a `sm` key among other, unrelated ones is not this shape, and
|
|
3960
3965
|
* an empty object is not a breakpoint decision at all.
|
|
3961
3966
|
*/
|
|
3962
|
-
function isResponsiveObjectValue(node) {
|
|
3967
|
+
function isResponsiveObjectValue(node, namedOnly = false) {
|
|
3963
3968
|
if (node === null || node === undefined || node.type !== 'ObjectExpression') return false
|
|
3964
3969
|
if (node.properties.length === 0) return false
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
+
const names = node.properties.map((prop) =>
|
|
3971
|
+
prop.type === 'Property' ? propertyKeyName(prop) : undefined,
|
|
3972
|
+
)
|
|
3973
|
+
if (!names.every((name) => typeof name === 'string' && RESPONSIVE_OBJECT_KEYS.has(name)))
|
|
3974
|
+
return false
|
|
3975
|
+
// Under `type="container"` a lone `base` is the unconditional value, not a breakpoint decision.
|
|
3976
|
+
return !namedOnly || names.some((name) => name !== 'base')
|
|
3970
3977
|
}
|
|
3971
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
|
+
|
|
3972
3987
|
/**
|
|
3973
|
-
*
|
|
3974
|
-
*
|
|
3975
|
-
*
|
|
3976
|
-
*
|
|
3977
|
-
*
|
|
3978
|
-
* `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.
|
|
3979
3993
|
*/
|
|
3980
|
-
function
|
|
3981
|
-
const resolved =
|
|
3982
|
-
|
|
3983
|
-
if (resolved === null || resolved === undefined || resolved.type !== 'ObjectExpression')
|
|
3984
|
-
return false
|
|
3985
|
-
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 []
|
|
3986
3997
|
seen.add(resolved)
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
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
|
+
),
|
|
3990
4030
|
)
|
|
3991
4031
|
}
|
|
3992
4032
|
|
|
4033
|
+
/**
|
|
4034
|
+
* The props each Mantine grid routes through `@container` queries under `type="container"` —
|
|
4035
|
+
* confirmed in `@mantine/core` 9.3 source: `SimpleGridContainerVariables` reads `cols`/`spacing`/
|
|
4036
|
+
* `verticalSpacing`, `GridVariables` reads `gap`/`rowGap`/`columnGap`, `GridColVariables` reads
|
|
4037
|
+
* `span`/`offset`/`order`/`align`. Every OTHER responsive prop on the same element (`p`, `mt`,
|
|
4038
|
+
* `mih`, …) is a Box style prop and still compiles to viewport `@media`, so the container exemption
|
|
4039
|
+
* stops at this set. `Grid`'s own `breakpoints` is the container scale itself, not a viewport key.
|
|
4040
|
+
*/
|
|
4041
|
+
const CONTAINER_ROUTED_PROPS = {
|
|
4042
|
+
SimpleGrid: new Set(['cols', 'spacing', 'verticalSpacing']),
|
|
4043
|
+
Grid: new Set(['gap', 'rowGap', 'columnGap', 'breakpoints']),
|
|
4044
|
+
'Grid.Col': new Set(['span', 'offset', 'order', 'align']),
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
/**
|
|
4048
|
+
* How Mantine 9 resolves a responsive object on this element's grid, read from `@mantine/core`
|
|
4049
|
+
* 9.3 source — the sanctioned answer to this rule, so it has to be exempt exactly where it works:
|
|
4050
|
+
*
|
|
4051
|
+
* - `'container'` — a `<Grid type="container" breakpoints={…}>` (or a `Grid.Col` inside one) whose
|
|
4052
|
+
* map statically resolves to a non-empty object. Keys resolve through that map into
|
|
4053
|
+
* `@container mantine-grid` queries, so an `sm` key is whatever width the map gives it: exempt.
|
|
4054
|
+
* Mantine types the map as `Record<MantineSize, string>`, so the recipe's keys ARE `xs`–`xl` —
|
|
4055
|
+
* exempting by key name instead of by owner would flag the sanctioned answer itself.
|
|
4056
|
+
* - `'simple-grid'` — `<SimpleGrid type="container">`. `SimpleGridContainerVariables` writes each
|
|
4057
|
+
* key VERBATIM as `@container simple-grid (min-width: <key>)` and never consults
|
|
4058
|
+
* `theme.breakpoints`: a `'480px'` key works (and is not a responsive-object key, so it never
|
|
4059
|
+
* reaches here), while `sm` emits `(min-width: sm)` — invalid CSS, a dead query. Flagged.
|
|
4060
|
+
* - `'fallback'` — `<Grid type="container">` whose `breakpoints` is absent, `undefined`, `{}` or not
|
|
4061
|
+
* statically resolvable (or a `Grid.Col` in one). `Grid` takes its container branch only on a
|
|
4062
|
+
* truthy map, and an empty one emits no query at all — so none of these is the container law,
|
|
4063
|
+
* and an unreadable map is reported rather than trusted. Flagged.
|
|
4064
|
+
* - `undefined` — not a container grid; the ordinary viewport law applies.
|
|
4065
|
+
*
|
|
4066
|
+
* `type` has to be the string literal `"container"`; a dynamic value is not a decision this rule
|
|
4067
|
+
* can read. `Grid.Col` takes its grid's mode by walking to the nearest enclosing `Grid` element.
|
|
4068
|
+
* `consts` is the rule's same-file name→object map, so `breakpoints={map}` resolves like any value.
|
|
4069
|
+
*/
|
|
4070
|
+
function containerGridMode(opening, mantineImports, consts) {
|
|
4071
|
+
let tag = resolveMantineTag(opening.name, mantineImports)
|
|
4072
|
+
let grid = opening
|
|
4073
|
+
if (tag === 'Grid.Col') {
|
|
4074
|
+
grid = undefined
|
|
4075
|
+
let node = opening.parent?.parent
|
|
4076
|
+
for (let depth = 0; node !== null && node !== undefined; depth++, node = node.parent) {
|
|
4077
|
+
if (depth > ANCESTRY_MAX_DEPTH || node.type === 'Program') break
|
|
4078
|
+
if (node.type !== 'JSXElement') continue
|
|
4079
|
+
if (resolveMantineTag(node.openingElement.name, mantineImports) !== 'Grid') continue
|
|
4080
|
+
grid = node.openingElement
|
|
4081
|
+
tag = 'Grid'
|
|
4082
|
+
break
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
if (grid === undefined || (tag !== 'Grid' && tag !== 'SimpleGrid')) return undefined
|
|
4086
|
+
const attr = (name) =>
|
|
4087
|
+
grid.attributes.find((a) => a.type === 'JSXAttribute' && a.name?.name === name)
|
|
4088
|
+
const type = attr('type')
|
|
4089
|
+
if (
|
|
4090
|
+
type === undefined ||
|
|
4091
|
+
!isStringLiteral(attrValue(type)) ||
|
|
4092
|
+
attrValue(type).value !== 'container'
|
|
4093
|
+
)
|
|
4094
|
+
return undefined
|
|
4095
|
+
if (tag === 'SimpleGrid') return 'simple-grid'
|
|
4096
|
+
const breakpoints = attr('breakpoints')
|
|
4097
|
+
if (breakpoints === undefined) return 'fallback'
|
|
4098
|
+
let map = attrValue(breakpoints)
|
|
4099
|
+
if (map?.type === 'Identifier') map = consts.get(map.name)
|
|
4100
|
+
const nonEmpty =
|
|
4101
|
+
map?.type === 'ObjectExpression' && map.properties.some((prop) => prop.type === 'Property')
|
|
4102
|
+
return nonEmpty ? 'container' : 'fallback'
|
|
4103
|
+
}
|
|
4104
|
+
|
|
3993
4105
|
/**
|
|
3994
4106
|
* The shell components that are `visibleFrom`/`hiddenFrom`'s legitimate home — the size-class
|
|
3995
4107
|
* axis's one JSX escape hatch, alongside overlays. Matched by DECLARATION name, the same
|
|
@@ -4011,27 +4123,72 @@ const SHELL_HOME_NAMES = new Set(['BasaltShell', 'AppSidebar', 'MobileNav', 'App
|
|
|
4011
4123
|
*/
|
|
4012
4124
|
const RAW_BREAKPOINT_EXEMPT_FILE = /(?:^|[\\/])(?:use-media-query\.ts|ChartTooltip\.tsx)$/
|
|
4013
4125
|
|
|
4126
|
+
/**
|
|
4127
|
+
* The one recipe every raw-breakpoint message points at. A page's own layout keys on its CONTAINER
|
|
4128
|
+
* (`CONTAINER_CLASSES`, `basalt-ui/tokens`: compact 240 / regular 480 / wide 800), never on the
|
|
4129
|
+
* viewport — `useSizeClass()` is the shell's seam, not a page's. Mantine 9's own `type="container"`
|
|
4130
|
+
* grids are the primitive: both self-wrap in an `inline-size` container, so they need no ancestor.
|
|
4131
|
+
* The px literals are `CONTAINER_CLASSES` written out (the plugin cannot import TS source);
|
|
4132
|
+
* `oxlint-plugin.test.ts` asserts every one against it, and the MIGRATING recipe too.
|
|
4133
|
+
*/
|
|
4134
|
+
export const RAW_BREAKPOINT_RECIPE =
|
|
4135
|
+
'A page’s own layout keys on its container: <SimpleGrid type="container" cols={{ base: 1, ' +
|
|
4136
|
+
"'480px': 2, '800px': 3 }}> (px keys = CONTAINER_CLASSES, basalt-ui/tokens), or <Grid " +
|
|
4137
|
+
'type="container" breakpoints={…}> mapping all five xs–xl onto those px widths, with Grid.Col ' +
|
|
4138
|
+
'span={{ base: 12, sm: 6 }} keyed through that map; a Flex direction swap becomes that SimpleGrid (equal ' +
|
|
4139
|
+
'columns), a wrapping Group, or a CSS-module @container basalt-grid rule. useSizeClass() is for ' +
|
|
4140
|
+
'shell chrome only. Recipes: basalt-ui MIGRATING.md § basalt/raw-breakpoint.'
|
|
4141
|
+
|
|
4014
4142
|
const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
|
|
4015
|
-
'Responsive-object
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4143
|
+
'Responsive-object prop keyed on theme breakpoints — those are viewport widths. ' +
|
|
4144
|
+
`${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
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
|
+
|
|
4173
|
+
const RAW_BREAKPOINT_CONTAINER_KEY_MESSAGE =
|
|
4174
|
+
'Theme breakpoint name under type="container" — Mantine does not resolve it there: ' +
|
|
4175
|
+
"SimpleGrid uses each key verbatim as a (min-width: …) length, so 'sm' is a dead query, " +
|
|
4176
|
+
'and Grid only goes container with a breakpoints map (without one it silently falls back to ' +
|
|
4177
|
+
`viewport media queries). ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4019
4178
|
|
|
4020
4179
|
const RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE =
|
|
4021
|
-
"visibleFrom/hiddenFrom outside a shell home or a control's own C9 swap —
|
|
4022
|
-
|
|
4023
|
-
"the framework cannot see or move. Use a container query for a component's own width instead. " +
|
|
4024
|
-
'(basalt/raw-breakpoint)'
|
|
4180
|
+
"visibleFrom/hiddenFrom outside a shell home or a control's own C9 swap — a viewport " +
|
|
4181
|
+
`breakpoint the framework cannot see or move. ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4025
4182
|
|
|
4026
4183
|
const RAW_BREAKPOINT_HOOK_MESSAGE =
|
|
4027
|
-
"useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's
|
|
4028
|
-
'
|
|
4029
|
-
|
|
4184
|
+
"useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's " +
|
|
4185
|
+
'one seam. Shell chrome reads useSizeClass(); a page swapping its own layout (a table for a card ' +
|
|
4186
|
+
`list, a column count) is a container decision. ${RAW_BREAKPOINT_POINTER_HINT} ` +
|
|
4187
|
+
`${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4030
4188
|
|
|
4031
4189
|
const RAW_BREAKPOINT_GLOBAL_MESSAGE =
|
|
4032
|
-
'window.matchMedia/window.innerWidth read directly — the
|
|
4033
|
-
|
|
4034
|
-
'(basalt/raw-breakpoint)'
|
|
4190
|
+
'window.matchMedia/window.innerWidth read directly — the viewport read useSizeClass() owns ' +
|
|
4191
|
+
`for shell chrome. ${RAW_BREAKPOINT_POINTER_HINT} ${RAW_BREAKPOINT_RECIPE} (basalt/raw-breakpoint)`
|
|
4035
4192
|
|
|
4036
4193
|
/** The three viewport hooks the law names, whatever module they are imported from. */
|
|
4037
4194
|
const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useViewportSize'])
|
|
@@ -4047,7 +4204,7 @@ const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useVi
|
|
|
4047
4204
|
* declares a shell home (or a control's own C9 swap) is exempt for all four shapes uniformly — the
|
|
4048
4205
|
* shell's own responsive chrome is the sanctioned use, whatever shape it takes.
|
|
4049
4206
|
*/
|
|
4050
|
-
// Ships: warn (grace → 1.
|
|
4207
|
+
// Ships: warn (grace → 1.33.0)
|
|
4051
4208
|
const rawBreakpoint = {
|
|
4052
4209
|
meta: {
|
|
4053
4210
|
type: 'suggestion',
|
|
@@ -4056,7 +4213,8 @@ const rawBreakpoint = {
|
|
|
4056
4213
|
'Disallow a hand-rolled viewport breakpoint — a responsive-object Mantine prop, ' +
|
|
4057
4214
|
'visibleFrom/hiddenFrom outside a shell home, useMediaQuery/useMatches/useViewportSize, or ' +
|
|
4058
4215
|
'a raw window.matchMedia/innerWidth read. All four shapes are exempt inside a file that ' +
|
|
4059
|
-
'declares a shell-home component (or a control implementing its own C9 swap)
|
|
4216
|
+
'declares a shell-home component (or a control implementing its own C9 swap); a ' +
|
|
4217
|
+
'responsive object on a Mantine type="container" grid is exempt where Mantine resolves it.',
|
|
4060
4218
|
},
|
|
4061
4219
|
schema: [],
|
|
4062
4220
|
},
|
|
@@ -4073,8 +4231,9 @@ const rawBreakpoint = {
|
|
|
4073
4231
|
let definesShellHome = false
|
|
4074
4232
|
const visibleHiddenCandidates = []
|
|
4075
4233
|
const responsiveObjectCandidates = []
|
|
4076
|
-
// Same-file `const NAME = { base, sm }` bindings
|
|
4077
|
-
//
|
|
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.
|
|
4078
4237
|
// A name declared more than once in the file is ambiguous (its two bindings cannot be told
|
|
4079
4238
|
// apart), so it is dropped from the map entirely rather than guessed at last-wins.
|
|
4080
4239
|
const responsiveObjectConsts = new Map()
|
|
@@ -4122,7 +4281,7 @@ const rawBreakpoint = {
|
|
|
4122
4281
|
return
|
|
4123
4282
|
}
|
|
4124
4283
|
seenDeclaratorNames.add(name)
|
|
4125
|
-
if (node.init?.type
|
|
4284
|
+
if (RESOLVABLE_INIT_TYPES.has(node.init?.type)) responsiveObjectConsts.set(name, node.init)
|
|
4126
4285
|
},
|
|
4127
4286
|
JSXAttribute(node) {
|
|
4128
4287
|
const name = node.name?.name
|
|
@@ -4153,12 +4312,27 @@ const rawBreakpoint = {
|
|
|
4153
4312
|
if (definesShellHome || controlOwner.isOwner()) return
|
|
4154
4313
|
for (const node of responsiveObjectCandidates) {
|
|
4155
4314
|
const value = unwrapExpressionContainer(node.value)
|
|
4156
|
-
if (!containsResponsiveObjectValue(value, responsiveObjectConsts)) continue
|
|
4157
4315
|
const owner = node.parent
|
|
4158
4316
|
if (owner === null || owner === undefined || owner.type !== 'JSXOpeningElement') continue
|
|
4159
|
-
|
|
4317
|
+
const tag = resolveMantineTag(owner.name, mantineImports)
|
|
4318
|
+
if (tag === undefined) continue
|
|
4319
|
+
// The container law reaches only the props Mantine routes through the grid's container
|
|
4320
|
+
// queries; a `p={{ base, lg }}` on the same element is still a viewport @media.
|
|
4321
|
+
const routed = Object.hasOwn(CONTAINER_ROUTED_PROPS, tag)
|
|
4322
|
+
? CONTAINER_ROUTED_PROPS[tag]
|
|
4323
|
+
: undefined
|
|
4324
|
+
const mode = routed?.has(node.name.name)
|
|
4325
|
+
? containerGridMode(owner, mantineImports, responsiveObjectConsts)
|
|
4326
|
+
: undefined
|
|
4327
|
+
if (mode === 'container') continue
|
|
4328
|
+
if (!containsResponsiveObjectValue(value, responsiveObjectConsts, mode !== undefined))
|
|
4329
|
+
continue
|
|
4160
4330
|
if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
|
|
4161
|
-
|
|
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
|
|
4335
|
+
context.report({ node, message })
|
|
4162
4336
|
}
|
|
4163
4337
|
for (const node of visibleHiddenCandidates) {
|
|
4164
4338
|
if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
|
|
@@ -4202,8 +4376,9 @@ const rawBreakpoint = {
|
|
|
4202
4376
|
export const PLUGIN_RULE_GRACE = {
|
|
4203
4377
|
'control-outside-home': {
|
|
4204
4378
|
since: '1.26.0',
|
|
4205
|
-
promote: '1.
|
|
4379
|
+
promote: '1.33.0',
|
|
4206
4380
|
why:
|
|
4381
|
+
"Extended 1.32.0 -> 1.33.0 at the 1.32.0 release, the last extension: basalt's own tree is at 0 (consumer-loop r1 dogfood) and argo is at 2, but RE-MEASURED 2026-09-30 the fleet carries ~33 (image-gen 11, rb 7, image-share 6, weatherorb 5, email-gateway 4, argo 2). The 1.32.0 fleet upgrade clears them while they warn, together with raw-breakpoint, so 1.33.0 promotes all four ids against a measured-clean fleet. " +
|
|
4207
4382
|
'Extended 1.31.0 -> 1.32.0 at the 1.31.0 release: the basalt tree still carries incumbents (data-table, notifications center, theme-lab) and the argo sweep has not run. ' +
|
|
4208
4383
|
'the wave-6 control guards (docs/CONTROLS-SPEC.md §6). The one openly HEURISTIC rule of the ' +
|
|
4209
4384
|
'set — "this control has no home" is a claim about layout intent, so its false-positive load ' +
|
|
@@ -4232,17 +4407,23 @@ export const PLUGIN_RULE_GRACE = {
|
|
|
4232
4407
|
},
|
|
4233
4408
|
'raw-breakpoint': {
|
|
4234
4409
|
since: '1.31.0',
|
|
4235
|
-
promote: '1.
|
|
4410
|
+
promote: '1.33.0',
|
|
4236
4411
|
why:
|
|
4237
|
-
'
|
|
4238
|
-
'
|
|
4239
|
-
'
|
|
4240
|
-
'
|
|
4241
|
-
"
|
|
4242
|
-
"
|
|
4243
|
-
'
|
|
4244
|
-
'1.32.0
|
|
4245
|
-
'
|
|
4412
|
+
'New in the wave-11 responsive/touch guards. Catches four independent shapes at once — a ' +
|
|
4413
|
+
'responsive-object Mantine prop, visibleFrom/hiddenFrom outside the three shell homes, the ' +
|
|
4414
|
+
'three raw viewport hooks, and window.matchMedia/innerWidth — none of which basalt policed ' +
|
|
4415
|
+
'before this wave, so every consumer on an earlier minor has a green build with all four ' +
|
|
4416
|
+
"shapes already in it. basalt-ui/content's own article-card.tsx (a `SimpleGrid cols={{ base: " +
|
|
4417
|
+
"1, sm: 2, lg }}`) was one incumbent measured in basalt's own tree at ship time — a real " +
|
|
4418
|
+
'number, not zero, so it shipped warn rather than error. ' +
|
|
4419
|
+
'Extended 1.32.0 -> 1.33.0: 1.31.0 shipped the rule naming "a container query" but no ' +
|
|
4420
|
+
'primitive, and MIGRATING never mentioned it (argo: 42 warns, no documented target). 1.32.0 ' +
|
|
4421
|
+
'is the first minor with a reachable, documented answer (Mantine type="container" grids keyed ' +
|
|
4422
|
+
'on CONTAINER_CLASSES, exempted here), and promoting in the minor that first makes a rule ' +
|
|
4423
|
+
'satisfiable hands a consumer zero minors to act on it — the control-outside-home ' +
|
|
4424
|
+
"extension's reasoning. 1.33.0 is when the incumbents are expected to have moved to a " +
|
|
4425
|
+
'container grid or a theme-allow with a stated reason. Promotes together with its CSS twin, ' +
|
|
4426
|
+
'the `raw-media-query` guard kind.',
|
|
4246
4427
|
},
|
|
4247
4428
|
}
|
|
4248
4429
|
|