@gooddata/sdk-ui-filters 11.40.0-alpha.5 → 11.40.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.
- package/esm/MeasureValueFilter/ComparisonInput.d.ts +7 -1
- package/esm/MeasureValueFilter/ComparisonInput.js +22 -12
- package/esm/MeasureValueFilter/ConditionInputSection.d.ts +5 -0
- package/esm/MeasureValueFilter/ConditionInputSection.js +3 -3
- package/esm/MeasureValueFilter/Dropdown.d.ts +3 -0
- package/esm/MeasureValueFilter/Dropdown.js +2 -2
- package/esm/MeasureValueFilter/DropdownBody.d.ts +4 -0
- package/esm/MeasureValueFilter/DropdownBody.js +10 -6
- package/esm/MeasureValueFilter/MeasureValueFilter.js +6 -4
- package/esm/MeasureValueFilter/MeasureValueFilterButton.d.ts +7 -1
- package/esm/MeasureValueFilter/MeasureValueFilterButton.js +2 -2
- package/esm/MeasureValueFilter/MeasureValueFilterDropdown.d.ts +7 -0
- package/esm/MeasureValueFilter/MeasureValueFilterDropdown.js +2 -2
- package/esm/MeasureValueFilter/OperatorDropdown.d.ts +1 -0
- package/esm/MeasureValueFilter/OperatorDropdown.js +18 -8
- package/esm/MeasureValueFilter/OperatorDropdownBody.d.ts +5 -0
- package/esm/MeasureValueFilter/OperatorDropdownBody.js +79 -14
- package/esm/MeasureValueFilter/PreviewSection.d.ts +5 -0
- package/esm/MeasureValueFilter/PreviewSection.js +33 -4
- package/esm/MeasureValueFilter/RangeInput.d.ts +7 -1
- package/esm/MeasureValueFilter/RangeInput.js +17 -8
- package/esm/MeasureValueFilter/TreatNullValuesAsZeroCheckbox.d.ts +2 -1
- package/esm/MeasureValueFilter/TreatNullValuesAsZeroCheckbox.js +8 -5
- package/esm/MeasureValueFilter/WarningMessage.js +1 -1
- package/esm/MeasureValueFilter/typings.d.ts +16 -0
- package/esm/locales.js +15 -0
- package/esm/sdk-ui-filters.d.ts +29 -0
- package/package.json +16 -16
- package/styles/css/attributeFilter.css +11 -1
- package/styles/css/attributeFilter.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css +11 -1
- package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css.map +1 -1
- package/styles/css/attributeFilterNext.css +11 -1
- package/styles/css/attributeFilterNext.css.map +1 -1
- package/styles/css/main.css +16 -1
- package/styles/css/main.css.map +1 -1
- package/styles/css/measureValueFilter.css +5 -0
- package/styles/css/measureValueFilter.css.map +1 -1
- package/styles/scss/measureValueFilter.scss +7 -2
- package/esm/MeasureValueFilter/OperatorDropdownItem.d.ts +0 -12
- package/esm/MeasureValueFilter/OperatorDropdownItem.js +0 -39
|
@@ -10,6 +10,12 @@ interface IComparisonInputProps {
|
|
|
10
10
|
hasError?: boolean;
|
|
11
11
|
ariaDescribedBy?: string;
|
|
12
12
|
separators?: ISeparators;
|
|
13
|
+
/**
|
|
14
|
+
* 1-based condition position. When set, it is appended to the accessible label so a filter
|
|
15
|
+
* with multiple conditions does not expose several identically-named inputs (WCAG 2.4.6) —
|
|
16
|
+
* a unique id alone is not enough, the checker compares the resolved name text.
|
|
17
|
+
*/
|
|
18
|
+
conditionNumber?: number;
|
|
13
19
|
}
|
|
14
|
-
export declare function ComparisonInput({ value, usePercentage, disableAutofocus, onValueChange, onEnterKeyPress, onBlur, hasError, ariaDescribedBy, separators }: IComparisonInputProps): ReactElement;
|
|
20
|
+
export declare function ComparisonInput({ value, usePercentage, disableAutofocus, onValueChange, onEnterKeyPress, onBlur, hasError, ariaDescribedBy, separators, conditionNumber }: IComparisonInputProps): ReactElement;
|
|
15
21
|
export {};
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useIntl } from "react-intl";
|
|
3
|
-
import { InputWithNumberFormat } from "@gooddata/sdk-ui-kit";
|
|
4
|
-
export function ComparisonInput({ value, usePercentage, disableAutofocus, onValueChange, onEnterKeyPress, onBlur, hasError, ariaDescribedBy, separators, }) {
|
|
3
|
+
import { InputWithNumberFormat, useIdPrefixed } from "@gooddata/sdk-ui-kit";
|
|
4
|
+
export function ComparisonInput({ value, usePercentage, disableAutofocus, onValueChange, onEnterKeyPress, onBlur, hasError, ariaDescribedBy, separators, conditionNumber, }) {
|
|
5
5
|
const intl = useIntl();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
// Label is provided via a hidden element referenced by aria-labelledby (rather than a direct
|
|
7
|
+
// aria-label string), following the codebase convention for input labelling.
|
|
8
|
+
const labelId = useIdPrefixed("mvf-comparison-value-label");
|
|
9
|
+
const baseLabel = intl.formatMessage({ id: "mvf.comparisonInput.ariaLabel" });
|
|
10
|
+
const labelText = conditionNumber === undefined
|
|
11
|
+
? baseLabel
|
|
12
|
+
: intl.formatMessage({ id: "mvf.input.ariaLabel.withCondition" }, { label: baseLabel, number: conditionNumber });
|
|
13
|
+
return (_jsxs(_Fragment, { children: [
|
|
14
|
+
_jsx(InputWithNumberFormat, { className: "s-mvf-comparison-value-input", dataTestId: "mvf-comparison-value-input", autocomplete: "off", value: value ?? undefined, onEnterKeyPress: onEnterKeyPress, onChange: (val) => onValueChange(val), onBlur: onBlur ? () => onBlur() : undefined, hasError: hasError, isSmall: true, autofocus: !disableAutofocus, suffix: usePercentage ? "%" : "", accessibilityConfig: {
|
|
15
|
+
ariaLabelledBy: labelId,
|
|
16
|
+
suffixAriaLabel: usePercentage
|
|
17
|
+
? intl.formatMessage({
|
|
18
|
+
id: "input.unit.percent",
|
|
19
|
+
})
|
|
20
|
+
: undefined,
|
|
21
|
+
ariaDescribedBy,
|
|
22
|
+
ariaInvalid: hasError ? true : undefined,
|
|
23
|
+
}, separators: separators }), _jsx("span", { className: "sr-only", id: labelId, children: labelText })
|
|
24
|
+
] }));
|
|
15
25
|
}
|
|
@@ -2,6 +2,11 @@ import { type ISeparators } from "@gooddata/sdk-model";
|
|
|
2
2
|
import { type IMeasureValueFilterValue, type MeasureValueFilterOperator } from "./types.js";
|
|
3
3
|
export interface IConditionInputSectionProps {
|
|
4
4
|
index: number;
|
|
5
|
+
/**
|
|
6
|
+
* 1-based condition position, passed to the value inputs to disambiguate their accessible
|
|
7
|
+
* names when a filter has multiple conditions (WCAG 2.4.6). Undefined for a single condition.
|
|
8
|
+
*/
|
|
9
|
+
conditionNumber?: number;
|
|
5
10
|
condition: {
|
|
6
11
|
operator: MeasureValueFilterOperator;
|
|
7
12
|
value: IMeasureValueFilterValue;
|
|
@@ -7,7 +7,7 @@ import { ComparisonInput } from "./ComparisonInput.js";
|
|
|
7
7
|
import { RangeInput } from "./RangeInput.js";
|
|
8
8
|
export const ConditionInputSection = memo(function ConditionInputSection(props) {
|
|
9
9
|
const intl = useIntl();
|
|
10
|
-
const { index, condition, usePercentage, baseDisableAutofocus, separators, onValueChange, onFromChange, onToChange, onValueBlur, onFromBlur, onToBlur, onApply, } = props;
|
|
10
|
+
const { index, conditionNumber, condition, usePercentage, baseDisableAutofocus, separators, onValueChange, onFromChange, onToChange, onValueBlur, onFromBlur, onToBlur, onApply, } = props;
|
|
11
11
|
if (!condition || condition.operator === "ALL") {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
@@ -25,7 +25,7 @@ export const ConditionInputSection = memo(function ConditionInputSection(props)
|
|
|
25
25
|
})
|
|
26
26
|
: undefined;
|
|
27
27
|
return (_jsxs(_Fragment, { children: [
|
|
28
|
-
_jsx(ComparisonInput, { value: condition.value.value, usePercentage: usePercentage, onValueChange: (v) => onValueChange(index, v), onEnterKeyPress: onApply, onBlur: () => onValueBlur(index), hasError: shouldShowError, ariaDescribedBy: shouldShowError ? errorId : undefined, disableAutofocus: disableAutofocus, separators: separators }), shouldShowError ? (_jsx("div", { id: errorId, className: "gd-mvf-input-error s-mvf-input-error", "data-testid": errorId, role: "alert", children: validationErrorText })) : null] }));
|
|
28
|
+
_jsx(ComparisonInput, { value: condition.value.value, usePercentage: usePercentage, onValueChange: (v) => onValueChange(index, v), onEnterKeyPress: onApply, onBlur: () => onValueBlur(index), hasError: shouldShowError, ariaDescribedBy: shouldShowError ? errorId : undefined, disableAutofocus: disableAutofocus, separators: separators, conditionNumber: conditionNumber }), shouldShowError ? (_jsx("div", { id: errorId, className: "gd-mvf-input-error s-mvf-input-error", "data-testid": errorId, role: "alert", children: validationErrorText })) : null] }));
|
|
29
29
|
}
|
|
30
30
|
if (isRangeConditionOperator(condition.operator)) {
|
|
31
31
|
const { from = null, to = null } = condition.value;
|
|
@@ -43,7 +43,7 @@ export const ConditionInputSection = memo(function ConditionInputSection(props)
|
|
|
43
43
|
errorText: fromErrorText,
|
|
44
44
|
}, toField: {
|
|
45
45
|
errorText: toErrorText,
|
|
46
|
-
}, disableAutofocus: disableAutofocus, separators: separators }));
|
|
46
|
+
}, disableAutofocus: disableAutofocus, separators: separators, conditionNumber: conditionNumber }));
|
|
47
47
|
}
|
|
48
48
|
return null;
|
|
49
49
|
});
|
|
@@ -29,6 +29,7 @@ interface IDropdownProps extends IMeasureValueFilterCustomComponentProps {
|
|
|
29
29
|
insightDimensionality?: IDimensionalityItem[];
|
|
30
30
|
isDimensionalityEnabled?: boolean;
|
|
31
31
|
isFilterSummaryEnabled?: boolean;
|
|
32
|
+
showSimplifiedSummary?: boolean;
|
|
32
33
|
catalogDimensionality?: IDimensionalityItem[];
|
|
33
34
|
loadCatalogDimensionality?: (dimensionality: ObjRefInScope[]) => Promise<IDimensionalityItem[]>;
|
|
34
35
|
onDimensionalityChange?: (dimensionality: ObjRefInScope[]) => void;
|
|
@@ -38,6 +39,8 @@ interface IDropdownProps extends IMeasureValueFilterCustomComponentProps {
|
|
|
38
39
|
alignPoints?: IAlignPoint[];
|
|
39
40
|
fullscreenOnMobile?: boolean;
|
|
40
41
|
mobileHeader?: ReactNode;
|
|
42
|
+
dialogId?: string;
|
|
43
|
+
isViewMode?: boolean;
|
|
41
44
|
}
|
|
42
45
|
export declare function Dropdown(props: IDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
43
46
|
export {};
|
|
@@ -13,14 +13,14 @@ const alignPoints = ["bl tl", "tl bl", "br tr", "tr br"];
|
|
|
13
13
|
const DROPDOWN_ALIGNMENTS = alignPoints.map((align) => ({ align, offset: { x: 1, y: 0 } }));
|
|
14
14
|
const MOBILE_DROPDOWN_ALIGN_POINTS = [{ align: "tl tl" }];
|
|
15
15
|
const DropdownWithIntl = memo(function DropdownWithIntl(props) {
|
|
16
|
-
const { operator = "ALL", usePercentage, warningMessage, locale, onCancel, anchorEl, separators, format, useShortFormat, measureTitle, displayTreatNullAsZeroOption = false, treatNullAsZeroValue = false, enableOperatorSelection, onApply: onApplyProp, onChange: onChangeProp, withoutApply, BodyComponent, DropdownActionsComponent, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled = true, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, conditions = [], enableMultipleConditions = false, enableRankingWithMvf, applyOnResult, loadMetricDetails, isHeaderEnabled, alignPoints, fullscreenOnMobile, mobileHeader, } = props;
|
|
16
|
+
const { operator = "ALL", usePercentage, warningMessage, locale, onCancel, anchorEl, separators, format, useShortFormat, measureTitle, displayTreatNullAsZeroOption = false, treatNullAsZeroValue = false, enableOperatorSelection, onApply: onApplyProp, onChange: onChangeProp, withoutApply, BodyComponent, DropdownActionsComponent, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled = true, showSimplifiedSummary = false, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, conditions = [], enableMultipleConditions = false, enableRankingWithMvf, applyOnResult, loadMetricDetails, isHeaderEnabled, alignPoints, fullscreenOnMobile, mobileHeader, dialogId, isViewMode, } = props;
|
|
17
17
|
const isMobile = useMediaQuery("mobileDevice");
|
|
18
18
|
const useFullScreen = !!fullscreenOnMobile && isMobile;
|
|
19
19
|
const onApply = useCallback((conditions, newDimensionality, applyOnResult) => {
|
|
20
20
|
onApplyProp(conditions, newDimensionality, applyOnResult);
|
|
21
21
|
}, [onApplyProp]);
|
|
22
22
|
const selectedOperator = operator === null ? "ALL" : operator;
|
|
23
|
-
const body = (_jsx(UiFocusManager, { enableFocusTrap: true, enableAutofocus: true, enableReturnFocusOnUnmount: true, children: _jsx(DropdownBody, { operator: selectedOperator, conditions: conditions, enableMultipleConditions: enableMultipleConditions, usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, onChange: onChangeProp, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: onCancel, onApply: onApply, separators: separators, format: format, useShortFormat: useShortFormat, measureTitle: measureTitle, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroValue: treatNullAsZeroValue, enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableRankingWithMvf: enableRankingWithMvf, applyOnResult: applyOnResult, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, isMobile: useFullScreen }) }));
|
|
23
|
+
const body = (_jsx(UiFocusManager, { enableFocusTrap: true, enableAutofocus: true, enableReturnFocusOnUnmount: true, children: _jsx(DropdownBody, { operator: selectedOperator, conditions: conditions, enableMultipleConditions: enableMultipleConditions, usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, onChange: onChangeProp, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: onCancel, onApply: onApply, separators: separators, format: format, useShortFormat: useShortFormat, measureTitle: measureTitle, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroValue: treatNullAsZeroValue, enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, showSimplifiedSummary: showSimplifiedSummary, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableRankingWithMvf: enableRankingWithMvf, applyOnResult: applyOnResult, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, isMobile: useFullScreen, isViewMode: isViewMode, dialogId: dialogId }) }));
|
|
24
24
|
if (useFullScreen) {
|
|
25
25
|
return (_jsx(FullScreenOverlay, { alignTo: "body", alignPoints: MOBILE_DROPDOWN_ALIGN_POINTS, onClose: onCancel, children: _jsxs("div", { className: "gd-mobile-dropdown-overlay overlay gd-flex-row-container gd-mvf-mobile-dropdown", children: [mobileHeader ? (_jsx("div", { className: "gd-mobile-dropdown-header gd-flex-item gd-mvf-mobile-dropdown-header gd-is-mobile", children: mobileHeader })) : null, _jsx("div", { className: "gd-mobile-dropdown-content gd-flex-item-stretch gd-flex-row-container gd-mvf-mobile-dropdown-content", children: body })
|
|
26
26
|
] }) }));
|
|
@@ -27,6 +27,7 @@ interface IDropdownBodyProps extends IMeasureValueFilterCustomComponentProps {
|
|
|
27
27
|
insightDimensionality?: IDimensionalityItem[];
|
|
28
28
|
isDimensionalityEnabled?: boolean;
|
|
29
29
|
isFilterSummaryEnabled?: boolean;
|
|
30
|
+
showSimplifiedSummary?: boolean;
|
|
30
31
|
catalogDimensionality?: IDimensionalityItem[];
|
|
31
32
|
loadCatalogDimensionality?: (dimensionality: ObjRefInScope[]) => Promise<IDimensionalityItem[]>;
|
|
32
33
|
onDimensionalityChange?: (dimensionality: ObjRefInScope[]) => void;
|
|
@@ -34,6 +35,9 @@ interface IDropdownBodyProps extends IMeasureValueFilterCustomComponentProps {
|
|
|
34
35
|
loadMetricDetails?: () => Promise<IMeasureMetadataObject | undefined>;
|
|
35
36
|
isHeaderEnabled?: boolean;
|
|
36
37
|
isMobile?: boolean;
|
|
38
|
+
isViewMode?: boolean;
|
|
39
|
+
/** Stable id of the dialog, referenced by the trigger button's aria-controls. */
|
|
40
|
+
dialogId?: string;
|
|
37
41
|
}
|
|
38
42
|
export declare const DropdownBodyWithIntl: import("react").NamedExoticComponent<IDropdownBodyProps>;
|
|
39
43
|
export declare const DropdownBody: import("react").NamedExoticComponent<IDropdownBodyProps>;
|
|
@@ -73,7 +73,7 @@ export const DropdownBodyWithIntl = memo(function DropdownBodyWithIntl(props) {
|
|
|
73
73
|
const conditionsJoinerOr = intl.formatMessage({
|
|
74
74
|
id: "mvf.conditionsJoiner.or",
|
|
75
75
|
});
|
|
76
|
-
const { operator: propsOperator, conditions: propsConditions, enableMultipleConditions = false, enableRankingWithMvf = false, applyOnResult: initialApplyOnResult, usePercentage, treatNullAsZeroValue, valuePrecision = DefaultValuePrecision, isDimensionalityEnabled = true, isFilterSummaryEnabled = true, insightDimensionality, separators, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, } = props;
|
|
76
|
+
const { operator: propsOperator, conditions: propsConditions, enableMultipleConditions = false, enableRankingWithMvf = false, applyOnResult: initialApplyOnResult, usePercentage, treatNullAsZeroValue, valuePrecision = DefaultValuePrecision, isDimensionalityEnabled = true, isFilterSummaryEnabled = true, showSimplifiedSummary = false, insightDimensionality, separators, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, } = props;
|
|
77
77
|
// This flag determines if the message, which explains different filter behavior, is shown for the filters
|
|
78
78
|
// created before the dimensionality feature was introduced. The new filters or re-saved filters are
|
|
79
79
|
// considered as "migrated" and the message is not shown.
|
|
@@ -552,24 +552,28 @@ export const DropdownBodyWithIntl = memo(function DropdownBodyWithIntl(props) {
|
|
|
552
552
|
isCommitPending.current = true;
|
|
553
553
|
setApplyOnResult(event.target.checked);
|
|
554
554
|
}, [setApplyOnResult]);
|
|
555
|
-
return (_jsxs("div", { className: cx(MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS, "gd-dialog gd-dropdown overlay s-mvf-dropdown-body", { "gd-is-mobile": props.isMobile }), "data-testid": "mvf-dropdown-body", children: [props.isHeaderEnabled && props.measureTitle && !props.isMobile ? (_jsx(MeasureValueFilterDropdownHeader, { title: props.measureTitle, loadMetricDetails: props.loadMetricDetails })) : null, _jsx("div", { className: "gd-mvf-dropdown-content", children: BodyComponent ? (_jsx(BodyComponent, { onApplyButtonClick: onApply, onCancelButtonClick: onCancel ?? (() => undefined) })) : (_jsxs(_Fragment, { children: [warningMessage ? (_jsx("div", { className: "gd-mvf-dropdown-section", children: _jsx(WarningMessageComponent, { warningMessage: warningMessage }) })) : null, _jsxs("div", { className: "gd-mvf-conditions-scroll-container", children: [(enableMultipleConditions ? state.conditions : state.conditions.slice(0, 1)).map((c, idx) => (_jsxs("div", { className: cx("gd-mvf-dropdown-section", "gd-mvf-condition-section", {
|
|
555
|
+
return (_jsxs("div", { id: props.dialogId, role: "dialog", "aria-modal": true, "aria-label": props.measureTitle, className: cx(MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS, "gd-dialog gd-dropdown overlay s-mvf-dropdown-body", { "gd-is-mobile": props.isMobile }), "data-testid": "mvf-dropdown-body", children: [props.isHeaderEnabled && props.measureTitle && !props.isMobile ? (_jsx(MeasureValueFilterDropdownHeader, { title: props.measureTitle, loadMetricDetails: props.loadMetricDetails })) : null, _jsx("div", { className: "gd-mvf-dropdown-content", children: BodyComponent ? (_jsx(BodyComponent, { onApplyButtonClick: onApply, onCancelButtonClick: onCancel ?? (() => undefined) })) : (_jsxs(_Fragment, { children: [warningMessage ? (_jsx("div", { className: "gd-mvf-dropdown-section", children: _jsx(WarningMessageComponent, { warningMessage: warningMessage }) })) : null, _jsxs("div", { className: "gd-mvf-conditions-scroll-container", children: [(enableMultipleConditions ? state.conditions : state.conditions.slice(0, 1)).map((c, idx) => (_jsxs("div", { className: cx("gd-mvf-dropdown-section", "gd-mvf-condition-section", {
|
|
556
556
|
"gd-mvf-condition-section--multi": enableMultipleConditions,
|
|
557
557
|
}), children: [
|
|
558
558
|
_jsxs("div", { className: "gd-mvf-condition-header", "data-testid": `mvf-condition-${idx}`, children: [
|
|
559
|
-
_jsx("div", { className: "gd-mvf-condition-operator", children: _jsx(OperatorDropdown, { onSelect: (op) => handleOperatorSelection(idx, op), operator: c.operator, isDisabled: !enableOperatorSelection, isAllOperatorDisabled: isAllOperatorDisabled, isMobile: props.isMobile }) }), enableMultipleConditions ? (_jsx("div", { className: "gd-mvf-condition-action", children: idx === 0 ? (_jsx(ConditionActionButton, { icon: "plus", isMobile: props.isMobile, isDisabled: isAddConditionDisabled, onClick: handleAddCondition, dataTestId: "mvf-add-condition", label: addConditionTooltip, tooltip: isAddConditionDisabled
|
|
559
|
+
_jsx("div", { className: "gd-mvf-condition-operator", children: _jsx(OperatorDropdown, { onSelect: (op) => handleOperatorSelection(idx, op), operator: c.operator, isDisabled: !enableOperatorSelection, isAllOperatorDisabled: isAllOperatorDisabled, isMobile: props.isMobile, isViewMode: props.isViewMode }) }), enableMultipleConditions ? (_jsx("div", { className: "gd-mvf-condition-action", children: idx === 0 ? (_jsx(ConditionActionButton, { icon: "plus", isMobile: props.isMobile, isDisabled: isAddConditionDisabled, onClick: handleAddCondition, dataTestId: "mvf-add-condition", label: addConditionTooltip, tooltip: isAddConditionDisabled
|
|
560
560
|
? addConditionDisabledTooltip
|
|
561
561
|
: addConditionTooltip })) : (_jsx(ConditionActionButton, { icon: "cross", isMobile: props.isMobile, isDestructive: true, onClick: () => handleRemoveCondition(idx), dataTestId: `mvf-remove-condition-${idx}`, label: removeConditionTooltip, tooltip: removeConditionTooltip })) })) : null] }), c.operator === "ALL" ? null : (_jsxs("div", { className: "gd-mvf-condition-inputs", children: [
|
|
562
|
-
_jsx(ConditionInputSection, { index: idx,
|
|
562
|
+
_jsx(ConditionInputSection, { index: idx, conditionNumber: (enableMultipleConditions
|
|
563
|
+
? state.conditions.length
|
|
564
|
+
: 1) > 1
|
|
565
|
+
? idx + 1
|
|
566
|
+
: undefined, condition: c, usePercentage: props.usePercentage ?? false, baseDisableAutofocus: props.disableAutofocus, separators: props.separators, onValueChange: handleValueChange, onFromChange: handleFromChange, onToChange: handleToChange, onValueBlur: handleValueBlur, onFromBlur: handleFromBlur, onToBlur: handleToBlur, onApply: onApply }), idx ===
|
|
563
567
|
(enableMultipleConditions
|
|
564
568
|
? state.conditions.length - 1
|
|
565
|
-
: 0) && shouldShowTreatNullAsZeroCheckbox ? (_jsx(TreatNullValuesAsZeroCheckbox, { onChange: handleTreatNullAsZeroClicked, checked: enabledTreatNullValuesAsZero, isMobile: props.isMobile, intl: intl })) : null, idx ===
|
|
569
|
+
: 0) && shouldShowTreatNullAsZeroCheckbox ? (_jsx(TreatNullValuesAsZeroCheckbox, { onChange: handleTreatNullAsZeroClicked, checked: enabledTreatNullValuesAsZero, isMobile: props.isMobile, isViewMode: props.isViewMode, intl: intl })) : null, idx ===
|
|
566
570
|
(enableMultipleConditions
|
|
567
571
|
? state.conditions.length - 1
|
|
568
572
|
: 0) && enableRankingWithMvf ? (_jsxs("label", { className: "input-checkbox-label gd-mvf-apply-on-result-checkbox", "data-testid": "mvf-apply-on-result", children: [
|
|
569
573
|
_jsx("input", { type: "checkbox", name: "apply-on-result", className: "input-checkbox", checked: applyOnResult, onChange: handleApplyOnResultChange }), _jsx("span", { className: "input-label-text", children: intl.formatMessage({
|
|
570
574
|
id: "mvf.applyOnResultLabel",
|
|
571
575
|
}) })
|
|
572
|
-
] })) : null] })), enableMultipleConditions && idx < state.conditions.length - 1 ? (_jsx("div", { className: "gd-mvf-conditions-joiner", children: conditionsJoinerOr })) : null] }, idx))), isDimensionalityEnabled ? (_jsx(DimensionalitySection, { dimensionality: dimensionality, insightDimensionality: insightDimensionality, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, onDimensionalityChange: handleDimensionalityChange, isMigratedFilter: isMigratedFilter })) : null] }), isFilterSummaryEnabled ? (_jsx(PreviewSection, { measureTitle: props.measureTitle, usePercentage: props.usePercentage, separators: separators, format: props.format, useShortFormat: props.useShortFormat, dimensionality: dimensionality, showAllPreview: enableMultipleConditions, conditions: state.conditions.map(({ operator, value }) => ({
|
|
576
|
+
] })) : null] })), enableMultipleConditions && idx < state.conditions.length - 1 ? (_jsx("div", { className: "gd-mvf-conditions-joiner", children: conditionsJoinerOr })) : null] }, idx))), isDimensionalityEnabled ? (_jsx(DimensionalitySection, { dimensionality: dimensionality, insightDimensionality: insightDimensionality, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, onDimensionalityChange: handleDimensionalityChange, isMigratedFilter: isMigratedFilter })) : null] }), isFilterSummaryEnabled ? (_jsx(PreviewSection, { measureTitle: props.measureTitle, showSimplifiedSummary: showSimplifiedSummary, usePercentage: props.usePercentage, separators: separators, format: props.format, useShortFormat: props.useShortFormat, dimensionality: dimensionality, showAllPreview: enableMultipleConditions, conditions: state.conditions.map(({ operator, value }) => ({
|
|
573
577
|
operator,
|
|
574
578
|
value,
|
|
575
579
|
})) })) : null] })) }), (() => {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2020-2026 GoodData Corporation
|
|
3
3
|
import { Fragment, memo, useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
-
import { useMediaQuery } from "@gooddata/sdk-ui-kit";
|
|
4
|
+
import { useIdPrefixed, useMediaQuery } from "@gooddata/sdk-ui-kit";
|
|
5
5
|
import { DropdownButton } from "./MeasureValueFilterButton.js";
|
|
6
6
|
import { MeasureValueFilterDropdown } from "./MeasureValueFilterDropdown.js";
|
|
7
7
|
/**
|
|
8
8
|
* @beta
|
|
9
9
|
*/
|
|
10
|
-
export const MeasureValueFilter = memo(function MeasureValueFilter({ onCancel = () => { }, filter, measureIdentifier, buttonTitle, buttonSubtitle, buttonTitleExtension, buttonDisabled, measureTitle, usePercentage, warningMessage, locale, separators, format, useShortFormat, displayTreatNullAsZeroOption, treatNullAsZeroDefaultValue, enableOperatorSelection, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, withoutApply, BodyComponent, DropdownActionsComponent, enableMultipleConditions = false, enableRankingWithMvf, onApply, DropdownButtonComponent = DropdownButton, autoOpen, loadMetricDetails, isHeaderEnabled, onChange, alignPoints, fullscreenOnMobile, }) {
|
|
10
|
+
export const MeasureValueFilter = memo(function MeasureValueFilter({ onCancel = () => { }, filter, measureIdentifier, buttonTitle, buttonSubtitle, buttonTitleExtension, buttonDisabled, measureTitle, usePercentage, warningMessage, locale, separators, format, useShortFormat, displayTreatNullAsZeroOption, treatNullAsZeroDefaultValue, enableOperatorSelection, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled, showSimplifiedSummary, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, withoutApply, BodyComponent, DropdownActionsComponent, enableMultipleConditions = false, enableRankingWithMvf, onApply, DropdownButtonComponent = DropdownButton, autoOpen, loadMetricDetails, isHeaderEnabled, onChange, alignPoints, fullscreenOnMobile, isViewMode, }) {
|
|
11
11
|
const [displayDropdown, setDisplayDropdown] = useState(false);
|
|
12
|
+
const dialogId = useIdPrefixed("mvf-dialog");
|
|
12
13
|
const buttonRef = useRef(null);
|
|
13
14
|
const autoOpenedRef = useRef(false);
|
|
14
15
|
const isMobile = useMediaQuery("mobileDevice");
|
|
@@ -33,16 +34,17 @@ export const MeasureValueFilter = memo(function MeasureValueFilter({ onCancel =
|
|
|
33
34
|
const toggleDropdown = useCallback(() => {
|
|
34
35
|
setDisplayDropdown((state) => !state);
|
|
35
36
|
}, []);
|
|
36
|
-
const renderDropdownButton = useCallback((onClickHandler) => (_jsx(DropdownButtonComponent, { onClick: onClickHandler, isActive: displayDropdown, buttonTitle: buttonTitle, buttonSubtitle: buttonSubtitle, buttonTitleExtension: buttonTitleExtension, disabled: buttonDisabled })), [
|
|
37
|
+
const renderDropdownButton = useCallback((onClickHandler) => (_jsx(DropdownButtonComponent, { onClick: onClickHandler, isActive: displayDropdown, buttonTitle: buttonTitle, buttonSubtitle: buttonSubtitle, buttonTitleExtension: buttonTitleExtension, disabled: buttonDisabled, dropdownId: dialogId })), [
|
|
37
38
|
DropdownButtonComponent,
|
|
38
39
|
buttonDisabled,
|
|
39
40
|
buttonSubtitle,
|
|
40
41
|
buttonTitle,
|
|
41
42
|
buttonTitleExtension,
|
|
43
|
+
dialogId,
|
|
42
44
|
displayDropdown,
|
|
43
45
|
]);
|
|
44
46
|
return (_jsxs(Fragment, { children: [
|
|
45
|
-
_jsx("div", { ref: buttonRef, children: renderDropdownButton(toggleDropdown) }), displayDropdown ? (_jsx(MeasureValueFilterDropdown, { onApply: handleApply, onChange: onChange, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: handleCancel, filter: filter, measureIdentifier: measureIdentifier, measureTitle: measureTitle, usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, separators: separators, format: format, useShortFormat: useShortFormat, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroDefaultValue: treatNullAsZeroDefaultValue, enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableMultipleConditions: enableMultipleConditions, enableRankingWithMvf: enableRankingWithMvf, anchorEl: buttonRef.current ?? undefined, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, alignPoints: alignPoints, fullscreenOnMobile: fullscreenOnMobile,
|
|
47
|
+
_jsx("div", { ref: buttonRef, children: renderDropdownButton(toggleDropdown) }), displayDropdown ? (_jsx(MeasureValueFilterDropdown, { onApply: handleApply, onChange: onChange, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: handleCancel, filter: filter, measureIdentifier: measureIdentifier, measureTitle: measureTitle, usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, separators: separators, format: format, useShortFormat: useShortFormat, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroDefaultValue: treatNullAsZeroDefaultValue, enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, showSimplifiedSummary: showSimplifiedSummary, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableMultipleConditions: enableMultipleConditions, enableRankingWithMvf: enableRankingWithMvf, anchorEl: buttonRef.current ?? undefined, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, alignPoints: alignPoints, fullscreenOnMobile: fullscreenOnMobile, isViewMode: isViewMode, dialogId: dialogId,
|
|
46
48
|
// Mobile header is the same visual button but dismisses via handleCancel
|
|
47
49
|
// so host onCancel cleanup (e.g. closing the configuration panel,
|
|
48
50
|
// clearing autoOpen) runs — toggleDropdown would skip that path.
|
|
@@ -11,5 +11,11 @@ export interface IMeasureValueFilterDropdownButtonProps {
|
|
|
11
11
|
buttonTitleExtension?: ReactNode;
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
onClick: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Id of the dropdown dialog this button opens. Wired to `aria-controls` while open.
|
|
16
|
+
*
|
|
17
|
+
* @beta
|
|
18
|
+
*/
|
|
19
|
+
dropdownId?: string;
|
|
14
20
|
}
|
|
15
|
-
export declare function DropdownButton({ isActive, buttonTitle, onClick }: IMeasureValueFilterDropdownButtonProps): ReactElement;
|
|
21
|
+
export declare function DropdownButton({ isActive, buttonTitle, onClick, disabled, dropdownId }: IMeasureValueFilterDropdownButtonProps): ReactElement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import cx from "classnames";
|
|
3
|
-
export function DropdownButton({ isActive, buttonTitle, onClick, }) {
|
|
3
|
+
export function DropdownButton({ isActive, buttonTitle, onClick, disabled, dropdownId, }) {
|
|
4
4
|
const className = cx("gd-mvf-dropdown-button", "s-mvf-dropdown-button", "gd-button", "gd-button-secondary", "button-dropdown", "gd-icon-right", { "gd-icon-navigateup": isActive, "gd-icon-navigatedown": !isActive });
|
|
5
|
-
return (_jsx("button", { className: className, onClick: onClick, children: buttonTitle }));
|
|
5
|
+
return (_jsx("button", { className: className, onClick: onClick, disabled: disabled, "aria-haspopup": "dialog", "aria-expanded": isActive, "aria-controls": isActive ? dropdownId : undefined, children: buttonTitle }));
|
|
6
6
|
}
|
|
@@ -16,6 +16,13 @@ export interface IMeasureValueFilterDropdownProps extends IMeasureValueFilterCom
|
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
18
|
mobileHeader?: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Stable id assigned to the dropdown dialog so the trigger button can reference it via
|
|
21
|
+
* `aria-controls`.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
dialogId?: string;
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
21
28
|
* @beta
|
|
@@ -21,7 +21,7 @@ const getTreatNullAsZeroValue = (filter, treatNullAsZeroDefaultValue, enableMult
|
|
|
21
21
|
/**
|
|
22
22
|
* @beta
|
|
23
23
|
*/
|
|
24
|
-
export const MeasureValueFilterDropdown = memo(function MeasureValueFilterDropdown({ filter, onCancel, onApply, onChange, withoutApply, BodyComponent, DropdownActionsComponent, measureIdentifier, measureTitle, usePercentage, warningMessage, locale, anchorEl, separators, format, useShortFormat, displayTreatNullAsZeroOption = false, treatNullAsZeroDefaultValue = false, enableOperatorSelection = true, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, enableMultipleConditions = false, enableRankingWithMvf, loadMetricDetails, isHeaderEnabled, alignPoints, fullscreenOnMobile, mobileHeader, }) {
|
|
24
|
+
export const MeasureValueFilterDropdown = memo(function MeasureValueFilterDropdown({ filter, onCancel, onApply, onChange, withoutApply, BodyComponent, DropdownActionsComponent, measureIdentifier, measureTitle, usePercentage, warningMessage, locale, anchorEl, separators, format, useShortFormat, displayTreatNullAsZeroOption = false, treatNullAsZeroDefaultValue = false, enableOperatorSelection = true, dimensionality, insightDimensionality, isDimensionalityEnabled, isFilterSummaryEnabled, showSimplifiedSummary, catalogDimensionality, loadCatalogDimensionality, onDimensionalityChange, isLoadingCatalogDimensionality, enableMultipleConditions = false, enableRankingWithMvf, loadMetricDetails, isHeaderEnabled, alignPoints, fullscreenOnMobile, mobileHeader, dialogId, isViewMode, }) {
|
|
25
25
|
const applyOnResult = filter?.measureValueFilter.applyOnResult;
|
|
26
26
|
const buildFilter = useCallback((conditions, newDimensionality, applyOnResult) => {
|
|
27
27
|
const effectiveConditions = enableMultipleConditions ? conditions : conditions?.slice(0, 1);
|
|
@@ -79,5 +79,5 @@ export const MeasureValueFilterDropdown = memo(function MeasureValueFilterDropdo
|
|
|
79
79
|
const handleChange = useCallback((conditions, newDimensionality, applyOnResult) => {
|
|
80
80
|
onChange?.(buildFilter(conditions, newDimensionality, applyOnResult));
|
|
81
81
|
}, [onChange, buildFilter]);
|
|
82
|
-
return (_jsx(Dropdown, { onApply: handleApply, onChange: onChange ? handleChange : undefined, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: onCancel, operator: (filter && measureValueFilterOperator(filter)) || null, conditions: getConditionsFromFilter(filter, enableMultipleConditions), usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, anchorEl: anchorEl, separators: separators, format: format, useShortFormat: useShortFormat, measureTitle: measureTitle, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroValue: getTreatNullAsZeroValue(filter, treatNullAsZeroDefaultValue, enableMultipleConditions), enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableMultipleConditions: enableMultipleConditions, enableRankingWithMvf: enableRankingWithMvf, applyOnResult: applyOnResult, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, alignPoints: alignPoints, fullscreenOnMobile: fullscreenOnMobile, mobileHeader: mobileHeader }));
|
|
82
|
+
return (_jsx(Dropdown, { onApply: handleApply, onChange: onChange ? handleChange : undefined, withoutApply: withoutApply, BodyComponent: BodyComponent, DropdownActionsComponent: DropdownActionsComponent, onCancel: onCancel, operator: (filter && measureValueFilterOperator(filter)) || null, conditions: getConditionsFromFilter(filter, enableMultipleConditions), usePercentage: usePercentage, warningMessage: warningMessage, locale: locale, anchorEl: anchorEl, separators: separators, format: format, useShortFormat: useShortFormat, measureTitle: measureTitle, displayTreatNullAsZeroOption: displayTreatNullAsZeroOption, treatNullAsZeroValue: getTreatNullAsZeroValue(filter, treatNullAsZeroDefaultValue, enableMultipleConditions), enableOperatorSelection: enableOperatorSelection, dimensionality: dimensionality, insightDimensionality: insightDimensionality, isDimensionalityEnabled: isDimensionalityEnabled, isFilterSummaryEnabled: isFilterSummaryEnabled, showSimplifiedSummary: showSimplifiedSummary, catalogDimensionality: catalogDimensionality, loadCatalogDimensionality: loadCatalogDimensionality, onDimensionalityChange: onDimensionalityChange, isLoadingCatalogDimensionality: isLoadingCatalogDimensionality, enableMultipleConditions: enableMultipleConditions, enableRankingWithMvf: enableRankingWithMvf, applyOnResult: applyOnResult, loadMetricDetails: loadMetricDetails, isHeaderEnabled: isHeaderEnabled, alignPoints: alignPoints, fullscreenOnMobile: fullscreenOnMobile, mobileHeader: mobileHeader, dialogId: dialogId, isViewMode: isViewMode }));
|
|
83
83
|
});
|
|
@@ -4,25 +4,35 @@ import { memo, useState } from "react";
|
|
|
4
4
|
import cx from "classnames";
|
|
5
5
|
import { capitalize } from "lodash-es";
|
|
6
6
|
import { useIntl } from "react-intl";
|
|
7
|
-
import {
|
|
7
|
+
import { DropdownButton, useId } from "@gooddata/sdk-ui-kit";
|
|
8
8
|
import { simplifyText } from "@gooddata/util";
|
|
9
9
|
import { getOperatorIcon, getOperatorTranslationKey } from "./helpers/measureValueFilterOperator.js";
|
|
10
10
|
import { OperatorDropdownBody } from "./OperatorDropdownBody.js";
|
|
11
11
|
export const OperatorDropdown = memo(function OperatorDropdown(props) {
|
|
12
12
|
const intl = useIntl();
|
|
13
13
|
const [opened, setOpened] = useState(false);
|
|
14
|
+
const id = useId();
|
|
15
|
+
const buttonId = `mvf-operator-button-${id}`;
|
|
16
|
+
const listboxId = `mvf-operator-listbox-${id}`;
|
|
17
|
+
const labelId = `mvf-operator-label-${id}`;
|
|
14
18
|
const renderDropdownButton = () => {
|
|
15
19
|
const { operator, isDisabled } = props;
|
|
16
20
|
const operatorTranslationKey = getOperatorTranslationKey(operator);
|
|
17
21
|
const title = capitalize(operatorTranslationKey === undefined
|
|
18
22
|
? operator
|
|
19
23
|
: intl.formatMessage({ id: operatorTranslationKey }));
|
|
20
|
-
const buttonClasses = cx("gd-mvf-operator-dropdown-button", "s-mvf-operator-dropdown-button", `s-mvf-operator-dropdown-button-${simplifyText(operator)}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const buttonClasses = cx("gd-mvf-operator-dropdown-button", "s-mvf-operator-dropdown-button", `s-mvf-operator-dropdown-button-${simplifyText(operator)}`);
|
|
25
|
+
return (_jsx(DropdownButton, { id: buttonId, title: title, value: title, className: buttonClasses, onClick: handleOperatorDropdownButtonClick, iconLeft: `gd-icon-${getOperatorIcon(operator)}`, disabled: isDisabled, isOpen: opened, dropdownId: listboxId, accessibilityConfig: {
|
|
26
|
+
// A plain button that toggles a listbox popup. Using role="button" (rather
|
|
27
|
+
// than the DropdownButton default of "combobox") avoids the WCAG 4.1.2
|
|
28
|
+
// requirement that a combobox always expose aria-controls — our listbox only
|
|
29
|
+
// exists in the DOM while open.
|
|
30
|
+
role: "button",
|
|
31
|
+
popupType: "listbox",
|
|
32
|
+
// Compose the accessible name from the visible "Condition" label and the
|
|
33
|
+
// current operator value rendered inside the button.
|
|
34
|
+
ariaLabelledBy: `${labelId} ${buttonId}`,
|
|
35
|
+
} }));
|
|
26
36
|
};
|
|
27
37
|
const handleOperatorSelected = (operator) => {
|
|
28
38
|
closeOperatorDropdown();
|
|
@@ -32,5 +42,5 @@ export const OperatorDropdown = memo(function OperatorDropdown(props) {
|
|
|
32
42
|
const handleOperatorDropdownButtonClick = () => setOpened((state) => !state);
|
|
33
43
|
return (_jsxs(_Fragment, { children: [
|
|
34
44
|
_jsxs("div", { className: "gd-mvf-operator-dropdown", "data-testid": "mvf-operator-section", children: [
|
|
35
|
-
_jsx("div", { className: "gd-mvf-operator-dropdown-label", children: intl.formatMessage({ id: "mvf.condition" }) }), renderDropdownButton()] }), opened ? (_jsx(OperatorDropdownBody, { alignTo:
|
|
45
|
+
_jsx("div", { className: "gd-mvf-operator-dropdown-label", id: labelId, children: intl.formatMessage({ id: "mvf.condition" }) }), renderDropdownButton()] }), opened ? (_jsx(OperatorDropdownBody, { alignTo: `#${buttonId}`, onSelect: handleOperatorSelected, selectedOperator: props.operator, onClose: closeOperatorDropdown, listboxId: listboxId, labelId: labelId, isAllOperatorDisabled: props.isAllOperatorDisabled, isMobile: props.isMobile, isViewMode: props.isViewMode })) : null] }));
|
|
36
46
|
});
|
|
@@ -4,8 +4,13 @@ interface IOperatorDropdownBodyProps {
|
|
|
4
4
|
onSelect: (operator: MeasureValueFilterOperator) => void;
|
|
5
5
|
onClose: () => void;
|
|
6
6
|
alignTo: string;
|
|
7
|
+
/** id of the listbox, referenced by the trigger button's aria-controls. */
|
|
8
|
+
listboxId: string;
|
|
9
|
+
/** id of the visible "Condition" label, used as the listbox accessible name. */
|
|
10
|
+
labelId?: string;
|
|
7
11
|
isAllOperatorDisabled?: boolean;
|
|
8
12
|
isMobile?: boolean;
|
|
13
|
+
isViewMode?: boolean;
|
|
9
14
|
}
|
|
10
15
|
export declare const OperatorDropdownBody: import("react").NamedExoticComponent<IOperatorDropdownBodyProps>;
|
|
11
16
|
export {};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2019-2026 GoodData Corporation
|
|
3
|
-
import {
|
|
3
|
+
import { memo, useMemo } from "react";
|
|
4
4
|
import cx from "classnames";
|
|
5
5
|
import { capitalize } from "lodash-es";
|
|
6
6
|
import { defineMessages, useIntl } from "react-intl";
|
|
7
|
-
import { FullScreenOverlay, Overlay,
|
|
7
|
+
import { Bubble, BubbleHoverTrigger, FullScreenOverlay, Overlay, SingleSelectListItem, UiFocusManager, UiListbox, } from "@gooddata/sdk-ui-kit";
|
|
8
|
+
import { simplifyText } from "@gooddata/util";
|
|
8
9
|
import { MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS } from "./constants.js";
|
|
9
|
-
import { getOperatorTranslationKey } from "./helpers/measureValueFilterOperator.js";
|
|
10
|
-
import { OperatorDropdownItem } from "./OperatorDropdownItem.js";
|
|
10
|
+
import { getOperatorIcon, getOperatorTranslationKey } from "./helpers/measureValueFilterOperator.js";
|
|
11
11
|
const MOBILE_DROPDOWN_ALIGN_POINTS = [{ align: "tl tl" }];
|
|
12
|
-
|
|
12
|
+
const DISABLED_BUBBLE_ALIGN_POINTS = [{ align: "cr cl" }, { align: "cl cr" }];
|
|
13
|
+
const DESKTOP_LISTBOX_MAX_HEIGHT = 350;
|
|
14
|
+
// Operators grouped as they appear in the picker; a separator is rendered between groups
|
|
13
15
|
// (desktop only — mobile rows already carry a bottom divider).
|
|
14
16
|
const OPERATOR_GROUPS = [
|
|
15
17
|
["ALL"],
|
|
@@ -30,7 +32,29 @@ const OPERATOR_BUBBLE_MESSAGES = {
|
|
|
30
32
|
BETWEEN: bubbleMessages.BETWEEN,
|
|
31
33
|
NOT_BETWEEN: bubbleMessages.NOT_BETWEEN,
|
|
32
34
|
};
|
|
33
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Renders a single operator as an accessible listbox option (role="option" is supplied by
|
|
37
|
+
* the enclosing {@link UiListbox}). Preserves the legacy `s-mvf-operator-*` hooks, the operator
|
|
38
|
+
* icon, the explanatory bubble for range operators and the disabled-state tooltip. The icon and
|
|
39
|
+
* info bubble are handled by SingleSelectListItem's built-in renderers (which also apply
|
|
40
|
+
* `aria-hidden` to the decorative icon).
|
|
41
|
+
*/
|
|
42
|
+
function OperatorListItem({ item, isSelected, isFocused, onSelect, }) {
|
|
43
|
+
const { operator, iconClass, bubbleText, disabledTooltip } = item.data;
|
|
44
|
+
const isDisabled = !!item.isDisabled;
|
|
45
|
+
const listItem = (_jsx(SingleSelectListItem, { className: cx("gd-list-item-shortened", `s-mvf-operator-${simplifyText(operator)}`, {
|
|
46
|
+
"is-disabled": isDisabled,
|
|
47
|
+
}), title: item.stringTitle, icon: iconClass, info: bubbleText, isSelected: isSelected, isFocused: isFocused, onClick: isDisabled ? undefined : onSelect }));
|
|
48
|
+
if (isDisabled && disabledTooltip) {
|
|
49
|
+
return (_jsxs(BubbleHoverTrigger, { tagName: "div", showDelay: 400, hideDelay: 200, children: [listItem, _jsx(Bubble, { className: "bubble-primary", alignPoints: DISABLED_BUBBLE_ALIGN_POINTS, children: disabledTooltip })
|
|
50
|
+
] }));
|
|
51
|
+
}
|
|
52
|
+
return listItem;
|
|
53
|
+
}
|
|
54
|
+
function OperatorSeparatorItem() {
|
|
55
|
+
return _jsx(SingleSelectListItem, { type: "separator", accessibilityConfig: { role: "separator" } });
|
|
56
|
+
}
|
|
57
|
+
export const OperatorDropdownBody = memo(function OperatorDropdownBody({ onSelect, onClose, selectedOperator, alignTo, listboxId, labelId, isAllOperatorDisabled = false, isMobile = false, isViewMode = false, }) {
|
|
34
58
|
const intl = useIntl();
|
|
35
59
|
const allOperatorDisabledTooltip = isAllOperatorDisabled
|
|
36
60
|
? intl.formatMessage({ id: "mvf.operator.all.disabled.tooltip" })
|
|
@@ -39,18 +63,59 @@ export const OperatorDropdownBody = memo(function OperatorDropdownBody({ onSelec
|
|
|
39
63
|
const selectedOperatorTitle = capitalize(selectedOperatorTranslationKey === undefined
|
|
40
64
|
? selectedOperator
|
|
41
65
|
: intl.formatMessage({ id: selectedOperatorTranslationKey }));
|
|
42
|
-
const items = (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
const items = useMemo(() => {
|
|
67
|
+
const result = [];
|
|
68
|
+
OPERATOR_GROUPS.forEach((group, groupIdx) => {
|
|
69
|
+
if (groupIdx > 0 && !isMobile) {
|
|
70
|
+
result.push({
|
|
71
|
+
type: "static",
|
|
72
|
+
id: `mvf-operator-separator-${groupIdx}`,
|
|
73
|
+
data: null,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
group.forEach((operator) => {
|
|
77
|
+
const translationKey = getOperatorTranslationKey(operator);
|
|
78
|
+
const title = translationKey === undefined ? operator : intl.formatMessage({ id: translationKey });
|
|
79
|
+
const bubbleMessage = OPERATOR_BUBBLE_MESSAGES[operator];
|
|
80
|
+
const isDisabled = operator === "ALL" ? isAllOperatorDisabled : false;
|
|
81
|
+
result.push({
|
|
82
|
+
type: "interactive",
|
|
83
|
+
id: operator,
|
|
84
|
+
stringTitle: capitalize(title),
|
|
85
|
+
isDisabled,
|
|
86
|
+
data: {
|
|
87
|
+
operator,
|
|
88
|
+
iconClass: isMobile ? undefined : `gd-icon-${getOperatorIcon(operator)}`,
|
|
89
|
+
bubbleText: bubbleMessage && !isMobile && !isViewMode
|
|
90
|
+
? intl.formatMessage(bubbleMessage)
|
|
91
|
+
: undefined,
|
|
92
|
+
disabledTooltip: isDisabled ? allOperatorDisabledTooltip : undefined,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
return result;
|
|
98
|
+
}, [allOperatorDisabledTooltip, intl, isAllOperatorDisabled, isMobile, isViewMode]);
|
|
99
|
+
const ariaAttributes = {
|
|
100
|
+
id: listboxId,
|
|
101
|
+
"aria-labelledby": labelId,
|
|
102
|
+
};
|
|
103
|
+
const body = (
|
|
104
|
+
// Focus moves into the listbox on open and returns to the trigger on close, while Tab
|
|
105
|
+
// stays inside the popup — matching the listbox APG pattern used by the text filter.
|
|
106
|
+
_jsx(UiFocusManager, { enableFocusTrap: true, enableAutofocus: true, enableReturnFocusOnUnmount: true, children: _jsx("div", { className: cx(MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS, "s-mvf-operator-dropdown-body", { "gd-is-mobile": isMobile }), children: _jsx(UiListbox, { shouldKeyboardActionPreventDefault: true, shouldKeyboardActionStopPropagation: true, isDisabledFocusable: true, maxHeight: isMobile ? undefined : DESKTOP_LISTBOX_MAX_HEIGHT, dataTestId: "mvf-operator-dropdown-body",
|
|
107
|
+
// Put the per-operator testid on the role="option" <li> (which carries
|
|
108
|
+
// aria-disabled) rather than the inner item, so disabled-state assertions read
|
|
109
|
+
// the attribute off the element that actually exposes it.
|
|
110
|
+
itemDataTestId: (item) => item.type === "interactive"
|
|
111
|
+
? `mvf-operator-${simplifyText(item.data.operator)}`
|
|
112
|
+
: undefined, items: items, selectedItemId: selectedOperator, onSelect: (item) => onSelect(item.data.operator), onClose: onClose, ariaAttributes: ariaAttributes, InteractiveItemComponent: OperatorListItem, StaticItemComponent: OperatorSeparatorItem }) }) }));
|
|
48
113
|
if (isMobile) {
|
|
49
114
|
return (_jsx(FullScreenOverlay, { alignTo: "body", alignPoints: MOBILE_DROPDOWN_ALIGN_POINTS, onClose: onClose, children: _jsxs("div", { className: "gd-mobile-dropdown-overlay overlay gd-flex-row-container gd-mvf-mobile-dropdown", children: [
|
|
50
115
|
_jsx("div", { className: "gd-mobile-dropdown-header gd-flex-item gd-mvf-mobile-dropdown-header", children: _jsxs("button", { type: "button", className: "gd-mvf-operator-mobile-header s-mvf-operator-mobile-header", onClick: onClose, children: [
|
|
51
116
|
_jsx("span", { className: "gd-mvf-operator-mobile-header__label", children: intl.formatMessage({ id: "mvf.condition" }) }), _jsx("span", { className: "gd-mvf-operator-mobile-header__value", children: selectedOperatorTitle }), _jsx("span", { className: "gd-mvf-operator-mobile-header__chevron gd-icon-navigateup" })
|
|
52
|
-
] }) }), _jsx("div", { className: "gd-mobile-dropdown-content gd-flex-item-stretch gd-mvf-mobile-dropdown-content", children:
|
|
117
|
+
] }) }), _jsx("div", { className: "gd-mobile-dropdown-content gd-flex-item-stretch gd-mvf-mobile-dropdown-content", children: body })
|
|
53
118
|
] }) }));
|
|
54
119
|
}
|
|
55
|
-
return (_jsx(Overlay, { closeOnOutsideClick: true, alignTo: alignTo, alignPoints: [{ align: "bl tl" }], onClose: onClose, children: _jsx("div", { className: "gd-dropdown overlay", children:
|
|
120
|
+
return (_jsx(Overlay, { closeOnOutsideClick: true, alignTo: alignTo, alignPoints: [{ align: "bl tl" }], onClose: onClose, children: _jsx("div", { className: "gd-dropdown overlay", children: body }) }));
|
|
56
121
|
});
|
|
@@ -3,6 +3,11 @@ import { type MeasureValueFilterOperator } from "./types.js";
|
|
|
3
3
|
import { type IDimensionalityItem } from "./typings.js";
|
|
4
4
|
interface IPreviewSectionProps {
|
|
5
5
|
measureTitle?: string;
|
|
6
|
+
/**
|
|
7
|
+
* When true, renders a simplified summary: the "Preview:" header and the metric title are
|
|
8
|
+
* omitted, so only the conditions (and dimensionality, if any) are shown.
|
|
9
|
+
*/
|
|
10
|
+
showSimplifiedSummary?: boolean;
|
|
6
11
|
usePercentage?: boolean;
|
|
7
12
|
separators?: ISeparators;
|
|
8
13
|
format?: string;
|