basalt-ui 1.14.0 → 1.16.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 (176) 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 +279 -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/agent/templates/CLAUDE-block.md.tpl +9 -6
  9. package/configs/oxlint-plugin.js +152 -0
  10. package/configs/oxlint-plugin.test.ts +107 -0
  11. package/configs/oxlint.json +2 -0
  12. package/dist/charts/cursor/resolve.d.ts +33 -0
  13. package/dist/charts/cursor/resolve.d.ts.map +1 -0
  14. package/dist/charts/cursor/resolve.js +61 -0
  15. package/dist/charts/cursor/resolve.test.d.ts +2 -0
  16. package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
  17. package/dist/charts/cursor/scope.d.ts +17 -0
  18. package/dist/charts/cursor/scope.d.ts.map +1 -0
  19. package/dist/charts/cursor/scope.js +20 -0
  20. package/dist/charts/cursor/store.d.ts +31 -0
  21. package/dist/charts/cursor/store.d.ts.map +1 -0
  22. package/dist/charts/cursor/store.js +25 -0
  23. package/dist/charts/cursor/store.test.d.ts +2 -0
  24. package/dist/charts/cursor/store.test.d.ts.map +1 -0
  25. package/dist/charts/hooks/useChartCursor.d.ts +39 -0
  26. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
  27. package/dist/charts/hooks/useChartCursor.js +104 -0
  28. package/dist/charts/hooks/useChartSize.d.ts +8 -4
  29. package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
  30. package/dist/charts/index.d.ts +16 -13
  31. package/dist/charts/index.d.ts.map +1 -1
  32. package/dist/charts/index.js +38 -19
  33. package/dist/charts/kinds/Bars.d.ts +45 -54
  34. package/dist/charts/kinds/Bars.d.ts.map +1 -1
  35. package/dist/charts/kinds/Bars.js +235 -392
  36. package/dist/charts/kinds/Bars.test.d.ts +2 -0
  37. package/dist/charts/kinds/Bars.test.d.ts.map +1 -0
  38. package/dist/charts/kinds/Donut.d.ts +5 -4
  39. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  40. package/dist/charts/kinds/Donut.js +17 -14
  41. package/dist/charts/kinds/DualPanel.d.ts +13 -0
  42. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  43. package/dist/charts/kinds/DualPanel.js +185 -92
  44. package/dist/charts/kinds/DualPanel.test.d.ts +2 -0
  45. package/dist/charts/kinds/DualPanel.test.d.ts.map +1 -0
  46. package/dist/charts/kinds/Heatmap.d.ts +15 -11
  47. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  48. package/dist/charts/kinds/Heatmap.js +38 -22
  49. package/dist/charts/kinds/MultiLine.d.ts +27 -35
  50. package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
  51. package/dist/charts/kinds/MultiLine.js +122 -235
  52. package/dist/charts/kinds/MultiLine.test.d.ts +2 -0
  53. package/dist/charts/kinds/MultiLine.test.d.ts.map +1 -0
  54. package/dist/charts/kinds/StackedArea.d.ts +21 -10
  55. package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
  56. package/dist/charts/kinds/StackedArea.js +74 -132
  57. package/dist/charts/kinds/ZonedLine.d.ts +24 -40
  58. package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
  59. package/dist/charts/kinds/ZonedLine.js +104 -184
  60. package/dist/charts/kinds/ZonedLine.test.d.ts +2 -0
  61. package/dist/charts/kinds/ZonedLine.test.d.ts.map +1 -0
  62. package/dist/charts/layout/auto-margin.d.ts +60 -0
  63. package/dist/charts/layout/auto-margin.d.ts.map +1 -0
  64. package/dist/charts/layout/auto-margin.js +37 -0
  65. package/dist/charts/layout/auto-margin.test.d.ts +2 -0
  66. package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
  67. package/dist/charts/primitives/CartesianChart.d.ts +161 -0
  68. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
  69. package/dist/charts/primitives/CartesianChart.js +359 -0
  70. package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
  71. package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
  72. package/dist/charts/primitives/ChartFrame.d.ts +16 -5
  73. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  74. package/dist/charts/primitives/ChartFrame.js +17 -2
  75. package/dist/charts/primitives/ChartLegend.d.ts +15 -1
  76. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  77. package/dist/charts/primitives/ChartLegend.js +25 -3
  78. package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
  79. package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
  80. package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
  81. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  82. package/dist/charts/primitives/ChartTooltip.js +45 -19
  83. package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
  84. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  85. package/dist/charts/primitives/HoverOverlay.js +22 -1
  86. package/dist/charts/primitives/XZoneRects.d.ts +54 -0
  87. package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
  88. package/dist/charts/primitives/XZoneRects.js +65 -0
  89. package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
  90. package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
  91. package/dist/charts/series.d.ts +28 -3
  92. package/dist/charts/series.d.ts.map +1 -1
  93. package/dist/charts/series.js +5 -2
  94. package/dist/charts/series.test.d.ts +2 -0
  95. package/dist/charts/series.test.d.ts.map +1 -0
  96. package/dist/charts/utils/measure-text.d.ts +25 -0
  97. package/dist/charts/utils/measure-text.d.ts.map +1 -0
  98. package/dist/charts/utils/measure-text.js +64 -0
  99. package/dist/charts/utils/measure-text.test.d.ts +2 -0
  100. package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
  101. package/dist/content/fence-block.d.ts.map +1 -1
  102. package/dist/surfaces.d.ts +1 -1
  103. package/dist/surfaces.d.ts.map +1 -1
  104. package/dist/surfaces.js +1 -1
  105. package/dist/tokens/chart-margin.test.d.ts +2 -0
  106. package/dist/tokens/chart-margin.test.d.ts.map +1 -0
  107. package/dist/tokens/index.d.ts +29 -0
  108. package/dist/tokens/index.d.ts.map +1 -1
  109. package/dist/tokens/index.js +10 -0
  110. package/dist/tokens/palette.d.ts +4 -0
  111. package/dist/tokens/palette.d.ts.map +1 -1
  112. package/dist/tokens/palette.js +3 -0
  113. package/llms.txt +3 -3
  114. package/package.json +1 -1
  115. package/src/charts/cursor/resolve.test.ts +61 -0
  116. package/src/charts/cursor/resolve.ts +102 -0
  117. package/src/charts/cursor/scope.tsx +32 -0
  118. package/src/charts/cursor/store.test.ts +46 -0
  119. package/src/charts/cursor/store.ts +53 -0
  120. package/src/charts/hooks/useChartCursor.ts +163 -0
  121. package/src/charts/hooks/useChartSize.ts +8 -4
  122. package/src/charts/index.ts +35 -14
  123. package/src/charts/kinds/Bars.test.tsx +98 -0
  124. package/src/charts/kinds/Bars.tsx +350 -467
  125. package/src/charts/kinds/Donut.tsx +31 -23
  126. package/src/charts/kinds/DualPanel.test.tsx +131 -0
  127. package/src/charts/kinds/DualPanel.tsx +301 -135
  128. package/src/charts/kinds/Heatmap.tsx +72 -47
  129. package/src/charts/kinds/MultiLine.test.tsx +37 -0
  130. package/src/charts/kinds/MultiLine.tsx +171 -290
  131. package/src/charts/kinds/StackedArea.tsx +116 -175
  132. package/src/charts/kinds/ZonedLine.test.tsx +37 -0
  133. package/src/charts/kinds/ZonedLine.tsx +149 -255
  134. package/src/charts/layout/auto-margin.test.ts +42 -0
  135. package/src/charts/layout/auto-margin.ts +110 -0
  136. package/src/charts/primitives/CartesianChart.test.tsx +160 -0
  137. package/src/charts/primitives/CartesianChart.tsx +598 -0
  138. package/src/charts/primitives/ChartFrame.test.tsx +33 -0
  139. package/src/charts/primitives/ChartFrame.tsx +40 -4
  140. package/src/charts/primitives/ChartLegend.test.tsx +78 -0
  141. package/src/charts/primitives/ChartLegend.tsx +52 -2
  142. package/src/charts/primitives/ChartTooltip.tsx +67 -37
  143. package/src/charts/primitives/HoverOverlay.tsx +39 -3
  144. package/src/charts/primitives/XZoneRects.test.tsx +123 -0
  145. package/src/charts/primitives/XZoneRects.tsx +132 -0
  146. package/src/charts/series.test.ts +112 -0
  147. package/src/charts/series.ts +33 -5
  148. package/src/charts/utils/measure-text.test.ts +36 -0
  149. package/src/charts/utils/measure-text.ts +109 -0
  150. package/src/content/fence-block.tsx +2 -3
  151. package/src/surfaces.ts +2 -1
  152. package/src/theme/spacing.test.ts +2 -0
  153. package/src/tokens/chart-margin.test.ts +39 -0
  154. package/src/tokens/density.test.ts +1 -0
  155. package/src/tokens/index.ts +36 -0
  156. package/src/tokens/palette.ts +3 -0
  157. package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
  158. package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
  159. package/dist/charts/hooks/useChartTooltip.js +0 -26
  160. package/dist/charts/hooks/useHoverSync.d.ts +0 -46
  161. package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
  162. package/dist/charts/hooks/useHoverSync.js +0 -75
  163. package/dist/charts/hover-context.d.ts +0 -21
  164. package/dist/charts/hover-context.d.ts.map +0 -1
  165. package/dist/charts/hover-context.js +0 -12
  166. package/dist/charts/hover-sync.d.ts +0 -14
  167. package/dist/charts/hover-sync.d.ts.map +0 -1
  168. package/dist/charts/hover-sync.js +0 -17
  169. package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
  170. package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
  171. package/dist/charts/primitives/ResponsiveChart.js +0 -24
  172. package/src/charts/hooks/useChartTooltip.ts +0 -31
  173. package/src/charts/hooks/useHoverSync.ts +0 -152
  174. package/src/charts/hover-context.ts +0 -28
  175. package/src/charts/hover-sync.tsx +0 -37
  176. package/src/charts/primitives/ResponsiveChart.tsx +0 -64
