feat: Cascader support multiple mode (#31936)

* chore: Update cascader version

* chore: replace cascader

* chore: default allowClear

* chore: Update docs

* test: Back of part

* test: More snapshot

* test: more and more

* test: Failed of defaultValue

* test: all basic test case

* test: All snapshot

* chore: Update cascader

* chore: disable react/jsx-no-bind

* chore: fix lint

* chore: fix less order

* chore: fix deps

* docs: Update multiple demo

* docs: Add multiple example

* test: Update snapshot

* test: update snapshot
This commit is contained in:
二货机器人 2021-08-31 15:51:02 +08:00 committed by GitHub
parent d86e1b5153
commit 282b7c8575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 3568 additions and 3055 deletions

View File

@ -94,6 +94,7 @@ module.exports = {
'react/sort-comp': 0,
'react/display-name': 0,
'react/static-property-placement': 0,
'react/jsx-no-bind': 0, // Should not check test file
'react/no-find-dom-node': 0,
'react/no-unused-prop-types': 0,
'react/default-props-match-prop-types': 0,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,31 @@
import React from 'react';
import { render, mount } from 'enzyme';
import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import Cascader from '..';
import ConfigProvider from '../../config-provider';
import excludeAllWarning from '../../../tests/shared/excludeWarning';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils';
function toggleOpen(wrapper) {
wrapper.find('.ant-select-selector').simulate('mousedown');
}
function isOpen(wrapper) {
return !!wrapper.find('Trigger').props().popupVisible;
}
function getDropdown(wrapper) {
return wrapper.find('.ant-select-dropdown');
}
function clickOption(wrapper, menuIndex, itemIndex, type = 'click') {
const menu = wrapper.find('ul.ant-cascader-menu').at(menuIndex);
const itemList = menu.find('li.ant-cascader-menu-item');
itemList.at(itemIndex).simulate(type);
}
const options = [
{
@ -48,13 +67,15 @@ function filter(inputValue, path) {
}
describe('Cascader', () => {
focusTest(Cascader);
excludeAllWarning();
focusTest(Cascader, { refFocus: true });
mountTest(Cascader);
rtlTest(Cascader);
it('popup correctly when panel is hidden', () => {
const wrapper = mount(<Cascader options={options} />);
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(isOpen(wrapper)).toBeFalsy();
});
it('popup correctly when panel is open', () => {
@ -62,8 +83,8 @@ describe('Cascader', () => {
const wrapper = mount(
<Cascader options={options} onPopupVisibleChange={onPopupVisibleChange} />,
);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
toggleOpen(wrapper);
expect(isOpen(wrapper)).toBeTruthy();
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
});
@ -77,83 +98,71 @@ describe('Cascader', () => {
it('popup correctly with defaultValue', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
toggleOpen(wrapper);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
});
it('should support popupVisible', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
expect(wrapper.find('Trigger').instance().getComponent().props.visible).toBe(false);
expect(isOpen(wrapper)).toBeFalsy();
wrapper.setProps({ popupVisible: true });
expect(wrapper.find('Trigger').instance().getComponent().props.visible).toBe(true);
expect(isOpen(wrapper)).toBeTruthy();
});
it('can be selected', () => {
const onChange = jest.fn();
const wrapper = mount(<Cascader options={options} onChange={onChange} />);
wrapper.find('input').simulate('click');
let popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(1)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(2)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
toggleOpen(wrapper);
expect(isOpen(wrapper)).toBeTruthy();
clickOption(wrapper, 0, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
clickOption(wrapper, 1, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
clickOption(wrapper, 2, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('backspace should work with `Cascader[showSearch]`', () => {
const wrapper = mount(<Cascader options={options} showSearch />);
wrapper.find('input').simulate('change', { target: { value: '123' } });
expect(wrapper.state('inputValue')).toBe('123');
wrapper.find('input').simulate('keydown', { keyCode: KeyCode.BACKSPACE });
// Simulate onKeyDown will not trigger onChange by default, so the value is still '123'
expect(wrapper.state('inputValue')).toBe('123');
expect(isOpen(wrapper)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.BACKSPACE });
expect(isOpen(wrapper)).toBeTruthy();
wrapper.find('input').simulate('change', { target: { value: '' } });
expect(isOpen(wrapper)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.BACKSPACE });
expect(isOpen(wrapper)).toBeFalsy();
});
it('should highlight keyword and filter when search in Cascader', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'z' } });
expect(wrapper.state('inputValue')).toBe('z');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(popupWrapper.render()).toMatchSnapshot();
expect(getDropdown(wrapper).render()).toMatchSnapshot();
});
it('should highlight keyword and filter when search in Cascader with same field name of label and value', () => {
const customOptions = [
{
name: 'Zhejiang',
value: 'Zhejiang',
children: [
{
name: 'Hangzhou',
value: 'Hangzhou',
children: [
{
name: 'West Lake',
value: 'West Lake',
},
{
name: 'Xia Sha',
value: 'Xia Sha',
disabled: true,
},
],
@ -171,81 +180,35 @@ describe('Cascader', () => {
showSearch={{ filter: customFilter }}
/>,
);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'z' } });
expect(wrapper.state('inputValue')).toBe('z');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(popupWrapper.render()).toMatchSnapshot();
expect(getDropdown(wrapper).render()).toMatchSnapshot();
});
it('should render not found content', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: '__notfoundkeyword__' } });
expect(wrapper.state('inputValue')).toBe('__notfoundkeyword__');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(popupWrapper.render()).toMatchSnapshot();
expect(getDropdown(wrapper).render()).toMatchSnapshot();
});
it('should support to clear selection', async () => {
it('should support to clear selection', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
const willUnmount = jest.spyOn(wrapper.instance(), 'componentWillUnmount');
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
expect(wrapper.find('.ant-cascader-picker-label').text()).toBe('Zhejiang / Hangzhou');
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
await sleep(300);
expect(wrapper.find('.ant-cascader-picker-label').text()).toBe('');
wrapper.unmount();
expect(willUnmount).toHaveBeenCalled();
expect(clearTimeoutSpy).toHaveBeenCalled();
clearTimeoutSpy.mockRestore();
});
it('should close popup when clear selection', () => {
const onPopupVisibleChange = jest.fn();
const wrapper = mount(
<Cascader
options={options}
popupVisible
defaultValue={['zhejiang', 'hangzhou']}
onPopupVisibleChange={onPopupVisibleChange}
/>,
);
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
expect(onPopupVisibleChange).toHaveBeenCalledWith(false);
expect(wrapper.find('.ant-select-selection-item').text()).toEqual('Zhejiang / Hangzhou');
wrapper.find('.ant-select-clear').at(0).simulate('mouseDown');
expect(wrapper.exists('.ant-select-selection-item')).toBeFalsy();
});
it('should clear search input when clear selection', () => {
const wrapper = mount(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} showSearch />,
);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'xxx' } });
expect(wrapper.state('inputValue')).toBe('xxx');
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
expect(wrapper.state('inputValue')).toBe('');
});
it('should not trigger visible change when click search input', () => {
const onPopupVisibleChange = jest.fn();
const wrapper = mount(
<Cascader options={options} showSearch onPopupVisibleChange={onPopupVisibleChange} />,
);
wrapper.find('input').simulate('focus');
expect(onPopupVisibleChange).toHaveBeenCalledTimes(0);
wrapper.find('input').simulate('click');
expect(onPopupVisibleChange).toHaveBeenCalledTimes(1);
wrapper.find('input').simulate('click');
expect(onPopupVisibleChange).toHaveBeenCalledTimes(1);
wrapper.find('input').simulate('blur');
wrapper.setState({ popupVisible: false });
wrapper.find('input').simulate('click');
expect(onPopupVisibleChange).toHaveBeenCalledTimes(2);
wrapper.find('.ant-select-clear').at(0).simulate('mouseDown');
expect(wrapper.find('input').props().value).toEqual('');
});
it('should change filtered item when options are changed', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
wrapper.setProps({ options: [options[0]] });
@ -254,12 +217,12 @@ describe('Cascader', () => {
it('should select item immediately when searching and pressing down arrow key', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
expect(wrapper.find('.ant-cascader-menu-item-active').length).toBe(0);
wrapper.find('input').simulate('keyDown', {
keyCode: KeyCode.DOWN,
which: KeyCode.DOWN,
});
expect(wrapper.find('.ant-cascader-menu-item-active').length).toBe(1);
});
@ -299,31 +262,40 @@ describe('Cascader', () => {
],
},
];
const onChange = jest.fn();
const wrapper = mount(
<Cascader
options={customerOptions}
onChange={onChange}
fieldNames={{
children: 'items',
label: 'name',
value: 'code',
}}
open
/>,
);
wrapper.instance().handleChange(['zhejiang', 'hangzhou', 'xihu'], customerOptions);
expect(wrapper.find('.ant-cascader-picker-label').text().split('/').length).toBe(3);
clickOption(wrapper, 0, 0);
clickOption(wrapper, 1, 0);
clickOption(wrapper, 2, 0);
expect(wrapper.find('.ant-select-selection-item').text()).toEqual(
'Zhejiang / Hangzhou / West Lake',
);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('should show not found content when options.length is 0', () => {
const customerOptions = [];
const wrapper = mount(<Cascader options={customerOptions} />);
wrapper.find('input').simulate('click');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(popupWrapper.render()).toMatchSnapshot();
toggleOpen(wrapper);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
});
it('not found content shoule be disabled', () => {
const wrapper = mount(<Cascader options={[]} />);
wrapper.find('input').simulate('click');
it('not found content should be disabled', () => {
const wrapper = mount(<Cascader options={[]} open />);
expect(wrapper.find('.ant-cascader-menu-item-disabled').length).toBe(1);
});
@ -336,37 +308,33 @@ describe('Cascader', () => {
it('limit with positive number', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: 1 }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(1);
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(1);
});
it('not limit', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: false }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(2);
});
it('negative limit', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: -1 }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
expect(errorSpy).toHaveBeenCalledWith(
"Warning: [antd: Cascader] 'limit' of showSearch should be positive number or false.",
);
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(2);
});
});
it('should warning if not find `value` in `options`', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Cascader options={[{ label: 'a', value: 'a', children: [{ label: 'b' }] }]} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] Not found `value` in `options`.',
);
errorSpy.mockRestore();
});
// FIXME: Move to `rc-tree-select` instead
// it('should warning if not find `value` in `options`', () => {
// const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// mount(<Cascader options={[{ label: 'a', value: 'a', children: [{ label: 'b' }] }]} />);
// expect(errorSpy).toHaveBeenCalledWith(
// 'Warning: [antd: Cascader] Not found `value` in `options`.',
// );
// errorSpy.mockRestore();
// });
// https://github.com/ant-design/ant-design/issues/17690
it('should not breaks when children is null', () => {
@ -388,40 +356,24 @@ describe('Cascader', () => {
}).not.toThrow();
});
// https://github.com/ant-design/ant-design/issues/18176
it('have a notFoundContent that fit trigger input width', () => {
const wrapper = mount(
<Cascader
popupVisible
options={[]}
fieldNames={{ label: 'name', value: 'code', children: 'items' }}
/>,
);
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(popupWrapper.render()).toMatchSnapshot();
});
it('placeholder works correctly', () => {
const wrapper = mount(<Cascader options={[]} />);
expect(wrapper.find('input').prop('placeholder')).toBe('Please select');
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual('');
const customPlaceholder = 'Custom placeholder';
wrapper.setProps({
placeholder: customPlaceholder,
});
expect(wrapper.find('input').prop('placeholder')).toBe(customPlaceholder);
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual(customPlaceholder);
});
it('popup correctly with defaultValue RTL', () => {
const wrapper = mount(
<ConfigProvider direction="rtl">
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} open />
</ConfigProvider>,
);
wrapper.find('Cascader').find('input').simulate('click');
expect(
render(wrapper.find('Cascader').find('Trigger').instance().getComponent()),
).toMatchSnapshot();
expect(wrapper.render()).toMatchSnapshot();
});
it('can be selected in RTL direction', () => {
@ -471,81 +423,54 @@ describe('Cascader', () => {
</ConfigProvider>,
);
wrapper.find('Cascader').find('input').simulate('click');
let popupWrapper = mount(wrapper.find('Cascader').find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(
render(wrapper.find('Cascader').find('Trigger').instance().getComponent()),
).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Cascader').find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(1)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(
render(wrapper.find('Cascader').find('Trigger').instance().getComponent()),
).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Cascader').find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(2)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
toggleOpen(wrapper);
clickOption(wrapper, 0, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
toggleOpen(wrapper);
clickOption(wrapper, 1, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
toggleOpen(wrapper);
clickOption(wrapper, 2, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('defaultValue works correctly when no match options', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['options1', 'options2']} />);
expect(wrapper.find('.ant-cascader-picker-label').text()).toBe('options1 / options2');
expect(wrapper.find('.ant-select-selection-item').text()).toEqual('options1 / options2');
});
it('can be selected when showSearch', () => {
const onChange = jest.fn();
const wrapper = mount(<Cascader options={options} onChange={onChange} showSearch />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'Zh' } });
const popupWrapper = mount(wrapper.find('Cascader').find('Trigger').instance().getComponent());
expect(popupWrapper.find('.ant-cascader-menu').length).toBe(1);
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(wrapper.find('.ant-cascader-menu').length).toBe(1);
clickOption(wrapper, 0, 0);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('options should open after press esc and then search', () => {
const wrapper = mount(<Cascader options={options} showSearch />);
wrapper.find('input').simulate('change', { target: { value: 'jin' } });
wrapper.find('input').simulate('keydown', { keyCode: KeyCode.ESC });
expect(isOpen(wrapper)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.ESC });
expect(isOpen(wrapper)).toBeFalsy();
wrapper.find('input').simulate('change', { target: { value: 'jin' } });
expect(wrapper.state('popupVisible')).toBe(true);
expect(isOpen(wrapper)).toBeTruthy();
});
it('onChange works correctly when the label of fieldNames is the same as value', () => {
const onChange = jest.fn();
const sameNames = { label: 'label', value: 'label', children: 'children' };
const sameNames = { label: 'label', value: 'label' };
const wrapper = mount(
<Cascader options={options} onChange={onChange} showSearch fieldNames={sameNames} />,
);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'est' } });
const popupWrapper = mount(wrapper.find('Cascader').find('Trigger').instance().getComponent());
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
clickOption(wrapper, 0, 0);
expect(onChange).toHaveBeenCalledWith(['Zhejiang', 'Hangzhou', 'West Lake'], expect.anything());
});
});

