@carto/ps-react-ui 6.0.0 → 6.1.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 (135) hide show
  1. package/dist/actions-BWlS5tpP.js +1150 -0
  2. package/dist/actions-BWlS5tpP.js.map +1 -0
  3. package/dist/category-alzjKzvQ.js +839 -0
  4. package/dist/category-alzjKzvQ.js.map +1 -0
  5. package/dist/{download-Vlzn2jzb.js → download-B4sykOFm.js} +94 -83
  6. package/dist/download-B4sykOFm.js.map +1 -0
  7. package/dist/{echart-DjcbtKgi.js → echart-CV8F50gn.js} +6 -5
  8. package/dist/echart-CV8F50gn.js.map +1 -0
  9. package/dist/{formula-CdHWtepq.js → formula-DJS-JLpk.js} +10 -16
  10. package/dist/{formula-CdHWtepq.js.map → formula-DJS-JLpk.js.map} +1 -1
  11. package/dist/legend.js +34 -21
  12. package/dist/legend.js.map +1 -1
  13. package/dist/{markdown-CY-67NRE.js → markdown-Bn2nKCbn.js} +12 -18
  14. package/dist/{markdown-CY-67NRE.js.map → markdown-Bn2nKCbn.js.map} +1 -1
  15. package/dist/{range-DHQSsl2_.js → range-BpU2zBmq.js} +2 -2
  16. package/dist/{range-DHQSsl2_.js.map → range-BpU2zBmq.js.map} +1 -1
  17. package/dist/{spread-DuM5oTI1.js → spread-CQwFIwuz.js} +10 -16
  18. package/dist/{spread-DuM5oTI1.js.map → spread-CQwFIwuz.js.map} +1 -1
  19. package/dist/{stores-B8CHbgw-.js → stores-DcBOZlU3.js} +30 -24
  20. package/dist/stores-DcBOZlU3.js.map +1 -0
  21. package/dist/{table-BE4uO9pp.js → table-DEdW_fDj.js} +7 -13
  22. package/dist/{table-BE4uO9pp.js.map → table-DEdW_fDj.js.map} +1 -1
  23. package/dist/types/legend/stores/types.d.ts +4 -2
  24. package/dist/types/widgets/actions/download/csv-item.d.ts +3 -3
  25. package/dist/types/widgets/actions/download/index.d.ts +2 -1
  26. package/dist/types/widgets/actions/download/png-item.d.ts +2 -2
  27. package/dist/types/widgets/actions/download/types.d.ts +12 -1
  28. package/dist/types/widgets/actions/download/widget-download.d.ts +26 -0
  29. package/dist/types/widgets/actions/fullscreen/fullscreen.d.ts +20 -13
  30. package/dist/types/widgets/actions/index.d.ts +1 -1
  31. package/dist/types/widgets/actions/searcher/filter.d.ts +3 -2
  32. package/dist/types/widgets/actions/searcher/searcher-toggle.d.ts +3 -0
  33. package/dist/types/widgets/bar/download.d.ts +6 -20
  34. package/dist/types/widgets/category/category-ui.d.ts +9 -1
  35. package/dist/types/widgets/category/download.d.ts +5 -10
  36. package/dist/types/widgets/category/helpers.d.ts +10 -0
  37. package/dist/types/widgets/echart/style.d.ts +1 -0
  38. package/dist/types/widgets/formula/download.d.ts +5 -15
  39. package/dist/types/widgets/histogram/download.d.ts +3 -8
  40. package/dist/types/widgets/index.d.ts +1 -1
  41. package/dist/types/widgets/markdown/download.d.ts +5 -11
  42. package/dist/types/widgets/pie/download.d.ts +3 -8
  43. package/dist/types/widgets/scatterplot/download.d.ts +6 -11
  44. package/dist/types/widgets/spread/download.d.ts +6 -12
  45. package/dist/types/widgets/stores/index.d.ts +1 -1
  46. package/dist/types/widgets/stores/widget-store-registry.d.ts +14 -0
  47. package/dist/types/widgets/table/download.d.ts +7 -13
  48. package/dist/types/widgets/timeseries/download.d.ts +7 -12
  49. package/dist/types/widgets/toolbox/toolbox.d.ts +1 -1
  50. package/dist/{utils-BStJWhZN.js → utils-C-eWwOhE.js} +2 -2
  51. package/dist/{utils-BStJWhZN.js.map → utils-C-eWwOhE.js.map} +1 -1
  52. package/dist/widgets/actions.js +4 -4
  53. package/dist/widgets/bar.js +11 -17
  54. package/dist/widgets/bar.js.map +1 -1
  55. package/dist/widgets/category.js +1 -1
  56. package/dist/widgets/echart.js +1 -1
  57. package/dist/widgets/formula.js +1 -1
  58. package/dist/widgets/histogram.js +14 -20
  59. package/dist/widgets/histogram.js.map +1 -1
  60. package/dist/widgets/markdown.js +1 -1
  61. package/dist/widgets/pie.js +13 -19
  62. package/dist/widgets/pie.js.map +1 -1
  63. package/dist/widgets/range.js +1 -1
  64. package/dist/widgets/scatterplot.js +13 -19
  65. package/dist/widgets/scatterplot.js.map +1 -1
  66. package/dist/widgets/spread.js +1 -1
  67. package/dist/widgets/stores.js +2 -2
  68. package/dist/widgets/table.js +1 -1
  69. package/dist/widgets/timeseries.js +16 -22
  70. package/dist/widgets/timeseries.js.map +1 -1
  71. package/dist/widgets/utils.js +1 -1
  72. package/dist/widgets.js +274 -333
  73. package/dist/widgets.js.map +1 -1
  74. package/dist/{zoom-toggle-D_VuA01l.js → zoom-toggle-MmLjMTEw.js} +2 -2
  75. package/dist/{zoom-toggle-D_VuA01l.js.map → zoom-toggle-MmLjMTEw.js.map} +1 -1
  76. package/package.json +1 -1
  77. package/src/legend/components/legend-item/legend-item-ui.test.tsx +22 -1
  78. package/src/legend/components/legend-ramp/legend-ramp-ui.tsx +22 -6
  79. package/src/legend/stores/types.ts +4 -2
  80. package/src/widgets/actions/download/csv-item.test.tsx +3 -3
  81. package/src/widgets/actions/download/csv-item.tsx +6 -6
  82. package/src/widgets/actions/download/download.tsx +5 -3
  83. package/src/widgets/actions/download/index.ts +2 -1
  84. package/src/widgets/actions/download/png-item.test.tsx +5 -3
  85. package/src/widgets/actions/download/png-item.tsx +4 -4
  86. package/src/widgets/actions/download/types.ts +15 -1
  87. package/src/widgets/actions/download/widget-download.test.ts +30 -0
  88. package/src/widgets/actions/download/widget-download.ts +41 -0
  89. package/src/widgets/actions/fullscreen/fullscreen-store.test.tsx +155 -0
  90. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +39 -41
  91. package/src/widgets/actions/fullscreen/fullscreen.tsx +198 -96
  92. package/src/widgets/actions/index.ts +3 -0
  93. package/src/widgets/actions/searcher/filter.test.ts +28 -0
  94. package/src/widgets/actions/searcher/filter.ts +22 -4
  95. package/src/widgets/actions/searcher/searcher-toggle.tsx +16 -8
  96. package/src/widgets/actions/searcher/searcher.test.tsx +22 -0
  97. package/src/widgets/actions/searcher/searcher.tsx +17 -7
  98. package/src/widgets/bar/download.test.tsx +5 -3
  99. package/src/widgets/bar/download.tsx +11 -35
  100. package/src/widgets/category/category-ui.tsx +26 -14
  101. package/src/widgets/category/category.tsx +37 -2
  102. package/src/widgets/category/components/category-legend.tsx +1 -2
  103. package/src/widgets/category/download.test.ts +4 -2
  104. package/src/widgets/category/download.ts +14 -27
  105. package/src/widgets/category/helpers.test.ts +17 -0
  106. package/src/widgets/category/helpers.ts +16 -0
  107. package/src/widgets/echart/style.ts +6 -0
  108. package/src/widgets/formula/download.test.tsx +3 -1
  109. package/src/widgets/formula/download.tsx +10 -30
  110. package/src/widgets/histogram/download.test.ts +6 -4
  111. package/src/widgets/histogram/download.ts +13 -26
  112. package/src/widgets/index.ts +1 -0
  113. package/src/widgets/markdown/download.test.ts +2 -2
  114. package/src/widgets/markdown/download.tsx +11 -26
  115. package/src/widgets/pie/download.test.ts +7 -3
  116. package/src/widgets/pie/download.ts +12 -25
  117. package/src/widgets/scatterplot/download.test.ts +4 -2
  118. package/src/widgets/scatterplot/download.ts +15 -28
  119. package/src/widgets/spread/download.test.ts +3 -1
  120. package/src/widgets/spread/download.ts +13 -27
  121. package/src/widgets/stores/index.ts +2 -0
  122. package/src/widgets/stores/widget-store-registry.test.ts +25 -0
  123. package/src/widgets/stores/widget-store-registry.ts +22 -0
  124. package/src/widgets/table/download.test.ts +5 -3
  125. package/src/widgets/table/download.ts +15 -29
  126. package/src/widgets/timeseries/download.test.ts +6 -4
  127. package/src/widgets/timeseries/download.ts +16 -29
  128. package/src/widgets/toolbox/toolbox.tsx +1 -1
  129. package/dist/actions-KsX1xdDM.js +0 -1024
  130. package/dist/actions-KsX1xdDM.js.map +0 -1
  131. package/dist/category-CsdDqDzo.js +0 -833
  132. package/dist/category-CsdDqDzo.js.map +0 -1
  133. package/dist/download-Vlzn2jzb.js.map +0 -1
  134. package/dist/echart-DjcbtKgi.js.map +0 -1
  135. package/dist/stores-B8CHbgw-.js.map +0 -1
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  buildCsvDownloadItem,
3
- buildPngDownloadItem,
3
+ withPngItem,
4
4
  type DownloadItem,
