ant-design-vue/components/transfer/list.tsx
tangjinzhou 3aeeeb2aed
3.0 ready (#4523)
* refactor: transfer、tooltip (#4306)

* refactor(transfer): use composition api (#4135)

* refactor(transfer): use composition api

* fix: remove console

* refactor(tooltip): use composition api (#4059)

* refactor(tooltip): use composition api

* chore: useConfigInject

* fix: remove useless

* style: format code

* refactor: transfer

* refactor: tooltip

Co-authored-by: ajuner <106791576@qq.com>

* Refactor mentions (#4341)

* refactor(mentions): use compositionAPI (#4313)

* refactor: mentions

* refactor: mentions

Co-authored-by: ajuner <106791576@qq.com>

* Refactor progress (#4358)

* fix: timepicker error border not show #4331

* fix(UploadDragger): fix UploadDrager no export (#4334)

* refactor(switch): support customize checked value #4329 (#4332)

* refactor(switch): support customize checked value #4329

* test: add test case

* refactor: update props name

* refactor: update ts

* refactor: optimize

* style: uncheckedValue to unCheckedValue

* test: update snap

* feat: udpate switch ts

* docs: remove ie11

* fix: tree-select throw error when use slot title

* fix: TypeScript definition of Table interface for typescript 4.3.5 (#4353)

* fix type for typescript 4.3.5

* Update interface.ts

close #4296

* fix: dropdown submenu style error #4351
close #4351

* fix(notification): 完善notification类型 (#4346)

* refactor(progress): use composition API (#4355)

* refactor(progress): use composition API

* refactor(vc-progress): update

* refactor: progress

* refactor: progress

* fix: timepicker error border not show #4331

* fix(UploadDragger): fix UploadDrager no export (#4334)

* refactor(switch): support customize checked value #4329 (#4332)

* refactor(switch): support customize checked value #4329

* test: add test case

* refactor: update props name

* refactor: update ts

* refactor: optimize

* style: uncheckedValue to unCheckedValue

* test: update snap

* feat: udpate switch ts

* docs: remove ie11

* fix: tree-select throw error when use slot title

* fix: TypeScript definition of Table interface for typescript 4.3.5 (#4353)

* fix type for typescript 4.3.5

* Update interface.ts

close #4296

* fix: dropdown submenu style error #4351
close #4351

* fix(notification): 完善notification类型 (#4346)

* refactor(progress): use composition API (#4355)

* refactor(progress): use composition API

* refactor(vc-progress): update

* refactor: progress

* refactor: progress

Co-authored-by: Jarvis <35361626+fanhaoyuan@users.noreply.github.com>
Co-authored-by: John <John60676@qq.com>
Co-authored-by: 艾斯特洛 <axetroy.dev@gmail.com>
Co-authored-by: zanllp <qc@zanllp.cn>

* docs: add changelog

* refactor: tree

* refactor: tree

* style: lint

* refactor: tree

* 热factor: tree

* refactor: tree

* refactor: tree

* refactor: tree

* refactor: directory tree

* refactor: tree

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* style: lint format

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* refactor: tree-select

* fix: upload ts error

* fix: update tree title render & switchIcon

* test: update tree test

* feat: add VirtualScroll tree

* refactor: datePicker & calendar & trigger (#4522)

* style: update

* test: update calendar test

* test: update test

* test: update test

* refactor: slider

* feat: update slider css

* refactor: slider to ts

* refactor: slider to ts

* perf: update default itemHeight

* test: update

* fix: uddate ts type

* fix: update skeleton

* fix: update skeleton

* refactor: update vc-pagination

* refactor: pagination

* refactor: timeline

* refactor: steps

* refactor: collapse

* refactor: collapse

* refactor: popconfirm

* refactor: popover

* refactor: dropdown

* doc: merge doc

* chore: vite for dev (#4602)

* style: js to jsx

* doc: add site

* style: lint

* style: format ts type

* doc: update

* style: format code

* style: format site

* doc: update

* style: dmeo

* style: format scripts

* chore: remove sub-modules

* chore: update vite

* site: add site build

* test: update snap

* doc(select): add tip (#4606)

* refactor: table (#4641)

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* refactor: table

* fix: column not pass to cell

* doc: uppate table

* fix: update bodyCell headerCell

* doc: remove examples

* refactor: table

* fix: table title not work

* fix: table selection

* fix: table checkStrictly

* refactor: table

* fix: table template error

* feat: table support summary

* test: update snap

* perf: table

* docs(table): fix ajax demo (#4639)

* test: update table

* refactor: remove old table

* doc: update  table doc

* doc: update doc

* doc: update select

* doc: update summary

Co-authored-by: John <John60676@qq.com>

* doc: update doc

* fix: menu arrow not work

* test: update

* doc: add next site

* style: format

* doc: update

* doc: update site script

* fix: expand icon not fixed

* feat: use renderSlot

* test: update table snap

* feat: confirm support reactively

* feat: configProvider.config

* feat: message support configprovider.config

* feat: notification support configprovider.config

* doc: update doc

* fix: typescript compile error

* style: add import eslint

* doc: update demo

* chore: set transpileOnly true

* style: fix eslint error

* test: update snap

* doc: update

* test: mock date

* test: update snap

* chore: remove gulp-typescript (#4675)

* feat: V3 form (#4678)

* chore: update husky

* perf: update formItem

* perf: useInjectFormItemContext

* fix: table ts error

* doc: add Customized Form Controls demo

* feat: export useInjectFormItemContext

* doc: update form doc

* doc: update doc

* doc: update doc

* feat: autocomplete support option slot

* doc: update

* feat: add form item rest

* style: remove omit.js

* refactor: autocomplete

* doc: add changelog to site

* doc: update site anchor

* doc: update doc layout

* test: update table test

* doc: update

* chore: udpate gulp script

* chore: udpate gulp script

* doc: add changelog

* doc: update

* test: ignore some test wait vue-test-utils

* fix: form id error #4582
close #4582

* doc: add select Responsive demo

* doc: remove temp doc

Co-authored-by: ajuner <106791576@qq.com>
Co-authored-by: Jarvis <35361626+fanhaoyuan@users.noreply.github.com>
Co-authored-by: John <John60676@qq.com>
Co-authored-by: 艾斯特洛 <axetroy.dev@gmail.com>
Co-authored-by: zanllp <qc@zanllp.cn>
Co-authored-by: Amour1688 <lcz_1996@foxmail.com>
2021-09-25 16:51:32 +08:00

404 lines
12 KiB
Vue

import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
import { isValidElement, splitAttrs, filterEmpty } from '../_util/props-util';
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
import Checkbox from '../checkbox';
import Menu from '../menu';
import Dropdown from '../dropdown';
import Search from './search';
import ListBody from './ListBody';
import type { VNode, VNodeTypes, ExtractPropTypes, PropType } from 'vue';
import { watchEffect, computed, defineComponent, ref } from 'vue';
import type { RadioChangeEvent } from '../radio/interface';
import type { TransferItem } from './index';
const defaultRender = () => null;
function isRenderResultPlainObject(result: VNode) {
return (
result &&
!isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]'
);
}
function getEnabledItemKeys<RecordType extends TransferItem>(items: RecordType[]) {
return items.filter(data => !data.disabled).map(data => data.key);
}
export const transferListProps = {
prefixCls: PropTypes.string,
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
filter: PropTypes.string,
filterOption: PropTypes.func,
checkedKeys: PropTypes.arrayOf(PropTypes.string),
handleFilter: PropTypes.func,
handleClear: PropTypes.func,
renderItem: PropTypes.func,
showSearch: PropTypes.looseBool.def(false),
searchPlaceholder: PropTypes.string,
notFoundContent: PropTypes.any,
itemUnit: PropTypes.string,
itemsUnit: PropTypes.string,
renderList: PropTypes.any,
disabled: PropTypes.looseBool,
direction: PropTypes.string,
showSelectAll: PropTypes.looseBool,
remove: PropTypes.string,
selectAll: PropTypes.string,
selectCurrent: PropTypes.string,
selectInvert: PropTypes.string,
removeAll: PropTypes.string,
removeCurrent: PropTypes.string,
selectAllLabel: PropTypes.any,
showRemove: PropTypes.looseBool,
pagination: PropTypes.any,
onItemSelect: PropTypes.func,
onItemSelectAll: PropTypes.func,
onItemRemove: PropTypes.func,
onScroll: PropTypes.func,
};
export type TransferListProps = Partial<ExtractPropTypes<typeof transferListProps>>;
export default defineComponent({
name: 'TransferList',
inheritAttrs: false,
props: transferListProps,
emits: ['scroll', 'itemSelectAll', 'itemRemove', 'itemSelect'],
slots: ['footer', 'titleText'],
setup(props, { attrs, slots }) {
const filterValue = ref('');
const transferNode = ref();
const defaultListBodyRef = ref();
const renderListBody = (renderList: any, props: any) => {
let bodyContent = renderList ? renderList(props) : null;
const customize = !!bodyContent && filterEmpty(bodyContent).length > 0;
if (!customize) {
bodyContent = <ListBody {...props} ref={defaultListBodyRef} />;
}
return {
customize,
bodyContent,
};
};
const renderItemHtml = (item: TransferItem) => {
const { renderItem = defaultRender } = props;
const renderResult = renderItem(item);
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
return {
renderedText: isRenderResultPlain ? renderResult.value : renderResult,
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
item,
};
};
const filteredItems = ref([]);
const filteredRenderItems = ref([]);
watchEffect(() => {
const fItems = [];
const fRenderItems = [];
props.dataSource.forEach(item => {
const renderedItem = renderItemHtml(item);
const { renderedText } = renderedItem;
// Filter skip
if (filterValue.value && filterValue.value.trim() && !matchFilter(renderedText, item)) {
return null;
}
fItems.push(item);
fRenderItems.push(renderedItem);
});
filteredItems.value = fItems;
filteredRenderItems.value = fRenderItems;
});
const checkStatus = computed(() => {
const { checkedKeys } = props;
if (checkedKeys.length === 0) {
return 'none';
}
if (
filteredItems.value.every(item => checkedKeys.indexOf(item.key) >= 0 || !!item.disabled)
) {
return 'all';
}
return 'part';
});
const enabledItemKeys = computed(() => {
return getEnabledItemKeys(filteredItems.value);
});
const getNewSelectKeys = (keys, unCheckedKeys) => {
return Array.from(new Set([...keys, ...props.checkedKeys])).filter(
key => unCheckedKeys.indexOf(key) === -1,
);
};
const getCheckBox = (showSelectAll: boolean, disabled?: boolean, prefixCls?: string) => {
const checkedAll = checkStatus.value === 'all';
const checkAllCheckbox = showSelectAll !== false && (
<Checkbox
disabled={disabled}
checked={checkedAll}
indeterminate={checkStatus.value === 'part'}
class={`${prefixCls}-checkbox`}
onChange={() => {
// Only select enabled items
const keys = enabledItemKeys.value;
props.onItemSelectAll(
getNewSelectKeys(!checkedAll ? keys : [], checkedAll ? props.checkedKeys : []),
);
}}
/>
);
return checkAllCheckbox;
};
const handleFilter = (e: RadioChangeEvent) => {
const {
target: { value: filter },
} = e;
filterValue.value = filter;
props.handleFilter?.(e);
};
const handleClear = (e: Event) => {
filterValue.value = '';
props.handleClear?.(e);
};
const matchFilter = (text: string, item: TransferItem) => {
const { filterOption } = props;
if (filterOption) {
return filterOption(filterValue.value, item);
}
return text.indexOf(filterValue.value) >= 0;
};
const getSelectAllLabel = (selectedCount: number, totalCount: number) => {
const { itemsUnit, itemUnit, selectAllLabel } = props;
if (selectAllLabel) {
return typeof selectAllLabel === 'function'
? selectAllLabel({ selectedCount, totalCount })
: selectAllLabel;
}
const unit = totalCount > 1 ? itemsUnit : itemUnit;
return (
<>
{(selectedCount > 0 ? `${selectedCount}/` : '') + totalCount} {unit}
</>
);
};
const getListBody = (
prefixCls: string,
searchPlaceholder: string,
checkedKeys: string[],
renderList: Function,
showSearch: boolean,
disabled: boolean,
) => {
const search = showSearch ? (
<div class={`${prefixCls}-body-search-wrapper`}>
<Search
prefixCls={`${prefixCls}-search`}
onChange={handleFilter}
handleClear={handleClear}
placeholder={searchPlaceholder}
value={filterValue.value}
disabled={disabled}
/>
</div>
) : null;
let bodyNode: VNodeTypes;
const { onEvents } = splitAttrs(attrs);
const { bodyContent, customize } = renderListBody(renderList, {
...props,
filteredItems: filteredItems.value,
filteredRenderItems: filteredRenderItems.value,
selectedKeys: checkedKeys,
...onEvents,
});
// We should wrap customize list body in a classNamed div to use flex layout.
if (customize) {
bodyNode = <div class={`${prefixCls}-body-customize-wrapper`}>{bodyContent}</div>;
} else {
bodyNode = filteredItems.value.length ? (
bodyContent
) : (
<div class={`${prefixCls}-body-not-found`}>{props.notFoundContent}</div>
);
}
return (
<div
class={
showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`
}
ref={transferNode}
>
{search}
{bodyNode}
</div>
);
};
return () => {
const {
prefixCls,
checkedKeys,
disabled,
showSearch,
searchPlaceholder,
selectAll,
selectCurrent,
selectInvert,
removeAll,
removeCurrent,
renderList,
onItemSelectAll,
onItemRemove,
showSelectAll,
showRemove,
pagination,
} = props;
// Custom Layout
const footerDom = slots.footer?.({ ...props });
const listCls = classNames(prefixCls, {
[`${prefixCls}-with-pagination`]: !!pagination,
[`${prefixCls}-with-footer`]: !!footerDom,
});
// ================================= List Body =================================
const listBody = getListBody(
prefixCls,
searchPlaceholder,
checkedKeys,
renderList,
showSearch,
disabled,
);
const listFooter = footerDom ? <div class={`${prefixCls}-footer`}>{footerDom}</div> : null;
const checkAllCheckbox =
!showRemove && !pagination && getCheckBox(showSelectAll, disabled, prefixCls);
let menu = null;
if (showRemove) {
menu = (
<Menu>
{/* Remove Current Page */}
{pagination && (
<Menu.Item
onClick={() => {
const pageKeys = getEnabledItemKeys(
(defaultListBodyRef.value.items || []).map(entity => entity.item),
);
onItemRemove?.(pageKeys);
}}
>
{removeCurrent}
</Menu.Item>
)}
{/* Remove All */}
<Menu.Item
onClick={() => {
onItemRemove?.(enabledItemKeys.value);
}}
>
{removeAll}
</Menu.Item>
</Menu>
);
} else {
menu = (
<Menu>
<Menu.Item
onClick={() => {
const keys = enabledItemKeys.value;
onItemSelectAll(getNewSelectKeys(keys, []));
}}
>
{selectAll}
</Menu.Item>
{pagination && (
<Menu.Item
onClick={() => {
const pageKeys = getEnabledItemKeys(
(defaultListBodyRef.value.items || []).map(entity => entity.item),
);
onItemSelectAll(getNewSelectKeys(pageKeys, []));
}}
>
{selectCurrent}
</Menu.Item>
)}
<Menu.Item
onClick={() => {
let availableKeys: string[];
if (pagination) {
availableKeys = getEnabledItemKeys(
(defaultListBodyRef.value.items || []).map(entity => entity.item),
);
} else {
availableKeys = enabledItemKeys.value;
}
const checkedKeySet = new Set(checkedKeys);
const newCheckedKeys: string[] = [];
const newUnCheckedKeys: string[] = [];
availableKeys.forEach(key => {
if (checkedKeySet.has(key)) {
newUnCheckedKeys.push(key);
} else {
newCheckedKeys.push(key);
}
});
onItemSelectAll(getNewSelectKeys(newCheckedKeys, newUnCheckedKeys));
}}
>
{selectInvert}
</Menu.Item>
</Menu>
);
}
const dropdown = (
<Dropdown class={`${prefixCls}-header-dropdown`} overlay={menu} disabled={disabled}>
<DownOutlined />
</Dropdown>
);
return (
<div class={listCls} style={attrs.style}>
<div class={`${prefixCls}-header`}>
{checkAllCheckbox}
{dropdown}
<span class={`${prefixCls}-header-selected`}>
<span>{getSelectAllLabel(checkedKeys.length, filteredItems.value.length)}</span>
<span class={`${prefixCls}-header-title`}>{slots.titleText?.()}</span>
</span>
</div>
{listBody}
{listFooter}
</div>
);
};
},
});