basalt-ui 1.31.0 → 1.32.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/MIGRATING.md +101 -17
  3. package/agent/rules/basalt-mantine.md +4 -4
  4. package/configs/oxlint-plugin.js +159 -42
  5. package/configs/oxlint-plugin.test.ts +182 -0
  6. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  7. package/dist/charts/kinds/BandStrip.js +18 -66
  8. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  9. package/dist/charts/kinds/DualPanel.js +5 -26
  10. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  11. package/dist/charts/kinds/MirroredBars.js +10 -68
  12. package/dist/charts/kinds/band-chrome.d.ts +31 -0
  13. package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
  14. package/dist/charts/kinds/band-chrome.js +71 -0
  15. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  16. package/dist/charts/primitives/CartesianChart.js +49 -76
  17. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  18. package/dist/charts/primitives/ChartCard.js +5 -23
  19. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  20. package/dist/charts/primitives/ChartFrame.js +23 -40
  21. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  22. package/dist/charts/primitives/ChartLegend.js +5 -133
  23. package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
  24. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  25. package/dist/charts/primitives/ChartTooltip.js +16 -1
  26. package/dist/charts/primitives/HoverOverlay.d.ts +24 -9
  27. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  28. package/dist/charts/primitives/HoverOverlay.js +17 -1
  29. package/dist/charts/primitives/chart-card-context.d.ts +17 -13
  30. package/dist/charts/primitives/chart-card-context.d.ts.map +1 -1
  31. package/dist/charts/primitives/chart-card-context.js +10 -5
  32. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  33. package/dist/charts/primitives/chart-frame-layout.js +25 -31
  34. package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
  35. package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
  36. package/dist/charts/primitives/legend-disclosure.js +153 -0
  37. package/dist/controls/controls.module.css +5 -0
  38. package/dist/controls/sync-button.module.css +1 -0
  39. package/dist/dashboard/stat-group.module.css +19 -64
  40. package/dist/dashboard/widget-grid.module.css +11 -38
  41. package/dist/data/data-table-state.d.ts +83 -0
  42. package/dist/data/data-table-state.d.ts.map +1 -0
  43. package/dist/data/data-table-state.js +139 -0
  44. package/dist/data/data-table.d.ts +33 -2
  45. package/dist/data/data-table.d.ts.map +1 -1
  46. package/dist/data/data-table.js +179 -277
  47. package/dist/forms/form-layout.module.css +3 -13
  48. package/dist/guard/index.d.ts.map +1 -1
  49. package/dist/guard/index.js +6 -5
  50. package/dist/notifications/center.d.ts.map +1 -1
  51. package/dist/provider/index.d.ts.map +1 -1
  52. package/dist/provider/index.js +12 -10
  53. package/dist/shell/aside-docking.d.ts +31 -0
  54. package/dist/shell/aside-docking.d.ts.map +1 -0
  55. package/dist/shell/aside-docking.js +19 -0
  56. package/dist/shell/index.d.ts +0 -11
  57. package/dist/shell/index.d.ts.map +1 -1
  58. package/dist/shell/index.js +13 -14
  59. package/dist/shell/use-breakpoint.d.ts +1 -1
  60. package/dist/theme/floating.module.css +1 -0
  61. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  62. package/llms.txt +2 -2
  63. package/package.json +2 -1
  64. package/src/charts/kinds/BandStrip.tsx +18 -64
  65. package/src/charts/kinds/DualPanel.tsx +4 -24
  66. package/src/charts/kinds/MirroredBars.tsx +12 -66
  67. package/src/charts/kinds/band-chrome.tsx +91 -0
  68. package/src/charts/primitives/CartesianChart.tsx +79 -122
  69. package/src/charts/primitives/ChartCard.tsx +5 -27
  70. package/src/charts/primitives/ChartFrame.tsx +37 -76
  71. package/src/charts/primitives/ChartLegend.tsx +7 -154
  72. package/src/charts/primitives/ChartTooltip.tsx +16 -0
  73. package/src/charts/primitives/HoverOverlay.tsx +52 -21
  74. package/src/charts/primitives/chart-card-context.ts +25 -16
  75. package/src/charts/primitives/chart-frame-layout.ts +44 -56
  76. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  77. package/src/controls/controls.module.css +5 -0
  78. package/src/controls/sync-button.module.css +1 -0
  79. package/src/dashboard/stat-group.module.css +19 -64
  80. package/src/dashboard/widget-grid.module.css +11 -38
  81. package/src/data/data-table-state.ts +218 -0
  82. package/src/data/data-table.tsx +127 -218
  83. package/src/forms/form-layout.module.css +3 -13
  84. package/src/guard/index.ts +21 -16
  85. package/src/notifications/center.tsx +3 -0
  86. package/src/provider/index.tsx +18 -17
  87. package/src/shell/aside-docking.ts +54 -0
  88. package/src/shell/index.tsx +15 -37
  89. package/src/shell/use-breakpoint.ts +2 -2
  90. package/src/theme/floating.module.css +1 -0
  91. package/src/theme-lab/derive-controls.tsx +2 -0
