feat: tree-select

This commit is contained in:
tangjinzhou 2020-07-18 21:40:26 +08:00
parent b442cc0553
commit 61b5bfa0f6
25 changed files with 264 additions and 397 deletions

@ -1 +1 @@
Subproject commit 1cf8913c4b662bc2bda8ec44734e445ec370cc60
Subproject commit e142c7f8cb2025c14d973691cfb90603e9790d46

View File

@ -93,3 +93,7 @@ v-model -> v-model:activeKey
## List
renderItem(item, index) => renderItem({item, index}) 该用单参数
## TreeSelect
treeData 中 scopedSlots => slots , v-model => v-model:value

View File

@ -112,17 +112,8 @@ const getAllChildren = ele => {
}
return ele.children || componentOptions.children || [];
};
const getSlotOptions = ele => {
const getSlotOptions = () => {
throw Error('使用 .type 直接取值');
if (ele.fnOptions) {
// 函数式组件
return ele.fnOptions;
}
let componentOptions = ele.componentOptions;
if (ele.$vnode) {
componentOptions = ele.$vnode.componentOptions;
}
return componentOptions ? componentOptions.Ctor.options || {} : {};
};
const findDOMNode = instance => {
let node = instance.$el || instance;
@ -148,7 +139,7 @@ const getOptionProps = instance => {
Object.keys(originProps).forEach(key => {
props[camelize(key)] = originProps[key];
});
const options = instance.type.props;
const options = instance.type.props || {};
Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]);
if (v !== undefined || k in props) {
@ -246,13 +237,14 @@ const getPropsData = ins => {
props[camelize(key)] = originProps[key];
});
const options = isPlainObject(vnode.type) ? vnode.type.props : {};
Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]);
if (k in props) {
// 仅包含 props不包含默认值
res[k] = v;
}
});
options &&
Object.keys(options).forEach(k => {
const v = resolvePropValue(options, props, k, props[k]);
if (k in props) {
// 仅包含 props不包含默认值
res[k] = v;
}
});
return { ...props, ...res }; // 合并事件、未声明属性等
};
const getValueByProp = (ele, prop) => {

View File

@ -302,7 +302,6 @@ const Cascader = {
},
generateFilteredOptions(prefixCls, renderEmpty) {
const h = this.$createElement;
const { showSearch, notFoundContent } = this;
const names = getFilledFieldNames(this.$props);
const {
@ -356,7 +355,7 @@ const Cascader = {
}
return [
{
[names.label]: notFoundContent || renderEmpty(h, 'Cascader'),
[names.label]: notFoundContent || renderEmpty('Cascader'),
[names.value]: 'ANT_CASCADER_NOT_FOUND',
disabled: true,
},
@ -543,4 +542,8 @@ const Cascader = {
},
};
Cascader.install = function(app) {
app.component(Cascader.name, Cascader);
};
export default Cascader;

View File

@ -96,7 +96,6 @@ export default {
return null;
},
renderIndicator(h, prefixCls) {
// const h = this.$createElement
const dotClassName = `${prefixCls}-dot`;
let indicator = getComponent(this, 'indicator');
// should not be render default indicator when indicator value is null

View File

@ -120,10 +120,9 @@ const Transfer = {
},
getLocale(transferLocale, renderEmpty) {
const h = this.$createElement;
// Keep old locale props still working.
const oldLocale = {
notFoundContent: renderEmpty(h, 'Transfer'),
notFoundContent: renderEmpty('Transfer'),
};
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
if (notFoundContent) {

View File

@ -174,7 +174,7 @@ export default {
if (!listBody) {
let bodyNode;
const { bodyContent, customize } = renderListNode(this.$createElement, renderList, {
const { bodyContent, customize } = renderListNode(renderList, {
props: { ...this.$props, filteredItems, filteredRenderItems, selectedKeys: checkedKeys },
on: getListeners(this),
});

View File

@ -1,16 +1,10 @@
import VcTreeSelect, { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from '../vc-tree-select';
import { inject } from 'vue';
import classNames from 'classnames';
import { TreeSelectProps } from './interface';
import warning from '../_util/warning';
import {
initDefaultProps,
getOptionProps,
getComponentFromProp,
filterEmpty,
getListeners,
} from '../_util/props-util';
import { initDefaultProps, getOptionProps, getComponent, getSlot } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
import Base from '../base';
export { TreeData, TreeSelectProps } from './interface';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
@ -19,24 +13,26 @@ import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import CloseCircleOutlined from '@ant-design/icons-vue/CloseCircleOutlined';
import omit from 'omit.js';
const ATreeSelectNode = function ATreeSelectNode(props, ctx) {
return TreeNode(props, ctx);
};
Object.keys(TreeNode).forEach(key => (ATreeSelectNode[key] = TreeNode[key]));
const TreeSelect = {
TreeNode: { ...TreeNode, name: 'ATreeSelectNode' },
TreeNode: ATreeSelectNode,
SHOW_ALL,
SHOW_PARENT,
SHOW_CHILD,
inheritAttrs: false,
name: 'ATreeSelect',
props: initDefaultProps(TreeSelectProps(), {
transitionName: 'slide-up',
choiceTransitionName: 'zoom',
showSearch: false,
}),
model: {
prop: 'value',
event: 'change',
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
},
created() {
warning(
@ -46,12 +42,15 @@ const TreeSelect = {
);
},
methods: {
saveTreeSelect(node) {
this.vcTreeSelect = node;
},
focus() {
this.$refs.vcTreeSelect.focus();
this.vcTreeSelect.focus();
},
blur() {
this.$refs.vcTreeSelect.blur();
this.vcTreeSelect.blur();
},
renderSwitcherIcon(prefixCls, { isLeaf, loading }) {
if (loading) {
@ -62,11 +61,12 @@ const TreeSelect = {
}
return <CaretDownOutlined class={`${prefixCls}-switcher-icon`} />;
},
onChange() {
this.$emit('change', ...arguments);
onChange(...args) {
this.$emit('update:value', args[0]);
this.$emit('change', ...args);
},
updateTreeData(treeData) {
const { $scopedSlots } = this;
const { $slots } = this;
const defaultFields = {
children: 'children',
title: 'title',
@ -76,19 +76,19 @@ const TreeSelect = {
};
const replaceFields = { ...defaultFields, ...this.$props.replaceFields };
return treeData.map(item => {
const { scopedSlots = {} } = item;
const { slots = {} } = item;
const label = item[replaceFields.label];
const title = item[replaceFields.title];
const value = item[replaceFields.value];
const key = item[replaceFields.key];
const children = item[replaceFields.children];
let newLabel = typeof label === 'function' ? label(this.$createElement) : label;
let newTitle = typeof title === 'function' ? title(this.$createElement) : title;
if (!newLabel && scopedSlots.label && $scopedSlots[scopedSlots.label]) {
newLabel = $scopedSlots.label(item);
let newLabel = typeof label === 'function' ? label() : label;
let newTitle = typeof title === 'function' ? title() : title;
if (!newLabel && slots.label && $slots[slots.label]) {
newLabel = $slots.label(item);
}
if (!newTitle && scopedSlots.title && $scopedSlots[scopedSlots.title]) {
newTitle = $scopedSlots.title(item);
if (!newTitle && slots.title && $slots[slots.title]) {
newTitle = $slots.title(item);
}
const treeNodeProps = {
...item,
@ -105,7 +105,7 @@ const TreeSelect = {
},
},
render(h) {
render() {
const props = getOptionProps(this);
const {
prefixCls: customizePrefixCls,
@ -115,13 +115,14 @@ const TreeSelect = {
getPopupContainer,
...restProps
} = props;
const { class: className } = this.$attrs;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('select', customizePrefixCls);
const renderEmpty = this.configProvider.renderEmpty;
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
const removeIcon = getComponentFromProp(this, 'removeIcon');
const clearIcon = getComponentFromProp(this, 'clearIcon');
const notFoundContent = getComponent(this, 'notFoundContent');
const removeIcon = getComponent(this, 'removeIcon');
const clearIcon = getComponent(this, 'clearIcon');
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const rest = omit(restProps, [
'inputIcon',
@ -130,7 +131,7 @@ const TreeSelect = {
'switcherIcon',
'suffixIcon',
]);
let suffixIcon = getComponentFromProp(this, 'suffixIcon');
let suffixIcon = getComponent(this, 'suffixIcon');
suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon;
let treeData = props.treeData;
if (treeData) {
@ -139,6 +140,7 @@ const TreeSelect = {
const cls = {
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
[className]: className,
};
// showSearch: single - false, multiple - true
@ -147,7 +149,7 @@ const TreeSelect = {
showSearch = !!(restProps.multiple || restProps.treeCheckable);
}
let checkable = getComponentFromProp(this, 'treeCheckable');
let checkable = getComponent(this, 'treeCheckable');
if (checkable) {
checkable = <span class={`${prefixCls}-tree-checkbox-inner`} />;
}
@ -160,7 +162,8 @@ const TreeSelect = {
<CloseCircleOutlined class={`${prefixCls}-clear-icon`} theme="filled" />
);
const VcTreeSelectProps = {
props: Object.assign(
...this.$attrs,
...Object.assign(
{
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, nodeProps),
inputIcon,
@ -173,25 +176,24 @@ const TreeSelect = {
prefixCls,
dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle },
treeCheckable: checkable,
notFoundContent: notFoundContent || renderEmpty(h, 'Select'),
notFoundContent: notFoundContent || renderEmpty('Select'),
__propsSymbol__: Symbol(),
},
treeData ? { treeData } : {},
),
class: cls,
on: { ...getListeners(this), change: this.onChange },
ref: 'vcTreeSelect',
scopedSlots: this.$scopedSlots,
onChange: this.onChange,
ref: this.saveTreeSelect,
children: getSlot(this),
};
return <VcTreeSelect {...VcTreeSelectProps}>{filterEmpty(this.$slots.default)}</VcTreeSelect>;
return <VcTreeSelect {...VcTreeSelectProps} vSlots={omit(this.$slots, ['default'])} />;
},
};
/* istanbul ignore next */
TreeSelect.install = function(Vue) {
Vue.use(Base);
Vue.component(TreeSelect.name, TreeSelect);
Vue.component(TreeSelect.TreeNode.name, TreeSelect.TreeNode);
TreeSelect.install = function(app) {
app.component(TreeSelect.name, TreeSelect);
app.component(TreeSelect.TreeNode.name, TreeSelect.TreeNode);
};
export default TreeSelect;

View File

@ -54,4 +54,6 @@ export const TreeSelectProps = () => ({
treeNodeFilterProp: PropTypes.string,
treeNodeLabelProp: PropTypes.string,
replaceFields: PropTypes.object.def({}),
clearIcon: PropTypes.any,
removeIcon: PropTypes.any,
});

View File

@ -1,10 +1,6 @@
// export this package's api
// base 2.9.3
import Vue from 'vue';
import TreeSelect from './src';
import ref from 'vue-ref';
Vue.use(ref, { name: 'ant-ref' });
export default TreeSelect;
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './src';

View File

@ -1,3 +1,4 @@
import { inject } from 'vue';
import warning from 'warning';
import PropTypes from '../../../_util/vue-types';
import { Tree } from '../../../vc-tree';
@ -69,6 +70,7 @@ function getDerivedState(nextProps, prevState) {
}
const BasePopup = {
mixins: [BaseMixin],
inheritAttrs: false,
name: 'BasePopup',
props: {
prefixCls: PropTypes.string,
@ -101,8 +103,10 @@ const BasePopup = {
__propsSymbol__: PropTypes.any,
},
inject: {
vcTreeSelect: { default: () => ({}) },
setup() {
return {
vcTreeSelect: inject('vcTreeSelect', {}),
};
},
watch: {
__propsSymbol__() {
@ -248,37 +252,27 @@ const BasePopup = {
$tree = $notFound;
} else {
const treeAllProps = {
props: {
prefixCls: `${prefixCls}-tree`,
showIcon: treeIcon,
showLine: treeLine,
selectable: !treeCheckable,
checkable: treeCheckable,
checkStrictly: treeCheckStrictly,
multiple,
loadData,
loadedKeys,
expandedKeys: expandedKeyList,
filterTreeNode: this.filterTreeNode,
switcherIcon,
...treeProps,
__propsSymbol__: Symbol(),
children: $treeNodes,
},
on: {
select: onTreeNodeSelect,
check: onTreeNodeCheck,
expand: this.onTreeExpand,
load: this.onLoad,
},
directives: [
{
name: 'ant-ref',
value: this.treeRef,
},
],
prefixCls: `${prefixCls}-tree`,
showIcon: treeIcon,
showLine: treeLine,
selectable: !treeCheckable,
checkable: treeCheckable,
checkStrictly: treeCheckStrictly,
multiple,
loadData,
loadedKeys,
expandedKeys: expandedKeyList,
filterTreeNode: this.filterTreeNode,
switcherIcon,
...treeProps,
__propsSymbol__: Symbol(),
children: $treeNodes,
onSelect: onTreeNodeSelect,
onCheck: onTreeNodeCheck,
onExpand: this.onTreeExpand,
onLoad: this.onLoad,
};
$tree = <Tree {...treeAllProps} />;
$tree = <Tree {...treeAllProps} ref={this.treeRef} />;
}
return (

View File

@ -5,14 +5,14 @@
*
* So this file named as Selector to avoid confuse.
*/
import { inject } from 'vue';
import { createRef } from '../util';
import PropTypes from '../../../_util/vue-types';
import classNames from 'classnames';
import { initDefaultProps, getComponentFromProp, getListeners } from '../../../_util/props-util';
import { initDefaultProps, getComponent } from '../../../_util/props-util';
import BaseMixin from '../../../_util/BaseMixin';
export const selectorPropTypes = () => ({
prefixCls: PropTypes.string,
className: PropTypes.string,
open: PropTypes.bool,
selectorValueList: PropTypes.array,
allowClear: PropTypes.bool,
@ -36,6 +36,7 @@ function noop() {}
export default function(modeName) {
const BaseSelector = {
name: 'BaseSelector',
inheritAttrs: false,
mixins: [BaseMixin],
props: initDefaultProps(
{
@ -50,8 +51,10 @@ export default function(modeName) {
tabindex: 0,
},
),
inject: {
vcTreeSelect: { default: () => ({}) },
setup() {
return {
vcTreeSelect: inject('vcTreeSelect', {}),
};
},
created() {
this.domRef = createRef();
@ -96,7 +99,7 @@ export default function(modeName) {
if (!allowClear || !selectorValueList.length || !selectorValueList[0].value) {
return null;
}
const clearIcon = getComponentFromProp(this, 'clearIcon');
const clearIcon = getComponent(this, 'clearIcon');
return (
<span key="clear" class={`${prefixCls}-selection__clear`} onClick={onSelectorClear}>
{clearIcon}
@ -109,7 +112,7 @@ export default function(modeName) {
if (!showArrow) {
return null;
}
const inputIcon = getComponentFromProp(this, 'inputIcon');
const inputIcon = getComponent(this, 'inputIcon');
return (
<span key="arrow" class={`${prefixCls}-arrow`} style={{ outline: 'none' }}>
{inputIcon}
@ -121,8 +124,6 @@ export default function(modeName) {
render() {
const {
prefixCls,
className,
style,
open,
focused,
disabled,
@ -132,6 +133,7 @@ export default function(modeName) {
renderPlaceholder,
tabindex,
} = this.$props;
const { class: className, style, onClick = noop } = this.$attrs;
const {
vcTreeSelect: { onSelectorKeyDown },
} = this;
@ -144,7 +146,7 @@ export default function(modeName) {
return (
<span
style={style}
onClick={getListeners(this).click || noop}
onClick={onClick}
class={classNames(className, prefixCls, {
[`${prefixCls}-open`]: open,
[`${prefixCls}-focused`]: open || focused,
@ -152,14 +154,7 @@ export default function(modeName) {
[`${prefixCls}-enabled`]: !disabled,
[`${prefixCls}-allow-clear`]: allowClear,
})}
{...{
directives: [
{
name: 'ant-ref',
value: this.domRef,
},
],
}}
ref={this.domRef}
role="combobox"
aria-expanded={open}
aria-owns={open ? ariaId : undefined}

View File

@ -2,10 +2,10 @@ import PropTypes from '../../../_util/vue-types';
import BasePopup from '../Base/BasePopup';
import SearchInput from '../SearchInput';
import { createRef } from '../util';
import { getListeners } from '../../../_util/props-util';
const SinglePopup = {
name: 'SinglePopup',
inheritAttrs: false,
props: {
...BasePopup.props,
...SearchInput.props,
@ -55,28 +55,10 @@ const SinglePopup = {
}
return (
<span
class={`${dropdownPrefixCls}-search`}
{...{
directives: [
{
name: 'ant-ref',
value: this.searchRef,
},
],
}}
>
<span class={`${dropdownPrefixCls}-search`} ref={this.searchRef}>
<SearchInput
{...{
props: { ...this.$props, renderPlaceholder: this._renderPlaceholder },
on: getListeners(this),
directives: [
{
name: 'ant-ref',
value: this.inputRef,
},
],
}}
{...{ ...this.$props, ...this.$attrs, renderPlaceholder: this._renderPlaceholder }}
ref={this.inputRef}
/>
</span>
);
@ -86,15 +68,12 @@ const SinglePopup = {
return (
<BasePopup
{...{
props: { ...this.$props, renderSearch: this._renderSearch, __propsSymbol__: Symbol() },
on: getListeners(this),
directives: [
{
name: 'ant-ref',
value: this.popupRef,
},
],
...this.$props,
...this.$attrs,
renderSearch: this._renderSearch,
__propsSymbol__: Symbol(),
}}
ref={this.popupRef}
/>
);
},

View File

@ -4,12 +4,13 @@
* - multiple: in the selector
* Move the code as a SearchInput for easy management.
*/
import { inject } from 'vue';
import PropTypes from '../../_util/vue-types';
import { createRef } from './util';
const SearchInput = {
name: 'SearchInput',
inheritAttrs: false,
props: {
open: PropTypes.bool,
searchValue: PropTypes.string,
@ -19,8 +20,10 @@ const SearchInput = {
needAlign: PropTypes.bool,
ariaId: PropTypes.string,
},
inject: {
vcTreeSelect: { default: () => ({}) },
setup() {
return {
vcTreeSelect: inject('vcTreeSelect', {}),
};
},
data() {
return {
@ -117,17 +120,7 @@ const SearchInput = {
<span class={`${prefixCls}-search__field__wrap`}>
<input
type="text"
{...{
directives: [
{
name: 'ant-ref',
value: this.inputRef,
},
{
name: 'ant-input',
},
],
}}
ref={this.inputRef}
onInput={handleInputChange}
onKeydown={onSearchInputKeyDown}
value={searchValue}
@ -138,17 +131,7 @@ const SearchInput = {
aria-controls={open ? ariaId : undefined}
aria-multiline="false"
/>
<span
{...{
directives: [
{
name: 'ant-ref',
value: this.mirrorInputRef,
},
],
}}
class={`${prefixCls}-search__field__mirror`}
>
<span ref={this.mirrorInputRef} class={`${prefixCls}-search__field__mirror`}>
{mirrorSearchValue}&nbsp;
</span>
{renderPlaceholder && !mirrorSearchValue ? renderPlaceholder() : null}

View File

@ -18,7 +18,7 @@
* In single mode, we should focus on the `span`
* In multiple mode, we should focus on the `input`
*/
import { provide } from 'vue';
import shallowEqual from 'shallowequal';
import raf from 'raf';
import scrollIntoView from 'dom-scroll-into-view';
@ -55,10 +55,8 @@ import SelectNode from './SelectNode';
import {
initDefaultProps,
getOptionProps,
mergeProps,
getPropsData,
filterEmpty,
getListeners,
findDOMNode,
} from '../../_util/props-util';
function getWatch(keys = []) {
const watch = {};
@ -71,6 +69,7 @@ function getWatch(keys = []) {
}
const Select = {
name: 'Select',
inheritAttrs: false,
mixins: [BaseMixin],
props: initDefaultProps(
{
@ -193,24 +192,21 @@ const Select = {
...newState,
};
},
created() {
provide('vcTreeSelect', {
onSelectorFocus: this.onSelectorFocus,
onSelectorBlur: this.onSelectorBlur,
onSelectorKeyDown: this.onComponentKeyDown,
onSelectorClear: this.onSelectorClear,
onMultipleSelectorRemove: this.onMultipleSelectorRemove,
provide() {
return {
vcTreeSelect: {
onSelectorFocus: this.onSelectorFocus,
onSelectorBlur: this.onSelectorBlur,
onSelectorKeyDown: this.onComponentKeyDown,
onSelectorClear: this.onSelectorClear,
onMultipleSelectorRemove: this.onMultipleSelectorRemove,
onTreeNodeSelect: this.onTreeNodeSelect,
onTreeNodeCheck: this.onTreeNodeCheck,
onPopupKeyDown: this.onComponentKeyDown,
onTreeNodeSelect: this.onTreeNodeSelect,
onTreeNodeCheck: this.onTreeNodeCheck,
onPopupKeyDown: this.onComponentKeyDown,
onSearchInputChange: this.onSearchInputChange,
onSearchInputKeyDown: this.onSearchInputKeyDown,
},
};
onSearchInputChange: this.onSearchInputChange,
onSearchInputKeyDown: this.onSearchInputKeyDown,
});
},
watch: {
...getWatch(['treeData', 'defaultValue', 'value']),
@ -238,7 +234,7 @@ const Select = {
const treeNode = domTreeNodes[key];
if (treeNode) {
const domNode = treeNode.$el;
const domNode = findDOMNode(treeNode);
raf(() => {
const popupNode = this.popup.$el;
const triggerContainer = findPopupContainer(popupNode, `${prefixCls}-dropdown`);
@ -267,7 +263,6 @@ const Select = {
methods: {
getDerivedState(nextProps, prevState) {
const h = this.$createElement;
const { _prevProps: prevProps = {} } = prevState;
const {
treeCheckable,
@ -302,7 +297,7 @@ const Select = {
let treeDataChanged = false;
let treeDataModeChanged = false;
processState('treeData', propValue => {
treeNodes = convertDataToTree(h, propValue);
treeNodes = convertDataToTree(propValue);
treeDataChanged = true;
});
@ -325,7 +320,7 @@ const Select = {
rootPId: null,
...(treeDataSimpleMode !== true ? treeDataSimpleMode : {}),
};
treeNodes = convertDataToTree(h, parseSimpleTreeData(nextProps.treeData, simpleMapper));
treeNodes = convertDataToTree(parseSimpleTreeData(nextProps.treeData, simpleMapper));
}
// If `treeData` not provide, use children TreeNodes
@ -333,7 +328,7 @@ const Select = {
// processState('children', (propValue) => {
// treeNodes = Array.isArray(propValue) ? propValue : [propValue]
// })
treeNodes = filterEmpty(this.$slots.default);
treeNodes = this.children || [];
}
// Convert `treeData` to entities
@ -462,7 +457,6 @@ const Select = {
}
newState._filteredTreeNodes = getFilterTree(
this.$createElement,
newState._treeNodes || prevState._treeNodes,
searchValue,
filterTreeNodeFn,
@ -868,7 +862,6 @@ const Select = {
this.setState({
_filteredTreeNodes: getFilterTree(
this.$createElement,
treeNodes,
value,
filterTreeNodeFn,
@ -1012,7 +1005,7 @@ const Select = {
}
// Only do the logic when `onChange` function provided
if (getListeners(this).change) {
if (this.$attrs.onChange) {
let connectValueList;
// Get value by mode
@ -1038,6 +1031,7 @@ const Select = {
if (!this.isMultiple()) {
returnValue = returnValue[0];
}
// this.__emit('update:value', returnValue);
this.__emit('change', returnValue, labelList, extra);
}
},
@ -1072,75 +1066,45 @@ const Select = {
const isMultiple = this.isMultiple();
const passProps = {
props: {
...props,
isMultiple,
valueList,
searchHalfCheckedKeys,
selectorValueList: [...missValueList, ...selectorValueList],
valueEntities,
keyEntities,
searchValue,
upperSearchValue: (searchValue || '').toUpperCase(), // Perf save
open,
focused,
dropdownPrefixCls: `${prefixCls}-dropdown`,
ariaId: this.ariaId,
},
on: {
...getListeners(this),
choiceAnimationLeave: this.onChoiceAnimationLeave,
},
scopedSlots: this.$scopedSlots,
...props,
...this.$attrs,
isMultiple,
valueList,
searchHalfCheckedKeys,
selectorValueList: [...missValueList, ...selectorValueList],
valueEntities,
keyEntities,
searchValue,
upperSearchValue: (searchValue || '').toUpperCase(), // Perf save
open,
focused,
dropdownPrefixCls: `${prefixCls}-dropdown`,
ariaId: this.ariaId,
onChoiceAnimationLeave: this.onChoiceAnimationLeave,
vSlots: this.$slots,
};
const popupProps = {
...passProps,
treeNodes,
filteredTreeNodes,
// Tree expanded control
treeExpandedKeys,
__propsSymbol__: Symbol(),
onTreeExpanded: this.delayForcePopupAlign,
ref: this.setPopupRef,
};
const popupProps = mergeProps(passProps, {
props: {
treeNodes,
filteredTreeNodes,
// Tree expanded control
treeExpandedKeys,
__propsSymbol__: Symbol(),
},
on: {
treeExpanded: this.delayForcePopupAlign,
},
directives: [
{
name: 'ant-ref',
value: this.setPopupRef,
},
],
});
const Popup = isMultiple ? MultiplePopup : SinglePopup;
const $popup = <Popup {...popupProps} />;
const Selector = isMultiple ? MultipleSelector : SingleSelector;
const $selector = (
<Selector
{...passProps}
{...{
directives: [
{
name: 'ant-ref',
value: this.selectorRef,
},
],
}}
/>
);
const selectTriggerProps = mergeProps(passProps, {
props: {
popupElement: $popup,
dropdownVisibleChange: this.onDropdownVisibleChange,
},
directives: [
{
name: 'ant-ref',
value: this.selectTriggerRef,
},
],
});
const $selector = <Selector {...passProps} ref={this.selectorRef} />;
const selectTriggerProps = {
...passProps,
popupElement: $popup,
dropdownVisibleChange: this.onDropdownVisibleChange,
ref: this.selectTriggerRef,
};
return <SelectTrigger {...selectTriggerProps}>{$selector}</SelectTrigger>;
},
};

View File

@ -4,29 +4,10 @@ import { TreeNode } from '../../vc-tree';
* Let's use SelectNode instead of TreeNode
* since TreeNode is so confuse here.
*/
export default {
name: 'SelectNode',
functional: true,
isTreeNode: true,
props: TreeNode.props,
render(h, context) {
const { props, slots, listeners, data, scopedSlots } = context;
const $slots = slots() || {};
const children = $slots.default;
const slotsKey = Object.keys($slots);
const scopedSlotsTemp = {}; // for vue 2.5.x
slotsKey.forEach(name => {
scopedSlotsTemp[name] = () => $slots[name];
});
const treeNodeProps = {
...data,
on: { ...listeners, ...data.nativeOn },
props,
scopedSlots: {
...scopedSlotsTemp,
...scopedSlots,
},
};
return <TreeNode {...treeNodeProps}>{children}</TreeNode>;
},
};
function SelectNode(_, { attrs, slots }) {
return <TreeNode {...attrs} vSlots={slots} />;
}
SelectNode.isTreeNode = true;
SelectNode.inheritAttrs = false;
export default SelectNode;

View File

@ -1,8 +1,8 @@
import PropTypes from '../../_util/vue-types';
import Trigger from '../../vc-trigger';
import { createRef } from './util';
import classNames from 'classnames';
import { getSlot } from '../../_util/props-util';
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
@ -27,6 +27,7 @@ const BUILT_IN_PLACEMENTS = {
const SelectTrigger = {
name: 'SelectTrigger',
inheritAttrs: false,
props: {
// Pass by outside user props
disabled: PropTypes.bool,
@ -93,14 +94,7 @@ const SelectTrigger = {
}
return (
<Trigger
{...{
directives: [
{
name: 'ant-ref',
value: this.triggerRef,
},
],
}}
ref={this.triggerRef}
action={disabled ? [] : ['click']}
popupPlacement="bottomLeft"
builtinPlacements={BUILT_IN_PLACEMENTS}
@ -118,7 +112,7 @@ const SelectTrigger = {
})}
popupStyle={dropdownStyle}
>
{this.$slots.default}
{getSlot(this)}
</Trigger>
);
},

View File

@ -1,10 +1,12 @@
import classNames from 'classnames';
import PropTypes from '../../../../_util/vue-types';
import { toTitle, UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE } from '../../util';
import { getComponentFromProp, getListeners } from '../../../../_util/props-util';
import { getComponent } from '../../../../_util/props-util';
import BaseMixin from '../../../../_util/BaseMixin';
const Selection = {
mixins: [BaseMixin],
inheritAttrs: false,
props: {
prefixCls: PropTypes.string,
maxTagTextLength: PropTypes.number,
@ -28,18 +30,18 @@ const Selection = {
if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
content = `${content.slice(0, maxTagTextLength)}...`;
}
const { class: className, style, onRemove } = this.$attrs;
return (
<li
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
style={{ ...UNSELECTABLE_STYLE, ...style }}
{...UNSELECTABLE_ATTRIBUTE}
role="menuitem"
class={`${prefixCls}-selection__choice`}
class={classNames(`${prefixCls}-selection__choice`, className)}
title={toTitle(label)}
>
{getListeners(this).remove && (
{onRemove && (
<span class={`${prefixCls}-selection__choice__remove`} onClick={this.onRemove}>
{getComponentFromProp(this, 'removeIcon')}
{getComponent(this, 'removeIcon')}
</span>
)}
<span class={`${prefixCls}-selection__choice__content`}>{content}</span>

View File

@ -1,9 +1,10 @@
import { inject, TransitionGroup } from 'vue';
import PropTypes from '../../../../_util/vue-types';
import { createRef } from '../../util';
import generateSelector, { selectorPropTypes } from '../../Base/BaseSelector';
import SearchInput from '../../SearchInput';
import Selection from './Selection';
import { getComponentFromProp, getListeners } from '../../../../_util/props-util';
import { getComponent, getSlot } from '../../../../_util/props-util';
import getTransitionProps from '../../../../_util/getTransitionProps';
import BaseMixin from '../../../../_util/BaseMixin';
const TREE_SELECT_EMPTY_VALUE_KEY = 'RC_TREE_SELECT_EMPTY_VALUE_KEY';
@ -15,7 +16,9 @@ const Selector = generateSelector('multiple');
// }
const MultipleSelector = {
name: 'MultipleSelector',
mixins: [BaseMixin],
inheritAttrs: false,
props: {
...selectorPropTypes(),
...SearchInput.props,
@ -28,8 +31,10 @@ const MultipleSelector = {
// onChoiceAnimationLeave: PropTypes.func,
},
inject: {
vcTreeSelect: { default: () => ({}) },
setup() {
return {
vcTreeSelect: inject('vcTreeSelect', {}),
};
},
created() {
this.inputRef = createRef();
@ -85,11 +90,10 @@ const MultipleSelector = {
labelInValue,
maxTagCount,
} = this.$props;
const children = getSlot(this);
const {
vcTreeSelect: { onMultipleSelectorRemove },
$slots,
} = this;
const listeners = getListeners(this);
// Check if `maxTagCount` is set
let myValueList = selectorValueList;
if (maxTagCount >= 0) {
@ -99,23 +103,21 @@ const MultipleSelector = {
const selectedValueNodes = myValueList.map(({ label, value }) => (
<Selection
{...{
props: {
...this.$props,
label,
value,
},
on: { ...listeners, remove: onMultipleSelectorRemove },
...this.$props,
label,
value,
onRemove: onMultipleSelectorRemove,
}}
key={value || TREE_SELECT_EMPTY_VALUE_KEY}
>
{$slots.default}
{children}
</Selection>
));
// Rest node count
if (maxTagCount >= 0 && maxTagCount < selectorValueList.length) {
let content = `+ ${selectorValueList.length - maxTagCount} ...`;
const maxTagPlaceholder = getComponentFromProp(this, 'maxTagPlaceholder', {}, false);
const maxTagPlaceholder = getComponent(this, 'maxTagPlaceholder', {}, false);
if (typeof maxTagPlaceholder === 'string') {
content = maxTagPlaceholder;
} else if (typeof maxTagPlaceholder === 'function') {
@ -128,16 +130,13 @@ const MultipleSelector = {
const restNodeSelect = (
<Selection
{...{
props: {
...this.$props,
label: content,
value: null,
},
on: listeners,
...this.$props,
label: content,
value: null,
}}
key="rc-tree-select-internal-max-tag-counter"
>
{$slots.default}
{children}
</Selection>
);
@ -148,20 +147,13 @@ const MultipleSelector = {
<li class={`${prefixCls}-search ${prefixCls}-search--inline`} key="__input">
<SearchInput
{...{
props: {
...this.$props,
needAlign: true,
},
on: listeners,
directives: [
{
name: 'ant-ref',
value: this.inputRef,
},
],
...this.$props,
...this.$attrs,
needAlign: true,
}}
ref={this.inputRef}
>
{$slots.default}
{children}
</SearchInput>
</li>,
);
@ -169,12 +161,12 @@ const MultipleSelector = {
if (choiceTransitionName) {
const transitionProps = getTransitionProps(choiceTransitionName, {
tag: 'ul',
afterLeave: this.onChoiceAnimationLeave,
onAfterLeave: this.onChoiceAnimationLeave,
});
return (
<transition-group class={className} {...transitionProps}>
<TransitionGroup class={className} {...transitionProps}>
{selectedValueNodes}
</transition-group>
</TransitionGroup>
);
}
return (
@ -186,22 +178,18 @@ const MultipleSelector = {
},
render() {
const { $slots } = this;
const listeners = getListeners(this);
return (
<Selector
{...{
props: {
...this.$props,
tabindex: -1,
showArrow: false,
renderSelection: this.renderSelection,
renderPlaceholder: this._renderPlaceholder,
},
on: listeners,
...this.$props,
...this.$attrs,
tabindex: -1,
showArrow: false,
renderSelection: this.renderSelection,
renderPlaceholder: this._renderPlaceholder,
}}
>
{$slots.default}
{getSlot(this)}
</Selector>
);
},

View File

@ -1,11 +1,12 @@
import generateSelector, { selectorPropTypes } from '../Base/BaseSelector';
import { toTitle } from '../util';
import { getOptionProps, getListeners } from '../../../_util/props-util';
import { getOptionProps } from '../../../_util/props-util';
import { createRef } from '../util';
const Selector = generateSelector('single');
const SingleSelector = {
name: 'SingleSelector',
inheritAttrs: false,
props: selectorPropTypes(),
created() {
this.selectorRef = createRef();
@ -43,17 +44,10 @@ const SingleSelector = {
render() {
const props = {
props: {
...getOptionProps(this),
renderSelection: this.renderSelection,
},
on: getListeners(this),
directives: [
{
name: 'ant-ref',
value: this.selectorRef,
},
],
...getOptionProps(this),
...this.$attrs,
renderSelection: this.renderSelection,
ref: this.selectorRef,
};
return <Selector {...props} />;
},

View File

@ -1,5 +1,4 @@
import warning from 'warning';
import omit from 'omit.js';
import {
convertDataToTree as vcConvertDataToTree,
convertTreeToEntities as vcConvertTreeToEntities,
@ -7,7 +6,7 @@ import {
} from '../../vc-tree/src/util';
import { hasClass } from '../../vc-util/Dom/class';
import { SHOW_CHILD, SHOW_PARENT } from './strategies';
import { getSlots, getPropsData, isEmptyElement } from '../../_util/props-util';
import { getSlot, getPropsData, isEmptyElement } from '../../_util/props-util';
let warnDeprecatedLabel = false;
@ -203,7 +202,7 @@ export function cleanEntity({ node, pos, children }) {
* we have to convert `treeNodes > data > treeNodes` to keep the key.
* Such performance hungry!
*/
export function getFilterTree(h, treeNodes, searchValue, filterFunc, valueEntities, Component) {
export function getFilterTree(treeNodes, searchValue, filterFunc, valueEntities, Component) {
if (!searchValue) {
return null;
}
@ -215,13 +214,13 @@ export function getFilterTree(h, treeNodes, searchValue, filterFunc, valueEntiti
if (filterFunc(searchValue, node)) {
match = true;
}
let children = getSlots(node).default;
let children = getSlot(node);
children = ((typeof children === 'function' ? children() : children) || [])
.map(mapFilteredNodeToData)
.filter(n => n);
if (children.length || match) {
return (
<Component {...node.data} key={valueEntities[getPropsData(node).value].key}>
<Component {...node.props} key={valueEntities[getPropsData(node).value].key}>
{children}
</Component>
);
@ -341,18 +340,8 @@ export function formatSelectorValue(valueList, props, valueEntities) {
* This will change the label to title value
*/
function processProps(props) {
const { title, label, value, class: cls, style, on = {} } = props;
let key = props.key;
if (!key && (key === undefined || key === null)) {
key = value;
}
const p = {
props: omit(props, ['on', 'key', 'class', 'className', 'style']),
on,
class: cls || props.className,
style,
key,
};
const { title, label, key, value } = props;
const cloneProps = { ...props };
// Warning user not to use deprecated label prop.
if (label && !title) {
if (!warnDeprecatedLabel) {
@ -360,14 +349,18 @@ function processProps(props) {
warnDeprecatedLabel = true;
}
p.props.title = label;
cloneProps.title = label;
}
return p;
if (!key && (key === undefined || key === null)) {
cloneProps.key = value;
}
return cloneProps;
}
export function convertDataToTree(h, treeData) {
return vcConvertDataToTree(h, treeData, { processProps });
export function convertDataToTree(treeData) {
return vcConvertDataToTree(treeData, { processProps });
}
/**

View File

@ -40,7 +40,7 @@ export function getPosition(level, index) {
}
export function isTreeNode(node) {
return typeof node.type === 'object' && node.type.isTreeNode;
return node.type && node.type.isTreeNode;
}
export function getNodeChildren(children = []) {

View File

@ -92,6 +92,7 @@ export default {
};
});
this._component = null;
this.focusTime = null;
return {
prevPopupVisible: popupVisible,
sPopupVisible: popupVisible,

View File

@ -4,7 +4,7 @@
</div>
</template>
<script>
import demo from '../antdv-demo/docs/tree/demo/directory';
import demo from '../antdv-demo/docs/tree-select/demo/treeData';
export default {
components: {

View File

@ -28,6 +28,7 @@ import {
Card,
Avatar,
Tree,
TreeSelect,
notification,
message,
} from 'ant-design-vue';
@ -45,11 +46,11 @@ const app = createApp(App);
app.config.globalProperties.$notification = notification;
app.config.globalProperties.$message = message;
app
.component('demo-sort', { ...basic })
.component('md', { ...basic })
.component('api', { ...basic })
.component('CN', { ...basic })
.component('US', { ...basic })
// .component('demo-sort', { ...basic })
// .component('md', { ...basic })
// .component('api', { ...basic })
// .component('CN', { ...basic })
// .component('US', { ...basic })
.use(Pagination)
.use(Select)
.use(Spin)
@ -76,4 +77,5 @@ app
.use(Avatar)
.use(Card)
.use(Tree)
.use(TreeSelect)
.mount('#app');