@carto/ps-react-ui 6.3.0 → 6.5.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 (113) hide show
  1. package/dist/actions-D_PVFZaM.js +1272 -0
  2. package/dist/actions-D_PVFZaM.js.map +1 -0
  3. package/dist/{category-DULnoxvk.js → category-UIGvFN3D.js} +263 -246
  4. package/dist/category-UIGvFN3D.js.map +1 -0
  5. package/dist/chat.js +472 -470
  6. package/dist/chat.js.map +1 -1
  7. package/dist/{components-CTUpdQuj.js → components-DDJU4m0t.js} +8 -8
  8. package/dist/components-DDJU4m0t.js.map +1 -0
  9. package/dist/components.js +2 -2
  10. package/dist/{download-B4sykOFm.js → download-DIj1Iz0t.js} +2 -2
  11. package/dist/{download-B4sykOFm.js.map → download-DIj1Iz0t.js.map} +1 -1
  12. package/dist/{formula-DJS-JLpk.js → formula-CSHhAoyW.js} +4 -4
  13. package/dist/{formula-DJS-JLpk.js.map → formula-CSHhAoyW.js.map} +1 -1
  14. package/dist/{item-color-B7xkIpDZ.js → item-color-7o7pFBR2.js} +26 -18
  15. package/dist/item-color-7o7pFBR2.js.map +1 -0
  16. package/dist/markdown-B8lLR-NY.js +106 -0
  17. package/dist/markdown-B8lLR-NY.js.map +1 -0
  18. package/dist/scale-Bhx8HKEB.js +29 -0
  19. package/dist/scale-Bhx8HKEB.js.map +1 -0
  20. package/dist/{spread-CQwFIwuz.js → spread-BRfPPIep.js} +3 -3
  21. package/dist/{spread-CQwFIwuz.js.map → spread-BRfPPIep.js.map} +1 -1
  22. package/dist/style-DTLuIo84.js +87 -0
  23. package/dist/style-DTLuIo84.js.map +1 -0
  24. package/dist/{styles-BCEBm7xh.js → styles-yKcpfyNV.js} +5 -5
  25. package/dist/styles-yKcpfyNV.js.map +1 -0
  26. package/dist/{table-DEdW_fDj.js → table-Col_FRvb.js} +2 -2
  27. package/dist/{table-DEdW_fDj.js.map → table-Col_FRvb.js.map} +1 -1
  28. package/dist/theme/tailwind.css +373 -0
  29. package/dist/theme.js +123 -0
  30. package/dist/theme.js.map +1 -0
  31. package/dist/types/components/lasso-tool/styles.d.ts +4 -4
  32. package/dist/types/theme/create-carto-theme.d.ts +7 -0
  33. package/dist/types/theme/deep-merge.d.ts +6 -0
  34. package/dist/types/theme/generate-tailwind-css.d.ts +8 -0
  35. package/dist/types/theme/index.d.ts +2 -0
  36. package/dist/types/widgets/actions/index.d.ts +1 -1
  37. package/dist/types/widgets/actions/relative-data/index.d.ts +1 -0
  38. package/dist/types/widgets/actions/relative-data/labels.d.ts +3 -0
  39. package/dist/types/widgets/actions/relative-data/relative-data.d.ts +16 -2
  40. package/dist/types/widgets/actions/relative-data/scale.d.ts +26 -0
  41. package/dist/types/widgets/actions/relative-data/style.d.ts +12 -0
  42. package/dist/types/widgets/category/category.d.ts +2 -1
  43. package/dist/types/widgets/index.d.ts +1 -1
  44. package/dist/types/widgets/markdown/style.d.ts +74 -0
  45. package/dist/types/widgets/utils/spacing-px.d.ts +13 -0
  46. package/dist/{utils-C-eWwOhE.js → utils-Dw4y8tiq.js} +2 -2
  47. package/dist/{utils-C-eWwOhE.js.map → utils-Dw4y8tiq.js.map} +1 -1
  48. package/dist/widgets/actions.js +3 -3
  49. package/dist/widgets/bar.js +85 -84
  50. package/dist/widgets/bar.js.map +1 -1
  51. package/dist/widgets/category.js +1 -1
  52. package/dist/widgets/formula.js +1 -1
  53. package/dist/widgets/histogram.js +109 -108
  54. package/dist/widgets/histogram.js.map +1 -1
  55. package/dist/widgets/markdown.js +1 -1
  56. package/dist/widgets/pie.js +78 -78
  57. package/dist/widgets/pie.js.map +1 -1
  58. package/dist/widgets/scatterplot.js +121 -120
  59. package/dist/widgets/scatterplot.js.map +1 -1
  60. package/dist/widgets/spread.js +1 -1
  61. package/dist/widgets/table.js +1 -1
  62. package/dist/widgets/timeseries.js +91 -90
  63. package/dist/widgets/timeseries.js.map +1 -1
  64. package/dist/widgets/utils.js +1 -1
  65. package/dist/widgets.js +283 -279
  66. package/dist/widgets.js.map +1 -1
  67. package/dist/{zoom-toggle-MmLjMTEw.js → zoom-toggle-DqrV_0Ji.js} +2 -2
  68. package/dist/{zoom-toggle-MmLjMTEw.js.map → zoom-toggle-DqrV_0Ji.js.map} +1 -1
  69. package/package.json +8 -1
  70. package/src/chat/bubbles/styles.ts +2 -0
  71. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +4 -4
  72. package/src/components/lasso-tool/lasso-tool-inline.tsx +6 -6
  73. package/src/components/lasso-tool/styles.ts +4 -4
  74. package/src/theme/create-carto-theme.test.ts +53 -0
  75. package/src/theme/create-carto-theme.ts +21 -0
  76. package/src/theme/deep-merge.test.ts +25 -0
  77. package/src/theme/deep-merge.ts +22 -0
  78. package/src/theme/generate-tailwind-css.test.ts +87 -0
  79. package/src/theme/generate-tailwind-css.ts +178 -0
  80. package/src/theme/index.ts +2 -0
  81. package/src/theme/tailwind-compile.test.ts +71 -0
  82. package/src/theme/tailwind.css +373 -0
  83. package/src/widgets/actions/index.ts +1 -0
  84. package/src/widgets/actions/relative-data/index.ts +1 -0
  85. package/src/widgets/actions/relative-data/labels.ts +6 -0
  86. package/src/widgets/actions/relative-data/relative-data.test.tsx +78 -1
  87. package/src/widgets/actions/relative-data/relative-data.tsx +137 -20
  88. package/src/widgets/actions/relative-data/scale.ts +48 -0
  89. package/src/widgets/actions/relative-data/style.ts +10 -0
  90. package/src/widgets/bar/options.test.ts +16 -0
  91. package/src/widgets/bar/options.ts +13 -8
  92. package/src/widgets/category/category.relative-data.test.tsx +51 -63
  93. package/src/widgets/category/category.tsx +25 -11
  94. package/src/widgets/histogram/options.ts +13 -8
  95. package/src/widgets/index.ts +1 -0
  96. package/src/widgets/markdown/style.ts +40 -0
  97. package/src/widgets/note/widget-note.tsx +4 -1
  98. package/src/widgets/pie/options.ts +5 -4
  99. package/src/widgets/scatterplot/options.test.ts +12 -0
  100. package/src/widgets/scatterplot/options.ts +25 -10
  101. package/src/widgets/timeseries/options.ts +13 -8
  102. package/src/widgets/utils/css-variables-theme.test.ts +32 -0
  103. package/src/widgets/utils/option-builders.ts +4 -6
  104. package/src/widgets/utils/spacing-px.test.ts +42 -0
  105. package/src/widgets/utils/spacing-px.ts +24 -0
  106. package/dist/actions-CkxKe8-J.js +0 -1160
  107. package/dist/actions-CkxKe8-J.js.map +0 -1
  108. package/dist/category-DULnoxvk.js.map +0 -1
  109. package/dist/components-CTUpdQuj.js.map +0 -1
  110. package/dist/item-color-B7xkIpDZ.js.map +0 -1
  111. package/dist/markdown-DmvLi3vx.js +0 -159
  112. package/dist/markdown-DmvLi3vx.js.map +0 -1
  113. package/dist/styles-BCEBm7xh.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { r as e } from "./styles-BCEBm7xh.js";
2
- import { $ as t, A as n, B as r, C as i, D as a, E as o, F as s, G as c, H as l, I as u, J as d, K as f, L as p, M as m, N as h, O as g, P as _, Q as v, R as y, S as b, T as x, U as S, V as C, W as w, X as T, Y as E, Z as D, _ as O, _t as k, a as A, at as j, b as M, bt as N, c as P, ct as F, d as I, dt as L, et as R, f as z, ft as B, g as V, gt as H, h as U, ht as W, i as G, it as K, j as q, k as J, l as Y, lt as X, m as Z, mt as Q, n as $, nt as ee, o as te, ot as ne, p as re, pt as ie, q as ae, r as oe, rt as se, s as ce, st as le, t as ue, tt as de, u as fe, ut as pe, v as me, vt as he, w as ge, x as _e, y as ve, yt as ye, z as be } from "./components-CTUpdQuj.js";
1
+ import { r as e } from "./styles-yKcpfyNV.js";
2
+ import { $ as t, A as n, B as r, C as i, D as a, E as o, F as s, G as c, H as l, I as u, J as d, K as f, L as p, M as m, N as h, O as g, P as _, Q as v, R as y, S as b, T as x, U as S, V as C, W as w, X as T, Y as E, Z as D, _ as O, _t as k, a as A, at as j, b as M, bt as N, c as P, ct as F, d as I, dt as L, et as R, f as z, ft as B, g as V, gt as H, h as U, ht as W, i as G, it as K, j as q, k as J, l as Y, lt as X, m as Z, mt as Q, n as $, nt as ee, o as te, ot as ne, p as re, pt as ie, q as ae, r as oe, rt as se, s as ce, st as le, t as ue, tt as de, u as fe, ut as pe, v as me, vt as he, w as ge, x as _e, y as ve, yt as ye, z as be } from "./components-DDJU4m0t.js";
3
3
  import { n as xe, r as Se, t as Ce } from "./smart-tooltip-Dc_rHhyI.js";
4
4
  export { ve as ACCEPT_ACTION_LABELS, M as AcceptAction, Y as AreaIcon, ue as BasemapsUI, fe as BufferIcon, O as CANCEL_ACTION_LABELS, c as COLOR_ACTION_LABELS, me as CancelAction, f as ColorAction, ae as ColorDot, e as CopyButton, pe as DEFAULT_LASSO_TOOLS_MODES_MAPPING, te as DEFAULT_MEASUREMENT_TOOLS_LABELS, ce as DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING, P as DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING, p as DELETE_ACTION_LABELS, _e as DUPLICATE_ACTION_LABELS, y as DeleteAction, B as DrawCircleIcon, ie as DrawLassoIcon, Q as DrawPolygonIcon, W as DrawSquareIcon, b as DuplicateAction, q as EDIT_GEOMETRY_ACTION_LABELS, C as EDIT_PROPERTIES_ACTION_LABELS, m as EditGeometryAction, l as EditPropertiesAction, s as FILL_ACTION_LABELS, u as FillAction, G as GeolocationControls, i as INTERSECT_ACTION_LABELS, X as InteractionModeSection, ge as IntersectAction, S as LABEL_ACTION_LABELS, d as LASSO_DRAW_COLOR_HEX, L as LASSO_TOOLS_LABELS, be as LOCK_ACTION_LABELS, w as LabelAction, z as LassoEditConfirmToolbarUI, U as LassoGeometryToolbarUI, Z as LassoLayerFiltersUI, re as LassoMessageUI, H as LassoSelectIcon, F as LassoToolbarButtonUI, t as LassoToolbarUI, R as LassoToolsInlineUI, de as LassoToolsUI, se as LassoUndoRedoUI, r as LockAction, A as MeasurementToolsUI, ne as REDO_ACTION_LABELS, le as RedoAction, $ as ResponsiveDrawer, I as RulerIcon, a as SPLIT_ACTION_LABELS, h as STROKE_ACTION_LABELS, x as SUBTRACT_ACTION_LABELS, k as SelectToolIcon, Ce as SmartTooltip, he as SpatialMaskIcon, g as SplitAction, _ as StrokeAction, o as SubtractAction, D as TOGGLE_TYPE_ACTION_LABELS, ye as TextAaIcon, v as ToggleTypeAction, xe as Tooltip, K as UNDO_ACTION_LABELS, J as UNION_ACTION_LABELS, j as UndoAction, n as UnionAction, N as UnionIcon, oe as ZoomControlsUI, E as isLassoDrawColorPreset, T as normalizeHex, Se as setTooltipEnterDelay, V as useLassoGeometryToolbarAction, ee as useLassoToolsUIArgs };
