This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2014-present alipay.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+319
View File
@@ -0,0 +1,319 @@
# @rc-component/cascader
React Cascader Component.
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
[![build status][github-actions-image]][github-actions-url]
[![Codecov][codecov-image]][codecov-url]
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]
[npm-image]: https://img.shields.io/npm/v/@rc-component/cascader.svg?style=flat-square
[npm-url]: https://npmjs.org/package/@rc-component/cascader
[travis-image]: https://img.shields.io/travis/react-component/cascader/master?style=flat-square
[travis-url]: https://travis-ci.com/react-component/cascader
[github-actions-image]: https://github.com/react-component/cascader/actions/workflows/main.yml/badge.svg
[github-actions-url]: https://github.com/react-component/cascader/actions/workflows/main.yml
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/cascader/master.svg?style=flat-square
[codecov-url]: https://app.codecov.io/gh/react-component/cascader
[david-url]: https://david-dm.org/react-component/cascader
[david-image]: https://david-dm.org/react-component/cascader/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/cascader?type=dev
[david-dev-image]: https://david-dm.org/react-component/cascader/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/@rc-component/cascader.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/cascader
[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/cascader
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/cascader
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Browser Support
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Electron |
| --- | --- | --- | --- | --- |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Screenshots
<img src="https://os.alipayobjects.com/rmsportal/TYFXEbuQXIaMqQF.png" width="288"/>
## Example
https://cascader-react-component.vercel.app
## Install
[![@rc-component/cascader](https://nodei.co/npm/@rc-component/cascader.png)](https://npmjs.org/package/@rc-component/cascader)
```bash
$ npm install @rc-component/cascader --save
```
## Usage
```js
import React from 'react';
import Cascader from '@rc-component/cascader';
const options = [{
'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',
}],
}];
React.render(
<Cascader options={options}>
...
</Cascader>
, container);
```
## API
### props
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>options</td>
<td>Object</td>
<td></td>
<td>The data options of cascade</td>
</tr>
<tr>
<td>value</td>
<td>Array</td>
<td></td>
<td>selected value</td>
</tr>
<tr>
<td>defaultValue</td>
<td>Array</td>
<td></td>
<td>initial selected value</td>
</tr>
<tr>
<td>onChange</td>
<td>Function(value, selectedOptions)</td>
<td></td>
<td>callback when finishing cascader select</td>
</tr>
<tr>
<td>changeOnSelect</td>
<td>Boolean</td>
<td>false</td>
<td>change value on each selection</td>
</tr>
<tr>
<td>loadData</td>
<td>Function(selectedOptions)</td>
<td></td>
<td>callback when click any option, use for loading more options</td>
</tr>
<tr>
<td>expandTrigger</td>
<td>String</td>
<td>'click'</td>
<td>expand current item when click or hover</td>
</tr>
<tr>
<td>open</td>
<td>Boolean</td>
<td></td>
<td>visibility of popup overlay</td>
</tr>
<tr>
<td>onPopupVisibleChange</td>
<td>Function(visible)</td>
<td></td>
<td>callback when popup overlay's visibility changed</td>
</tr>
<tr>
<td>transitionName</td>
<td>String</td>
<td></td>
<td>transition className like "slide-up"</td>
</tr>
<tr>
<td>prefixCls</td>
<td>String</td>
<td>rc-cascader</td>
<td>prefix className of popup overlay</td>
</tr>
<tr>
<td>popupClassName</td>
<td>String</td>
<td></td>
<td>additional className of popup overlay</td>
</tr>
<tr>
<td>popupPlacement</td>
<td>String</td>
<td>bottomLeft</td>
<td>use preset popup align config from builtinPlacementsbottomRight topRight bottomLeft topLeft</td>
</tr>
<tr>
<td>getPopupContainer</td>
<td>function(trigger:Node):Node</td>
<td>() => document.body</td>
<td>container which popup select menu rendered into</td>
</tr>
<tr>
<td>dropdownMenuColumnStyle</td>
<td>Object</td>
<td></td>
<td>style object for each cascader pop menu</td>
</tr>
<tr>
<td>fieldNames</td>
<td>Object</td>
<td>{ label: 'label', value: 'value', children: 'children' }</td>
<td>custom field name for label and value and children</td>
</tr>
<tr>
<td>expandIcon</td>
<td>ReactNode</td>
<td>></td>
<td>specific the default expand icon</td>
</tr>
<tr>
<td>loadingIcon</td>
<td>ReactNode</td>
<td>></td>
<td>specific the default loading icon</td>
</tr>
<tr>
<td>hidePopupOnSelect</td>
<td>Boolean</td>
<td>>true</td>
<td>hide popup on select</td>
</tr>
<tr>
<td>showSearch</td>
<td>boolean | object</td>
<td>false</td>
<td>Whether show search input in single mode</td>
</tr>
</tbody>
</table>
### showSearch
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoClearSearchValue | Whether the current search will be cleared on selecting an item. Only applies when checkable| boolean | true |
| filter | The function will receive two arguments, inputValue and option, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | function(inputValue, path): boolean | - | |
| limit | Set the count of filtered items | number \| false | 50 | |
| matchInputWidth | Whether the width of list matches input, ([how it looks](https://github.com/ant-design/ant-design/issues/25779)) | boolean | true | |
| render | Used to render filtered options | function(inputValue, path): ReactNode | - | |
| sort | Used to sort filtered options | function(a, b, inputValue) | - | |
| searchValue | The current input "search" text | string | - | - |
| onSearch | called when input changed | function | - | - |
### option
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="width: 100px;">name</th>
<th style="width: 50px;">type</th>
<th style="width: 50px;">default</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>label</td>
<td>String</td>
<td></td>
<td>option text to display</td>
</tr>
<tr>
<td>value</td>
<td>String</td>
<td></td>
<td>option value as react key</td>
</tr>
<tr>
<td>disabled</td>
<td>Boolean</td>
<td></td>
<td>disabled option</td>
</tr>
<tr>
<td>children</td>
<td>Array</td>
<td></td>
<td>children options</td>
</tr>
</tbody>
</table>
## Development
```bash
$ npm install
$ npm start
```
## Test Case
```bash
$ npm test
```
## Coverage
```bash
$ npm run coverage
```
## License
@rc-component/cascader is released under the MIT license.
## 🤝 Contributing
<a href="https://openomy.app/github/react-component/cascader" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://www.openomy.app/svg?repo=react-component/cascader&chart=bubble&latestMonth=24" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
@@ -0,0 +1,3 @@
@import "./select.less";
@import "./list.less";
@import "./panel.less";
+106
View File
@@ -0,0 +1,106 @@
@select-prefix: ~'rc-cascader';
.@{select-prefix} {
&-dropdown {
min-height: auto;
}
&-menus {
display: flex;
flex-wrap: nowrap;
}
&-menu {
flex: none;
margin: 0;
padding: 0;
list-style: none;
border-left: 1px solid blue;
height: 180px;
min-width: 100px;
overflow: auto;
&:first-child {
border-left: 0;
}
&-item {
display: flex;
flex-wrap: nowrap;
padding-right: 20px;
position: relative;
&:hover {
background: rgba(0, 0, 255, 0.1);
}
&-selected {
background: rgba(0, 0, 255, 0.05);
}
&-active {
background: rgba(0, 255, 0, 0.1);
}
&-disabled {
opacity: 0.5;
}
&-content {
flex: auto;
}
&-expand-icon {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
}
}
}
&-checkbox {
position: relative;
display: block;
flex: none;
width: 20px;
height: 20px;
border: 1px solid blue;
&::after {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
content: '';
}
&-checked::after {
content: '✔️';
}
&-indeterminate::after {
content: '';
}
}
// ====================== RTL ======================
&-rtl {
direction: rtl;
.@{select-prefix}-menu {
flex: none;
margin: 0;
padding: 0;
list-style: none;
border-left: none;
border-right: 1px solid blue;
&:first-child {
border-right: 0;
}
}
}
}
@@ -0,0 +1,7 @@
@import (reference) './index.less';
.@{select-prefix} {
&-panel {
border: 1px solid green;
}
}
@@ -0,0 +1,3 @@
@import '~@rc-component/select/assets/index';
@select-prefix: ~'rc-cascader';
+98
View File
@@ -0,0 +1,98 @@
import type { BuildInPlacements } from '@rc-component/trigger/lib/interface';
import type { BaseSelectPropsWithoutPrivate, BaseSelectRef } from '@rc-component/select';
import type { Placement } from '@rc-component/select/lib/BaseSelect';
import * as React from 'react';
import Panel from './Panel';
import { SHOW_CHILD, SHOW_PARENT } from './utils/commonUtil';
export interface BaseOptionType {
disabled?: boolean;
disableCheckbox?: boolean;
label?: React.ReactNode;
value?: string | number | null;
children?: DefaultOptionType[];
}
export type DefaultOptionType = BaseOptionType & Record<string, any>;
export interface SearchConfig<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> {
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames<OptionType, ValueField>) => boolean;
render?: (inputValue: string, path: OptionType[], prefixCls: string, fieldNames: FieldNames<OptionType, ValueField>) => React.ReactNode;
sort?: (a: OptionType[], b: OptionType[], inputValue: string, fieldNames: FieldNames<OptionType, ValueField>) => number;
matchInputWidth?: boolean;
limit?: number | false;
searchValue?: string;
onSearch?: (value: string) => void;
autoClearSearchValue?: boolean;
}
export type ShowCheckedStrategy = typeof SHOW_PARENT | typeof SHOW_CHILD;
interface BaseCascaderProps<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> extends Omit<BaseSelectPropsWithoutPrivate, 'tokenSeparators' | 'labelInValue' | 'mode' | 'showSearch'> {
id?: string;
prefixCls?: string;
fieldNames?: FieldNames<OptionType, ValueField>;
optionRender?: (option: OptionType) => React.ReactNode;
children?: React.ReactElement;
changeOnSelect?: boolean;
displayRender?: (label: string[], selectedOptions?: OptionType[]) => React.ReactNode;
checkable?: boolean | React.ReactNode;
showCheckedStrategy?: ShowCheckedStrategy;
/** @deprecated please use showSearch.autoClearSearchValue */
autoClearSearchValue?: boolean;
showSearch?: boolean | SearchConfig<OptionType>;
/** @deprecated please use showSearch.searchValue */
searchValue?: string;
/** @deprecated please use showSearch.onSearch */
onSearch?: (value: string) => void;
expandTrigger?: 'hover' | 'click';
options?: OptionType[];
/** @private Internal usage. Do not use in your production. */
popupPrefixCls?: string;
loadData?: (selectOptions: OptionType[]) => void;
popupClassName?: string;
popupMenuColumnStyle?: React.CSSProperties;
placement?: Placement;
builtinPlacements?: BuildInPlacements;
onPopupVisibleChange?: (open: boolean) => void;
expandIcon?: React.ReactNode;
loadingIcon?: React.ReactNode;
}
export interface FieldNames<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> {
label?: keyof OptionType;
value?: keyof OptionType | ValueField;
children?: keyof OptionType;
}
export type ValueType<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> = keyof OptionType extends ValueField ? unknown extends OptionType['value'] ? OptionType[ValueField] : OptionType['value'] : OptionType[ValueField];
export type GetValueType<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false> = false extends Multiple ? ValueType<Required<OptionType>, ValueField>[] : ValueType<Required<OptionType>, ValueField>[][];
export type GetOptionType<OptionType extends DefaultOptionType = DefaultOptionType, Multiple extends boolean | React.ReactNode = false> = false extends Multiple ? OptionType[] : OptionType[][];
type SemanticName = 'input' | 'prefix' | 'suffix' | 'placeholder' | 'content' | 'item' | 'itemContent' | 'itemRemove';
type PopupSemantic = 'list' | 'listItem';
export interface CascaderProps<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false> extends BaseCascaderProps<OptionType, ValueField> {
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
popup?: Partial<Record<PopupSemantic, React.CSSProperties>>;
};
classNames?: Partial<Record<SemanticName, string>> & {
popup?: Partial<Record<PopupSemantic, string>>;
};
checkable?: Multiple;
value?: GetValueType<OptionType, ValueField, Multiple>;
defaultValue?: GetValueType<OptionType, ValueField, Multiple>;
onChange?: (value: GetValueType<OptionType, ValueField, Multiple>, selectOptions: GetOptionType<OptionType, Multiple>) => void;
}
export type SingleValueType = (string | number)[];
export type LegacyKey = string | number;
export type InternalValueType = SingleValueType | SingleValueType[];
export interface InternalFieldNames extends Required<FieldNames> {
key: string;
}
export type InternalCascaderProps = Omit<CascaderProps, 'onChange' | 'value' | 'defaultValue'> & {
value?: InternalValueType;
defaultValue?: InternalValueType;
onChange?: (value: InternalValueType, selectOptions: BaseOptionType[] | BaseOptionType[][]) => void;
};
export type CascaderRef = Omit<BaseSelectRef, 'scrollTo'>;
declare const Cascader: (<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends React.ReactNode = false>(props: React.PropsWithChildren<CascaderProps<OptionType, ValueField, Multiple>> & {
ref?: React.Ref<CascaderRef>;
}) => React.ReactElement) & {
displayName?: string | undefined;
SHOW_PARENT: typeof SHOW_PARENT;
SHOW_CHILD: typeof SHOW_CHILD;
Panel: typeof Panel;
};
export default Cascader;
+226
View File
@@ -0,0 +1,226 @@
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); }
import { BaseSelect } from '@rc-component/select';
import useId from "@rc-component/util/es/hooks/useId";
import useEvent from "@rc-component/util/es/hooks/useEvent";
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
import * as React from 'react';
import CascaderContext from "./context";
import useDisplayValues from "./hooks/useDisplayValues";
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 Panel from "./Panel";
import { fillFieldNames, SHOW_CHILD, SHOW_PARENT, toPathKeys, toRawValues } from "./utils/commonUtil";
import { formatStrategyValues, toPathOptions } from "./utils/treeUtil";
import { warningNullOptions } from "./utils/warningPropsUtil";
const Cascader = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
// MISC
id,
prefixCls = 'rc-cascader',
fieldNames,
// Value
defaultValue,
value,
changeOnSelect,
onChange,
displayRender,
checkable,
// Search
showSearch,
// Trigger
expandTrigger,
// Options
options,
popupPrefixCls,
loadData,
open,
popupClassName,
popupMenuColumnStyle,
popupStyle: customPopupStyle,
classNames,
styles,
placement,
onPopupVisibleChange,
// Icon
expandIcon = '>',
loadingIcon,
// Children
children,
popupMatchSelectWidth = false,
showCheckedStrategy = SHOW_PARENT,
optionRender,
...restProps
} = props;
const mergedId = useId(id);
const multiple = !!checkable;
// =========================== Values ===========================
const [interanlRawValues, setRawValues] = useControlledState(defaultValue, value);
const rawValues = toRawValues(interanlRawValues);
// ========================= FieldNames =========================
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
[JSON.stringify(fieldNames)]
/* eslint-enable react-hooks/exhaustive-deps */);
// =========================== Option ===========================
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = useOptions(mergedFieldNames, options);
// =========================== Search ===========================
const [mergedShowSearch, searchConfig] = useSearchConfig(showSearch, props);
const {
autoClearSearchValue = true,
searchValue,
onSearch
} = searchConfig;
const [internalSearchValue, setSearchValue] = useControlledState('', searchValue);
const mergedSearchValue = internalSearchValue || '';
const onInternalSearch = (searchText, info) => {
setSearchValue(searchText);
if (info.source !== 'blur' && onSearch) {
onSearch(searchText);
}
};
const searchOptions = useSearchOptions(mergedSearchValue, mergedOptions, mergedFieldNames, popupPrefixCls || prefixCls, searchConfig, changeOnSelect || multiple);
// =========================== Values ===========================
const getMissingValues = useMissingValues(mergedOptions, mergedFieldNames);
// Fill `rawValues` with checked conduction values
const [checkedValues, halfCheckedValues, missingCheckedValues] = useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues);
const deDuplicatedValues = React.useMemo(() => {
const checkedKeys = toPathKeys(checkedValues);
const deduplicateKeys = formatStrategyValues(checkedKeys, getPathKeyEntities, showCheckedStrategy);
return [...missingCheckedValues, ...getValueByKeyPath(deduplicateKeys)];
}, [checkedValues, getPathKeyEntities, getValueByKeyPath, missingCheckedValues, showCheckedStrategy]);
const displayValues = useDisplayValues(deDuplicatedValues, mergedOptions, mergedFieldNames, multiple, displayRender);
// =========================== Change ===========================
const triggerChange = useEvent(nextValues => {
setRawValues(nextValues);
// Save perf if no need trigger event
if (onChange) {
const nextRawValues = toRawValues(nextValues);
const valueOptions = nextRawValues.map(valueCells => toPathOptions(valueCells, mergedOptions, mergedFieldNames).map(valueOpt => valueOpt.option));
const triggerValues = multiple ? nextRawValues : nextRawValues[0];
const triggerOptions = multiple ? valueOptions : valueOptions[0];
onChange(triggerValues, triggerOptions);
}
});
// =========================== Select ===========================
const handleSelection = useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy);
const onInternalSelect = useEvent(valuePath => {
if (!multiple || autoClearSearchValue) {
setSearchValue('');
}
handleSelection(valuePath);
});
// Display Value change logic
const onDisplayValuesChange = (_, info) => {
if (info.type === 'clear') {
triggerChange([]);
return;
}
// Cascader do not support `add` type. Only support `remove`
const {
valueCells
} = info.values[0];
onInternalSelect(valueCells);
};
const onInternalPopupVisibleChange = nextVisible => {
onPopupVisibleChange?.(nextVisible);
};
// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
warningNullOptions(mergedOptions, mergedFieldNames);
}
// ========================== Context ===========================
const cascaderContext = React.useMemo(() => ({
classNames,
styles,
options: mergedOptions,
fieldNames: mergedFieldNames,
values: checkedValues,
halfValues: halfCheckedValues,
changeOnSelect,
onSelect: onInternalSelect,
checkable,
searchOptions,
popupPrefixCls,
loadData,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle,
optionRender
}), [classNames, styles, mergedOptions, mergedFieldNames, checkedValues, halfCheckedValues, changeOnSelect, onInternalSelect, checkable, searchOptions, popupPrefixCls, loadData, expandTrigger, expandIcon, loadingIcon, popupMenuColumnStyle, optionRender]);
// ==============================================================
// == Render ==
// ==============================================================
const emptyOptions = !(mergedSearchValue ? searchOptions : mergedOptions).length;
const popupStyle =
// Search to match width
mergedSearchValue && searchConfig.matchInputWidth ||
// Empty keep the width
emptyOptions ? {} : {
minWidth: 'auto'
};
return /*#__PURE__*/React.createElement(CascaderContext.Provider, {
value: cascaderContext
}, /*#__PURE__*/React.createElement(BaseSelect, _extends({}, restProps, {
// MISC
ref: ref,
id: mergedId,
prefixCls: prefixCls,
autoClearSearchValue: autoClearSearchValue,
popupMatchSelectWidth: popupMatchSelectWidth,
classNames: classNames,
styles: styles,
popupStyle: {
...popupStyle,
...customPopupStyle
}
// Value
,
displayValues: displayValues,
onDisplayValuesChange: onDisplayValuesChange,
mode: multiple ? 'multiple' : undefined
// Search
,
searchValue: mergedSearchValue,
onSearch: onInternalSearch,
showSearch: mergedShowSearch
// Options
,
OptionList: OptionList,
emptyOptions: emptyOptions
// Open
,
open: open,
popupClassName: popupClassName,
placement: placement,
onPopupVisibleChange: onInternalPopupVisibleChange
// Children
,
getRawInputElement: () => children
})));
});
if (process.env.NODE_ENV !== 'production') {
Cascader.displayName = 'Cascader';
}
Cascader.SHOW_PARENT = SHOW_PARENT;
Cascader.SHOW_CHILD = SHOW_CHILD;
Cascader.Panel = Panel;
export default Cascader;
@@ -0,0 +1,10 @@
import * as React from 'react';
export interface CheckboxProps {
prefixCls: string;
checked?: boolean;
halfChecked?: boolean;
disabled?: boolean;
onClick?: React.MouseEventHandler;
disableCheckbox?: boolean;
}
export default function Checkbox({ prefixCls, checked, halfChecked, disabled, onClick, disableCheckbox, }: CheckboxProps): React.JSX.Element;
@@ -0,0 +1,24 @@
import * as React from 'react';
import { clsx } from 'clsx';
import CascaderContext from "../context";
export default function Checkbox({
prefixCls,
checked,
halfChecked,
disabled,
onClick,
disableCheckbox
}) {
const {
checkable
} = React.useContext(CascaderContext);
const customCheckbox = typeof checkable !== 'boolean' ? checkable : null;
return /*#__PURE__*/React.createElement("span", {
className: clsx(`${prefixCls}`, {
[`${prefixCls}-checked`]: checked,
[`${prefixCls}-indeterminate`]: !checked && halfChecked,
[`${prefixCls}-disabled`]: disabled || disableCheckbox
}),
onClick: onClick
}, customCheckbox);
}
@@ -0,0 +1,21 @@
import * as React from 'react';
import type { DefaultOptionType, SingleValueType } from '../Cascader';
export declare const FIX_LABEL = "__cascader_fix_label__";
export interface ColumnProps<OptionType extends DefaultOptionType = DefaultOptionType> {
prefixCls: string;
multiple?: boolean;
options: OptionType[];
/** Current Column opened item key */
activeValue?: React.Key;
/** The value path before current column */
prevValuePath: React.Key[];
onToggleOpen: (open: boolean) => void;
onSelect: (valuePath: SingleValueType, leaf: boolean) => void;
onActive: (valuePath: SingleValueType) => void;
checkedSet: Set<React.Key>;
halfCheckedSet: Set<React.Key>;
loadingKeys: React.Key[];
isSelectable: (option: DefaultOptionType) => boolean;
disabled?: boolean;
}
export default function Column<OptionType extends DefaultOptionType = DefaultOptionType>({ prefixCls, multiple, options, activeValue, prevValuePath, onToggleOpen, onSelect, onActive, checkedSet, halfCheckedSet, loadingKeys, isSelectable, disabled: propsDisabled, }: ColumnProps<OptionType>): React.JSX.Element;
@@ -0,0 +1,199 @@
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); }
import { clsx } from 'clsx';
import * as React from 'react';
import pickAttrs from "@rc-component/util/es/pickAttrs";
import CascaderContext from "../context";
import { SEARCH_MARK } from "../hooks/useSearchOptions";
import { isLeaf, scrollIntoParentView, toPathKey } from "../utils/commonUtil";
import Checkbox from "./Checkbox";
export const FIX_LABEL = '__cascader_fix_label__';
export default function Column({
prefixCls,
multiple,
options,
activeValue,
prevValuePath,
onToggleOpen,
onSelect,
onActive,
checkedSet,
halfCheckedSet,
loadingKeys,
isSelectable,
disabled: propsDisabled
}) {
const menuPrefixCls = `${prefixCls}-menu`;
const menuItemPrefixCls = `${prefixCls}-menu-item`;
const menuRef = React.useRef(null);
const {
fieldNames,
changeOnSelect,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle,
optionRender,
classNames,
styles
} = React.useContext(CascaderContext);
const hoverOpen = expandTrigger === 'hover';
const isOptionDisabled = disabled => propsDisabled || disabled;
// ============================ Option ============================
const optionInfoList = React.useMemo(() => options.map(option => {
const {
disabled,
disableCheckbox
} = option;
const searchOptions = option[SEARCH_MARK];
const label = option[FIX_LABEL] ?? option[fieldNames.label];
const value = option[fieldNames.value];
const isMergedLeaf = isLeaf(option, fieldNames);
// Get real value of option. Search option is different way.
const fullPath = searchOptions ? searchOptions.map(opt => opt[fieldNames.value]) : [...prevValuePath, value];
const fullPathKey = toPathKey(fullPath);
const isLoading = loadingKeys.includes(fullPathKey);
// >>>>> checked
const checked = checkedSet.has(fullPathKey);
// >>>>> halfChecked
const halfChecked = halfCheckedSet.has(fullPathKey);
return {
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
disableCheckbox,
fullPath,
fullPathKey
};
}), [options, checkedSet, fieldNames, halfCheckedSet, loadingKeys, prevValuePath]);
React.useEffect(() => {
if (menuRef.current) {
const selector = `.${menuItemPrefixCls}-active`;
const activeElement = menuRef.current.querySelector(selector);
if (activeElement) {
scrollIntoParentView(activeElement);
}
}
}, [activeValue, menuItemPrefixCls]);
// ============================ Render ============================
return /*#__PURE__*/React.createElement("ul", {
className: clsx(menuPrefixCls, classNames?.popup?.list),
style: styles?.popup?.list,
ref: menuRef,
role: "menu"
}, optionInfoList.map(({
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
fullPath,
fullPathKey,
disableCheckbox
}) => {
const ariaProps = pickAttrs(option, {
aria: true,
data: true
});
// >>>>> Open
const triggerOpenPath = () => {
if (isOptionDisabled(disabled)) {
return;
}
const nextValueCells = [...fullPath];
if (hoverOpen && isMergedLeaf) {
nextValueCells.pop();
}
onActive(nextValueCells);
};
// >>>>> Selection
const triggerSelect = () => {
if (isSelectable(option) && !isOptionDisabled(disabled)) {
onSelect(fullPath, isMergedLeaf);
}
};
// >>>>> Title
let title;
if (typeof option.title === 'string') {
title = option.title;
} else if (typeof label === 'string') {
title = label;
}
// >>>>> Render
return /*#__PURE__*/React.createElement("li", _extends({
key: fullPathKey
}, ariaProps, {
className: clsx(menuItemPrefixCls, classNames?.popup?.listItem, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
[`${menuItemPrefixCls}-active`]: activeValue === value || activeValue === fullPathKey,
[`${menuItemPrefixCls}-disabled`]: isOptionDisabled(disabled),
[`${menuItemPrefixCls}-loading`]: isLoading
}),
style: {
...popupMenuColumnStyle,
...styles?.popup?.listItem
},
role: "menuitemcheckbox",
title: title,
"aria-checked": checked,
"data-path-key": fullPathKey,
onClick: () => {
triggerOpenPath();
if (disableCheckbox) {
return;
}
if (!multiple || isMergedLeaf) {
triggerSelect();
}
},
onDoubleClick: () => {
if (changeOnSelect) {
onToggleOpen(false);
}
},
onMouseEnter: () => {
if (hoverOpen) {
triggerOpenPath();
}
},
onMouseDown: e => {
// Prevent selector from blurring
e.preventDefault();
}
}), multiple && /*#__PURE__*/React.createElement(Checkbox, {
prefixCls: `${prefixCls}-checkbox`,
checked: checked,
halfChecked: halfChecked,
disabled: isOptionDisabled(disabled) || disableCheckbox,
disableCheckbox: disableCheckbox,
onClick: e => {
if (disableCheckbox) {
return;
}
e.stopPropagation();
triggerSelect();
}
}), /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-content`
}, optionRender && value !== '__EMPTY__' ? optionRender(option) : label), !isLoading && expandIcon && !isMergedLeaf && /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-expand-icon`
}, expandIcon), isLoading && loadingIcon && /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-loading-icon`
}, loadingIcon));
}));
}
@@ -0,0 +1,10 @@
import type { useBaseProps } from '@rc-component/select';
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
export type RawOptionListProps = Pick<ReturnType<typeof useBaseProps>, 'prefixCls' | 'multiple' | 'searchValue' | 'toggleOpen' | 'notFoundContent' | 'direction' | 'open' | 'disabled'> & {
lockOptions?: boolean;
};
declare const RawOptionList: React.ForwardRefExoticComponent<Pick<import("@rc-component/select/lib/hooks/useBaseProps").BaseSelectContextProps, "disabled" | "prefixCls" | "multiple" | "searchValue" | "direction" | "notFoundContent" | "open" | "toggleOpen"> & {
lockOptions?: boolean | undefined;
} & React.RefAttributes<RefOptionListProps>>;
export default RawOptionList;
@@ -0,0 +1,217 @@
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); }
/* eslint-disable default-case */
import { clsx } from 'clsx';
import * as React from 'react';
import useMemo from "@rc-component/util/es/hooks/useMemo";
import CascaderContext from "../context";
import { getFullPathKeys, isLeaf, scrollIntoParentView, toPathKey, toPathKeys, toPathValueStr } from "../utils/commonUtil";
import { toPathOptions } from "../utils/treeUtil";
import Column, { FIX_LABEL } from "./Column";
import useActive from "./useActive";
import useKeyboard from "./useKeyboard";
const RawOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
multiple,
searchValue,
toggleOpen,
notFoundContent,
direction,
open,
disabled,
lockOptions = false
} = props;
const containerRef = React.useRef(null);
const rtl = direction === 'rtl';
const {
options,
values,
halfValues,
fieldNames,
changeOnSelect,
onSelect,
searchOptions,
popupPrefixCls,
loadData,
expandTrigger
} = React.useContext(CascaderContext);
const mergedPrefixCls = popupPrefixCls || prefixCls;
// ========================= loadData =========================
const [loadingKeys, setLoadingKeys] = React.useState([]);
const internalLoadData = valueCells => {
// Do not load when search
if (!loadData || searchValue) {
return;
}
const optionList = toPathOptions(valueCells, options, fieldNames);
const rawOptions = optionList.map(({
option
}) => option);
const lastOption = rawOptions[rawOptions.length - 1];
if (lastOption && !isLeaf(lastOption, fieldNames)) {
const pathKey = toPathKey(valueCells);
setLoadingKeys(keys => [...keys, pathKey]);
loadData(rawOptions);
}
};
// zombieJ: This is bad. We should make this same as `rc-tree` to use Promise instead.
React.useEffect(() => {
if (loadingKeys.length) {
loadingKeys.forEach(loadingKey => {
const valueStrCells = toPathValueStr(loadingKey);
const optionList = toPathOptions(valueStrCells, options, fieldNames, true).map(({
option
}) => option);
const lastOption = optionList[optionList.length - 1];
if (!lastOption || lastOption[fieldNames.children] || isLeaf(lastOption, fieldNames)) {
setLoadingKeys(keys => keys.filter(key => key !== loadingKey));
}
});
}
}, [options, loadingKeys, fieldNames]);
// ========================== Values ==========================
const checkedSet = React.useMemo(() => new Set(toPathKeys(values)), [values]);
const halfCheckedSet = React.useMemo(() => new Set(toPathKeys(halfValues)), [halfValues]);
// ====================== Accessibility =======================
const [activeValueCells, setActiveValueCells] = useActive(multiple, open);
// =========================== Path ===========================
const onPathOpen = nextValueCells => {
setActiveValueCells(nextValueCells);
// Trigger loadData
internalLoadData(nextValueCells);
};
const isSelectable = option => {
if (disabled) {
return false;
}
const {
disabled: optionDisabled
} = option;
const isMergedLeaf = isLeaf(option, fieldNames);
return !optionDisabled && (isMergedLeaf || changeOnSelect || multiple);
};
const onPathSelect = (valuePath, leaf, fromKeyboard = false) => {
onSelect(valuePath);
if (!multiple && (leaf || changeOnSelect && (expandTrigger === 'hover' || fromKeyboard))) {
toggleOpen(false);
}
};
// ========================== Option ==========================
const filteredOptions = React.useMemo(() => {
if (searchValue) {
return searchOptions;
}
return options;
}, [searchValue, searchOptions, options]);
// Update only when open or lockOptions
const mergedOptions = useMemo(() => filteredOptions, [open, lockOptions], (prev, next) => !!next[0] && !next[1]);
// ========================== Column ==========================
const optionColumns = React.useMemo(() => {
const optionList = [{
options: mergedOptions
}];
let currentList = mergedOptions;
const fullPathKeys = getFullPathKeys(currentList, fieldNames);
for (let i = 0; i < activeValueCells.length; i += 1) {
const activeValueCell = activeValueCells[i];
const currentOption = currentList.find((option, index) => (fullPathKeys[index] ? toPathKey(fullPathKeys[index]) : option[fieldNames.value]) === activeValueCell);
const subOptions = currentOption?.[fieldNames.children];
if (!subOptions?.length) {
break;
}
currentList = subOptions;
optionList.push({
options: subOptions
});
}
return optionList;
}, [mergedOptions, activeValueCells, fieldNames]);
// ========================= Keyboard =========================
const onKeyboardSelect = (selectValueCells, option) => {
if (isSelectable(option)) {
onPathSelect(selectValueCells, isLeaf(option, fieldNames), true);
}
};
useKeyboard(ref, mergedOptions, fieldNames, activeValueCells, onPathOpen, onKeyboardSelect, {
direction,
searchValue,
toggleOpen,
open
});
// >>>>> Active Scroll
React.useEffect(() => {
if (searchValue) {
return;
}
for (let i = 0; i < activeValueCells.length; i += 1) {
const cellPath = activeValueCells.slice(0, i + 1);
const cellKeyPath = toPathKey(cellPath);
const ele = containerRef.current?.querySelector(`li[data-path-key="${cellKeyPath.replace(/\\{0,2}"/g, '\\"')}"]` // matches unescaped double quotes
);
if (ele) {
scrollIntoParentView(ele);
}
}
}, [activeValueCells, searchValue]);
// ========================== Render ==========================
// >>>>> Empty
const isEmpty = !optionColumns[0]?.options?.length;
const emptyList = [{
[fieldNames.value]: '__EMPTY__',
[FIX_LABEL]: notFoundContent,
disabled: true
}];
const columnProps = {
...props,
multiple: !isEmpty && multiple,
onSelect: onPathSelect,
onActive: onPathOpen,
onToggleOpen: toggleOpen,
checkedSet,
halfCheckedSet,
loadingKeys,
isSelectable
};
// >>>>> Columns
const mergedOptionColumns = isEmpty ? [{
options: emptyList
}] : optionColumns;
const columnNodes = mergedOptionColumns.map((col, index) => {
const prevValuePath = activeValueCells.slice(0, index);
const activeValue = activeValueCells[index];
return /*#__PURE__*/React.createElement(Column, _extends({
key: index
}, columnProps, {
prefixCls: mergedPrefixCls,
options: col.options,
prevValuePath: prevValuePath,
activeValue: activeValue
}));
});
// >>>>> Render
return /*#__PURE__*/React.createElement("div", {
className: clsx(`${mergedPrefixCls}-menus`, {
[`${mergedPrefixCls}-menu-empty`]: isEmpty,
[`${mergedPrefixCls}-rtl`]: rtl
}),
ref: containerRef
}, columnNodes);
});
if (process.env.NODE_ENV !== 'production') {
RawOptionList.displayName = 'RawOptionList';
}
export default RawOptionList;
@@ -0,0 +1,4 @@
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
declare const RefOptionList: React.ForwardRefExoticComponent<React.RefAttributes<RefOptionListProps>>;
export default RefOptionList;
@@ -0,0 +1,17 @@
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); }
import { useBaseProps } from '@rc-component/select';
import * as React from 'react';
import RawOptionList from "./List";
const RefOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
lockOptions,
...baseProps
} = useBaseProps();
// >>>>> Render
return /*#__PURE__*/React.createElement(RawOptionList, _extends({}, props, baseProps, {
lockOptions: lockOptions,
ref: ref
}));
});
export default RefOptionList;
@@ -0,0 +1,6 @@
import type { LegacyKey } from '../Cascader';
/**
* Control the active open options path.
*/
declare const useActive: (multiple?: boolean, open?: boolean) => [LegacyKey[], (activeValueCells: LegacyKey[]) => void];
export default useActive;
@@ -0,0 +1,24 @@
import * as React from 'react';
import CascaderContext from "../context";
/**
* Control the active open options path.
*/
const useActive = (multiple, open) => {
const {
values
} = React.useContext(CascaderContext);
const firstValueCells = values[0];
// Record current dropdown active options
// This also control the open status
const [activeValueCells, setActiveValueCells] = React.useState([]);
React.useEffect(() => {
if (!multiple) {
setActiveValueCells(firstValueCells || []);
}
}, /* eslint-disable react-hooks/exhaustive-deps */
[open, firstValueCells]
/* eslint-enable react-hooks/exhaustive-deps */);
return [activeValueCells, setActiveValueCells];
};
export default useActive;
@@ -0,0 +1,10 @@
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
import type { DefaultOptionType, InternalFieldNames, LegacyKey, SingleValueType } from '../Cascader';
declare const _default: (ref: React.Ref<RefOptionListProps>, options: DefaultOptionType[], fieldNames: InternalFieldNames, activeValueCells: LegacyKey[], setActiveValueCells: (activeValueCells: LegacyKey[]) => void, onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void, contextProps: {
direction?: "ltr" | "rtl" | undefined;
searchValue: string;
toggleOpen: (open?: boolean) => void;
open?: boolean | undefined;
}) => void;
export default _default;
@@ -0,0 +1,165 @@
import KeyCode from "@rc-component/util/es/KeyCode";
import * as React from 'react';
import { SEARCH_MARK } from "../hooks/useSearchOptions";
import { getFullPathKeys, toPathKey } from "../utils/commonUtil";
export default ((ref, options, fieldNames, activeValueCells, setActiveValueCells, onKeyBoardSelect, contextProps) => {
const {
direction,
searchValue,
toggleOpen,
open
} = contextProps;
const rtl = direction === 'rtl';
const [validActiveValueCells, lastActiveIndex, lastActiveOptions, fullPathKeys] = React.useMemo(() => {
let activeIndex = -1;
let currentOptions = options;
const mergedActiveIndexes = [];
const mergedActiveValueCells = [];
const len = activeValueCells.length;
const pathKeys = getFullPathKeys(options, fieldNames);
// Fill validate active value cells and index
for (let i = 0; i < len && currentOptions; i += 1) {
// Mark the active index for current options
const nextActiveIndex = currentOptions.findIndex((option, index) => (pathKeys[index] ? toPathKey(pathKeys[index]) : option[fieldNames.value]) === activeValueCells[i]);
if (nextActiveIndex === -1) {
break;
}
activeIndex = nextActiveIndex;
mergedActiveIndexes.push(activeIndex);
mergedActiveValueCells.push(activeValueCells[i]);
currentOptions = currentOptions[activeIndex][fieldNames.children];
}
// Fill last active options
let activeOptions = options;
for (let i = 0; i < mergedActiveIndexes.length - 1; i += 1) {
activeOptions = activeOptions[mergedActiveIndexes[i]][fieldNames.children];
}
return [mergedActiveValueCells, activeIndex, activeOptions, pathKeys];
}, [activeValueCells, fieldNames, options]);
// Update active value cells and scroll to target element
const internalSetActiveValueCells = next => {
setActiveValueCells(next);
};
// Same options offset
const offsetActiveOption = offset => {
const len = lastActiveOptions.length;
let currentIndex = lastActiveIndex;
if (currentIndex === -1 && offset < 0) {
currentIndex = len;
}
for (let i = 0; i < len; i += 1) {
currentIndex = (currentIndex + offset + len) % len;
const option = lastActiveOptions[currentIndex];
if (option && !option.disabled) {
const nextActiveCells = validActiveValueCells.slice(0, -1).concat(fullPathKeys[currentIndex] ? toPathKey(fullPathKeys[currentIndex]) : option[fieldNames.value]);
internalSetActiveValueCells(nextActiveCells);
return;
}
}
};
// Different options offset
const prevColumn = () => {
if (validActiveValueCells.length > 1) {
const nextActiveCells = validActiveValueCells.slice(0, -1);
internalSetActiveValueCells(nextActiveCells);
} else {
toggleOpen(false);
}
};
const nextColumn = () => {
const nextOptions = lastActiveOptions[lastActiveIndex]?.[fieldNames.children] || [];
const nextOption = nextOptions.find(option => !option.disabled);
if (nextOption) {
const nextActiveCells = [...validActiveValueCells, nextOption[fieldNames.value]];
internalSetActiveValueCells(nextActiveCells);
}
};
React.useImperativeHandle(ref, () => ({
// scrollTo: treeRef.current?.scrollTo,
onKeyDown: event => {
const {
which
} = event;
switch (which) {
// >>> Arrow keys
case KeyCode.UP:
case KeyCode.DOWN:
{
let offset = 0;
if (which === KeyCode.UP) {
offset = -1;
} else if (which === KeyCode.DOWN) {
offset = 1;
}
if (offset !== 0) {
offsetActiveOption(offset);
}
break;
}
case KeyCode.LEFT:
{
if (searchValue) {
break;
}
if (rtl) {
nextColumn();
} else {
prevColumn();
}
break;
}
case KeyCode.RIGHT:
{
if (searchValue) {
break;
}
if (rtl) {
prevColumn();
} else {
nextColumn();
}
break;
}
case KeyCode.BACKSPACE:
{
if (!searchValue) {
prevColumn();
}
break;
}
// >>> Select
case KeyCode.ENTER:
{
if (validActiveValueCells.length) {
const option = lastActiveOptions[lastActiveIndex];
// Search option should revert back of origin options
const originOptions = option?.[SEARCH_MARK] || [];
if (originOptions.length) {
onKeyBoardSelect(originOptions.map(opt => opt[fieldNames.value]), originOptions[originOptions.length - 1]);
} else {
onKeyBoardSelect(validActiveValueCells, lastActiveOptions[lastActiveIndex]);
}
}
break;
}
// >>> Close
case KeyCode.ESC:
{
toggleOpen(false);
if (open) {
event.stopPropagation();
}
}
}
},
onKeyUp: () => {}
}));
});
+5
View File
@@ -0,0 +1,5 @@
import * as React from 'react';
import type { CascaderProps, DefaultOptionType } from './Cascader';
export type PickType = 'value' | 'defaultValue' | 'changeOnSelect' | 'onChange' | 'options' | 'prefixCls' | 'checkable' | 'fieldNames' | 'showCheckedStrategy' | 'loadData' | 'expandTrigger' | 'expandIcon' | 'loadingIcon' | 'className' | 'style' | 'direction' | 'notFoundContent' | 'disabled' | 'optionRender';
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>;
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;
+116
View File
@@ -0,0 +1,116 @@
import { clsx } from 'clsx';
import { useEvent, useControlledState } from '@rc-component/util';
import * as React from 'react';
import CascaderContext from "./context";
import useMissingValues from "./hooks/useMissingValues";
import useOptions from "./hooks/useOptions";
import useSelect from "./hooks/useSelect";
import useValues from "./hooks/useValues";
import RawOptionList from "./OptionList/List";
import { fillFieldNames, toRawValues } from "./utils/commonUtil";
import { toPathOptions } from "./utils/treeUtil";
function noop() {}
export default function Panel(props) {
const {
prefixCls = 'rc-cascader',
style,
className,
options,
checkable,
defaultValue,
value,
fieldNames,
changeOnSelect,
onChange,
showCheckedStrategy,
loadData,
expandTrigger,
expandIcon = '>',
loadingIcon,
direction,
notFoundContent = 'Not Found',
disabled,
optionRender
} = props;
// ======================== Multiple ========================
const multiple = !!checkable;
// ========================= Values =========================
const [interanlRawValues, setRawValues] = useControlledState(defaultValue, value);
const rawValues = toRawValues(interanlRawValues);
// ========================= FieldNames =========================
const mergedFieldNames = React.useMemo(() => fillFieldNames(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
[JSON.stringify(fieldNames)]
/* eslint-enable react-hooks/exhaustive-deps */);
// =========================== Option ===========================
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = useOptions(mergedFieldNames, options);
// ========================= Values =========================
const getMissingValues = useMissingValues(mergedOptions, mergedFieldNames);
// Fill `rawValues` with checked conduction values
const [checkedValues, halfCheckedValues, missingCheckedValues] = useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues);
// =========================== Change ===========================
const triggerChange = useEvent(nextValues => {
setRawValues(nextValues);
// Save perf if no need trigger event
if (onChange) {
const nextRawValues = toRawValues(nextValues);
const valueOptions = nextRawValues.map(valueCells => toPathOptions(valueCells, mergedOptions, mergedFieldNames).map(valueOpt => valueOpt.option));
const triggerValues = multiple ? nextRawValues : nextRawValues[0];
const triggerOptions = multiple ? valueOptions : valueOptions[0];
onChange(triggerValues, triggerOptions);
}
});
// =========================== Select ===========================
const handleSelection = useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy);
const onInternalSelect = useEvent(valuePath => {
handleSelection(valuePath);
});
// ======================== Context =========================
const cascaderContext = React.useMemo(() => ({
options: mergedOptions,
fieldNames: mergedFieldNames,
values: checkedValues,
halfValues: halfCheckedValues,
changeOnSelect,
onSelect: onInternalSelect,
checkable,
searchOptions: [],
popupPrefixCls: undefined,
loadData,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle: undefined,
optionRender
}), [mergedOptions, mergedFieldNames, checkedValues, halfCheckedValues, changeOnSelect, onInternalSelect, checkable, loadData, expandTrigger, expandIcon, loadingIcon, optionRender]);
// ========================= Render =========================
const panelPrefixCls = `${prefixCls}-panel`;
const isEmpty = !mergedOptions.length;
return /*#__PURE__*/React.createElement(CascaderContext.Provider, {
value: cascaderContext
}, /*#__PURE__*/React.createElement("div", {
className: clsx(panelPrefixCls, {
[`${panelPrefixCls}-rtl`]: direction === 'rtl',
[`${panelPrefixCls}-empty`]: isEmpty
}, className),
style: style
}, isEmpty ? notFoundContent : /*#__PURE__*/React.createElement(RawOptionList, {
prefixCls: prefixCls,
searchValue: "",
multiple: multiple,
toggleOpen: noop,
open: true,
direction: direction,
disabled: disabled
})));
}
+23
View File
@@ -0,0 +1,23 @@
import * as React from 'react';
import type { CascaderProps, InternalFieldNames, DefaultOptionType, SingleValueType } from './Cascader';
export interface CascaderContextProps {
options: NonNullable<CascaderProps['options']>;
fieldNames: InternalFieldNames;
values: SingleValueType[];
halfValues: SingleValueType[];
changeOnSelect?: boolean;
onSelect: (valuePath: SingleValueType) => void;
checkable?: boolean | React.ReactNode;
searchOptions: DefaultOptionType[];
popupPrefixCls?: string;
loadData?: (selectOptions: DefaultOptionType[]) => void;
expandTrigger?: 'hover' | 'click';
expandIcon?: React.ReactNode;
loadingIcon?: React.ReactNode;
popupMenuColumnStyle?: React.CSSProperties;
optionRender?: CascaderProps['optionRender'];
classNames?: CascaderProps['classNames'];
styles?: CascaderProps['styles'];
}
declare const CascaderContext: React.Context<CascaderContextProps>;
export default CascaderContext;
+3
View File
@@ -0,0 +1,3 @@
import * as React from 'react';
const CascaderContext = /*#__PURE__*/React.createContext({});
export default CascaderContext;
@@ -0,0 +1,10 @@
import * as React from 'react';
import type { DefaultOptionType, SingleValueType, CascaderProps, InternalFieldNames } from '../Cascader';
declare const _default: (rawValues: SingleValueType[], options: DefaultOptionType[], fieldNames: InternalFieldNames, multiple: boolean, displayRender: CascaderProps['displayRender']) => {
label: React.ReactNode;
value: string;
key: string;
valueCells: SingleValueType;
disabled: boolean | undefined;
}[];
export default _default;
@@ -0,0 +1,44 @@
import { toPathOptions } from "../utils/treeUtil";
import * as React from 'react';
import { toPathKey } from "../utils/commonUtil";
export default ((rawValues, options, fieldNames, multiple, displayRender) => {
return React.useMemo(() => {
const mergedDisplayRender = displayRender || (
// Default displayRender
labels => {
const mergedLabels = multiple ? labels.slice(-1) : labels;
const SPLIT = ' / ';
if (mergedLabels.every(label => ['string', 'number'].includes(typeof label))) {
return mergedLabels.join(SPLIT);
}
// If exist non-string value, use ReactNode instead
return mergedLabels.reduce((list, label, index) => {
const keyedLabel = /*#__PURE__*/React.isValidElement(label) ? /*#__PURE__*/React.cloneElement(label, {
key: index
}) : label;
if (index === 0) {
return [keyedLabel];
}
return [...list, SPLIT, keyedLabel];
}, []);
});
return rawValues.map(valueCells => {
const valueOptions = toPathOptions(valueCells, options, fieldNames);
const label = mergedDisplayRender(valueOptions.map(({
option,
value
}) => option?.[fieldNames.label] ?? value), valueOptions.map(({
option
}) => option));
const value = toPathKey(valueCells);
return {
label,
value,
key: value,
valueCells,
disabled: valueOptions[valueOptions.length - 1]?.option?.disabled
};
});
}, [rawValues, options, fieldNames, displayRender, multiple]);
});
@@ -0,0 +1,10 @@
import type { DefaultOptionType, InternalFieldNames } from '../Cascader';
import type { DataEntity } from '@rc-component/tree/lib/interface';
export interface OptionsInfo {
keyEntities: Record<string, DataEntity>;
pathKeyEntities: Record<string, DataEntity>;
}
export type GetEntities = () => OptionsInfo['pathKeyEntities'];
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
declare const _default: (options: DefaultOptionType[], fieldNames: InternalFieldNames) => GetEntities;
export default _default;
@@ -0,0 +1,35 @@
import * as React from 'react';
import { convertDataToEntities } from "@rc-component/tree/es/utils/treeUtil";
import { VALUE_SPLIT } from "../utils/commonUtil";
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
export default ((options, fieldNames) => {
const cacheRef = React.useRef({
options: [],
info: {
keyEntities: {},
pathKeyEntities: {}
}
});
const getEntities = React.useCallback(() => {
if (cacheRef.current.options !== options) {
cacheRef.current.options = options;
cacheRef.current.info = convertDataToEntities(options, {
fieldNames: fieldNames,
initWrapper: wrapper => ({
...wrapper,
pathKeyEntities: {}
}),
processEntity: (entity, wrapper) => {
const pathKey = entity.nodes.map(node => node[fieldNames.value]).join(VALUE_SPLIT);
wrapper.pathKeyEntities[pathKey] = entity;
// Overwrite origin key.
// this is very hack but we need let conduct logic work with connect path
entity.key = pathKey;
}
});
}
return cacheRef.current.info.pathKeyEntities;
}, [fieldNames, options]);
return getEntities;
});
@@ -0,0 +1,3 @@
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
export type GetMissValues = ReturnType<typeof useMissingValues>;
export default function useMissingValues(options: DefaultOptionType[], fieldNames: InternalFieldNames): (rawValues: SingleValueType[]) => [SingleValueType[], SingleValueType[]];
@@ -0,0 +1,17 @@
import * as React from 'react';
import { toPathOptions } from "../utils/treeUtil";
export default function useMissingValues(options, fieldNames) {
return React.useCallback(rawValues => {
const missingValues = [];
const existsValues = [];
rawValues.forEach(valueCell => {
const pathOptions = toPathOptions(valueCell, options, fieldNames);
if (pathOptions.every(opt => opt.option)) {
existsValues.push(valueCell);
} else {
missingValues.push(valueCell);
}
});
return [existsValues, missingValues];
}, [options, fieldNames]);
}
@@ -0,0 +1,8 @@
import type { DefaultOptionType } from '..';
import type { InternalFieldNames, SingleValueType, LegacyKey } from '../Cascader';
import { type GetEntities } from './useEntities';
export default function useOptions(mergedFieldNames: InternalFieldNames, options?: DefaultOptionType[]): [
mergedOptions: DefaultOptionType[],
getPathKeyEntities: GetEntities,
getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[]
];
@@ -0,0 +1,20 @@
import * as React from 'react';
import useEntities from "./useEntities";
export default function useOptions(mergedFieldNames, options) {
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 => {
const keyPathEntities = getPathKeyEntities();
return pathKeys.map(pathKey => {
const {
nodes
} = keyPathEntities[pathKey];
return nodes.map(node => node[mergedFieldNames.value]);
});
}, [getPathKeyEntities, mergedFieldNames]);
return [mergedOptions, getPathKeyEntities, getValueByKeyPath];
}
@@ -0,0 +1,2 @@
import type { CascaderProps, SearchConfig } from '../Cascader';
export default function useSearchConfig(showSearch?: CascaderProps['showSearch'], props?: any): [boolean, SearchConfig<import("../Cascader").DefaultOptionType, string>];
@@ -0,0 +1,35 @@
import warning from "@rc-component/util/es/warning";
import * as React from 'react';
// Convert `showSearch` to unique config
export default function useSearchConfig(showSearch, props) {
const {
autoClearSearchValue,
searchValue,
onSearch
} = props;
return React.useMemo(() => {
if (!showSearch) {
return [false, {}];
}
let searchConfig = {
matchInputWidth: true,
limit: 50,
autoClearSearchValue,
searchValue,
onSearch
};
if (showSearch && typeof showSearch === 'object') {
searchConfig = {
...searchConfig,
...showSearch
};
}
if (searchConfig.limit <= 0) {
searchConfig.limit = false;
if (process.env.NODE_ENV !== 'production') {
warning(false, "'limit' of showSearch should be positive number or false.");
}
}
return [true, searchConfig];
}, [showSearch, autoClearSearchValue, searchValue, onSearch]);
}
@@ -0,0 +1,4 @@
import type { DefaultOptionType, InternalFieldNames, SearchConfig } from '../Cascader';
export declare const SEARCH_MARK = "__rc_cascader_search_mark__";
declare const useSearchOptions: (search: string, options: DefaultOptionType[], fieldNames: InternalFieldNames, prefixCls: string, config: SearchConfig, enableHalfPath?: boolean) => DefaultOptionType[];
export default useSearchOptions;
@@ -0,0 +1,63 @@
import * as React from 'react';
export const SEARCH_MARK = '__rc_cascader_search_mark__';
const defaultFilter = (search, options, {
label = ''
}) => options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase()));
const defaultRender = (inputValue, path, prefixCls, fieldNames) => path.map(opt => opt[fieldNames.label]).join(' / ');
const useSearchOptions = (search, options, fieldNames, prefixCls, config, enableHalfPath) => {
const {
filter = defaultFilter,
render = defaultRender,
limit = 50,
sort
} = config;
return React.useMemo(() => {
const filteredOptions = [];
if (!search) {
return [];
}
function dig(list, pathOptions, parentDisabled = false) {
list.forEach(option => {
// Perf saving when `sort` is disabled and `limit` is provided
if (!sort && limit !== false && limit > 0 && filteredOptions.length >= limit) {
return;
}
const connectedPathOptions = [...pathOptions, option];
const children = option[fieldNames.children];
const mergedDisabled = parentDisabled || option.disabled;
// If current option is filterable
if (
// If is leaf option
!children || children.length === 0 ||
// If is changeOnSelect or multiple
enableHalfPath) {
if (filter(search, connectedPathOptions, {
label: fieldNames.label
})) {
filteredOptions.push({
...option,
disabled: mergedDisabled,
[fieldNames.label]: render(search, connectedPathOptions, prefixCls, fieldNames),
[SEARCH_MARK]: connectedPathOptions,
[fieldNames.children]: undefined
});
}
}
if (children) {
dig(option[fieldNames.children], connectedPathOptions, mergedDisabled);
}
});
}
dig(options, []);
// Do sort
if (sort) {
filteredOptions.sort((a, b) => {
return sort(a[SEARCH_MARK], b[SEARCH_MARK], search, fieldNames);
});
}
return limit !== false && limit > 0 ? filteredOptions.slice(0, limit) : filteredOptions;
}, [search, options, fieldNames, prefixCls, render, enableHalfPath, filter, sort, limit]);
};
export default useSearchOptions;
@@ -0,0 +1,3 @@
import type { InternalValueType, LegacyKey, ShowCheckedStrategy, SingleValueType } from '../Cascader';
import type { GetEntities } from './useEntities';
export default function useSelect(multiple: boolean, triggerChange: (nextValues: InternalValueType) => void, checkedValues: SingleValueType[], halfCheckedValues: SingleValueType[], missingCheckedValues: SingleValueType[], getPathKeyEntities: GetEntities, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], showCheckedStrategy?: ShowCheckedStrategy): (valuePath: SingleValueType) => void;
@@ -0,0 +1,49 @@
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
import { toPathKey, toPathKeys } from "../utils/commonUtil";
import { formatStrategyValues } from "../utils/treeUtil";
export default function useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy) {
return valuePath => {
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;
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]);
}
};
}
@@ -0,0 +1,8 @@
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { LegacyKey, SingleValueType } from '../Cascader';
import type { GetMissValues } from './useMissingValues';
export default function useValues(multiple: boolean, rawValues: SingleValueType[], getPathKeyEntities: () => Record<string, DataEntity>, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], getMissingValues: GetMissValues): [
checkedValues: SingleValueType[],
halfCheckedValues: SingleValueType[],
missingCheckedValues: SingleValueType[]
];
@@ -0,0 +1,21 @@
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
import * as React from 'react';
import { toPathKeys } from "../utils/commonUtil";
export default function useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues) {
// Fill `rawValues` with checked conduction values
return 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]);
}
+5
View File
@@ -0,0 +1,5 @@
import Cascader from './Cascader';
import Panel from './Panel';
export type { BaseOptionType, DefaultOptionType, CascaderProps, FieldNames, SearchConfig, CascaderRef, } from './Cascader';
export { Panel };
export default Cascader;
+4
View File
@@ -0,0 +1,4 @@
import Cascader from "./Cascader";
import Panel from "./Panel";
export { Panel };
export default Cascader;
@@ -0,0 +1,18 @@
import type { DefaultOptionType, FieldNames, InternalFieldNames, InternalValueType, SingleValueType } from '../Cascader';
export declare const VALUE_SPLIT = "__RC_CASCADER_SPLIT__";
export declare const SHOW_PARENT = "SHOW_PARENT";
export declare const SHOW_CHILD = "SHOW_CHILD";
/**
* Will convert value to string, and join with `VALUE_SPLIT`
*/
export declare function toPathKey(value: SingleValueType): string;
/**
* Batch convert value to string, and join with `VALUE_SPLIT`
*/
export declare function toPathKeys(value: SingleValueType[]): string[];
export declare function toPathValueStr(pathKey: string): string[];
export declare function fillFieldNames(fieldNames?: FieldNames): InternalFieldNames;
export declare function isLeaf(option: DefaultOptionType, fieldNames: FieldNames): any;
export declare function scrollIntoParentView(element: HTMLElement): void;
export declare function getFullPathKeys(options: DefaultOptionType[], fieldNames: FieldNames): any[];
export declare function toRawValues(value?: InternalValueType): SingleValueType[];
@@ -0,0 +1,69 @@
import { SEARCH_MARK } from "../hooks/useSearchOptions";
export const VALUE_SPLIT = '__RC_CASCADER_SPLIT__';
export const SHOW_PARENT = 'SHOW_PARENT';
export const SHOW_CHILD = 'SHOW_CHILD';
/**
* Will convert value to string, and join with `VALUE_SPLIT`
*/
export function toPathKey(value) {
return value.join(VALUE_SPLIT);
}
/**
* Batch convert value to string, and join with `VALUE_SPLIT`
*/
export function toPathKeys(value) {
return value.map(toPathKey);
}
export function toPathValueStr(pathKey) {
return pathKey.split(VALUE_SPLIT);
}
export function fillFieldNames(fieldNames) {
const {
label,
value,
children
} = fieldNames || {};
const val = value || 'value';
return {
label: label || 'label',
value: val,
key: val,
children: children || 'children'
};
}
export function isLeaf(option, fieldNames) {
return option.isLeaf ?? !option[fieldNames.children]?.length;
}
export function scrollIntoParentView(element) {
const parent = element.parentElement;
if (!parent) {
return;
}
const elementToParent = element.offsetTop - parent.offsetTop; // offsetParent may not be parent.
if (elementToParent - parent.scrollTop < 0) {
parent.scrollTo({
top: elementToParent
});
} else if (elementToParent + element.offsetHeight - parent.scrollTop > parent.offsetHeight) {
parent.scrollTo({
top: elementToParent + element.offsetHeight - parent.offsetHeight
});
}
}
export function getFullPathKeys(options, fieldNames) {
return options.map(item => item[SEARCH_MARK]?.map(opt => opt[fieldNames.value]));
}
function isMultipleValue(value) {
return Array.isArray(value) && Array.isArray(value[0]);
}
export function toRawValues(value) {
if (!value) {
return [];
}
if (isMultipleValue(value)) {
return value;
}
return (value.length === 0 ? [] : [value]).map(val => Array.isArray(val) ? val : [val]);
}
@@ -0,0 +1,8 @@
import type { SingleValueType, DefaultOptionType, InternalFieldNames, ShowCheckedStrategy, LegacyKey } from '../Cascader';
import type { GetEntities } from '../hooks/useEntities';
export declare function formatStrategyValues(pathKeys: LegacyKey[], getKeyPathEntities: GetEntities, showCheckedStrategy?: ShowCheckedStrategy): LegacyKey[];
export declare function toPathOptions(valueCells: SingleValueType, options: DefaultOptionType[], fieldNames: InternalFieldNames, stringMode?: boolean): {
value: SingleValueType[number];
index: number;
option: DefaultOptionType;
}[];
@@ -0,0 +1,35 @@
import { SHOW_CHILD } from "./commonUtil";
export function formatStrategyValues(pathKeys, getKeyPathEntities, showCheckedStrategy) {
const valueSet = new Set(pathKeys);
const keyPathEntities = getKeyPathEntities();
return pathKeys.filter(key => {
const entity = keyPathEntities[key];
const parent = entity ? entity.parent : null;
const children = entity ? entity.children : null;
if (entity && entity.node.disabled) {
return true;
}
return showCheckedStrategy === SHOW_CHILD ? !(children && children.some(child => child.key && valueSet.has(child.key))) : !(parent && !parent.node.disabled && valueSet.has(parent.key));
});
}
export function toPathOptions(valueCells, options, fieldNames,
// Used for loadingKeys which saved loaded keys as string
stringMode = false) {
let currentList = options;
const valueOptions = [];
for (let i = 0; i < valueCells.length; i += 1) {
const valueCell = valueCells[i];
const foundIndex = currentList?.findIndex(option => {
const val = option[fieldNames.value];
return stringMode ? String(val) === String(valueCell) : val === valueCell;
});
const foundOption = foundIndex !== -1 ? currentList?.[foundIndex] : null;
valueOptions.push({
value: foundOption?.[fieldNames.value] ?? valueCell,
index: foundIndex,
option: foundOption
});
currentList = foundOption?.[fieldNames.children];
}
return valueOptions;
}
@@ -0,0 +1,2 @@
import type { DefaultOptionType, FieldNames } from '../Cascader';
export declare function warningNullOptions(options: DefaultOptionType[], fieldNames: FieldNames): void;
@@ -0,0 +1,19 @@
import warning from "@rc-component/util/es/warning";
// value in Cascader options should not be null
export function warningNullOptions(options, fieldNames) {
if (options) {
const recursiveOptions = optionsList => {
for (let i = 0; i < optionsList.length; i++) {
const option = optionsList[i];
if (option[fieldNames?.value] === null) {
warning(false, '`value` in Cascader options should not be `null`.');
return true;
}
if (Array.isArray(option[fieldNames?.children]) && recursiveOptions(option[fieldNames?.children])) {
return true;
}
}
};
recursiveOptions(options);
}
}
+98
View File
@@ -0,0 +1,98 @@
import type { BuildInPlacements } from '@rc-component/trigger/lib/interface';
import type { BaseSelectPropsWithoutPrivate, BaseSelectRef } from '@rc-component/select';
import type { Placement } from '@rc-component/select/lib/BaseSelect';
import * as React from 'react';
import Panel from './Panel';
import { SHOW_CHILD, SHOW_PARENT } from './utils/commonUtil';
export interface BaseOptionType {
disabled?: boolean;
disableCheckbox?: boolean;
label?: React.ReactNode;
value?: string | number | null;
children?: DefaultOptionType[];
}
export type DefaultOptionType = BaseOptionType & Record<string, any>;
export interface SearchConfig<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> {
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames<OptionType, ValueField>) => boolean;
render?: (inputValue: string, path: OptionType[], prefixCls: string, fieldNames: FieldNames<OptionType, ValueField>) => React.ReactNode;
sort?: (a: OptionType[], b: OptionType[], inputValue: string, fieldNames: FieldNames<OptionType, ValueField>) => number;
matchInputWidth?: boolean;
limit?: number | false;
searchValue?: string;
onSearch?: (value: string) => void;
autoClearSearchValue?: boolean;
}
export type ShowCheckedStrategy = typeof SHOW_PARENT | typeof SHOW_CHILD;
interface BaseCascaderProps<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> extends Omit<BaseSelectPropsWithoutPrivate, 'tokenSeparators' | 'labelInValue' | 'mode' | 'showSearch'> {
id?: string;
prefixCls?: string;
fieldNames?: FieldNames<OptionType, ValueField>;
optionRender?: (option: OptionType) => React.ReactNode;
children?: React.ReactElement;
changeOnSelect?: boolean;
displayRender?: (label: string[], selectedOptions?: OptionType[]) => React.ReactNode;
checkable?: boolean | React.ReactNode;
showCheckedStrategy?: ShowCheckedStrategy;
/** @deprecated please use showSearch.autoClearSearchValue */
autoClearSearchValue?: boolean;
showSearch?: boolean | SearchConfig<OptionType>;
/** @deprecated please use showSearch.searchValue */
searchValue?: string;
/** @deprecated please use showSearch.onSearch */
onSearch?: (value: string) => void;
expandTrigger?: 'hover' | 'click';
options?: OptionType[];
/** @private Internal usage. Do not use in your production. */
popupPrefixCls?: string;
loadData?: (selectOptions: OptionType[]) => void;
popupClassName?: string;
popupMenuColumnStyle?: React.CSSProperties;
placement?: Placement;
builtinPlacements?: BuildInPlacements;
onPopupVisibleChange?: (open: boolean) => void;
expandIcon?: React.ReactNode;
loadingIcon?: React.ReactNode;
}
export interface FieldNames<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> {
label?: keyof OptionType;
value?: keyof OptionType | ValueField;
children?: keyof OptionType;
}
export type ValueType<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType> = keyof OptionType extends ValueField ? unknown extends OptionType['value'] ? OptionType[ValueField] : OptionType['value'] : OptionType[ValueField];
export type GetValueType<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false> = false extends Multiple ? ValueType<Required<OptionType>, ValueField>[] : ValueType<Required<OptionType>, ValueField>[][];
export type GetOptionType<OptionType extends DefaultOptionType = DefaultOptionType, Multiple extends boolean | React.ReactNode = false> = false extends Multiple ? OptionType[] : OptionType[][];
type SemanticName = 'input' | 'prefix' | 'suffix' | 'placeholder' | 'content' | 'item' | 'itemContent' | 'itemRemove';
type PopupSemantic = 'list' | 'listItem';
export interface CascaderProps<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends boolean | React.ReactNode = false> extends BaseCascaderProps<OptionType, ValueField> {
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
popup?: Partial<Record<PopupSemantic, React.CSSProperties>>;
};
classNames?: Partial<Record<SemanticName, string>> & {
popup?: Partial<Record<PopupSemantic, string>>;
};
checkable?: Multiple;
value?: GetValueType<OptionType, ValueField, Multiple>;
defaultValue?: GetValueType<OptionType, ValueField, Multiple>;
onChange?: (value: GetValueType<OptionType, ValueField, Multiple>, selectOptions: GetOptionType<OptionType, Multiple>) => void;
}
export type SingleValueType = (string | number)[];
export type LegacyKey = string | number;
export type InternalValueType = SingleValueType | SingleValueType[];
export interface InternalFieldNames extends Required<FieldNames> {
key: string;
}
export type InternalCascaderProps = Omit<CascaderProps, 'onChange' | 'value' | 'defaultValue'> & {
value?: InternalValueType;
defaultValue?: InternalValueType;
onChange?: (value: InternalValueType, selectOptions: BaseOptionType[] | BaseOptionType[][]) => void;
};
export type CascaderRef = Omit<BaseSelectRef, 'scrollTo'>;
declare const Cascader: (<OptionType extends DefaultOptionType = DefaultOptionType, ValueField extends keyof OptionType = keyof OptionType, Multiple extends React.ReactNode = false>(props: React.PropsWithChildren<CascaderProps<OptionType, ValueField, Multiple>> & {
ref?: React.Ref<CascaderRef>;
}) => React.ReactElement) & {
displayName?: string | undefined;
SHOW_PARENT: typeof SHOW_PARENT;
SHOW_CHILD: typeof SHOW_CHILD;
Panel: typeof Panel;
};
export default Cascader;
+235
View File
@@ -0,0 +1,235 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _select = require("@rc-component/select");
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("./context"));
var _useDisplayValues = _interopRequireDefault(require("./hooks/useDisplayValues"));
var _useMissingValues = _interopRequireDefault(require("./hooks/useMissingValues"));
var _useOptions = _interopRequireDefault(require("./hooks/useOptions"));
var _useSearchConfig = _interopRequireDefault(require("./hooks/useSearchConfig"));
var _useSearchOptions = _interopRequireDefault(require("./hooks/useSearchOptions"));
var _useSelect = _interopRequireDefault(require("./hooks/useSelect"));
var _useValues = _interopRequireDefault(require("./hooks/useValues"));
var _OptionList = _interopRequireDefault(require("./OptionList"));
var _Panel = _interopRequireDefault(require("./Panel"));
var _commonUtil = require("./utils/commonUtil");
var _treeUtil = require("./utils/treeUtil");
var _warningPropsUtil = require("./utils/warningPropsUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); }
const Cascader = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
// MISC
id,
prefixCls = 'rc-cascader',
fieldNames,
// Value
defaultValue,
value,
changeOnSelect,
onChange,
displayRender,
checkable,
// Search
showSearch,
// Trigger
expandTrigger,
// Options
options,
popupPrefixCls,
loadData,
open,
popupClassName,
popupMenuColumnStyle,
popupStyle: customPopupStyle,
classNames,
styles,
placement,
onPopupVisibleChange,
// Icon
expandIcon = '>',
loadingIcon,
// Children
children,
popupMatchSelectWidth = false,
showCheckedStrategy = _commonUtil.SHOW_PARENT,
optionRender,
...restProps
} = props;
const mergedId = (0, _useId.default)(id);
const multiple = !!checkable;
// =========================== Values ===========================
const [interanlRawValues, setRawValues] = (0, _useControlledState.default)(defaultValue, value);
const rawValues = (0, _commonUtil.toRawValues)(interanlRawValues);
// ========================= FieldNames =========================
const mergedFieldNames = React.useMemo(() => (0, _commonUtil.fillFieldNames)(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
[JSON.stringify(fieldNames)]
/* eslint-enable react-hooks/exhaustive-deps */);
// =========================== Option ===========================
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = (0, _useOptions.default)(mergedFieldNames, options);
// =========================== Search ===========================
const [mergedShowSearch, searchConfig] = (0, _useSearchConfig.default)(showSearch, props);
const {
autoClearSearchValue = true,
searchValue,
onSearch
} = searchConfig;
const [internalSearchValue, setSearchValue] = (0, _useControlledState.default)('', searchValue);
const mergedSearchValue = internalSearchValue || '';
const onInternalSearch = (searchText, info) => {
setSearchValue(searchText);
if (info.source !== 'blur' && onSearch) {
onSearch(searchText);
}
};
const searchOptions = (0, _useSearchOptions.default)(mergedSearchValue, mergedOptions, mergedFieldNames, popupPrefixCls || prefixCls, searchConfig, changeOnSelect || multiple);
// =========================== Values ===========================
const getMissingValues = (0, _useMissingValues.default)(mergedOptions, mergedFieldNames);
// Fill `rawValues` with checked conduction values
const [checkedValues, halfCheckedValues, missingCheckedValues] = (0, _useValues.default)(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues);
const deDuplicatedValues = React.useMemo(() => {
const checkedKeys = (0, _commonUtil.toPathKeys)(checkedValues);
const deduplicateKeys = (0, _treeUtil.formatStrategyValues)(checkedKeys, getPathKeyEntities, showCheckedStrategy);
return [...missingCheckedValues, ...getValueByKeyPath(deduplicateKeys)];
}, [checkedValues, getPathKeyEntities, getValueByKeyPath, missingCheckedValues, showCheckedStrategy]);
const displayValues = (0, _useDisplayValues.default)(deDuplicatedValues, mergedOptions, mergedFieldNames, multiple, displayRender);
// =========================== Change ===========================
const triggerChange = (0, _useEvent.default)(nextValues => {
setRawValues(nextValues);
// Save perf if no need trigger event
if (onChange) {
const nextRawValues = (0, _commonUtil.toRawValues)(nextValues);
const valueOptions = nextRawValues.map(valueCells => (0, _treeUtil.toPathOptions)(valueCells, mergedOptions, mergedFieldNames).map(valueOpt => valueOpt.option));
const triggerValues = multiple ? nextRawValues : nextRawValues[0];
const triggerOptions = multiple ? valueOptions : valueOptions[0];
onChange(triggerValues, triggerOptions);
}
});
// =========================== Select ===========================
const handleSelection = (0, _useSelect.default)(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy);
const onInternalSelect = (0, _useEvent.default)(valuePath => {
if (!multiple || autoClearSearchValue) {
setSearchValue('');
}
handleSelection(valuePath);
});
// Display Value change logic
const onDisplayValuesChange = (_, info) => {
if (info.type === 'clear') {
triggerChange([]);
return;
}
// Cascader do not support `add` type. Only support `remove`
const {
valueCells
} = info.values[0];
onInternalSelect(valueCells);
};
const onInternalPopupVisibleChange = nextVisible => {
onPopupVisibleChange?.(nextVisible);
};
// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
(0, _warningPropsUtil.warningNullOptions)(mergedOptions, mergedFieldNames);
}
// ========================== Context ===========================
const cascaderContext = React.useMemo(() => ({
classNames,
styles,
options: mergedOptions,
fieldNames: mergedFieldNames,
values: checkedValues,
halfValues: halfCheckedValues,
changeOnSelect,
onSelect: onInternalSelect,
checkable,
searchOptions,
popupPrefixCls,
loadData,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle,
optionRender
}), [classNames, styles, mergedOptions, mergedFieldNames, checkedValues, halfCheckedValues, changeOnSelect, onInternalSelect, checkable, searchOptions, popupPrefixCls, loadData, expandTrigger, expandIcon, loadingIcon, popupMenuColumnStyle, optionRender]);
// ==============================================================
// == Render ==
// ==============================================================
const emptyOptions = !(mergedSearchValue ? searchOptions : mergedOptions).length;
const popupStyle =
// Search to match width
mergedSearchValue && searchConfig.matchInputWidth ||
// Empty keep the width
emptyOptions ? {} : {
minWidth: 'auto'
};
return /*#__PURE__*/React.createElement(_context.default.Provider, {
value: cascaderContext
}, /*#__PURE__*/React.createElement(_select.BaseSelect, _extends({}, restProps, {
// MISC
ref: ref,
id: mergedId,
prefixCls: prefixCls,
autoClearSearchValue: autoClearSearchValue,
popupMatchSelectWidth: popupMatchSelectWidth,
classNames: classNames,
styles: styles,
popupStyle: {
...popupStyle,
...customPopupStyle
}
// Value
,
displayValues: displayValues,
onDisplayValuesChange: onDisplayValuesChange,
mode: multiple ? 'multiple' : undefined
// Search
,
searchValue: mergedSearchValue,
onSearch: onInternalSearch,
showSearch: mergedShowSearch
// Options
,
OptionList: _OptionList.default,
emptyOptions: emptyOptions
// Open
,
open: open,
popupClassName: popupClassName,
placement: placement,
onPopupVisibleChange: onInternalPopupVisibleChange
// Children
,
getRawInputElement: () => children
})));
});
if (process.env.NODE_ENV !== 'production') {
Cascader.displayName = 'Cascader';
}
Cascader.SHOW_PARENT = _commonUtil.SHOW_PARENT;
Cascader.SHOW_CHILD = _commonUtil.SHOW_CHILD;
Cascader.Panel = _Panel.default;
var _default = exports.default = Cascader;
@@ -0,0 +1,10 @@
import * as React from 'react';
export interface CheckboxProps {
prefixCls: string;
checked?: boolean;
halfChecked?: boolean;
disabled?: boolean;
onClick?: React.MouseEventHandler;
disableCheckbox?: boolean;
}
export default function Checkbox({ prefixCls, checked, halfChecked, disabled, onClick, disableCheckbox, }: CheckboxProps): React.JSX.Element;
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Checkbox;
var React = _interopRequireWildcard(require("react"));
var _clsx = require("clsx");
var _context = _interopRequireDefault(require("../context"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function Checkbox({
prefixCls,
checked,
halfChecked,
disabled,
onClick,
disableCheckbox
}) {
const {
checkable
} = React.useContext(_context.default);
const customCheckbox = typeof checkable !== 'boolean' ? checkable : null;
return /*#__PURE__*/React.createElement("span", {
className: (0, _clsx.clsx)(`${prefixCls}`, {
[`${prefixCls}-checked`]: checked,
[`${prefixCls}-indeterminate`]: !checked && halfChecked,
[`${prefixCls}-disabled`]: disabled || disableCheckbox
}),
onClick: onClick
}, customCheckbox);
}
@@ -0,0 +1,21 @@
import * as React from 'react';
import type { DefaultOptionType, SingleValueType } from '../Cascader';
export declare const FIX_LABEL = "__cascader_fix_label__";
export interface ColumnProps<OptionType extends DefaultOptionType = DefaultOptionType> {
prefixCls: string;
multiple?: boolean;
options: OptionType[];
/** Current Column opened item key */
activeValue?: React.Key;
/** The value path before current column */
prevValuePath: React.Key[];
onToggleOpen: (open: boolean) => void;
onSelect: (valuePath: SingleValueType, leaf: boolean) => void;
onActive: (valuePath: SingleValueType) => void;
checkedSet: Set<React.Key>;
halfCheckedSet: Set<React.Key>;
loadingKeys: React.Key[];
isSelectable: (option: DefaultOptionType) => boolean;
disabled?: boolean;
}
export default function Column<OptionType extends DefaultOptionType = DefaultOptionType>({ prefixCls, multiple, options, activeValue, prevValuePath, onToggleOpen, onSelect, onActive, checkedSet, halfCheckedSet, loadingKeys, isSelectable, disabled: propsDisabled, }: ColumnProps<OptionType>): React.JSX.Element;
@@ -0,0 +1,209 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FIX_LABEL = void 0;
exports.default = Column;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
var _context = _interopRequireDefault(require("../context"));
var _useSearchOptions = require("../hooks/useSearchOptions");
var _commonUtil = require("../utils/commonUtil");
var _Checkbox = _interopRequireDefault(require("./Checkbox"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); }
const FIX_LABEL = exports.FIX_LABEL = '__cascader_fix_label__';
function Column({
prefixCls,
multiple,
options,
activeValue,
prevValuePath,
onToggleOpen,
onSelect,
onActive,
checkedSet,
halfCheckedSet,
loadingKeys,
isSelectable,
disabled: propsDisabled
}) {
const menuPrefixCls = `${prefixCls}-menu`;
const menuItemPrefixCls = `${prefixCls}-menu-item`;
const menuRef = React.useRef(null);
const {
fieldNames,
changeOnSelect,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle,
optionRender,
classNames,
styles
} = React.useContext(_context.default);
const hoverOpen = expandTrigger === 'hover';
const isOptionDisabled = disabled => propsDisabled || disabled;
// ============================ Option ============================
const optionInfoList = React.useMemo(() => options.map(option => {
const {
disabled,
disableCheckbox
} = option;
const searchOptions = option[_useSearchOptions.SEARCH_MARK];
const label = option[FIX_LABEL] ?? option[fieldNames.label];
const value = option[fieldNames.value];
const isMergedLeaf = (0, _commonUtil.isLeaf)(option, fieldNames);
// Get real value of option. Search option is different way.
const fullPath = searchOptions ? searchOptions.map(opt => opt[fieldNames.value]) : [...prevValuePath, value];
const fullPathKey = (0, _commonUtil.toPathKey)(fullPath);
const isLoading = loadingKeys.includes(fullPathKey);
// >>>>> checked
const checked = checkedSet.has(fullPathKey);
// >>>>> halfChecked
const halfChecked = halfCheckedSet.has(fullPathKey);
return {
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
disableCheckbox,
fullPath,
fullPathKey
};
}), [options, checkedSet, fieldNames, halfCheckedSet, loadingKeys, prevValuePath]);
React.useEffect(() => {
if (menuRef.current) {
const selector = `.${menuItemPrefixCls}-active`;
const activeElement = menuRef.current.querySelector(selector);
if (activeElement) {
(0, _commonUtil.scrollIntoParentView)(activeElement);
}
}
}, [activeValue, menuItemPrefixCls]);
// ============================ Render ============================
return /*#__PURE__*/React.createElement("ul", {
className: (0, _clsx.clsx)(menuPrefixCls, classNames?.popup?.list),
style: styles?.popup?.list,
ref: menuRef,
role: "menu"
}, optionInfoList.map(({
disabled,
label,
value,
isLeaf: isMergedLeaf,
isLoading,
checked,
halfChecked,
option,
fullPath,
fullPathKey,
disableCheckbox
}) => {
const ariaProps = (0, _pickAttrs.default)(option, {
aria: true,
data: true
});
// >>>>> Open
const triggerOpenPath = () => {
if (isOptionDisabled(disabled)) {
return;
}
const nextValueCells = [...fullPath];
if (hoverOpen && isMergedLeaf) {
nextValueCells.pop();
}
onActive(nextValueCells);
};
// >>>>> Selection
const triggerSelect = () => {
if (isSelectable(option) && !isOptionDisabled(disabled)) {
onSelect(fullPath, isMergedLeaf);
}
};
// >>>>> Title
let title;
if (typeof option.title === 'string') {
title = option.title;
} else if (typeof label === 'string') {
title = label;
}
// >>>>> Render
return /*#__PURE__*/React.createElement("li", _extends({
key: fullPathKey
}, ariaProps, {
className: (0, _clsx.clsx)(menuItemPrefixCls, classNames?.popup?.listItem, {
[`${menuItemPrefixCls}-expand`]: !isMergedLeaf,
[`${menuItemPrefixCls}-active`]: activeValue === value || activeValue === fullPathKey,
[`${menuItemPrefixCls}-disabled`]: isOptionDisabled(disabled),
[`${menuItemPrefixCls}-loading`]: isLoading
}),
style: {
...popupMenuColumnStyle,
...styles?.popup?.listItem
},
role: "menuitemcheckbox",
title: title,
"aria-checked": checked,
"data-path-key": fullPathKey,
onClick: () => {
triggerOpenPath();
if (disableCheckbox) {
return;
}
if (!multiple || isMergedLeaf) {
triggerSelect();
}
},
onDoubleClick: () => {
if (changeOnSelect) {
onToggleOpen(false);
}
},
onMouseEnter: () => {
if (hoverOpen) {
triggerOpenPath();
}
},
onMouseDown: e => {
// Prevent selector from blurring
e.preventDefault();
}
}), multiple && /*#__PURE__*/React.createElement(_Checkbox.default, {
prefixCls: `${prefixCls}-checkbox`,
checked: checked,
halfChecked: halfChecked,
disabled: isOptionDisabled(disabled) || disableCheckbox,
disableCheckbox: disableCheckbox,
onClick: e => {
if (disableCheckbox) {
return;
}
e.stopPropagation();
triggerSelect();
}
}), /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-content`
}, optionRender && value !== '__EMPTY__' ? optionRender(option) : label), !isLoading && expandIcon && !isMergedLeaf && /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-expand-icon`
}, expandIcon), isLoading && loadingIcon && /*#__PURE__*/React.createElement("div", {
className: `${menuItemPrefixCls}-loading-icon`
}, loadingIcon));
}));
}
@@ -0,0 +1,10 @@
import type { useBaseProps } from '@rc-component/select';
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
export type RawOptionListProps = Pick<ReturnType<typeof useBaseProps>, 'prefixCls' | 'multiple' | 'searchValue' | 'toggleOpen' | 'notFoundContent' | 'direction' | 'open' | 'disabled'> & {
lockOptions?: boolean;
};
declare const RawOptionList: React.ForwardRefExoticComponent<Pick<import("@rc-component/select/lib/hooks/useBaseProps").BaseSelectContextProps, "disabled" | "prefixCls" | "multiple" | "searchValue" | "direction" | "notFoundContent" | "open" | "toggleOpen"> & {
lockOptions?: boolean | undefined;
} & React.RefAttributes<RefOptionListProps>>;
export default RawOptionList;
@@ -0,0 +1,225 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _useMemo = _interopRequireDefault(require("@rc-component/util/lib/hooks/useMemo"));
var _context = _interopRequireDefault(require("../context"));
var _commonUtil = require("../utils/commonUtil");
var _treeUtil = require("../utils/treeUtil");
var _Column = _interopRequireWildcard(require("./Column"));
var _useActive = _interopRequireDefault(require("./useActive"));
var _useKeyboard = _interopRequireDefault(require("./useKeyboard"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); } /* eslint-disable default-case */
const RawOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
multiple,
searchValue,
toggleOpen,
notFoundContent,
direction,
open,
disabled,
lockOptions = false
} = props;
const containerRef = React.useRef(null);
const rtl = direction === 'rtl';
const {
options,
values,
halfValues,
fieldNames,
changeOnSelect,
onSelect,
searchOptions,
popupPrefixCls,
loadData,
expandTrigger
} = React.useContext(_context.default);
const mergedPrefixCls = popupPrefixCls || prefixCls;
// ========================= loadData =========================
const [loadingKeys, setLoadingKeys] = React.useState([]);
const internalLoadData = valueCells => {
// Do not load when search
if (!loadData || searchValue) {
return;
}
const optionList = (0, _treeUtil.toPathOptions)(valueCells, options, fieldNames);
const rawOptions = optionList.map(({
option
}) => option);
const lastOption = rawOptions[rawOptions.length - 1];
if (lastOption && !(0, _commonUtil.isLeaf)(lastOption, fieldNames)) {
const pathKey = (0, _commonUtil.toPathKey)(valueCells);
setLoadingKeys(keys => [...keys, pathKey]);
loadData(rawOptions);
}
};
// zombieJ: This is bad. We should make this same as `rc-tree` to use Promise instead.
React.useEffect(() => {
if (loadingKeys.length) {
loadingKeys.forEach(loadingKey => {
const valueStrCells = (0, _commonUtil.toPathValueStr)(loadingKey);
const optionList = (0, _treeUtil.toPathOptions)(valueStrCells, options, fieldNames, true).map(({
option
}) => option);
const lastOption = optionList[optionList.length - 1];
if (!lastOption || lastOption[fieldNames.children] || (0, _commonUtil.isLeaf)(lastOption, fieldNames)) {
setLoadingKeys(keys => keys.filter(key => key !== loadingKey));
}
});
}
}, [options, loadingKeys, fieldNames]);
// ========================== Values ==========================
const checkedSet = React.useMemo(() => new Set((0, _commonUtil.toPathKeys)(values)), [values]);
const halfCheckedSet = React.useMemo(() => new Set((0, _commonUtil.toPathKeys)(halfValues)), [halfValues]);
// ====================== Accessibility =======================
const [activeValueCells, setActiveValueCells] = (0, _useActive.default)(multiple, open);
// =========================== Path ===========================
const onPathOpen = nextValueCells => {
setActiveValueCells(nextValueCells);
// Trigger loadData
internalLoadData(nextValueCells);
};
const isSelectable = option => {
if (disabled) {
return false;
}
const {
disabled: optionDisabled
} = option;
const isMergedLeaf = (0, _commonUtil.isLeaf)(option, fieldNames);
return !optionDisabled && (isMergedLeaf || changeOnSelect || multiple);
};
const onPathSelect = (valuePath, leaf, fromKeyboard = false) => {
onSelect(valuePath);
if (!multiple && (leaf || changeOnSelect && (expandTrigger === 'hover' || fromKeyboard))) {
toggleOpen(false);
}
};
// ========================== Option ==========================
const filteredOptions = React.useMemo(() => {
if (searchValue) {
return searchOptions;
}
return options;
}, [searchValue, searchOptions, options]);
// Update only when open or lockOptions
const mergedOptions = (0, _useMemo.default)(() => filteredOptions, [open, lockOptions], (prev, next) => !!next[0] && !next[1]);
// ========================== Column ==========================
const optionColumns = React.useMemo(() => {
const optionList = [{
options: mergedOptions
}];
let currentList = mergedOptions;
const fullPathKeys = (0, _commonUtil.getFullPathKeys)(currentList, fieldNames);
for (let i = 0; i < activeValueCells.length; i += 1) {
const activeValueCell = activeValueCells[i];
const currentOption = currentList.find((option, index) => (fullPathKeys[index] ? (0, _commonUtil.toPathKey)(fullPathKeys[index]) : option[fieldNames.value]) === activeValueCell);
const subOptions = currentOption?.[fieldNames.children];
if (!subOptions?.length) {
break;
}
currentList = subOptions;
optionList.push({
options: subOptions
});
}
return optionList;
}, [mergedOptions, activeValueCells, fieldNames]);
// ========================= Keyboard =========================
const onKeyboardSelect = (selectValueCells, option) => {
if (isSelectable(option)) {
onPathSelect(selectValueCells, (0, _commonUtil.isLeaf)(option, fieldNames), true);
}
};
(0, _useKeyboard.default)(ref, mergedOptions, fieldNames, activeValueCells, onPathOpen, onKeyboardSelect, {
direction,
searchValue,
toggleOpen,
open
});
// >>>>> Active Scroll
React.useEffect(() => {
if (searchValue) {
return;
}
for (let i = 0; i < activeValueCells.length; i += 1) {
const cellPath = activeValueCells.slice(0, i + 1);
const cellKeyPath = (0, _commonUtil.toPathKey)(cellPath);
const ele = containerRef.current?.querySelector(`li[data-path-key="${cellKeyPath.replace(/\\{0,2}"/g, '\\"')}"]` // matches unescaped double quotes
);
if (ele) {
(0, _commonUtil.scrollIntoParentView)(ele);
}
}
}, [activeValueCells, searchValue]);
// ========================== Render ==========================
// >>>>> Empty
const isEmpty = !optionColumns[0]?.options?.length;
const emptyList = [{
[fieldNames.value]: '__EMPTY__',
[_Column.FIX_LABEL]: notFoundContent,
disabled: true
}];
const columnProps = {
...props,
multiple: !isEmpty && multiple,
onSelect: onPathSelect,
onActive: onPathOpen,
onToggleOpen: toggleOpen,
checkedSet,
halfCheckedSet,
loadingKeys,
isSelectable
};
// >>>>> Columns
const mergedOptionColumns = isEmpty ? [{
options: emptyList
}] : optionColumns;
const columnNodes = mergedOptionColumns.map((col, index) => {
const prevValuePath = activeValueCells.slice(0, index);
const activeValue = activeValueCells[index];
return /*#__PURE__*/React.createElement(_Column.default, _extends({
key: index
}, columnProps, {
prefixCls: mergedPrefixCls,
options: col.options,
prevValuePath: prevValuePath,
activeValue: activeValue
}));
});
// >>>>> Render
return /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(`${mergedPrefixCls}-menus`, {
[`${mergedPrefixCls}-menu-empty`]: isEmpty,
[`${mergedPrefixCls}-rtl`]: rtl
}),
ref: containerRef
}, columnNodes);
});
if (process.env.NODE_ENV !== 'production') {
RawOptionList.displayName = 'RawOptionList';
}
var _default = exports.default = RawOptionList;
@@ -0,0 +1,4 @@
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
declare const RefOptionList: React.ForwardRefExoticComponent<React.RefAttributes<RefOptionListProps>>;
export default RefOptionList;
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _select = require("@rc-component/select");
var React = _interopRequireWildcard(require("react"));
var _List = _interopRequireDefault(require("./List"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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); }
const RefOptionList = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
lockOptions,
...baseProps
} = (0, _select.useBaseProps)();
// >>>>> Render
return /*#__PURE__*/React.createElement(_List.default, _extends({}, props, baseProps, {
lockOptions: lockOptions,
ref: ref
}));
});
var _default = exports.default = RefOptionList;
@@ -0,0 +1,6 @@
import type { LegacyKey } from '../Cascader';
/**
* Control the active open options path.
*/
declare const useActive: (multiple?: boolean, open?: boolean) => [LegacyKey[], (activeValueCells: LegacyKey[]) => void];
export default useActive;
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* Control the active open options path.
*/
const useActive = (multiple, open) => {
const {
values
} = React.useContext(_context.default);
const firstValueCells = values[0];
// Record current dropdown active options
// This also control the open status
const [activeValueCells, setActiveValueCells] = React.useState([]);
React.useEffect(() => {
if (!multiple) {
setActiveValueCells(firstValueCells || []);
}
}, /* eslint-disable react-hooks/exhaustive-deps */
[open, firstValueCells]
/* eslint-enable react-hooks/exhaustive-deps */);
return [activeValueCells, setActiveValueCells];
};
var _default = exports.default = useActive;
@@ -0,0 +1,10 @@
import type { RefOptionListProps } from '@rc-component/select/lib/OptionList';
import * as React from 'react';
import type { DefaultOptionType, InternalFieldNames, LegacyKey, SingleValueType } from '../Cascader';
declare const _default: (ref: React.Ref<RefOptionListProps>, options: DefaultOptionType[], fieldNames: InternalFieldNames, activeValueCells: LegacyKey[], setActiveValueCells: (activeValueCells: LegacyKey[]) => void, onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void, contextProps: {
direction?: "ltr" | "rtl" | undefined;
searchValue: string;
toggleOpen: (open?: boolean) => void;
open?: boolean | undefined;
}) => void;
export default _default;
@@ -0,0 +1,175 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var React = _interopRequireWildcard(require("react"));
var _useSearchOptions = require("../hooks/useSearchOptions");
var _commonUtil = require("../utils/commonUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = (ref, options, fieldNames, activeValueCells, setActiveValueCells, onKeyBoardSelect, contextProps) => {
const {
direction,
searchValue,
toggleOpen,
open
} = contextProps;
const rtl = direction === 'rtl';
const [validActiveValueCells, lastActiveIndex, lastActiveOptions, fullPathKeys] = React.useMemo(() => {
let activeIndex = -1;
let currentOptions = options;
const mergedActiveIndexes = [];
const mergedActiveValueCells = [];
const len = activeValueCells.length;
const pathKeys = (0, _commonUtil.getFullPathKeys)(options, fieldNames);
// Fill validate active value cells and index
for (let i = 0; i < len && currentOptions; i += 1) {
// Mark the active index for current options
const nextActiveIndex = currentOptions.findIndex((option, index) => (pathKeys[index] ? (0, _commonUtil.toPathKey)(pathKeys[index]) : option[fieldNames.value]) === activeValueCells[i]);
if (nextActiveIndex === -1) {
break;
}
activeIndex = nextActiveIndex;
mergedActiveIndexes.push(activeIndex);
mergedActiveValueCells.push(activeValueCells[i]);
currentOptions = currentOptions[activeIndex][fieldNames.children];
}
// Fill last active options
let activeOptions = options;
for (let i = 0; i < mergedActiveIndexes.length - 1; i += 1) {
activeOptions = activeOptions[mergedActiveIndexes[i]][fieldNames.children];
}
return [mergedActiveValueCells, activeIndex, activeOptions, pathKeys];
}, [activeValueCells, fieldNames, options]);
// Update active value cells and scroll to target element
const internalSetActiveValueCells = next => {
setActiveValueCells(next);
};
// Same options offset
const offsetActiveOption = offset => {
const len = lastActiveOptions.length;
let currentIndex = lastActiveIndex;
if (currentIndex === -1 && offset < 0) {
currentIndex = len;
}
for (let i = 0; i < len; i += 1) {
currentIndex = (currentIndex + offset + len) % len;
const option = lastActiveOptions[currentIndex];
if (option && !option.disabled) {
const nextActiveCells = validActiveValueCells.slice(0, -1).concat(fullPathKeys[currentIndex] ? (0, _commonUtil.toPathKey)(fullPathKeys[currentIndex]) : option[fieldNames.value]);
internalSetActiveValueCells(nextActiveCells);
return;
}
}
};
// Different options offset
const prevColumn = () => {
if (validActiveValueCells.length > 1) {
const nextActiveCells = validActiveValueCells.slice(0, -1);
internalSetActiveValueCells(nextActiveCells);
} else {
toggleOpen(false);
}
};
const nextColumn = () => {
const nextOptions = lastActiveOptions[lastActiveIndex]?.[fieldNames.children] || [];
const nextOption = nextOptions.find(option => !option.disabled);
if (nextOption) {
const nextActiveCells = [...validActiveValueCells, nextOption[fieldNames.value]];
internalSetActiveValueCells(nextActiveCells);
}
};
React.useImperativeHandle(ref, () => ({
// scrollTo: treeRef.current?.scrollTo,
onKeyDown: event => {
const {
which
} = event;
switch (which) {
// >>> Arrow keys
case _KeyCode.default.UP:
case _KeyCode.default.DOWN:
{
let offset = 0;
if (which === _KeyCode.default.UP) {
offset = -1;
} else if (which === _KeyCode.default.DOWN) {
offset = 1;
}
if (offset !== 0) {
offsetActiveOption(offset);
}
break;
}
case _KeyCode.default.LEFT:
{
if (searchValue) {
break;
}
if (rtl) {
nextColumn();
} else {
prevColumn();
}
break;
}
case _KeyCode.default.RIGHT:
{
if (searchValue) {
break;
}
if (rtl) {
prevColumn();
} else {
nextColumn();
}
break;
}
case _KeyCode.default.BACKSPACE:
{
if (!searchValue) {
prevColumn();
}
break;
}
// >>> Select
case _KeyCode.default.ENTER:
{
if (validActiveValueCells.length) {
const option = lastActiveOptions[lastActiveIndex];
// Search option should revert back of origin options
const originOptions = option?.[_useSearchOptions.SEARCH_MARK] || [];
if (originOptions.length) {
onKeyBoardSelect(originOptions.map(opt => opt[fieldNames.value]), originOptions[originOptions.length - 1]);
} else {
onKeyBoardSelect(validActiveValueCells, lastActiveOptions[lastActiveIndex]);
}
}
break;
}
// >>> Close
case _KeyCode.default.ESC:
{
toggleOpen(false);
if (open) {
event.stopPropagation();
}
}
}
},
onKeyUp: () => {}
}));
};
exports.default = _default;
+5
View File
@@ -0,0 +1,5 @@
import * as React from 'react';
import type { CascaderProps, DefaultOptionType } from './Cascader';
export type PickType = 'value' | 'defaultValue' | 'changeOnSelect' | 'onChange' | 'options' | 'prefixCls' | 'checkable' | 'fieldNames' | 'showCheckedStrategy' | 'loadData' | 'expandTrigger' | 'expandIcon' | 'loadingIcon' | 'className' | 'style' | 'direction' | 'notFoundContent' | 'disabled' | 'optionRender';
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>;
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;
+125
View File
@@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = Panel;
var _clsx = require("clsx");
var _util = require("@rc-component/util");
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("./context"));
var _useMissingValues = _interopRequireDefault(require("./hooks/useMissingValues"));
var _useOptions = _interopRequireDefault(require("./hooks/useOptions"));
var _useSelect = _interopRequireDefault(require("./hooks/useSelect"));
var _useValues = _interopRequireDefault(require("./hooks/useValues"));
var _List = _interopRequireDefault(require("./OptionList/List"));
var _commonUtil = require("./utils/commonUtil");
var _treeUtil = require("./utils/treeUtil");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function noop() {}
function Panel(props) {
const {
prefixCls = 'rc-cascader',
style,
className,
options,
checkable,
defaultValue,
value,
fieldNames,
changeOnSelect,
onChange,
showCheckedStrategy,
loadData,
expandTrigger,
expandIcon = '>',
loadingIcon,
direction,
notFoundContent = 'Not Found',
disabled,
optionRender
} = props;
// ======================== Multiple ========================
const multiple = !!checkable;
// ========================= Values =========================
const [interanlRawValues, setRawValues] = (0, _util.useControlledState)(defaultValue, value);
const rawValues = (0, _commonUtil.toRawValues)(interanlRawValues);
// ========================= FieldNames =========================
const mergedFieldNames = React.useMemo(() => (0, _commonUtil.fillFieldNames)(fieldNames), /* eslint-disable react-hooks/exhaustive-deps */
[JSON.stringify(fieldNames)]
/* eslint-enable react-hooks/exhaustive-deps */);
// =========================== Option ===========================
const [mergedOptions, getPathKeyEntities, getValueByKeyPath] = (0, _useOptions.default)(mergedFieldNames, options);
// ========================= Values =========================
const getMissingValues = (0, _useMissingValues.default)(mergedOptions, mergedFieldNames);
// Fill `rawValues` with checked conduction values
const [checkedValues, halfCheckedValues, missingCheckedValues] = (0, _useValues.default)(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues);
// =========================== Change ===========================
const triggerChange = (0, _util.useEvent)(nextValues => {
setRawValues(nextValues);
// Save perf if no need trigger event
if (onChange) {
const nextRawValues = (0, _commonUtil.toRawValues)(nextValues);
const valueOptions = nextRawValues.map(valueCells => (0, _treeUtil.toPathOptions)(valueCells, mergedOptions, mergedFieldNames).map(valueOpt => valueOpt.option));
const triggerValues = multiple ? nextRawValues : nextRawValues[0];
const triggerOptions = multiple ? valueOptions : valueOptions[0];
onChange(triggerValues, triggerOptions);
}
});
// =========================== Select ===========================
const handleSelection = (0, _useSelect.default)(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy);
const onInternalSelect = (0, _util.useEvent)(valuePath => {
handleSelection(valuePath);
});
// ======================== Context =========================
const cascaderContext = React.useMemo(() => ({
options: mergedOptions,
fieldNames: mergedFieldNames,
values: checkedValues,
halfValues: halfCheckedValues,
changeOnSelect,
onSelect: onInternalSelect,
checkable,
searchOptions: [],
popupPrefixCls: undefined,
loadData,
expandTrigger,
expandIcon,
loadingIcon,
popupMenuColumnStyle: undefined,
optionRender
}), [mergedOptions, mergedFieldNames, checkedValues, halfCheckedValues, changeOnSelect, onInternalSelect, checkable, loadData, expandTrigger, expandIcon, loadingIcon, optionRender]);
// ========================= Render =========================
const panelPrefixCls = `${prefixCls}-panel`;
const isEmpty = !mergedOptions.length;
return /*#__PURE__*/React.createElement(_context.default.Provider, {
value: cascaderContext
}, /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(panelPrefixCls, {
[`${panelPrefixCls}-rtl`]: direction === 'rtl',
[`${panelPrefixCls}-empty`]: isEmpty
}, className),
style: style
}, isEmpty ? notFoundContent : /*#__PURE__*/React.createElement(_List.default, {
prefixCls: prefixCls,
searchValue: "",
multiple: multiple,
toggleOpen: noop,
open: true,
direction: direction,
disabled: disabled
})));
}
+23
View File
@@ -0,0 +1,23 @@
import * as React from 'react';
import type { CascaderProps, InternalFieldNames, DefaultOptionType, SingleValueType } from './Cascader';
export interface CascaderContextProps {
options: NonNullable<CascaderProps['options']>;
fieldNames: InternalFieldNames;
values: SingleValueType[];
halfValues: SingleValueType[];
changeOnSelect?: boolean;
onSelect: (valuePath: SingleValueType) => void;
checkable?: boolean | React.ReactNode;
searchOptions: DefaultOptionType[];
popupPrefixCls?: string;
loadData?: (selectOptions: DefaultOptionType[]) => void;
expandTrigger?: 'hover' | 'click';
expandIcon?: React.ReactNode;
loadingIcon?: React.ReactNode;
popupMenuColumnStyle?: React.CSSProperties;
optionRender?: CascaderProps['optionRender'];
classNames?: CascaderProps['classNames'];
styles?: CascaderProps['styles'];
}
declare const CascaderContext: React.Context<CascaderContextProps>;
export default CascaderContext;
+11
View File
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const CascaderContext = /*#__PURE__*/React.createContext({});
var _default = exports.default = CascaderContext;
@@ -0,0 +1,10 @@
import * as React from 'react';
import type { DefaultOptionType, SingleValueType, CascaderProps, InternalFieldNames } from '../Cascader';
declare const _default: (rawValues: SingleValueType[], options: DefaultOptionType[], fieldNames: InternalFieldNames, multiple: boolean, displayRender: CascaderProps['displayRender']) => {
label: React.ReactNode;
value: string;
key: string;
valueCells: SingleValueType;
disabled: boolean | undefined;
}[];
export default _default;
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _treeUtil = require("../utils/treeUtil");
var React = _interopRequireWildcard(require("react"));
var _commonUtil = require("../utils/commonUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
var _default = (rawValues, options, fieldNames, multiple, displayRender) => {
return React.useMemo(() => {
const mergedDisplayRender = displayRender || (
// Default displayRender
labels => {
const mergedLabels = multiple ? labels.slice(-1) : labels;
const SPLIT = ' / ';
if (mergedLabels.every(label => ['string', 'number'].includes(typeof label))) {
return mergedLabels.join(SPLIT);
}
// If exist non-string value, use ReactNode instead
return mergedLabels.reduce((list, label, index) => {
const keyedLabel = /*#__PURE__*/React.isValidElement(label) ? /*#__PURE__*/React.cloneElement(label, {
key: index
}) : label;
if (index === 0) {
return [keyedLabel];
}
return [...list, SPLIT, keyedLabel];
}, []);
});
return rawValues.map(valueCells => {
const valueOptions = (0, _treeUtil.toPathOptions)(valueCells, options, fieldNames);
const label = mergedDisplayRender(valueOptions.map(({
option,
value
}) => option?.[fieldNames.label] ?? value), valueOptions.map(({
option
}) => option));
const value = (0, _commonUtil.toPathKey)(valueCells);
return {
label,
value,
key: value,
valueCells,
disabled: valueOptions[valueOptions.length - 1]?.option?.disabled
};
});
}, [rawValues, options, fieldNames, displayRender, multiple]);
};
exports.default = _default;
@@ -0,0 +1,10 @@
import type { DefaultOptionType, InternalFieldNames } from '../Cascader';
import type { DataEntity } from '@rc-component/tree/lib/interface';
export interface OptionsInfo {
keyEntities: Record<string, DataEntity>;
pathKeyEntities: Record<string, DataEntity>;
}
export type GetEntities = () => OptionsInfo['pathKeyEntities'];
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
declare const _default: (options: DefaultOptionType[], fieldNames: InternalFieldNames) => GetEntities;
export default _default;
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _treeUtil = require("@rc-component/tree/lib/utils/treeUtil");
var _commonUtil = require("../utils/commonUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
var _default = (options, fieldNames) => {
const cacheRef = React.useRef({
options: [],
info: {
keyEntities: {},
pathKeyEntities: {}
}
});
const getEntities = React.useCallback(() => {
if (cacheRef.current.options !== options) {
cacheRef.current.options = options;
cacheRef.current.info = (0, _treeUtil.convertDataToEntities)(options, {
fieldNames: fieldNames,
initWrapper: wrapper => ({
...wrapper,
pathKeyEntities: {}
}),
processEntity: (entity, wrapper) => {
const pathKey = entity.nodes.map(node => node[fieldNames.value]).join(_commonUtil.VALUE_SPLIT);
wrapper.pathKeyEntities[pathKey] = entity;
// Overwrite origin key.
// this is very hack but we need let conduct logic work with connect path
entity.key = pathKey;
}
});
}
return cacheRef.current.info.pathKeyEntities;
}, [fieldNames, options]);
return getEntities;
};
exports.default = _default;
@@ -0,0 +1,3 @@
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
export type GetMissValues = ReturnType<typeof useMissingValues>;
export default function useMissingValues(options: DefaultOptionType[], fieldNames: InternalFieldNames): (rawValues: SingleValueType[]) => [SingleValueType[], SingleValueType[]];
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useMissingValues;
var React = _interopRequireWildcard(require("react"));
var _treeUtil = require("../utils/treeUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useMissingValues(options, fieldNames) {
return React.useCallback(rawValues => {
const missingValues = [];
const existsValues = [];
rawValues.forEach(valueCell => {
const pathOptions = (0, _treeUtil.toPathOptions)(valueCell, options, fieldNames);
if (pathOptions.every(opt => opt.option)) {
existsValues.push(valueCell);
} else {
missingValues.push(valueCell);
}
});
return [existsValues, missingValues];
}, [options, fieldNames]);
}
@@ -0,0 +1,8 @@
import type { DefaultOptionType } from '..';
import type { InternalFieldNames, SingleValueType, LegacyKey } from '../Cascader';
import { type GetEntities } from './useEntities';
export default function useOptions(mergedFieldNames: InternalFieldNames, options?: DefaultOptionType[]): [
mergedOptions: DefaultOptionType[],
getPathKeyEntities: GetEntities,
getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[]
];
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useOptions;
var React = _interopRequireWildcard(require("react"));
var _useEntities = _interopRequireDefault(require("./useEntities"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useOptions(mergedFieldNames, options) {
const mergedOptions = React.useMemo(() => options || [], [options]);
// Only used in multiple mode, this fn will not call in single mode
const getPathKeyEntities = (0, _useEntities.default)(mergedOptions, mergedFieldNames);
/** Convert path key back to value format */
const getValueByKeyPath = React.useCallback(pathKeys => {
const keyPathEntities = getPathKeyEntities();
return pathKeys.map(pathKey => {
const {
nodes
} = keyPathEntities[pathKey];
return nodes.map(node => node[mergedFieldNames.value]);
});
}, [getPathKeyEntities, mergedFieldNames]);
return [mergedOptions, getPathKeyEntities, getValueByKeyPath];
}
@@ -0,0 +1,2 @@
import type { CascaderProps, SearchConfig } from '../Cascader';
export default function useSearchConfig(showSearch?: CascaderProps['showSearch'], props?: any): [boolean, SearchConfig<import("../Cascader").DefaultOptionType, string>];
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useSearchConfig;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Convert `showSearch` to unique config
function useSearchConfig(showSearch, props) {
const {
autoClearSearchValue,
searchValue,
onSearch
} = props;
return React.useMemo(() => {
if (!showSearch) {
return [false, {}];
}
let searchConfig = {
matchInputWidth: true,
limit: 50,
autoClearSearchValue,
searchValue,
onSearch
};
if (showSearch && typeof showSearch === 'object') {
searchConfig = {
...searchConfig,
...showSearch
};
}
if (searchConfig.limit <= 0) {
searchConfig.limit = false;
if (process.env.NODE_ENV !== 'production') {
(0, _warning.default)(false, "'limit' of showSearch should be positive number or false.");
}
}
return [true, searchConfig];
}, [showSearch, autoClearSearchValue, searchValue, onSearch]);
}
@@ -0,0 +1,4 @@
import type { DefaultOptionType, InternalFieldNames, SearchConfig } from '../Cascader';
export declare const SEARCH_MARK = "__rc_cascader_search_mark__";
declare const useSearchOptions: (search: string, options: DefaultOptionType[], fieldNames: InternalFieldNames, prefixCls: string, config: SearchConfig, enableHalfPath?: boolean) => DefaultOptionType[];
export default useSearchOptions;
@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.SEARCH_MARK = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const SEARCH_MARK = exports.SEARCH_MARK = '__rc_cascader_search_mark__';
const defaultFilter = (search, options, {
label = ''
}) => options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase()));
const defaultRender = (inputValue, path, prefixCls, fieldNames) => path.map(opt => opt[fieldNames.label]).join(' / ');
const useSearchOptions = (search, options, fieldNames, prefixCls, config, enableHalfPath) => {
const {
filter = defaultFilter,
render = defaultRender,
limit = 50,
sort
} = config;
return React.useMemo(() => {
const filteredOptions = [];
if (!search) {
return [];
}
function dig(list, pathOptions, parentDisabled = false) {
list.forEach(option => {
// Perf saving when `sort` is disabled and `limit` is provided
if (!sort && limit !== false && limit > 0 && filteredOptions.length >= limit) {
return;
}
const connectedPathOptions = [...pathOptions, option];
const children = option[fieldNames.children];
const mergedDisabled = parentDisabled || option.disabled;
// If current option is filterable
if (
// If is leaf option
!children || children.length === 0 ||
// If is changeOnSelect or multiple
enableHalfPath) {
if (filter(search, connectedPathOptions, {
label: fieldNames.label
})) {
filteredOptions.push({
...option,
disabled: mergedDisabled,
[fieldNames.label]: render(search, connectedPathOptions, prefixCls, fieldNames),
[SEARCH_MARK]: connectedPathOptions,
[fieldNames.children]: undefined
});
}
}
if (children) {
dig(option[fieldNames.children], connectedPathOptions, mergedDisabled);
}
});
}
dig(options, []);
// Do sort
if (sort) {
filteredOptions.sort((a, b) => {
return sort(a[SEARCH_MARK], b[SEARCH_MARK], search, fieldNames);
});
}
return limit !== false && limit > 0 ? filteredOptions.slice(0, limit) : filteredOptions;
}, [search, options, fieldNames, prefixCls, render, enableHalfPath, filter, sort, limit]);
};
var _default = exports.default = useSearchOptions;
@@ -0,0 +1,3 @@
import type { InternalValueType, LegacyKey, ShowCheckedStrategy, SingleValueType } from '../Cascader';
import type { GetEntities } from './useEntities';
export default function useSelect(multiple: boolean, triggerChange: (nextValues: InternalValueType) => void, checkedValues: SingleValueType[], halfCheckedValues: SingleValueType[], missingCheckedValues: SingleValueType[], getPathKeyEntities: GetEntities, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], showCheckedStrategy?: ShowCheckedStrategy): (valuePath: SingleValueType) => void;
@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useSelect;
var _conductUtil = require("@rc-component/tree/lib/utils/conductUtil");
var _commonUtil = require("../utils/commonUtil");
var _treeUtil = require("../utils/treeUtil");
function useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy) {
return valuePath => {
if (!multiple) {
triggerChange(valuePath);
} else {
// Prepare conduct required info
const pathKey = (0, _commonUtil.toPathKey)(valuePath);
const checkedPathKeys = (0, _commonUtil.toPathKeys)(checkedValues);
const halfCheckedPathKeys = (0, _commonUtil.toPathKeys)(halfCheckedValues);
const existInChecked = checkedPathKeys.includes(pathKey);
const existInMissing = missingCheckedValues.some(valueCells => (0, _commonUtil.toPathKey)(valueCells) === pathKey);
// Do update
let nextCheckedValues = checkedValues;
let nextMissingValues = missingCheckedValues;
if (existInMissing && !existInChecked) {
// Missing value only do filter
nextMissingValues = missingCheckedValues.filter(valueCells => (0, _commonUtil.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;
if (existInChecked) {
({
checkedKeys
} = (0, _conductUtil.conductCheck)(nextRawCheckedKeys, {
checked: false,
halfCheckedKeys: halfCheckedPathKeys
}, pathKeyEntities));
} else {
({
checkedKeys
} = (0, _conductUtil.conductCheck)(nextRawCheckedKeys, true, pathKeyEntities));
}
// Roll up to parent level keys
const deDuplicatedKeys = (0, _treeUtil.formatStrategyValues)(checkedKeys, getPathKeyEntities, showCheckedStrategy);
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
}
triggerChange([...nextMissingValues, ...nextCheckedValues]);
}
};
}
@@ -0,0 +1,8 @@
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { LegacyKey, SingleValueType } from '../Cascader';
import type { GetMissValues } from './useMissingValues';
export default function useValues(multiple: boolean, rawValues: SingleValueType[], getPathKeyEntities: () => Record<string, DataEntity>, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], getMissingValues: GetMissValues): [
checkedValues: SingleValueType[],
halfCheckedValues: SingleValueType[],
missingCheckedValues: SingleValueType[]
];
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useValues;
var _conductUtil = require("@rc-component/tree/lib/utils/conductUtil");
var React = _interopRequireWildcard(require("react"));
var _commonUtil = require("../utils/commonUtil");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues) {
// Fill `rawValues` with checked conduction values
return React.useMemo(() => {
const [existValues, missingValues] = getMissingValues(rawValues);
if (!multiple || !rawValues.length) {
return [existValues, [], missingValues];
}
const keyPathValues = (0, _commonUtil.toPathKeys)(existValues);
const keyPathEntities = getPathKeyEntities();
const {
checkedKeys,
halfCheckedKeys
} = (0, _conductUtil.conductCheck)(keyPathValues, true, keyPathEntities);
// Convert key back to value cells
return [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
}, [multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues]);
}
+5
View File
@@ -0,0 +1,5 @@
import Cascader from './Cascader';
import Panel from './Panel';
export type { BaseOptionType, DefaultOptionType, CascaderProps, FieldNames, SearchConfig, CascaderRef, } from './Cascader';
export { Panel };
export default Cascader;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "Panel", {
enumerable: true,
get: function () {
return _Panel.default;
}
});
exports.default = void 0;
var _Cascader = _interopRequireDefault(require("./Cascader"));
var _Panel = _interopRequireDefault(require("./Panel"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = exports.default = _Cascader.default;
@@ -0,0 +1,18 @@
import type { DefaultOptionType, FieldNames, InternalFieldNames, InternalValueType, SingleValueType } from '../Cascader';
export declare const VALUE_SPLIT = "__RC_CASCADER_SPLIT__";
export declare const SHOW_PARENT = "SHOW_PARENT";
export declare const SHOW_CHILD = "SHOW_CHILD";
/**
* Will convert value to string, and join with `VALUE_SPLIT`
*/
export declare function toPathKey(value: SingleValueType): string;
/**
* Batch convert value to string, and join with `VALUE_SPLIT`
*/
export declare function toPathKeys(value: SingleValueType[]): string[];
export declare function toPathValueStr(pathKey: string): string[];
export declare function fillFieldNames(fieldNames?: FieldNames): InternalFieldNames;
export declare function isLeaf(option: DefaultOptionType, fieldNames: FieldNames): any;
export declare function scrollIntoParentView(element: HTMLElement): void;
export declare function getFullPathKeys(options: DefaultOptionType[], fieldNames: FieldNames): any[];
export declare function toRawValues(value?: InternalValueType): SingleValueType[];
@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VALUE_SPLIT = exports.SHOW_PARENT = exports.SHOW_CHILD = void 0;
exports.fillFieldNames = fillFieldNames;
exports.getFullPathKeys = getFullPathKeys;
exports.isLeaf = isLeaf;
exports.scrollIntoParentView = scrollIntoParentView;
exports.toPathKey = toPathKey;
exports.toPathKeys = toPathKeys;
exports.toPathValueStr = toPathValueStr;
exports.toRawValues = toRawValues;
var _useSearchOptions = require("../hooks/useSearchOptions");
const VALUE_SPLIT = exports.VALUE_SPLIT = '__RC_CASCADER_SPLIT__';
const SHOW_PARENT = exports.SHOW_PARENT = 'SHOW_PARENT';
const SHOW_CHILD = exports.SHOW_CHILD = 'SHOW_CHILD';
/**
* Will convert value to string, and join with `VALUE_SPLIT`
*/
function toPathKey(value) {
return value.join(VALUE_SPLIT);
}
/**
* Batch convert value to string, and join with `VALUE_SPLIT`
*/
function toPathKeys(value) {
return value.map(toPathKey);
}
function toPathValueStr(pathKey) {
return pathKey.split(VALUE_SPLIT);
}
function fillFieldNames(fieldNames) {
const {
label,
value,
children
} = fieldNames || {};
const val = value || 'value';
return {
label: label || 'label',
value: val,
key: val,
children: children || 'children'
};
}
function isLeaf(option, fieldNames) {
return option.isLeaf ?? !option[fieldNames.children]?.length;
}
function scrollIntoParentView(element) {
const parent = element.parentElement;
if (!parent) {
return;
}
const elementToParent = element.offsetTop - parent.offsetTop; // offsetParent may not be parent.
if (elementToParent - parent.scrollTop < 0) {
parent.scrollTo({
top: elementToParent
});
} else if (elementToParent + element.offsetHeight - parent.scrollTop > parent.offsetHeight) {
parent.scrollTo({
top: elementToParent + element.offsetHeight - parent.offsetHeight
});
}
}
function getFullPathKeys(options, fieldNames) {
return options.map(item => item[_useSearchOptions.SEARCH_MARK]?.map(opt => opt[fieldNames.value]));
}
function isMultipleValue(value) {
return Array.isArray(value) && Array.isArray(value[0]);
}
function toRawValues(value) {
if (!value) {
return [];
}
if (isMultipleValue(value)) {
return value;
}
return (value.length === 0 ? [] : [value]).map(val => Array.isArray(val) ? val : [val]);
}
@@ -0,0 +1,8 @@
import type { SingleValueType, DefaultOptionType, InternalFieldNames, ShowCheckedStrategy, LegacyKey } from '../Cascader';
import type { GetEntities } from '../hooks/useEntities';
export declare function formatStrategyValues(pathKeys: LegacyKey[], getKeyPathEntities: GetEntities, showCheckedStrategy?: ShowCheckedStrategy): LegacyKey[];
export declare function toPathOptions(valueCells: SingleValueType, options: DefaultOptionType[], fieldNames: InternalFieldNames, stringMode?: boolean): {
value: SingleValueType[number];
index: number;
option: DefaultOptionType;
}[];
@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.formatStrategyValues = formatStrategyValues;
exports.toPathOptions = toPathOptions;
var _commonUtil = require("./commonUtil");
function formatStrategyValues(pathKeys, getKeyPathEntities, showCheckedStrategy) {
const valueSet = new Set(pathKeys);
const keyPathEntities = getKeyPathEntities();
return pathKeys.filter(key => {
const entity = keyPathEntities[key];
const parent = entity ? entity.parent : null;
const children = entity ? entity.children : null;
if (entity && entity.node.disabled) {
return true;
}
return showCheckedStrategy === _commonUtil.SHOW_CHILD ? !(children && children.some(child => child.key && valueSet.has(child.key))) : !(parent && !parent.node.disabled && valueSet.has(parent.key));
});
}
function toPathOptions(valueCells, options, fieldNames,
// Used for loadingKeys which saved loaded keys as string
stringMode = false) {
let currentList = options;
const valueOptions = [];
for (let i = 0; i < valueCells.length; i += 1) {
const valueCell = valueCells[i];
const foundIndex = currentList?.findIndex(option => {
const val = option[fieldNames.value];
return stringMode ? String(val) === String(valueCell) : val === valueCell;
});
const foundOption = foundIndex !== -1 ? currentList?.[foundIndex] : null;
valueOptions.push({
value: foundOption?.[fieldNames.value] ?? valueCell,
index: foundIndex,
option: foundOption
});
currentList = foundOption?.[fieldNames.children];
}
return valueOptions;
}
@@ -0,0 +1,2 @@
import type { DefaultOptionType, FieldNames } from '../Cascader';
export declare function warningNullOptions(options: DefaultOptionType[], fieldNames: FieldNames): void;
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.warningNullOptions = warningNullOptions;
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// value in Cascader options should not be null
function warningNullOptions(options, fieldNames) {
if (options) {
const recursiveOptions = optionsList => {
for (let i = 0; i < optionsList.length; i++) {
const option = optionsList[i];
if (option[fieldNames?.value] === null) {
(0, _warning.default)(false, '`value` in Cascader options should not be `null`.');
return true;
}
if (Array.isArray(option[fieldNames?.children]) && recursiveOptions(option[fieldNames?.children])) {
return true;
}
}
};
recursiveOptions(options);
}
}
+85
View File
@@ -0,0 +1,85 @@
{
"name": "@rc-component/cascader",
"version": "1.14.0",
"description": "cascade select ui component for react",
"keywords": [
"react",
"react-component",
"react-cascader",
"react-select",
"select",
"cascade",
"cascader"
],
"homepage": "https://github.com/react-component/cascader",
"bugs": {
"url": "https://github.com/react-component/cascader/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/react-component/cascader.git"
},
"license": "MIT",
"author": "afc163@gmail.com",
"main": "./lib/index",
"module": "./es/index",
"files": [
"lib",
"es",
"assets/*.css",
"assets/*.less"
],
"scripts": {
"build": "dumi build",
"compile": "father build",
"coverage": "father test --coverage",
"tsc": "bunx tsc --noEmit",
"deploy": "UMI_ENV=gh npm run build && gh-pages -d dist",
"lint": "eslint src/ examples/ tests/ --ext .tsx,.ts,.jsx,.jsx",
"now-build": "npm run build",
"prepublishOnly": "npm run compile && rc-np",
"lint:tsc": "tsc -p tsconfig.json --noEmit",
"start": "dumi dev",
"test": "rc-test"
},
"dependencies": {
"@rc-component/select": "~1.6.0",
"@rc-component/tree": "~1.2.0",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"devDependencies": {
"@rc-component/father-plugin": "^2.0.2",
"@rc-component/form": "^1.4.0",
"@rc-component/np": "^1.0.3",
"@rc-component/trigger": "^3.0.0",
"@testing-library/react": "^16.3.0",
"@types/jest": "^29.4.0",
"@types/node": "^24.5.2",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/warning": "^3.0.0",
"@umijs/fabric": "^4.0.0",
"array-tree-filter": "^3.0.2",
"cheerio": "1.0.0-rc.12",
"core-js": "^3.40.0",
"cross-env": "^7.0.0",
"dumi": "^2.1.10",
"eslint": "^8.54.0",
"eslint-plugin-jest": "^28.8.3",
"eslint-plugin-unicorn": "^56.0.1",
"father": "^4.0.0",
"gh-pages": "^6.1.1",
"glob": "^7.1.6",
"less": "^4.2.0",
"prettier": "^3.1.0",
"rc-test": "^7.1.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.3.2"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
}