@@ -0,0 +1,160 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react'
2
+ import { describe, expect, test } from 'bun:test'
3
+ import { renderToStaticMarkup } from 'react-dom/server'
4
+ import type { ChartSeries } from '../series'
5
+ import { CartesianChart, resolveAxisDomain } from './CartesianChart'
6
+
7
+ type Row = { date: string; a: number; b: number }
8
+
9
+ const rows: Row[] = [
10
+ { date: '2026-08-01', a: 10, b: 4 },
11
+ { date: '2026-08-02', a: 40, b: 6 },
12
+ { date: '2026-08-03', a: 25, b: 5 },
13
+ ]
14
+
15
+ const seriesFor = (key: 'a' | 'b'): ChartSeries<Row> => ({
16
+ key,
17
+ label: key,
18
+ color: '#000',
19
+ mark: 'line',
20
+ getValue: (d) => d[key],
21
+ })
22
+
23
+ const both = [seriesFor('a'), seriesFor('b')]
24
+
25
+ describe('resolveAxisDomain', () => {
26
+ test('a fixed tuple passes through untouched', () => {
27
+ expect(resolveAxisDomain({ domain: [0, 100] }, rows, both)).toEqual([0, 100])
28
+ })
29
+
30
+ test('auto pads the upper bound away from zero and keeps the zero baseline', () => {
31
+ const [min, max] = resolveAxisDomain(undefined, rows, both)
32
+ expect(min).toBe(0)
33
+ expect(max).toBeCloseTo(44, 5)
34
+ })
35
+
36
+ test('auto over an ALL-NEGATIVE series keeps the largest value inside the plot', () => {
37
+ // `max * pad` would push the upper bound to -5.5, i.e. below the largest datum, clipping it.
38
+ const negative: ChartSeries<Row>[] = [
39
+ { ...seriesFor('a'), getValue: (d) => -d.a },
40
+ { ...seriesFor('b'), getValue: (d) => -d.b },
41
+ ]
42
+ const [min, max] = resolveAxisDomain({ autoMinCeil: Infinity }, rows, negative)
43
+ expect(max).toBeGreaterThanOrEqual(-4)
44
+ expect(min).toBeLessThanOrEqual(-40)
45
+ })
46
+
47
+ test('autoMaxFloor raises a low upper bound', () => {
48
+ const [, max] = resolveAxisDomain({ autoMaxFloor: 500 }, rows, both)
49
+ expect(max).toBe(500)
50
+ })
51
+
52
+ test('the domain follows the VISIBLE series — hiding the tall one shrinks the axis', () => {
53
+ const [, withBoth] = resolveAxisDomain(undefined, rows, both)
54
+ const [, onlyB] = resolveAxisDomain(undefined, rows, [seriesFor('b')])
55
+ expect(onlyB).toBeLessThan(withBoth)
56
+ })
57
+
58
+ test('a domain function receives the data AND the visible series', () => {
59
+ const domain = resolveAxisDomain(
60
+ { domain: (data, visible) => [0, data.length * visible.length] },
61
+ rows,
62
+ both,
63
+ )
64
+ expect(domain).toEqual([0, 6])
65
+ })
66
+
67
+ test('empty data falls back to a usable unit domain', () => {
68
+ expect(resolveAxisDomain(undefined, [], both)).toEqual([0, 1])
69
+ })
70
+ })
71
+
72
+ describe('resolveAxisDomain — overlay bounds', () => {
73
+ test('a zone past the data stretches the axis instead of clipping', () => {
74
+ const [, max] = resolveAxisDomain(undefined, rows, both, [0, 200])
75
+ expect(max).toBeGreaterThanOrEqual(200)
76
+ })
77
+
78
+ test('a reference line below the data lowers the floor', () => {
79
+ const [min] = resolveAxisDomain({ autoMinCeil: Infinity }, rows, both, [-30])
80
+ expect(min).toBeLessThanOrEqual(-30)
81
+ })
82
+
83
+ test('infinite bounds ("top/bottom of axis") are ignored, not blown up', () => {
84
+ const plain = resolveAxisDomain(undefined, rows, both)
85
+ expect(resolveAxisDomain(undefined, rows, both, [-Infinity, Infinity])).toEqual(plain)
86
+ })
87
+ })
88
+
89
+ describe('resolveAxisDomain — degenerate input', () => {
90
+ test('a flat all-zero series still gets a usable axis, not a zero-extent scale', () => {
91
+ const flat: ChartSeries<Row>[] = [{ ...seriesFor('a'), getValue: () => 0 }]
92
+ const [min, max] = resolveAxisDomain(undefined, rows, flat)
93
+ expect(max).toBeGreaterThan(min)
94
+ })
95
+
96
+ test('a flat non-zero series is not collapsed onto its own value', () => {
97
+ const flat: ChartSeries<Row>[] = [{ ...seriesFor('a'), getValue: () => 7 }]
98
+ const [min, max] = resolveAxisDomain({ autoMinCeil: Infinity }, rows, flat)
99
+ expect(max).toBeGreaterThan(min)
100
+ })
101
+ })
102
+
103
+ describe('AxisConfig.nice — threaded to BOTH the probe and the real scale', () => {
104
+ // A fixed, deliberately non-round domain: d3's `.ticks()` never produces a tick past the raw
105
+ // domain max (95.7) without `nice`, but WITH `nice` the scale first rounds its domain outward
106
+ // (e.g. to 100), so a tick at "100" can only appear on the `nice: true` render.
107
+ function renderChart(nice: boolean): string {
108
+ return renderToStaticMarkup(
109
+ <CartesianChart<Row>
110
+ data={rows}
111
+ chartId="nice-test"
112
+ getX={(d) => d.date}
113
+ series={[seriesFor('a')]}
114
+ y={{ domain: [0, 95.7], ticks: 5, format: (v) => String(v), nice }}
115
+ >
116
+ {() => null}
117
+ </CartesianChart>,
118
+ )
119
+ }
120
+
121
+ test('nice: false never paints a tick past the raw domain max', () => {
122
+ expect(renderChart(false)).not.toContain('>100<')
123
+ })
124
+
125
+ test('nice: true rounds the scale outward — a tick beyond the raw domain max appears', () => {
126
+ expect(renderChart(true)).toContain('>100<')
127
+ })
128
+ })
129
+
130
+ describe('tooltip extraRows — ctx.visible/ctx.hidden track legend toggling', () => {
131
+ test('toggling a series off via its legend entry updates the ctx the row reads', async () => {
132
+ render(
133
+ <CartesianChart<Row>
134
+ data={rows}
135
+ chartId="extra-rows-ctx"
136
+ getX={(d) => d.date}
137
+ series={both}
138
+ tooltip={{
139
+ extraRows: (_d, ctx) => (
140
+ <div data-testid="extra-ctx">
141
+ visible:{ctx.visible.map((s) => s.key).join(',')}|hidden:
142
+ {[...ctx.hidden].join(',')}
143
+ </div>
144
+ ),
145
+ }}
146
+ >
147
+ {() => null}
148
+ </CartesianChart>,
149
+ )
150
+
151
+ const slider = screen.getByRole('slider')
152
+ fireEvent.keyDown(slider, { key: 'ArrowRight' })
153
+
154
+ expect((await screen.findByTestId('extra-ctx')).textContent).toContain('visible:a,b|hidden:')
155
+
156
+ fireEvent.click(screen.getByRole('button', { name: 'b' }))
157
+
158
+ expect((await screen.findByTestId('extra-ctx')).textContent).toContain('visible:a|hidden:b')
159
+ })
160
+ })