ant-design-vue/components/transfer/index.jsx
tangjinzhou 73bef787cd
Feat 1.5.0 (#1853)
* 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>
2020-03-07 19:45:13 +08:00

488 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;