@@ -1,4 +1,4 @@
1
- import "./components-CTUpdQuj.js";
1
+ import "./components-DDJU4m0t.js";
2
2
  import { n as e } from "./smart-tooltip-Dc_rHhyI.js";
3
3
  import { E as t } from "./stores-DcBOZlU3.js";
4
4
  import { c as n } from "react/compiler-runtime";
@@ -240,4 +240,4 @@ function N(e, ...t) {
240
240
  //#endregion
241
241
  export { k as a, E as c, y as d, v as f, A as i, T as l, M as n, O as o, C as p, j as r, D as s, N as t, w as u };
242
242
 
243
- //# sourceMappingURL=download-B4sykOFm.js.map
243
+ //# sourceMappingURL=download-DIj1Iz0t.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"download-B4sykOFm.js","names":["Icon = DownloadIcon","{\n items,\n labels,\n icon: Icon = DownloadIcon,\n iconProps,\n onError,\n}: DownloadProps","{ ...DEFAULT_DOWNLOAD_LABELS, ...labels }","(e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }","useCallback((e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }, [])","() => setAnchorEl(null)","useCallback(() => setAnchorEl(null), [])","(item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n }","{ url, filename, revoke }","e","useCallback(\n (item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n },\n [close, id, isDownloading, onError],\n )","isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )","<IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>","triggerActive ? 'active' : undefined","anchorEl ? 'true' : undefined","isDownloading || undefined","error ? 'true' : undefined","isDownloading || items.length === 0","<Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>","{\n vertical: 'bottom',\n horizontal: 'right',\n }","{\n vertical: 'top',\n horizontal: 'right',\n }","items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))","(item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n )","item","<Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>","Boolean(anchorEl)","<>\n <Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>\n <Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>\n </>","_c","DownloadIcon","Tooltip","_Fragment","<ImageOutlined {...props} />","<ArticleOutlined {...props} />","<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 />","<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>","_c","Tooltip$1","WidgetContext","html2canvas","CSV_FORMULA_PREFIX","toCsvString","escape","cell","value","rows","row","downloadToCSV","csv","blob","url","UNSAFE_FILENAME_CHARS","MAX_FILENAME_LENGTH","sanitizeFilename","name","triggerLinkDownload","args","a","filename","downloadDOMToPNG","opts","canvas","element","pixelRatio","DEFAULT_DOWNLOAD_LABELS","Download","$","c","t1","t0","Icon","FileDownload","t2","labels","_labels","anchorEl","setAnchorEl","useState","id","useContext","isDownloading","setIsDownloading","error","setError","t3","open","t4","close","t5","onError","item","t6","revoke","e_0","err","onSelect","triggerLabel","triggerActive","t7","t8","t9","items","t10","t11","iconProps","CircularProgress","t12","IconButton","t13","t14","t15","t16","t17","t18","MenuItem","item_0","ListItemIcon","ListItemText","Menu","t19","Fragment","PNGIcon","props","ImageOutlined","MarkdownIcon","ArticleOutlined","CSVIcon","SvgIcon","DOWNLOAD_ITEM_IDS","buildPngDownloadItem","el","ctx","handle","buildCsvDownloadItem","withPngItem"],"sources":["../src/widgets/actions/download/exports.ts","../src/widgets/actions/download/labels.ts","../src/widgets/actions/download/download.tsx","../src/widgets/actions/download/icons.tsx","../src/widgets/actions/download/constants.ts","../src/widgets/actions/download/png-item.tsx","../src/widgets/actions/download/csv-item.tsx","../src/widgets/actions/download/widget-download.ts"],"sourcesContent":["/**\n * Pure download helpers — no React, no DOM lifecycle outside the link click.\n * Component layer in {@link ./download.tsx} orchestrates these.\n */\n\nimport html2canvas from 'html2canvas'\n\nexport interface DownloadHandle {\n url: string\n revoke: () => void\n}\n\n// Leading characters that Excel / Sheets / Numbers interpret as a formula\n// when the CSV is opened. Prefixing the cell with a single quote forces the\n// spreadsheet app to render it as plain text. Tab and CR are included\n// because some tooling strips leading whitespace before formula evaluation.\nconst CSV_FORMULA_PREFIX = /^[=+\\-@\\t\\r]/\n\nexport function toCsvString(rows: readonly (readonly unknown[])[]): string {\n const escape = (cell: unknown): string => {\n if (cell == null) return ''\n let value =\n typeof cell === 'string'\n ? cell\n : typeof cell === 'number' || typeof cell === 'boolean'\n ? String(cell)\n : JSON.stringify(cell)\n if (CSV_FORMULA_PREFIX.test(value)) value = `'${value}`\n if (/[\",\\n\\r]/.test(value)) return `\"${value.replace(/\"/g, '\"\"')}\"`\n return value\n }\n return rows.map((row) => row.map(escape).join(',')).join('\\n')\n}\n\nexport function downloadToCSV(\n rows: readonly (readonly unknown[])[],\n): DownloadHandle {\n const csv = toCsvString(rows)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n return { url, revoke: () => URL.revokeObjectURL(url) }\n}\n\n// Path separators, NUL, and ASCII control chars. Filenames containing these\n// can confuse the browser download stack and historically enabled\n// Content-Disposition-style trickery.\n// eslint-disable-next-line no-control-regex\nconst UNSAFE_FILENAME_CHARS = /[\\\\/\\x00-\\x1f\\x7f]/g\nconst MAX_FILENAME_LENGTH = 200\n\nexport function sanitizeFilename(name: string): string {\n return (\n (name ?? '')\n .replace(UNSAFE_FILENAME_CHARS, '_')\n .slice(0, MAX_FILENAME_LENGTH) || 'download'\n )\n}\n\n/**\n * Triggers a browser download for the given URL by synthesizing an anchor,\n * clicking it, and removing it. Pure DOM — no React. The filename is run\n * through {@link sanitizeFilename} before being assigned to `a.download`.\n */\nexport function triggerLinkDownload(args: {\n url: string\n filename: string\n}): void {\n const { url, filename } = args\n const a = document.createElement('a')\n a.href = url\n a.download = sanitizeFilename(filename)\n a.style.display = 'none'\n document.body.appendChild(a)\n a.click()\n a.remove()\n}\n\nexport interface DownloadDOMToPNGOptions {\n element: HTMLElement\n /** html2canvas `scale`. Default 2 — crisp on hi-DPI displays. */\n pixelRatio?: number\n /**\n * html2canvas `backgroundColor`. Default `null` (transparent) so the PNG\n * inherits whatever surface it lands on when the user pastes it.\n */\n backgroundColor?: string | null\n}\n\n/**\n * Rasterise an HTMLElement to PNG via `html2canvas` and wrap the result in\n * a `DownloadHandle`. The caller is responsible for calling `handle.revoke`\n * after the link click is dispatched. Used by per-widget download configs\n * to power the PNG menu item.\n */\nexport async function downloadDOMToPNG(\n opts: DownloadDOMToPNGOptions,\n): Promise<DownloadHandle> {\n const { element, pixelRatio = 2, backgroundColor = null } = opts\n const canvas = await html2canvas(element, {\n scale: pixelRatio,\n backgroundColor,\n useCORS: true,\n logging: false,\n })\n return new Promise<DownloadHandle>((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('[widgets] toBlob() returned null'))\n return\n }\n const url = URL.createObjectURL(blob)\n resolve({ url, revoke: () => URL.revokeObjectURL(url) })\n }, 'image/png')\n })\n}\n","export interface DownloadLabels {\n trigger: string\n /** Tooltip / aria-label shown on the trigger while a download is in flight. */\n loading: string\n /**\n * Tooltip / aria-label shown on the trigger after a download failed. Cleared\n * the next time the menu opens.\n */\n error: string\n}\n\nexport const DEFAULT_DOWNLOAD_LABELS: DownloadLabels = {\n trigger: 'Download',\n loading: 'Downloading…',\n error: 'Download failed',\n}\n","import { useCallback, useContext, useState, type ComponentType } from 'react'\nimport {\n CircularProgress,\n IconButton,\n ListItemIcon,\n ListItemText,\n Menu,\n MenuItem,\n type SvgIconProps,\n} from '@mui/material'\nimport { FileDownload as DownloadIcon } from '@mui/icons-material'\nimport { Tooltip } from '../../../components'\nimport { WidgetContext } from '../../stores'\nimport { triggerLinkDownload } from './exports'\nimport type { DownloadItem } from './types'\nimport { DEFAULT_DOWNLOAD_LABELS, type DownloadLabels } from './labels'\n\nexport interface DownloadProps {\n items: readonly DownloadItem[]\n labels?: Partial<DownloadLabels>\n icon?: ComponentType<SvgIconProps>\n iconProps?: SvgIconProps\n /**\n * Fires when an item's `resolve()` rejects (or any step in the click-through\n * path throws). The error is normalised to a real `Error` first. Surface\n * to your telemetry / toast layer here; the trigger already shows a visual\n * error indicator independently.\n */\n onError?: (err: Error) => void\n}\n\nexport function Download({\n items,\n labels,\n icon: Icon = DownloadIcon,\n iconProps,\n onError,\n}: DownloadProps) {\n const _labels = { ...DEFAULT_DOWNLOAD_LABELS, ...labels }\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)\n const id = useContext(WidgetContext)\n const [isDownloading, setIsDownloading] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n const open = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }, [])\n const close = useCallback(() => setAnchorEl(null), [])\n\n const onSelect = useCallback(\n (item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n },\n [close, id, isDownloading, onError],\n )\n\n const triggerLabel = isDownloading\n ? _labels.loading\n : error\n ? _labels.error\n : _labels.trigger\n const triggerActive = Boolean(anchorEl) || isDownloading\n\n return (\n <>\n <Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>\n <Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>\n </>\n )\n}\n","import { SvgIcon, type SvgIconProps } from '@mui/material'\nimport { ArticleOutlined, 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 * Generic \"document\" glyph used for the Markdown download item. Wraps MUI's\n * `ArticleOutlined`, mirroring {@link PNGIcon}, so the Markdown menu item\n * carries an icon consistent with the other download formats.\n */\nexport function MarkdownIcon(props: SvgIconProps) {\n return <ArticleOutlined {...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","/**\n * Canonical `DownloadItem.id`s for the built-in download formats. Centralised\n * so the per-format builders — and anything that keys off the id (menu logic,\n * tests) — reference one source of truth instead of re-typing string literals.\n * The Markdown format keeps the short `'md'` id to match its `.md` extension.\n */\nexport const DOWNLOAD_ITEM_IDS = {\n png: 'png',\n csv: 'csv',\n markdown: 'md',\n} as const\n\nexport type DownloadItemId =\n (typeof DOWNLOAD_ITEM_IDS)[keyof typeof DOWNLOAD_ITEM_IDS]\n","import { DOWNLOAD_ITEM_IDS } from './constants'\nimport { downloadDOMToPNG } from './exports'\nimport { PNGIcon } from './icons'\nimport type { DownloadContext, 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/stores`.\n */\n getCaptureEl: (ctx: DownloadContext) => 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: DOWNLOAD_ITEM_IDS.png,\n label: args.label ?? 'PNG',\n icon: <PNGIcon fontSize='small' />,\n resolve: async (ctx) => {\n const el = args.getCaptureEl(ctx)\n if (!el) {\n throw new Error('[widgets] 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","import { DOWNLOAD_ITEM_IDS } from './constants'\nimport { downloadToCSV } from './exports'\nimport { CSVIcon } from './icons'\nimport type { DownloadContext, DownloadItem } from './types'\n\ninterface BuildCsvDownloadItemBase {\n /** Base filename (without extension). The item appends `.csv`. */\n filename: string\n /** Override the menu label. Default `'CSV'`. */\n label?: string\n}\n\n/**\n * Args for {@link buildCsvDownloadItem}. A discriminated union: a caller must\n * supply exactly one content source — `getRows` (rows serialised through the\n * shared `toCsvString`) or `getCsv` (a pre-built CSV string). The `?: never`\n * arms make passing both — or neither — a compile-time error.\n */\nexport type BuildCsvDownloadItemArgs =\n | (BuildCsvDownloadItemBase & {\n /**\n * Builds the CSV rows at click time. Used by most widgets — rows are run\n * through `toCsvString` so escaping (incl. the spreadsheet\n * formula-injection guard) stays consistent across widgets.\n */\n getRows: (ctx: DownloadContext) => readonly (readonly unknown[])[]\n getCsv?: never\n })\n | (BuildCsvDownloadItemBase & {\n /**\n * Returns a pre-built CSV string at click time. Escape hatch for widgets\n * (e.g. Table) that already serialise their own CSV with bespoke\n * header/cell handling.\n */\n getCsv: (ctx: DownloadContext) => string\n getRows?: never\n })\n\n/**\n * Builds the standard CSV `DownloadItem` used by every per-widget download\n * config. Centralised so the menu label, icon, and `.csv` filename suffix stay\n * consistent across widgets — mirrors {@link buildPngDownloadItem} so neither\n * format can drift again.\n */\nexport function buildCsvDownloadItem(\n args: BuildCsvDownloadItemArgs,\n): DownloadItem {\n return {\n id: DOWNLOAD_ITEM_IDS.csv,\n label: args.label ?? 'CSV',\n icon: <CSVIcon fontSize='small' />,\n resolve: (ctx) => {\n if (args.getCsv) {\n const csv = args.getCsv(ctx)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n return Promise.resolve({\n url,\n filename: `${args.filename}.csv`,\n revoke: () => URL.revokeObjectURL(url),\n })\n }\n const handle = downloadToCSV(args.getRows(ctx))\n return Promise.resolve({\n url: handle.url,\n filename: `${args.filename}.csv`,\n revoke: handle.revoke,\n })\n },\n }\n}\n","import { buildPngDownloadItem } from './png-item'\nimport type { DownloadContext, DownloadItem } from './types'\n\n/** Arguments every per-widget `create*DownloadConfig` shares. */\nexport interface WidgetDownloadArgs<T> {\n /** Base filename (without extension). */\n filename: string\n /**\n * Reads the data to export at click time. `ctx.id` is the store the menu\n * was opened from — the fullscreen dialog's own while it's open. See\n * `widgetStoreData` in `widgets/stores`.\n */\n getData: (ctx: DownloadContext) => T\n /**\n * Reads the element the PNG item rasterises. When given, a PNG item is\n * prepended to the menu. See `widgetCaptureEl` in `widgets/stores`.\n */\n getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null\n /** PNG `pixelRatio` (default 2). */\n pngPixelRatio?: number\n /** PNG `backgroundColor` (default transparent). */\n pngBackgroundColor?: string | null\n}\n\n/**\n * `items`, with a PNG item in front when `args.getCaptureEl` is given — the\n * order every widget's download menu uses.\n */\nexport function withPngItem(\n args: Omit<WidgetDownloadArgs<unknown>, 'getData'>,\n ...items: DownloadItem[]\n): DownloadItem[] {\n if (!args.getCaptureEl) return items\n const png = buildPngDownloadItem({\n filename: args.filename,\n getCaptureEl: args.getCaptureEl,\n pixelRatio: args.pngPixelRatio,\n backgroundColor: args.pngBackgroundColor,\n })\n return [png, ...items]\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"download-DIj1Iz0t.js","names":["Icon = DownloadIcon","{\n items,\n labels,\n icon: Icon = DownloadIcon,\n iconProps,\n onError,\n}: DownloadProps","{ ...DEFAULT_DOWNLOAD_LABELS, ...labels }","(e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }","useCallback((e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }, [])","() => setAnchorEl(null)","useCallback(() => setAnchorEl(null), [])","(item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n }","{ url, filename, revoke }","e","useCallback(\n (item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n },\n [close, id, isDownloading, onError],\n )","isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )","<IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>","triggerActive ? 'active' : undefined","anchorEl ? 'true' : undefined","isDownloading || undefined","error ? 'true' : undefined","isDownloading || items.length === 0","<Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>","{\n vertical: 'bottom',\n horizontal: 'right',\n }","{\n vertical: 'top',\n horizontal: 'right',\n }","items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))","(item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n )","item","<Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>","Boolean(anchorEl)","<>\n <Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>\n <Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>\n </>","_c","DownloadIcon","Tooltip","_Fragment","<ImageOutlined {...props} />","<ArticleOutlined {...props} />","<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 />","<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>","_c","Tooltip$1","WidgetContext","html2canvas","CSV_FORMULA_PREFIX","toCsvString","escape","cell","value","rows","row","downloadToCSV","csv","blob","url","UNSAFE_FILENAME_CHARS","MAX_FILENAME_LENGTH","sanitizeFilename","name","triggerLinkDownload","args","a","filename","downloadDOMToPNG","opts","canvas","element","pixelRatio","DEFAULT_DOWNLOAD_LABELS","Download","$","c","t1","t0","Icon","FileDownload","t2","labels","_labels","anchorEl","setAnchorEl","useState","id","useContext","isDownloading","setIsDownloading","error","setError","t3","open","t4","close","t5","onError","item","t6","revoke","e_0","err","onSelect","triggerLabel","triggerActive","t7","t8","t9","items","t10","t11","iconProps","CircularProgress","t12","IconButton","t13","t14","t15","t16","t17","t18","MenuItem","item_0","ListItemIcon","ListItemText","Menu","t19","Fragment","PNGIcon","props","ImageOutlined","MarkdownIcon","ArticleOutlined","CSVIcon","SvgIcon","DOWNLOAD_ITEM_IDS","buildPngDownloadItem","el","ctx","handle","buildCsvDownloadItem","withPngItem"],"sources":["../src/widgets/actions/download/exports.ts","../src/widgets/actions/download/labels.ts","../src/widgets/actions/download/download.tsx","../src/widgets/actions/download/icons.tsx","../src/widgets/actions/download/constants.ts","../src/widgets/actions/download/png-item.tsx","../src/widgets/actions/download/csv-item.tsx","../src/widgets/actions/download/widget-download.ts"],"sourcesContent":["/**\n * Pure download helpers — no React, no DOM lifecycle outside the link click.\n * Component layer in {@link ./download.tsx} orchestrates these.\n */\n\nimport html2canvas from 'html2canvas'\n\nexport interface DownloadHandle {\n url: string\n revoke: () => void\n}\n\n// Leading characters that Excel / Sheets / Numbers interpret as a formula\n// when the CSV is opened. Prefixing the cell with a single quote forces the\n// spreadsheet app to render it as plain text. Tab and CR are included\n// because some tooling strips leading whitespace before formula evaluation.\nconst CSV_FORMULA_PREFIX = /^[=+\\-@\\t\\r]/\n\nexport function toCsvString(rows: readonly (readonly unknown[])[]): string {\n const escape = (cell: unknown): string => {\n if (cell == null) return ''\n let value =\n typeof cell === 'string'\n ? cell\n : typeof cell === 'number' || typeof cell === 'boolean'\n ? String(cell)\n : JSON.stringify(cell)\n if (CSV_FORMULA_PREFIX.test(value)) value = `'${value}`\n if (/[\",\\n\\r]/.test(value)) return `\"${value.replace(/\"/g, '\"\"')}\"`\n return value\n }\n return rows.map((row) => row.map(escape).join(',')).join('\\n')\n}\n\nexport function downloadToCSV(\n rows: readonly (readonly unknown[])[],\n): DownloadHandle {\n const csv = toCsvString(rows)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n return { url, revoke: () => URL.revokeObjectURL(url) }\n}\n\n// Path separators, NUL, and ASCII control chars. Filenames containing these\n// can confuse the browser download stack and historically enabled\n// Content-Disposition-style trickery.\n// eslint-disable-next-line no-control-regex\nconst UNSAFE_FILENAME_CHARS = /[\\\\/\\x00-\\x1f\\x7f]/g\nconst MAX_FILENAME_LENGTH = 200\n\nexport function sanitizeFilename(name: string): string {\n return (\n (name ?? '')\n .replace(UNSAFE_FILENAME_CHARS, '_')\n .slice(0, MAX_FILENAME_LENGTH) || 'download'\n )\n}\n\n/**\n * Triggers a browser download for the given URL by synthesizing an anchor,\n * clicking it, and removing it. Pure DOM — no React. The filename is run\n * through {@link sanitizeFilename} before being assigned to `a.download`.\n */\nexport function triggerLinkDownload(args: {\n url: string\n filename: string\n}): void {\n const { url, filename } = args\n const a = document.createElement('a')\n a.href = url\n a.download = sanitizeFilename(filename)\n a.style.display = 'none'\n document.body.appendChild(a)\n a.click()\n a.remove()\n}\n\nexport interface DownloadDOMToPNGOptions {\n element: HTMLElement\n /** html2canvas `scale`. Default 2 — crisp on hi-DPI displays. */\n pixelRatio?: number\n /**\n * html2canvas `backgroundColor`. Default `null` (transparent) so the PNG\n * inherits whatever surface it lands on when the user pastes it.\n */\n backgroundColor?: string | null\n}\n\n/**\n * Rasterise an HTMLElement to PNG via `html2canvas` and wrap the result in\n * a `DownloadHandle`. The caller is responsible for calling `handle.revoke`\n * after the link click is dispatched. Used by per-widget download configs\n * to power the PNG menu item.\n */\nexport async function downloadDOMToPNG(\n opts: DownloadDOMToPNGOptions,\n): Promise<DownloadHandle> {\n const { element, pixelRatio = 2, backgroundColor = null } = opts\n const canvas = await html2canvas(element, {\n scale: pixelRatio,\n backgroundColor,\n useCORS: true,\n logging: false,\n })\n return new Promise<DownloadHandle>((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('[widgets] toBlob() returned null'))\n return\n }\n const url = URL.createObjectURL(blob)\n resolve({ url, revoke: () => URL.revokeObjectURL(url) })\n }, 'image/png')\n })\n}\n","export interface DownloadLabels {\n trigger: string\n /** Tooltip / aria-label shown on the trigger while a download is in flight. */\n loading: string\n /**\n * Tooltip / aria-label shown on the trigger after a download failed. Cleared\n * the next time the menu opens.\n */\n error: string\n}\n\nexport const DEFAULT_DOWNLOAD_LABELS: DownloadLabels = {\n trigger: 'Download',\n loading: 'Downloading…',\n error: 'Download failed',\n}\n","import { useCallback, useContext, useState, type ComponentType } from 'react'\nimport {\n CircularProgress,\n IconButton,\n ListItemIcon,\n ListItemText,\n Menu,\n MenuItem,\n type SvgIconProps,\n} from '@mui/material'\nimport { FileDownload as DownloadIcon } from '@mui/icons-material'\nimport { Tooltip } from '../../../components'\nimport { WidgetContext } from '../../stores'\nimport { triggerLinkDownload } from './exports'\nimport type { DownloadItem } from './types'\nimport { DEFAULT_DOWNLOAD_LABELS, type DownloadLabels } from './labels'\n\nexport interface DownloadProps {\n items: readonly DownloadItem[]\n labels?: Partial<DownloadLabels>\n icon?: ComponentType<SvgIconProps>\n iconProps?: SvgIconProps\n /**\n * Fires when an item's `resolve()` rejects (or any step in the click-through\n * path throws). The error is normalised to a real `Error` first. Surface\n * to your telemetry / toast layer here; the trigger already shows a visual\n * error indicator independently.\n */\n onError?: (err: Error) => void\n}\n\nexport function Download({\n items,\n labels,\n icon: Icon = DownloadIcon,\n iconProps,\n onError,\n}: DownloadProps) {\n const _labels = { ...DEFAULT_DOWNLOAD_LABELS, ...labels }\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)\n const id = useContext(WidgetContext)\n const [isDownloading, setIsDownloading] = useState(false)\n const [error, setError] = useState<Error | null>(null)\n\n const open = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {\n // Clear any prior error when the user re-engages the menu — the next\n // attempt is a fresh interaction, not a continuation of the failed one.\n setError(null)\n setAnchorEl(e.currentTarget)\n }, [])\n const close = useCallback(() => setAnchorEl(null), [])\n\n const onSelect = useCallback(\n (item: DownloadItem) => {\n // Single-flight: ignore re-entry while a download is already in flight.\n // The trigger is `disabled` and the menu is closed during that window,\n // so this guard mostly defends against future call sites that bypass\n // the menu (e.g. keyboard shortcuts).\n if (item.disabled || isDownloading) return\n close()\n setIsDownloading(true)\n setError(null)\n item\n .resolve({ id })\n .then(({ url, filename, revoke }) => {\n // `item.filename` overrides what `resolve()` returns — lets consumers\n // template the filename with widget metadata declaratively.\n triggerLinkDownload({ url, filename: item.filename ?? filename })\n // Defer revoke past the current task so Safari / Firefox-on-slow-disk\n // can dispatch the download before the blob URL is invalidated.\n // Synchronous revoke here can silently cancel the download.\n if (revoke) setTimeout(revoke, 0)\n })\n .catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err))\n setError(e)\n onError?.(e)\n })\n .finally(() => {\n setIsDownloading(false)\n })\n },\n [close, id, isDownloading, onError],\n )\n\n const triggerLabel = isDownloading\n ? _labels.loading\n : error\n ? _labels.error\n : _labels.trigger\n const triggerActive = Boolean(anchorEl) || isDownloading\n\n return (\n <>\n <Tooltip title={triggerLabel}>\n <IconButton\n size='small'\n aria-label={triggerLabel}\n aria-haspopup='true'\n aria-expanded={anchorEl ? 'true' : undefined}\n aria-busy={isDownloading || undefined}\n data-error={error ? 'true' : undefined}\n disabled={isDownloading || items.length === 0}\n onClick={open}\n className={triggerActive ? 'active' : undefined}\n >\n {isDownloading ? (\n <CircularProgress size={18} color='inherit' />\n ) : (\n <Icon fontSize='small' {...iconProps} />\n )}\n </IconButton>\n </Tooltip>\n <Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={close}\n variant='menu'\n elevation={8}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'right',\n }}\n transformOrigin={{\n vertical: 'top',\n horizontal: 'right',\n }}\n >\n {items.map((item) => (\n <MenuItem\n key={item.id}\n disabled={item.disabled}\n onClick={() => void onSelect(item)}\n >\n {item.icon && (\n <ListItemIcon sx={{ color: 'text.primary' }}>\n {item.icon}\n </ListItemIcon>\n )}\n <ListItemText>{item.label}</ListItemText>\n </MenuItem>\n ))}\n </Menu>\n </>\n )\n}\n","import { SvgIcon, type SvgIconProps } from '@mui/material'\nimport { ArticleOutlined, 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 * Generic \"document\" glyph used for the Markdown download item. Wraps MUI's\n * `ArticleOutlined`, mirroring {@link PNGIcon}, so the Markdown menu item\n * carries an icon consistent with the other download formats.\n */\nexport function MarkdownIcon(props: SvgIconProps) {\n return <ArticleOutlined {...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","/**\n * Canonical `DownloadItem.id`s for the built-in download formats. Centralised\n * so the per-format builders — and anything that keys off the id (menu logic,\n * tests) — reference one source of truth instead of re-typing string literals.\n * The Markdown format keeps the short `'md'` id to match its `.md` extension.\n */\nexport const DOWNLOAD_ITEM_IDS = {\n png: 'png',\n csv: 'csv',\n markdown: 'md',\n} as const\n\nexport type DownloadItemId =\n (typeof DOWNLOAD_ITEM_IDS)[keyof typeof DOWNLOAD_ITEM_IDS]\n","import { DOWNLOAD_ITEM_IDS } from './constants'\nimport { downloadDOMToPNG } from './exports'\nimport { PNGIcon } from './icons'\nimport type { DownloadContext, 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/stores`.\n */\n getCaptureEl: (ctx: DownloadContext) => 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: DOWNLOAD_ITEM_IDS.png,\n label: args.label ?? 'PNG',\n icon: <PNGIcon fontSize='small' />,\n resolve: async (ctx) => {\n const el = args.getCaptureEl(ctx)\n if (!el) {\n throw new Error('[widgets] 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","import { DOWNLOAD_ITEM_IDS } from './constants'\nimport { downloadToCSV } from './exports'\nimport { CSVIcon } from './icons'\nimport type { DownloadContext, DownloadItem } from './types'\n\ninterface BuildCsvDownloadItemBase {\n /** Base filename (without extension). The item appends `.csv`. */\n filename: string\n /** Override the menu label. Default `'CSV'`. */\n label?: string\n}\n\n/**\n * Args for {@link buildCsvDownloadItem}. A discriminated union: a caller must\n * supply exactly one content source — `getRows` (rows serialised through the\n * shared `toCsvString`) or `getCsv` (a pre-built CSV string). The `?: never`\n * arms make passing both — or neither — a compile-time error.\n */\nexport type BuildCsvDownloadItemArgs =\n | (BuildCsvDownloadItemBase & {\n /**\n * Builds the CSV rows at click time. Used by most widgets — rows are run\n * through `toCsvString` so escaping (incl. the spreadsheet\n * formula-injection guard) stays consistent across widgets.\n */\n getRows: (ctx: DownloadContext) => readonly (readonly unknown[])[]\n getCsv?: never\n })\n | (BuildCsvDownloadItemBase & {\n /**\n * Returns a pre-built CSV string at click time. Escape hatch for widgets\n * (e.g. Table) that already serialise their own CSV with bespoke\n * header/cell handling.\n */\n getCsv: (ctx: DownloadContext) => string\n getRows?: never\n })\n\n/**\n * Builds the standard CSV `DownloadItem` used by every per-widget download\n * config. Centralised so the menu label, icon, and `.csv` filename suffix stay\n * consistent across widgets — mirrors {@link buildPngDownloadItem} so neither\n * format can drift again.\n */\nexport function buildCsvDownloadItem(\n args: BuildCsvDownloadItemArgs,\n): DownloadItem {\n return {\n id: DOWNLOAD_ITEM_IDS.csv,\n label: args.label ?? 'CSV',\n icon: <CSVIcon fontSize='small' />,\n resolve: (ctx) => {\n if (args.getCsv) {\n const csv = args.getCsv(ctx)\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n return Promise.resolve({\n url,\n filename: `${args.filename}.csv`,\n revoke: () => URL.revokeObjectURL(url),\n })\n }\n const handle = downloadToCSV(args.getRows(ctx))\n return Promise.resolve({\n url: handle.url,\n filename: `${args.filename}.csv`,\n revoke: handle.revoke,\n })\n },\n }\n}\n","import { buildPngDownloadItem } from './png-item'\nimport type { DownloadContext, DownloadItem } from './types'\n\n/** Arguments every per-widget `create*DownloadConfig` shares. */\nexport interface WidgetDownloadArgs<T> {\n /** Base filename (without extension). */\n filename: string\n /**\n * Reads the data to export at click time. `ctx.id` is the store the menu\n * was opened from — the fullscreen dialog's own while it's open. See\n * `widgetStoreData` in `widgets/stores`.\n */\n getData: (ctx: DownloadContext) => T\n /**\n * Reads the element the PNG item rasterises. When given, a PNG item is\n * prepended to the menu. See `widgetCaptureEl` in `widgets/stores`.\n */\n getCaptureEl?: (ctx: DownloadContext) => HTMLElement | null\n /** PNG `pixelRatio` (default 2). */\n pngPixelRatio?: number\n /** PNG `backgroundColor` (default transparent). */\n pngBackgroundColor?: string | null\n}\n\n/**\n * `items`, with a PNG item in front when `args.getCaptureEl` is given — the\n * order every widget's download menu uses.\n */\nexport function withPngItem(\n args: Omit<WidgetDownloadArgs<unknown>, 'getData'>,\n ...items: DownloadItem[]\n): DownloadItem[] {\n if (!args.getCaptureEl) return items\n const png = buildPngDownloadItem({\n filename: args.filename,\n getCaptureEl: args.getCaptureEl,\n pixelRatio: args.pngPixelRatio,\n backgroundColor: args.pngBackgroundColor,\n })\n return [png, ...items]\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { t as e } from "./styles-BCEBm7xh.js";
1
+ import { t as e } from "./styles-yKcpfyNV.js";
2
2
  import { C as t, D as n } from "./stores-DcBOZlU3.js";
3
- import { n as r, t as i } from "./download-B4sykOFm.js";
4
- import { t as a } from "./utils-C-eWwOhE.js";
3
+ import { n as r, t as i } from "./download-DIj1Iz0t.js";
4
+ import { t as a } from "./utils-Dw4y8tiq.js";
5
5
  import { c as o } from "react/compiler-runtime";
6
6
  import { Avatar as s, Box as c, Chip as l, Skeleton as u, Typography as d } from "@mui/material";
7
7
  import { jsx as f, jsxs as p } from "react/jsx-runtime";
@@ -273,4 +273,4 @@ function j(e) {
273
273
  //#endregion
274
274
  export { C as a, b as c, g as d, w as i, y as l, k as n, S as o, D as r, x as s, j as t, v as u };
275
275
 
276
- //# sourceMappingURL=formula-DJS-JLpk.js.map
276
+ //# sourceMappingURL=formula-CSHhAoyW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formula-DJS-JLpk.js","names":["{\n value,\n label,\n severity,\n format,\n ChipProps: chipProps,\n}: DeltaProps","label ?? (format ? format(value) : defaultPercentFormatter.format(value))","{ fontWeight: 600 }","<Chip\n size='small'\n color={SEVERITY_TO_COLOR[resolvedSeverity]}\n label={text}\n sx={{ fontWeight: 600 }}\n {...chipProps}\n />","SEVERITY_TO_COLOR[resolvedSeverity]","_c","{ children, TypographyProps }: NoteProps","mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )","sx","<Typography\n variant='caption'\n component='p'\n {...TypographyProps}\n sx={mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )}\n >\n {children}\n </Typography>","_c","{ children, TypographyProps }: PrefixProps","{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }","<Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","{\n name,\n color,\n size = 24,\n AvatarProps: avatarProps,\n}: SeriesProps","size = 24","name.charAt(0).toUpperCase()","color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary'","{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }","size * 0.5","color ?? 'action.disabledBackground'","<Avatar\n aria-label={name}\n sx={{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }}\n {...avatarProps}\n >\n {letter}\n </Avatar>","_c","{ children, TypographyProps }: SuffixProps","{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }","<Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","{ children, color, TypographyProps }: ValueProps","{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }","color ?? 'text.primary'","<Typography\n variant='h5'\n component='span'\n sx={{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","styles","{ items, formatter, ref }: FormulaUIProps","items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))","(item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n )","<Box ref={ref} sx={styles.root}>\n {items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))}\n </Box>","_c","styles","<FormulaUI items={slice.data} formatter={slice.formatter} />","data","formatter","_c","{ count = 1 }: FormulaSkeletonProps","count = 1","<Box sx={styles.root}>\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n ))}\n </Box>","(_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n )","_c","mergeSx","useWidgetShallow","useWidgetId","buildCsvDownloadItem","withPngItem","resolveThemeColor","defaultPercentFormatter","SEVERITY_TO_COLOR","Delta","$","c","chipProps","t0","resolvedSeverity","severity","inferSeverity","value","t1","format","label","text","t2","t3","t4","Chip","Note","TypographyProps","children","Typography","Prefix","Series","avatarProps","size","name","letter","color","t5","theme","t6","t7","Avatar","Suffix","Value","styles$1","FormulaUI","formatter","items","Box","item","i","ref","EMPTY_DATA","formulaSelector","s","Formula","id","slice","styles","FormulaSkeleton","count","_temp","Skeleton","createFormulaDownloadConfig","args","data","ctx","rows","d"],"sources":["../src/widgets/formula/delta.tsx","../src/widgets/formula/note.tsx","../src/widgets/formula/prefix.tsx","../src/widgets/formula/series.tsx","../src/widgets/formula/suffix.tsx","../src/widgets/formula/value.tsx","../src/widgets/formula/style.ts","../src/widgets/formula/formula-ui.tsx","../src/widgets/formula/formula.tsx","../src/widgets/formula/skeleton.tsx","../src/widgets/formula/download.tsx"],"sourcesContent":["import { Chip, type ChipProps } from '@mui/material'\nimport type { DeltaSeverity } from './types'\n\nexport interface DeltaProps {\n /** Numeric magnitude. Used for the default percent label and severity inference. */\n value: number\n /** Pre-formatted label override; skips the default percent formatter. */\n label?: string\n /**\n * Visual treatment. When omitted it's derived from the sign of `value`:\n * `>0 → 'positive'`, `<0 → 'negative'`, `=0 → 'neutral'`.\n */\n severity?: DeltaSeverity\n /** Override the default `Intl.NumberFormat({style:'percent'})` formatter. */\n format?: (value: number) => string\n ChipProps?: ChipProps\n}\n\nconst defaultPercentFormatter = new Intl.NumberFormat(undefined, {\n style: 'percent',\n signDisplay: 'always',\n maximumFractionDigits: 1,\n})\n\nconst SEVERITY_TO_COLOR: Record<DeltaSeverity, ChipProps['color']> = {\n positive: 'success',\n negative: 'error',\n neutral: 'default',\n}\n\n/**\n * Comparative delta rendered as a small filled chip. Colour is driven by\n * `severity` (or the sign of `value`); content is `label`, the result of\n * `format(value)`, or the default percent format.\n */\nexport function Delta({\n value,\n label,\n severity,\n format,\n ChipProps: chipProps,\n}: DeltaProps) {\n const resolvedSeverity = severity ?? inferSeverity(value)\n const text =\n label ?? (format ? format(value) : defaultPercentFormatter.format(value))\n return (\n <Chip\n size='small'\n color={SEVERITY_TO_COLOR[resolvedSeverity]}\n label={text}\n sx={{ fontWeight: 600 }}\n {...chipProps}\n />\n )\n}\n\nfunction inferSeverity(value: number): DeltaSeverity {\n if (value > 0) return 'positive'\n if (value < 0) return 'negative'\n return 'neutral'\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport { mergeSx } from '../../components/lasso-tool/styles'\n\nexport interface NoteProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Small subdued caption rendered below a Formula value. Use for short\n * descriptive text (e.g. \"Last 7 days\", \"Excludes refunds\").\n */\nexport function Note({ children, TypographyProps }: NoteProps) {\n return (\n <Typography\n variant='caption'\n component='p'\n {...TypographyProps}\n sx={mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )}\n >\n {children}\n </Typography>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\n\nexport interface PrefixProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Prefix slot for a Formula KPI — large, bold, muted (`text.secondary`).\n * Composable building block for custom formula layouts; the canonical\n * {@link FormulaUI} renders one of these per item when `prefix` is set.\n */\nexport function Prefix({ children, TypographyProps }: PrefixProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import { Avatar, type AvatarProps, type Theme } from '@mui/material'\nimport type { PaletteColor } from '../../components/types'\nimport { resolveThemeColor } from '../utils'\n\nexport interface SeriesProps {\n /** Used for the avatar's first-letter glyph and as accessible label. */\n name: string\n /** Avatar background colour (any MUI palette path or CSS colour). */\n color?: PaletteColor\n /** Diameter in px. Default `24`. */\n size?: number\n AvatarProps?: AvatarProps\n}\n\n/**\n * Series indicator — circular avatar showing the first letter of `name`.\n * Background colour is `color` (consumer-supplied) or a neutral disabled\n * colour, with text colour computed for contrast against it.\n */\nexport function Series({\n name,\n color,\n size = 24,\n AvatarProps: avatarProps,\n}: SeriesProps) {\n const letter = name.charAt(0).toUpperCase()\n return (\n <Avatar\n aria-label={name}\n sx={{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }}\n {...avatarProps}\n >\n {letter}\n </Avatar>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\n\nexport interface SuffixProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Suffix slot for a Formula KPI — large, bold, muted (`text.secondary`).\n * Composable building block for custom formula layouts; the canonical\n * {@link FormulaUI} renders one of these per item when `suffix` is set.\n */\nexport function Suffix({ children, TypographyProps }: SuffixProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport type { PaletteColor } from '../../components/types'\n\nexport interface ValueProps {\n children: ReactNode\n /** Override the default `text.primary` color — useful for sparkline tints. */\n color?: PaletteColor\n TypographyProps?: TypographyProps\n}\n\n/**\n * Value slot for a Formula KPI — large, bold, primary text color. Composable\n * building block for custom formula layouts; the canonical {@link FormulaUI}\n * renders one of these per item.\n */\nexport function Value({ children, color, TypographyProps }: ValueProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import type { SxProps, Theme } from '@mui/material'\n\nexport const styles = {\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n alignItems: 'stretch',\n },\n row: {\n display: 'flex',\n alignItems: 'flex-start',\n gap: 1.5,\n },\n body: {\n flex: 1,\n minWidth: 0,\n display: 'flex',\n flexDirection: 'column',\n gap: 0.25,\n },\n valueRow: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: 0.25,\n },\n delta: {\n alignSelf: 'center',\n flexShrink: 0,\n },\n} satisfies Record<string, SxProps<Theme>>\n","import { Box } from '@mui/material'\nimport type { Ref } from 'react'\nimport type { FormulaDataItem } from './types'\nimport { Delta } from './delta'\nimport { Note } from './note'\nimport { Prefix } from './prefix'\nimport { Series } from './series'\nimport { Suffix } from './suffix'\nimport { Value } from './value'\nimport { styles } from './style'\n\nexport interface FormulaUIProps {\n items: readonly FormulaDataItem[]\n /** Number formatter — applied to each item's `value`. */\n formatter?: (value: number) => string\n /** Forwarded to the root `<Box>` so consumers can capture the DOM (PNG export). */\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Pure presentational component for the Formula widget. Each item renders as\n * a row containing (in order): optional `Series` avatar, a body with\n * `[prefix] value [suffix]` plus optional `Note`, and an optional `Delta`\n * chip pushed to the right edge.\n *\n * For fully custom layouts, compose {@link Series}, {@link Prefix},\n * {@link Value}, {@link Suffix}, {@link Note}, and {@link Delta} directly\n * instead of passing `items`.\n */\nexport function FormulaUI({ items, formatter, ref }: FormulaUIProps) {\n return (\n <Box ref={ref} sx={styles.root}>\n {items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))}\n </Box>\n )\n}\n","import { useWidgetId, useWidgetShallow } from '../stores'\nimport { FormulaUI } from './formula-ui'\nimport type { FormulaWidgetData } from './types'\n\ninterface FormulaSlice {\n data: FormulaWidgetData\n formatter?: (value: number) => string\n}\n\n// Stable empty-data sentinel — keeps the slice's `data` reference stable\n// when the underlying store value is null/undefined so the shallow-equality\n// gate in `useWidgetShallow` doesn't re-render every commit.\nconst EMPTY_DATA: FormulaWidgetData = Object.freeze([]) as FormulaWidgetData\n\nconst formulaSelector = (s: {\n data: unknown\n formatter?: (value: number) => string\n}): FormulaSlice => ({\n data: (s.data ?? EMPTY_DATA) as FormulaWidgetData,\n formatter: s.formatter,\n})\n\n/**\n * Stateful Formula bridge — reads `data` (post-pipeline) and `formatter` from\n * the per-widget store and forwards them to the pure {@link FormulaUI}. Sits\n * inside `<Widget.Provider>`. Demonstrates that the composition shell\n * (Provider, Wrapper, State) doesn't care whether the rendered child is\n * ECharts (Bar) or a custom DOM tree (Formula).\n */\nexport function Formula() {\n const id = useWidgetId()\n const slice = useWidgetShallow(id, formulaSelector)\n return <FormulaUI items={slice.data} formatter={slice.formatter} />\n}\n","import { Box, Skeleton } from '@mui/material'\nimport type { SxProps, Theme } from '@mui/material'\n\nconst styles = {\n root: {\n display: 'flex',\n gap: 3,\n alignItems: 'baseline',\n minHeight: 64,\n py: 1,\n },\n block: { width: 120, height: 36 },\n} satisfies Record<string, SxProps<Theme>>\n\nexport interface FormulaSkeletonProps {\n count?: number\n}\n\nexport function FormulaSkeleton({ count = 1 }: FormulaSkeletonProps) {\n return (\n <Box sx={styles.root}>\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n ))}\n </Box>\n )\n}\n","import {\n buildCsvDownloadItem,\n withPngItem,\n type DownloadItem,\n type WidgetDownloadArgs,\n} from '../actions/download'\nimport type { FormulaWidgetData } from './types'\n\nexport type FormulaDownloadConfigArgs = WidgetDownloadArgs<FormulaWidgetData>\n\n/**\n * Builds download items for the Formula widget. Always includes a CSV item\n * with one row per KPI (`prefix`, `value`, `suffix`, `note`, `delta`). When\n * `getCaptureEl` is given, prepends a PNG item that rasterises the captured\n * element via `html2canvas`.\n */\nexport function createFormulaDownloadConfig(\n args: FormulaDownloadConfigArgs,\n): DownloadItem[] {\n return withPngItem(\n args,\n buildCsvDownloadItem({\n filename: args.filename,\n getRows: (ctx) => {\n const data = args.getData(ctx)\n const rows: unknown[][] = [\n ['series', 'prefix', 'value', 'suffix', 'note', 'delta'],\n ]\n for (const d of data) {\n rows.push([\n d.series?.name ?? '',\n d.prefix ?? '',\n d.value,\n d.suffix ?? '',\n d.note ?? '',\n d.delta?.value ?? '',\n ])\n }\n return rows\n },\n }),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"formula-CSHhAoyW.js","names":["{\n value,\n label,\n severity,\n format,\n ChipProps: chipProps,\n}: DeltaProps","label ?? (format ? format(value) : defaultPercentFormatter.format(value))","{ fontWeight: 600 }","<Chip\n size='small'\n color={SEVERITY_TO_COLOR[resolvedSeverity]}\n label={text}\n sx={{ fontWeight: 600 }}\n {...chipProps}\n />","SEVERITY_TO_COLOR[resolvedSeverity]","_c","{ children, TypographyProps }: NoteProps","mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )","sx","<Typography\n variant='caption'\n component='p'\n {...TypographyProps}\n sx={mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )}\n >\n {children}\n </Typography>","_c","{ children, TypographyProps }: PrefixProps","{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }","<Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","{\n name,\n color,\n size = 24,\n AvatarProps: avatarProps,\n}: SeriesProps","size = 24","name.charAt(0).toUpperCase()","color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary'","{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }","size * 0.5","color ?? 'action.disabledBackground'","<Avatar\n aria-label={name}\n sx={{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }}\n {...avatarProps}\n >\n {letter}\n </Avatar>","_c","{ children, TypographyProps }: SuffixProps","{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }","<Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","{ children, color, TypographyProps }: ValueProps","{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }","color ?? 'text.primary'","<Typography\n variant='h5'\n component='span'\n sx={{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }}\n {...TypographyProps}\n >\n {children}\n </Typography>","_c","styles","{ items, formatter, ref }: FormulaUIProps","items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))","(item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n )","<Box ref={ref} sx={styles.root}>\n {items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))}\n </Box>","_c","styles","<FormulaUI items={slice.data} formatter={slice.formatter} />","data","formatter","_c","{ count = 1 }: FormulaSkeletonProps","count = 1","<Box sx={styles.root}>\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n ))}\n </Box>","(_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n )","_c","mergeSx","useWidgetShallow","useWidgetId","buildCsvDownloadItem","withPngItem","resolveThemeColor","defaultPercentFormatter","SEVERITY_TO_COLOR","Delta","$","c","chipProps","t0","resolvedSeverity","severity","inferSeverity","value","t1","format","label","text","t2","t3","t4","Chip","Note","TypographyProps","children","Typography","Prefix","Series","avatarProps","size","name","letter","color","t5","theme","t6","t7","Avatar","Suffix","Value","styles$1","FormulaUI","formatter","items","Box","item","i","ref","EMPTY_DATA","formulaSelector","s","Formula","id","slice","styles","FormulaSkeleton","count","_temp","Skeleton","createFormulaDownloadConfig","args","data","ctx","rows","d"],"sources":["../src/widgets/formula/delta.tsx","../src/widgets/formula/note.tsx","../src/widgets/formula/prefix.tsx","../src/widgets/formula/series.tsx","../src/widgets/formula/suffix.tsx","../src/widgets/formula/value.tsx","../src/widgets/formula/style.ts","../src/widgets/formula/formula-ui.tsx","../src/widgets/formula/formula.tsx","../src/widgets/formula/skeleton.tsx","../src/widgets/formula/download.tsx"],"sourcesContent":["import { Chip, type ChipProps } from '@mui/material'\nimport type { DeltaSeverity } from './types'\n\nexport interface DeltaProps {\n /** Numeric magnitude. Used for the default percent label and severity inference. */\n value: number\n /** Pre-formatted label override; skips the default percent formatter. */\n label?: string\n /**\n * Visual treatment. When omitted it's derived from the sign of `value`:\n * `>0 → 'positive'`, `<0 → 'negative'`, `=0 → 'neutral'`.\n */\n severity?: DeltaSeverity\n /** Override the default `Intl.NumberFormat({style:'percent'})` formatter. */\n format?: (value: number) => string\n ChipProps?: ChipProps\n}\n\nconst defaultPercentFormatter = new Intl.NumberFormat(undefined, {\n style: 'percent',\n signDisplay: 'always',\n maximumFractionDigits: 1,\n})\n\nconst SEVERITY_TO_COLOR: Record<DeltaSeverity, ChipProps['color']> = {\n positive: 'success',\n negative: 'error',\n neutral: 'default',\n}\n\n/**\n * Comparative delta rendered as a small filled chip. Colour is driven by\n * `severity` (or the sign of `value`); content is `label`, the result of\n * `format(value)`, or the default percent format.\n */\nexport function Delta({\n value,\n label,\n severity,\n format,\n ChipProps: chipProps,\n}: DeltaProps) {\n const resolvedSeverity = severity ?? inferSeverity(value)\n const text =\n label ?? (format ? format(value) : defaultPercentFormatter.format(value))\n return (\n <Chip\n size='small'\n color={SEVERITY_TO_COLOR[resolvedSeverity]}\n label={text}\n sx={{ fontWeight: 600 }}\n {...chipProps}\n />\n )\n}\n\nfunction inferSeverity(value: number): DeltaSeverity {\n if (value > 0) return 'positive'\n if (value < 0) return 'negative'\n return 'neutral'\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport { mergeSx } from '../../components/lasso-tool/styles'\n\nexport interface NoteProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Small subdued caption rendered below a Formula value. Use for short\n * descriptive text (e.g. \"Last 7 days\", \"Excludes refunds\").\n */\nexport function Note({ children, TypographyProps }: NoteProps) {\n return (\n <Typography\n variant='caption'\n component='p'\n {...TypographyProps}\n sx={mergeSx(\n {\n color: 'text.secondary',\n m: 0,\n },\n TypographyProps?.sx,\n )}\n >\n {children}\n </Typography>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\n\nexport interface PrefixProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Prefix slot for a Formula KPI — large, bold, muted (`text.secondary`).\n * Composable building block for custom formula layouts; the canonical\n * {@link FormulaUI} renders one of these per item when `prefix` is set.\n */\nexport function Prefix({ children, TypographyProps }: PrefixProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import { Avatar, type AvatarProps, type Theme } from '@mui/material'\nimport type { PaletteColor } from '../../components/types'\nimport { resolveThemeColor } from '../utils'\n\nexport interface SeriesProps {\n /** Used for the avatar's first-letter glyph and as accessible label. */\n name: string\n /** Avatar background colour (any MUI palette path or CSS colour). */\n color?: PaletteColor\n /** Diameter in px. Default `24`. */\n size?: number\n AvatarProps?: AvatarProps\n}\n\n/**\n * Series indicator — circular avatar showing the first letter of `name`.\n * Background colour is `color` (consumer-supplied) or a neutral disabled\n * colour, with text colour computed for contrast against it.\n */\nexport function Series({\n name,\n color,\n size = 24,\n AvatarProps: avatarProps,\n}: SeriesProps) {\n const letter = name.charAt(0).toUpperCase()\n return (\n <Avatar\n aria-label={name}\n sx={{\n width: size,\n height: size,\n fontSize: size * 0.5,\n fontWeight: 600,\n bgcolor: color ?? 'action.disabledBackground',\n // `getContrastText` needs a concrete colour, so the palette path is\n // resolved here even though `bgcolor` above takes it as it comes.\n color: color\n ? (theme: Theme) =>\n theme.palette.getContrastText(\n resolveThemeColor(theme, color) ?? color,\n )\n : 'text.primary',\n }}\n {...avatarProps}\n >\n {letter}\n </Avatar>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\n\nexport interface SuffixProps {\n children: ReactNode\n TypographyProps?: TypographyProps\n}\n\n/**\n * Suffix slot for a Formula KPI — large, bold, muted (`text.secondary`).\n * Composable building block for custom formula layouts; the canonical\n * {@link FormulaUI} renders one of these per item when `suffix` is set.\n */\nexport function Suffix({ children, TypographyProps }: SuffixProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{ fontWeight: 600, color: 'text.secondary', lineHeight: 1 }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import { Typography, type TypographyProps } from '@mui/material'\nimport type { ReactNode } from 'react'\nimport type { PaletteColor } from '../../components/types'\n\nexport interface ValueProps {\n children: ReactNode\n /** Override the default `text.primary` color — useful for sparkline tints. */\n color?: PaletteColor\n TypographyProps?: TypographyProps\n}\n\n/**\n * Value slot for a Formula KPI — large, bold, primary text color. Composable\n * building block for custom formula layouts; the canonical {@link FormulaUI}\n * renders one of these per item.\n */\nexport function Value({ children, color, TypographyProps }: ValueProps) {\n return (\n <Typography\n variant='h5'\n component='span'\n sx={{\n fontWeight: 600,\n color: color ?? 'text.primary',\n lineHeight: 1,\n }}\n {...TypographyProps}\n >\n {children}\n </Typography>\n )\n}\n","import type { SxProps, Theme } from '@mui/material'\n\nexport const styles = {\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n alignItems: 'stretch',\n },\n row: {\n display: 'flex',\n alignItems: 'flex-start',\n gap: 1.5,\n },\n body: {\n flex: 1,\n minWidth: 0,\n display: 'flex',\n flexDirection: 'column',\n gap: 0.25,\n },\n valueRow: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: 0.25,\n },\n delta: {\n alignSelf: 'center',\n flexShrink: 0,\n },\n} satisfies Record<string, SxProps<Theme>>\n","import { Box } from '@mui/material'\nimport type { Ref } from 'react'\nimport type { FormulaDataItem } from './types'\nimport { Delta } from './delta'\nimport { Note } from './note'\nimport { Prefix } from './prefix'\nimport { Series } from './series'\nimport { Suffix } from './suffix'\nimport { Value } from './value'\nimport { styles } from './style'\n\nexport interface FormulaUIProps {\n items: readonly FormulaDataItem[]\n /** Number formatter — applied to each item's `value`. */\n formatter?: (value: number) => string\n /** Forwarded to the root `<Box>` so consumers can capture the DOM (PNG export). */\n ref?: Ref<HTMLDivElement>\n}\n\n/**\n * Pure presentational component for the Formula widget. Each item renders as\n * a row containing (in order): optional `Series` avatar, a body with\n * `[prefix] value [suffix]` plus optional `Note`, and an optional `Delta`\n * chip pushed to the right edge.\n *\n * For fully custom layouts, compose {@link Series}, {@link Prefix},\n * {@link Value}, {@link Suffix}, {@link Note}, and {@link Delta} directly\n * instead of passing `items`.\n */\nexport function FormulaUI({ items, formatter, ref }: FormulaUIProps) {\n return (\n <Box ref={ref} sx={styles.root}>\n {items.map((item, i) => (\n // Series name is the closest stable id; fall back to the literal\n // value (numeric KPIs rarely repeat with the same prefix/suffix).\n <Box\n key={`formula-${item.series?.name ?? `${item.prefix ?? ''}${item.value}${item.suffix ?? ''}`}-${i}`}\n sx={styles.row}\n >\n {item.series ? (\n <Series name={item.series.name} color={item.series.color} />\n ) : null}\n <Box sx={styles.body}>\n <Box sx={styles.valueRow}>\n {item.prefix ? <Prefix>{item.prefix}</Prefix> : null}\n <Value color={item.color}>\n {formatter ? formatter(item.value) : String(item.value)}\n </Value>\n {item.suffix ? <Suffix>{item.suffix}</Suffix> : null}\n </Box>\n {item.note ? <Note>{item.note}</Note> : null}\n </Box>\n {item.delta ? (\n <Box sx={styles.delta}>\n <Delta\n value={item.delta.value}\n label={item.delta.label}\n severity={item.delta.severity}\n />\n </Box>\n ) : null}\n </Box>\n ))}\n </Box>\n )\n}\n","import { useWidgetId, useWidgetShallow } from '../stores'\nimport { FormulaUI } from './formula-ui'\nimport type { FormulaWidgetData } from './types'\n\ninterface FormulaSlice {\n data: FormulaWidgetData\n formatter?: (value: number) => string\n}\n\n// Stable empty-data sentinel — keeps the slice's `data` reference stable\n// when the underlying store value is null/undefined so the shallow-equality\n// gate in `useWidgetShallow` doesn't re-render every commit.\nconst EMPTY_DATA: FormulaWidgetData = Object.freeze([]) as FormulaWidgetData\n\nconst formulaSelector = (s: {\n data: unknown\n formatter?: (value: number) => string\n}): FormulaSlice => ({\n data: (s.data ?? EMPTY_DATA) as FormulaWidgetData,\n formatter: s.formatter,\n})\n\n/**\n * Stateful Formula bridge — reads `data` (post-pipeline) and `formatter` from\n * the per-widget store and forwards them to the pure {@link FormulaUI}. Sits\n * inside `<Widget.Provider>`. Demonstrates that the composition shell\n * (Provider, Wrapper, State) doesn't care whether the rendered child is\n * ECharts (Bar) or a custom DOM tree (Formula).\n */\nexport function Formula() {\n const id = useWidgetId()\n const slice = useWidgetShallow(id, formulaSelector)\n return <FormulaUI items={slice.data} formatter={slice.formatter} />\n}\n","import { Box, Skeleton } from '@mui/material'\nimport type { SxProps, Theme } from '@mui/material'\n\nconst styles = {\n root: {\n display: 'flex',\n gap: 3,\n alignItems: 'baseline',\n minHeight: 64,\n py: 1,\n },\n block: { width: 120, height: 36 },\n} satisfies Record<string, SxProps<Theme>>\n\nexport interface FormulaSkeletonProps {\n count?: number\n}\n\nexport function FormulaSkeleton({ count = 1 }: FormulaSkeletonProps) {\n return (\n <Box sx={styles.root}>\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={`row-${i}`} variant='rectangular' sx={styles.block} />\n ))}\n </Box>\n )\n}\n","import {\n buildCsvDownloadItem,\n withPngItem,\n type DownloadItem,\n type WidgetDownloadArgs,\n} from '../actions/download'\nimport type { FormulaWidgetData } from './types'\n\nexport type FormulaDownloadConfigArgs = WidgetDownloadArgs<FormulaWidgetData>\n\n/**\n * Builds download items for the Formula widget. Always includes a CSV item\n * with one row per KPI (`prefix`, `value`, `suffix`, `note`, `delta`). When\n * `getCaptureEl` is given, prepends a PNG item that rasterises the captured\n * element via `html2canvas`.\n */\nexport function createFormulaDownloadConfig(\n args: FormulaDownloadConfigArgs,\n): DownloadItem[] {\n return withPngItem(\n args,\n buildCsvDownloadItem({\n filename: args.filename,\n getRows: (ctx) => {\n const data = args.getData(ctx)\n const rows: unknown[][] = [\n ['series', 'prefix', 'value', 'suffix', 'note', 'delta'],\n ]\n for (const d of data) {\n rows.push([\n d.series?.name ?? '',\n d.prefix ?? '',\n d.value,\n d.suffix ?? '',\n d.note ?? '',\n d.delta?.value ?? '',\n ])\n }\n return rows\n },\n }),\n )\n}\n"],"mappings":"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"}
@@ -1,11 +1,19 @@
1
- import { t as e } from "./utils-C-eWwOhE.js";
1
+ import { t as e } from "./utils-Dw4y8tiq.js";
2
+ //#region src/widgets/utils/spacing-px.ts
3
+ function t(e, t) {
4
+ let n = parseFloat(e.spacing(t));
5
+ if (Number.isFinite(n)) return n;
6
+ let r = /,\s*(-?[\d.]+)px\)\s*$/.exec(e.spacing(1)), i = r ? Number(r[1]) : NaN;
7
+ return Number.isFinite(i) ? i * t : (e.spacingValue ?? 8) * t;
8
+ }
9
+ //#endregion
2
10
  //#region src/widgets/utils/option-builders.ts
3
- function t(e) {
11
+ function n(e) {
4
12
  if (e === 0) return 0;
5
13
  let t = Math.abs(e), n = 10 ** Math.floor(Math.log10(t)), r = Math.ceil(t / n) * n;
6
14
  return e < 0 ? -r : r;
7
15
  }
8
- function n({ hasLegend: e, labelFormatter: t }) {
16
+ function r({ hasLegend: e, labelFormatter: t }) {
9
17
  return {
10
18
  show: e,
11
19
  icon: "circle",
@@ -16,26 +24,26 @@ function n({ hasLegend: e, labelFormatter: t }) {
16
24
  ...t && { formatter: (e) => String(t(e)) }
17
25
  };
18
26
  }
19
- function r(e, t) {
27
+ function i(e, n) {
20
28
  return {
21
- ...!e && { bottom: parseInt(t.spacing(3)) },
22
- ...e && { bottom: parseInt(t.spacing(7)) }
29
+ ...!e && { bottom: t(n, 3) },
30
+ ...e && { bottom: t(n, 7) }
23
31
  };
24
32
  }
25
- function i(e) {
33
+ function a(e) {
26
34
  return {
27
35
  fontSize: e.typography.overlineDelicate?.fontSize,
28
36
  fontFamily: e.typography.overlineDelicate?.fontFamily,
29
37
  color: e.palette.black?.[60] ?? e.palette.text.secondary
30
38
  };
31
39
  }
32
- function a(e) {
33
- return function(t, n, r, i, a) {
34
- let o = { top: parseInt(e.spacing(.5)) };
35
- return a.contentSize[0] < a.viewSize[0] - t[0] ? o.left = t[0] : o.right = a.viewSize[0] - t[0], o;
40
+ function o(e) {
41
+ return function(n, r, i, a, o) {
42
+ let s = { top: t(e, .5) };
43
+ return o.contentSize[0] < o.viewSize[0] - n[0] ? s.left = n[0] : s.right = o.viewSize[0] - n[0], s;
36
44
  };
37
45
  }
38
- function o(e) {
46
+ function s(e) {
39
47
  return (t) => {
40
48
  let n = Array.isArray(t) ? t : [t], r = (e, t) => `<div style="margin: 0px 0 0;line-height:1;">${e ? `<div style="font-size:11px;color:#FFFFFF;font-weight:400;line-height:1; margin-bottom: 10px">${e}</div>` : ""}<div style="margin: 0;line-height:1;">${t}</div><div style="clear:both"></div></div>`, i = n.map((t) => {
41
49
  let { name: r, seriesName: i, marker: a, value: o } = e(t, n);
@@ -49,7 +57,7 @@ function o(e) {
49
57
  return r(a, i.map(({ seriesName: e, marker: t, value: n }) => `<div style="${o}"><div style="margin: 0px 0 0;line-height:1;">${t}${e ? `<span style="font-size:11px;color:#FFFFFF;font-weight:400;margin-left:2px;margin-right:10px">${e}</span>` : ""}<span style="float:right;margin-left:10px;font-size:11px;color:#FFFFFF;font-weight:900">${n}</span></div></div>`).join(""));
50
58
  };
51
59
  }
52
- function s(e, t = "y") {
60
+ function c(e, t = "y") {
53
61
  return e ? { label: { formatter: (n) => {
54
62
  let r = n.encode?.[t]?.[0];
55
63
  if (r === void 0) return "";
@@ -59,21 +67,21 @@ function s(e, t = "y") {
59
67
  }
60
68
  //#endregion
61
69
  //#region src/widgets/utils/item-color.ts
62
- function c({ theme: t, datums: n, template: r, fallback: i }) {
70
+ function l({ theme: t, datums: n, template: r, fallback: i }) {
63
71
  let a = r?.itemStyle?.color;
64
72
  return (r) => {
65
- let o = l(n[r.dataIndex]);
73
+ let o = u(n[r.dataIndex]);
66
74
  if (o) return e(t, o);
67
75
  let s = typeof a == "function" ? a(r) : a;
68
76
  return typeof s == "string" && s !== "" ? e(t, s) : i(r);
69
77
  };
70
78
  }
71
- function l(e) {
79
+ function u(e) {
72
80
  if (typeof e != "object" || !e) return;
73
81
  let t = e.color;
74
82
  return typeof t == "string" && t !== "" ? t : void 0;
75
83
  }
76
84
  //#endregion
77
- export { s as a, t as c, n as i, i as n, o, r, a as s, c as t };
85
+ export { c as a, n as c, r as i, t as l, a as n, s as o, i as r, o as s, l as t };
78
86
 
79
- //# sourceMappingURL=item-color-B7xkIpDZ.js.map
87
+ //# sourceMappingURL=item-color-7o7pFBR2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-color-7o7pFBR2.js","names":["resolveThemeColor","spacingPx","parsed","theme","factor","fallback","px","niceNum","value","absValue","base","rounded","buildLegendConfig","hasLegend","labelFormatter","name","buildGridConfig","buildAxisLabelStyle","createTooltipPositioner","position","size","point","createTooltipFormatter","items","params","tooltip","callback","values","item","marginStyle","marker","seriesName","buildSeriesLabelConfig","formatter","encodeIndex","encodeKey","dimName","row","createItemColorResolver","templateColor","template","datumColor","readColor","datums","custom","datum","color"],"sources":["../src/widgets/utils/spacing-px.ts","../src/widgets/utils/option-builders.ts","../src/widgets/utils/item-color.ts"],"sourcesContent":["import type { Theme } from '@mui/material'\n\n/**\n * Theme spacing as a pixel number, for APIs that need numbers (ECharts).\n *\n * `theme.spacing(n)` returns `\"8px\"` for a regular theme but\n * `\"var(--mui-spacing, 8px)\"` / `\"calc(...)\"` once the theme uses\n * `cssVariables`, which does not parse. Resolution order:\n * 1. `parseFloat` of `theme.spacing(factor)` (regular themes);\n * 2. the px unit MUI writes into `theme.spacing(1)` (`var(--mui-spacing, Npx)`),\n * which honours a `spacing` override such as `createCartoTheme({ spacing: 4 })`;\n * 3. meridian's numeric `spacingValue`, or MUI's default 8px.\n */\nexport function spacingPx(theme: Theme, factor: number): number {\n const parsed = parseFloat(theme.spacing(factor))\n if (Number.isFinite(parsed)) return parsed\n // `spacing(1)` is `var(--mui-spacing, Npx)`; larger factors are\n // `calc(f * var(--mui-spacing, Npx))`, so read the unit from the former.\n const fallback = /,\\s*(-?[\\d.]+)px\\)\\s*$/.exec(theme.spacing(1))\n const px = fallback ? Number(fallback[1]) : NaN\n if (Number.isFinite(px)) return px * factor\n const unit = (theme.spacingValue as number | undefined) ?? 8\n return unit * factor\n}\n","import type { Theme } from '@mui/material'\nimport type { LegendComponentOption } from 'echarts'\nimport type {\n CallbackDataParams,\n TopLevelFormatterParams,\n} from 'echarts/types/dist/shared'\nimport { spacingPx } from './spacing-px'\n\n/**\n * Shared EChart configuration builders for chart widgets\n */\n\n/**\n * Rounds a value up to the nearest \"nice\" number.\n * A nice number is a multiple of 10^floor(log10(value)).\n *\n * Examples: 547 → 600, 200 → 200, 1200 → 2000, 18 → 20, 5 → 5, -547 → -500\n */\nexport function niceNum(value: number): number {\n if (value === 0) return 0\n const absValue = Math.abs(value)\n const base = Math.pow(10, Math.floor(Math.log10(absValue)))\n const rounded = Math.ceil(absValue / base) * base\n return value < 0 ? -rounded : rounded\n}\n\n/**\n * Builds standard legend configuration for chart widgets\n *\n * @param params - Legend configuration parameters\n * @param params.hasLegend - Whether to show the legend\n * @param params.labelFormatter - Optional formatter for legend item names\n * @returns Legend configuration object\n */\nexport function buildLegendConfig({\n hasLegend,\n labelFormatter,\n}: {\n hasLegend: boolean\n labelFormatter?: (value: string | number) => string | number\n}): LegendComponentOption {\n return {\n show: hasLegend,\n icon: 'circle' as const,\n left: 0,\n bottom: 0,\n orient: 'horizontal',\n type: 'scroll',\n ...(labelFormatter && {\n formatter: (name: string) => String(labelFormatter(name)),\n }),\n }\n}\n\n/**\n * Builds standard grid configuration with legend-aware spacing\n *\n * @param hasLegend - Whether the chart has a legend\n * @param theme - MUI theme for spacing\n * @param additionalConfig - Additional grid configuration to merge\n * @returns Grid configuration object\n */\nexport function buildGridConfig(hasLegend: boolean, theme: Theme) {\n return {\n ...(!hasLegend && { bottom: spacingPx(theme, 3) }),\n ...(hasLegend && { bottom: spacingPx(theme, 7) }),\n }\n}\n\n/**\n * Builds the shared axis-label text style for chart widgets, matching the\n * design's \"Overline (Delicate)\" token (Inter, 10px) in the secondary text\n * colour (`rgba(44,48,50,0.6)`). Spread into an ECharts `axisLabel` for both\n * x and y axes so every chart's labels stay consistent.\n *\n * Note: the token's `letterSpacing` and `textTransform: uppercase` are not\n * representable in ECharts canvas text, so only font size/family/colour are\n * applied.\n *\n * @param theme - MUI theme providing the typography token and palette\n * @returns `{ fontSize, fontFamily, color }` for an ECharts `axisLabel`\n */\nexport function buildAxisLabelStyle(theme: Theme): {\n fontSize: string | number | undefined\n fontFamily: string | undefined\n color: string\n} {\n return {\n fontSize: theme.typography.overlineDelicate?.fontSize,\n fontFamily: theme.typography.overlineDelicate?.fontFamily,\n color: theme.palette.black?.[60] ?? theme.palette.text.secondary,\n }\n}\n\n/**\n * Creates a tooltip position calculator that handles overflow\n * Used by bar, histogram, and scatterplot widgets\n *\n * @param theme - MUI theme for spacing\n * @returns Tooltip position function\n */\nexport function createTooltipPositioner(theme: Theme) {\n return function (\n point: [number, number],\n _params: unknown,\n _dom: unknown,\n _rect: unknown,\n size: { contentSize: [number, number]; viewSize: [number, number] },\n ) {\n const position = { top: spacingPx(theme, 0.5) } as Record<string, number>\n\n // Position tooltip left or right based on available space\n if (size.contentSize[0] < size.viewSize[0] - point[0]) {\n position.left = point[0]\n } else {\n position.right = size.viewSize[0] - point[0]\n }\n\n return position\n }\n}\n\n/**\n * Creates an axis label formatter for ECharts\n * Used to format numeric axis labels with a widget formatter\n *\n * @param formatter - Optional formatter function from widget config\n * @returns Axis label formatter function or undefined\n */\nexport function createAxisLabelFormatter(\n formatter?: (value: number) => string,\n) {\n if (!formatter) return undefined\n return (value: number) => formatter(value)\n}\n\n/**\n * Applies labelFormatter to xAxis configuration\n * Applies to any xAxis regardless of axis type (category, value, etc.)\n *\n * @param xAxis - Existing xAxis configuration\n * @param labelFormatter - Optional labelFormatter function from widget config\n * @returns Updated xAxis configuration\n */\nexport function applyXAxisFormatter(\n xAxis: unknown,\n formatter?: (value: string | number) => string | number,\n) {\n const xAxisIsObject = xAxis && !Array.isArray(xAxis)\n const xAxisTyped = xAxis as { type?: string; axisLabel?: unknown }\n\n const axisFormatter =\n formatter && xAxisIsObject\n ? (value: string | number) => String(formatter(value))\n : undefined\n\n return {\n ...xAxisTyped,\n axisLabel: {\n ...(typeof xAxisTyped.axisLabel === 'object' && xAxisTyped.axisLabel\n ? xAxisTyped.axisLabel\n : {}),\n formatter: axisFormatter,\n },\n }\n}\n\n/**\n * Applies formatter to yAxis configuration\n * Only applies to single axis objects (not arrays) with type 'value'\n *\n * @param yAxis - Existing yAxis configuration\n * @param formatter - Optional formatter function from widget config\n * @returns Updated yAxis configuration or undefined if no changes needed\n */\nexport function applyYAxisFormatter(\n yAxis: unknown,\n formatter?: (value: number) => string,\n) {\n let axisFormatter = createAxisLabelFormatter(formatter)\n\n const yAxisIsObject = yAxis && !Array.isArray(yAxis)\n const yAxisTyped = yAxis as { type?: string; axisLabel?: unknown }\n\n if (!yAxisIsObject || yAxisTyped.type !== 'value') {\n axisFormatter = undefined\n }\n\n return {\n ...yAxisTyped,\n axisLabel: {\n ...(typeof yAxisTyped.axisLabel === 'object' && yAxisTyped.axisLabel\n ? yAxisTyped.axisLabel\n : {}),\n formatter: axisFormatter,\n },\n }\n}\n\n/**\n * Creates a tooltip formatter for ECharts\n * Formats numeric values in tooltip using widget formatter\n * Handles both axis trigger (array) and item trigger (object) modes\n *\n * @param formatter - Optional formatter function from widget config\n * @returns Tooltip formatter function or undefined\n */\nexport function createTooltipFormatter(\n callback: (\n item: CallbackDataParams,\n items: CallbackDataParams[],\n ) => {\n name: string\n seriesName: string\n marker: string\n value: string | number\n },\n) {\n return (params: TopLevelFormatterParams) => {\n // Handle both array (axis trigger) and object (item trigger)\n const items = Array.isArray(params) ? params : [params]\n\n const tooltip = (name: string, callback: string) =>\n `<div style=\"margin: 0px 0 0;line-height:1;\">${name ? `<div style=\"font-size:11px;color:#FFFFFF;font-weight:400;line-height:1; margin-bottom: 10px\">${name}</div>` : ''}<div style=\"margin: 0;line-height:1;\">${callback}</div><div style=\"clear:both\"></div></div>`\n\n const values = items.map((item) => {\n const { name, seriesName, marker, value } = callback(item, items)\n return {\n name,\n seriesName,\n marker,\n value,\n }\n })\n\n const name = values[0]?.name ?? ''\n // Show margin if name exists or there are multiple items\n const showMargin = name || items.length > 1\n const marginStyle = showMargin\n ? 'margin: 10px 0 0;line-height:1;'\n : 'margin: 0;line-height:1;'\n\n const formattedValues = values.map(\n ({ seriesName, marker, value }) =>\n `<div style=\"${marginStyle}\"><div style=\"margin: 0px 0 0;line-height:1;\">${marker}${seriesName ? `<span style=\"font-size:11px;color:#FFFFFF;font-weight:400;margin-left:2px;margin-right:10px\">${seriesName}</span>` : ''}<span style=\"float:right;margin-left:10px;font-size:11px;color:#FFFFFF;font-weight:900\">${value}</span></div></div>`,\n )\n\n return tooltip(name, formattedValues.join(''))\n }\n}\n\n/**\n * Builds a series `label` config that applies formatter to the data value\n * extracted from a dataset row using ECharts encode/dimensionNames.\n *\n * Does not set `show` — labels remain hidden by default per ECharts defaults.\n *\n * @param formatter - Optional numeric value formatter\n * @param encodeKey - The encode dimension key to extract ('y' for vertical charts, 'x' for horizontal)\n */\nexport function buildSeriesLabelConfig(\n formatter?: (value: number) => string,\n encodeKey = 'y',\n): { label: { formatter: (params: CallbackDataParams) => string } } | object {\n if (!formatter) return {}\n\n return {\n label: {\n formatter: (params: CallbackDataParams) => {\n const encodeIndex = params.encode?.[encodeKey]?.[0]\n if (encodeIndex === undefined) return ''\n const dimName = params.dimensionNames?.[encodeIndex]\n const row = params.value as Record<string, string | number>\n const value = dimName ? row[dimName] : undefined\n return typeof value === 'number'\n ? formatter(value)\n : String(value ?? '')\n },\n },\n }\n}\n\n/**\n * Builds a series `label` config that applies formatter to a raw numeric value.\n * Used by histogram where series data is number[] (not datasets).\n *\n * Does not set `show` — labels remain hidden by default per ECharts defaults.\n *\n * @param formatter - Optional numeric value formatter\n */\nexport function buildHistogramSeriesLabelConfig(\n formatter?: (value: number) => string,\n): { label: { formatter: (params: CallbackDataParams) => string } } | object {\n if (!formatter) return {}\n\n return {\n label: {\n formatter: (params: CallbackDataParams) => {\n const value = params.value as number\n return typeof value === 'number'\n ? formatter(value)\n : String(value ?? '')\n },\n },\n }\n}\n","import type { Theme } from '@mui/material'\nimport type { CallbackDataParams } from 'echarts/types/dist/shared'\nimport { resolveThemeColor } from './resolve-theme-color'\n\n/** ECharts `itemStyle.color` callback, as emitted by the chart widgets. */\nexport type ItemColorCallback = (params: CallbackDataParams) => string\n\n/**\n * Builds the callback that resolves the **base** colour of every item of one\n * series, before any selection dimming. Priority:\n *\n * 1. the datum's own `color` (`datums[params.dataIndex].color`);\n * 2. the consumer's `template.itemStyle.color` (from `optionsOverride`):\n * a string is used as is, a function is called with the ECharts params;\n * 3. `fallback` — the widget's default (series colour / palette).\n *\n * The datum is looked up by `params.dataIndex` in the widget's own data\n * rather than read off `params.data`, so it works whatever the dataset row\n * shape is (object rows, `[label, count]` tuples, `[x, y]` tuples).\n *\n * Datum and template colours go through {@link resolveThemeColor}, same as\n * per-series colours, so palette paths such as `'primary.main'` work.\n */\nexport function createItemColorResolver({\n theme,\n datums,\n template,\n fallback,\n}: {\n theme: Theme\n datums: readonly unknown[]\n template: unknown\n fallback: ItemColorCallback\n}): ItemColorCallback {\n const templateColor = (template as { itemStyle?: { color?: unknown } })\n ?.itemStyle?.color\n return (params) => {\n const datumColor = readColor(datums[params.dataIndex])\n if (datumColor) return resolveThemeColor(theme, datumColor)!\n const custom =\n typeof templateColor === 'function'\n ? (templateColor as (p: CallbackDataParams) => unknown)(params)\n : templateColor\n if (typeof custom === 'string' && custom !== '') {\n return resolveThemeColor(theme, custom)!\n }\n return fallback(params)\n }\n}\n\nfunction readColor(datum: unknown): string | undefined {\n if (datum == null || typeof datum !== 'object') return undefined\n const color = (datum as { color?: unknown }).color\n return typeof color === 'string' && color !== '' ? color : undefined\n}\n"],"mappings":"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"}