basalt-ui 1.13.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.
Files changed (180) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +38 -2
  4. package/agent/rules/basalt-charts.md +260 -108
  5. package/agent/rules/basalt-mantine.md +4 -2
  6. package/agent/skills/basalt-charts/SKILL.md +114 -64
  7. package/agent/skills/basalt-design/SKILL.md +4 -3
  8. package/configs/oxlint-plugin.js +152 -0
  9. package/configs/oxlint-plugin.test.ts +107 -0
  10. package/configs/oxlint.json +2 -0
  11. package/dist/charts/cursor/resolve.d.ts +33 -0
  12. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  13. package/dist/charts/cursor/resolve.js +61 -0
  14. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  15. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  16. package/dist/charts/cursor/scope.d.ts +17 -0
  17. package/dist/charts/cursor/scope.d.ts.map +1 -0
  18. package/dist/charts/cursor/scope.js +20 -0
  19. package/dist/charts/cursor/store.d.ts +31 -0
  20. package/dist/charts/cursor/store.d.ts.map +1 -0
  21. package/dist/charts/cursor/store.js +25 -0
  22. package/dist/charts/cursor/store.test.d.ts +2 -0
  23. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  24. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  26. package/dist/charts/hooks/useChartCursor.js +104 -0
  27. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  28. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  29. package/dist/charts/index.d.ts +16 -13
  30. package/dist/charts/index.d.ts.map +1 -1
  31. package/dist/charts/index.js +38 -19
  32. package/dist/charts/kinds/Bars.d.ts +25 -47
  33. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  34. package/dist/charts/kinds/Bars.js +230 -390
  35. package/dist/charts/kinds/Donut.d.ts +5 -4
  36. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  37. package/dist/charts/kinds/Donut.js +17 -14
  38. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  39. package/dist/charts/kinds/DualPanel.js +136 -90
  40. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  41. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  42. package/dist/charts/kinds/Heatmap.js +38 -22
  43. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  44. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  45. package/dist/charts/kinds/MultiLine.js +114 -237
  46. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  47. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  48. package/dist/charts/kinds/StackedArea.js +74 -132
  49. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  50. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/ZonedLine.js +103 -184
  52. package/dist/charts/layout/auto-margin.d.ts +60 -0
  53. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  54. package/dist/charts/layout/auto-margin.js +37 -0
  55. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  56. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  57. package/dist/charts/primitives/CartesianChart.d.ts +143 -0
  58. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  59. package/dist/charts/primitives/CartesianChart.js +349 -0
  60. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  61. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  62. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  63. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  64. package/dist/charts/primitives/ChartFrame.js +17 -2
  65. package/dist/charts/primitives/ChartLegend.d.ts +12 -1
  66. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartLegend.js +21 -3
  68. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  69. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  70. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  71. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  72. package/dist/charts/primitives/ChartTooltip.js +45 -19
  73. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  74. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  75. package/dist/charts/primitives/HoverOverlay.js +22 -1
  76. package/dist/charts/primitives/XZoneRects.d.ts +40 -0
  77. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  78. package/dist/charts/primitives/XZoneRects.js +28 -0
  79. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  80. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  81. package/dist/charts/series.d.ts +16 -1
  82. package/dist/charts/series.d.ts.map +1 -1
  83. package/dist/charts/series.js +3 -1
  84. package/dist/charts/series.test.d.ts +2 -0
  85. package/dist/charts/series.test.d.ts.map +1 -0
  86. package/dist/charts/utils/measure-text.d.ts +25 -0
  87. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  88. package/dist/charts/utils/measure-text.js +64 -0
  89. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  90. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  91. package/dist/content/fence-block.d.ts.map +1 -1
  92. package/dist/shell/app-sidebar.d.ts +19 -1
  93. package/dist/shell/app-sidebar.d.ts.map +1 -1
  94. package/dist/shell/app-sidebar.js +30 -26
  95. package/dist/shell/app-sidebar.module.css +11 -0
  96. package/dist/shell/app-sidebar.module.css.d.ts +1 -0
  97. package/dist/shell/app-sidebar.test.d.ts +2 -0
  98. package/dist/shell/app-sidebar.test.d.ts.map +1 -0
  99. package/dist/shell/index.d.ts +10 -1
  100. package/dist/shell/index.d.ts.map +1 -1
  101. package/dist/shell/index.js +2 -0
  102. package/dist/shell/index.test.d.ts +2 -0
  103. package/dist/shell/index.test.d.ts.map +1 -0
  104. package/dist/surfaces.d.ts +1 -1
  105. package/dist/surfaces.d.ts.map +1 -1
  106. package/dist/surfaces.js +1 -1
  107. package/dist/tokens/chart-margin.test.d.ts +2 -0
  108. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  109. package/dist/tokens/index.d.ts +29 -0
  110. package/dist/tokens/index.d.ts.map +1 -1
  111. package/dist/tokens/index.js +10 -0
  112. package/dist/tokens/palette.d.ts +4 -0
  113. package/dist/tokens/palette.d.ts.map +1 -1
  114. package/dist/tokens/palette.js +3 -0
  115. package/llms.txt +3 -3
  116. package/package.json +1 -1
  117. package/src/charts/cursor/resolve.test.ts +61 -0
  118. package/src/charts/cursor/resolve.ts +102 -0
  119. package/src/charts/cursor/scope.tsx +32 -0
  120. package/src/charts/cursor/store.test.ts +46 -0
  121. package/src/charts/cursor/store.ts +53 -0
  122. package/src/charts/hooks/useChartCursor.ts +163 -0
  123. package/src/charts/hooks/useChartSize.ts +8 -4
  124. package/src/charts/index.ts +34 -14
  125. package/src/charts/kinds/Bars.tsx +327 -460
  126. package/src/charts/kinds/Donut.tsx +31 -23
  127. package/src/charts/kinds/DualPanel.tsx +228 -134
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.tsx +158 -294
  130. package/src/charts/kinds/StackedArea.tsx +116 -175
  131. package/src/charts/kinds/ZonedLine.tsx +148 -255
  132. package/src/charts/layout/auto-margin.test.ts +42 -0
  133. package/src/charts/layout/auto-margin.ts +110 -0
  134. package/src/charts/primitives/CartesianChart.test.ts +99 -0
  135. package/src/charts/primitives/CartesianChart.tsx +566 -0
  136. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  137. package/src/charts/primitives/ChartFrame.tsx +40 -4
  138. package/src/charts/primitives/ChartLegend.test.tsx +48 -0
  139. package/src/charts/primitives/ChartLegend.tsx +45 -2
  140. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  141. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  142. package/src/charts/primitives/XZoneRects.test.tsx +63 -0
  143. package/src/charts/primitives/XZoneRects.tsx +67 -0
  144. package/src/charts/series.test.ts +61 -0
  145. package/src/charts/series.ts +18 -1
  146. package/src/charts/utils/measure-text.test.ts +36 -0
  147. package/src/charts/utils/measure-text.ts +109 -0
  148. package/src/content/fence-block.tsx +2 -3
  149. package/src/shell/app-sidebar.module.css +11 -0
  150. package/src/shell/app-sidebar.module.css.d.ts +1 -0
  151. package/src/shell/app-sidebar.test.tsx +98 -0
  152. package/src/shell/app-sidebar.tsx +22 -0
  153. package/src/shell/index.test.tsx +35 -0
  154. package/src/shell/index.tsx +11 -0
  155. package/src/surfaces.ts +2 -1
  156. package/src/theme/spacing.test.ts +2 -0
  157. package/src/tokens/chart-margin.test.ts +39 -0
  158. package/src/tokens/density.test.ts +1 -0
  159. package/src/tokens/index.ts +36 -0
  160. package/src/tokens/palette.ts +3 -0
  161. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  162. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  163. package/dist/charts/hooks/useChartTooltip.js +0 -26
  164. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  165. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  166. package/dist/charts/hooks/useHoverSync.js +0 -75
  167. package/dist/charts/hover-context.d.ts +0 -21
  168. package/dist/charts/hover-context.d.ts.map +0 -1
  169. package/dist/charts/hover-context.js +0 -12
  170. package/dist/charts/hover-sync.d.ts +0 -14
  171. package/dist/charts/hover-sync.d.ts.map +0 -1
  172. package/dist/charts/hover-sync.js +0 -17
  173. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  174. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  175. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  176. package/src/charts/hooks/useChartTooltip.ts +0 -31
  177. package/src/charts/hooks/useHoverSync.ts +0 -152
  178. package/src/charts/hover-context.ts +0 -28
  179. package/src/charts/hover-sync.tsx +0 -37
  180. package/src/charts/primitives/ResponsiveChart.tsx +0 -64
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { createCursorStore, globalCursorStore } from './store'
3
+
4
+ describe('cursor store', () => {
5
+ test('notifies subscribers on change', () => {
6
+ const store = createCursorStore()
7
+ let calls = 0
8
+ const unsubscribe = store.subscribe(() => {
9
+ calls++
10
+ })
11
+
12
+ store.set('2026-08-18', 'chart-a')
13
+ expect(calls).toBe(1)
14
+ expect(store.get()).toEqual({ key: '2026-08-18', source: 'chart-a' })
15
+
16
+ unsubscribe()
17
+ store.set('2026-08-19', 'chart-a')
18
+ expect(calls).toBe(1)
19
+ })
20
+
21
+ test('a redundant set does not notify — a re-hover of the same point costs no render', () => {
22
+ const store = createCursorStore()
23
+ let calls = 0
24
+ store.subscribe(() => {
25
+ calls++
26
+ })
27
+
28
+ store.set('2026-08-18', 'chart-a')
29
+ store.set('2026-08-18', 'chart-a')
30
+ expect(calls).toBe(1)
31
+ })
32
+
33
+ test('clearing returns the stable empty identity', () => {
34
+ const store = createCursorStore()
35
+ const before = store.get()
36
+ store.set('2026-08-18', 'chart-a')
37
+ store.set(null, null)
38
+ expect(store.get()).toBe(before)
39
+ })
40
+
41
+ test('scoped stores are independent of the global one', () => {
42
+ const scoped = createCursorStore()
43
+ scoped.set('2026-08-18', 'chart-a')
44
+ expect(globalCursorStore.get().key).toBeNull()
45
+ })
46
+ })
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The shared chart cursor — an external store, not a React context value.
3
+ *
4
+ * Sharing used to require a `ChartHoverSync` ancestor, so it was opt-in and silently absent
5
+ * whenever someone forgot the wrapper. This inverts that: a module-level store means every
6
+ * chart on the page shares a cursor by default, and `ChartCursorScope` exists to ISOLATE a
7
+ * subtree (`docs/CHARTS-SPEC.md` §3). Charts with non-overlapping domains never resolve each
8
+ * other's keys, so "shared by default" cannot produce a false cursor.
9
+ *
10
+ * External store rather than context for a second reason: a cursor move re-renders only the
11
+ * charts subscribed to it, instead of every descendant of a provider whose value object is new
12
+ * on each pointer frame.
13
+ */
14
+
15
+ /** Broadcast cursor position: an opaque domain key plus the chart that owns it. */
16
+ export type CursorState = {
17
+ /** The hovered x-domain key (a date/category string), or null when nothing is hovered. */
18
+ key: string | null
19
+ /** `chartId` of the chart the pointer is actually over — the one that owns the tooltip. */
20
+ source: string | null
21
+ }
22
+
23
+ export type CursorStore = {
24
+ get: () => CursorState
25
+ set: (key: string | null, source: string | null) => void
26
+ subscribe: (onChange: () => void) => () => void
27
+ }
28
+
29
+ /** Stable identity for "nothing hovered" — also the SSR snapshot, so it must never be recreated. */
30
+ export const EMPTY_CURSOR: CursorState = { key: null, source: null }
31
+
32
+ export function createCursorStore(): CursorStore {
33
+ let state: CursorState = EMPTY_CURSOR
34
+ const listeners = new Set<() => void>()
35
+
36
+ return {
37
+ get: () => state,
38
+ set: (key, source) => {
39
+ if (state.key === key && state.source === source) return
40
+ state = key === null && source === null ? EMPTY_CURSOR : { key, source }
41
+ for (const listener of listeners) listener()
42
+ },
43
+ subscribe: (onChange) => {
44
+ listeners.add(onChange)
45
+ return () => {
46
+ listeners.delete(onChange)
47
+ }
48
+ },
49
+ }
50
+ }
51
+
52
+ /** The default store every chart shares unless a `ChartCursorScope` overrides it. */
53
+ export const globalCursorStore: CursorStore = createCursorStore()
@@ -0,0 +1,163 @@
1
+ import { localPoint } from '@visx/event'
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
+ import type { KeyboardEvent, PointerEvent } from 'react'
4
+ import { buildDomainIndex, resolveCursorPoint } from '../cursor/resolve'
5
+ import { useCursorState, useCursorStore } from '../cursor/scope'
6
+
7
+ /** Viewport-space pointer anchor the tooltip positions against. */
8
+ export type CursorAnchor = { x: number; y: number }
9
+
10
+ export type ChartCursor<T> = {
11
+ /** The point this chart paints its crosshair + dots at — own hover, or a resolved sibling key. */
12
+ point: T | null
13
+ /** True when the pointer is over THIS chart. Only the source chart shows the floating tooltip. */
14
+ isSource: boolean
15
+ /** Latest pointer position in viewport coords, or null when not hovered. */
16
+ anchor: CursorAnchor | null
17
+ onPointerMove: (event: PointerEvent<SVGRectElement>) => void
18
+ onPointerLeave: () => void
19
+ onKeyDown: (event: KeyboardEvent<SVGRectElement>) => void
20
+ onBlur: () => void
21
+ }
22
+
23
+ /**
24
+ * Wires a chart into the ambient cursor store: snaps the pointer to the nearest own point,
25
+ * broadcasts it, and reads back a sibling's broadcast through domain-aware resolution. Replaces
26
+ * the removed `useHoverSync` + `useChartTooltip` pair, including their `resolveKey` escape hatch —
27
+ * resolution is now automatic.
28
+ *
29
+ * The tooltip anchor is coalesced through `requestAnimationFrame`, so a fast scrub costs one local
30
+ * state write per frame instead of one per event. The BROADCAST is not rAF-gated — it is deduped
31
+ * instead (the store ignores a set that doesn't change the value), so a scrub within one x-bucket
32
+ * costs nothing while a real bucket change reaches siblings on the same tick rather than a frame
33
+ * late.
34
+ */
35
+ export function useChartCursor<T>({
36
+ data,
37
+ chartId,
38
+ getKey,
39
+ xScale,
40
+ marginLeft,
41
+ }: {
42
+ data: readonly T[]
43
+ chartId: string
44
+ getKey: (d: T) => string
45
+ /** Own x scale — maps a domain key to a plot-local x offset. */
46
+ xScale: (key: string) => number | undefined
47
+ marginLeft: number
48
+ }): ChartCursor<T> {
49
+ const store = useCursorStore()
50
+ const cursor = useCursorState()
51
+ const [anchor, setAnchor] = useState<CursorAnchor | null>(null)
52
+
53
+ // Accessors read through refs so the pointer callbacks stay referentially stable: the natural
54
+ // call style is an inline arrow, fresh every render, and re-creating these would re-bind the
55
+ // overlay's listeners on every cursor frame in every sibling chart.
56
+ const getKeyRef = useRef(getKey)
57
+ getKeyRef.current = getKey
58
+ const xScaleRef = useRef(xScale)
59
+ xScaleRef.current = xScale
60
+
61
+ const index = useMemo(() => buildDomainIndex(data, getKeyRef.current), [data])
62
+
63
+ const frameRef = useRef<number | null>(null)
64
+ const pendingRef = useRef<CursorAnchor | null>(null)
65
+ useEffect(
66
+ () => () => {
67
+ if (frameRef.current !== null) cancelAnimationFrame(frameRef.current)
68
+ // Release the shared cursor if this chart still owns it. Unmounting while hovered (a filter
69
+ // drops the chart, a tab switches) fires no leave/blur, so the store would keep pointing at
70
+ // a dead chartId — and any sibling whose domain resolves that stale key would paint a ghost
71
+ // crosshair with no way to clear it. Same ownership guard as `clear()`.
72
+ if (store.get().source === chartId) store.set(null, null)
73
+ },
74
+ [store, chartId],
75
+ )
76
+
77
+ const scheduleAnchor = useCallback((next: CursorAnchor) => {
78
+ pendingRef.current = next
79
+ if (frameRef.current !== null) return
80
+ frameRef.current = requestAnimationFrame(() => {
81
+ frameRef.current = null
82
+ setAnchor(pendingRef.current)
83
+ })
84
+ }, [])
85
+
86
+ const onPointerMove = useCallback(
87
+ (event: PointerEvent<SVGRectElement>) => {
88
+ if (data.length === 0) return
89
+ // Explicit reference node: `localPoint(event)` resolves it from `event.target`, which is the
90
+ // overlay rect only while the overlay is genuinely the topmost hit element. Passing
91
+ // `currentTarget` pins the coordinate space to the overlay regardless of what a kind draws
92
+ // above it.
93
+ const local = localPoint(event.currentTarget, event.nativeEvent)
94
+ if (local === null) return
95
+ const px = local.x - marginLeft
96
+
97
+ let closest = data[0] as T
98
+ let minDist = Infinity
99
+ for (const d of data) {
100
+ const x = xScaleRef.current(getKeyRef.current(d)) ?? 0
101
+ const dist = Math.abs(x - px)
102
+ if (dist < minDist) {
103
+ minDist = dist
104
+ closest = d
105
+ }
106
+ }
107
+
108
+ scheduleAnchor({ x: event.clientX, y: event.clientY })
109
+ store.set(getKeyRef.current(closest), chartId)
110
+ },
111
+ [data, marginLeft, chartId, store, scheduleAnchor],
112
+ )
113
+
114
+ const clear = useCallback(() => {
115
+ if (frameRef.current !== null) {
116
+ cancelAnimationFrame(frameRef.current)
117
+ frameRef.current = null
118
+ }
119
+ pendingRef.current = null
120
+ setAnchor(null)
121
+ // Only clear the SHARED cursor if this chart still owns it: moving fast from chart A to B lets
122
+ // A's leave fire after B's move, and an unconditional clear would wipe B's cursor.
123
+ if (store.get().source === chartId) store.set(null, null)
124
+ }, [store, chartId])
125
+
126
+ const onKeyDown = useCallback(
127
+ (event: KeyboardEvent<SVGRectElement>) => {
128
+ if (data.length === 0) return
129
+ if (event.key === 'Escape') {
130
+ clear()
131
+ return
132
+ }
133
+ const delta = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
134
+ if (delta === 0) return
135
+ event.preventDefault()
136
+
137
+ const current = store.get().key
138
+ const currentIndex = data.findIndex((d) => getKeyRef.current(d) === current)
139
+ const nextIndex = Math.min(
140
+ Math.max((currentIndex === -1 ? (delta > 0 ? -1 : data.length) : currentIndex) + delta, 0),
141
+ data.length - 1,
142
+ )
143
+ const next = data[nextIndex] as T
144
+ const rect = event.currentTarget.getBoundingClientRect()
145
+ const x = xScaleRef.current(getKeyRef.current(next)) ?? 0
146
+ scheduleAnchor({ x: rect.left + x, y: rect.top })
147
+ store.set(getKeyRef.current(next), chartId)
148
+ },
149
+ [data, store, chartId, clear, scheduleAnchor],
150
+ )
151
+
152
+ const point = cursor.key === null ? null : resolveCursorPoint(index, cursor.key)
153
+
154
+ return {
155
+ point,
156
+ isSource: cursor.source === chartId,
157
+ anchor,
158
+ onPointerMove,
159
+ onPointerLeave: clear,
160
+ onKeyDown,
161
+ onBlur: clear,
162
+ }
163
+ }
@@ -15,12 +15,16 @@ export type UseChartSizeResult = ChartSize & {
15
15
  * A thin basalt wrapper over `@visx/responsive`'s `useParentSize` — keeps the
16
16
  * `@visx/*` import inside `src/charts/**` per the Mantine-free boundary rule.
17
17
  *
18
+ * Charts do not call this directly — `ChartFrame` (and `CartesianChart` above it) measure for
19
+ * them, including the legend band. Reach for it when something OUTSIDE the chart system needs a
20
+ * measured box, e.g. sizing a sparkline to its table cell.
21
+ *
18
22
  * @example
19
- * function MyChart() {
20
- * const { ref, width, height } = useChartSize()
23
+ * function CellSparkline({ points }: { points: number[] }) {
24
+ * const { ref, width } = useChartSize()
21
25
  * return (
22
- * <div ref={ref} style={{ width: '100%', height: 240 }}>
23
- * {width > 0 && <Bars width={width} height={height} ... />}
26
+ * <div ref={ref} style={{ width: '100%', height: 24 }}>
27
+ * {width > 0 && <LineSparkline data={points} width={width} height={24} />}
24
28
  * </div>
25
29
  * )
26
30
  * }
@@ -2,8 +2,9 @@
2
2
  * `./charts` — Mantine-free visx chart system (ZERO `@mantine/*` imports, lint-enforced).
3
3
  *
4
4
  * Re-exports the framework token layer (so chart consumers have one import surface), the chart
5
- * theme context, the shared-cursor hover context, the primitives, kinds, sparklines, hooks, utils,
6
- * and a curated set of raw @visx primitives for bespoke charts.
5
+ * theme context, the shared cursor (module-level store — charts sync with no provider; see
6
+ * `docs/CHARTS-SPEC.md` §3), the primitives, kinds, sparklines, hooks, utils, and a curated set of
7
+ * raw @visx primitives for bespoke charts.
7
8
  *
8
9
  * The framework ships ONLY generic primitives + framework palette data — no domain series tree
9
10
  * (apps rebuild that app-side with `seriesTokens` / `groupTokens` against their own series maps).
@@ -20,15 +21,22 @@ export {
20
21
  seriesTokens,
21
22
  defineSeries,
22
23
  groupTokens,
24
+ chartMargin,
25
+ type ChartMargin,
23
26
  } from '../tokens'
24
27
 
25
28
  // ── Design seam type (type-only; erased at runtime — no @mantine value) ─
26
29
  export type { SeriesKey } from '../register'
27
30
 
28
- // ── Chart theme + hover context ──────────────────────────────────────────
31
+ // ── Chart theme + shared cursor ──────────────────────────────────────────
29
32
  export { VxThemeProvider, useVxTheme, type VxTheme } from './theme'
30
- export { HoverContext, DEFAULT_NO_OP_SET_HOVER, type HoverCtx } from './hover-context'
31
- export { ChartHoverSync, type ChartHoverSyncProps } from './hover-sync'
33
+ export { ChartCursorScope, type ChartCursorScopeProps, useCursorState } from './cursor/scope'
34
+ export {
35
+ createCursorStore,
36
+ globalCursorStore,
37
+ type CursorState,
38
+ type CursorStore,
39
+ } from './cursor/store'
32
40
 
33
41
  // ── Series descriptor (the legend/tooltip single source of truth) ────────
34
42
  export {
@@ -44,8 +52,21 @@ export {
44
52
  } from './series'
45
53
 
46
54
  // ── Primitives ───────────────────────────────────────────────────────────
47
- export { ResponsiveChart, type ResponsiveChartProps } from './primitives/ResponsiveChart'
48
- export { ChartFrame, type ChartFrameProps, type ChartFrameLegend } from './primitives/ChartFrame'
55
+ export {
56
+ CartesianChart,
57
+ resolveAxisDomain,
58
+ type CartesianChartProps,
59
+ type CartesianTooltipConfig,
60
+ type AxisConfig,
61
+ type PlotContext,
62
+ } from './primitives/CartesianChart'
63
+ export {
64
+ ChartFrame,
65
+ type ChartFrameProps,
66
+ type ChartFrameLegend,
67
+ type PlotRect,
68
+ resolveLegend,
69
+ } from './primitives/ChartFrame'
49
70
  export {
50
71
  ChartCenter,
51
72
  type ChartCenterProps,
@@ -56,25 +77,26 @@ export { Crosshair, SeriesDot } from './primitives/Crosshair'
56
77
  export { ChartCard } from './primitives/ChartCard'
57
78
  export { ChartLegend, type LegendEntry } from './primitives/ChartLegend'
58
79
  export {
59
- ChartTooltip,
80
+ ChartTooltipFloat,
60
81
  TooltipHeader,
61
82
  TooltipRow,
62
83
  TooltipBody,
63
- useTooltipStyles,
64
84
  } from './primitives/ChartTooltip'
65
85
  export { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from './primitives/Axes'
66
86
  export { HoverOverlay } from './primitives/HoverOverlay'
67
87
  export { ZoneRects, type ZoneSpec } from './primitives/ZoneRects'
88
+ export { XZoneRects, type XZoneSpec } from './primitives/XZoneRects'
68
89
  export { AreaGradient, areaFillUrl } from './primitives/AreaGradient'
69
90
 
70
91
  // ── Hooks ────────────────────────────────────────────────────────────────
71
- export { useChartTooltip, type TooltipState } from './hooks/useChartTooltip'
72
- export { useHoverSync } from './hooks/useHoverSync'
92
+ export { useChartCursor, type ChartCursor, type CursorAnchor } from './hooks/useChartCursor'
73
93
  export { useChartSize, type UseChartSizeResult, type ChartSize } from './hooks/useChartSize'
74
94
 
75
95
  // ── Utils ────────────────────────────────────────────────────────────────
76
96
  export { fmtAxisDate, fmtTooltipDate } from './utils/format'
77
- export { smartTicks } from './utils/ticks'
97
+ export { smartTicks, smartTicksEvery } from './utils/ticks'
98
+ export { autoMargin, probeAxisLabels, type AutoMarginInput } from './layout/auto-margin'
99
+ export { measureText, maxTextWidth } from './utils/measure-text'
78
100
 
79
101
  // ── Kind components (owned by a sibling agent under ./kinds) ──────────────
80
102
  export {
@@ -83,7 +105,6 @@ export {
83
105
  type ZonedLineZone,
84
106
  type ZonedLineThreshold,
85
107
  type ZonedLineRefLine,
86
- type ZonedLineTooltipLabel,
87
108
  } from './kinds/ZonedLine'
88
109
 
89
110
  export {
@@ -93,7 +114,6 @@ export {
93
114
  type BarsLine,
94
115
  type BarsZone,
95
116
  type BarsRefLine,
96
- type BarsAxisConfig,
97
117
  } from './kinds/Bars'
98
118
 
99
119
  export { StackedArea, type StackedAreaProps } from './kinds/StackedArea'