@gooddata/sdk-ui-filters 11.37.0-alpha.1 → 11.37.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.
@@ -4,7 +4,7 @@ import { Bubble, BubbleHoverTrigger, IconBoldHyperlink } from "@gooddata/sdk-ui-
4
4
  * @internal
5
5
  */
6
6
  export function AttributeFilterDependencyTooltip({ tooltipContent, ariaLabel, }) {
7
- return (_jsx("span", { className: "gd-attribute-filter-dropdown-button-icon-tooltip", "aria-label": ariaLabel, children: _jsxs(BubbleHoverTrigger, { children: [
8
- _jsx(IconBoldHyperlink, { width: 12, height: 16 }), _jsx(Bubble, { arrowOffsets: { "bc tl": [-12, 9], "bc tr": [12, 9] }, alignPoints: [{ align: "bc tl" }, { align: "bc tr" }], children: _jsx("div", { className: "gd-attribute-filter-dropdown-button-icon-tooltip__content", children: tooltipContent }) })
7
+ return (_jsx("span", { className: "gd-attribute-filter-dropdown-button-icon-tooltip", children: _jsxs(BubbleHoverTrigger, { children: [
8
+ _jsx(IconBoldHyperlink, { width: 12, height: 16, ariaLabel: ariaLabel }), _jsx(Bubble, { arrowOffsets: { "bc tl": [-12, 9], "bc tr": [12, 9] }, alignPoints: [{ align: "bc tl" }, { align: "bc tr" }], children: _jsx("div", { className: "gd-attribute-filter-dropdown-button-icon-tooltip__content", children: tooltipContent }) })
9
9
  ] }) }));
10
10
  }
@@ -1,6 +1,7 @@
1
1
  import { type ComponentType, type ReactElement, type ReactNode } from "react";
2
2
  import { type IRenderDropdownListItemProps } from "@gooddata/sdk-ui-kit";
3
3
  import type { IAttributeFilterProps } from "../AttributeFilter/AttributeFilter.js";
4
+ import { type IMeasureValueFilterProps } from "../MeasureValueFilter/MeasureValueFilter.js";
4
5
  /**
5
6
  * @public
6
7
  */
@@ -8,9 +9,17 @@ export interface IFilterGroupProps<P> {
8
9
  title: string;
9
10
  filters: P[];
10
11
  getFilterIdentifier: (filter: P) => string;
11
- hasSelectedElements: (filter: P) => boolean;
12
+ isFilterActive: (filter: P) => boolean;
13
+ /**
14
+ * @deprecated
15
+ * Use isFilterActive instead.
16
+ */
17
+ hasSelectedElements?: (filter: P) => boolean;
12
18
  getTitleExtension?: (filterIdentifier: string, filterTitle?: string) => ReactNode;
13
- renderFilter: (filter: P, AttributeFilterComponent?: ComponentType<IAttributeFilterProps>) => ReactElement;
19
+ /**
20
+ * @beta
21
+ */
22
+ renderFilter: (filter: P, AttributeFilterComponent?: ComponentType<IAttributeFilterProps>, MeasureValueFilterComponent?: ComponentType<IMeasureValueFilterProps>) => ReactElement;
14
23
  }
15
24
  /**
16
25
  * @internal
@@ -9,6 +9,8 @@ import { AttributeFilterButton } from "../AttributeFilter/AttributeFilterButton.
9
9
  import { AttributeFilterDropdownButton, } from "../AttributeFilter/Components/DropdownButton/AttributeFilterDropdownButton.js";
10
10
  import { AttributeFilterElementsSearchBar } from "../AttributeFilter/Components/ElementsSelect/AttributeFilterElementsSearchBar.js";
11
11
  import { ATTRIBUTE_DISPLAY_FORM_DROPDOWN_BODY_CLASS, ATTRIBUTE_FILTER_DROPDOWN_BODY_CLASS, ATTRIBUTE_FILTER_DROPDOWN_BUBBLE_CLASS, } from "../AttributeFilter/constants.js";
12
+ import { MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS, MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS, MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS, } from "../MeasureValueFilter/constants.js";
13
+ import { MeasureValueFilter, } from "../MeasureValueFilter/MeasureValueFilter.js";
12
14
  import { FilterButtonCustomIcon } from "../shared/components/internal/FilterButtonCustomIcon.js";
13
15
  import { useFilterGroupStatus } from "./useFilterGroupStatus.js";
14
16
  const GROUP_ALIGN_POINTS = [
@@ -28,9 +30,12 @@ const ITEM_ALIGN_POINTS = [
28
30
  * Closing the group dropdown must be prevented when interacting with the nested filter dropdown content.
29
31
  */
30
32
  const IGNORE_CLICKS_ON_BY_CLASS = [
31
- ATTRIBUTE_FILTER_DROPDOWN_BODY_CLASS,
32
- ATTRIBUTE_DISPLAY_FORM_DROPDOWN_BODY_CLASS,
33
- ATTRIBUTE_FILTER_DROPDOWN_BUBBLE_CLASS,
33
+ `.${ATTRIBUTE_FILTER_DROPDOWN_BODY_CLASS}`,
34
+ `.${ATTRIBUTE_DISPLAY_FORM_DROPDOWN_BODY_CLASS}`,
35
+ `.${ATTRIBUTE_FILTER_DROPDOWN_BUBBLE_CLASS}`,
36
+ `.${MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS}`,
37
+ `.${MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS}`,
38
+ `.${MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS}`,
34
39
  ];
35
40
  function isEditableElement(target) {
36
41
  return (target instanceof HTMLElement &&
@@ -42,7 +47,8 @@ function isEditableElement(target) {
42
47
  * @public
43
48
  */
44
49
  export function FilterGroup(props) {
45
- const { title, filters, getFilterIdentifier, hasSelectedElements, getTitleExtension, renderFilter } = props;
50
+ const { title, filters, getFilterIdentifier, isFilterActive: propsIsFilterActive, hasSelectedElements, getTitleExtension, renderFilter, } = props;
51
+ const isFilterActive = propsIsFilterActive ?? hasSelectedElements;
46
52
  const intl = useIntl();
47
53
  const [isOpen, setIsOpen] = useState(false);
48
54
  const filterItemRefs = useRef(new Map());
@@ -55,7 +61,7 @@ export function FilterGroup(props) {
55
61
  if (isAnyFilterError) {
56
62
  return intl.formatMessage({ id: "gs.list.notAvailableAbbreviation" });
57
63
  }
58
- const activeFilters = filters.filter((filter) => hasSelectedElements(filter));
64
+ const activeFilters = filters.filter((filter) => isFilterActive(filter));
59
65
  const activeFiltersCount = activeFilters.length;
60
66
  if (activeFiltersCount === 0) {
61
67
  return intl.formatMessage({ id: "gs.list.allAndCount" }, { count: filters.length });
@@ -63,12 +69,12 @@ export function FilterGroup(props) {
63
69
  else {
64
70
  return `(${activeFiltersCount}/${availableFilterIdentifiers.length})`;
65
71
  }
66
- }, [filters, isAnyFilterError, hasSelectedElements, intl, availableFilterIdentifiers]);
72
+ }, [filters, isAnyFilterError, isFilterActive, intl, availableFilterIdentifiers]);
67
73
  const { selectedItemsCount, totalItemsCount } = useMemo(() => {
68
74
  if (isAnyFilterError) {
69
75
  return {};
70
76
  }
71
- const activeFilters = filters.filter((filter) => hasSelectedElements(filter));
77
+ const activeFilters = filters.filter((filter) => isFilterActive(filter));
72
78
  const activeFiltersCount = activeFilters.length;
73
79
  if (activeFiltersCount === 0) {
74
80
  return { totalItemsCount: filters.length };
@@ -76,7 +82,7 @@ export function FilterGroup(props) {
76
82
  else {
77
83
  return { selectedItemsCount: activeFiltersCount, totalItemsCount: filters.length };
78
84
  }
79
- }, [filters, isAnyFilterError, hasSelectedElements]);
85
+ }, [filters, isAnyFilterError, isFilterActive]);
80
86
  const errorHandler = useCallback((filterIdentifier) => (error) => {
81
87
  setFilterError(filterIdentifier, error);
82
88
  }, [setFilterError]);
@@ -140,11 +146,37 @@ export function FilterGroup(props) {
140
146
  });
141
147
  return result;
142
148
  }, [availableFilterIdentifiers, getTitleExtension, errorHandler, initLoadingChangedHandler]);
149
+ const measureValueFilterComponentsByIdentifier = useMemo(() => {
150
+ const result = new Map();
151
+ availableFilterIdentifiers.forEach((filterIdentifier) => {
152
+ if (result.has(filterIdentifier)) {
153
+ return;
154
+ }
155
+ function MeasureValueFilterComponent(measureValueFilterProps) {
156
+ const setFilterItemRef = useCallback((element) => {
157
+ filterItemRefs.current.set(filterIdentifier, element);
158
+ }, []);
159
+ const DropdownButtonComponent = useCallback(function DropdownButtonComponent({ buttonTitle, buttonSubtitle, buttonTitleExtension, disabled, isActive, onClick, }) {
160
+ const titleExtension = getTitleExtension?.(filterIdentifier, buttonTitle);
161
+ return (_jsx(FilterGroupItem, { title: buttonTitle, subtitle: buttonSubtitle ?? undefined, isOpen: isActive, isLoaded: true, disabled: disabled, titleExtension: _jsxs(_Fragment, { children: [buttonTitleExtension, titleExtension] }), onClick: onClick, buttonRef: setFilterItemRef }));
162
+ }, [setFilterItemRef]);
163
+ return (_jsx(MeasureValueFilter, { ...measureValueFilterProps, alignPoints: ITEM_ALIGN_POINTS, DropdownButtonComponent: measureValueFilterProps.DropdownButtonComponent ?? DropdownButtonComponent }));
164
+ }
165
+ result.set(filterIdentifier, MeasureValueFilterComponent);
166
+ });
167
+ return result;
168
+ }, [availableFilterIdentifiers, getTitleExtension]);
143
169
  const renderItem = useCallback(({ item }) => {
144
170
  const identifier = getFilterIdentifier(item);
145
171
  const AttributeFilterComponent = attributeFilterComponentsByIdentifier.get(identifier);
146
- return renderFilter(item, AttributeFilterComponent);
147
- }, [attributeFilterComponentsByIdentifier, getFilterIdentifier, renderFilter]);
172
+ const MeasureValueFilterComponent = measureValueFilterComponentsByIdentifier.get(identifier);
173
+ return renderFilter(item, AttributeFilterComponent, MeasureValueFilterComponent);
174
+ }, [
175
+ attributeFilterComponentsByIdentifier,
176
+ measureValueFilterComponentsByIdentifier,
177
+ getFilterIdentifier,
178
+ renderFilter,
179
+ ]);
148
180
  const handleKeyDownCapture = useCallback((e) => {
149
181
  if (isSpaceKey(e) && isEditableElement(e.target)) {
150
182
  e.stopPropagation();
@@ -1,5 +1,6 @@
1
1
  import { type IMeasureMetadataObject, type MeasureValueFilterCondition, type ObjRefInScope } from "@gooddata/sdk-model";
2
2
  import { type ISeparators } from "@gooddata/sdk-ui";
3
+ import { type IAlignPoint } from "@gooddata/sdk-ui-kit";
3
4
  import { type MeasureValueFilterOperator } from "./types.js";
4
5
  import { type IDimensionalityItem, type IMeasureValueFilterCustomComponentProps, type IMeasureValueFilterDropdownCallback, type WarningMessage } from "./typings.js";
5
6
  interface IDropdownProps extends IMeasureValueFilterCustomComponentProps {
@@ -33,6 +34,7 @@ interface IDropdownProps extends IMeasureValueFilterCustomComponentProps {
33
34
  isLoadingCatalogDimensionality?: boolean;
34
35
  loadMetricDetails?: () => Promise<IMeasureMetadataObject | undefined>;
35
36
  isHeaderEnabled?: boolean;
37
+ alignPoints?: IAlignPoint[];
36
38
  }
37
39
  export declare function Dropdown(props: IDropdownProps): import("react/jsx-runtime").JSX.Element;
38
40
  export {};
@@ -12,12 +12,12 @@ const alignPoints = ["bl tl", "tl bl", "br tr", "tr br"];
12
12
  */
13
13
  const DROPDOWN_ALIGNMENTS = alignPoints.map((align) => ({ align, offset: { x: 1, y: 0 } }));
14
14
  const DropdownWithIntl = memo(function DropdownWithIntl(props) {
15
- 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, } = props;
15
+ 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, } = props;
16
16
  const onApply = useCallback((conditions, newDimensionality, applyOnResult) => {
17
17
  onApplyProp(conditions, newDimensionality, applyOnResult);
18
18
  }, [onApplyProp]);
19
19
  const selectedOperator = operator === null ? "ALL" : operator;
20
- return (_jsx(Overlay, { alignTo: anchorEl, alignPoints: DROPDOWN_ALIGNMENTS, closeOnOutsideClick: true, closeOnParentScroll: true, closeOnMouseDrag: true, onClose: onCancel, children: _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 }) }) }));
20
+ return (_jsx(Overlay, { alignTo: anchorEl, alignPoints: alignPoints ?? DROPDOWN_ALIGNMENTS, closeOnOutsideClick: true, closeOnParentScroll: true, closeOnMouseDrag: true, onClose: onCancel, children: _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 }) }) }));
21
21
  });
