basalt-ui 1.30.2 → 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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +54 -0
- package/MIGRATING.md +256 -12
- package/agent/rules/basalt-controls.md +19 -19
- package/agent/rules/basalt-mantine.md +5 -5
- package/configs/oxlint-plugin.js +371 -4
- package/configs/oxlint-plugin.test.ts +327 -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 +47 -82
- 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 +138 -145
- 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 +68 -111
- 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/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 +264 -145
- 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 +56 -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 +116 -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 +86 -73
- 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 +20 -4
- package/dist/charts/primitives/Crosshair.js +2 -2
- package/dist/charts/primitives/HoverOverlay.d.ts +29 -9
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +54 -27
- 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 +33 -0
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
- package/dist/charts/primitives/chart-card-context.js +18 -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 +75 -34
- 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/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/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 +13 -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 +2 -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 +33 -66
- package/dist/dashboard/widget-grid.d.ts.map +1 -1
- package/dist/dashboard/widget-grid.module.css +11 -38
- 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-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 +56 -2
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +436 -336
- 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 +20 -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 +68 -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/notifications/center.d.ts.map +1 -1
- package/dist/provider/index.d.ts +20 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +30 -8
- 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/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 +1 -0
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +56 -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 +37 -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/theme-lab/derive-controls.d.ts.map +1 -1
- 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 +18 -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 +25 -61
- package/src/charts/kinds/Donut.tsx +153 -62
- package/src/charts/kinds/DualPanel.tsx +21 -26
- package/src/charts/kinds/Heatmap.tsx +61 -22
- package/src/charts/kinds/MirroredBars.tsx +23 -65
- package/src/charts/kinds/band-chrome.tsx +91 -0
- 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 -111
- package/src/charts/primitives/ChartCard.tsx +119 -48
- package/src/charts/primitives/ChartFrame.tsx +204 -60
- package/src/charts/primitives/ChartLegend.tsx +115 -78
- package/src/charts/primitives/ChartTooltip.tsx +24 -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 +62 -20
- package/src/charts/primitives/chart-card-context.ts +46 -0
- package/src/charts/primitives/chart-frame-layout.ts +207 -182
- package/src/charts/primitives/chart-layout.ts +318 -0
- package/src/charts/primitives/chart-tier.tsx +66 -21
- package/src/charts/primitives/legend-disclosure.tsx +177 -0
- 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 +13 -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 +2 -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 +33 -66
- package/src/dashboard/widget-grid.module.css +11 -38
- 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-state.ts +218 -0
- 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 +547 -299
- package/src/data/virtual-list.tsx +31 -0
- package/src/forms/form-layout.module.css +20 -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 +188 -3
- package/src/guard/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/notifications/center.tsx +3 -0
- package/src/provider/index.tsx +60 -7
- 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/aside-docking.ts +54 -0
- package/src/shell/index.tsx +178 -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 +37 -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/theme-lab/derive-controls.tsx +2 -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
|
@@ -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,
|
|
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,13 +345,17 @@ 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 (
|
|
359
351
|
<>
|
|
360
|
-
<svg
|
|
352
|
+
<svg
|
|
353
|
+
ref={band.svgRef}
|
|
354
|
+
width={plot.width}
|
|
355
|
+
height={plot.height}
|
|
356
|
+
// `touch-action` on the plot `<svg>`: Chrome ignores it on the overlay `<rect>` (`R2C-2`).
|
|
357
|
+
style={{ touchAction: 'pan-y' }}
|
|
358
|
+
>
|
|
361
359
|
<defs>
|
|
362
360
|
<HatchPattern
|
|
363
361
|
id={hatchId}
|
|
@@ -377,65 +375,31 @@ function BandStripPlot<T>(props: BandStripPlotProps<T>) {
|
|
|
377
375
|
|
|
378
376
|
{crosshairX !== null && <Crosshair x={crosshairX} top={0} bottom={stripHeight} />}
|
|
379
377
|
|
|
380
|
-
<
|
|
381
|
-
|
|
382
|
-
scale={scale}
|
|
383
|
-
tickValues={band.tickValues}
|
|
384
|
-
tickFormat={(v) => formatX(String(v))}
|
|
385
|
-
/>
|
|
386
|
-
|
|
387
|
-
<HoverOverlay
|
|
388
|
-
width={plotWidth}
|
|
378
|
+
<BandAxisOverlay
|
|
379
|
+
band={band}
|
|
389
380
|
height={stripHeight}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
onBlur={cursor.onBlur}
|
|
394
|
-
valueMax={Math.max(bands.length - 1, 0)}
|
|
395
|
-
{...(point !== null && {
|
|
396
|
-
valueNow: bands.indexOf(point),
|
|
397
|
-
valueText: formatX(getX(point)),
|
|
398
|
-
})}
|
|
399
|
-
{...(ariaLabel !== undefined && { ariaLabel })}
|
|
381
|
+
getX={getX}
|
|
382
|
+
formatX={formatX}
|
|
383
|
+
ariaLabel={ariaLabel}
|
|
400
384
|
/>
|
|
401
385
|
</Group>
|
|
402
386
|
</svg>
|
|
403
387
|
|
|
404
|
-
{band
|
|
405
|
-
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
{
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
{
|
|
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}
|
|
412
396
|
/>
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{row !== null && (
|
|
416
|
-
<TooltipRow
|
|
417
|
-
color={row.color}
|
|
418
|
-
label={row.label}
|
|
419
|
-
value={row.value}
|
|
420
|
-
shape={row.shape}
|
|
421
|
-
dashed={row.dashed}
|
|
422
|
-
/>
|
|
423
|
-
)}
|
|
424
|
-
{cfg?.extraRows?.(point, { hidden })}
|
|
425
|
-
</TooltipBody>
|
|
426
|
-
</ChartTooltipFloat>
|
|
427
|
-
)}
|
|
397
|
+
)}
|
|
398
|
+
</BandTooltip>
|
|
428
399
|
</>
|
|
429
400
|
)
|
|
430
401
|
}
|
|
431
402
|
|
|
432
|
-
/** A 0..1 share. Non-finite (a 0/0 fold count) reads as "nothing is absent" — the conservative
|
|
433
|
-
* end, since the alternative is a band that does not render at all. */
|
|
434
|
-
function clampFraction(value: number | undefined): number {
|
|
435
|
-
if (value === undefined || !Number.isFinite(value)) return 0
|
|
436
|
-
return Math.min(Math.max(value, 0), 1)
|
|
437
|
-
}
|
|
438
|
-
|
|
439
403
|
/**
|
|
440
404
|
* The strip's one derived row: the hovered band's STATE, named and formatted by its own `series`
|
|
441
405
|
* entry. A strip cannot show a row for a state it does not draw, and cannot name a state the
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Group } from '@visx/group'
|
|
2
2
|
import { Pie } from '@visx/shape'
|
|
3
|
-
import { memo, useMemo
|
|
4
|
-
import type {
|
|
3
|
+
import { memo, useMemo } from 'react'
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
import { useChartSize } from '../hooks/useChartSize'
|
|
6
|
+
import { useDiscreteCursor } from '../hooks/use-discrete-cursor'
|
|
7
|
+
import { useHysteresis } from '../hooks/use-hysteresis'
|
|
5
8
|
import { assertRequiredProps } from '../../common/validate'
|
|
6
9
|
import type { BasaltProps } from '../../common/props'
|
|
7
10
|
import { ChartTooltipFloat, TooltipBody, TooltipRow } from '../primitives/ChartTooltip'
|
|
@@ -9,6 +12,15 @@ import { ChartFrame } from '../primitives/ChartFrame'
|
|
|
9
12
|
import type { ResponsiveChartHeight } from '../primitives/ChartFrame'
|
|
10
13
|
import type { ChartState } from '../primitives/ChartPending'
|
|
11
14
|
import { VX } from '../../tokens'
|
|
15
|
+
import { resolveContainerClass } from '../../tokens/size-classes'
|
|
16
|
+
import { resolveFrameHeight } from '../primitives/chart-frame-layout'
|
|
17
|
+
import {
|
|
18
|
+
foldOther,
|
|
19
|
+
resolveDonutLayout,
|
|
20
|
+
resolveDonutRadius,
|
|
21
|
+
resolveOtherKey,
|
|
22
|
+
sharePercent,
|
|
23
|
+
} from './donut-layout'
|
|
12
24
|
import type { SeriesStyle } from '../series'
|
|
13
25
|
import type { SeriesKey } from '../../register'
|
|
14
26
|
|
|
@@ -64,14 +76,22 @@ export type DonutProps<K extends string = SeriesKey> = BasaltProps & {
|
|
|
64
76
|
state?: ChartState
|
|
65
77
|
}
|
|
66
78
|
|
|
79
|
+
const identityLabel = (key: string): string => key
|
|
80
|
+
|
|
67
81
|
/**
|
|
68
82
|
* Radial slice-share chart with a punched-out center label. Composes `ChartFrame` for a
|
|
69
83
|
* categorical legend derived from the slices (one `SeriesStyle` per slice, `mark: 'bar'`) so the
|
|
70
|
-
* legend can never drift from what's plotted — legend-hidden slices drop out of the ring
|
|
71
|
-
*
|
|
72
|
-
* meaningless for a radial layout. Hover stays local to the pie (dimming siblings on hover) rather
|
|
84
|
+
* legend can never drift from what's plotted — legend-hidden slices drop out of the ring and the
|
|
85
|
+
* tooltip together, while every share is taken of ALL slices so the legend and the tooltip never
|
|
86
|
+
* disagree (`docs/CHARTS-SPEC.md` §5). No crosshair — meaningless for a radial layout. Hover stays local to the pie (dimming siblings on hover) rather
|
|
73
87
|
* than joining the shared cursor: a date-keyed cursor has no counterpart on a donut, and cross-kind
|
|
74
88
|
* category sync (donut ↔ bar, via a generalized key) is a distinct, deliberately deferred feature.
|
|
89
|
+
* Tap-to-pin, keyboard stepping and focus over the slice ring come from `useDiscreteCursor`
|
|
90
|
+
* (`docs/CHARTS-SPEC.md` §4, Tooltip), the discrete counterpart of `HoverOverlay`.
|
|
91
|
+
*
|
|
92
|
+
* Layout law (`docs/CHARTS-SPEC.md` §5, Legend): frame W/H > 1.5 puts the legend beside the ring
|
|
93
|
+
* with value and %, the ring capped at `min(h, 0.55w)`; `micro`/`compact` stack the full legend
|
|
94
|
+
* under the ring, never rolled up. 7 or more slices fold the smallest into a neutral "Other".
|
|
75
95
|
*/
|
|
76
96
|
function DonutInner<K extends string = SeriesKey>(props: DonutProps<K>) {
|
|
77
97
|
// F-ERR-1: name the component and the prop. Without this a missing accessor surfaces
|
|
@@ -82,7 +102,8 @@ function DonutInner<K extends string = SeriesKey>(props: DonutProps<K>) {
|
|
|
82
102
|
data,
|
|
83
103
|
height,
|
|
84
104
|
colorForKey,
|
|
85
|
-
|
|
105
|
+
formatValue,
|
|
106
|
+
seriesLabel = identityLabel,
|
|
86
107
|
ariaLabel,
|
|
87
108
|
isPending,
|
|
88
109
|
state,
|
|
@@ -90,44 +111,118 @@ function DonutInner<K extends string = SeriesKey>(props: DonutProps<K>) {
|
|
|
90
111
|
style,
|
|
91
112
|
} = props
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
// The legend side depends on the frame's own measured box, which only exists inside
|
|
115
|
+
// `ChartFrame` — so measure the wrapper (same width) one level up. A stated height is used as
|
|
116
|
+
// is; the measured one moves with the legend's placement, so an unstated height gets hysteresis.
|
|
117
|
+
const { ref, width: frameW, height: measuredH } = useChartSize()
|
|
118
|
+
const containerClass = resolveContainerClass(frameW)
|
|
119
|
+
const frameH = height === undefined ? measuredH : resolveFrameHeight(height, containerClass)
|
|
120
|
+
const side = useHysteresis(
|
|
121
|
+
(wasSide) =>
|
|
122
|
+
resolveDonutLayout({
|
|
123
|
+
frameW,
|
|
124
|
+
frameH,
|
|
125
|
+
containerClass,
|
|
126
|
+
wasSide: height === undefined && wasSide,
|
|
127
|
+
}).side,
|
|
128
|
+
false,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const slices = useMemo(() => {
|
|
132
|
+
const { kept, other } = foldOther(data)
|
|
133
|
+
const out: Slice[] = kept.map((d) => ({
|
|
134
|
+
key: d.key,
|
|
135
|
+
label: seriesLabel(d.key),
|
|
136
|
+
color: colorForKey(d.key),
|
|
137
|
+
value: d.value,
|
|
138
|
+
}))
|
|
139
|
+
if (other !== null)
|
|
140
|
+
out.push({
|
|
141
|
+
key: resolveOtherKey(kept.map((d) => d.key)),
|
|
142
|
+
label: 'Other',
|
|
143
|
+
color: VX.neutral,
|
|
144
|
+
value: other,
|
|
145
|
+
})
|
|
146
|
+
return out
|
|
147
|
+
}, [data, seriesLabel, colorForKey])
|
|
148
|
+
|
|
149
|
+
// One denominator for the side legend and the tooltip: every slice, hidden or not.
|
|
150
|
+
const total = useMemo(() => slices.reduce((sum, d) => sum + d.value, 0), [slices])
|
|
151
|
+
const series = useMemo<SeriesStyle[]>(
|
|
152
|
+
() =>
|
|
153
|
+
slices.map((d) => ({
|
|
154
|
+
key: d.key,
|
|
155
|
+
label: side
|
|
156
|
+
? `${d.label} · ${formatValue(d.value)} · ${sharePercent(d.value, total)}%`
|
|
157
|
+
: d.label,
|
|
158
|
+
color: d.color,
|
|
159
|
+
mark: 'bar',
|
|
160
|
+
})),
|
|
161
|
+
[slices, side, formatValue, total],
|
|
162
|
+
)
|
|
99
163
|
|
|
100
164
|
return (
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
{...(height !== undefined && { height })}
|
|
104
|
-
{...(ariaLabel !== undefined && { ariaLabel })}
|
|
105
|
-
{...(isPending !== undefined && { isPending })}
|
|
106
|
-
{...(state !== undefined && { state })}
|
|
165
|
+
<div
|
|
166
|
+
ref={ref}
|
|
107
167
|
{...(className !== undefined && { className })}
|
|
108
|
-
{
|
|
168
|
+
style={{ display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0, ...style }}
|
|
109
169
|
>
|
|
110
|
-
|
|
111
|
-
|
|
170
|
+
<ChartFrame
|
|
171
|
+
series={series}
|
|
172
|
+
// Never roll the legend up: the full list is the donut's only key. Side legends read as a
|
|
173
|
+
// column. `headerSlot: false` (P1-4) keeps it under/beside the ring even inside a
|
|
174
|
+
// `ChartCard` with a header slot free — a Donut's legend IS its key, so it must never
|
|
175
|
+
// shrink to unlabelled header dots.
|
|
176
|
+
legend={{ placement: side ? 'right' : 'bottom', maxRows: series.length, headerSlot: false }}
|
|
177
|
+
{...(height !== undefined && { height })}
|
|
178
|
+
{...(ariaLabel !== undefined && { ariaLabel })}
|
|
179
|
+
{...(isPending !== undefined && { isPending })}
|
|
180
|
+
{...(state !== undefined && { state })}
|
|
181
|
+
>
|
|
182
|
+
{(plot) => (
|
|
183
|
+
<DonutPlot
|
|
184
|
+
slices={slices}
|
|
185
|
+
total={total}
|
|
186
|
+
side={side}
|
|
187
|
+
frameW={frameW}
|
|
188
|
+
plot={plot}
|
|
189
|
+
formatValue={formatValue}
|
|
190
|
+
{...(props.centerLabel !== undefined && { centerLabel: props.centerLabel })}
|
|
191
|
+
{...(props.centerSubLabel !== undefined && { centerSubLabel: props.centerSubLabel })}
|
|
192
|
+
{...(props.centerContent !== undefined && { centerContent: props.centerContent })}
|
|
193
|
+
{...(props.innerRatio !== undefined && { innerRatio: props.innerRatio })}
|
|
194
|
+
{...(props.padAngle !== undefined && { padAngle: props.padAngle })}
|
|
195
|
+
/>
|
|
196
|
+
)}
|
|
197
|
+
</ChartFrame>
|
|
198
|
+
</div>
|
|
112
199
|
)
|
|
113
200
|
}
|
|
114
201
|
|
|
115
|
-
type
|
|
202
|
+
type Slice = { key: string; label: string; color: string; value: number }
|
|
203
|
+
|
|
204
|
+
type DonutPlotProps = Pick<
|
|
205
|
+
DonutProps,
|
|
206
|
+
'formatValue' | 'centerLabel' | 'centerSubLabel' | 'centerContent' | 'innerRatio' | 'padAngle'
|
|
207
|
+
> & {
|
|
116
208
|
plot: { width: number; height: number; hidden: ReadonlySet<string> }
|
|
209
|
+
slices: Slice[]
|
|
210
|
+
/** Sum of ALL slices (hidden included) — the one denominator for every share. */
|
|
211
|
+
total: number
|
|
212
|
+
side: boolean
|
|
213
|
+
frameW: number
|
|
117
214
|
}
|
|
118
215
|
|
|
119
|
-
/** A hovered slice plus the viewport anchor `ChartTooltipFloat` positions against. */
|
|
120
|
-
type DonutTip<K extends string> = { key: K; value: number; anchor: { x: number; y: number } }
|
|
121
|
-
|
|
122
216
|
/** The measured plot — split from {@link DonutInner} so it only draws once `ChartFrame` has
|
|
123
217
|
* resolved a non-empty plot rect (radius/center depend on the measured size). */
|
|
124
|
-
function DonutPlot
|
|
218
|
+
function DonutPlot(props: DonutPlotProps) {
|
|
125
219
|
const {
|
|
126
|
-
|
|
220
|
+
slices,
|
|
221
|
+
total,
|
|
127
222
|
plot,
|
|
128
|
-
|
|
223
|
+
side,
|
|
224
|
+
frameW,
|
|
129
225
|
formatValue,
|
|
130
|
-
seriesLabel = (k) => k,
|
|
131
226
|
centerLabel,
|
|
132
227
|
centerSubLabel,
|
|
133
228
|
centerContent,
|
|
@@ -136,28 +231,28 @@ function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
|
|
|
136
231
|
} = props
|
|
137
232
|
const { width, height, hidden } = plot
|
|
138
233
|
|
|
139
|
-
const
|
|
140
|
-
const hoveredKey = tip?.key ?? null
|
|
234
|
+
const visibleData = useMemo(() => slices.filter((d) => !hidden.has(d.key)), [slices, hidden])
|
|
141
235
|
|
|
142
|
-
|
|
236
|
+
// One ring, one shared keyboard host: `useDiscreteCursor` gives arrow-key stepping through the
|
|
237
|
+
// VISIBLE slices (a hidden legend entry is not a reachable target either), plus tap-to-pin on
|
|
238
|
+
// touch. Left/Right and Up/Down are equivalent (no `columns` — a ring has no 2D shape).
|
|
239
|
+
const cursor = useDiscreteCursor<Slice>({
|
|
240
|
+
targets: visibleData,
|
|
241
|
+
getKey: (d) => d.key,
|
|
242
|
+
ariaLabel: 'Donut slices — use arrow keys to select, Escape to dismiss',
|
|
243
|
+
})
|
|
244
|
+
const activeKey = cursor.tip?.target.key ?? null
|
|
143
245
|
|
|
144
|
-
const radius =
|
|
246
|
+
const radius = resolveDonutRadius({ plotW: width, plotH: height, frameW, side })
|
|
145
247
|
const innerRadius = radius * innerRatio
|
|
146
248
|
const centerX = width / 2
|
|
147
249
|
const centerY = height / 2
|
|
148
250
|
|
|
149
|
-
const total = useMemo(() => visibleData.reduce((sum, d) => sum + d.value, 0), [visibleData])
|
|
150
|
-
|
|
151
|
-
const show = (d: DonutDatum<K>, event: PointerEvent<SVGGElement>) => {
|
|
152
|
-
setTip({ key: d.key, value: d.value, anchor: { x: event.clientX, y: event.clientY } })
|
|
153
|
-
}
|
|
154
|
-
const hide = () => setTip(null)
|
|
155
|
-
|
|
156
251
|
return (
|
|
157
252
|
<div style={{ position: 'relative' }}>
|
|
158
|
-
<svg width={width} height={height}>
|
|
253
|
+
<svg width={width} height={height} {...cursor.hostProps}>
|
|
159
254
|
<Group left={centerX} top={centerY}>
|
|
160
|
-
<Pie<
|
|
255
|
+
<Pie<Slice>
|
|
161
256
|
data={visibleData}
|
|
162
257
|
pieValue={(d) => d.value}
|
|
163
258
|
pieSortValues={() => 0}
|
|
@@ -172,26 +267,22 @@ function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
|
|
|
172
267
|
return (
|
|
173
268
|
<g
|
|
174
269
|
key={key}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}}
|
|
184
|
-
onPointerCancel={() => {
|
|
185
|
-
hide()
|
|
186
|
-
}}
|
|
270
|
+
id={cursor.optionId(arc.data)}
|
|
271
|
+
// An SVG `<g>` has no `<option>` counterpart — `useDiscreteCursor`'s
|
|
272
|
+
// listbox/option pair (see its JSDoc) is a role over an arbitrary shape.
|
|
273
|
+
// oxlint-disable-next-line jsx-a11y/prefer-tag-over-role
|
|
274
|
+
role="option"
|
|
275
|
+
aria-selected={activeKey === key}
|
|
276
|
+
aria-label={`${arc.data.label}: ${formatValue(arc.data.value)}, ${sharePercent(arc.data.value, total)}%`}
|
|
277
|
+
{...cursor.pointerProps(arc.data)}
|
|
187
278
|
style={{ cursor: 'pointer' }}
|
|
188
279
|
>
|
|
189
280
|
<path
|
|
190
281
|
d={pie.path(arc) || ''}
|
|
191
|
-
fill={
|
|
282
|
+
fill={arc.data.color}
|
|
192
283
|
stroke={VX.surface.panel}
|
|
193
284
|
strokeWidth={1.5}
|
|
194
|
-
opacity={
|
|
285
|
+
opacity={activeKey === null || activeKey === key ? 1 : 0.4}
|
|
195
286
|
/>
|
|
196
287
|
</g>
|
|
197
288
|
)
|
|
@@ -244,19 +335,19 @@ function DonutPlot<K extends string = SeriesKey>(props: DonutPlotProps<K>) {
|
|
|
244
335
|
</div>
|
|
245
336
|
)}
|
|
246
337
|
|
|
247
|
-
{tip !== null && (
|
|
248
|
-
<ChartTooltipFloat anchor={tip.anchor}>
|
|
338
|
+
{cursor.tip !== null && (
|
|
339
|
+
<ChartTooltipFloat anchor={cursor.tip.anchor}>
|
|
249
340
|
<TooltipBody>
|
|
250
341
|
<TooltipRow
|
|
251
|
-
color={
|
|
252
|
-
label={
|
|
253
|
-
value={formatValue(tip.value)}
|
|
342
|
+
color={cursor.tip.target.color}
|
|
343
|
+
label={cursor.tip.target.label}
|
|
344
|
+
value={formatValue(cursor.tip.target.value)}
|
|
254
345
|
shape="bar"
|
|
255
346
|
/>
|
|
256
347
|
<TooltipRow
|
|
257
348
|
color={VX.grid}
|
|
258
349
|
label="Share"
|
|
259
|
-
value={`${
|
|
350
|
+
value={`${sharePercent(cursor.tip.target.value, total)}%`}
|
|
260
351
|
shape="bar"
|
|
261
352
|
/>
|
|
262
353
|
</TooltipBody>
|
|
@@ -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'
|
|
@@ -336,12 +337,16 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
336
337
|
[bottomDomain, bottomH, useNiceBottom],
|
|
337
338
|
)
|
|
338
339
|
|
|
340
|
+
// ONE `<svg>` wraps both panes below, so it doubles as the tap-outside boundary
|
|
341
|
+
// (`docs/CHARTS-SPEC.md` §4, Tooltip) for the single cursor they share.
|
|
342
|
+
const svgRef = useRef<SVGSVGElement>(null)
|
|
339
343
|
const cursor = useChartCursor<T>({
|
|
340
344
|
data,
|
|
341
345
|
chartId,
|
|
342
346
|
getKey: getX,
|
|
343
347
|
xScale,
|
|
344
348
|
marginLeft: margin.left,
|
|
349
|
+
boundaryRef: svgRef,
|
|
345
350
|
...(cursorResolution !== undefined && { resolution: cursorResolution }),
|
|
346
351
|
})
|
|
347
352
|
|
|
@@ -477,7 +482,6 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
477
482
|
[visibleSeries, lineValid, xScale, topYScale, getX],
|
|
478
483
|
)
|
|
479
484
|
|
|
480
|
-
const svgRef = useRef<SVGSVGElement>(null)
|
|
481
485
|
const point = cursor.point
|
|
482
486
|
const sx = point !== null ? (xScale(getX(point)) ?? 0) : 0
|
|
483
487
|
const syncedBar = point !== null ? getBar(point) : null
|
|
@@ -485,10 +489,12 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
485
489
|
// Same seam as `CartesianChart`: this chart renders its tooltip as the cursor SOURCE (always) or,
|
|
486
490
|
// opted in via `onFollow`, as a FOLLOWER. A follower has no pointer under it, so it is always
|
|
487
491
|
// positioned via the anchored (crosshair) path — `cursor.anchor` stays null on a chart nobody is
|
|
488
|
-
// hovering, so following it here would render nothing.
|
|
492
|
+
// hovering, so following it here would render nothing. `DualPanel` has no `follow` prop to
|
|
493
|
+
// override the default, so touch (spec §5) anchors unconditionally, same as an unset `follow`
|
|
494
|
+
// elsewhere.
|
|
489
495
|
const isFollowerRender = !cursor.isSource && onFollow === true
|
|
490
496
|
const showTooltip = point !== null && (cursor.isSource || isFollowerRender)
|
|
491
|
-
const anchorX = isFollowerRender && point !== null ? sx : null
|
|
497
|
+
const anchorX = (isFollowerRender || cursor.isTouch) && point !== null ? sx : null
|
|
492
498
|
const svgRect = anchorX === null ? undefined : svgRef.current?.getBoundingClientRect()
|
|
493
499
|
const tooltipAnchor =
|
|
494
500
|
svgRect === undefined || anchorX === null
|
|
@@ -497,7 +503,13 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
497
503
|
|
|
498
504
|
return (
|
|
499
505
|
<>
|
|
500
|
-
<svg
|
|
506
|
+
<svg
|
|
507
|
+
ref={svgRef}
|
|
508
|
+
width={plot.width}
|
|
509
|
+
height={plot.height}
|
|
510
|
+
// `touch-action` on the plot `<svg>`: Chrome ignores it on the overlay `<rect>` (`R2C-2`).
|
|
511
|
+
style={{ touchAction: 'pan-y' }}
|
|
512
|
+
>
|
|
501
513
|
{/* Top pane: line series + fill-between + zones + ref lines. */}
|
|
502
514
|
<Group left={margin.left} top={margin.top}>
|
|
503
515
|
<GridRows scale={topYScale} width={xMax} stroke={VX.grid} numTicks={4} />
|
|
@@ -584,16 +596,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
584
596
|
<HoverOverlay
|
|
585
597
|
width={xMax}
|
|
586
598
|
height={topH + PANE_GAP}
|
|
587
|
-
|
|
588
|
-
onLeave={cursor.onPointerLeave}
|
|
589
|
-
onKeyDown={cursor.onKeyDown}
|
|
590
|
-
onBlur={cursor.onBlur}
|
|
591
|
-
valueMax={Math.max(data.length - 1, 0)}
|
|
592
|
-
{...(point !== null && {
|
|
593
|
-
valueNow: data.indexOf(point),
|
|
594
|
-
valueText: formatX(getX(point)),
|
|
595
|
-
})}
|
|
596
|
-
{...(ariaLabel !== undefined && { ariaLabel })}
|
|
599
|
+
{...cursorSliderProps({ cursor, data, getX, formatX, ariaLabel })}
|
|
597
600
|
/>
|
|
598
601
|
</Group>
|
|
599
602
|
|
|
@@ -648,6 +651,8 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
648
651
|
width={xMax}
|
|
649
652
|
height={bottomH + margin.bottom}
|
|
650
653
|
onMove={cursor.onPointerMove}
|
|
654
|
+
onDown={cursor.onPointerDown}
|
|
655
|
+
onUp={cursor.onPointerUp}
|
|
651
656
|
onLeave={cursor.onPointerLeave}
|
|
652
657
|
/>
|
|
653
658
|
</Group>
|
|
@@ -673,17 +678,7 @@ function DualPanelPlot<T>(props: DualPanelPlotProps<T>) {
|
|
|
673
678
|
})()}
|
|
674
679
|
/>
|
|
675
680
|
<TooltipBody>
|
|
676
|
-
{deriveTooltipRows(visibleSeries, point, formatTop)
|
|
677
|
-
<TooltipRow
|
|
678
|
-
key={row.key}
|
|
679
|
-
color={row.color}
|
|
680
|
-
label={row.label}
|
|
681
|
-
value={row.value}
|
|
682
|
-
shape={row.shape}
|
|
683
|
-
dashed={row.dashed}
|
|
684
|
-
{...(row.strokeWidth !== undefined && { strokeWidth: row.strokeWidth })}
|
|
685
|
-
/>
|
|
686
|
-
))}
|
|
681
|
+
<TooltipRows rows={deriveTooltipRows(visibleSeries, point, formatTop)} />
|
|
687
682
|
{barVisible &&
|
|
688
683
|
(() => {
|
|
689
684
|
const v = getBar(point)
|