diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 7e9c8c5d8..b69c9513c 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -1,10 +1,5 @@ -import type { - ShowSearchType, - FieldNames, - BaseOptionType, - DefaultOptionType, -} from '../vc-cascader2'; -import VcCascader, { cascaderProps as vcCascaderProps } from '../vc-cascader2'; +import type { ShowSearchType, FieldNames, BaseOptionType, DefaultOptionType } from '../vc-cascader'; +import VcCascader, { cascaderProps as vcCascaderProps } from '../vc-cascader'; import RightOutlined from '@ant-design/icons-vue/RightOutlined'; import RedoOutlined from '@ant-design/icons-vue/RedoOutlined'; import LeftOutlined from '@ant-design/icons-vue/LeftOutlined'; @@ -22,7 +17,7 @@ import type { SizeType } from '../config-provider'; import devWarning from '../vc-util/devWarning'; import { getTransitionName } from '../_util/transition'; import { useInjectFormItemContext } from '../form'; -import type { ValueType } from '../vc-cascader2/Cascader'; +import type { ValueType } from '../vc-cascader/Cascader'; // Align the design since we use `rc-select` in root. This help: // - List search content will show all content diff --git a/components/cascader/index1.tsx b/components/cascader/index1.tsx deleted file mode 100644 index d0c2983ff..000000000 --- a/components/cascader/index1.tsx +++ /dev/null @@ -1,647 +0,0 @@ -import type { PropType, CSSProperties, ExtractPropTypes } from 'vue'; -import { inject, provide, defineComponent } from 'vue'; -import PropTypes from '../_util/vue-types'; -import VcCascader from '../vc-cascader'; -import arrayTreeFilter from 'array-tree-filter'; -import classNames from '../_util/classNames'; -import KeyCode from '../_util/KeyCode'; -import Input from '../input'; -import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; -import DownOutlined from '@ant-design/icons-vue/DownOutlined'; -import RightOutlined from '@ant-design/icons-vue/RightOutlined'; -import RedoOutlined from '@ant-design/icons-vue/RedoOutlined'; -import { - hasProp, - getOptionProps, - isValidElement, - getComponent, - splitAttrs, - findDOMNode, - getSlot, -} from '../_util/props-util'; -import BaseMixin from '../_util/BaseMixin'; -import { cloneElement } from '../_util/vnode'; -import warning from '../_util/warning'; -import { defaultConfigProvider } from '../config-provider'; -import type { VueNode } from '../_util/type'; -import { tuple, withInstall } from '../_util/type'; -import type { RenderEmptyHandler } from '../config-provider/renderEmpty'; -import { useInjectFormItemContext } from '../form/FormItemContext'; -import omit from '../_util/omit'; -import { getTransitionName } from '../_util/transition'; - -export interface CascaderOptionType { - value?: string | number; - label?: VueNode; - disabled?: boolean; - isLeaf?: boolean; - loading?: boolean; - children?: CascaderOptionType[]; - [key: string]: any; -} - -export interface FieldNamesType { - value?: string; - label?: string; - children?: string; -} - -export interface FilledFieldNamesType { - value: string; - label: string; - children: string; -} - -// const CascaderOptionType = PropTypes.shape({ -// value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -// label: PropTypes.any, -// disabled: PropTypes.looseBool, -// children: PropTypes.array, -// key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -// }).loose; - -// const FieldNamesType = PropTypes.shape({ -// value: PropTypes.string.isRequired, -// label: PropTypes.string.isRequired, -// children: PropTypes.string, -// }).loose; - -export interface ShowSearchType { - filter?: (inputValue: string, path: CascaderOptionType[], names: FilledFieldNamesType) => boolean; - render?: ( - inputValue: string, - path: CascaderOptionType[], - prefixCls: string | undefined, - names: FilledFieldNamesType, - ) => VueNode; - sort?: ( - a: CascaderOptionType[], - b: CascaderOptionType[], - inputValue: string, - names: FilledFieldNamesType, - ) => number; - matchInputWidth?: boolean; - limit?: number | false; -} - -export interface EmptyFilteredOptionsType { - disabled: boolean; - [key: string]: any; -} - -export interface FilteredOptionsType extends EmptyFilteredOptionsType { - __IS_FILTERED_OPTION: boolean; - path: CascaderOptionType[]; -} - -// const ShowSearchType = PropTypes.shape({ -// filter: PropTypes.func, -// render: PropTypes.func, -// sort: PropTypes.func, -// matchInputWidth: PropTypes.looseBool, -// limit: withUndefined(PropTypes.oneOfType([Boolean, Number])), -// }).loose; -function noop() {} - -const cascaderProps = { - /** 可选项数据源 */ - options: { type: Array as PropType, default: [] }, - /** 默认的选中项 */ - defaultValue: PropTypes.array, - /** 指定选中项 */ - value: PropTypes.array, - /** 选择完成后的回调 */ - // onChange?: (value: string[], selectedOptions?: CascaderOptionType[]) => void; - /** 选择后展示的渲染函数 */ - displayRender: PropTypes.func, - transitionName: PropTypes.string, - popupStyle: PropTypes.object.def(() => ({})), - /** 自定义浮层类名 */ - popupClassName: PropTypes.string, - /** 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` */ - popupPlacement: PropTypes.oneOf(tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight')).def( - 'bottomLeft', - ), - /** 输入框占位文本*/ - placeholder: PropTypes.string.def('Please select'), - /** 输入框大小,可选 `large` `default` `small` */ - size: PropTypes.oneOf(tuple('large', 'default', 'small')), - /** 禁用*/ - disabled: PropTypes.looseBool.def(false), - /** 是否支持清除*/ - allowClear: PropTypes.looseBool.def(true), - showSearch: { - type: [Boolean, Object] as PropType, - default: undefined as PropType, - }, - notFoundContent: PropTypes.any, - loadData: PropTypes.func, - /** 次级菜单的展开方式,可选 'click' 和 'hover' */ - expandTrigger: PropTypes.oneOf(tuple('click', 'hover')), - /** 当此项为 true 时,点选每级菜单选项值都会发生变化 */ - changeOnSelect: PropTypes.looseBool, - /** 浮层可见变化时回调 */ - // onPopupVisibleChange?: (popupVisible: boolean) => void; - prefixCls: PropTypes.string, - inputPrefixCls: PropTypes.string, - getPopupContainer: PropTypes.func, - popupVisible: PropTypes.looseBool, - fieldNames: { type: Object as PropType }, - autofocus: PropTypes.looseBool, - suffixIcon: PropTypes.any, - showSearchRender: PropTypes.any, - onChange: PropTypes.func, - onPopupVisibleChange: PropTypes.func, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - onSearch: PropTypes.func, - 'onUpdate:value': PropTypes.func, -}; - -export type CascaderProps = Partial>; - -// We limit the filtered item count by default -const defaultLimit = 50; - -function defaultFilterOption( - inputValue: string, - path: CascaderOptionType[], - names: FilledFieldNamesType, -) { - return path.some(option => option[names.label].indexOf(inputValue) > -1); -} - -function defaultSortFilteredOption( - a: CascaderOptionType[], - b: CascaderOptionType[], - inputValue: string, - names: FilledFieldNamesType, -) { - function callback(elem: CascaderOptionType) { - return elem[names.label].indexOf(inputValue) > -1; - } - - return a.findIndex(callback) - b.findIndex(callback); -} - -function getFilledFieldNames(props: any) { - const fieldNames = (props.fieldNames || {}) as FieldNamesType; - const names: FilledFieldNamesType = { - children: fieldNames.children || 'children', - label: fieldNames.label || 'label', - value: fieldNames.value || 'value', - }; - return names; -} - -function flattenTree( - options: CascaderOptionType[], - props: any, - ancestor: CascaderOptionType[] = [], -) { - const names: FilledFieldNamesType = getFilledFieldNames(props); - let flattenOptions = []; - const childrenName = names.children; - options.forEach(option => { - const path = ancestor.concat(option); - if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) { - flattenOptions.push(path); - } - if (option[childrenName]) { - flattenOptions = flattenOptions.concat(flattenTree(option[childrenName], props, path)); - } - }); - return flattenOptions; -} - -const defaultDisplayRender = ({ labels }) => labels.join(' / '); - -const Cascader = defineComponent({ - name: 'ACascader', - mixins: [BaseMixin], - inheritAttrs: false, - props: cascaderProps, - setup() { - const formItemContext = useInjectFormItemContext(); - return { - configProvider: inject('configProvider', defaultConfigProvider), - localeData: inject('localeData', {} as any), - cachedOptions: [], - popupRef: undefined, - input: undefined, - formItemContext, - }; - }, - data() { - const { value, defaultValue, popupVisible, showSearch, options } = this.$props; - return { - sValue: (value || defaultValue || []) as any[], - inputValue: '', - inputFocused: false, - sPopupVisible: popupVisible as boolean, - flattenOptions: showSearch - ? flattenTree(options as CascaderOptionType[], this.$props) - : undefined, - }; - }, - watch: { - value(val) { - this.setState({ sValue: val || [] }); - }, - popupVisible(val) { - this.setState({ sPopupVisible: val }); - }, - options(val) { - if (this.showSearch) { - this.setState({ flattenOptions: flattenTree(val, this.$props as any) }); - } - }, - }, - // model: { - // prop: 'value', - // event: 'change', - // }, - created() { - provide('savePopupRef', this.savePopupRef); - }, - methods: { - savePopupRef(ref: any) { - this.popupRef = ref; - }, - highlightKeyword(str: string, keyword: string, prefixCls: string | undefined) { - return str - .split(keyword) - .map((node, index) => - index === 0 - ? node - : [{keyword}, node], - ); - }, - - defaultRenderFilteredOption(opt: { - inputValue: string; - path: CascaderOptionType[]; - prefixCls: string | undefined; - names: FilledFieldNamesType; - }) { - const { inputValue, path, prefixCls, names } = opt; - return path.map((option, index) => { - const label = option[names.label]; - const node = - label.indexOf(inputValue) > -1 - ? this.highlightKeyword(label, inputValue, prefixCls) - : label; - return index === 0 ? node : [' / ', node]; - }); - }, - saveInput(node: any) { - this.input = node; - }, - handleChange(value: any, selectedOptions: CascaderOptionType[]) { - this.setState({ inputValue: '' }); - if (selectedOptions[0].__IS_FILTERED_OPTION) { - const unwrappedValue = value[0]; - const unwrappedSelectedOptions = selectedOptions[0].path; - this.setValue(unwrappedValue, unwrappedSelectedOptions); - return; - } - this.setValue(value, selectedOptions); - }, - - handlePopupVisibleChange(popupVisible: boolean) { - if (!hasProp(this, 'popupVisible')) { - this.setState((state: any) => ({ - sPopupVisible: popupVisible, - inputFocused: popupVisible, - inputValue: popupVisible ? state.inputValue : '', - })); - } - this.$emit('popupVisibleChange', popupVisible); - }, - handleInputFocus(e: InputEvent) { - this.$emit('focus', e); - }, - - handleInputBlur(e: InputEvent) { - this.setState({ - inputFocused: false, - }); - this.$emit('blur', e); - this.formItemContext.onFieldBlur(); - }, - - handleInputClick(e: MouseEvent & { nativeEvent?: any }) { - const { inputFocused, sPopupVisible } = this; - // Prevent `Trigger` behavior. - if (inputFocused || sPopupVisible) { - e.stopPropagation(); - if (e.nativeEvent && e.nativeEvent.stopImmediatePropagation) { - e.nativeEvent.stopImmediatePropagation(); - } - } - }, - - handleKeyDown(e: KeyboardEvent) { - if (e.keyCode === KeyCode.BACKSPACE || e.keyCode === KeyCode.SPACE) { - e.stopPropagation(); - } - }, - - handleInputChange(e: Event) { - const inputValue = (e.target as HTMLInputElement).value; - this.setState({ inputValue }); - this.$emit('search', inputValue); - }, - - setValue(value: string[] | number[], selectedOptions: CascaderOptionType[] = []) { - if (!hasProp(this, 'value')) { - this.setState({ sValue: value }); - } - this.$emit('update:value', value); - this.$emit('change', value, selectedOptions); - this.formItemContext.onFieldChange(); - }, - - getLabel() { - const { options } = this; - const names = getFilledFieldNames(this.$props); - const displayRender = getComponent(this, 'displayRender', {}, false) || defaultDisplayRender; - const value = this.sValue; - const unwrappedValue = Array.isArray(value[0]) ? value[0] : value; - const selectedOptions = arrayTreeFilter( - options as CascaderOptionType[], - (o, level) => o[names.value] === unwrappedValue[level], - { childrenKeyName: names.children }, - ); - const labels = selectedOptions.map(o => o[names.label]); - return displayRender({ labels, selectedOptions }); - }, - - clearSelection(e: MouseEvent) { - e.preventDefault(); - e.stopPropagation(); - if (!this.inputValue) { - this.setValue([]); - this.handlePopupVisibleChange(false); - } else { - this.setState({ inputValue: '' }); - } - }, - - generateFilteredOptions( - prefixCls: string | undefined, - renderEmpty: RenderEmptyHandler, - ): EmptyFilteredOptionsType[] | FilteredOptionsType[] { - const { showSearch, notFoundContent } = this; - const names: FilledFieldNamesType = getFilledFieldNames(this.$props); - const { - filter = defaultFilterOption, - // render = this.defaultRenderFilteredOption, - sort = defaultSortFilteredOption, - limit = defaultLimit, - } = showSearch as ShowSearchType; - const render = - (showSearch as ShowSearchType).render || - getComponent(this, 'showSearchRender') || - this.defaultRenderFilteredOption; - const { flattenOptions = [], inputValue } = this.$data; - - // Limit the filter if needed - let filtered: Array; - if (limit > 0) { - filtered = []; - let matchCount = 0; - - // Perf optimization to filter items only below the limit - flattenOptions.some(path => { - const match = filter(inputValue, path, names); - if (match) { - filtered.push(path); - matchCount += 1; - } - return matchCount >= limit; - }); - } else { - warning( - typeof limit !== 'number', - 'Cascader', - "'limit' of showSearch in Cascader should be positive number or false.", - ); - filtered = flattenOptions.filter(path => filter(inputValue, path, names)); - } - - filtered.sort((a, b) => sort(a, b, inputValue, names)); - - if (filtered.length > 0) { - return filtered.map(path => { - return { - __IS_FILTERED_OPTION: true, - path, - [names.label]: render({ inputValue, path, prefixCls, names }), - [names.value]: path.map(o => o[names.value]), - disabled: path.some(o => !!o.disabled), - }; - }); - } - return [ - { - [names.label]: notFoundContent || renderEmpty('Cascader'), - [names.value]: 'ANT_CASCADER_NOT_FOUND', - disabled: true, - }, - ]; - }, - - focus() { - this.input && this.input.focus(); - }, - - blur() { - this.input && this.input.blur(); - }, - }, - - render() { - const { sPopupVisible, inputValue, configProvider, localeData } = this; - const { sValue: value, inputFocused } = this.$data; - const props = getOptionProps(this); - let suffixIcon = getComponent(this, 'suffixIcon'); - suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon; - const { getPopupContainer: getContextPopupContainer } = configProvider; - const { - prefixCls: customizePrefixCls, - inputPrefixCls: customizeInputPrefixCls, - placeholder = localeData.placeholder, - size, - disabled, - allowClear, - showSearch = false, - notFoundContent, - ...otherProps - } = props as any; - const { onEvents, extraAttrs } = splitAttrs(this.$attrs); - const { - class: className, - style, - id = this.formItemContext.id.value, - ...restAttrs - } = extraAttrs; - const getPrefixCls = this.configProvider.getPrefixCls; - const renderEmpty = this.configProvider.renderEmpty; - const rootPrefixCls = getPrefixCls(); - const prefixCls = getPrefixCls('cascader', customizePrefixCls); - const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls); - - const sizeCls = classNames({ - [`${inputPrefixCls}-lg`]: size === 'large', - [`${inputPrefixCls}-sm`]: size === 'small', - }); - const clearIcon = - (allowClear && !disabled && value.length > 0) || inputValue ? ( - - ) : null; - const arrowCls = classNames({ - [`${prefixCls}-picker-arrow`]: true, - [`${prefixCls}-picker-arrow-expand`]: sPopupVisible, - }); - const pickerCls = classNames(className, `${prefixCls}-picker`, { - [`${prefixCls}-picker-with-value`]: inputValue, - [`${prefixCls}-picker-disabled`]: disabled, - [`${prefixCls}-picker-${size}`]: !!size, - [`${prefixCls}-picker-show-search`]: !!showSearch, - [`${prefixCls}-picker-focused`]: inputFocused, - }); - - // Fix bug of https://github.com/facebook/react/pull/5004 - // and https://fb.me/react-unknown-prop - const tempInputProps = omit(otherProps, [ - 'popupStyle', - 'options', - 'popupPlacement', - 'transitionName', - 'displayRender', - 'changeOnSelect', - 'expandTrigger', - 'popupVisible', - 'getPopupContainer', - 'loadData', - 'popupClassName', - 'filterOption', - 'renderFilteredOption', - 'sortFilteredOption', - 'notFoundContent', - 'defaultValue', - 'fieldNames', - 'onChange', - 'onPopupVisibleChange', - 'onFocus', - 'onBlur', - 'onSearch', - 'onUpdate:value', - ]); - - let options = props.options; - const names = getFilledFieldNames(this.$props); - if (options && options.length > 0) { - if (inputValue) { - options = this.generateFilteredOptions(prefixCls, renderEmpty); - } - } else { - options = [ - { - [names.label]: notFoundContent || renderEmpty('Cascader'), - [names.value]: 'ANT_CASCADER_NOT_FOUND', - disabled: true, - }, - ]; - } - - // Dropdown menu should keep previous status until it is fully closed. - if (!sPopupVisible) { - options = this.cachedOptions; - } else { - this.cachedOptions = options; - } - - const dropdownMenuColumnStyle: CSSProperties = {}; - const isNotFound = - (options || []).length === 1 && options[0].value === 'ANT_CASCADER_NOT_FOUND'; - if (isNotFound) { - dropdownMenuColumnStyle.height = 'auto'; // Height of one row. - } - // The default value of `matchInputWidth` is `true` - const resultListMatchInputWidth = showSearch.matchInputWidth !== false; - if (resultListMatchInputWidth && (inputValue || isNotFound) && this.input) { - dropdownMenuColumnStyle.width = findDOMNode(this.input.input).offsetWidth + 'px'; - } - // showSearch时,focus、blur在input上触发,反之在ref='picker'上触发 - const inputProps = { - ...restAttrs, - ...tempInputProps, - id, - prefixCls: inputPrefixCls, - placeholder: value && value.length > 0 ? undefined : placeholder, - value: inputValue, - disabled, - readonly: !showSearch, - autocomplete: 'off', - class: `${prefixCls}-input ${sizeCls}`, - onFocus: this.handleInputFocus, - onClick: showSearch ? this.handleInputClick : noop, - onBlur: showSearch ? this.handleInputBlur : props.onBlur, - onKeydown: this.handleKeyDown, - onChange: showSearch ? this.handleInputChange : noop, - }; - const children = getSlot(this); - const inputIcon = (suffixIcon && - (isValidElement(suffixIcon) ? ( - cloneElement(suffixIcon, { - class: `${prefixCls}-picker-arrow`, - }) - ) : ( - {suffixIcon} - ))) || ; - - const input = children.length ? ( - children - ) : ( - - {this.getLabel()} - - {clearIcon} - {inputIcon} - - ); - - const expandIcon = ; - - const loadingIcon = ( - - - - ); - const getPopupContainer = props.getPopupContainer || getContextPopupContainer; - const cascaderProps = { - ...props, - getPopupContainer, - options, - prefixCls, - value, - popupVisible: sPopupVisible, - dropdownMenuColumnStyle, - expandIcon, - loadingIcon, - ...onEvents, - onPopupVisibleChange: this.handlePopupVisibleChange, - onChange: this.handleChange, - transitionName: getTransitionName(rootPrefixCls, 'slide-up', props.transitionName), - }; - return {input}; - }, -}); - -export default withInstall(Cascader); diff --git a/components/vc-cascader/Cascader.jsx b/components/vc-cascader/Cascader.jsx deleted file mode 100644 index d388c286f..000000000 --- a/components/vc-cascader/Cascader.jsx +++ /dev/null @@ -1,388 +0,0 @@ -import { getComponent, getSlot, hasProp, getEvents } from '../_util/props-util'; -import PropTypes from '../_util/vue-types'; -import Trigger from '../vc-trigger'; -import Menus from './Menus'; -import KeyCode from '../_util/KeyCode'; -import arrayTreeFilter from 'array-tree-filter'; -import shallowEqualArrays from 'shallow-equal/arrays'; -import BaseMixin from '../_util/BaseMixin'; -import { cloneElement } from '../_util/vnode'; -import { defineComponent } from 'vue'; -import isEqual from 'lodash-es/isEqual'; - -const BUILT_IN_PLACEMENTS = { - bottomLeft: { - points: ['tl', 'bl'], - offset: [0, 4], - overflow: { - adjustX: 1, - adjustY: 1, - }, - }, - topLeft: { - points: ['bl', 'tl'], - offset: [0, -4], - overflow: { - adjustX: 1, - adjustY: 1, - }, - }, - bottomRight: { - points: ['tr', 'br'], - offset: [0, 4], - overflow: { - adjustX: 1, - adjustY: 1, - }, - }, - topRight: { - points: ['br', 'tr'], - offset: [0, -4], - overflow: { - adjustX: 1, - adjustY: 1, - }, - }, -}; - -export default defineComponent({ - name: 'Cascader', - mixins: [BaseMixin], - inheritAttrs: false, - // model: { - // prop: 'value', - // event: 'change', - // }, - props: { - value: PropTypes.array, - defaultValue: PropTypes.array, - options: PropTypes.array, - // onChange: PropTypes.func, - // onPopupVisibleChange: PropTypes.func, - popupVisible: PropTypes.looseBool, - disabled: PropTypes.looseBool.def(false), - transitionName: PropTypes.string.def(''), - popupClassName: PropTypes.string.def(''), - popupStyle: PropTypes.object.def(() => ({})), - popupPlacement: PropTypes.string.def('bottomLeft'), - prefixCls: PropTypes.string.def('rc-cascader'), - dropdownMenuColumnStyle: PropTypes.object, - builtinPlacements: PropTypes.object.def(BUILT_IN_PLACEMENTS), - loadData: PropTypes.func, - changeOnSelect: PropTypes.looseBool, - // onKeyDown: PropTypes.func, - expandTrigger: PropTypes.string.def('click'), - fieldNames: PropTypes.object.def(() => ({ - label: 'label', - value: 'value', - children: 'children', - })), - expandIcon: PropTypes.any, - loadingIcon: PropTypes.any, - getPopupContainer: PropTypes.func, - }, - data() { - let initialValue = []; - const { value, defaultValue, popupVisible } = this; - if (hasProp(this, 'value')) { - initialValue = value || []; - } else if (hasProp(this, 'defaultValue')) { - initialValue = defaultValue || []; - } - this.children = undefined; - // warning(!('filedNames' in props), - // '`filedNames` of Cascader is a typo usage and deprecated, please use `fieldNames` instead.'); - this.defaultFieldNames = { label: 'label', value: 'value', children: 'children' }; - return { - sPopupVisible: popupVisible, - sActiveValue: initialValue, - sValue: initialValue, - }; - }, - watch: { - value(val, oldValue) { - if (!shallowEqualArrays(val, oldValue)) { - const newValues = { - sValue: val || [], - }; - // allow activeValue diff from value - // https://github.com/ant-design/ant-design/issues/2767 - if (!hasProp(this, 'loadData')) { - newValues.sActiveValue = val || []; - } - this.setState(newValues); - } - }, - popupVisible(val) { - this.setState({ - sPopupVisible: val, - }); - }, - }, - methods: { - getPopupDOMNode() { - return this.trigger.getPopupDomNode(); - }, - getFieldName(name) { - const { defaultFieldNames, fieldNames } = this; - return fieldNames[name] || defaultFieldNames[name]; - }, - getFieldNames() { - return this.fieldNames; - }, - getCurrentLevelOptions() { - const { options = [], sActiveValue = [] } = this; - const result = arrayTreeFilter( - options, - (o, level) => isEqual(o[this.getFieldName('value')], sActiveValue[level]), - { childrenKeyName: this.getFieldName('children') }, - ); - if (result[result.length - 2]) { - return result[result.length - 2][this.getFieldName('children')]; - } - return [...options].filter(o => !o.disabled); - }, - getActiveOptions(activeValue) { - return arrayTreeFilter( - this.options || [], - (o, level) => isEqual(o[this.getFieldName('value')], activeValue[level]), - { childrenKeyName: this.getFieldName('children') }, - ); - }, - setPopupVisible(popupVisible) { - if (!hasProp(this, 'popupVisible')) { - this.setState({ sPopupVisible: popupVisible }); - } - // sync activeValue with value when panel open - if (popupVisible && !this.sPopupVisible) { - this.setState({ - sActiveValue: this.sValue, - }); - } - this.__emit('popupVisibleChange', popupVisible); - }, - handleChange(options, setProps, e) { - if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) { - const value = options.map(o => o[this.getFieldName('value')]); - this.__emit('change', value, options); - this.setPopupVisible(setProps.visible); - } - }, - handlePopupVisibleChange(popupVisible) { - this.setPopupVisible(popupVisible); - }, - handleMenuSelect(targetOption, menuIndex, e) { - // Keep focused state for keyboard support - const triggerNode = this.trigger.getRootDomNode(); - if (triggerNode && triggerNode.focus) { - triggerNode.focus(); - } - const { changeOnSelect, loadData, expandTrigger } = this; - if (!targetOption || targetOption.disabled) { - return; - } - let { sActiveValue } = this; - sActiveValue = sActiveValue.slice(0, menuIndex + 1); - sActiveValue[menuIndex] = targetOption[this.getFieldName('value')]; - const activeOptions = this.getActiveOptions(sActiveValue); - if ( - targetOption.isLeaf === false && - !targetOption[this.getFieldName('children')] && - loadData - ) { - if (changeOnSelect) { - this.handleChange(activeOptions, { visible: true }, e); - } - this.setState({ sActiveValue }); - loadData(activeOptions); - return; - } - const newState = {}; - if ( - !targetOption[this.getFieldName('children')] || - !targetOption[this.getFieldName('children')].length - ) { - this.handleChange(activeOptions, { visible: false }, e); - // set value to activeValue when select leaf option - newState.sValue = sActiveValue; - // add e.type judgement to prevent `onChange` being triggered by mouseEnter - } else if (changeOnSelect && (e.type === 'click' || e.type === 'keydown')) { - if (expandTrigger === 'hover') { - this.handleChange(activeOptions, { visible: false }, e); - } else { - this.handleChange(activeOptions, { visible: true }, e); - } - // set value to activeValue on every select - newState.sValue = sActiveValue; - } - newState.sActiveValue = sActiveValue; - // not change the value by keyboard - if (hasProp(this, 'value') || (e.type === 'keydown' && e.keyCode !== KeyCode.ENTER)) { - delete newState.sValue; - } - this.setState(newState); - }, - handleItemDoubleClick() { - const { changeOnSelect } = this.$props; - if (changeOnSelect) { - this.setPopupVisible(false); - } - }, - handleKeyDown(e) { - const children = this.children; - // https://github.com/ant-design/ant-design/issues/6717 - // Don't bind keyboard support when children specify the onKeyDown - if (children) { - const keydown = getEvents(children).onKeydown; - if (keydown) { - keydown(e); - return; - } - } - const activeValue = [...this.sActiveValue]; - const currentLevel = activeValue.length - 1 < 0 ? 0 : activeValue.length - 1; - const currentOptions = this.getCurrentLevelOptions(); - const currentIndex = currentOptions - .map(o => o[this.getFieldName('value')]) - .findIndex(val => isEqual(activeValue[currentLevel], val)); - if ( - e.keyCode !== KeyCode.DOWN && - e.keyCode !== KeyCode.UP && - e.keyCode !== KeyCode.LEFT && - e.keyCode !== KeyCode.RIGHT && - e.keyCode !== KeyCode.ENTER && - e.keyCode !== KeyCode.SPACE && - e.keyCode !== KeyCode.BACKSPACE && - e.keyCode !== KeyCode.ESC && - e.keyCode !== KeyCode.TAB - ) { - return; - } - // Press any keys above to reopen menu - if ( - !this.sPopupVisible && - e.keyCode !== KeyCode.BACKSPACE && - e.keyCode !== KeyCode.LEFT && - e.keyCode !== KeyCode.RIGHT && - e.keyCode !== KeyCode.ESC && - e.keyCode !== KeyCode.TAB - ) { - this.setPopupVisible(true); - return; - } - if (e.keyCode === KeyCode.DOWN || e.keyCode === KeyCode.UP) { - e.preventDefault(); - let nextIndex = currentIndex; - if (nextIndex !== -1) { - if (e.keyCode === KeyCode.DOWN) { - nextIndex += 1; - nextIndex = nextIndex >= currentOptions.length ? 0 : nextIndex; - } else { - nextIndex -= 1; - nextIndex = nextIndex < 0 ? currentOptions.length - 1 : nextIndex; - } - } else { - nextIndex = 0; - } - activeValue[currentLevel] = currentOptions[nextIndex][this.getFieldName('value')]; - } else if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.BACKSPACE) { - e.preventDefault(); - activeValue.splice(activeValue.length - 1, 1); - } else if (e.keyCode === KeyCode.RIGHT) { - e.preventDefault(); - if ( - currentOptions[currentIndex] && - currentOptions[currentIndex][this.getFieldName('children')] - ) { - activeValue.push( - currentOptions[currentIndex][this.getFieldName('children')][0][ - this.getFieldName('value') - ], - ); - } - } else if (e.keyCode === KeyCode.ESC || e.keyCode === KeyCode.TAB) { - this.setPopupVisible(false); - return; - } - if (!activeValue || activeValue.length === 0) { - this.setPopupVisible(false); - } - const activeOptions = this.getActiveOptions(activeValue); - const targetOption = activeOptions[activeOptions.length - 1]; - this.handleMenuSelect(targetOption, activeOptions.length - 1, e); - this.__emit('keydown', e); - }, - saveTrigger(node) { - this.trigger = node; - }, - }, - - render() { - const { - $props, - sActiveValue, - handleMenuSelect, - sPopupVisible, - handlePopupVisibleChange, - handleKeyDown, - } = this; - const { - prefixCls, - transitionName, - popupClassName, - options = [], - disabled, - builtinPlacements, - popupPlacement, - ...restProps - } = $props; - // Did not show popup when there is no options - let menus =
; - let emptyMenuClassName = ''; - if (options && options.length > 0) { - const loadingIcon = getComponent(this, 'loadingIcon'); - const expandIcon = getComponent(this, 'expandIcon') || '>'; - const menusProps = { - ...$props, - ...this.$attrs, - fieldNames: this.getFieldNames(), - defaultFieldNames: this.defaultFieldNames, - activeValue: sActiveValue, - visible: sPopupVisible, - loadingIcon, - expandIcon, - onSelect: handleMenuSelect, - onItemDoubleClick: this.handleItemDoubleClick, - }; - menus = ; - } else { - emptyMenuClassName = ` ${prefixCls}-menus-empty`; - } - const triggerProps = { - ...restProps, - ...this.$attrs, - disabled, - popupPlacement, - builtinPlacements, - popupTransitionName: transitionName, - action: disabled ? [] : ['click'], - popupVisible: disabled ? false : sPopupVisible, - prefixCls: `${prefixCls}-menus`, - popupClassName: popupClassName + emptyMenuClassName, - popup: menus, - onPopupVisibleChange: handlePopupVisibleChange, - ref: this.saveTrigger, - }; - const children = getSlot(this); - this.children = children; - return ( - - {children && - cloneElement(children[0], { - onKeydown: handleKeyDown, - tabindex: disabled ? undefined : 0, - })} - - ); - }, -}); diff --git a/components/vc-cascader2/Cascader.tsx b/components/vc-cascader/Cascader.tsx similarity index 100% rename from components/vc-cascader2/Cascader.tsx rename to components/vc-cascader/Cascader.tsx diff --git a/components/vc-cascader/Menus.jsx b/components/vc-cascader/Menus.jsx deleted file mode 100644 index a3f9ace7f..000000000 --- a/components/vc-cascader/Menus.jsx +++ /dev/null @@ -1,182 +0,0 @@ -import { getComponent, findDOMNode } from '../_util/props-util'; -import PropTypes from '../_util/vue-types'; -import arrayTreeFilter from 'array-tree-filter'; -import BaseMixin from '../_util/BaseMixin'; -import isEqual from 'lodash-es/isEqual'; - -export default { - name: 'CascaderMenus', - mixins: [BaseMixin], - inheritAttrs: false, - props: { - value: PropTypes.array.def([]), - activeValue: PropTypes.array.def([]), - options: PropTypes.array, - prefixCls: PropTypes.string.def('rc-cascader-menus'), - expandTrigger: PropTypes.string.def('click'), - // onSelect: PropTypes.func, - visible: PropTypes.looseBool.def(false), - dropdownMenuColumnStyle: PropTypes.object, - defaultFieldNames: PropTypes.object, - fieldNames: PropTypes.object, - expandIcon: PropTypes.any, - loadingIcon: PropTypes.any, - }, - data() { - this.menuItems = {}; - return {}; - }, - watch: { - visible(val) { - if (val) { - this.$nextTick(() => { - this.scrollActiveItemToView(); - }); - } - }, - }, - mounted() { - this.$nextTick(() => { - this.scrollActiveItemToView(); - }); - }, - methods: { - getFieldName(name) { - const { fieldNames, defaultFieldNames } = this.$props; - // 防止只设置单个属性的名字 - return fieldNames[name] || defaultFieldNames[name]; - }, - getOption(option, menuIndex) { - const { prefixCls, expandTrigger } = this; - const loadingIcon = getComponent(this, 'loadingIcon'); - const expandIcon = getComponent(this, 'expandIcon'); - const onSelect = e => { - this.__emit('select', option, menuIndex, e); - }; - const onItemDoubleClick = e => { - this.__emit('itemDoubleClick', option, menuIndex, e); - }; - const key = option[this.getFieldName('value')]; - let expandProps = { - onClick: onSelect, - onDblclick: onItemDoubleClick, - }; - let menuItemCls = `${prefixCls}-menu-item`; - let expandIconNode = null; - const hasChildren = - option[this.getFieldName('children')] && option[this.getFieldName('children')].length > 0; - if (hasChildren || option.isLeaf === false) { - menuItemCls += ` ${prefixCls}-menu-item-expand`; - if (!option.loading) { - expandIconNode = {expandIcon}; - } - } - if (expandTrigger === 'hover' && (hasChildren || option.isLeaf === false)) { - expandProps = { - onMouseenter: this.delayOnSelect.bind(this, onSelect), - onMouseleave: this.delayOnSelect.bind(this), - onClick: onSelect, - }; - } - if (this.isActiveOption(option, menuIndex)) { - menuItemCls += ` ${prefixCls}-menu-item-active`; - expandProps.ref = this.saveMenuItem(menuIndex); - } - if (option.disabled) { - menuItemCls += ` ${prefixCls}-menu-item-disabled`; - } - let loadingIconNode = null; - if (option.loading) { - menuItemCls += ` ${prefixCls}-menu-item-loading`; - loadingIconNode = loadingIcon || null; - } - let title = ''; - if (option.title) { - title = option.title; - } else if (typeof option[this.getFieldName('label')] === 'string') { - title = option[this.getFieldName('label')]; - } - return ( - - ); - }, - - getActiveOptions(values) { - const activeValue = values || this.activeValue; - const options = this.options; - return arrayTreeFilter( - options, - (o, level) => isEqual(o[this.getFieldName('value')], activeValue[level]), - { childrenKeyName: this.getFieldName('children') }, - ); - }, - - getShowOptions() { - const { options } = this; - const result = this.getActiveOptions() - .map(activeOption => activeOption[this.getFieldName('children')]) - .filter(activeOption => !!activeOption); - result.unshift(options); - return result; - }, - - delayOnSelect(onSelect, ...args) { - if (this.delayTimer) { - clearTimeout(this.delayTimer); - this.delayTimer = null; - } - if (typeof onSelect === 'function') { - this.delayTimer = setTimeout(() => { - onSelect(args); - this.delayTimer = null; - }, 150); - } - }, - - scrollActiveItemToView() { - // scroll into view - const optionsLength = this.getShowOptions().length; - for (let i = 0; i < optionsLength; i++) { - const itemComponent = this.menuItems[i]; - if (itemComponent) { - const target = findDOMNode(itemComponent); - target.parentNode.scrollTop = target.offsetTop; - } - } - }, - - isActiveOption(option, menuIndex) { - const { activeValue = [] } = this; - return isEqual(activeValue[menuIndex], option[this.getFieldName('value')]); - }, - saveMenuItem(index) { - return node => { - this.menuItems[index] = node; - }; - }, - }, - - render() { - const { prefixCls, dropdownMenuColumnStyle } = this; - return ( -
- {this.getShowOptions().map((options, menuIndex) => ( -
    - {options.map(option => this.getOption(option, menuIndex))} -
- ))} -
- ); - }, -}; diff --git a/components/vc-cascader2/OptionList/Checkbox.tsx b/components/vc-cascader/OptionList/Checkbox.tsx similarity index 100% rename from components/vc-cascader2/OptionList/Checkbox.tsx rename to components/vc-cascader/OptionList/Checkbox.tsx diff --git a/components/vc-cascader2/OptionList/Column.tsx b/components/vc-cascader/OptionList/Column.tsx similarity index 100% rename from components/vc-cascader2/OptionList/Column.tsx rename to components/vc-cascader/OptionList/Column.tsx diff --git a/components/vc-cascader2/OptionList/index.tsx b/components/vc-cascader/OptionList/index.tsx similarity index 100% rename from components/vc-cascader2/OptionList/index.tsx rename to components/vc-cascader/OptionList/index.tsx diff --git a/components/vc-cascader2/OptionList/useActive.ts b/components/vc-cascader/OptionList/useActive.ts similarity index 100% rename from components/vc-cascader2/OptionList/useActive.ts rename to components/vc-cascader/OptionList/useActive.ts diff --git a/components/vc-cascader2/OptionList/useKeyboard.ts b/components/vc-cascader/OptionList/useKeyboard.ts similarity index 94% rename from components/vc-cascader2/OptionList/useKeyboard.ts rename to components/vc-cascader/OptionList/useKeyboard.ts index aa566cc0d..fe161d00e 100644 --- a/components/vc-cascader2/OptionList/useKeyboard.ts +++ b/components/vc-cascader/OptionList/useKeyboard.ts @@ -1,7 +1,7 @@ import type { RefOptionListProps } from '../../vc-select/OptionList'; import type { Key } from 'ant-design-vue/es/_util/type'; import type { Ref, SetupContext } from 'vue'; -import { ref, watchEffect } from 'vue'; +import { computed, ref, watchEffect } from 'vue'; import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader'; import { toPathKey } from '../utils/commonUtil'; import { useBaseProps } from '../../vc-select'; @@ -16,8 +16,8 @@ export default ( containerRef: Ref, onKeyBoardSelect: (valueCells: SingleValueType, option: DefaultOptionType) => void, ) => { - const { direction, searchValue, toggleOpen, open } = useBaseProps(); - const rtl = direction === 'rtl'; + const baseProps = useBaseProps(); + const rtl = computed(() => baseProps.direction === 'rtl'); const [validActiveValueCells, lastActiveIndex, lastActiveOptions] = [ ref([]), ref(), @@ -31,7 +31,6 @@ export default ( const mergedActiveValueCells: Key[] = []; const len = activeValueCells.value.length; - // Fill validate active value cells and index for (let i = 0; i < len; i += 1) { // Mark the active index for current options @@ -99,7 +98,7 @@ export default ( const nextActiveCells = validActiveValueCells.value.slice(0, -1); internalSetActiveValueCells(nextActiveCells); } else { - toggleOpen(false); + baseProps.toggleOpen(false); } }; @@ -139,7 +138,7 @@ export default ( } case KeyCode.LEFT: { - if (rtl) { + if (rtl.value) { nextColumn(); } else { prevColumn(); @@ -148,7 +147,7 @@ export default ( } case KeyCode.RIGHT: { - if (rtl) { + if (rtl.value) { prevColumn(); } else { nextColumn(); @@ -157,7 +156,7 @@ export default ( } case KeyCode.BACKSPACE: { - if (!searchValue) { + if (!baseProps.searchValue) { prevColumn(); } break; @@ -176,7 +175,7 @@ export default ( // >>> Close case KeyCode.ESC: { - toggleOpen(false); + baseProps.toggleOpen(false); if (open) { event.stopPropagation(); diff --git a/components/vc-cascader/assets/index.less b/components/vc-cascader/assets/index.less deleted file mode 100644 index f48b1c753..000000000 --- a/components/vc-cascader/assets/index.less +++ /dev/null @@ -1,168 +0,0 @@ -.effect() { - animation-duration: 0.3s; - animation-fill-mode: both; - transform-origin: 0 0; -} - -.rc-cascader { - font-size: 12px; - &-menus { - font-size: 12px; - overflow: hidden; - background: #fff; - position: absolute; - border: 1px solid #d9d9d9; - border-radius: 6px; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.17); - white-space: nowrap; - - &-hidden { - display: none; - } - - &.slide-up-enter, - &.slide-up-appear { - .effect(); - opacity: 0; - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-play-state: paused; - } - - &.slide-up-leave { - .effect(); - opacity: 1; - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-play-state: paused; - } - - &.slide-up-enter.slide-up-enter-active&-placement-bottomLeft, - &.slide-up-appear.slide-up-appear-active&-placement-bottomLeft { - animation-name: SlideUpIn; - animation-play-state: running; - } - - &.slide-up-enter.slide-up-enter-active&-placement-topLeft, - &.slide-up-appear.slide-up-appear-active&-placement-topLeft { - animation-name: SlideDownIn; - animation-play-state: running; - } - - &.slide-up-leave.slide-up-leave-active&-placement-bottomLeft { - animation-name: SlideUpOut; - animation-play-state: running; - } - - &.slide-up-leave.slide-up-leave-active&-placement-topLeft { - animation-name: SlideDownOut; - animation-play-state: running; - } - } - &-menu { - display: inline-block; - width: 100px; - height: 192px; - list-style: none; - margin: 0; - padding: 0; - border-right: 1px solid #e9e9e9; - overflow: auto; - &:last-child { - border-right: 0; - } - } - &-menu-item { - height: 32px; - line-height: 32px; - padding: 0 16px; - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - transition: all 0.3s ease; - position: relative; - &:hover { - background: tint(#2db7f5, 90%); - } - &-disabled { - cursor: not-allowed; - color: #ccc; - &:hover { - background: transparent; - } - } - &-loading:after { - position: absolute; - right: 12px; - content: 'loading'; - color: #aaa; - font-style: italic; - } - &-active { - background: tint(#2db7f5, 80%); - &:hover { - background: tint(#2db7f5, 80%); - } - } - &-expand { - position: relative; - &:after { - content: '>'; - font-size: 12px; - color: #999; - position: absolute; - right: 16px; - line-height: 32px; - } - } - } -} - -@keyframes SlideUpIn { - 0% { - opacity: 0; - transform-origin: 0% 0%; - transform: scaleY(0.8); - } - 100% { - opacity: 1; - transform-origin: 0% 0%; - transform: scaleY(1); - } -} -@keyframes SlideUpOut { - 0% { - opacity: 1; - transform-origin: 0% 0%; - transform: scaleY(1); - } - 100% { - opacity: 0; - transform-origin: 0% 0%; - transform: scaleY(0.8); - } -} - -@keyframes SlideDownIn { - 0% { - opacity: 0; - transform-origin: 0% 100%; - transform: scaleY(0.8); - } - 100% { - opacity: 1; - transform-origin: 0% 100%; - transform: scaleY(1); - } -} -@keyframes SlideDownOut { - 0% { - opacity: 1; - transform-origin: 0% 100%; - transform: scaleY(1); - } - 100% { - opacity: 0; - transform-origin: 0% 100%; - transform: scaleY(0.8); - } -} diff --git a/components/vc-cascader2/context.ts b/components/vc-cascader/context.ts similarity index 100% rename from components/vc-cascader2/context.ts rename to components/vc-cascader/context.ts diff --git a/components/vc-cascader2/hooks/useDisplayValues.ts b/components/vc-cascader/hooks/useDisplayValues.ts similarity index 100% rename from components/vc-cascader2/hooks/useDisplayValues.ts rename to components/vc-cascader/hooks/useDisplayValues.ts diff --git a/components/vc-cascader2/hooks/useEntities.ts b/components/vc-cascader/hooks/useEntities.ts similarity index 100% rename from components/vc-cascader2/hooks/useEntities.ts rename to components/vc-cascader/hooks/useEntities.ts diff --git a/components/vc-cascader2/hooks/useMissingValues.ts b/components/vc-cascader/hooks/useMissingValues.ts similarity index 100% rename from components/vc-cascader2/hooks/useMissingValues.ts rename to components/vc-cascader/hooks/useMissingValues.ts diff --git a/components/vc-cascader2/hooks/useSearchConfig.ts b/components/vc-cascader/hooks/useSearchConfig.ts similarity index 100% rename from components/vc-cascader2/hooks/useSearchConfig.ts rename to components/vc-cascader/hooks/useSearchConfig.ts diff --git a/components/vc-cascader2/hooks/useSearchOptions.ts b/components/vc-cascader/hooks/useSearchOptions.ts similarity index 100% rename from components/vc-cascader2/hooks/useSearchOptions.ts rename to components/vc-cascader/hooks/useSearchOptions.ts diff --git a/components/vc-cascader/index.js b/components/vc-cascader/index.js deleted file mode 100644 index 4bd1fc80a..000000000 --- a/components/vc-cascader/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// based on rc-cascader 0.17.4 -import Cascader from './Cascader'; -export default Cascader; diff --git a/components/vc-cascader2/index.tsx b/components/vc-cascader/index.tsx similarity index 100% rename from components/vc-cascader2/index.tsx rename to components/vc-cascader/index.tsx diff --git a/components/vc-cascader2/utils/commonUtil.ts b/components/vc-cascader/utils/commonUtil.ts similarity index 100% rename from components/vc-cascader2/utils/commonUtil.ts rename to components/vc-cascader/utils/commonUtil.ts diff --git a/components/vc-cascader2/utils/treeUtil.ts b/components/vc-cascader/utils/treeUtil.ts similarity index 100% rename from components/vc-cascader2/utils/treeUtil.ts rename to components/vc-cascader/utils/treeUtil.ts diff --git a/components/vc-select/Selector/Input.tsx b/components/vc-select/Selector/Input.tsx index 91bf7f8a9..d60a2519b 100644 --- a/components/vc-select/Selector/Input.tsx +++ b/components/vc-select/Selector/Input.tsx @@ -174,11 +174,11 @@ const Input = defineComponent({ this.VCSelectContainerEvent?.focus(args[0]); }, onBlur: (...args: any[]) => { - // this.blurTimeout = setTimeout(() => { - onOriginBlur && onOriginBlur(args[0]); - onBlur && onBlur(args[0]); - this.VCSelectContainerEvent?.blur(args[0]); - // }, 200); + this.blurTimeout = setTimeout(() => { + onOriginBlur && onOriginBlur(args[0]); + onBlur && onBlur(args[0]); + this.VCSelectContainerEvent?.blur(args[0]); + }, 100); }, }, inputNode.type === 'textarea' ? {} : { type: 'search' },