@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
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
* Cursor rule:
|
|
4
|
+
* 1. Only `showSearch` enabled
|
|
5
|
+
* 2. Only `open` is `true`
|
|
6
|
+
* 3. When typing, set `open` to `true` which hit rule of 2
|
|
7
|
+
*
|
|
8
|
+
* Accessibility:
|
|
9
|
+
* - https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import KeyCode from "@rc-component/util/es/KeyCode";
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import { useRef } from 'react';
|
|
15
|
+
import useLock from "../hooks/useLock";
|
|
16
|
+
import { isValidateOpenKey } from "../utils/keyUtil";
|
|
17
|
+
import MultipleSelector from "./MultipleSelector";
|
|
18
|
+
import SingleSelector from "./SingleSelector";
|
|
19
|
+
const Selector = (props, ref) => {
|
|
20
|
+
const inputRef = useRef(null);
|
|
21
|
+
const compositionStatusRef = useRef(false);
|
|
22
|
+
const {
|
|
23
|
+
prefixCls,
|
|
24
|
+
open,
|
|
25
|
+
mode,
|
|
26
|
+
showSearch,
|
|
27
|
+
tokenWithEnter,
|
|
28
|
+
disabled,
|
|
29
|
+
prefix,
|
|
30
|
+
autoClearSearchValue,
|
|
31
|
+
onSearch,
|
|
32
|
+
onSearchSubmit,
|
|
33
|
+
onToggleOpen,
|
|
34
|
+
onInputKeyDown,
|
|
35
|
+
onInputBlur,
|
|
36
|
+
domRef
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
// ======================= Ref =======================
|
|
40
|
+
React.useImperativeHandle(ref, () => ({
|
|
41
|
+
focus: options => {
|
|
42
|
+
inputRef.current.focus(options);
|
|
43
|
+
},
|
|
44
|
+
blur: () => {
|
|
45
|
+
inputRef.current.blur();
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// ====================== Input ======================
|
|
50
|
+
const [getInputMouseDown, setInputMouseDown] = useLock(0);
|
|
51
|
+
const onInternalInputKeyDown = event => {
|
|
52
|
+
const {
|
|
53
|
+
which
|
|
54
|
+
} = event;
|
|
55
|
+
|
|
56
|
+
// Compatible with multiple lines in TextArea
|
|
57
|
+
const isTextAreaElement = inputRef.current instanceof HTMLTextAreaElement;
|
|
58
|
+
if (!isTextAreaElement && open && (which === KeyCode.UP || which === KeyCode.DOWN)) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
}
|
|
61
|
+
if (onInputKeyDown) {
|
|
62
|
+
onInputKeyDown(event);
|
|
63
|
+
}
|
|
64
|
+
if (which === KeyCode.ENTER && mode === 'tags' && !compositionStatusRef.current && !open) {
|
|
65
|
+
// When menu isn't open, OptionList won't trigger a value change
|
|
66
|
+
// So when enter is pressed, the tag's input value should be emitted here to let selector know
|
|
67
|
+
onSearchSubmit?.(event.target.value);
|
|
68
|
+
}
|
|
69
|
+
// Move within the text box
|
|
70
|
+
if (isTextAreaElement && !open && ~[KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].indexOf(which)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (isValidateOpenKey(which)) {
|
|
74
|
+
onToggleOpen(true);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* We can not use `findDOMNode` sine it will get warning,
|
|
80
|
+
* have to use timer to check if is input element.
|
|
81
|
+
*/
|
|
82
|
+
const onInternalInputMouseDown = () => {
|
|
83
|
+
setInputMouseDown(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// When paste come, ignore next onChange
|
|
87
|
+
const pastedTextRef = useRef(null);
|
|
88
|
+
const triggerOnSearch = value => {
|
|
89
|
+
if (onSearch(value, true, compositionStatusRef.current) !== false) {
|
|
90
|
+
onToggleOpen(true);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const onInputCompositionStart = () => {
|
|
94
|
+
compositionStatusRef.current = true;
|
|
95
|
+
};
|
|
96
|
+
const onInputCompositionEnd = e => {
|
|
97
|
+
compositionStatusRef.current = false;
|
|
98
|
+
|
|
99
|
+
// Trigger search again to support `tokenSeparators` with typewriting
|
|
100
|
+
if (mode !== 'combobox') {
|
|
101
|
+
triggerOnSearch(e.target.value);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const onInputChange = event => {
|
|
105
|
+
let {
|
|
106
|
+
target: {
|
|
107
|
+
value
|
|
108
|
+
}
|
|
109
|
+
} = event;
|
|
110
|
+
|
|
111
|
+
// Pasted text should replace back to origin content
|
|
112
|
+
if (tokenWithEnter && pastedTextRef.current && /[\r\n]/.test(pastedTextRef.current)) {
|
|
113
|
+
// CRLF will be treated as a single space for input element
|
|
114
|
+
const replacedText = pastedTextRef.current.replace(/[\r\n]+$/, '').replace(/\r\n/g, ' ').replace(/[\r\n]/g, ' ');
|
|
115
|
+
value = value.replace(replacedText, pastedTextRef.current);
|
|
116
|
+
}
|
|
117
|
+
pastedTextRef.current = null;
|
|
118
|
+
triggerOnSearch(value);
|
|
119
|
+
};
|
|
120
|
+
const onInputPaste = e => {
|
|
121
|
+
const {
|
|
122
|
+
clipboardData
|
|
123
|
+
} = e;
|
|
124
|
+
const value = clipboardData?.getData('text');
|
|
125
|
+
pastedTextRef.current = value || '';
|
|
126
|
+
};
|
|
127
|
+
const onClick = ({
|
|
128
|
+
target
|
|
129
|
+
}) => {
|
|
130
|
+
if (target !== inputRef.current) {
|
|
131
|
+
// Should focus input if click the selector
|
|
132
|
+
const isIE = document.body.style.msTouchAction !== undefined;
|
|
133
|
+
if (isIE) {
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
inputRef.current.focus();
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
inputRef.current.focus();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const onMouseDown = event => {
|
|
143
|
+
const inputMouseDown = getInputMouseDown();
|
|
144
|
+
|
|
145
|
+
// when mode is combobox and it is disabled, don't prevent default behavior
|
|
146
|
+
// https://github.com/ant-design/ant-design/issues/37320
|
|
147
|
+
// https://github.com/ant-design/ant-design/issues/48281
|
|
148
|
+
if (event.target !== inputRef.current && !inputMouseDown && !(mode === 'combobox' && disabled)) {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
}
|
|
151
|
+
if (mode !== 'combobox' && (!showSearch || !inputMouseDown) || !open) {
|
|
152
|
+
if (open && autoClearSearchValue !== false) {
|
|
153
|
+
onSearch('', true, false);
|
|
154
|
+
}
|
|
155
|
+
onToggleOpen();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ================= Inner Selector ==================
|
|
160
|
+
const sharedProps = {
|
|
161
|
+
inputRef,
|
|
162
|
+
onInputKeyDown: onInternalInputKeyDown,
|
|
163
|
+
onInputMouseDown: onInternalInputMouseDown,
|
|
164
|
+
onInputChange,
|
|
165
|
+
onInputPaste,
|
|
166
|
+
onInputCompositionStart,
|
|
167
|
+
onInputCompositionEnd,
|
|
168
|
+
onInputBlur
|
|
169
|
+
};
|
|
170
|
+
const selectNode = mode === 'multiple' || mode === 'tags' ? /*#__PURE__*/React.createElement(MultipleSelector, _extends({}, props, sharedProps)) : /*#__PURE__*/React.createElement(SingleSelector, _extends({}, props, sharedProps));
|
|
171
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
172
|
+
ref: domRef,
|
|
173
|
+
className: `${prefixCls}-selector`,
|
|
174
|
+
onClick: onClick,
|
|
175
|
+
onMouseDown: onMouseDown
|
|
176
|
+
}, prefix && /*#__PURE__*/React.createElement("div", {
|
|
177
|
+
className: `${prefixCls}-prefix`
|
|
178
|
+
}, prefix), selectNode);
|
|
179
|
+
};
|
|
180
|
+
const ForwardSelector = /*#__PURE__*/React.forwardRef(Selector);
|
|
181
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
182
|
+
ForwardSelector.displayName = 'Selector';
|
|
183
|
+
}
|
|
184
|
+
export default ForwardSelector;
|
package/es/TransBtn.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { RenderNode } from './BaseSelect';
|
|
3
|
+
export interface TransBtnProps {
|
|
4
|
+
className: string;
|
|
5
|
+
customizeIcon: RenderNode;
|
|
6
|
+
customizeIconProps?: any;
|
|
7
|
+
onMouseDown?: React.MouseEventHandler<HTMLSpanElement>;
|
|
8
|
+
onClick?: React.MouseEventHandler<HTMLSpanElement>;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
declare const TransBtn: React.FC<TransBtnProps>;
|
|
12
|
+
export default TransBtn;
|
package/es/TransBtn.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
const TransBtn = props => {
|
|
4
|
+
const {
|
|
5
|
+
className,
|
|
6
|
+
customizeIcon,
|
|
7
|
+
customizeIconProps,
|
|
8
|
+
children,
|
|
9
|
+
onMouseDown,
|
|
10
|
+
onClick
|
|
11
|
+
} = props;
|
|
12
|
+
const icon = typeof customizeIcon === 'function' ? customizeIcon(customizeIconProps) : customizeIcon;
|
|
13
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
14
|
+
className: className,
|
|
15
|
+
onMouseDown: event => {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
onMouseDown?.(event);
|
|
18
|
+
},
|
|
19
|
+
style: {
|
|
20
|
+
userSelect: 'none',
|
|
21
|
+
WebkitUserSelect: 'none'
|
|
22
|
+
},
|
|
23
|
+
unselectable: "on",
|
|
24
|
+
onClick: onClick,
|
|
25
|
+
"aria-hidden": true
|
|
26
|
+
}, icon !== undefined ? icon : /*#__PURE__*/React.createElement("span", {
|
|
27
|
+
className: classNames(className.split(/\s+/).map(cls => `${cls}-icon`))
|
|
28
|
+
}, children));
|
|
29
|
+
};
|
|
30
|
+
export default TransBtn;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DisplayValueType, Mode, RenderNode } from '../interface';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export declare const useAllowClear: (prefixCls: string, onClearMouseDown: React.MouseEventHandler<HTMLSpanElement>, displayValues: DisplayValueType[], allowClear?: boolean | {
|
|
4
|
+
clearIcon?: RenderNode;
|
|
5
|
+
}, clearIcon?: RenderNode, disabled?: boolean, mergedSearchValue?: string, mode?: Mode) => {
|
|
6
|
+
allowClear: boolean;
|
|
7
|
+
clearIcon: React.JSX.Element;
|
|
8
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import TransBtn from "../TransBtn";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export const useAllowClear = (prefixCls, onClearMouseDown, displayValues, allowClear, clearIcon, disabled = false, mergedSearchValue, mode) => {
|
|
4
|
+
const mergedClearIcon = React.useMemo(() => {
|
|
5
|
+
if (typeof allowClear === 'object') {
|
|
6
|
+
return allowClear.clearIcon;
|
|
7
|
+
}
|
|
8
|
+
if (clearIcon) {
|
|
9
|
+
return clearIcon;
|
|
10
|
+
}
|
|
11
|
+
}, [allowClear, clearIcon]);
|
|
12
|
+
const mergedAllowClear = React.useMemo(() => {
|
|
13
|
+
if (!disabled && !!allowClear && (displayValues.length || mergedSearchValue) && !(mode === 'combobox' && mergedSearchValue === '')) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}, [allowClear, disabled, displayValues.length, mergedSearchValue, mode]);
|
|
18
|
+
return {
|
|
19
|
+
allowClear: mergedAllowClear,
|
|
20
|
+
clearIcon: /*#__PURE__*/React.createElement(TransBtn, {
|
|
21
|
+
className: `${prefixCls}-clear`,
|
|
22
|
+
onMouseDown: onClearMouseDown,
|
|
23
|
+
customizeIcon: mergedClearIcon
|
|
24
|
+
}, "\xD7")
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseSelect provide some parsed data into context.
|
|
3
|
+
* You can use this hooks to get them.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import type { BaseSelectProps } from '../BaseSelect';
|
|
7
|
+
export interface BaseSelectContextProps extends BaseSelectProps {
|
|
8
|
+
triggerOpen: boolean;
|
|
9
|
+
multiple: boolean;
|
|
10
|
+
toggleOpen: (open?: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const BaseSelectContext: React.Context<BaseSelectContextProps>;
|
|
13
|
+
export default function useBaseProps(): BaseSelectContextProps;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseSelect provide some parsed data into context.
|
|
3
|
+
* You can use this hooks to get them.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
export const BaseSelectContext = /*#__PURE__*/React.createContext(null);
|
|
8
|
+
export default function useBaseProps() {
|
|
9
|
+
return React.useContext(BaseSelectContext);
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RawValueType } from '../BaseSelect';
|
|
2
|
+
import type { DefaultOptionType, LabelInValueType } from '../Select';
|
|
3
|
+
/**
|
|
4
|
+
* Cache `value` related LabeledValue & options.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: (labeledValues: LabelInValueType[], valueOptions: Map<RawValueType, DefaultOptionType>) => [LabelInValueType[], (val: RawValueType) => DefaultOptionType];
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Cache `value` related LabeledValue & options.
|
|
4
|
+
*/
|
|
5
|
+
export default ((labeledValues, valueOptions) => {
|
|
6
|
+
const cacheRef = React.useRef({
|
|
7
|
+
values: new Map(),
|
|
8
|
+
options: new Map()
|
|
9
|
+
});
|
|
10
|
+
const filledLabeledValues = React.useMemo(() => {
|
|
11
|
+
const {
|
|
12
|
+
values: prevValueCache,
|
|
13
|
+
options: prevOptionCache
|
|
14
|
+
} = cacheRef.current;
|
|
15
|
+
|
|
16
|
+
// Fill label by cache
|
|
17
|
+
const patchedValues = labeledValues.map(item => {
|
|
18
|
+
if (item.label === undefined) {
|
|
19
|
+
return {
|
|
20
|
+
...item,
|
|
21
|
+
label: prevValueCache.get(item.value)?.label
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return item;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Refresh cache
|
|
28
|
+
const valueCache = new Map();
|
|
29
|
+
const optionCache = new Map();
|
|
30
|
+
patchedValues.forEach(item => {
|
|
31
|
+
valueCache.set(item.value, item);
|
|
32
|
+
optionCache.set(item.value, valueOptions.get(item.value) || prevOptionCache.get(item.value));
|
|
33
|
+
});
|
|
34
|
+
cacheRef.current.values = valueCache;
|
|
35
|
+
cacheRef.current.options = optionCache;
|
|
36
|
+
return patchedValues;
|
|
37
|
+
}, [labeledValues, valueOptions]);
|
|
38
|
+
const getOption = React.useCallback(val => valueOptions.get(val) || cacheRef.current.options.get(val), [valueOptions]);
|
|
39
|
+
return [filledLabeledValues, getOption];
|
|
40
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similar with `useLock`, but this hook will always execute last value.
|
|
3
|
+
* When set to `true`, it will keep `true` for a short time even if `false` is set.
|
|
4
|
+
*/
|
|
5
|
+
export default function useDelayReset(timeout?: number): [boolean, (val: boolean, callback?: () => void) => void, () => void];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Similar with `useLock`, but this hook will always execute last value.
|
|
5
|
+
* When set to `true`, it will keep `true` for a short time even if `false` is set.
|
|
6
|
+
*/
|
|
7
|
+
export default function useDelayReset(timeout = 10) {
|
|
8
|
+
const [bool, setBool] = React.useState(false);
|
|
9
|
+
const delayRef = React.useRef(null);
|
|
10
|
+
const cancelLatest = () => {
|
|
11
|
+
window.clearTimeout(delayRef.current);
|
|
12
|
+
};
|
|
13
|
+
React.useEffect(() => cancelLatest, []);
|
|
14
|
+
const delaySetBool = (value, callback) => {
|
|
15
|
+
cancelLatest();
|
|
16
|
+
delayRef.current = window.setTimeout(() => {
|
|
17
|
+
setBool(value);
|
|
18
|
+
if (callback) {
|
|
19
|
+
callback();
|
|
20
|
+
}
|
|
21
|
+
}, timeout);
|
|
22
|
+
};
|
|
23
|
+
return [bool, delaySetBool, cancelLatest];
|
|
24
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FieldNames, DefaultOptionType, SelectProps } from '../Select';
|
|
2
|
+
declare const _default: (options: DefaultOptionType[], fieldNames: FieldNames, searchValue?: string, filterOption?: SelectProps['filterOption'], optionFilterProp?: string) => DefaultOptionType[];
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { toArray } from "../utils/commonUtil";
|
|
3
|
+
import { injectPropsWithOption } from "../utils/valueUtil";
|
|
4
|
+
function includes(test, search) {
|
|
5
|
+
return toArray(test).join('').toUpperCase().includes(search);
|
|
6
|
+
}
|
|
7
|
+
export default ((options, fieldNames, searchValue, filterOption, optionFilterProp) => React.useMemo(() => {
|
|
8
|
+
if (!searchValue || filterOption === false) {
|
|
9
|
+
return options;
|
|
10
|
+
}
|
|
11
|
+
const {
|
|
12
|
+
options: fieldOptions,
|
|
13
|
+
label: fieldLabel,
|
|
14
|
+
value: fieldValue
|
|
15
|
+
} = fieldNames;
|
|
16
|
+
const filteredOptions = [];
|
|
17
|
+
const customizeFilter = typeof filterOption === 'function';
|
|
18
|
+
const upperSearch = searchValue.toUpperCase();
|
|
19
|
+
const filterFunc = customizeFilter ? filterOption : (_, option) => {
|
|
20
|
+
// Use provided `optionFilterProp`
|
|
21
|
+
if (optionFilterProp) {
|
|
22
|
+
return includes(option[optionFilterProp], upperSearch);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Auto select `label` or `value` by option type
|
|
26
|
+
if (option[fieldOptions]) {
|
|
27
|
+
// hack `fieldLabel` since `OptionGroup` children is not `label`
|
|
28
|
+
return includes(option[fieldLabel !== 'children' ? fieldLabel : 'label'], upperSearch);
|
|
29
|
+
}
|
|
30
|
+
return includes(option[fieldValue], upperSearch);
|
|
31
|
+
};
|
|
32
|
+
const wrapOption = customizeFilter ? opt => injectPropsWithOption(opt) : opt => opt;
|
|
33
|
+
options.forEach(item => {
|
|
34
|
+
// Group should check child options
|
|
35
|
+
if (item[fieldOptions]) {
|
|
36
|
+
// Check group first
|
|
37
|
+
const matchGroup = filterFunc(searchValue, wrapOption(item));
|
|
38
|
+
if (matchGroup) {
|
|
39
|
+
filteredOptions.push(item);
|
|
40
|
+
} else {
|
|
41
|
+
// Check option
|
|
42
|
+
const subOptions = item[fieldOptions].filter(subItem => filterFunc(searchValue, wrapOption(subItem)));
|
|
43
|
+
if (subOptions.length) {
|
|
44
|
+
filteredOptions.push({
|
|
45
|
+
...item,
|
|
46
|
+
[fieldOptions]: subOptions
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (filterFunc(searchValue, wrapOption(item))) {
|
|
53
|
+
filteredOptions.push(item);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return filteredOptions;
|
|
57
|
+
}, [options, filterOption, optionFilterProp, searchValue, fieldNames]));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import canUseDom from "@rc-component/util/es/Dom/canUseDom";
|
|
3
|
+
let uuid = 0;
|
|
4
|
+
|
|
5
|
+
/** Is client side and not jsdom */
|
|
6
|
+
export const isBrowserClient = process.env.NODE_ENV !== 'test' && canUseDom();
|
|
7
|
+
|
|
8
|
+
/** Get unique id for accessibility usage */
|
|
9
|
+
export function getUUID() {
|
|
10
|
+
let retId;
|
|
11
|
+
|
|
12
|
+
// Test never reach
|
|
13
|
+
/* istanbul ignore if */
|
|
14
|
+
if (isBrowserClient) {
|
|
15
|
+
retId = uuid;
|
|
16
|
+
uuid += 1;
|
|
17
|
+
} else {
|
|
18
|
+
retId = 'TEST_OR_SSR';
|
|
19
|
+
}
|
|
20
|
+
return retId;
|
|
21
|
+
}
|
|
22
|
+
export default function useId(id) {
|
|
23
|
+
// Inner id for accessibility usage. Only work in client side
|
|
24
|
+
const [innerId, setInnerId] = React.useState();
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
setInnerId(`rc_select_${getUUID()}`);
|
|
27
|
+
}, []);
|
|
28
|
+
return id || innerId;
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { isBrowserClient } from "../utils/commonUtil";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
|
|
7
|
+
*/
|
|
8
|
+
export default function useLayoutEffect(effect, deps) {
|
|
9
|
+
// Never happen in test env
|
|
10
|
+
if (isBrowserClient) {
|
|
11
|
+
/* istanbul ignore next */
|
|
12
|
+
React.useLayoutEffect(effect, deps);
|
|
13
|
+
} else {
|
|
14
|
+
React.useEffect(effect, deps);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/* eslint-enable */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locker return cached mark.
|
|
3
|
+
* If set to `true`, will return `true` in a short time even if set `false`.
|
|
4
|
+
* If set to `false` and then set to `true`, will change to `true`.
|
|
5
|
+
* And after time duration, it will back to `null` automatically.
|
|
6
|
+
*/
|
|
7
|
+
export default function useLock(duration?: number): [() => boolean, (lock: boolean) => void];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Locker return cached mark.
|
|
5
|
+
* If set to `true`, will return `true` in a short time even if set `false`.
|
|
6
|
+
* If set to `false` and then set to `true`, will change to `true`.
|
|
7
|
+
* And after time duration, it will back to `null` automatically.
|
|
8
|
+
*/
|
|
9
|
+
export default function useLock(duration = 250) {
|
|
10
|
+
const lockRef = React.useRef(null);
|
|
11
|
+
const timeoutRef = React.useRef(null);
|
|
12
|
+
|
|
13
|
+
// Clean up
|
|
14
|
+
React.useEffect(() => () => {
|
|
15
|
+
window.clearTimeout(timeoutRef.current);
|
|
16
|
+
}, []);
|
|
17
|
+
function doLock(locked) {
|
|
18
|
+
if (locked || lockRef.current === null) {
|
|
19
|
+
lockRef.current = locked;
|
|
20
|
+
}
|
|
21
|
+
window.clearTimeout(timeoutRef.current);
|
|
22
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
23
|
+
lockRef.current = null;
|
|
24
|
+
}, duration);
|
|
25
|
+
}
|
|
26
|
+
return [() => lockRef.current, doLock];
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { FieldNames, RawValueType } from '../Select';
|
|
3
|
+
/**
|
|
4
|
+
* Parse `children` to `options` if `options` is not provided.
|
|
5
|
+
* Then flatten the `options`.
|
|
6
|
+
*/
|
|
7
|
+
declare const useOptions: <OptionType>(options: OptionType[], children: React.ReactNode, fieldNames: FieldNames, optionFilterProp: string, optionLabelProp: string) => {
|
|
8
|
+
options: OptionType[];
|
|
9
|
+
valueOptions: Map<RawValueType, OptionType>;
|
|
10
|
+
labelOptions: Map<React.ReactNode, OptionType>;
|
|
11
|
+
};
|
|
12
|
+
export default useOptions;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { convertChildrenToData } from "../utils/legacyUtil";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse `children` to `options` if `options` is not provided.
|
|
6
|
+
* Then flatten the `options`.
|
|
7
|
+
*/
|
|
8
|
+
const useOptions = (options, children, fieldNames, optionFilterProp, optionLabelProp) => {
|
|
9
|
+
return React.useMemo(() => {
|
|
10
|
+
let mergedOptions = options;
|
|
11
|
+
const childrenAsData = !options;
|
|
12
|
+
if (childrenAsData) {
|
|
13
|
+
mergedOptions = convertChildrenToData(children);
|
|
14
|
+
}
|
|
15
|
+
const valueOptions = new Map();
|
|
16
|
+
const labelOptions = new Map();
|
|
17
|
+
const setLabelOptions = (labelOptionsMap, option, key) => {
|
|
18
|
+
if (key && typeof key === 'string') {
|
|
19
|
+
labelOptionsMap.set(option[key], option);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const dig = (optionList, isChildren = false) => {
|
|
23
|
+
// for loop to speed up collection speed
|
|
24
|
+
for (let i = 0; i < optionList.length; i += 1) {
|
|
25
|
+
const option = optionList[i];
|
|
26
|
+
if (!option[fieldNames.options] || isChildren) {
|
|
27
|
+
valueOptions.set(option[fieldNames.value], option);
|
|
28
|
+
setLabelOptions(labelOptions, option, fieldNames.label);
|
|
29
|
+
// https://github.com/ant-design/ant-design/issues/35304
|
|
30
|
+
setLabelOptions(labelOptions, option, optionFilterProp);
|
|
31
|
+
setLabelOptions(labelOptions, option, optionLabelProp);
|
|
32
|
+
} else {
|
|
33
|
+
dig(option[fieldNames.options], true);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
dig(mergedOptions);
|
|
38
|
+
return {
|
|
39
|
+
options: mergedOptions,
|
|
40
|
+
valueOptions,
|
|
41
|
+
labelOptions
|
|
42
|
+
};
|
|
43
|
+
}, [options, children, fieldNames, optionFilterProp, optionLabelProp]);
|
|
44
|
+
};
|
|
45
|
+
export default useOptions;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Same as `React.useCallback` but always return a memoized function
|
|
5
|
+
* but redirect to real function.
|
|
6
|
+
*/
|
|
7
|
+
export default function useRefFunc(callback) {
|
|
8
|
+
const funcRef = React.useRef();
|
|
9
|
+
funcRef.current = callback;
|
|
10
|
+
const cacheFn = React.useCallback((...args) => {
|
|
11
|
+
return funcRef.current(...args);
|
|
12
|
+
}, []);
|
|
13
|
+
return cacheFn;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useSelectTriggerControl(elements: () => (HTMLElement | undefined)[], open: boolean, triggerOpen: (open: boolean) => void, customizedTrigger: boolean): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export default function useSelectTriggerControl(elements, open, triggerOpen, customizedTrigger) {
|
|
3
|
+
const propsRef = React.useRef(null);
|
|
4
|
+
propsRef.current = {
|
|
5
|
+
open,
|
|
6
|
+
triggerOpen,
|
|
7
|
+
customizedTrigger
|
|
8
|
+
};
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
function onGlobalMouseDown(event) {
|
|
11
|
+
// If trigger is customized, Trigger will take control of popupVisible
|
|
12
|
+
if (propsRef.current?.customizedTrigger) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let target = event.target;
|
|
16
|
+
if (target.shadowRoot && event.composed) {
|
|
17
|
+
target = event.composedPath()[0] || target;
|
|
18
|
+
}
|
|
19
|
+
if (propsRef.current.open && elements().filter(element => element).every(element => !element.contains(target) && element !== target)) {
|
|
20
|
+
// Should trigger close
|
|
21
|
+
propsRef.current.triggerOpen(false);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
window.addEventListener('mousedown', onGlobalMouseDown);
|
|
25
|
+
return () => window.removeEventListener('mousedown', onGlobalMouseDown);
|
|
26
|
+
}, []);
|
|
27
|
+
}
|
package/es/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Select from './Select';
|
|
2
|
+
import Option from './Option';
|
|
3
|
+
import OptGroup from './OptGroup';
|
|
4
|
+
import type { SelectProps } from './Select';
|
|
5
|
+
import BaseSelect from './BaseSelect';
|
|
6
|
+
import type { BaseSelectProps, BaseSelectRef, BaseSelectPropsWithoutPrivate } from './BaseSelect';
|
|
7
|
+
import useBaseProps from './hooks/useBaseProps';
|
|
8
|
+
export { Option, OptGroup, BaseSelect, useBaseProps };
|
|
9
|
+
export type { SelectProps, BaseSelectProps, BaseSelectRef, BaseSelectPropsWithoutPrivate };
|
|
10
|
+
export default Select;
|
package/es/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import Select from "./Select";
|
|
2
|
+
import Option from "./Option";
|
|
3
|
+
import OptGroup from "./OptGroup";
|
|
4
|
+
import BaseSelect from "./BaseSelect";
|
|
5
|
+
import useBaseProps from "./hooks/useBaseProps";
|
|
6
|
+
export { Option, OptGroup, BaseSelect, useBaseProps };
|
|
7
|
+
export default Select;
|