@gooddata/sdk-ui-ext 11.52.0-alpha.5 → 11.52.0-alpha.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 (40) hide show
  1. package/esm/insightView/InsightRenderer.d.ts +5 -5
  2. package/esm/insightView/InsightRenderer.d.ts.map +1 -1
  3. package/esm/insightView/InsightRenderer.js +164 -140
  4. package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.d.ts +3 -4
  5. package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.d.ts.map +1 -1
  6. package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.js +9 -7
  7. package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts.map +1 -1
  8. package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.js +57 -37
  9. package/esm/internal/components/configurationControls/conditionalFormatting/cfFieldProps.d.ts +12 -0
  10. package/esm/internal/components/configurationControls/conditionalFormatting/cfFieldProps.d.ts.map +1 -0
  11. package/esm/internal/components/configurationControls/conditionalFormatting/cfFieldProps.js +2 -0
  12. package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts +10 -10
  13. package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts.map +1 -1
  14. package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.js +11 -11
  15. package/esm/internal/components/configurationPanels/PivotTableConfigurationPanel.d.ts.map +1 -1
  16. package/esm/internal/components/configurationPanels/PivotTableConfigurationPanel.js +2 -1
  17. package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.d.ts.map +1 -1
  18. package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.js +14 -1
  19. package/esm/internal/components/pluggableVisualizations/pivotTableNext/PivotTableNextDescriptor.d.ts.map +1 -1
  20. package/esm/internal/components/pluggableVisualizations/pivotTableNext/PivotTableNextDescriptor.js +2 -9
  21. package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.d.ts.map +1 -1
  22. package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.js +2 -2
  23. package/esm/internal/components/pluggableVisualizations/pivotTableNext/pivotTableNextConfigFromInsight.d.ts +8 -3
  24. package/esm/internal/components/pluggableVisualizations/pivotTableNext/pivotTableNextConfigFromInsight.d.ts.map +1 -1
  25. package/esm/internal/components/pluggableVisualizations/pivotTableNext/pivotTableNextConfigFromInsight.js +41 -37
  26. package/esm/internal/translations/en-US.localization-bundle.d.ts +8 -0
  27. package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
  28. package/esm/internal/translations/en-US.localization-bundle.js +8 -0
  29. package/esm/internal/utils/bucketHelper.d.ts.map +1 -1
  30. package/esm/internal/utils/bucketHelper.js +7 -14
  31. package/esm/internal/utils/embeddingCodeGenerator/insightToPropsConverter/convertor.d.ts +2 -2
  32. package/esm/internal/utils/embeddingCodeGenerator/insightToPropsConverter/convertor.d.ts.map +1 -1
  33. package/esm/internal/utils/embeddingCodeGenerator/insightToPropsConverter/convertor.js +4 -2
  34. package/esm/internal/utils/propertiesHelper.d.ts +5 -0
  35. package/esm/internal/utils/propertiesHelper.d.ts.map +1 -1
  36. package/esm/internal/utils/propertiesHelper.js +7 -0
  37. package/esm/locales.d.ts +6 -0
  38. package/esm/locales.d.ts.map +1 -1
  39. package/esm/locales.js +2 -0
  40. package/package.json +21 -21
@@ -21,12 +21,11 @@ export interface IInsightRendererProps extends Omit<IInsightViewProps, "insight"
21
21
  */
22
22
  widget?: IInsightWidget;
23
23
  }