5
+ type WidgetDownloadArgs,
5
6
  } from '../actions/download'
6
7
  import type { PieWidgetData } from './types'
7
8
 
@@ -9,33 +10,20 @@ import type { PieWidgetData } from './types'
9
10
  * Download menu items for the Pie widget. Always includes a CSV item with
10
11
  * `series, name, value` columns (one row per slice; series labels come from
11
12
  * `seriesNames` if provided, else `series_<n>`). When `getCaptureEl` is
12
- * supplied, prepends a PNG item that rasterises the captured element via
13
+ * given, prepends a PNG item that rasterises the captured element via
13
14
  * `html2canvas`.
14
15
  */
15
- export function createPieDownloadConfig(args: {
16
- filename: string
17
- getData: () => PieWidgetData
18
- seriesNames?: readonly string[]
19
- getCaptureEl?: () => HTMLElement | null
20
- pngPixelRatio?: number
21
- pngBackgroundColor?: string | null
22
- }): DownloadItem[] {
23
- const items: DownloadItem[] = []
24
- if (args.getCaptureEl) {
25
- items.push(
26
- buildPngDownloadItem({
27
- filename: args.filename,
28
- getCaptureEl: args.getCaptureEl,
29
- pixelRatio: args.pngPixelRatio,
30
- backgroundColor: args.pngBackgroundColor,
31
- }),
32
- )
33
- }
34
- items.push(
16
+ export function createPieDownloadConfig(
17
+ args: WidgetDownloadArgs<PieWidgetData> & {
18
+ seriesNames?: readonly string[]
19
+ },
20
+ ): DownloadItem[] {
21
+ return withPngItem(
22
+ args,
35
23
  buildCsvDownloadItem({
36
24
  filename: args.filename,
37
- getRows: () => {
38
- const data = args.getData()
25
+ getRows: (ctx) => {
26
+ const data = args.getData(ctx)
39
27
  const rows: unknown[][] = [['series', 'name', 'value']]
40
28
  for (const [i, series] of data.entries()) {
41
29
  const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
@@ -47,5 +35,4 @@ export function createPieDownloadConfig(args: {
47
35
  },
48
36
  }),
49
37
  )
50
- return items
51
38
  }
@@ -52,7 +52,9 @@ describe('createScatterplotDownloadConfig', () => {
52
52
  filename: 's',
53
53
  getData: () => data,
54
54
  })
55
- const handle = await items.find((i) => i.id === 'csv')!.resolve()
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,40 +1,28 @@
1
1
  import {
2
2
  buildCsvDownloadItem,
3
- buildPngDownloadItem,
3
+ withPngItem,
4
4
  type DownloadItem,
5
+ type WidgetDownloadArgs,
5
6
  } from '../actions/download'
6
7
  import type { ScatterplotWidgetData } from './types'
7
8
 
8
9
  /**
9
- * Download menu items for the Scatterplot widget. Always includes a CSV
10
- * item with `series, x, y` columns (one row per point). When
11
- * `getCaptureEl` is supplied, prepends a PNG item that rasterises the
12
- * captured element via `html2canvas`.
10
+ * Download menu items for the Scatterplot widget. Always includes a CSV item
11
+ * with `series, x, y` columns (one row per point). When `getCaptureEl` is
12
+ * given, prepends a PNG item that rasterises the captured element via
13
+ * `html2canvas`.
13
14
  */
14
- export function createScatterplotDownloadConfig(args: {
15
- filename: string
16
- getData: () => ScatterplotWidgetData
17
- seriesNames?: readonly string[]
18
- getCaptureEl?: () => HTMLElement | null
19
- pngPixelRatio?: number
20
- pngBackgroundColor?: string | null
21
- }): DownloadItem[] {
22
- const items: DownloadItem[] = []
23
- if (args.getCaptureEl) {
24
- items.push(
25
- buildPngDownloadItem({
26
- filename: args.filename,
27
- getCaptureEl: args.getCaptureEl,
28
- pixelRatio: args.pngPixelRatio,
29
- backgroundColor: args.pngBackgroundColor,
30
- }),
31
- )
32
- }
33
- items.push(
15
+ export function createScatterplotDownloadConfig(
16
+ args: WidgetDownloadArgs<ScatterplotWidgetData> & {
17
+ seriesNames?: readonly string[]
18
+ },
19
+ ): DownloadItem[] {
20
+ return withPngItem(
21
+ args,
34
22
  buildCsvDownloadItem({
35
23
  filename: args.filename,
36
- getRows: () => {
37
- const data = args.getData()
24
+ getRows: (ctx) => {
25
+ const data = args.getData(ctx)
38
26
  const rows: unknown[][] = [['series', 'x', 'y']]
39
27
  for (const [i, series] of data.entries()) {
40
28
  const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
@@ -46,5 +34,4 @@ export function createScatterplotDownloadConfig(args: {
46
34
  },
47
35
  }),
48
36
  )
49
- return items
50
37
  }
@@ -55,7 +55,9 @@ describe('createSpreadDownloadConfig', () => {
55
55
  filename: 's',
56
56
  getData: () => data,
57
57
  })
58
- const handle = await items.find((i) => i.id === 'csv')!.resolve()
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,39 +1,26 @@
1
1
  import {
2
2
  buildCsvDownloadItem,
3
- buildPngDownloadItem,
3
+ withPngItem,
4
4
  type DownloadItem,
5
+ type WidgetDownloadArgs,
5
6
  } from '../actions/download'
6
7
  import type { SpreadWidgetData } from './types'
7
8
 
8
9
  /**
9
- * Download menu items for the Spread widget. Always includes a CSV item
10
- * with `series, prefix, min, max, suffix, note` columns (one row per
11
- * entry). When `getCaptureEl` is supplied, prepends a PNG item that
12
- * rasterises the captured element via `html2canvas`.
10
+ * Download menu items for the Spread widget. Always includes a CSV item with
11
+ * `series, prefix, min, max, suffix, note` columns (one row per entry). When
12
+ * `getCaptureEl` is given, prepends a PNG item that rasterises the captured
13
+ * element via `html2canvas`.
13
14
  */
14
- export function createSpreadDownloadConfig(args: {
15
- filename: string
16
- getData: () => SpreadWidgetData
17
- getCaptureEl?: () => HTMLElement | null
18
- pngPixelRatio?: number
19
- pngBackgroundColor?: string | null
20
- }): DownloadItem[] {
21
- const items: DownloadItem[] = []
22
- if (args.getCaptureEl) {
23
- items.push(
24
- buildPngDownloadItem({
25
- filename: args.filename,
26
- getCaptureEl: args.getCaptureEl,
27
- pixelRatio: args.pngPixelRatio,
28
- backgroundColor: args.pngBackgroundColor,
29
- }),
30
- )
31
- }
32
- items.push(
15
+ export function createSpreadDownloadConfig(
16
+ args: WidgetDownloadArgs<SpreadWidgetData>,
17
+ ): DownloadItem[] {
18
+ return withPngItem(
19
+ args,
33
20
  buildCsvDownloadItem({
34
21
  filename: args.filename,
35
- getRows: () => {
36
- const data = args.getData()
22
+ getRows: (ctx) => {
23
+ const data = args.getData(ctx)
37
24
  const rows: unknown[][] = [
38
25
  ['series', 'prefix', 'min', 'max', 'suffix', 'note'],
39
26
  ]
@@ -51,5 +38,4 @@ export function createSpreadDownloadConfig(args: {
51
38
  },
52
39
  }),
53
40
  )
54
- return items
55
41
  }
@@ -29,6 +29,8 @@ export {
29
29
  useWidgetShallow,
30
30
  setCaptureEl,
31
31
  getCaptureEl,
32
+ widgetStoreData,
33
+ widgetCaptureEl,
32
34
  clearAllCaptureEls,
33
35
  setEchartInstance,
34
36
  getEchartInstance,
@@ -20,6 +20,8 @@ import {
20
20
  setEchartInstance,
21
21
  subscribeEchartInstance,
22
22
  unregisterWidgetStore,
23
+ widgetCaptureEl,
24
+ widgetStoreData,
23
25
  } from './widget-store-registry'
24
26
  import type { ECharts } from 'echarts'
25
27
 
@@ -290,3 +292,26 @@ describe('widget-store-registry', () => {
290
292
  })
291
293
  })
292
294
  })
295
+
296
+ describe('download getters', () => {
297
+ beforeEach(() => clearAllWidgetStores())
298
+ afterEach(() => clearAllWidgetStores())
299
+
300
+ it('read the store the menu was opened from, else the given id', () => {
301
+ registerWidgetStore('card', createWidgetStore('card', { data: 'card' }))
302
+ registerWidgetStore('modal', createWidgetStore('modal', { data: 'modal' }))
303
+ const getData = widgetStoreData<string>('card')
304
+ expect(getData({ id: 'modal' })).toBe('modal')
305
+ expect(getData({ id: null })).toBe('card')
306
+ })
307
+
308
+ it('capture element: same rule', () => {
309
+ const card = document.createElement('div')
310
+ const modal = document.createElement('div')
311
+ setCaptureEl('card', card)
312
+ setCaptureEl('modal', modal)
313
+ const getEl = widgetCaptureEl('card')
314
+ expect(getEl({ id: 'modal' })).toBe(modal)
315
+ expect(getEl({ id: null })).toBe(card)
316
+ })
317
+ })
@@ -6,6 +6,7 @@ import { useShallow } from 'zustand/react/shallow'
6
6
  import { isDev } from '@carto/ps-utils'
7
7
  import { pipelineMiddleware } from './pipeline-middleware'
8
8
  import type { WidgetInit, WidgetState, WidgetStoreApi } from './types'
9
+ import type { DownloadContext } from '../actions/download/types'
9
10
 
10
11
  const widgetStores = new Map<string, WidgetStoreApi>()
11
12
 
@@ -86,6 +87,27 @@ export function getCaptureEl(id: string): HTMLElement | null {
86
87
  return captureEls.get(id) ?? null
87
88
  }
88
89
 
90
+ /**
91
+ * `getData` for a `create*DownloadConfig`: the `data` of the store the
92
+ * download menu was opened from (`ctx.id` — the fullscreen dialog's own
93
+ * while it's open), or of widget `id` when the menu sits outside a
94
+ * `<Widget.Provider>`.
95
+ */
96
+ export function widgetStoreData<T>(id: string): (ctx: DownloadContext) => T {
97
+ return (ctx) => getWidgetStore(ctx.id ?? id).getState().data as T
98
+ }
99
+
100
+ /**
101
+ * `getCaptureEl` for a `create*DownloadConfig`: the capture element of the
102
+ * store the download menu was opened from (`ctx.id`), or of widget `id`
103
+ * outside a `<Widget.Provider>`.
104
+ */
105
+ export function widgetCaptureEl(
106
+ id: string,
107
+ ): (ctx: DownloadContext) => HTMLElement | null {
108
+ return (ctx) => getCaptureEl(ctx.id ?? id)
109
+ }
110
+
89
111
  /** @internal — exposed for tests and debugging only. */
90
112
  export function clearAllCaptureEls(): void {
91
113
  captureEls.clear()
@@ -56,7 +56,9 @@ describe('createTableDownloadConfig', () => {
56
56
  getData: () => data,
57
57
  columns,
58
58
  })
59
- const handle = await items.find((i) => i.id === 'csv')!.resolve()
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,43 +1,29 @@
1
1
  import {
2
2
  buildCsvDownloadItem,
3
- buildPngDownloadItem,
3
+ withPngItem,
4
4
  type DownloadItem,
5
+ type WidgetDownloadArgs,
5
6
  } from '../actions/download'
6
7
  import { tableDataToCsv } from './helpers'
7
8
  import type { TableColumn, TableWidgetData } from './types'
8
9
 
9
10
  /**
10
- * Download menu items for the Table widget. Always includes a CSV item
11
- * that pulls the latest data from the widget store via `getData()` (so
12
- * locked / sorted / filtered state is honored); headers come from
13
- * `column.label`, cells from `row[column.id]`. When `getCaptureEl` is
14
- * supplied, prepends a PNG item that rasterises the captured element via
15
- * `html2canvas`.
11
+ * Download menu items for the Table widget. Always includes a CSV item that
12
+ * pulls the latest data from the widget store via `getData()` (so locked /
13
+ * sorted / filtered state is honored); headers come from `column.label`,
14
+ * cells from `row[column.id]`. When `getCaptureEl` is given, prepends a PNG
15
+ * item that rasterises the captured element via `html2canvas`.
16
16
  */
17
- export function createTableDownloadConfig(opts: {
18
- filename: string
19
- getData: () => TableWidgetData
20
- columns: readonly TableColumn[]
21
- getCaptureEl?: () => HTMLElement | null
22
- pngPixelRatio?: number
23
- pngBackgroundColor?: string | null
24
- }): DownloadItem[] {
25
- const items: DownloadItem[] = []
26
- if (opts.getCaptureEl) {
27
- items.push(
28
- buildPngDownloadItem({
29
- filename: opts.filename,
30
- getCaptureEl: opts.getCaptureEl,
31
- pixelRatio: opts.pngPixelRatio,
32
- backgroundColor: opts.pngBackgroundColor,
33
- }),
34
- )
35
- }
36
- items.push(
17
+ export function createTableDownloadConfig(
18
+ opts: WidgetDownloadArgs<TableWidgetData> & {
19
+ columns: readonly TableColumn[]
20
+ },
21
+ ): DownloadItem[] {
22
+ return withPngItem(
23
+ opts,
37
24
  buildCsvDownloadItem({
38
25
  filename: opts.filename,
39
- getCsv: () => tableDataToCsv(opts.getData(), opts.columns),
26
+ getCsv: (ctx) => tableDataToCsv(opts.getData(ctx), opts.columns),
40
27
  }),
41
28
  )
42
- return items
43
29
  }
@@ -53,7 +53,9 @@ describe('createTimeseriesDownloadConfig', () => {
53
53
  filename: 't',
54
54
  getData: () => data,
55
55
  })
56
- const handle = await items.find((i) => i.id === 'csv')!.resolve()
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,41 +1,29 @@
1
1
  import {
2
2
  buildCsvDownloadItem,
3
- buildPngDownloadItem,
3
+ withPngItem,
4
4
  type DownloadItem,
5
+ type WidgetDownloadArgs,
5
6
  } from '../actions/download'
6
7
  import type { TimeseriesWidgetData } from './types'
7
8
 
8
9
  /**
9
- * Download menu items for the Timeseries widget. Always includes a CSV
10
- * item with `time, series_1, series_2, …` columns (one row per unique time
11
- * across all series; ISO-8601 strings for `Date`/numeric times). When
12
- * `getCaptureEl` is supplied, prepends a PNG item that rasterises the
13
- * captured element via `html2canvas`.
10
+ * Download menu items for the Timeseries widget. Always includes a CSV item
11
+ * with `time, series_1, series_2, …` columns (one row per unique time across
12
+ * all series; ISO-8601 strings for `Date`/numeric times). When
13
+ * `getCaptureEl` is given, prepends a PNG item that rasterises the captured
14
+ * element via `html2canvas`.
14
15
  */
15
- export function createTimeseriesDownloadConfig(args: {
16
- filename: string
17
- getData: () => TimeseriesWidgetData
18
- seriesNames?: readonly string[]
19
- getCaptureEl?: () => HTMLElement | null
20
- pngPixelRatio?: number
21
- pngBackgroundColor?: string | null
22
- }): DownloadItem[] {
23
- const items: DownloadItem[] = []
24
- if (args.getCaptureEl) {
25
- items.push(
26
- buildPngDownloadItem({
27
- filename: args.filename,
28
- getCaptureEl: args.getCaptureEl,
29
- pixelRatio: args.pngPixelRatio,
30
- backgroundColor: args.pngBackgroundColor,
31
- }),
32
- )
33
- }
34
- items.push(
16
+ export function createTimeseriesDownloadConfig(
17
+ args: WidgetDownloadArgs<TimeseriesWidgetData> & {
18
+ seriesNames?: readonly string[]
19
+ },
20
+ ): DownloadItem[] {
21
+ return withPngItem(
22
+ args,
35
23
  buildCsvDownloadItem({
36
24
  filename: args.filename,
37
- getRows: () => {
38
- const data = args.getData()
25
+ getRows: (ctx) => {
26
+ const data = args.getData(ctx)
39
27
  const seriesCount = data.length
40
28
 
41
29
  // Collect every unique time, preserving insertion order.
@@ -72,7 +60,6 @@ export function createTimeseriesDownloadConfig(args: {
72
60
  },
73
61
  }),
74
62
  )
75
- return items
76
63
  }
77
64
 
78
65
  function formatTime(v: Date | number | string): string {
@@ -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 — same pattern as `Widget.FullScreen.Slot`.
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
  *