@gooddata/sdk-ui-filters 11.53.0-alpha.0 → 11.53.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/AttributeFilter/Components/TextFilterBody/ArbitraryValuesInput.js +1 -1
- package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.d.ts +10 -5
- package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.d.ts.map +1 -1
- package/esm/DateFilter/DateRangePicker/DateRangePickerInputFieldBody.js +22 -26
- package/esm/DateFilter/DynamicSelect/DynamicSelect.d.ts +3 -15
- package/esm/DateFilter/DynamicSelect/DynamicSelect.d.ts.map +1 -1
- package/esm/DateFilter/DynamicSelect/DynamicSelect.js +75 -100
- package/esm/DateFilter/Select/ScrollableSelectMenu.d.ts +1 -10
- package/esm/DateFilter/Select/ScrollableSelectMenu.d.ts.map +1 -1
- package/esm/DateFilter/Select/ScrollableSelectMenu.js +55 -53
- package/package.json +12 -12
- package/styles/css/attributeFilter.css +11 -0
- package/styles/css/attributeFilter.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilter.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilter.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterAddons.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterAddons.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterDropdownBody.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterDropdownBody.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterDropdownButton.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterElementsSelect.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterElementsSelect.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterEmptyResult.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterEmptyResult.css.map +1 -1
- package/styles/css/attributeFilterNext/attributeFilterStatusBar.css +11 -0
- package/styles/css/attributeFilterNext/attributeFilterStatusBar.css.map +1 -1
- package/styles/css/attributeFilterNext.css +11 -0
- package/styles/css/attributeFilterNext.css.map +1 -1
- package/styles/css/components/DateFilterCustomPeriodButtons.css +11 -0
- package/styles/css/components/DateFilterCustomPeriodButtons.css.map +1 -1
- package/styles/css/dateFilter.css +11 -0
- package/styles/css/dateFilter.css.map +1 -1
- package/styles/css/main.css +11 -0
- 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?.
|
|
55
|
+
inputRef.current?.focus();
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
previousValuesLengthRef.current = values.length;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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,
|
|
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-
|
|
3
|
-
import {
|
|
2
|
+
// (C) 2007-2026 GoodData Corporation
|
|
3
|
+
import { forwardRef, useImperativeHandle, useRef } from "react";
|
|
4
4
|
import cx from "classnames";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
2
|
-
import { type DynamicSelectItem
|
|
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
|
|
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,
|
|
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 {
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
43
|
-
this.setState({
|
|
44
|
-
inputValue,
|
|
45
|
-
});
|
|
29
|
+
setInputValue(findRelativeDateFilterOptionByValue(defaultItems, value)?.label || value.toString());
|
|
46
30
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
onInputValueChanged(value?.toString() ?? "");
|
|
115
45
|
}
|
|
116
46
|
else {
|
|
117
47
|
selectItem(selectedItem);
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
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":"
|
|
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 {
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
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 (!
|
|
68
|
+
}, [highlightedIndex, items]);
|
|
69
|
+
const scrollToTop = useCallback(() => {
|
|
70
|
+
if (!listRef.current) {
|
|
96
71
|
return;
|
|
97
72
|
}
|
|
98
|
-
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
|
|
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 =
|
|
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
|
-
|
|
121
|
+
scrollToIndex(medianIndex);
|
|
127
122
|
}
|
|
128
123
|
else if (isHighlightLoopedBack || isHighlightLoopedForward) {
|
|
129
|
-
|
|
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
|
-
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
componentDidMount() {
|
|
138
|
-
if (this.props.inputValue) {
|
|
139
|
-
this.scrollToIndex();
|
|
129
|
+
scrollToTop();
|
|
140
130
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
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.
|
|
50
|
-
"@gooddata/sdk-model": "11.53.0-alpha.
|
|
51
|
-
"@gooddata/sdk-ui": "11.53.0-alpha.
|
|
52
|
-
"@gooddata/sdk-ui
|
|
53
|
-
"@gooddata/util": "11.53.0-alpha.
|
|
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/
|
|
96
|
-
"@gooddata/
|
|
97
|
-
"@gooddata/
|
|
98
|
-
"@gooddata/
|
|
99
|
-
"@gooddata/sdk-ui-theme-provider": "11.53.0-alpha.
|
|
100
|
-
"@gooddata/
|
|
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
|
}
|