24
- export declare const IntlInsightRenderer: import("react").FC<import("react-intl").WithIntlProps<Omit<IInsightRendererProps & WrappedComponentProps & {
24
+ type InsightRendererCoreProps = IInsightRendererProps & WrappedComponentProps & {
25
25
  messages: ITranslations;
26
- }, "theme" | "themeIsLoading" | "themeStatus">>> & {
27
- WrappedComponent: import("react").ComponentType<Omit<IInsightRendererProps & WrappedComponentProps & {
28
- messages: ITranslations;
29
- }, "theme" | "themeIsLoading" | "themeStatus">>;
26
+ };
27
+ export declare const IntlInsightRenderer: import("react").FC<import("react-intl").WithIntlProps<Omit<InsightRendererCoreProps, "theme" | "themeIsLoading" | "themeStatus">>> & {
28
+ WrappedComponent: import("react").ComponentType<Omit<InsightRendererCoreProps, "theme" | "themeIsLoading" | "themeStatus">>;
30
29
  };
31
30
  /**
32
31
  * Renders insight passed as a parameter.
@@ -34,4 +33,5 @@ export declare const IntlInsightRenderer: import("react").FC<import("react-intl"
34
33
  * @internal
35
34
  */
36
35
  export declare function InsightRenderer(props: IInsightRendererProps): import("react/jsx-runtime").JSX.Element | null;
36
+ export {};
37
37
  //# sourceMappingURL=InsightRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InsightRenderer.d.ts","sourceRoot":"","sources":["../../src/insightView/InsightRenderer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,qBAAqB,EAAc,MAAM,YAAY,CAAC;AAOpE,OAAO,EAGH,KAAK,sBAAsB,EAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACH,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAM7B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAKH,KAAK,OAAO,EACZ,KAAK,aAAa,EAGlB,KAAK,OAAO,EAMf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAGH,KAAK,iBAAiB,EAIzB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAC/C,iBAAiB,EACjB,SAAS,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,WAAW,CACjE;IACG,OAAO,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC7C,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;CAC3B;AA+RD,eAAO,MAAM,mBAAmB;;;;;;CAA2D,CAAC;AAwB5F;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,kDAuC3D"}
1
+ {"version":3,"file":"InsightRenderer.d.ts","sourceRoot":"","sources":["../../src/insightView/InsightRenderer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,qBAAqB,EAAc,MAAM,YAAY,CAAC;AAOpE,OAAO,EAGH,KAAK,sBAAsB,EAC9B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACH,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAM7B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAKH,KAAK,OAAO,EACZ,KAAK,aAAa,EAGlB,KAAK,OAAO,EAMf,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAGH,KAAK,iBAAiB,EAKzB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAC/C,iBAAiB,EACjB,SAAS,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,WAAW,CACjE;IACG,OAAO,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAC7C,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,KAAK,wBAAwB,GAAG,qBAAqB,GAAG,qBAAqB,GAAG;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC;AAuV5G,eAAO,MAAM,mBAAmB;;CAA2D,CAAC;AAwB5F;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,kDAuC3D"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2020-2026 GoodData Corporation
3
- import { PureComponent, createRef, useCallback, useEffect, useRef } from "react";
3
+ import { memo, useCallback, useEffect, useRef, useState } from "react";
4
4
  import { isEqual } from "lodash-es";
5
5
  import { createRoot } from "react-dom/client";
6
6
  import { injectIntl } from "react-intl";
@@ -17,58 +17,95 @@ const visualizationUriRootStyle = {
17
17
  flex: "1 1 auto",
18
18
  flexDirection: "column",
19
19
  };
20
- // this needs to be a pure component as it can happen that this might be rendered multiple times
20
+ const noop = () => { };
21
+ const defaultFilters = [];
22
+ const defaultDrillableItems = [];
23
+ /**
24
+ * Applies the defaults on the props. The values used as defaults are referentially stable so that they do not
25
+ * trigger any unnecessary work down the line.
26
+ */
27
+ const resolveProps = (props) => ({
28
+ ...props,
29
+ ErrorComponent: props.ErrorComponent ?? ErrorComponent,
30
+ filters: props.filters ?? defaultFilters,
31
+ drillableItems: props.drillableItems ?? defaultDrillableItems,
32
+ LoadingComponent: props.LoadingComponent ?? LoadingComponent,
33
+ pushData: props.pushData ?? noop,
34
+ locale: props.locale ?? DefaultLocale,
35
+ afterRender: props.afterRender ?? noop,
36
+ });
37
+ // this needs to be a memoized component as it can happen that this might be rendered multiple times
21
38
  // with the same props (referentially) - this might make the rendered visualization behave unpredictably
22
39
  // and is bad for performance so we need to make sure the re-renders are performed only if necessary
23
- class InsightRendererCore extends PureComponent {
24
- elementId = getElementId();
25
- visualization;
26
- containerRef = createRef();
40
+ const InsightRendererCore = memo(function InsightRendererCore(props) {
41
+ const currentProps = resolveProps(props);
42
+ const [elementId] = useState(getElementId);
43
+ const visualizationRef = useRef(undefined);
44
+ const containerRef = useRef(null);
27
45
  /**
28
46
  * The component may render both visualization and config panel. In React18 we therefore need two
29
47
  * roots with their respective render methods. This Map holds the roots for both and provides
30
48
  * render and unmount methods whenever needed.
31
49
  */
32
- reactRootsMap = new Map();
33
- static defaultProps = {
34
- ErrorComponent,
35
- filters: [],
36
- drillableItems: [],
37
- LoadingComponent,
38
- pushData: () => { },
39
- locale: DefaultLocale,
40
- afterRender: () => { },
41
- };
42
- unmountVisualization = () => {
43
- if (this.visualization) {
44
- this.visualization.unmount();
50
+ const reactRootsMapRef = useRef(new Map());
51
+ /**
52
+ * The callbacks and element getters below outlive the render they were created in (the visualization
53
+ * instance only ever gets the ones from the render it was set up in). They therefore read the props from
54
+ * this ref to always see the current values - the very same way `this.props` did before.
55
+ *
56
+ * This effect must stay first so that the ref is up to date before any other effect runs.
57
+ */
58
+ const propsRef = useRef(currentProps);
59
+ useEffect(() => {
60
+ propsRef.current = currentProps;
61
+ });
62
+ const unmountVisualization = useCallback(() => {
63
+ if (visualizationRef.current) {
64
+ visualizationRef.current.unmount();
65
+ }
66
+ visualizationRef.current = undefined;
67
+ }, []);
68
+ const getExecutionFactory = useCallback(() => {
69
+ const { backend, workspace, executeByReference, filters } = propsRef.current;
70
+ const factory = backend.workspace(workspace).execution();
71
+ if (executeByReference) {
72
+ /*
73
+ * When executing by reference, decorate the original execution factory so that it
74
+ * transparently routes `forInsight` to `forInsightByRef` AND adds the filters
75
+ * from InsightView props.
76
+ *
77
+ * Code will pass this factory over to the pluggable visualizations - they will do execution
78
+ * `forInsight` and under the covers things will be routed and done differently without the
79
+ * plug viz knowing.
80
+ */
81
+ return new ExecutionFactoryUpgradingToExecByReference(new ExecutionFactoryWithFixedFilters(factory, filters));
45
82
  }
46
- this.visualization = undefined;
47
- };
48
- updateVisualization = () => {
83
+ return factory;
84
+ }, []);
85
+ const updateVisualization = useCallback(() => {
49
86
  // if the container no longer exists, update was called after unmount -> do nothing
50
- if (!this.visualization || !this.containerRef.current) {
87
+ if (!visualizationRef.current || !containerRef.current) {
51
88
  return;
52
89
  }
90
+ const { insight: insightProp, config = {}, settings, locale, messages, widget, drillableItems, colorPalette, execConfig, theme, } = propsRef.current;
53
91
  // if there is no insight, bail early
54
- if (!this.props.insight) {
92
+ if (!insightProp) {
55
93
  return;
56
94
  }
57
- const { config = {} } = this.props;
58
- const { responsiveUiDateFormat } = this.props.settings ?? {};
95
+ const { responsiveUiDateFormat } = settings ?? {};
59
96
  const visProps = {
60
- locale: this.props.locale,
61
- messages: this.props.messages,
97
+ locale,
98
+ messages,
62
99
  dateFormat: responsiveUiDateFormat,
63
- a11yTitle: this.props.widget?.title,
64
- a11yDescription: this.props.widget?.description,
100
+ a11yTitle: widget?.title,
101
+ a11yDescription: widget?.description,
65
102
  custom: {
66
- drillableItems: this.props.drillableItems,
67
- lastSavedVisClassUrl: insightVisualizationUrl(this.props.insight),
103
+ drillableItems,
104
+ lastSavedVisClassUrl: insightVisualizationUrl(insightProp),
68
105
  },
69
106
  config: {
70
107
  separators: config.separators,
71
- colorPalette: this.props.colorPalette,
108
+ colorPalette,
72
109
  mapboxToken: config.mapboxToken,
73
110
  agGridToken: config.agGridToken,
74
111
  forceDisableDrillOnAxes: config.forceDisableDrillOnAxes,
@@ -76,13 +113,13 @@ class InsightRendererCore extends PureComponent {
76
113
  isExportMode: config.isExportMode,
77
114
  enableExecutionCancelling: config.enableExecutionCancelling,
78
115
  },
79
- executionConfig: this.props.execConfig,
116
+ executionConfig: execConfig,
80
117
  customVisualizationConfig: config,
81
- theme: this.props.theme,
118
+ theme,
82
119
  supportsChartFill: config.supportsChartFill,
83
120
  };
84
- const visClass = insightVisualizationType(this.props.insight);
85
- const filled = fillMissingTitlesWithMessages(this.props.insight, this.props.locale, this.props.messages, undefined);
121
+ const visClass = insightVisualizationType(insightProp);
122
+ const filled = fillMissingTitlesWithMessages(insightProp, locale, messages, undefined);
86
123
  let insight = fillMissingFormats(filled);
87
124
  /**
88
125
  * Ignore titles for simple measures in all visualizations except for repeater.
@@ -95,29 +132,60 @@ class InsightRendererCore extends PureComponent {
95
132
  if (visClass !== "repeater") {
96
133
  insight = ignoreTitlesForSimpleMeasures(insight);
97
134
  }
98
- this.visualization?.update(visProps, insight, {}, this.getExecutionFactory());
99
- };
100
- setupVisualization = () => {
135
+ visualizationRef.current?.update(visProps, insight, {}, getExecutionFactory());
136
+ }, [getExecutionFactory]);
137
+ const reactRenderFunction = useCallback((children, element) => {
138
+ if (!element) {
139
+ return;
140
+ }
141
+ const htmlElement = element;
142
+ if (!reactRootsMapRef.current.get(htmlElement)) {
143
+ reactRootsMapRef.current.set(htmlElement, createRoot(htmlElement));
144
+ }
145
+ reactRootsMapRef.current.get(htmlElement).render(children);
146
+ }, []);
147
+ const reactUnmountFunction = useCallback(() => {
148
+ reactRootsMapRef.current.forEach((root) => root.render(null));
149
+ }, []);
150
+ const onExportReadyDecorator = useCallback((exportFunction) => {
151
+ const { onExportReady } = propsRef.current;
152
+ if (!onExportReady) {
153
+ return;
154
+ }
155
+ const decorator = (exportConfig) => {
156
+ const { insight } = propsRef.current;
157
+ if (exportConfig.title || !insight) {
158
+ return exportFunction(exportConfig);
159
+ }
160
+ return exportFunction({
161
+ ...exportConfig,
162
+ title: insightTitle(insight),
163
+ });
164
+ };
165
+ onExportReady(decorator);
166
+ }, []);
167
+ const setupVisualization = useCallback(() => {
168
+ const { insight, settings, backend, workspace, permissions, locale, messages, pushData, onDrill, onDataView, onLoadingChanged, afterRender, } = propsRef.current;
101
169
  // if there is no insight, bail early
102
- if (!this.props.insight) {
170
+ if (!insight) {
103
171
  return;
104
172
  }
105
173
  // the visualization we may have from earlier is no longer valid -> get rid of it
106
- this.unmountVisualization();
107
- const visualizationFactory = FullVisualizationCatalog.forInsight(this.props.insight, this.props.settings?.enableNewPivotTable ?? true, this.props.settings?.enableNewGeoPushpin ?? false).getFactory();
108
- this.visualization = visualizationFactory({
109
- backend: this.props.backend,
174
+ unmountVisualization();
175
+ const visualizationFactory = FullVisualizationCatalog.forInsight(insight, settings?.enableNewPivotTable ?? true, settings?.enableNewGeoPushpin ?? false).getFactory();
176
+ visualizationRef.current = visualizationFactory({
177
+ backend: backend,
110
178
  callbacks: {
111
179
  onError: (error) => {
112
- this.props.onError?.(error);
113
- this.props.onLoadingChanged?.({ isLoading: false });
180
+ propsRef.current.onError?.(error);
181
+ propsRef.current.onLoadingChanged?.({ isLoading: false });
114
182
  },
115
- onLoadingChanged: this.props.onLoadingChanged,
116
- pushData: this.props.pushData,
117
- onDrill: this.props.onDrill,
118
- onDataView: this.props.onDataView,
119
- onExportReady: this.onExportReadyDecorator,
120
- afterRender: this.props.afterRender,
183
+ onLoadingChanged,
184
+ pushData,
185
+ onDrill,
186
+ onDataView,
187
+ onExportReady: onExportReadyDecorator,
188
+ afterRender,
121
189
  },
122
190
  // This renderer has no configuration panel of its own — it renders read-only insights.
123
191
  // It used to resolve the panel with a document-wide lookup of the well-known
@@ -130,106 +198,62 @@ class InsightRendererCore extends PureComponent {
130
198
  // fix STL-3184
131
199
  configPanelElement: () => null,
132
200
  element: () => {
133
- const rootNode = this.containerRef.current?.getRootNode() ?? document;
134
- return rootNode.querySelector(`#${this.elementId}`);
201
+ const rootNode = containerRef.current?.getRootNode() ?? document;
202
+ return rootNode.querySelector(`#${elementId}`);
135
203
  },
136
204
  environment: "dashboards", // TODO get rid of this
137
- locale: this.props.locale,
138
- messages: this.props.messages,
139
- projectId: this.props.workspace,
140
- visualizationProperties: insightProperties(this.props.insight),
141
- featureFlags: this.props.settings,
142
- permissions: this.props.permissions,
143
- renderFun: this.getReactRenderFunction(),
144
- unmountFun: this.getReactUnmountFunction(),
205
+ locale,
206
+ messages,
207
+ projectId: workspace,
208
+ visualizationProperties: insightProperties(insight),
209
+ featureFlags: settings,
210
+ permissions,
211
+ renderFun: reactRenderFunction,
212
+ unmountFun: reactUnmountFunction,
145
213
  });
146
- };
147
- getReactRenderFunction = () => {
148
- return (children, element) => {
149
- if (!element) {
150
- return;
151
- }
152
- const htmlElement = element;
153
- if (!this.reactRootsMap.get(htmlElement)) {
154
- this.reactRootsMap.set(htmlElement, createRoot(htmlElement));
155
- }
156
- this.reactRootsMap.get(htmlElement).render(children);
157
- };
158
- };
159
- getReactUnmountFunction = () => {
160
- return () => this.reactRootsMap.forEach((root) => root.render(null));
161
- };
162
- onExportReadyDecorator = (exportFunction) => {
163
- if (!this.props.onExportReady) {
214
+ }, [elementId, onExportReadyDecorator, reactRenderFunction, reactUnmountFunction, unmountVisualization]);
215
+ const prevPropsRef = useRef(undefined);
216
+ // componentDidMount & componentDidUpdate: intentionally without dependencies so that it runs after
217
+ // every render of this (memoized) component
218
+ useEffect(() => {
219
+ const prevProps = prevPropsRef.current;
220
+ prevPropsRef.current = currentProps;
221
+ if (!prevProps) {
222
+ setupVisualization();
223
+ updateVisualization();
164
224
  return;
165
225
  }
166
- const decorator = (exportConfig) => {
167
- if (exportConfig.title || !this.props.insight) {
168
- return exportFunction(exportConfig);
169
- }
170
- return exportFunction({
171
- ...exportConfig,
172
- title: insightTitle(this.props.insight),
173
- });
174
- };
175
- this.props.onExportReady(decorator);
176
- };
177
- getExecutionFactory = () => {
178
- const factory = this.props.backend.workspace(this.props.workspace).execution();
179
- if (this.props.executeByReference) {
180
- /*
181
- * When executing by reference, decorate the original execution factory so that it
182
- * transparently routes `forInsight` to `forInsightByRef` AND adds the filters
183
- * from InsightView props.
184
- *
185
- * Code will pass this factory over to the pluggable visualizations - they will do execution
186
- * `forInsight` and under the covers things will be routed and done differently without the
187
- * plug viz knowing.
188
- */
189
- return new ExecutionFactoryUpgradingToExecByReference(new ExecutionFactoryWithFixedFilters(factory, this.props.filters));
190
- }
191
- return factory;
192
- };
193
- componentDidMountInner = () => {
194
- this.setupVisualization();
195
- return this.updateVisualization();
196
- };
197
- componentDidMount() {
198
- this.componentDidMountInner();
199
- }
200
- componentDidUpdateInner = (prevProps) => {
201
226
  /**
202
227
  * Ignore properties when comparing insights to determine if a new setup is needed: changes to properties
203
228
  * only will be handled using the updateVisualization without unnecessary new setup just fine.
204
229
  */
205
230
  const prevInsightForCompare = prevProps.insight && insightSetProperties(prevProps.insight, {});
206
- const newInsightForCompare = this.props.insight && insightSetProperties(this.props.insight, {});
231
+ const newInsightForCompare = currentProps.insight && insightSetProperties(currentProps.insight, {});
207
232
  const needsNewSetup = !isEqual(newInsightForCompare, prevInsightForCompare) ||
208
- !isEqual(this.props.filters, prevProps.filters) ||
209
- !isEqual(this.props.settings, prevProps.settings) ||
210
- this.props.workspace !== prevProps.workspace;
233
+ !isEqual(currentProps.filters, prevProps.filters) ||
234
+ !isEqual(currentProps.settings, prevProps.settings) ||
235
+ currentProps.workspace !== prevProps.workspace;
211
236
  if (needsNewSetup) {
212
- this.setupVisualization();
237
+ setupVisualization();
213
238
  }
214
- return this.updateVisualization();
215
- };
216
- componentDidUpdate(prevProps) {
217
- this.componentDidUpdateInner(prevProps);
218
- }
219
- componentWillUnmount() {
220
- this.unmountVisualization();
221
- // In order to avoid race conditions when mounting and unmounting synchronously,
222
- // we use timeout for React18.
223
- // https://github.com/facebook/react/issues/25675
224
- this.reactRootsMap.forEach((root) => setTimeout(() => root.unmount(), 0));
225
- }
226
- render() {
227
- return (
228
- // never ever dynamically change the props of this div, otherwise bad things will happen
229
- // e.g. visualization being rendered multiple times, etc.
230
- _jsx("div", { className: "visualization-uri-root", id: this.elementId, ref: this.containerRef, style: visualizationUriRootStyle }));
231
- }
232
- }
239
+ updateVisualization();
240
+ });
241
+ // componentWillUnmount
242
+ useEffect(() => {
243
+ const reactRootsMap = reactRootsMapRef.current;
244
+ return () => {
245
+ unmountVisualization();
246
+ // In order to avoid race conditions when mounting and unmounting synchronously,
247
+ // we use timeout for React18.
248
+ // https://github.com/facebook/react/issues/25675
249
+ reactRootsMap.forEach((root) => setTimeout(() => root.unmount(), 0));
250
+ };
251
+ }, [unmountVisualization]);
252
+ return (
253
+ // never ever dynamically change the props of this div, otherwise bad things will happen
254
+ // e.g. visualization being rendered multiple times, etc.
255
+ _jsx("div", { className: "visualization-uri-root", id: elementId, ref: containerRef, style: visualizationUriRootStyle }));
256
+ });
233
257
  export const IntlInsightRenderer = injectIntl(withTheme(withContexts(InsightRendererCore)));
234
258
  /**
235
259
  * Updated callback (callback with a different reference) is not properly propagated to the "visualization" instance
@@ -1,15 +1,14 @@
1
1
  import { type IDateFilterOptionsByType } from "@gooddata/sdk-ui-filters";
2
2
  import { type ConditionalFormattingValue } from "@gooddata/sdk-ui-pivot/next";
3
+ import { type ICfFieldProps } from "./cfFieldProps.js";
3
4
  import { type ICfDateMeta, type ICfDateSettings } from "./conditionalFormattingModel.js";
4
- export interface ICfDateValueDropdownProps {
5
+ export interface ICfDateValueDropdownProps extends ICfFieldProps {
5
6
  value: ConditionalFormattingValue;
6
7
  date: ICfDateMeta;
7
8
  /** Workspace preset catalog (shared with the dashboard date filter); undefined = static only. */
8
9
  catalogOptions?: IDateFilterOptionsByType;
9
10
  /** Workspace date-display settings (format, week start). */
10
11
  dateSettings?: ICfDateSettings;
11
- /** Id of the rendered inline error, associated with the trigger for assistive tech. */
12
- errorId?: string;
13
12
  onChange: (value: ConditionalFormattingValue) => void;
14
13
  }
15
14
  /**
@@ -21,5 +20,5 @@ export interface ICfDateValueDropdownProps {
21
20
  * already-shifted range (no flag), and reopening reconstructs the preset + checked toggle by
22
21
  * matching the stored range against "preset shifted by one".
23
22
  */
24
- export declare function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, errorId, onChange }: ICfDateValueDropdownProps): import("react/jsx-runtime").JSX.Element;
23
+ export declare function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, hasError, errorId, onChange, onVisit }: ICfDateValueDropdownProps): import("react/jsx-runtime").JSX.Element;
25
24
  //# sourceMappingURL=CfDateValueDropdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CfDateValueDropdown.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.tsx"],"names":[],"mappings":"AAQA,OAAO,EAKH,KAAK,wBAAwB,EAIhC,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACH,KAAK,0BAA0B,EAKlC,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AA6GzF,MAAM,WAAW,yBAAyB;IACtC,KAAK,EAAE,0BAA0B,CAAC;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,iGAAiG;IACjG,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,4DAA4D;IAC5D,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,KAAK,EACL,IAAI,EACJ,cAAc,EACd,YAAY,EACZ,OAAO,EACP,QAAQ,EACX,EAAE,yBAAyB,2CA4E3B"}
1
+ {"version":3,"file":"CfDateValueDropdown.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.tsx"],"names":[],"mappings":"AAQA,OAAO,EAKH,KAAK,wBAAwB,EAIhC,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACH,KAAK,0BAA0B,EAKlC,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AA4GzF,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC5D,KAAK,EAAE,0BAA0B,CAAC;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,iGAAiG;IACjG,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,4DAA4D;IAC5D,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,KAAK,EACL,IAAI,EACJ,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACV,EAAE,yBAAyB,2CA+E3B"}
@@ -65,15 +65,14 @@ function matchStoredValue(value, absoluteForm, filterOptions, granularity) {
65
65
  }
