@carto/ps-react-ui 4.11.3 → 4.12.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 (77) hide show
  1. package/dist/chat.js +962 -733
  2. package/dist/chat.js.map +1 -1
  3. package/dist/csv-item-hH_Gt7ur.js +32 -0
  4. package/dist/csv-item-hH_Gt7ur.js.map +1 -0
  5. package/dist/png-item-9dNbB37T.js +57 -0
  6. package/dist/png-item-9dNbB37T.js.map +1 -0
  7. package/dist/table-B3ZWWhJt.js +383 -0
  8. package/dist/table-B3ZWWhJt.js.map +1 -0
  9. package/dist/types/chat/containers/chat-footer.d.ts +1 -1
  10. package/dist/types/chat/containers/styles.d.ts +79 -12
  11. package/dist/types/chat/index.d.ts +1 -1
  12. package/dist/types/chat/types.d.ts +21 -0
  13. package/dist/types/chat/use-typewriter.d.ts +5 -3
  14. package/dist/types/widgets-v2/actions/download/constants.d.ts +12 -0
  15. package/dist/types/widgets-v2/actions/download/csv-item.d.ts +38 -0
  16. package/dist/types/widgets-v2/actions/download/icons.d.ts +6 -0
  17. package/dist/types/widgets-v2/actions/download/index.d.ts +3 -1
  18. package/dist/types/widgets-v2/actions/index.d.ts +1 -1
  19. package/dist/widgets-v2/actions.js +40 -36
  20. package/dist/widgets-v2/actions.js.map +1 -1
  21. package/dist/widgets-v2/bar.js +77 -84
  22. package/dist/widgets-v2/bar.js.map +1 -1
  23. package/dist/widgets-v2/category.js +50 -55
  24. package/dist/widgets-v2/category.js.map +1 -1
  25. package/dist/widgets-v2/formula.js +37 -43
  26. package/dist/widgets-v2/formula.js.map +1 -1
  27. package/dist/widgets-v2/histogram.js +138 -144
  28. package/dist/widgets-v2/histogram.js.map +1 -1
  29. package/dist/widgets-v2/markdown.js +18 -17
  30. package/dist/widgets-v2/markdown.js.map +1 -1
  31. package/dist/widgets-v2/pie.js +67 -73
  32. package/dist/widgets-v2/pie.js.map +1 -1
  33. package/dist/widgets-v2/scatterplot.js +75 -81
  34. package/dist/widgets-v2/scatterplot.js.map +1 -1
  35. package/dist/widgets-v2/spread.js +36 -41
  36. package/dist/widgets-v2/spread.js.map +1 -1
  37. package/dist/widgets-v2/table.js +46 -55
  38. package/dist/widgets-v2/table.js.map +1 -1
  39. package/dist/widgets-v2/timeseries.js +81 -87
  40. package/dist/widgets-v2/timeseries.js.map +1 -1
  41. package/dist/widgets-v2.js +1 -1
  42. package/package.json +1 -1
  43. package/src/chat/bubbles/styles.ts +5 -1
  44. package/src/chat/containers/chat-content.tsx +4 -1
  45. package/src/chat/containers/chat-footer.test.tsx +59 -0
  46. package/src/chat/containers/chat-footer.tsx +124 -36
  47. package/src/chat/containers/styles.ts +107 -16
  48. package/src/chat/feedback/styles.ts +11 -4
  49. package/src/chat/index.ts +1 -0
  50. package/src/chat/types.ts +22 -0
  51. package/src/chat/use-typewriter.ts +32 -24
  52. package/src/widgets-v2/actions/download/constants.ts +14 -0
  53. package/src/widgets-v2/actions/download/csv-item.test.tsx +77 -0
  54. package/src/widgets-v2/actions/download/csv-item.tsx +71 -0
  55. package/src/widgets-v2/actions/download/icons.tsx +10 -1
  56. package/src/widgets-v2/actions/download/index.ts +3 -1
  57. package/src/widgets-v2/actions/download/png-item.tsx +2 -1
  58. package/src/widgets-v2/actions/index.ts +5 -0
  59. package/src/widgets-v2/bar/download.tsx +16 -22
  60. package/src/widgets-v2/category/download.test.ts +9 -0
  61. package/src/widgets-v2/category/download.ts +16 -20
  62. package/src/widgets-v2/formula/download.tsx +23 -29
  63. package/src/widgets-v2/histogram/download.ts +22 -26
  64. package/src/widgets-v2/markdown/{download.ts → download.tsx} +5 -2
  65. package/src/widgets-v2/pie/download.ts +16 -20
  66. package/src/widgets-v2/scatterplot/download.ts +16 -20
  67. package/src/widgets-v2/spread/download.ts +23 -27
  68. package/src/widgets-v2/table/download.test.ts +10 -0
  69. package/src/widgets-v2/table/download.ts +11 -15
  70. package/src/widgets-v2/table/helpers.test.ts +19 -0
  71. package/src/widgets-v2/table/helpers.ts +7 -12
  72. package/src/widgets-v2/timeseries/download.ts +36 -40
  73. package/dist/png-item-BE9uEqlD.js +0 -45
  74. package/dist/png-item-BE9uEqlD.js.map +0 -1
  75. package/dist/table-C9IMbTr0.js +0 -385
  76. package/dist/table-C9IMbTr0.js.map +0 -1
  77. package/dist/types/chat/feedback/styles.d.ts +0 -211
