@gooddata/sdk-ui-filters 11.53.0-alpha.1 → 11.53.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/esm/AttributeFilter/Components/TextFilterBody/ArbitraryValuesInput.js +1 -1
  2. package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.d.ts +10 -5
  3. package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.d.ts.map +1 -1
  4. package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.js +22 -26
  5. package/esm/DateFilter/DynamicSelect/DynamicSelect.d.ts +3 -15
  6. package/esm/DateFilter/DynamicSelect/DynamicSelect.d.ts.map +1 -1
  7. package/esm/DateFilter/DynamicSelect/DynamicSelect.js +75 -100
  8. package/esm/DateFilter/Select/ScrollableSelectMenu.d.ts +1 -10
  9. package/esm/DateFilter/Select/ScrollableSelectMenu.d.ts.map +1 -1
  10. package/esm/DateFilter/Select/ScrollableSelectMenu.js +55 -53
  11. package/package.json +12 -12
  12. package/styles/css/attributeFilter.css +11 -0
  13. package/styles/css/attributeFilter.css.map +1 -1
  14. package/styles/css/attributeFilterNext/attributeFilter.css +11 -0
  15. package/styles/css/attributeFilterNext/attributeFilter.css.map +1 -1
  16. package/styles/css/attributeFilterNext/attributeFilterAddons.css +11 -0
  17. package/styles/css/attributeFilterNext/attributeFilterAddons.css.map +1 -1
  18. package/styles/css/attributeFilterNext/attributeFilterDropdownBody.css +11 -0
  19. package/styles/css/attributeFilterNext/attributeFilterDropdownBody.css.map +1 -1
  20. package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css +11 -0
  21. package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css.map +1 -1
  22. package/styles/css/attributeFilterNext/attributeFilterElementsSelect.css +11 -0
  23. package/styles/css/attributeFilterNext/attributeFilterElementsSelect.css.map +1 -1
  24. package/styles/css/attributeFilterNext/attributeFilterEmptyResult.css +11 -0
  25. package/styles/css/attributeFilterNext/attributeFilterEmptyResult.css.map +1 -1
  26. package/styles/css/attributeFilterNext/attributeFilterStatusBar.css +11 -0
  27. package/styles/css/attributeFilterNext/attributeFilterStatusBar.css.map +1 -1
  28. package/styles/css/attributeFilterNext.css +11 -0
  29. package/styles/css/attributeFilterNext.css.map +1 -1
  30. package/styles/css/components/DateFilterCustomPeriodButtons.css +11 -0
  31. package/styles/css/components/DateFilterCustomPeriodButtons.css.map +1 -1
  32. package/styles/css/dateFilter.css +11 -0
  33. package/styles/css/dateFilter.css.map +1 -1
  34. package/styles/css/main.css +11 -0
  35. package/styles/css/main.css.map +1 -1
