264 lines
9.4 KiB
JavaScript
264 lines
9.4 KiB
JavaScript
"use client";
|
|
|
|
import DownOutlined from "@ant-design/icons/es/icons/DownOutlined";
|
|
import classNames from 'classnames';
|
|
import omit from "rc-util/es/omit";
|
|
import React, { useMemo, useRef, useState } from 'react';
|
|
import { isValidElement } from '../_util/reactNode';
|
|
import { groupKeysMap } from '../_util/transKeys';
|
|
import Checkbox from '../checkbox';
|
|
import Dropdown from '../dropdown';
|
|
import DefaultListBody, { OmitProps } from './ListBody';
|
|
import Search from './search';
|
|
const defaultRender = () => null;
|
|
function isRenderResultPlainObject(result) {
|
|
return !!(result && !isValidElement(result) && Object.prototype.toString.call(result) === '[object Object]');
|
|
}
|
|
function getEnabledItemKeys(items) {
|
|
return items.filter(data => !data.disabled).map(data => data.key);
|
|
}
|
|
const isValidIcon = icon => icon !== undefined;
|
|
const TransferList = props => {
|
|
const {
|
|
prefixCls,
|
|
dataSource = [],
|
|
titleText = '',
|
|
checkedKeys,
|
|
disabled,
|
|
showSearch = false,
|
|
style,
|
|
searchPlaceholder,
|
|
notFoundContent,
|
|
selectAll,
|
|
selectCurrent,
|
|
selectInvert,
|
|
removeAll,
|
|
removeCurrent,
|
|
showSelectAll = true,
|
|
showRemove,
|
|
pagination,
|
|
direction,
|
|
itemsUnit,
|
|
itemUnit,
|
|
selectAllLabel,
|
|
selectionsIcon,
|
|
footer,
|
|
renderList,
|
|
onItemSelectAll,
|
|
onItemRemove,
|
|
handleFilter,
|
|
handleClear,
|
|
filterOption,
|
|
render = defaultRender
|
|
} = props;
|
|
const [filterValue, setFilterValue] = useState('');
|
|
const listBodyRef = useRef({});
|
|
const internalHandleFilter = e => {
|
|
setFilterValue(e.target.value);
|
|
handleFilter(e);
|
|
};
|
|
const internalHandleClear = () => {
|
|
setFilterValue('');
|
|
handleClear();
|
|
};
|
|
const matchFilter = (text, item) => {
|
|
if (filterOption) {
|
|
return filterOption(filterValue, item, direction);
|
|
}
|
|
return text.includes(filterValue);
|
|
};
|
|
const renderListBody = listProps => {
|
|
let bodyContent = renderList ? renderList(listProps) : null;
|
|
const customize = !!bodyContent;
|
|
if (!customize) {
|
|
bodyContent = /*#__PURE__*/React.createElement(DefaultListBody, Object.assign({
|
|
ref: listBodyRef
|
|
}, listProps));
|
|
}
|
|
return {
|
|
customize,
|
|
bodyContent
|
|
};
|
|
};
|
|
const renderItem = item => {
|
|
const renderResult = render(item);
|
|
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
|
|
return {
|
|
item,
|
|
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
|
|
renderedText: isRenderResultPlain ? renderResult.value : renderResult
|
|
};
|
|
};
|
|
const notFoundContentEle = useMemo(() => Array.isArray(notFoundContent) ? notFoundContent[direction === 'left' ? 0 : 1] : notFoundContent, [notFoundContent, direction]);
|
|
const [filteredItems, filteredRenderItems] = useMemo(() => {
|
|
const filterItems = [];
|
|
const filterRenderItems = [];
|
|
dataSource.forEach(item => {
|
|
const renderedItem = renderItem(item);
|
|
if (filterValue && !matchFilter(renderedItem.renderedText, item)) {
|
|
return;
|
|
}
|
|
filterItems.push(item);
|
|
filterRenderItems.push(renderedItem);
|
|
});
|
|
return [filterItems, filterRenderItems];
|
|
}, [dataSource, filterValue]);
|
|
const checkStatus = useMemo(() => {
|
|
if (checkedKeys.length === 0) {
|
|
return 'none';
|
|
}
|
|
const checkedKeysMap = groupKeysMap(checkedKeys);
|
|
if (filteredItems.every(item => checkedKeysMap.has(item.key) || !!item.disabled)) {
|
|
return 'all';
|
|
}
|
|
return 'part';
|
|
}, [checkedKeys, filteredItems]);
|
|
const listBody = useMemo(() => {
|
|
const search = showSearch ? /*#__PURE__*/React.createElement("div", {
|
|
className: `${prefixCls}-body-search-wrapper`
|
|
}, /*#__PURE__*/React.createElement(Search, {
|
|
prefixCls: `${prefixCls}-search`,
|
|
onChange: internalHandleFilter,
|
|
handleClear: internalHandleClear,
|
|
placeholder: searchPlaceholder,
|
|
value: filterValue,
|
|
disabled: disabled
|
|
})) : null;
|
|
const {
|
|
customize,
|
|
bodyContent
|
|
} = renderListBody(Object.assign(Object.assign({}, omit(props, OmitProps)), {
|
|
filteredItems,
|
|
filteredRenderItems,
|
|
selectedKeys: checkedKeys
|
|
}));
|
|
let bodyNode;
|
|
// We should wrap customize list body in a classNamed div to use flex layout.
|
|
if (customize) {
|
|
bodyNode = /*#__PURE__*/React.createElement("div", {
|
|
className: `${prefixCls}-body-customize-wrapper`
|
|
}, bodyContent);
|
|
} else {
|
|
bodyNode = filteredItems.length ? bodyContent : /*#__PURE__*/React.createElement("div", {
|
|
className: `${prefixCls}-body-not-found`
|
|
}, notFoundContentEle);
|
|
}
|
|
return /*#__PURE__*/React.createElement("div", {
|
|
className: classNames(showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`)
|
|
}, search, bodyNode);
|
|
}, [showSearch, prefixCls, searchPlaceholder, filterValue, disabled, checkedKeys, filteredItems, filteredRenderItems, notFoundContentEle]);
|
|
const checkBox = /*#__PURE__*/React.createElement(Checkbox, {
|
|
disabled: dataSource.length === 0 || disabled,
|
|
checked: checkStatus === 'all',
|
|
indeterminate: checkStatus === 'part',
|
|
className: `${prefixCls}-checkbox`,
|
|
onChange: () => {
|
|
// Only select enabled items
|
|
onItemSelectAll === null || onItemSelectAll === void 0 ? void 0 : onItemSelectAll(filteredItems.filter(item => !item.disabled).map(_ref => {
|
|
let {
|
|
key
|
|
} = _ref;
|
|
return key;
|
|
}), checkStatus !== 'all');
|
|
}
|
|
});
|
|
const getSelectAllLabel = (selectedCount, totalCount) => {
|
|
if (selectAllLabel) {
|
|
return typeof selectAllLabel === 'function' ? selectAllLabel({
|
|
selectedCount,
|
|
totalCount
|
|
}) : selectAllLabel;
|
|
}
|
|
const unit = totalCount > 1 ? itemsUnit : itemUnit;
|
|
return /*#__PURE__*/React.createElement(React.Fragment, null, (selectedCount > 0 ? `${selectedCount}/` : '') + totalCount, " ", unit);
|
|
};
|
|
// Custom Layout
|
|
const footerDom = footer && (footer.length < 2 ? footer(props) : footer(props, {
|
|
direction
|
|
}));
|
|
const listCls = classNames(prefixCls, {
|
|
[`${prefixCls}-with-pagination`]: !!pagination,
|
|
[`${prefixCls}-with-footer`]: !!footerDom
|
|
});
|
|
// ====================== Get filtered, checked item list ======================
|
|
const listFooter = footerDom ? /*#__PURE__*/React.createElement("div", {
|
|
className: `${prefixCls}-footer`
|
|
}, footerDom) : null;
|
|
const checkAllCheckbox = !showRemove && !pagination && checkBox;
|
|
let items;
|
|
if (showRemove) {
|
|
items = [/* Remove Current Page */
|
|
pagination ? {
|
|
key: 'removeCurrent',
|
|
label: removeCurrent,
|
|
onClick() {
|
|
var _a;
|
|
const pageKeys = getEnabledItemKeys((((_a = listBodyRef.current) === null || _a === void 0 ? void 0 : _a.items) || []).map(entity => entity.item));
|
|
onItemRemove === null || onItemRemove === void 0 ? void 0 : onItemRemove(pageKeys);
|
|
}
|
|
} : null, /* Remove All */
|
|
{
|
|
key: 'removeAll',
|
|
label: removeAll,
|
|
onClick() {
|
|
onItemRemove === null || onItemRemove === void 0 ? void 0 : onItemRemove(getEnabledItemKeys(filteredItems));
|
|
}
|
|
}].filter(Boolean);
|
|
} else {
|
|
items = [{
|
|
key: 'selectAll',
|
|
label: selectAll,
|
|
onClick() {
|
|
const keys = getEnabledItemKeys(filteredItems);
|
|
onItemSelectAll === null || onItemSelectAll === void 0 ? void 0 : onItemSelectAll(keys, keys.length !== checkedKeys.length);
|
|
}
|
|
}, pagination ? {
|
|
key: 'selectCurrent',
|
|
label: selectCurrent,
|
|
onClick() {
|
|
var _a;
|
|
const pageItems = ((_a = listBodyRef.current) === null || _a === void 0 ? void 0 : _a.items) || [];
|
|
onItemSelectAll === null || onItemSelectAll === void 0 ? void 0 : onItemSelectAll(getEnabledItemKeys(pageItems.map(entity => entity.item)), true);
|
|
}
|
|
} : null, {
|
|
key: 'selectInvert',
|
|
label: selectInvert,
|
|
onClick() {
|
|
var _a;
|
|
const availableKeys = getEnabledItemKeys(pagination ? (((_a = listBodyRef.current) === null || _a === void 0 ? void 0 : _a.items) || []).map(entity => entity.item) : filteredItems);
|
|
const checkedKeySet = new Set(checkedKeys);
|
|
const newCheckedKeys = [];
|
|
const newUnCheckedKeys = [];
|
|
availableKeys.forEach(key => {
|
|
if (checkedKeySet.has(key)) {
|
|
newUnCheckedKeys.push(key);
|
|
} else {
|
|
newCheckedKeys.push(key);
|
|
}
|
|
});
|
|
onItemSelectAll === null || onItemSelectAll === void 0 ? void 0 : onItemSelectAll(newCheckedKeys, 'replace');
|
|
}
|
|
}];
|
|
}
|
|
const dropdown = /*#__PURE__*/React.createElement(Dropdown, {
|
|
className: `${prefixCls}-header-dropdown`,
|
|
menu: {
|
|
items
|
|
},
|
|
disabled: disabled
|
|
}, isValidIcon(selectionsIcon) ? selectionsIcon : /*#__PURE__*/React.createElement(DownOutlined, null));
|
|
return /*#__PURE__*/React.createElement("div", {
|
|
className: listCls,
|
|
style: style
|
|
}, /*#__PURE__*/React.createElement("div", {
|
|
className: `${prefixCls}-header`
|
|
}, showSelectAll ? /*#__PURE__*/React.createElement(React.Fragment, null, checkAllCheckbox, dropdown) : null, /*#__PURE__*/React.createElement("span", {
|
|
className: `${prefixCls}-header-selected`
|
|
}, getSelectAllLabel(checkedKeys.length, filteredItems.length)), /*#__PURE__*/React.createElement("span", {
|
|
className: `${prefixCls}-header-title`
|
|
}, titleText)), listBody, listFooter);
|
|
};
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
TransferList.displayName = 'TransferList';
|
|
}
|
|
export default TransferList; |