feat: transfer support custom children

pull/1845/head
tangjinzhou 2020-02-28 16:04:37 +08:00
parent e0975469bc
commit 8f732fc421
15 changed files with 1021 additions and 293 deletions

View File

@ -1,5 +1,5 @@
module.exports = {
dev: {
componentName: 'tabs', // dev components
componentName: 'transfer', // dev components
},
};

View File

@ -1,7 +1,7 @@
import animate from './css-animation';
const noop = () => {};
const getTransitionProps = (transitionName, opt = {}) => {
const { beforeEnter, enter, afterEnter, leave, afterLeave, appear = true, tag } = opt;
const { beforeEnter, enter, afterEnter, leave, afterLeave, appear = true, tag, nativeOn } = opt;
const transitionProps = {
props: {
appear,
@ -22,6 +22,7 @@ const getTransitionProps = (transitionName, opt = {}) => {
}),
afterLeave: afterLeave || noop,
},
nativeOn,
};
// transition-group
if (tag) {

View File

@ -6,7 +6,7 @@ import Checkbox from '../checkbox';
function noop() {}
export default {
name: 'Item',
name: 'ListItem',
props: {
renderedText: PropTypes.any,
renderedEl: PropTypes.any,
@ -42,7 +42,7 @@ export default {
}
>
<Checkbox checked={checked} disabled={disabled || item.disabled} />
<span>{renderedEl}</span>
<span class={`${prefixCls}-content-item-text`}>{renderedEl}</span>
</li>
);
let children = null;

View File

@ -8,7 +8,7 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
<div class="ant-transfer-list-body-search-wrapper">
<div><input placeholder="Search here" type="text" class="ant-input ant-transfer-list-search"><span class="ant-transfer-list-search-action"><i aria-label="icon: search" class="anticon anticon-search"><svg viewBox="64 64 896 896" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></i></span></div>
</div>
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
@ -29,7 +29,7 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
<div class="ant-transfer-list-footer"><button type="button" class="ant-btn ant-btn-sm" style="float: right; margin: 5px;"><span>reload</span></button></div>
</div>
@ -59,7 +59,7 @@ exports[`renders ./components/transfer/demo/basic.md correctly 1`] = `
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox ant-checkbox-indeterminate"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>2/14 items</span><span class="ant-transfer-list-header-title">Source</span></span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
@ -74,7 +74,7 @@ exports[`renders ./components/transfer/demo/basic.md correctly 1`] = `
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
</div>
<div class="ant-transfer-operation"><button type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
@ -85,14 +85,14 @@ exports[`renders ./components/transfer/demo/basic.md correctly 1`] = `
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>6 items</span><span class="ant-transfer-list-header-title">Target</span></span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
</div>
</div> <button type="button" role="switch" class="ant-switch" style="margin-top: 16px;"><span class="ant-switch-inner">enabled</span></button>
@ -104,7 +104,7 @@ exports[`renders ./components/transfer/demo/custom-item.md correctly 1`] = `
<div class="ant-transfer-list" style="width: 300px; height: 300px;">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>20 items</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
@ -125,7 +125,7 @@ exports[`renders ./components/transfer/demo/custom-item.md correctly 1`] = `
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
</div>
<div class="ant-transfer-operation"><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
@ -137,8 +137,16 @@ exports[`renders ./components/transfer/demo/custom-item.md correctly 1`] = `
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>0 item</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body">
<div class="ant-transfer-list-body-not-found">
<div class="ant-empty ant-empty-small">
<div class="ant-empty-image"><img alt="No Data" src=""></div>
<div class="ant-empty ant-empty-normal ant-empty-small">
<div class="ant-empty-image"><svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 1)" fill="none" fillRule="evenodd">
<ellipse fill="#F5F5F5" cx="32" cy="33" rx="32" ry="7"></ellipse>
<g fillRule="nonzero" stroke="#D9D9D9">
<path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path>
<path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#FAFAFA"></path>
</g>
</g>
</svg></div>
<p class="ant-empty-description">No Data</p>
</div>
</div>
@ -155,7 +163,7 @@ exports[`renders ./components/transfer/demo/large-data.md correctly 1`] = `
<div class="ant-transfer-list-body-search-wrapper">
<div><input placeholder="Search here" type="text" class="ant-input ant-transfer-list-search"><span class="ant-transfer-list-search-action"><i aria-label="icon: search" class="anticon anticon-search"><svg viewBox="64 64 896 896" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></i></span></div>
</div>
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
@ -2156,7 +2164,7 @@ exports[`renders ./components/transfer/demo/large-data.md correctly 1`] = `
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
</div>
<div class="ant-transfer-operation"><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
@ -2171,8 +2179,16 @@ exports[`renders ./components/transfer/demo/large-data.md correctly 1`] = `
<div><input placeholder="Search here" type="text" class="ant-input ant-transfer-list-search"><span class="ant-transfer-list-search-action"><i aria-label="icon: search" class="anticon anticon-search"><svg viewBox="64 64 896 896" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></i></span></div>
</div>
<div class="ant-transfer-list-body-not-found">
<div class="ant-empty ant-empty-small">
<div class="ant-empty-image"><img alt="No Data" src=""></div>
<div class="ant-empty ant-empty-normal ant-empty-small">
<div class="ant-empty-image"><svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 1)" fill="none" fillRule="evenodd">
<ellipse fill="#F5F5F5" cx="32" cy="33" rx="32" ry="7"></ellipse>
<g fillRule="nonzero" stroke="#D9D9D9">
<path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path>
<path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#FAFAFA"></path>
</g>
</g>
</svg></div>
<p class="ant-empty-description">No Data</p>
</div>
</div>
@ -2189,7 +2205,7 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
<div class="ant-transfer-list-body-search-wrapper">
<div><input placeholder="Search here" type="text" class="ant-input ant-transfer-list-search"><span class="ant-transfer-list-search-action"><i aria-label="icon: search" class="anticon anticon-search"><svg viewBox="64 64 896 896" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></i></span></div>
</div>
<ul class="ant-transfer-list-content">
<div class="ant-transfer-list-content">
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
@ -2210,7 +2226,7 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
<div class="LazyLoad" style="height: 32px;"></div>
</ul>
</div>
</div>
</div>
<div class="ant-transfer-operation"><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
@ -2225,8 +2241,16 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
<div><input placeholder="Search here" type="text" class="ant-input ant-transfer-list-search"><span class="ant-transfer-list-search-action"><i aria-label="icon: search" class="anticon anticon-search"><svg viewBox="64 64 896 896" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></i></span></div>
</div>
<div class="ant-transfer-list-body-not-found">
<div class="ant-empty ant-empty-small">
<div class="ant-empty-image"><img alt="No Data" src=""></div>
<div class="ant-empty ant-empty-normal ant-empty-small">
<div class="ant-empty-image"><svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 1)" fill="none" fillRule="evenodd">
<ellipse fill="#F5F5F5" cx="32" cy="33" rx="32" ry="7"></ellipse>
<g fillRule="nonzero" stroke="#D9D9D9">
<path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"></path>
<path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="#FAFAFA"></path>
</g>
</g>
</svg></div>
<p class="ant-empty-description">No Data</p>
</div>
</div>
@ -2234,3 +2258,224 @@ exports[`renders ./components/transfer/demo/search.md correctly 1`] = `
</div>
</div>
`;
exports[`renders ./components/transfer/demo/table-transfer.md correctly 1`] = `
<div>
<div class="ant-transfer ant-transfer-customize-list" leftcolumns="[object Object],[object Object],[object Object]" rightcolumns="[object Object]">
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><span class="ant-transfer-list-header-selected"><span>14 items</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body">
<div class="ant-transfer-list-body-customize-wrapper">
<div class="ant-table-wrapper">
<div class="ant-spin-nested-loading">
<div class="ant-spin-container">
<div class="ant-table ant-table-scroll-position-left ant-table-small">
<div class="ant-table-content">
<!---->
<div class="ant-table-body">
<table class="">
<colgroup>
<col style="width: 62px; min-width: 62px;">
<col>
<col>
<col>
</colgroup>
<thead class="ant-table-thead">
<tr>
<th key="selection-column" class="ant-table-selection-column">
<div>
<div class="ant-table-selection"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></div>
</div>
</th>
<th key="title" class="">
<div>Name</div>
</th>
<th key="tag" class="">
<div>Tag</div>
</th>
<th key="description" class="">
<div>Description</div>
</th>
</tr>
</thead>
<tbody class="ant-table-tbody">
<tr class="ant-table-row ant-table-row-level-0" data-row-key="0">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content1</td>
<td></td>
<td>description of content1</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="1">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content2</td>
<td></td>
<td>description of content2</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="3">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content4</td>
<td></td>
<td>description of content4</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="4">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content5</td>
<td></td>
<td>description of content5</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="6">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content7</td>
<td></td>
<td>description of content7</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="7">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content8</td>
<td></td>
<td>description of content8</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="9">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content10</td>
<td></td>
<td>description of content10</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="10">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content11</td>
<td></td>
<td>description of content11</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="12">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content13</td>
<td></td>
<td>description of content13</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="13">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content14</td>
<td></td>
<td>description of content14</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ul unselectable="unselectable" class="ant-pagination mini ant-table-pagination">
<li title="Previous Page" aria-disabled="true" class="ant-pagination-disabled ant-pagination-prev"><a class="ant-pagination-item-link"><i aria-label="icon: left" class="anticon anticon-left"><svg viewBox="64 64 896 896" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path>
</svg></i></a></li>
<li title="1" tabindex="0" class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"><a>1</a></li>
<li title="2" tabindex="0" class="ant-pagination-item ant-pagination-item-2"><a>2</a></li>
<li title="Next Page" tabindex="0" class=" ant-pagination-next"><a class="ant-pagination-item-link"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path>
</svg></i></a></li>
<!---->
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ant-transfer-operation"><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path>
</svg></i></button><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: left" class="anticon anticon-left"><svg viewBox="64 64 896 896" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path>
</svg></i></button></div>
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><span class="ant-transfer-list-header-selected"><span>6 items</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body">
<div class="ant-transfer-list-body-customize-wrapper">
<div class="ant-table-wrapper">
<div class="ant-spin-nested-loading">
<div class="ant-spin-container">
<div class="ant-table ant-table-scroll-position-left ant-table-small">
<div class="ant-table-content">
<!---->
<div class="ant-table-body">
<table class="">
<colgroup>
<col style="width: 62px; min-width: 62px;">
<col>
</colgroup>
<thead class="ant-table-thead">
<tr>
<th key="selection-column" class="ant-table-selection-column">
<div>
<div class="ant-table-selection"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></div>
</div>
</th>
<th key="title" class="">
<div>Name</div>
</th>
</tr>
</thead>
<tbody class="ant-table-tbody">
<tr class="ant-table-row ant-table-row-level-0" data-row-key="2">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content3</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="5">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content6</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="8">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content9</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="11">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content12</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="14">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content15</td>
</tr>
<tr class="ant-table-row ant-table-row-level-0" data-row-key="17">
<td class="ant-table-selection-column"><span><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input" value=""><span class="ant-checkbox-inner"></span></span></label></span></td>
<td><span class="ant-table-row-indent indent-level-0" style="padding-left: 0px;"></span>
<!---->content18</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ul unselectable="unselectable" class="ant-pagination mini ant-table-pagination">
<li title="Previous Page" aria-disabled="true" class="ant-pagination-disabled ant-pagination-prev"><a class="ant-pagination-item-link"><i aria-label="icon: left" class="anticon anticon-left"><svg viewBox="64 64 896 896" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path>
</svg></i></a></li>
<li title="1" tabindex="0" class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"><a>1</a></li>
<li title="Next Page" aria-disabled="true" class="ant-pagination-disabled ant-pagination-next"><a class="ant-pagination-item-link"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class="">
<path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path>
</svg></i></a></li>
<!---->
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div> <button type="button" role="switch" class="ant-switch" style="margin-top: 16px;"><span class="ant-switch-inner">disabled</span></button> <button type="button" role="switch" class="ant-switch" style="margin-top: 16px;"><span class="ant-switch-inner">showSearch</span></button>
</div>
`;

View File

@ -4,9 +4,12 @@ exports[`Transfer should render correctly 1`] = `
<div class="ant-transfer">
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" checked="checked" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>1/2 items</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body"><span tag="ul" class="ant-transfer-list-content"><li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" checked="checked" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li>
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li>
</span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" checked="checked" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
</ul>
</div>
</div>
<div class="ant-transfer-operation"><button type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" focusable="false" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true">
<path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path>
@ -15,7 +18,11 @@ exports[`Transfer should render correctly 1`] = `
</svg></i></button></div>
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>1 item</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body"><span tag="ul" class="ant-transfer-list-content"><li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li></span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
</ul>
</div>
</div>
</div>
`;
@ -24,7 +31,11 @@ exports[`Transfer should show sorted targetkey 1`] = `
<div class="ant-transfer">
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>1 item</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body"><span tag="ul" class="ant-transfer-list-content"><li title="a" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span>a</span></li></span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<li title="a" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text">a</span></li>
</ul>
</div>
</div>
<div class="ant-transfer-operation"><button disabled="disabled" type="button" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only"><i aria-label="icon: right" class="anticon anticon-right"><svg viewBox="64 64 896 896" focusable="false" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true">
<path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path>
@ -33,9 +44,12 @@ exports[`Transfer should show sorted targetkey 1`] = `
</svg></i></button></div>
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>2 items</span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body"><span tag="ul" class="ant-transfer-list-content"><li title="c" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span>c</span></li>
<li title="b" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span>b</span></li>
</span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<li title="c" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text">c</span></li>
<li title="b" class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text">b</span></li>
</ul>
</div>
</div>
</div>
`;

View File

@ -3,9 +3,12 @@
exports[`List should render correctly 1`] = `
<div class="ant-transfer-list">
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper"><span class="ant-checkbox ant-checkbox-indeterminate"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-header-selected"><span>1/3 </span><span class="ant-transfer-list-header-title"></span></span></div>
<div class="ant-transfer-list-body"><span tag="ul" class="ant-transfer-list-content"><li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" checked="checked" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li>
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li>
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span></span></li>
</span></div>
<div class="ant-transfer-list-body">
<ul class="ant-transfer-list-content">
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" checked="checked" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="disabled" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span></label><span class="ant-transfer-list-content-item-text"></span></li>
</ul>
</div>
</div>
`;

View File

@ -108,7 +108,7 @@ describe('Search', () => {
input.trigger('input');
expect(errorSpy.mock.calls[0][0]).toMatch(
'Warning: `searchChange` in Transfer is deprecated. Please use `search` instead.',
'Warning: [antdv: Transfer] `searchChange` in Transfer is deprecated. Please use `search` instead.',
);
expect(onSearchChange.mock.calls[0][0]).toEqual('left');
expect(onSearchChange.mock.calls[0][1].target.value).toEqual('a');

View File

@ -3,6 +3,7 @@ import Basic from './basic.md';
import Search from './search.md';
import Advanced from './advanced.md';
import CustomItem from './custom-item.md';
import TableTransfer from './table-transfer';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -44,10 +45,13 @@ export default {
return (
<div>
<md cn={md.cn} us={md.us} />
<demo-sort cols={1}>
<Basic />
<Search />
<Advanced />
<CustomItem />
<TableTransfer />
</demo-sort>
<api>
<template slot="cn">
<CN />

View File

@ -0,0 +1,155 @@
<cn>
#### 表格穿梭框
使用 Table 组件作为自定义渲染列表。
</cn>
<us>
#### Table Transfer
Customize render list with Table component.
</us>
```tpl
<template>
<div>
<a-transfer
:dataSource="mockData"
:targetKeys="targetKeys"
:disabled="disabled"
:showSearch="showSearch"
@change="onChange"
:filterOption="(inputValue, item) => item.title.indexOf(inputValue) !== -1"
:leftColumns="leftColumns"
:rightColumns="rightColumns"
:showSelectAll="false"
>
<template
slot="children"
slot-scope="{
props: {
direction,
filteredItems,
selectedKeys,
disabled: listDisabled
}, on: {
itemSelectAll,
itemSelect,
}
}"
>
<a-table
:rowSelection="getRowSelection({disabled: listDisabled, selectedKeys, itemSelectAll, itemSelect})"
:columns="direction === 'left' ? leftColumns : rightColumns"
:dataSource="filteredItems"
size="small"
:style="{ pointerEvents: listDisabled ? 'none' : null }"
:customRow="({ key, disabled: itemDisabled }) => ({
on: {
click: () => {
if (itemDisabled || listDisabled) return;
itemSelect(key, !selectedKeys.includes(key));
}
}
})"
/>
</template>
</a-transfer>
<a-switch
unCheckedChildren="disabled"
checkedChildren="disabled"
:checked="disabled"
@change="triggerDisable"
style="margin-top: 16px"
/>
<a-switch
unCheckedChildren="showSearch"
checkedChildren="showSearch"
:checked="showSearch"
@change="triggerShowSearch"
style="margin-top: 16px"
/>
</div>
</template>
<script>
import Vue from 'vue';
import difference from 'lodash/difference';
import { Table } from 'ant-design-vue';
import 'ant-design-vue/table/style';
Vue.use(Table);
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 4 === 0,
});
}
const originTargetKeys = mockData.filter(item => +item.key % 3 > 1).map(item => item.key);
const leftTableColumns = [
{
dataIndex: 'title',
title: 'Name',
},
{
dataIndex: 'tag',
title: 'Tag',
},
{
dataIndex: 'description',
title: 'Description',
},
];
const rightTableColumns = [
{
dataIndex: 'title',
title: 'Name',
},
];
export default {
data() {
return {
mockData,
targetKeys: originTargetKeys,
disabled: false,
showSearch: false,
leftColumns: leftTableColumns,
rightColumns: rightTableColumns,
};
},
methods: {
onChange(nextTargetKeys) {
this.targetKeys = nextTargetKeys;
},
triggerDisable(disabled) {
this.disabled = disabled;
},
triggerShowSearch(showSearch) {
this.showSearch = showSearch;
},
getRowSelection({ disabled, selectedKeys, itemSelectAll, itemSelect }) {
return {
getCheckboxProps: item => ({ props: { disabled: disabled || item.disabled } }),
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows
.filter(item => !item.disabled)
.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, selectedKeys)
: difference(selectedKeys, treeSelectedKeys);
itemSelectAll(diffKeys, selected);
},
onSelect({ key }, selected) {
itemSelect(key, selected);
},
selectedRowKeys: selectedKeys,
};
},
},
};
</script>
```

View File

@ -1,20 +1,21 @@
## API
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop. | \[{key: string.isRequired,title: string.isRequired,description: string,disabled: bool}\] | \[] |
| disabled | Whether disabled transfer | boolean | false |
| filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | |
| footer | customize the progress dot by setting a scoped slot | slot="footer" slot-scope="props" | |
| lazy | property of vc-lazy-load for lazy rendering items. Turn off it by set to `false`. | object\|boolean | `{ height: 32, offset: 32 }` |
| listStyle | A custom CSS style used for rendering the transfer columns. | object | |
| locale | i18n text including filter, empty text, item unit, etc | object | `{ itemUnit: 'item', itemsUnit: 'items', notFoundContent: 'The list is empty', searchPlaceholder: 'Search here' }` |
| operations | A set of operations that are sorted from top to bottom. | string\[] | \['>', '<'] |
| render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a element and `value` is for title | Function(record) | |
| selectedKeys | A set of keys of selected items. | string\[] | \[] |
| showSearch | If included, a search box is shown on each column. | boolean | false |
| targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] |
| titles | A set of titles that are sorted from left to right. | string\[] | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop. | \[{key: string.isRequired,title: string.isRequired,description: string,disabled: bool}\] | \[] | |
| disabled | Whether disabled transfer | boolean | false | |
| filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | | |
| footer | customize the progress dot by setting a scoped slot | slot="footer" slot-scope="props" | | |
| lazy | property of vc-lazy-load for lazy rendering items. Turn off it by set to `false`. | object\|boolean | `{ height: 32, offset: 32 }` | |
| listStyle | A custom CSS style used for rendering the transfer columns. | object | | |
| locale | i18n text including filter, empty text, item unit, etc | object | `{ itemUnit: 'item', itemsUnit: 'items', notFoundContent: 'The list is empty', searchPlaceholder: 'Search here' }` | |
| operations | A set of operations that are sorted from top to bottom. | string\[] | \['>', '<'] | |
| render | The function to generate the item shown on a column. Based on an record (element of the dataSource array), this function should return a element which is generated from that record. Also, it can return a plain object with `value` and `label`, `label` is a element and `value` is for title | Function(record) | | |
| selectedKeys | A set of keys of selected items. | string\[] | \[] | |
| showSearch | If included, a search box is shown on each column. | boolean | false | |
| showSelectAll | Show select all checkbox on the header | boolean | true | 1.5.0 |
| targetKeys | A set of keys of elements that are listed on the right column. | string\[] | \[] | |
| titles | A set of titles that are sorted from left to right. | string\[] | - | |
### events
@ -25,6 +26,57 @@
| search | A callback function which is executed when search field are changed | (direction: 'left'\|'right', value: string): void | - |
| selectChange | A callback function which is executed when selected items are changed. | (sourceSelectedKeys, targetSelectedKeys): void | |
### Render Props
New in 1.5.0. Transfer accept `children` to customize render list, using follow props:
```json
{
"props": {
"direction": String,
"disabled": Boolean,
"filteredItems": Array,
"selectedKeys": Array
},
"on": {
"itemSelect": Function,
"itemSelectAll": Function
}
}
```
| Property | Description | Type | Version |
| --------------- | ----------------------- | ----------------------------------- | ------- |
| direction | List render direction | 'left' \| 'right' | 1.5.0 |
| disabled | Disable list or not | boolean | 1.5.0 |
| filteredItems | Filtered items | TransferItem[] | 1.5.0 |
| onItemSelect | Select item | (key: string, selected: boolean) | 1.5.0 |
| onItemSelectAll | Select a group of items | (keys: string[], selected: boolean) | 1.5.0 |
| selectedKeys | Selected items | string[] | 1.5.0 |
#### example
```html
<a-transfer>
<template
slot="children"
slot-scope="{
props: {
direction,
filteredItems,
selectedKeys,
disabled: listDisabled
}, on: {
itemSelectAll,
itemSelect,
}
}"
>
<your-component />
<template>
</a-transfer>
```
## Warning
According the standard of Vue, the key should always be supplied directly to the elements in the array. In Transfer, the keys should be set on the elements included in `dataSource` array. By default, `key` property is used as an unique identifier.

View File

@ -32,7 +32,7 @@ export const TransferProps = {
targetKeys: PropTypes.arrayOf(PropTypes.string),
selectedKeys: PropTypes.arrayOf(PropTypes.string),
render: PropTypes.func,
listStyle: PropTypes.object,
listStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
operationStyle: PropTypes.object,
titles: PropTypes.arrayOf(PropTypes.string),
operations: PropTypes.arrayOf(PropTypes.string),
@ -43,6 +43,7 @@ export const TransferProps = {
locale: PropTypes.object,
rowKey: PropTypes.func,
lazy: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
showSelectAll: PropTypes.bool,
};
export const TransferLocale = {
@ -59,6 +60,7 @@ const Transfer = {
dataSource: [],
locale: {},
showSearch: false,
listStyle: () => {},
}),
inject: {
configProvider: { default: () => ConfigConsumerProps },
@ -70,12 +72,6 @@ const Transfer = {
// 'Transfer[notFoundContent] and Transfer[searchPlaceholder] will be removed, ' +
// 'please use Transfer[locale] instead.',
// )
this.separatedDataSource =
{
leftDataSource: [],
rightDataSource: [],
} | null;
const { selectedKeys = [], targetKeys = [] } = this;
return {
leftFilter: '',
@ -112,6 +108,33 @@ const Transfer = {
},
},
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;
@ -136,37 +159,6 @@ const Transfer = {
});
}
},
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;
@ -198,51 +190,61 @@ const Transfer = {
this.moveTo('right');
},
handleSelectChange(direction, holder) {
const { sourceSelectedKeys, targetSelectedKeys } = this;
onItemSelectAll(direction, selectedKeys, checkAll) {
const originalSelectedKeys = this.$data[this.getSelectedKeysName(direction)] || [];
if (direction === 'left') {
this.$emit('selectChange', holder, targetSelectedKeys);
let mergedCheckedKeys = [];
if (checkAll) {
// Merge current keys with origin key
mergedCheckedKeys = Array.from(new Set([...originalSelectedKeys, ...selectedKeys]));
} else {
this.$emit('selectChange', sourceSelectedKeys, holder);
// Remove current keys from origin keys
mergedCheckedKeys = originalSelectedKeys.filter(key => selectedKeys.indexOf(key) === -1);
}
},
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.handleSelectChange(direction, mergedCheckedKeys);
if (!this.$props.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
[this.getSelectedKeysName(direction)]: mergedCheckedKeys,
});
}
},
handleLeftSelectAll(filteredDataSource, checkAll) {
this.handleSelectAll('left', filteredDataSource, checkAll);
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) {
this.handleSelectAll('right', 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;
this.setState({
// add filter
[`${direction}Filter`]: value,
});
if (getListeners(this).searchChange) {
warning(false, '`searchChange` in Transfer is deprecated. Please use `search` instead.');
warning(
false,
'Transfer',
'`searchChange` in Transfer is deprecated. Please use `search` instead.',
);
this.$emit('searchChange', direction, e);
}
this.$emit('search', direction, value);
@ -256,9 +258,6 @@ const Transfer = {
},
handleClear(direction) {
this.setState({
[`${direction}Filter`]: '',
});
this.$emit('search', direction, '');
},
@ -269,15 +268,15 @@ const Transfer = {
this.handleClear('right');
},
handleSelect(direction, selectedItem, checked) {
onItemSelect(direction, selectedKey, checked) {
const { sourceSelectedKeys, targetSelectedKeys } = this;
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
const index = holder.indexOf(selectedItem.key);
const index = holder.indexOf(selectedKey);
if (index > -1) {
holder.splice(index, 1);
}
if (checked) {
holder.push(selectedItem.key);
holder.push(selectedKey);
}
this.handleSelectChange(direction, holder);
@ -288,6 +287,11 @@ const Transfer = {
}
},
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);
},
@ -296,6 +300,13 @@ const Transfer = {
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);
},
@ -307,32 +318,46 @@ const Transfer = {
this.handleScroll('right', e);
},
getTitles(transferLocale) {
if (this.titles) {
return this.titles;
handleSelectChange(direction, holder) {
const { sourceSelectedKeys, targetSelectedKeys } = this;
if (direction === 'left') {
this.$emit('selectChange', holder, targetSelectedKeys);
} else {
this.$emit('selectChange', sourceSelectedKeys, holder);
}
return transferLocale.titles || ['', ''];
},
handleListStyle(listStyle, direction) {
if (typeof listStyle === 'function') {
return listStyle({ direction });
}
return listStyle;
},
getSelectedKeysName(direction) {
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
},
separateDataSource() {
const { dataSource, rowKey, targetKeys = [] } = this.$props;
getLocale(transferLocale, renderEmpty) {
const h = this.$createElement;
// Keep old locale props still working.
const oldLocale = {
notFoundContent: renderEmpty(h, 'Transfer'),
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,
};
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) {
@ -346,55 +371,59 @@ const Transfer = {
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 {
leftFilter,
rightFilter,
sourceSelectedKeys,
targetSelectedKeys,
$scopedSlots,
} = this;
const { sourceSelectedKeys, targetSelectedKeys, $scopedSlots } = this;
const { body, footer } = $scopedSlots;
const renderItem = props.render;
const { leftDataSource, rightDataSource } = this.separateDataSource(this.$props);
const { leftDataSource, rightDataSource } = this.separateDataSource();
const leftActive = targetSelectedKeys.length > 0;
const rightActive = sourceSelectedKeys.length > 0;
const cls = classNames(prefixCls, disabled && `${prefixCls}-disabled`);
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}
filter={leftFilter}
filterOption={filterOption}
style={listStyle}
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]}
@ -406,24 +435,29 @@ const Transfer = {
disabled={disabled}
/>
<List
key="rightList"
prefixCls={`${prefixCls}-list`}
titleText={titles[1]}
dataSource={rightDataSource}
filter={rightFilter}
filterOption={filterOption}
style={listStyle}
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}

View File

@ -1,20 +1,21 @@
## API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外。 | \[{key: string.isRequired,title: string.isRequired,description: string,disabled: bool}\]\[] | \[] |
| disabled | 是否禁用 | boolean | false |
| filterOption | 接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | (inputValue, option): boolean | |
| footer | 可以设置为一个 作用域插槽 | slot="footer" slot-scope="props" | |
| lazy | Transfer 使用了 [vc-lazy-load]优化性能,这里可以设置相关参数。设为 `false` 可以关闭懒加载。 | object\|boolean | `{ height: 32, offset: 32 }` |
| listStyle | 两个穿梭框的自定义样式 | object | |
| locale | 各种语言 | object | `{ itemUnit: '项', itemsUnit: '项', notFoundContent: '列表为空', searchPlaceholder: '请输入搜索内容' }` |
| operations | 操作文案集合,顺序从上至下 | string\[] | \['>', '<'] |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 element。或者返回一个普通对象其中 `label` 字段为 element`value` 字段为 title | Function(record) | |
| selectedKeys | 设置哪些项应该被选中 | string\[] | \[] |
| showSearch | 是否显示搜索框 | boolean | false |
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] |
| titles | 标题集合,顺序从左至右 | string\[] | \['', ''] |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外。 | \[{key: string.isRequired,title: string.isRequired,description: string,disabled: bool}\]\[] | \[] | |
| disabled | 是否禁用 | boolean | false | |
| filterOption | 接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | (inputValue, option): boolean | | |
| footer | 可以设置为一个 作用域插槽 | slot="footer" slot-scope="props" | | |
| lazy | Transfer 使用了 [vc-lazy-load]优化性能,这里可以设置相关参数。设为 `false` 可以关闭懒加载。 | object\|boolean | `{ height: 32, offset: 32 }` | |
| listStyle | 两个穿梭框的自定义样式 | object | | |
| locale | 各种语言 | object | `{ itemUnit: '项', itemsUnit: '项', notFoundContent: '列表为空', searchPlaceholder: '请输入搜索内容' }` | |
| operations | 操作文案集合,顺序从上至下 | string\[] | \['>', '<'] | |
| render | 每行数据渲染函数,该函数的入参为 `dataSource` 中的项,返回值为 element。或者返回一个普通对象其中 `label` 字段为 element`value` 字段为 title | Function(record) | | |
| selectedKeys | 设置哪些项应该被选中 | string\[] | \[] | |
| showSearch | 是否显示搜索框 | boolean | false | |
| showSelectAll | 是否展示全选勾选框 | boolean | true | 1.5.0 |
| targetKeys | 显示在右侧框数据的 key 集合 | string\[] | \[] | |
| titles | 标题集合,顺序从左至右 | string\[] | \['', ''] | |
### 事件
@ -25,6 +26,57 @@
| search | 搜索框内容时改变时的回调函数 | (direction: 'left'\|'right', value: string): void | - |
| selectChange | 选中项发生改变时的回调函数 | (sourceSelectedKeys, targetSelectedKeys): void | |
### Render Props
1.5.0 新增。Transfer 支持接收 `children` 自定义渲染列表,并返回以下参数:
```json
{
"props": {
"direction": String,
"disabled": Boolean,
"filteredItems": Array,
"selectedKeys": Array
},
"on": {
"itemSelect": Function,
"itemSelectAll": Function
}
}
```
| 参数 | 说明 | 类型 | 版本 |
| ------------- | -------------- | ----------------------------------- | ----- |
| direction | 渲染列表的方向 | 'left' \| 'right' | 1.5.0 |
| disabled | 是否禁用列表 | boolean | 1.5.0 |
| filteredItems | 过滤后的数据 | TransferItem[] | 1.5.0 |
| selectedKeys | 选中的条目 | string[] | 1.5.0 |
| itemSelect | 勾选条目 | (key: string, selected: boolean) | 1.5.0 |
| itemSelectAll | 勾选一组条目 | (keys: string[], selected: boolean) | 1.5.0 |
#### 参考示例
```html
<a-transfer>
<template
slot="children"
slot-scope="{
props: {
direction,
filteredItems,
selectedKeys,
disabled: listDisabled
}, on: {
itemSelectAll,
itemSelect,
}
}"
>
<your-component />
<template>
</a-transfer>
```
## 注意
按照 Vue 最新的规范,所有的组件数组最好绑定 key。在 Transfer 中,`dataSource`里的数据值需要指定 `key` 值。对于 `dataSource` 默认将每列数据的 `key` 属性作为唯一的标识。

View File

@ -1,15 +1,14 @@
import classNames from 'classnames';
import PropTypes from '../_util/vue-types';
import { isValidElement, initDefaultProps } from '../_util/props-util';
import { isValidElement, initDefaultProps, getListeners } from '../_util/props-util';
import BaseMixin from '../_util/BaseMixin';
import getTransitionProps from '../_util/getTransitionProps';
import Checkbox from '../checkbox';
import Search from './search';
import Item from './item';
import defaultRenderList from './renderListBody';
import triggerEvent from '../_util/triggerEvent';
import addEventListener from '../vc-util/Dom/addEventListener';
function noop() {}
const defaultRender = () => null;
const TransferItem = {
key: PropTypes.string.isRequired,
@ -44,11 +43,26 @@ export const TransferListProps = {
itemUnit: PropTypes.string,
itemsUnit: PropTypes.string,
body: PropTypes.any,
renderList: PropTypes.any,
footer: PropTypes.any,
lazy: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
disabled: PropTypes.bool,
direction: PropTypes.string,
showSelectAll: PropTypes.bool,
};
function renderListNode(h, renderList, props) {
let bodyContent = renderList ? renderList(props) : null;
const customize = !!bodyContent;
if (!customize) {
bodyContent = defaultRenderList(h, props);
}
return {
customize,
bodyContent,
};
}
export default {
name: 'TransferList',
mixins: [BaseMixin],
@ -56,35 +70,33 @@ export default {
dataSource: [],
titleText: '',
showSearch: false,
renderItem: noop,
lazy: {},
}),
data() {
this.timer = null;
this.triggerScrollTimer = null;
return {
mounted: false,
filterValue: '',
};
},
mounted() {
this.timer = setTimeout(() => {
this.setState({
mounted: true,
});
}, 0);
this.$nextTick(() => {
if (this.$refs.listContentWrapper) {
const listContentWrapperDom = this.$refs.listContentWrapper.$el;
this.scrollEvent = addEventListener(listContentWrapperDom, 'scroll', this.handleScroll);
}
});
},
// mounted() {
// this.timer = setTimeout(() => {
// this.setState({
// mounted: true,
// });
// }, 0);
// this.$nextTick(() => {
// if (this.$refs.listContentWrapper) {
// const listContentWrapperDom = this.$refs.listContentWrapper.$el;
// this.scrollEvent = addEventListener(listContentWrapperDom, 'scroll', this.handleScroll);
// }
// });
// },
beforeDestroy() {
clearTimeout(this.timer);
clearTimeout(this.triggerScrollTimer);
if (this.scrollEvent) {
this.scrollEvent.remove();
}
// if (this.scrollEvent) {
// this.scrollEvent.remove();
// }
},
updated() {
this.$nextTick(() => {
@ -101,23 +113,132 @@ export default {
handleScroll(e) {
this.$emit('scroll', e);
},
getCheckStatus(filteredDataSource) {
getCheckStatus(filteredItems) {
const { checkedKeys } = this.$props;
if (checkedKeys.length === 0) {
return 'none';
} else if (filteredDataSource.every(item => checkedKeys.indexOf(item.key) >= 0)) {
}
if (filteredItems.every(item => checkedKeys.indexOf(item.key) >= 0 || !!item.disabled)) {
return 'all';
}
return 'part';
},
getFilteredItems(dataSource, filterValue) {
const filteredItems = [];
const filteredRenderItems = [];
dataSource.forEach(item => {
const renderedItem = this.renderItemHtml(item);
const { renderedText } = renderedItem;
// Filter skip
if (filterValue && filterValue.trim() && !this.matchFilter(renderedText, item)) {
return null;
}
filteredItems.push(item);
filteredRenderItems.push(renderedItem);
});
return { filteredItems, filteredRenderItems };
},
getListBody(
prefixCls,
searchPlaceholder,
filterValue,
filteredItems,
notFoundContent,
bodyDom,
filteredRenderItems,
checkedKeys,
renderList,
showSearch,
disabled,
) {
const search = showSearch ? (
<div class={`${prefixCls}-body-search-wrapper`}>
<Search
prefixCls={`${prefixCls}-search`}
onChange={this._handleFilter}
handleClear={this._handleClear}
placeholder={searchPlaceholder}
value={filterValue}
disabled={disabled}
/>
</div>
) : null;
let listBody = bodyDom;
if (!listBody) {
let bodyNode;
const { bodyContent, customize } = renderListNode(this.$createElement, renderList, {
props: { ...this.$props, filteredItems, filteredRenderItems, selectedKeys: checkedKeys },
on: getListeners(this),
});
// 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.length ? (
bodyContent
) : (
<div class={`${prefixCls}-body-not-found`}>{notFoundContent}</div>
);
}
listBody = (
<div
class={classNames(
showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`,
)}
>
{search}
{bodyNode}
</div>
);
}
return listBody;
},
getCheckBox(filteredItems, showSelectAll, disabled) {
const checkStatus = this.getCheckStatus(filteredItems);
const checkedAll = checkStatus === 'all';
const checkAllCheckbox = showSelectAll !== false && (
<Checkbox
disabled={disabled}
checked={checkedAll}
indeterminate={checkStatus === 'part'}
onChange={() => {
// Only select enabled items
this.$emit(
'itemSelectAll',
filteredItems.filter(item => !item.disabled).map(({ key }) => key),
!checkedAll,
);
}}
/>
);
return checkAllCheckbox;
},
_handleSelect(selectedItem) {
const { checkedKeys } = this.$props;
const result = checkedKeys.some(key => key === selectedItem.key);
this.handleSelect(selectedItem, !result);
},
_handleFilter(e) {
this.handleFilter(e);
if (!e.target.value) {
const { handleFilter } = this.$props;
const {
target: { value: filterValue },
} = e;
this.setState({ filterValue });
handleFilter(e);
if (!filterValue) {
return;
}
// Manually trigger scroll event for lazy search bug
@ -131,22 +252,25 @@ export default {
}, 0);
},
_handleClear(e) {
this.setState({ filterValue: '' });
this.handleClear(e);
},
matchFilter(text, item) {
const { filter, filterOption } = this.$props;
const { filterValue } = this.$data;
const { filterOption } = this.$props;
if (filterOption) {
return filterOption(filter, item);
return filterOption(filterValue, item);
}
return text.indexOf(filter) >= 0;
return text.indexOf(filterValue) >= 0;
},
renderItemHtml(item) {
const { renderItem = noop } = this.$props;
const { renderItem = defaultRender } = this.$props;
const renderResult = renderItem(item);
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
return {
renderedText: isRenderResultPlain ? renderResult.value : renderResult,
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
item,
};
},
filterNull(arr) {
@ -157,21 +281,22 @@ export default {
},
render() {
const { filterValue } = this.$data;
const {
prefixCls,
dataSource,
titleText,
checkedKeys,
lazy,
disabled,
body,
footer,
showSearch,
filter,
searchPlaceholder,
notFoundContent,
itemUnit,
itemsUnit,
renderList,
showSelectAll,
} = this.$props;
// Custom Layout
@ -182,96 +307,31 @@ export default {
[`${prefixCls}-with-footer`]: !!footerDom,
});
const filteredDataSource = [];
const totalDataSource = [];
// ====================== Get filtered, checked item list ======================
const showItems = dataSource.map(item => {
const { renderedText, renderedEl } = this.renderItemHtml(item);
if (filter && filter.trim() && !this.matchFilter(renderedText, item)) {
return null;
}
const { filteredItems, filteredRenderItems } = this.getFilteredItems(dataSource, filterValue);
// all show items
totalDataSource.push(item);
if (!item.disabled) {
// response to checkAll items
filteredDataSource.push(item);
}
const checked = checkedKeys.indexOf(item.key) >= 0;
return (
<Item
disabled={disabled}
key={item.key}
item={item}
lazy={lazy}
renderedText={renderedText}
renderedEl={renderedEl}
checked={checked}
prefixCls={prefixCls}
onClick={this._handleSelect}
/>
);
});
// ================================= List Body =================================
const unit = dataSource.length > 1 ? itemsUnit : itemUnit;
const search = showSearch ? (
<div class={`${prefixCls}-body-search-wrapper`}>
<Search
prefixCls={`${prefixCls}-search`}
onChange={this._handleFilter}
handleClear={this.handleClear}
placeholder={searchPlaceholder}
value={filter}
disabled={disabled}
/>
</div>
) : null;
const transitionName = this.mounted ? `${prefixCls}-content-item-highlight` : '';
const transitionProps = getTransitionProps(transitionName, {
leave: noop,
});
const searchNotFound = showItems.every(item => item === null) && (
<div class={`${prefixCls}-body-not-found`}>{notFoundContent}</div>
);
const listBody = bodyDom || (
<div
class={classNames(
showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`,
)}
>
{search}
{!searchNotFound && (
<transition-group
{...transitionProps}
tag="ul"
class={`${prefixCls}-content`}
ref="listContentWrapper"
>
{showItems}
</transition-group>
)}
{searchNotFound}
</div>
const listBody = this.getListBody(
prefixCls,
searchPlaceholder,
filterValue,
filteredItems,
notFoundContent,
bodyDom,
filteredRenderItems,
checkedKeys,
renderList,
showSearch,
disabled,
);
const listFooter = footerDom ? <div class={`${prefixCls}-footer`}>{footerDom}</div> : null;
const checkStatus = this.getCheckStatus(filteredDataSource);
const checkedAll = checkStatus === 'all';
const checkAllCheckbox = (
<Checkbox
ref="checkbox"
disabled={disabled}
checked={checkedAll}
indeterminate={checkStatus === 'part'}
onChange={() => {
this.handleSelectAll(filteredDataSource, checkedAll);
}}
/>
);
const checkAllCheckbox = this.getCheckBox(filteredItems, showSelectAll, disabled);
return (
<div class={listCls}>
@ -279,7 +339,7 @@ export default {
{checkAllCheckbox}
<span class={`${prefixCls}-header-selected`}>
<span>
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + totalDataSource.length}{' '}
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + filteredItems.length}{' '}
{unit}
</span>
<span class={`${prefixCls}-header-title`}>{titleText}</span>

View File

@ -0,0 +1,108 @@
import raf from '../_util/raf';
import ListItem from './ListItem';
import PropTypes from '../_util/vue-types';
import getTransitionProps from '../_util/getTransitionProps';
function noop() {}
const ListBody = {
name: 'ListBody',
inheritAttrs: false,
props: {
prefixCls: PropTypes.string,
filteredRenderItems: PropTypes.array.def([]),
lazy: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
selectedKeys: PropTypes.array,
disabled: PropTypes.bool,
},
data() {
return {
mounted: false,
};
},
computed: {
itemsLength() {
return this.filteredRenderItems ? this.filteredRenderItems.length : 0;
},
},
watch: {
itemsLength() {
this.$nextTick(() => {
const { lazy } = this.$props;
if (lazy !== false) {
const container = this.$el;
raf.cancel(this.lazyId);
this.lazyId = raf(() => {
if (container) {
const scrollEvent = new Event('scroll', { bubbles: true });
container.dispatchEvent(scrollEvent);
}
});
}
});
},
},
mounted() {
this.mountId = raf(() => {
this.mounted = true;
});
},
beforeDestroy() {
raf.cancel(this.mountId);
raf.cancel(this.lazyId);
},
methods: {
onItemSelect(item) {
const { selectedKeys } = this.$props;
const checked = selectedKeys.indexOf(item.key) >= 0;
this.$emit('itemSelect', item.key, !checked);
},
onScroll(e) {
this.$emit('scroll', e);
},
},
render() {
const { mounted } = this.$data;
const {
prefixCls,
filteredRenderItems,
lazy,
selectedKeys,
disabled: globalDisabled,
} = this.$props;
const items = filteredRenderItems.map(({ renderedEl, renderedText, item }) => {
const { disabled } = item;
const checked = selectedKeys.indexOf(item.key) >= 0;
return (
<ListItem
disabled={globalDisabled || disabled}
key={item.key}
item={item}
lazy={lazy}
renderedText={renderedText}
renderedEl={renderedEl}
checked={checked}
prefixCls={prefixCls}
onClick={this.onItemSelect}
/>
);
});
const transitionProps = getTransitionProps(
mounted ? `${prefixCls}-content-item-highlight` : '',
{
tag: 'ul',
nativeOn: {
scroll: this.onScroll,
},
leave: noop,
},
);
return (
<transition-group class={`${prefixCls}-content`} {...transitionProps}>
{items}
</transition-group>
);
},
};
export default (h, props) => <ListBody {...props} />;

View File

@ -47,7 +47,7 @@
text-align: center;
.@{iconfont-css-prefix} {
color: @disabled-color;
transition: all 0.3s;
transition: all 10s;
&:hover {
color: @text-color-secondary;
}