@@ -1,6 +1,6 @@
1
1
  import {
2
+ buildCsvDownloadItem,
2
3
  buildPngDownloadItem,
3
- downloadToCSV,
4
4
  type DownloadItem,
5
5
  } from '../actions/download'
6
6
  import type { PieWidgetData } from './types'
@@ -31,25 +31,21 @@ export function createPieDownloadConfig(args: {
31
31
  }),
32
32
  )
33
33
  }
34
- items.push({
35
- id: 'csv',
36
- label: 'Download as CSV',
37
- resolve: () => {
38
- const data = args.getData()
39
- const rows: unknown[][] = [['series', 'name', 'value']]
40
- for (const [i, series] of data.entries()) {
41
- const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
42
- for (const slice of series) {
43
- rows.push([seriesName, slice.name, slice.value])
34
+ items.push(
35
+ buildCsvDownloadItem({
36
+ filename: args.filename,
37
+ getRows: () => {
38
+ const data = args.getData()
39
+ const rows: unknown[][] = [['series', 'name', 'value']]
40
+ for (const [i, series] of data.entries()) {
41
+ const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
42
+ for (const slice of series) {
43
+ rows.push([seriesName, slice.name, slice.value])
44
+ }
44
45
  }
45
- }
46
- const handle = downloadToCSV(rows)
47
- return Promise.resolve({
48
- url: handle.url,
49
- filename: `${args.filename}.csv`,
50
- revoke: handle.revoke,
51
- })
52
- },
53
- })
46
+ return rows
47
+ },
48
+ }),
49
+ )
54
50
  return items
55
51
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
+ buildCsvDownloadItem,
2
3
  buildPngDownloadItem,
3
- downloadToCSV,
4
4
  type DownloadItem,
5
5
  } from '../actions/download'
6
6
  import type { ScatterplotWidgetData } from './types'
@@ -30,25 +30,21 @@ export function createScatterplotDownloadConfig(args: {
30
30
  }),
31
31
  )
32
32
  }
