@carto/ps-react-ui 4.3.5 → 4.3.6

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 (59) hide show
  1. package/dist/components.js +2 -2
  2. package/dist/{error-B2IJ9d2h.js → error-piB8FwYO.js} +2 -2
  3. package/dist/{error-B2IJ9d2h.js.map → error-piB8FwYO.js.map} +1 -1
  4. package/dist/{lasso-tool-wFqOD6wk.js → lasso-tool-BctzdzBu.js} +185 -160
  5. package/dist/lasso-tool-BctzdzBu.js.map +1 -0
  6. package/dist/{no-data-C54XJt13.js → no-data-jdlbMef0.js} +2 -2
  7. package/dist/{no-data-C54XJt13.js.map → no-data-jdlbMef0.js.map} +1 -1
  8. package/dist/{row-DrHwXNvF.js → row-D3uVFImu.js} +2 -2
  9. package/dist/{row-DrHwXNvF.js.map → row-D3uVFImu.js.map} +1 -1
  10. package/dist/{series-D3Pc-kYX.js → series-BAImrSBo.js} +3 -3
  11. package/dist/{series-D3Pc-kYX.js.map → series-BAImrSBo.js.map} +1 -1
  12. package/dist/types/widgets/actions/index.d.ts +2 -2
  13. package/dist/types/widgets/actions/stack-toggle/stack-toggle.d.ts +3 -2
  14. package/dist/types/widgets/actions/zoom-toggle/zoom-toggle.d.ts +4 -0
  15. package/dist/types/widgets/loader/loader.d.ts +1 -1
  16. package/dist/types/widgets/loader/types.d.ts +1 -1
  17. package/dist/types/widgets/stores/index.d.ts +1 -1
  18. package/dist/types/widgets/stores/types.d.ts +15 -0
  19. package/dist/{use-widget-ref-B0aNCANx.js → use-widget-ref-B8x4sHIj.js} +2 -2
  20. package/dist/{use-widget-ref-B0aNCANx.js.map → use-widget-ref-B8x4sHIj.js.map} +1 -1
  21. package/dist/widget-store-Dn0Bnc4h.js +178 -0
  22. package/dist/widget-store-Dn0Bnc4h.js.map +1 -0
  23. package/dist/widgets/actions.js +698 -617
  24. package/dist/widgets/actions.js.map +1 -1
  25. package/dist/widgets/bar.js +2 -2
  26. package/dist/widgets/category.js +2 -2
  27. package/dist/widgets/echart.js +2 -2
  28. package/dist/widgets/error.js +1 -1
  29. package/dist/widgets/formula.js +5 -5
  30. package/dist/widgets/histogram.js +2 -2
  31. package/dist/widgets/loader.js +41 -40
  32. package/dist/widgets/loader.js.map +1 -1
  33. package/dist/widgets/markdown.js +2 -2
  34. package/dist/widgets/no-data.js +1 -1
  35. package/dist/widgets/pie.js +2 -2
  36. package/dist/widgets/range.js +2 -2
  37. package/dist/widgets/scatterplot.js +2 -2
  38. package/dist/widgets/skeleton-loader.js +1 -1
  39. package/dist/widgets/spread.js +5 -5
  40. package/dist/widgets/stores.js +1 -1
  41. package/dist/widgets/table.js +3 -3
  42. package/dist/widgets/timeseries.js +2 -2
  43. package/dist/widgets/wrapper.js +2 -2
  44. package/dist/widgets.js +4 -4
  45. package/package.json +1 -1
  46. package/src/components/lasso-tool/lasso-tool.tsx +5 -2
  47. package/src/widgets/actions/index.ts +2 -2
  48. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +143 -9
  49. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +61 -70
  50. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +85 -53
  51. package/src/widgets/loader/loader.tsx +18 -8
  52. package/src/widgets/loader/types.ts +1 -1
  53. package/src/widgets/stores/index.ts +1 -0
  54. package/src/widgets/stores/types.ts +20 -0
  55. package/src/widgets/stores/widget-store.test.ts +141 -0
  56. package/src/widgets/stores/widget-store.ts +99 -2
  57. package/dist/lasso-tool-wFqOD6wk.js.map +0 -1
  58. package/dist/widget-store-CB6Trp_0.js +0 -131
  59. package/dist/widget-store-CB6Trp_0.js.map +0 -1
