@gooddata/sdk-ui-ext 11.53.0-alpha.0 → 11.53.0-alpha.2
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.
- package/NOTICE +59 -9
- package/esm/index.d.ts +3 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -0
- package/esm/insightView/InsightRenderer.d.ts +1 -1
- package/esm/insightView/InsightRenderer.d.ts.map +1 -1
- package/esm/insightView/InsightRenderer.js +36 -9
- package/esm/internal/components/BaseVisualization.d.ts +6 -33
- package/esm/internal/components/BaseVisualization.d.ts.map +1 -1
- package/esm/internal/components/BaseVisualization.js +255 -213
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts +3 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.js +20 -6
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.d.ts +11 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.js +25 -10
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.js +64 -5
- package/esm/internal/components/configurationControls/conditionalFormatting/ReorderList.d.ts +3 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ReorderList.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ReorderList.js +2 -2
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts +46 -8
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.js +94 -16
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.d.ts +2 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.js +2 -0
- package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.d.ts.map +1 -1
- package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.js +8 -2
- package/esm/internal/index.d.ts +1 -1
- package/esm/internal/index.d.ts.map +1 -1
- package/esm/internal/index.js +1 -1
- package/esm/internal/translations/en-US.localization-bundle.d.ts +12 -0
- package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
- package/esm/internal/translations/en-US.localization-bundle.js +12 -0
- package/esm/locales.d.ts +9 -0
- package/esm/locales.d.ts.map +1 -1
- package/esm/locales.js +3 -0
- package/esm/sdk-ui-ext.d.ts +86 -0
- package/package.json +21 -21
- package/styles/css/main.css +59 -0
- package/styles/css/main.css.map +1 -1
- package/styles/internal/css/conditional_formatting.css +59 -0
- package/styles/internal/css/conditional_formatting.css.map +1 -1
- package/styles/internal/scss/conditional_formatting.scss +78 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2019-2026 GoodData Corporation
|
|
3
|
-
import {
|
|
3
|
+
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { isEmpty, isEqual, omit } from "lodash-es";
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
6
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -8,268 +8,310 @@ import { insightProperties, isInsight, visClassUrl, } from "@gooddata/sdk-model"
|
|
|
8
8
|
import { ConfigPanelClassName, } from "../interfaces/Visualization.js";
|
|
9
9
|
import { cleanupKeyDriverAnalysisOnMetrics, hideKeyDriverOnMetrics } from "../utils/keyDriverAnalysis.js";
|
|
10
10
|
import { FullVisualizationCatalog } from "./VisualizationCatalog.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const noop = () => { };
|
|
12
|
+
function getVisualizationId(insight) {
|
|
13
|
+
return isInsight(insight) ? insight.insight.identifier : "__new_visualization__";
|
|
14
|
+
}
|
|
15
|
+
function bucketReferencePointHasChanged(currentReferencePoint, nextReferencePoint) {
|
|
16
|
+
return !isEqual(omit(currentReferencePoint, ["properties", "availableSorts"]), omit(nextReferencePoint, ["properties", "availableSorts"]));
|
|
17
|
+
}
|
|
18
|
+
function propertiesControlsHasChanged(currentReferencePoint, nextReferencePoint) {
|
|
19
|
+
return !isEqual(currentReferencePoint?.properties?.controls, nextReferencePoint?.properties?.controls);
|
|
20
|
+
}
|
|
21
|
+
export const BaseVisualization = forwardRef(function BaseVisualization(props, ref) {
|
|
22
|
+
const { visualizationCatalog = FullVisualizationCatalog, newDerivedBucketItems = [], referencePoint = undefined, onExtendedReferencePointChanged = noop, onNewDerivedBucketItemsPlaced = noop, isMdObjectValid = true, configPanelClassName = ConfigPanelClassName, featureFlags = {}, } = props;
|
|
23
|
+
/**
|
|
24
|
+
* Props with the defaults applied; the lifecycle handling below compares and passes them around
|
|
25
|
+
* the very same way the class component did with props defaulted by React.
|
|
26
|
+
*/
|
|
27
|
+
const currentProps = {
|
|
28
|
+
...props,
|
|
29
|
+
visualizationCatalog,
|
|
30
|
+
newDerivedBucketItems,
|
|
31
|
+
referencePoint,
|
|
32
|
+
onExtendedReferencePointChanged,
|
|
33
|
+
onNewDerivedBucketItemsPlaced,
|
|
34
|
+
isMdObjectValid,
|
|
35
|
+
configPanelClassName,
|
|
36
|
+
featureFlags,
|
|
21
37
|
};
|
|
22
|
-
visElementId;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
const [visElementId, setVisElementId] = useState(() => uuidv4());
|
|
39
|
+
/**
|
|
40
|
+
* Mirror of the visElementId state. The element lookups handed over to the pluggable visualization
|
|
41
|
+
* are lazy, so they must see the new id as soon as it is generated - that is, before the state
|
|
42
|
+
* update is processed and the container div is remounted under the new key.
|
|
43
|
+
*/
|
|
44
|
+
const visElementIdRef = useRef(visElementId);
|
|
45
|
+
const visualization = useRef(undefined);
|
|
46
|
+
const containerRef = useRef(null);
|
|
26
47
|
/**
|
|
27
48
|
* The component may render both visualization and config panel. In React18 we therefore need two
|
|
28
49
|
* roots with their respective render methods. This Map holds the roots for both and provides
|
|
29
50
|
* render and unmount methods whenever needed.
|
|
30
51
|
*/
|
|
31
|
-
reactRootsMap = new Map();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (isInsight(insight)) {
|
|
37
|
-
visualizationId = insight.insight.identifier;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
visualizationId = "__new_visualization__";
|
|
41
|
-
}
|
|
42
|
-
this.visElementId = uuidv4();
|
|
43
|
-
this.executionFactory = props.backend
|
|
44
|
-
.withCorrelation({ visualizationId })
|
|
52
|
+
const reactRootsMap = useRef(new Map());
|
|
53
|
+
const executionFactory = useRef(undefined);
|
|
54
|
+
if (!executionFactory.current) {
|
|
55
|
+
executionFactory.current = props.backend
|
|
56
|
+
.withCorrelation({ visualizationId: getVisualizationId(props.insight) })
|
|
45
57
|
.workspace(props.projectId)
|
|
46
58
|
.execution();
|
|
47
|
-
this.containerRef = createRef();
|
|
48
|
-
}
|
|
49
|
-
componentWillUnmount() {
|
|
50
|
-
if (this.visualization) {
|
|
51
|
-
this.visualization.unmount();
|
|
52
|
-
}
|
|
53
|
-
// In order to avoid race conditions when mounting and unmounting synchronously,
|
|
54
|
-
// we use timeout for React18.
|
|
55
|
-
// https://github.com/facebook/react/issues/25675
|
|
56
|
-
this.reactRootsMap.forEach((root) => setTimeout(() => root.unmount(), 0));
|
|
57
|
-
}
|
|
58
|
-
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
59
|
-
const newDerivedBucketItemsChanged = !isEmpty(nextProps.newDerivedBucketItems) &&
|
|
60
|
-
!isEqual(nextProps.newDerivedBucketItems, this.props.newDerivedBucketItems);
|
|
61
|
-
if (newDerivedBucketItemsChanged) {
|
|
62
|
-
this.triggerPlaceNewDerivedBucketItems(nextProps);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
// buckets changed from within inner visualization logic
|
|
66
|
-
const bucketsToUpdate = this.visualization?.getBucketsToUpdate(this.props.referencePoint, nextProps.referencePoint);
|
|
67
|
-
if (bucketsToUpdate) {
|
|
68
|
-
this.triggerPlaceNewDerivedBucketItems(nextProps, bucketsToUpdate);
|
|
69
|
-
this.triggerExtendedReferencePointChanged(nextProps, this.props);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const visualizationClassChanged = !isEqual(nextProps.visualizationClass, this.props.visualizationClass);
|
|
73
|
-
const referencePointChanged = BaseVisualization.bucketReferencePointHasChanged(this.props.referencePoint, nextProps.referencePoint);
|
|
74
|
-
const relevantPropertiesChanged = this.somePropertiesRelevantForReferencePointChanged(this.props.referencePoint, nextProps.referencePoint);
|
|
75
|
-
const labelsChanged = !isEqual(this.props.insight?.insight.attributeFilterConfigs, nextProps.insight?.insight.attributeFilterConfigs);
|
|
76
|
-
const propertiesControlsChanged = BaseVisualization.propertiesControlsHasChanged(this.props.referencePoint, nextProps.referencePoint);
|
|
77
|
-
if (visualizationClassChanged) {
|
|
78
|
-
this.visElementId = uuidv4();
|
|
79
|
-
this.setupVisualization(nextProps);
|
|
80
|
-
}
|
|
81
|
-
if (referencePointChanged ||
|
|
82
|
-
relevantPropertiesChanged ||
|
|
83
|
-
visualizationClassChanged ||
|
|
84
|
-
labelsChanged) {
|
|
85
|
-
this.triggerExtendedReferencePointChanged(nextProps,
|
|
86
|
-
// only pass current props if the visualization class is the same (see getExtendedReferencePoint JSDoc)
|
|
87
|
-
visualizationClassChanged ? undefined : this.props);
|
|
88
|
-
// Some of the properties eg. stacking of measures, dual axes influence sorting
|
|
89
|
-
}
|
|
90
|
-
else if (propertiesControlsChanged) {
|
|
91
|
-
this.triggerPropertiesChanged(nextProps, this.props);
|
|
92
|
-
}
|
|
93
59
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
60
|
+
const getVisElementClassName = () => `gd-vis-${visElementIdRef.current}`;
|
|
61
|
+
const getClassName = () => `gd-base-visualization ${getVisElementClassName()}`;
|
|
62
|
+
const getReactRenderFunction = () => {
|
|
63
|
+
return (children, element) => {
|
|
64
|
+
if (!element) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!reactRootsMap.current.get(element)) {
|
|
68
|
+
reactRootsMap.current.set(element, createRoot(element));
|
|
69
|
+
}
|
|
70
|
+
reactRootsMap.current.get(element).render(children);
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const getReactUnmountFunction = () => {
|
|
74
|
+
return () => reactRootsMap.current.forEach((root) => root.render(null));
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The `hostProps` are the equivalent of `this.props` of the original class component: the class
|
|
78
|
+
* picked the catalog and the messages from the props of the last completed render, which - when
|
|
79
|
+
* setting up on a visualization class change - were still the previous ones. Everything else is
|
|
80
|
+
* taken from the props the visualization is being set up for.
|
|
81
|
+
*/
|
|
82
|
+
const setupVisualization = (setupProps, hostProps = setupProps) => {
|
|
83
|
+
const { insight, visualizationClass, environment, locale, featureFlags: setupFeatureFlags, permissions, projectId, configPanelClassName: setupConfigPanelClassName, renderer, unmount, } = setupProps;
|
|
84
|
+
if (visualization.current) {
|
|
85
|
+
visualization.current.unmount();
|
|
117
86
|
}
|
|
118
87
|
const visUri = visClassUrl(visualizationClass);
|
|
119
88
|
let visFactory;
|
|
120
89
|
try {
|
|
121
|
-
visFactory =
|
|
122
|
-
.visualizationCatalog.forUri(visUri,
|
|
90
|
+
visFactory = hostProps
|
|
91
|
+
.visualizationCatalog.forUri(visUri, setupFeatureFlags?.enableNewPivotTable ?? true, setupFeatureFlags?.enableNewGeoPushpin ?? false)
|
|
123
92
|
.getFactory();
|
|
124
93
|
}
|
|
125
94
|
catch {
|
|
126
95
|
console.error(`Error: unsupported visualization type - ${visUri}`);
|
|
127
96
|
}
|
|
128
|
-
let visualizationId;
|
|
129
|
-
if (isInsight(insight)) {
|
|
130
|
-
visualizationId = insight.insight.identifier;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
visualizationId = "__new_visualization__";
|
|
134
|
-
}
|
|
135
97
|
if (visFactory) {
|
|
136
98
|
const constructorParams = {
|
|
137
99
|
projectId,
|
|
138
100
|
locale,
|
|
139
|
-
messages:
|
|
101
|
+
messages: hostProps.messages,
|
|
140
102
|
environment,
|
|
141
|
-
backend:
|
|
103
|
+
backend: setupProps.backend.withCorrelation({
|
|
104
|
+
visualizationId: getVisualizationId(insight),
|
|
105
|
+
}),
|
|
142
106
|
element: () => {
|
|
143
|
-
const rootNode =
|
|
144
|
-
return rootNode.querySelector(`.${
|
|
107
|
+
const rootNode = containerRef.current?.getRootNode() ?? document;
|
|
108
|
+
return rootNode.querySelector(`.${getVisElementClassName()}`);
|
|
145
109
|
},
|
|
146
110
|
configPanelElement: () => {
|
|
147
|
-
const rootNode =
|
|
148
|
-
return rootNode.querySelector(`.${
|
|
111
|
+
const rootNode = containerRef.current?.getRootNode() ?? document;
|
|
112
|
+
return rootNode.querySelector(`.${setupConfigPanelClassName}`);
|
|
149
113
|
},
|
|
150
114
|
callbacks: {
|
|
151
|
-
afterRender:
|
|
152
|
-
onLoadingChanged:
|
|
153
|
-
onError:
|
|
154
|
-
onExportReady:
|
|
155
|
-
pushData:
|
|
156
|
-
onDrill:
|
|
157
|
-
onDataView:
|
|
115
|
+
afterRender: setupProps.afterRender,
|
|
116
|
+
onLoadingChanged: setupProps.onLoadingChanged,
|
|
117
|
+
onError: setupProps.onError,
|
|
118
|
+
onExportReady: setupProps.onExportReady,
|
|
119
|
+
pushData: setupProps.pushData,
|
|
120
|
+
onDrill: setupProps.onDrill,
|
|
121
|
+
onDataView: setupProps.onDataView,
|
|
158
122
|
},
|
|
159
|
-
featureFlags,
|
|
123
|
+
featureFlags: setupFeatureFlags,
|
|
160
124
|
permissions,
|
|
161
|
-
visualizationProperties: insightProperties(
|
|
162
|
-
renderFun: (renderer ??
|
|
163
|
-
unmountFun: unmount ??
|
|
125
|
+
visualizationProperties: insightProperties(setupProps.insight),
|
|
126
|
+
renderFun: (renderer ?? getReactRenderFunction()),
|
|
127
|
+
unmountFun: unmount ?? getReactUnmountFunction(),
|
|
164
128
|
};
|
|
165
|
-
|
|
129
|
+
visualization.current = visFactory(constructorParams);
|
|
166
130
|
}
|
|
167
|
-
}
|
|
168
|
-
getReactRenderFunction = () => {
|
|
169
|
-
return (children, element) => {
|
|
170
|
-
if (!element) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (!this.reactRootsMap.get(element)) {
|
|
174
|
-
this.reactRootsMap.set(element, createRoot(element));
|
|
175
|
-
}
|
|
176
|
-
this.reactRootsMap.get(element).render(children);
|
|
177
|
-
};
|
|
178
131
|
};
|
|
179
|
-
|
|
180
|
-
return
|
|
132
|
+
const getVisualizationProps = () => {
|
|
133
|
+
return {
|
|
134
|
+
locale: props.locale,
|
|
135
|
+
dateFormat: props.dateFormat,
|
|
136
|
+
dimensions: {
|
|
137
|
+
width: props.width,
|
|
138
|
+
height: props.height,
|
|
139
|
+
},
|
|
140
|
+
custom: {
|
|
141
|
+
drillableItems: props.drillableItems,
|
|
142
|
+
totalsEditAllowed: props.totalsEditAllowed,
|
|
143
|
+
lastSavedVisClassUrl: props.lastSavedVisClassUrl,
|
|
144
|
+
sourceInsightId: props.sourceInsightId,
|
|
145
|
+
configurationPanelRenderers: props.configurationPanelRenderers,
|
|
146
|
+
},
|
|
147
|
+
messages: props.messages,
|
|
148
|
+
config: props.config,
|
|
149
|
+
theme: props.theme,
|
|
150
|
+
referenceTheme: props.referenceTheme,
|
|
151
|
+
executionConfig: props.executionConfig,
|
|
152
|
+
supportsChartFill: props.supportsChartFill,
|
|
153
|
+
};
|
|
181
154
|
};
|
|
182
|
-
updateVisualization() {
|
|
183
|
-
if (!
|
|
155
|
+
const updateVisualization = () => {
|
|
156
|
+
if (!visualization.current) {
|
|
184
157
|
return;
|
|
185
158
|
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
triggerPlaceNewDerivedBucketItems(
|
|
189
|
-
const { newDerivedBucketItems, referencePoint, onNewDerivedBucketItemsPlaced } =
|
|
190
|
-
const newDerivedBucketItemsToPlace = newBucketItemsFromVisualization ??
|
|
191
|
-
if (
|
|
192
|
-
|
|
159
|
+
visualization.current.update(getVisualizationProps(), props.insight, props.insightPropertiesMeta, executionFactory.current);
|
|
160
|
+
};
|
|
161
|
+
const triggerPlaceNewDerivedBucketItems = (triggerProps, newBucketItemsFromVisualization) => {
|
|
162
|
+
const { newDerivedBucketItems: triggerNewDerivedBucketItems, referencePoint: triggerReferencePoint, onNewDerivedBucketItemsPlaced: triggerOnNewDerivedBucketItemsPlaced, } = triggerProps;
|
|
163
|
+
const newDerivedBucketItemsToPlace = newBucketItemsFromVisualization ?? triggerNewDerivedBucketItems;
|
|
164
|
+
if (visualization.current &&
|
|
165
|
+
triggerReferencePoint &&
|
|
193
166
|
newDerivedBucketItemsToPlace &&
|
|
194
|
-
|
|
195
|
-
void
|
|
196
|
-
.addNewDerivedBucketItems(
|
|
197
|
-
.then(
|
|
167
|
+
triggerOnNewDerivedBucketItemsPlaced) {
|
|
168
|
+
void visualization.current
|
|
169
|
+
.addNewDerivedBucketItems(triggerReferencePoint, newDerivedBucketItemsToPlace)
|
|
170
|
+
.then(triggerOnNewDerivedBucketItemsPlaced);
|
|
198
171
|
}
|
|
199
|
-
}
|
|
200
|
-
triggerExtendedReferencePointChanged(newProps,
|
|
201
|
-
const { referencePoint: newReferencePoint, onExtendedReferencePointChanged } = newProps;
|
|
202
|
-
if (
|
|
203
|
-
void
|
|
204
|
-
.getExtendedReferencePoint(newReferencePoint,
|
|
172
|
+
};
|
|
173
|
+
const triggerExtendedReferencePointChanged = (newProps, previousProps) => {
|
|
174
|
+
const { referencePoint: newReferencePoint, onExtendedReferencePointChanged: newOnExtendedReferencePointChanged, } = newProps;
|
|
175
|
+
if (visualization.current && newReferencePoint && newOnExtendedReferencePointChanged) {
|
|
176
|
+
void visualization.current
|
|
177
|
+
.getExtendedReferencePoint(newReferencePoint, previousProps?.referencePoint)
|
|
205
178
|
.then(async (extendedReferencePoint) => {
|
|
206
|
-
const sortConfig = await
|
|
179
|
+
const sortConfig = await visualization.current.getSortConfig(extendedReferencePoint);
|
|
207
180
|
let updatedExtendedReferencePoint = cleanupKeyDriverAnalysisOnMetrics(extendedReferencePoint);
|
|
208
181
|
updatedExtendedReferencePoint = hideKeyDriverOnMetrics(updatedExtendedReferencePoint);
|
|
209
182
|
// new sort config needs to be sent together with new reference point to avoid double executions with old invalid sort until new one arrives by its own handler
|
|
210
|
-
|
|
183
|
+
newOnExtendedReferencePointChanged(updatedExtendedReferencePoint, sortConfig);
|
|
211
184
|
});
|
|
212
185
|
}
|
|
213
|
-
}
|
|
214
|
-
triggerPropertiesChanged(newProps,
|
|
186
|
+
};
|
|
187
|
+
const triggerPropertiesChanged = (newProps, previousProps) => {
|
|
215
188
|
const { referencePoint: newReferencePoint, onSortingChanged } = newProps;
|
|
216
189
|
// Some of the properties eg. stacking of measures, dual axes influence sorting
|
|
217
|
-
if (
|
|
218
|
-
void
|
|
219
|
-
.getExtendedReferencePoint(newReferencePoint,
|
|
190
|
+
if (visualization.current && newReferencePoint && onSortingChanged) {
|
|
191
|
+
void visualization.current
|
|
192
|
+
.getExtendedReferencePoint(newReferencePoint, previousProps?.referencePoint)
|
|
220
193
|
.then((extendedRefPoint) => {
|
|
221
|
-
void
|
|
194
|
+
void visualization.current.getSortConfig(extendedRefPoint).then(onSortingChanged);
|
|
222
195
|
});
|
|
223
196
|
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
static propertiesControlsHasChanged(currentReferencePoint, nextReferencePoint) {
|
|
229
|
-
return !isEqual(currentReferencePoint?.properties?.controls, nextReferencePoint?.properties?.controls);
|
|
230
|
-
}
|
|
231
|
-
somePropertiesRelevantForReferencePointChanged(currentReferencePoint, nextReferencePoint) {
|
|
232
|
-
if (this.visualization) {
|
|
233
|
-
return this.visualization.haveSomePropertiesRelevantForReferencePointChanged(currentReferencePoint, nextReferencePoint);
|
|
197
|
+
};
|
|
198
|
+
const somePropertiesRelevantForReferencePointChanged = (currentReferencePoint, nextReferencePoint) => {
|
|
199
|
+
if (visualization.current) {
|
|
200
|
+
return visualization.current.haveSomePropertiesRelevantForReferencePointChanged(currentReferencePoint, nextReferencePoint);
|
|
234
201
|
}
|
|
235
202
|
return false;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
height: this.props.height,
|
|
244
|
-
},
|
|
245
|
-
custom: {
|
|
246
|
-
drillableItems: this.props.drillableItems,
|
|
247
|
-
totalsEditAllowed: this.props.totalsEditAllowed,
|
|
248
|
-
lastSavedVisClassUrl: this.props.lastSavedVisClassUrl,
|
|
249
|
-
sourceInsightId: this.props.sourceInsightId,
|
|
250
|
-
configurationPanelRenderers: this.props.configurationPanelRenderers,
|
|
251
|
-
},
|
|
252
|
-
messages: this.props.messages,
|
|
253
|
-
config: this.props.config,
|
|
254
|
-
theme: this.props.theme,
|
|
255
|
-
referenceTheme: this.props.referenceTheme,
|
|
256
|
-
executionConfig: this.props.executionConfig,
|
|
257
|
-
supportsChartFill: this.props.supportsChartFill,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
getInsightWithDrillDownApplied(sourceVisualization, drillDownContext) {
|
|
261
|
-
return this.visualization.getInsightWithDrillDownApplied(sourceVisualization, drillDownContext, this.props.backend.capabilities.supportsElementUris ?? true);
|
|
262
|
-
}
|
|
263
|
-
getExecution() {
|
|
264
|
-
if (!this.visualization) {
|
|
265
|
-
this.setupVisualization(this.props);
|
|
203
|
+
};
|
|
204
|
+
const getInsightWithDrillDownApplied = (sourceVisualization, drillDownContext) => {
|
|
205
|
+
return visualization.current.getInsightWithDrillDownApplied(sourceVisualization, drillDownContext, props.backend.capabilities.supportsElementUris ?? true);
|
|
206
|
+
};
|
|
207
|
+
const getExecution = () => {
|
|
208
|
+
if (!visualization.current) {
|
|
209
|
+
setupVisualization(currentProps);
|
|
266
210
|
}
|
|
267
|
-
return
|
|
268
|
-
}
|
|
269
|
-
getExecutions() {
|
|
270
|
-
if (!
|
|
271
|
-
|
|
211
|
+
return visualization.current.getExecution(getVisualizationProps(), props.insight, executionFactory.current);
|
|
212
|
+
};
|
|
213
|
+
const getExecutions = () => {
|
|
214
|
+
if (!visualization.current) {
|
|
215
|
+
setupVisualization(currentProps);
|
|
272
216
|
}
|
|
273
|
-
return
|
|
274
|
-
}
|
|
275
|
-
|
|
217
|
+
return visualization.current?.getExecutions?.(getVisualizationProps(), props.insight, executionFactory.current);
|
|
218
|
+
};
|
|
219
|
+
useImperativeHandle(ref, () => ({
|
|
220
|
+
getExecution,
|
|
221
|
+
getExecutions,
|
|
222
|
+
getInsightWithDrillDownApplied,
|
|
223
|
+
}));
|
|
224
|
+
const prevPropsRef = useRef(currentProps);
|
|
225
|
+
const mountedRef = useRef(false);
|
|
226
|
+
/**
|
|
227
|
+
* Replacement of UNSAFE_componentWillReceiveProps. Runs before the mount & update effect below so
|
|
228
|
+
* that the derived bucket items and the reference point are handled before the visualization is
|
|
229
|
+
* updated, the same way the class did it in the pre-render phase.
|
|
230
|
+
*
|
|
231
|
+
* The effect intentionally has no dependency list - it has to see every single commit, exactly like
|
|
232
|
+
* the class lifecycle did; the props diff below decides what actually happens. The visElementId is
|
|
233
|
+
* regenerated only when the visualization class really changes, so there is no update loop.
|
|
234
|
+
*/
|
|
235
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
236
|
+
useLayoutEffect(() => {
|
|
237
|
+
const previousProps = prevPropsRef.current;
|
|
238
|
+
prevPropsRef.current = currentProps;
|
|
239
|
+
if (previousProps === currentProps) {
|
|
240
|
+
// the very first run, there is nothing to diff against yet
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const newDerivedBucketItemsChanged = !isEmpty(currentProps.newDerivedBucketItems) &&
|
|
244
|
+
!isEqual(currentProps.newDerivedBucketItems, previousProps.newDerivedBucketItems);
|
|
245
|
+
if (newDerivedBucketItemsChanged) {
|
|
246
|
+
triggerPlaceNewDerivedBucketItems(currentProps);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// buckets changed from within inner visualization logic
|
|
250
|
+
const bucketsToUpdate = visualization.current?.getBucketsToUpdate(previousProps.referencePoint, currentProps.referencePoint);
|
|
251
|
+
if (bucketsToUpdate) {
|
|
252
|
+
triggerPlaceNewDerivedBucketItems(currentProps, bucketsToUpdate);
|
|
253
|
+
triggerExtendedReferencePointChanged(currentProps, previousProps);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const visualizationClassChanged = !isEqual(currentProps.visualizationClass, previousProps.visualizationClass);
|
|
257
|
+
const referencePointChanged = bucketReferencePointHasChanged(previousProps.referencePoint, currentProps.referencePoint);
|
|
258
|
+
const relevantPropertiesChanged = somePropertiesRelevantForReferencePointChanged(previousProps.referencePoint, currentProps.referencePoint);
|
|
259
|
+
const labelsChanged = !isEqual(previousProps.insight?.insight.attributeFilterConfigs, currentProps.insight?.insight.attributeFilterConfigs);
|
|
260
|
+
const propertiesControlsChanged = propertiesControlsHasChanged(previousProps.referencePoint, currentProps.referencePoint);
|
|
261
|
+
if (visualizationClassChanged) {
|
|
262
|
+
const newVisElementId = uuidv4();
|
|
263
|
+
visElementIdRef.current = newVisElementId;
|
|
264
|
+
setVisElementId(newVisElementId);
|
|
265
|
+
setupVisualization(currentProps, previousProps);
|
|
266
|
+
}
|
|
267
|
+
if (referencePointChanged ||
|
|
268
|
+
relevantPropertiesChanged ||
|
|
269
|
+
visualizationClassChanged ||
|
|
270
|
+
labelsChanged) {
|
|
271
|
+
triggerExtendedReferencePointChanged(currentProps,
|
|
272
|
+
// only pass current props if the visualization class is the same (see getExtendedReferencePoint JSDoc)
|
|
273
|
+
visualizationClassChanged ? undefined : previousProps);
|
|
274
|
+
// Some of the properties eg. stacking of measures, dual axes influence sorting
|
|
275
|
+
}
|
|
276
|
+
else if (propertiesControlsChanged) {
|
|
277
|
+
triggerPropertiesChanged(currentProps, previousProps);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
/**
|
|
281
|
+
* Replacement of componentDidMount and componentDidUpdate.
|
|
282
|
+
*/
|
|
283
|
+
useLayoutEffect(() => {
|
|
284
|
+
if (!mountedRef.current) {
|
|
285
|
+
mountedRef.current = true;
|
|
286
|
+
setupVisualization(currentProps);
|
|
287
|
+
updateVisualization();
|
|
288
|
+
triggerExtendedReferencePointChanged(currentProps);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (visElementId !== visElementIdRef.current) {
|
|
292
|
+
// the visualization class has just changed; the container div is about to be remounted
|
|
293
|
+
// under the new key, so postpone the update until it is in the DOM
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (isMdObjectValid) {
|
|
297
|
+
updateVisualization();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
/**
|
|
301
|
+
* Replacement of componentWillUnmount.
|
|
302
|
+
*/
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
const reactRoots = reactRootsMap.current;
|
|
305
|
+
return () => {
|
|
306
|
+
// a subsequent mount (React StrictMode remounts the tree in development) has to set the
|
|
307
|
+
// visualization up again, exactly like componentDidMount did
|
|
308
|
+
mountedRef.current = false;
|
|
309
|
+
visualization.current?.unmount();
|
|
310
|
+
// In order to avoid race conditions when mounting and unmounting synchronously,
|
|
311
|
+
// we use timeout for React18.
|
|
312
|
+
// https://github.com/facebook/react/issues/25675
|
|
313
|
+
reactRoots.forEach((root) => setTimeout(() => root.unmount(), 0));
|
|
314
|
+
};
|
|
315
|
+
}, []);
|
|
316
|
+
return (_jsx("div", { "aria-label": "base-visualization", style: { height: "100%" }, className: getClassName(), ref: containerRef }, visElementId));
|
|
317
|
+
});
|
package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export interface IConditionEditorProps {
|
|
|
14
14
|
dateSettings: ICfDateSettings | undefined;
|
|
15
15
|
removable: boolean;
|
|
16
16
|
slot: IReorderSlot;
|
|
17
|
+
/** A read-only rule may carry an operator/value the editor doesn't curate for this target. */
|
|
18
|
+
readOnly?: boolean;
|
|
17
19
|
onChange: (condition: IConditionalFormattingCondition) => void;
|
|
18
20
|
onRemove: () => void;
|
|
19
21
|
}
|
|
20
|
-
export declare function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, onChange, onRemove }: IConditionEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, readOnly, onChange, onRemove }: IConditionEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
21
23
|
//# sourceMappingURL=ConditionEditor.d.ts.map
|
package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts.map
CHANGED
|
@@ -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;AAarC,OAAO,EAGH,KAAK,WAAW,EAChB,KAAK,eAAe,
|
|
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,EAavB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkYrD,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,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,OAAO,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,QAAgB,EAChB,QAAQ,EACR,QAAQ,EACX,EAAE,qBAAqB,2CA8FvB"}
|
package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.js
CHANGED
|
@@ -9,7 +9,7 @@ import { ColorDropdown } from "../colors/colorDropdown/ColorDropdown.js";
|
|
|
9
9
|
import { CfDateValueDropdown } from "./CfDateValueDropdown.js";
|
|
10
10
|
import { CfSelect } from "./CfSelect.js";
|
|
11
11
|
import { CF_COLOR_PALETTE, colorToHex, hexToColor } from "./conditionalFormattingColors.js";
|
|
12
|
-
import { displayToRawNumber, literalRaw, literalText, operatorIcon, operatorsForTarget, rangeRaw, rawToDisplayNumber, validateCondition, valueEditorKind, valueForOperator, } from "./conditionalFormattingModel.js";
|
|
12
|
+
import { displayToRawNumber, literalRaw, literalText, operatorIcon, operatorsForTarget, rangeRaw, rawToDisplayNumber, rawValueText, validateCondition, valueEditorKind, valueForOperator, valueMatchesEditorKind, } from "./conditionalFormattingModel.js";
|
|
13
13
|
// InputPure's own contract for the same error state.
|
|
14
14
|
const inputErrorProps = ({ hasError, errorId, }) => hasError ? { hasError, accessibilityConfig: { ariaDescribedBy: errorId, ariaInvalid: true } } : {};
|
|
15
15
|
function CfMeasureValueInput({ raw, isPercent, separators, placeholder, onChangeRaw, ...field }) {
|
|
@@ -58,7 +58,7 @@ function CfFieldError({ id, error }) {
|
|
|
58
58
|
message: intl.formatMessage(FIELD_ERROR_MESSAGE[error]),
|
|
59
59
|
}) }));
|
|
60
60
|
}
|
|
61
|
-
function ConditionValueInput({ condition, editorKind, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, onChange, }) {
|
|
61
|
+
function ConditionValueInput({ condition, editorKind, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, readOnly, onChange, }) {
|
|
62
62
|
const intl = useIntl();
|
|
63
63
|
const errorId = useId();
|
|
64
64
|
// Empty is only an error once the field has been visited, so authoring a rule top-down never reddens
|
|
@@ -77,6 +77,12 @@ function ConditionValueInput({ condition, editorKind, kind, isPercent, separator
|
|
|
77
77
|
return { hasError, errorId: hasError ? errorId : undefined, onVisit: markVisited(field) };
|
|
78
78
|
};
|
|
79
79
|
const error = invalid ?? emptyIn("value") ?? emptyIn("from") ?? emptyIn("to");
|
|
80
|
+
// A read-only rule may pair a value shape its editor can't represent (e.g. a semantic-layer
|
|
81
|
+
// condition the pivot editor never authors) — fall back to raw text instead of a dead-ended
|
|
82
|
+
// control. Placed after every hook call above so it never changes the hooks called per render.
|
|
83
|
+
if (readOnly && !valueMatchesEditorKind(editorKind, condition.value)) {
|
|
84
|
+
return _jsx("span", { className: "gd-cf-condition__raw-value", children: rawValueText(condition.value) });
|
|
85
|
+
}
|
|
80
86
|
const renderControl = () => {
|
|
81
87
|
switch (editorKind) {
|
|
82
88
|
case "none":
|
|
@@ -126,11 +132,19 @@ function FormatEditor({ format, onChange }) {
|
|
|
126
132
|
] })
|
|
127
133
|
] })) }));
|
|
128
134
|
}
|
|
129
|
-
export function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, onChange, onRemove, }) {
|
|
135
|
+
export function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, readOnly = false, onChange, onRemove, }) {
|
|
130
136
|
const intl = useIntl();
|
|
131
137
|
const isDate = date !== undefined;
|
|
138
|
+
const curatedOperators = operatorsForTarget(kind, isDate);
|
|
139
|
+
// A read-only rule may carry an operator the editor doesn't curate for this target (e.g. a
|
|
140
|
+
// semantic-layer rule the pivot editor can't author); keep it selectable so its real label shows
|
|
141
|
+
// instead of leaving the picker blank. Gated on readOnly: sanitizeRuleForEditing normalizes the
|
|
142
|
+
// operator on the editable path, so this must never offer an uncurated choice a user could pick.
|
|
143
|
+
const operators = readOnly && !curatedOperators.includes(condition.operator)
|
|
144
|
+
? [...curatedOperators, condition.operator]
|
|
145
|
+
: curatedOperators;
|
|
132
146
|
// Date operators render label-only (per design), so no icon lookup on that path.
|
|
133
|
-
const operatorItems =
|
|
147
|
+
const operatorItems = operators.map((operator) => ({
|
|
134
148
|
value: operator,
|
|
135
149
|
title: intl.formatMessage((isDate ? conditionalFormattingDateOperatorMessages[operator] : undefined) ??
|
|
136
150
|
conditionalFormattingOperatorMessages[operator]),
|
|
@@ -143,13 +157,13 @@ export function ConditionEditor({ condition, kind, isPercent, separators, sugges
|
|
|
143
157
|
...(condition.format.backgroundColor ? { backgroundColor: condition.format.backgroundColor } : {}),
|
|
144
158
|
};
|
|
145
159
|
return (_jsxs("div", { className: cx("gd-cf-condition", slot.className), ...slot.rootProps, children: [slot.handle, _jsxs("div", { className: "gd-cf-condition__header", children: [
|
|
146
|
-
_jsx("span", { className: "gd-cf-condition__title", children: intl.formatMessage(conditionalFormattingMessages.dialogCondition) }), removable ? (_jsx(Button, { className: "gd-button-link gd-button-icon-only gd-icon-cross", accessibilityConfig: {
|
|
160
|
+
_jsx("span", { className: "gd-cf-condition__title", children: intl.formatMessage(conditionalFormattingMessages.dialogCondition) }), removable ? (_jsx(Button, { className: "gd-button-link gd-button-icon-only gd-icon-cross gd-cf-condition__delete", accessibilityConfig: {
|
|
147
161
|
ariaLabel: intl.formatMessage(conditionalFormattingMessages.dialogRemoveCondition),
|
|
148
162
|
}, onClick: onRemove })) : null] }), _jsx(CfSelect, { value: condition.operator, items: operatorItems, onSelect: (operator) => onChange({
|
|
149
163
|
...condition,
|
|
150
164
|
operator,
|
|
151
165
|
value: valueForOperator(operator, condition.value, isDate),
|
|
152
|
-
}) }), _jsx(ConditionValueInput, { condition: condition, editorKind: editorKind, kind: kind, isPercent: isPercent, separators: separators, suggestions: suggestions, date: date, dateFilterOptions: dateFilterOptions, dateSettings: dateSettings, onChange: onChange }, editorKind), _jsxs("div", { className: "gd-cf-condition__format-row", children: [
|
|
166
|
+
}) }), _jsx(ConditionValueInput, { condition: condition, editorKind: editorKind, kind: kind, isPercent: isPercent, separators: separators, suggestions: suggestions, date: date, dateFilterOptions: dateFilterOptions, dateSettings: dateSettings, readOnly: readOnly, onChange: onChange }, editorKind), _jsxs("div", { className: "gd-cf-condition__format-row", children: [
|
|
153
167
|
_jsx("span", { className: "gd-cf-dialog__label", children: intl.formatMessage(conditionalFormattingMessages.dialogFormat) }), _jsx("div", { className: "gd-cf-preview", children: condition.format.scope === "row" ? (ROW_PREVIEW_SAMPLE.map((sample, index) => (_jsx("span", { className: "gd-cf-preview__cell", style: previewStyle, children: sample }, index)))) : (_jsx("span", { className: "gd-cf-preview__cell", style: previewStyle, children: PREVIEW_SAMPLE[kind] })) }), _jsx(FormatEditor, { format: condition.format, onChange: setFormat })
|
|
154
168
|
] })
|
|
155
169
|
] }));
|