22
22
  export function Dropdown(props) {
23
23
  return (_jsx(IntlWrapper, { locale: props.locale, children: _jsx(DropdownWithIntl, { ...props }) }));
@@ -7,6 +7,7 @@ import { isComparisonCondition, isComparisonConditionOperator, isRangeCondition,
7
7
  import { IntlWrapper } from "@gooddata/sdk-ui";
8
8
  import { Bubble, BubbleHoverTrigger, UiIconButton } from "@gooddata/sdk-ui-kit";
9
9
  import { ConditionInputSection } from "./ConditionInputSection.js";
10
+ import { MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS } from "./constants.js";
10
11
  import { DimensionalitySection, areDimensionalitySetsEqual } from "./DimensionalitySection.js";
11
12
  import { intervalIncludesZero } from "./helpers/intervalIncludesZero.js";
12
13
  import { MeasureValueFilterDropdownActions } from "./MeasureValueFilterDropdownActions.js";
@@ -538,7 +539,7 @@ export const DropdownBodyWithIntl = memo(function DropdownBodyWithIntl(props) {
538
539
  isCommitPending.current = true;
539
540
  setApplyOnResult(event.target.checked);
540
541
  }, [setApplyOnResult]);
541
- return (_jsxs("div", { className: "gd-mvf-dropdown-body gd-dialog gd-dropdown overlay s-mvf-dropdown-body", "data-testid": "mvf-dropdown-body", children: [props.isHeaderEnabled && props.measureTitle ? (_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", {
542
+ return (_jsxs("div", { className: `${MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS} gd-dialog gd-dropdown overlay s-mvf-dropdown-body`, "data-testid": "mvf-dropdown-body", children: [props.isHeaderEnabled && props.measureTitle ? (_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", {
542
543
  "gd-mvf-condition-section--multi": enableMultipleConditions,
543
544
  }), children: [
544
545
  _jsxs("div", { className: "gd-mvf-condition-header", "data-testid": `mvf-condition-${idx}`, children: [
@@ -1,9 +1,13 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type IMeasureValueFilterCommonProps, type IMeasureValueFilterCustomComponentsProps } from "./typings.js";
2
3
  /**
3
4
  * @beta
4
5
  */
5
6
  export interface IMeasureValueFilterProps extends IMeasureValueFilterCommonProps, IMeasureValueFilterCustomComponentsProps {
6
7
  buttonTitle: string;
8
+ buttonSubtitle?: string;
9
+ buttonTitleExtension?: ReactNode;
10
+ buttonDisabled?: boolean;
7
11
  onCancel?: () => void;
8
12
  /**
9
13
  * When toggled from falsy to truthy, opens the dropdown once. A re-render with
@@ -6,7 +6,7 @@ import { MeasureValueFilterDropdown } from "./MeasureValueFilterDropdown.js";
6
6
  /**
7
7
  * @beta
8
8
  */
9
- export const MeasureValueFilter = memo(function MeasureValueFilter({ onCancel = () => { }, filter, measureIdentifier, buttonTitle, 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, }) {
9
+ 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, }) {
10
10
  const [displayDropdown, setDisplayDropdown] = useState(false);
11
11
  const buttonRef = useRef(null);
12
12
  const autoOpenedRef = useRef(false);
@@ -31,5 +31,5 @@ export const MeasureValueFilter = memo(function MeasureValueFilter({ onCancel =
31
31
  setDisplayDropdown((state) => !state);
32
32
  }, []);
33
33
  return (_jsxs(Fragment, { children: [
34
- _jsx("div", { ref: buttonRef, children: _jsx(DropdownButtonComponent, { onClick: toggleDropdown, isActive: displayDropdown, buttonTitle: buttonTitle }) }), 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 })) : null] }));
34
+ _jsx("div", { ref: buttonRef, children: _jsx(DropdownButtonComponent, { onClick: toggleDropdown, isActive: displayDropdown, buttonTitle: buttonTitle, buttonSubtitle: buttonSubtitle, buttonTitleExtension: buttonTitleExtension, disabled: buttonDisabled }) }), 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 })) : null] }));
35
35
  });
@@ -1,4 +1,4 @@
1
- import { type ReactElement } from "react";
1
+ import { type ReactElement, type ReactNode } from "react";
2
2
  /**
3
3
  * Props passed to the MeasureValueFilter dropdown button (header) component.
4
4
  *
@@ -7,6 +7,9 @@ import { type ReactElement } from "react";
7
7
  export interface IMeasureValueFilterDropdownButtonProps {
8
8
  isActive: boolean;
9
9
  buttonTitle: string;
10
+ buttonSubtitle?: string;
11
+ buttonTitleExtension?: ReactNode;
12
+ disabled?: boolean;
10
13
  onClick: () => void;
11
14
  }
12
15
  export declare function DropdownButton({ isActive, buttonTitle, onClick }: IMeasureValueFilterDropdownButtonProps): ReactElement;
@@ -4,6 +4,7 @@ import { useCallback, useState } from "react";
4
4
  import { FormattedMessage, useIntl } from "react-intl";
5
5
  import { UiIconButton, UiTooltip } from "@gooddata/sdk-ui-kit";
6
6
  import { messages } from "../locales.js";
7
+ import { MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS } from "./constants.js";
7
8
  import { useMeasureValueFilterDetails } from "./useMeasureValueFilterDetails.js";
8
9
  /**
9
10
  * Info bubble showing metric details when the question icon is hovered/focused.
@@ -16,7 +17,7 @@ export function MeasureValueFilterDetailsBubble({ title, requestHandler, }) {
16
17
  const { details, isLoading, error } = useMeasureValueFilterDetails({ isOpen, requestHandler });
17
18
  const handleOpen = useCallback(() => setIsOpen(true), []);
18
19
  const handleClose = useCallback(() => setIsOpen(false), []);
19
- const tooltipContent = (_jsx("div", { className: "gd-mvf-details-bubble s-mvf-details-bubble", children: _jsxs("div", { className: "gd-mvf-details-bubble__content", children: [
20
+ const tooltipContent = (_jsx("div", { className: `${MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS} s-mvf-details-bubble`, children: _jsxs("div", { className: "gd-mvf-details-bubble__content", children: [
20
21
  _jsxs("div", { className: "gd-mvf-details-bubble__section", children: [
21
22
  _jsx("h3", { className: "gd-mvf-details-bubble__title", children: details?.title ?? title }), details?.description ? (_jsx("p", { className: "gd-mvf-details-bubble__description", children: details.description })) : null] }), _jsxs("div", { className: "gd-mvf-details-bubble__section", children: [
22
23
  _jsx("span", { className: "gd-mvf-details-bubble__key", children: _jsx(FormattedMessage, { ...messages["measureValueFilterDetailsType"] }) }), _jsx("span", { className: "gd-mvf-details-bubble__value", children: _jsx(FormattedMessage, { ...messages["measureValueFilterDetailsTypeValue"] }) })
@@ -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, }) {
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, }) {
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 }));
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 }));
83
83
  });
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { memo } from "react";
4
4
  import { useIntl } from "react-intl";
5
5
  import { Overlay, Separator } from "@gooddata/sdk-ui-kit";
6
+ import { MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS } from "./constants.js";
6
7
  import { OperatorDropdownItem } from "./OperatorDropdownItem.js";
7
8
  export const OperatorDropdownBody = memo(function OperatorDropdownBody({ onSelect, onClose, selectedOperator, alignTo, isAllOperatorDisabled = false, }) {
8
9
  const intl = useIntl();
9
10
  const allOperatorDisabledTooltip = isAllOperatorDisabled
10
11
  ? intl.formatMessage({ id: "mvf.operator.all.disabled.tooltip" })
11
12
  : undefined;
12
- return (_jsx(Overlay, { closeOnOutsideClick: true, alignTo: alignTo, alignPoints: [{ align: "bl tl" }], onClose: onClose, children: _jsx("div", { className: "gd-dropdown overlay", children: _jsxs("div", { className: "gd-mvf-operator-dropdown-body s-mvf-operator-dropdown-body", "data-testid": "mvf-operator-dropdown-body", children: [
13
+ return (_jsx(Overlay, { closeOnOutsideClick: true, alignTo: alignTo, alignPoints: [{ align: "bl tl" }], onClose: onClose, children: _jsx("div", { className: "gd-dropdown overlay", children: _jsxs("div", { className: `${MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS} s-mvf-operator-dropdown-body`, "data-testid": "mvf-operator-dropdown-body", children: [
13
14
  _jsx(OperatorDropdownItem, { operator: "ALL", selectedOperator: selectedOperator, onClick: onSelect, isDisabled: isAllOperatorDisabled, disabledTooltip: allOperatorDisabledTooltip }), _jsx(Separator, {}), _jsx(OperatorDropdownItem, { operator: "GREATER_THAN", selectedOperator: selectedOperator, onClick: onSelect }), _jsx(OperatorDropdownItem, { operator: "GREATER_THAN_OR_EQUAL_TO", selectedOperator: selectedOperator, onClick: onSelect }), _jsx(Separator, {}), _jsx(OperatorDropdownItem, { operator: "LESS_THAN", selectedOperator: selectedOperator, onClick: onSelect }), _jsx(OperatorDropdownItem, { operator: "LESS_THAN_OR_EQUAL_TO", selectedOperator: selectedOperator, onClick: onSelect }), _jsx(Separator, {}), _jsx(OperatorDropdownItem, { operator: "BETWEEN", selectedOperator: selectedOperator, onClick: onSelect, bubbleText: intl.formatMessage({ id: "mvf.operator.between.tooltip.bubble" }) }), _jsx(OperatorDropdownItem, { operator: "NOT_BETWEEN", selectedOperator: selectedOperator, onClick: onSelect, bubbleText: intl.formatMessage({ id: "mvf.operator.notBetween.tooltip.bubble" }) }), _jsx(Separator, {}), _jsx(OperatorDropdownItem, { operator: "EQUAL_TO", selectedOperator: selectedOperator, onClick: onSelect }), _jsx(OperatorDropdownItem, { operator: "NOT_EQUAL_TO", selectedOperator: selectedOperator, onClick: onSelect })
14
15
  ] }) }) }));
15
16
  });
@@ -0,0 +1,3 @@
1
+ export declare const MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS = "gd-mvf-dropdown-body";
2
+ export declare const MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS = "gd-mvf-operator-dropdown-body";
3
+ export declare const MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS = "gd-mvf-details-bubble";
@@ -0,0 +1,6 @@
1
+ // (C) 2026 GoodData Corporation
2
+ // Those classes are extracted to constants because they are used in FilterGroup component
3
+ // to prevent group dropdown from closing when clicking on the nested attribute filterdropdown content.
4
+ export const MEASURE_VALUE_FILTER_DROPDOWN_BODY_CLASS = "gd-mvf-dropdown-body";
5
+ export const MEASURE_VALUE_FILTER_OPERATOR_DROPDOWN_BODY_CLASS = "gd-mvf-operator-dropdown-body";
6
+ export const MEASURE_VALUE_FILTER_DETAILS_BUBBLE_CLASS = "gd-mvf-details-bubble";
@@ -1,6 +1,7 @@
1
1
  import { type ComponentType } from "react";
2
2
  import { type IMeasureMetadataObject, type IMeasureValueFilter, type MeasureValueFilterCondition, type ObjRef, type ObjRefInScope } from "@gooddata/sdk-model";
3
3
  import { type ISeparators } from "@gooddata/sdk-ui";
4
+ import { type IAlignPoint } from "@gooddata/sdk-ui-kit";
4
5
  import { type IMeasureValueFilterDropdownButtonProps } from "./MeasureValueFilterButton.js";
5
6
  /**
6
7
  * Type of dimensionality item for display purposes.
@@ -274,6 +275,16 @@ export interface IMeasureValueFilterCommonProps extends IMeasureValueFilterCusto
274
275
  * @beta
275
276
  */
276
277
  isHeaderEnabled?: boolean;
278
+ /**
279
+ * Optional alignment points for the dropdown overlay.
280
+ *
281
+ * @remarks
282
+ * Mirrors the attribute filter alignment hook so hosts can position the MVF dropdown
283
+ * differently in nested surfaces, for example inside a dashboard filter group.
284
+ *
285
+ * @beta
286
+ */
287
+ alignPoints?: IAlignPoint[];
277
288
  }
278
289
  /**
279
290
  * These customization properties allow you to specify custom components that the MeasureValueFilter
@@ -3416,9 +3416,17 @@ export declare interface IFilterGroupProps<P> {
3416
3416
  title: string;
3417
3417
  filters: P[];
3418
3418
  getFilterIdentifier: (filter: P) => string;
3419
- hasSelectedElements: (filter: P) => boolean;
3419
+ isFilterActive: (filter: P) => boolean;
3420
+ /**
3421
+ * @deprecated
3422
+ * Use isFilterActive instead.
3423
+ */
3424
+ hasSelectedElements?: (filter: P) => boolean;
3420
3425
  getTitleExtension?: (filterIdentifier: string, filterTitle?: string) => ReactNode;
3421
- renderFilter: (filter: P, AttributeFilterComponent?: ComponentType<IAttributeFilterProps>) => ReactElement;
3426
+ /**
3427
+ * @beta
3428
+ */
3429
+ renderFilter: (filter: P, AttributeFilterComponent?: ComponentType<IAttributeFilterProps>, MeasureValueFilterComponent?: ComponentType<IMeasureValueFilterProps>) => ReactElement;
3422
3430
  }
3423
3431
 
3424
3432
  /**
@@ -3741,6 +3749,16 @@ export declare interface IMeasureValueFilterCommonProps extends IMeasureValueFil
3741
3749
  * @beta
3742
3750
  */
3743
3751
  isHeaderEnabled?: boolean;
3752
+ /**
3753
+ * Optional alignment points for the dropdown overlay.
3754
+ *
3755
+ * @remarks
3756
+ * Mirrors the attribute filter alignment hook so hosts can position the MVF dropdown
3757
+ * differently in nested surfaces, for example inside a dashboard filter group.
3758
+ *
3759
+ * @beta
3760
+ */
3761
+ alignPoints?: IAlignPoint[];
3744
3762
  }
3745
3763
 
3746
3764
  /**
@@ -3860,6 +3878,9 @@ export declare interface IMeasureValueFilterDropdownActionsProps {
3860
3878
  export declare interface IMeasureValueFilterDropdownButtonProps {
3861
3879
  isActive: boolean;
3862
3880
  buttonTitle: string;
3881
+ buttonSubtitle?: string;
3882
+ buttonTitleExtension?: ReactNode;
3883
+ disabled?: boolean;
3863
3884
  onClick: () => void;
3864
3885
  }
3865
3886
 
@@ -3876,6 +3897,9 @@ export declare interface IMeasureValueFilterDropdownProps extends IMeasureValueF
3876
3897
  */
3877
3898
  export declare interface IMeasureValueFilterProps extends IMeasureValueFilterCommonProps, IMeasureValueFilterCustomComponentsProps {
3878
3899
  buttonTitle: string;
3900
+ buttonSubtitle?: string;
3901
+ buttonTitleExtension?: ReactNode;
3902
+ buttonDisabled?: boolean;
3879
3903
  onCancel?: () => void;
3880
3904
  /**
3881
3905
  * When toggled from falsy to truthy, opens the dropdown once. A re-render with
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-filters",
3
- "version": "11.37.0-alpha.1",
3
+ "version": "11.37.0-alpha.3",
4
4
  "description": "GoodData.UI SDK - Filter Components",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -47,11 +47,11 @@
47
47
  "ts-invariant": "0.10.3",
48
48
  "tslib": "2.8.1",
49
49
  "uuid": "11.1.0",
50
- "@gooddata/sdk-backend-spi": "11.37.0-alpha.1",
51
- "@gooddata/sdk-model": "11.37.0-alpha.1",
52
- "@gooddata/sdk-ui": "11.37.0-alpha.1",
53
- "@gooddata/sdk-ui-kit": "11.37.0-alpha.1",
54
- "@gooddata/util": "11.37.0-alpha.1"
50
+ "@gooddata/sdk-backend-spi": "11.37.0-alpha.3",
51
+ "@gooddata/sdk-model": "11.37.0-alpha.3",
52
+ "@gooddata/sdk-ui-kit": "11.37.0-alpha.3",
53
+ "@gooddata/util": "11.37.0-alpha.3",
54
+ "@gooddata/sdk-ui": "11.37.0-alpha.3"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@microsoft/api-documenter": "^7.17.0",
@@ -93,12 +93,12 @@
93
93
  "typescript": "5.9.3",
94
94
  "vitest": "4.1.0",
95
95
  "vitest-dom": "0.1.1",
96
- "@gooddata/eslint-config": "11.37.0-alpha.1",
97
- "@gooddata/oxlint-config": "11.37.0-alpha.1",
98
- "@gooddata/reference-workspace": "11.37.0-alpha.1",
99
- "@gooddata/sdk-backend-mockingbird": "11.37.0-alpha.1",
100
- "@gooddata/sdk-ui-theme-provider": "11.37.0-alpha.1",
101
- "@gooddata/stylelint-config": "11.37.0-alpha.1"
96
+ "@gooddata/eslint-config": "11.37.0-alpha.3",
97
+ "@gooddata/oxlint-config": "11.37.0-alpha.3",
98
+ "@gooddata/reference-workspace": "11.37.0-alpha.3",
99
+ "@gooddata/sdk-backend-mockingbird": "11.37.0-alpha.3",
100
+ "@gooddata/sdk-ui-theme-provider": "11.37.0-alpha.3",
101
+ "@gooddata/stylelint-config": "11.37.0-alpha.3"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "react": "^18.0.0 || ^19.0.0",