@rescui/select 0.15.1-RUI-280-Update-select-component-downshift-2f40ef205.67 → 0.15.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/lib/_virtual/index.css.js +12 -12
- package/lib/common-prop-types.d.ts +14 -9
- package/lib/common-prop-types.js +3 -4
- package/lib/index.css +432 -808
- package/lib/index.d.ts +6 -4
- package/lib/index.js +3 -3
- package/lib/key-codes.d.ts +7 -0
- package/lib/key-codes.js +8 -0
- package/lib/parts/common-select.p.module.css.js +15 -0
- package/lib/parts/foldable.js +2 -1
- package/lib/parts/form-trigger-wrapper.d.ts +7 -7
- package/lib/parts/form-trigger-wrapper.js +34 -16
- package/lib/parts/form-trigger.p.module.css.js +15 -0
- package/lib/parts/multiselect.d.ts +86 -0
- package/lib/parts/multiselect.js +455 -0
- package/lib/parts/native-select.d.ts +36 -0
- package/lib/parts/native-select.js +255 -0
- package/lib/parts/native-select.p.module.css.js +9 -0
- package/lib/parts/throttle.d.ts +1 -0
- package/lib/parts/trigger.d.ts +37 -0
- package/lib/parts/trigger.js +255 -0
- package/lib/parts/trigger.p.module.css.js +31 -0
- package/lib/parts/use-observable-force-rerender.d.ts +2 -0
- package/lib/parts/use-observable-force-rerender.js +16 -0
- package/lib/public-api.p.css +309 -545
- package/lib/public-api.pcss +309 -545
- package/lib/select.d.ts +91 -27
- package/lib/select.js +351 -51
- package/lib/types.d.ts +4 -167
- package/lib/utils.d.ts +22 -0
- package/lib/utils.js +199 -0
- package/package.json +12 -12
- package/lib/constants.d.ts +0 -6
- package/lib/constants.js +0 -7
- package/lib/hooks/use-a11y.d.ts +0 -29
- package/lib/hooks/use-a11y.js +0 -53
- package/lib/hooks/use-focus.d.ts +0 -10
- package/lib/hooks/use-focus.js +0 -17
- package/lib/hooks/use-hover.d.ts +0 -10
- package/lib/hooks/use-hover.js +0 -17
- package/lib/hooks/use-label-focus.d.ts +0 -9
- package/lib/hooks/use-label-focus.js +0 -22
- package/lib/hooks/use-multi-handlers.d.ts +0 -11
- package/lib/hooks/use-multi-handlers.js +0 -48
- package/lib/hooks/use-options.d.ts +0 -13
- package/lib/hooks/use-options.js +0 -89
- package/lib/multiselect.d.ts +0 -3
- package/lib/multiselect.js +0 -312
- package/lib/native-select.d.ts +0 -4
- package/lib/native-select.js +0 -211
- package/lib/parts/hidden-select.d.ts +0 -13
- package/lib/parts/hidden-select.js +0 -45
- package/lib/parts/icons.d.ts +0 -17
- package/lib/parts/icons.js +0 -69
- package/lib/parts/list-box.d.ts +0 -11
- package/lib/parts/list-box.js +0 -116
- package/lib/parts/multi-trigger.d.ts +0 -12
- package/lib/parts/multi-trigger.js +0 -74
- package/lib/parts/native-select-options-with-groups.d.ts +0 -8
- package/lib/parts/native-select-options-with-groups.js +0 -76
- package/lib/select-basic.d.ts +0 -3
- package/lib/select-basic.js +0 -203
- package/lib/select-searchable.d.ts +0 -3
- package/lib/select-searchable.js +0 -250
- package/lib/styles/common.p.module.css.js +0 -31
- package/lib/styles/form-trigger.p.module.css.js +0 -15
- package/lib/styles/list-box.p.module.css.js +0 -14
- package/lib/utils/calculate-max-height-by-items.d.ts +0 -13
- package/lib/utils/calculate-max-height-by-items.js +0 -22
- package/lib/utils/get-empty-message.d.ts +0 -6
- package/lib/utils/get-empty-message.js +0 -32
- package/lib/utils/get-option-value.d.ts +0 -2
- package/lib/utils/get-option-value.js +0 -11
- package/lib/utils/get-real-items-cnt.d.ts +0 -2
- package/lib/utils/get-real-items-cnt.js +0 -19
- package/lib/utils/get-text-from-nodes.d.ts +0 -2
- package/lib/utils/get-text-from-nodes.js +0 -28
- package/lib/utils/is-option-a-group.d.ts +0 -2
- package/lib/utils/is-option-a-group.js +0 -5
- package/lib/utils/item-to-string.d.ts +0 -2
- package/lib/utils/item-to-string.js +0 -7
- package/lib/utils/merge-props.d.ts +0 -29
- package/lib/utils/merge-props.js +0 -106
- package/lib/utils/throttle.d.ts +0 -1
- /package/lib/{utils → parts}/throttle.js +0 -0
- /package/lib/{hooks → parts}/use-backward-compatible-id.d.ts +0 -0
- /package/lib/{hooks → parts}/use-backward-compatible-id.js +0 -0
package/lib/select.d.ts
CHANGED
|
@@ -1,28 +1,92 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
import { Theme } from '@rescui/ui-contexts';
|
|
3
|
+
import type { SelectOption, NoOptionsMessage, SelectOptionOrGroup, SelectSize, SelectMode } from './types';
|
|
4
|
+
export interface CustomTriggerOptions {
|
|
5
|
+
selectedLabel?: string;
|
|
6
|
+
selectedValue: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
hovered: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface SelectProps {
|
|
11
|
+
/**
|
|
12
|
+
* Additional class name
|
|
13
|
+
*/
|
|
14
|
+
className?: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Array of options or option groups
|
|
18
|
+
*/
|
|
19
|
+
options: SelectOptionOrGroup[];
|
|
20
|
+
/**
|
|
21
|
+
* Is select supports clear the value
|
|
22
|
+
*/
|
|
23
|
+
isClearable?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Is select supports inplace filtering
|
|
26
|
+
*/
|
|
27
|
+
isSearchable?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Is select has been disabled
|
|
30
|
+
*/
|
|
31
|
+
isDisabled?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Is loader displayed inside select
|
|
34
|
+
*/
|
|
35
|
+
isLoading?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* A hint to the user of what can be entered in the control
|
|
38
|
+
*/
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Size
|
|
42
|
+
*/
|
|
43
|
+
size?: SelectSize;
|
|
44
|
+
/**
|
|
45
|
+
* Appearance mode
|
|
46
|
+
*/
|
|
47
|
+
mode?: SelectMode;
|
|
48
|
+
/**
|
|
49
|
+
* Appearance theme
|
|
50
|
+
*/
|
|
51
|
+
theme?: Theme;
|
|
52
|
+
/**
|
|
53
|
+
* Value of the select
|
|
54
|
+
*/
|
|
55
|
+
value: SelectOption;
|
|
56
|
+
/**
|
|
57
|
+
* Change value callback. `value => ...`
|
|
58
|
+
*/
|
|
59
|
+
onChange?: (newValue: SelectOption) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Error state flag or React.ReactNode (use with forms)
|
|
62
|
+
*/
|
|
63
|
+
error?: boolean | React.ReactNode;
|
|
64
|
+
/**
|
|
65
|
+
* If `true`, there will be reserved a space for a 1-line error message. Subsequent lines will be placed below normally.
|
|
66
|
+
* If `false`, there will be no reservation.
|
|
67
|
+
*/
|
|
68
|
+
reserveErrorLine?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Label element upper then Select element (use with forms)
|
|
71
|
+
*/
|
|
72
|
+
label?: React.ReactNode;
|
|
73
|
+
/**
|
|
74
|
+
* Placement of dropdown element
|
|
75
|
+
*/
|
|
76
|
+
placement?: 'auto-start' | 'auto' | 'auto-end' | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-end' | 'bottom' | 'bottom-start' | 'left-end' | 'left' | 'left-start';
|
|
77
|
+
/**
|
|
78
|
+
* Note element below then Select element (use with forms)
|
|
79
|
+
*/
|
|
80
|
+
note?: React.ReactNode;
|
|
81
|
+
/**
|
|
82
|
+
* Text to display when there are no options
|
|
83
|
+
*/
|
|
84
|
+
noOptionsMessage?: NoOptionsMessage;
|
|
85
|
+
/**
|
|
86
|
+
* Input name attribute
|
|
87
|
+
*/
|
|
88
|
+
name?: string;
|
|
89
|
+
boldLabel?: boolean;
|
|
90
|
+
}
|
|
91
|
+
declare const Select: React.FC<SelectProps>;
|
|
92
|
+
export default Select;
|
package/lib/select.js
CHANGED
|
@@ -1,36 +1,103 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import
|
|
2
|
+
import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
|
|
3
|
+
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
4
|
import PropTypes from 'prop-types';
|
|
4
|
-
import { ErrorMessage } from '@rescui/form-field';
|
|
5
5
|
import cn from 'classnames';
|
|
6
|
+
import scrollIntoView from 'scroll-into-view-if-needed';
|
|
7
|
+
import { Dropdown } from '@rescui/dropdown';
|
|
8
|
+
import { Menu, MenuItem, Divider, GroupHeader } from '@rescui/menu';
|
|
9
|
+
import { ErrorMessage } from '@rescui/form-field';
|
|
6
10
|
import { useThemeWithUndefined } from '@rescui/ui-contexts';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import SelectTrigger from './parts/trigger.js';
|
|
12
|
+
import FormTriggerWrapper from './parts/form-trigger-wrapper.js';
|
|
13
|
+
import { useObservableForceRerender } from './parts/use-observable-force-rerender.js';
|
|
14
|
+
import { useBackwardCompatibleId } from './parts/use-backward-compatible-id.js';
|
|
15
|
+
import { optionType, optionsGroupType, noOptionsMessageConfigType } from './common-prop-types.js';
|
|
16
|
+
import { useStrongMemo, getDefaultCursorIndex, isGroupsEmpty, getMovedCursorIndex, shouldOpenSelect, isIndexMatchCursor, withoutGroupsLabel, getPlainOptionsFromGroups, getEmptyMessage, OptionGroupTypes, getOptionValue, convertToOptionGroupsIfNeeded, escapeRegex, getTextFromNodes, calculateMaxHeight } from './utils.js';
|
|
17
|
+
import { ARROW_DOWN, ARROW_UP, ENTER, SPACE, TAB, ESCAPE, BACKSPACE } from './key-codes.js';
|
|
18
|
+
import styles from './parts/common-select.p.module.css.js'; // eslint-disable-next-line complexity
|
|
19
|
+
|
|
20
|
+
const Option = _ref => {
|
|
21
|
+
let {
|
|
22
|
+
selected,
|
|
23
|
+
cursorIndex,
|
|
24
|
+
item,
|
|
25
|
+
isLastItem,
|
|
26
|
+
highlightedItemRef,
|
|
27
|
+
handleSelectIndex,
|
|
28
|
+
handleHighlightIndex
|
|
29
|
+
} = _ref;
|
|
30
|
+
|
|
31
|
+
if (item.type === OptionGroupTypes.OPTION) {
|
|
32
|
+
const {
|
|
33
|
+
index,
|
|
34
|
+
label
|
|
35
|
+
} = item;
|
|
36
|
+
const value = getOptionValue(item);
|
|
37
|
+
const isSelected = selected && value === getOptionValue(selected);
|
|
38
|
+
const isHighlighted = isIndexMatchCursor(cursorIndex, index);
|
|
39
|
+
return /*#__PURE__*/React.createElement(MenuItem, {
|
|
40
|
+
selected: isSelected,
|
|
41
|
+
highlighted: isHighlighted,
|
|
42
|
+
ref: isHighlighted ? highlightedItemRef : null,
|
|
43
|
+
onMouseMove: () => handleHighlightIndex(index),
|
|
44
|
+
onClick: () => handleSelectIndex(index),
|
|
45
|
+
className: cn(isLastItem && styles.lastOption),
|
|
46
|
+
"data-test": ['select-menu-item', isHighlighted && 'select-menu-item-highlighted', isSelected && 'select-menu-item-selected'].filter(Boolean).join(' ')
|
|
47
|
+
}, label);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (item.type === OptionGroupTypes.DIVIDER) {
|
|
51
|
+
return /*#__PURE__*/React.createElement(Divider, {
|
|
52
|
+
className: styles.divider
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (item.type === OptionGroupTypes.HEADER) {
|
|
57
|
+
const {
|
|
58
|
+
label
|
|
59
|
+
} = item;
|
|
60
|
+
return /*#__PURE__*/React.createElement(GroupHeader, {
|
|
61
|
+
text: label
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getOptionKey = (item, i) => {
|
|
69
|
+
if (item.type === OptionGroupTypes.OPTION) {
|
|
70
|
+
const value = getOptionValue(item);
|
|
71
|
+
return `${value}_${item.index[0]}`;
|
|
72
|
+
} else if (item.type === OptionGroupTypes.DIVIDER) {
|
|
73
|
+
return `divider${i}`;
|
|
74
|
+
} else if (item.type === OptionGroupTypes.HEADER) {
|
|
75
|
+
return `header${i}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
14
79
|
};
|
|
80
|
+
|
|
15
81
|
const SIZE_STYLES = {
|
|
16
82
|
s: styles.sizeS,
|
|
17
83
|
m: styles.sizeM,
|
|
18
84
|
l: styles.sizeL
|
|
19
85
|
};
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
86
|
+
const MENU_SIZE_STYLES = {
|
|
87
|
+
s: styles.menuSizeS,
|
|
88
|
+
m: styles.menuSizeM,
|
|
89
|
+
l: styles.menuSizeL
|
|
90
|
+
}; // eslint-disable-next-line complexity
|
|
24
91
|
|
|
25
|
-
const Select =
|
|
92
|
+
const Select = _ref2 => {
|
|
26
93
|
let {
|
|
27
|
-
options
|
|
94
|
+
options,
|
|
28
95
|
size = 'm',
|
|
29
96
|
mode = 'classic',
|
|
30
97
|
theme: themeFromProps,
|
|
31
|
-
className,
|
|
32
|
-
|
|
33
|
-
value,
|
|
98
|
+
className = null,
|
|
99
|
+
id,
|
|
100
|
+
value = undefined,
|
|
34
101
|
onChange,
|
|
35
102
|
placeholder,
|
|
36
103
|
isClearable = false,
|
|
@@ -42,45 +109,278 @@ const Select = _ref => {
|
|
|
42
109
|
placement = 'bottom',
|
|
43
110
|
boldLabel = true,
|
|
44
111
|
reserveErrorLine = false,
|
|
45
|
-
note,
|
|
46
|
-
maxVisibleItems = 5,
|
|
47
|
-
id,
|
|
48
112
|
...restProps
|
|
49
|
-
} =
|
|
113
|
+
} = _ref2;
|
|
114
|
+
const [triggerFormWrapperRef, setTriggerFormWrapperRef] = useState(null);
|
|
50
115
|
const theme = useThemeWithUndefined(themeFromProps);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
116
|
+
const dropdownRef = useRef(null);
|
|
117
|
+
const highlightedItemRef = /*#__PURE__*/React.createRef();
|
|
118
|
+
const [isKeyboardNavigating, setIsKeyboardNavigating] = useState(false);
|
|
119
|
+
const skipScrollingRef = useRef(false);
|
|
120
|
+
const [searchString, setSearchString] = useState('');
|
|
121
|
+
const groups = useStrongMemo(() => convertToOptionGroupsIfNeeded(options), [options]);
|
|
122
|
+
const filteredGroups = useStrongMemo(() => {
|
|
123
|
+
if (searchString === '') {
|
|
124
|
+
return groups;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const filter = new RegExp(`(?:^|\\s)${escapeRegex(searchString)}`, 'i');
|
|
128
|
+
return groups.map(g => ({ ...g,
|
|
129
|
+
options: g.options.filter(_ref3 => {
|
|
130
|
+
let {
|
|
131
|
+
label
|
|
132
|
+
} = _ref3;
|
|
133
|
+
return filter.test(getTextFromNodes(label));
|
|
134
|
+
})
|
|
135
|
+
}));
|
|
136
|
+
}, [searchString, groups]);
|
|
137
|
+
const [menuIsOpen, setMenuIsOpen] = useState(false);
|
|
138
|
+
const [cursorIndex, setCursorIndex] = useState(() => getDefaultCursorIndex(filteredGroups));
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
setCursorIndex(getDefaultCursorIndex(filteredGroups));
|
|
141
|
+
|
|
142
|
+
if (searchString !== '') {
|
|
143
|
+
setMenuIsOpen(true);
|
|
144
|
+
}
|
|
145
|
+
}, [searchString, filteredGroups]);
|
|
146
|
+
const isEmptyMenu = useMemo(() => isGroupsEmpty(filteredGroups), [filteredGroups]);
|
|
147
|
+
const handleSelectIndex = useCallback(index => {
|
|
148
|
+
const [grpIndex, optIndex] = index;
|
|
149
|
+
const selectedOption = filteredGroups[grpIndex].options[optIndex];
|
|
150
|
+
setMenuIsOpen(false);
|
|
151
|
+
setSearchString('');
|
|
152
|
+
setIsKeyboardNavigating(false);
|
|
153
|
+
|
|
154
|
+
if (onChange) {
|
|
155
|
+
onChange(selectedOption);
|
|
156
|
+
}
|
|
157
|
+
}, [onChange, filteredGroups]);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (dropdownRef.current) {
|
|
160
|
+
dropdownRef.current.scheduleUpdate();
|
|
161
|
+
}
|
|
162
|
+
}, [searchString]);
|
|
163
|
+
const handleMouseMove = useCallback(() => setIsKeyboardNavigating(false), []);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (isKeyboardNavigating) {
|
|
166
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return () => document.removeEventListener('mousemove', handleMouseMove);
|
|
170
|
+
}, [handleMouseMove, isKeyboardNavigating]);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (isKeyboardNavigating && highlightedItemRef.current) {
|
|
173
|
+
if (skipScrollingRef.current) {
|
|
174
|
+
skipScrollingRef.current = false;
|
|
175
|
+
} else {
|
|
176
|
+
let elementToScrollTo = highlightedItemRef.current;
|
|
177
|
+
|
|
178
|
+
if (cursorIndex[0] === 0 && cursorIndex[1] === 0) {
|
|
179
|
+
const groupHeader = highlightedItemRef.current.previousSibling;
|
|
180
|
+
|
|
181
|
+
if (groupHeader) {
|
|
182
|
+
elementToScrollTo = groupHeader;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
scrollIntoView(elementToScrollTo, {
|
|
187
|
+
behavior: 'instant',
|
|
188
|
+
scrollMode: 'if-needed',
|
|
189
|
+
block: 'nearest',
|
|
190
|
+
inline: 'nearest'
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}, [highlightedItemRef, cursorIndex, isKeyboardNavigating]);
|
|
195
|
+
const handleClose = useCallback(() => {
|
|
196
|
+
setSearchString('');
|
|
197
|
+
setMenuIsOpen(false);
|
|
198
|
+
setIsKeyboardNavigating(false);
|
|
199
|
+
}, []);
|
|
200
|
+
const handleKeyNavigation = useCallback( // eslint-disable-next-line complexity
|
|
201
|
+
e => {
|
|
202
|
+
if (e.key === ARROW_DOWN || e.key === ARROW_UP) {
|
|
203
|
+
const isDownDirection = e.key === ARROW_DOWN;
|
|
204
|
+
setCursorIndex(getMovedCursorIndex(filteredGroups, cursorIndex, isDownDirection));
|
|
205
|
+
setIsKeyboardNavigating(true);
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (e.key === ENTER || !isSearchable && e.key === SPACE) {
|
|
210
|
+
if (cursorIndex !== null) {
|
|
211
|
+
handleSelectIndex(cursorIndex);
|
|
212
|
+
} else {
|
|
213
|
+
handleClose();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (e.key === TAB) {
|
|
220
|
+
handleClose();
|
|
221
|
+
}
|
|
222
|
+
}, [filteredGroups, cursorIndex, handleClose, handleSelectIndex, isSearchable]);
|
|
223
|
+
const handleClick = useCallback(() => {
|
|
224
|
+
if (isDisabled) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setMenuIsOpen(currentMenuIsOpen => !currentMenuIsOpen);
|
|
229
|
+
setSearchString('');
|
|
230
|
+
}, [isDisabled]);
|
|
231
|
+
const handleSearchInputChange = useCallback(searchValue => setSearchString(searchValue), []);
|
|
232
|
+
const handleClearClick = useCallback(() => {
|
|
233
|
+
setCursorIndex(getDefaultCursorIndex(filteredGroups));
|
|
234
|
+
setMenuIsOpen(false);
|
|
235
|
+
|
|
236
|
+
if (onChange) {
|
|
237
|
+
onChange(null);
|
|
238
|
+
}
|
|
239
|
+
}, [filteredGroups, onChange]);
|
|
240
|
+
const handleTriggerKeyDown = useCallback(e => {
|
|
241
|
+
if (e.key === ESCAPE) {
|
|
242
|
+
handleClose();
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (isDisabled) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (menuIsOpen) {
|
|
251
|
+
handleKeyNavigation(e);
|
|
252
|
+
} else {
|
|
253
|
+
const target = e.target;
|
|
254
|
+
const trigger = target.closest('[data-target="trigger"]');
|
|
255
|
+
const isTriggerTarget = trigger === target;
|
|
256
|
+
|
|
257
|
+
if (shouldOpenSelect(e.key, isTriggerTarget)) {
|
|
258
|
+
setMenuIsOpen(true);
|
|
259
|
+
setSearchString('');
|
|
260
|
+
setIsKeyboardNavigating(true);
|
|
261
|
+
skipScrollingRef.current = true;
|
|
262
|
+
trigger.focus();
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (e.key === BACKSPACE && isClearable) {
|
|
267
|
+
handleClearClick();
|
|
268
|
+
trigger.focus();
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}, [menuIsOpen, isDisabled, isClearable, handleClose, handleClearClick, handleKeyNavigation]);
|
|
273
|
+
const handleHighlightIndex = useCallback(index => {
|
|
274
|
+
if (!isIndexMatchCursor(cursorIndex, index)) {
|
|
275
|
+
setCursorIndex(index);
|
|
276
|
+
}
|
|
277
|
+
}, [cursorIndex]); // We use invisible and "untouchable" dummy element to observe `size` changes from css to trigger force rerenders
|
|
278
|
+
|
|
279
|
+
const observableRef = useObservableForceRerender();
|
|
280
|
+
let sizeFromCss = null;
|
|
281
|
+
|
|
282
|
+
if (triggerFormWrapperRef && menuIsOpen) {
|
|
283
|
+
var _context;
|
|
284
|
+
|
|
285
|
+
// In theory this part of the code might cause issues with SSR.
|
|
286
|
+
// But in this specific context, because of checking ref "triggerFormWrapperRef",
|
|
287
|
+
// we are sure that the value will still be "null" on the first render.
|
|
288
|
+
// Therefore, the first render on the client will be the same as on the server.
|
|
289
|
+
// Also, there will be no layout shifts on the following renders because
|
|
290
|
+
// it's a dropdown-content and in most cases it opens only on-demand.
|
|
291
|
+
// In additional, we use "observable" el to watch size changes between rerenders (@media query case).
|
|
292
|
+
const triggerFormWrapperStyles = getComputedStyle(triggerFormWrapperRef);
|
|
293
|
+
const rawSizeFromCss = triggerFormWrapperStyles.getPropertyValue('--_rs-select-size');
|
|
294
|
+
sizeFromCss = _includesInstanceProperty(_context = ['s', 'm', 'l']).call(_context, rawSizeFromCss) ? rawSizeFromCss : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const actualSize = sizeFromCss || size;
|
|
298
|
+
const maxHeight = calculateMaxHeight(actualSize);
|
|
299
|
+
const withGroupsLabel = !withoutGroupsLabel(filteredGroups);
|
|
300
|
+
const uniqueId = `select-${useBackwardCompatibleId()}`;
|
|
301
|
+
const errorId = `${id ? id : uniqueId}-error`;
|
|
302
|
+
return /*#__PURE__*/React.createElement(Dropdown, {
|
|
61
303
|
theme: theme,
|
|
62
|
-
|
|
63
|
-
id: id,
|
|
64
|
-
style: style,
|
|
65
|
-
value: value,
|
|
66
|
-
onChange: onChange,
|
|
67
|
-
placeholder: placeholder,
|
|
68
|
-
isClearable: isClearable,
|
|
69
|
-
isLoading: isLoading,
|
|
70
|
-
isDisabled: isDisabled,
|
|
71
|
-
noOptionsMessage: noOptionsMessage,
|
|
72
|
-
error: error,
|
|
304
|
+
onRequestClose: handleClose,
|
|
73
305
|
placement: placement,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
306
|
+
mode: mode,
|
|
307
|
+
ref: dropdownRef,
|
|
308
|
+
cancelCloseAnimation: true,
|
|
309
|
+
minWidth: "trigger",
|
|
310
|
+
maxWidth: "trigger",
|
|
311
|
+
maxHeight: maxHeight,
|
|
312
|
+
leastMaxHeight: maxHeight,
|
|
313
|
+
disableFocusLock: true,
|
|
314
|
+
trigger: _ref4 => {
|
|
315
|
+
let {
|
|
316
|
+
ref
|
|
317
|
+
} = _ref4;
|
|
318
|
+
return /*#__PURE__*/React.createElement(FormTriggerWrapper, {
|
|
319
|
+
ref: setTriggerFormWrapperRef,
|
|
320
|
+
className: cn(className, SIZE_STYLES[size]),
|
|
321
|
+
id: id,
|
|
322
|
+
errorId: errorId,
|
|
323
|
+
theme: theme,
|
|
324
|
+
size: size,
|
|
325
|
+
disabled: isDisabled,
|
|
326
|
+
error: error,
|
|
327
|
+
reserveErrorLine: reserveErrorLine,
|
|
328
|
+
label: restProps.label,
|
|
329
|
+
note: restProps.note,
|
|
330
|
+
onKeyDown: handleTriggerKeyDown,
|
|
331
|
+
boldLabel: boldLabel,
|
|
332
|
+
role: "listbox",
|
|
333
|
+
"aria-errormessage": error ? errorId : undefined,
|
|
334
|
+
"aria-invalid": Boolean(error)
|
|
335
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
336
|
+
ref: observableRef,
|
|
337
|
+
className: styles.observable
|
|
338
|
+
}), restProps.name && /*#__PURE__*/React.createElement("input", {
|
|
339
|
+
type: "hidden",
|
|
340
|
+
name: restProps.name,
|
|
341
|
+
value: value && value.value || ''
|
|
342
|
+
}), /*#__PURE__*/React.createElement(SelectTrigger, {
|
|
343
|
+
theme: theme,
|
|
344
|
+
mode: mode,
|
|
345
|
+
size: size,
|
|
346
|
+
isOpen: menuIsOpen,
|
|
347
|
+
triggerRef: ref,
|
|
348
|
+
selected: value,
|
|
349
|
+
placeholder: placeholder,
|
|
350
|
+
isClearable: isClearable,
|
|
351
|
+
isLoading: isLoading,
|
|
352
|
+
isDisabled: isDisabled,
|
|
353
|
+
searchInput: searchString,
|
|
354
|
+
isSearchable: isSearchable,
|
|
355
|
+
onSearchChange: handleSearchInputChange,
|
|
356
|
+
onClick: handleClick,
|
|
357
|
+
onClearClick: handleClearClick,
|
|
358
|
+
error: Boolean(error),
|
|
359
|
+
"data-target": "trigger"
|
|
360
|
+
}));
|
|
361
|
+
},
|
|
362
|
+
isOpen: menuIsOpen
|
|
363
|
+
}, /*#__PURE__*/React.createElement(Menu, {
|
|
364
|
+
size: actualSize,
|
|
365
|
+
disableCursor: isKeyboardNavigating,
|
|
366
|
+
className: cn(styles.menu, withGroupsLabel && styles.withGroupsLabel, MENU_SIZE_STYLES[actualSize]),
|
|
367
|
+
enableKeyboardNavigation: false
|
|
368
|
+
}, !isEmptyMenu ? getPlainOptionsFromGroups(filteredGroups).map((item, i, collection) => /*#__PURE__*/React.createElement(Option, {
|
|
369
|
+
item: item,
|
|
370
|
+
isLastItem: i === collection.length - 1,
|
|
371
|
+
key: getOptionKey(item, i),
|
|
372
|
+
selected: value,
|
|
373
|
+
cursorIndex: cursorIndex,
|
|
374
|
+
highlightedItemRef: highlightedItemRef,
|
|
375
|
+
handleSelectIndex: handleSelectIndex,
|
|
376
|
+
handleHighlightIndex: handleHighlightIndex
|
|
377
|
+
})) : /*#__PURE__*/React.createElement(MenuItem, {
|
|
378
|
+
disabled: true
|
|
379
|
+
}, getEmptyMessage(isLoading, isSearchable && searchString.length > 0, noOptionsMessage))));
|
|
79
380
|
};
|
|
80
381
|
|
|
81
382
|
Select.propTypes = {
|
|
82
383
|
className: PropTypes.string,
|
|
83
|
-
style: PropTypes.object,
|
|
84
384
|
options: PropTypes.oneOfType([PropTypes.arrayOf(optionType), PropTypes.arrayOf(optionsGroupType)]),
|
|
85
385
|
value: optionType,
|
|
86
386
|
onChange: PropTypes.func,
|
|
@@ -96,8 +396,8 @@ Select.propTypes = {
|
|
|
96
396
|
label: PropTypes.node,
|
|
97
397
|
id: PropTypes.string,
|
|
98
398
|
note: PropTypes.node,
|
|
99
|
-
noOptionsMessage:
|
|
399
|
+
noOptionsMessage: PropTypes.oneOfType([PropTypes.string, noOptionsMessageConfigType]),
|
|
100
400
|
placement: PropTypes.oneOf(['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']),
|
|
101
401
|
name: PropTypes.string
|
|
102
402
|
};
|
|
103
|
-
export { Select };
|
|
403
|
+
export { Select as default };
|