@rc-component/tree-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 +22 -0
- package/README.md +140 -0
- package/assets/icons.png +0 -0
- package/assets/index.less +2 -0
- package/assets/loading.gif +0 -0
- package/assets/minus.gif +0 -0
- package/assets/select.less +3 -0
- package/assets/tree.less +3 -0
- package/dist/223.90c7e648.async.js +3 -0
- package/dist/338.a2a48f3b.async.js +15 -0
- package/dist/338.e8c51481.chunk.css +5 -0
- package/dist/404.html +20 -0
- package/dist/439.67bede3f.async.js +137 -0
- package/dist/929.df8dd03f.async.js +32 -0
- package/dist/demo/basic/index.html +20 -0
- package/dist/demo/big-data/index.html +20 -0
- package/dist/demo/controlled/index.html +20 -0
- package/dist/demo/custom-icons/index.html +20 -0
- package/dist/demo/debug/index.html +20 -0
- package/dist/demo/disable/index.html +20 -0
- package/dist/demo/dynamic/index.html +20 -0
- package/dist/demo/field-names/index.html +20 -0
- package/dist/demo/filter/index.html +20 -0
- package/dist/demo/form/index.html +20 -0
- package/dist/demo/mutiple-with-max-count/index.html +20 -0
- package/dist/demo/tree-node-label-prop/index.html +20 -0
- package/dist/demo/width/index.html +20 -0
- package/dist/demos.f221b577.async.js +1 -0
- package/dist/docs__demo__basic.md.bcb0edb7.async.js +1 -0
- package/dist/docs__demo__big-data.md.8783cd6b.async.js +1 -0
- package/dist/docs__demo__controlled.md.e65ef7d7.async.js +1 -0
- package/dist/docs__demo__custom-icons.md.d53bf2f9.async.js +1 -0
- package/dist/docs__demo__debug.md.099ad226.async.js +1 -0
- package/dist/docs__demo__disable.md.6ba57652.async.js +1 -0
- package/dist/docs__demo__dynamic.md.f7559890.async.js +1 -0
- package/dist/docs__demo__fieldNames.md.16131d9f.async.js +1 -0
- package/dist/docs__demo__filter.md.02527db4.async.js +1 -0
- package/dist/docs__demo__form.md.6b77dffe.async.js +1 -0
- package/dist/docs__demo__mutiple-with-maxCount.md.7268d4ad.async.js +1 -0
- package/dist/docs__demo__treeNodeLabelProp.md.24709916.async.js +1 -0
- package/dist/docs__demo__width.md.24bfecd6.async.js +1 -0
- package/dist/docs__index.md.a6e45331.async.js +1 -0
- package/dist/dumi__tmp-production__dumi__theme__ContextWrapper.f56a0670.async.js +1 -0
- package/dist/index.html +20 -0
- package/dist/meta__docs.255fc42b.chunk.css +1 -0
- package/dist/meta__docs.dbf04b66.async.js +3359 -0
- package/dist/nm__dumi__dist__client__pages__404.8b85f2d9.chunk.css +1 -0
- package/dist/nm__dumi__dist__client__pages__404.ca2add38.async.js +1 -0
- package/dist/nm__dumi__dist__client__pages__Demo__index.29e9ef8f.async.js +1 -0
- package/dist/nm__dumi__dist__client__pages__Demo__index.578aa5c0.chunk.css +1 -0
- package/dist/nm__dumi__theme-default__layouts__DocLayout__index.8ed833d7.async.js +1 -0
- package/dist/preload_helper.d41c4da0.js +1 -0
- package/dist/umi.4f47b921.js +124 -0
- package/dist/umi.8faca2de.css +1 -0
- package/dist/~demos/:id/index.html +20 -0
- package/dist/~demos/docs-demo-basic-demo-basic/index.html +20 -0
- package/dist/~demos/docs-demo-big-data-demo-big-data/index.html +20 -0
- package/dist/~demos/docs-demo-controlled-demo-controlled/index.html +20 -0
- package/dist/~demos/docs-demo-custom-icons-demo-custom-icons/index.html +20 -0
- package/dist/~demos/docs-demo-debug-demo-debug/index.html +20 -0
- package/dist/~demos/docs-demo-disable-demo-disable/index.html +20 -0
- package/dist/~demos/docs-demo-dynamic-demo-dynamic/index.html +20 -0
- package/dist/~demos/docs-demo-field-names-demo-fieldnames/index.html +20 -0
- package/dist/~demos/docs-demo-filter-demo-filter/index.html +20 -0
- package/dist/~demos/docs-demo-form-demo-form/index.html +20 -0
- package/dist/~demos/docs-demo-mutiple-with-max-count-demo-mutiple-with-maxcount/index.html +20 -0
- package/dist/~demos/docs-demo-tree-node-label-prop-demo-treenodelabelprop/index.html +20 -0
- package/dist/~demos/docs-demo-width-demo-width/index.html +20 -0
- package/es/LegacyContext.d.ts +24 -0
- package/es/LegacyContext.js +3 -0
- package/es/OptionList.d.ts +8 -0
- package/es/OptionList.js +351 -0
- package/es/TreeNode.d.ts +9 -0
- package/es/TreeNode.js +5 -0
- package/es/TreeSelect.d.ts +65 -0
- package/es/TreeSelect.js +507 -0
- package/es/TreeSelectContext.d.ts +25 -0
- package/es/TreeSelectContext.js +3 -0
- package/es/hooks/useCache.d.ts +7 -0
- package/es/hooks/useCache.js +32 -0
- package/es/hooks/useCheckedKeys.d.ts +5 -0
- package/es/hooks/useCheckedKeys.js +21 -0
- package/es/hooks/useDataEntities.d.ts +7 -0
- package/es/hooks/useDataEntities.js +26 -0
- package/es/hooks/useFilterTreeData.d.ts +8 -0
- package/es/hooks/useFilterTreeData.js +33 -0
- package/es/hooks/useRefFunc.d.ts +5 -0
- package/es/hooks/useRefFunc.js +14 -0
- package/es/hooks/useTreeData.d.ts +7 -0
- package/es/hooks/useTreeData.js +52 -0
- package/es/index.d.ts +7 -0
- package/es/index.js +5 -0
- package/es/interface.d.ts +57 -0
- package/es/interface.js +1 -0
- package/es/utils/legacyUtil.d.ts +5 -0
- package/es/utils/legacyUtil.js +120 -0
- package/es/utils/strategyUtil.d.ts +7 -0
- package/es/utils/strategyUtil.js +25 -0
- package/es/utils/valueUtil.d.ts +11 -0
- package/es/utils/valueUtil.js +30 -0
- package/es/utils/warningPropsUtil.d.ts +5 -0
- package/es/utils/warningPropsUtil.js +30 -0
- package/lib/LegacyContext.d.ts +24 -0
- package/lib/LegacyContext.js +11 -0
- package/lib/OptionList.d.ts +8 -0
- package/lib/OptionList.js +359 -0
- package/lib/TreeNode.d.ts +9 -0
- package/lib/TreeNode.js +11 -0
- package/lib/TreeSelect.d.ts +65 -0
- package/lib/TreeSelect.js +516 -0
- package/lib/TreeSelectContext.d.ts +25 -0
- package/lib/TreeSelectContext.js +11 -0
- package/lib/hooks/useCache.d.ts +7 -0
- package/lib/hooks/useCache.js +41 -0
- package/lib/hooks/useCheckedKeys.d.ts +5 -0
- package/lib/hooks/useCheckedKeys.js +29 -0
- package/lib/hooks/useDataEntities.d.ts +7 -0
- package/lib/hooks/useDataEntities.js +36 -0
- package/lib/hooks/useFilterTreeData.d.ts +8 -0
- package/lib/hooks/useFilterTreeData.js +41 -0
- package/lib/hooks/useRefFunc.d.ts +5 -0
- package/lib/hooks/useRefFunc.js +21 -0
- package/lib/hooks/useTreeData.d.ts +7 -0
- package/lib/hooks/useTreeData.js +60 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +35 -0
- package/lib/interface.d.ts +57 -0
- package/lib/interface.js +5 -0
- package/lib/utils/legacyUtil.d.ts +5 -0
- package/lib/utils/legacyUtil.js +131 -0
- package/lib/utils/strategyUtil.d.ts +7 -0
- package/lib/utils/strategyUtil.js +32 -0
- package/lib/utils/valueUtil.d.ts +11 -0
- package/lib/utils/valueUtil.js +41 -0
- package/lib/utils/warningPropsUtil.d.ts +5 -0
- package/lib/utils/warningPropsUtil.js +37 -0
- package/package.json +89 -0
package/es/TreeSelect.js
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
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 { BaseSelect } from '@rc-component/select';
|
|
3
|
+
import useId from "@rc-component/select/es/hooks/useId";
|
|
4
|
+
import { conductCheck } from "rc-tree/es/utils/conductUtil";
|
|
5
|
+
import useMergedState from "@rc-component/util/es/hooks/useMergedState";
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import useCache from "./hooks/useCache";
|
|
8
|
+
import useCheckedKeys from "./hooks/useCheckedKeys";
|
|
9
|
+
import useDataEntities from "./hooks/useDataEntities";
|
|
10
|
+
import useFilterTreeData from "./hooks/useFilterTreeData";
|
|
11
|
+
import useRefFunc from "./hooks/useRefFunc";
|
|
12
|
+
import useTreeData from "./hooks/useTreeData";
|
|
13
|
+
import LegacyContext from "./LegacyContext";
|
|
14
|
+
import OptionList from "./OptionList";
|
|
15
|
+
import TreeNode from "./TreeNode";
|
|
16
|
+
import TreeSelectContext from "./TreeSelectContext";
|
|
17
|
+
import { fillAdditionalInfo, fillLegacyProps } from "./utils/legacyUtil";
|
|
18
|
+
import { formatStrategyValues, SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from "./utils/strategyUtil";
|
|
19
|
+
import { fillFieldNames, isNil, toArray } from "./utils/valueUtil";
|
|
20
|
+
import warningProps from "./utils/warningPropsUtil";
|
|
21
|
+
function isRawValue(value) {
|
|
22
|
+
return !value || typeof value !== 'object';
|
|
23
|
+
}
|
|
24
|
+
const TreeSelect = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
25
|
+
const {
|
|
26
|
+
id,
|
|
27
|
+
prefixCls = 'rc-tree-select',
|
|
28
|
+
// Value
|
|
29
|
+
value,
|
|
30
|
+
defaultValue,
|
|
31
|
+
onChange,
|
|
32
|
+
onSelect,
|
|
33
|
+
onDeselect,
|
|
34
|
+
// Search
|
|
35
|
+
searchValue,
|
|
36
|
+
inputValue,
|
|
37
|
+
onSearch,
|
|
38
|
+
autoClearSearchValue = true,
|
|
39
|
+
filterTreeNode,
|
|
40
|
+
treeNodeFilterProp = 'value',
|
|
41
|
+
// Selector
|
|
42
|
+
showCheckedStrategy,
|
|
43
|
+
treeNodeLabelProp,
|
|
44
|
+
// Mode
|
|
45
|
+
multiple,
|
|
46
|
+
treeCheckable,
|
|
47
|
+
treeCheckStrictly,
|
|
48
|
+
labelInValue,
|
|
49
|
+
maxCount,
|
|
50
|
+
// FieldNames
|
|
51
|
+
fieldNames,
|
|
52
|
+
// Data
|
|
53
|
+
treeDataSimpleMode,
|
|
54
|
+
treeData,
|
|
55
|
+
children,
|
|
56
|
+
loadData,
|
|
57
|
+
treeLoadedKeys,
|
|
58
|
+
onTreeLoad,
|
|
59
|
+
// Expanded
|
|
60
|
+
treeDefaultExpandAll,
|
|
61
|
+
treeExpandedKeys,
|
|
62
|
+
treeDefaultExpandedKeys,
|
|
63
|
+
onTreeExpand,
|
|
64
|
+
treeExpandAction,
|
|
65
|
+
// Options
|
|
66
|
+
virtual,
|
|
67
|
+
listHeight = 200,
|
|
68
|
+
listItemHeight = 20,
|
|
69
|
+
listItemScrollOffset = 0,
|
|
70
|
+
onPopupVisibleChange,
|
|
71
|
+
popupMatchSelectWidth = true,
|
|
72
|
+
// Tree
|
|
73
|
+
treeLine,
|
|
74
|
+
treeIcon,
|
|
75
|
+
showTreeIcon,
|
|
76
|
+
switcherIcon,
|
|
77
|
+
treeMotion,
|
|
78
|
+
treeTitleRender,
|
|
79
|
+
onPopupScroll,
|
|
80
|
+
...restProps
|
|
81
|
+
} = props;
|
|
82
|
+
const mergedId = useId(id);
|
|
83
|
+
const treeConduction = treeCheckable && !treeCheckStrictly;
|
|
84
|
+
const mergedCheckable = treeCheckable || treeCheckStrictly;
|
|
85
|
+
const mergedLabelInValue = treeCheckStrictly || labelInValue;
|
|
86
|
+
const mergedMultiple = mergedCheckable || multiple;
|
|
87
|
+
const [internalValue, setInternalValue] = useMergedState(defaultValue, {
|
|
88
|
+
value
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// `multiple` && `!treeCheckable` should be show all
|
|
92
|
+
const mergedShowCheckedStrategy = React.useMemo(() => {
|
|
93
|
+
if (!treeCheckable) {
|
|
94
|
+
return SHOW_ALL;
|
|
95
|
+
}
|
|
96
|
+
return showCheckedStrategy || SHOW_CHILD;
|
|
97
|
+
}, [showCheckedStrategy, treeCheckable]);
|
|
98
|
+
|
|
99
|
+
// ========================== Warning ===========================
|
|
100
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
101
|
+
warningProps(props);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ========================= FieldNames =========================
|
|
105
|
+
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
|
|
106
|
+
[JSON.stringify(fieldNames)]
|
|
107
|
+
/* eslint-enable react-hooks/exhaustive-deps */);
|
|
108
|
+
|
|
109
|
+
// =========================== Search ===========================
|
|
110
|
+
const [mergedSearchValue, setSearchValue] = useMergedState('', {
|
|
111
|
+
value: searchValue !== undefined ? searchValue : inputValue,
|
|
112
|
+
postState: search => search || ''
|
|
113
|
+
});
|
|
114
|
+
const onInternalSearch = searchText => {
|
|
115
|
+
setSearchValue(searchText);
|
|
116
|
+
onSearch?.(searchText);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// ============================ Data ============================
|
|
120
|
+
// `useTreeData` only do convert of `children` or `simpleMode`.
|
|
121
|
+
// Else will return origin `treeData` for perf consideration.
|
|
122
|
+
// Do not do anything to loop the data.
|
|
123
|
+
const mergedTreeData = useTreeData(treeData, children, treeDataSimpleMode);
|
|
124
|
+
const {
|
|
125
|
+
keyEntities,
|
|
126
|
+
valueEntities
|
|
127
|
+
} = useDataEntities(mergedTreeData, mergedFieldNames);
|
|
128
|
+
|
|
129
|
+
/** Get `missingRawValues` which not exist in the tree yet */
|
|
130
|
+
const splitRawValues = React.useCallback(newRawValues => {
|
|
131
|
+
const missingRawValues = [];
|
|
132
|
+
const existRawValues = [];
|
|
133
|
+
|
|
134
|
+
// Keep missing value in the cache
|
|
135
|
+
newRawValues.forEach(val => {
|
|
136
|
+
if (valueEntities.has(val)) {
|
|
137
|
+
existRawValues.push(val);
|
|
138
|
+
} else {
|
|
139
|
+
missingRawValues.push(val);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
missingRawValues,
|
|
144
|
+
existRawValues
|
|
145
|
+
};
|
|
146
|
+
}, [valueEntities]);
|
|
147
|
+
|
|
148
|
+
// Filtered Tree
|
|
149
|
+
const filteredTreeData = useFilterTreeData(mergedTreeData, mergedSearchValue, {
|
|
150
|
+
fieldNames: mergedFieldNames,
|
|
151
|
+
treeNodeFilterProp,
|
|
152
|
+
filterTreeNode
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// =========================== Label ============================
|
|
156
|
+
const getLabel = React.useCallback(item => {
|
|
157
|
+
if (item) {
|
|
158
|
+
if (treeNodeLabelProp) {
|
|
159
|
+
return item[treeNodeLabelProp];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Loop from fieldNames
|
|
163
|
+
const {
|
|
164
|
+
_title: titleList
|
|
165
|
+
} = mergedFieldNames;
|
|
166
|
+
for (let i = 0; i < titleList.length; i += 1) {
|
|
167
|
+
const title = item[titleList[i]];
|
|
168
|
+
if (title !== undefined) {
|
|
169
|
+
return title;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [mergedFieldNames, treeNodeLabelProp]);
|
|
174
|
+
|
|
175
|
+
// ========================= Wrap Value =========================
|
|
176
|
+
const toLabeledValues = React.useCallback(draftValues => {
|
|
177
|
+
const values = toArray(draftValues);
|
|
178
|
+
return values.map(val => {
|
|
179
|
+
if (isRawValue(val)) {
|
|
180
|
+
return {
|
|
181
|
+
value: val
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return val;
|
|
185
|
+
});
|
|
186
|
+
}, []);
|
|
187
|
+
const convert2LabelValues = React.useCallback(draftValues => {
|
|
188
|
+
const values = toLabeledValues(draftValues);
|
|
189
|
+
return values.map(item => {
|
|
190
|
+
let {
|
|
191
|
+
label: rawLabel
|
|
192
|
+
} = item;
|
|
193
|
+
const {
|
|
194
|
+
value: rawValue,
|
|
195
|
+
halfChecked: rawHalfChecked
|
|
196
|
+
} = item;
|
|
197
|
+
let rawDisabled;
|
|
198
|
+
const entity = valueEntities.get(rawValue);
|
|
199
|
+
|
|
200
|
+
// Fill missing label & status
|
|
201
|
+
if (entity) {
|
|
202
|
+
rawLabel = treeTitleRender ? treeTitleRender(entity.node) : rawLabel ?? getLabel(entity.node);
|
|
203
|
+
rawDisabled = entity.node.disabled;
|
|
204
|
+
} else if (rawLabel === undefined) {
|
|
205
|
+
// We try to find in current `labelInValue` value
|
|
206
|
+
const labelInValueItem = toLabeledValues(internalValue).find(labeledItem => labeledItem.value === rawValue);
|
|
207
|
+
rawLabel = labelInValueItem.label;
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
label: rawLabel,
|
|
211
|
+
value: rawValue,
|
|
212
|
+
halfChecked: rawHalfChecked,
|
|
213
|
+
disabled: rawDisabled
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}, [valueEntities, getLabel, toLabeledValues, internalValue]);
|
|
217
|
+
|
|
218
|
+
// =========================== Values ===========================
|
|
219
|
+
const rawMixedLabeledValues = React.useMemo(() => toLabeledValues(internalValue === null ? [] : internalValue), [toLabeledValues, internalValue]);
|
|
220
|
+
|
|
221
|
+
// Split value into full check and half check
|
|
222
|
+
const [rawLabeledValues, rawHalfLabeledValues] = React.useMemo(() => {
|
|
223
|
+
const fullCheckValues = [];
|
|
224
|
+
const halfCheckValues = [];
|
|
225
|
+
rawMixedLabeledValues.forEach(item => {
|
|
226
|
+
if (item.halfChecked) {
|
|
227
|
+
halfCheckValues.push(item);
|
|
228
|
+
} else {
|
|
229
|
+
fullCheckValues.push(item);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return [fullCheckValues, halfCheckValues];
|
|
233
|
+
}, [rawMixedLabeledValues]);
|
|
234
|
+
|
|
235
|
+
// const [mergedValues] = useCache(rawLabeledValues);
|
|
236
|
+
const rawValues = React.useMemo(() => rawLabeledValues.map(item => item.value), [rawLabeledValues]);
|
|
237
|
+
|
|
238
|
+
// Convert value to key. Will fill missed keys for conduct check.
|
|
239
|
+
const [rawCheckedValues, rawHalfCheckedValues] = useCheckedKeys(rawLabeledValues, rawHalfLabeledValues, treeConduction, keyEntities);
|
|
240
|
+
|
|
241
|
+
// Convert rawCheckedKeys to check strategy related values
|
|
242
|
+
const displayValues = React.useMemo(() => {
|
|
243
|
+
// Collect keys which need to show
|
|
244
|
+
const displayKeys = formatStrategyValues(rawCheckedValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames);
|
|
245
|
+
|
|
246
|
+
// Convert to value and filled with label
|
|
247
|
+
const values = displayKeys.map(key => keyEntities[key]?.node?.[mergedFieldNames.value] ?? key);
|
|
248
|
+
|
|
249
|
+
// Back fill with origin label
|
|
250
|
+
const labeledValues = values.map(val => {
|
|
251
|
+
const targetItem = rawLabeledValues.find(item => item.value === val);
|
|
252
|
+
const label = labelInValue ? targetItem?.label : treeTitleRender?.(targetItem);
|
|
253
|
+
return {
|
|
254
|
+
value: val,
|
|
255
|
+
label
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
const rawDisplayValues = convert2LabelValues(labeledValues);
|
|
259
|
+
const firstVal = rawDisplayValues[0];
|
|
260
|
+
if (!mergedMultiple && firstVal && isNil(firstVal.value) && isNil(firstVal.label)) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
return rawDisplayValues.map(item => ({
|
|
264
|
+
...item,
|
|
265
|
+
label: item.label ?? item.value
|
|
266
|
+
}));
|
|
267
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
268
|
+
}, [mergedFieldNames, mergedMultiple, rawCheckedValues, rawLabeledValues, convert2LabelValues, mergedShowCheckedStrategy, keyEntities]);
|
|
269
|
+
const [cachedDisplayValues] = useCache(displayValues);
|
|
270
|
+
|
|
271
|
+
// ========================== MaxCount ==========================
|
|
272
|
+
const mergedMaxCount = React.useMemo(() => {
|
|
273
|
+
if (mergedMultiple && (mergedShowCheckedStrategy === 'SHOW_CHILD' || treeCheckStrictly || !treeCheckable)) {
|
|
274
|
+
return maxCount;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}, [maxCount, mergedMultiple, treeCheckStrictly, mergedShowCheckedStrategy, treeCheckable]);
|
|
278
|
+
|
|
279
|
+
// =========================== Change ===========================
|
|
280
|
+
const triggerChange = useRefFunc((newRawValues, extra, source) => {
|
|
281
|
+
const formattedKeyList = formatStrategyValues(newRawValues, mergedShowCheckedStrategy, keyEntities, mergedFieldNames);
|
|
282
|
+
|
|
283
|
+
// Not allow pass with `maxCount`
|
|
284
|
+
if (mergedMaxCount && formattedKeyList.length > mergedMaxCount) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const labeledValues = convert2LabelValues(newRawValues);
|
|
288
|
+
setInternalValue(labeledValues);
|
|
289
|
+
|
|
290
|
+
// Clean up if needed
|
|
291
|
+
if (autoClearSearchValue) {
|
|
292
|
+
setSearchValue('');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate rest parameters is costly, so only do it when necessary
|
|
296
|
+
if (onChange) {
|
|
297
|
+
let eventValues = newRawValues;
|
|
298
|
+
if (treeConduction) {
|
|
299
|
+
eventValues = formattedKeyList.map(key => {
|
|
300
|
+
const entity = valueEntities.get(key);
|
|
301
|
+
return entity ? entity.node[mergedFieldNames.value] : key;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const {
|
|
305
|
+
triggerValue,
|
|
306
|
+
selected
|
|
307
|
+
} = extra || {
|
|
308
|
+
triggerValue: undefined,
|
|
309
|
+
selected: undefined
|
|
310
|
+
};
|
|
311
|
+
let returnRawValues = eventValues;
|
|
312
|
+
|
|
313
|
+
// We need fill half check back
|
|
314
|
+
if (treeCheckStrictly) {
|
|
315
|
+
const halfValues = rawHalfLabeledValues.filter(item => !eventValues.includes(item.value));
|
|
316
|
+
returnRawValues = [...returnRawValues, ...halfValues];
|
|
317
|
+
}
|
|
318
|
+
const returnLabeledValues = convert2LabelValues(returnRawValues);
|
|
319
|
+
const additionalInfo = {
|
|
320
|
+
// [Legacy] Always return as array contains label & value
|
|
321
|
+
preValue: rawLabeledValues,
|
|
322
|
+
triggerValue
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// [Legacy] Fill legacy data if user query.
|
|
326
|
+
// This is expansive that we only fill when user query
|
|
327
|
+
// https://github.com/react-component/tree-select/blob/fe33eb7c27830c9ac70cd1fdb1ebbe7bc679c16a/src/Select.jsx
|
|
328
|
+
let showPosition = true;
|
|
329
|
+
if (treeCheckStrictly || source === 'selection' && !selected) {
|
|
330
|
+
showPosition = false;
|
|
331
|
+
}
|
|
332
|
+
fillAdditionalInfo(additionalInfo, triggerValue, newRawValues, mergedTreeData, showPosition, mergedFieldNames);
|
|
333
|
+
if (mergedCheckable) {
|
|
334
|
+
additionalInfo.checked = selected;
|
|
335
|
+
} else {
|
|
336
|
+
additionalInfo.selected = selected;
|
|
337
|
+
}
|
|
338
|
+
const returnValues = mergedLabelInValue ? returnLabeledValues : returnLabeledValues.map(item => item.value);
|
|
339
|
+
onChange(mergedMultiple ? returnValues : returnValues[0], mergedLabelInValue ? null : returnLabeledValues.map(item => item.label), additionalInfo);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// ========================== Options ===========================
|
|
344
|
+
/** Trigger by option list */
|
|
345
|
+
const onOptionSelect = React.useCallback((selectedKey, {
|
|
346
|
+
selected,
|
|
347
|
+
source
|
|
348
|
+
}) => {
|
|
349
|
+
const entity = keyEntities[selectedKey];
|
|
350
|
+
const node = entity?.node;
|
|
351
|
+
const selectedValue = node?.[mergedFieldNames.value] ?? selectedKey;
|
|
352
|
+
|
|
353
|
+
// Never be falsy but keep it safe
|
|
354
|
+
if (!mergedMultiple) {
|
|
355
|
+
// Single mode always set value
|
|
356
|
+
triggerChange([selectedValue], {
|
|
357
|
+
selected: true,
|
|
358
|
+
triggerValue: selectedValue
|
|
359
|
+
}, 'option');
|
|
360
|
+
} else {
|
|
361
|
+
let newRawValues = selected ? [...rawValues, selectedValue] : rawCheckedValues.filter(v => v !== selectedValue);
|
|
362
|
+
|
|
363
|
+
// Add keys if tree conduction
|
|
364
|
+
if (treeConduction) {
|
|
365
|
+
// Should keep missing values
|
|
366
|
+
const {
|
|
367
|
+
missingRawValues,
|
|
368
|
+
existRawValues
|
|
369
|
+
} = splitRawValues(newRawValues);
|
|
370
|
+
const keyList = existRawValues.map(val => valueEntities.get(val).key);
|
|
371
|
+
|
|
372
|
+
// Conduction by selected or not
|
|
373
|
+
let checkedKeys;
|
|
374
|
+
if (selected) {
|
|
375
|
+
({
|
|
376
|
+
checkedKeys
|
|
377
|
+
} = conductCheck(keyList, true, keyEntities));
|
|
378
|
+
} else {
|
|
379
|
+
({
|
|
380
|
+
checkedKeys
|
|
381
|
+
} = conductCheck(keyList, {
|
|
382
|
+
checked: false,
|
|
383
|
+
halfCheckedKeys: rawHalfCheckedValues
|
|
384
|
+
}, keyEntities));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Fill back of keys
|
|
388
|
+
newRawValues = [...missingRawValues, ...checkedKeys.map(key => keyEntities[key].node[mergedFieldNames.value])];
|
|
389
|
+
}
|
|
390
|
+
triggerChange(newRawValues, {
|
|
391
|
+
selected,
|
|
392
|
+
triggerValue: selectedValue
|
|
393
|
+
}, source || 'option');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Trigger select event
|
|
397
|
+
if (selected || !mergedMultiple) {
|
|
398
|
+
onSelect?.(selectedValue, fillLegacyProps(node));
|
|
399
|
+
} else {
|
|
400
|
+
onDeselect?.(selectedValue, fillLegacyProps(node));
|
|
401
|
+
}
|
|
402
|
+
}, [splitRawValues, valueEntities, keyEntities, mergedFieldNames, mergedMultiple, rawValues, triggerChange, treeConduction, onSelect, onDeselect, rawCheckedValues, rawHalfCheckedValues, maxCount]);
|
|
403
|
+
|
|
404
|
+
// ========================== Dropdown ==========================
|
|
405
|
+
const onInternalPopupVisibleChange = React.useCallback(open => {
|
|
406
|
+
if (onPopupVisibleChange) {
|
|
407
|
+
onPopupVisibleChange(open);
|
|
408
|
+
}
|
|
409
|
+
}, [onPopupVisibleChange]);
|
|
410
|
+
|
|
411
|
+
// ====================== Display Change ========================
|
|
412
|
+
const onDisplayValuesChange = useRefFunc((newValues, info) => {
|
|
413
|
+
const newRawValues = newValues.map(item => item.value);
|
|
414
|
+
if (info.type === 'clear') {
|
|
415
|
+
triggerChange(newRawValues, {}, 'selection');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// TreeSelect only have multiple mode which means display change only has remove
|
|
420
|
+
if (info.values.length) {
|
|
421
|
+
onOptionSelect(info.values[0].value, {
|
|
422
|
+
selected: false,
|
|
423
|
+
source: 'selection'
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// ========================== Context ===========================
|
|
429
|
+
const treeSelectContext = React.useMemo(() => {
|
|
430
|
+
return {
|
|
431
|
+
virtual,
|
|
432
|
+
popupMatchSelectWidth,
|
|
433
|
+
listHeight,
|
|
434
|
+
listItemHeight,
|
|
435
|
+
listItemScrollOffset,
|
|
436
|
+
treeData: filteredTreeData,
|
|
437
|
+
fieldNames: mergedFieldNames,
|
|
438
|
+
onSelect: onOptionSelect,
|
|
439
|
+
treeExpandAction,
|
|
440
|
+
treeTitleRender,
|
|
441
|
+
onPopupScroll,
|
|
442
|
+
leftMaxCount: maxCount === undefined ? null : maxCount - cachedDisplayValues.length,
|
|
443
|
+
leafCountOnly: mergedShowCheckedStrategy === 'SHOW_CHILD' && !treeCheckStrictly && !!treeCheckable,
|
|
444
|
+
valueEntities
|
|
445
|
+
};
|
|
446
|
+
}, [virtual, popupMatchSelectWidth, listHeight, listItemHeight, listItemScrollOffset, filteredTreeData, mergedFieldNames, onOptionSelect, treeExpandAction, treeTitleRender, onPopupScroll, maxCount, cachedDisplayValues.length, mergedShowCheckedStrategy, treeCheckStrictly, treeCheckable, valueEntities]);
|
|
447
|
+
|
|
448
|
+
// ======================= Legacy Context =======================
|
|
449
|
+
const legacyContext = React.useMemo(() => ({
|
|
450
|
+
checkable: mergedCheckable,
|
|
451
|
+
loadData,
|
|
452
|
+
treeLoadedKeys,
|
|
453
|
+
onTreeLoad,
|
|
454
|
+
checkedKeys: rawCheckedValues,
|
|
455
|
+
halfCheckedKeys: rawHalfCheckedValues,
|
|
456
|
+
treeDefaultExpandAll,
|
|
457
|
+
treeExpandedKeys,
|
|
458
|
+
treeDefaultExpandedKeys,
|
|
459
|
+
onTreeExpand,
|
|
460
|
+
treeIcon,
|
|
461
|
+
treeMotion,
|
|
462
|
+
showTreeIcon,
|
|
463
|
+
switcherIcon,
|
|
464
|
+
treeLine,
|
|
465
|
+
treeNodeFilterProp,
|
|
466
|
+
keyEntities
|
|
467
|
+
}), [mergedCheckable, loadData, treeLoadedKeys, onTreeLoad, rawCheckedValues, rawHalfCheckedValues, treeDefaultExpandAll, treeExpandedKeys, treeDefaultExpandedKeys, onTreeExpand, treeIcon, treeMotion, showTreeIcon, switcherIcon, treeLine, treeNodeFilterProp, keyEntities]);
|
|
468
|
+
|
|
469
|
+
// =========================== Render ===========================
|
|
470
|
+
return /*#__PURE__*/React.createElement(TreeSelectContext.Provider, {
|
|
471
|
+
value: treeSelectContext
|
|
472
|
+
}, /*#__PURE__*/React.createElement(LegacyContext.Provider, {
|
|
473
|
+
value: legacyContext
|
|
474
|
+
}, /*#__PURE__*/React.createElement(BaseSelect, _extends({
|
|
475
|
+
ref: ref
|
|
476
|
+
}, restProps, {
|
|
477
|
+
// >>> MISC
|
|
478
|
+
id: mergedId,
|
|
479
|
+
prefixCls: prefixCls,
|
|
480
|
+
mode: mergedMultiple ? 'multiple' : undefined
|
|
481
|
+
// >>> Display Value
|
|
482
|
+
,
|
|
483
|
+
displayValues: cachedDisplayValues,
|
|
484
|
+
onDisplayValuesChange: onDisplayValuesChange
|
|
485
|
+
// >>> Search
|
|
486
|
+
,
|
|
487
|
+
searchValue: mergedSearchValue,
|
|
488
|
+
onSearch: onInternalSearch
|
|
489
|
+
// >>> Options
|
|
490
|
+
,
|
|
491
|
+
OptionList: OptionList,
|
|
492
|
+
emptyOptions: !mergedTreeData.length,
|
|
493
|
+
onPopupVisibleChange: onInternalPopupVisibleChange,
|
|
494
|
+
popupMatchSelectWidth: popupMatchSelectWidth
|
|
495
|
+
}))));
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Assign name for Debug
|
|
499
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
500
|
+
TreeSelect.displayName = 'TreeSelect';
|
|
501
|
+
}
|
|
502
|
+
const GenericTreeSelect = TreeSelect;
|
|
503
|
+
GenericTreeSelect.TreeNode = TreeNode;
|
|
504
|
+
GenericTreeSelect.SHOW_ALL = SHOW_ALL;
|
|
505
|
+
GenericTreeSelect.SHOW_PARENT = SHOW_PARENT;
|
|
506
|
+
GenericTreeSelect.SHOW_CHILD = SHOW_CHILD;
|
|
507
|
+
export default GenericTreeSelect;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ExpandAction } from 'rc-tree/lib/Tree';
|
|
3
|
+
import type { DataNode, FieldNames, Key } from './interface';
|
|
4
|
+
import type useDataEntities from './hooks/useDataEntities';
|
|
5
|
+
export interface TreeSelectContextProps {
|
|
6
|
+
virtual?: boolean;
|
|
7
|
+
popupMatchSelectWidth?: boolean | number;
|
|
8
|
+
listHeight: number;
|
|
9
|
+
listItemHeight: number;
|
|
10
|
+
listItemScrollOffset?: number;
|
|
11
|
+
treeData: DataNode[];
|
|
12
|
+
fieldNames: FieldNames;
|
|
13
|
+
onSelect: (value: Key, info: {
|
|
14
|
+
selected: boolean;
|
|
15
|
+
}) => void;
|
|
16
|
+
treeExpandAction?: ExpandAction;
|
|
17
|
+
treeTitleRender?: (node: any) => React.ReactNode;
|
|
18
|
+
onPopupScroll?: React.UIEventHandler<HTMLDivElement>;
|
|
19
|
+
leftMaxCount: number | null;
|
|
20
|
+
/** When `true`, only take leaf node as count, or take all as count with `maxCount` limitation */
|
|
21
|
+
leafCountOnly: boolean;
|
|
22
|
+
valueEntities: ReturnType<typeof useDataEntities>['valueEntities'];
|
|
23
|
+
}
|
|
24
|
+
declare const TreeSelectContext: React.Context<TreeSelectContextProps>;
|
|
25
|
+
export default TreeSelectContext;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LabeledValueType } from '../interface';
|
|
2
|
+
/**
|
|
3
|
+
* This function will try to call requestIdleCallback if available to save performance.
|
|
4
|
+
* No need `getLabel` here since already fetch on `rawLabeledValue`.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: (values: LabeledValueType[]) => [LabeledValueType[]];
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* This function will try to call requestIdleCallback if available to save performance.
|
|
4
|
+
* No need `getLabel` here since already fetch on `rawLabeledValue`.
|
|
5
|
+
*/
|
|
6
|
+
export default (values => {
|
|
7
|
+
const cacheRef = React.useRef({
|
|
8
|
+
valueLabels: new Map()
|
|
9
|
+
});
|
|
10
|
+
return React.useMemo(() => {
|
|
11
|
+
const {
|
|
12
|
+
valueLabels
|
|
13
|
+
} = cacheRef.current;
|
|
14
|
+
const valueLabelsCache = new Map();
|
|
15
|
+
const filledValues = values.map(item => {
|
|
16
|
+
const {
|
|
17
|
+
value,
|
|
18
|
+
label
|
|
19
|
+
} = item;
|
|
20
|
+
const mergedLabel = label ?? valueLabels.get(value);
|
|
21
|
+
|
|
22
|
+
// Save in cache
|
|
23
|
+
valueLabelsCache.set(value, mergedLabel);
|
|
24
|
+
return {
|
|
25
|
+
...item,
|
|
26
|
+
label: mergedLabel
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
cacheRef.current.valueLabels = valueLabelsCache;
|
|
30
|
+
return [filledValues];
|
|
31
|
+
}, [values]);
|
|
32
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { DataEntity } from 'rc-tree/lib/interface';
|
|
3
|
+
import type { LabeledValueType, SafeKey } from '../interface';
|
|
4
|
+
declare const useCheckedKeys: (rawLabeledValues: LabeledValueType[], rawHalfCheckedValues: LabeledValueType[], treeConduction: boolean, keyEntities: Record<SafeKey, DataEntity>) => React.Key[][];
|
|
5
|
+
export default useCheckedKeys;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { conductCheck } from "rc-tree/es/utils/conductUtil";
|
|
3
|
+
const useCheckedKeys = (rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities) => {
|
|
4
|
+
return React.useMemo(() => {
|
|
5
|
+
const extractValues = values => values.map(({
|
|
6
|
+
value
|
|
7
|
+
}) => value);
|
|
8
|
+
const checkedKeys = extractValues(rawLabeledValues);
|
|
9
|
+
const halfCheckedKeys = extractValues(rawHalfCheckedValues);
|
|
10
|
+
const missingValues = checkedKeys.filter(key => !keyEntities[key]);
|
|
11
|
+
let finalCheckedKeys = checkedKeys;
|
|
12
|
+
let finalHalfCheckedKeys = halfCheckedKeys;
|
|
13
|
+
if (treeConduction) {
|
|
14
|
+
const conductResult = conductCheck(checkedKeys, true, keyEntities);
|
|
15
|
+
finalCheckedKeys = conductResult.checkedKeys;
|
|
16
|
+
finalHalfCheckedKeys = conductResult.halfCheckedKeys;
|
|
17
|
+
}
|
|
18
|
+
return [Array.from(new Set([...missingValues, ...finalCheckedKeys])), finalHalfCheckedKeys];
|
|
19
|
+
}, [rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities]);
|
|
20
|
+
};
|
|
21
|
+
export default useCheckedKeys;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DataEntity } from 'rc-tree/lib/interface';
|
|
2
|
+
import type { SafeKey, FieldNames } from '../interface';
|
|
3
|
+
declare const _default: (treeData: any, fieldNames: FieldNames) => {
|
|
4
|
+
valueEntities: Map<SafeKey, DataEntity>;
|
|
5
|
+
keyEntities: Record<string, DataEntity>;
|
|
6
|
+
};
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { convertDataToEntities } from "rc-tree/es/utils/treeUtil";
|
|
3
|
+
import warning from "@rc-component/util/es/warning";
|
|
4
|
+
import { isNil } from "../utils/valueUtil";
|
|
5
|
+
export default ((treeData, fieldNames) => React.useMemo(() => {
|
|
6
|
+
const collection = convertDataToEntities(treeData, {
|
|
7
|
+
fieldNames,
|
|
8
|
+
initWrapper: wrapper => ({
|
|
9
|
+
...wrapper,
|
|
10
|
+
valueEntities: new Map()
|
|
11
|
+
}),
|
|
12
|
+
processEntity: (entity, wrapper) => {
|
|
13
|
+
const val = entity.node[fieldNames.value];
|
|
14
|
+
|
|
15
|
+
// Check if exist same value
|
|
16
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
17
|
+
const key = entity.node.key;
|
|
18
|
+
warning(!isNil(val), 'TreeNode `value` is invalidate: undefined');
|
|
19
|
+
warning(!wrapper.valueEntities.has(val), `Same \`value\` exist in the tree: ${val}`);
|
|
20
|
+
warning(!key || String(key) === String(val), `\`key\` or \`value\` with TreeNode must be the same or you can remove one of them. key: ${key}, value: ${val}.`);
|
|
21
|
+
}
|
|
22
|
+
wrapper.valueEntities.set(val, entity);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return collection;
|
|
26
|
+
}, [treeData, fieldNames]));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TreeSelectProps } from '../TreeSelect';
|
|
2
|
+
import type { DataNode, FieldNames } from '../interface';
|
|
3
|
+
declare const useFilterTreeData: (treeData: DataNode[], searchValue: string, options: {
|
|
4
|
+
fieldNames: FieldNames;
|
|
5
|
+
treeNodeFilterProp: string;
|
|
6
|
+
filterTreeNode: TreeSelectProps['filterTreeNode'];
|
|
7
|
+
}) => DataNode[];
|
|
8
|
+
export default useFilterTreeData;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { fillLegacyProps } from "../utils/legacyUtil";
|
|
3
|
+
const useFilterTreeData = (treeData, searchValue, options) => {
|
|
4
|
+
const {
|
|
5
|
+
fieldNames,
|
|
6
|
+
treeNodeFilterProp,
|
|
7
|
+
filterTreeNode
|
|
8
|
+
} = options;
|
|
9
|
+
const {
|
|
10
|
+
children: fieldChildren
|
|
11
|
+
} = fieldNames;
|
|
12
|
+
return React.useMemo(() => {
|
|
13
|
+
if (!searchValue || filterTreeNode === false) {
|
|
14
|
+
return treeData;
|
|
15
|
+
}
|
|
16
|
+
const filterOptionFunc = typeof filterTreeNode === 'function' ? filterTreeNode : (_, dataNode) => String(dataNode[treeNodeFilterProp]).toUpperCase().includes(searchValue.toUpperCase());
|
|
17
|
+
const filterTreeNodes = (nodes, keepAll = false) => nodes.reduce((filtered, node) => {
|
|
18
|
+
const children = node[fieldChildren];
|
|
19
|
+
const isMatch = keepAll || filterOptionFunc(searchValue, fillLegacyProps(node));
|
|
20
|
+
const filteredChildren = filterTreeNodes(children || [], isMatch);
|
|
21
|
+
if (isMatch || filteredChildren.length) {
|
|
22
|
+
filtered.push({
|
|
23
|
+
...node,
|
|
24
|
+
isLeaf: undefined,
|
|
25
|
+
[fieldChildren]: filteredChildren
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return filtered;
|
|
29
|
+
}, []);
|
|
30
|
+
return filterTreeNodes(treeData);
|
|
31
|
+
}, [treeData, searchValue, fieldChildren, treeNodeFilterProp, filterTreeNode]);
|
|
32
|
+
};
|
|
33
|
+
export default useFilterTreeData;
|