Skip to content

Commit 239b6fc

Browse files
authored
feat: Add Cascader.Panel (#430)
* chore: init * refactor: more hooks * refactor: more hooks * chore: more * chore: ab hooks * chore: update style * chore: update demo * test: add test case * test: add test case * test: add test case
1 parent 785e52d commit 239b6fc

File tree

18 files changed

+814
-336
lines changed

18 files changed

+814
-336
lines changed

assets/index.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
@import "./select.less";
2-
@import "./list.less";
2+
@import "./list.less";
3+
@import "./panel.less";

assets/list.less

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
padding-right: 20px;
3131
position: relative;
3232

33+
&:hover {
34+
background: rgba(0, 0, 255, 0.1);
35+
}
36+
3337
&-selected {
3438
background: rgba(0, 0, 255, 0.05);
3539
}

assets/panel.less

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import (reference) './index.less';
2+
3+
.@{select-prefix} {
4+
&-panel {
5+
border: 1px solid green;
6+
}
7+
}

docs/demo/panel.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Panel
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../../examples/panel.tsx"></code>

examples/panel.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/* eslint-disable no-console */
2+
import React from 'react';
3+
import '../assets/index.less';
4+
import Cascader from '../src';
5+
6+
const addressOptions = [
7+
{
8+
label: '福建',
9+
value: 'fj',
10+
children: [
11+
{
12+
label: '福州',
13+
value: 'fuzhou',
14+
children: [
15+
{
16+
label: '马尾',
17+
value: 'mawei',
18+
},
19+
],
20+
},
21+
{
22+
label: '泉州',
23+
value: 'quanzhou',
24+
},
25+
],
26+
},
27+
{
28+
label: '浙江',
29+
value: 'zj',
30+
children: [
31+
{
32+
label: '杭州',
33+
value: 'hangzhou',
34+
children: [
35+
{
36+
label: '余杭',
37+
value: 'yuhang',
38+
},
39+
],
40+
},
41+
],
42+
},
43+
{
44+
label: '北京',
45+
value: 'bj',
46+
children: [
47+
{
48+
label: '朝阳区',
49+
value: 'chaoyang',
50+
},
51+
{
52+
label: '海淀区',
53+
value: 'haidian',
54+
},
55+
],
56+
},
57+
];
58+
59+
export default () => {
60+
return (
61+
<>
62+
<h1>Panel</h1>
63+
<Cascader.Panel
64+
checkable
65+
options={addressOptions}
66+
onChange={value => {
67+
console.log('Change:', value);
68+
}}
69+
/>
70+
</>
71+
);
72+
};

src/Cascader.tsx

Lines changed: 36 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ import type { BaseSelectProps, BaseSelectPropsWithoutPrivate, BaseSelectRef } fr
33
import { BaseSelect } from 'rc-select';
44
import type { DisplayValueType, Placement } from 'rc-select/lib/BaseSelect';
55
import useId from 'rc-select/lib/hooks/useId';
6-
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
76
import useEvent from 'rc-util/lib/hooks/useEvent';
87
import useMergedState from 'rc-util/lib/hooks/useMergedState';
98
import * as React from 'react';
109
import CascaderContext from './context';
1110
import useDisplayValues from './hooks/useDisplayValues';
12-
import useEntities from './hooks/useEntities';
1311
import useMissingValues from './hooks/useMissingValues';
12+
import useOptions from './hooks/useOptions';
1413
import useSearchConfig from './hooks/useSearchConfig';
1514
import useSearchOptions from './hooks/useSearchOptions';
15+
import useSelect from './hooks/useSelect';
16+
import useValues from './hooks/useValues';
1617
import OptionList from './OptionList';
17-
import { fillFieldNames, SHOW_CHILD, SHOW_PARENT, toPathKey, toPathKeys } from './utils/commonUtil';
18+
import Panel from './Panel';
19+
import {
20+
fillFieldNames,
21+
SHOW_CHILD,
22+
SHOW_PARENT,
23+
toPathKeys,
24+
toRawValues,
25+
} from './utils/commonUtil';
1826
import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
1927
import warningProps, { warningNullOptions } from './utils/warningPropsUtil';
2028

@@ -150,22 +158,6 @@ export type InternalCascaderProps<OptionType extends BaseOptionType = DefaultOpt
150158

151159
export type CascaderRef = Omit<BaseSelectRef, 'scrollTo'>;
152160

153-
function isMultipleValue(value: ValueType): value is SingleValueType[] {
154-
return Array.isArray(value) && Array.isArray(value[0]);
155-
}
156-
157-
function toRawValues(value: ValueType): SingleValueType[] {
158-
if (!value) {
159-
return [];
160-
}
161-
162-
if (isMultipleValue(value)) {
163-
return value;
164-
}
165-
166-
return (value.length === 0 ? [] : [value]).map(val => (Array.isArray(val) ? val : [val]));
167-
}
168-
169161
const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, ref) => {
170162
const {
171163
// MISC
@@ -238,23 +230,9 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
238230
);
239231

240232
// =========================== Option ===========================
241-
const mergedOptions = React.useMemo(() => options || [], [options]);
242-
243-
// Only used in multiple mode, this fn will not call in single mode
244-
const getPathKeyEntities = useEntities(mergedOptions, mergedFieldNames);
245-
246-
/** Convert path key back to value format */
247-
const getValueByKeyPath = React.useCallback(
248-
(pathKeys: React.Key[]): SingleValueType[] => {
249-
const keyPathEntities = getPathKeyEntities();
250-
251-
return pathKeys.map(pathKey => {
252-
const { nodes } = keyPathEntities[pathKey];
253-
254-
return nodes.map(node => node[mergedFieldNames.value]);
255-
});
256-
},
257-
[getPathKeyEntities, mergedFieldNames],
233+
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = useOptions(
234+
mergedFieldNames,
235+
options,
258236
);
259237

260238
// =========================== Search ===========================
@@ -286,21 +264,13 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
286264
const getMissingValues = useMissingValues(mergedOptions, mergedFieldNames);
287265

288266
// Fill `rawValues` with checked conduction values
289-
const [checkedValues, halfCheckedValues, missingCheckedValues] = React.useMemo(() => {
290-
const [existValues, missingValues] = getMissingValues(rawValues);
291-
292-
if (!multiple || !rawValues.length) {
293-
return [existValues, [], missingValues];
294-
}
295-
296-
const keyPathValues = toPathKeys(existValues);
297-
const keyPathEntities = getPathKeyEntities();
298-
299-
const { checkedKeys, halfCheckedKeys } = conductCheck(keyPathValues, true, keyPathEntities);
300-
301-
// Convert key back to value cells
302-
return [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
303-
}, [multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues]);
267+
const [checkedValues, halfCheckedValues, missingCheckedValues] = useValues(
268+
multiple,
269+
rawValues,
270+
getPathKeyEntities,
271+
getValueByKeyPath,
272+
getMissingValues,
273+
);
304274

305275
const deDuplicatedValues = React.useMemo(() => {
306276
const checkedKeys = toPathKeys(checkedValues);
@@ -347,63 +317,23 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
347317
});
348318

349319
// =========================== Select ===========================
320+
const handleSelection = useSelect(
321+
multiple,
322+
triggerChange,
323+
checkedValues,
324+
halfCheckedValues,
325+
missingCheckedValues,
326+
getPathKeyEntities,
327+
getValueByKeyPath,
328+
showCheckedStrategy,
329+
);
330+
350331
const onInternalSelect = useEvent((valuePath: SingleValueType) => {
351332
if (!multiple || autoClearSearchValue) {
352333
setSearchValue('');
353334
}
354-
if (!multiple) {
355-
triggerChange(valuePath);
356-
} else {
357-
// Prepare conduct required info
358-
const pathKey = toPathKey(valuePath);
359-
const checkedPathKeys = toPathKeys(checkedValues);
360-
const halfCheckedPathKeys = toPathKeys(halfCheckedValues);
361-
362-
const existInChecked = checkedPathKeys.includes(pathKey);
363-
const existInMissing = missingCheckedValues.some(
364-
valueCells => toPathKey(valueCells) === pathKey,
365-
);
366335

367-
// Do update
368-
let nextCheckedValues = checkedValues;
369-
let nextMissingValues = missingCheckedValues;
370-
371-
if (existInMissing && !existInChecked) {
372-
// Missing value only do filter
373-
nextMissingValues = missingCheckedValues.filter(
374-
valueCells => toPathKey(valueCells) !== pathKey,
375-
);
376-
} else {
377-
// Update checked key first
378-
const nextRawCheckedKeys = existInChecked
379-
? checkedPathKeys.filter(key => key !== pathKey)
380-
: [...checkedPathKeys, pathKey];
381-
382-
const pathKeyEntities = getPathKeyEntities();
383-
384-
// Conduction by selected or not
385-
let checkedKeys: React.Key[];
386-
if (existInChecked) {
387-
({ checkedKeys } = conductCheck(
388-
nextRawCheckedKeys,
389-
{ checked: false, halfCheckedKeys: halfCheckedPathKeys },
390-
pathKeyEntities,
391-
));
392-
} else {
393-
({ checkedKeys } = conductCheck(nextRawCheckedKeys, true, pathKeyEntities));
394-
}
395-
396-
// Roll up to parent level keys
397-
const deDuplicatedKeys = formatStrategyValues(
398-
checkedKeys,
399-
getPathKeyEntities,
400-
showCheckedStrategy,
401-
);
402-
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
403-
}
404-
405-
triggerChange([...nextMissingValues, ...nextCheckedValues]);
406-
}
336+
handleSelection(valuePath);
407337
});
408338

409339
// Display Value change logic
@@ -527,6 +457,7 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
527457
displayName?: string;
528458
SHOW_PARENT: typeof SHOW_PARENT;
529459
SHOW_CHILD: typeof SHOW_CHILD;
460+
Panel: typeof Panel;
530461
};
531462

532463
if (process.env.NODE_ENV !== 'production') {
@@ -535,4 +466,6 @@ if (process.env.NODE_ENV !== 'production') {
535466

536467
Cascader.SHOW_PARENT = SHOW_PARENT;
537468
Cascader.SHOW_CHILD = SHOW_CHILD;
469+
Cascader.Panel = Panel;
470+
538471
export default Cascader;

0 commit comments

Comments
 (0)