You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ant-design-vue/components/transfer/index.jsx

433 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import PropTypes from '../_util/vue-types'
import { hasProp, initDefaultProps, getOptionProps, getComponentFromProp } 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 warning from '../_util/warning'
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.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,
]),
}
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,
}),
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.',
// )
this.separatedDataSource = {
leftDataSource: [],
rightDataSource: [],
} | null
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: {
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,
})
}
},
separateDataSource (props) {
if (this.separatedDataSource) {
return this.separatedDataSource
}
const { dataSource, rowKey, targetKeys = [] } = 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)
}
})
this.separatedDataSource = {
leftDataSource,
rightDataSource,
}
return this.separatedDataSource
},
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')
},
handleSelectChange (direction, holder) {
const { sourceSelectedKeys, targetSelectedKeys } = this
if (direction === 'left') {
this.$emit('selectChange', holder, targetSelectedKeys)
} else {
this.$emit('selectChange', sourceSelectedKeys, holder)
}
},
handleSelectAll (direction, filteredDataSource, checkAll) {
const originalSelectedKeys = this[this.getSelectedKeysName(direction)] || []
const currentKeys = filteredDataSource.map(item => item.key)
// Only operate current keys from original selected keys
const newKeys1 = originalSelectedKeys.filter((key) => currentKeys.indexOf(key) === -1)
const newKeys2 = [...originalSelectedKeys]
currentKeys.forEach((key) => {
if (newKeys2.indexOf(key) === -1) {
newKeys2.push(key)
}
})
const holder = checkAll ? newKeys1 : newKeys2
this.handleSelectChange(direction, holder)
if (!this.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
})
}
},
handleLeftSelectAll (filteredDataSource, checkAll) {
this.handleSelectAll('left', filteredDataSource, checkAll)
},
handleRightSelectAll (filteredDataSource, checkAll) {
this.handleSelectAll('right', filteredDataSource, checkAll)
},
handleFilter (direction, e) {
const value = e.target.value
this.setState({
// add filter
[`${direction}Filter`]: value,
})
if (this.$listeners.searchChange) {
warning(false, '`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.setState({
[`${direction}Filter`]: '',
})
this.$emit('search', direction, '')
},
handleLeftClear () {
this.handleClear('left')
},
handleRightClear () {
this.handleClear('right')
},
handleSelect (direction, selectedItem, checked) {
const { sourceSelectedKeys, targetSelectedKeys } = this
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys]
const index = holder.indexOf(selectedItem.key)
if (index > -1) {
holder.splice(index, 1)
}
if (checked) {
holder.push(selectedItem.key)
}
this.handleSelectChange(direction, holder)
if (!this.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
})
}
},
handleLeftSelect (selectedItem, checked) {
return this.handleSelect('left', selectedItem, checked)
},
handleRightSelect (selectedItem, checked) {
return this.handleSelect('right', selectedItem, checked)
},
handleScroll (direction, e) {
this.$emit('scroll', direction, e)
},
handleLeftScroll (e) {
this.handleScroll('left', e)
},
handleRightScroll (e) {
this.handleScroll('right', e)
},
getTitles (transferLocale) {
if (this.titles) {
return this.titles
}
return transferLocale.titles || ['', '']
},
getSelectedKeysName (direction) {
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys'
},
getLocale (transferLocale) {
// Keep old locale props still working.
const oldLocale = {}
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 })
},
renderTransfer (transferLocale) {
const props = getOptionProps(this)
const {
prefixCls = 'ant-transfer',
disabled,
operations = [],
showSearch,
listStyle,
operationStyle,
filterOption,
lazy,
} = props
const locale = this.getLocale(transferLocale)
const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys, $scopedSlots } = this
const { body, footer } = $scopedSlots
const renderItem = props.render
const { leftDataSource, rightDataSource } = this.separateDataSource(this.$props)
const leftActive = targetSelectedKeys.length > 0
const rightActive = sourceSelectedKeys.length > 0
const cls = classNames(prefixCls, disabled && `${prefixCls}-disabled`)
const titles = this.getTitles(locale)
return (
<div class={cls}>
<List
prefixCls={`${prefixCls}-list`}
titleText={titles[0]}
dataSource={leftDataSource}
filter={leftFilter}
filterOption={filterOption}
style={listStyle}
checkedKeys={sourceSelectedKeys}
handleFilter={this.handleLeftFilter}
handleClear={this.handleLeftClear}
handleSelect={this.handleLeftSelect}
handleSelectAll={this.handleLeftSelectAll}
renderItem={renderItem}
showSearch={showSearch}
body={body}
footer={footer}
lazy={lazy}
onScroll={this.handleLeftScroll}
disabled={disabled}
itemUnit={locale.itemUnit}
itemsUnit={locale.itemsUnit}
notFoundContent={locale.notFoundContent}
searchPlaceholder={locale.searchPlaceholder}
/>
<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
prefixCls={`${prefixCls}-list`}
titleText={titles[1]}
dataSource={rightDataSource}
filter={rightFilter}
filterOption={filterOption}
style={listStyle}
checkedKeys={targetSelectedKeys}
handleFilter={this.handleRightFilter}
handleClear={this.handleRightClear}
handleSelect={this.handleRightSelect}
handleSelectAll={this.handleRightSelectAll}
renderItem={renderItem}
showSearch={showSearch}
body={body}
footer={footer}
lazy={lazy}
onScroll={this.handleRightScroll}
disabled={disabled}
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 }
}
>
</LocaleReceiver>
)
},
}
/* istanbul ignore next */
Transfer.install = function (Vue) {
Vue.component(Transfer.name, Transfer)
}
export default Transfer