@gooddata/sdk-ui-ext 11.50.0-alpha.1 → 11.50.0-alpha.3
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 +216 -203
- package/esm/internal/components/configurationControls/DataLabelsControl.d.ts +2 -1
- package/esm/internal/components/configurationControls/DataLabelsControl.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/DataLabelsControl.js +6 -4
- package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.d.ts +25 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.d.ts.map +1 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.js +140 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/CfSelect.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/CfSelect.js +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts +6 -3
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.js +40 -36
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.d.ts +7 -2
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.js +17 -8
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.d.ts +7 -2
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.js +20 -8
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts +54 -19
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.d.ts.map +1 -1
- package/esm/internal/components/configurationControls/conditionalFormatting/conditionalFormattingModel.js +160 -30
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.d.ts +12 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.d.ts.map +1 -0
- package/esm/internal/components/configurationControls/conditionalFormatting/useCfDateFilterOptions.js +44 -0
- package/esm/internal/components/configurationPanels/PieChartConfigurationPanel.d.ts.map +1 -1
- package/esm/internal/components/configurationPanels/PieChartConfigurationPanel.js +3 -2
- package/esm/internal/components/configurationPanels/PivotTableConfigurationPanel.d.ts +6 -1
- package/esm/internal/components/configurationPanels/PivotTableConfigurationPanel.d.ts.map +1 -1
- package/esm/internal/components/configurationPanels/PivotTableConfigurationPanel.js +2 -2
- package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.d.ts +3 -0
- package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.d.ts.map +1 -1
- package/esm/internal/components/pluggableVisualizations/pivotTableNext/PluggablePivotTableNext.js +14 -2
- package/esm/internal/constants/dropdowns.d.ts +1 -0
- package/esm/internal/constants/dropdowns.d.ts.map +1 -1
- package/esm/internal/constants/dropdowns.js +20 -2
- package/esm/internal/constants/supportedProperties.d.ts.map +1 -1
- package/esm/internal/constants/supportedProperties.js +1 -0
- package/esm/internal/translations/en-US.localization-bundle.d.ts +73 -1
- package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
- package/esm/internal/translations/en-US.localization-bundle.js +76 -4
- package/esm/internal/utils/translations.d.ts.map +1 -1
- package/esm/internal/utils/translations.js +2 -1
- package/esm/locales.d.ts +36 -1
- package/esm/locales.d.ts.map +1 -1
- package/esm/locales.js +22 -1
- package/esm/tsdoc-metadata.json +1 -1
- package/package.json +21 -21
- package/styles/css/main.css +4 -0
- package/styles/css/main.css.map +1 -1
- package/styles/internal/css/conditional_formatting.css +4 -0
- package/styles/internal/css/conditional_formatting.css.map +1 -1
- package/styles/internal/scss/conditional_formatting.scss +6 -0
|
@@ -9,6 +9,7 @@ export interface IDataLabelsControlProps {
|
|
|
9
9
|
enableSeparateTotalLabels?: boolean;
|
|
10
10
|
enablePercentLabels?: boolean;
|
|
11
11
|
enableStyleSelector?: boolean;
|
|
12
|
+
enablePositionSelector?: boolean;
|
|
12
13
|
}
|
|
13
|
-
export declare function DataLabelsControl({ pushData, properties, isDisabled, showDisabledMessage, defaultValue, isTotalsDisabled, enableSeparateTotalLabels, enablePercentLabels, enableStyleSelector }: IDataLabelsControlProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function DataLabelsControl({ pushData, properties, isDisabled, showDisabledMessage, defaultValue, isTotalsDisabled, enableSeparateTotalLabels, enablePercentLabels, enableStyleSelector, enablePositionSelector }: IDataLabelsControlProps): import("react/jsx-runtime").JSX.Element;
|
|
14
15
|
//# sourceMappingURL=DataLabelsControl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataLabelsControl.d.ts","sourceRoot":"","sources":["../../../../src/internal/components/configurationControls/DataLabelsControl.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DataLabelsControl.d.ts","sourceRoot":"","sources":["../../../../src/internal/components/configurationControls/DataLabelsControl.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAOlF,MAAM,WAAW,uBAAuB;IACpC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IAC9B,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,UAAU,EACV,UAAU,EACV,mBAA2B,EAC3B,YAAqB,EACrB,gBAAuB,EACvB,yBAAiC,EACjC,mBAAmB,EACnB,mBAA0B,EAC1B,sBAA8B,EACjC,EAAE,uBAAuB,2CAmFzB"}
|
|
@@ -2,15 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
// (C) 2019-2026 GoodData Corporation
|
|
3
3
|
import { useIntl } from "react-intl";
|
|
4
4
|
import { messages } from "../../../locales.js";
|
|
5
|
-
import { dataLabelStyleDropdownItems, dataLabelsDropdownItems } from "../../constants/dropdowns.js";
|
|
5
|
+
import { dataLabelPositionDropdownItems, dataLabelStyleDropdownItems, dataLabelsDropdownItems, } from "../../constants/dropdowns.js";
|
|
6
6
|
import { getTranslatedDropdownItems } from "../../utils/translations.js";
|
|
7
7
|
import { CheckboxControl } from "../configurationControls/CheckboxControl.js";
|
|
8
|
+
import { ConfigSubsection } from "./ConfigSubsection.js";
|
|
8
9
|
import { DropdownControl } from "./DropdownControl.js";
|
|
9
|
-
export function DataLabelsControl({ pushData, properties, isDisabled, showDisabledMessage = false, defaultValue = "auto", isTotalsDisabled = true, enableSeparateTotalLabels = false, enablePercentLabels, enableStyleSelector = true, }) {
|
|
10
|
+
export function DataLabelsControl({ pushData, properties, isDisabled, showDisabledMessage = false, defaultValue = "auto", isTotalsDisabled = true, enableSeparateTotalLabels = false, enablePercentLabels, enableStyleSelector = true, enablePositionSelector = false, }) {
|
|
10
11
|
const intl = useIntl();
|
|
11
12
|
const dataLabels = properties?.controls?.["dataLabels"]?.visible ?? defaultValue;
|
|
12
13
|
const totalLabels = properties?.controls?.["dataLabels"]?.totalsVisible ?? defaultValue;
|
|
13
14
|
const dataLabelStyle = properties?.controls?.["dataLabels"]?.style ?? "auto";
|
|
15
|
+
const dataLabelPosition = properties?.controls?.["dataLabels"]?.position ?? "auto";
|
|
14
16
|
const percentLabels = properties?.controls?.["dataLabels"]?.percentsVisible ?? true;
|
|
15
17
|
const percentLabelsDisabled = isDisabled || !dataLabels;
|
|
16
18
|
// Decide about percents tooltip message: show info variant when not disabled,
|
|
@@ -23,6 +25,6 @@ export function DataLabelsControl({ pushData, properties, isDisabled, showDisabl
|
|
|
23
25
|
else if (!percentLabelsDisabled) {
|
|
24
26
|
percentLabelsMessageId = messages["canvasLabelsPercentagesInfo"].id;
|
|
25
27
|
}
|
|
26
|
-
return (
|
|
27
|
-
|
|
28
|
+
return (_jsx("div", { className: "s-data-labels-config", children: _jsxs(ConfigSubsection, { title: messages["labelsSubsectionTitle"].id, children: [
|
|
29
|
+
_jsx(DropdownControl, { value: dataLabels, valuePath: "dataLabels.visible", labelText: messages["dataLabelDisplay"].id, disabled: isDisabled, properties: properties, pushData: pushData, items: getTranslatedDropdownItems(dataLabelsDropdownItems, intl), showDisabledMessage: showDisabledMessage }), enableSeparateTotalLabels ? (_jsx(DropdownControl, { value: totalLabels, valuePath: "dataLabels.totalsVisible", labelText: messages["totalLabels"].id, disabled: isTotalsDisabled, properties: properties, pushData: pushData, items: getTranslatedDropdownItems(dataLabelsDropdownItems, intl), showDisabledMessage: isTotalsDisabled })) : null, enableStyleSelector ? (_jsx(DropdownControl, { value: dataLabelStyle, valuePath: "dataLabels.style", labelText: messages["dataLabelStyle"].id, disabled: isDisabled, properties: properties, pushData: pushData, items: getTranslatedDropdownItems(dataLabelStyleDropdownItems, intl), showDisabledMessage: showDisabledMessage })) : null, enablePositionSelector ? (_jsx(DropdownControl, { value: dataLabelPosition, valuePath: "dataLabels.position", labelText: messages["dataLabelPosition"].id, disabled: isDisabled, properties: properties, pushData: pushData, items: getTranslatedDropdownItems(dataLabelPositionDropdownItems, intl), showDisabledMessage: showDisabledMessage })) : null, enablePercentLabels ? (_jsx(CheckboxControl, { valuePath: "dataLabels.percentsVisible", labelText: messages["canvasLabelsPercentages"].id, properties: properties, checked: percentLabels, disabled: percentLabelsDisabled, disabledMessageId: percentLabelsMessageId, showDisabledMessage: !!percentLabelsMessageId, pushData: pushData })) : null] }) }));
|
|
28
30
|
}
|
package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type IDateFilterOptionsByType } from "@gooddata/sdk-ui-filters";
|
|
2
|
+
import { type ConditionalFormattingValue } from "@gooddata/sdk-ui-pivot/next";
|
|
3
|
+
import { type ICfDateMeta, type ICfDateSettings } from "./conditionalFormattingModel.js";
|
|
4
|
+
export interface ICfDateValueDropdownProps {
|
|
5
|
+
value: ConditionalFormattingValue;
|
|
6
|
+
date: ICfDateMeta;
|
|
7
|
+
/** Workspace preset catalog (shared with the dashboard date filter); undefined = static only. */
|
|
8
|
+
catalogOptions?: IDateFilterOptionsByType;
|
|
9
|
+
/** Workspace date-display settings (format, week start). */
|
|
10
|
+
dateSettings?: ICfDateSettings;
|
|
11
|
+
/** Id of the rendered inline error, associated with the trigger for assistive tech. */
|
|
12
|
+
errorId?: string;
|
|
13
|
+
onChange: (value: ConditionalFormattingValue) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The value picker of a date condition: the standard DateFilter embedded with a dialog-styled
|
|
17
|
+
* trigger. Offers the static period form plus the workspace preset catalog and relative form,
|
|
18
|
+
* scoped to the target column's granularity.
|
|
19
|
+
*
|
|
20
|
+
* Exclude-current-period follows the dashboard's persistence exactly: the stored offsets are the
|
|
21
|
+
* already-shifted range (no flag), and reopening reconstructs the preset + checked toggle by
|
|
22
|
+
* matching the stored range against "preset shifted by one".
|
|
23
|
+
*/
|
|
24
|
+
export declare function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, errorId, onChange }: ICfDateValueDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=CfDateValueDropdown.d.ts.map
|
|
@@ -0,0 +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"}
|
package/esm/internal/components/configurationControls/conditionalFormatting/CfDateValueDropdown.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { createContext, useContext, useState } from "react";
|
|
4
|
+
import cx from "classnames";
|
|
5
|
+
import { useIntl } from "react-intl";
|
|
6
|
+
import { DateFilter, excludeCurrentPeriodFromRange, matchDateFilterToDateFilterOptionWithPreference, } from "@gooddata/sdk-ui-filters";
|
|
7
|
+
import { DropdownButton } from "@gooddata/sdk-ui-kit";
|
|
8
|
+
import { allowedValueGranularities, snapPlatformRangeToPeriodBounds, snapToPeriodBounds, usesTimeResolution, } from "@gooddata/sdk-ui-pivot/next";
|
|
9
|
+
import { conditionalFormattingMessages } from "../../../../locales.js";
|
|
10
|
+
const ABSOLUTE_FORM_ID = "ABSOLUTE_FORM";
|
|
11
|
+
/**
|
|
12
|
+
* The workspace catalog scoped down to what the column's granularity can honor (granularity
|
|
13
|
+
* applicability is column-driven; the preset catalog is workspace-shared). The "All time" and
|
|
14
|
+
* empty-values entries never show — the All time and Is empty operators cover those.
|
|
15
|
+
*/
|
|
16
|
+
function scopeCatalogToColumn(catalog, absoluteForm, granularity) {
|
|
17
|
+
const allowed = allowedValueGranularities(granularity);
|
|
18
|
+
const relativePresetEntries = Object.entries(catalog?.relativePreset ?? {}).flatMap(([presetGranularity, presets]) => {
|
|
19
|
+
if (!allowed.some((candidate) => candidate === presetGranularity)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
// Bounded ("… to date") presets are not offered: the stored value cannot carry the
|
|
23
|
+
// bound, so applying one would silently persist the whole unbounded period.
|
|
24
|
+
const offerable = (presets ?? []).filter((preset) => preset.boundedFilter === undefined);
|
|
25
|
+
return offerable.length > 0 ? [[presetGranularity, offerable]] : [];
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
absoluteForm,
|
|
29
|
+
...(catalog?.relativeForm ? { relativeForm: catalog.relativeForm } : {}),
|
|
30
|
+
...(catalog?.absolutePreset ? { absolutePreset: catalog.absolutePreset } : {}),
|
|
31
|
+
...(relativePresetEntries.length > 0
|
|
32
|
+
? { relativePreset: Object.fromEntries(relativePresetEntries) }
|
|
33
|
+
: {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// The stored condition value expressed as the dashboard filter shape the option matcher speaks.
|
|
37
|
+
function toDashboardDateFilter(value, columnGranularity) {
|
|
38
|
+
if (value.kind === "absoluteDate") {
|
|
39
|
+
return {
|
|
40
|
+
dateFilter: {
|
|
41
|
+
type: "absolute",
|
|
42
|
+
granularity: columnGranularity,
|
|
43
|
+
from: value.from,
|
|
44
|
+
to: value.to,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (value.kind === "relativeDate") {
|
|
49
|
+
return {
|
|
50
|
+
dateFilter: {
|
|
51
|
+
type: "relative",
|
|
52
|
+
granularity: value.granularity,
|
|
53
|
+
from: value.from,
|
|
54
|
+
to: value.to,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
// The option (and reconstructed exclude-toggle state) the picker opens on for a stored value.
|
|
61
|
+
function matchStoredValue(value, absoluteForm, filterOptions, granularity) {
|
|
62
|
+
const storedFilter = toDashboardDateFilter(value, granularity);
|
|
63
|
+
if (!storedFilter) {
|
|
64
|
+
return { dateFilterOption: absoluteForm, excludeCurrentPeriod: false };
|
|
65
|
+
}
|
|
66
|
+
return matchDateFilterToDateFilterOptionWithPreference(storedFilter, filterOptions, undefined);
|
|
67
|
+
}
|
|
68
|
+
// DateFilter's button contract is fixed, so the error association travels via context.
|
|
69
|
+
const CfDateButtonA11yContext = createContext({});
|
|
70
|
+
// Rendered through DateFilter's button slot. Module-level so its identity is stable across renders.
|
|
71
|
+
// Deliberately NOT wiring props.onClick: DateFilterCore's wrapping span already toggles the dropdown,
|
|
72
|
+
// so wiring both toggles it twice per click (open → instantly closed).
|
|
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 }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The value picker of a date condition: the standard DateFilter embedded with a dialog-styled
|
|
80
|
+
* trigger. Offers the static period form plus the workspace preset catalog and relative form,
|
|
81
|
+
* scoped to the target column's granularity.
|
|
82
|
+
*
|
|
83
|
+
* Exclude-current-period follows the dashboard's persistence exactly: the stored offsets are the
|
|
84
|
+
* already-shifted range (no flag), and reopening reconstructs the preset + checked toggle by
|
|
85
|
+
* matching the stored range against "preset shifted by one".
|
|
86
|
+
*/
|
|
87
|
+
export function CfDateValueDropdown({ value, date, catalogOptions, dateSettings, errorId, onChange, }) {
|
|
88
|
+
const intl = useIntl();
|
|
89
|
+
const stored = value.kind === "absoluteDate" ? { from: value.from, to: value.to } : undefined;
|
|
90
|
+
// The static form needs bounds to open on; pin the current period at mount (recomputing "now"
|
|
91
|
+
// every render would tick across minute boundaries and reset in-progress state). A granularity
|
|
92
|
+
// change while mounted keeps the stale template — harmless, applying always snaps against current.
|
|
93
|
+
const [pristineTemplate] = useState(() => snapToPeriodBounds(new Date(), new Date(), date.granularity));
|
|
94
|
+
const template = stored ?? pristineTemplate ?? undefined;
|
|
95
|
+
const absoluteForm = {
|
|
96
|
+
localIdentifier: ABSOLUTE_FORM_ID,
|
|
97
|
+
type: "absoluteForm",
|
|
98
|
+
name: "",
|
|
99
|
+
visible: true,
|
|
100
|
+
from: template?.from,
|
|
101
|
+
to: template?.to,
|
|
102
|
+
};
|
|
103
|
+
const filterOptions = scopeCatalogToColumn(catalogOptions, absoluteForm, date.granularity);
|
|
104
|
+
const matched = matchStoredValue(value, absoluteForm, filterOptions, date.granularity);
|
|
105
|
+
const handleApply = (option, excludeCurrentPeriod) => {
|
|
106
|
+
if (option.type === "absoluteForm" || option.type === "absolutePreset") {
|
|
107
|
+
if (option.from === undefined || option.to === undefined) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Store the range as picked (overlap semantics resolve it against any column
|
|
111
|
+
// granularity per render); snapping at the value's own precision only validates and
|
|
112
|
+
// normalizes ordering.
|
|
113
|
+
const from = String(option.from);
|
|
114
|
+
const to = String(option.to);
|
|
115
|
+
const precision = from.length > 10 || to.length > 10 ? "GDC.time.minute" : "GDC.time.date";
|
|
116
|
+
const snapped = snapPlatformRangeToPeriodBounds(from, to, precision);
|
|
117
|
+
if (snapped) {
|
|
118
|
+
onChange({ kind: "absoluteDate", from: snapped.from, to: snapped.to });
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (option.type === "relativeForm" || option.type === "relativePreset") {
|
|
123
|
+
const { granularity, from, to } = option;
|
|
124
|
+
if (granularity === undefined || from === undefined || to === undefined) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Bake exclude-current-period into the offsets (dashboard parity): the shared helper
|
|
128
|
+
// shifts a current-period-ending range back by one, exactly what the filter bar persists.
|
|
129
|
+
const range = excludeCurrentPeriodFromRange({ from, to }, excludeCurrentPeriod);
|
|
130
|
+
onChange({ kind: "relativeDate", granularity, from: range.from, to: range.to });
|
|
131
|
+
}
|
|
132
|
+
// allTime/emptyValues never appear among the options (the All time / Is empty operators cover them).
|
|
133
|
+
};
|
|
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,
|
|
135
|
+
// Week columns pin Monday: the resolver snaps ISO weeks, and a Sunday-start calendar
|
|
136
|
+
// 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"
|
|
138
|
+
? intl.formatMessage(conditionalFormattingMessages.dialogSelectPeriod)
|
|
139
|
+
: undefined }) }));
|
|
140
|
+
}
|
package/esm/internal/components/configurationControls/conditionalFormatting/CfSelect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CfSelect.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx"],"names":[],"mappings":"AAaA,UAAU,WAAW,CAAC,CAAC,SAAS,MAAM;IAClC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,cAAc,CAAC,CAAC,SAAS,MAAM;IACrC,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"CfSelect.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx"],"names":[],"mappings":"AAaA,UAAU,WAAW,CAAC,CAAC,SAAS,MAAM;IAClC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,cAAc,CAAC,CAAC,SAAS,MAAM;IACrC,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACjC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAoCD,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,2CAyCpG"}
|
|
@@ -9,7 +9,7 @@ function CfListItem({ item, isSelected, isFocused, onSelect, }) {
|
|
|
9
9
|
if (typeof ic !== "string" || !ic) {
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
-
const isTypeIcon = ic === "gd-icon-attribute" || ic === "gd-icon-metric";
|
|
12
|
+
const isTypeIcon = ic === "gd-icon-attribute" || ic === "gd-icon-metric" || ic === "gd-icon-date";
|
|
13
13
|
return (_jsx("span", { "aria-hidden": "true", className: cx("gd-list-icon", ic, { "gd-cf-type-icon": isTypeIcon }) }));
|
|
14
14
|
}, isSelected: isSelected, isFocused: isFocused, onClick: onSelect }) }));
|
|
15
15
|
}
|
package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type IntlShape } from "react-intl";
|
|
2
1
|
import { type ISeparators } from "@gooddata/sdk-model";
|
|
2
|
+
import { type IDateFilterOptionsByType } from "@gooddata/sdk-ui-filters";
|
|
3
3
|
import { type ConditionalFormattingTarget, type IConditionalFormattingCondition } from "@gooddata/sdk-ui-pivot/next";
|
|
4
|
+
import { type ICfDateMeta, type ICfDateSettings } from "./conditionalFormattingModel.js";
|
|
4
5
|
import { type IReorderSlot } from "./ReorderList.js";
|
|
5
6
|
export interface IConditionEditorProps {
|
|
6
7
|
condition: IConditionalFormattingCondition;
|
|
@@ -8,11 +9,13 @@ export interface IConditionEditorProps {
|
|
|
8
9
|
isPercent: boolean;
|
|
9
10
|
separators: ISeparators | undefined;
|
|
10
11
|
suggestions: readonly string[];
|
|
12
|
+
date: ICfDateMeta | undefined;
|
|
13
|
+
dateFilterOptions: IDateFilterOptionsByType | undefined;
|
|
14
|
+
dateSettings: ICfDateSettings | undefined;
|
|
11
15
|
removable: boolean;
|
|
12
16
|
slot: IReorderSlot;
|
|
13
|
-
intl: IntlShape;
|
|
14
17
|
onChange: (condition: IConditionalFormattingCondition) => void;
|
|
15
18
|
onRemove: () => void;
|
|
16
19
|
}
|
|
17
|
-
export declare function ConditionEditor({ condition, kind, isPercent, separators, suggestions, removable, slot,
|
|
20
|
+
export declare function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, onChange, onRemove }: IConditionEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
18
21
|
//# 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":"
|
|
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"}
|
package/esm/internal/components/configurationControls/conditionalFormatting/ConditionEditor.js
CHANGED
|
@@ -2,33 +2,16 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
// (C) 2026 GoodData Corporation
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import cx from "classnames";
|
|
5
|
+
import { useIntl } from "react-intl";
|
|
5
6
|
import { Button, Dropdown, Input, InputWithNumberFormat, UiButton, UiButtonSegmentedControl, UiCombobox, UiComboboxInput, UiComboboxList, UiComboboxListItem, UiComboboxListItemLabel, UiComboboxPopup, } from "@gooddata/sdk-ui-kit";
|
|
6
|
-
import { conditionalFormattingMessages, conditionalFormattingOperatorMessages } from "../../../../locales.js";
|
|
7
|
+
import { conditionalFormattingDateOperatorMessages, conditionalFormattingMessages, conditionalFormattingOperatorMessages, } from "../../../../locales.js";
|
|
7
8
|
import { ColorDropdown } from "../colors/colorDropdown/ColorDropdown.js";
|
|
9
|
+
import { CfDateValueDropdown } from "./CfDateValueDropdown.js";
|
|
8
10
|
import { CfSelect } from "./CfSelect.js";
|
|
9
11
|
import { CF_COLOR_PALETTE, colorToHex, hexToColor } from "./conditionalFormattingColors.js";
|
|
10
|
-
import { displayToRawNumber,
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const empty = emptyValueForOperator(operator);
|
|
14
|
-
return previous.kind === empty.kind ? previous : empty;
|
|
15
|
-
};
|
|
16
|
-
const literalText = (value) => value.kind === "literal" ? String(value.value) : "";
|
|
17
|
-
const literalRaw = (value) => {
|
|
18
|
-
if (value.kind !== "literal" || String(value.value).trim() === "") {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
const n = Number(value.value);
|
|
22
|
-
return Number.isFinite(n) ? n : null;
|
|
23
|
-
};
|
|
24
|
-
const rangeRaw = (value, bound) => {
|
|
25
|
-
if (value.kind !== "literalRange") {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
const n = value[bound];
|
|
29
|
-
return Number.isFinite(n) ? n : null;
|
|
30
|
-
};
|
|
31
|
-
function CfMeasureValueInput({ raw, isPercent, separators, placeholder, intl, onChangeRaw, hasError, accessibilityConfig, }) {
|
|
12
|
+
import { displayToRawNumber, literalRaw, literalText, operatorIcon, operatorsForTarget, rangeRaw, rawToDisplayNumber, validateCondition, valueEditorKind, valueForOperator, } from "./conditionalFormattingModel.js";
|
|
13
|
+
function CfMeasureValueInput({ raw, isPercent, separators, placeholder, onChangeRaw, hasError, accessibilityConfig, }) {
|
|
14
|
+
const intl = useIntl();
|
|
32
15
|
return (_jsx(InputWithNumberFormat, { value: raw === null ? undefined : rawToDisplayNumber(raw, isPercent), suffix: isPercent ? "%" : undefined, separators: separators, placeholder: placeholder, onChange: (display) => onChangeRaw(display === null ? null : displayToRawNumber(display, isPercent)), hasError: hasError, accessibilityConfig: {
|
|
33
16
|
// InputPure renders the suffix aria-hidden; this is its accessible name.
|
|
34
17
|
...(isPercent
|
|
@@ -53,21 +36,33 @@ function CfValueCombobox({ value, suggestions, placeholder, onChangeText }) {
|
|
|
53
36
|
}
|
|
54
37
|
const FIELD_ERROR_MESSAGE = {
|
|
55
38
|
rangeOrder: conditionalFormattingMessages.dialogErrorRangeOrder,
|
|
39
|
+
dateUnresolvable: conditionalFormattingMessages.dialogErrorDateUnresolvable,
|
|
56
40
|
};
|
|
57
|
-
function CfFieldError({ id, error
|
|
41
|
+
function CfFieldError({ id, error }) {
|
|
42
|
+
const intl = useIntl();
|
|
58
43
|
if (!error) {
|
|
59
44
|
return null;
|
|
60
45
|
}
|
|
61
46
|
return (_jsx("span", { id: id, role: "alert", className: "gd-cf-condition__error", children: intl.formatMessage(FIELD_ERROR_MESSAGE[error]) }));
|
|
62
47
|
}
|
|
63
|
-
function ConditionValueInput({ condition, kind, isPercent, separators, suggestions,
|
|
48
|
+
function ConditionValueInput({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, onChange, }) {
|
|
49
|
+
const intl = useIntl();
|
|
64
50
|
const errorId = useId();
|
|
65
51
|
const setValue = (value) => onChange({ ...condition, value });
|
|
66
|
-
switch (valueEditorKind(condition, kind, suggestions.length > 0)) {
|
|
52
|
+
switch (valueEditorKind(condition, kind, suggestions.length > 0, date !== undefined)) {
|
|
67
53
|
case "none":
|
|
68
54
|
return null;
|
|
55
|
+
case "date": {
|
|
56
|
+
if (!date) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const { errors } = validateCondition(condition, kind, date);
|
|
60
|
+
return (_jsxs(_Fragment, { children: [
|
|
61
|
+
_jsx(CfDateValueDropdown, { value: condition.value, date: date, catalogOptions: dateFilterOptions, dateSettings: dateSettings, errorId: errors.date ? errorId : undefined, onChange: setValue }), _jsx(CfFieldError, { id: errorId, error: errors.date })
|
|
62
|
+
] }));
|
|
63
|
+
}
|
|
69
64
|
case "number":
|
|
70
|
-
return (_jsx(CfMeasureValueInput, { raw: literalRaw(condition.value), isPercent: isPercent, separators: separators, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogValuePlaceholder),
|
|
65
|
+
return (_jsx(CfMeasureValueInput, { raw: literalRaw(condition.value), isPercent: isPercent, separators: separators, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogValuePlaceholder), onChangeRaw: (raw) => setValue({ kind: "literal", value: raw ?? "" }) }));
|
|
71
66
|
case "combobox":
|
|
72
67
|
return (_jsx(CfValueCombobox, { value: literalText(condition.value), suggestions: suggestions, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogValuePlaceholder), onChangeText: (v) => setValue({ kind: "literal", value: v }) }));
|
|
73
68
|
case "text":
|
|
@@ -81,8 +76,8 @@ function ConditionValueInput({ condition, kind, isPercent, separators, suggestio
|
|
|
81
76
|
: {};
|
|
82
77
|
return (_jsxs(_Fragment, { children: [
|
|
83
78
|
_jsxs("div", { className: "gd-cf-condition__range", children: [
|
|
84
|
-
_jsx(CfMeasureValueInput, { raw: rangeRaw(condition.value, "from"), isPercent: isPercent, separators: separators, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogFromPlaceholder),
|
|
85
|
-
] }), _jsx(CfFieldError, { id: errorId, error: errors.range
|
|
79
|
+
_jsx(CfMeasureValueInput, { raw: rangeRaw(condition.value, "from"), isPercent: isPercent, separators: separators, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogFromPlaceholder), onChangeRaw: (raw) => setRange(raw ?? NaN, range.to), ...invalidInputProps(errors.range) }), _jsx(CfMeasureValueInput, { raw: rangeRaw(condition.value, "to"), isPercent: isPercent, separators: separators, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogToPlaceholder), onChangeRaw: (raw) => setRange(range.from, raw ?? NaN), ...invalidInputProps(errors.range) })
|
|
80
|
+
] }), _jsx(CfFieldError, { id: errorId, error: errors.range })
|
|
86
81
|
] }));
|
|
87
82
|
}
|
|
88
83
|
}
|
|
@@ -101,7 +96,8 @@ const PREVIEW_SAMPLE = {
|
|
|
101
96
|
};
|
|
102
97
|
// Row scope shows two sample cells to convey that the whole row is formatted.
|
|
103
98
|
const ROW_PREVIEW_SAMPLE = ["Abc", "123"];
|
|
104
|
-
function FormatEditor({ format,
|
|
99
|
+
function FormatEditor({ format, onChange }) {
|
|
100
|
+
const intl = useIntl();
|
|
105
101
|
return (_jsx(Dropdown, { closeOnParentScroll: true, closeOnMouseDrag: true, closeOnEscape: true, renderButton: ({ toggleDropdown, accessibilityConfig }) => (_jsx(Button, { className: "gd-button-link gd-button-icon-only gd-icon-pencil gd-cf-condition__edit", accessibilityConfig: {
|
|
106
102
|
...accessibilityConfig,
|
|
107
103
|
ariaLabel: intl.formatMessage(conditionalFormattingMessages.dialogFormat),
|
|
@@ -111,11 +107,15 @@ function FormatEditor({ format, intl, onChange }) {
|
|
|
111
107
|
] })
|
|
112
108
|
] })) }));
|
|
113
109
|
}
|
|
114
|
-
export function ConditionEditor({ condition, kind, isPercent, separators, suggestions, removable, slot,
|
|
115
|
-
const
|
|
110
|
+
export function ConditionEditor({ condition, kind, isPercent, separators, suggestions, date, dateFilterOptions, dateSettings, removable, slot, onChange, onRemove, }) {
|
|
111
|
+
const intl = useIntl();
|
|
112
|
+
const isDate = date !== undefined;
|
|
113
|
+
// Date operators render label-only (per design), so no icon lookup on that path.
|
|
114
|
+
const operatorItems = operatorsForTarget(kind, isDate).map((operator) => ({
|
|
116
115
|
value: operator,
|
|
117
|
-
title: intl.formatMessage(
|
|
118
|
-
|
|
116
|
+
title: intl.formatMessage((isDate ? conditionalFormattingDateOperatorMessages[operator] : undefined) ??
|
|
117
|
+
conditionalFormattingOperatorMessages[operator]),
|
|
118
|
+
icon: isDate ? undefined : operatorIcon(operator),
|
|
119
119
|
}));
|
|
120
120
|
const setFormat = (format) => onChange({ ...condition, format });
|
|
121
121
|
const previewStyle = {
|
|
@@ -125,8 +125,12 @@ export function ConditionEditor({ condition, kind, isPercent, separators, sugges
|
|
|
125
125
|
return (_jsxs("div", { className: cx("gd-cf-condition", slot.className), ...slot.rootProps, children: [slot.handle, _jsxs("div", { className: "gd-cf-condition__header", children: [
|
|
126
126
|
_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: {
|
|
127
127
|
ariaLabel: intl.formatMessage(conditionalFormattingMessages.dialogRemoveCondition),
|
|
128
|
-
}, onClick: onRemove })) : null] }), _jsx(CfSelect, { value: condition.operator, items: operatorItems, onSelect: (operator) => onChange({
|
|
129
|
-
|
|
128
|
+
}, onClick: onRemove })) : null] }), _jsx(CfSelect, { value: condition.operator, items: operatorItems, onSelect: (operator) => onChange({
|
|
129
|
+
...condition,
|
|
130
|
+
operator,
|
|
131
|
+
value: valueForOperator(operator, condition.value, isDate),
|
|
132
|
+
}) }), _jsx(ConditionValueInput, { condition: condition, kind: kind, isPercent: isPercent, separators: separators, suggestions: suggestions, date: date, dateFilterOptions: dateFilterOptions, dateSettings: dateSettings, onChange: onChange }), _jsxs("div", { className: "gd-cf-condition__format-row", children: [
|
|
133
|
+
_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 })
|
|
130
134
|
] })
|
|
131
135
|
] }));
|
|
132
136
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type ISeparators } from "@gooddata/sdk-model";
|
|
2
|
+
import { type IDateFilterOptionsByType } from "@gooddata/sdk-ui-filters";
|
|
2
3
|
import { type IConditionalFormattingRule } from "@gooddata/sdk-ui-pivot/next";
|
|
3
|
-
import { type ITargetOption } from "./conditionalFormattingModel.js";
|
|
4
|
+
import { type ICfDateSettings, type ITargetOption } from "./conditionalFormattingModel.js";
|
|
4
5
|
export interface IConditionalFormattingDialogProps {
|
|
5
6
|
rule: IConditionalFormattingRule;
|
|
6
7
|
isNew: boolean;
|
|
7
8
|
targetOptions: ITargetOption[];
|
|
8
9
|
/** Workspace number separators; measure value inputs validate and format with them. */
|
|
9
10
|
separators?: ISeparators;
|
|
11
|
+
/** Workspace date-filter preset catalog; date value pickers offer it (undefined = static only). */
|
|
12
|
+
dateFilterOptions?: IDateFilterOptionsByType;
|
|
13
|
+
/** Workspace date-display settings (format, week start) for the date value pickers. */
|
|
14
|
+
dateSettings?: ICfDateSettings;
|
|
10
15
|
/** CSS selector of a small, stable element the popover anchors to. */
|
|
11
16
|
alignTo: string;
|
|
12
17
|
onSave: (rule: IConditionalFormattingRule) => void;
|
|
13
18
|
onClose: () => void;
|
|
14
19
|
}
|
|
15
|
-
export declare function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOptions, separators, alignTo, onSave, onClose }: IConditionalFormattingDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOptions, separators, dateFilterOptions, dateSettings, alignTo, onSave, onClose }: IConditionalFormattingDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
16
21
|
//# sourceMappingURL=ConditionalFormattingDialog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConditionalFormattingDialog.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConditionalFormattingDialog.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingDialog.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EAEH,KAAK,0BAA0B,EAClC,MAAM,6BAA6B,CAAC;AAKrC,OAAO,EACH,KAAK,eAAe,EACpB,KAAK,aAAa,EAQrB,MAAM,iCAAiC,CAAC;AAWzC,MAAM,WAAW,iCAAiC;IAC9C,IAAI,EAAE,0BAA0B,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,aAAa,EAAE,CAAC;IAC/B,uFAAuF;IACvF,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C,uFAAuF;IACvF,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnD,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,2BAA2B,CAAC,EACxC,IAAI,EAAE,WAAW,EACjB,KAAK,EACL,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,MAAM,EACN,OAAO,EACV,EAAE,iCAAiC,2CAgInC"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2026 GoodData Corporation
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { isEqual } from "lodash-es";
|
|
4
5
|
import { useIntl } from "react-intl";
|
|
5
6
|
import { Button, Overlay } from "@gooddata/sdk-ui-kit";
|
|
6
7
|
import { conditionalFormattingMessages } from "../../../../locales.js";
|
|
7
8
|
import { CfSelect } from "./CfSelect.js";
|
|
8
|
-
import { findTargetOption, isRuleComplete, newCondition, ruleWithTarget, sanitizeRuleForEditing, targetIcon, } from "./conditionalFormattingModel.js";
|
|
9
|
+
import { findTargetOption, isDateTarget, isRuleComplete, newCondition, ruleWithTarget, sanitizeRuleForEditing, targetIcon, } from "./conditionalFormattingModel.js";
|
|
9
10
|
import { ConditionEditor } from "./ConditionEditor.js";
|
|
10
11
|
import { ReorderList } from "./ReorderList.js";
|
|
11
12
|
// The config panel sits on the right edge of the screen, so open the popover to the LEFT of the
|
|
@@ -14,9 +15,10 @@ const POPOVER_ALIGN_POINTS = [
|
|
|
14
15
|
{ align: "cl cr", offset: { x: -5, y: 0 } },
|
|
15
16
|
{ align: "cr cl", offset: { x: 5, y: 0 } },
|
|
16
17
|
];
|
|
17
|
-
export function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOptions, separators, alignTo, onSave, onClose, }) {
|
|
18
|
+
export function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOptions, separators, dateFilterOptions, dateSettings, alignTo, onSave, onClose, }) {
|
|
18
19
|
const intl = useIntl();
|
|
19
|
-
const
|
|
20
|
+
const initial = sanitizeRuleForEditing(initialRule, isDateTarget(findTargetOption(targetOptions, initialRule.target)));
|
|
21
|
+
const [rule, setRule] = useState(initial);
|
|
20
22
|
const updateCondition = (id, next) => setRule((current) => ({
|
|
21
23
|
...current,
|
|
22
24
|
conditions: current.conditions.map((condition) => (condition.id === id ? next : condition)),
|
|
@@ -25,7 +27,13 @@ export function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOp
|
|
|
25
27
|
...current,
|
|
26
28
|
conditions: current.conditions.filter((condition) => condition.id !== id),
|
|
27
29
|
}));
|
|
28
|
-
const addCondition = () => setRule((current) => ({
|
|
30
|
+
const addCondition = () => setRule((current) => ({
|
|
31
|
+
...current,
|
|
32
|
+
conditions: [
|
|
33
|
+
...current.conditions,
|
|
34
|
+
newCondition(isDateTarget(findTargetOption(targetOptions, current.target))),
|
|
35
|
+
],
|
|
36
|
+
}));
|
|
29
37
|
const reorderConditions = (conditions) => setRule((current) => ({ ...current, conditions }));
|
|
30
38
|
const changeTarget = (value) => {
|
|
31
39
|
const option = targetOptions.find((candidate) => candidate.value === value);
|
|
@@ -37,7 +45,8 @@ export function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOp
|
|
|
37
45
|
const selectedTarget = findTargetOption(targetOptions, rule.target);
|
|
38
46
|
const isPercent = selectedTarget?.isPercent ?? false;
|
|
39
47
|
const suggestions = selectedTarget?.elements ?? [];
|
|
40
|
-
const complete = isRuleComplete(rule);
|
|
48
|
+
const complete = isRuleComplete(rule, selectedTarget?.date);
|
|
49
|
+
const isDirty = isNew || !isEqual(rule, initial);
|
|
41
50
|
const title = intl.formatMessage(isNew ? conditionalFormattingMessages.dialogAddTitle : conditionalFormattingMessages.dialogEditTitle);
|
|
42
51
|
return (_jsx(Overlay, { alignTo: alignTo, alignPoints: POPOVER_ALIGN_POINTS, closeOnParentScroll: true, closeOnMouseDrag: true, onClose: onClose, children: _jsxs("div", { className: "gd-cf-dialog", "aria-label": title, children: [
|
|
43
52
|
_jsxs("div", { className: "gd-cf-dialog__header", children: [
|
|
@@ -48,10 +57,10 @@ export function ConditionalFormattingDialog({ rule: initialRule, isNew, targetOp
|
|
|
48
57
|
_jsx("span", { className: "gd-cf-dialog__label", children: intl.formatMessage(conditionalFormattingMessages.dialogTarget) }), _jsx(CfSelect, { value: selectedTarget?.value, items: targetOptions.map((option) => ({
|
|
49
58
|
value: option.value,
|
|
50
59
|
title: option.title,
|
|
51
|
-
icon: targetIcon(option.target.kind),
|
|
52
|
-
})), onSelect: changeTarget, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogSelectTarget) }), _jsx(ReorderList, { items: rule.conditions, getKey: (condition) => condition.id, onReorder: reorderConditions, renderItem: (condition, slot) => (_jsx(ConditionEditor, { condition: condition, kind: rule.target.kind, isPercent: isPercent, separators: separators, suggestions: suggestions, removable: rule.conditions.length > 1, slot: slot,
|
|
60
|
+
icon: targetIcon(option.target.kind, isDateTarget(option)),
|
|
61
|
+
})), onSelect: changeTarget, placeholder: intl.formatMessage(conditionalFormattingMessages.dialogSelectTarget) }), _jsx(ReorderList, { items: rule.conditions, getKey: (condition) => condition.id, onReorder: reorderConditions, renderItem: (condition, slot) => (_jsx(ConditionEditor, { condition: condition, kind: rule.target.kind, isPercent: isPercent, separators: separators, suggestions: suggestions, date: selectedTarget?.date, dateFilterOptions: dateFilterOptions, dateSettings: dateSettings, removable: rule.conditions.length > 1, slot: slot, onChange: (next) => updateCondition(condition.id, next), onRemove: () => removeCondition(condition.id) })) }), _jsx(Button, { className: "gd-button-secondary gd-cf-dialog__add-condition", iconLeft: "gd-icon-plus", value: intl.formatMessage(conditionalFormattingMessages.dialogAddCondition), onClick: addCondition })
|
|
53
62
|
] }), _jsxs("div", { className: "gd-cf-dialog__footer", children: [
|
|
54
|
-
_jsx(Button, { className: "gd-button-secondary", value: intl.formatMessage(conditionalFormattingMessages.dialogCancel), onClick: onClose }), _jsx(Button, { className: "gd-button-action", value: intl.formatMessage(conditionalFormattingMessages.dialogSave), disabled: !complete, onClick: () => onSave(rule) })
|
|
63
|
+
_jsx(Button, { className: "gd-button-secondary", value: intl.formatMessage(conditionalFormattingMessages.dialogCancel), onClick: onClose }), _jsx(Button, { className: "gd-button-action", value: intl.formatMessage(conditionalFormattingMessages.dialogSave), disabled: !complete || !isDirty, onClick: () => onSave(rule) })
|
|
55
64
|
] })
|
|
56
65
|
] }) }));
|
|
57
66
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
|
|
1
2
|
import { type IInsightDefinition, type ISeparators } from "@gooddata/sdk-model";
|
|
2
3
|
import { type IVisualizationProperties } from "../../../interfaces/Visualization.js";
|
|
3
|
-
import { type ICfTargetData } from "./conditionalFormattingModel.js";
|
|
4
|
+
import { type ICfDateSettings, type ICfTargetData } from "./conditionalFormattingModel.js";
|
|
4
5
|
export interface IConditionalFormattingSectionProps {
|
|
5
6
|
properties?: IVisualizationProperties;
|
|
6
7
|
propertiesMeta?: Record<string, unknown>;
|
|
7
8
|
insight?: IInsightDefinition;
|
|
8
9
|
targetData?: ICfTargetData;
|
|
10
|
+
/** Backend + workspace for fetching the date-filter preset catalog (date conditions). */
|
|
11
|
+
backend?: IAnalyticalBackend;
|
|
12
|
+
workspace?: string;
|
|
13
|
+
dateSettings?: ICfDateSettings;
|
|
9
14
|
separators?: ISeparators;
|
|
10
15
|
isLoading?: boolean;
|
|
11
16
|
pushData?: (data: unknown) => void;
|
|
12
17
|
}
|
|
13
|
-
export declare function ConditionalFormattingSection({ properties, propertiesMeta, insight, targetData, separators, isLoading, pushData }: IConditionalFormattingSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function ConditionalFormattingSection({ properties, propertiesMeta, insight, targetData, backend, workspace, dateSettings, separators, isLoading, pushData }: IConditionalFormattingSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
14
19
|
//# sourceMappingURL=ConditionalFormattingSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConditionalFormattingSection.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAKhF,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAIrF,OAAO,EACH,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"ConditionalFormattingSection.d.ts","sourceRoot":"","sources":["../../../../../src/internal/components/configurationControls/conditionalFormatting/ConditionalFormattingSection.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAKhF,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAIrF,OAAO,EACH,KAAK,eAAe,EACpB,KAAK,aAAa,EASrB,MAAM,iCAAiC,CAAC;AAWzC,MAAM,WAAW,kCAAkC;IAC/C,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,yFAAyF;IACzF,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,UAAU,CAAC,EAAE,WAAW,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACtC;AA0DD,wBAAgB,4BAA4B,CAAC,EACzC,UAAU,EACV,cAAc,EACd,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,QAAQ,EACX,EAAE,kCAAkC,2CA4HpC"}
|