import * as React from 'react'; import * as ReactDOM from 'react-dom'; import classNames from 'classnames'; import Animate from 'rc-animate'; import PureRenderMixin from 'rc-util/lib/PureRenderMixin'; import Checkbox from '../checkbox'; import { TransferItem } from './index'; import Search from './search'; import Item from './item'; import triggerEvent from '../_util/triggerEvent'; function isIEorEDGE() { return (document as any).documentMode || /Edge/.test(navigator.userAgent); } function noop() {} function isRenderResultPlainObject(result: any) { return ( result && !React.isValidElement(result) && Object.prototype.toString.call(result) === '[object Object]' ); } export interface TransferListProps { prefixCls: string; titleText: string; dataSource: TransferItem[]; filter: string; filterOption?: (filterText: any, item: any) => boolean; style?: React.CSSProperties; checkedKeys: string[]; handleFilter: (e: any) => void; handleSelect: (selectedItem: any, checked: boolean) => void; handleSelectAll: (dataSource: any[], checkAll: boolean) => void; handleClear: () => void; render?: (item: any) => any; showSearch?: boolean; searchPlaceholder: string; notFoundContent: React.ReactNode; itemUnit: string; itemsUnit: string; body?: (props: TransferListProps) => React.ReactNode; footer?: (props: TransferListProps) => React.ReactNode; lazy?: boolean | {}; onScroll: Function; disabled?: boolean; } export default class TransferList extends React.Component { static defaultProps = { dataSource: [], titleText: '', showSearch: false, render: noop, lazy: {}, }; timer: number; triggerScrollTimer: number; fixIERepaintTimer: number; notFoundNode: HTMLDivElement; constructor(props: TransferListProps) { super(props); this.state = { mounted: false, }; } componentDidMount() { this.timer = window.setTimeout(() => { this.setState({ mounted: true, }); }, 0); } componentWillUnmount() { clearTimeout(this.timer); clearTimeout(this.triggerScrollTimer); clearTimeout(this.fixIERepaintTimer); } shouldComponentUpdate(...args: any[]) { return PureRenderMixin.shouldComponentUpdate.apply(this, args); } getCheckStatus(filteredDataSource: TransferItem[]) { const { checkedKeys } = this.props; if (checkedKeys.length === 0) { return 'none'; } else if (filteredDataSource.every(item => checkedKeys.indexOf(item.key) >= 0)) { return 'all'; } return 'part'; } handleSelect = (selectedItem: TransferItem) => { const { checkedKeys } = this.props; const result = checkedKeys.some(key => key === selectedItem.key); this.props.handleSelect(selectedItem, !result); }; handleFilter = (e: React.ChangeEvent) => { this.props.handleFilter(e); if (!e.target.value) { return; } // Manually trigger scroll event for lazy search bug // https://github.com/ant-design/ant-design/issues/5631 this.triggerScrollTimer = window.setTimeout(() => { const transferNode = ReactDOM.findDOMNode(this) as Element; const listNode = transferNode.querySelectorAll('.ant-transfer-list-content')[0]; if (listNode) { triggerEvent(listNode, 'scroll'); } }, 0); this.fixIERepaint(); }; handleClear = () => { this.props.handleClear(); this.fixIERepaint(); }; matchFilter = (text: string, item: TransferItem) => { const { filter, filterOption } = this.props; if (filterOption) { return filterOption(filter, item); } return text.indexOf(filter) >= 0; }; renderItem = (item: TransferItem) => { const { render = noop } = this.props; const renderResult = render(item); const isRenderResultPlain = isRenderResultPlainObject(renderResult); return { renderedText: isRenderResultPlain ? renderResult.value : renderResult, renderedEl: isRenderResultPlain ? renderResult.label : renderResult, }; }; saveNotFoundRef = (node: HTMLDivElement) => { this.notFoundNode = node; }; // Fix IE/Edge repaint // https://github.com/ant-design/ant-design/issues/9697 // https://stackoverflow.com/q/27947912/3040605 fixIERepaint() { if (!isIEorEDGE()) { return; } this.fixIERepaintTimer = window.setTimeout(() => { if (this.notFoundNode) { this.notFoundNode.className = this.notFoundNode.className; } }, 0); } render() { const { prefixCls, dataSource, titleText, checkedKeys, lazy, disabled, body, footer, showSearch, style, filter, searchPlaceholder, notFoundContent, itemUnit, itemsUnit, onScroll, } = this.props; // Custom Layout const footerDom = footer && footer(this.props); const bodyDom = body && body(this.props); const listCls = classNames(prefixCls, { [`${prefixCls}-with-footer`]: !!footerDom, }); const filteredDataSource: TransferItem[] = []; const totalDataSource: TransferItem[] = []; const showItems = dataSource.map(item => { const { renderedText, renderedEl } = this.renderItem(item); if (filter && filter.trim() && !this.matchFilter(renderedText, item)) { return null; } // all show items totalDataSource.push(item); if (!item.disabled) { // response to checkAll items filteredDataSource.push(item); } const checked = checkedKeys.indexOf(item.key) >= 0; return ( ); }); const unit = dataSource.length > 1 ? itemsUnit : itemUnit; const search = showSearch ? (
) : null; const listBody = bodyDom || (
{search} {showItems}
{notFoundContent}
); const listFooter = footerDom ?
{footerDom}
: null; const checkStatus = this.getCheckStatus(filteredDataSource); const checkedAll = checkStatus === 'all'; const checkAllCheckbox = ( this.props.handleSelectAll(filteredDataSource, checkedAll)} /> ); return (
{checkAllCheckbox} {(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + totalDataSource.length}{' '} {unit} {titleText}
{listBody} {listFooter}
); } }