View File

@ -0,0 +1,67 @@
---
order: 5.1
title:
zh-CN: 多选
en-US: Multiple
---
## zh-CN
一次性选择多个选项。
## en-US
Select multiple options
```jsx
import { Cascader } from 'antd';
const options = [
{
label: 'Light',
value: 'light',
children: new Array(20)
.fill(null)
.map((_, index) => ({ label: `Number ${index}`, value: index })),
},
{
label: 'Bamboo',
value: 'bamboo',
children: [
{
label: 'Little',
value: 'little',
children: [
{
label: 'Toy Fish',
value: 'fish',
},
{
label: 'Toy Cards',
value: 'cards',
},
{
label: 'Toy Bird',
value: 'bird',
},
],
},
],
},
];
function onChange(value) {
console.log(value);
}
ReactDOM.render(
<Cascader
style={{ width: 233 }}
options={options}
onChange={onChange}
multiple
maxTagCount="responsive"
/>,
mountNode,
);
```

View File

@ -36,18 +36,18 @@ Cascade selection box.
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | function(triggerNode) | () => document.body | |
| loadData | To load option lazily, and it cannot work with `showSearch` | (selectedOptions) => void | - | |
| notFoundContent | Specify content to show when no result matches | string | `Not Found` | |
| open | Set visible of cascader popup | boolean | - | 4.17.0 |
| options | The data options of cascade | [Option](#Option)\[] | - | |
| placeholder | The input placeholder | string | `Please select` | |
| popupClassName | The additional className of popup overlay | string | - | |
| popupPlacement | Use preset popup align config from builtinPlacements`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | |
| popupVisible | Set visible of cascader popup | boolean | - | |
| showSearch | Whether show search input in single mode | boolean \| [Object](#showSearch) | false | |
| size | The input size | `large` \| `middle` \| `small` | - | |
| style | The additional style | CSSProperties | - | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| value | The selected value | string\[] \| number\[] | - | |
| onChange | Callback when finishing cascader select | (value, selectedOptions) => void | - | |
| onPopupVisibleChange | Callback when popup shown or hidden | (value) => void | - | |
| onDropdownVisibleChange | Callback when popup shown or hidden | (value) => void | - | 4.17.0 |
### showSearch

View File

@ -1,716 +1,215 @@
import * as React from 'react';
import RcCascader from 'rc-cascader';
import arrayTreeFilter from 'array-tree-filter';
import classNames from 'classnames';
import RcCascader from 'rc-cascader';
import type { CascaderProps as RcCascaderProps } from 'rc-cascader';
import type { ShowSearchType, FieldNames } from 'rc-cascader/lib/interface';
import omit from 'rc-util/lib/omit';
import KeyCode from 'rc-util/lib/KeyCode';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import DownOutlined from '@ant-design/icons/DownOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import RedoOutlined from '@ant-design/icons/RedoOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import Input from '../input';
import {
ConfigConsumer,
ConfigConsumerProps,
RenderEmptyHandler,
DirectionType,
} from '../config-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import devWarning from '../_util/devWarning';
import SizeContext, { SizeType } from '../config-provider/SizeContext';
import { replaceElement } from '../_util/reactNode';
import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import getIcons from '../select/utils/iconUtil';
import { getTransitionName } from '../_util/motion';
export interface CascaderOptionType {
value?: string | number;
label?: React.ReactNode;
disabled?: boolean;
isLeaf?: boolean;
loading?: boolean;
children?: Array<CascaderOptionType>;
[key: string]: any;
// Align the design since we use `rc-select` in root. This help:
// - List search content will show all content
// - Hover opacity style
// - Search filter match case
export type FieldNamesType = FieldNames;
export type FilledFieldNamesType = Required<FieldNamesType>;
function highlightKeyword(str: string, lowerKeyword: string, prefixCls: string | undefined) {
const cells = str
.toLowerCase()
.split(lowerKeyword)
.reduce((list, cur, index) => (index === 0 ? [cur] : [...list, lowerKeyword, cur]), []);
const fillCells: React.ReactNode[] = [];
let start = 0;
cells.forEach((cell, index) => {
const end = start + cell.length;
let originWorld: React.ReactNode = str.slice(start, end);
start = end;
if (index % 2 === 1) {
originWorld = (
<span className={`${prefixCls}-menu-item-keyword`} key="seperator">
{originWorld}
</span>
);
}
fillCells.push(originWorld);
});
return fillCells;
}
export interface FieldNamesType {
value?: string | number;
label?: string;
children?: string;
}
const defaultSearchRender: ShowSearchType['render'] = (inputValue, path, prefixCls, fieldNames) => {
const optionList: React.ReactNode[] = [];
export interface FilledFieldNamesType {
value: string | number;
label: string;
children: string;
}
// We do lower here to save perf
const lower = inputValue.toLowerCase();
export type CascaderExpandTrigger = 'click' | 'hover';
path.forEach((node, index) => {
if (index !== 0) {
optionList.push(' / ');
}
export type CascaderValueType = (string | number)[];
let label = (node as any)[fieldNames.label!];
const type = typeof label;
if (type === 'string' || type === 'number') {
label = highlightKeyword(String(label), lower, prefixCls);
}
export interface ShowSearchType {
filter?: (inputValue: string, path: CascaderOptionType[], names: FilledFieldNamesType) => boolean;
render?: (
inputValue: string,
path: CascaderOptionType[],
prefixCls: string | undefined,
names: FilledFieldNamesType,
) => React.ReactNode;
sort?: (
a: CascaderOptionType[],
b: CascaderOptionType[],
inputValue: string,
names: FilledFieldNamesType,
) => number;
matchInputWidth?: boolean;
limit?: number | false;
}
optionList.push(label);
});
return optionList;
};
export interface CascaderProps {
/** 可选项数据源 */
options: CascaderOptionType[];
/** 默认的选中项 */
defaultValue?: CascaderValueType;
/** 指定选中项 */
value?: CascaderValueType;
/** 选择完成后的回调 */
onChange?: (value: CascaderValueType, selectedOptions?: CascaderOptionType[]) => void;
/** 选择后展示的渲染函数 */
displayRender?: (label: string[], selectedOptions?: CascaderOptionType[]) => React.ReactNode;
/** 自定义样式 */
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 自定义浮层类名 */
popupClassName?: string;
/** 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` */
popupPlacement?: string;
/** 输入框占位文本 */
placeholder?: string;
/** 输入框大小,可选 `large` `default` `small` */
export interface CascaderProps extends Omit<RcCascaderProps, 'checkable'> {
multiple?: boolean;
size?: SizeType;
/** 输入框 name */
name?: string;
/** 输入框 id */
id?: string;
/** Whether has border style */
bordered?: boolean;
/** 禁用 */
disabled?: boolean;
/** 是否支持清除 */
allowClear?: boolean;
/** 自动获取焦点 */
autoFocus?: boolean;
showSearch?: boolean | ShowSearchType;
notFoundContent?: React.ReactNode;
loadData?: (selectedOptions?: CascaderOptionType[]) => void;
/** 次级菜单的展开方式,可选 'click' 和 'hover' */
expandTrigger?: CascaderExpandTrigger;
expandIcon?: React.ReactNode;
/** 当此项为 true 时,点选每级菜单选项值都会发生变化 */
changeOnSelect?: boolean;
/** 浮层可见变化时回调 */
onPopupVisibleChange?: (popupVisible: boolean) => void;
prefixCls?: string;
inputPrefixCls?: string;
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
popupVisible?: boolean;
/** Use this after antd@3.7.0 */
fieldNames?: FieldNamesType;
suffixIcon?: React.ReactNode;
dropdownRender?: (menus: React.ReactNode) => React.ReactNode;
// Miss prop defines.
autoComplete?: string;
transitionName?: string;
children?: React.ReactElement;
}
export interface CascaderState {
inputFocused: boolean;
inputValue: string;
value: CascaderValueType;
popupVisible: boolean | undefined;
flattenOptions: CascaderOptionType[][] | undefined;
prevProps: CascaderProps;
interface CascaderRef {
focus: () => void;
blur: () => void;
}
interface CascaderLocale {
placeholder?: string;
}
const Cascader = React.forwardRef((props: CascaderProps, ref: React.Ref<CascaderRef>) => {
const {
prefixCls: customizePrefixCls,
size: customizeSize,
className,
multiple,
bordered = true,
transitionName,
choiceTransitionName = '',
dropdownClassName,
expandIcon,
showSearch,
allowClear = true,
notFoundContent,
direction,
...rest
} = props;
// We limit the filtered item count by default
const defaultLimit = 50;
const restProps = omit(rest, ['suffixIcon' as any]);
// keep value when filtering
const keepFilteredValueField = '__KEEP_FILTERED_OPTION_VALUE';
const {
// getPopupContainer: getContextPopupContainer,
getPrefixCls,
renderEmpty,
direction: rootDirection,
// virtual,
// dropdownMatchSelectWidth,
} = React.useContext(ConfigContext);
function highlightKeyword(str: string, keyword: string, prefixCls: string | undefined) {
return str.split(keyword).map((node: string, index: number) =>
index === 0
? node
: [
<span className={`${prefixCls}-menu-item-keyword`} key="seperator">
{keyword}
</span>,
node,
],
);
}
const mergedDirection = direction || rootDirection;
const isRtl = mergedDirection === 'rtl';
function defaultFilterOption(
inputValue: string,
path: CascaderOptionType[],
names: FilledFieldNamesType,
) {
return path.some(option => (option[names.label] as string).indexOf(inputValue) > -1);
}
// =================== No Found ====================
const mergedNotFoundContent = notFoundContent || renderEmpty('Cascader');
function defaultRenderFilteredOption(
inputValue: string,
path: CascaderOptionType[],
prefixCls: string | undefined,
names: FilledFieldNamesType,
) {
return path.map((option, index) => {
const label = option[names.label];
const node =
(label as string).indexOf(inputValue) > -1
? highlightKeyword(label as string, inputValue, prefixCls)
: label;
return index === 0 ? node : [' / ', node];
// ==================== Prefix =====================
const rootPrefixCls = getPrefixCls();
const prefixCls = getPrefixCls('select', customizePrefixCls);
const cascaderPrefixCls = getPrefixCls('cascader', customizePrefixCls);
// =================== Dropdown ====================
const mergedDropdownClassName = classNames(dropdownClassName, `${cascaderPrefixCls}-dropdown`, {
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
});
}
function defaultSortFilteredOption(
a: CascaderOptionType[],
b: CascaderOptionType[],
inputValue: string,
names: FilledFieldNamesType,
) {
function callback(elem: CascaderOptionType) {
return (elem[names.label] as string).indexOf(inputValue) > -1;
}
return a.findIndex(callback) - b.findIndex(callback);
}
function getFieldNames({ fieldNames }: CascaderProps) {
return fieldNames;
}
function getFilledFieldNames(props: CascaderProps) {
const fieldNames = getFieldNames(props) || {};
const names: FilledFieldNamesType = {
children: fieldNames.children || 'children',
label: fieldNames.label || 'label',
value: fieldNames.value || 'value',
};
return names;
}
function flattenTree(
options: CascaderOptionType[],
props: CascaderProps,
ancestor: CascaderOptionType[] = [],
) {
const names: FilledFieldNamesType = getFilledFieldNames(props);
let flattenOptions: CascaderOptionType[][] = [];
const childrenName = names.children;
options.forEach(option => {
const path = ancestor.concat(option);
if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) {
flattenOptions.push(path);
// ==================== Search =====================
const mergedShowSearch = React.useMemo(() => {
if (!showSearch) {
return showSearch;
}
if (option[childrenName]) {
flattenOptions = flattenOptions.concat(flattenTree(option[childrenName], props, path));
}
});
return flattenOptions;
}
const defaultDisplayRender = (label: string[]) => label.join(' / ');
function warningValueNotExist(list: CascaderOptionType[], fieldNames: FieldNamesType = {}) {
(list || []).forEach(item => {
const valueFieldName = fieldNames.value || 'value';
devWarning(valueFieldName in item, 'Cascader', 'Not found `value` in `options`.');
warningValueNotExist(item[fieldNames.children || 'children'], fieldNames);
});
}
function getEmptyNode(
renderEmpty: RenderEmptyHandler,
names: FilledFieldNamesType,
notFoundContent?: React.ReactNode,
) {
return {
[names.value]: 'ANT_CASCADER_NOT_FOUND',
[names.label]: notFoundContent || renderEmpty('Cascader'),
disabled: true,
isEmptyNode: true,
};
}
class Cascader extends React.Component<CascaderProps, CascaderState> {
static defaultProps = {
options: [],
disabled: false,
allowClear: true,
bordered: true,
};
static getDerivedStateFromProps(nextProps: CascaderProps, { prevProps }: CascaderState) {
const newState: Partial<CascaderState> = {
prevProps: nextProps,
let searchConfig: ShowSearchType = {
render: defaultSearchRender,
};
if ('value' in nextProps) {
newState.value = nextProps.value || [];
}
if ('popupVisible' in nextProps) {
newState.popupVisible = nextProps.popupVisible;
}
if (nextProps.showSearch && prevProps.options !== nextProps.options) {
newState.flattenOptions = flattenTree(nextProps.options, nextProps);
if (typeof showSearch === 'object') {
searchConfig = {
...searchConfig,
...showSearch,
};
}
if (process.env.NODE_ENV !== 'production' && nextProps.options) {
warningValueNotExist(nextProps.options, getFieldNames(nextProps));
}
return searchConfig;
}, [showSearch]);
return newState;
// ===================== Size ======================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
// ===================== Icon ======================
let mergedExpandIcon = expandIcon;
if (!expandIcon) {
mergedExpandIcon = isRtl ? <LeftOutlined /> : <RightOutlined />;
}
cachedOptions: CascaderOptionType[] = [];
clearSelectionTimeout: any;
private input: Input;
constructor(props: CascaderProps) {
super(props);
this.state = {
value: props.value || props.defaultValue || [],
inputValue: '',
inputFocused: false,
popupVisible: props.popupVisible,
flattenOptions: props.showSearch ? flattenTree(props.options, props) : undefined,
prevProps: props,
};
}
componentWillUnmount() {
if (this.clearSelectionTimeout) {
clearTimeout(this.clearSelectionTimeout);
}
}
setValue = (value: CascaderValueType, selectedOptions: CascaderOptionType[] = []) => {
if (!('value' in this.props)) {
this.setState({ value });
}
const { onChange } = this.props;
onChange?.(value, selectedOptions);
};
getLabel() {
const { options, displayRender = defaultDisplayRender } = this.props;
const names = getFilledFieldNames(this.props);
const { value } = this.state;
const unwrappedValue = Array.isArray(value[0]) ? value[0] : value;
const selectedOptions: CascaderOptionType[] = arrayTreeFilter(
options,
(o: CascaderOptionType, level: number) => o[names.value] === unwrappedValue[level],
{ childrenKeyName: names.children },
);
const label = selectedOptions.length ? selectedOptions.map(o => o[names.label]) : value;
return displayRender(label, selectedOptions);
}
saveInput = (node: Input) => {
this.input = node;
};
handleChange = (value: any, selectedOptions: CascaderOptionType[]) => {
this.setState({ inputValue: '' });
if (selectedOptions[0].__IS_FILTERED_OPTION) {
const unwrappedValue =
selectedOptions[0][keepFilteredValueField] === undefined
? value[0]
: selectedOptions[0][keepFilteredValueField];
const unwrappedSelectedOptions = selectedOptions[0].path;
this.setValue(unwrappedValue, unwrappedSelectedOptions);
return;
}
this.setValue(value, selectedOptions);
};
handlePopupVisibleChange = (popupVisible: boolean) => {
if (!('popupVisible' in this.props)) {
this.setState(state => ({
popupVisible,
inputFocused: popupVisible,
inputValue: popupVisible ? state.inputValue : '',
}));
}
const { onPopupVisibleChange } = this.props;
onPopupVisibleChange?.(popupVisible);
};
handleInputBlur = () => {
this.setState({
inputFocused: false,
});
};
handleInputClick = (e: React.MouseEvent<HTMLInputElement>) => {
const { inputFocused, popupVisible } = this.state;
// Prevent `Trigger` behaviour.
if (inputFocused || popupVisible) {
e.stopPropagation();
}
};
handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
// SPACE => https://github.com/ant-design/ant-design/issues/16871
if (e.keyCode === KeyCode.BACKSPACE || e.keyCode === KeyCode.SPACE) {
e.stopPropagation();
}
};
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { popupVisible } = this.state;
const inputValue = e.target.value;
if (!popupVisible) {
this.handlePopupVisibleChange(true);
}
this.setState({ inputValue });
};
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
const { inputValue } = this.state;
e.preventDefault();
e.stopPropagation();
if (!inputValue) {
this.handlePopupVisibleChange(false);
this.clearSelectionTimeout = setTimeout(() => {
this.setValue([]);
}, 200);
} else {
this.setState({ inputValue: '' });
}
};
generateFilteredOptions(prefixCls: string | undefined, renderEmpty: RenderEmptyHandler) {
const { showSearch, notFoundContent } = this.props;
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
const {
filter = defaultFilterOption,
render = defaultRenderFilteredOption,
sort = defaultSortFilteredOption,
limit = defaultLimit,
} = showSearch as ShowSearchType;
const { flattenOptions = [], inputValue } = this.state;
// Limit the filter if needed
let filtered: Array<CascaderOptionType[]>;
if (limit > 0) {
filtered = [];
let matchCount = 0;
// Perf optimization to filter items only below the limit
flattenOptions.some(path => {
const match = filter(this.state.inputValue, path, names);
if (match) {
filtered.push(path);
matchCount += 1;
}
return matchCount >= limit;
});
} else {
devWarning(
typeof limit !== 'number',
'Cascader',
"'limit' of showSearch should be positive number or false.",
);
filtered = flattenOptions.filter(path => filter(this.state.inputValue, path, names));
}
filtered = filtered.sort((a, b) => sort(a, b, inputValue, names));
if (filtered.length > 0) {
// Fix issue: https://github.com/ant-design/ant-design/issues/26554
const field = names.value === names.label ? keepFilteredValueField : names.value;
return filtered.map(
(path: CascaderOptionType[]) =>
({
__IS_FILTERED_OPTION: true,
path,
[field]: path.map((o: CascaderOptionType) => o[names.value]),
[names.label]: render(inputValue, path, prefixCls, names),
disabled: path.some((o: CascaderOptionType) => !!o.disabled),
isEmptyNode: true,
} as CascaderOptionType),
);
}
return [getEmptyNode(renderEmpty, names, notFoundContent)];
}
focus() {
this.input.focus();
}
blur() {
this.input.blur();
}
getPopupPlacement(direction: DirectionType = 'ltr') {
const { popupPlacement } = this.props;
if (popupPlacement !== undefined) {
return popupPlacement;
}
return direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
}
renderCascader = (
{
getPopupContainer: getContextPopupContainer,
getPrefixCls,
renderEmpty,
direction,
}: ConfigConsumerProps,
locale: CascaderLocale,
) => (
<SizeContext.Consumer>
{size => {
const { props, state } = this;
const {
prefixCls: customizePrefixCls,
inputPrefixCls: customizeInputPrefixCls,
children,
placeholder = locale.placeholder || 'Please select',
size: customizeSize,
disabled,
className,
style,
allowClear,
showSearch = false,
suffixIcon,
expandIcon,
notFoundContent,
popupClassName,
bordered,
dropdownRender,
...otherProps
} = props;
const mergedSize = customizeSize || size;
const { value, inputFocused } = state;
const isRtlLayout = direction === 'rtl';
const prefixCls = getPrefixCls('cascader', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const sizeCls = classNames({
[`${inputPrefixCls}-lg`]: mergedSize === 'large',
[`${inputPrefixCls}-sm`]: mergedSize === 'small',
});
const clearIcon =
(allowClear && !disabled && value.length > 0) || state.inputValue ? (
<CloseCircleFilled
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
/>
) : null;
const arrowCls = classNames({
[`${prefixCls}-picker-arrow`]: true,
[`${prefixCls}-picker-arrow-expand`]: state.popupVisible,
});
const pickerCls = classNames(
`${prefixCls}-picker`,
{
[`${prefixCls}-picker-rtl`]: isRtlLayout,
[`${prefixCls}-picker-with-value`]: state.inputValue,
[`${prefixCls}-picker-disabled`]: disabled,
[`${prefixCls}-picker-${mergedSize}`]: !!mergedSize,
[`${prefixCls}-picker-show-search`]: !!showSearch,
[`${prefixCls}-picker-focused`]: inputFocused,
[`${prefixCls}-picker-borderless`]: !bordered,
},
className,
);
// Fix bug of https://github.com/facebook/react/pull/5004
// and https://fb.me/react-unknown-prop
const inputProps = omit(
// Not know why these props left
otherProps as typeof otherProps & {
filterOption: any;
renderFilteredOption: any;
sortFilteredOption: any;
defaultValue: any;
},
[
'onChange',
'options',
'popupPlacement',
'transitionName',
'displayRender',
'onPopupVisibleChange',
'changeOnSelect',
'expandTrigger',
'popupVisible',
'getPopupContainer',
'loadData',
'filterOption',
'renderFilteredOption',
'sortFilteredOption',
'fieldNames',
],
);
let { options } = props;
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
if (options && options.length > 0) {
if (state.inputValue) {
options = this.generateFilteredOptions(prefixCls, renderEmpty);
}
} else {
options = [getEmptyNode(renderEmpty, names, notFoundContent)];
}
// Dropdown menu should keep previous status until it is fully closed.
if (!state.popupVisible) {
options = this.cachedOptions;
} else {
this.cachedOptions = options;
}
const dropdownMenuColumnStyle: { width?: number; height?: string } = {};
const isNotFound = (options || []).length === 1 && options[0].isEmptyNode;
if (isNotFound) {
dropdownMenuColumnStyle.height = 'auto'; // Height of one row.
}
// The default value of `matchInputWidth` is `true`
const resultListMatchInputWidth = (showSearch as ShowSearchType).matchInputWidth !== false;
if (resultListMatchInputWidth && (state.inputValue || isNotFound) && this.input) {
dropdownMenuColumnStyle.width = this.input.input.offsetWidth;
}
let inputIcon: React.ReactNode;
if (suffixIcon) {
inputIcon = replaceElement(
suffixIcon,
<span className={`${prefixCls}-picker-arrow`}>{suffixIcon}</span>,
() => ({
className: classNames({
[(suffixIcon as any).props.className!]: (suffixIcon as any).props.className,
[`${prefixCls}-picker-arrow`]: true,
}),
}),
);
} else {
inputIcon = <DownOutlined className={arrowCls} />;
}
const label = this.getLabel();
const input: React.ReactElement = children || (
<span style={style} className={pickerCls}>
<span
className={`${prefixCls}-picker-label`}
title={typeof label === 'string' && label ? label : undefined}
>
{label}
</span>
<Input
{...inputProps}
tabIndex={-1}
ref={this.saveInput}
prefixCls={inputPrefixCls}
placeholder={value && value.length > 0 ? undefined : placeholder}
className={`${prefixCls}-input ${sizeCls}`}
value={state.inputValue}
disabled={disabled}
readOnly={!showSearch}
autoComplete={inputProps.autoComplete || 'off'}
onClick={showSearch ? this.handleInputClick : undefined}
onBlur={showSearch ? this.handleInputBlur : undefined}
onKeyDown={this.handleKeyDown}
onChange={showSearch ? this.handleInputChange : undefined}
/>
{clearIcon}
{inputIcon}
</span>
);
let expandIconNode;
if (expandIcon) {
expandIconNode = expandIcon;
} else {
expandIconNode = isRtlLayout ? <LeftOutlined /> : <RightOutlined />;
}
const loadingIcon = (
<span className={`${prefixCls}-menu-item-loading-icon`}>
<RedoOutlined spin />
</span>
);
const getPopupContainer = props.getPopupContainer || getContextPopupContainer;
const rest = omit(props as typeof props & { inputIcon: any; loadingIcon: any }, [
'inputIcon',
'expandIcon',
'loadingIcon',
'bordered',
'className',
]);
const rcCascaderPopupClassName = classNames(popupClassName, {
[`${prefixCls}-menu-${direction}`]: direction === 'rtl',
[`${prefixCls}-menu-empty`]:
options.length === 1 && options[0].value === 'ANT_CASCADER_NOT_FOUND',
});
const rootPrefixCls = getPrefixCls();
return (
<RcCascader
{...rest}
prefixCls={prefixCls}
getPopupContainer={getPopupContainer}
options={options}
value={value}
popupVisible={state.popupVisible}
onPopupVisibleChange={this.handlePopupVisibleChange}
onChange={this.handleChange}
dropdownMenuColumnStyle={dropdownMenuColumnStyle}
expandIcon={expandIconNode}
loadingIcon={loadingIcon}
popupClassName={rcCascaderPopupClassName}
popupPlacement={this.getPopupPlacement(direction)}
// rc-cascader should update ts define to fix this case
dropdownRender={dropdownRender as any}
transitionName={getTransitionName(rootPrefixCls, 'slide-up', props.transitionName)}
>
{input}
</RcCascader>
);
}}
</SizeContext.Consumer>
const loadingIcon = (
<span className={`${prefixCls}-menu-item-loading-icon`}>
<RedoOutlined spin />
</span>
);
render() {
return (
<ConfigConsumer>
{(configArgument: ConfigConsumerProps) => (
<LocaleReceiver>{locale => this.renderCascader(configArgument, locale)}</LocaleReceiver>
)}
</ConfigConsumer>
);
}
}
// =================== Multiple ====================
const checkable = React.useMemo(
() => (multiple ? <span className={`${cascaderPrefixCls}-checkbox-inner`} /> : false),
[multiple],
);
// ===================== Icons =====================
const { suffixIcon, removeIcon, clearIcon } = getIcons({
...props,
multiple,
prefixCls,
});
// ==================== Render =====================
return (
<RcCascader
prefixCls={prefixCls}
className={classNames(
!customizePrefixCls && cascaderPrefixCls,
{
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-rtl`]: isRtl,
[`${prefixCls}-borderless`]: !bordered,
},
className,
)}
{...(restProps as any)}
direction={mergedDirection}
notFoundContent={mergedNotFoundContent}
allowClear={allowClear}
showSearch={mergedShowSearch}
expandIcon={mergedExpandIcon}
inputIcon={suffixIcon}
removeIcon={removeIcon}
clearIcon={clearIcon}
loadingIcon={loadingIcon}
checkable={checkable}
dropdownClassName={mergedDropdownClassName}
dropdownPrefixCls={customizePrefixCls || cascaderPrefixCls}
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
ref={ref}
/>
);
});
Cascader.displayName = 'Cascader';
export default Cascader;

View File

@ -37,18 +37,18 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | function(triggerNode) | () => document.body | |
| loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | (selectedOptions) => void | - | |
| notFoundContent | 当下拉列表为空时显示的内容 | string | `Not Found` | |
| open | 控制浮层显隐 | boolean | - | 4.17.0 |
| options | 可选项数据源 | [Option](#Option)\[] | - | |
| placeholder | 输入框占位文本 | string | `请选择` | |
| popupClassName | 自定义浮层类名 | string | - | |
| popupPlacement | 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | |
| popupVisible | 控制浮层显隐 | boolean | - | |
| showSearch | 在选择框中显示搜索框 | boolean \| [Object](#showSearch) | false | |
| size | 输入框大小 | `large` \| `middle` \| `small` | - | |
| style | 自定义样式 | CSSProperties | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| value | 指定选中项 | string\[] \| number\[] | - | |
| onChange | 选择完成后的回调 | (value, selectedOptions) => void | - | |
| onPopupVisibleChange | 显示/隐藏浮层的回调 | (value) => void | - | |
| onDropdownVisibleChange | 显示/隐藏浮层的回调 | (value) => void | - | 4.17.0 |
### showSearch

View File

@ -1,172 +1,38 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import '../../checkbox/style/mixin';
@cascader-prefix-cls: ~'@{ant-prefix}-cascader';
.antCheckboxFn(@checkbox-prefix-cls: ~'@{cascader-prefix-cls}-checkbox');
.@{cascader-prefix-cls} {
.reset-component();
width: 184px;
&-input.@{ant-prefix}-input {
// Keep it static for https://github.com/ant-design/ant-design/issues/16738
position: static;
width: 100%;
// https://github.com/ant-design/ant-design/issues/17582
padding-right: 24px;
// Add important to fix https://github.com/ant-design/ant-design/issues/5078
// because input.less will compile after cascader.less
background-color: transparent !important;
cursor: pointer;
}
&-picker-show-search &-input.@{ant-prefix}-input {
position: relative;
}
&-picker {
.reset-component();
position: relative;
display: inline-block;
background-color: @cascader-bg;
border-radius: @border-radius-base;
outline: 0;
cursor: pointer;
transition: color 0.3s;
&-with-value &-label {
color: transparent;
}
&-disabled {
color: @disabled-color;
background: @input-disabled-bg;
cursor: not-allowed;
.@{cascader-prefix-cls}-input {
cursor: not-allowed;
}
}
&:focus .@{cascader-prefix-cls}-input {
.active();
}
&-borderless .@{cascader-prefix-cls}-input {
border-color: transparent !important;
box-shadow: none !important;
}
&-show-search&-focused {
color: @disabled-color;
}
&-label {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 20px;
margin-top: -10px;
padding: 0 20px 0 @control-padding-horizontal;
overflow: hidden;
line-height: 20px;
white-space: nowrap;
text-overflow: ellipsis;
}
&-clear {
position: absolute;
top: 50%;
right: @control-padding-horizontal;
z-index: 2;
width: 12px;
height: 12px;
margin-top: -6px;
color: @disabled-color;
font-size: @font-size-sm;
line-height: 12px;
background: @component-background;
cursor: pointer;
opacity: 0;
transition: color 0.3s ease, opacity 0.15s ease;
&:hover {
color: @text-color-secondary;
}
}
&:hover &-clear {
opacity: 1;
}
// arrow
&-arrow {
position: absolute;
top: 50%;
right: @control-padding-horizontal;
z-index: 1;
width: 12px;
height: 12px;
margin-top: -6px;
color: @disabled-color;
font-size: 12px;
line-height: 12px;
}
}
// https://github.com/ant-design/ant-design/pull/12407#issuecomment-424657810
&-picker-label:hover + &-input {
&:not(.@{cascader-prefix-cls}-picker-disabled &) {
.hover();
}
}
&-picker-small &-picker-clear,
&-picker-small &-picker-arrow {
right: @control-padding-horizontal-sm;
&-checkbox {
top: 0;
margin-right: @padding-xs;
}
&-menus {
position: absolute;
z-index: @zindex-dropdown;
font-size: @cascader-dropdown-font-size;
white-space: nowrap;
background: @cascader-menu-bg;
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
ul,
ol {
margin: 0;
list-style: none;
}
&-empty,
&-hidden {
display: none;
}
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
animation-name: antSlideUpIn;
}
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
animation-name: antSlideDownIn;
}
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft {
animation-name: antSlideUpOut;
}
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft {
animation-name: antSlideDownOut;
&.@{cascader-prefix-cls}-menu-empty {
.@{cascader-prefix-cls}-menu {
width: 100%;
height: auto;
}
}
}
&-menu {
display: inline-block;
min-width: 111px;
height: 180px;
margin: 0;
margin: -@dropdown-edge-child-vertical-padding 0;
padding: @cascader-dropdown-edge-child-vertical-padding 0;
overflow: auto;
vertical-align: top;
@ -174,67 +40,62 @@
border-right: @border-width-base @border-style-base @cascader-menu-border-color-split;
-ms-overflow-style: -ms-autohiding-scrollbar; // https://github.com/ant-design/ant-design/issues/11857
&:first-child {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
&:last-child {
margin-right: -1px;
border-right-color: transparent;
border-radius: 0 @border-radius-base @border-radius-base 0;
}
&:only-child {
border-radius: @border-radius-base;
}
}
&-menu-item {
padding: @cascader-dropdown-vertical-padding @control-padding-horizontal;
overflow: hidden;
line-height: @cascader-dropdown-line-height;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: @item-hover-bg;
}
&-disabled {
color: @disabled-color;
cursor: not-allowed;
&:hover {
background: transparent;
}
}
.@{cascader-prefix-cls}-menu-empty & {
color: @disabled-color;
cursor: default;
pointer-events: none;
}
&-active:not(&-disabled) {
&,
&:hover {
font-weight: @select-item-selected-font-weight;
background-color: @cascader-item-selected-bg;
}
}
&-expand {
position: relative;
padding-right: 24px;
}
&-item {
display: flex;
flex-wrap: nowrap;
align-items: center;
padding: @cascader-dropdown-vertical-padding @control-padding-horizontal;
overflow: hidden;
line-height: @cascader-dropdown-line-height;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
transition: all 0.3s;
&-expand &-expand-icon,
&-loading-icon {
position: absolute;
right: @control-padding-horizontal;
color: @text-color-secondary;
font-size: 10px;
&:hover {
background: @item-hover-bg;
}
.@{cascader-prefix-cls}-menu-item-disabled& {
&-disabled {
color: @disabled-color;
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
& &-keyword {
color: @highlight-color;
.@{cascader-prefix-cls}-menu-empty & {
color: @disabled-color;
cursor: default;
pointer-events: none;
}
&-active:not(&-disabled) {
&,
&:hover {
font-weight: @select-item-selected-font-weight;
background-color: @cascader-item-selected-bg;
}
}
&-content {
flex: auto;
}
&-expand &-expand-icon,
&-loading-icon {
margin-left: @padding-xss;
color: @text-color-secondary;
font-size: 10px;
.@{cascader-prefix-cls}-menu-item-disabled& {
color: @disabled-color;
}
}
&-keyword {
color: @highlight-color;
}
}
}
}

View File

@ -3,4 +3,4 @@ import './index.less';
// style dependencies
import '../../empty/style';
import '../../input/style';
import '../../select/style';

View File

@ -1,95 +1,17 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import (reference) './index';
@cascader-prefix-cls: ~'@{ant-prefix}-cascader';
@picker-rtl-cls: ~'@{cascader-prefix-cls}-picker-rtl';
@menu-rtl-cls: ~'@{cascader-prefix-cls}-menu-rtl';
.@{cascader-prefix-cls} {
&-input.@{ant-prefix}-input {
.@{picker-rtl-cls} & {
padding-right: @input-padding-horizontal-base;
padding-left: 24px;
text-align: right;
}
}
&-picker {
&-rtl {
direction: rtl;
}
&-label {
.@{picker-rtl-cls} & {
padding: 0 @control-padding-horizontal 0 20px;
text-align: right;
}
}
&-clear {
.@{picker-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
&-arrow {
.@{picker-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
}
}
&-picker-small &-picker-clear,
&-picker-small &-picker-arrow {
.@{picker-rtl-cls}& {
right: auto;
left: @control-padding-horizontal-sm;
}
}
&-menu {
&-rtl & {
direction: rtl;
border-right: none;
border-left: @border-width-base @border-style-base @border-color-split;
&:first-child {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
&:last-child {
margin-right: 0;
margin-left: -1px;
border-left-color: transparent;
border-radius: @border-radius-base 0 0 @border-radius-base;
}
&:only-child {
border-radius: @border-radius-base;
}
}
}
&-menu-item {
&-expand {
.@{menu-rtl-cls} & {
padding-right: @control-padding-horizontal;
padding-left: 24px;
}
}
&-expand &-expand-icon,
.@{cascader-prefix-cls}-rtl {
.@{cascader-prefix-cls}-menu-item {
&-expand-icon,
&-loading-icon {
.@{menu-rtl-cls} & {
right: auto;
left: @control-padding-horizontal;
}
margin-right: @padding-xss;
margin-left: 0;
}
}
&-loading-icon {
.@{menu-rtl-cls} & {
transform: scaleY(-1);
}
}
.@{cascader-prefix-cls}-checkbox {
top: 0;
margin-right: 0;
margin-left: @padding-xs;
}
}

View File

@ -9985,231 +9985,339 @@ exports[`ConfigProvider components Carousel prefixCls 1`] = `
`;
exports[`ConfigProvider components Cascader configProvider 1`] = `
<span
class="config-cascader-picker config-cascader-picker-show-search"
tabindex="0"
<div
class="config-select config-cascader config-select-single config-select-allow-clear config-select-show-arrow config-select-show-search"
>
<span
class="config-cascader-picker-label"
/>
<input
autocomplete="off"
class="config-input config-cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down config-cascader-picker-arrow"
role="img"
<div
class="config-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="config-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="config-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="config-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="config-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down config-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Cascader configProvider componentSize large 1`] = `
<span
class="config-cascader-picker config-cascader-picker-large config-cascader-picker-show-search"
tabindex="0"
<div
class="config-select config-cascader config-select-lg config-select-single config-select-allow-clear config-select-show-arrow config-select-show-search"
>
<span
class="config-cascader-picker-label"
/>
<input
autocomplete="off"
class="config-input config-input-lg config-cascader-input config-input-lg"
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down config-cascader-picker-arrow"
role="img"
<div
class="config-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="config-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="config-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="config-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="config-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down config-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Cascader configProvider componentSize middle 1`] = `
<span
class="config-cascader-picker config-cascader-picker-middle config-cascader-picker-show-search"
tabindex="0"
<div
class="config-select config-cascader config-select-single config-select-allow-clear config-select-show-arrow config-select-show-search"
>
<span
class="config-cascader-picker-label"
/>
<input
autocomplete="off"
class="config-input config-cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down config-cascader-picker-arrow"
role="img"
<div
class="config-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="config-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="config-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="config-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="config-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down config-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Cascader configProvider virtual and dropdownMatchSelectWidth 1`] = `
<span
class="ant-cascader-picker ant-cascader-picker-show-search"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Cascader normal 1`] = `
<span
class="ant-cascader-picker ant-cascader-picker-show-search"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Cascader prefixCls 1`] = `
<span
class="prefix-Cascader-picker prefix-Cascader-picker-show-search"
tabindex="0"
<div
class="prefix-Cascader prefix-Cascader-single prefix-Cascader-allow-clear prefix-Cascader-show-arrow prefix-Cascader-show-search"
>
<span
class="prefix-Cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input prefix-Cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down prefix-Cascader-picker-arrow"
role="img"
<div
class="prefix-Cascader-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="prefix-Cascader-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="prefix-Cascader-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="prefix-Cascader-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="prefix-Cascader-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down prefix-Cascader-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
`;
exports[`ConfigProvider components Checkbox configProvider 1`] = `

View File

@ -222,42 +222,60 @@ exports[`renders ./components/empty/demo/config-provider.md correctly 1`] = `
<h4>
Cascader
</h4>
<span
class="ant-cascader-picker ant-cascader-picker-show-search"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
style="width:200px"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
<h4>
Transfer
</h4>

View File

@ -3430,65 +3430,92 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
<div
class="ant-form-item-control-input-content"
>
<span
class="ant-cascader-picker"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<span
class="ant-cascader-picker-label"
title="Zhejiang / Hangzhou / West Lake"
<div
class="ant-select-selector"
>
Zhejiang / Hangzhou / West Lake
</span>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
id="register_residence"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-cascader-picker-clear"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
<input
aria-activedescendant="register_residence_list_0"
aria-autocomplete="list"
aria-controls="register_residence_list"
aria-haspopup="listbox"
aria-owns="register_residence_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_residence"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-item"
title="Zhejiang / Hangzhou / West Lake"
>
Zhejiang / Hangzhou / West Lake
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
aria-hidden="true"
class="ant-select-clear"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -4596,42 +4623,62 @@ exports[`renders ./components/form/demo/size.md correctly 1`] = `
<div
class="ant-form-item-control-input-content"
>
<span
class="ant-cascader-picker ant-cascader-picker-default"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -7396,42 +7443,62 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
<div
class="ant-form-item-control-input-content"
>
<span
class="ant-cascader-picker"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
<span
class="ant-form-item-children-icon"

View File

@ -436,43 +436,65 @@ exports[`renders ./components/input-number/demo/addon.md correctly 1`] = `
<div
class="ant-input-number-group-addon"
>
<span
class="ant-cascader-picker"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
style="width:150px"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="cascader"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
>
cascader
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
<div
class="ant-input-number"

View File

@ -261,43 +261,65 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
<span
class="ant-input-group-addon"
>
<span
class="ant-cascader-picker"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
style="width:150px"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="cascader"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
>
cascader
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
</span>
<input
class="ant-input"
@ -690,64 +712,91 @@ Array [
</span>
</span>
</div>,
<span
class="ant-cascader-picker"
tabindex="0"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<span
class="ant-cascader-picker-label"
title="Zhejiang / Hangzhou / West Lake"
<div
class="ant-select-selector"
>
Zhejiang / Hangzhou / West Lake
</span>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-cascader-picker-clear"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-item"
title="Zhejiang / Hangzhou / West Lake"
>
Zhejiang / Hangzhou / West Lake
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
aria-hidden="true"
class="ant-select-clear"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
aria-label="close-circle"
class="anticon anticon-close-circle"
role="img"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
<svg
aria-hidden="true"
data-icon="close-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</span>,
</div>,
<div
class="ant-picker ant-picker-range"
>
@ -2390,43 +2439,65 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
</span>
</span>
</div>
<span
class="ant-cascader-picker"
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
style="width:70%"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Select Address"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
<div
class="ant-select-selector"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
<span
class="ant-select-selection-search"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</svg>
</span>
<span
class="ant-select-selection-placeholder"
>
Select Address
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</span>
</div>
</span>
</div>
`;

View File

@ -117,7 +117,7 @@
"copy-to-clipboard": "^3.2.0",
"lodash": "^4.17.21",
"moment": "^2.25.3",
"rc-cascader": "~1.4.0",
"rc-cascader": "~2.0.0-alpha.17",
"rc-checkbox": "~2.3.0",
"rc-collapse": "~3.1.0",
"rc-dialog": "~8.6.0",

View File

@ -2,6 +2,7 @@ import glob from 'glob';
import { render } from 'enzyme';
import MockDate from 'mockdate';
import moment from 'moment';
import { excludeWarning } from './excludeWarning';
type CheerIO = ReturnType<typeof render>;
type CheerIOElement = CheerIO[0];
@ -57,6 +58,8 @@ export default function demoTest(component: string, options: Options = {}) {
testMethod = test.skip;
}
testMethod(`renders ${file} correctly`, () => {
const errSpy = excludeWarning();
MockDate.set(moment('2016-11-22').valueOf());
const demo = require(`../.${file}`).default; // eslint-disable-line global-require, import/no-dynamic-require
const wrapper = render(demo);
@ -66,6 +69,8 @@ export default function demoTest(component: string, options: Options = {}) {
expect(wrapper).toMatchSnapshot();
MockDate.reset();
errSpy();
});
});
}

View File

@ -0,0 +1,27 @@
const originError = console.error;
/** This function will remove `useLayoutEffect` server side warning. Since it's useless. */
export function excludeWarning() {
const errorSpy = jest.spyOn(console, 'error').mockImplementation((msg, ...rest) => {
if (String(msg).includes('useLayoutEffect does nothing on the server')) {
return;
}
originError(msg, ...rest);
});
return () => {
errorSpy.mockRestore();
};
}
export default function excludeAllWarning() {
let cleanUp: Function;
beforeAll(() => {
cleanUp = excludeWarning();
});
afterAll(() => {
cleanUp();
});
}