66
66
  return matchDateFilterToDateFilterOptionWithPreference(storedFilter, filterOptions, undefined);
67
67
  }
68
- // DateFilter's button contract is fixed, so the error association travels via context.
68
+ // DateFilter's button contract is fixed, so the error state travels via context.
69
69
  const CfDateButtonA11yContext = createContext({});
70
70
  // Rendered through DateFilter's button slot. Module-level so its identity is stable across renders.
71
71
  // Deliberately NOT wiring props.onClick: DateFilterCore's wrapping span already toggles the dropdown,
72
72
  // so wiring both toggles it twice per click (open → instantly closed).
73
73
  function CfDateButton({ customFilterName, textSubtitle, isOpen, buttonRef, dropdownId, }) {
74
- // describedBy is only set while the value has an inline error — it doubles as the error state.
75
- const { describedBy } = useContext(CfDateButtonA11yContext);
76
- return (_jsx(DropdownButton, { className: cx("gd-cf-date-value", { "gd-cf-date-value--error": describedBy !== undefined }), value: customFilterName ?? textSubtitle, isOpen: isOpen, isFullWidth: true, buttonRef: buttonRef, dropdownId: dropdownId, accessibilityConfig: describedBy ? { ariaDescribedBy: describedBy } : undefined }));
74
+ const { describedBy, hasError } = useContext(CfDateButtonA11yContext);
75
+ return (_jsx(DropdownButton, { className: cx("gd-cf-date-value", { "gd-cf-date-value--error": hasError }), value: customFilterName ?? textSubtitle, isOpen: isOpen, isFullWidth: true, buttonRef: buttonRef, dropdownId: dropdownId, accessibilityConfig: describedBy ? { ariaDescribedBy: describedBy } : undefined }));
77
76
  }
