@carto/ps-react-ui 5.0.0 → 5.2.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/dist/actions-CF8RNKd6.js +1161 -0
- package/dist/actions-CF8RNKd6.js.map +1 -0
- package/dist/category-D01ruG-l.js +842 -0
- package/dist/category-D01ruG-l.js.map +1 -0
- package/dist/{download-DdaK3qgC.js → download-DGgFOaXj.js} +81 -80
- package/dist/download-DGgFOaXj.js.map +1 -0
- package/dist/{echart-DeBgdOtX.js → echart-CwzbPElc.js} +29 -29
- package/dist/echart-CwzbPElc.js.map +1 -0
- package/dist/{formula-noJzQwKo.js → formula-BIxJr1P3.js} +6 -6
- package/dist/{formula-noJzQwKo.js.map → formula-BIxJr1P3.js.map} +1 -1
- package/dist/{markdown-2dfL6Cmb.js → markdown-oo9mnJun.js} +6 -6
- package/dist/{markdown-2dfL6Cmb.js.map → markdown-oo9mnJun.js.map} +1 -1
- package/dist/{spread-Ccpg9fi0.js → spread-CgCTKPQD.js} +7 -7
- package/dist/{spread-Ccpg9fi0.js.map → spread-CgCTKPQD.js.map} +1 -1
- package/dist/{table-Bx9Uj3U0.js → table-lb5momun.js} +3 -3
- package/dist/{table-Bx9Uj3U0.js.map → table-lb5momun.js.map} +1 -1
- package/dist/types/widgets-v2/actions/download/csv-item.d.ts +3 -3
- package/dist/types/widgets-v2/actions/download/index.d.ts +1 -1
- package/dist/types/widgets-v2/actions/download/png-item.d.ts +2 -2
- package/dist/types/widgets-v2/actions/download/types.d.ts +12 -1
- package/dist/types/widgets-v2/actions/fullscreen/fullscreen.d.ts +42 -16
- package/dist/types/widgets-v2/actions/fullscreen/style.d.ts +9 -0
- package/dist/types/widgets-v2/actions/index.d.ts +1 -1
- package/dist/types/widgets-v2/bar/download.d.ts +3 -3
- package/dist/types/widgets-v2/category/category-ui.d.ts +9 -1
- package/dist/types/widgets-v2/category/download.d.ts +3 -3
- package/dist/types/widgets-v2/echart/echart-ui.d.ts +2 -2
- package/dist/types/widgets-v2/echart/echart.d.ts +9 -1
- package/dist/types/widgets-v2/echart/style.d.ts +2 -0
- package/dist/types/widgets-v2/formula/download.d.ts +3 -3
- package/dist/types/widgets-v2/histogram/download.d.ts +3 -3
- package/dist/types/widgets-v2/index.d.ts +2 -2
- package/dist/types/widgets-v2/markdown/download.d.ts +3 -3
- package/dist/types/widgets-v2/pie/download.d.ts +3 -3
- package/dist/types/widgets-v2/scatterplot/download.d.ts +3 -3
- package/dist/types/widgets-v2/spread/download.d.ts +3 -3
- package/dist/types/widgets-v2/table/download.d.ts +3 -3
- package/dist/types/widgets-v2/timeseries/download.d.ts +3 -3
- package/dist/types/widgets-v2/toolbox/toolbox.d.ts +1 -1
- package/dist/widgets-v2/actions.js +2 -2
- package/dist/widgets-v2/bar.js +7 -7
- package/dist/widgets-v2/bar.js.map +1 -1
- package/dist/widgets-v2/category.js +1 -1
- package/dist/widgets-v2/echart.js +1 -1
- package/dist/widgets-v2/formula.js +1 -1
- package/dist/widgets-v2/histogram.js +10 -10
- package/dist/widgets-v2/histogram.js.map +1 -1
- package/dist/widgets-v2/markdown.js +1 -1
- package/dist/widgets-v2/pie.js +10 -11
- package/dist/widgets-v2/pie.js.map +1 -1
- package/dist/widgets-v2/scatterplot.js +9 -9
- package/dist/widgets-v2/scatterplot.js.map +1 -1
- package/dist/widgets-v2/spread.js +1 -1
- package/dist/widgets-v2/table.js +1 -1
- package/dist/widgets-v2/timeseries.js +12 -12
- package/dist/widgets-v2/timeseries.js.map +1 -1
- package/dist/widgets-v2.js +274 -333
- package/dist/widgets-v2.js.map +1 -1
- package/package.json +1 -1
- package/src/widgets-v2/actions/download/csv-item.test.tsx +3 -3
- package/src/widgets-v2/actions/download/csv-item.tsx +6 -6
- package/src/widgets-v2/actions/download/download.tsx +5 -3
- package/src/widgets-v2/actions/download/index.ts +1 -1
- package/src/widgets-v2/actions/download/png-item.test.tsx +5 -3
- package/src/widgets-v2/actions/download/png-item.tsx +4 -4
- package/src/widgets-v2/actions/download/types.ts +15 -1
- package/src/widgets-v2/actions/fullscreen/fullscreen-store.test.tsx +155 -0
- package/src/widgets-v2/actions/fullscreen/fullscreen.test.tsx +93 -33
- package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +272 -86
- package/src/widgets-v2/actions/fullscreen/style.ts +31 -4
- package/src/widgets-v2/actions/index.ts +1 -0
- package/src/widgets-v2/actions/searcher/searcher.tsx +16 -0
- package/src/widgets-v2/bar/download.test.tsx +5 -3
- package/src/widgets-v2/bar/download.tsx +5 -4
- package/src/widgets-v2/category/category-ui.tsx +24 -13
- package/src/widgets-v2/category/category.tsx +37 -2
- package/src/widgets-v2/category/download.test.ts +4 -2
- package/src/widgets-v2/category/download.ts +5 -4
- package/src/widgets-v2/echart/echart-ui.test.tsx +6 -3
- package/src/widgets-v2/echart/echart-ui.tsx +6 -2
- package/src/widgets-v2/echart/echart.test.tsx +1 -1
- package/src/widgets-v2/echart/echart.tsx +10 -0
- package/src/widgets-v2/echart/style.ts +16 -1
- package/src/widgets-v2/formula/download.test.tsx +3 -1
- package/src/widgets-v2/formula/download.tsx +5 -4
- package/src/widgets-v2/histogram/download.test.ts +6 -4
- package/src/widgets-v2/histogram/download.ts +5 -4
- package/src/widgets-v2/index.ts +1 -0
- package/src/widgets-v2/markdown/download.test.ts +2 -2
- package/src/widgets-v2/markdown/download.tsx +5 -4
- package/src/widgets-v2/pie/download.test.ts +7 -3
- package/src/widgets-v2/pie/download.ts +5 -4
- package/src/widgets-v2/pie/options.test.ts +8 -0
- package/src/widgets-v2/pie/options.ts +3 -8
- package/src/widgets-v2/scatterplot/download.test.ts +4 -2
- package/src/widgets-v2/scatterplot/download.ts +5 -4
- package/src/widgets-v2/spread/download.test.ts +3 -1
- package/src/widgets-v2/spread/download.ts +5 -4
- package/src/widgets-v2/table/download.test.ts +5 -3
- package/src/widgets-v2/table/download.ts +4 -3
- package/src/widgets-v2/timeseries/download.test.ts +6 -4
- package/src/widgets-v2/timeseries/download.ts +5 -4
- package/src/widgets-v2/toolbox/toolbox.tsx +1 -1
- package/dist/actions-yPZwFxSZ.js +0 -1004
- package/dist/actions-yPZwFxSZ.js.map +0 -1
- package/dist/category-DGUDabsi.js +0 -829
- package/dist/category-DGUDabsi.js.map +0 -1
- package/dist/download-DdaK3qgC.js.map +0 -1
- package/dist/echart-DeBgdOtX.js.map +0 -1
|
@@ -96,6 +96,14 @@ export interface CategoryUIProps {
|
|
|
96
96
|
* No-op for single-series data — same convention as the legend.
|
|
97
97
|
*/
|
|
98
98
|
stacked?: boolean
|
|
99
|
+
/**
|
|
100
|
+
* Series indices hidden via the legend. Leave both this and
|
|
101
|
+
* `onHiddenSeriesChange` unset and CategoryUI keeps the set itself; pass
|
|
102
|
+
* them to own it — `Category` does, so the card and the fullscreen copy
|
|
103
|
+
* share it through the widget store.
|
|
104
|
+
*/
|
|
105
|
+
hiddenSeries?: ReadonlySet<number>
|
|
106
|
+
onHiddenSeriesChange?: (next: ReadonlySet<number>) => void
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
interface GroupedRow {
|
|
@@ -181,6 +189,8 @@ export function CategoryUI({
|
|
|
181
189
|
maxOverride,
|
|
182
190
|
size = 'small',
|
|
183
191
|
stacked = false,
|
|
192
|
+
hiddenSeries: hiddenSeriesProp,
|
|
193
|
+
onHiddenSeriesChange,
|
|
184
194
|
}: CategoryUIProps) {
|
|
185
195
|
const theme = useTheme()
|
|
186
196
|
const fmt = formatter ?? ((n: number) => String(n))
|
|
@@ -300,9 +310,10 @@ export function CategoryUI({
|
|
|
300
310
|
// selection, unrelated). Never allowed to reach "every series hidden":
|
|
301
311
|
// toggling the last visible one instead resets to "all visible" rather
|
|
302
312
|
// than leaving the widget blank.
|
|
303
|
-
const [
|
|
313
|
+
const [localHidden, setLocalHidden] = useState<ReadonlySet<number>>(
|
|
304
314
|
() => new Set(),
|
|
305
315
|
)
|
|
316
|
+
const hiddenSeries = hiddenSeriesProp ?? localHidden
|
|
306
317
|
// A reload with a different series breakdown invalidates hidden indices —
|
|
307
318
|
// index 2 may now point at an entirely different series. Reset rather
|
|
308
319
|
// than keep hiding whatever now occupies a stale index. Adjusted directly
|
|
@@ -317,7 +328,7 @@ export function CategoryUI({
|
|
|
317
328
|
const [prevSeriesKey, setPrevSeriesKey] = useState(seriesKey)
|
|
318
329
|
if (seriesKey !== prevSeriesKey) {
|
|
319
330
|
setPrevSeriesKey(seriesKey)
|
|
320
|
-
|
|
331
|
+
setLocalHidden(new Set())
|
|
321
332
|
}
|
|
322
333
|
// Real number of data series — NOT `series?.length`, which is legend
|
|
323
334
|
// metadata and may legitimately be shorter than the actual series count
|
|
@@ -327,18 +338,18 @@ export function CategoryUI({
|
|
|
327
338
|
const seriesCount = Math.max(data.length, 1)
|
|
328
339
|
const toggleSeries = useCallback(
|
|
329
340
|
(index: number) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
341
|
+
// Start from what the user sees, not the stored set: `data` can
|
|
342
|
+
// shrink while the `series` names hold (the reset only watches the
|
|
343
|
+
// names), and a stale index would otherwise count toward the guard
|
|
344
|
+
// below — or make unchecking one series hide a different one.
|
|
345
|
+
const next = effectiveHidden(hiddenSeries, seriesCount)
|
|
346
|
+
if (next.has(index)) next.delete(index)
|
|
347
|
+
else next.add(index)
|
|
348
|
+
;(onHiddenSeriesChange ?? setLocalHidden)(
|
|
349
|
+
next.size >= seriesCount ? new Set() : next,
|
|
350
|
+
)
|
|
340
351
|
},
|
|
341
|
-
[seriesCount],
|
|
352
|
+
[hiddenSeries, onHiddenSeriesChange, seriesCount],
|
|
342
353
|
)
|
|
343
354
|
|
|
344
355
|
if (grouped.length === 0) return null
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
getWidgetStore,
|
|
4
|
+
useWidget,
|
|
5
|
+
useWidgetId,
|
|
6
|
+
useWidgetShallow,
|
|
7
|
+
type WidgetState,
|
|
8
|
+
} from '../stores'
|
|
3
9
|
import { CategoryUI } from './category-ui'
|
|
4
10
|
import type {
|
|
5
11
|
CategoryKey,
|
|
@@ -23,6 +29,19 @@ const categorySelector = (s: WidgetState): CategorySlice => ({
|
|
|
23
29
|
labelFormatter: s.labelFormatter,
|
|
24
30
|
})
|
|
25
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Legend-hidden series, kept on the widget store so every mounted Category
|
|
34
|
+
* of the widget — the card and its fullscreen copy — shows the same set.
|
|
35
|
+
* Tagged with the series breakdown it was made for: after a reload with
|
|
36
|
+
* different series the indices point elsewhere, so a stale set reads as
|
|
37
|
+
* empty instead of being reset with a write.
|
|
38
|
+
*/
|
|
39
|
+
interface CategoryWidgetState extends WidgetState {
|
|
40
|
+
categoryHiddenSeries?: { key: string; indices: ReadonlySet<number> }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const NO_HIDDEN: ReadonlySet<number> = new Set()
|
|
44
|
+
|
|
26
45
|
function maxFromCategoryData(d: CategoryWidgetData | undefined): number {
|
|
27
46
|
if (!d) return 0
|
|
28
47
|
let m = 0
|
|
@@ -111,6 +130,20 @@ export function Category({
|
|
|
111
130
|
)
|
|
112
131
|
const effectiveMaxOverride =
|
|
113
132
|
maxOverride ?? (naturalMax > 0 ? naturalMax : undefined)
|
|
133
|
+
|
|
134
|
+
const seriesKey = JSON.stringify(series?.map((s) => s.name) ?? [])
|
|
135
|
+
const stored = useWidget<
|
|
136
|
+
CategoryWidgetState['categoryHiddenSeries'],
|
|
137
|
+
CategoryWidgetState
|
|
138
|
+
>(id, (s) => s.categoryHiddenSeries)
|
|
139
|
+
const hiddenSeries = stored?.key === seriesKey ? stored.indices : NO_HIDDEN
|
|
140
|
+
const onHiddenSeriesChange = useCallback(
|
|
141
|
+
(indices: ReadonlySet<number>) =>
|
|
142
|
+
getWidgetStore(id).setState({
|
|
143
|
+
categoryHiddenSeries: { key: seriesKey, indices },
|
|
144
|
+
} as Partial<CategoryWidgetState>),
|
|
145
|
+
[id, seriesKey],
|
|
146
|
+
)
|
|
114
147
|
return (
|
|
115
148
|
<CategoryUI
|
|
116
149
|
data={slice.data}
|
|
@@ -125,6 +158,8 @@ export function Category({
|
|
|
125
158
|
maxOverride={effectiveMaxOverride}
|
|
126
159
|
size={size}
|
|
127
160
|
stacked={stacked}
|
|
161
|
+
hiddenSeries={hiddenSeries}
|
|
162
|
+
onHiddenSeriesChange={onHiddenSeriesChange}
|
|
128
163
|
/>
|
|
129
164
|
)
|
|
130
165
|
}
|
|
@@ -60,7 +60,9 @@ describe('createCategoryDownloadConfig', () => {
|
|
|
60
60
|
filename: 'c',
|
|
61
61
|
getData: () => data,
|
|
62
62
|
})
|
|
63
|
-
const handle = await items
|
|
63
|
+
const handle = await items
|
|
64
|
+
.find((i) => i.id === 'csv')!
|
|
65
|
+
.resolve({ id: null })
|
|
64
66
|
expect(handle.filename).toBe('c.csv')
|
|
65
67
|
expect(csvText).toBe(
|
|
66
68
|
'series,name,value\nseries_1,a,1\nseries_1,b,2\nseries_2,a,10',
|
|
@@ -73,7 +75,7 @@ describe('createCategoryDownloadConfig', () => {
|
|
|
73
75
|
getData: () => data,
|
|
74
76
|
seriesNames: ['baseline', 'override'],
|
|
75
77
|
})
|
|
76
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
78
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
77
79
|
expect(csvText.split('\n')[1]).toBe('baseline,a,1')
|
|
78
80
|
expect(csvText.split('\n')[3]).toBe('override,a,10')
|
|
79
81
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCsvDownloadItem,
|
|
3
3
|
buildPngDownloadItem,
|
|
4
|
+
type DownloadContext,
|
|
4
5
|
type DownloadItem,
|
|
5
6
|
} from '../actions/download'
|
|
6
7
|
import type { CategoryWidgetData } from './types'
|
|
@@ -13,9 +14,9 @@ import type { CategoryWidgetData } from './types'
|
|
|
13
14
|
*/
|
|
14
15
|
export function createCategoryDownloadConfig(args: {
|
|
15
16
|
filename: string
|
|
16
|
-
getData: () => CategoryWidgetData
|
|
17
|
+
getData: (ctx: DownloadContext) => CategoryWidgetData
|
|
17
18
|
seriesNames?: readonly string[]
|
|
18
|
-
getCaptureEl?: () => HTMLElement | null
|
|
19
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
19
20
|
pngPixelRatio?: number
|
|
20
21
|
pngBackgroundColor?: string | null
|
|
21
22
|
}): DownloadItem[] {
|
|
@@ -33,8 +34,8 @@ export function createCategoryDownloadConfig(args: {
|
|
|
33
34
|
items.push(
|
|
34
35
|
buildCsvDownloadItem({
|
|
35
36
|
filename: args.filename,
|
|
36
|
-
getRows: () => {
|
|
37
|
-
const data = args.getData()
|
|
37
|
+
getRows: (ctx) => {
|
|
38
|
+
const data = args.getData(ctx)
|
|
38
39
|
const rows: unknown[][] = [['series', 'name', 'value']]
|
|
39
40
|
for (const [i, series] of data.entries()) {
|
|
40
41
|
const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
|
|
@@ -103,11 +103,14 @@ describe('<EchartUI>', () => {
|
|
|
103
103
|
/>,
|
|
104
104
|
)
|
|
105
105
|
expect(initSpy).toHaveBeenCalledTimes(1)
|
|
106
|
-
// 3rd arg merges the default `{ renderer: 'svg'
|
|
107
|
-
//
|
|
106
|
+
// 3rd arg merges the default `{ renderer: 'svg' }` with the caller's
|
|
107
|
+
// init — canvas wins.
|
|
108
108
|
const initOpts = initSpy.mock.calls[0]![2] as Record<string, unknown>
|
|
109
109
|
expect(initOpts.renderer).toBe('canvas')
|
|
110
|
-
|
|
110
|
+
// No `height`: zrender would store it and every later `resize()` would
|
|
111
|
+
// snap back to it, so the chart could never grow into a fullscreen
|
|
112
|
+
// dialog. Height is container-driven instead.
|
|
113
|
+
expect(initOpts.height).toBeUndefined()
|
|
111
114
|
expect(onInstance).toHaveBeenCalledWith(mockChart)
|
|
112
115
|
})
|
|
113
116
|
|
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
import { observeResize } from './shared-resize-observer'
|
|
10
10
|
import { styles } from './style'
|
|
11
11
|
|
|
12
|
+
// No `height`: a numeric height here is stored in zrender's init opts, and
|
|
13
|
+
// every later `resize()` resolves back to it — the chart would stay that tall
|
|
14
|
+
// inside a fullscreen dialog. Height comes from the container instead
|
|
15
|
+
// (`styles.root`'s `min-height` inline, `flex: 1` when the parent has one).
|
|
12
16
|
export const DEFAULT_INIT_OPTS = {
|
|
13
17
|
renderer: 'svg',
|
|
14
|
-
height: 304,
|
|
15
18
|
} satisfies echarts.EChartsInitOpts
|
|
16
19
|
|
|
17
20
|
export type EchartsEventHandler = (event: unknown) => void
|
|
@@ -36,7 +39,8 @@ export interface EchartUIProps {
|
|
|
36
39
|
/**
|
|
37
40
|
* Init options forwarded to echarts.init. Captured at mount only — to change
|
|
38
41
|
* `renderer` or `height` after mount, unmount and remount the chart.
|
|
39
|
-
* Defaults: `{ renderer: 'svg'
|
|
42
|
+
* Defaults: `{ renderer: 'svg' }` — height is container-driven, so prefer
|
|
43
|
+
* `sx={{ minHeight }}` over pinning `height` here.
|
|
40
44
|
*/
|
|
41
45
|
init?: echarts.EChartsInitOpts
|
|
42
46
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import type { SxProps, Theme } from '@mui/material'
|
|
2
3
|
import type * as echarts from 'echarts'
|
|
3
4
|
import {
|
|
4
5
|
applyTransforms,
|
|
@@ -77,6 +78,13 @@ export interface EchartProps {
|
|
|
77
78
|
onEvents?: Record<string, EchartsEventHandler>
|
|
78
79
|
init?: echarts.EChartsInitOpts
|
|
79
80
|
className?: string
|
|
81
|
+
/**
|
|
82
|
+
* Styles for the chart container — this is where its height comes from.
|
|
83
|
+
* `min-height` defaults to the card height (304px) and `flex: 1` lets the
|
|
84
|
+
* chart fill a taller parent; override either here, e.g.
|
|
85
|
+
* `sx={{ minHeight: 400 }}` for a taller card.
|
|
86
|
+
*/
|
|
87
|
+
sx?: SxProps<Theme>
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
interface EchartSlice {
|
|
@@ -126,6 +134,7 @@ export function Echart({
|
|
|
126
134
|
onEvents,
|
|
127
135
|
init,
|
|
128
136
|
className,
|
|
137
|
+
sx,
|
|
129
138
|
}: EchartProps) {
|
|
130
139
|
const id = useWidgetId()
|
|
131
140
|
const slice = useWidgetShallow(id, echartSelector)
|
|
@@ -194,6 +203,7 @@ export function Echart({
|
|
|
194
203
|
init={init}
|
|
195
204
|
onInstance={onInstance}
|
|
196
205
|
className={className}
|
|
206
|
+
sx={sx}
|
|
197
207
|
/>
|
|
198
208
|
)
|
|
199
209
|
}
|
|
@@ -3,6 +3,21 @@ import type { SxProps, Theme } from '@mui/material'
|
|
|
3
3
|
export const styles = {
|
|
4
4
|
root: {
|
|
5
5
|
width: '100%',
|
|
6
|
-
|
|
6
|
+
// `flex: 1` lets the chart fill a parent that has a definite height —
|
|
7
|
+
// the fullscreen dialog. Inline it has none (Widget.Content is an
|
|
8
|
+
// auto-height flex column), so flex-basis resolves to the content box,
|
|
9
|
+
// which is empty until ECharts measures it: at `min-height: 0` the
|
|
10
|
+
// container is 0px tall, ECharts draws nothing and the widget renders
|
|
11
|
+
// blank. 304 is not a new magic number — it's the height `init.height`
|
|
12
|
+
// used to pin, kept so cards look exactly as before. Override per widget
|
|
13
|
+
// with `<Widget.Echart sx={{ minHeight }} />`.
|
|
14
|
+
flex: 1,
|
|
15
|
+
minHeight: 304,
|
|
16
|
+
// ECharts puts its tooltip inside this box at `z-index: 9999999`. With
|
|
17
|
+
// no stacking context of its own, that number competes page-wide and
|
|
18
|
+
// beats MUI's modal layer — under `FullScreen.Slot` the card's chart
|
|
19
|
+
// mirrors the dialog's tooltip (echarts.connect) and it painted over
|
|
20
|
+
// the dialog. Isolating keeps the tooltip above its own chart only.
|
|
21
|
+
isolation: 'isolate',
|
|
7
22
|
},
|
|
8
23
|
} satisfies Record<string, SxProps<Theme>>
|
|
@@ -56,7 +56,9 @@ describe('createFormulaDownloadConfig', () => {
|
|
|
56
56
|
filename: 'f',
|
|
57
57
|
getData: () => data,
|
|
58
58
|
})
|
|
59
|
-
const handle = await items
|
|
59
|
+
const handle = await items
|
|
60
|
+
.find((i) => i.id === 'csv')!
|
|
61
|
+
.resolve({ id: null })
|
|
60
62
|
expect(handle.filename).toBe('f.csv')
|
|
61
63
|
expect(csvText).toBe(
|
|
62
64
|
'series,prefix,value,suffix,note,delta\nRev,$,42,k,q1,12\n,,10,,,',
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCsvDownloadItem,
|
|
3
3
|
buildPngDownloadItem,
|
|
4
|
+
type DownloadContext,
|
|
4
5
|
type DownloadItem,
|
|
5
6
|
} from '../actions/download'
|
|
6
7
|
import type { FormulaWidgetData } from './types'
|
|
7
8
|
|
|
8
9
|
export interface FormulaDownloadConfigArgs {
|
|
9
10
|
filename: string
|
|
10
|
-
getData: () => FormulaWidgetData
|
|
11
|
+
getData: (ctx: DownloadContext) => FormulaWidgetData
|
|
11
12
|
/**
|
|
12
13
|
* Optional getter for the widget's capture element (registered by
|
|
13
14
|
* `Widget.State`). When provided, a PNG item is prepended.
|
|
14
15
|
*/
|
|
15
|
-
getCaptureEl?: () => HTMLElement | null
|
|
16
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
16
17
|
pngPixelRatio?: number
|
|
17
18
|
pngBackgroundColor?: string | null
|
|
18
19
|
}
|
|
@@ -40,8 +41,8 @@ export function createFormulaDownloadConfig(
|
|
|
40
41
|
items.push(
|
|
41
42
|
buildCsvDownloadItem({
|
|
42
43
|
filename: args.filename,
|
|
43
|
-
getRows: () => {
|
|
44
|
-
const data = args.getData()
|
|
44
|
+
getRows: (ctx) => {
|
|
45
|
+
const data = args.getData(ctx)
|
|
45
46
|
const rows: unknown[][] = [
|
|
46
47
|
['series', 'prefix', 'value', 'suffix', 'note', 'delta'],
|
|
47
48
|
]
|
|
@@ -52,7 +52,9 @@ describe('createHistogramDownloadConfig', () => {
|
|
|
52
52
|
getData: () => data,
|
|
53
53
|
getTicks: () => ticks,
|
|
54
54
|
})
|
|
55
|
-
const handle = await items
|
|
55
|
+
const handle = await items
|
|
56
|
+
.find((i) => i.id === 'csv')!
|
|
57
|
+
.resolve({ id: null })
|
|
56
58
|
expect(handle.filename).toBe('h.csv')
|
|
57
59
|
expect(csvText).toBe(
|
|
58
60
|
'bin_low,bin_high,series_1,series_2\n0,10,1,4\n10,20,2,5\n20,30,3,6',
|
|
@@ -66,7 +68,7 @@ describe('createHistogramDownloadConfig', () => {
|
|
|
66
68
|
getTicks: () => ticks,
|
|
67
69
|
seriesNames: ['lo', 'hi'],
|
|
68
70
|
})
|
|
69
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
71
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
70
72
|
expect(csvText.startsWith('bin_low,bin_high,lo,hi')).toBe(true)
|
|
71
73
|
})
|
|
72
74
|
|
|
@@ -76,7 +78,7 @@ describe('createHistogramDownloadConfig', () => {
|
|
|
76
78
|
getData: () => data,
|
|
77
79
|
getTicks: () => [0],
|
|
78
80
|
})
|
|
79
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
81
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
80
82
|
expect(csvText).toBe('bin_low,bin_high,series_1,series_2')
|
|
81
83
|
})
|
|
82
84
|
|
|
@@ -87,7 +89,7 @@ describe('createHistogramDownloadConfig', () => {
|
|
|
87
89
|
getData: () => sparse,
|
|
88
90
|
getTicks: () => ticks,
|
|
89
91
|
})
|
|
90
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
92
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
91
93
|
expect(csvText).toContain('0,10,1,0')
|
|
92
94
|
expect(csvText).toContain('10,20,2,0')
|
|
93
95
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCsvDownloadItem,
|
|
3
3
|
buildPngDownloadItem,
|
|
4
|
+
type DownloadContext,
|
|
4
5
|
type DownloadItem,
|
|
5
6
|
} from '../actions/download'
|
|
6
7
|
import type { HistogramWidgetData } from './types'
|
|
@@ -13,10 +14,10 @@ import type { HistogramWidgetData } from './types'
|
|
|
13
14
|
*/
|
|
14
15
|
export function createHistogramDownloadConfig(args: {
|
|
15
16
|
filename: string
|
|
16
|
-
getData: () => HistogramWidgetData
|
|
17
|
+
getData: (ctx: DownloadContext) => HistogramWidgetData
|
|
17
18
|
getTicks: () => readonly number[]
|
|
18
19
|
seriesNames?: readonly string[]
|
|
19
|
-
getCaptureEl?: () => HTMLElement | null
|
|
20
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
20
21
|
pngPixelRatio?: number
|
|
21
22
|
pngBackgroundColor?: string | null
|
|
22
23
|
}): DownloadItem[] {
|
|
@@ -34,8 +35,8 @@ export function createHistogramDownloadConfig(args: {
|
|
|
34
35
|
items.push(
|
|
35
36
|
buildCsvDownloadItem({
|
|
36
37
|
filename: args.filename,
|
|
37
|
-
getRows: () => {
|
|
38
|
-
const data = args.getData()
|
|
38
|
+
getRows: (ctx) => {
|
|
39
|
+
const data = args.getData(ctx)
|
|
39
40
|
const ticks = args.getTicks()
|
|
40
41
|
const seriesCount = data.length
|
|
41
42
|
const header: unknown[] = ['bin_low', 'bin_high']
|
package/src/widgets-v2/index.ts
CHANGED
|
@@ -46,7 +46,7 @@ describe('createMarkdownDownloadConfig', () => {
|
|
|
46
46
|
filename: 'doc',
|
|
47
47
|
getData: () => ({ content: '# title\n\nbody' }),
|
|
48
48
|
})
|
|
49
|
-
const handle = await items.find((i) => i.id === 'md')!.resolve()
|
|
49
|
+
const handle = await items.find((i) => i.id === 'md')!.resolve({ id: null })
|
|
50
50
|
expect(handle.url).toBe('blob:mock')
|
|
51
51
|
expect(handle.filename).toBe('doc.md')
|
|
52
52
|
expect(mdText).toBe('# title\n\nbody')
|
|
@@ -60,7 +60,7 @@ describe('createMarkdownDownloadConfig', () => {
|
|
|
60
60
|
// missing content field — exercise the `?? ''` branch
|
|
61
61
|
getData: () => ({}) as { content: string },
|
|
62
62
|
})
|
|
63
|
-
await items.find((i) => i.id === 'md')!.resolve()
|
|
63
|
+
await items.find((i) => i.id === 'md')!.resolve({ id: null })
|
|
64
64
|
expect(mdText).toBe('')
|
|
65
65
|
})
|
|
66
66
|
})
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
buildPngDownloadItem,
|
|
5
5
|
toCsvString,
|
|
6
6
|
triggerLinkDownload,
|
|
7
|
+
type DownloadContext,
|
|
7
8
|
type DownloadItem,
|
|
8
9
|
} from '../actions/download'
|
|
9
10
|
import type { MarkdownWidgetData } from './types'
|
|
@@ -16,8 +17,8 @@ import type { MarkdownWidgetData } from './types'
|
|
|
16
17
|
*/
|
|
17
18
|
export function createMarkdownDownloadConfig(args: {
|
|
18
19
|
filename: string
|
|
19
|
-
getData: () => MarkdownWidgetData
|
|
20
|
-
getCaptureEl?: () => HTMLElement | null
|
|
20
|
+
getData: (ctx: DownloadContext) => MarkdownWidgetData
|
|
21
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
21
22
|
pngPixelRatio?: number
|
|
22
23
|
pngBackgroundColor?: string | null
|
|
23
24
|
}): DownloadItem[] {
|
|
@@ -36,8 +37,8 @@ export function createMarkdownDownloadConfig(args: {
|
|
|
36
37
|
id: DOWNLOAD_ITEM_IDS.markdown,
|
|
37
38
|
label: 'Markdown',
|
|
38
39
|
icon: <MarkdownIcon fontSize='small' />,
|
|
39
|
-
resolve: () => {
|
|
40
|
-
const data = args.getData()
|
|
40
|
+
resolve: (ctx) => {
|
|
41
|
+
const data = args.getData(ctx)
|
|
41
42
|
const blob = new Blob([data.content ?? ''], {
|
|
42
43
|
type: 'text/markdown;charset=utf-8;',
|
|
43
44
|
})
|
|
@@ -51,7 +51,9 @@ describe('createPieDownloadConfig', () => {
|
|
|
51
51
|
getCaptureEl: () => null,
|
|
52
52
|
})
|
|
53
53
|
expect(items.map((i) => i.id)).toEqual(['png', 'csv'])
|
|
54
|
-
await expect(items[0]!.resolve()).rejects.toThrow(
|
|
54
|
+
await expect(items[0]!.resolve({ id: null })).rejects.toThrow(
|
|
55
|
+
/No PNG capture element/,
|
|
56
|
+
)
|
|
55
57
|
})
|
|
56
58
|
|
|
57
59
|
it('CSV resolve uses default series_<n> when seriesNames is absent', async () => {
|
|
@@ -59,7 +61,9 @@ describe('createPieDownloadConfig', () => {
|
|
|
59
61
|
filename: 'p',
|
|
60
62
|
getData: () => multi,
|
|
61
63
|
})
|
|
62
|
-
const handle = await items
|
|
64
|
+
const handle = await items
|
|
65
|
+
.find((i) => i.id === 'csv')!
|
|
66
|
+
.resolve({ id: null })
|
|
63
67
|
expect(handle.filename).toBe('p.csv')
|
|
64
68
|
expect(csvText).toBe('series,name,value\nseries_1,a,1\nseries_2,c,3')
|
|
65
69
|
handle.revoke?.()
|
|
@@ -72,7 +76,7 @@ describe('createPieDownloadConfig', () => {
|
|
|
72
76
|
getData: () => multi,
|
|
73
77
|
seriesNames: ['alpha', 'beta'],
|
|
74
78
|
})
|
|
75
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
79
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
76
80
|
expect(csvText).toBe('series,name,value\nalpha,a,1\nbeta,c,3')
|
|
77
81
|
})
|
|
78
82
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCsvDownloadItem,
|
|
3
3
|
buildPngDownloadItem,
|
|
4
|
+
type DownloadContext,
|
|
4
5
|
type DownloadItem,
|
|
5
6
|
} from '../actions/download'
|
|
6
7
|
import type { PieWidgetData } from './types'
|
|
@@ -14,9 +15,9 @@ import type { PieWidgetData } from './types'
|
|
|
14
15
|
*/
|
|
15
16
|
export function createPieDownloadConfig(args: {
|
|
16
17
|
filename: string
|
|
17
|
-
getData: () => PieWidgetData
|
|
18
|
+
getData: (ctx: DownloadContext) => PieWidgetData
|
|
18
19
|
seriesNames?: readonly string[]
|
|
19
|
-
getCaptureEl?: () => HTMLElement | null
|
|
20
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
20
21
|
pngPixelRatio?: number
|
|
21
22
|
pngBackgroundColor?: string | null
|
|
22
23
|
}): DownloadItem[] {
|
|
@@ -34,8 +35,8 @@ export function createPieDownloadConfig(args: {
|
|
|
34
35
|
items.push(
|
|
35
36
|
buildCsvDownloadItem({
|
|
36
37
|
filename: args.filename,
|
|
37
|
-
getRows: () => {
|
|
38
|
-
const data = args.getData()
|
|
38
|
+
getRows: (ctx) => {
|
|
39
|
+
const data = args.getData(ctx)
|
|
39
40
|
const rows: unknown[][] = [['series', 'name', 'value']]
|
|
40
41
|
for (const [i, series] of data.entries()) {
|
|
41
42
|
const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
|
|
@@ -84,6 +84,14 @@ describe('pieOptions (structural)', () => {
|
|
|
84
84
|
expect(out.legend?.show).toBe(true)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
+
it('anchors the legend bottom-left, like every other widget', () => {
|
|
88
|
+
const out = pieOptions({ theme }) as {
|
|
89
|
+
legend?: { left?: unknown; bottom?: unknown }
|
|
90
|
+
}
|
|
91
|
+
expect(out.legend?.left).toBe(0)
|
|
92
|
+
expect(out.legend?.bottom).toBe(0)
|
|
93
|
+
})
|
|
94
|
+
|
|
87
95
|
it('disables legend click-to-toggle', () => {
|
|
88
96
|
const out = pieOptions({ theme }) as {
|
|
89
97
|
legend?: { selectedMode?: boolean }
|
|
@@ -45,14 +45,9 @@ export function pieOptions({
|
|
|
45
45
|
return {
|
|
46
46
|
legend: {
|
|
47
47
|
...buildLegendConfig({ hasLegend: true, labelFormatter }),
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// `'plain'` so long category lists wrap to multiple rows instead
|
|
52
|
-
// of clipping the last visible item against the chart container.
|
|
53
|
-
// The wider `itemGap` gives chips breathing room in the typical
|
|
54
|
-
// 3–6 category case.
|
|
55
|
-
left: 'center',
|
|
48
|
+
// Bottom-left, same as every other widget — `buildLegendConfig`'s
|
|
49
|
+
// `left: 0` is kept deliberately. The wider `itemGap` gives chips
|
|
50
|
+
// breathing room in the typical 3–6 category case.
|
|
56
51
|
type: 'scroll',
|
|
57
52
|
itemGap: 16,
|
|
58
53
|
// The legend is presentational only here — the pie has its own
|
|
@@ -52,7 +52,9 @@ describe('createScatterplotDownloadConfig', () => {
|
|
|
52
52
|
filename: 's',
|
|
53
53
|
getData: () => data,
|
|
54
54
|
})
|
|
55
|
-
const handle = await items
|
|
55
|
+
const handle = await items
|
|
56
|
+
.find((i) => i.id === 'csv')!
|
|
57
|
+
.resolve({ id: null })
|
|
56
58
|
expect(handle.filename).toBe('s.csv')
|
|
57
59
|
expect(csvText).toBe(
|
|
58
60
|
'series,x,y\nseries_1,1,10\nseries_1,2,20\nseries_2,3,30',
|
|
@@ -65,7 +67,7 @@ describe('createScatterplotDownloadConfig', () => {
|
|
|
65
67
|
getData: () => data,
|
|
66
68
|
seriesNames: ['A', 'B'],
|
|
67
69
|
})
|
|
68
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
70
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
69
71
|
expect(csvText.split('\n')[1]).toBe('A,1,10')
|
|
70
72
|
})
|
|
71
73
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCsvDownloadItem,
|
|
3
3
|
buildPngDownloadItem,
|
|
4
|
+
type DownloadContext,
|
|
4
5
|
type DownloadItem,
|
|
5
6
|
} from '../actions/download'
|
|
6
7
|
import type { ScatterplotWidgetData } from './types'
|
|
@@ -13,9 +14,9 @@ import type { ScatterplotWidgetData } from './types'
|
|
|
13
14
|
*/
|
|
14
15
|
export function createScatterplotDownloadConfig(args: {
|
|
15
16
|
filename: string
|
|
16
|
-
getData: () => ScatterplotWidgetData
|
|
17
|
+
getData: (ctx: DownloadContext) => ScatterplotWidgetData
|
|
17
18
|
seriesNames?: readonly string[]
|
|
18
|
-
getCaptureEl?: () => HTMLElement | null
|
|
19
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
19
20
|
pngPixelRatio?: number
|
|
20
21
|
pngBackgroundColor?: string | null
|
|
21
22
|
}): DownloadItem[] {
|
|
@@ -33,8 +34,8 @@ export function createScatterplotDownloadConfig(args: {
|
|
|
33
34
|
items.push(
|
|
34
35
|
buildCsvDownloadItem({
|
|
35
36
|
filename: args.filename,
|
|
36
|
-
getRows: () => {
|
|
37
|
-
const data = args.getData()
|
|
37
|
+
getRows: (ctx) => {
|
|
38
|
+
const data = args.getData(ctx)
|
|
38
39
|
const rows: unknown[][] = [['series', 'x', 'y']]
|
|
39
40
|
for (const [i, series] of data.entries()) {
|
|
40
41
|
const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
|
|
@@ -55,7 +55,9 @@ describe('createSpreadDownloadConfig', () => {
|
|
|
55
55
|
filename: 's',
|
|
56
56
|
getData: () => data,
|
|
57
57
|
})
|
|
58
|
-
const handle = await items
|
|
58
|
+
const handle = await items
|
|
59
|
+
.find((i) => i.id === 'csv')!
|
|
60
|
+
.resolve({ id: null })
|
|
59
61
|
expect(handle.filename).toBe('s.csv')
|
|
60
62
|
expect(csvText).toBe(
|
|
61
63
|
'series,prefix,min,max,suffix,note\nRevenue,$,1,9,k,q1\n,,0,10,,',
|