@rc-component/cascader 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 +300 -0
- package/assets/index.less +3 -0
- package/assets/list.less +106 -0
- package/assets/panel.less +7 -0
- package/assets/select.less +3 -0
- package/es/Cascader.d.ts +88 -0
- package/es/Cascader.js +230 -0
- package/es/OptionList/CacheContent.d.ts +7 -0
- package/es/OptionList/CacheContent.js +8 -0
- package/es/OptionList/Checkbox.d.ts +10 -0
- package/es/OptionList/Checkbox.js +24 -0
- package/es/OptionList/Column.d.ts +21 -0
- package/es/OptionList/Column.js +175 -0
- package/es/OptionList/List.d.ts +6 -0
- package/es/OptionList/List.js +216 -0
- package/es/OptionList/index.d.ts +4 -0
- package/es/OptionList/index.js +13 -0
- package/es/OptionList/useActive.d.ts +6 -0
- package/es/OptionList/useActive.js +26 -0
- package/es/OptionList/useKeyboard.d.ts +10 -0
- package/es/OptionList/useKeyboard.js +165 -0
- package/es/Panel.d.ts +5 -0
- package/es/Panel.js +116 -0
- package/es/context.d.ts +21 -0
- package/es/context.js +3 -0
- package/es/hooks/useDisplayValues.d.ts +10 -0
- package/es/hooks/useDisplayValues.js +44 -0
- package/es/hooks/useEntities.d.ts +10 -0
- package/es/hooks/useEntities.js +35 -0
- package/es/hooks/useMissingValues.d.ts +3 -0
- package/es/hooks/useMissingValues.js +17 -0
- package/es/hooks/useOptions.d.ts +9 -0
- package/es/hooks/useOptions.js +20 -0
- package/es/hooks/useSearchConfig.d.ts +2 -0
- package/es/hooks/useSearchConfig.js +27 -0
- package/es/hooks/useSearchOptions.d.ts +4 -0
- package/es/hooks/useSearchOptions.js +63 -0
- package/es/hooks/useSelect.d.ts +4 -0
- package/es/hooks/useSelect.js +49 -0
- package/es/hooks/useValues.d.ts +9 -0
- package/es/hooks/useValues.js +21 -0
- package/es/index.d.ts +5 -0
- package/es/index.js +4 -0
- package/es/utils/commonUtil.d.ts +18 -0
- package/es/utils/commonUtil.js +69 -0
- package/es/utils/treeUtil.d.ts +9 -0
- package/es/utils/treeUtil.js +35 -0
- package/es/utils/warningPropsUtil.d.ts +4 -0
- package/es/utils/warningPropsUtil.js +29 -0
- package/lib/Cascader.d.ts +88 -0
- package/lib/Cascader.js +239 -0
- package/lib/OptionList/CacheContent.d.ts +7 -0
- package/lib/OptionList/CacheContent.js +16 -0
- package/lib/OptionList/Checkbox.d.ts +10 -0
- package/lib/OptionList/Checkbox.js +33 -0
- package/lib/OptionList/Column.d.ts +21 -0
- package/lib/OptionList/Column.js +185 -0
- package/lib/OptionList/List.d.ts +6 -0
- package/lib/OptionList/List.js +224 -0
- package/lib/OptionList/index.d.ts +4 -0
- package/lib/OptionList/index.js +22 -0
- package/lib/OptionList/useActive.d.ts +6 -0
- package/lib/OptionList/useActive.js +34 -0
- package/lib/OptionList/useKeyboard.d.ts +10 -0
- package/lib/OptionList/useKeyboard.js +175 -0
- package/lib/Panel.d.ts +5 -0
- package/lib/Panel.js +125 -0
- package/lib/context.d.ts +21 -0
- package/lib/context.js +11 -0
- package/lib/hooks/useDisplayValues.d.ts +10 -0
- package/lib/hooks/useDisplayValues.js +53 -0
- package/lib/hooks/useEntities.d.ts +10 -0
- package/lib/hooks/useEntities.js +44 -0
- package/lib/hooks/useMissingValues.d.ts +3 -0
- package/lib/hooks/useMissingValues.js +25 -0
- package/lib/hooks/useOptions.d.ts +9 -0
- package/lib/hooks/useOptions.js +29 -0
- package/lib/hooks/useSearchConfig.d.ts +2 -0
- package/lib/hooks/useSearchConfig.js +36 -0
- package/lib/hooks/useSearchOptions.d.ts +4 -0
- package/lib/hooks/useSearchOptions.js +71 -0
- package/lib/hooks/useSelect.d.ts +4 -0
- package/lib/hooks/useSelect.js +55 -0
- package/lib/hooks/useValues.d.ts +9 -0
- package/lib/hooks/useValues.js +29 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +16 -0
- package/lib/utils/commonUtil.d.ts +18 -0
- package/lib/utils/commonUtil.js +83 -0
- package/lib/utils/treeUtil.d.ts +9 -0
- package/lib/utils/treeUtil.js +42 -0
- package/lib/utils/warningPropsUtil.d.ts +4 -0
- package/lib/utils/warningPropsUtil.js +37 -0
- package/package.json +88 -0
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
/* eslint-disable default-case */
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import CascaderContext from "../context";
|
|
6
|
+
import { getFullPathKeys, isLeaf, scrollIntoParentView, toPathKey, toPathKeys, toPathValueStr } from "../utils/commonUtil";
|
|
7
|
+
import { toPathOptions } from "../utils/treeUtil";
|
|
8
|
+
import CacheContent from "./CacheContent";
|
|
9
|
+
import Column, { FIX_LABEL } from "./Column";
|
|
10
|
+
import useActive from "./useActive";
|
|
11
|
+
import useKeyboard from "./useKeyboard";
|
|
12
|
+
const RawOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
13
|
+
const {
|
|
14
|
+
prefixCls,
|
|
15
|
+
multiple,
|
|
16
|
+
searchValue,
|
|
17
|
+
toggleOpen,
|
|
18
|
+
notFoundContent,
|
|
19
|
+
direction,
|
|
20
|
+
open,
|
|
21
|
+
disabled
|
|
22
|
+
} = props;
|
|
23
|
+
const containerRef = React.useRef(null);
|
|
24
|
+
const rtl = direction === 'rtl';
|
|
25
|
+
const {
|
|
26
|
+
options,
|
|
27
|
+
values,
|
|
28
|
+
halfValues,
|
|
29
|
+
fieldNames,
|
|
30
|
+
changeOnSelect,
|
|
31
|
+
onSelect,
|
|
32
|
+
searchOptions,
|
|
33
|
+
dropdownPrefixCls,
|
|
34
|
+
loadData,
|
|
35
|
+
expandTrigger
|
|
36
|
+
} = React.useContext(CascaderContext);
|
|
37
|
+
const mergedPrefixCls = dropdownPrefixCls || prefixCls;
|
|
38
|
+
|
|
39
|
+
// ========================= loadData =========================
|
|
40
|
+
const [loadingKeys, setLoadingKeys] = React.useState([]);
|
|
41
|
+
const internalLoadData = valueCells => {
|
|
42
|
+
// Do not load when search
|
|
43
|
+
if (!loadData || searchValue) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const optionList = toPathOptions(valueCells, options, fieldNames);
|
|
47
|
+
const rawOptions = optionList.map(({
|
|
48
|
+
option
|
|
49
|
+
}) => option);
|
|
50
|
+
const lastOption = rawOptions[rawOptions.length - 1];
|
|
51
|
+
if (lastOption && !isLeaf(lastOption, fieldNames)) {
|
|
52
|
+
const pathKey = toPathKey(valueCells);
|
|
53
|
+
setLoadingKeys(keys => [...keys, pathKey]);
|
|
54
|
+
loadData(rawOptions);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// zombieJ: This is bad. We should make this same as `rc-tree` to use Promise instead.
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
if (loadingKeys.length) {
|
|
61
|
+
loadingKeys.forEach(loadingKey => {
|
|
62
|
+
const valueStrCells = toPathValueStr(loadingKey);
|
|
63
|
+
const optionList = toPathOptions(valueStrCells, options, fieldNames, true).map(({
|
|
64
|
+
option
|
|
65
|
+
}) => option);
|
|
66
|
+
const lastOption = optionList[optionList.length - 1];
|
|
67
|
+
if (!lastOption || lastOption[fieldNames.children] || isLeaf(lastOption, fieldNames)) {
|
|
68
|
+
setLoadingKeys(keys => keys.filter(key => key !== loadingKey));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}, [options, loadingKeys, fieldNames]);
|
|
73
|
+
|
|
74
|
+
// ========================== Values ==========================
|
|
75
|
+
const checkedSet = React.useMemo(() => new Set(toPathKeys(values)), [values]);
|
|
76
|
+
const halfCheckedSet = React.useMemo(() => new Set(toPathKeys(halfValues)), [halfValues]);
|
|
77
|
+
|
|
78
|
+
// ====================== Accessibility =======================
|
|
79
|
+
const [activeValueCells, setActiveValueCells] = useActive(multiple, open);
|
|
80
|
+
|
|
81
|
+
// =========================== Path ===========================
|
|
82
|
+
const onPathOpen = nextValueCells => {
|
|
83
|
+
setActiveValueCells(nextValueCells);
|
|
84
|
+
|
|
85
|
+
// Trigger loadData
|
|
86
|
+
internalLoadData(nextValueCells);
|
|
87
|
+
};
|
|
88
|
+
const isSelectable = option => {
|
|
89
|
+
if (disabled) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const {
|
|
93
|
+
disabled: optionDisabled
|
|
94
|
+
} = option;
|
|
95
|
+
const isMergedLeaf = isLeaf(option, fieldNames);
|
|
96
|
+
return !optionDisabled && (isMergedLeaf || changeOnSelect || multiple);
|
|
97
|
+
};
|
|
98
|
+
const onPathSelect = (valuePath, leaf, fromKeyboard = false) => {
|
|
99
|
+
onSelect(valuePath);
|
|
100
|
+
if (!multiple && (leaf || changeOnSelect && (expandTrigger === 'hover' || fromKeyboard))) {
|
|
101
|
+
toggleOpen(false);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ========================== Option ==========================
|
|
106
|
+
const mergedOptions = React.useMemo(() => {
|
|
107
|
+
if (searchValue) {
|
|
108
|
+
return searchOptions;
|
|
109
|
+
}
|
|
110
|
+
return options;
|
|
111
|
+
}, [searchValue, searchOptions, options]);
|
|
112
|
+
|
|
113
|
+
// ========================== Column ==========================
|
|
114
|
+
const optionColumns = React.useMemo(() => {
|
|
115
|
+
const optionList = [{
|
|
116
|
+
options: mergedOptions
|
|
117
|
+
}];
|
|
118
|
+
let currentList = mergedOptions;
|
|
119
|
+
const fullPathKeys = getFullPathKeys(currentList, fieldNames);
|
|
120
|
+
for (let i = 0; i < activeValueCells.length; i += 1) {
|
|
121
|
+
const activeValueCell = activeValueCells[i];
|
|
122
|
+
const currentOption = currentList.find((option, index) => (fullPathKeys[index] ? toPathKey(fullPathKeys[index]) : option[fieldNames.value]) === activeValueCell);
|
|
123
|
+
const subOptions = currentOption?.[fieldNames.children];
|
|
124
|
+
if (!subOptions?.length) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
currentList = subOptions;
|
|
128
|
+
optionList.push({
|
|
129
|
+
options: subOptions
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return optionList;
|
|
133
|
+
}, [mergedOptions, activeValueCells, fieldNames]);
|
|
134
|
+
|
|
135
|
+
// ========================= Keyboard =========================
|
|
136
|
+
const onKeyboardSelect = (selectValueCells, option) => {
|
|
137
|
+
if (isSelectable(option)) {
|
|
138
|
+
onPathSelect(selectValueCells, isLeaf(option, fieldNames), true);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
useKeyboard(ref, mergedOptions, fieldNames, activeValueCells, onPathOpen, onKeyboardSelect, {
|
|
142
|
+
direction,
|
|
143
|
+
searchValue,
|
|
144
|
+
toggleOpen,
|
|
145
|
+
open
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// >>>>> Active Scroll
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
if (searchValue) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (let i = 0; i < activeValueCells.length; i += 1) {
|
|
154
|
+
const cellPath = activeValueCells.slice(0, i + 1);
|
|
155
|
+
const cellKeyPath = toPathKey(cellPath);
|
|
156
|
+
const ele = containerRef.current?.querySelector(`li[data-path-key="${cellKeyPath.replace(/\\{0,2}"/g, '\\"')}"]` // matches unescaped double quotes
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (ele) {
|
|
160
|
+
scrollIntoParentView(ele);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}, [activeValueCells, searchValue]);
|
|
164
|
+
|
|
165
|
+
// ========================== Render ==========================
|
|
166
|
+
// >>>>> Empty
|
|
167
|
+
const isEmpty = !optionColumns[0]?.options?.length;
|
|
168
|
+
const emptyList = [{
|
|
169
|
+
[fieldNames.value]: '__EMPTY__',
|
|
170
|
+
[FIX_LABEL]: notFoundContent,
|
|
171
|
+
disabled: true
|
|
172
|
+
}];
|
|
173
|
+
const columnProps = {
|
|
174
|
+
...props,
|
|
175
|
+
multiple: !isEmpty && multiple,
|
|
176
|
+
onSelect: onPathSelect,
|
|
177
|
+
onActive: onPathOpen,
|
|
178
|
+
onToggleOpen: toggleOpen,
|
|
179
|
+
checkedSet,
|
|
180
|
+
halfCheckedSet,
|
|
181
|
+
loadingKeys,
|
|
182
|
+
isSelectable
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// >>>>> Columns
|
|
186
|
+
const mergedOptionColumns = isEmpty ? [{
|
|
187
|
+
options: emptyList
|
|
188
|
+
}] : optionColumns;
|
|
189
|
+
const columnNodes = mergedOptionColumns.map((col, index) => {
|
|
190
|
+
const prevValuePath = activeValueCells.slice(0, index);
|
|
191
|
+
const activeValue = activeValueCells[index];
|
|
192
|
+
return /*#__PURE__*/React.createElement(Column, _extends({
|
|
193
|
+
key: index
|
|
194
|
+
}, columnProps, {
|
|
195
|
+
prefixCls: mergedPrefixCls,
|
|
196
|
+
options: col.options,
|
|
197
|
+
prevValuePath: prevValuePath,
|
|
198
|
+
activeValue: activeValue
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// >>>>> Render
|
|
203
|
+
return /*#__PURE__*/React.createElement(CacheContent, {
|
|
204
|
+
open: open
|
|
205
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
206
|
+
className: classNames(`${mergedPrefixCls}-menus`, {
|
|
207
|
+
[`${mergedPrefixCls}-menu-empty`]: isEmpty,
|
|
208
|
+
[`${mergedPrefixCls}-rtl`]: rtl
|
|
209
|
+
}),
|
|
210
|
+
ref: containerRef
|
|
211
|
+
}, columnNodes));
|
|
212
|
+
});
|
|
213
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
214
|
+
RawOptionList.displayName = 'RawOptionList';
|
|
215
|
+
}
|
|
216
|
+
export default RawOptionList;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { useBaseProps } from '@rc-component/select';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import RawOptionList from "./List";
|
|
5
|
+
const RefOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
6
|
+
const baseProps = useBaseProps();
|
|
7
|
+
|
|
8
|
+
// >>>>> Render
|
|
9
|
+
return /*#__PURE__*/React.createElement(RawOptionList, _extends({}, props, baseProps, {
|
|
10
|
+
ref: ref
|
|
11
|
+
}));
|
|
12
|
+
});
|
|
13
|
+
export default RefOptionList;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import CascaderContext from "../context";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Control the active open options path.
|
|
6
|
+
*/
|
|
7
|
+
const useActive = (multiple, open) => {
|
|
8
|
+
const {
|
|
9
|
+
values
|
|
10
|
+
} = React.useContext(CascaderContext);
|
|
11
|
+
const firstValueCells = values[0];
|
|
12
|
+
|
|
13
|
+
// Record current dropdown active options
|
|
14
|
+
// This also control the open status
|
|
15
|
+
const [activeValueCells, setActiveValueCells] = React.useState([]);
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
if (!multiple) {
|
|
18
|
+
setActiveValueCells(firstValueCells || []);
|
|
19
|
+
}
|
|
20
|
+
}, /* eslint-disable react-hooks/exhaustive-deps */
|
|
21
|
+
[open, firstValueCells]
|
|
22
|
+
/* eslint-enable react-hooks/exhaustive-deps */);
|
|
23
|
+
|
|
24
|
+
return [activeValueCells, setActiveValueCells];
|
|
25
|
+
};
|
|
26
|
+
export default useActive;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
|
|
4
|
+
declare const _default: (ref: React.Ref<RefOptionListProps>, options: DefaultOptionType[], fieldNames: InternalFieldNames, activeValueCells: React.Key[], setActiveValueCells: (activeValueCells: React.Key[]) => void, onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void, contextProps: {
|
|
5
|
+
direction?: "ltr" | "rtl" | undefined;
|
|
6
|
+
searchValue: string;
|
|
7
|
+
toggleOpen: (open?: boolean) => void;
|
|
8
|
+
open?: boolean | undefined;
|
|
9
|
+
}) => void;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import KeyCode from "@rc-component/util/es/KeyCode";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { SEARCH_MARK } from "../hooks/useSearchOptions";
|
|
4
|
+
import { getFullPathKeys, toPathKey } from "../utils/commonUtil";
|
|
5
|
+
export default ((ref, options, fieldNames, activeValueCells, setActiveValueCells, onKeyBoardSelect, contextProps) => {
|
|
6
|
+
const {
|
|
7
|
+
direction,
|
|
8
|
+
searchValue,
|
|
9
|
+
toggleOpen,
|
|
10
|
+
open
|
|
11
|
+
} = contextProps;
|
|
12
|
+
const rtl = direction === 'rtl';
|
|
13
|
+
const [validActiveValueCells, lastActiveIndex, lastActiveOptions, fullPathKeys] = React.useMemo(() => {
|
|
14
|
+
let activeIndex = -1;
|
|
15
|
+
let currentOptions = options;
|
|
16
|
+
const mergedActiveIndexes = [];
|
|
17
|
+
const mergedActiveValueCells = [];
|
|
18
|
+
const len = activeValueCells.length;
|
|
19
|
+
const pathKeys = getFullPathKeys(options, fieldNames);
|
|
20
|
+
|
|
21
|
+
// Fill validate active value cells and index
|
|
22
|
+
for (let i = 0; i < len && currentOptions; i += 1) {
|
|
23
|
+
// Mark the active index for current options
|
|
24
|
+
const nextActiveIndex = currentOptions.findIndex((option, index) => (pathKeys[index] ? toPathKey(pathKeys[index]) : option[fieldNames.value]) === activeValueCells[i]);
|
|
25
|
+
if (nextActiveIndex === -1) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
activeIndex = nextActiveIndex;
|
|
29
|
+
mergedActiveIndexes.push(activeIndex);
|
|
30
|
+
mergedActiveValueCells.push(activeValueCells[i]);
|
|
31
|
+
currentOptions = currentOptions[activeIndex][fieldNames.children];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fill last active options
|
|
35
|
+
let activeOptions = options;
|
|
36
|
+
for (let i = 0; i < mergedActiveIndexes.length - 1; i += 1) {
|
|
37
|
+
activeOptions = activeOptions[mergedActiveIndexes[i]][fieldNames.children];
|
|
38
|
+
}
|
|
39
|
+
return [mergedActiveValueCells, activeIndex, activeOptions, pathKeys];
|
|
40
|
+
}, [activeValueCells, fieldNames, options]);
|
|
41
|
+
|
|
42
|
+
// Update active value cells and scroll to target element
|
|
43
|
+
const internalSetActiveValueCells = next => {
|
|
44
|
+
setActiveValueCells(next);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Same options offset
|
|
48
|
+
const offsetActiveOption = offset => {
|
|
49
|
+
const len = lastActiveOptions.length;
|
|
50
|
+
let currentIndex = lastActiveIndex;
|
|
51
|
+
if (currentIndex === -1 && offset < 0) {
|
|
52
|
+
currentIndex = len;
|
|
53
|
+
}
|
|
54
|
+
for (let i = 0; i < len; i += 1) {
|
|
55
|
+
currentIndex = (currentIndex + offset + len) % len;
|
|
56
|
+
const option = lastActiveOptions[currentIndex];
|
|
57
|
+
if (option && !option.disabled) {
|
|
58
|
+
const nextActiveCells = validActiveValueCells.slice(0, -1).concat(fullPathKeys[currentIndex] ? toPathKey(fullPathKeys[currentIndex]) : option[fieldNames.value]);
|
|
59
|
+
internalSetActiveValueCells(nextActiveCells);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Different options offset
|
|
66
|
+
const prevColumn = () => {
|
|
67
|
+
if (validActiveValueCells.length > 1) {
|
|
68
|
+
const nextActiveCells = validActiveValueCells.slice(0, -1);
|
|
69
|
+
internalSetActiveValueCells(nextActiveCells);
|
|
70
|
+
} else {
|
|
71
|
+
toggleOpen(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const nextColumn = () => {
|
|
75
|
+
const nextOptions = lastActiveOptions[lastActiveIndex]?.[fieldNames.children] || [];
|
|
76
|
+
const nextOption = nextOptions.find(option => !option.disabled);
|
|
77
|
+
if (nextOption) {
|
|
78
|
+
const nextActiveCells = [...validActiveValueCells, nextOption[fieldNames.value]];
|
|
79
|
+
internalSetActiveValueCells(nextActiveCells);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
React.useImperativeHandle(ref, () => ({
|
|
83
|
+
// scrollTo: treeRef.current?.scrollTo,
|
|
84
|
+
onKeyDown: event => {
|
|
85
|
+
const {
|
|
86
|
+
which
|
|
87
|
+
} = event;
|
|
88
|
+
switch (which) {
|
|
89
|
+
// >>> Arrow keys
|
|
90
|
+
case KeyCode.UP:
|
|
91
|
+
case KeyCode.DOWN:
|
|
92
|
+
{
|
|
93
|
+
let offset = 0;
|
|
94
|
+
if (which === KeyCode.UP) {
|
|
95
|
+
offset = -1;
|
|
96
|
+
} else if (which === KeyCode.DOWN) {
|
|
97
|
+
offset = 1;
|
|
98
|
+
}
|
|
99
|
+
if (offset !== 0) {
|
|
100
|
+
offsetActiveOption(offset);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case KeyCode.LEFT:
|
|
105
|
+
{
|
|
106
|
+
if (searchValue) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
if (rtl) {
|
|
110
|
+
nextColumn();
|
|
111
|
+
} else {
|
|
112
|
+
prevColumn();
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case KeyCode.RIGHT:
|
|
117
|
+
{
|
|
118
|
+
if (searchValue) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (rtl) {
|
|
122
|
+
prevColumn();
|
|
123
|
+
} else {
|
|
124
|
+
nextColumn();
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case KeyCode.BACKSPACE:
|
|
129
|
+
{
|
|
130
|
+
if (!searchValue) {
|
|
131
|
+
prevColumn();
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// >>> Select
|
|
137
|
+
case KeyCode.ENTER:
|
|
138
|
+
{
|
|
139
|
+
if (validActiveValueCells.length) {
|
|
140
|
+
const option = lastActiveOptions[lastActiveIndex];
|
|
141
|
+
|
|
142
|
+
// Search option should revert back of origin options
|
|
143
|
+
const originOptions = option?.[SEARCH_MARK] || [];
|
|
144
|
+
if (originOptions.length) {
|
|
145
|
+
onKeyBoardSelect(originOptions.map(opt => opt[fieldNames.value]), originOptions[originOptions.length - 1]);
|
|
146
|
+
} else {
|
|
147
|
+
onKeyBoardSelect(validActiveValueCells, lastActiveOptions[lastActiveIndex]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// >>> Close
|
|
154
|
+
case KeyCode.ESC:
|
|
155
|
+
{
|
|
156
|
+
toggleOpen(false);
|
|
157
|
+
if (open) {
|
|
158
|
+
event.stopPropagation();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
onKeyUp: () => {}
|
|
164
|
+
}));
|
|
165
|
+
});
|
package/es/Panel.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { CascaderProps, DefaultOptionType } from './Cascader';
|
|
3
|
+
export type PickType = 'value' | 'defaultValue' | 'changeOnSelect' | 'onChange' | 'options' | 'prefixCls' | 'checkable' | 'fieldNames' | 'showCheckedStrategy' | 'loadData' | 'expandTrigger' | 'expandIcon' | 'loadingIcon' | 'className' | 'style' | 'direction' | 'notFoundContent' | 'disabled';
|
|
4
|
+
export type PanelProps<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false> = Pick<CascaderProps<OptionType, ValueField, Multiple>, PickType>;
|
|
5
|
+
export default function Panel<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false>(props: PanelProps<OptionType, ValueField, Multiple>): React.JSX.Element;
|
package/es/Panel.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { useEvent, useMergedState } from '@rc-component/util';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import CascaderContext from "./context";
|
|
5
|
+
import useMissingValues from "./hooks/useMissingValues";
|
|
6
|
+
import useOptions from "./hooks/useOptions";
|
|
7
|
+
import useSelect from "./hooks/useSelect";
|
|
8
|
+
import useValues from "./hooks/useValues";
|
|
9
|
+
import RawOptionList from "./OptionList/List";
|
|
10
|
+
import { fillFieldNames, toRawValues } from "./utils/commonUtil";
|
|
11
|
+
import { toPathOptions } from "./utils/treeUtil";
|
|
12
|
+
function noop() {}
|
|
13
|
+
export default function Panel(props) {
|
|
14
|
+
const {
|
|
15
|
+
prefixCls = 'rc-cascader',
|
|
16
|
+
style,
|
|
17
|
+
className,
|
|
18
|
+
options,
|
|
19
|
+
checkable,
|
|
20
|
+
defaultValue,
|
|
21
|
+
value,
|
|
22
|
+
fieldNames,
|
|
23
|
+
changeOnSelect,
|
|
24
|
+
onChange,
|
|
25
|
+
showCheckedStrategy,
|
|
26
|
+
loadData,
|
|
27
|
+
expandTrigger,
|
|
28
|
+
expandIcon = '>',
|
|
29
|
+
loadingIcon,
|
|
30
|
+
direction,
|
|
31
|
+
notFoundContent = 'Not Found',
|
|
32
|
+
disabled
|
|
33
|
+
} = props;
|
|
34
|
+
|
|
35
|
+
// ======================== Multiple ========================
|
|
36
|
+
const multiple = !!checkable;
|
|
37
|
+
|
|
38
|
+
// ========================= Values =========================
|
|
39
|
+
const [rawValues, setRawValues] = useMergedState(defaultValue, {
|
|
40
|
+
value,
|
|
41
|
+
postState: toRawValues
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ========================= FieldNames =========================
|
|
45
|
+
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
|
|
46
|
+
[JSON.stringify(fieldNames)]
|
|
47
|
+
/* eslint-enable react-hooks/exhaustive-deps */);
|
|
48
|
+
|
|
49
|
+
// =========================== Option ===========================
|
|
50
|
+
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = useOptions(mergedFieldNames, options);
|
|
51
|
+
|
|
52
|
+
// ========================= Values =========================
|
|
53
|
+
const getMissingValues = useMissingValues(mergedOptions, mergedFieldNames);
|
|
54
|
+
|
|
55
|
+
// Fill `rawValues` with checked conduction values
|
|
56
|
+
const [checkedValues, halfCheckedValues, missingCheckedValues] = useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues);
|
|
57
|
+
|
|
58
|
+
// =========================== Change ===========================
|
|
59
|
+
const triggerChange = useEvent(nextValues => {
|
|
60
|
+
setRawValues(nextValues);
|
|
61
|
+
|
|
62
|
+
// Save perf if no need trigger event
|
|
63
|
+
if (onChange) {
|
|
64
|
+
const nextRawValues = toRawValues(nextValues);
|
|
65
|
+
const valueOptions = nextRawValues.map(valueCells => toPathOptions(valueCells, mergedOptions, mergedFieldNames).map(valueOpt => valueOpt.option));
|
|
66
|
+
const triggerValues = multiple ? nextRawValues : nextRawValues[0];
|
|
67
|
+
const triggerOptions = multiple ? valueOptions : valueOptions[0];
|
|
68
|
+
onChange(triggerValues, triggerOptions);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// =========================== Select ===========================
|
|
73
|
+
const handleSelection = useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy);
|
|
74
|
+
const onInternalSelect = useEvent(valuePath => {
|
|
75
|
+
handleSelection(valuePath);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ======================== Context =========================
|
|
79
|
+
const cascaderContext = React.useMemo(() => ({
|
|
80
|
+
options: mergedOptions,
|
|
81
|
+
fieldNames: mergedFieldNames,
|
|
82
|
+
values: checkedValues,
|
|
83
|
+
halfValues: halfCheckedValues,
|
|
84
|
+
changeOnSelect,
|
|
85
|
+
onSelect: onInternalSelect,
|
|
86
|
+
checkable,
|
|
87
|
+
searchOptions: [],
|
|
88
|
+
dropdownPrefixCls: undefined,
|
|
89
|
+
loadData,
|
|
90
|
+
expandTrigger,
|
|
91
|
+
expandIcon,
|
|
92
|
+
loadingIcon,
|
|
93
|
+
dropdownMenuColumnStyle: undefined
|
|
94
|
+
}), [mergedOptions, mergedFieldNames, checkedValues, halfCheckedValues, changeOnSelect, onInternalSelect, checkable, loadData, expandTrigger, expandIcon, loadingIcon]);
|
|
95
|
+
|
|
96
|
+
// ========================= Render =========================
|
|
97
|
+
const panelPrefixCls = `${prefixCls}-panel`;
|
|
98
|
+
const isEmpty = !mergedOptions.length;
|
|
99
|
+
return /*#__PURE__*/React.createElement(CascaderContext.Provider, {
|
|
100
|
+
value: cascaderContext
|
|
101
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
102
|
+
className: classNames(panelPrefixCls, {
|
|
103
|
+
[`${panelPrefixCls}-rtl`]: direction === 'rtl',
|
|
104
|
+
[`${panelPrefixCls}-empty`]: isEmpty
|
|
105
|
+
}, className),
|
|
106
|
+
style: style
|
|
107
|
+
}, isEmpty ? notFoundContent : /*#__PURE__*/React.createElement(RawOptionList, {
|
|
108
|
+
prefixCls: prefixCls,
|
|
109
|
+
searchValue: "",
|
|
110
|
+
multiple: multiple,
|
|
111
|
+
toggleOpen: noop,
|
|
112
|
+
open: true,
|
|
113
|
+
direction: direction,
|
|
114
|
+
disabled: disabled
|
|
115
|
+
})));
|
|
116
|
+
}
|
package/es/context.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { CascaderProps, InternalFieldNames, DefaultOptionType, SingleValueType } from './Cascader';
|
|
3
|
+
export interface CascaderContextProps {
|
|
4
|
+
options: NonNullable<CascaderProps['options']>;
|
|
5
|
+
fieldNames: InternalFieldNames;
|
|
6
|
+
values: SingleValueType[];
|
|
7
|
+
halfValues: SingleValueType[];
|
|
8
|
+
changeOnSelect?: boolean;
|
|
9
|
+
onSelect: (valuePath: SingleValueType) => void;
|
|
10
|
+
checkable?: boolean | React.ReactNode;
|
|
11
|
+
searchOptions: DefaultOptionType[];
|
|
12
|
+
dropdownPrefixCls?: string;
|
|
13
|
+
loadData?: (selectOptions: DefaultOptionType[]) => void;
|
|
14
|
+
expandTrigger?: 'hover' | 'click';
|
|
15
|
+
expandIcon?: React.ReactNode;
|
|
16
|
+
loadingIcon?: React.ReactNode;
|
|
17
|
+
dropdownMenuColumnStyle?: React.CSSProperties;
|
|
18
|
+
optionRender?: CascaderProps['optionRender'];
|
|
19
|
+
}
|
|
20
|
+
declare const CascaderContext: React.Context<CascaderContextProps>;
|
|
21
|
+
export default CascaderContext;
|
package/es/context.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { DefaultOptionType, SingleValueType, CascaderProps, InternalFieldNames } from '../Cascader';
|
|
3
|
+
declare const _default: (rawValues: SingleValueType[], options: DefaultOptionType[], fieldNames: InternalFieldNames, multiple: boolean, displayRender: CascaderProps['displayRender']) => {
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
value: string;
|
|
6
|
+
key: string;
|
|
7
|
+
valueCells: SingleValueType;
|
|
8
|
+
disabled: boolean | undefined;
|
|
9
|
+
}[];
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { toPathOptions } from "../utils/treeUtil";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { toPathKey } from "../utils/commonUtil";
|
|
4
|
+
export default ((rawValues, options, fieldNames, multiple, displayRender) => {
|
|
5
|
+
return React.useMemo(() => {
|
|
6
|
+
const mergedDisplayRender = displayRender || (
|
|
7
|
+
// Default displayRender
|
|
8
|
+
labels => {
|
|
9
|
+
const mergedLabels = multiple ? labels.slice(-1) : labels;
|
|
10
|
+
const SPLIT = ' / ';
|
|
11
|
+
if (mergedLabels.every(label => ['string', 'number'].includes(typeof label))) {
|
|
12
|
+
return mergedLabels.join(SPLIT);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// If exist non-string value, use ReactNode instead
|
|
16
|
+
return mergedLabels.reduce((list, label, index) => {
|
|
17
|
+
const keyedLabel = /*#__PURE__*/React.isValidElement(label) ? /*#__PURE__*/React.cloneElement(label, {
|
|
18
|
+
key: index
|
|
19
|
+
}) : label;
|
|
20
|
+
if (index === 0) {
|
|
21
|
+
return [keyedLabel];
|
|
22
|
+
}
|
|
23
|
+
return [...list, SPLIT, keyedLabel];
|
|
24
|
+
}, []);
|
|
25
|
+
});
|
|
26
|
+
return rawValues.map(valueCells => {
|
|
27
|
+
const valueOptions = toPathOptions(valueCells, options, fieldNames);
|
|
28
|
+
const label = mergedDisplayRender(valueOptions.map(({
|
|
29
|
+
option,
|
|
30
|
+
value
|
|
31
|
+
}) => option?.[fieldNames.label] ?? value), valueOptions.map(({
|
|
32
|
+
option
|
|
33
|
+
}) => option));
|
|
34
|
+
const value = toPathKey(valueCells);
|
|
35
|
+
return {
|
|
36
|
+
label,
|
|
37
|
+
value,
|
|
38
|
+
key: value,
|
|
39
|
+
valueCells,
|
|
40
|
+
disabled: valueOptions[valueOptions.length - 1]?.option?.disabled
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}, [rawValues, options, fieldNames, displayRender, multiple]);
|
|
44
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DefaultOptionType, InternalFieldNames } from '../Cascader';
|
|
2
|
+
import type { DataEntity } from 'rc-tree/lib/interface';
|
|
3
|
+
export interface OptionsInfo {
|
|
4
|
+
keyEntities: Record<string, DataEntity>;
|
|
5
|
+
pathKeyEntities: Record<string, DataEntity>;
|
|
6
|
+
}
|
|
7
|
+
export type GetEntities = () => OptionsInfo['pathKeyEntities'];
|
|
8
|
+
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
|
|
9
|
+
declare const _default: (options: DefaultOptionType[], fieldNames: InternalFieldNames) => GetEntities;
|
|
10
|
+
export default _default;
|