@rc-component/select 1.0.0
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/LICENSE.md +9 -0
- package/README.md +191 -0
- package/assets/index.css +306 -0
- package/assets/index.less +397 -0
- package/es/BaseSelect/Polite.d.ts +7 -0
- package/es/BaseSelect/Polite.js +26 -0
- package/es/BaseSelect/index.d.ts +118 -0
- package/es/BaseSelect/index.js +569 -0
- package/es/OptGroup.d.ts +12 -0
- package/es/OptGroup.js +6 -0
- package/es/Option.d.ts +14 -0
- package/es/Option.js +6 -0
- package/es/OptionList.d.ts +10 -0
- package/es/OptionList.js +379 -0
- package/es/Select.d.ts +114 -0
- package/es/Select.js +480 -0
- package/es/SelectContext.d.ts +23 -0
- package/es/SelectContext.js +6 -0
- package/es/SelectTrigger.d.ts +30 -0
- package/es/SelectTrigger.js +138 -0
- package/es/Selector/Input.d.ts +27 -0
- package/es/Selector/Input.js +114 -0
- package/es/Selector/MultipleSelector.d.ts +16 -0
- package/es/Selector/MultipleSelector.js +185 -0
- package/es/Selector/SingleSelector.d.ts +8 -0
- package/es/Selector/SingleSelector.js +104 -0
- package/es/Selector/index.d.ts +85 -0
- package/es/Selector/index.js +184 -0
- package/es/TransBtn.d.ts +12 -0
- package/es/TransBtn.js +30 -0
- package/es/hooks/useAllowClear.d.ts +8 -0
- package/es/hooks/useAllowClear.js +26 -0
- package/es/hooks/useBaseProps.d.ts +13 -0
- package/es/hooks/useBaseProps.js +10 -0
- package/es/hooks/useCache.d.ts +7 -0
- package/es/hooks/useCache.js +40 -0
- package/es/hooks/useDelayReset.d.ts +5 -0
- package/es/hooks/useDelayReset.js +24 -0
- package/es/hooks/useFilterOptions.d.ts +3 -0
- package/es/hooks/useFilterOptions.js +57 -0
- package/es/hooks/useId.d.ts +5 -0
- package/es/hooks/useId.js +29 -0
- package/es/hooks/useLayoutEffect.d.ts +5 -0
- package/es/hooks/useLayoutEffect.js +17 -0
- package/es/hooks/useLock.d.ts +7 -0
- package/es/hooks/useLock.js +27 -0
- package/es/hooks/useOptions.d.ts +12 -0
- package/es/hooks/useOptions.js +45 -0
- package/es/hooks/useRefFunc.d.ts +5 -0
- package/es/hooks/useRefFunc.js +14 -0
- package/es/hooks/useSelectTriggerControl.d.ts +1 -0
- package/es/hooks/useSelectTriggerControl.js +27 -0
- package/es/index.d.ts +10 -0
- package/es/index.js +7 -0
- package/es/interface.d.ts +23 -0
- package/es/interface.js +1 -0
- package/es/utils/__mocks__/platformUtil.d.ts +1 -0
- package/es/utils/__mocks__/platformUtil.js +3 -0
- package/es/utils/commonUtil.d.ts +9 -0
- package/es/utils/commonUtil.js +32 -0
- package/es/utils/keyUtil.d.ts +2 -0
- package/es/utils/keyUtil.js +16 -0
- package/es/utils/legacyUtil.d.ts +3 -0
- package/es/utils/legacyUtil.js +44 -0
- package/es/utils/platformUtil.d.ts +1 -0
- package/es/utils/platformUtil.js +4 -0
- package/es/utils/valueUtil.d.ts +24 -0
- package/es/utils/valueUtil.js +128 -0
- package/es/utils/warningPropsUtil.d.ts +4 -0
- package/es/utils/warningPropsUtil.js +119 -0
- package/lib/BaseSelect/Polite.d.ts +7 -0
- package/lib/BaseSelect/Polite.js +34 -0
- package/lib/BaseSelect/index.d.ts +118 -0
- package/lib/BaseSelect/index.js +579 -0
- package/lib/OptGroup.d.ts +12 -0
- package/lib/OptGroup.js +12 -0
- package/lib/Option.d.ts +14 -0
- package/lib/Option.js +12 -0
- package/lib/OptionList.d.ts +10 -0
- package/lib/OptionList.js +387 -0
- package/lib/Select.d.ts +114 -0
- package/lib/Select.js +487 -0
- package/lib/SelectContext.d.ts +23 -0
- package/lib/SelectContext.js +13 -0
- package/lib/SelectTrigger.d.ts +30 -0
- package/lib/SelectTrigger.js +147 -0
- package/lib/Selector/Input.d.ts +27 -0
- package/lib/Selector/Input.js +123 -0
- package/lib/Selector/MultipleSelector.d.ts +16 -0
- package/lib/Selector/MultipleSelector.js +194 -0
- package/lib/Selector/SingleSelector.d.ts +8 -0
- package/lib/Selector/SingleSelector.js +113 -0
- package/lib/Selector/index.d.ts +85 -0
- package/lib/Selector/index.js +191 -0
- package/lib/TransBtn.d.ts +12 -0
- package/lib/TransBtn.js +39 -0
- package/lib/hooks/useAllowClear.d.ts +8 -0
- package/lib/hooks/useAllowClear.js +34 -0
- package/lib/hooks/useBaseProps.d.ts +13 -0
- package/lib/hooks/useBaseProps.js +19 -0
- package/lib/hooks/useCache.d.ts +7 -0
- package/lib/hooks/useCache.js +49 -0
- package/lib/hooks/useDelayReset.d.ts +5 -0
- package/lib/hooks/useDelayReset.js +31 -0
- package/lib/hooks/useFilterOptions.d.ts +3 -0
- package/lib/hooks/useFilterOptions.js +66 -0
- package/lib/hooks/useId.d.ts +5 -0
- package/lib/hooks/useId.js +40 -0
- package/lib/hooks/useLayoutEffect.d.ts +5 -0
- package/lib/hooks/useLayoutEffect.js +25 -0
- package/lib/hooks/useLock.d.ts +7 -0
- package/lib/hooks/useLock.js +34 -0
- package/lib/hooks/useOptions.d.ts +12 -0
- package/lib/hooks/useOptions.js +52 -0
- package/lib/hooks/useRefFunc.d.ts +5 -0
- package/lib/hooks/useRefFunc.js +21 -0
- package/lib/hooks/useSelectTriggerControl.d.ts +1 -0
- package/lib/hooks/useSelectTriggerControl.js +35 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.js +37 -0
- package/lib/interface.d.ts +23 -0
- package/lib/interface.js +5 -0
- package/lib/utils/__mocks__/platformUtil.d.ts +1 -0
- package/lib/utils/__mocks__/platformUtil.js +9 -0
- package/lib/utils/commonUtil.d.ts +9 -0
- package/lib/utils/commonUtil.js +42 -0
- package/lib/utils/keyUtil.d.ts +2 -0
- package/lib/utils/keyUtil.js +22 -0
- package/lib/utils/legacyUtil.d.ts +3 -0
- package/lib/utils/legacyUtil.js +53 -0
- package/lib/utils/platformUtil.d.ts +1 -0
- package/lib/utils/platformUtil.js +10 -0
- package/lib/utils/valueUtil.d.ts +24 -0
- package/lib/utils/valueUtil.js +140 -0
- package/lib/utils/warningPropsUtil.d.ts +4 -0
- package/lib/utils/warningPropsUtil.js +129 -0
- package/package.json +86 -0
package/es/Select.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
|
+
/**
|
|
3
|
+
* To match accessibility requirement, we always provide an input in the component.
|
|
4
|
+
* Other element will not set `tabIndex` to avoid `onBlur` sequence problem.
|
|
5
|
+
* For focused select, we set `aria-live="polite"` to update the accessibility content.
|
|
6
|
+
*
|
|
7
|
+
* ref:
|
|
8
|
+
* - keyboard: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role#Keyboard_interactions
|
|
9
|
+
*
|
|
10
|
+
* New api:
|
|
11
|
+
* - listHeight
|
|
12
|
+
* - listItemHeight
|
|
13
|
+
* - component
|
|
14
|
+
*
|
|
15
|
+
* Remove deprecated api:
|
|
16
|
+
* - multiple
|
|
17
|
+
* - tags
|
|
18
|
+
* - combobox
|
|
19
|
+
* - firstActiveValue
|
|
20
|
+
* - dropdownMenuStyle
|
|
21
|
+
* - openClassName (Not list in api)
|
|
22
|
+
*
|
|
23
|
+
* Update:
|
|
24
|
+
* - `backfill` only support `combobox` mode
|
|
25
|
+
* - `combobox` mode not support `labelInValue` since it's meaningless
|
|
26
|
+
* - `getInputElement` only support `combobox` mode
|
|
27
|
+
* - `onChange` return OptionData instead of ReactNode
|
|
28
|
+
* - `filterOption` `onChange` `onSelect` accept OptionData instead of ReactNode
|
|
29
|
+
* - `combobox` mode trigger `onChange` will get `undefined` if no `value` match in Option
|
|
30
|
+
* - `combobox` mode not support `optionLabelProp`
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import useMergedState from "@rc-component/util/es/hooks/useMergedState";
|
|
34
|
+
import warning from "@rc-component/util/es/warning";
|
|
35
|
+
import * as React from 'react';
|
|
36
|
+
import BaseSelect, { isMultiple } from "./BaseSelect";
|
|
37
|
+
import OptGroup from "./OptGroup";
|
|
38
|
+
import Option from "./Option";
|
|
39
|
+
import OptionList from "./OptionList";
|
|
40
|
+
import SelectContext from "./SelectContext";
|
|
41
|
+
import useCache from "./hooks/useCache";
|
|
42
|
+
import useFilterOptions from "./hooks/useFilterOptions";
|
|
43
|
+
import useId from "./hooks/useId";
|
|
44
|
+
import useOptions from "./hooks/useOptions";
|
|
45
|
+
import useRefFunc from "./hooks/useRefFunc";
|
|
46
|
+
import { hasValue, isComboNoValue, toArray } from "./utils/commonUtil";
|
|
47
|
+
import { fillFieldNames, flattenOptions, injectPropsWithOption } from "./utils/valueUtil";
|
|
48
|
+
import warningProps, { warningNullOptions } from "./utils/warningPropsUtil";
|
|
49
|
+
const OMIT_DOM_PROPS = ['inputValue'];
|
|
50
|
+
function isRawValue(value) {
|
|
51
|
+
return !value || typeof value !== 'object';
|
|
52
|
+
}
|
|
53
|
+
const Select = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
54
|
+
const {
|
|
55
|
+
id,
|
|
56
|
+
mode,
|
|
57
|
+
prefixCls = 'rc-select',
|
|
58
|
+
backfill,
|
|
59
|
+
fieldNames,
|
|
60
|
+
// Search
|
|
61
|
+
searchValue,
|
|
62
|
+
onSearch,
|
|
63
|
+
autoClearSearchValue = true,
|
|
64
|
+
// Select
|
|
65
|
+
onSelect,
|
|
66
|
+
onDeselect,
|
|
67
|
+
popupMatchSelectWidth = true,
|
|
68
|
+
// Options
|
|
69
|
+
filterOption,
|
|
70
|
+
filterSort,
|
|
71
|
+
optionFilterProp,
|
|
72
|
+
optionLabelProp,
|
|
73
|
+
options,
|
|
74
|
+
optionRender,
|
|
75
|
+
children,
|
|
76
|
+
defaultActiveFirstOption,
|
|
77
|
+
menuItemSelectedIcon,
|
|
78
|
+
virtual,
|
|
79
|
+
direction,
|
|
80
|
+
listHeight = 200,
|
|
81
|
+
listItemHeight = 20,
|
|
82
|
+
labelRender,
|
|
83
|
+
// Value
|
|
84
|
+
value,
|
|
85
|
+
defaultValue,
|
|
86
|
+
labelInValue,
|
|
87
|
+
onChange,
|
|
88
|
+
maxCount,
|
|
89
|
+
...restProps
|
|
90
|
+
} = props;
|
|
91
|
+
const mergedId = useId(id);
|
|
92
|
+
const multiple = isMultiple(mode);
|
|
93
|
+
const childrenAsData = !!(!options && children);
|
|
94
|
+
const mergedFilterOption = React.useMemo(() => {
|
|
95
|
+
if (filterOption === undefined && mode === 'combobox') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return filterOption;
|
|
99
|
+
}, [filterOption, mode]);
|
|
100
|
+
|
|
101
|
+
// ========================= FieldNames =========================
|
|
102
|
+
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames, childrenAsData), /* eslint-disable react-hooks/exhaustive-deps */
|
|
103
|
+
[
|
|
104
|
+
// We stringify fieldNames to avoid unnecessary re-renders.
|
|
105
|
+
JSON.stringify(fieldNames), childrenAsData]
|
|
106
|
+
/* eslint-enable react-hooks/exhaustive-deps */);
|
|
107
|
+
|
|
108
|
+
// =========================== Search ===========================
|
|
109
|
+
const [mergedSearchValue, setSearchValue] = useMergedState('', {
|
|
110
|
+
value: searchValue,
|
|
111
|
+
postState: search => search || ''
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// =========================== Option ===========================
|
|
115
|
+
const parsedOptions = useOptions(options, children, mergedFieldNames, optionFilterProp, optionLabelProp);
|
|
116
|
+
const {
|
|
117
|
+
valueOptions,
|
|
118
|
+
labelOptions,
|
|
119
|
+
options: mergedOptions
|
|
120
|
+
} = parsedOptions;
|
|
121
|
+
|
|
122
|
+
// ========================= Wrap Value =========================
|
|
123
|
+
const convert2LabelValues = React.useCallback(draftValues => {
|
|
124
|
+
// Convert to array
|
|
125
|
+
const valueList = toArray(draftValues);
|
|
126
|
+
|
|
127
|
+
// Convert to labelInValue type
|
|
128
|
+
return valueList.map(val => {
|
|
129
|
+
let rawValue;
|
|
130
|
+
let rawLabel;
|
|
131
|
+
let rawDisabled;
|
|
132
|
+
let rawTitle;
|
|
133
|
+
|
|
134
|
+
// Fill label & value
|
|
135
|
+
if (isRawValue(val)) {
|
|
136
|
+
rawValue = val;
|
|
137
|
+
} else {
|
|
138
|
+
rawLabel = val.label;
|
|
139
|
+
rawValue = val.value;
|
|
140
|
+
}
|
|
141
|
+
const option = valueOptions.get(rawValue);
|
|
142
|
+
if (option) {
|
|
143
|
+
// Fill missing props
|
|
144
|
+
if (rawLabel === undefined) rawLabel = option?.[optionLabelProp || mergedFieldNames.label];
|
|
145
|
+
rawDisabled = option?.disabled;
|
|
146
|
+
rawTitle = option?.title;
|
|
147
|
+
|
|
148
|
+
// Warning if label not same as provided
|
|
149
|
+
if (process.env.NODE_ENV !== 'production' && !optionLabelProp) {
|
|
150
|
+
const optionLabel = option?.[mergedFieldNames.label];
|
|
151
|
+
if (optionLabel !== undefined && ! /*#__PURE__*/React.isValidElement(optionLabel) && ! /*#__PURE__*/React.isValidElement(rawLabel) && optionLabel !== rawLabel) {
|
|
152
|
+
warning(false, '`label` of `value` is not same as `label` in Select options.');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
label: rawLabel,
|
|
158
|
+
value: rawValue,
|
|
159
|
+
key: rawValue,
|
|
160
|
+
disabled: rawDisabled,
|
|
161
|
+
title: rawTitle
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}, [mergedFieldNames, optionLabelProp, valueOptions]);
|
|
165
|
+
|
|
166
|
+
// =========================== Values ===========================
|
|
167
|
+
const [internalValue, setInternalValue] = useMergedState(defaultValue, {
|
|
168
|
+
value
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Merged value with LabelValueType
|
|
172
|
+
const rawLabeledValues = React.useMemo(() => {
|
|
173
|
+
const newInternalValue = multiple && internalValue === null ? [] : internalValue;
|
|
174
|
+
const values = convert2LabelValues(newInternalValue);
|
|
175
|
+
|
|
176
|
+
// combobox no need save value when it's no value (exclude value equal 0)
|
|
177
|
+
if (mode === 'combobox' && isComboNoValue(values[0]?.value)) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
return values;
|
|
181
|
+
}, [internalValue, convert2LabelValues, mode, multiple]);
|
|
182
|
+
|
|
183
|
+
// Fill label with cache to avoid option remove
|
|
184
|
+
const [mergedValues, getMixedOption] = useCache(rawLabeledValues, valueOptions);
|
|
185
|
+
const displayValues = React.useMemo(() => {
|
|
186
|
+
// `null` need show as placeholder instead
|
|
187
|
+
// https://github.com/ant-design/ant-design/issues/25057
|
|
188
|
+
if (!mode && mergedValues.length === 1) {
|
|
189
|
+
const firstValue = mergedValues[0];
|
|
190
|
+
if (firstValue.value === null && (firstValue.label === null || firstValue.label === undefined)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return mergedValues.map(item => ({
|
|
195
|
+
...item,
|
|
196
|
+
label: (typeof labelRender === 'function' ? labelRender(item) : item.label) ?? item.value
|
|
197
|
+
}));
|
|
198
|
+
}, [mode, mergedValues, labelRender]);
|
|
199
|
+
|
|
200
|
+
/** Convert `displayValues` to raw value type set */
|
|
201
|
+
const rawValues = React.useMemo(() => new Set(mergedValues.map(val => val.value)), [mergedValues]);
|
|
202
|
+
React.useEffect(() => {
|
|
203
|
+
if (mode === 'combobox') {
|
|
204
|
+
const strValue = mergedValues[0]?.value;
|
|
205
|
+
setSearchValue(hasValue(strValue) ? String(strValue) : '');
|
|
206
|
+
}
|
|
207
|
+
}, [mergedValues]);
|
|
208
|
+
|
|
209
|
+
// ======================= Display Option =======================
|
|
210
|
+
// Create a placeholder item if not exist in `options`
|
|
211
|
+
const createTagOption = useRefFunc((val, label) => {
|
|
212
|
+
const mergedLabel = label ?? val;
|
|
213
|
+
return {
|
|
214
|
+
[mergedFieldNames.value]: val,
|
|
215
|
+
[mergedFieldNames.label]: mergedLabel
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Fill tag as option if mode is `tags`
|
|
220
|
+
const filledTagOptions = React.useMemo(() => {
|
|
221
|
+
if (mode !== 'tags') {
|
|
222
|
+
return mergedOptions;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// >>> Tag mode
|
|
226
|
+
const cloneOptions = [...mergedOptions];
|
|
227
|
+
|
|
228
|
+
// Check if value exist in options (include new patch item)
|
|
229
|
+
const existOptions = val => valueOptions.has(val);
|
|
230
|
+
|
|
231
|
+
// Fill current value as option
|
|
232
|
+
[...mergedValues].sort((a, b) => a.value < b.value ? -1 : 1).forEach(item => {
|
|
233
|
+
const val = item.value;
|
|
234
|
+
if (!existOptions(val)) {
|
|
235
|
+
cloneOptions.push(createTagOption(val, item.label));
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
return cloneOptions;
|
|
239
|
+
}, [createTagOption, mergedOptions, valueOptions, mergedValues, mode]);
|
|
240
|
+
const filteredOptions = useFilterOptions(filledTagOptions, mergedFieldNames, mergedSearchValue, mergedFilterOption, optionFilterProp);
|
|
241
|
+
|
|
242
|
+
// Fill options with search value if needed
|
|
243
|
+
const filledSearchOptions = React.useMemo(() => {
|
|
244
|
+
if (mode !== 'tags' || !mergedSearchValue || filteredOptions.some(item => item[optionFilterProp || 'value'] === mergedSearchValue)) {
|
|
245
|
+
return filteredOptions;
|
|
246
|
+
}
|
|
247
|
+
// ignore when search value equal select input value
|
|
248
|
+
if (filteredOptions.some(item => item[mergedFieldNames.value] === mergedSearchValue)) {
|
|
249
|
+
return filteredOptions;
|
|
250
|
+
}
|
|
251
|
+
// Fill search value as option
|
|
252
|
+
return [createTagOption(mergedSearchValue), ...filteredOptions];
|
|
253
|
+
}, [createTagOption, optionFilterProp, mode, filteredOptions, mergedSearchValue, mergedFieldNames]);
|
|
254
|
+
const sorter = inputOptions => {
|
|
255
|
+
const sortedOptions = [...inputOptions].sort((a, b) => filterSort(a, b, {
|
|
256
|
+
searchValue: mergedSearchValue
|
|
257
|
+
}));
|
|
258
|
+
return sortedOptions.map(item => {
|
|
259
|
+
if (Array.isArray(item.options)) {
|
|
260
|
+
return {
|
|
261
|
+
...item,
|
|
262
|
+
options: item.options.length > 0 ? sorter(item.options) : item.options
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return item;
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
const orderedFilteredOptions = React.useMemo(() => {
|
|
269
|
+
if (!filterSort) {
|
|
270
|
+
return filledSearchOptions;
|
|
271
|
+
}
|
|
272
|
+
return sorter(filledSearchOptions);
|
|
273
|
+
}, [filledSearchOptions, filterSort, mergedSearchValue]);
|
|
274
|
+
const displayOptions = React.useMemo(() => flattenOptions(orderedFilteredOptions, {
|
|
275
|
+
fieldNames: mergedFieldNames,
|
|
276
|
+
childrenAsData
|
|
277
|
+
}), [orderedFilteredOptions, mergedFieldNames, childrenAsData]);
|
|
278
|
+
|
|
279
|
+
// =========================== Change ===========================
|
|
280
|
+
const triggerChange = values => {
|
|
281
|
+
const labeledValues = convert2LabelValues(values);
|
|
282
|
+
setInternalValue(labeledValues);
|
|
283
|
+
if (onChange && (
|
|
284
|
+
// Trigger event only when value changed
|
|
285
|
+
labeledValues.length !== mergedValues.length || labeledValues.some((newVal, index) => mergedValues[index]?.value !== newVal?.value))) {
|
|
286
|
+
const returnValues = labelInValue ? labeledValues.map(({
|
|
287
|
+
label: l,
|
|
288
|
+
value: v
|
|
289
|
+
}) => ({
|
|
290
|
+
label: l,
|
|
291
|
+
value: v
|
|
292
|
+
})) : labeledValues.map(v => v.value);
|
|
293
|
+
const returnOptions = labeledValues.map(v => injectPropsWithOption(getMixedOption(v.value)));
|
|
294
|
+
onChange(
|
|
295
|
+
// Value
|
|
296
|
+
multiple ? returnValues : returnValues[0],
|
|
297
|
+
// Option
|
|
298
|
+
multiple ? returnOptions : returnOptions[0]);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// ======================= Accessibility ========================
|
|
303
|
+
const [activeValue, setActiveValue] = React.useState(null);
|
|
304
|
+
const [accessibilityIndex, setAccessibilityIndex] = React.useState(0);
|
|
305
|
+
const mergedDefaultActiveFirstOption = defaultActiveFirstOption !== undefined ? defaultActiveFirstOption : mode !== 'combobox';
|
|
306
|
+
const onActiveValue = React.useCallback((active, index, {
|
|
307
|
+
source = 'keyboard'
|
|
308
|
+
} = {}) => {
|
|
309
|
+
setAccessibilityIndex(index);
|
|
310
|
+
if (backfill && mode === 'combobox' && active !== null && source === 'keyboard') {
|
|
311
|
+
setActiveValue(String(active));
|
|
312
|
+
}
|
|
313
|
+
}, [backfill, mode]);
|
|
314
|
+
|
|
315
|
+
// ========================= OptionList =========================
|
|
316
|
+
const triggerSelect = (val, selected, type) => {
|
|
317
|
+
const getSelectEnt = () => {
|
|
318
|
+
const option = getMixedOption(val);
|
|
319
|
+
return [labelInValue ? {
|
|
320
|
+
label: option?.[mergedFieldNames.label],
|
|
321
|
+
value: val
|
|
322
|
+
} : val, injectPropsWithOption(option)];
|
|
323
|
+
};
|
|
324
|
+
if (selected && onSelect) {
|
|
325
|
+
const [wrappedValue, option] = getSelectEnt();
|
|
326
|
+
onSelect(wrappedValue, option);
|
|
327
|
+
} else if (!selected && onDeselect && type !== 'clear') {
|
|
328
|
+
const [wrappedValue, option] = getSelectEnt();
|
|
329
|
+
onDeselect(wrappedValue, option);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Used for OptionList selection
|
|
334
|
+
const onInternalSelect = useRefFunc((val, info) => {
|
|
335
|
+
let cloneValues;
|
|
336
|
+
|
|
337
|
+
// Single mode always trigger select only with option list
|
|
338
|
+
const mergedSelect = multiple ? info.selected : true;
|
|
339
|
+
if (mergedSelect) {
|
|
340
|
+
cloneValues = multiple ? [...mergedValues, val] : [val];
|
|
341
|
+
} else {
|
|
342
|
+
cloneValues = mergedValues.filter(v => v.value !== val);
|
|
343
|
+
}
|
|
344
|
+
triggerChange(cloneValues);
|
|
345
|
+
triggerSelect(val, mergedSelect);
|
|
346
|
+
|
|
347
|
+
// Clean search value if single or configured
|
|
348
|
+
if (mode === 'combobox') {
|
|
349
|
+
setActiveValue('');
|
|
350
|
+
} else if (!isMultiple || autoClearSearchValue) {
|
|
351
|
+
setSearchValue('');
|
|
352
|
+
setActiveValue('');
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ======================= Display Change =======================
|
|
357
|
+
// BaseSelect display values change
|
|
358
|
+
const onDisplayValuesChange = (nextValues, info) => {
|
|
359
|
+
triggerChange(nextValues);
|
|
360
|
+
const {
|
|
361
|
+
type,
|
|
362
|
+
values
|
|
363
|
+
} = info;
|
|
364
|
+
if (type === 'remove' || type === 'clear') {
|
|
365
|
+
values.forEach(item => {
|
|
366
|
+
triggerSelect(item.value, false, type);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// =========================== Search ===========================
|
|
372
|
+
const onInternalSearch = (searchText, info) => {
|
|
373
|
+
setSearchValue(searchText);
|
|
374
|
+
setActiveValue(null);
|
|
375
|
+
|
|
376
|
+
// [Submit] Tag mode should flush input
|
|
377
|
+
if (info.source === 'submit') {
|
|
378
|
+
const formatted = (searchText || '').trim();
|
|
379
|
+
// prevent empty tags from appearing when you click the Enter button
|
|
380
|
+
if (formatted) {
|
|
381
|
+
const newRawValues = Array.from(new Set([...rawValues, formatted]));
|
|
382
|
+
triggerChange(newRawValues);
|
|
383
|
+
triggerSelect(formatted, true);
|
|
384
|
+
setSearchValue('');
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (info.source !== 'blur') {
|
|
389
|
+
if (mode === 'combobox') {
|
|
390
|
+
triggerChange(searchText);
|
|
391
|
+
}
|
|
392
|
+
onSearch?.(searchText);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const onInternalSearchSplit = words => {
|
|
396
|
+
let patchValues = words;
|
|
397
|
+
if (mode !== 'tags') {
|
|
398
|
+
patchValues = words.map(word => {
|
|
399
|
+
const opt = labelOptions.get(word);
|
|
400
|
+
return opt?.value;
|
|
401
|
+
}).filter(val => val !== undefined);
|
|
402
|
+
}
|
|
403
|
+
const newRawValues = Array.from(new Set([...rawValues, ...patchValues]));
|
|
404
|
+
triggerChange(newRawValues);
|
|
405
|
+
newRawValues.forEach(newRawValue => {
|
|
406
|
+
triggerSelect(newRawValue, true);
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// ========================== Context ===========================
|
|
411
|
+
const selectContext = React.useMemo(() => {
|
|
412
|
+
const realVirtual = virtual !== false && popupMatchSelectWidth !== false;
|
|
413
|
+
return {
|
|
414
|
+
...parsedOptions,
|
|
415
|
+
flattenOptions: displayOptions,
|
|
416
|
+
onActiveValue,
|
|
417
|
+
defaultActiveFirstOption: mergedDefaultActiveFirstOption,
|
|
418
|
+
onSelect: onInternalSelect,
|
|
419
|
+
menuItemSelectedIcon,
|
|
420
|
+
rawValues,
|
|
421
|
+
fieldNames: mergedFieldNames,
|
|
422
|
+
virtual: realVirtual,
|
|
423
|
+
direction,
|
|
424
|
+
listHeight,
|
|
425
|
+
listItemHeight,
|
|
426
|
+
childrenAsData,
|
|
427
|
+
maxCount,
|
|
428
|
+
optionRender
|
|
429
|
+
};
|
|
430
|
+
}, [maxCount, parsedOptions, displayOptions, onActiveValue, mergedDefaultActiveFirstOption, onInternalSelect, menuItemSelectedIcon, rawValues, mergedFieldNames, virtual, popupMatchSelectWidth, direction, listHeight, listItemHeight, childrenAsData, optionRender]);
|
|
431
|
+
|
|
432
|
+
// ========================== Warning ===========================
|
|
433
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
434
|
+
warningProps(props);
|
|
435
|
+
warningNullOptions(mergedOptions, mergedFieldNames);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ==============================================================
|
|
439
|
+
// == Render ==
|
|
440
|
+
// ==============================================================
|
|
441
|
+
return /*#__PURE__*/React.createElement(SelectContext.Provider, {
|
|
442
|
+
value: selectContext
|
|
443
|
+
}, /*#__PURE__*/React.createElement(BaseSelect, _extends({}, restProps, {
|
|
444
|
+
// >>> MISC
|
|
445
|
+
id: mergedId,
|
|
446
|
+
prefixCls: prefixCls,
|
|
447
|
+
ref: ref,
|
|
448
|
+
omitDomProps: OMIT_DOM_PROPS,
|
|
449
|
+
mode: mode
|
|
450
|
+
// >>> Values
|
|
451
|
+
,
|
|
452
|
+
displayValues: displayValues,
|
|
453
|
+
onDisplayValuesChange: onDisplayValuesChange
|
|
454
|
+
// >>> Trigger
|
|
455
|
+
,
|
|
456
|
+
direction: direction
|
|
457
|
+
// >>> Search
|
|
458
|
+
,
|
|
459
|
+
searchValue: mergedSearchValue,
|
|
460
|
+
onSearch: onInternalSearch,
|
|
461
|
+
autoClearSearchValue: autoClearSearchValue,
|
|
462
|
+
onSearchSplit: onInternalSearchSplit,
|
|
463
|
+
popupMatchSelectWidth: popupMatchSelectWidth
|
|
464
|
+
// >>> OptionList
|
|
465
|
+
,
|
|
466
|
+
OptionList: OptionList,
|
|
467
|
+
emptyOptions: !displayOptions.length
|
|
468
|
+
// >>> Accessibility
|
|
469
|
+
,
|
|
470
|
+
activeValue: activeValue,
|
|
471
|
+
activeDescendantId: `${mergedId}_list_${accessibilityIndex}`
|
|
472
|
+
})));
|
|
473
|
+
});
|
|
474
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
475
|
+
Select.displayName = 'Select';
|
|
476
|
+
}
|
|
477
|
+
const TypedSelect = Select;
|
|
478
|
+
TypedSelect.Option = Option;
|
|
479
|
+
TypedSelect.OptGroup = OptGroup;
|
|
480
|
+
export default TypedSelect;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RawValueType, RenderNode } from './BaseSelect';
|
|
3
|
+
import type { BaseOptionType, FieldNames, OnActiveValue, OnInternalSelect, SelectProps } from './Select';
|
|
4
|
+
import type { FlattenOptionData } from './interface';
|
|
5
|
+
export interface SelectContextProps {
|
|
6
|
+
options: BaseOptionType[];
|
|
7
|
+
optionRender?: SelectProps['optionRender'];
|
|
8
|
+
flattenOptions: FlattenOptionData<BaseOptionType>[];
|
|
9
|
+
onActiveValue: OnActiveValue;
|
|
10
|
+
defaultActiveFirstOption?: boolean;
|
|
11
|
+
onSelect: OnInternalSelect;
|
|
12
|
+
menuItemSelectedIcon?: RenderNode;
|
|
13
|
+
rawValues: Set<RawValueType>;
|
|
14
|
+
fieldNames?: FieldNames;
|
|
15
|
+
virtual?: boolean;
|
|
16
|
+
direction?: 'ltr' | 'rtl';
|
|
17
|
+
listHeight?: number;
|
|
18
|
+
listItemHeight?: number;
|
|
19
|
+
childrenAsData?: boolean;
|
|
20
|
+
maxCount?: number;
|
|
21
|
+
}
|
|
22
|
+
declare const SelectContext: React.Context<SelectContextProps>;
|
|
23
|
+
export default SelectContext;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AlignType, BuildInPlacements } from '@rc-component/trigger/lib/interface';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import type { Placement, RenderDOMFunc } from './BaseSelect';
|
|
4
|
+
export interface RefTriggerProps {
|
|
5
|
+
getPopupElement: () => HTMLDivElement;
|
|
6
|
+
}
|
|
7
|
+
export interface SelectTriggerProps {
|
|
8
|
+
prefixCls: string;
|
|
9
|
+
children: React.ReactElement;
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
visible: boolean;
|
|
12
|
+
popupElement: React.ReactElement;
|
|
13
|
+
animation?: string;
|
|
14
|
+
transitionName?: string;
|
|
15
|
+
placement?: Placement;
|
|
16
|
+
builtinPlacements?: BuildInPlacements;
|
|
17
|
+
popupStyle: React.CSSProperties;
|
|
18
|
+
popupClassName: string;
|
|
19
|
+
direction: string;
|
|
20
|
+
popupMatchSelectWidth?: boolean | number;
|
|
21
|
+
popupRender?: (menu: React.ReactElement) => React.ReactElement;
|
|
22
|
+
getPopupContainer?: RenderDOMFunc;
|
|
23
|
+
popupAlign: AlignType;
|
|
24
|
+
empty: boolean;
|
|
25
|
+
getTriggerDOMNode: (node: HTMLElement) => HTMLElement;
|
|
26
|
+
onPopupVisibleChange?: (visible: boolean) => void;
|
|
27
|
+
onPopupMouseEnter: () => void;
|
|
28
|
+
}
|
|
29
|
+
declare const RefSelectTrigger: React.ForwardRefExoticComponent<SelectTriggerProps & React.RefAttributes<RefTriggerProps>>;
|
|
30
|
+
export default RefSelectTrigger;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
|
+
import Trigger from '@rc-component/trigger';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
const getBuiltInPlacements = popupMatchSelectWidth => {
|
|
6
|
+
// Enable horizontal overflow auto-adjustment when a custom dropdown width is provided
|
|
7
|
+
const adjustX = popupMatchSelectWidth === true ? 0 : 1;
|
|
8
|
+
return {
|
|
9
|
+
bottomLeft: {
|
|
10
|
+
points: ['tl', 'bl'],
|
|
11
|
+
offset: [0, 4],
|
|
12
|
+
overflow: {
|
|
13
|
+
adjustX,
|
|
14
|
+
adjustY: 1
|
|
15
|
+
},
|
|
16
|
+
htmlRegion: 'scroll'
|
|
17
|
+
},
|
|
18
|
+
bottomRight: {
|
|
19
|
+
points: ['tr', 'br'],
|
|
20
|
+
offset: [0, 4],
|
|
21
|
+
overflow: {
|
|
22
|
+
adjustX,
|
|
23
|
+
adjustY: 1
|
|
24
|
+
},
|
|
25
|
+
htmlRegion: 'scroll'
|
|
26
|
+
},
|
|
27
|
+
topLeft: {
|
|
28
|
+
points: ['bl', 'tl'],
|
|
29
|
+
offset: [0, -4],
|
|
30
|
+
overflow: {
|
|
31
|
+
adjustX,
|
|
32
|
+
adjustY: 1
|
|
33
|
+
},
|
|
34
|
+
htmlRegion: 'scroll'
|
|
35
|
+
},
|
|
36
|
+
topRight: {
|
|
37
|
+
points: ['br', 'tr'],
|
|
38
|
+
offset: [0, -4],
|
|
39
|
+
overflow: {
|
|
40
|
+
adjustX,
|
|
41
|
+
adjustY: 1
|
|
42
|
+
},
|
|
43
|
+
htmlRegion: 'scroll'
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const SelectTrigger = (props, ref) => {
|
|
48
|
+
const {
|
|
49
|
+
prefixCls,
|
|
50
|
+
disabled,
|
|
51
|
+
visible,
|
|
52
|
+
children,
|
|
53
|
+
popupElement,
|
|
54
|
+
animation,
|
|
55
|
+
transitionName,
|
|
56
|
+
popupStyle,
|
|
57
|
+
popupClassName,
|
|
58
|
+
direction = 'ltr',
|
|
59
|
+
placement,
|
|
60
|
+
builtinPlacements,
|
|
61
|
+
popupMatchSelectWidth,
|
|
62
|
+
popupRender,
|
|
63
|
+
popupAlign,
|
|
64
|
+
getPopupContainer,
|
|
65
|
+
empty,
|
|
66
|
+
getTriggerDOMNode,
|
|
67
|
+
onPopupVisibleChange,
|
|
68
|
+
onPopupMouseEnter,
|
|
69
|
+
...restProps
|
|
70
|
+
} = props;
|
|
71
|
+
|
|
72
|
+
// We still use `dropdown` className to keep compatibility
|
|
73
|
+
// This is used for:
|
|
74
|
+
// 1. Styles
|
|
75
|
+
// 2. Animation
|
|
76
|
+
// 3. Theme customization
|
|
77
|
+
// Please do not modify this since it's a breaking change
|
|
78
|
+
const popupPrefixCls = `${prefixCls}-dropdown`;
|
|
79
|
+
let popupNode = popupElement;
|
|
80
|
+
if (popupRender) {
|
|
81
|
+
popupNode = popupRender(popupElement);
|
|
82
|
+
}
|
|
83
|
+
const mergedBuiltinPlacements = React.useMemo(() => builtinPlacements || getBuiltInPlacements(popupMatchSelectWidth), [builtinPlacements, popupMatchSelectWidth]);
|
|
84
|
+
|
|
85
|
+
// ===================== Motion ======================
|
|
86
|
+
const mergedTransitionName = animation ? `${popupPrefixCls}-${animation}` : transitionName;
|
|
87
|
+
|
|
88
|
+
// =================== Popup Width ===================
|
|
89
|
+
const isNumberPopupWidth = typeof popupMatchSelectWidth === 'number';
|
|
90
|
+
const stretch = React.useMemo(() => {
|
|
91
|
+
if (isNumberPopupWidth) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return popupMatchSelectWidth === false ? 'minWidth' : 'width';
|
|
95
|
+
}, [popupMatchSelectWidth, isNumberPopupWidth]);
|
|
96
|
+
let mergedPopupStyle = popupStyle;
|
|
97
|
+
if (isNumberPopupWidth) {
|
|
98
|
+
mergedPopupStyle = {
|
|
99
|
+
...popupStyle,
|
|
100
|
+
width: popupMatchSelectWidth
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ======================= Ref =======================
|
|
105
|
+
const triggerPopupRef = React.useRef(null);
|
|
106
|
+
React.useImperativeHandle(ref, () => ({
|
|
107
|
+
getPopupElement: () => triggerPopupRef.current?.popupElement
|
|
108
|
+
}));
|
|
109
|
+
return /*#__PURE__*/React.createElement(Trigger, _extends({}, restProps, {
|
|
110
|
+
showAction: onPopupVisibleChange ? ['click'] : [],
|
|
111
|
+
hideAction: onPopupVisibleChange ? ['click'] : [],
|
|
112
|
+
popupPlacement: placement || (direction === 'rtl' ? 'bottomRight' : 'bottomLeft'),
|
|
113
|
+
builtinPlacements: mergedBuiltinPlacements,
|
|
114
|
+
prefixCls: popupPrefixCls,
|
|
115
|
+
popupMotion: {
|
|
116
|
+
motionName: mergedTransitionName
|
|
117
|
+
},
|
|
118
|
+
popup: /*#__PURE__*/React.createElement("div", {
|
|
119
|
+
onMouseEnter: onPopupMouseEnter
|
|
120
|
+
}, popupNode),
|
|
121
|
+
ref: triggerPopupRef,
|
|
122
|
+
stretch: stretch,
|
|
123
|
+
popupAlign: popupAlign,
|
|
124
|
+
popupVisible: visible,
|
|
125
|
+
getPopupContainer: getPopupContainer,
|
|
126
|
+
popupClassName: classNames(popupClassName, {
|
|
127
|
+
[`${popupPrefixCls}-empty`]: empty
|
|
128
|
+
}),
|
|
129
|
+
popupStyle: mergedPopupStyle,
|
|
130
|
+
getTriggerDOMNode: getTriggerDOMNode,
|
|
131
|
+
onPopupVisibleChange: onPopupVisibleChange
|
|
132
|
+
}), children);
|
|
133
|
+
};
|
|
134
|
+
const RefSelectTrigger = /*#__PURE__*/React.forwardRef(SelectTrigger);
|
|
135
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
136
|
+
RefSelectTrigger.displayName = 'SelectTrigger';
|
|
137
|
+
}
|
|
138
|
+
export default RefSelectTrigger;
|