@@ -52,7 +52,7 @@ export function ArbitraryValuesInput(props) {
52
52
  useEffect(() => {
53
53
  if (previousValuesLengthRef.current === 1 && values.length === 0) {
54
54
  requestAnimationFrame(() => {
55
- inputRef.current?.inputNodeRef?.focus();
55
+ inputRef.current?.focus();
56
56
  });
57
57
  }
58
58
  previousValuesLengthRef.current = values.length;
@@ -1,10 +1,15 @@
1
- import { Component, type InputHTMLAttributes } from "react";
2
- export declare class DateRangePickerInputFieldBody extends Component<InputHTMLAttributes<HTMLInputElement>> {
3
- private inputRef;
1
+ import { type InputHTMLAttributes } from "react";
2
+ /**
3
+ * Imperative API exposed on the DateRangePickerInputFieldBody ref.
4
+ *
5
+ * DayPickerInput refs to this component internally and calls these methods.
6
+ * See https://github.com/gpbl/react-day-picker/issues/748 for more information
7
+ */
8
+ export interface IDateRangePickerInputFieldBody {
4
9
  invokeInputMethod: (key: "blur" | "focus") => void;
5
10
  blur: () => void;
6
11
  focus: () => void;
7
- get value(): string;
8
- render(): import("react/jsx-runtime").JSX.Element;
12
+ readonly value: string;
9
13
  }
14
+ export declare const DateRangePickerInputFieldBody: import("react").ForwardRefExoticComponent<InputHTMLAttributes<HTMLInputElement> & import("react").RefAttributes<IDateRangePickerInputFieldBody>>;
10
15
  //# sourceMappingURL=DateRangePickerInputFieldBody.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DateRangePickerInputFieldBody.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,mBAAmB,EAAa,MAAM,OAAO,CAAC;AAMvE,qBAAa,6BAA8B,SAAQ,SAAS,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAC/F,OAAO,CAAC,QAAQ,CAAiC;IAE1C,iBAAiB,kCAItB;IAEK,IAAI,aAA8C;IAClD,KAAK,aAA+C;IAE3D,IAAW,KAAK,IAAI,MAAM,CAMzB;IAEe,MAAM,4CAYrB;CACJ"}
1
+ {"version":3,"file":"DateRangePickerInputFieldBody.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,mBAAmB,EAA2C,MAAM,OAAO,CAAC;AAI1F;;;;;GAKG;AACH,MAAM,WAAW,8BAA8B;IAC3C,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IACnD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,6BAA6B,kJA4BxC,CAAC"}
@@ -1,28 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // (C) 2007-2025 GoodData Corporation
3
- import { Component, createRef } from "react";
2
+ // (C) 2007-2026 GoodData Corporation
3
+ import { forwardRef, useImperativeHandle, useRef } from "react";
4
4
  import cx from "classnames";
5
- // This has to be a class because DayPickerInput refs to it internally.
6
- // See https://github.com/gpbl/react-day-picker/issues/748 for more information
7
- export class DateRangePickerInputFieldBody extends Component {
8
- inputRef = createRef();
9
- invokeInputMethod = (key) => {
10
- if (this.inputRef.current) {
11
- this.inputRef.current[key]();
12
- }
13
- };
14
- blur = () => this.invokeInputMethod("blur");
15
- focus = () => this.invokeInputMethod("focus");
16
- get value() {
17
- if (this.inputRef.current) {
18
- return this.inputRef.current.value;
19
- }
20
- return "";
21
- }
22
- render() {
23
- const { className } = this.props;
24
- return (_jsxs("span", { className: cx(className), children: [
25
- _jsx("span", { className: "gd-icon-calendar", "aria-hidden": "true" }), _jsx("input", { ...this.props, ref: this.inputRef, className: "input-text s-date-range-picker-input-field" })
26
- ] }));
27
- }
28
- }
5
+ export const DateRangePickerInputFieldBody = forwardRef(function DateRangePickerInputFieldBody(props, ref) {
6
+ const inputRef = useRef(null);
7
+ useImperativeHandle(ref, () => {
8
+ const invokeInputMethod = (key) => {
9
+ inputRef.current?.[key]();
10
+ };
11
+ return {
12
+ invokeInputMethod,
13
+ blur: () => invokeInputMethod("blur"),
14
+ focus: () => invokeInputMethod("focus"),
15
+ get value() {
16
+ return inputRef.current?.value ?? "";
17
+ },
18
+ };
19
+ }, []);
20
+ const { className } = props;
21
+ return (_jsxs("span", { className: cx(className), children: [
22
+ _jsx("span", { className: "gd-icon-calendar", "aria-hidden": "true" }), _jsx("input", { ...props, ref: inputRef, className: "input-text s-date-range-picker-input-field" })
23
+ ] }));
24
+ });
@@ -1,5 +1,5 @@
1
- import { type CSSProperties, Component } from "react";
2
- import { type DynamicSelectItem, type DynamicSelectOption } from "./types.js";
1
+ import { type CSSProperties } from "react";
2
+ import { type DynamicSelectItem } from "./types.js";
3
3
  export interface IDynamicSelectProps {
4
4
  getItems: (inputValue: string) => DynamicSelectItem[];
5
5
  onChange?: (item: number) => void;
@@ -16,17 +16,5 @@ export interface IDynamicSelectProps {
16
16
  export interface IDynamicSelectState {
17
17
  inputValue: string;
18
18
  }
19
- export declare class DynamicSelect extends Component<IDynamicSelectProps, IDynamicSelectState> {
20
- constructor(props: IDynamicSelectProps);
21
- inputRef: import("react").RefObject<HTMLDivElement | null>;
22
- static defaultProps: Pick<IDynamicSelectProps, "onChange" | "initialIsOpen" | "visibleItemsRange">;
23
- onChange: (option: DynamicSelectOption | null) => void;
24
- componentDidUpdate: (lastProps: IDynamicSelectProps) => void;
25
- focus: () => void;
26
- blur: () => void;
27
- onInputValueChanged: (inputValue: string) => void;
28
- render(): import("react/jsx-runtime").JSX.Element;
29
- private onBlurHandler;
30
- private onChangeHandler;
31
- }
19
+ export declare function DynamicSelect({ getItems, onChange, initialIsOpen, placeholder, value, className, style, optionClassName, visibleItemsRange, ariaLabel, customValueValidator }: IDynamicSelectProps): import("react/jsx-runtime").JSX.Element;
32
20
  //# sourceMappingURL=DynamicSelect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DynamicSelect.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/DynamicSelect/DynamicSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAoB,SAAS,EAAa,MAAM,OAAO,CAAC;AAenF,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAQ9E,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,iBAAiB,EAAE,CAAC;IACtD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB;IAChC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,aAAc,SAAQ,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClF,YAAY,KAAK,EAAE,mBAAmB,EAerC;IAEM,QAAQ,mDAA+B;IAE9C,OAAc,YAAY,EAAE,IAAI,CAC5B,mBAAmB,EACnB,UAAU,GAAG,eAAe,GAAG,mBAAmB,CACrD,CAIC;IAEK,QAAQ,+CAKb;IAEc,kBAAkB,2CAUhC;IAEK,KAAK,aAIV;IAEK,IAAI,aAIT;IAEK,mBAAmB,+BAIxB;IAEc,MAAM,4CAyGrB;IAED,OAAO,CAAC,aAAa,CAanB;IAEF,OAAO,CAAC,eAAe,CAgBrB;CACL"}
1
+ {"version":3,"file":"DynamicSelect.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/DynamicSelect/DynamicSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAA8D,MAAM,OAAO,CAAC;AAevG,OAAO,EAAE,KAAK,iBAAiB,EAA4B,MAAM,YAAY,CAAC;AAQ9E,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,iBAAiB,EAAE,CAAC;IACtD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB;IAChC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,aAAa,CAAC,EAC1B,QAAQ,EACR,QAAmB,EACnB,aAAqB,EACrB,WAAW,EACX,KAAK,EACL,SAAS,EACT,KAAK,EACL,eAAe,EACf,iBAA4C,EAC5C,SAAS,EACT,oBAAoB,EACvB,EAAE,mBAAmB,2CA6KrB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // (C) 2019-2026 GoodData Corporation
3
- import { Component, createRef } from "react";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import cx from "classnames";
5
5
  import { defaultImport } from "default-import";
6
6
  import DefaultDownshift from "downshift";
@@ -11,115 +11,44 @@ import { findRelativeDateFilterOptionByValue } from "./utils.js";
11
11
  // In ESM, default exports of CJS modules are wrapped in default properties instead of being exposed directly.
12
12
  // https://github.com/microsoft/TypeScript/issues/52086#issuecomment-1385978414
13
13
  const Downshift = defaultImport(DefaultDownshift);
14
- export class DynamicSelect extends Component {
15
- constructor(props) {
16
- super(props);
17
- const selectedItem = props.value === undefined
14
+ export function DynamicSelect({ getItems, onChange = () => { }, initialIsOpen = false, placeholder, value, className, style, optionClassName, visibleItemsRange = defaultVisibleItemsRange, ariaLabel, customValueValidator, }) {
15
+ const [inputValue, setInputValue] = useState(() => {
16
+ const selectedItem = value === undefined
18
17
  ? null
19
18
  : findRelativeDateFilterOptionByValue(
20
19
  // pass the current value to make sure the searched options include it even if it is outside the default range
21
- props.getItems(props.value.toString()), props.value);
22
- this.state = {
23
- inputValue: selectedItem ? itemToString(selectedItem) : props.value ? props.value.toString() : "",
24
- };
25
- }
26
- inputRef = createRef();
27
- static defaultProps = {
28
- onChange: () => { },
29
- initialIsOpen: false,
30
- visibleItemsRange: defaultVisibleItemsRange,
31
- };
32
- onChange = (option) => {
33
- const { onChange } = this.props;
34
- if (option && onChange) {
35
- onChange(option.value);
36
- }
37
- };
38
- componentDidUpdate = (lastProps) => {
39
- const { value, getItems } = this.props;
40
- if (lastProps.value !== value && value !== undefined) {
20
+ getItems(value.toString()), value);
21
+ return selectedItem ? itemToString(selectedItem) : value ? value.toString() : "";
22
+ });
23
+ const inputRef = useRef(null);
24
+ // keep the input value in sync with the controlled value prop, but not on the initial render
25
+ const prevValue = useRef(value);
26
+ useEffect(() => {
27
+ if (prevValue.current !== value && value !== undefined) {
41
28
  const defaultItems = getItems(value.toString());
42
- const inputValue = findRelativeDateFilterOptionByValue(defaultItems, value)?.label || value.toString();
43
- this.setState({
44
- inputValue,
45
- });
29
+ setInputValue(findRelativeDateFilterOptionByValue(defaultItems, value)?.label || value.toString());
46
30
  }
47
- };
48
- focus = () => {
49
- if (this.inputRef.current) {
50
- this.inputRef.current.focus();
51
- }
52
- };
53
- blur = () => {
54
- if (this.inputRef.current) {
55
- this.inputRef.current.blur();
56
- }
57
- };
58
- onInputValueChanged = (inputValue) => {
59
- if (inputValue !== this.state.inputValue) {
60
- this.setState({ inputValue });
31
+ prevValue.current = value;
32
+ }, [value, getItems]);
33
+ const onInputValueChanged = useCallback((newInputValue) => {
34
+ setInputValue((currentInputValue) => newInputValue === currentInputValue ? currentInputValue : newInputValue);
35
+ }, []);
36
+ const handleChange = useCallback((option) => {
37
+ if (option && onChange) {
38
+ onChange(option.value);
61
39
  }
62
- };
63
- render() {
64
- const { initialIsOpen, placeholder, getItems, value = null, className, style, optionClassName, visibleItemsRange, ariaLabel, } = this.props;
65
- const items = getItems(this.state.inputValue);
66
- // this is important to correctly find out selected option. It is different than 'items'.
67
- const itemsByValue = value === null ? [] : getItems(value.toString());
68
- // Downshift requires null as empty selected item, if we would pass undefined it would change
69
- // from controlled to uncontrolled component
70
- const selectedItem = (value !== null && findRelativeDateFilterOptionByValue(itemsByValue, value)) || null;
71
- const selectableItems = getSelectableItems(items);
72
- const isFiltered = this.state.inputValue.trim() !== "";
73
- return (_jsx(Downshift, { onChange: this.onChange, itemToString: itemToString, initialIsOpen: initialIsOpen, selectedItem: selectedItem, itemCount: selectableItems.length, inputValue: this.state.inputValue,
74
- // automatically highlight (and therefore scroll to) the middle option if default items are displayed
75
- defaultHighlightedIndex: selectedItem || isFiltered ? 0 : getMedianIndex(selectableItems), children: ({ getInputProps, getMenuProps, getItemProps, isOpen, openMenu, closeMenu, inputValue: downshiftInputValue, highlightedIndex, setHighlightedIndex, selectItem, }) => {
76
- // Without this, highlight is not properly reset during filtering
77
- const effectiveHighlightedIndex = highlightedIndex === null || highlightedIndex > selectableItems.length - 1
78
- ? 0
79
- : highlightedIndex;
80
- const effectiveInputValue = downshiftInputValue ?? "";
81
- const menuProps = {
82
- items,
83
- selectedItem: selectedItem,
84
- highlightedIndex: effectiveHighlightedIndex,
85
- getItemProps: getItemProps,
86
- getMenuProps: getMenuProps,
87
- className: "gd-dynamic-select-menu",
88
- optionClassName,
89
- inputValue: effectiveInputValue,
90
- setHighlightedIndex,
91
- visibleItemsRange,
92
- };
93
- return (_jsxs("div", { className: cx("gd-dynamic-select", className), style: style, "aria-labelledby": undefined, children: [
94
- _jsx("div", { className: "gd-dynamic-select-input-wrapper", children: _jsx("input", { type: "text", className: "s-relative-range-input gd-input-field", "aria-label": ariaLabel, ...getInputProps({
95
- "aria-labelledby": undefined,
96
- ref: this.inputRef,
97
- placeholder: selectedItem ? selectedItem.label : placeholder,
98
- value: effectiveInputValue,
99
- onFocus: () => {
100
- this.setState({ inputValue: "" });
101
- openMenu();
102
- },
103
- onChange: (event) => this.onChangeHandler(event, selectItem),
104
- onBlur: () => {
105
- this.onBlurHandler(selectedItem, selectItem, closeMenu);
106
- },
107
- }) }) }), isOpen && items.length > 0 ? (_jsx(ScrollableSelectMenu, { ...menuProps })) : null] }));
108
- } }));
109
- }
110
- onBlurHandler = (selectedItem, selectItem, closeMenu) => {
111
- const { customValueValidator, value } = this.props;
40
+ }, [onChange]);
41
+ const onBlurHandler = useCallback((selectedItem, selectItem, closeMenu) => {
112
42
  if (customValueValidator) {
113
43
  closeMenu();
114
- this.onInputValueChanged(value?.toString() ?? "");
44
+ onInputValueChanged(value?.toString() ?? "");
115
45
  }
116
46
  else {
117
47
  selectItem(selectedItem);
118
- this.onInputValueChanged(selectedItem.label);
48
+ onInputValueChanged(selectedItem.label);
119
49
  }
120
- };
121
- onChangeHandler = (event, selectItem) => {
122
- const { customValueValidator } = this.props;
50
+ }, [customValueValidator, value, onInputValueChanged]);
51
+ const onChangeHandler = useCallback((event, selectItem) => {
123
52
  const currentValue = event.target.value;
124
53
  if (customValueValidator?.(currentValue)) {
125
54
  selectItem({
@@ -130,6 +59,52 @@ export class DynamicSelect extends Component {
130
59
  }
131
60
  // Downshifts onInputValueChanged fires twice and with an old value,
132
61
  // so we need to use our own callback
133
- this.onInputValueChanged(currentValue);
134
- };
62
+ onInputValueChanged(currentValue);
63
+ }, [customValueValidator, onInputValueChanged]);
64
+ const effectiveValue = value ?? null;
65
+ const items = getItems(inputValue);
66
+ // this is important to correctly find out selected option. It is different than 'items'.
67
+ const itemsByValue = effectiveValue === null ? [] : getItems(effectiveValue.toString());
68
+ // Downshift requires null as empty selected item, if we would pass undefined it would change
69
+ // from controlled to uncontrolled component
70
+ const selectedItem = (effectiveValue !== null && findRelativeDateFilterOptionByValue(itemsByValue, effectiveValue)) ||
71
+ null;
72
+ const selectableItems = getSelectableItems(items);
73
+ const isFiltered = inputValue.trim() !== "";
74
+ return (_jsx(Downshift, { onChange: handleChange, itemToString: itemToString, initialIsOpen: initialIsOpen, selectedItem: selectedItem, itemCount: selectableItems.length, inputValue: inputValue,
75
+ // automatically highlight (and therefore scroll to) the middle option if default items are displayed
76
+ defaultHighlightedIndex: selectedItem || isFiltered ? 0 : getMedianIndex(selectableItems), children: ({ getInputProps, getMenuProps, getItemProps, isOpen, openMenu, closeMenu, inputValue: downshiftInputValue, highlightedIndex, setHighlightedIndex, selectItem, }) => {
77
+ // Without this, highlight is not properly reset during filtering
78
+ const effectiveHighlightedIndex = highlightedIndex === null || highlightedIndex > selectableItems.length - 1
79
+ ? 0
80
+ : highlightedIndex;
81
+ const effectiveInputValue = downshiftInputValue ?? "";
82
+ const menuProps = {
83
+ items,
84
+ selectedItem: selectedItem,
85
+ highlightedIndex: effectiveHighlightedIndex,
86
+ getItemProps: getItemProps,
87
+ getMenuProps: getMenuProps,
88
+ className: "gd-dynamic-select-menu",
89
+ optionClassName,
90
+ inputValue: effectiveInputValue,
91
+ setHighlightedIndex,
92
+ visibleItemsRange,
93
+ };
94
+ return (_jsxs("div", { className: cx("gd-dynamic-select", className), style: style, "aria-labelledby": undefined, children: [
95
+ _jsx("div", { className: "gd-dynamic-select-input-wrapper", children: _jsx("input", { type: "text", className: "s-relative-range-input gd-input-field", "aria-label": ariaLabel, ...getInputProps({
96
+ "aria-labelledby": undefined,
97
+ ref: inputRef,
98
+ placeholder: selectedItem ? selectedItem.label : placeholder,
99
+ value: effectiveInputValue,
100
+ onFocus: () => {
101
+ setInputValue("");
102
+ openMenu();
103
+ },
104
+ onChange: (event) => onChangeHandler(event, selectItem),
105
+ onBlur: () => {
106
+ onBlurHandler(selectedItem, selectItem, closeMenu);
107
+ },
108
+ }) }) }), isOpen && items.length > 0 ? (_jsx(ScrollableSelectMenu, { ...menuProps })) : null] }));
109
+ } }));
135
110
  }
@@ -1,4 +1,3 @@
1
- import { Component } from "react";
2
1
  import { type ControllerStateAndHelpers } from "downshift";
3
2
  import { type ISelectItem, type ISelectItemOption } from "./types.js";
4
3
  export interface IOptionGetterProps<V> {
@@ -17,13 +16,5 @@ export interface ISelectMenuProps<V> extends IOptionGetterProps<V> {
17
16
  }
18
17
  export declare const defaultVisibleItemsRange = 3;
19
18
  export declare const getMedianIndex: (array: any[]) => number;
20
- export declare class ScrollableSelectMenu<V extends object> extends Component<ISelectMenuProps<V>> {
21
- static defaultProps: Partial<ISelectMenuProps<any>>;
22
- private listRef;
23
- render(): import("react/jsx-runtime").JSX.Element;
24
- scrollToIndex: (index?: number | undefined) => void;
25
- scrollToTop: () => void;
26
- componentDidUpdate: (lastProps: ISelectMenuProps<V>) => void;
27
- componentDidMount(): void;
28
- }
19
+ export declare function ScrollableSelectMenu<V extends object>({ items, selectedItem, highlightedIndex, getItemProps, getMenuProps, className, optionClassName, visibleItemsRange, inputValue, setHighlightedIndex }: ISelectMenuProps<V>): import("react/jsx-runtime").JSX.Element;
29
20
  //# sourceMappingURL=ScrollableSelectMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollableSelectMenu.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/Select/ScrollableSelectMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,SAAS,EAAa,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAM3D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAwB,MAAM,YAAY,CAAC;AAG5F,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AACD,MAAM,WAAW,gBAAgB,CAAC,CAAC,CAAE,SAAQ,kBAAkB,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAgE1C,eAAO,MAAM,cAAc,0BAAyD,CAAC;AAErF,qBAAa,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAE,SAAQ,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAEtF,OAAc,YAAY,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAGxD;IAEF,OAAO,CAAC,OAAO,CAAsC;IAErC,MAAM,4CAuDrB;IAEM,aAAa,uCAqBlB;IAEK,WAAW,aAKhB;IAEc,kBAAkB,2CAwChC;IAEc,iBAAiB,IAAI,IAAI,CAQxC;CACJ"}
1
+ {"version":3,"file":"ScrollableSelectMenu.d.ts","sourceRoot":"","sources":["../../../src/DateFilter/Select/ScrollableSelectMenu.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAM3D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAwB,MAAM,YAAY,CAAC;AAG5F,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACjC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AACD,MAAM,WAAW,gBAAgB,CAAC,CAAC,CAAE,SAAQ,kBAAkB,CAAC,CAAC,CAAC;IAC9D,YAAY,EAAE,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAgE1C,eAAO,MAAM,cAAc,0BAAyD,CAAC;AAErF,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,EAAE,EACnD,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,eAAe,EACf,iBAA4C,EAC5C,UAAU,EACV,mBAAmB,EACtB,EAAE,gBAAgB,CAAC,CAAC,CAAC,2CA4IrB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2019-2026 GoodData Corporation
3
- import { Component, createRef } from "react";
3
+ import { useCallback, useLayoutEffect, useRef } from "react";
4
4
  import cx from "classnames";
5
5
  import { range } from "lodash-es";
6
6
  import { SelectHeading } from "./SelectHeading.js";
@@ -46,41 +46,16 @@ const getItemHeight = (items) => (index) => {
46
46
  return itemHeightByTypeMap[itemType];
47
47
  };
48
48
  export const getMedianIndex = (array) => Math.floor(array.length / 2);
49
- export class ScrollableSelectMenu extends Component {
50
- // static cannot have <V>
51
- static defaultProps = {
52
- selectedItem: undefined,
53
- visibleItemsRange: defaultVisibleItemsRange,
54
- };
55
- listRef = createRef();
56
- render() {
57
- const { items, selectedItem, highlightedIndex, getItemProps, getMenuProps, className, optionClassName, visibleItemsRange, } = this.props;
58
- const Option = optionGetter({
59
- items,
60
- selectedItem: selectedItem,
61
- highlightedIndex,
62
- getItemProps,
63
- optionClassName,
64
- });
65
- const middleItemIndex = getMedianIndex(getSelectableItems(items));
66
- const visibleIndexes = range(Math.max(middleItemIndex - visibleItemsRange, 0), Math.min(middleItemIndex + visibleItemsRange + 1, items.length));
67
- const itemHeightFn = getItemHeight(items);
68
- const listHeight = visibleIndexes.reduce((totalHeight, itemIndex) => totalHeight + itemHeightFn(itemIndex), 0);
69
- return (_jsx("div", { ...getMenuProps({ className: cx("gd-select-menu-wrapper", className) }), children: _jsx("div", { className: "gd-select-menu s-select-menu", children: _jsx("div", { className: "List", ref: this.listRef, style: {
70
- height: listHeight,
71
- width: "100%",
72
- overflowY: items.length === 1 ? "hidden" : "auto",
73
- }, children: items.map((_, index) => (_jsx(Option, { index: index, style: { height: itemHeightFn(index) } }, `item-${index}`))) }) }) }));
74
- }
75
- scrollToIndex = (index = this.props.highlightedIndex) => {
76
- if (this.listRef.current) {
77
- const { items } = this.props;
49
+ export function ScrollableSelectMenu({ items, selectedItem, highlightedIndex, getItemProps, getMenuProps, className, optionClassName, visibleItemsRange = defaultVisibleItemsRange, inputValue, setHighlightedIndex, }) {
50
+ const listRef = useRef(null);
51
+ const scrollToIndex = useCallback((index = highlightedIndex) => {
52
+ if (listRef.current) {
78
53
  const selectableOptions = getSelectableItems(items);
79
54
  const optionIndex = index === null ? getMedianIndex(getSelectableItems(items)) : index;
80
55
  const highlightedOption = selectableOptions[optionIndex];
81
56
  const actualItemIndex = items.indexOf(highlightedOption);
82
57
  if (actualItemIndex >= 0) {
83
- const container = this.listRef.current;
58
+ const container = listRef.current;
84
59
  const heightFn = getItemHeight(items);
85
60
  const itemOffset = items
86
61
  .slice(0, actualItemIndex)
@@ -90,15 +65,35 @@ export class ScrollableSelectMenu extends Component {
90
65
  container.scrollTo({ top: targetTop });
91
66
  }
92
67
  }
93
- };
94
- scrollToTop = () => {
95
- if (!this.listRef.current) {
68
+ }, [highlightedIndex, items]);
69
+ const scrollToTop = useCallback(() => {
70
+ if (!listRef.current) {
96
71
  return;
97
72
  }
98
- this.listRef.current.scrollTo({ top: 0 });
99
- };
100
- componentDidUpdate = (lastProps) => {
101
- const { highlightedIndex, items, setHighlightedIndex, inputValue } = this.props;
73
+ listRef.current.scrollTo({ top: 0 });
74
+ }, []);
75
+ const isMounted = useRef(false);
76
+ const prev = useRef({ items, highlightedIndex, inputValue });
77
+ // componentDidMount; layout effect so it runs in the commit phase, as the class lifecycle did
78
+ useLayoutEffect(() => {
79
+ if (inputValue) {
80
+ scrollToIndex();
81
+ }
82
+ else {
83
+ const medianIndex = getMedianIndex(getSelectableItems(items));
84
+ setHighlightedIndex(medianIndex);
85
+ scrollToIndex(medianIndex);
86
+ }
87
+ // this must run only once on mount, exactly as componentDidMount did
88
+ // eslint-disable-next-line react-hooks/exhaustive-deps
89
+ }, []);
90
+ // componentDidUpdate: runs after every commit except the initial one
91
+ useLayoutEffect(() => {
92
+ if (!isMounted.current) {
93
+ isMounted.current = true;
94
+ return;
95
+ }
96
+ const lastProps = prev.current;
102
97
  const lastSelectibleLastItemIndex = getSelectableItems(lastProps.items).length - 1;
103
98
  const selectiblelastItemIndex = getSelectableItems(items).length - 1;
104
99
  const lastHighlightIsAtStart = lastProps.highlightedIndex === 0;
@@ -119,29 +114,36 @@ export class ScrollableSelectMenu extends Component {
119
114
  // We need to set highlight to 0 manually after filtering
120
115
  setHighlightedIndex(0);
121
116
  }
122
- const hasOnlyOneItem = this.props.items.length === 1;
117
+ const hasOnlyOneItem = items.length === 1;
123
118
  if (isInputValueReset) {
124
119
  // We need to restore explicitly medianIndex scroll position immediately after inputValue reset
125
120
  // even though setHighlightedIndex(medianIndex) is called, because it takes effect after one tick
126
- this.scrollToIndex(medianIndex);
121
+ scrollToIndex(medianIndex);
127
122
  }
128
123
  else if (isHighlightLoopedBack || isHighlightLoopedForward) {
129
- this.scrollToIndex();
124
+ scrollToIndex();
130
125
  }
131
126
  else if (hasOnlyOneItem) {
132
127
  // if there is only one item, we need to explicitly scroll to top
133
128
  // in order to handle error messages being scrolled out of view
134
- this.scrollToTop();
135
- }
136
- };
137
- componentDidMount() {
138
- if (this.props.inputValue) {
139
- this.scrollToIndex();
129
+ scrollToTop();
140
130
  }
141
- else {
142
- const medianIndex = getMedianIndex(getSelectableItems(this.props.items));
143
- this.props.setHighlightedIndex(medianIndex);
144
- this.scrollToIndex(medianIndex);
145
- }
146
- }
131
+ prev.current = { items, highlightedIndex, inputValue };
132
+ });
133
+ const Option = optionGetter({
134
+ items,
135
+ selectedItem: selectedItem,
136
+ highlightedIndex,
137
+ getItemProps,
138
+ optionClassName,
139
+ });
140
+ const middleItemIndex = getMedianIndex(getSelectableItems(items));
141
+ const visibleIndexes = range(Math.max(middleItemIndex - visibleItemsRange, 0), Math.min(middleItemIndex + visibleItemsRange + 1, items.length));
142
+ const itemHeightFn = getItemHeight(items);
143
+ const listHeight = visibleIndexes.reduce((totalHeight, itemIndex) => totalHeight + itemHeightFn(itemIndex), 0);
144
+ return (_jsx("div", { ...getMenuProps({ className: cx("gd-select-menu-wrapper", className) }), children: _jsx("div", { className: "gd-select-menu s-select-menu", children: _jsx("div", { className: "List", ref: listRef, style: {
145
+ height: listHeight,
146
+ width: "100%",
147
+ overflowY: items.length === 1 ? "hidden" : "auto",
148
+ }, children: items.map((_, index) => (_jsx(Option, { index: index, style: { height: itemHeightFn(index) } }, `item-${index}`))) }) }) }));
147
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-filters",
3
- "version": "11.53.0-alpha.1",
3
+ "version": "11.53.0-alpha.2",
4
4
  "description": "GoodData.UI SDK - Filter Components",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -46,11 +46,11 @@
46
46
  "ts-invariant": "0.10.3",
47
47
  "tslib": "2.8.1",
48
48
  "uuid": "11.1.1",
49
- "@gooddata/sdk-backend-spi": "11.53.0-alpha.1",
50
- "@gooddata/sdk-model": "11.53.0-alpha.1",
51
- "@gooddata/sdk-ui": "11.53.0-alpha.1",
52
- "@gooddata/util": "11.53.0-alpha.1",
53
- "@gooddata/sdk-ui-kit": "11.53.0-alpha.1"
49
+ "@gooddata/sdk-backend-spi": "11.53.0-alpha.2",
50
+ "@gooddata/sdk-model": "11.53.0-alpha.2",
51
+ "@gooddata/sdk-ui-kit": "11.53.0-alpha.2",
52
+ "@gooddata/sdk-ui": "11.53.0-alpha.2",
53
+ "@gooddata/util": "11.53.0-alpha.2"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@microsoft/api-documenter": "^7.17.0",
@@ -92,12 +92,12 @@
92
92
  "typescript": "5.9.3",
93
93
  "vitest": "4.1.8",
94
94
  "vitest-dom": "0.1.1",
95
- "@gooddata/eslint-config": "11.53.0-alpha.1",
96
- "@gooddata/oxlint-config": "11.53.0-alpha.1",
97
- "@gooddata/sdk-backend-mockingbird": "11.53.0-alpha.1",
98
- "@gooddata/reference-workspace": "11.53.0-alpha.1",
99
- "@gooddata/sdk-ui-theme-provider": "11.53.0-alpha.1",
100
- "@gooddata/stylelint-config": "11.53.0-alpha.1"
95
+ "@gooddata/oxlint-config": "11.53.0-alpha.2",
96
+ "@gooddata/eslint-config": "11.53.0-alpha.2",
97
+ "@gooddata/sdk-backend-mockingbird": "11.53.0-alpha.2",
98
+ "@gooddata/reference-workspace": "11.53.0-alpha.2",
99
+ "@gooddata/sdk-ui-theme-provider": "11.53.0-alpha.2",
100
+ "@gooddata/stylelint-config": "11.53.0-alpha.2"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": "^18.0.0 || ^19.0.0",
@@ -842,6 +842,17 @@ button.gd-list-item {
842
842
  color: var(--gd-palette-complementary-8, #464e56);
843
843
  text-decoration: underline;
844
844
  }
845
+ .gd-list-item:has(.gd-list-item-suffix) {
846
+ justify-content: flex-start;
847
+ }
848
+ .gd-list-item:has(.gd-list-item-suffix) > span {
849
+ flex-grow: 0;
850
+ }
851
+ .gd-list-item .gd-list-item-suffix {
852
+ flex: 0 0 auto;
853
+ margin-left: 5px;
854
+ color: var(--gd-palette-complementary-6);
855
+ }
845
856
  .gd-list-item.gd-list-item-separator, .gd-list-item.gd-list-item-header {
846
857
  cursor: default;
847
858
  }