basalt-ui 1.14.0 → 1.15.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 +8 -0
- package/README.md +38 -2
- package/agent/rules/basalt-charts.md +260 -108
- package/agent/rules/basalt-mantine.md +4 -2
- package/agent/skills/basalt-charts/SKILL.md +114 -64
- package/agent/skills/basalt-design/SKILL.md +4 -3
- package/configs/oxlint-plugin.js +152 -0
- package/configs/oxlint-plugin.test.ts +107 -0
- package/configs/oxlint.json +2 -0
- package/dist/charts/cursor/resolve.d.ts +33 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -0
- package/dist/charts/cursor/resolve.js +61 -0
- package/dist/charts/cursor/resolve.test.d.ts +2 -0
- package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
- package/dist/charts/cursor/scope.d.ts +17 -0
- package/dist/charts/cursor/scope.d.ts.map +1 -0
- package/dist/charts/cursor/scope.js +20 -0
- package/dist/charts/cursor/store.d.ts +31 -0
- package/dist/charts/cursor/store.d.ts.map +1 -0
- package/dist/charts/cursor/store.js +25 -0
- package/dist/charts/cursor/store.test.d.ts +2 -0
- package/dist/charts/cursor/store.test.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.d.ts +39 -0
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.js +104 -0
- package/dist/charts/hooks/useChartSize.d.ts +8 -4
- package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
- package/dist/charts/index.d.ts +16 -13
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +38 -19
- package/dist/charts/kinds/Bars.d.ts +25 -47
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +230 -390
- package/dist/charts/kinds/Donut.d.ts +5 -4
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +17 -14
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +136 -90
- package/dist/charts/kinds/Heatmap.d.ts +15 -11
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +38 -22
- package/dist/charts/kinds/MultiLine.d.ts +27 -35
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +114 -237
- package/dist/charts/kinds/StackedArea.d.ts +21 -10
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +74 -132
- package/dist/charts/kinds/ZonedLine.d.ts +24 -40
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +103 -184
- package/dist/charts/layout/auto-margin.d.ts +60 -0
- package/dist/charts/layout/auto-margin.d.ts.map +1 -0
- package/dist/charts/layout/auto-margin.js +37 -0
- package/dist/charts/layout/auto-margin.test.d.ts +2 -0
- package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.d.ts +143 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.js +349 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.d.ts +16 -5
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +17 -2
- package/dist/charts/primitives/ChartLegend.d.ts +12 -1
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +21 -3
- package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +45 -19
- package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +22 -1
- package/dist/charts/primitives/XZoneRects.d.ts +40 -0
- package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
- package/dist/charts/primitives/XZoneRects.js +28 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
- package/dist/charts/series.d.ts +16 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +3 -1
- package/dist/charts/series.test.d.ts +2 -0
- package/dist/charts/series.test.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.d.ts +25 -0
- package/dist/charts/utils/measure-text.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.js +64 -0
- package/dist/charts/utils/measure-text.test.d.ts +2 -0
- package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/chart-margin.test.d.ts +2 -0
- package/dist/tokens/chart-margin.test.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +29 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/palette.d.ts +4 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +3 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/cursor/resolve.test.ts +61 -0
- package/src/charts/cursor/resolve.ts +102 -0
- package/src/charts/cursor/scope.tsx +32 -0
- package/src/charts/cursor/store.test.ts +46 -0
- package/src/charts/cursor/store.ts +53 -0
- package/src/charts/hooks/useChartCursor.ts +163 -0
- package/src/charts/hooks/useChartSize.ts +8 -4
- package/src/charts/index.ts +34 -14
- package/src/charts/kinds/Bars.tsx +327 -460
- package/src/charts/kinds/Donut.tsx +31 -23
- package/src/charts/kinds/DualPanel.tsx +228 -134
- package/src/charts/kinds/Heatmap.tsx +72 -47
- package/src/charts/kinds/MultiLine.tsx +158 -294
- package/src/charts/kinds/StackedArea.tsx +116 -175
- package/src/charts/kinds/ZonedLine.tsx +148 -255
- package/src/charts/layout/auto-margin.test.ts +42 -0
- package/src/charts/layout/auto-margin.ts +110 -0
- package/src/charts/primitives/CartesianChart.test.ts +99 -0
- package/src/charts/primitives/CartesianChart.tsx +566 -0
- package/src/charts/primitives/ChartFrame.test.tsx +33 -0
- package/src/charts/primitives/ChartFrame.tsx +40 -4
- package/src/charts/primitives/ChartLegend.test.tsx +48 -0
- package/src/charts/primitives/ChartLegend.tsx +45 -2
- package/src/charts/primitives/ChartTooltip.tsx +67 -37
- package/src/charts/primitives/HoverOverlay.tsx +39 -3
- package/src/charts/primitives/XZoneRects.test.tsx +63 -0
- package/src/charts/primitives/XZoneRects.tsx +67 -0
- package/src/charts/series.test.ts +61 -0
- package/src/charts/series.ts +18 -1
- package/src/charts/utils/measure-text.test.ts +36 -0
- package/src/charts/utils/measure-text.ts +109 -0
- package/src/content/fence-block.tsx +2 -3
- package/src/surfaces.ts +2 -1
- package/src/theme/spacing.test.ts +2 -0
- package/src/tokens/chart-margin.test.ts +39 -0
- package/src/tokens/density.test.ts +1 -0
- package/src/tokens/index.ts +36 -0
- package/src/tokens/palette.ts +3 -0
- package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
- package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
- package/dist/charts/hooks/useChartTooltip.js +0 -26
- package/dist/charts/hooks/useHoverSync.d.ts +0 -46
- package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
- package/dist/charts/hooks/useHoverSync.js +0 -75
- package/dist/charts/hover-context.d.ts +0 -21
- package/dist/charts/hover-context.d.ts.map +0 -1
- package/dist/charts/hover-context.js +0 -12
- package/dist/charts/hover-sync.d.ts +0 -14
- package/dist/charts/hover-sync.d.ts.map +0 -1
- package/dist/charts/hover-sync.js +0 -17
- package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
- package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
- package/dist/charts/primitives/ResponsiveChart.js +0 -24
- package/src/charts/hooks/useChartTooltip.ts +0 -31
- package/src/charts/hooks/useHoverSync.ts +0 -152
- package/src/charts/hover-context.ts +0 -28
- package/src/charts/hover-sync.tsx +0 -37
- package/src/charts/primitives/ResponsiveChart.tsx +0 -64
package/src/charts/series.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type ChartLegendConfig = {
|
|
|
43
43
|
groups?: boolean
|
|
44
44
|
/** Wrap cap → "+N more" rollup at high cardinality. */
|
|
45
45
|
maxRows?: number
|
|
46
|
+
/** Clicking an entry hides that series. Default: on whenever there is more than one entry. */
|
|
47
|
+
toggle?: boolean
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/** Visual identity of a series — everything the legend + tooltip swatch need. No accessors, no `T`. */
|
|
@@ -58,12 +60,24 @@ export type SeriesStyle = {
|
|
|
58
60
|
strokeWidth?: number
|
|
59
61
|
/** bar/area: the swatch honors this so it cannot lie. */
|
|
60
62
|
fillOpacity?: number
|
|
63
|
+
/** Which y-axis this series is measured against. Default 'left'. `'right'` is what makes a
|
|
64
|
+
* chart dual-axis — `CartesianChart` reads it for the crosshair dots and the tooltip. */
|
|
65
|
+
axis?: 'left' | 'right'
|
|
61
66
|
/** Default 'series'. */
|
|
62
67
|
role?: SeriesRole
|
|
63
68
|
/** Default true; false = companion folded under `parent`. */
|
|
64
69
|
legend?: boolean
|
|
70
|
+
/**
|
|
71
|
+
* Default true; false = drawn and legended, but NEVER listed as a tooltip row. The replacement
|
|
72
|
+
* for the per-kind `hideBarTooltipRows` escape the old layer had: it belongs to the series, not to the kind, so
|
|
73
|
+
* one series can opt out without the kind growing a prop for it.
|
|
74
|
+
*/
|
|
75
|
+
tooltip?: boolean
|
|
65
76
|
/** e.g. an MA line names its parent so hover-dimming keeps the pair. */
|
|
66
77
|
parent?: string
|
|
78
|
+
/** Short qualifier rendered after the label in muted text — e.g. a flat-at-zero series that is
|
|
79
|
+
* invisible in the plot. */
|
|
80
|
+
note?: string
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
/** Full descriptor = visual identity + data accessors. Generic over the point type. */
|
|
@@ -99,6 +113,7 @@ function legendEntryFor(s: SeriesStyle): LegendEntry {
|
|
|
99
113
|
...(s.strokeWidth !== undefined && { strokeWidth: s.strokeWidth }),
|
|
100
114
|
...(s.fillOpacity !== undefined && { fillOpacity: s.fillOpacity }),
|
|
101
115
|
...(s.role !== undefined && { role: s.role }),
|
|
116
|
+
...(s.note !== undefined && { note: s.note }),
|
|
102
117
|
}
|
|
103
118
|
}
|
|
104
119
|
|
|
@@ -136,7 +151,8 @@ export function deriveLegend(series: readonly SeriesStyle[]): LegendEntry[] {
|
|
|
136
151
|
|
|
137
152
|
/**
|
|
138
153
|
* Derive tooltip rows from a series descriptor array + the hovered datum. Skips rows where
|
|
139
|
-
* `getValue(datum) === null`; honors per-series `formatValue` over the
|
|
154
|
+
* `getValue(datum) === null` or `tooltip === false`; honors per-series `formatValue` over the
|
|
155
|
+
* shared `fallbackFormat`.
|
|
140
156
|
* Kinds pass `series.toReversed()` for stacked charts so the tooltip stack matches the visual stack.
|
|
141
157
|
*/
|
|
142
158
|
export function deriveTooltipRows<T>(
|
|
@@ -146,6 +162,7 @@ export function deriveTooltipRows<T>(
|
|
|
146
162
|
): TooltipRowData[] {
|
|
147
163
|
const rows: TooltipRowData[] = []
|
|
148
164
|
for (const s of series) {
|
|
165
|
+
if (s.tooltip === false) continue
|
|
149
166
|
const value = s.getValue(datum)
|
|
150
167
|
if (value === null) continue
|
|
151
168
|
const format = s.formatValue ?? fallbackFormat
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { maxTextWidth, measureText, resetTextMetrics } from './measure-text'
|
|
3
|
+
|
|
4
|
+
describe('measureText', () => {
|
|
5
|
+
test('width grows with the string and with the font size', () => {
|
|
6
|
+
resetTextMetrics()
|
|
7
|
+
expect(measureText('1,250,000', 11)).toBeGreaterThan(measureText('0', 11))
|
|
8
|
+
expect(measureText('0', 22)).toBeGreaterThan(measureText('0', 11))
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('an empty string measures zero', () => {
|
|
12
|
+
expect(measureText('', 11)).toBe(0)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('repeated measurement is stable — the memo cannot return a different width', () => {
|
|
16
|
+
resetTextMetrics()
|
|
17
|
+
const first = measureText('2026-08-18', 11)
|
|
18
|
+
expect(measureText('2026-08-18', 11)).toBe(first)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('the same text at a different size is NOT a cache hit', () => {
|
|
22
|
+
resetTextMetrics()
|
|
23
|
+
expect(measureText('88', 11)).not.toBe(measureText('88', 30))
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('maxTextWidth', () => {
|
|
28
|
+
test('returns 0 for no labels — an axis with no ticks reserves nothing extra', () => {
|
|
29
|
+
expect(maxTextWidth([], 11)).toBe(0)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('returns the widest label, not the last', () => {
|
|
33
|
+
const labels = ['1', '1,250,000', '25']
|
|
34
|
+
expect(maxTextWidth(labels, 11)).toBe(measureText('1,250,000', 11))
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text measurement for chart layout — the input to {@link autoMargin}.
|
|
3
|
+
*
|
|
4
|
+
* A static margin token cannot know how wide a tick label is, which is why charts used to have to be
|
|
5
|
+
* hand-nudged whenever a label grew (`docs/CHARTS-SPEC.md` §1). This measures the strings that
|
|
6
|
+
* will actually be painted, via an OFFSCREEN canvas 2D context: no element is attached, so there
|
|
7
|
+
* is no layout thrash and no reflow — unlike an SVG `getBBox()` probe.
|
|
8
|
+
*
|
|
9
|
+
* Mantine-free and dependency-free by construction (`charts/**` boundary).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Fallback advance width per character, as a fraction of the font size, when no DOM is
|
|
13
|
+
* available (SSR, `renderToStaticMarkup`). Mono faces sit near 0.6em; being deterministic matters
|
|
14
|
+
* more than being exact, since the measured value is only ever a floor-raiser. */
|
|
15
|
+
const FALLBACK_CHAR_RATIO = 0.6
|
|
16
|
+
|
|
17
|
+
/** Last-resort family when the `--basalt-font-mono` custom property cannot be read. */
|
|
18
|
+
const FALLBACK_MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cap on the memo table. Tick labels are low-cardinality per chart, but a long-lived SPA with a
|
|
22
|
+
* live counter or currency axis mints a new string every update, and an unbounded module-level Map
|
|
23
|
+
* would then grow for the life of the tab. On overflow the whole table is dropped rather than
|
|
24
|
+
* evicted one by one: the working set is tiny and re-measuring it costs microseconds, so an LRU's
|
|
25
|
+
* bookkeeping would cost more than the misses it avoids.
|
|
26
|
+
*/
|
|
27
|
+
const CACHE_LIMIT = 2000
|
|
28
|
+
|
|
29
|
+
const cache = new Map<string, number>()
|
|
30
|
+
|
|
31
|
+
let ctx: CanvasRenderingContext2D | null | undefined
|
|
32
|
+
let monoFamily: string | undefined
|
|
33
|
+
|
|
34
|
+
function canvasContext(): CanvasRenderingContext2D | null {
|
|
35
|
+
if (ctx !== undefined) return ctx
|
|
36
|
+
if (typeof document === 'undefined') {
|
|
37
|
+
ctx = null
|
|
38
|
+
return ctx
|
|
39
|
+
}
|
|
40
|
+
ctx = document.createElement('canvas').getContext('2d')
|
|
41
|
+
// Webfonts swap in after first paint; measurements taken against the fallback face would then
|
|
42
|
+
// be stale (usually too narrow). Drop the cache once, when the real faces are ready.
|
|
43
|
+
// `?.` on every hop: a non-browser DOM harness can expose `document` with no `fonts`, or a
|
|
44
|
+
// `fonts` with no `ready` — neither is a reason to fail a measurement.
|
|
45
|
+
document.fonts?.ready?.then?.(() => cache.clear()).catch(() => {})
|
|
46
|
+
return ctx
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The resolved value of `--basalt-font-mono` — canvas needs a concrete family list, it cannot
|
|
51
|
+
* resolve a CSS custom property. Read once from the document element.
|
|
52
|
+
*/
|
|
53
|
+
export function monoFontFamily(): string {
|
|
54
|
+
if (monoFamily !== undefined) return monoFamily
|
|
55
|
+
if (typeof document === 'undefined' || typeof getComputedStyle !== 'function') {
|
|
56
|
+
monoFamily = FALLBACK_MONO
|
|
57
|
+
return monoFamily
|
|
58
|
+
}
|
|
59
|
+
const value = getComputedStyle(document.documentElement)
|
|
60
|
+
.getPropertyValue('--basalt-font-mono')
|
|
61
|
+
.trim()
|
|
62
|
+
monoFamily = value.length > 0 ? value : FALLBACK_MONO
|
|
63
|
+
return monoFamily
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Width in px of `text` at `fontPx` in the chart tick font. Memoized per `font|text` — a chart
|
|
68
|
+
* re-measures the same ~10 tick labels on every resize, and the cache turns that into a Map hit.
|
|
69
|
+
*/
|
|
70
|
+
export function measureText(text: string, fontPx: number, fontFamily?: string): number {
|
|
71
|
+
const family = fontFamily ?? monoFontFamily()
|
|
72
|
+
const font = `${fontPx}px ${family}`
|
|
73
|
+
const key = `${font}|${text}`
|
|
74
|
+
const hit = cache.get(key)
|
|
75
|
+
if (hit !== undefined) return hit
|
|
76
|
+
|
|
77
|
+
const c = canvasContext()
|
|
78
|
+
let width: number
|
|
79
|
+
if (c === null) {
|
|
80
|
+
width = text.length * fontPx * FALLBACK_CHAR_RATIO
|
|
81
|
+
} else {
|
|
82
|
+
c.font = font
|
|
83
|
+
width = c.measureText(text).width
|
|
84
|
+
}
|
|
85
|
+
if (cache.size >= CACHE_LIMIT) cache.clear()
|
|
86
|
+
cache.set(key, width)
|
|
87
|
+
return width
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Widest of `texts` at `fontPx`. Returns 0 for an empty list. */
|
|
91
|
+
export function maxTextWidth(
|
|
92
|
+
texts: readonly string[],
|
|
93
|
+
fontPx: number,
|
|
94
|
+
fontFamily?: string,
|
|
95
|
+
): number {
|
|
96
|
+
let max = 0
|
|
97
|
+
for (const text of texts) {
|
|
98
|
+
const width = measureText(text, fontPx, fontFamily)
|
|
99
|
+
if (width > max) max = width
|
|
100
|
+
}
|
|
101
|
+
return max
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Test seam — drops the memo table (and the resolved family) so a test can change fonts. */
|
|
105
|
+
export function resetTextMetrics(): void {
|
|
106
|
+
cache.clear()
|
|
107
|
+
ctx = undefined
|
|
108
|
+
monoFamily = undefined
|
|
109
|
+
}
|
|
@@ -139,9 +139,8 @@ function lookupFenceRenderer(
|
|
|
139
139
|
|
|
140
140
|
// ── Throw containment ────────────────────────────────────────────────────────────────────────────
|
|
141
141
|
|
|
142
|
-
/** A `useRef(false)` handle, mutated in place —
|
|
143
|
-
*
|
|
144
|
-
* singleton: a singleton would make the FIRST fence anywhere in the process to throw silence the
|
|
142
|
+
/** A `useRef(false)` handle, mutated in place — a per-instance "warn once" gate rather than a
|
|
143
|
+
* module-level singleton: a singleton would make the FIRST fence anywhere in the process to throw silence the
|
|
145
144
|
* warning for every OTHER, unrelated broken fence for the rest of the run (and, worse, for every
|
|
146
145
|
* later test in this suite — this file has module-singleton order-dependence history). Scoping the
|
|
147
146
|
* gate to one `FenceBlock`'s `useRef` means each rendered fence position gets its own warning, reset
|
package/src/surfaces.ts
CHANGED
|
@@ -211,7 +211,8 @@ export const SURFACES = {
|
|
|
211
211
|
rule: 'charts',
|
|
212
212
|
skill: ['basalt-charts'],
|
|
213
213
|
guardKinds: ['raw-hex', 'raw-color-fn', 'raw-visx-axis', 'unframed-chart'],
|
|
214
|
-
description:
|
|
214
|
+
description:
|
|
215
|
+
'CartesianChart + visx chart primitives, kinds, sparklines, hooks, and token re-exports (Mantine-free)',
|
|
215
216
|
globs: {
|
|
216
217
|
shipped: ['**/charts/**'],
|
|
217
218
|
repo: ['packages/basalt-ui/src/charts/**'],
|
|
@@ -369,6 +369,7 @@ const SPACE_STEP_SWEEP: ReadonlyArray<
|
|
|
369
369
|
['chartLegendGap', 22, null],
|
|
370
370
|
['chartMarginTop', 12, null],
|
|
371
371
|
['chartMarginRight', 16, null],
|
|
372
|
+
['chartMarginRightAxis', 40, null],
|
|
372
373
|
['chartMarginBottom', 30, null],
|
|
373
374
|
['chartMarginLeft', 44, null],
|
|
374
375
|
['chartDotR', 5, null],
|
|
@@ -406,6 +407,7 @@ describe('SPACE_STEP CSS-module spacing-sweep one-offs match the shipped identit
|
|
|
406
407
|
'chartLegendGap',
|
|
407
408
|
'chartMarginTop',
|
|
408
409
|
'chartMarginRight',
|
|
410
|
+
'chartMarginRightAxis',
|
|
409
411
|
'chartMarginBottom',
|
|
410
412
|
'chartMarginLeft',
|
|
411
413
|
'chartDotR',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { chartMargin, VX } from './index'
|
|
3
|
+
|
|
4
|
+
describe('chartMargin', () => {
|
|
5
|
+
test('a bare call deep-equals VX.margin', () => {
|
|
6
|
+
expect(chartMargin()).toEqual(VX.margin)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('{ rightAxis: true } widens right to the right-axis minimum', () => {
|
|
10
|
+
expect(chartMargin({ rightAxis: true })).toEqual({
|
|
11
|
+
...VX.margin,
|
|
12
|
+
right: 40,
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('{ rightAxis: false } (or omitted) leaves right at VX.margin.right', () => {
|
|
17
|
+
expect(chartMargin({ rightAxis: false })).toEqual(VX.margin)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('an explicit right overrides the rightAxis-widened value', () => {
|
|
21
|
+
expect(chartMargin({ rightAxis: true, right: 12 })).toEqual({
|
|
22
|
+
...VX.margin,
|
|
23
|
+
right: 12,
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('explicit sides override their VX.margin defaults independently', () => {
|
|
28
|
+
expect(chartMargin({ top: 1, left: 2 })).toEqual({
|
|
29
|
+
top: 1,
|
|
30
|
+
right: VX.margin.right,
|
|
31
|
+
bottom: VX.margin.bottom,
|
|
32
|
+
left: 2,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('returns a new object each call', () => {
|
|
37
|
+
expect(chartMargin()).not.toBe(chartMargin())
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -242,6 +242,7 @@ describe('buildDensityCss', () => {
|
|
|
242
242
|
'space-chart-legend-gap', // VX.legendGap / VX.margin / VX.dotR — visx SVG props
|
|
243
243
|
'space-chart-margin-top',
|
|
244
244
|
'space-chart-margin-right',
|
|
245
|
+
'space-chart-margin-right-axis',
|
|
245
246
|
'space-chart-margin-bottom',
|
|
246
247
|
'space-chart-margin-left',
|
|
247
248
|
'space-chart-dot-r',
|
package/src/tokens/index.ts
CHANGED
|
@@ -241,6 +241,42 @@ export const VX = {
|
|
|
241
241
|
minPxPerTick: 55,
|
|
242
242
|
} as const
|
|
243
243
|
|
|
244
|
+
/** Resolved chart plot-area margins — the return shape of {@link chartMargin}. */
|
|
245
|
+
export type ChartMargin = { top: number; right: number; bottom: number; left: number }
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Chart plot-area margins — `VX.margin` widened for an `AxisRightNumeric`'s tick labels, and/or
|
|
249
|
+
* overridden per side. A token can't express "only when a right axis is rendered", so this is a
|
|
250
|
+
* function, not a second static token — also the seam that could one day track `density`
|
|
251
|
+
* (`VX.margin` itself is frozen at the level-0 snapshot; see `deriveSpacing`'s JSDoc).
|
|
252
|
+
*
|
|
253
|
+
* Law, applied in this order: start from `VX.margin`; when `rightAxis` is true, widen `right` to
|
|
254
|
+
* `Math.max(VX.margin.right, <right-axis minimum>)` (`SPACE_STEP.chartMarginRightAxis`); then any
|
|
255
|
+
* explicitly passed side wins last (including overriding `rightAxis`'s widened `right`).
|
|
256
|
+
* `chartMargin()` with no args deep-equals `VX.margin`.
|
|
257
|
+
*
|
|
258
|
+
* Returns a NEW object every call — memoize at the call site (`useMemo`), the same way `Bars`
|
|
259
|
+
* already memoizes its own margin.
|
|
260
|
+
*/
|
|
261
|
+
export function chartMargin(opts?: {
|
|
262
|
+
/** Reserve room for an `AxisRightNumeric`'s tick labels. */
|
|
263
|
+
rightAxis?: boolean
|
|
264
|
+
top?: number
|
|
265
|
+
right?: number
|
|
266
|
+
bottom?: number
|
|
267
|
+
left?: number
|
|
268
|
+
}): ChartMargin {
|
|
269
|
+
const widenedRight = opts?.rightAxis
|
|
270
|
+
? Math.max(VX.margin.right, SPACE_STEP.chartMarginRightAxis)
|
|
271
|
+
: VX.margin.right
|
|
272
|
+
return {
|
|
273
|
+
top: opts?.top ?? VX.margin.top,
|
|
274
|
+
right: opts?.right ?? widenedRight,
|
|
275
|
+
bottom: opts?.bottom ?? VX.margin.bottom,
|
|
276
|
+
left: opts?.left ?? VX.margin.left,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
244
280
|
type Side = 'light' | 'dark'
|
|
245
281
|
|
|
246
282
|
/** A CSS declaration line for a single `--vx-*` custom property. */
|
package/src/tokens/palette.ts
CHANGED
|
@@ -620,6 +620,9 @@ const SPACE_STEP_BASE = {
|
|
|
620
620
|
chartMarginTop: 12,
|
|
621
621
|
/** Chart plot-area margin, right (`VX.margin.right`). */
|
|
622
622
|
chartMarginRight: 16,
|
|
623
|
+
/** Minimum right margin when an `AxisRightNumeric` is rendered — labels like "100"/"60m" need
|
|
624
|
+
* more room than the no-right-axis default (`chartMargin({ rightAxis: true })`, `tokens/index.ts`). */
|
|
625
|
+
chartMarginRightAxis: 40,
|
|
623
626
|
/** Chart plot-area margin, bottom (`VX.margin.bottom`). */
|
|
624
627
|
chartMarginBottom: 30,
|
|
625
628
|
/** Chart plot-area margin, left (`VX.margin.left`). */
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export type TooltipState<T> = {
|
|
2
|
-
data: T;
|
|
3
|
-
x: number;
|
|
4
|
-
y: number;
|
|
5
|
-
} | null;
|
|
6
|
-
export declare function useChartTooltip<T>(): {
|
|
7
|
-
tip: TooltipState<T>;
|
|
8
|
-
show: (data: T, event: React.MouseEvent) => void;
|
|
9
|
-
hide: () => void;
|
|
10
|
-
tooltipRef: import("react").RefObject<HTMLDivElement | null>;
|
|
11
|
-
lastDateRef: import("react").RefObject<string | null>;
|
|
12
|
-
};
|
|
13
|
-
//# sourceMappingURL=useChartTooltip.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useChartTooltip.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAEtE,wBAAgB,eAAe,CAAC,CAAC;;iBAKC,CAAC,SAAS,KAAK,CAAC,UAAU;;;;EAqB3D"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from "react";
|
|
2
|
-
function useChartTooltip() {
|
|
3
|
-
const [tip, setTip] = useState(null);
|
|
4
|
-
const tooltipRef = useRef(null);
|
|
5
|
-
const lastDateRef = useRef(null);
|
|
6
|
-
const show = useCallback((data, event) => {
|
|
7
|
-
const el = tooltipRef.current;
|
|
8
|
-
const width = el?.offsetWidth ?? 0;
|
|
9
|
-
const height = el?.offsetHeight ?? 0;
|
|
10
|
-
const margin = 8;
|
|
11
|
-
const vw = window.innerWidth;
|
|
12
|
-
const vh = window.innerHeight;
|
|
13
|
-
const overflowsRight = event.clientX + 12 + width + margin > vw;
|
|
14
|
-
const x = overflowsRight ? Math.max(margin, event.clientX - 12 - width) : event.clientX + 12;
|
|
15
|
-
const y = Math.min(Math.max(margin, event.clientY - 12), vh - height - margin);
|
|
16
|
-
setTip({ data, x, y });
|
|
17
|
-
}, []);
|
|
18
|
-
const hide = useCallback(() => {
|
|
19
|
-
setTip(null);
|
|
20
|
-
lastDateRef.current = null;
|
|
21
|
-
}, []);
|
|
22
|
-
return { tip, show, hide, tooltipRef, lastDateRef };
|
|
23
|
-
}
|
|
24
|
-
export {
|
|
25
|
-
useChartTooltip
|
|
26
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
type XScale = (x: string) => number | undefined;
|
|
2
|
-
/**
|
|
3
|
-
* Wires a chart into the shared-cursor HoverContext plus the local tooltip state.
|
|
4
|
-
*
|
|
5
|
-
* Returns everything a chart needs to:
|
|
6
|
-
* - snap mouse position to the nearest data point (by x-category)
|
|
7
|
-
* - broadcast that hover to other charts via HoverContext
|
|
8
|
-
* - read back hover from other charts (for ghost crosshair + dot)
|
|
9
|
-
* - drive its local tooltip
|
|
10
|
-
*
|
|
11
|
-
* Every non-sparkline chart kind MUST use this hook. Do not reimplement the
|
|
12
|
-
* closest-point loop inline — drift across 15+ charts is the exact problem
|
|
13
|
-
* this hook prevents.
|
|
14
|
-
*/
|
|
15
|
-
export declare function useHoverSync<T>({ data, chartId, getKey, xScale, marginLeft, resolveKey, }: {
|
|
16
|
-
data: T[];
|
|
17
|
-
chartId: string;
|
|
18
|
-
getKey: (d: T) => string;
|
|
19
|
-
xScale: XScale;
|
|
20
|
-
marginLeft: number;
|
|
21
|
-
/**
|
|
22
|
-
* Overrides how a SIBLING's broadcast key resolves to one of THIS chart's own points. Only
|
|
23
|
-
* affects reading another chart's hover — this chart's own hover (via `getKey`/`xScale`) is
|
|
24
|
-
* untouched.
|
|
25
|
-
*
|
|
26
|
-
* Cross-chart sync is exact string match by default (`pointByKey.get(ctx.key)`), so two charts
|
|
27
|
-
* share a cursor only if they emit identical key strings. A chart that downsamples/folds its
|
|
28
|
-
* domain to fit a narrow viewport (e.g. ~288 buckets folded into ~97 columns at 390px) no longer
|
|
29
|
-
* owns most of the keys its unfolded siblings broadcast — the shared crosshair then appears on
|
|
30
|
-
* roughly one hover in three, with no rule a reader can infer, which is worse than no shared
|
|
31
|
-
* cursor at all. Pass `resolveKey` to map a foreign key onto whichever of this chart's own points
|
|
32
|
-
* swallowed it (e.g. the folded bucket the key falls into).
|
|
33
|
-
*
|
|
34
|
-
* Omit it and behaviour is byte-identical to today.
|
|
35
|
-
*/
|
|
36
|
-
resolveKey?: (key: string) => T | null;
|
|
37
|
-
}): {
|
|
38
|
-
tip: import("./useChartTooltip.js").TooltipState<T>;
|
|
39
|
-
tooltipRef: import("react").RefObject<HTMLDivElement | null>;
|
|
40
|
-
syncedPoint: T | null;
|
|
41
|
-
isDirectHover: boolean;
|
|
42
|
-
handleMouse: (event: React.MouseEvent<SVGRectElement>) => void;
|
|
43
|
-
handleLeave: () => void;
|
|
44
|
-
};
|
|
45
|
-
export {};
|
|
46
|
-
//# sourceMappingURL=useHoverSync.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useHoverSync.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useHoverSync.ts"],"names":[],"mappings":"AAKA,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAC9B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,EACV,UAAU,GACX,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,CAAA;CACvC;;;;;yBA6CW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC;;EAyD3C"}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { localPoint } from "@visx/event";
|
|
2
|
-
import { useCallback, useContext, useMemo, useRef } from "react";
|
|
3
|
-
import { DEFAULT_NO_OP_SET_HOVER, HoverContext } from "../hover-context.js";
|
|
4
|
-
import { useChartTooltip } from "./useChartTooltip.js";
|
|
5
|
-
function useHoverSync({
|
|
6
|
-
data,
|
|
7
|
-
chartId,
|
|
8
|
-
getKey,
|
|
9
|
-
xScale,
|
|
10
|
-
marginLeft,
|
|
11
|
-
resolveKey
|
|
12
|
-
}) {
|
|
13
|
-
const ctx = useContext(HoverContext);
|
|
14
|
-
const ctxRef = useRef(ctx);
|
|
15
|
-
ctxRef.current = ctx;
|
|
16
|
-
const warnedRef = useRef(false);
|
|
17
|
-
if (process.env["NODE_ENV"] !== "production" && ctx.setHover === DEFAULT_NO_OP_SET_HOVER && !warnedRef.current) {
|
|
18
|
-
warnedRef.current = true;
|
|
19
|
-
console.warn(
|
|
20
|
-
"[charts] useHoverSync used outside <HoverContext.Provider>. Cross-chart cursor sync will not work."
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
const { tip, show, hide, tooltipRef, lastDateRef } = useChartTooltip();
|
|
24
|
-
const getKeyRef = useRef(getKey);
|
|
25
|
-
getKeyRef.current = getKey;
|
|
26
|
-
const xScaleRef = useRef(xScale);
|
|
27
|
-
xScaleRef.current = xScale;
|
|
28
|
-
const pointByKey = useMemo(() => {
|
|
29
|
-
const m = /* @__PURE__ */ new Map();
|
|
30
|
-
for (const d of data) m.set(getKeyRef.current(d), d);
|
|
31
|
-
return m;
|
|
32
|
-
}, [data]);
|
|
33
|
-
const handleMouse = useCallback(
|
|
34
|
-
(event) => {
|
|
35
|
-
const point = localPoint(event);
|
|
36
|
-
if (!point || data.length === 0) return;
|
|
37
|
-
const px = point.x - marginLeft;
|
|
38
|
-
let closest = data[0];
|
|
39
|
-
let minDist = Infinity;
|
|
40
|
-
for (const d of data) {
|
|
41
|
-
const sx = xScaleRef.current(getKeyRef.current(d)) ?? 0;
|
|
42
|
-
const dist = Math.abs(sx - px);
|
|
43
|
-
if (dist < minDist) {
|
|
44
|
-
minDist = dist;
|
|
45
|
-
closest = d;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
show(closest, event);
|
|
49
|
-
const key = getKeyRef.current(closest);
|
|
50
|
-
if (lastDateRef.current !== key) {
|
|
51
|
-
lastDateRef.current = key;
|
|
52
|
-
ctxRef.current.setHover(key, chartId);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
[data, chartId, marginLeft, show, lastDateRef]
|
|
56
|
-
);
|
|
57
|
-
const handleLeave = useCallback(() => {
|
|
58
|
-
hide();
|
|
59
|
-
if (ctxRef.current.source === chartId) ctxRef.current.setHover(null, null);
|
|
60
|
-
}, [hide, chartId]);
|
|
61
|
-
const syncedPoint = ctx.key ? resolveKey ? resolveKey(ctx.key) : pointByKey.get(ctx.key) ?? null : tip?.data ?? null;
|
|
62
|
-
const hasProvider = ctx.setHover !== DEFAULT_NO_OP_SET_HOVER;
|
|
63
|
-
const isDirectHover = hasProvider ? ctx.source === chartId : tip !== null;
|
|
64
|
-
return {
|
|
65
|
-
tip,
|
|
66
|
-
tooltipRef,
|
|
67
|
-
syncedPoint,
|
|
68
|
-
isDirectHover,
|
|
69
|
-
handleMouse,
|
|
70
|
-
handleLeave
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
export {
|
|
74
|
-
useHoverSync
|
|
75
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type HoverCtx = {
|
|
2
|
-
key: string | null;
|
|
3
|
-
source: string | null;
|
|
4
|
-
setHover: (key: string | null, source: string | null) => void;
|
|
5
|
-
};
|
|
6
|
-
/**
|
|
7
|
-
* Sentinel identity used as the default context's `setHover`. `useHoverSync` compares against it
|
|
8
|
-
* (`ctx.setHover !== DEFAULT_NO_OP_SET_HOVER`) to decide whether a `ChartHoverSync` provider is
|
|
9
|
-
* mounted — which is load-bearing, not just a dev warning: standalone charts (no provider) drive
|
|
10
|
-
* their tooltip + crosshair from local hover, while inside a provider only the broadcast source
|
|
11
|
-
* owns the tooltip. CONTRACT: a real provider MUST supply its own `setHover`, never re-use this
|
|
12
|
-
* no-op (a provider that passes this sentinel would be (mis)read as "no provider").
|
|
13
|
-
* Exported only for that identity check — do NOT call directly.
|
|
14
|
-
*/
|
|
15
|
-
export declare const DEFAULT_NO_OP_SET_HOVER: HoverCtx['setHover'];
|
|
16
|
-
/**
|
|
17
|
-
* Shared-cursor context — charts write the hovered key (an opaque category/date string) + their
|
|
18
|
-
* own chartId; other charts read and show a ghost crosshair + dot on the same key.
|
|
19
|
-
*/
|
|
20
|
-
export declare const HoverContext: import("react").Context<HoverCtx>;
|
|
21
|
-
//# sourceMappingURL=hover-context.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hover-context.d.ts","sourceRoot":"","sources":["../../src/charts/hover-context.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC9D,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,UAAU,CAAY,CAAA;AAErE;;;GAGG;AACH,eAAO,MAAM,YAAY,mCAIvB,CAAA"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
export type ChartHoverSyncProps = {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
};
|
|
5
|
-
/**
|
|
6
|
-
* Activates the shared-cursor HoverContext: holds the hovered key (an opaque
|
|
7
|
-
* category/date string) + source chartId in one place so every descendant
|
|
8
|
-
* chart's `useHoverSync` reads and writes the same cursor. Hovering one chart
|
|
9
|
-
* shows a ghost crosshair + dot on all sibling charts under this provider.
|
|
10
|
-
*
|
|
11
|
-
* Wrap a group of charts that should share a cursor in `<ChartHoverSync>`.
|
|
12
|
-
*/
|
|
13
|
-
export declare function ChartHoverSync({ children }: ChartHoverSyncProps): import("react").JSX.Element;
|
|
14
|
-
//# sourceMappingURL=hover-sync.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hover-sync.d.ts","sourceRoot":"","sources":["../../src/charts/hover-sync.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAOD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,mBAAmB,+BAc/D"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useState } from "react";
|
|
3
|
-
import { HoverContext } from "./hover-context.js";
|
|
4
|
-
function ChartHoverSync({ children }) {
|
|
5
|
-
const [hover, setHoverState] = useState({ key: null, source: null });
|
|
6
|
-
const setHover = useCallback((key, source) => {
|
|
7
|
-
setHoverState({ key, source });
|
|
8
|
-
}, []);
|
|
9
|
-
const value = useMemo(
|
|
10
|
-
() => ({ key: hover.key, source: hover.source, setHover }),
|
|
11
|
-
[hover.key, hover.source, setHover]
|
|
12
|
-
);
|
|
13
|
-
return /* @__PURE__ */ jsx(HoverContext.Provider, { value, children });
|
|
14
|
-
}
|
|
15
|
-
export {
|
|
16
|
-
ChartHoverSync
|
|
17
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
export type ResponsiveChartProps = {
|
|
3
|
-
/**
|
|
4
|
-
* Fixed height in pixels. Used when `aspectRatio` is not set. Default 240.
|
|
5
|
-
* Ignored when `aspectRatio` is set — height is derived from measured width.
|
|
6
|
-
*/
|
|
7
|
-
height?: number;
|
|
8
|
-
/**
|
|
9
|
-
* When set, height = Math.round(width / aspectRatio) and `height` prop is ignored.
|
|
10
|
-
* E.g. `aspectRatio={16 / 9}` produces a 16:9 panel.
|
|
11
|
-
*/
|
|
12
|
-
aspectRatio?: number;
|
|
13
|
-
/** ResizeObserver debounce in milliseconds. Default 0 (immediate). */
|
|
14
|
-
debounceMs?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Accessible text alternative for the chart, applied as `aria-label` (+ `role="img"`) on the
|
|
17
|
-
* outer measuring container — e.g. when wrapping `Heatmap`, which measures via this primitive
|
|
18
|
-
* rather than `ChartFrame`.
|
|
19
|
-
*/
|
|
20
|
-
ariaLabel?: string;
|
|
21
|
-
/** Render prop — called with the measured `{ width, height }`. */
|
|
22
|
-
children: (size: {
|
|
23
|
-
width: number;
|
|
24
|
-
height: number;
|
|
25
|
-
}) => ReactNode;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Render-prop container that measures its own width and derives a final height
|
|
29
|
-
* from `height` (default 240) or `aspectRatio`. Renders nothing until the first
|
|
30
|
-
* measurement arrives (`width > 0`). Backed by `@visx/responsive`'s `useParentSize`.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* <ResponsiveChart height={320}>
|
|
34
|
-
* {({ width, height }) => (
|
|
35
|
-
* <Bars width={width} height={height} data={data} ... />
|
|
36
|
-
* )}
|
|
37
|
-
* </ResponsiveChart>
|
|
38
|
-
*
|
|
39
|
-
* @example aspectRatio
|
|
40
|
-
* <ResponsiveChart aspectRatio={16 / 9}>
|
|
41
|
-
* {({ width, height }) => <MyChart width={width} height={height} />}
|
|
42
|
-
* </ResponsiveChart>
|
|
43
|
-
*/
|
|
44
|
-
export declare function ResponsiveChart({ height, aspectRatio, debounceMs, ariaLabel, children, }: ResponsiveChartProps): ReactNode;
|
|
45
|
-
//# sourceMappingURL=ResponsiveChart.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ResponsiveChart.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ResponsiveChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAA;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAY,EACZ,WAAW,EACX,UAAc,EACd,SAAS,EACT,QAAQ,GACT,EAAE,oBAAoB,GAAG,SAAS,CAclC"}
|