78
77
  /**
79
78
  * The value picker of a date condition: the standard DateFilter embedded with a dialog-styled
@@ -84,7 +83,7 @@ function CfDateButton({ customFilterName, textSubtitle, isOpen, buttonRef, dropd
84
83
  * already-shifted range (no flag), and reopening reconstructs the preset + checked toggle by
85
84
  * matching the stored range against "preset shifted by one".
86
85
  */
87
- export function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, errorId, onChange, }) {
86
+ export function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, hasError, errorId, onChange, onVisit, }) {
88
87
  const intl = useIntl();
89
88
  const stored = value.kind === "absoluteDate" ? { from: value.from, to: value.to } : undefined;
90
89
  // The static form needs bounds to open on; pin the current period at mount (recomputing "now"
@@ -131,10 +130,13 @@ export function CfDateValueDropdown({ value, date, catalogOptions, dateSettings,
131
130
  }
132
131
  // allTime/emptyValues never appear among the options (the All time / Is empty operators cover them).
133
132
  };
134
- return (_jsx(CfDateButtonA11yContext.Provider, { value: { describedBy: errorId }, children: _jsx(DateFilter, { dateFilterMode: "active", filterOptions: filterOptions, availableGranularities: filterOptions.relativeForm ? [...allowedValueGranularities(date.granularity)] : [], selectedFilterOption: matched.dateFilterOption, excludeCurrentPeriod: matched.excludeCurrentPeriod, isTimeForAbsoluteRangeEnabled: usesTimeResolution(date.granularity), dateFormat: dateSettings?.dateFormat,
133
+ return (_jsx(CfDateButtonA11yContext.Provider, { value: { describedBy: errorId, hasError }, children: _jsx(DateFilter, { dateFilterMode: "active", filterOptions: filterOptions, availableGranularities: filterOptions.relativeForm ? [...allowedValueGranularities(date.granularity)] : [], selectedFilterOption: matched.dateFilterOption, excludeCurrentPeriod: matched.excludeCurrentPeriod, isTimeForAbsoluteRangeEnabled: usesTimeResolution(date.granularity), dateFormat: dateSettings?.dateFormat,
135
134
  // Week columns pin Monday: the resolver snaps ISO weeks, and a Sunday-start calendar
136
135
  // would let a selection straddle the snap boundary.
137
- weekStart: date.granularity === "GDC.time.week_us" ? "Monday" : dateSettings?.weekStart, locale: intl.locale, onApply: handleApply, overlayPositionType: "sameAsTarget", ButtonComponent: CfDateButton, customFilterName: value.kind === "none"
136
+ weekStart: date.granularity === "GDC.time.week_us" ? "Monday" : dateSettings?.weekStart, locale: intl.locale, onApply: handleApply,
137
+ // Every close counts as a visit, applied or not. Blur can't serve as that signal: opening
138
+ // the picker moves focus into its own overlay.
139
+ onClose: onVisit, overlayPositionType: "sameAsTarget", ButtonComponent: CfDateButton, customFilterName: value.kind === "none"
138
140
  ? intl.formatMessage(conditionalFormattingMessages.dialogSelectPeriod)
139
141
  : undefined }) }));
140
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConditionEditor.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAiBzE,OAAO,EACH,KAAK,2BAA2B,EAEhC,KAAK,+BAA+B,EAEvC,MAAM,6BAA6B,CAAC;AAYrC,OAAO,EAEH,KAAK,WAAW,EAChB,KAAK,eAAe,EAWvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAsTrD,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,+BAA+B,CAAC;IAC3C,IAAI,EAAE,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,WAAW,GAAG,SAAS,CAAC;IACpC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9B,iBAAiB,EAAE,wBAAwB,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,eAAe,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,CAAC,SAAS,EAAE,+BAA+B,KAAK,IAAI,CAAC;IAC/D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,IAAI,EACJ,SAAS,EACT,UAAU,EACV,WAAW,EACX,IAAI,EACJ,iBAAiB,EACjB,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,QAAQ,EACX,EAAE,qBAAqB,2CAiFvB"}
1
+ {"version":3,"file":"ConditionEditor.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionEditor.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAiBzE,OAAO,EACH,KAAK,2BAA2B,EAEhC,KAAK,+BAA+B,EAEvC,MAAM,6BAA6B,CAAC;AAarC,OAAO,EAGH,KAAK,WAAW,EAChB,KAAK,eAAe,EAWvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAwXrD,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,+BAA+B,CAAC;IAC3C,IAAI,EAAE,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,WAAW,GAAG,SAAS,CAAC;IACpC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9B,iBAAiB,EAAE,wBAAwB,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,eAAe,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,CAAC,SAAS,EAAE,+BAA+B,KAAK,IAAI,CAAC;IAC/D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,IAAI,EACJ,SAAS,EACT,UAAU,EACV,WAAW,EACX,IAAI,EACJ,iBAAiB,EACjB,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,QAAQ,EACX,EAAE,qBAAqB,2CAoFvB"}