mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-12-16 01:41:15 +08:00
73bef787cd
* feat: add Result component * fix: update md template tag html>tpl - fix `result` typo - update jest `result` snapshots * refactor: svg file to functional component icon - update jest snapshot * feat: add result * Feat descriptions (#1251) * feat: add descriptions * fix: add descriptions types and fix docs * fix: lint change code * fix: demo warning * fix: update demo, snapshot and remove classnames * test: add descriptions test * fix: descriptions demo (#1498) * feat: add page header (#1250) * feat: add page-header component * update site: page-header * ts definition update: page-header * get page-header props with getComponentFromProp func * optimize page-header * doc: add page-header actions.md responsive.md * breadcrumb itemRender add pure function support * style: format code * feat: update style to 3.23.6 from 2.13.6 * feat: update style to 3.26.8 from 3.23.6 * chore: update util * chore: update util * feat: update affix * feat: update alert * feat: update anchor * feat: update auto-complete * feat: update avatar * feat: update back-top * feat: update badge * feat: update button * feat: update breadcrumb * feat: update ts * docs: update doc * feat: update calendat * feat: update card * feat: update carousel * feat: update carousel * feat: update checkbox * feat: update comment * feat: update config-provider * docs: update doc * feat: update collapse * feat: update locale * feat: update date-picker * feat: update divider * feat: update drawer * feat: update dropdown * feat: update rc-trigger * feat: update dropdown * feat: update empty * test: add empty test * feat: update form * feat: update form * feat: update spin * feat: update grid * docs: update grid doc * feat: update icon * feat: update slider * feat: update textarea * feat: update input-number * feat: update layout * feat: update list * feat: update menu * feat: meaage add key for update content * feat: modal add closeIcon support * feat: update notification * feat: add pagination disabled support * feat: popconfirm add disabled support * test: update popover * feat: progress support custom line-gradiend * feat: update radio * test: update radio test * docs: update rate demo * feat: skeleton add avatar support number type * test: add switch test * test: update statistic test * fix: input clear icon event * feat: steps add type、 v-model、subTitle * feat: delete typography component * feat: delete Typography style * perf: update select * feat: add download transformFile previewFile actio * docs: update upload * feat: update tree-select * docs: update tree-select * feat: tree add blockNode selectable * docs: add tree demo * test: update snap * docs: updatedoc * feat: update tag * docs: update ad doc * feat: update tooltip * feat: update timeline * feat: time-picker add clearIcon * docs: update tabs * feat: transfer support custom children * test: update transfer test * feat: update table * test: update table test * test: update test * feat: calendar update locale * test: update test snap * feat: add mentions (#1790) * feat: mentions style * feat: theme default * feat: add mentions component * feat: mentions API * feat: add unit test for mentions * feat: update mentions demo * perf: model and inheritAttrs for mentions * perf: use getComponentFromProp instead of this.$props * perf: mentions rm defaultProps * feat: rm rows in mentionsProps * fix: mentions keyDown didn't work * docs: update mentions api * perf: mentions code * feat: update mentions * bump 1.5.0-alpha.1 * feat: pageheader add ghost prop * docs: update descriptions demo * chore: page-header add ghost type * fix: color error * feat: update to 3.26.12 * fix: some prop default value * fix(typo): form, carousel, upload. duplicate identifier (#1848) * Add Mentions Type (#1845) * feat: add mentions type * feat: add mentions in ant-design-vue.d.ts * docs: update doc * docs: add changelog * fix: mentions getPopupCotainer value (#1850) * docs: update doc * docs: uptate demo * docs: update demo * docs: delete demo * docs: delete doc * test: update snapshots * style: format code * chore: update travis * docs: update demo Co-authored-by: Sendya <18x@loacg.com> Co-authored-by: zkwolf <chenhao5866@gmail.com> Co-authored-by: drafish <xwlyy1991@163.com> Co-authored-by: Amour1688 <31695475+Amour1688@users.noreply.github.com>
488 lines
15 KiB
Vue
488 lines
15 KiB
Vue
import PropTypes from '../_util/vue-types';
|
||
import {
|
||
hasProp,
|
||
initDefaultProps,
|
||
getOptionProps,
|
||
getComponentFromProp,
|
||
getListeners,
|
||
} from '../_util/props-util';
|
||
import BaseMixin from '../_util/BaseMixin';
|
||
import classNames from 'classnames';
|
||
import List from './list';
|
||
import Operation from './operation';
|
||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||
import defaultLocale from '../locale-provider/default';
|
||
import { ConfigConsumerProps } from '../config-provider';
|
||
import warning from '../_util/warning';
|
||
import Base from '../base';
|
||
|
||
export const TransferDirection = 'left' | 'right';
|
||
|
||
export const TransferItem = {
|
||
key: PropTypes.string.isRequired,
|
||
title: PropTypes.string.isRequired,
|
||
description: PropTypes.string,
|
||
disabled: PropTypes.bool,
|
||
};
|
||
|
||
export const TransferProps = {
|
||
prefixCls: PropTypes.string,
|
||
dataSource: PropTypes.arrayOf(PropTypes.shape(TransferItem).loose),
|
||
disabled: PropTypes.boolean,
|
||
targetKeys: PropTypes.arrayOf(PropTypes.string),
|
||
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||
render: PropTypes.func,
|
||
listStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
||
operationStyle: PropTypes.object,
|
||
titles: PropTypes.arrayOf(PropTypes.string),
|
||
operations: PropTypes.arrayOf(PropTypes.string),
|
||
showSearch: PropTypes.bool,
|
||
filterOption: PropTypes.func,
|
||
searchPlaceholder: PropTypes.string,
|
||
notFoundContent: PropTypes.any,
|
||
locale: PropTypes.object,
|
||
rowKey: PropTypes.func,
|
||
lazy: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
|
||
showSelectAll: PropTypes.bool,
|
||
};
|
||
|
||
export const TransferLocale = {
|
||
titles: PropTypes.arrayOf(PropTypes.string),
|
||
notFoundContent: PropTypes.string,
|
||
itemUnit: PropTypes.string,
|
||
itemsUnit: PropTypes.string,
|
||
};
|
||
|
||
const Transfer = {
|
||
name: 'ATransfer',
|
||
mixins: [BaseMixin],
|
||
props: initDefaultProps(TransferProps, {
|
||
dataSource: [],
|
||
locale: {},
|
||
showSearch: false,
|
||
listStyle: () => {},
|
||
}),
|
||
inject: {
|
||
configProvider: { default: () => ConfigConsumerProps },
|
||
},
|
||
data() {
|
||
// vue 中 通过slot,不方便传递,保留notFoundContent及searchPlaceholder
|
||
// warning(
|
||
// !(getComponentFromProp(this, 'notFoundContent') || hasProp(this, 'searchPlaceholder')),
|
||
// 'Transfer[notFoundContent] and Transfer[searchPlaceholder] will be removed, ' +
|
||
// 'please use Transfer[locale] instead.',
|
||
// )
|
||
const { selectedKeys = [], targetKeys = [] } = this;
|
||
return {
|
||
leftFilter: '',
|
||
rightFilter: '',
|
||
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
||
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
||
};
|
||
},
|
||
mounted() {
|
||
// this.currentProps = { ...this.$props }
|
||
},
|
||
watch: {
|
||
targetKeys() {
|
||
this.updateState();
|
||
if (this.selectedKeys) {
|
||
const targetKeys = this.targetKeys || [];
|
||
this.setState({
|
||
sourceSelectedKeys: this.selectedKeys.filter(key => !targetKeys.includes(key)),
|
||
targetSelectedKeys: this.selectedKeys.filter(key => targetKeys.includes(key)),
|
||
});
|
||
}
|
||
},
|
||
dataSource() {
|
||
this.updateState();
|
||
},
|
||
selectedKeys() {
|
||
if (this.selectedKeys) {
|
||
const targetKeys = this.targetKeys || [];
|
||
this.setState({
|
||
sourceSelectedKeys: this.selectedKeys.filter(key => !targetKeys.includes(key)),
|
||
targetSelectedKeys: this.selectedKeys.filter(key => targetKeys.includes(key)),
|
||
});
|
||
}
|
||
},
|
||
},
|
||
methods: {
|
||
getSelectedKeysName(direction) {
|
||
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
|
||
},
|
||
|
||
getTitles(transferLocale) {
|
||
if (this.titles) {
|
||
return this.titles;
|
||
}
|
||
return transferLocale.titles || ['', ''];
|
||
},
|
||
|
||
getLocale(transferLocale, renderEmpty) {
|
||
const h = this.$createElement;
|
||
// Keep old locale props still working.
|
||
const oldLocale = {
|
||
notFoundContent: renderEmpty(h, 'Transfer'),
|
||
};
|
||
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
|
||
if (notFoundContent) {
|
||
oldLocale.notFoundContent = notFoundContent;
|
||
}
|
||
if (hasProp(this, 'searchPlaceholder')) {
|
||
oldLocale.searchPlaceholder = this.$props.searchPlaceholder;
|
||
}
|
||
|
||
return { ...transferLocale, ...oldLocale, ...this.$props.locale };
|
||
},
|
||
updateState() {
|
||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
||
this.separatedDataSource = null;
|
||
if (!this.selectedKeys) {
|
||
// clear key nolonger existed
|
||
// clear checkedKeys according to targetKeys
|
||
const { dataSource, targetKeys = [] } = this;
|
||
|
||
const newSourceSelectedKeys = [];
|
||
const newTargetSelectedKeys = [];
|
||
dataSource.forEach(({ key }) => {
|
||
if (sourceSelectedKeys.includes(key) && !targetKeys.includes(key)) {
|
||
newSourceSelectedKeys.push(key);
|
||
}
|
||
if (targetSelectedKeys.includes(key) && targetKeys.includes(key)) {
|
||
newTargetSelectedKeys.push(key);
|
||
}
|
||
});
|
||
this.setState({
|
||
sourceSelectedKeys: newSourceSelectedKeys,
|
||
targetSelectedKeys: newTargetSelectedKeys,
|
||
});
|
||
}
|
||
},
|
||
|
||
moveTo(direction) {
|
||
const { targetKeys = [], dataSource = [] } = this.$props;
|
||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
||
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
|
||
// filter the disabled options
|
||
const newMoveKeys = moveKeys.filter(
|
||
key => !dataSource.some(data => !!(key === data.key && data.disabled)),
|
||
);
|
||
// move items to target box
|
||
const newTargetKeys =
|
||
direction === 'right'
|
||
? newMoveKeys.concat(targetKeys)
|
||
: targetKeys.filter(targetKey => newMoveKeys.indexOf(targetKey) === -1);
|
||
|
||
// empty checked keys
|
||
const oppositeDirection = direction === 'right' ? 'left' : 'right';
|
||
this.setState({
|
||
[this.getSelectedKeysName(oppositeDirection)]: [],
|
||
});
|
||
this.handleSelectChange(oppositeDirection, []);
|
||
|
||
this.$emit('change', newTargetKeys, direction, newMoveKeys);
|
||
},
|
||
moveToLeft() {
|
||
this.moveTo('left');
|
||
},
|
||
moveToRight() {
|
||
this.moveTo('right');
|
||
},
|
||
|
||
onItemSelectAll(direction, selectedKeys, checkAll) {
|
||
const originalSelectedKeys = this.$data[this.getSelectedKeysName(direction)] || [];
|
||
|
||
let mergedCheckedKeys = [];
|
||
if (checkAll) {
|
||
// Merge current keys with origin key
|
||
mergedCheckedKeys = Array.from(new Set([...originalSelectedKeys, ...selectedKeys]));
|
||
} else {
|
||
// Remove current keys from origin keys
|
||
mergedCheckedKeys = originalSelectedKeys.filter(key => selectedKeys.indexOf(key) === -1);
|
||
}
|
||
|
||
this.handleSelectChange(direction, mergedCheckedKeys);
|
||
|
||
if (!this.$props.selectedKeys) {
|
||
this.setState({
|
||
[this.getSelectedKeysName(direction)]: mergedCheckedKeys,
|
||
});
|
||
}
|
||
},
|
||
|
||
handleSelectAll(direction, filteredDataSource, checkAll) {
|
||
this.onItemSelectAll(
|
||
direction,
|
||
filteredDataSource.map(({ key }) => key),
|
||
!checkAll,
|
||
);
|
||
},
|
||
|
||
// [Legacy] Old prop `body` pass origin check as arg. It's confusing.
|
||
// TODO: Remove this in next version.
|
||
handleLeftSelectAll(filteredDataSource, checkAll) {
|
||
return this.handleSelectAll('left', filteredDataSource, !checkAll);
|
||
},
|
||
|
||
handleRightSelectAll(filteredDataSource, checkAll) {
|
||
return this.handleSelectAll('right', filteredDataSource, !checkAll);
|
||
},
|
||
|
||
onLeftItemSelectAll(selectedKeys, checkAll) {
|
||
return this.onItemSelectAll('left', selectedKeys, checkAll);
|
||
},
|
||
|
||
onRightItemSelectAll(selectedKeys, checkAll) {
|
||
return this.onItemSelectAll('right', selectedKeys, checkAll);
|
||
},
|
||
|
||
handleFilter(direction, e) {
|
||
const value = e.target.value;
|
||
if (getListeners(this).searchChange) {
|
||
warning(
|
||
false,
|
||
'Transfer',
|
||
'`searchChange` in Transfer is deprecated. Please use `search` instead.',
|
||
);
|
||
this.$emit('searchChange', direction, e);
|
||
}
|
||
this.$emit('search', direction, value);
|
||
},
|
||
|
||
handleLeftFilter(e) {
|
||
this.handleFilter('left', e);
|
||
},
|
||
handleRightFilter(e) {
|
||
this.handleFilter('right', e);
|
||
},
|
||
|
||
handleClear(direction) {
|
||
this.$emit('search', direction, '');
|
||
},
|
||
|
||
handleLeftClear() {
|
||
this.handleClear('left');
|
||
},
|
||
handleRightClear() {
|
||
this.handleClear('right');
|
||
},
|
||
|
||
onItemSelect(direction, selectedKey, checked) {
|
||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
||
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
|
||
const index = holder.indexOf(selectedKey);
|
||
if (index > -1) {
|
||
holder.splice(index, 1);
|
||
}
|
||
if (checked) {
|
||
holder.push(selectedKey);
|
||
}
|
||
this.handleSelectChange(direction, holder);
|
||
|
||
if (!this.selectedKeys) {
|
||
this.setState({
|
||
[this.getSelectedKeysName(direction)]: holder,
|
||
});
|
||
}
|
||
},
|
||
|
||
handleSelect(direction, selectedItem, checked) {
|
||
warning(false, 'Transfer', '`handleSelect` will be removed, please use `onSelect` instead.');
|
||
this.onItemSelect(direction, selectedItem.key, checked);
|
||
},
|
||
|
||
handleLeftSelect(selectedItem, checked) {
|
||
return this.handleSelect('left', selectedItem, checked);
|
||
},
|
||
|
||
handleRightSelect(selectedItem, checked) {
|
||
return this.handleSelect('right', selectedItem, checked);
|
||
},
|
||
|
||
onLeftItemSelect(selectedKey, checked) {
|
||
return this.onItemSelect('left', selectedKey, checked);
|
||
},
|
||
onRightItemSelect(selectedKey, checked) {
|
||
return this.onItemSelect('right', selectedKey, checked);
|
||
},
|
||
|
||
handleScroll(direction, e) {
|
||
this.$emit('scroll', direction, e);
|
||
},
|
||
|
||
handleLeftScroll(e) {
|
||
this.handleScroll('left', e);
|
||
},
|
||
handleRightScroll(e) {
|
||
this.handleScroll('right', e);
|
||
},
|
||
|
||
handleSelectChange(direction, holder) {
|
||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
||
|
||
if (direction === 'left') {
|
||
this.$emit('selectChange', holder, targetSelectedKeys);
|
||
} else {
|
||
this.$emit('selectChange', sourceSelectedKeys, holder);
|
||
}
|
||
},
|
||
handleListStyle(listStyle, direction) {
|
||
if (typeof listStyle === 'function') {
|
||
return listStyle({ direction });
|
||
}
|
||
return listStyle;
|
||
},
|
||
|
||
separateDataSource() {
|
||
const { dataSource, rowKey, targetKeys = [] } = this.$props;
|
||
|
||
const leftDataSource = [];
|
||
const rightDataSource = new Array(targetKeys.length);
|
||
dataSource.forEach(record => {
|
||
if (rowKey) {
|
||
record.key = rowKey(record);
|
||
}
|
||
|
||
// rightDataSource should be ordered by targetKeys
|
||
// leftDataSource should be ordered by dataSource
|
||
const indexOfKey = targetKeys.indexOf(record.key);
|
||
if (indexOfKey !== -1) {
|
||
rightDataSource[indexOfKey] = record;
|
||
} else {
|
||
leftDataSource.push(record);
|
||
}
|
||
});
|
||
|
||
return {
|
||
leftDataSource,
|
||
rightDataSource,
|
||
};
|
||
},
|
||
|
||
renderTransfer(transferLocale) {
|
||
const props = getOptionProps(this);
|
||
const {
|
||
prefixCls: customizePrefixCls,
|
||
disabled,
|
||
operations = [],
|
||
showSearch,
|
||
listStyle,
|
||
operationStyle,
|
||
filterOption,
|
||
lazy,
|
||
showSelectAll,
|
||
} = props;
|
||
const children = getComponentFromProp(this, 'children', {}, false);
|
||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||
const prefixCls = getPrefixCls('transfer', customizePrefixCls);
|
||
|
||
const renderEmpty = this.configProvider.renderEmpty;
|
||
const locale = this.getLocale(transferLocale, renderEmpty);
|
||
const { sourceSelectedKeys, targetSelectedKeys, $scopedSlots } = this;
|
||
const { body, footer } = $scopedSlots;
|
||
const renderItem = props.render;
|
||
const { leftDataSource, rightDataSource } = this.separateDataSource();
|
||
const leftActive = targetSelectedKeys.length > 0;
|
||
const rightActive = sourceSelectedKeys.length > 0;
|
||
|
||
const cls = classNames(prefixCls, {
|
||
[`${prefixCls}-disabled`]: disabled,
|
||
[`${prefixCls}-customize-list`]: !!children,
|
||
});
|
||
const titles = this.getTitles(locale);
|
||
return (
|
||
<div class={cls}>
|
||
<List
|
||
key="leftList"
|
||
prefixCls={`${prefixCls}-list`}
|
||
titleText={titles[0]}
|
||
dataSource={leftDataSource}
|
||
filterOption={filterOption}
|
||
style={this.handleListStyle(listStyle, 'left')}
|
||
checkedKeys={sourceSelectedKeys}
|
||
handleFilter={this.handleLeftFilter}
|
||
handleClear={this.handleLeftClear}
|
||
handleSelect={this.handleLeftSelect}
|
||
handleSelectAll={this.handleLeftSelectAll}
|
||
onItemSelect={this.onLeftItemSelect}
|
||
onItemSelectAll={this.onLeftItemSelectAll}
|
||
renderItem={renderItem}
|
||
showSearch={showSearch}
|
||
body={body}
|
||
renderList={children}
|
||
footer={footer}
|
||
lazy={lazy}
|
||
onScroll={this.handleLeftScroll}
|
||
disabled={disabled}
|
||
direction="left"
|
||
showSelectAll={showSelectAll}
|
||
itemUnit={locale.itemUnit}
|
||
itemsUnit={locale.itemsUnit}
|
||
notFoundContent={locale.notFoundContent}
|
||
searchPlaceholder={locale.searchPlaceholder}
|
||
/>
|
||
<Operation
|
||
key="operation"
|
||
class={`${prefixCls}-operation`}
|
||
rightActive={rightActive}
|
||
rightArrowText={operations[0]}
|
||
moveToRight={this.moveToRight}
|
||
leftActive={leftActive}
|
||
leftArrowText={operations[1]}
|
||
moveToLeft={this.moveToLeft}
|
||
style={operationStyle}
|
||
disabled={disabled}
|
||
/>
|
||
<List
|
||
key="rightList"
|
||
prefixCls={`${prefixCls}-list`}
|
||
titleText={titles[1]}
|
||
dataSource={rightDataSource}
|
||
filterOption={filterOption}
|
||
style={this.handleListStyle(listStyle, 'right')}
|
||
checkedKeys={targetSelectedKeys}
|
||
handleFilter={this.handleRightFilter}
|
||
handleClear={this.handleRightClear}
|
||
handleSelect={this.handleRightSelect}
|
||
handleSelectAll={this.handleRightSelectAll}
|
||
onItemSelect={this.onRightItemSelect}
|
||
onItemSelectAll={this.onRightItemSelectAll}
|
||
renderItem={renderItem}
|
||
showSearch={showSearch}
|
||
body={body}
|
||
renderList={children}
|
||
footer={footer}
|
||
lazy={lazy}
|
||
onScroll={this.handleRightScroll}
|
||
disabled={disabled}
|
||
direction="right"
|
||
showSelectAll={showSelectAll}
|
||
itemUnit={locale.itemUnit}
|
||
itemsUnit={locale.itemsUnit}
|
||
notFoundContent={locale.notFoundContent}
|
||
searchPlaceholder={locale.searchPlaceholder}
|
||
/>
|
||
</div>
|
||
);
|
||
},
|
||
},
|
||
render() {
|
||
return (
|
||
<LocaleReceiver
|
||
componentName="Transfer"
|
||
defaultLocale={defaultLocale.Transfer}
|
||
scopedSlots={{ default: this.renderTransfer }}
|
||
/>
|
||
);
|
||
},
|
||
};
|
||
|
||
/* istanbul ignore next */
|
||
Transfer.install = function(Vue) {
|
||
Vue.use(Base);
|
||
Vue.component(Transfer.name, Transfer);
|
||
};
|
||
|
||
export default Transfer;
|