basalt-ui 1.30.2 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +31 -0
- package/MIGRATING.md +172 -12
- package/agent/rules/basalt-controls.md +19 -19
- package/agent/rules/basalt-mantine.md +1 -1
- package/configs/oxlint-plugin.js +254 -4
- package/configs/oxlint-plugin.test.ts +145 -2
- package/configs/oxlint.json +2 -1
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +41 -43
- package/dist/agent-chat/thread-workspace.d.ts +2 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +3 -4
- package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
- package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
- package/dist/agent-chat/use-keyboard-inset.js +34 -0
- package/dist/charts/cursor/touch-pin.d.ts +45 -0
- package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
- package/dist/charts/cursor/touch-pin.js +79 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
- package/dist/charts/hooks/use-discrete-cursor.js +198 -0
- package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
- package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
- package/dist/charts/hooks/use-hysteresis.js +12 -0
- package/dist/charts/hooks/useBandPlot.d.ts +7 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
- package/dist/charts/hooks/useBandPlot.js +62 -18
- package/dist/charts/hooks/useChartCursor.d.ts +40 -11
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +102 -11
- package/dist/charts/index.d.ts +0 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +0 -10
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +54 -41
- package/dist/charts/kinds/Donut.d.ts +9 -3
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +110 -48
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +145 -131
- package/dist/charts/kinds/Heatmap.d.ts +7 -0
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +37 -16
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +83 -68
- package/dist/charts/kinds/donut-layout.d.ts +47 -0
- package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
- package/dist/charts/kinds/donut-layout.js +52 -0
- package/dist/charts/layout/auto-margin.d.ts +7 -2
- package/dist/charts/layout/auto-margin.d.ts.map +1 -1
- package/dist/charts/layout/auto-margin.js +12 -3
- package/dist/charts/layout/end-labels.d.ts +64 -0
- package/dist/charts/layout/end-labels.d.ts.map +1 -0
- package/dist/charts/layout/end-labels.js +76 -0
- package/dist/charts/primitives/Axes.d.ts +32 -4
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +62 -16
- package/dist/charts/primitives/CartesianChart.d.ts +7 -6
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +271 -125
- package/dist/charts/primitives/ChartCard.d.ts +0 -14
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +74 -21
- package/dist/charts/primitives/ChartFrame.d.ts +28 -22
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +133 -34
- package/dist/charts/primitives/ChartLegend.d.ts +19 -15
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +212 -71
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +4 -3
- package/dist/charts/primitives/Crosshair.js +2 -2
- package/dist/charts/primitives/HoverOverlay.d.ts +6 -1
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +37 -26
- package/dist/charts/primitives/HoverOverlay.module.css +13 -0
- package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/dist/charts/primitives/chart-card-context.d.ts +29 -0
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
- package/dist/charts/primitives/chart-card-context.js +13 -0
- package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +76 -29
- package/dist/charts/primitives/chart-layout.d.ts +112 -0
- package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
- package/dist/charts/primitives/chart-layout.js +175 -0
- package/dist/charts/primitives/chart-tier.d.ts +13 -11
- package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
- package/dist/charts/primitives/chart-tier.js +42 -13
- package/dist/charts/series.d.ts +4 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/utils/ticks.d.ts +27 -8
- package/dist/charts/utils/ticks.d.ts.map +1 -1
- package/dist/charts/utils/ticks.js +35 -11
- package/dist/common/errors.d.ts +11 -1
- package/dist/common/errors.d.ts.map +1 -1
- package/dist/common/errors.js +16 -2
- package/dist/common/hit-floor.d.ts +10 -0
- package/dist/common/hit-floor.d.ts.map +1 -0
- package/dist/common/hit-floor.js +6 -0
- package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
- package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/common/use-measured-widths.d.ts +37 -0
- package/dist/common/use-measured-widths.d.ts.map +1 -0
- package/dist/common/use-measured-widths.js +51 -0
- package/dist/common/use-media-query.d.ts +6 -0
- package/dist/common/use-media-query.d.ts.map +1 -0
- package/dist/common/use-media-query.js +40 -0
- package/dist/common/validate.d.ts +9 -0
- package/dist/common/validate.d.ts.map +1 -1
- package/dist/common/validate.js +9 -4
- package/dist/content/article-layout.d.ts +4 -2
- package/dist/content/article-layout.d.ts.map +1 -1
- package/dist/content/article-layout.js +43 -3
- package/dist/content/article-layout.module.css +45 -4
- package/dist/content/article-layout.module.css.d.ts +3 -0
- package/dist/content/code-block.d.ts.map +1 -1
- package/dist/content/code-block.js +40 -2
- package/dist/content/code-block.module.css +32 -2
- package/dist/content/prose.module.css +13 -8
- package/dist/controls/actions.d.ts +29 -6
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/actions.js +158 -27
- package/dist/controls/actions.module.css +14 -1
- package/dist/controls/actions.module.css.d.ts +1 -0
- package/dist/controls/controls.module.css +8 -31
- package/dist/controls/enum-filter.js +2 -2
- package/dist/controls/filter-context.d.ts +10 -0
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-context.js +2 -0
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +1 -0
- package/dist/controls/index.d.ts +1 -1
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +0 -2
- package/dist/controls/multi-select-filter.js +2 -2
- package/dist/controls/sync-button.module.css +1 -1
- package/dist/dashboard/section.d.ts.map +1 -1
- package/dist/dashboard/section.js +1 -0
- package/dist/dashboard/section.module.css +1 -26
- package/dist/dashboard/stat-card.d.ts +1 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +4 -1
- package/dist/dashboard/stat-card.module.css +6 -9
- package/dist/dashboard/stat-group.module.css +16 -4
- package/dist/dashboard/widget-grid.d.ts.map +1 -1
- package/dist/dashboard/widget-header.d.ts +14 -4
- package/dist/dashboard/widget-header.d.ts.map +1 -1
- package/dist/dashboard/widget-header.js +23 -6
- package/dist/dashboard/widget-header.module.css +145 -133
- package/dist/dashboard/widget-header.module.css.d.ts +2 -0
- package/dist/data/data-table.d.ts +23 -0
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +339 -141
- package/dist/data/data-table.module.css +94 -0
- package/dist/data/data-table.module.css.d.ts +11 -0
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual-list.js +10 -0
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.js +3 -3
- package/dist/forms/form-layout.module.css +30 -5
- package/dist/forms/form-layout.module.css.d.ts +1 -0
- package/dist/guard/index.d.ts +8 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +67 -2
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/provider/index.d.ts +20 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +22 -2
- package/dist/shell/app-brand.d.ts.map +1 -1
- package/dist/shell/app-brand.js +1 -0
- package/dist/shell/app-brand.module.css +2 -2
- package/dist/shell/app-header.module.css +18 -8
- package/dist/shell/app-main.module.css +54 -3
- package/dist/shell/app-main.module.css.d.ts +1 -0
- package/dist/shell/app-mobile-nav.module.css +16 -4
- package/dist/shell/app-sidebar.module.css +4 -6
- package/dist/shell/index.d.ts +12 -0
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +57 -16
- package/dist/shell/mobile-nav-model.d.ts +1 -1
- package/dist/shell/page-aside.d.ts +22 -3
- package/dist/shell/page-aside.d.ts.map +1 -1
- package/dist/shell/page-aside.js +27 -13
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +10 -5
- package/dist/shell/page-bar.module.css +10 -34
- package/dist/shell/use-breakpoint.d.ts +3 -4
- package/dist/shell/use-breakpoint.d.ts.map +1 -1
- package/dist/shell/use-breakpoint.js +3 -25
- package/dist/shell/use-size-class.d.ts +6 -0
- package/dist/shell/use-size-class.d.ts.map +1 -0
- package/dist/shell/use-size-class.js +19 -0
- package/dist/state/persisted-or-local.d.ts +4 -1
- package/dist/state/persisted-or-local.d.ts.map +1 -1
- package/dist/state/persisted-or-local.js +4 -3
- package/dist/state/persisted.d.ts +10 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +44 -14
- package/dist/styles.css +41 -0
- package/dist/surfaces.d.ts +4 -4
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +11 -3
- package/dist/theme/controls.module.css +7 -0
- package/dist/theme/ctl-theme.d.ts +1 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -1
- package/dist/theme/ctl-theme.js +12 -4
- package/dist/theme/floating.module.css +36 -0
- package/dist/theme/floating.module.css.d.ts +3 -0
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +34 -5
- package/dist/theme/nav-link.module.css +7 -0
- package/dist/tokens/index.d.ts +7 -16
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +23 -28
- package/dist/tokens/palette.d.ts +5 -27
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +8 -30
- package/dist/tokens/size-classes.d.ts +40 -0
- package/dist/tokens/size-classes.d.ts.map +1 -0
- package/dist/tokens/size-classes.js +29 -0
- package/dist/tokens.css +8 -4
- package/llms.txt +3 -3
- package/package.json +17 -17
- package/src/agent-chat/composer.tsx +74 -47
- package/src/agent-chat/thread-workspace.tsx +8 -11
- package/src/agent-chat/use-keyboard-inset.ts +68 -0
- package/src/charts/cursor/touch-pin.ts +138 -0
- package/src/charts/hooks/use-discrete-cursor.ts +335 -0
- package/src/charts/hooks/use-hysteresis.ts +26 -0
- package/src/charts/hooks/useBandPlot.ts +90 -27
- package/src/charts/hooks/useChartCursor.ts +211 -26
- package/src/charts/index.ts +0 -8
- package/src/charts/kinds/BandStrip.tsx +11 -1
- package/src/charts/kinds/Donut.tsx +153 -62
- package/src/charts/kinds/DualPanel.tsx +19 -4
- package/src/charts/kinds/Heatmap.tsx +61 -22
- package/src/charts/kinds/MirroredBars.tsx +15 -3
- package/src/charts/kinds/donut-layout.ts +82 -0
- package/src/charts/layout/auto-margin.ts +24 -5
- package/src/charts/layout/end-labels.ts +135 -0
- package/src/charts/primitives/Axes.tsx +112 -21
- package/src/charts/primitives/CartesianChart.tsx +282 -68
- package/src/charts/primitives/ChartCard.tsx +141 -48
- package/src/charts/primitives/ChartFrame.tsx +243 -60
- package/src/charts/primitives/ChartLegend.tsx +257 -73
- package/src/charts/primitives/ChartTooltip.tsx +8 -3
- package/src/charts/primitives/Crosshair.tsx +2 -2
- package/src/charts/primitives/HoverOverlay.module.css +13 -0
- package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/src/charts/primitives/HoverOverlay.tsx +15 -4
- package/src/charts/primitives/chart-card-context.ts +37 -0
- package/src/charts/primitives/chart-frame-layout.ts +214 -177
- package/src/charts/primitives/chart-layout.ts +318 -0
- package/src/charts/primitives/chart-tier.tsx +66 -21
- package/src/charts/series.ts +4 -1
- package/src/charts/utils/ticks.ts +95 -19
- package/src/common/errors.ts +25 -1
- package/src/common/hit-floor.ts +9 -0
- package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
- package/src/common/use-measured-widths.ts +88 -0
- package/src/common/use-media-query.ts +56 -0
- package/src/common/validate.ts +17 -4
- package/src/content/article-layout.module.css +45 -4
- package/src/content/article-layout.module.css.d.ts +3 -0
- package/src/content/article-layout.tsx +60 -5
- package/src/content/code-block.module.css +32 -2
- package/src/content/code-block.tsx +47 -3
- package/src/content/prose.module.css +13 -8
- package/src/controls/actions.module.css +14 -1
- package/src/controls/actions.module.css.d.ts +1 -0
- package/src/controls/actions.tsx +223 -75
- package/src/controls/controls.module.css +8 -31
- package/src/controls/enum-filter.tsx +2 -2
- package/src/controls/filter-context.tsx +11 -0
- package/src/controls/filter-pill.tsx +1 -0
- package/src/controls/index.ts +0 -1
- package/src/controls/multi-select-filter.tsx +2 -2
- package/src/controls/sync-button.module.css +1 -1
- package/src/dashboard/section.module.css +1 -26
- package/src/dashboard/section.tsx +1 -0
- package/src/dashboard/stat-card.module.css +6 -9
- package/src/dashboard/stat-card.tsx +6 -3
- package/src/dashboard/stat-group.module.css +16 -4
- package/src/dashboard/widget-grid.tsx +7 -6
- package/src/dashboard/widget-header.module.css +145 -133
- package/src/dashboard/widget-header.module.css.d.ts +2 -0
- package/src/dashboard/widget-header.tsx +45 -11
- package/src/data/data-table.module.css +94 -0
- package/src/data/data-table.module.css.d.ts +11 -0
- package/src/data/data-table.tsx +503 -164
- package/src/data/virtual-list.tsx +31 -0
- package/src/forms/form-layout.module.css +30 -5
- package/src/forms/form-layout.module.css.d.ts +1 -0
- package/src/forms/form-layout.tsx +12 -10
- package/src/guard/index.ts +183 -3
- package/src/guard/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/provider/index.tsx +53 -1
- package/src/shell/app-brand.module.css +2 -2
- package/src/shell/app-brand.tsx +1 -0
- package/src/shell/app-header.module.css +18 -8
- package/src/shell/app-main.module.css +54 -3
- package/src/shell/app-main.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.module.css +16 -4
- package/src/shell/app-sidebar.module.css +4 -6
- package/src/shell/index.tsx +200 -129
- package/src/shell/mobile-nav-model.ts +1 -1
- package/src/shell/page-aside.tsx +70 -21
- package/src/shell/page-bar.module.css +10 -34
- package/src/shell/page-bar.tsx +10 -5
- package/src/shell/use-breakpoint.ts +9 -45
- package/src/shell/use-size-class.ts +31 -0
- package/src/state/persisted-or-local.ts +10 -4
- package/src/state/persisted.ts +76 -17
- package/src/styles.css +41 -0
- package/src/surfaces.ts +9 -1
- package/src/theme/controls.module.css +7 -0
- package/src/theme/ctl-theme.tsx +15 -3
- package/src/theme/floating.module.css +36 -0
- package/src/theme/floating.module.css.d.ts +3 -0
- package/src/theme/index.ts +46 -8
- package/src/theme/nav-link.module.css +7 -0
- package/src/tokens/index.ts +20 -30
- package/src/tokens/palette.ts +6 -28
- package/src/tokens/size-classes.ts +46 -0
- package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
- /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
package/configs/oxlint-plugin.js
CHANGED
|
@@ -143,6 +143,7 @@ export const KNOWN_RULE_IDS = new Set([
|
|
|
143
143
|
'visx-tooltip',
|
|
144
144
|
'token-layer-boundary',
|
|
145
145
|
'no-import-meta-env',
|
|
146
|
+
'raw-breakpoint',
|
|
146
147
|
// These three honour `basalt-agent-allow`, never `theme-allow` — but they are real ids, so an
|
|
147
148
|
// annotation naming one must parse as a (useless) scoped annotation rather than as prose.
|
|
148
149
|
'agent-resume-guard',
|
|
@@ -175,6 +176,7 @@ export const KNOWN_RULE_IDS = new Set([
|
|
|
175
176
|
'hidden-inline-style',
|
|
176
177
|
'in-body-page-title',
|
|
177
178
|
'raw-selection-control',
|
|
179
|
+
'raw-media-query',
|
|
178
180
|
// Retired guard kinds — no longer in GUARD_RULES, but a `theme-allow` naming one must still
|
|
179
181
|
// parse as a real (dead) waiver rather than an unknown-id typo. See RETIRED_RULE_IDS above.
|
|
180
182
|
...RETIRED_RULE_IDS,
|
|
@@ -2706,7 +2708,7 @@ const CONTROL_OUTSIDE_HOME_MESSAGE =
|
|
|
2706
2708
|
* `@mantine/form` (a form is the third home and its inputs are not filters), and the owner
|
|
2707
2709
|
* exemption — a file DEFINING a basalt control cannot be told to use one.
|
|
2708
2710
|
*/
|
|
2709
|
-
// Ships: warn (grace → 1.
|
|
2711
|
+
// Ships: warn (grace → 1.32.0)
|
|
2710
2712
|
const controlOutsideHome = {
|
|
2711
2713
|
meta: {
|
|
2712
2714
|
type: 'suggestion',
|
|
@@ -3645,8 +3647,19 @@ const queryFnUnwrap = {
|
|
|
3645
3647
|
* @type {readonly DeprecatedExport[]}
|
|
3646
3648
|
*/
|
|
3647
3649
|
export const DEPRECATED_EXPORTS = [
|
|
3648
|
-
//
|
|
3649
|
-
//
|
|
3650
|
+
// The chart phone-tier API (`resolveChartTier`/`chartTierMetrics`/`useChartTier`/
|
|
3651
|
+
// `useChartTierMetrics`/`ChartTierMetrics`/`phoneChartWidth`) is NOT here: MIGRATING.md's own
|
|
3652
|
+
// Unreleased section states it a hard removal with no grace window (zero consumers), and these
|
|
3653
|
+
// exports are no longer reachable from any public barrel in `src/charts`/`src/tokens` — a row
|
|
3654
|
+
// here would nudge a rewrite to an import that no longer resolves, i.e. "it still resolves today"
|
|
3655
|
+
// would be false.
|
|
3656
|
+
{
|
|
3657
|
+
subpath: 'basalt-ui',
|
|
3658
|
+
name: 'useBreakpoint',
|
|
3659
|
+
replacement: 'useSizeClass (compact/medium/expanded) or @container for a container concern',
|
|
3660
|
+
removeIn: '1.31.0',
|
|
3661
|
+
fix: false,
|
|
3662
|
+
},
|
|
3650
3663
|
]
|
|
3651
3664
|
|
|
3652
3665
|
function deprecatedExportMessage(row) {
|
|
@@ -3935,6 +3948,227 @@ const noImportMetaEnv = {
|
|
|
3935
3948
|
},
|
|
3936
3949
|
}
|
|
3937
3950
|
|
|
3951
|
+
// ── Rule 32 — raw-breakpoint ─────────────────────────────────────────────────────────────────────
|
|
3952
|
+
|
|
3953
|
+
/** The Mantine responsive-style-prop shorthand keys — `{ base, xs, sm, md, lg, xl }`, never a mix. */
|
|
3954
|
+
const RESPONSIVE_OBJECT_KEYS = new Set(['base', 'xs', 'sm', 'md', 'lg', 'xl'])
|
|
3955
|
+
|
|
3956
|
+
/**
|
|
3957
|
+
* Is `node` a Mantine responsive-style-prop object literal — `{ base: 1, sm: 2 }`? Every property
|
|
3958
|
+
* key has to be one of {@link RESPONSIVE_OBJECT_KEYS}, and there has to be at least one: a random
|
|
3959
|
+
* object that merely happens to have a `sm` key among other, unrelated ones is not this shape, and
|
|
3960
|
+
* an empty object is not a breakpoint decision at all.
|
|
3961
|
+
*/
|
|
3962
|
+
function isResponsiveObjectValue(node) {
|
|
3963
|
+
if (node === null || node === undefined || node.type !== 'ObjectExpression') return false
|
|
3964
|
+
if (node.properties.length === 0) return false
|
|
3965
|
+
return node.properties.every((prop) => {
|
|
3966
|
+
if (prop.type !== 'Property') return false
|
|
3967
|
+
const name = propertyKeyName(prop)
|
|
3968
|
+
return typeof name === 'string' && RESPONSIVE_OBJECT_KEYS.has(name)
|
|
3969
|
+
})
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
/**
|
|
3973
|
+
* Does this prop value CONTAIN a responsive object — itself, nested one level down in a property
|
|
3974
|
+
* (`footer={{ height: { base: 60, sm: 0 } }}`, Mantine's own AppShell idiom), or behind a same-file
|
|
3975
|
+
* `const` binding at either place (`const h = { base: 60, sm: 0 }`, then `footer={{ height: h }}`)?
|
|
3976
|
+
* `consts` is the same-file name→object map, so an Identifier resolves identically wherever it
|
|
3977
|
+
* appears — the top-level attribute value and a nested property value alike. Matching only a nested
|
|
3978
|
+
* `ObjectExpression` left the Identifier-in-a-property shape trivially bypassable.
|
|
3979
|
+
*/
|
|
3980
|
+
function containsResponsiveObjectValue(node, consts, seen = new Set()) {
|
|
3981
|
+
const resolved =
|
|
3982
|
+
node !== null && node !== undefined && node.type === 'Identifier' ? consts.get(node.name) : node
|
|
3983
|
+
if (resolved === null || resolved === undefined || resolved.type !== 'ObjectExpression')
|
|
3984
|
+
return false
|
|
3985
|
+
if (seen.has(resolved)) return false
|
|
3986
|
+
seen.add(resolved)
|
|
3987
|
+
if (isResponsiveObjectValue(resolved)) return true
|
|
3988
|
+
return resolved.properties.some(
|
|
3989
|
+
(prop) => prop.type === 'Property' && containsResponsiveObjectValue(prop.value, consts, seen),
|
|
3990
|
+
)
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
/**
|
|
3994
|
+
* The shell components that are `visibleFrom`/`hiddenFrom`'s legitimate home — the size-class
|
|
3995
|
+
* axis's one JSX escape hatch, alongside overlays. Matched by DECLARATION name, the same
|
|
3996
|
+
* owner-by-declaration test `hand-rolled-shell`'s
|
|
3997
|
+
* `notesOwnerDefinition` already uses for "this file IS the framework piece, not a consumer of it"
|
|
3998
|
+
* — a file defining one of these is exempt for the whole file, because deciding which JSX line
|
|
3999
|
+
* inside a shell's own render tree is "the shell deciding its own chrome" vs. "a stray breakpoint"
|
|
4000
|
+
* is not a distinction the shell's own implementation can make about itself. The OTHER legitimate
|
|
4001
|
+
* home is a control implementing its own C9 swap (`ViewTabs`, `ActionGroup`, …) — that half reuses
|
|
4002
|
+
* {@link createControlOwnerProbe} (`controlOwner` below) rather than a second name list.
|
|
4003
|
+
*/
|
|
4004
|
+
const SHELL_HOME_NAMES = new Set(['BasaltShell', 'AppSidebar', 'MobileNav', 'AppBrand'])
|
|
4005
|
+
|
|
4006
|
+
/**
|
|
4007
|
+
* The two files exempted by NAME rather than by declaration: `useMediaQuery`'s own implementation
|
|
4008
|
+
* (`common/use-media-query.ts` — the one sanctioned `window.matchMedia` read every other file,
|
|
4009
|
+
* including `useSizeClass`, now funnels through) and `ChartTooltip` (the Mantine-free chart layer's
|
|
4010
|
+
* own pointer-tier read).
|
|
4011
|
+
*/
|
|
4012
|
+
const RAW_BREAKPOINT_EXEMPT_FILE = /(?:^|[\\/])(?:use-media-query\.ts|ChartTooltip\.tsx)$/
|
|
4013
|
+
|
|
4014
|
+
const RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE =
|
|
4015
|
+
'Responsive-object style prop on a Mantine component — the framework owns the three responsive ' +
|
|
4016
|
+
'axes (size class for shell chrome, container class for a component’s own width, pointer tier), ' +
|
|
4017
|
+
'never a per-component `{ base, sm, … }`. Reach for a container query for this component’s own ' +
|
|
4018
|
+
'width, or useSizeClass() for shell-only chrome. (basalt/raw-breakpoint)'
|
|
4019
|
+
|
|
4020
|
+
const RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE =
|
|
4021
|
+
"visibleFrom/hiddenFrom outside a shell home or a control's own C9 swap — this pair is the " +
|
|
4022
|
+
"size-class axis's one JSX escape hatch. Anywhere else it is a hand-rolled viewport breakpoint " +
|
|
4023
|
+
"the framework cannot see or move. Use a container query for a component's own width instead. " +
|
|
4024
|
+
'(basalt/raw-breakpoint)'
|
|
4025
|
+
|
|
4026
|
+
const RAW_BREAKPOINT_HOOK_MESSAGE =
|
|
4027
|
+
"useMediaQuery/useMatches/useViewportSize — a raw viewport read outside the framework's one seam " +
|
|
4028
|
+
'(useSizeClass(), shell-only). A component deciding its own layout from a viewport read instead of ' +
|
|
4029
|
+
'a container query is exactly the law this rule exists to catch. (basalt/raw-breakpoint)'
|
|
4030
|
+
|
|
4031
|
+
const RAW_BREAKPOINT_GLOBAL_MESSAGE =
|
|
4032
|
+
'window.matchMedia/window.innerWidth read directly — the same viewport read useSizeClass() already ' +
|
|
4033
|
+
"owns, shell-only. Route a component's own layout decision through a container query instead. " +
|
|
4034
|
+
'(basalt/raw-breakpoint)'
|
|
4035
|
+
|
|
4036
|
+
/** The three viewport hooks the law names, whatever module they are imported from. */
|
|
4037
|
+
const RAW_BREAKPOINT_HOOK_NAMES = new Set(['useMediaQuery', 'useMatches', 'useViewportSize'])
|
|
4038
|
+
|
|
4039
|
+
/**
|
|
4040
|
+
* Four independent shapes, one law: a consumer (or basalt itself, outside the shell homes)
|
|
4041
|
+
* reaching past the framework's two sanctioned seams — `useSizeClass()` for shell-only viewport
|
|
4042
|
+
* chrome, a container query for everything else — to read or react to a raw
|
|
4043
|
+
* viewport breakpoint. `visibleFrom`/`hiddenFrom` and the responsive-object shape are reported once
|
|
4044
|
+
* the whole file has been walked ({@link SHELL_HOME_NAMES} may be declared after its first JSX use,
|
|
4045
|
+
* the same ordering hazard `hand-rolled-shell` defers a `Program:exit` report to avoid); the import
|
|
4046
|
+
* and the two global reads carry no such ordering hazard and report immediately. A file that
|
|
4047
|
+
* declares a shell home (or a control's own C9 swap) is exempt for all four shapes uniformly — the
|
|
4048
|
+
* shell's own responsive chrome is the sanctioned use, whatever shape it takes.
|
|
4049
|
+
*/
|
|
4050
|
+
// Ships: warn (grace → 1.32.0)
|
|
4051
|
+
const rawBreakpoint = {
|
|
4052
|
+
meta: {
|
|
4053
|
+
type: 'suggestion',
|
|
4054
|
+
docs: {
|
|
4055
|
+
description:
|
|
4056
|
+
'Disallow a hand-rolled viewport breakpoint — a responsive-object Mantine prop, ' +
|
|
4057
|
+
'visibleFrom/hiddenFrom outside a shell home, useMediaQuery/useMatches/useViewportSize, or ' +
|
|
4058
|
+
'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).',
|
|
4060
|
+
},
|
|
4061
|
+
schema: [],
|
|
4062
|
+
},
|
|
4063
|
+
create(context) {
|
|
4064
|
+
if (isTestFile(context)) return {}
|
|
4065
|
+
if (RAW_BREAKPOINT_EXEMPT_FILE.test(getFilename(context))) return {}
|
|
4066
|
+
|
|
4067
|
+
const mantineImports = new Map()
|
|
4068
|
+
// A control implementing its OWN C9 responsive swap (`ViewTabs`, `ActionGroup`'s file, …) is
|
|
4069
|
+
// the other legitimate visibleFrom/hiddenFrom home, alongside the three named shell pieces —
|
|
4070
|
+
// reuse `responsive-twin`'s existing owner probe rather than hand-listing every control file
|
|
4071
|
+
// here too (`view-tabs.tsx`/`actions.tsx` were both false positives before this reuse).
|
|
4072
|
+
const controlOwner = createControlOwnerProbe()
|
|
4073
|
+
let definesShellHome = false
|
|
4074
|
+
const visibleHiddenCandidates = []
|
|
4075
|
+
const responsiveObjectCandidates = []
|
|
4076
|
+
// Same-file `const NAME = { base, sm }` bindings, resolved when a JSX attribute or a nested
|
|
4077
|
+
// property passes the Identifier — no scope manager here, so this is a flat name→object map.
|
|
4078
|
+
// A name declared more than once in the file is ambiguous (its two bindings cannot be told
|
|
4079
|
+
// apart), so it is dropped from the map entirely rather than guessed at last-wins.
|
|
4080
|
+
const responsiveObjectConsts = new Map()
|
|
4081
|
+
const seenDeclaratorNames = new Set()
|
|
4082
|
+
|
|
4083
|
+
const noteShellHomeOwner = (name) => {
|
|
4084
|
+
if (typeof name === 'string' && SHELL_HOME_NAMES.has(name)) definesShellHome = true
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
return {
|
|
4088
|
+
ImportDeclaration(node) {
|
|
4089
|
+
controlOwner.noteImport(node)
|
|
4090
|
+
collectMantineImports(node, mantineImports)
|
|
4091
|
+
if (node.importKind === 'type') return
|
|
4092
|
+
// `useMatches` collides with `@tanstack/react-router`'s own hook of the same name (basalt's
|
|
4093
|
+
// own `src/router-tanstack/index.ts` imports it) — scope the whole set to a `@mantine/*`
|
|
4094
|
+
// source rather than name alone, the same provenance gate every other Mantine-name rule in
|
|
4095
|
+
// this file already uses (`collectMantineImports`).
|
|
4096
|
+
const source = node.source?.value
|
|
4097
|
+
if (typeof source !== 'string' || !source.startsWith('@mantine/')) return
|
|
4098
|
+
for (const specifier of node.specifiers ?? []) {
|
|
4099
|
+
if (specifier.type !== 'ImportSpecifier') continue
|
|
4100
|
+
if (specifier.importKind === 'type') continue
|
|
4101
|
+
const imported = specifier.imported
|
|
4102
|
+
const importedName = imported.type === 'Identifier' ? imported.name : imported.value
|
|
4103
|
+
if (typeof importedName !== 'string' || !RAW_BREAKPOINT_HOOK_NAMES.has(importedName))
|
|
4104
|
+
continue
|
|
4105
|
+
if (hasThemeAllow(context, specifier, 'raw-breakpoint')) continue
|
|
4106
|
+
context.report({ node: specifier, message: RAW_BREAKPOINT_HOOK_MESSAGE })
|
|
4107
|
+
}
|
|
4108
|
+
},
|
|
4109
|
+
FunctionDeclaration(node) {
|
|
4110
|
+
noteShellHomeOwner(node.id?.name)
|
|
4111
|
+
controlOwner.visitors.FunctionDeclaration(node)
|
|
4112
|
+
},
|
|
4113
|
+
VariableDeclarator(node) {
|
|
4114
|
+
noteShellHomeOwner(node.id?.name)
|
|
4115
|
+
controlOwner.visitors.VariableDeclarator(node)
|
|
4116
|
+
if (node.id?.type !== 'Identifier') return
|
|
4117
|
+
const name = node.id.name
|
|
4118
|
+
if (seenDeclaratorNames.has(name)) {
|
|
4119
|
+
// A redeclared name — drop whatever the first declaration put here, so the ambiguous
|
|
4120
|
+
// binding is never used to flag (or clear) a JSX value it may not even be.
|
|
4121
|
+
responsiveObjectConsts.delete(name)
|
|
4122
|
+
return
|
|
4123
|
+
}
|
|
4124
|
+
seenDeclaratorNames.add(name)
|
|
4125
|
+
if (node.init?.type === 'ObjectExpression') responsiveObjectConsts.set(name, node.init)
|
|
4126
|
+
},
|
|
4127
|
+
JSXAttribute(node) {
|
|
4128
|
+
const name = node.name?.name
|
|
4129
|
+
if (typeof name !== 'string') return
|
|
4130
|
+
if (name === 'visibleFrom' || name === 'hiddenFrom') {
|
|
4131
|
+
visibleHiddenCandidates.push(node)
|
|
4132
|
+
return
|
|
4133
|
+
}
|
|
4134
|
+
// Resolved at `Program:exit` so a `const` declared after its first JSX use still resolves.
|
|
4135
|
+
responsiveObjectCandidates.push(node)
|
|
4136
|
+
},
|
|
4137
|
+
CallExpression(node) {
|
|
4138
|
+
if (node.callee?.type !== 'Identifier' || node.callee.name !== 'matchMedia') return
|
|
4139
|
+
if (hasThemeAllow(context, node, 'raw-breakpoint')) return
|
|
4140
|
+
context.report({ node, message: RAW_BREAKPOINT_GLOBAL_MESSAGE })
|
|
4141
|
+
},
|
|
4142
|
+
MemberExpression(node) {
|
|
4143
|
+
if (node.object?.type !== 'Identifier' || node.object.name !== 'window') return
|
|
4144
|
+
if (node.computed) return
|
|
4145
|
+
const property = node.property?.name
|
|
4146
|
+
if (property !== 'matchMedia' && property !== 'innerWidth') return
|
|
4147
|
+
if (hasThemeAllow(context, node, 'raw-breakpoint')) return
|
|
4148
|
+
context.report({ node, message: RAW_BREAKPOINT_GLOBAL_MESSAGE })
|
|
4149
|
+
},
|
|
4150
|
+
'Program:exit'() {
|
|
4151
|
+
// A shell home (or a control implementing its own C9 swap) IS the framework piece — the
|
|
4152
|
+
// shell's own responsive chrome is the sanctioned use, so nothing here reports there.
|
|
4153
|
+
if (definesShellHome || controlOwner.isOwner()) return
|
|
4154
|
+
for (const node of responsiveObjectCandidates) {
|
|
4155
|
+
const value = unwrapExpressionContainer(node.value)
|
|
4156
|
+
if (!containsResponsiveObjectValue(value, responsiveObjectConsts)) continue
|
|
4157
|
+
const owner = node.parent
|
|
4158
|
+
if (owner === null || owner === undefined || owner.type !== 'JSXOpeningElement') continue
|
|
4159
|
+
if (resolveMantineTag(owner.name, mantineImports) === undefined) continue
|
|
4160
|
+
if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
|
|
4161
|
+
context.report({ node, message: RAW_BREAKPOINT_RESPONSIVE_PROP_MESSAGE })
|
|
4162
|
+
}
|
|
4163
|
+
for (const node of visibleHiddenCandidates) {
|
|
4164
|
+
if (hasThemeAllow(context, node, 'raw-breakpoint')) continue
|
|
4165
|
+
context.report({ node, message: RAW_BREAKPOINT_VISIBLE_HIDDEN_MESSAGE })
|
|
4166
|
+
}
|
|
4167
|
+
},
|
|
4168
|
+
}
|
|
4169
|
+
},
|
|
4170
|
+
}
|
|
4171
|
+
|
|
3938
4172
|
// ── Grace ledger ────────────────────────────────────────────────────────────────────────────────
|
|
3939
4173
|
|
|
3940
4174
|
/**
|
|
@@ -3968,8 +4202,9 @@ const noImportMetaEnv = {
|
|
|
3968
4202
|
export const PLUGIN_RULE_GRACE = {
|
|
3969
4203
|
'control-outside-home': {
|
|
3970
4204
|
since: '1.26.0',
|
|
3971
|
-
promote: '1.
|
|
4205
|
+
promote: '1.32.0',
|
|
3972
4206
|
why:
|
|
4207
|
+
'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. ' +
|
|
3973
4208
|
'the wave-6 control guards (docs/CONTROLS-SPEC.md §6). The one openly HEURISTIC rule of the ' +
|
|
3974
4209
|
'set — "this control has no home" is a claim about layout intent, so its false-positive load ' +
|
|
3975
4210
|
'is carried by four exemptions (overlay/settings-row ancestor, the overlay FILENAME ' +
|
|
@@ -3995,6 +4230,20 @@ export const PLUGIN_RULE_GRACE = {
|
|
|
3995
4230
|
"still an overlay's own body declares it with " +
|
|
3996
4231
|
'`theme-allow-file control-outside-home — overlay`.',
|
|
3997
4232
|
},
|
|
4233
|
+
'raw-breakpoint': {
|
|
4234
|
+
since: '1.31.0',
|
|
4235
|
+
promote: '1.32.0',
|
|
4236
|
+
why:
|
|
4237
|
+
'new in the wave-11 responsive/touch guards. Catches four ' +
|
|
4238
|
+
'independent shapes at once — a responsive-object Mantine prop, visibleFrom/hiddenFrom outside ' +
|
|
4239
|
+
'the three shell homes, the three raw viewport hooks, and window.matchMedia/innerWidth — none ' +
|
|
4240
|
+
'of which basalt policed before this wave, so every consumer on an earlier minor has a green ' +
|
|
4241
|
+
"build with all four shapes already in it. basalt-ui/content's own article-card.tsx (a " +
|
|
4242
|
+
"`SimpleGrid cols={{ base: 1, sm: 2, lg }}`) is one incumbent measured in basalt's own tree at " +
|
|
4243
|
+
'ship time — a real number, not zero, so this ships warn rather than error from the start. ' +
|
|
4244
|
+
'1.32.0 is when that incumbent and any consumer-side ones are expected to have moved to a ' +
|
|
4245
|
+
'container query or a theme-allow with a stated reason.',
|
|
4246
|
+
},
|
|
3998
4247
|
}
|
|
3999
4248
|
|
|
4000
4249
|
/**
|
|
@@ -4064,5 +4313,6 @@ export default {
|
|
|
4064
4313
|
'agent-no-raw-usechat': agentNoRawUseChat,
|
|
4065
4314
|
'ai-sdk-major': aiSdkMajor,
|
|
4066
4315
|
'no-import-meta-env': noImportMetaEnv,
|
|
4316
|
+
'raw-breakpoint': rawBreakpoint,
|
|
4067
4317
|
},
|
|
4068
4318
|
}
|
|
@@ -70,6 +70,7 @@ beforeEach(() => {
|
|
|
70
70
|
'basalt/agent-resume-guard': 'error',
|
|
71
71
|
'basalt/agent-no-raw-usechat': 'error',
|
|
72
72
|
'basalt/ai-sdk-major': 'error',
|
|
73
|
+
'basalt/raw-breakpoint': 'error',
|
|
73
74
|
},
|
|
74
75
|
}),
|
|
75
76
|
)
|
|
@@ -2735,10 +2736,11 @@ describe('basalt/responsive-twin', () => {
|
|
|
2735
2736
|
})
|
|
2736
2737
|
|
|
2737
2738
|
it('does NOT flag two halves on DIFFERENT breakpoints', () => {
|
|
2738
|
-
|
|
2739
|
+
// `code` is not asserted here: raw visibleFrom/hiddenFrom outside a shell home is its own,
|
|
2740
|
+
// unrelated finding (basalt/raw-breakpoint) — this test is only about responsive-twin's silence.
|
|
2741
|
+
const { rules } = run(
|
|
2739
2742
|
`export const C = () => (\n <div>\n <SegmentedControl visibleFrom="sm" data={[]} />\n <SegmentedControl hiddenFrom="md" data={[]} />\n </div>\n)\n`,
|
|
2740
2743
|
)
|
|
2741
|
-
expect(code).toBe(0)
|
|
2742
2744
|
expect(rules).not.toContain('responsive-twin')
|
|
2743
2745
|
})
|
|
2744
2746
|
|
|
@@ -3400,6 +3402,147 @@ describe('basalt/forms-field-key', () => {
|
|
|
3400
3402
|
})
|
|
3401
3403
|
})
|
|
3402
3404
|
|
|
3405
|
+
// ── raw-breakpoint ────────────────────────────────────────────────────────────
|
|
3406
|
+
|
|
3407
|
+
describe('basalt/raw-breakpoint', () => {
|
|
3408
|
+
it('flags a responsive-object prop on an imported Mantine component', () => {
|
|
3409
|
+
const { rules } = run(
|
|
3410
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3411
|
+
`export const C = () => <SimpleGrid cols={{ base: 1, sm: 2 }} />\n`,
|
|
3412
|
+
)
|
|
3413
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3414
|
+
})
|
|
3415
|
+
|
|
3416
|
+
it('flags a responsive object nested one level down (Mantine AppShell idiom)', () => {
|
|
3417
|
+
const { rules } = run(
|
|
3418
|
+
`import { AppShell } from '@mantine/core'\n` +
|
|
3419
|
+
`export const C = () => <AppShell footer={{ height: { base: 60, sm: 0 } }}>hi</AppShell>\n`,
|
|
3420
|
+
)
|
|
3421
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3422
|
+
})
|
|
3423
|
+
|
|
3424
|
+
it('flags a responsive object behind a same-file const binding', () => {
|
|
3425
|
+
const { rules } = run(
|
|
3426
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3427
|
+
`const responsiveCols = { base: 1, sm: 2 }\n` +
|
|
3428
|
+
`export const C = () => <SimpleGrid cols={responsiveCols} />\n`,
|
|
3429
|
+
)
|
|
3430
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3431
|
+
})
|
|
3432
|
+
|
|
3433
|
+
// The nested-property half: a property VALUE that is itself an Identifier must resolve through
|
|
3434
|
+
// the same-file const map, exactly as the attribute's own top-level value already does.
|
|
3435
|
+
it('flags a responsive object behind a const used as a nested property value', () => {
|
|
3436
|
+
const { rules } = run(
|
|
3437
|
+
`import { AppShell } from '@mantine/core'\n` +
|
|
3438
|
+
`const h = { base: 60, sm: 0 }\n` +
|
|
3439
|
+
`export const C = () => <AppShell footer={{ height: h }}>hi</AppShell>\n`,
|
|
3440
|
+
)
|
|
3441
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3442
|
+
})
|
|
3443
|
+
|
|
3444
|
+
// A name declared twice in the file has no single binding without a scope manager, so it is
|
|
3445
|
+
// dropped rather than resolved last-declaration-wins — the outer (non-responsive) `cols` the JSX
|
|
3446
|
+
// actually references must not be flagged via the inner function's responsive one.
|
|
3447
|
+
it('does NOT flag a name redeclared in the file — an ambiguous binding is skipped, not guessed', () => {
|
|
3448
|
+
const { rules } = run(
|
|
3449
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3450
|
+
`const cols = { fixed: 2 }\n` +
|
|
3451
|
+
`function Other() {\n const cols = { base: 1, sm: 2 }\n return null\n}\n` +
|
|
3452
|
+
`export const C = () => <SimpleGrid cols={cols} />\n`,
|
|
3453
|
+
)
|
|
3454
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3455
|
+
})
|
|
3456
|
+
|
|
3457
|
+
it('does NOT flag a fixed cols on the same component', () => {
|
|
3458
|
+
const { rules } = run(
|
|
3459
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3460
|
+
`export const C = () => <SimpleGrid cols={2} />\n`,
|
|
3461
|
+
)
|
|
3462
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3463
|
+
})
|
|
3464
|
+
|
|
3465
|
+
it('does NOT flag an object prop on a component never imported from @mantine/*', () => {
|
|
3466
|
+
const { rules } = run(`export const C = () => <WidgetGrid cols={{ base: 1, sm: 2 }} />\n`)
|
|
3467
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3468
|
+
})
|
|
3469
|
+
|
|
3470
|
+
it('flags visibleFrom/hiddenFrom outside a shell home', () => {
|
|
3471
|
+
const { rules } = run(
|
|
3472
|
+
`import { Box } from '@mantine/core'\n` +
|
|
3473
|
+
`export const C = () => <Box visibleFrom="sm">desktop only</Box>\n`,
|
|
3474
|
+
)
|
|
3475
|
+
expect(rules).toContain('raw-breakpoint')
|
|
3476
|
+
})
|
|
3477
|
+
|
|
3478
|
+
it('does NOT flag visibleFrom/hiddenFrom inside a file that DEFINES BasaltShell', () => {
|
|
3479
|
+
const { rules } = run(
|
|
3480
|
+
`import { Box } from '@mantine/core'\n` +
|
|
3481
|
+
`export function BasaltShell() {\n return <Box visibleFrom="sm">desktop only</Box>\n}\n`,
|
|
3482
|
+
)
|
|
3483
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3484
|
+
})
|
|
3485
|
+
|
|
3486
|
+
// The exemption is uniform across all four shapes: a file that IS the shell may also carry a
|
|
3487
|
+
// responsive-object prop, not only visibleFrom/hiddenFrom.
|
|
3488
|
+
it('does NOT flag a responsive-object prop inside a file that DEFINES BasaltShell', () => {
|
|
3489
|
+
const { rules } = run(
|
|
3490
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3491
|
+
`export function BasaltShell() {\n return null\n}\n` +
|
|
3492
|
+
`export const C = () => <SimpleGrid cols={{ base: 1, sm: 2 }} />\n`,
|
|
3493
|
+
)
|
|
3494
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3495
|
+
})
|
|
3496
|
+
|
|
3497
|
+
it('does NOT flag visibleFrom/hiddenFrom inside a file that DEFINES a C9 control (ViewTabs) and imports no basalt-ui', () => {
|
|
3498
|
+
const { rules } = run(
|
|
3499
|
+
`import { Box } from '@mantine/core'\n` +
|
|
3500
|
+
`export function ViewTabs() {\n return <Box visibleFrom="sm">desktop only</Box>\n}\n`,
|
|
3501
|
+
)
|
|
3502
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3503
|
+
})
|
|
3504
|
+
|
|
3505
|
+
it('flags an import of useMediaQuery/useMatches/useViewportSize from @mantine/*', () => {
|
|
3506
|
+
expect(run(`import { useMediaQuery } from '@mantine/hooks'\n`).rules).toContain(
|
|
3507
|
+
'raw-breakpoint',
|
|
3508
|
+
)
|
|
3509
|
+
expect(run(`import { useMatches } from '@mantine/core'\n`).rules).toContain('raw-breakpoint')
|
|
3510
|
+
expect(run(`import { useViewportSize } from '@mantine/hooks'\n`).rules).toContain(
|
|
3511
|
+
'raw-breakpoint',
|
|
3512
|
+
)
|
|
3513
|
+
})
|
|
3514
|
+
|
|
3515
|
+
it('does NOT flag useMatches imported from a non-Mantine router (name collision)', () => {
|
|
3516
|
+
// @tanstack/react-router (and react-router) export their own unrelated `useMatches` —
|
|
3517
|
+
// basalt's own `src/router-tanstack/index.ts` imports it; scoped to `@mantine/*` sources only.
|
|
3518
|
+
expect(run(`import { useMatches } from '@tanstack/react-router'\n`).rules).not.toContain(
|
|
3519
|
+
'raw-breakpoint',
|
|
3520
|
+
)
|
|
3521
|
+
expect(run(`import { useViewportSize } from 'some-lib'\n`).rules).not.toContain(
|
|
3522
|
+
'raw-breakpoint',
|
|
3523
|
+
)
|
|
3524
|
+
})
|
|
3525
|
+
|
|
3526
|
+
it('flags window.matchMedia and window.innerWidth', () => {
|
|
3527
|
+
expect(run(`const mq = window.matchMedia('(max-width: 600px)')\n`).rules).toContain(
|
|
3528
|
+
'raw-breakpoint',
|
|
3529
|
+
)
|
|
3530
|
+
expect(run(`const w = window.innerWidth\n`).rules).toContain('raw-breakpoint')
|
|
3531
|
+
})
|
|
3532
|
+
|
|
3533
|
+
it('flags a bare matchMedia() call', () => {
|
|
3534
|
+
expect(run(`const mq = matchMedia('(max-width: 600px)')\n`).rules).toContain('raw-breakpoint')
|
|
3535
|
+
})
|
|
3536
|
+
|
|
3537
|
+
it('honors a scoped theme-allow on the responsive-object prop', () => {
|
|
3538
|
+
const { rules } = run(
|
|
3539
|
+
`import { SimpleGrid } from '@mantine/core'\n` +
|
|
3540
|
+
`export const C = () => (\n // theme-allow raw-breakpoint — migrating in a follow-up\n <SimpleGrid cols={{ base: 1, sm: 2 }} />\n)\n`,
|
|
3541
|
+
)
|
|
3542
|
+
expect(rules).not.toContain('raw-breakpoint')
|
|
3543
|
+
})
|
|
3544
|
+
})
|
|
3545
|
+
|
|
3403
3546
|
// ── the deprecation ledger (B4) ──────────────────────────────────────────────
|
|
3404
3547
|
|
|
3405
3548
|
describe('DEPRECATED_EXPORTS', () => {
|
package/configs/oxlint.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/agent-chat/composer.tsx"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,cAAc,EAAkB,GAAG,EAAiB,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAG/F,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/agent-chat/composer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ActionIcon, Group, Kbd, Stack, Text, Textarea } from "@mantine/core";
|
|
2
|
+
import { ActionIcon, Box, Group, Kbd, Stack, Text, Textarea } from "@mantine/core";
|
|
3
3
|
import { useImperativeHandle, useLayoutEffect, useRef, useSyncExternalStore } from "react";
|
|
4
4
|
import { cx } from "../common/props.js";
|
|
5
5
|
import { readPersistedValue } from "../state.js";
|
|
6
6
|
import { VX } from "../tokens/index.js";
|
|
7
|
+
import { useKeyboardInsetRef } from "./use-keyboard-inset.js";
|
|
7
8
|
function SendGlyph() {
|
|
8
9
|
return /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
9
10
|
"path",
|
|
@@ -90,6 +91,7 @@ const noopSubscribe = (_cb) => () => {
|
|
|
90
91
|
};
|
|
91
92
|
const emptyDraft = () => "";
|
|
92
93
|
const NO_ATTACHMENTS = [];
|
|
94
|
+
const ROOT_GAP = 6;
|
|
93
95
|
function liveSelection(el) {
|
|
94
96
|
if (document.activeElement !== el) return null;
|
|
95
97
|
const { selectionStart, selectionEnd } = el;
|
|
@@ -170,6 +172,7 @@ function Composer({
|
|
|
170
172
|
textareaRef.current?.focus();
|
|
171
173
|
};
|
|
172
174
|
useImperativeHandle(ref, () => ({ insertText, setValue, focus: focusTextarea }));
|
|
175
|
+
const keyboardInsetRef = useKeyboardInsetRef();
|
|
173
176
|
const submit = () => {
|
|
174
177
|
if (inputDisabled || !hasPayload) return;
|
|
175
178
|
const submittedText = trimmed;
|
|
@@ -221,48 +224,43 @@ function Composer({
|
|
|
221
224
|
if (onPaste !== void 0) textareaProps.onPaste = onPaste;
|
|
222
225
|
if (classNames?.input !== void 0) textareaProps.className = cx(classNames.input);
|
|
223
226
|
const showActions = showStop || showSend;
|
|
224
|
-
return /* @__PURE__ */ jsxs(
|
|
225
|
-
Stack,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
] }),
|
|
262
|
-
hint === void 0 ? /* @__PURE__ */ jsx(DefaultHint, {}) : hint
|
|
263
|
-
]
|
|
264
|
-
}
|
|
265
|
-
);
|
|
227
|
+
return /* @__PURE__ */ jsxs(Box, { ref: keyboardInsetRef, className: cx(classNames?.root, className), style, children: [
|
|
228
|
+
/* @__PURE__ */ jsxs(Stack, { gap: ROOT_GAP, children: [
|
|
229
|
+
/* @__PURE__ */ jsxs(Group, { gap: "xs", align: "flex-end", wrap: "nowrap", children: [
|
|
230
|
+
leftSection,
|
|
231
|
+
/* @__PURE__ */ jsx(Textarea, { ...textareaProps, ref: textareaRef }),
|
|
232
|
+
rightSection,
|
|
233
|
+
showActions && /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", className: cx(classNames?.actions), children: [
|
|
234
|
+
showStop ? /* @__PURE__ */ jsx(
|
|
235
|
+
ActionIcon,
|
|
236
|
+
{
|
|
237
|
+
size: 42,
|
|
238
|
+
radius: "md",
|
|
239
|
+
variant: "filled",
|
|
240
|
+
color: "red",
|
|
241
|
+
onClick: onStop,
|
|
242
|
+
"aria-label": "Stop generating",
|
|
243
|
+
children: /* @__PURE__ */ jsx(StopGlyph, {})
|
|
244
|
+
}
|
|
245
|
+
) : null,
|
|
246
|
+
showSend ? /* @__PURE__ */ jsx(
|
|
247
|
+
ActionIcon,
|
|
248
|
+
{
|
|
249
|
+
size: 42,
|
|
250
|
+
radius: "md",
|
|
251
|
+
variant: "filled",
|
|
252
|
+
onClick: submit,
|
|
253
|
+
disabled: inputDisabled || !hasPayload,
|
|
254
|
+
"aria-label": "Send message",
|
|
255
|
+
children: /* @__PURE__ */ jsx(SendGlyph, {})
|
|
256
|
+
}
|
|
257
|
+
) : null
|
|
258
|
+
] })
|
|
259
|
+
] }),
|
|
260
|
+
hint === void 0 ? /* @__PURE__ */ jsx(DefaultHint, {}) : hint
|
|
261
|
+
] }),
|
|
262
|
+
/* @__PURE__ */ jsx(Box, { "aria-hidden": true, style: { height: "var(--vx-keyboard-inset, 0px)" } })
|
|
263
|
+
] });
|
|
266
264
|
}
|
|
267
265
|
export {
|
|
268
266
|
Composer
|
|
@@ -3,7 +3,7 @@ import type { AgentPart, AgentTransport, OutcomeResolver, ThreadsStore } from '.
|
|
|
3
3
|
import type { BasaltProps, SlotStylesProps } from '../common/props.js';
|
|
4
4
|
/** The four boxes `ThreadWorkspace` paints: `root` is the outermost pane split, `feed` is the
|
|
5
5
|
* thread-list pane, `composer` is the new-thread composer pinned under the feed, `detail` is the
|
|
6
|
-
* right-hand (or,
|
|
6
|
+
* right-hand (or, in the `compact` size class, full-screen) open-thread pane. */
|
|
7
7
|
export type ThreadWorkspaceSlot = 'root' | 'feed' | 'composer' | 'detail';
|
|
8
8
|
export type ThreadWorkspaceProps = BasaltProps & SlotStylesProps<ThreadWorkspaceSlot> & {
|
|
9
9
|
/**
|
|
@@ -35,7 +35,7 @@ export type ThreadWorkspaceProps = BasaltProps & SlotStylesProps<ThreadWorkspace
|
|
|
35
35
|
/**
|
|
36
36
|
* A two-pane master-detail thread workspace: a scrollable feed of threads with an anchored
|
|
37
37
|
* new-thread composer, and a detail panel for the open thread's live transcript. Collapses to a
|
|
38
|
-
* single pane
|
|
38
|
+
* single pane in the `compact` size class (< 840px).
|
|
39
39
|
*
|
|
40
40
|
* @example
|
|
41
41
|
* <ThreadWorkspace useThreads={useThreads} transport={transport} resolveOutcome={heuristicOutcome} />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-workspace.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-workspace.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"thread-workspace.d.ts","sourceRoot":"","sources":["../../src/agent-chat/thread-workspace.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGxF,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAcnE;;iFAEiF;AACjF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;AAEzE,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAC5C,eAAe,CAAC,mBAAmB,CAAC,GAAG;IACrC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,YAAY,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IACrD,mEAAmE;IACnE,QAAQ,CAAC,cAAc,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IACnD,qEAAqE;IACrE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAChC,CAAA;AA4DH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,SAAS,EACT,KAAK,EACL,UAAU,GACX,EAAE,oBAAoB,GAAG,GAAG,CAAC,OAAO,CAmHpC"}
|