Skip to content

Commit

Permalink
feat: Add Cascader.Panel (#430)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
zombieJ authored Sep 25, 2023
1 parent 785e52d commit 239b6fc
Show file tree
Hide file tree
Showing 18 changed files with 814 additions and 336 deletions.
3 changes: 2 additions & 1 deletion assets/index.less
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "./select.less";
@import "./list.less";
@import "./list.less";
@import "./panel.less";
4 changes: 4 additions & 0 deletions assets/list.less
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
padding-right: 20px;
position: relative;

&:hover {
background: rgba(0, 0, 255, 0.1);
}

&-selected {
background: rgba(0, 0, 255, 0.05);
}
Expand Down
7 changes: 7 additions & 0 deletions assets/panel.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import (reference) './index.less';

.@{select-prefix} {
&-panel {
border: 1px solid green;
}
}
8 changes: 8 additions & 0 deletions docs/demo/panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Panel
nav:
title: Demo
path: /demo
---

<code src="../../examples/panel.tsx"></code>
72 changes: 72 additions & 0 deletions examples/panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-disable no-console */
import React from 'react';
import '../assets/index.less';
import Cascader from '../src';

const addressOptions = [
{
label: '福建',
value: 'fj',
children: [
{
label: '福州',
value: 'fuzhou',
children: [
{
label: '马尾',
value: 'mawei',
},
],
},
{
label: '泉州',
value: 'quanzhou',
},
],
},
{
label: '浙江',
value: 'zj',
children: [
{
label: '杭州',
value: 'hangzhou',
children: [
{
label: '余杭',
value: 'yuhang',
},
],
},
],
},
{
label: '北京',
value: 'bj',
children: [
{
label: '朝阳区',
value: 'chaoyang',
},
{
label: '海淀区',
value: 'haidian',
},
],
},
];

export default () => {
return (
<>
<h1>Panel</h1>
<Cascader.Panel
checkable
options={addressOptions}
onChange={value => {
console.log('Change:', value);
}}
/>
</>
);
};
139 changes: 36 additions & 103 deletions src/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ import type { BaseSelectProps, BaseSelectPropsWithoutPrivate, BaseSelectRef } fr
import { BaseSelect } from 'rc-select';
import type { DisplayValueType, Placement } from 'rc-select/lib/BaseSelect';
import useId from 'rc-select/lib/hooks/useId';
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
import useEvent from 'rc-util/lib/hooks/useEvent';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react';
import CascaderContext from './context';
import useDisplayValues from './hooks/useDisplayValues';
import useEntities from './hooks/useEntities';
import useMissingValues from './hooks/useMissingValues';
import useOptions from './hooks/useOptions';
import useSearchConfig from './hooks/useSearchConfig';
import useSearchOptions from './hooks/useSearchOptions';
import useSelect from './hooks/useSelect';
import useValues from './hooks/useValues';
import OptionList from './OptionList';
import { fillFieldNames, SHOW_CHILD, SHOW_PARENT, toPathKey, toPathKeys } from './utils/commonUtil';
import Panel from './Panel';
import {
fillFieldNames,
SHOW_CHILD,
SHOW_PARENT,
toPathKeys,
toRawValues,
} from './utils/commonUtil';
import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
import warningProps, { warningNullOptions } from './utils/warningPropsUtil';

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

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

function isMultipleValue(value: ValueType): value is SingleValueType[] {
return Array.isArray(value) && Array.isArray(value[0]);
}

function toRawValues(value: ValueType): SingleValueType[] {
if (!value) {
return [];
}

if (isMultipleValue(value)) {
return value;
}

return (value.length === 0 ? [] : [value]).map(val => (Array.isArray(val) ? val : [val]));
}

const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, ref) => {
const {
// MISC
Expand Down Expand Up @@ -238,23 +230,9 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
);

// =========================== Option ===========================
const mergedOptions = React.useMemo(() => options || [], [options]);

// Only used in multiple mode, this fn will not call in single mode
const getPathKeyEntities = useEntities(mergedOptions, mergedFieldNames);

/** Convert path key back to value format */
const getValueByKeyPath = React.useCallback(
(pathKeys: React.Key[]): SingleValueType[] => {
const keyPathEntities = getPathKeyEntities();

return pathKeys.map(pathKey => {
const { nodes } = keyPathEntities[pathKey];

return nodes.map(node => node[mergedFieldNames.value]);
});
},
[getPathKeyEntities, mergedFieldNames],
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = useOptions(
mergedFieldNames,
options,
);

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

// Fill `rawValues` with checked conduction values
const [checkedValues, halfCheckedValues, missingCheckedValues] = React.useMemo(() => {
const [existValues, missingValues] = getMissingValues(rawValues);

if (!multiple || !rawValues.length) {
return [existValues, [], missingValues];
}

const keyPathValues = toPathKeys(existValues);
const keyPathEntities = getPathKeyEntities();

const { checkedKeys, halfCheckedKeys } = conductCheck(keyPathValues, true, keyPathEntities);

// Convert key back to value cells
return [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
}, [multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues]);
const [checkedValues, halfCheckedValues, missingCheckedValues] = useValues(
multiple,
rawValues,
getPathKeyEntities,
getValueByKeyPath,
getMissingValues,
);

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

// =========================== Select ===========================
const handleSelection = useSelect(
multiple,
triggerChange,
checkedValues,
halfCheckedValues,
missingCheckedValues,
getPathKeyEntities,
getValueByKeyPath,
showCheckedStrategy,
);

const onInternalSelect = useEvent((valuePath: SingleValueType) => {
if (!multiple || autoClearSearchValue) {
setSearchValue('');
}
if (!multiple) {
triggerChange(valuePath);
} else {
// Prepare conduct required info
const pathKey = toPathKey(valuePath);
const checkedPathKeys = toPathKeys(checkedValues);
const halfCheckedPathKeys = toPathKeys(halfCheckedValues);

const existInChecked = checkedPathKeys.includes(pathKey);
const existInMissing = missingCheckedValues.some(
valueCells => toPathKey(valueCells) === pathKey,
);

// Do update
let nextCheckedValues = checkedValues;
let nextMissingValues = missingCheckedValues;

if (existInMissing && !existInChecked) {
// Missing value only do filter
nextMissingValues = missingCheckedValues.filter(
valueCells => toPathKey(valueCells) !== pathKey,
);
} else {
// Update checked key first
const nextRawCheckedKeys = existInChecked
? checkedPathKeys.filter(key => key !== pathKey)
: [...checkedPathKeys, pathKey];

const pathKeyEntities = getPathKeyEntities();

// Conduction by selected or not
let checkedKeys: React.Key[];
if (existInChecked) {
({ checkedKeys } = conductCheck(
nextRawCheckedKeys,
{ checked: false, halfCheckedKeys: halfCheckedPathKeys },
pathKeyEntities,
));
} else {
({ checkedKeys } = conductCheck(nextRawCheckedKeys, true, pathKeyEntities));
}

// Roll up to parent level keys
const deDuplicatedKeys = formatStrategyValues(
checkedKeys,
getPathKeyEntities,
showCheckedStrategy,
);
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
}

triggerChange([...nextMissingValues, ...nextCheckedValues]);
}
handleSelection(valuePath);
});

// Display Value change logic
Expand Down Expand Up @@ -527,6 +457,7 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
displayName?: string;
SHOW_PARENT: typeof SHOW_PARENT;
SHOW_CHILD: typeof SHOW_CHILD;
Panel: typeof Panel;
};

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

Cascader.SHOW_PARENT = SHOW_PARENT;
Cascader.SHOW_CHILD = SHOW_CHILD;
Cascader.Panel = Panel;

export default Cascader;
Loading

0 comments on commit 239b6fc

Please sign in to comment.