33
- items.push({
34
- id: 'csv',
35
- label: 'Download as CSV',
36
- resolve: () => {
37
- const data = args.getData()
38
- const rows: unknown[][] = [['series', 'x', 'y']]
39
- for (const [i, series] of data.entries()) {
40
- const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
41
- for (const [x, y] of series) {
42
- rows.push([seriesName, x, y])
33
+ items.push(
34
+ buildCsvDownloadItem({
35
+ filename: args.filename,
36
+ getRows: () => {
37
+ const data = args.getData()
38
+ const rows: unknown[][] = [['series', 'x', 'y']]
39
+ for (const [i, series] of data.entries()) {
40
+ const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`
41
+ for (const [x, y] of series) {
42
+ rows.push([seriesName, x, y])
43
+ }
43
44
  }
44
- }
45
- const handle = downloadToCSV(rows)
46
- return Promise.resolve({
47
- url: handle.url,
48
- filename: `${args.filename}.csv`,
49
- revoke: handle.revoke,
50
- })
51
- },
52
- })
45
+ return rows
46
+ },
47
+ }),
48
+ )
53
49
  return items
54
50
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
+ buildCsvDownloadItem,
2
3
  buildPngDownloadItem,
3
- downloadToCSV,
4
4
  type DownloadItem,
5
5
  } from '../actions/download'
6
6
  import type { SpreadWidgetData } from './types'
@@ -29,31 +29,27 @@ export function createSpreadDownloadConfig(args: {
29
29
  }),
30
30
  )
31
31
  }
32
- items.push({
33
- id: 'csv',
34
- label: 'Download as CSV',
35
- resolve: () => {
36
- const data = args.getData()
37
- const rows: unknown[][] = [
38
- ['series', 'prefix', 'min', 'max', 'suffix', 'note'],
39
- ]
40
- for (const item of data) {
41
- rows.push([
42
- item.series?.name ?? '',
43
- item.prefix ?? '',
44
- item.min,
45
- item.max,
46
- item.suffix ?? '',
47
- item.note ?? '',
48
- ])
49
- }
50
- const handle = downloadToCSV(rows)
51
- return Promise.resolve({
52
- url: handle.url,
53
- filename: `${args.filename}.csv`,
54
- revoke: handle.revoke,
55
- })
56
- },
57
- })
32
+ items.push(
33
+ buildCsvDownloadItem({
34
+ filename: args.filename,
35
+ getRows: () => {
36
+ const data = args.getData()
37
+ const rows: unknown[][] = [
38
+ ['series', 'prefix', 'min', 'max', 'suffix', 'note'],
39
+ ]
40
+ for (const item of data) {
41
+ rows.push([
42
+ item.series?.name ?? '',
43
+ item.prefix ?? '',
44
+ item.min,
45
+ item.max,
46
+ item.suffix ?? '',
47
+ item.note ?? '',
48
+ ])
49
+ }
50
+ return rows
51
+ },
52
+ }),
53
+ )
58
54
  return items
59
55
  }
@@ -61,6 +61,16 @@ describe('createTableDownloadConfig', () => {
61
61
  expect(csvText).toBe('Name,Score\nAlpha,10\nBeta,20')
62
62
  })
63
63
 
64
+ it('CSV guards formula-injection cells end-to-end', async () => {
65
+ const items = createTableDownloadConfig({
66
+ filename: 't',
67
+ getData: () => [{ id: 1, name: '=HYPERLINK("x")', score: 1 }],
68
+ columns,
69
+ })
70
+ await items.find((i) => i.id === 'csv')!.resolve()
71
+ expect(csvText).toBe('Name,Score\n"\'=HYPERLINK(""x"")",1')
72
+ })
73
+
64
74
  it('CSV calls getData() at click time (not at config creation)', async () => {
65
75
  let snapshot: TableWidgetData = data
66
76
  const items = createTableDownloadConfig({
@@ -1,4 +1,8 @@
1
- import { buildPngDownloadItem, type DownloadItem } from '../actions/download'
1
+ import {
2
+ buildCsvDownloadItem,
3
+ buildPngDownloadItem,
4
+ type DownloadItem,
5
+ } from '../actions/download'
2
6
  import { tableDataToCsv } from './helpers'
3
7
  import type { TableColumn, TableWidgetData } from './types'
4
8
 
@@ -29,19 +33,11 @@ export function createTableDownloadConfig(opts: {
29
33
  }),
30
34
  )
31
35
  }
32
- items.push({
33
- id: 'csv',
34
- label: 'Download as CSV',
35
- resolve: () => {
36
- const csv = tableDataToCsv(opts.getData(), opts.columns)
37
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
38
- const url = URL.createObjectURL(blob)
39
- return Promise.resolve({
40
- url,
41
- filename: `${opts.filename}.csv`,
42
- revoke: () => URL.revokeObjectURL(url),
43
- })
44
- },
45
- })
36
+ items.push(
37
+ buildCsvDownloadItem({
38
+ filename: opts.filename,
39
+ getCsv: () => tableDataToCsv(opts.getData(), opts.columns),
40
+ }),
41
+ )
46
42
  return items
47
43
  }
@@ -211,4 +211,23 @@ describe('tableDataToCsv', () => {
211
211
  expect(csv).toContain('"[""x"",""y""]"')
212
212
  expect(csv).toContain('"{""z"":1}"')
213
213
  })
214
+
215
+ it('guards against formula injection by prefixing leading =,+,-,@', () => {
216
+ const csv = tableDataToCsv(
217
+ [
218
+ { id: 1, name: '=SUM(A1)', score: '+1' },
219
+ { id: 2, name: '-2', score: '@cmd' },
220
+ ],
221
+ cols,
222
+ )
223
+ const lines = csv.split('\n')
224
+ expect(lines[1]).toBe("'=SUM(A1),'+1")
225
+ expect(lines[2]).toBe("'-2,'@cmd")
226
+ })
227
+
228
+ it('still wraps a formula cell that also contains a comma', () => {
229
+ const csv = tableDataToCsv([{ id: 1, name: '=A1,B2', score: 1 }], cols)
230
+ // Prefixed against injection, then quoted for the embedded comma.
231
+ expect(csv.split('\n')[1]).toBe('"\'=A1,B2",1')
232
+ })
214
233
  })
@@ -1,3 +1,4 @@
1
+ import { toCsvString } from '../actions/download'
1
2
  import type {
2
3
  TableColumn,
3
4
  TableRow,
@@ -102,20 +103,14 @@ export function tableDataToCsv(
102
103
  data: TableWidgetData,
103
104
  columns: readonly TableColumn[],
104
105
  ): string {
105
- const head = columns.map((c) => csvEscape(stringifyHeader(c.label))).join(',')
106
- const lines = [head]
106
+ // Pre-stringify with the table's own header/cell rules, then delegate the
107
+ // CSV assembly (quoting + spreadsheet formula-injection guard) to the shared
108
+ // `toCsvString` so escaping stays identical to every other widget's export.
109
+ const rows: string[][] = [columns.map((c) => stringifyHeader(c.label))]
107
110
  for (const row of data) {
108
- const cells = columns.map((c) => csvEscape(stringifyCell(row[c.id])))
109
- lines.push(cells.join(','))
111
+ rows.push(columns.map((c) => stringifyCell(row[c.id])))
110
112
  }
111
- return lines.join('\n')
112
- }
113
-
114
- function csvEscape(value: string): string {
115
- if (/[",\n\r]/.test(value)) {
116
- return `"${value.replace(/"/g, '""')}"`
117
- }
118
- return value
113
+ return toCsvString(rows)
119
114
  }
120
115
 
121
116
  function stringifyHeader(label: unknown): string {
@@ -1,6 +1,6 @@
1
1
  import {
2
+ buildCsvDownloadItem,
2
3
  buildPngDownloadItem,
3
- downloadToCSV,
4
4
  type DownloadItem,
5
5
  } from '../actions/download'
6
6
  import type { TimeseriesWidgetData } from './types'
@@ -31,51 +31,47 @@ export function createTimeseriesDownloadConfig(args: {
31
31
  }),
32
32
  )
33
33
  }
34
- items.push({
35
- id: 'csv',
36
- label: 'Download as CSV',
37
- resolve: () => {
38
- const data = args.getData()
39
- const seriesCount = data.length
34
+ items.push(
35
+ buildCsvDownloadItem({
36
+ filename: args.filename,
37
+ getRows: () => {
38
+ const data = args.getData()
39
+ const seriesCount = data.length
40
40
 
41
- // Collect every unique time, preserving insertion order.
42
- const timeKeys: (Date | number | string)[] = []
43
- const seenKeys = new Set<string>()
44
- for (const series of data) {
45
- for (const point of series) {
46
- const key = String(point.name)
47
- if (!seenKeys.has(key)) {
48
- seenKeys.add(key)
49
- timeKeys.push(point.name)
41
+ // Collect every unique time, preserving insertion order.
42
+ const timeKeys: (Date | number | string)[] = []
43
+ const seenKeys = new Set<string>()
44
+ for (const series of data) {
45
+ for (const point of series) {
46
+ const key = String(point.name)
47
+ if (!seenKeys.has(key)) {
48
+ seenKeys.add(key)
49
+ timeKeys.push(point.name)
50
+ }
50
51
  }
51
52
  }
52
- }
53
53
 
54
- // Build a quick lookup per series for O(rows × series) emit.
55
- const lookups = data.map(
56
- (series) => new Map(series.map((p) => [String(p.name), p.value])),
57
- )
54
+ // Build a quick lookup per series for O(rows × series) emit.
55
+ const lookups = data.map(
56
+ (series) => new Map(series.map((p) => [String(p.name), p.value])),
57
+ )
58
58
 
59
- const header: unknown[] = ['time']
60
- for (let i = 0; i < seriesCount; i++) {
61
- header.push(args.seriesNames?.[i] ?? `series_${i + 1}`)
62
- }
63
- const rows: unknown[][] = [header]
64
- for (const key of timeKeys) {
65
- const row: unknown[] = [formatTime(key)]
66
- const lookupKey = String(key)
67
- for (const lookup of lookups) row.push(lookup.get(lookupKey) ?? '')
68
- rows.push(row)
69
- }
59
+ const header: unknown[] = ['time']
60
+ for (let i = 0; i < seriesCount; i++) {
61
+ header.push(args.seriesNames?.[i] ?? `series_${i + 1}`)
62
+ }
63
+ const rows: unknown[][] = [header]
64
+ for (const key of timeKeys) {
65
+ const row: unknown[] = [formatTime(key)]
66
+ const lookupKey = String(key)
67
+ for (const lookup of lookups) row.push(lookup.get(lookupKey) ?? '')
68
+ rows.push(row)
69
+ }
70
70
 
71
- const handle = downloadToCSV(rows)
72
- return Promise.resolve({
73
- url: handle.url,
74
- filename: `${args.filename}.csv`,
75
- revoke: handle.revoke,
76
- })
77
- },
78
- })
71
+ return rows
72
+ },
73
+ }),
74
+ )
79
75
  return items
80
76
  }
81
77
 
@@ -1,45 +0,0 @@
1
- import { jsx as n } from "react/jsx-runtime";
2
- import { d as a } from "./exports-Cx-f6m6U.js";
3
- import { c as r } from "react/compiler-runtime";
4
- import { SvgIcon as i } from "@mui/material";
5
- import { ImageOutlined as c } from "@mui/icons-material";
6
- function m(o) {
7
- const e = r(2);
8
- let l;
9
- return e[0] !== o ? (l = /* @__PURE__ */ n(c, { ...o }), e[0] = o, e[1] = l) : l = e[1], l;
10
- }
11
- function s(o) {
12
- const e = r(3);
13
- let l;
14
- e[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (l = /* @__PURE__ */ n("path", { fill: "currentColor", d: "M4.313 11.25h2.25v-1.125H4.688v-2.25h1.875V6.75h-2.25a.726.726 0 0 0-.535.216.726.726 0 0 0-.216.534v3c0 .213.072.39.216.534a.726.726 0 0 0 .534.216Zm2.925 0h2.25c.212 0 .39-.072.534-.216a.726.726 0 0 0 .216-.534V9.375a.931.931 0 0 0-.216-.59.658.658 0 0 0-.534-.273H8.363v-.637h1.875V6.75h-2.25a.726.726 0 0 0-.535.216.726.726 0 0 0-.216.534v1.125c0 .213.072.403.216.572a.675.675 0 0 0 .534.253h1.126v.675H7.238v1.125Zm4.95 0h1.124l1.313-4.5H13.5l-.75 2.588L12 6.75h-1.125l1.313 4.5ZM3 15c-.413 0-.766-.147-1.06-.44a1.445 1.445 0 0 1-.44-1.06v-9c0-.412.147-.766.44-1.06C2.235 3.148 2.588 3 3 3h12c.412 0 .766.147 1.06.44.293.294.44.648.44 1.06v9c0 .412-.147.766-.44 1.06-.294.293-.647.44-1.06.44H3Zm0-1.5h12v-9H3v9Z" }), e[0] = l) : l = e[0];
15
- let t;
16
- return e[1] !== o ? (t = /* @__PURE__ */ n(i, { viewBox: "0 0 18 18", ...o, children: l }), e[1] = o, e[2] = t) : t = e[2], t;
17
- }
18
- function b(o) {
19
- return {
20
- id: "png",
21
- label: o.label ?? "PNG",
22
- icon: /* @__PURE__ */ n(m, { fontSize: "small" }),
23
- resolve: async () => {
24
- const e = o.getCaptureEl();
25
- if (!e)
26
- throw new Error("[widgets-v2] No PNG capture element available");
27
- const l = await a({
28
- element: e,
29
- pixelRatio: o.pixelRatio,
30
- backgroundColor: o.backgroundColor
31
- });
32
- return {
33
- url: l.url,
34
- filename: `${o.filename}.png`,
35
- revoke: l.revoke
36
- };
37
- }
38
- };
39
- }
40
- export {
41
- s as C,
42
- m as P,
43
- b
44
- };
45
- //# sourceMappingURL=png-item-BE9uEqlD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"png-item-BE9uEqlD.js","sources":["../src/widgets-v2/actions/download/icons.tsx","../src/widgets-v2/actions/download/png-item.tsx"],"sourcesContent":["import { SvgIcon, type SvgIconProps } from '@mui/material'\nimport { ImageOutlined } from '@mui/icons-material'\n\n/**\n * Generic \"image\" glyph used for the PNG download item. Wraps MUI's\n * `ImageOutlined` so callers can swap it via the per-widget download\n * config's `icon` override without pulling MUI directly.\n */\nexport function PNGIcon(props: SvgIconProps) {\n return <ImageOutlined {...props} />\n}\n\n/**\n * \"CSV\" rectangle with the letters spelled inside — matches v1 visual and is\n * easier to recognise in a download menu than a generic table glyph.\n */\nexport function CSVIcon(props: SvgIconProps) {\n return (\n <SvgIcon viewBox='0 0 18 18' {...props}>\n <path\n fill='currentColor'\n d='M4.313 11.25h2.25v-1.125H4.688v-2.25h1.875V6.75h-2.25a.726.726 0 0 0-.535.216.726.726 0 0 0-.216.534v3c0 .213.072.39.216.534a.726.726 0 0 0 .534.216Zm2.925 0h2.25c.212 0 .39-.072.534-.216a.726.726 0 0 0 .216-.534V9.375a.931.931 0 0 0-.216-.59.658.658 0 0 0-.534-.273H8.363v-.637h1.875V6.75h-2.25a.726.726 0 0 0-.535.216.726.726 0 0 0-.216.534v1.125c0 .213.072.403.216.572a.675.675 0 0 0 .534.253h1.126v.675H7.238v1.125Zm4.95 0h1.124l1.313-4.5H13.5l-.75 2.588L12 6.75h-1.125l1.313 4.5ZM3 15c-.413 0-.766-.147-1.06-.44a1.445 1.445 0 0 1-.44-1.06v-9c0-.412.147-.766.44-1.06C2.235 3.148 2.588 3 3 3h12c.412 0 .766.147 1.06.44.293.294.44.648.44 1.06v9c0 .412-.147.766-.44 1.06-.294.293-.647.44-1.06.44H3Zm0-1.5h12v-9H3v9Z'\n />\n </SvgIcon>\n )\n}\n","import { downloadDOMToPNG } from './exports'\nimport { PNGIcon } from './icons'\nimport type { DownloadItem } from './types'\n\nexport interface BuildPngDownloadItemArgs {\n /** Base filename (without extension). The item appends `.png`. */\n filename: string\n /**\n * Reads the capture element to rasterise. Called at click time so the\n * download config doesn't capture a stale reference. Wire it to\n * `() => getCaptureEl(id)` from `widgets-v2/stores`.\n */\n getCaptureEl: () => HTMLElement | null\n /** html2canvas `scale`. Default 2. */\n pixelRatio?: number\n /** html2canvas `backgroundColor`. Default transparent (`null`). */\n backgroundColor?: string | null\n /** Override the menu label. Default `'PNG'`. */\n label?: string\n}\n\n/**\n * Builds the standard PNG `DownloadItem` used by every per-widget download\n * config. Centralised so the menu label, icon, error message, and filename\n * suffix stay consistent across widgets without each `create*DownloadConfig`\n * re-deriving the same shape.\n */\nexport function buildPngDownloadItem(\n args: BuildPngDownloadItemArgs,\n): DownloadItem {\n return {\n id: 'png',\n label: args.label ?? 'PNG',\n icon: <PNGIcon fontSize='small' />,\n resolve: async () => {\n const el = args.getCaptureEl()\n if (!el) {\n throw new Error('[widgets-v2] No PNG capture element available')\n }\n const handle = await downloadDOMToPNG({\n element: el,\n pixelRatio: args.pixelRatio,\n backgroundColor: args.backgroundColor,\n })\n return {\n url: handle.url,\n filename: `${args.filename}.png`,\n revoke: handle.revoke,\n }\n },\n }\n}\n"],"names":["PNGIcon","props","$","_c","t0","jsx","ImageOutlined","CSVIcon","Symbol","for","t1","SvgIcon","buildPngDownloadItem","args","id","label","icon","resolve","el","getCaptureEl","Error","handle","downloadDOMToPNG","element","pixelRatio","backgroundColor","url","filename","revoke"],"mappings":";;;;;AAQO,SAAAA,EAAAC,GAAA;AAAA,QAAAC,IAAAC,EAAA,CAAA;AAAA,MAAAC;AAAA,SAAAF,SAAAD,KACEG,IAAA,gBAAAC,EAACC,GAAA,EAAa,GAAKL,EAAAA,CAAK,GAAIC,OAAAD,GAAAC,OAAAE,KAAAA,IAAAF,EAAA,CAAA,GAA5BE;AAA4B;AAO9B,SAAAG,EAAAN,GAAA;AAAA,QAAAC,IAAAC,EAAA,CAAA;AAAA,MAAAC;AAAA,EAAAF,EAAA,CAAA,MAAAM,uBAAAC,IAAA,2BAAA,KAGDL,IAAA,gBAAAC,EAAA,QAAA,EACO,MAAA,gBACH,GAAA,gtBAA8sB,GAChtBH,OAAAE,KAAAA,IAAAF,EAAA,CAAA;AAAA,MAAAQ;AAAA,SAAAR,SAAAD,KAJJS,sBAACC,GAAA,EAAgB,SAAA,aAAW,GAAKV,GAC/BG,UAAAA,GAIF,GAAUF,OAAAD,GAAAC,OAAAQ,KAAAA,IAAAR,EAAA,CAAA,GALVQ;AAKU;ACIP,SAASE,EACdC,GACc;AACd,SAAO;AAAA,IACLC,IAAI;AAAA,IACJC,OAAOF,EAAKE,SAAS;AAAA,IACrBC,MAAM,gBAAAX,EAACL,GAAA,EAAQ,UAAS,QAAA,CAAO;AAAA,IAC/BiB,SAAS,YAAY;AACnB,YAAMC,IAAKL,EAAKM,aAAAA;AAChB,UAAI,CAACD;AACH,cAAM,IAAIE,MAAM,+CAA+C;AAEjE,YAAMC,IAAS,MAAMC,EAAiB;AAAA,QACpCC,SAASL;AAAAA,QACTM,YAAYX,EAAKW;AAAAA,QACjBC,iBAAiBZ,EAAKY;AAAAA,MAAAA,CACvB;AACD,aAAO;AAAA,QACLC,KAAKL,EAAOK;AAAAA,QACZC,UAAU,GAAGd,EAAKc,QAAQ;AAAA,QAC1BC,QAAQP,EAAOO;AAAAA,MAAAA;AAAAA,IAEnB;AAAA,EAAA;AAEJ;"}