@@ -33,7 +33,8 @@ const SIZE_CLASS_LITERALS = /* @__PURE__ */ new Set(["52.5em", "75em", "52.49375
33
33
  const SHELL_CSS_PATH = /(?:^|\/)shell\//;
34
34
  function isSanctionedShellMediaQuery(relPath, query) {
35
35
  if (!SHELL_CSS_PATH.test(relPath)) return false;
36
- const widths = query.match(new RegExp(CSS_WIDTH_NUM, "g")) ?? [];
36
+ const widthClauses = query.match(/\([^()]*\bwidth\b[^()]*\)/g) ?? [];
37
+ const widths = widthClauses.flatMap((c) => c.match(new RegExp(CSS_WIDTH_NUM, "g")) ?? []);
37
38
  return widths.length > 0 && widths.every((w) => SIZE_CLASS_LITERALS.has(w));
38
39
  }
39
40
  const MICRO_RADIUS_CEILING_PX = 3;
@@ -230,13 +231,13 @@ const DEFAULT_GUARD_CONFIG = {
230
231
  const GRACE_PERIOD_KINDS = {
231
232
  "raw-selection-control": {
232
233
  since: "1.26.0",
233
- promote: "1.32.0",
234
- why: '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. new in the wave-6 control guards (docs/CONTROLS-SPEC.md \xA76, law C1). The text lane cannot see ancestry, so "no home" is approximated by a 12-line host-tag window \u2014 the loosest reading in the guard, and the reason this one lands warn rather than error even though its law is settled. ONE LAW, TWO LANES, ONE PROMOTION: it moves with its AST twin, `basalt/control-outside-home`, whose PLUGIN_RULE_GRACE entry carries the full argument and the measurement, and it must never be promoted or extended alone \u2014 a consumer waiving one spelling and failing the other is the exact confusion `WAIVER_ID_ALIASES` exists to close. RE-MEASURED 2026-09-09 across the whole fleet on 1.29.2: 29 AST-lane incumbents in five repos, plus three of basalt\'s own (data-table, notifications/center, theme-lab). Extended to 1.31.0 because 1.30.0 is the FIRST release in which either lane names a home a provider-only consumer can reach \u2014 every home both messages listed was a shell slot, and the shell-less `PageBar` sentence ships in this minor. Promoting in the same minor that makes a rule satisfiable gives nobody a window to act. 1.31.0 is when the 29 are re-measured against that number.'
234
+ promote: "1.33.0",
235
+ why: "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. 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. new in the wave-6 control guards (docs/CONTROLS-SPEC.md \xA76, law C1). The text lane cannot see ancestry, so \"no home\" is approximated by a 12-line host-tag window \u2014 the loosest reading in the guard, and the reason this one lands warn rather than error even though its law is settled. ONE LAW, TWO LANES, ONE PROMOTION: it moves with its AST twin, `basalt/control-outside-home`, whose PLUGIN_RULE_GRACE entry carries the full argument and the measurement, and it must never be promoted or extended alone \u2014 a consumer waiving one spelling and failing the other is the exact confusion `WAIVER_ID_ALIASES` exists to close. RE-MEASURED 2026-09-09 across the whole fleet on 1.29.2: 29 AST-lane incumbents in five repos, plus three of basalt's own (data-table, notifications/center, theme-lab). Extended to 1.31.0 because 1.30.0 is the FIRST release in which either lane names a home a provider-only consumer can reach \u2014 every home both messages listed was a shell slot, and the shell-less `PageBar` sentence ships in this minor. Promoting in the same minor that makes a rule satisfiable gives nobody a window to act. 1.31.0 is when the 29 are re-measured against that number."
235
236
  },
236
237
  "raw-media-query": {
237
238
  since: "1.31.0",
238
- promote: "1.32.0",
239
- why: "new in the wave-11 responsive/touch guards \u2014 the CSS-text twin of the oxlint plugin's `basalt/raw-breakpoint`. Reaching CSS is a NEW file type for this check, same reasoning as `css-raw-surface`, and basalt's own `src/dashboard/stat-group.module.css` / `widget-grid.module.css` already declare `@container` names (`basalt-stat-group`, `basalt-widget-grid`) outside the declared four-name table, plus `shell/**`'s own legitimate width `@media` rules \u2014 real incumbents in basalt's own tree at ship time, so this ships warn rather than error from the start. Promotes with its AST twin at 1.32.0."
239
+ promote: "1.33.0",
240
+ why: "The CSS-text twin of the oxlint plugin's `basalt/raw-breakpoint` \u2014 ONE LAW, TWO LANES, ONE PROMOTION, so it moves with that rule and never alone (`configs/oxlint-plugin.test.ts` \u203A twin lanes share one promotion pins the pair). New in the wave-11 responsive/touch guards; reaching CSS is a NEW file type for this check, same reasoning as `css-raw-surface`, with incumbents in basalt's own tree at ship time, so it shipped warn. Extended 1.32.0 -> 1.33.0 with its twin: 1.31.0 named no reachable container primitive, and 1.32.0 is the first minor that documents one (`CONTAINER_CLASSES` + the declared `@container` names, MIGRATING \xA7 `basalt/raw-breakpoint`)."
240
241
  }
241
242
  };
242
243
  function severityOf(kind, cfg) {
@@ -1 +1 @@
1
- {"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../../src/notifications/center.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAyClD;;4CAE4C;AAC5C,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAwF7C,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,SAAe,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,uBAAuB,+BAyEhG"}
1
+ {"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../../src/notifications/center.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAyClD;;4CAE4C;AAC5C,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAwF7C,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAClD,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,SAAe,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,uBAAuB,+BA4EhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACzD,OAAO,EAAE,SAAS,EAAsB,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAIjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAA;AAIrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AASjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAA;AAEnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,CAAA;IACnB,oFAAoF;IACpF,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACrC;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAA;IAC/D;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC3D;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,IAAI,CAAC,yBAAyB,EAAE,UAAU,CAAC,CAAA;IAC1D;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,SAAS,CAAA;CAC1B,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,OAAO,GAAG,oBAAoB,GAAG,sBAAsB,CAAC,CAAA;AAcpG;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,SAAS,CAAA;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAA;CACvD,CAAA;AAED,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AA2B1D;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,SAAS,CAAC,wBAAwB,EAAE,aAAa,CAAC;gBAC7E,KAAK,EAAE,wBAAwB;IAK3C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;IAIrD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAKxD,MAAM,IAAI,SAAS;CAS7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAOD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAoBvC;AAwHD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,EACL,aAAoB,EACpB,cAAc,EACd,kBAA2B,EAC3B,OAAO,EACP,KAAK,EACL,YAAY,EACZ,aAAyB,EACzB,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAsCrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"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"}
@@ -70,17 +70,22 @@ class BasaltErrorBoundary extends Component {
70
70
  function composeInjectedCss(base, fontsCss, radiusCss, densityCss) {
71
71
  return [base, fontsCss, radiusCss, densityCss].filter(Boolean).join("\n");
72
72
  }
73
- let hostAttributeCount = 0;
73
+ function createMountCounter() {
74
+ let count = 0;
75
+ return { mount: () => ++count, unmount: () => --count };
76
+ }
77
+ const hostAttributeMounts = createMountCounter();
74
78
  function useHostAttribute() {
75
79
  const isPwa = useMediaQuery("(display-mode: standalone)", false);
76
80
  useIsomorphicLayoutEffect(() => {
77
81
  document.documentElement.setAttribute("data-basalt-host", isPwa ? "pwa" : "web");
78
82
  }, [isPwa]);
79
83
  useIsomorphicLayoutEffect(() => {
80
- hostAttributeCount++;
84
+ hostAttributeMounts.mount();
81
85
  return () => {
82
- hostAttributeCount--;
83
- if (hostAttributeCount === 0) document.documentElement.removeAttribute("data-basalt-host");
86
+ if (hostAttributeMounts.unmount() === 0) {
87
+ document.documentElement.removeAttribute("data-basalt-host");
88
+ }
84
89
  };
85
90
  }, []);
86
91
  }
@@ -134,18 +139,15 @@ function BasaltBridge({
134
139
  children
135
140
  ] });
136
141
  }
137
- let mountedProviderCount = 0;
142
+ const providerMounts = createMountCounter();
138
143
  function useDuplicateProviderGuard() {
139
144
  useEffect(() => {
140
- mountedProviderCount++;
141
- if (mountedProviderCount > 1 && isDev()) {
145
+ if (providerMounts.mount() > 1 && isDev()) {
142
146
  console.warn(
143
147
  "[basalt] BasaltProvider: more than one instance is mounted at once \u2014 nested/duplicate BasaltProviders double-mount MantineProvider, the palette <style> and ConnectivityProvider. Mount exactly one, at the app root."
144
148
  );
145
149
  }
146
- return () => {
147
- mountedProviderCount--;
148
- };
150
+ return () => void providerMounts.unmount();
149
151
  }, []);
150
152
  }
151
153
  function BasaltProvider({
@@ -0,0 +1,31 @@
1
+ import type { SizeClass } from './use-size-class.js';
2
+ export type AsideDockingInput = {
3
+ claimed: boolean;
4
+ folded: boolean;
5
+ sizeClass: SizeClass;
6
+ /** The `asideDockQuery` media query's result. */
7
+ roomToDock: boolean;
8
+ asideWidth: number;
9
+ asideRailWidth: number;
10
+ };
11
+ export type AsideDocking = {
12
+ /** An open aside pushes main rather than overlaying it. */
13
+ docks: boolean;
14
+ /** An open aside renders over main (reserving only its rail). */
15
+ overlay: boolean;
16
+ /** The width `AppShell` reserves for the region. */
17
+ width: number;
18
+ };
19
+ /** The `min-width` query that holds while navbar + open aside + `MAIN_MIN_WIDTH` fit side by side. */
20
+ export declare function asideDockQuery(input: {
21
+ navbarWidth: number;
22
+ asideWidth: number;
23
+ }): string;
24
+ /**
25
+ * The room-to-dock query's SSR/no-`matchMedia` fallback — an `'expanded'` `sizeClassHint` means the
26
+ * server already believes there is room, matching `useSizeClass`'s own hint-seeded guard
27
+ * (`use-size-class.ts`), so the first client paint doesn't fold a default-open aside for one commit.
28
+ */
29
+ export declare function roomToDockServerFallback(sizeClassHint: SizeClass | undefined): boolean;
30
+ export declare function resolveAsideDocking(input: AsideDockingInput): AsideDocking;
31
+ //# sourceMappingURL=aside-docking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aside-docking.d.ts","sourceRoot":"","sources":["../../src/shell/aside-docking.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAQjD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,SAAS,CAAA;IACpB,iDAAiD;IACjD,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,2DAA2D;IAC3D,KAAK,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAEtF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAK1E"}
@@ -0,0 +1,19 @@
1
+ import { toEm } from "../tokens/size-classes.js";
2
+ const MAIN_MIN_WIDTH = 720;
3
+ function asideDockQuery(input) {
4
+ return `(min-width: ${toEm(input.navbarWidth + input.asideWidth + MAIN_MIN_WIDTH)})`;
5
+ }
6
+ function roomToDockServerFallback(sizeClassHint) {
7
+ return sizeClassHint === "expanded";
8
+ }
9
+ function resolveAsideDocking(input) {
10
+ const docks = input.sizeClass === "expanded" && input.roomToDock;
11
+ const open = input.claimed && !input.folded;
12
+ const reserved = open && docks ? input.asideWidth : input.asideRailWidth;
13
+ return { docks, overlay: open && !docks, width: input.claimed ? reserved : 0 };
14
+ }
15
+ export {
16
+ asideDockQuery,
17
+ resolveAsideDocking,
18
+ roomToDockServerFallback
19
+ };
@@ -4,7 +4,6 @@ import type { GlobalAction } from '../controls/actions.js';
4
4
  import type { AccountMenuItem, BasaltAccountProps } from './account-types.js';
5
5
  import type { SidebarSearchActions, SidebarSearchConfig } from './sidebar-search.js';
6
6
  import type { MobileNavConfig, SidebarBlock, SidebarSection } from './nav-types.js';
7
- import type { SizeClass } from './use-size-class.js';
8
7
  export { AppSidebar, type AppSidebarProps } from './app-sidebar.js';
9
8
  export { NavCountBadge, type NavCountBadgeProps } from './nav-count-badge.js';
10
9
  export { SidebarSearch, type SidebarSearchProps, type SidebarSearchConfig, type SidebarSearchActions, } from './sidebar-search.js';
@@ -136,16 +135,6 @@ export type BasaltShellProps = BasaltProps & {
136
135
  /** Page content. */
137
136
  children?: ReactNode;
138
137
  };
139
- /**
140
- * The room-to-dock `min-width` query's SSR/no-`matchMedia` fallback — an `'expanded'`
141
- * `sizeClassHint` means the server already believes there is room, matching `useSizeClass`'s own
142
- * hint-seeded guard one level up (`use-size-class.ts`). Extracted as its own pure function (rather
143
- * than the inline `sizeClassHint === 'expanded'` it replaces) purely so `index.test.tsx` can pin it
144
- * without needing to render `BasaltShell` through a real SSR pass — `AppShell`'s + `PageAside`'s own
145
- * effects/refs/portals make that path unobservable through rendered markup, and the previous
146
- * hardcoded `false` fallback here shipped with no test at all for exactly that reason.
147
- */
148
- export declare function roomToDockServerFallback(sizeClassHint: SizeClass | undefined): boolean;
149
138
  /**
150
139
  * The shell's two page-level regions are providers, and both wrap the frame rather than living
151
140
  * inside it: `PageBarProvider` owns the header portal and the single-kebab claim, `AsideProvider`
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.tsx"],"names":[],"mappings":"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"}
@@ -12,10 +12,10 @@ import { blockRowCount, projectMobileNav } from "./mobile-nav-model.js";
12
12
  import { AppBreadcrumbs } from "./app-breadcrumbs.js";
13
13
  import { PageBarBandOutlet, PageBarOutlet, PageBarProvider, usePageKebabClaimed } from "./page-bar.js";
14
14
  import { AsideDocksContext, AsideOutlet, AsideProvider, useAsideRegion } from "./page-aside.js";
15
+ import { asideDockQuery, resolveAsideDocking, roomToDockServerFallback } from "./aside-docking.js";
15
16
  import { OverflowMenu, globalActionAsBarAction, globalActionMobile } from "../controls/actions.js";
16
17
  import { CtlSlot, useBasaltSpacing } from "../theme/index.js";
17
18
  import { createPersistedStore } from "../state/persisted.js";
18
- import { toEm } from "../tokens/size-classes.js";
19
19
  import { SizeClassHintContext, useSizeClass } from "./use-size-class.js";
20
20
  import brandClasses from "./app-brand.module.css";
21
21
  import headerClasses from "./app-header.module.css";
@@ -73,10 +73,6 @@ function collapseStore(key) {
73
73
  collapseStores.set(key, store);
74
74
  return store;
75
75
  }
76
- const MAIN_MIN_WIDTH = 720;
77
- function roomToDockServerFallback(sizeClassHint) {
78
- return sizeClassHint === "expanded";
79
- }
80
76
  function BasaltShell(props) {
81
77
  assertRequiredProps("BasaltShell", props, ["brand", "sections"]);
82
78
  return /* @__PURE__ */ jsx(PageBarProvider, { globalActions: props.globalActions ?? [], children: /* @__PURE__ */ jsx(AsideProvider, { children: /* @__PURE__ */ jsx(ShellFrame, { ...props }) }) });
@@ -109,14 +105,18 @@ function ShellFrame({
109
105
  const navbarWidth = collapsed ? step.appShellNavbarRailWidth : step.appShellNavbarWidth;
110
106
  const sizeClassHint = useContext(SizeClassHintContext);
111
107
  const roomToDock = useMediaQuery(
112
- `(min-width: ${toEm(navbarWidth + step.appShellAsideWidth + MAIN_MIN_WIDTH)})`,
108
+ asideDockQuery({ navbarWidth, asideWidth: step.appShellAsideWidth }),
113
109
  roomToDockServerFallback(sizeClassHint)
114
110
  );
115
- const docks = sizeClass === "expanded" && roomToDock;
116
- const asideOpen = aside.claimed && !aside.folded;
117
- const asideOverlay = asideOpen && !docks;
118
- const asideReserved = asideOpen && docks ? step.appShellAsideWidth : step.appShellAsideRailWidth;
119
- const asideWidth = aside.claimed ? asideReserved : 0;
111
+ const docking = resolveAsideDocking({
112
+ claimed: aside.claimed,
113
+ folded: aside.folded,
114
+ sizeClass,
115
+ roomToDock,
116
+ asideWidth: step.appShellAsideWidth,
117
+ asideRailWidth: step.appShellAsideRailWidth
118
+ });
119
+ const asideOverlay = docking.overlay;
120
120
  const { requestClose } = aside;
121
121
  useEffect(() => {
122
122
  if (!asideOverlay) return;
@@ -141,7 +141,7 @@ function ShellFrame({
141
141
  () => projectMobileNav(sections, { config: mobileNav, extraMoreRows }),
142
142
  [sections, mobileNav, extraMoreRows]
143
143
  );
144
- return /* @__PURE__ */ jsx(AsideDocksContext.Provider, { value: docks, children: /* @__PURE__ */ jsxs(
144
+ return /* @__PURE__ */ jsx(AsideDocksContext.Provider, { value: docking.docks, children: /* @__PURE__ */ jsxs(
145
145
  AppShell,
146
146
  {
147
147
  h: "100dvh",
@@ -157,7 +157,7 @@ function ShellFrame({
157
157
  },
158
158
  aside: {
159
159
  // An overlay reserves the rail only — `<AppShell.Aside>` below widens itself over main.
160
- width: asideWidth,
160
+ width: docking.width,
161
161
  breakpoint: "sm",
162
162
  // Below `sm` there is no region at all — `PageAside` renders its content in the page
163
163
  // flow instead, one node, no responsive twin (law C9).
@@ -256,7 +256,6 @@ export {
256
256
  SidebarAccount,
257
257
  SidebarSearch,
258
258
  projectMobileNav2 as projectMobileNav,
259
- roomToDockServerFallback,
260
259
  useBreakpoint,
261
260
  useSizeClass2 as useSizeClass
262
261
  };
@@ -1,6 +1,6 @@
1
1
  export type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
2
2
  /**
3
- * @deprecated Use `useSizeClass` — removed next minor (see `MIGRATING.md`). `theme.breakpoints` is
3
+ * @deprecated Use `useSizeClass` — removed in 1.33.0 (see `MIGRATING.md`). `theme.breakpoints` is
4
4
  * now derived: `sm` moved 48em → 52.5em and `md`/`lg`/`xl` all sit at 75em (`xs`/`sm` collapse,
5
5
  * `md`/`lg`/`xl` collapse).
6
6
  */
@@ -42,6 +42,7 @@
42
42
  * `.sheetBody` is the Modal body; it reaches the trailing button row `overlays.confirm`
43
43
  * renders (Mantine `Group`), stretching the buttons to full width at the `--vx-hit` floor.
44
44
  */
45
+ /* theme-allow raw-media-query — the bottom sheet IS the compact size class's overlay form */
45
46
  @media (max-width: 52.49375em) {
46
47
  .sheetInner {
47
48
  align-items: flex-end;
@@ -1 +1 @@
1
- {"version":3,"file":"derive-controls.d.ts","sourceRoot":"","sources":["../../src/theme-lab/derive-controls.tsx"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAiBtC,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAiDpG,MAAM,MAAM,mBAAmB,GAAG;IAChC,uFAAuF;IACvF,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,+BAwFhE"}
1
+ {"version":3,"file":"derive-controls.d.ts","sourceRoot":"","sources":["../../src/theme-lab/derive-controls.tsx"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAiBtC,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAiDpG,MAAM,MAAM,mBAAmB,GAAG;IAChC,uFAAuF;IACvF,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,+BA0FhE"}
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v1.31.0
1
+ # basalt-ui v1.32.0
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
3
3
  #
4
4
  # basalt-ui is an opinionated framework for Mantine-based React apps.
@@ -19,7 +19,7 @@
19
19
  # ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
20
20
 
21
21
  Package: basalt-ui
22
- Version: 1.31.0
22
+ Version: 1.32.0
23
23
 
24
24
  ## basalt-ui
25
25
  Import: import { ... } from 'basalt-ui'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -171,6 +171,7 @@
171
171
  "@shikijs/themes": "^4.4.3",
172
172
  "@tanstack/react-hotkeys": "0.10.0",
173
173
  "@tanstack/react-router-devtools": "^1.167.2",
174
+ "@testing-library/react": "16.3.3",
174
175
  "@types/bun": "^1.4.2",
175
176
  "@types/hast": "^3.0.5",
176
177
  "@types/react": "^19.3.0",
@@ -8,21 +8,15 @@ import type { ChartMargin } from '../../tokens'
8
8
  import type { CursorResolution } from '../cursor/resolve'
9
9
  import { useBandPlot } from '../hooks/useBandPlot'
10
10
  import type { BandFold, BandTooltipConfig } from '../hooks/useBandPlot'
11
- import { AxisBottomDate } from '../primitives/Axes'
12
11
  import { ChartFrame, resolveLegend } from '../primitives/ChartFrame'
13
12
  import type { ResponsiveChartHeight } from '../primitives/ChartFrame'
14
- import {
15
- ChartTooltipFloat,
16
- TooltipBody,
17
- TooltipHeader,
18
- TooltipRow,
19
- } from '../primitives/ChartTooltip'
13
+ import { TooltipRow } from '../primitives/ChartTooltip'
20
14
  import { Crosshair } from '../primitives/Crosshair'
21
15
  import { HatchPattern, hatchFill, hatchSizeFor } from '../primitives/HatchPattern'
22
- import { HoverOverlay } from '../primitives/HoverOverlay'
23
16
  import type { ChartState } from '../primitives/ChartPending'
24
17
  import type { ChartLegendConfig, SeriesStyle } from '../series'
25
18
  import { fmtAxisDate } from '../utils/format'
19
+ import { BandAxisOverlay, BandTooltip, clampFraction } from './band-chrome'
26
20
  import { isDev } from '../../common/is-dev'
27
21
 
28
22
  /**
@@ -256,7 +250,7 @@ function BandStripPlot<T>(props: BandStripPlotProps<T>) {
256
250
  ...(tooltip !== undefined && { tooltip }),
257
251
  })
258
252
 
259
- const { bands, margin, plotWidth, scale, cursor, point, crosshairX, step, bandWidth } = band
253
+ const { bands, margin, plotWidth, point, crosshairX, step, bandWidth } = band
260
254
  const stripHeight = Math.max(plot.height - margin.top - margin.bottom, 1)
261
255
  const hatchId = `${chartId}-band-absent`
262
256
  if (absentState !== undefined && !styleByKey.has(absentState)) {
@@ -351,8 +345,6 @@ function BandStripPlot<T>(props: BandStripPlotProps<T>) {
351
345
 
352
346
  if (plotWidth <= 0 || bands.length === 0) return null
353
347
 
354
- const cfg = tooltip === false ? undefined : tooltip
355
- const badge = cfg?.label === undefined || point === null ? null : cfg.label(point)
356
348
  const row = point === null ? null : deriveBandRow(getBand(point), styleByKey, hidden, point)
357
349
 
358
350
  return (
@@ -383,69 +375,31 @@ function BandStripPlot<T>(props: BandStripPlotProps<T>) {
383
375
 
384
376
  {crosshairX !== null && <Crosshair x={crosshairX} top={0} bottom={stripHeight} />}
385
377
 
386
- <AxisBottomDate
387
- top={stripHeight}
388
- scale={scale}
389
- tickValues={band.tickValues}
390
- tickFormat={(v) => formatX(String(v))}
391
- anchorTerminals={band.xAnchorTerminals}
392
- {...(band.xWrapWidth !== undefined && { wrapWidth: band.xWrapWidth })}
393
- />
394
-
395
- <HoverOverlay
396
- width={plotWidth}
378
+ <BandAxisOverlay
379
+ band={band}
397
380
  height={stripHeight}
398
- onMove={cursor.onPointerMove}
399
- onDown={cursor.onPointerDown}
400
- onUp={cursor.onPointerUp}
401
- onLeave={cursor.onPointerLeave}
402
- onKeyDown={cursor.onKeyDown}
403
- onBlur={cursor.onBlur}
404
- valueMax={Math.max(bands.length - 1, 0)}
405
- {...(point !== null && {
406
- valueNow: bands.indexOf(point),
407
- valueText: formatX(getX(point)),
408
- })}
409
- {...(ariaLabel !== undefined && { ariaLabel })}
381
+ getX={getX}
382
+ formatX={formatX}
383
+ ariaLabel={ariaLabel}
410
384
  />
411
385
  </Group>
412
386
  </svg>
413
387
 
414
- {band.showTooltip && point !== null && (
415
- <ChartTooltipFloat anchor={band.tooltipAnchor} ariaLive={band.ariaLive}>
416
- <TooltipHeader
417
- date={getX(point)}
418
- {...(cfg?.formatHeader !== undefined && {
419
- format: (key: string) => cfg.formatHeader?.(key, point) ?? key,
420
- })}
421
- {...(badge !== null && { label: badge.text, labelColor: badge.color })}
388
+ <BandTooltip band={band} tooltip={tooltip} getX={getX} hidden={hidden}>
389
+ {row !== null && (
390
+ <TooltipRow
391
+ color={row.color}
392
+ label={row.label}
393
+ value={row.value}
394
+ shape={row.shape}
395
+ dashed={row.dashed}
422
396
  />
423
- <TooltipBody>
424
- {cfg?.prependRows?.(point, { hidden })}
425
- {row !== null && (
426
- <TooltipRow
427
- color={row.color}
428
- label={row.label}
429
- value={row.value}
430
- shape={row.shape}
431
- dashed={row.dashed}
432
- />
433
- )}
434
- {cfg?.extraRows?.(point, { hidden })}
435
- </TooltipBody>
436
- </ChartTooltipFloat>
437
- )}
397
+ )}
398
+ </BandTooltip>
438
399
  </>
439
400
  )
440
401
  }
441
402
 
442
- /** A 0..1 share. Non-finite (a 0/0 fold count) reads as "nothing is absent" — the conservative
443
- * end, since the alternative is a band that does not render at all. */
444
- function clampFraction(value: number | undefined): number {
445
- if (value === undefined || !Number.isFinite(value)) return 0
446
- return Math.min(Math.max(value, 0), 1)
447
- }
448
-
449
403
  /**
450
404
  * The strip's one derived row: the hovered band's STATE, named and formatted by its own `series`
451
405
  * entry. A strip cannot show a row for a state it does not draw, and cannot name a state the
@@ -13,11 +13,12 @@ import {
13
13
  TooltipBody,
14
14
  TooltipHeader,
15
15
  TooltipRow,
16
+ TooltipRows,
16
17
  } from '../primitives/ChartTooltip'
17
18
  import { ChartFrame, resolveLegend } from '../primitives/ChartFrame'
18
19
  import type { ResponsiveChartHeight } from '../primitives/ChartFrame'
19
20
  import { Crosshair, SeriesDot } from '../primitives/Crosshair'
20
- import { HoverOverlay } from '../primitives/HoverOverlay'
21
+ import { cursorSliderProps, HoverOverlay } from '../primitives/HoverOverlay'
21
22
  import { ZoneRects } from '../primitives/ZoneRects'
22
23
  import type { ZoneSpec } from '../primitives/ZoneRects'
23
24
  import type { ChartState } from '../primitives/ChartPending'
@@ -595,18 +596,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
595
596
  <HoverOverlay
596
597
  width={xMax}
597
598
  height={topH + PANE_GAP}
598
- onMove={cursor.onPointerMove}
599
- onDown={cursor.onPointerDown}
600
- onUp={cursor.onPointerUp}
601
- onLeave={cursor.onPointerLeave}
602
- onKeyDown={cursor.onKeyDown}
603
- onBlur={cursor.onBlur}
604
- valueMax={Math.max(data.length - 1, 0)}
605
- {...(point !== null && {
606
- valueNow: data.indexOf(point),
607
- valueText: formatX(getX(point)),
608
- })}
609
- {...(ariaLabel !== undefined && { ariaLabel })}
599
+ {...cursorSliderProps({ cursor, data, getX, formatX, ariaLabel })}
610
600
  />
611
601
  </Group>
612
602
 
@@ -688,17 +678,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
688
678
  })()}
689
679
  />
690
680
  <TooltipBody>
691
- {deriveTooltipRows(visibleSeries, point, formatTop).map((row) => (
692
- <TooltipRow
693
- key={row.key}
694
- color={row.color}
695
- label={row.label}
696
- value={row.value}
697
- shape={row.shape}
698
- dashed={row.dashed}
699
- {...(row.strokeWidth !== undefined && { strokeWidth: row.strokeWidth })}
700
- />
701
- ))}
681
+ <TooltipRows rows={deriveTooltipRows(visibleSeries, point, formatTop)} />
702
682
  {barVisible &&
703
683
  (() => {
704
684
  const v = getBar(point)
@@ -10,22 +10,17 @@ import type { CursorResolution } from '../cursor/resolve'
10
10
  import { useBandPlot } from '../hooks/useBandPlot'
11
11
  import type { BandFold, BandTooltipConfig } from '../hooks/useBandPlot'
12
12
  import { probeAxisLabels } from '../layout/auto-margin'
13
- import { AxisBottomDate, AxisLeftNumeric } from '../primitives/Axes'
13
+ import { AxisLeftNumeric } from '../primitives/Axes'
14
14
  import { ChartFrame, resolveLegend } from '../primitives/ChartFrame'
15
15
  import type { ResponsiveChartHeight } from '../primitives/ChartFrame'
16
- import {
17
- ChartTooltipFloat,
18
- TooltipBody,
19
- TooltipHeader,
20
- TooltipRow,
21
- } from '../primitives/ChartTooltip'
16
+ import { TooltipRows } from '../primitives/ChartTooltip'
22
17
  import { Crosshair } from '../primitives/Crosshair'
23
18
  import { HatchPattern, hatchFill, hatchSizeFor } from '../primitives/HatchPattern'
24
- import { HoverOverlay } from '../primitives/HoverOverlay'
25
19
  import type { ChartState } from '../primitives/ChartPending'
26
20
  import { deriveTooltipRows } from '../series'
27
21
  import type { ChartLegendConfig, ChartSeries } from '../series'
28
22
  import { fmtAxisDate } from '../utils/format'
23
+ import { BandAxisOverlay, BandTooltip, clampFraction } from './band-chrome'
29
24
 
30
25
  /** One of the two panes. Each resolves its own domain — that is the whole point of the kind. */
31
26
  export type MirroredBarPane = {
@@ -242,7 +237,7 @@ function MirroredBarsPlot<T>(props: MirroredBarsPlotProps<T>) {
242
237
  ...(tooltip !== undefined && { tooltip }),
243
238
  })
244
239
 
245
- const { bands, margin, plotWidth, scale, cursor, point, crosshairX, step, bandWidth } = band
240
+ const { bands, margin, plotWidth, point, crosshairX, step, bandWidth } = band
246
241
 
247
242
  // ── Pass 2: the pane rects, now that the plot height is known ───────────────────────────────
248
243
  const barBand = Math.max(plot.height - margin.top - margin.bottom, 2)
@@ -346,8 +341,6 @@ function MirroredBarsPlot<T>(props: MirroredBarsPlotProps<T>) {
346
341
 
347
342
  if (plotWidth <= 0 || bands.length === 0) return null
348
343
 
349
- const cfg = tooltip === false ? undefined : tooltip
350
- const badge = cfg?.label === undefined || point === null ? null : cfg.label(point)
351
344
  // Each pane's row falls back to ITS OWN axis formatter, never the other pane's — the two panes
352
345
  // are in different units by construction (an explicit per-series `formatValue` still wins).
353
346
  const visibleSeries = series
@@ -419,70 +412,23 @@ function MirroredBarsPlot<T>(props: MirroredBarsPlotProps<T>) {
419
412
 
420
413
  {crosshairX !== null && <Crosshair x={crosshairX} top={0} bottom={barBand} />}
421
414
 
422
- <AxisBottomDate
423
- top={barBand}
424
- scale={scale}
425
- tickValues={band.tickValues}
426
- tickFormat={(v) => formatX(String(v))}
427
- anchorTerminals={band.xAnchorTerminals}
428
- {...(band.xWrapWidth !== undefined && { wrapWidth: band.xWrapWidth })}
429
- />
430
-
431
- <HoverOverlay
432
- width={plotWidth}
415
+ <BandAxisOverlay
416
+ band={band}
433
417
  height={barBand}
434
- onMove={cursor.onPointerMove}
435
- onDown={cursor.onPointerDown}
436
- onUp={cursor.onPointerUp}
437
- onLeave={cursor.onPointerLeave}
438
- onKeyDown={cursor.onKeyDown}
439
- onBlur={cursor.onBlur}
440
- valueMax={Math.max(bands.length - 1, 0)}
441
- {...(point !== null && {
442
- valueNow: bands.indexOf(point),
443
- valueText: formatX(getX(point)),
444
- })}
445
- {...(ariaLabel !== undefined && { ariaLabel })}
418
+ getX={getX}
419
+ formatX={formatX}
420
+ ariaLabel={ariaLabel}
446
421
  />
447
422
  </Group>
448
423
  </svg>
449
424
 
450
- {band.showTooltip && point !== null && (
451
- <ChartTooltipFloat anchor={band.tooltipAnchor} ariaLive={band.ariaLive}>
452
- <TooltipHeader
453
- date={getX(point)}
454
- {...(cfg?.formatHeader !== undefined && {
455
- format: (key: string) => cfg.formatHeader?.(key, point) ?? key,
456
- })}
457
- {...(badge !== null && { label: badge.text, labelColor: badge.color })}
458
- />
459
- <TooltipBody>
460
- {cfg?.prependRows?.(point, { hidden })}
461
- {rows.map((row) => (
462
- <TooltipRow
463
- key={row.key}
464
- color={row.color}
465
- label={row.label}
466
- value={row.value}
467
- shape={row.shape}
468
- dashed={row.dashed}
469
- {...(row.strokeWidth !== undefined && { strokeWidth: row.strokeWidth })}
470
- />
471
- ))}
472
- {cfg?.extraRows?.(point, { hidden })}
473
- </TooltipBody>
474
- </ChartTooltipFloat>
475
- )}
425
+ <BandTooltip band={band} tooltip={tooltip} getX={getX} hidden={hidden}>
426
+ <TooltipRows rows={rows} />
427
+ </BandTooltip>
476
428
  </>
477
429
  )
478
430
  }
479
431
 
480
- /** A 0..1 share, non-finite included — see `BandStrip`'s identical guard. */
481
- function clampFraction(value: number | undefined): number {
482
- if (value === undefined || !Number.isFinite(value)) return 0
483
- return Math.min(Math.max(value, 0), 1)
484
- }
485
-
486
432
  /** A drawable value, or null. See the call site for why NaN is not drawable. */
487
433
  function finiteOrNull(v: number | null | undefined): number | null {
488
434
  return v === null || v === undefined || Number.isNaN(v) ? null : v