@carto/ps-react-ui 4.22.0 → 4.24.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-B1ss030_.js +1167 -0
- package/dist/actions-B1ss030_.js.map +1 -0
- package/dist/category-Ca5XDx0N.js +839 -0
- package/dist/category-Ca5XDx0N.js.map +1 -0
- package/dist/{download-C7he8mSh.js → download--wRRnf19.js} +81 -80
- package/dist/download--wRRnf19.js.map +1 -0
- package/dist/{echart-BnJYXH7H.js → echart-CgOQw9XB.js} +3 -2
- package/dist/echart-CgOQw9XB.js.map +1 -0
- package/dist/{formula-Cqj84ZGX.js → formula-DRuMIVo8.js} +6 -6
- package/dist/formula-DRuMIVo8.js.map +1 -0
- package/dist/legend.js +34 -21
- package/dist/legend.js.map +1 -1
- package/dist/{markdown-CV_KP-at.js → markdown-C3OX9jyy.js} +6 -6
- package/dist/markdown-C3OX9jyy.js.map +1 -0
- package/dist/{spread-C8T12Nfh.js → spread-BT07rCJw.js} +7 -7
- package/dist/spread-BT07rCJw.js.map +1 -0
- package/dist/{table-CNQijNNB.js → table-CRG9eOs9.js} +3 -3
- package/dist/table-CRG9eOs9.js.map +1 -0
- package/dist/types/legend/stores/types.d.ts +4 -2
- 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 +20 -13
- package/dist/types/widgets-v2/actions/index.d.ts +1 -1
- package/dist/types/widgets-v2/actions/searcher/filter.d.ts +3 -2
- 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/style.d.ts +1 -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 +1 -1
- 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/types/widgets-v2/wrapper/style.d.ts +31 -2
- package/dist/types/widgets-v2/wrapper/widget-actions.d.ts +2 -2
- package/dist/types/widgets-v2/wrapper/widget-wrapper.d.ts +2 -2
- 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 -10
- 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 +364 -376
- package/dist/widgets-v2.js.map +1 -1
- package/package.json +3 -3
- package/src/legend/components/legend-item/legend-item-ui.test.tsx +22 -1
- package/src/legend/components/legend-ramp/legend-ramp-ui.tsx +22 -6
- package/src/legend/stores/types.ts +6 -11
- 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 +39 -41
- package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +211 -96
- package/src/widgets-v2/actions/index.ts +1 -0
- package/src/widgets-v2/actions/searcher/filter.test.ts +28 -0
- package/src/widgets-v2/actions/searcher/filter.ts +22 -4
- package/src/widgets-v2/actions/searcher/searcher-toggle.tsx +9 -4
- package/src/widgets-v2/actions/searcher/searcher.test.tsx +22 -0
- package/src/widgets-v2/actions/searcher/searcher.tsx +17 -2
- 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 +20 -9
- 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/style.ts +6 -0
- 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/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/src/widgets-v2/wrapper/style.ts +38 -2
- package/src/widgets-v2/wrapper/widget-actions.tsx +2 -2
- package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +32 -2
- package/src/widgets-v2/wrapper/widget-wrapper.tsx +58 -32
- package/dist/actions-iN1jwyxA.js +0 -1024
- package/dist/actions-iN1jwyxA.js.map +0 -1
- package/dist/category-DZt6yiST.js +0 -826
- package/dist/category-DZt6yiST.js.map +0 -1
- package/dist/download-C7he8mSh.js.map +0 -1
- package/dist/echart-BnJYXH7H.js.map +0 -1
- package/dist/formula-Cqj84ZGX.js.map +0 -1
- package/dist/markdown-CV_KP-at.js.map +0 -1
- package/dist/spread-C8T12Nfh.js.map +0 -1
- package/dist/table-CNQijNNB.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 {
|
|
@@ -167,6 +175,8 @@ export function CategoryUI({
|
|
|
167
175
|
maxOverride,
|
|
168
176
|
size = 'small',
|
|
169
177
|
stacked = false,
|
|
178
|
+
hiddenSeries: hiddenSeriesProp,
|
|
179
|
+
onHiddenSeriesChange,
|
|
170
180
|
}: CategoryUIProps) {
|
|
171
181
|
const theme = useTheme()
|
|
172
182
|
const fmt = formatter ?? ((n: number) => String(n))
|
|
@@ -286,9 +296,10 @@ export function CategoryUI({
|
|
|
286
296
|
// selection, unrelated). Never allowed to reach "every series hidden":
|
|
287
297
|
// toggling the last visible one instead resets to "all visible" rather
|
|
288
298
|
// than leaving the widget blank.
|
|
289
|
-
const [
|
|
299
|
+
const [localHidden, setLocalHidden] = useState<ReadonlySet<number>>(
|
|
290
300
|
() => new Set(),
|
|
291
301
|
)
|
|
302
|
+
const hiddenSeries = hiddenSeriesProp ?? localHidden
|
|
292
303
|
// A reload with a different series breakdown invalidates hidden indices —
|
|
293
304
|
// index 2 may now point at an entirely different series. Reset rather
|
|
294
305
|
// than keep hiding whatever now occupies a stale index. Adjusted directly
|
|
@@ -303,7 +314,7 @@ export function CategoryUI({
|
|
|
303
314
|
const [prevSeriesKey, setPrevSeriesKey] = useState(seriesKey)
|
|
304
315
|
if (seriesKey !== prevSeriesKey) {
|
|
305
316
|
setPrevSeriesKey(seriesKey)
|
|
306
|
-
|
|
317
|
+
setLocalHidden(new Set())
|
|
307
318
|
}
|
|
308
319
|
// Real number of data series — NOT `series?.length`, which is legend
|
|
309
320
|
// metadata and may legitimately be shorter than the actual series count
|
|
@@ -313,14 +324,14 @@ export function CategoryUI({
|
|
|
313
324
|
const seriesCount = Math.max(data.length, 1)
|
|
314
325
|
const toggleSeries = useCallback(
|
|
315
326
|
(index: number) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
const next = new Set(hiddenSeries)
|
|
328
|
+
if (next.has(index)) next.delete(index)
|
|
329
|
+
else next.add(index)
|
|
330
|
+
;(onHiddenSeriesChange ?? setLocalHidden)(
|
|
331
|
+
next.size >= seriesCount ? new Set() : next,
|
|
332
|
+
)
|
|
322
333
|
},
|
|
323
|
-
[seriesCount],
|
|
334
|
+
[hiddenSeries, onHiddenSeriesChange, seriesCount],
|
|
324
335
|
)
|
|
325
336
|
|
|
326
337
|
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}`
|
|
@@ -13,5 +13,11 @@ export const styles = {
|
|
|
13
13
|
// with `<Widget.Echart sx={{ minHeight }} />`.
|
|
14
14
|
flex: 1,
|
|
15
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',
|
|
16
22
|
},
|
|
17
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}`
|
|
@@ -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,,',
|
|
@@ -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 { SpreadWidgetData } from './types'
|
|
@@ -13,8 +14,8 @@ import type { SpreadWidgetData } from './types'
|
|
|
13
14
|
*/
|
|
14
15
|
export function createSpreadDownloadConfig(args: {
|
|
15
16
|
filename: string
|
|
16
|
-
getData: () => SpreadWidgetData
|
|
17
|
-
getCaptureEl?: () => HTMLElement | null
|
|
17
|
+
getData: (ctx: DownloadContext) => SpreadWidgetData
|
|
18
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
18
19
|
pngPixelRatio?: number
|
|
19
20
|
pngBackgroundColor?: string | null
|
|
20
21
|
}): DownloadItem[] {
|
|
@@ -32,8 +33,8 @@ export function createSpreadDownloadConfig(args: {
|
|
|
32
33
|
items.push(
|
|
33
34
|
buildCsvDownloadItem({
|
|
34
35
|
filename: args.filename,
|
|
35
|
-
getRows: () => {
|
|
36
|
-
const data = args.getData()
|
|
36
|
+
getRows: (ctx) => {
|
|
37
|
+
const data = args.getData(ctx)
|
|
37
38
|
const rows: unknown[][] = [
|
|
38
39
|
['series', 'prefix', 'min', 'max', 'suffix', 'note'],
|
|
39
40
|
]
|
|
@@ -56,7 +56,9 @@ describe('createTableDownloadConfig', () => {
|
|
|
56
56
|
getData: () => data,
|
|
57
57
|
columns,
|
|
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('t.csv')
|
|
61
63
|
expect(csvText).toBe('Name,Score\nAlpha,10\nBeta,20')
|
|
62
64
|
})
|
|
@@ -67,7 +69,7 @@ describe('createTableDownloadConfig', () => {
|
|
|
67
69
|
getData: () => [{ id: 1, name: '=HYPERLINK("x")', score: 1 }],
|
|
68
70
|
columns,
|
|
69
71
|
})
|
|
70
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
72
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
71
73
|
expect(csvText).toBe('Name,Score\n"\'=HYPERLINK(""x"")",1')
|
|
72
74
|
})
|
|
73
75
|
|
|
@@ -79,7 +81,7 @@ describe('createTableDownloadConfig', () => {
|
|
|
79
81
|
columns,
|
|
80
82
|
})
|
|
81
83
|
snapshot = [{ id: 9, name: 'Gamma', score: 99 }]
|
|
82
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
84
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
83
85
|
expect(csvText).toBe('Name,Score\nGamma,99')
|
|
84
86
|
})
|
|
85
87
|
})
|
|
@@ -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 { tableDataToCsv } from './helpers'
|
|
@@ -16,9 +17,9 @@ import type { TableColumn, TableWidgetData } from './types'
|
|
|
16
17
|
*/
|
|
17
18
|
export function createTableDownloadConfig(opts: {
|
|
18
19
|
filename: string
|
|
19
|
-
getData: () => TableWidgetData
|
|
20
|
+
getData: (ctx: DownloadContext) => TableWidgetData
|
|
20
21
|
columns: readonly TableColumn[]
|
|
21
|
-
getCaptureEl?: () => HTMLElement | null
|
|
22
|
+
getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null
|
|
22
23
|
pngPixelRatio?: number
|
|
23
24
|
pngBackgroundColor?: string | null
|
|
24
25
|
}): DownloadItem[] {
|
|
@@ -36,7 +37,7 @@ export function createTableDownloadConfig(opts: {
|
|
|
36
37
|
items.push(
|
|
37
38
|
buildCsvDownloadItem({
|
|
38
39
|
filename: opts.filename,
|
|
39
|
-
getCsv: () => tableDataToCsv(opts.getData(), opts.columns),
|
|
40
|
+
getCsv: (ctx) => tableDataToCsv(opts.getData(ctx), opts.columns),
|
|
40
41
|
}),
|
|
41
42
|
)
|
|
42
43
|
return items
|
|
@@ -53,7 +53,9 @@ describe('createTimeseriesDownloadConfig', () => {
|
|
|
53
53
|
filename: 't',
|
|
54
54
|
getData: () => data,
|
|
55
55
|
})
|
|
56
|
-
const handle = await items
|
|
56
|
+
const handle = await items
|
|
57
|
+
.find((i) => i.id === 'csv')!
|
|
58
|
+
.resolve({ id: null })
|
|
57
59
|
expect(handle.filename).toBe('t.csv')
|
|
58
60
|
expect(csvText).toBe(
|
|
59
61
|
'time,series_1,series_2\n' +
|
|
@@ -69,7 +71,7 @@ describe('createTimeseriesDownloadConfig', () => {
|
|
|
69
71
|
filename: 't',
|
|
70
72
|
getData: () => data,
|
|
71
73
|
})
|
|
72
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
74
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
73
75
|
expect(csvText).toContain('2024-01-01T00:00:00.000Z,7')
|
|
74
76
|
})
|
|
75
77
|
|
|
@@ -79,7 +81,7 @@ describe('createTimeseriesDownloadConfig', () => {
|
|
|
79
81
|
filename: 't',
|
|
80
82
|
getData: () => data,
|
|
81
83
|
})
|
|
82
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
84
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
83
85
|
expect(csvText).toContain('2024-Q1,1')
|
|
84
86
|
})
|
|
85
87
|
|
|
@@ -89,7 +91,7 @@ describe('createTimeseriesDownloadConfig', () => {
|
|
|
89
91
|
getData: () => [[{ name: '2024', value: 1 }]],
|
|
90
92
|
seriesNames: ['revenue'],
|
|
91
93
|
})
|
|
92
|
-
await items.find((i) => i.id === 'csv')!.resolve()
|
|
94
|
+
await items.find((i) => i.id === 'csv')!.resolve({ id: null })
|
|
93
95
|
expect(csvText.split('\n')[0]).toBe('time,revenue')
|
|
94
96
|
})
|
|
95
97
|
})
|
|
@@ -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 { TimeseriesWidgetData } from './types'
|
|
@@ -14,9 +15,9 @@ import type { TimeseriesWidgetData } from './types'
|
|
|
14
15
|
*/
|
|
15
16
|
export function createTimeseriesDownloadConfig(args: {
|
|
16
17
|
filename: string
|
|
17
|
-
getData: () => TimeseriesWidgetData
|
|
18
|
+
getData: (ctx: DownloadContext) => TimeseriesWidgetData
|
|
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 createTimeseriesDownloadConfig(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 seriesCount = data.length
|
|
40
41
|
|
|
41
42
|
// Collect every unique time, preserving insertion order.
|
|
@@ -56,7 +56,7 @@ export interface ToolboxProps {
|
|
|
56
56
|
* Smart overflow toolbar with **single-mount** items. Children mount once
|
|
57
57
|
* into a stable, hidden host element and are *physically reparented* via
|
|
58
58
|
* `appendChild` between the inline preview area and the overflow `<Paper>`
|
|
59
|
-
* as the popper opens / closes
|
|
59
|
+
* as the popper opens / closes.
|
|
60
60
|
* Items with no inline space when closed are kept mounted but visually
|
|
61
61
|
* hidden via `display: none`.
|
|
62
62
|
*
|