Skip to content

Commit b858a0c

Browse files
aojunhao123zombieJ
andauthored
feat: supplement maxCount logic for complicated cases (#602)
* feat: supplement maxCount logic for complicated cases * chore: remove unreachable logic * feat: supplement maxCount logic for complicated cases * fix: lint fix * chore: remove console.log * chore: remove conductCheck * chore: change limit maxCount * chore: update maxCount logic * chore: add warning * chore: cache cal * fix: optimize null check logic * chore: enhance warning message * test: add warnings for maxCount --------- Co-authored-by: 二货机器人 <[email protected]>
1 parent 78fdfa5 commit b858a0c

7 files changed

+266
-56
lines changed

examples/mutiple-with-maxCount.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ export default () => {
2020
key: '1-2',
2121
value: '1-2',
2222
title: '1-2',
23+
disabled: true,
24+
children: [
25+
{
26+
key: '1-2-1',
27+
value: '1-2-1',
28+
title: '1-2-1',
29+
disabled: true,
30+
},
31+
{
32+
key: '1-2-2',
33+
value: '1-2-2',
34+
title: '1-2-2',
35+
},
36+
],
2337
},
2438
{
2539
key: '1-3',
@@ -63,21 +77,17 @@ export default () => {
6377
maxCount={3}
6478
treeData={treeData}
6579
/>
66-
6780
<h2>checkable with maxCount</h2>
6881
<TreeSelect
6982
style={{ width: 300 }}
70-
multiple
7183
treeCheckable
7284
// showCheckedStrategy="SHOW_ALL"
73-
showCheckedStrategy="SHOW_PARENT"
74-
// showCheckedStrategy="SHOW_CHILD"
85+
// showCheckedStrategy="SHOW_PARENT"
7586
maxCount={4}
7687
treeData={treeData}
7788
onChange={onChange}
7889
value={value}
7990
/>
80-
8191
<h2>checkable with maxCount and treeCheckStrictly</h2>
8292
<TreeSelect
8393
style={{ width: 300 }}

src/OptionList.tsx

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
4747
treeExpandAction,
4848
treeTitleRender,
4949
onPopupScroll,
50-
displayValues,
51-
isOverMaxCount,
50+
leftMaxCount,
51+
leafCountOnly,
52+
valueEntities,
5253
} = React.useContext(TreeSelectContext);
5354

5455
const {
@@ -80,11 +81,6 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
8081
(prev, next) => next[0] && prev[1] !== next[1],
8182
);
8283

83-
const memoRawValues = React.useMemo(
84-
() => (displayValues || []).map(v => v.value),
85-
[displayValues],
86-
);
87-
8884
// ========================== Values ==========================
8985
const mergedCheckedKeys = React.useMemo(() => {
9086
if (!checkable) {
@@ -163,8 +159,60 @@ const OptionList: React.ForwardRefRenderFunction<ReviseRefOptionListProps> = (_,
163159
// eslint-disable-next-line react-hooks/exhaustive-deps
164160
}, [searchValue]);
165161

162+
// ========================= Disabled =========================
163+
const disabledCacheRef = React.useRef<Map<string, boolean>>(new Map());
164+
165+
// Clear cache if `leftMaxCount` changed
166+
React.useEffect(() => {
167+
if (leftMaxCount) {
168+
disabledCacheRef.current.clear();
169+
}
170+
}, [leftMaxCount]);
171+
172+
function getDisabledWithCache(node: DataNode) {
173+
const value = node[fieldNames.value];
174+
if (!disabledCacheRef.current.has(value)) {
175+
const entity = valueEntities.get(value);
176+
const isLeaf = (entity.children || []).length === 0;
177+
178+
if (!isLeaf) {
179+
const checkableChildren = entity.children.filter(
180+
childTreeNode =>
181+
!childTreeNode.node.disabled &&
182+
!childTreeNode.node.disableCheckbox &&
183+
!checkedKeys.includes(childTreeNode.node[fieldNames.value]),
184+
);
185+
186+
const checkableChildrenCount = checkableChildren.length;
187+
disabledCacheRef.current.set(value, checkableChildrenCount > leftMaxCount);
188+
} else {
189+
disabledCacheRef.current.set(value, false);
190+
}
191+
}
192+
return disabledCacheRef.current.get(value);
193+
}
194+
166195
const nodeDisabled = useEvent((node: DataNode) => {
167-
return isOverMaxCount && !memoRawValues.includes(node[fieldNames.value]);
196+
const nodeValue = node[fieldNames.value];
197+
198+
if (checkedKeys.includes(nodeValue)) {
199+
return false;
200+
}
201+
202+
if (leftMaxCount === null) {
203+
return false;
204+
}
205+
206+
if (leftMaxCount <= 0) {
207+
return true;
208+
}
209+
210+
// This is a low performance calculation
211+
if (leafCountOnly && leftMaxCount) {
212+
return getDisabledWithCache(node);
213+
}
214+
215+
return false;
168216
});
169217

170218
// ========================== Get First Selectable Node ==========================

src/TreeSelect.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,17 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
408408

409409
const [cachedDisplayValues] = useCache(displayValues);
410410

411+
// ========================== MaxCount ==========================
412+
const mergedMaxCount = React.useMemo(() => {
413+
if (
414+
mergedMultiple &&
415+
(mergedShowCheckedStrategy === 'SHOW_CHILD' || treeCheckStrictly || !treeCheckable)
416+
) {
417+
return maxCount;
418+
}
419+
return null;
420+
}, [maxCount, mergedMultiple, treeCheckStrictly, mergedShowCheckedStrategy, treeCheckable]);
421+
411422
// =========================== Change ===========================
412423
const triggerChange = useRefFunc(
413424
(
@@ -422,11 +433,9 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
422433
mergedFieldNames,
423434
);
424435

425-
// if multiple and maxCount is set, check if exceed maxCount
426-
if (mergedMultiple && maxCount !== undefined) {
427-
if (formattedKeyList.length > maxCount) {
428-
return;
429-
}
436+
// Not allow pass with `maxCount`
437+
if (mergedMaxCount && formattedKeyList.length > mergedMaxCount) {
438+
return;
430439
}
431440

432441
const labeledValues = convert2LabelValues(newRawValues);
@@ -607,9 +616,6 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
607616
});
608617

609618
// ========================== Context ===========================
610-
const isOverMaxCount =
611-
mergedMultiple && maxCount !== undefined && cachedDisplayValues?.length >= maxCount;
612-
613619
const treeSelectContext = React.useMemo<TreeSelectContextProps>(() => {
614620
return {
615621
virtual,
@@ -623,8 +629,10 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
623629
treeExpandAction,
624630
treeTitleRender,
625631
onPopupScroll,
626-
displayValues: cachedDisplayValues,
627-
isOverMaxCount,
632+
leftMaxCount: maxCount === undefined ? null : maxCount - cachedDisplayValues.length,
633+
leafCountOnly:
634+
mergedShowCheckedStrategy === 'SHOW_CHILD' && !treeCheckStrictly && !!treeCheckable,
635+
valueEntities,
628636
};
629637
}, [
630638
virtual,
@@ -639,8 +647,11 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
639647
treeTitleRender,
640648
onPopupScroll,
641649
maxCount,
642-
cachedDisplayValues,
643-
mergedMultiple,
650+
cachedDisplayValues.length,
651+
mergedShowCheckedStrategy,
652+
treeCheckStrictly,
653+
treeCheckable,
654+
valueEntities,
644655
]);
645656

646657
// ======================= Legacy Context =======================

src/TreeSelectContext.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import type { ExpandAction } from 'rc-tree/lib/Tree';
3-
import type { DataNode, FieldNames, Key, LabeledValueType } from './interface';
3+
import type { DataNode, FieldNames, Key } from './interface';
4+
import type useDataEntities from './hooks/useDataEntities';
45

56
export interface TreeSelectContextProps {
67
virtual?: boolean;
@@ -14,8 +15,12 @@ export interface TreeSelectContextProps {
1415
treeExpandAction?: ExpandAction;
1516
treeTitleRender?: (node: any) => React.ReactNode;
1617
onPopupScroll?: React.UIEventHandler<HTMLDivElement>;
17-
displayValues?: LabeledValueType[];
18-
isOverMaxCount?: boolean;
18+
19+
// For `maxCount` usage
20+
leftMaxCount: number | null;
21+
/** When `true`, only take leaf node as count, or take all as count with `maxCount` limitation */
22+
leafCountOnly: boolean;
23+
valueEntities: ReturnType<typeof useDataEntities>['valueEntities'];
1924
}
2025

2126
const TreeSelectContext = React.createContext<TreeSelectContextProps>(null as any);

src/utils/warningPropsUtil.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) {
1010
labelInValue,
1111
value,
1212
multiple,
13+
showCheckedStrategy,
14+
maxCount,
1315
} = props;
1416

1517
warning(!searchPlaceholder, '`searchPlaceholder` has been removed.');
@@ -20,7 +22,7 @@ function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) {
2022

2123
if (labelInValue || treeCheckStrictly) {
2224
warning(
23-
toArray(value).every((val) => val && typeof val === 'object' && 'value' in val),
25+
toArray(value).every(val => val && typeof val === 'object' && 'value' in val),
2426
'Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.',
2527
);
2628
}
@@ -33,6 +35,17 @@ function warningProps(props: TreeSelectProps & { searchPlaceholder?: string }) {
3335
} else {
3436
warning(!Array.isArray(value), '`value` should not be array when `TreeSelect` is single mode.');
3537
}
38+
39+
if (
40+
maxCount &&
41+
((showCheckedStrategy === 'SHOW_ALL' && !treeCheckStrictly) ||
42+
showCheckedStrategy === 'SHOW_PARENT')
43+
) {
44+
warning(
45+
false,
46+
'`maxCount` not work with `showCheckedStrategy=SHOW_ALL` (when `treeCheckStrictly=false`) or `showCheckedStrategy=SHOW_PARENT`.',
47+
);
48+
}
3649
}
3750

3851
export default warningProps;

0 commit comments

Comments
 (0)