@@ -1,41 +1,42 @@
1
- import { c as W } from "react/compiler-runtime";
2
- import { useEffect as n } from "react";
3
- import { u as h } from "../widget-store-CB6Trp_0.js";
4
- import { d as x } from "../cjs-D4KH3azB.js";
5
- function M(e) {
6
- const t = W(27), i = h(v), d = h(_);
7
- let a, c;
8
- t[0] !== e.id || t[1] !== e.type || t[2] !== i ? (a = () => {
9
- i(e.id, {
1
+ import { c as r } from "react/compiler-runtime";
2
+ import { useEffect as c } from "react";
3
+ import { u as f } from "../widget-store-Dn0Bnc4h.js";
4
+ import { d as C } from "../cjs-D4KH3azB.js";
5
+ function $(e) {
6
+ const i = r(29), t = f(v), n = f(_), d = f(P);
7
+ let g, l;
8
+ i[0] !== e.id || i[1] !== e.type || i[2] !== t ? (g = () => {
9
+ t(e.id, {
10
10
  type: e.type
11
11
  });
12
- }, c = [e.id, e.type, i], t[0] = e.id, t[1] = e.type, t[2] = i, t[3] = a, t[4] = c) : (a = t[3], c = t[4]), n(a, c);
13
- let f, g;
14
- t[5] !== e.error || t[6] !== e.id || t[7] !== e.isFetching || t[8] !== e.isLoading || t[9] !== i ? (f = () => {
15
- i(e.id, {
12
+ }, l = [e.id, e.type, t], i[0] = e.id, i[1] = e.type, i[2] = t, i[3] = g, i[4] = l) : (g = i[3], l = i[4]), c(g, l);
13
+ let a, u;
14
+ i[5] !== e.error || i[6] !== e.id || i[7] !== e.isFetching || i[8] !== e.isLoading || i[9] !== t ? (a = () => {
15
+ t(e.id, {
16
16
  isLoading: e.isLoading ?? !1,
17
17
  isFetching: e.isFetching ?? !1,
18
18
  error: e.error
19
19
  });
20
- }, g = [e.id, e.isLoading, e.isFetching, e.error, i], t[5] = e.error, t[6] = e.id, t[7] = e.isFetching, t[8] = e.isLoading, t[9] = i, t[10] = f, t[11] = g) : (f = t[10], g = t[11]), n(f, g);
21
- let l, u;
22
- t[12] !== e.config || t[13] !== e.id || t[14] !== i ? (l = () => {
23
- e.config && i(e.id, {
24
- ...e.config
20
+ }, u = [e.id, e.isLoading, e.isFetching, e.error, t], i[5] = e.error, i[6] = e.id, i[7] = e.isFetching, i[8] = e.isLoading, i[9] = t, i[10] = a, i[11] = u) : (a = i[10], u = i[11]), c(a, u);
21
+ let m, y;
22
+ i[12] !== d || i[13] !== e.config || i[14] !== e.id ? (m = () => {
23
+ e.config && d(e.id, e.config);
24
+ }, y = [e.id, e.config, d], i[12] = d, i[13] = e.config, i[14] = e.id, i[15] = m, i[16] = y) : (m = i[15], y = i[16]), c(m, y);
25
+ let b, h;
26
+ i[17] !== n || i[18] !== e.data || i[19] !== e.id ? (b = () => {
27
+ n(e.id, e.data);
28
+ }, h = [e.id, e.data, n], i[17] = n, i[18] = e.data, i[19] = e.id, i[20] = b, i[21] = h) : (b = i[20], h = i[21]), c(b, h);
29
+ let L, T;
30
+ return i[22] !== d || i[23] !== n || i[24] !== e.config || i[25] !== e.data || i[26] !== e.id ? (L = () => {
31
+ let x = f.getState().widgets[e.id]?.registeredTools;
32
+ return f.subscribe((W) => {
33
+ const F = W.widgets[e.id]?.registeredTools;
34
+ F !== x && (x = F, n(e.id, e.data), e.config && d(e.id, e.config));
25
35
  });
26
- }, u = [e.id, e.config, i], t[12] = e.config, t[13] = e.id, t[14] = i, t[15] = l, t[16] = u) : (l = t[15], u = t[16]), n(l, u);
27
- let m, r;
28
- t[17] !== d || t[18] !== e.data || t[19] !== e.id ? (m = () => {
29
- d(e.id, e.data);
30
- }, r = [e.id, e.data, d], t[17] = d, t[18] = e.data, t[19] = e.id, t[20] = m, t[21] = r) : (m = t[20], r = t[21]), n(m, r);
31
- let y, b;
32
- return t[22] !== d || t[23] !== e.data || t[24] !== e.id ? (y = () => {
33
- let L = h.getState().widgets[e.id]?.registeredTools;
34
- return h.subscribe((F) => {
35
- const T = F.widgets[e.id]?.registeredTools;
36
- T !== L && (L = T, d(e.id, e.data));
37
- });
38
- }, b = [e.id, e.data, d], t[22] = d, t[23] = e.data, t[24] = e.id, t[25] = y, t[26] = b) : (y = t[25], b = t[26]), n(y, b), e.children;
36
+ }, T = [e.id, e.data, e.config, n, d], i[22] = d, i[23] = n, i[24] = e.config, i[25] = e.data, i[26] = e.id, i[27] = L, i[28] = T) : (L = i[27], T = i[28]), c(L, T), e.children;
37
+ }
38
+ function P(e) {
39
+ return e.executeConfigPipeline;
39
40
  }
40
41
  function _(e) {
41
42
  return e.executeToolPipeline;
@@ -43,19 +44,19 @@ function _(e) {
43
44
  function v(e) {
44
45
  return e.setWidget;
45
46
  }
46
- function $(e, t, i) {
47
- return typeof e == "function" ? e(t, i) : e;
47
+ function j(e, i, t) {
48
+ return typeof e == "function" ? e(i, t) : e;
48
49
  }
49
- function j(...e) {
50
- return x(e[0] ?? {}, e[1] ?? {}, {
51
- arrayMerge(t, i) {
52
- return i;
50
+ function k(...e) {
51
+ return C(e[0] ?? {}, e[1] ?? {}, {
52
+ arrayMerge(i, t) {
53
+ return t;
53
54
  }
54
55
  });
55
56
  }
56
57
  export {
57
- M as WidgetLoader,
58
- j as mergeWidgetConfig,
59
- $ as resolveConfig
58
+ $ as WidgetLoader,
59
+ k as mergeWidgetConfig,
60
+ j as resolveConfig
60
61
  };
61
62
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sources":["../../src/widgets/loader/loader.tsx","../../src/widgets/loader/utils.ts"],"sourcesContent":["import { useEffect } from 'react'\nimport type { WidgetLoaderProps } from './types'\nimport { useWidgetStore } from '../stores/widget-store'\nimport type { WrapperState } from '../wrapper'\n\nexport function WidgetLoader<T>(props: WidgetLoaderProps<T>) {\n const setWidget = useWidgetStore((state) => state.setWidget)\n const executeToolPipeline = useWidgetStore(\n (state) => state.executeToolPipeline,\n )\n\n // Split into 3 effects for metadata and 1 for data pipeline:\n // Each property that can be modified independently gets its own effect to avoid\n // accidentally resetting other properties.\n //\n // - Effect 1: Type (can be modified by tools that change visualization type)\n // - Effect 2: Loading/Error states (change during fetch lifecycle)\n // - Effect 3: Config (can be modified by tools that change widget configuration)\n // - Effect 4: Data pipeline execution (transforms data through registered tools)\n // - Effect 5: Re-execute pipeline when tool state changes\n\n // Effect 1: Type updates\n useEffect(() => {\n setWidget<WrapperState>(props.id, {\n type: props.type,\n })\n }, [props.id, props.type, setWidget])\n\n // Effect 2: Loading and error states\n useEffect(() => {\n setWidget<WrapperState>(props.id, {\n isLoading: props.isLoading ?? false,\n isFetching: props.isFetching ?? false,\n error: props.error,\n })\n }, [props.id, props.isLoading, props.isFetching, props.error, setWidget])\n\n // Effect 3: Config updates\n useEffect(() => {\n if (props.config) {\n setWidget<WrapperState>(props.id, {\n ...props.config,\n })\n }\n }, [props.id, props.config, setWidget])\n\n // Effect 4: Execute tool pipeline when props.data changes\n useEffect(() => {\n void executeToolPipeline(props.id, props.data)\n }, [props.id, props.data, executeToolPipeline])\n\n // Effect 5: Re-execute pipeline when tool state changes (enabled/config)\n useEffect(() => {\n let prevTools = useWidgetStore.getState().widgets[props.id]?.registeredTools\n\n const unsubscribe = useWidgetStore.subscribe((state) => {\n const currentTools = state.widgets[props.id]?.registeredTools\n\n // Only re-execute if tools array changed\n if (currentTools !== prevTools) {\n prevTools = currentTools\n void executeToolPipeline(props.id, props.data)\n }\n })\n\n return unsubscribe\n }, [props.id, props.data, executeToolPipeline])\n\n return props.children\n}\n","import deepmerge from 'deepmerge'\n\n/**\n * Config can be either an object or a function that receives baseConfig and data\n * and returns a partial config to be merged with the base.\n */\nexport type ConfigOrFn<TConfig, TData = unknown> =\n | Partial<TConfig>\n | ((baseConfig: TConfig, data: TData) => Partial<TConfig>)\n\n/**\n * Resolves a config that may be either an object or a function.\n * If it's a function, calls it with baseConfig and data.\n * If it's an object (or undefined), returns it as-is.\n */\nexport function resolveConfig<TConfig, TData>(\n config: ConfigOrFn<TConfig, TData> | undefined,\n baseConfig: TConfig,\n data: TData,\n): Partial<TConfig> | undefined {\n if (typeof config === 'function') {\n return config(baseConfig, data)\n }\n return config\n}\n\nexport function mergeWidgetConfig<T>(\n ...options: [Partial<T> | undefined, Partial<T> | undefined]\n): T {\n return deepmerge(options[0] ?? {}, options[1] ?? {}, {\n arrayMerge(_, source) {\n return source as T[keyof T][]\n },\n })\n}\n"],"names":["WidgetLoader","props","$","_c","setWidget","useWidgetStore","_temp","executeToolPipeline","_temp2","t0","t1","id","type","useEffect","t2","t3","error","isFetching","isLoading","t4","t5","config","t6","t7","data","t8","t9","prevTools","getState","widgets","registeredTools","subscribe","state_1","currentTools","state","children","state_0","resolveConfig","baseConfig","mergeWidgetConfig","options","deepmerge","arrayMerge","_","source"],"mappings":"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"}
1
+ {"version":3,"file":"loader.js","sources":["../../src/widgets/loader/loader.tsx","../../src/widgets/loader/utils.ts"],"sourcesContent":["import { useEffect } from 'react'\nimport type { WidgetLoaderProps } from './types'\nimport { useWidgetStore } from '../stores/widget-store'\nimport type { WrapperState } from '../wrapper'\n\nexport function WidgetLoader<T extends Record<string, unknown> = Record<string, unknown>>(props: WidgetLoaderProps<T>) {\n const setWidget = useWidgetStore((state) => state.setWidget)\n const executeToolPipeline = useWidgetStore(\n (state) => state.executeToolPipeline,\n )\n const executeConfigPipeline = useWidgetStore(\n (state) => state.executeConfigPipeline,\n )\n\n // Split into 3 effects for metadata and 1 for data pipeline:\n // Each property that can be modified independently gets its own effect to avoid\n // accidentally resetting other properties.\n //\n // - Effect 1: Type (can be modified by tools that change visualization type)\n // - Effect 2: Loading/Error states (change during fetch lifecycle)\n // - Effect 3: Config (can be modified by tools that change widget configuration)\n // - Effect 4: Data pipeline execution (transforms data through registered tools)\n // - Effect 5: Re-execute pipeline when tool state changes\n\n // Effect 1: Type updates\n useEffect(() => {\n setWidget<WrapperState>(props.id, {\n type: props.type,\n })\n }, [props.id, props.type, setWidget])\n\n // Effect 2: Loading and error states\n useEffect(() => {\n setWidget<WrapperState>(props.id, {\n isLoading: props.isLoading ?? false,\n isFetching: props.isFetching ?? false,\n error: props.error,\n })\n }, [props.id, props.isLoading, props.isFetching, props.error, setWidget])\n\n // Effect 3: Config updates — run through config pipeline\n useEffect(() => {\n if (props.config) {\n void executeConfigPipeline(props.id, props.config)\n }\n }, [props.id, props.config, executeConfigPipeline])\n\n // Effect 4: Execute tool pipeline when props.data changes\n useEffect(() => {\n void executeToolPipeline(props.id, props.data)\n }, [props.id, props.data, executeToolPipeline])\n\n // Effect 5: Re-execute pipelines when tool state changes (enabled/config)\n useEffect(() => {\n let prevTools = useWidgetStore.getState().widgets[props.id]?.registeredTools\n\n const unsubscribe = useWidgetStore.subscribe((state) => {\n const currentTools = state.widgets[props.id]?.registeredTools\n\n // Only re-execute if tools array changed\n if (currentTools !== prevTools) {\n prevTools = currentTools\n void executeToolPipeline(props.id, props.data)\n if (props.config) {\n void executeConfigPipeline(props.id, props.config)\n }\n }\n })\n\n return unsubscribe\n }, [\n props.id,\n props.data,\n props.config,\n executeToolPipeline,\n executeConfigPipeline,\n ])\n\n return props.children\n}\n","import deepmerge from 'deepmerge'\n\n/**\n * Config can be either an object or a function that receives baseConfig and data\n * and returns a partial config to be merged with the base.\n */\nexport type ConfigOrFn<TConfig, TData = unknown> =\n | Partial<TConfig>\n | ((baseConfig: TConfig, data: TData) => Partial<TConfig>)\n\n/**\n * Resolves a config that may be either an object or a function.\n * If it's a function, calls it with baseConfig and data.\n * If it's an object (or undefined), returns it as-is.\n */\nexport function resolveConfig<TConfig, TData>(\n config: ConfigOrFn<TConfig, TData> | undefined,\n baseConfig: TConfig,\n data: TData,\n): Partial<TConfig> | undefined {\n if (typeof config === 'function') {\n return config(baseConfig, data)\n }\n return config\n}\n\nexport function mergeWidgetConfig<T>(\n ...options: [Partial<T> | undefined, Partial<T> | undefined]\n): T {\n return deepmerge(options[0] ?? {}, options[1] ?? {}, {\n arrayMerge(_, source) {\n return source as T[keyof T][]\n },\n })\n}\n"],"names":["WidgetLoader","props","$","_c","setWidget","useWidgetStore","_temp","executeToolPipeline","_temp2","executeConfigPipeline","_temp3","t0","t1","id","type","useEffect","t2","t3","error","isFetching","isLoading","t4","t5","config","t6","t7","data","t8","t9","prevTools","getState","widgets","registeredTools","subscribe","state_2","currentTools","state","children","state_1","state_0","resolveConfig","baseConfig","mergeWidgetConfig","options","deepmerge","arrayMerge","_","source"],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  import { jsx as i, jsxs as f } from "react/jsx-runtime";
2
2
  import { c } from "react/compiler-runtime";
3
- import { u } from "../widget-store-CB6Trp_0.js";
3
+ import { u } from "../widget-store-Dn0Bnc4h.js";
4
4
  import { useShallow as g } from "zustand/shallow";
5
5
  import { ListItem as w, List as d, Link as k, Typography as m, Skeleton as s, Box as y } from "@mui/material";
6
6
  import S from "react-markdown";
7
7
  import "@mui/icons-material";
8
8
  import "react";
9
9
  import "html2canvas";
10
- import "../lasso-tool-wFqOD6wk.js";
10
+ import "../lasso-tool-BctzdzBu.js";
11
11
  import "../cjs-D4KH3azB.js";
12
12
  import "@dnd-kit/core";
13
13
  import "@dnd-kit/sortable";
@@ -1,4 +1,4 @@
1
- import { W as t } from "../no-data-C54XJt13.js";
1
+ import { W as t } from "../no-data-jdlbMef0.js";
2
2
  export {
3
3
  t as WidgetNoData
4
4
  };
@@ -2,7 +2,7 @@ import { jsxs as f, jsx as i } from "react/jsx-runtime";
2
2
  import { c as S } from "react/compiler-runtime";
3
3
  import "react";
4
4
  import "echarts";
5
- import "../widget-store-CB6Trp_0.js";
5
+ import "../widget-store-Dn0Bnc4h.js";
6
6
  import "zustand/shallow";
7
7
  import { g as w } from "../options-D9wflre6.js";
8
8
  import { m as I } from "../utils-D3-eQyDR.js";
@@ -11,7 +11,7 @@ import { Box as d, Skeleton as a } from "@mui/material";
11
11
  import "@mui/icons-material";
12
12
  import "react-markdown";
13
13
  import { d as y, a as x } from "../exports-Cr43OCul.js";
14
- import "../lasso-tool-wFqOD6wk.js";
14
+ import "../lasso-tool-BctzdzBu.js";
15
15
  import "../cjs-D4KH3azB.js";
16
16
  import "@dnd-kit/core";
17
17
  import "@dnd-kit/sortable";
@@ -1,9 +1,9 @@
1
1
  import { jsx as g, jsxs as P } from "react/jsx-runtime";
2
2
  import { c as j } from "react/compiler-runtime";
3
- import { R as Z } from "../row-DrHwXNvF.js";
3
+ import { R as Z } from "../row-D3uVFImu.js";
4
4
  import { Box as $, Slider as ee, TextField as te, Skeleton as G } from "@mui/material";
5
5
  import { useState as Q } from "react";
6
- import { u as H } from "../widget-store-CB6Trp_0.js";
6
+ import { u as H } from "../widget-store-Dn0Bnc4h.js";
7
7
  import { useShallow as ne } from "zustand/shallow";
8
8
  const _ = {
9
9
  rangeItem: {
@@ -2,7 +2,7 @@ import { jsxs as g, jsx as i } from "react/jsx-runtime";
2
2
  import { c as b } from "react/compiler-runtime";
3
3
  import "react";
4
4
  import "echarts";
5
- import "../widget-store-CB6Trp_0.js";
5
+ import "../widget-store-Dn0Bnc4h.js";
6
6
  import "zustand/shallow";
7
7
  import { g as w } from "../options-D9wflre6.js";
8
8
  import { m as S } from "../utils-D3-eQyDR.js";
@@ -11,7 +11,7 @@ import { Box as s, Skeleton as p } from "@mui/material";
11
11
  import "@mui/icons-material";
12
12
  import "react-markdown";
13
13
  import { d as c, a as f } from "../exports-Cr43OCul.js";
14
- import "../lasso-tool-wFqOD6wk.js";
14
+ import "../lasso-tool-BctzdzBu.js";
15
15
  import "../cjs-D4KH3azB.js";
16
16
  import "@dnd-kit/core";
17
17
  import "@dnd-kit/sortable";
@@ -1,6 +1,6 @@
1
1
  import { jsx as n } from "react/jsx-runtime";
2
2
  import { c as f } from "react/compiler-runtime";
3
- import { u as m } from "../widget-store-CB6Trp_0.js";
3
+ import { u as m } from "../widget-store-Dn0Bnc4h.js";
4
4
  import { Suspense as u } from "react";
5
5
  import { useShallow as d } from "zustand/shallow";
6
6
  function h(l) {
@@ -1,16 +1,16 @@
1
1
  import { jsx as d, jsxs as I, Fragment as M } from "react/jsx-runtime";
2
2
  import { c as S } from "react/compiler-runtime";
3
- import { I as w, S as W, P as k, a as C } from "../series-D3Pc-kYX.js";
4
- import { u as P } from "../widget-store-CB6Trp_0.js";
3
+ import { I as w, S as W, P as k, a as C } from "../series-BAImrSBo.js";
4
+ import { u as P } from "../widget-store-Dn0Bnc4h.js";
5
5
  import { useShallow as T } from "zustand/shallow";
6
6
  import { Box as v, Skeleton as F } from "@mui/material";
7
- import { R } from "../row-DrHwXNvF.js";
7
+ import { R } from "../row-D3uVFImu.js";
8
8
  import "@mui/icons-material";
9
9
  import "react";
10
- import { u as V } from "../use-widget-ref-B0aNCANx.js";
10
+ import { u as V } from "../use-widget-ref-B8x4sHIj.js";
11
11
  import "react-markdown";
12
12
  import "html2canvas";
13
- import "../lasso-tool-wFqOD6wk.js";
13
+ import "../lasso-tool-BctzdzBu.js";
14
14
  import "../cjs-D4KH3azB.js";
15
15
  import "@dnd-kit/core";
16
16
  import "@dnd-kit/sortable";
@@ -1,4 +1,4 @@
1
- import { u as r } from "../widget-store-CB6Trp_0.js";
1
+ import { u as r } from "../widget-store-Dn0Bnc4h.js";
2
2
  export {
3
3
  r as useWidgetStore
4
4
  };
@@ -7,12 +7,12 @@ import ue from "@mui/icons-material/KeyboardArrowLeft";
7
7
  import ge from "@mui/icons-material/KeyboardArrowRight";
8
8
  import de from "@mui/icons-material/LastPage";
9
9
  import "react";
10
- import { u as pe } from "../use-widget-ref-B0aNCANx.js";
11
- import { u as M } from "../widget-store-CB6Trp_0.js";
10
+ import { u as pe } from "../use-widget-ref-B8x4sHIj.js";
11
+ import { u as M } from "../widget-store-Dn0Bnc4h.js";
12
12
  import { useShallow as j } from "zustand/shallow";
13
13
  import "@mui/icons-material";
14
14
  import { d as K, a as H } from "../exports-Cr43OCul.js";
15
- import "../lasso-tool-wFqOD6wk.js";
15
+ import "../lasso-tool-BctzdzBu.js";
16
16
  import "../cjs-D4KH3azB.js";
17
17
  import "@dnd-kit/core";
18
18
  import "@dnd-kit/sortable";
@@ -2,7 +2,7 @@ import { jsxs as c, jsx as n } from "react/jsx-runtime";
2
2
  import { c as b } from "react/compiler-runtime";
3
3
  import "react";
4
4
  import "echarts";
5
- import "../widget-store-CB6Trp_0.js";
5
+ import "../widget-store-Dn0Bnc4h.js";
6
6
  import "zustand/shallow";
7
7
  import { g as k } from "../options-D9wflre6.js";
8
8
  import { m as w } from "../utils-D3-eQyDR.js";
@@ -11,7 +11,7 @@ import { Box as s, Skeleton as o } from "@mui/material";
11
11
  import "@mui/icons-material";
12
12
  import "react-markdown";
13
13
  import { d as g, a as d } from "../exports-Cr43OCul.js";
14
- import "../lasso-tool-wFqOD6wk.js";
14
+ import "../lasso-tool-BctzdzBu.js";
15
15
  import "../cjs-D4KH3azB.js";
16
16
  import "@dnd-kit/core";
17
17
  import "@dnd-kit/sortable";
@@ -3,10 +3,10 @@ import { c as _ } from "react/compiler-runtime";
3
3
  import { Box as O, IconButton as L, MenuItem as P, ListItemIcon as j, ListItemText as z, Menu as D, Typography as H, LinearProgress as N, AccordionSummary as G, AccordionDetails as U, Accordion as V } from "@mui/material";
4
4
  import { MoreVert as q } from "@mui/icons-material";
5
5
  import { useState as J, useEffect as K } from "react";
6
- import "../lasso-tool-wFqOD6wk.js";
6
+ import "../lasso-tool-BctzdzBu.js";
7
7
  import "../cjs-D4KH3azB.js";
8
8
  import { S as Q } from "../smart-tooltip-BEtBaIdz.js";
9
- import { u as T } from "../widget-store-CB6Trp_0.js";
9
+ import { u as T } from "../widget-store-Dn0Bnc4h.js";
10
10
  import { useShallow as R } from "zustand/shallow";
11
11
  const A = {
12
12
  root: {
package/dist/widgets.js CHANGED
@@ -1,7 +1,7 @@
1
- import { u as r } from "./widget-store-CB6Trp_0.js";
2
- import { u as W } from "./use-widget-ref-B0aNCANx.js";
3
- import { W as s } from "./no-data-C54XJt13.js";
4
- import { W as d } from "./error-B2IJ9d2h.js";
1
+ import { u as r } from "./widget-store-Dn0Bnc4h.js";
2
+ import { u as W } from "./use-widget-ref-B8x4sHIj.js";
3
+ import { W as s } from "./no-data-jdlbMef0.js";
4
+ import { W as d } from "./error-piB8FwYO.js";
5
5
  import { W as i } from "./note-t51drNe0.js";
6
6
  export {
7
7
  d as WidgetError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carto/ps-react-ui",
3
- "version": "4.3.5",
3
+ "version": "4.3.6",
4
4
  "description": "CARTO's Professional Service React Material library",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -131,7 +131,7 @@ function LassoToolsUIAction({
131
131
  }
132
132
 
133
133
  function Options({
134
- TriggerProps: { Icon = <ArrowDropDown />, sx } = {},
134
+ TriggerProps = {},
135
135
  MenuProps,
136
136
  children,
137
137
  }: {
@@ -142,6 +142,9 @@ function Options({
142
142
  MenuProps?: Partial<ComponentProps<typeof Menu>>
143
143
  children: (props: OptionsChildrenProps) => JSX.Element
144
144
  }) {
145
+ const { Icon, sx } = TriggerProps
146
+ const IconNode = Icon ?? <ArrowDropDown />
147
+
145
148
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
146
149
 
147
150
  const open = Boolean(anchorEl)
@@ -163,7 +166,7 @@ function Options({
163
166
  }}
164
167
  onClick={handleToggle}
165
168
  >
166
- {Icon}
169
+ {IconNode}
167
170
  </IconButton>
168
171
  <Menu
169
172
  id='lasso-menu'
@@ -15,7 +15,7 @@ export {
15
15
  export type { RelativeDataProps } from './relative-data/types'
16
16
 
17
17
  /* Zoom Toggle Widget */
18
- export { ZoomToggle } from './zoom-toggle/zoom-toggle'
18
+ export { ZoomToggle, ZOOM_TOGGLE_TOOL_ID } from './zoom-toggle/zoom-toggle'
19
19
  export type {
20
20
  ZoomToggleProps,
21
21
  ZoomState,
@@ -23,7 +23,7 @@ export type {
23
23
  } from './zoom-toggle/types'
24
24
 
25
25
  /* Stack Toggle Widget */
26
- export { StackToggle } from './stack-toggle/stack-toggle'
26
+ export { StackToggle, STACK_TOGGLE_TOOL_ID } from './stack-toggle/stack-toggle'
27
27
  export type { StackToggleProps, StackToggleState } from './stack-toggle/types'
28
28
 
29
29
  /* Searcher Toggle Widget */
@@ -1,6 +1,6 @@
1
1
  import { describe, test, expect, beforeEach } from 'vitest'
2
2
  import { render, screen, fireEvent, waitFor } from '@testing-library/react'
3
- import { StackToggle } from './stack-toggle'
3
+ import { StackToggle, STACK_TOGGLE_TOOL_ID } from './stack-toggle'
4
4
  import { useWidgetStore } from '../../stores/widget-store'
5
5
  import { DEFAULT_STACK_GROUP } from '../../echart/const'
6
6
 
@@ -113,19 +113,28 @@ describe('StackToggle', () => {
113
113
  expect((widget as { isStacked?: boolean })?.isStacked).toBe(true)
114
114
  })
115
115
 
116
- test('updates EChart series options when toggling to stacked', () => {
117
- // setupMultiSeriesWidget already ran in beforeEach
116
+ test('applies stack to EChart series via config pipeline when toggling to stacked', async () => {
118
117
  render(<StackToggle id={widgetId} />)
119
118
  const button = screen.getByRole('button')
120
119
  fireEvent.click(button)
121
120
 
121
+ // Run the config pipeline to apply the stack tool's transformation
122
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
123
+ option: {
124
+ series: [
125
+ { name: 'Series 1', type: 'bar' },
126
+ { name: 'Series 2', type: 'bar' },
127
+ ],
128
+ },
129
+ })
130
+
122
131
  const widget = useWidgetStore.getState().getWidget(widgetId)
123
132
  const series = (widget as { option?: { series?: { stack?: string }[] } })
124
133
  ?.option?.series?.[0]
125
134
  expect(series?.stack).toBe(DEFAULT_STACK_GROUP)
126
135
  })
127
136
 
128
- test('removes stack from EChart series when toggling to unstacked', () => {
137
+ test('removes stack from EChart series via config pipeline when toggling to unstacked', async () => {
129
138
  useWidgetStore.getState().setWidget(widgetId, {
130
139
  option: {
131
140
  series: [
@@ -141,13 +150,23 @@ describe('StackToggle', () => {
141
150
  // First click unstacks (since series has stack, it starts stacked)
142
151
  fireEvent.click(button)
143
152
 
153
+ // Run the config pipeline — tool is disabled when unstacked, so stack is removed
154
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
155
+ option: {
156
+ series: [
157
+ { name: 'Series 1', type: 'bar' },
158
+ { name: 'Series 2', type: 'bar' },
159
+ ],
160
+ },
161
+ })
162
+
144
163
  const widget = useWidgetStore.getState().getWidget(widgetId)
145
164
  const series = (widget as { option?: { series?: { stack?: string }[] } })
146
165
  ?.option?.series?.[0]
147
166
  expect(series?.stack).toBeUndefined()
148
167
  })
149
168
 
150
- test('updates multiple series when toggling stack', () => {
169
+ test('applies stack to multiple series via config pipeline', async () => {
151
170
  useWidgetStore.getState().setWidget(widgetId, {
152
171
  option: {
153
172
  series: [
@@ -162,6 +181,16 @@ describe('StackToggle', () => {
162
181
  const button = screen.getByRole('button')
163
182
  fireEvent.click(button)
164
183
 
184
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
185
+ option: {
186
+ series: [
187
+ { name: 'Series 1', type: 'bar' },
188
+ { name: 'Series 2', type: 'bar' },
189
+ { name: 'Series 3', type: 'bar' },
190
+ ],
191
+ },
192
+ })
193
+
165
194
  const widget = useWidgetStore.getState().getWidget(widgetId)
166
195
  const seriesArray = (
167
196
  widget as { option?: { series?: { stack?: string }[] } }
@@ -173,7 +202,7 @@ describe('StackToggle', () => {
173
202
  })
174
203
  })
175
204
 
176
- test('uses default stack group when re-stacking after unstacking', () => {
205
+ test('preserves custom stack group via config pipeline', async () => {
177
206
  const customStackGroup = 'custom-group'
178
207
  useWidgetStore.getState().setWidget(widgetId, {
179
208
  option: {
@@ -187,11 +216,20 @@ describe('StackToggle', () => {
187
216
  render(<StackToggle id={widgetId} />)
188
217
  const button = screen.getByRole('button')
189
218
 
190
- // Toggle off then on - re-stacking uses default stack group
219
+ // Toggle off then on
191
220
  fireEvent.click(button) // unstacked
192
221
  fireEvent.click(button) // stacked again
193
222
 
194
- void waitFor(() => {
223
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
224
+ option: {
225
+ series: [
226
+ { name: 'Series 1', type: 'bar', stack: customStackGroup },
227
+ { name: 'Series 2', type: 'bar', stack: customStackGroup },
228
+ ],
229
+ },
230
+ })
231
+
232
+ await waitFor(() => {
195
233
  const widget = useWidgetStore.getState().getWidget(widgetId)
196
234
  const series = (widget as { option?: { series?: { stack?: string }[] } })
197
235
  ?.option?.series?.[0]
@@ -238,7 +276,75 @@ describe('StackToggle', () => {
238
276
  expect(container.firstChild).toBeNull()
239
277
  })
240
278
 
241
- test('preserves other option properties when updating series', () => {
279
+ test('config tool re-applies stack when config pipeline re-runs', async () => {
280
+ // Start with stacked widget
281
+ useWidgetStore.getState().setWidget(widgetId, {
282
+ isStacked: true,
283
+ option: {
284
+ series: [
285
+ { name: 'Series 1', type: 'bar', stack: DEFAULT_STACK_GROUP },
286
+ { name: 'Series 2', type: 'bar', stack: DEFAULT_STACK_GROUP },
287
+ ],
288
+ },
289
+ })
290
+
291
+ render(<StackToggle id={widgetId} defaultIsStacked />)
292
+
293
+ // Simulate loader running config pipeline with base config (no stack)
294
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
295
+ option: {
296
+ series: [
297
+ { name: 'Series 1', type: 'bar' },
298
+ { name: 'Series 2', type: 'bar' },
299
+ ],
300
+ },
301
+ })
302
+
303
+ // Stack should be re-applied by the config tool
304
+ await waitFor(() => {
305
+ const widget = useWidgetStore.getState().getWidget(widgetId)
306
+ const series = (widget as { option?: { series?: { stack?: string }[] } })
307
+ ?.option?.series
308
+ expect(series?.[0]?.stack).toBe(DEFAULT_STACK_GROUP)
309
+ expect(series?.[1]?.stack).toBe(DEFAULT_STACK_GROUP)
310
+ })
311
+ })
312
+
313
+ test('config tool does not apply stack when unstacked and pipeline re-runs', async () => {
314
+ // Start with unstacked widget
315
+ useWidgetStore.getState().setWidget(widgetId, {
316
+ isStacked: false,
317
+ option: {
318
+ series: [
319
+ { name: 'Series 1', type: 'bar' },
320
+ { name: 'Series 2', type: 'bar' },
321
+ ],
322
+ },
323
+ })
324
+
325
+ render(<StackToggle id={widgetId} />)
326
+
327
+ // Run config pipeline with base config
328
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
329
+ option: {
330
+ series: [
331
+ { name: 'Series 1', type: 'bar' },
332
+ { name: 'Series 2', type: 'bar' },
333
+ ],
334
+ },
335
+ })
336
+
337
+ // Stack should NOT be applied since isStacked is false (tool disabled)
338
+ await waitFor(() => {
339
+ const widget = useWidgetStore.getState().getWidget(widgetId)
340
+ const series = (widget as { option?: { series?: { stack?: string }[] } })
341
+ ?.option?.series
342
+ expect(series?.[0]?.stack).toBeUndefined()
343
+ expect(series?.[1]?.stack).toBeUndefined()
344
+ })
345
+ })
346
+
347
+ test('preserves other option properties via config pipeline', async () => {
242
348
  useWidgetStore.getState().setWidget(widgetId, {
243
349
  option: {
244
350
  title: { text: 'My Chart' },
@@ -254,6 +360,17 @@ describe('StackToggle', () => {
254
360
  const button = screen.getByRole('button')
255
361
  fireEvent.click(button)
256
362
 
363
+ await useWidgetStore.getState().executeConfigPipeline(widgetId, {
364
+ option: {
365
+ title: { text: 'My Chart' },
366
+ xAxis: { type: 'category' },
367
+ series: [
368
+ { name: 'Series 1', type: 'bar' },
369
+ { name: 'Series 2', type: 'bar' },
370
+ ],
371
+ },
372
+ })
373
+
257
374
  const widget = useWidgetStore.getState().getWidget(widgetId)
258
375
  const option = (
259
376
  widget as {
@@ -267,4 +384,21 @@ describe('StackToggle', () => {
267
384
  expect(option?.title?.text).toBe('My Chart')
268
385
  expect(option?.xAxis?.type).toBe('category')
269
386
  })
387
+
388
+ test('registers config tool on mount and unregisters on unmount', () => {
389
+ const { unmount } = render(<StackToggle id={widgetId} />)
390
+
391
+ const widget = useWidgetStore.getState().getWidget(widgetId)
392
+ const tools = widget?.registeredTools ?? []
393
+ const stackTool = tools.find((t) => t.id === STACK_TOGGLE_TOOL_ID)
394
+ expect(stackTool).toBeDefined()
395
+ expect(stackTool?.type).toBe('config')
396
+
397
+ unmount()
398
+
399
+ const widgetAfter = useWidgetStore.getState().getWidget(widgetId)
400
+ const toolsAfter = widgetAfter?.registeredTools ?? []
401
+ const stackToolAfter = toolsAfter.find((t) => t.id === STACK_TOGGLE_TOOL_ID)
402
+ expect(stackToolAfter).toBeUndefined()
403
+ })
270
404
  })