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-progress
parent
16ee0dd2f1
commit
8198cab549
|
|
@ -92,10 +92,15 @@ const Dropdown = defineComponent({
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||||
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
|
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
|
||||||
const child = getSlot(this)[0];
|
const child = getSlot(this)[0];
|
||||||
const dropdownTrigger = cloneElement(child, {
|
const dropdownTrigger = cloneElement(
|
||||||
class: classNames(child?.props?.class, `${prefixCls}-trigger`),
|
child,
|
||||||
disabled,
|
Object.assign(
|
||||||
});
|
{
|
||||||
|
class: classNames(child?.props?.class, `${prefixCls}-trigger`),
|
||||||
|
},
|
||||||
|
disabled ? { disabled } : {},
|
||||||
|
),
|
||||||
|
);
|
||||||
const triggerActions = disabled ? [] : typeof trigger === 'string' ? [trigger] : trigger;
|
const triggerActions = disabled ? [] : typeof trigger === 'string' ? [trigger] : trigger;
|
||||||
let alignPoint;
|
let alignPoint;
|
||||||
if (triggerActions && triggerActions.indexOf('contextmenu') !== -1) {
|
if (triggerActions && triggerActions.indexOf('contextmenu') !== -1) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,9 +19,9 @@ describe('Slider', () => {
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(document.body.innerHTML).toMatchSnapshot();
|
expect(document.body.innerHTML).toMatchSnapshot();
|
||||||
wrapper.findAll('.ant-slider-handle')[0].trigger('mouseleave');
|
wrapper.findAll('.ant-slider-handle')[0].trigger('mouseleave');
|
||||||
}, 0);
|
}, 100);
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(document.body.innerHTML).toMatchSnapshot();
|
expect(document.body.innerHTML).toMatchSnapshot();
|
||||||
}, 0);
|
}, 100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -775,9 +775,12 @@
|
||||||
// Transfer
|
// Transfer
|
||||||
// ---
|
// ---
|
||||||
@transfer-header-height: 40px;
|
@transfer-header-height: 40px;
|
||||||
|
@transfer-item-height: @height-base;
|
||||||
@transfer-disabled-bg: @disabled-bg;
|
@transfer-disabled-bg: @disabled-bg;
|
||||||
@transfer-list-height: 200px;
|
@transfer-list-height: 200px;
|
||||||
@transfer-item-hover-bg: @item-hover-bg;
|
@transfer-item-hover-bg: @item-hover-bg;
|
||||||
|
@transfer-item-padding-vertical: 6px;
|
||||||
|
@transfer-list-search-icon-top: 12px;
|
||||||
|
|
||||||
// Message
|
// Message
|
||||||
// ---
|
// ---
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,33 @@
|
||||||
import type { ExtractPropTypes, CSSProperties } from 'vue';
|
import type { ExtractPropTypes, CSSProperties } from 'vue';
|
||||||
import { defineComponent, inject } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
import VcTooltip from '../vc-tooltip';
|
import VcTooltip from '../vc-tooltip';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import getPlacements from './placements';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { PresetColorTypes } from '../_util/colors';
|
import { PresetColorTypes } from '../_util/colors';
|
||||||
import {
|
import warning from '../_util/warning';
|
||||||
hasProp,
|
import { getPropsSlot, getStyle, filterEmpty, isValidElement } from '../_util/props-util';
|
||||||
getComponent,
|
|
||||||
getStyle,
|
|
||||||
filterEmpty,
|
|
||||||
getSlot,
|
|
||||||
isValidElement,
|
|
||||||
} from '../_util/props-util';
|
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import type { triggerTypes, placementTypes } from './abstractTooltipProps';
|
||||||
import abstractTooltipProps from './abstractTooltipProps';
|
import abstractTooltipProps from './abstractTooltipProps';
|
||||||
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
import getPlacements, { AdjustOverflow, PlacementsConfig } from './placements';
|
||||||
|
|
||||||
|
export { AdjustOverflow, PlacementsConfig };
|
||||||
|
|
||||||
|
export type TooltipPlacement = typeof placementTypes;
|
||||||
|
|
||||||
|
// https://github.com/react-component/tooltip
|
||||||
|
// https://github.com/yiminghe/dom-align
|
||||||
|
export interface TooltipAlignConfig {
|
||||||
|
points?: [string, string];
|
||||||
|
offset?: [number | string, number | string];
|
||||||
|
targetOffset?: [number | string, number | string];
|
||||||
|
overflow?: { adjustX: boolean; adjustY: boolean };
|
||||||
|
useCssRight?: boolean;
|
||||||
|
useCssBottom?: boolean;
|
||||||
|
useCssTransform?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const splitObject = (obj: any, keys: string[]) => {
|
const splitObject = (obj: any, keys: string[]) => {
|
||||||
const picked = {};
|
const picked = {};
|
||||||
|
|
@ -37,6 +49,10 @@ const tooltipProps = {
|
||||||
title: PropTypes.VNodeChild,
|
title: PropTypes.VNodeChild,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TriggerTypes = typeof triggerTypes[number];
|
||||||
|
|
||||||
|
export type PlacementTypes = typeof placementTypes[number];
|
||||||
|
|
||||||
export type TooltipProps = Partial<ExtractPropTypes<typeof tooltipProps>>;
|
export type TooltipProps = Partial<ExtractPropTypes<typeof tooltipProps>>;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
@ -44,52 +60,59 @@ export default defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: tooltipProps,
|
props: tooltipProps,
|
||||||
emits: ['update:visible', 'visibleChange'],
|
emits: ['update:visible', 'visibleChange'],
|
||||||
setup() {
|
setup(props, { slots, emit, attrs, expose }) {
|
||||||
return {
|
const { prefixCls, getTargetContainer } = useConfigInject('tooltip', props);
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
sVisible: !!this.$props.visible || !!this.$props.defaultVisible,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
visible(val) {
|
|
||||||
this.sVisible = val;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleVisibleChange(visible: boolean) {
|
|
||||||
if (!hasProp(this, 'visible')) {
|
|
||||||
this.sVisible = this.isNoTitle() ? false : visible;
|
|
||||||
}
|
|
||||||
if (!this.isNoTitle()) {
|
|
||||||
this.$emit('update:visible', visible);
|
|
||||||
this.$emit('visibleChange', visible);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getPopupDomNode() {
|
const visible = ref(false);
|
||||||
return (this.$refs.tooltip as any).getPopupDomNode();
|
|
||||||
},
|
|
||||||
|
|
||||||
getPlacements() {
|
const tooltip = ref();
|
||||||
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = this.$props;
|
|
||||||
|
onMounted(() => {
|
||||||
|
warning(
|
||||||
|
!('default-visible' in attrs) || !('defaultVisible' in attrs),
|
||||||
|
'Tooltip',
|
||||||
|
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
val => {
|
||||||
|
visible.value = !!val;
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNoTitle = () => {
|
||||||
|
const title = getPropsSlot(slots, props, 'title');
|
||||||
|
return !title && title !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVisibleChange = (val: boolean) => {
|
||||||
|
visible.value = isNoTitle() ? false : val;
|
||||||
|
if (!isNoTitle()) {
|
||||||
|
emit('update:visible', val);
|
||||||
|
emit('visibleChange', val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPopupDomNode = () => {
|
||||||
|
return tooltip.value.getPopupDomNode();
|
||||||
|
};
|
||||||
|
|
||||||
|
expose({ getPopupDomNode, visible });
|
||||||
|
|
||||||
|
const tooltipPlacements = computed(() => {
|
||||||
|
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = props;
|
||||||
return (
|
return (
|
||||||
builtinPlacements ||
|
builtinPlacements ||
|
||||||
getPlacements({
|
getPlacements({
|
||||||
arrowPointAtCenter,
|
arrowPointAtCenter,
|
||||||
verticalArrowShift: 8,
|
|
||||||
autoAdjustOverflow,
|
autoAdjustOverflow,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
|
|
||||||
// Fix Tooltip won't hide at disabled button
|
const getDisabledCompatibleChildren = (ele: any) => {
|
||||||
// mouse events don't trigger at disabled button in Chrome
|
|
||||||
// https://github.com/react-component/tooltip/issues/18
|
|
||||||
getDisabledCompatibleChildren(ele: any) {
|
|
||||||
if (
|
if (
|
||||||
((typeof ele.type === 'object' &&
|
((typeof ele.type === 'object' &&
|
||||||
(ele.type.__ANT_BUTTON === true ||
|
(ele.type.__ANT_BUTTON === true ||
|
||||||
|
|
@ -128,27 +151,22 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
return <span style={spanStyle}>{child}</span>;
|
return (
|
||||||
|
<span style={spanStyle} class={`${prefixCls}-disabled-compatible-wrapper`}>
|
||||||
|
{child}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return ele;
|
return ele;
|
||||||
},
|
};
|
||||||
|
|
||||||
isNoTitle() {
|
const getOverlay = () => {
|
||||||
const title = getComponent(this, 'title');
|
const title = getPropsSlot(slots, props, 'title');
|
||||||
return !title && title !== 0;
|
return title ?? '';
|
||||||
},
|
};
|
||||||
|
|
||||||
getOverlay() {
|
const onPopupAlign = (domNode: HTMLElement, align: any) => {
|
||||||
const title = getComponent(this, 'title');
|
const placements = tooltipPlacements.value;
|
||||||
if (title === 0) {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
return title || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
// 动态设置动画点
|
|
||||||
onPopupAlign(domNode: HTMLElement, align: any) {
|
|
||||||
const placements = this.getPlacements();
|
|
||||||
// 当前返回的位置
|
// 当前返回的位置
|
||||||
const placement = Object.keys(placements).filter(
|
const placement = Object.keys(placements).filter(
|
||||||
key =>
|
key =>
|
||||||
|
|
@ -175,67 +193,64 @@ export default defineComponent({
|
||||||
transformOrigin.left = `${-align.offset[0]}px`;
|
transformOrigin.left = `${-align.offset[0]}px`;
|
||||||
}
|
}
|
||||||
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
|
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { $props, $data, $attrs } = this;
|
|
||||||
const {
|
|
||||||
prefixCls: customizePrefixCls,
|
|
||||||
openClassName,
|
|
||||||
getPopupContainer,
|
|
||||||
color,
|
|
||||||
overlayClassName,
|
|
||||||
} = $props;
|
|
||||||
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('tooltip', customizePrefixCls);
|
|
||||||
let children = this.children || filterEmpty(getSlot(this));
|
|
||||||
children = children.length === 1 ? children[0] : children;
|
|
||||||
let sVisible = $data.sVisible;
|
|
||||||
// Hide tooltip when there is no title
|
|
||||||
if (!hasProp(this, 'visible') && this.isNoTitle()) {
|
|
||||||
sVisible = false;
|
|
||||||
}
|
|
||||||
if (!children) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const child = this.getDisabledCompatibleChildren(
|
|
||||||
isValidElement(children) ? children : <span>{children}</span>,
|
|
||||||
);
|
|
||||||
const childCls = classNames({
|
|
||||||
[openClassName || `${prefixCls}-open`]: sVisible,
|
|
||||||
[child.props && child.props.class]: child.props && child.props.class,
|
|
||||||
});
|
|
||||||
const customOverlayClassName = classNames(overlayClassName, {
|
|
||||||
[`${prefixCls}-${color}`]: color && PresetColorRegex.test(color),
|
|
||||||
});
|
|
||||||
let formattedOverlayInnerStyle: CSSProperties;
|
|
||||||
let arrowContentStyle: CSSProperties;
|
|
||||||
if (color && !PresetColorRegex.test(color)) {
|
|
||||||
formattedOverlayInnerStyle = { backgroundColor: color };
|
|
||||||
arrowContentStyle = { backgroundColor: color };
|
|
||||||
}
|
|
||||||
|
|
||||||
const vcTooltipProps = {
|
|
||||||
...$attrs,
|
|
||||||
...$props,
|
|
||||||
prefixCls,
|
|
||||||
getTooltipContainer: getPopupContainer || getContextPopupContainer,
|
|
||||||
builtinPlacements: this.getPlacements(),
|
|
||||||
overlay: this.getOverlay(),
|
|
||||||
visible: sVisible,
|
|
||||||
ref: 'tooltip',
|
|
||||||
overlayClassName: customOverlayClassName,
|
|
||||||
overlayInnerStyle: formattedOverlayInnerStyle,
|
|
||||||
arrowContent: <span class={`${prefixCls}-arrow-content`} style={arrowContentStyle}></span>,
|
|
||||||
onVisibleChange: this.handleVisibleChange,
|
|
||||||
onPopupAlign: this.onPopupAlign,
|
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
<VcTooltip {...vcTooltipProps}>
|
return () => {
|
||||||
{sVisible ? cloneElement(child, { class: childCls }) : child}
|
const { openClassName, getPopupContainer, color, overlayClassName } = props;
|
||||||
</VcTooltip>
|
let children = filterEmpty(slots.default?.()) ?? null;
|
||||||
);
|
children = children.length === 1 ? children[0] : children;
|
||||||
|
|
||||||
|
let tempVisible = visible.value;
|
||||||
|
// Hide tooltip when there is no title
|
||||||
|
if (props.visible === undefined && isNoTitle()) {
|
||||||
|
tempVisible = false;
|
||||||
|
}
|
||||||
|
if (!children) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const child = getDisabledCompatibleChildren(
|
||||||
|
isValidElement(children) ? children : <span>{children}</span>,
|
||||||
|
);
|
||||||
|
const childCls = classNames({
|
||||||
|
[openClassName || `${prefixCls.value}-open`]: true,
|
||||||
|
[child.props && child.props.class]: child.props && child.props.class,
|
||||||
|
});
|
||||||
|
const customOverlayClassName = classNames(overlayClassName, {
|
||||||
|
[`${prefixCls.value}-${color}`]: color && PresetColorRegex.test(color),
|
||||||
|
});
|
||||||
|
let formattedOverlayInnerStyle: CSSProperties;
|
||||||
|
let arrowContentStyle: CSSProperties;
|
||||||
|
if (color && !PresetColorRegex.test(color)) {
|
||||||
|
formattedOverlayInnerStyle = { backgroundColor: color };
|
||||||
|
arrowContentStyle = { backgroundColor: color };
|
||||||
|
}
|
||||||
|
|
||||||
|
const vcTooltipProps = {
|
||||||
|
...attrs,
|
||||||
|
...props,
|
||||||
|
prefixCls: prefixCls.value,
|
||||||
|
getTooltipContainer: getPopupContainer || getTargetContainer.value,
|
||||||
|
builtinPlacements: tooltipPlacements.value,
|
||||||
|
overlay: getOverlay(),
|
||||||
|
visible: tempVisible,
|
||||||
|
ref: tooltip,
|
||||||
|
overlayClassName: customOverlayClassName,
|
||||||
|
overlayInnerStyle: formattedOverlayInnerStyle,
|
||||||
|
onVisibleChange: handleVisibleChange,
|
||||||
|
onPopupAlign,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<VcTooltip
|
||||||
|
{...vcTooltipProps}
|
||||||
|
v-slots={{
|
||||||
|
arrowContent: () => (
|
||||||
|
<span class={`${prefixCls.value}-arrow-content`} style={arrowContentStyle}></span>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{visible.value ? cloneElement(child, { class: childCls }) : child}
|
||||||
|
</VcTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import mountTest from '../../../tests/shared/mountTest';
|
||||||
|
|
||||||
describe('Tooltip', () => {
|
describe('Tooltip', () => {
|
||||||
mountTest(Tooltip);
|
mountTest(Tooltip);
|
||||||
it('check `onVisibleChange` arguments', async () => {
|
fit('check `onVisibleChange` arguments', async () => {
|
||||||
const onVisibleChange = jest.fn();
|
const onVisibleChange = jest.fn();
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
{
|
{
|
||||||
|
|
@ -44,14 +44,14 @@ describe('Tooltip', () => {
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange).not.toHaveBeenCalled();
|
expect(onVisibleChange).not.toHaveBeenCalled();
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(false);
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
div.dispatchEvent(new MouseEvent('mouseleave'));
|
div.dispatchEvent(new MouseEvent('mouseleave'));
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange).not.toHaveBeenCalled();
|
expect(onVisibleChange).not.toHaveBeenCalled();
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(false);
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
// update `title` value.
|
// update `title` value.
|
||||||
|
|
@ -62,14 +62,14 @@ describe('Tooltip', () => {
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
|
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(true);
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(true);
|
||||||
}, 0);
|
}, 0);
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
|
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
|
expect(onVisibleChange).toHaveBeenLastCalledWith(false);
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(false);
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
// add `visible` props.
|
// add `visible` props.
|
||||||
|
|
@ -80,16 +80,16 @@ describe('Tooltip', () => {
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
|
expect(onVisibleChange).toHaveBeenLastCalledWith(true);
|
||||||
lastCount = onVisibleChange.mock.calls.length;
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(true);
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
|
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
// always trigger onVisibleChange
|
// always trigger onVisibleChange
|
||||||
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
|
wrapper.findAll('#hello')[0].element.dispatchEvent(new MouseEvent('mouseleave'));
|
||||||
|
lastCount = onVisibleChange.mock.calls.length;
|
||||||
});
|
});
|
||||||
await asyncExpect(() => {
|
await asyncExpect(() => {
|
||||||
expect(onVisibleChange.mock.calls.length).toBe(lastCount); // no change with lastCount
|
expect(onVisibleChange.mock.calls.length).toBe(lastCount); // no change with lastCount
|
||||||
expect(wrapper.vm.$refs.tooltip.$refs.tooltip.visible).toBe(false);
|
expect(wrapper.vm.$refs.tooltip.visible).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,30 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { tuple } from '../_util/type';
|
import { tuple } from '../_util/type';
|
||||||
const triggerType = PropTypes.oneOf(tuple('hover', 'focus', 'click', 'contextmenu'));
|
export const triggerTypes = tuple('hover', 'focus', 'click', 'contextmenu');
|
||||||
|
|
||||||
|
export const placementTypes = tuple(
|
||||||
|
'top',
|
||||||
|
'left',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'topLeft',
|
||||||
|
'topRight',
|
||||||
|
'bottomLeft',
|
||||||
|
'bottomRight',
|
||||||
|
'leftTop',
|
||||||
|
'leftBottom',
|
||||||
|
'rightTop',
|
||||||
|
'rightBottom',
|
||||||
|
);
|
||||||
|
|
||||||
export default () => ({
|
export default () => ({
|
||||||
trigger: PropTypes.oneOfType([triggerType, PropTypes.arrayOf(triggerType)]).def('hover'),
|
trigger: PropTypes.oneOfType([
|
||||||
|
PropTypes.oneOf(triggerTypes),
|
||||||
|
PropTypes.arrayOf(PropTypes.oneOf(triggerTypes)),
|
||||||
|
]).def('hover'),
|
||||||
visible: PropTypes.looseBool,
|
visible: PropTypes.looseBool,
|
||||||
defaultVisible: PropTypes.looseBool,
|
// defaultVisible: PropTypes.looseBool,
|
||||||
placement: PropTypes.oneOf(
|
placement: PropTypes.oneOf(placementTypes).def('top'),
|
||||||
tuple(
|
|
||||||
'top',
|
|
||||||
'left',
|
|
||||||
'right',
|
|
||||||
'bottom',
|
|
||||||
'topLeft',
|
|
||||||
'topRight',
|
|
||||||
'bottomLeft',
|
|
||||||
'bottomRight',
|
|
||||||
'leftTop',
|
|
||||||
'leftBottom',
|
|
||||||
'rightTop',
|
|
||||||
'rightBottom',
|
|
||||||
),
|
|
||||||
).def('top'),
|
|
||||||
color: PropTypes.string,
|
color: PropTypes.string,
|
||||||
transitionName: PropTypes.string.def('zoom-big-fast'),
|
transitionName: PropTypes.string.def('zoom-big-fast'),
|
||||||
overlayStyle: PropTypes.object.def(() => ({})),
|
overlayStyle: PropTypes.object.def(() => ({})),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { withInstall } from '../_util/type';
|
import { withInstall } from '../_util/type';
|
||||||
import ToolTip from './Tooltip';
|
import ToolTip from './Tooltip';
|
||||||
|
|
||||||
export { TooltipProps } from './Tooltip';
|
export type {
|
||||||
|
TooltipProps,
|
||||||
|
AdjustOverflow,
|
||||||
|
PlacementsConfig,
|
||||||
|
TooltipAlignConfig,
|
||||||
|
PlacementTypes,
|
||||||
|
} from './Tooltip';
|
||||||
|
|
||||||
export default withInstall(ToolTip);
|
export default withInstall(ToolTip);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { placements as rcPlacements } from '../vc-tooltip/placements';
|
import { placements } from '../vc-tooltip/src/placements';
|
||||||
|
|
||||||
const autoAdjustOverflowEnabled = {
|
const autoAdjustOverflowEnabled = {
|
||||||
adjustX: 1,
|
adjustX: 1,
|
||||||
|
|
@ -12,15 +12,20 @@ const autoAdjustOverflowDisabled = {
|
||||||
|
|
||||||
const targetOffset = [0, 0];
|
const targetOffset = [0, 0];
|
||||||
|
|
||||||
interface PlacementsConfig {
|
export interface AdjustOverflow {
|
||||||
arrowPointAtCenter: boolean;
|
adjustX?: 0 | 1;
|
||||||
arrowWidth?: number;
|
adjustY?: 0 | 1;
|
||||||
verticalArrowShift?: number;
|
|
||||||
horizontalArrowShift?: number;
|
|
||||||
autoAdjustOverflow?: boolean | Object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOverflowOptions(autoAdjustOverflow: boolean | Object) {
|
export interface PlacementsConfig {
|
||||||
|
arrowWidth?: number;
|
||||||
|
horizontalArrowShift?: number;
|
||||||
|
verticalArrowShift?: number;
|
||||||
|
arrowPointAtCenter?: boolean;
|
||||||
|
autoAdjustOverflow?: boolean | AdjustOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow) {
|
||||||
if (typeof autoAdjustOverflow === 'boolean') {
|
if (typeof autoAdjustOverflow === 'boolean') {
|
||||||
return autoAdjustOverflow ? autoAdjustOverflowEnabled : autoAdjustOverflowDisabled;
|
return autoAdjustOverflow ? autoAdjustOverflowEnabled : autoAdjustOverflowDisabled;
|
||||||
}
|
}
|
||||||
|
|
@ -34,8 +39,8 @@ export default function getPlacements(config: PlacementsConfig) {
|
||||||
const {
|
const {
|
||||||
arrowWidth = 5,
|
arrowWidth = 5,
|
||||||
horizontalArrowShift = 16,
|
horizontalArrowShift = 16,
|
||||||
verticalArrowShift = 12,
|
verticalArrowShift = 8,
|
||||||
autoAdjustOverflow = true,
|
autoAdjustOverflow,
|
||||||
} = config;
|
} = config;
|
||||||
const placementMap = {
|
const placementMap = {
|
||||||
left: {
|
left: {
|
||||||
|
|
@ -95,9 +100,10 @@ export default function getPlacements(config: PlacementsConfig) {
|
||||||
targetOffset,
|
targetOffset,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...rcPlacements[key],
|
...placements[key],
|
||||||
overflow: getOverflowOptions(autoAdjustOverflow),
|
overflow: getOverflowOptions(autoAdjustOverflow),
|
||||||
};
|
};
|
||||||
|
|
||||||
placementMap[key].ignoreShake = true;
|
placementMap[key].ignoreShake = true;
|
||||||
});
|
});
|
||||||
return placementMap;
|
return placementMap;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: @zindex-tooltip;
|
z-index: @zindex-tooltip;
|
||||||
display: block;
|
display: block;
|
||||||
|
width: max-content;
|
||||||
max-width: @tooltip-max-width;
|
max-width: @tooltip-max-width;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
|
|
@ -202,3 +203,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.generator-tooltip-preset-color();
|
.generator-tooltip-preset-color();
|
||||||
|
|
||||||
|
@import './rtl';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
@tooltip-prefix-cls: ~'@{ant-prefix}-tooltip';
|
||||||
|
|
||||||
|
// Base class
|
||||||
|
.@{tooltip-prefix-cls} {
|
||||||
|
&-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
// Wrapper for the tooltip content
|
||||||
|
&-inner {
|
||||||
|
.@{tooltip-prefix-cls}-rtl & {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import type { ExtractPropTypes } from 'vue';
|
||||||
|
import { defineComponent, computed, ref, watch } from 'vue';
|
||||||
|
import classNames from '../_util/classNames';
|
||||||
|
import ListItem from './ListItem';
|
||||||
|
import Pagination from '../pagination';
|
||||||
|
import PropTypes from '../_util/vue-types';
|
||||||
|
import type { TransferItem } from '.';
|
||||||
|
|
||||||
|
export const transferListBodyProps = {
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
filteredRenderItems: PropTypes.array.def([]),
|
||||||
|
selectedKeys: PropTypes.array,
|
||||||
|
disabled: PropTypes.looseBool,
|
||||||
|
showRemove: PropTypes.looseBool,
|
||||||
|
pagination: PropTypes.any,
|
||||||
|
onItemSelect: PropTypes.func,
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
onItemRemove: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TransferListBodyProps = Partial<ExtractPropTypes<typeof transferListBodyProps>>;
|
||||||
|
|
||||||
|
function parsePagination(pagination) {
|
||||||
|
if (!pagination) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPagination = {
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof pagination === 'object') {
|
||||||
|
return {
|
||||||
|
...defaultPagination,
|
||||||
|
...pagination,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPagination;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListBody = defineComponent({
|
||||||
|
name: 'ListBody',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: transferListBodyProps,
|
||||||
|
emits: ['itemSelect', 'itemRemove', 'scroll'],
|
||||||
|
setup(props, { emit, expose }) {
|
||||||
|
const current = ref(1);
|
||||||
|
|
||||||
|
const handleItemSelect = (item: TransferItem) => {
|
||||||
|
const { selectedKeys } = props;
|
||||||
|
const checked = selectedKeys.indexOf(item.key) >= 0;
|
||||||
|
emit('itemSelect', item.key, !checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemRemove = (item: TransferItem) => {
|
||||||
|
emit('itemRemove', item.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScroll = (e: Event) => {
|
||||||
|
emit('scroll', e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergedPagination = computed(() => parsePagination(props.pagination));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[mergedPagination, () => props.filteredRenderItems],
|
||||||
|
() => {
|
||||||
|
if (mergedPagination.value) {
|
||||||
|
// Calculate the page number
|
||||||
|
const maxPageCount = Math.ceil(
|
||||||
|
props.filteredRenderItems.length / mergedPagination.value.pageSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (current.value > maxPageCount) {
|
||||||
|
current.value = maxPageCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
const items = computed(() => {
|
||||||
|
const { filteredRenderItems } = props;
|
||||||
|
|
||||||
|
let displayItems = filteredRenderItems;
|
||||||
|
|
||||||
|
if (mergedPagination.value) {
|
||||||
|
displayItems = filteredRenderItems.slice(
|
||||||
|
(current.value - 1) * mergedPagination.value.pageSize,
|
||||||
|
current.value * mergedPagination.value.pageSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayItems;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPageChange = (cur: number) => {
|
||||||
|
current.value = cur;
|
||||||
|
};
|
||||||
|
|
||||||
|
expose({ items });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
prefixCls,
|
||||||
|
filteredRenderItems,
|
||||||
|
selectedKeys,
|
||||||
|
disabled: globalDisabled,
|
||||||
|
showRemove,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let paginationNode = null;
|
||||||
|
|
||||||
|
if (mergedPagination.value) {
|
||||||
|
paginationNode = (
|
||||||
|
<Pagination
|
||||||
|
simple
|
||||||
|
size="small"
|
||||||
|
disabled={globalDisabled}
|
||||||
|
class={`${prefixCls}-pagination`}
|
||||||
|
total={filteredRenderItems.length}
|
||||||
|
pageSize={mergedPagination.value.pageSize}
|
||||||
|
current={current.value}
|
||||||
|
onChange={onPageChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsList = items.value.map(({ renderedEl, renderedText, item }: any) => {
|
||||||
|
const { disabled } = item;
|
||||||
|
const checked = selectedKeys.indexOf(item.key) >= 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
disabled={globalDisabled || disabled}
|
||||||
|
key={item.key}
|
||||||
|
item={item}
|
||||||
|
renderedText={renderedText}
|
||||||
|
renderedEl={renderedEl}
|
||||||
|
checked={checked}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
onClick={handleItemSelect}
|
||||||
|
onRemove={handleItemRemove}
|
||||||
|
showRemove={showRemove}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ul
|
||||||
|
class={classNames(`${prefixCls}-content`, {
|
||||||
|
[`${prefixCls}-content-show-remove`]: showRemove,
|
||||||
|
})}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
|
{itemsList}
|
||||||
|
</ul>
|
||||||
|
{paginationNode}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListBody;
|
||||||
|
|
@ -1,66 +1,93 @@
|
||||||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import Lazyload from '../vc-lazy-load';
|
import type { TransferLocale } from '.';
|
||||||
|
import DeleteOutlined from '@ant-design/icons-vue/DeleteOutlined';
|
||||||
|
import defaultLocale from '../locale/default';
|
||||||
import Checkbox from '../checkbox';
|
import Checkbox from '../checkbox';
|
||||||
|
import TransButton from '../_util/transButton';
|
||||||
|
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||||
|
import type { ExtractPropTypes } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
|
export const transferListItemProps = {
|
||||||
|
renderedText: PropTypes.any,
|
||||||
|
renderedEl: PropTypes.any,
|
||||||
|
item: PropTypes.any,
|
||||||
|
checked: PropTypes.looseBool,
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
disabled: PropTypes.looseBool,
|
||||||
|
showRemove: PropTypes.looseBool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onRemove: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TransferListItemProps = Partial<ExtractPropTypes<typeof transferListItemProps>>;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ListItem',
|
name: 'ListItem',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {
|
props: transferListItemProps,
|
||||||
renderedText: PropTypes.any,
|
emits: ['click', 'remove'],
|
||||||
renderedEl: PropTypes.any,
|
setup(props, { emit }) {
|
||||||
item: PropTypes.any,
|
return () => {
|
||||||
lazy: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object])),
|
const { renderedText, renderedEl, item, checked, disabled, prefixCls, showRemove } = props;
|
||||||
checked: PropTypes.looseBool,
|
const className = classNames({
|
||||||
prefixCls: PropTypes.string,
|
[`${prefixCls}-content-item`]: true,
|
||||||
disabled: PropTypes.looseBool,
|
[`${prefixCls}-content-item-disabled`]: disabled || item.disabled,
|
||||||
onClick: PropTypes.func,
|
});
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { renderedText, renderedEl, item, lazy, checked, disabled, prefixCls } = this.$props;
|
|
||||||
|
|
||||||
const className = classNames({
|
let title: string;
|
||||||
[`${prefixCls}-content-item`]: true,
|
if (typeof renderedText === 'string' || typeof renderedText === 'number') {
|
||||||
[`${prefixCls}-content-item-disabled`]: disabled || item.disabled,
|
title = String(renderedText);
|
||||||
});
|
}
|
||||||
|
|
||||||
let title;
|
return (
|
||||||
if (typeof renderedText === 'string' || typeof renderedText === 'number') {
|
<LocaleReceiver componentName="Transfer" defaultLocale={defaultLocale.Transfer}>
|
||||||
title = String(renderedText);
|
{(transferLocale: TransferLocale) => {
|
||||||
}
|
const labelNode = <span class={`${prefixCls}-content-item-text`}>{renderedEl}</span>;
|
||||||
|
if (showRemove) {
|
||||||
|
return (
|
||||||
|
<li class={className} title={title}>
|
||||||
|
{labelNode}
|
||||||
|
<TransButton
|
||||||
|
disabled={disabled || item.disabled}
|
||||||
|
class={`${prefixCls}-content-item-remove`}
|
||||||
|
aria-label={transferLocale.remove}
|
||||||
|
onClick={() => {
|
||||||
|
emit('remove', item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</TransButton>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const listItem = (
|
return (
|
||||||
<li
|
<li
|
||||||
class={className}
|
class={className}
|
||||||
title={title}
|
title={title}
|
||||||
onClick={
|
onClick={
|
||||||
disabled || item.disabled
|
disabled || item.disabled
|
||||||
? noop
|
? noop
|
||||||
: () => {
|
: () => {
|
||||||
this.$emit('click', item);
|
emit('click', item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Checkbox checked={checked} disabled={disabled || item.disabled} />
|
<Checkbox
|
||||||
<span class={`${prefixCls}-content-item-text`}>{renderedEl}</span>
|
class={`${prefixCls}-checkbox`}
|
||||||
</li>
|
checked={checked}
|
||||||
);
|
disabled={disabled || item.disabled}
|
||||||
let children = null;
|
/>
|
||||||
if (lazy) {
|
{labelNode}
|
||||||
const lazyProps = {
|
</li>
|
||||||
height: 32,
|
);
|
||||||
offset: 500,
|
}}
|
||||||
throttle: 0,
|
</LocaleReceiver>
|
||||||
debounce: false,
|
);
|
||||||
...(lazy as any),
|
};
|
||||||
};
|
|
||||||
children = <Lazyload {...lazyProps}>{listItem}</Lazyload>;
|
|
||||||
} else {
|
|
||||||
children = listItem;
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,40 @@
|
||||||
exports[`Transfer should render correctly 1`] = `
|
exports[`Transfer should render correctly 1`] = `
|
||||||
<div class="ant-transfer">
|
<div class="ant-transfer">
|
||||||
<div class="ant-transfer-list">
|
<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" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-transfer-list-checkbox"><span class="ant-checkbox ant-checkbox-checked"><input type="checkbox" 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>
|
</label>
|
||||||
|
<!----><span tabindex="-1" role="img" aria-label="down" class="anticon anticon-down ant-dropdown-trigger ant-transfer-list-header-dropdown"><svg focusable="false" class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span><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">
|
<div class="ant-transfer-list-body">
|
||||||
<!---->
|
<!---->
|
||||||
<ul class="ant-transfer-list-content">
|
<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" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-transfer-list-checkbox"><span class="ant-checkbox ant-checkbox-checked"><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>
|
</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="" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-transfer-list-checkbox"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
||||||
<!---->
|
<!---->
|
||||||
</label><span class="ant-transfer-list-content-item-text"><!----></span></li>
|
</label><span class="ant-transfer-list-content-item-text"><!----></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<div class="ant-transfer-operation"><button class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="right" class="anticon anticon-right"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 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 000-50.4z"></path></svg></span></button><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="left" class="anticon anticon-left"><svg focusable="false" class="" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 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></span></button></div>
|
<div class="ant-transfer-operation"><button class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="right" class="anticon anticon-right"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 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 000-50.4z"></path></svg></span></button><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="left" class="anticon anticon-left"><svg focusable="false" class="" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 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></span></button></div>
|
||||||
<div class="ant-transfer-list">
|
<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>
|
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label>
|
||||||
|
<!----><span tabindex="-1" role="img" aria-label="down" class="anticon anticon-down ant-dropdown-trigger ant-transfer-list-header-dropdown"><svg focusable="false" class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span><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">
|
<div class="ant-transfer-list-body">
|
||||||
<!---->
|
<!---->
|
||||||
<ul class="ant-transfer-list-content">
|
<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>
|
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label><span class="ant-transfer-list-content-item-text"><!----></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -40,34 +46,40 @@ exports[`Transfer should render correctly 1`] = `
|
||||||
exports[`Transfer should show sorted targetkey 1`] = `
|
exports[`Transfer should show sorted targetkey 1`] = `
|
||||||
<div class="ant-transfer">
|
<div class="ant-transfer">
|
||||||
<div class="ant-transfer-list">
|
<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>
|
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label>
|
||||||
|
<!----><span tabindex="-1" role="img" aria-label="down" class="anticon anticon-down ant-dropdown-trigger ant-transfer-list-header-dropdown"><svg focusable="false" class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span><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">
|
<div class="ant-transfer-list-body">
|
||||||
<!---->
|
<!---->
|
||||||
<ul class="ant-transfer-list-content">
|
<ul class="ant-transfer-list-content">
|
||||||
<li class="ant-transfer-list-content-item" title="a"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item" title="a"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label><span class="ant-transfer-list-content-item-text">a</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<div class="ant-transfer-operation"><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="right" class="anticon anticon-right"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 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 000-50.4z"></path></svg></span></button><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="left" class="anticon anticon-left"><svg focusable="false" class="" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 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></span></button></div>
|
<div class="ant-transfer-operation"><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="right" class="anticon anticon-right"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 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 000-50.4z"></path></svg></span></button><button disabled="" class="ant-btn ant-btn-primary ant-btn-sm ant-btn-icon-only" type="button"><span role="img" aria-label="left" class="anticon anticon-left"><svg focusable="false" class="" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 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></span></button></div>
|
||||||
<div class="ant-transfer-list">
|
<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>
|
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label>
|
||||||
|
<!----><span tabindex="-1" role="img" aria-label="down" class="anticon anticon-down ant-dropdown-trigger ant-transfer-list-header-dropdown"><svg focusable="false" class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span><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">
|
<div class="ant-transfer-list-body">
|
||||||
<!---->
|
<!---->
|
||||||
<ul class="ant-transfer-list-content">
|
<ul class="ant-transfer-list-content">
|
||||||
<li class="ant-transfer-list-content-item" title="c"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item" title="c"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label><span class="ant-transfer-list-content-item-text">c</span></li>
|
||||||
<li class="ant-transfer-list-content-item" title="b"><label class="ant-checkbox-wrapper"><span class="ant-checkbox"><input type="checkbox" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item" title="b"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label><span class="ant-transfer-list-content-item-text">b</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,25 @@
|
||||||
|
|
||||||
exports[`List should render correctly 1`] = `
|
exports[`List should render correctly 1`] = `
|
||||||
<div class="ant-transfer-list">
|
<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>
|
<div class="ant-transfer-list-header"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</label>
|
||||||
|
<!----><span tabindex="-1" role="img" aria-label="down" class="anticon anticon-down ant-dropdown-trigger ant-transfer-list-header-dropdown"><svg focusable="false" class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span><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">
|
<div class="ant-transfer-list-body">
|
||||||
<!---->
|
<!---->
|
||||||
<ul class="ant-transfer-list-content">
|
<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" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-checked ant-transfer-list-checkbox"><span class="ant-checkbox ant-checkbox-checked"><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>
|
</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>
|
<li class="ant-transfer-list-content-item"><label class="ant-checkbox-wrapper ant-transfer-list-checkbox"><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>
|
</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="" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
<li class="ant-transfer-list-content-item ant-transfer-list-content-item-disabled"><label class="ant-checkbox-wrapper ant-checkbox-wrapper-disabled ant-transfer-list-checkbox"><span class="ant-checkbox ant-checkbox-disabled"><input type="checkbox" disabled="" class="ant-checkbox-input"><span class="ant-checkbox-inner"></span></span>
|
||||||
<!---->
|
<!---->
|
||||||
</label><span class="ant-transfer-list-content-item-text"><!----></span></li>
|
</label><span class="ant-transfer-list-content-item-text"><!----></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -90,19 +90,28 @@ const searchTransferProps = {
|
||||||
describe('Transfer', () => {
|
describe('Transfer', () => {
|
||||||
mountTest(Transfer);
|
mountTest(Transfer);
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
const props = {
|
const wrapper = mount({
|
||||||
props: listCommonProps,
|
setup() {
|
||||||
};
|
return () => <Transfer {...{ ...listCommonProps }} />;
|
||||||
const wrapper = mount(Transfer, props);
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move selected keys to corresponding list', done => {
|
it('should move selected keys to corresponding list', done => {
|
||||||
const handleChange = jest.fn();
|
const handleChange = jest.fn();
|
||||||
const wrapper = mount(Transfer, {
|
|
||||||
props: { ...listCommonProps, onChange: handleChange },
|
const wrapper = mount(
|
||||||
sync: false,
|
{
|
||||||
});
|
setup() {
|
||||||
|
return () => <Transfer {...{ ...listCommonProps, onChange: handleChange }} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
wrapper.findAll('.ant-btn')[0].trigger('click'); // move selected keys to right list
|
wrapper.findAll('.ant-btn')[0].trigger('click'); // move selected keys to right list
|
||||||
expect(handleChange).toHaveBeenCalledWith(['a', 'b'], 'right', ['a']);
|
expect(handleChange).toHaveBeenCalledWith(['a', 'b'], 'right', ['a']);
|
||||||
|
|
@ -111,10 +120,16 @@ describe('Transfer', () => {
|
||||||
});
|
});
|
||||||
it('should move selected keys expect disabled to corresponding list', done => {
|
it('should move selected keys expect disabled to corresponding list', done => {
|
||||||
const handleChange = jest.fn();
|
const handleChange = jest.fn();
|
||||||
const wrapper = mount(Transfer, {
|
const wrapper = mount(
|
||||||
props: { ...listDisabledProps, onChange: handleChange },
|
{
|
||||||
sync: false,
|
setup() {
|
||||||
});
|
return () => <Transfer {...{ ...listDisabledProps, onChange: handleChange }} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
wrapper.findAll('.ant-btn')[0].trigger('click');
|
wrapper.findAll('.ant-btn')[0].trigger('click');
|
||||||
expect(handleChange).toHaveBeenCalledWith(['b'], 'right', ['b']);
|
expect(handleChange).toHaveBeenCalledWith(['b'], 'right', ['b']);
|
||||||
|
|
@ -124,10 +139,18 @@ describe('Transfer', () => {
|
||||||
|
|
||||||
it('should uncheck checkbox when click on checked item', async () => {
|
it('should uncheck checkbox when click on checked item', async () => {
|
||||||
const handleSelectChange = jest.fn();
|
const handleSelectChange = jest.fn();
|
||||||
const wrapper = mount(Transfer, {
|
|
||||||
props: { ...listCommonProps, onSelectChange: handleSelectChange },
|
const wrapper = mount(
|
||||||
sync: false,
|
{
|
||||||
});
|
setup() {
|
||||||
|
return () => <Transfer {...{ ...listCommonProps, onSelectChange: handleSelectChange }} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await sleep();
|
await sleep();
|
||||||
wrapper.findAll('.ant-transfer-list-content-item')[0].trigger('click');
|
wrapper.findAll('.ant-transfer-list-content-item')[0].trigger('click');
|
||||||
expect(handleSelectChange).toHaveBeenLastCalledWith([], []);
|
expect(handleSelectChange).toHaveBeenLastCalledWith([], []);
|
||||||
|
|
@ -135,10 +158,18 @@ describe('Transfer', () => {
|
||||||
|
|
||||||
it('should check checkbox when click on unchecked item', async () => {
|
it('should check checkbox when click on unchecked item', async () => {
|
||||||
const handleSelectChange = jest.fn();
|
const handleSelectChange = jest.fn();
|
||||||
const wrapper = mount(Transfer, {
|
|
||||||
props: { ...listCommonProps, onSelectChange: handleSelectChange },
|
const wrapper = mount(
|
||||||
sync: false,
|
{
|
||||||
});
|
setup() {
|
||||||
|
return () => <Transfer {...{ ...listCommonProps, onSelectChange: handleSelectChange }} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await sleep();
|
await sleep();
|
||||||
wrapper.findAll('.ant-transfer-list-content-item')[2].trigger('click');
|
wrapper.findAll('.ant-transfer-list-content-item')[2].trigger('click');
|
||||||
await sleep();
|
await sleep();
|
||||||
|
|
@ -147,10 +178,18 @@ describe('Transfer', () => {
|
||||||
|
|
||||||
it('should not check checkbox when click on disabled item', async () => {
|
it('should not check checkbox when click on disabled item', async () => {
|
||||||
const handleSelectChange = jest.fn();
|
const handleSelectChange = jest.fn();
|
||||||
const wrapper = mount(Transfer, {
|
|
||||||
props: { ...listCommonProps, onSelectChange: handleSelectChange },
|
const wrapper = mount(
|
||||||
sync: false,
|
{
|
||||||
});
|
setup() {
|
||||||
|
return () => <Transfer {...{ ...listCommonProps, onSelectChange: handleSelectChange }} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await sleep();
|
await sleep();
|
||||||
wrapper.findAll('.ant-transfer-list-content-item')[1].trigger('click');
|
wrapper.findAll('.ant-transfer-list-content-item')[1].trigger('click');
|
||||||
expect(handleSelectChange).not.toHaveBeenCalled();
|
expect(handleSelectChange).not.toHaveBeenCalled();
|
||||||
|
|
@ -200,14 +239,26 @@ describe('Transfer', () => {
|
||||||
|
|
||||||
it('should call `filterOption` when use input in search box', done => {
|
it('should call `filterOption` when use input in search box', done => {
|
||||||
const filterOption = (inputValue, option) => inputValue === option.title;
|
const filterOption = (inputValue, option) => inputValue === option.title;
|
||||||
const wrapper = mount(Transfer, {
|
|
||||||
props: {
|
const wrapper = mount(
|
||||||
...listCommonProps,
|
{
|
||||||
showSearch: true,
|
setup() {
|
||||||
filterOption,
|
return () => (
|
||||||
|
<Transfer
|
||||||
|
{...{
|
||||||
|
...listCommonProps,
|
||||||
|
showSearch: true,
|
||||||
|
filterOption,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
sync: false,
|
{
|
||||||
});
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
const input = wrapper.findAll('.ant-transfer-list-body-search-wrapper input')[0];
|
const input = wrapper.findAll('.ant-transfer-list-body-search-wrapper input')[0];
|
||||||
input.element.value = 'a';
|
input.element.value = 'a';
|
||||||
|
|
@ -227,15 +278,26 @@ describe('Transfer', () => {
|
||||||
it('should display the correct count of items when filter by input', done => {
|
it('should display the correct count of items when filter by input', done => {
|
||||||
const filterOption = (inputValue, option) => option.description.indexOf(inputValue) > -1;
|
const filterOption = (inputValue, option) => option.description.indexOf(inputValue) > -1;
|
||||||
const renderFunc = item => item.title;
|
const renderFunc = item => item.title;
|
||||||
const wrapper = mount(Transfer, {
|
const wrapper = mount(
|
||||||
props: {
|
{
|
||||||
...searchTransferProps,
|
setup() {
|
||||||
showSearch: true,
|
return () => (
|
||||||
filterOption,
|
<Transfer
|
||||||
render: renderFunc,
|
{...{
|
||||||
|
...searchTransferProps,
|
||||||
|
showSearch: true,
|
||||||
|
filterOption,
|
||||||
|
render: renderFunc,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
sync: false,
|
{
|
||||||
});
|
sync: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
const input = wrapper.findAll('.ant-transfer-list-body-search-wrapper input')[0];
|
const input = wrapper.findAll('.ant-transfer-list-body-search-wrapper input')[0];
|
||||||
input.element.value = 'content2';
|
input.element.value = 'content2';
|
||||||
|
|
@ -247,7 +309,7 @@ describe('Transfer', () => {
|
||||||
.findAll('.ant-transfer-list-header-selected > span')[0]
|
.findAll('.ant-transfer-list-header-selected > span')[0]
|
||||||
.text()
|
.text()
|
||||||
.trim(),
|
.trim(),
|
||||||
).toEqual('1 items');
|
).toEqual('1 item');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -395,14 +457,11 @@ describe('Transfer', () => {
|
||||||
targetKeys: ['c', 'b'],
|
targetKeys: ['c', 'b'],
|
||||||
lazy: false,
|
lazy: false,
|
||||||
};
|
};
|
||||||
|
const wrapper = mount({
|
||||||
const props = {
|
setup() {
|
||||||
props: {
|
return () => <Transfer {...sortedTargetKeyProps} render={item => item.title} />;
|
||||||
...sortedTargetKeyProps,
|
|
||||||
render: item => item.title,
|
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
const wrapper = mount(Transfer, props);
|
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('should add custom styles when their props are provided', async () => {
|
it('should add custom styles when their props are provided', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,175 +1,154 @@
|
||||||
import { defineComponent, inject } from 'vue';
|
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||||
|
import { watchEffect } from 'vue';
|
||||||
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { hasProp, getOptionProps, getComponent } from '../_util/props-util';
|
import { getPropsSlot } from '../_util/props-util';
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import List from './list';
|
import List from './list';
|
||||||
import Operation from './operation';
|
import Operation from './operation';
|
||||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||||
import defaultLocale from '../locale-provider/default';
|
import defaultLocale from '../locale-provider/default';
|
||||||
import type { RenderEmptyHandler } from '../config-provider';
|
import type { RenderEmptyHandler } from '../config-provider';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import type { VueNode } from '../_util/type';
|
||||||
import { withInstall } from '../_util/type';
|
import { withInstall } from '../_util/type';
|
||||||
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
import type { TransferListBodyProps } from './ListBody';
|
||||||
|
import type { PaginationType } from './interface';
|
||||||
|
|
||||||
|
export type { TransferListProps } from './list';
|
||||||
|
export type { TransferOperationProps } from './operation';
|
||||||
|
export type { TransferSearchProps } from './search';
|
||||||
|
|
||||||
export type TransferDirection = 'left' | 'right';
|
export type TransferDirection = 'left' | 'right';
|
||||||
|
|
||||||
export const TransferItem = {
|
export interface RenderResultObject {
|
||||||
key: PropTypes.string.isRequired,
|
label: VueNode;
|
||||||
title: PropTypes.string.isRequired,
|
value: string;
|
||||||
description: PropTypes.string,
|
}
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TransferProps = {
|
export type RenderResult = VueNode | RenderResultObject | string | null;
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
dataSource: PropTypes.arrayOf(PropTypes.shape(TransferItem).loose),
|
export interface TransferItem {
|
||||||
disabled: PropTypes.looseBool,
|
key?: string;
|
||||||
targetKeys: PropTypes.arrayOf(PropTypes.string),
|
title?: string;
|
||||||
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
description?: string;
|
||||||
render: PropTypes.func,
|
disabled?: boolean;
|
||||||
listStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
[name: string]: any;
|
||||||
operationStyle: PropTypes.object,
|
}
|
||||||
titles: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
operations: PropTypes.arrayOf(PropTypes.string),
|
export type KeyWise<T> = T & { key: string };
|
||||||
showSearch: PropTypes.looseBool,
|
|
||||||
filterOption: PropTypes.func,
|
export type KeyWiseTransferItem = KeyWise<TransferItem>;
|
||||||
searchPlaceholder: PropTypes.string,
|
|
||||||
|
type TransferRender<RecordType> = (item: RecordType) => RenderResult;
|
||||||
|
|
||||||
|
export interface ListStyle {
|
||||||
|
direction: TransferDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectAllLabel =
|
||||||
|
| VueNode
|
||||||
|
| ((info: { selectedCount: number; totalCount: number }) => VueNode);
|
||||||
|
|
||||||
|
export interface TransferLocale {
|
||||||
|
titles: VueNode[];
|
||||||
|
notFoundContent?: VueNode;
|
||||||
|
searchPlaceholder: string;
|
||||||
|
itemUnit: string;
|
||||||
|
itemsUnit: string;
|
||||||
|
remove: string;
|
||||||
|
selectAll: string;
|
||||||
|
selectCurrent: string;
|
||||||
|
selectInvert: string;
|
||||||
|
removeAll: string;
|
||||||
|
removeCurrent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transferProps = {
|
||||||
|
prefixCls: String,
|
||||||
|
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
|
||||||
|
disabled: { type: Boolean, default: undefined },
|
||||||
|
targetKeys: { type: Array as PropType<string[]>, default: undefined },
|
||||||
|
selectedKeys: { type: Array as PropType<string[]>, default: undefined },
|
||||||
|
render: { type: Function as PropType<TransferRender<TransferItem>> },
|
||||||
|
listStyle: {
|
||||||
|
type: [Function, Object] as PropType<((style: ListStyle) => CSSProperties) | CSSProperties>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
operationStyle: PropTypes.style,
|
||||||
|
titles: { type: Array as PropType<string[]> },
|
||||||
|
operations: { type: Array as PropType<string[]> },
|
||||||
|
showSearch: { type: Boolean, default: false },
|
||||||
|
filterOption: { type: Function as PropType<(inputValue: string, item: TransferItem) => boolean> },
|
||||||
|
searchPlaceholder: String,
|
||||||
notFoundContent: PropTypes.any,
|
notFoundContent: PropTypes.any,
|
||||||
locale: PropTypes.object,
|
locale: { type: Object as PropType<Partial<TransferLocale>>, default: () => ({}) },
|
||||||
rowKey: PropTypes.func,
|
rowKey: { type: Function as PropType<(record: TransferItem) => string> },
|
||||||
lazy: PropTypes.oneOfType([PropTypes.object, PropTypes.looseBool]),
|
showSelectAll: { type: Boolean, default: undefined },
|
||||||
showSelectAll: PropTypes.looseBool,
|
selectAllLabels: { type: Array as PropType<SelectAllLabel[]> },
|
||||||
children: PropTypes.any,
|
children: { type: Function as PropType<(props: TransferListBodyProps) => VueNode> },
|
||||||
|
oneWay: { type: Boolean, default: undefined },
|
||||||
|
pagination: { type: [Object, Boolean] as PropType<PaginationType>, default: undefined },
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
onSelectChange: PropTypes.func,
|
onSelectChange: PropTypes.func,
|
||||||
onSearchChange: PropTypes.func,
|
onSearchChange: PropTypes.func,
|
||||||
onSearch: PropTypes.func,
|
onSearch: PropTypes.func,
|
||||||
onScroll: PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
['onUpdate:targetKeys']: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TransferLocale {
|
export type TransferProps = Partial<ExtractPropTypes<typeof transferProps>>;
|
||||||
titles: string[];
|
|
||||||
notFoundContent: string;
|
|
||||||
searchPlaceholder: string;
|
|
||||||
itemUnit: string;
|
|
||||||
itemsUnit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Transfer = defineComponent({
|
const Transfer = defineComponent({
|
||||||
name: 'ATransfer',
|
name: 'ATransfer',
|
||||||
mixins: [BaseMixin],
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: initDefaultProps(TransferProps, {
|
props: transferProps,
|
||||||
dataSource: [],
|
slots: [
|
||||||
locale: {},
|
'leftTitle',
|
||||||
showSearch: false,
|
'rightTitle',
|
||||||
listStyle: () => {},
|
'children',
|
||||||
}),
|
'render',
|
||||||
setup() {
|
'notFoundContent',
|
||||||
return {
|
'leftSelectAllLabel',
|
||||||
separatedDataSource: null,
|
'rightSelectAllLabel',
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
'footer',
|
||||||
};
|
],
|
||||||
},
|
emits: ['update:targetKeys', 'change', 'search', 'scroll', 'selectChange', 'searchChange'],
|
||||||
data() {
|
setup(props, { emit, attrs, slots, expose }) {
|
||||||
// vue 中 通过slot,不方便传递,保留notFoundContent及searchPlaceholder
|
const { configProvider, prefixCls, direction } = useConfigInject('transfer', props);
|
||||||
// warning(
|
const sourceSelectedKeys = ref([]);
|
||||||
// !(getComponent(this, 'notFoundContent') || hasProp(this, 'searchPlaceholder')),
|
const targetSelectedKeys = ref([]);
|
||||||
// 'Transfer[notFoundContent] and Transfer[searchPlaceholder] will be removed, ' +
|
|
||||||
// 'please use Transfer[locale] instead.',
|
|
||||||
// )
|
|
||||||
const { selectedKeys = [], targetKeys = [] } = this;
|
|
||||||
return {
|
|
||||||
leftFilter: '',
|
|
||||||
rightFilter: '',
|
|
||||||
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
|
||||||
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
targetKeys() {
|
|
||||||
this.updateState();
|
|
||||||
if (this.selectedKeys) {
|
|
||||||
const targetKeys = this.targetKeys || [];
|
|
||||||
this.setState({
|
|
||||||
sourceSelectedKeys: this.selectedKeys.filter(key => !targetKeys.includes(key)),
|
|
||||||
targetSelectedKeys: this.selectedKeys.filter(key => targetKeys.includes(key)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataSource() {
|
|
||||||
this.updateState();
|
|
||||||
},
|
|
||||||
selectedKeys() {
|
|
||||||
if (this.selectedKeys) {
|
|
||||||
const targetKeys = this.targetKeys || [];
|
|
||||||
this.setState({
|
|
||||||
sourceSelectedKeys: this.selectedKeys.filter(key => !targetKeys.includes(key)),
|
|
||||||
targetSelectedKeys: this.selectedKeys.filter(key => targetKeys.includes(key)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// this.currentProps = { ...this.$props }
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getSelectedKeysName(direction) {
|
|
||||||
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
|
|
||||||
},
|
|
||||||
|
|
||||||
getTitles(transferLocale: TransferLocale) {
|
watch(
|
||||||
if (this.titles) {
|
() => props.selectedKeys,
|
||||||
return this.titles;
|
() => {
|
||||||
}
|
sourceSelectedKeys.value =
|
||||||
return transferLocale.titles || ['', ''];
|
props.selectedKeys?.filter(key => props.targetKeys.indexOf(key) === -1) || [];
|
||||||
},
|
targetSelectedKeys.value =
|
||||||
|
props.selectedKeys?.filter(key => props.targetKeys.indexOf(key) > -1) || [];
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
getLocale(transferLocale: TransferLocale, renderEmpty: RenderEmptyHandler) {
|
const getLocale = (transferLocale: TransferLocale, renderEmpty: RenderEmptyHandler) => {
|
||||||
// Keep old locale props still working.
|
// Keep old locale props still working.
|
||||||
const oldLocale: { notFoundContent?: any; searchPlaceholder?: string } = {
|
const oldLocale: { notFoundContent?: any; searchPlaceholder?: string } = {
|
||||||
notFoundContent: renderEmpty('Transfer'),
|
notFoundContent: renderEmpty('Transfer'),
|
||||||
};
|
};
|
||||||
const notFoundContent = getComponent(this, 'notFoundContent');
|
const notFoundContent = getPropsSlot(slots, props, 'notFoundContent');
|
||||||
if (notFoundContent) {
|
if (notFoundContent) {
|
||||||
oldLocale.notFoundContent = notFoundContent;
|
oldLocale.notFoundContent = notFoundContent;
|
||||||
}
|
}
|
||||||
if (hasProp(this, 'searchPlaceholder')) {
|
if (props.searchPlaceholder !== undefined) {
|
||||||
oldLocale.searchPlaceholder = this.$props.searchPlaceholder;
|
oldLocale.searchPlaceholder = props.searchPlaceholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...transferLocale, ...oldLocale, ...this.$props.locale };
|
return { ...transferLocale, ...oldLocale, ...props.locale };
|
||||||
},
|
};
|
||||||
updateState() {
|
|
||||||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
|
||||||
this.separatedDataSource = null;
|
|
||||||
if (!this.selectedKeys) {
|
|
||||||
// clear key nolonger existed
|
|
||||||
// clear checkedKeys according to targetKeys
|
|
||||||
const { dataSource, targetKeys = [] } = this;
|
|
||||||
|
|
||||||
const newSourceSelectedKeys = [];
|
const moveTo = (direction: TransferDirection) => {
|
||||||
const newTargetSelectedKeys = [];
|
const { targetKeys = [], dataSource = [] } = props;
|
||||||
dataSource.forEach(({ key }) => {
|
const moveKeys = direction === 'right' ? sourceSelectedKeys.value : targetSelectedKeys.value;
|
||||||
if (sourceSelectedKeys.includes(key) && !targetKeys.includes(key)) {
|
|
||||||
newSourceSelectedKeys.push(key);
|
|
||||||
}
|
|
||||||
if (targetSelectedKeys.includes(key) && targetKeys.includes(key)) {
|
|
||||||
newTargetSelectedKeys.push(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({
|
|
||||||
sourceSelectedKeys: newSourceSelectedKeys,
|
|
||||||
targetSelectedKeys: newTargetSelectedKeys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
moveTo(direction: TransferDirection) {
|
|
||||||
const { targetKeys = [], dataSource = [] } = this.$props;
|
|
||||||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
|
||||||
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
|
|
||||||
// filter the disabled options
|
// filter the disabled options
|
||||||
const newMoveKeys = moveKeys.filter(
|
const newMoveKeys = moveKeys.filter(
|
||||||
key => !dataSource.some(data => !!(key === data.key && data.disabled)),
|
key => !dataSource.some(data => !!(key === data.key && data.disabled)),
|
||||||
|
|
@ -182,101 +161,74 @@ const Transfer = defineComponent({
|
||||||
|
|
||||||
// empty checked keys
|
// empty checked keys
|
||||||
const oppositeDirection = direction === 'right' ? 'left' : 'right';
|
const oppositeDirection = direction === 'right' ? 'left' : 'right';
|
||||||
this.setState({
|
direction === 'right' ? (sourceSelectedKeys.value = []) : (targetSelectedKeys.value = []);
|
||||||
[this.getSelectedKeysName(oppositeDirection)]: [],
|
emit('update:targetKeys', newTargetKeys);
|
||||||
});
|
handleSelectChange(oppositeDirection, []);
|
||||||
this.handleSelectChange(oppositeDirection, []);
|
emit('change', newTargetKeys, direction, newMoveKeys);
|
||||||
|
};
|
||||||
|
|
||||||
this.$emit('change', newTargetKeys, direction, newMoveKeys);
|
const moveToLeft = () => {
|
||||||
},
|
moveTo('left');
|
||||||
moveToLeft() {
|
};
|
||||||
this.moveTo('left');
|
const moveToRight = () => {
|
||||||
},
|
moveTo('right');
|
||||||
moveToRight() {
|
};
|
||||||
this.moveTo('right');
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemSelectAll(direction: TransferDirection, selectedKeys: string[], checkAll: boolean) {
|
const onItemSelectAll = (direction: TransferDirection, selectedKeys: string[]) => {
|
||||||
const originalSelectedKeys = this.$data[this.getSelectedKeysName(direction)] || [];
|
handleSelectChange(direction, selectedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
let mergedCheckedKeys = [];
|
const onLeftItemSelectAll = (selectedKeys: string[]) => {
|
||||||
if (checkAll) {
|
return onItemSelectAll('left', selectedKeys);
|
||||||
// Merge current keys with origin key
|
};
|
||||||
mergedCheckedKeys = Array.from(new Set([...originalSelectedKeys, ...selectedKeys]));
|
|
||||||
|
const onRightItemSelectAll = (selectedKeys: string[]) => {
|
||||||
|
return onItemSelectAll('right', selectedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (direction: TransferDirection, holder: string[]) => {
|
||||||
|
if (direction === 'left') {
|
||||||
|
if (!props.selectedKeys) {
|
||||||
|
sourceSelectedKeys.value = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('selectChange', holder, targetSelectedKeys.value);
|
||||||
} else {
|
} else {
|
||||||
// Remove current keys from origin keys
|
if (!props.selectedKeys) {
|
||||||
mergedCheckedKeys = originalSelectedKeys.filter(key => selectedKeys.indexOf(key) === -1);
|
targetSelectedKeys.value = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('selectChange', sourceSelectedKeys.value, holder);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.handleSelectChange(direction, mergedCheckedKeys);
|
const handleFilter = (direction: TransferDirection, e) => {
|
||||||
|
|
||||||
if (!this.$props.selectedKeys) {
|
|
||||||
this.setState({
|
|
||||||
[this.getSelectedKeysName(direction)]: mergedCheckedKeys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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;
|
const value = e.target.value;
|
||||||
// if (getListeners(this).searchChange) {
|
emit('search', direction, value);
|
||||||
// warning(
|
};
|
||||||
// false,
|
|
||||||
// 'Transfer',
|
|
||||||
// '`searchChange` in Transfer is deprecated. Please use `search` instead.',
|
|
||||||
// );
|
|
||||||
// this.$emit('searchChange', direction, e);
|
|
||||||
// }
|
|
||||||
this.$emit('search', direction, value);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLeftFilter(e) {
|
const handleLeftFilter = (e: Event) => {
|
||||||
this.handleFilter('left', e);
|
handleFilter('left', e);
|
||||||
},
|
};
|
||||||
handleRightFilter(e) {
|
const handleRightFilter = (e: Event) => {
|
||||||
this.handleFilter('right', e);
|
handleFilter('right', e);
|
||||||
},
|
};
|
||||||
|
|
||||||
handleClear(direction) {
|
const handleClear = (direction: TransferDirection) => {
|
||||||
this.$emit('search', direction, '');
|
emit('search', direction, '');
|
||||||
},
|
};
|
||||||
|
|
||||||
handleLeftClear() {
|
const handleLeftClear = () => {
|
||||||
this.handleClear('left');
|
handleClear('left');
|
||||||
},
|
};
|
||||||
handleRightClear() {
|
|
||||||
this.handleClear('right');
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemSelect(direction, selectedKey, checked) {
|
const handleRightClear = () => {
|
||||||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
handleClear('right');
|
||||||
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
|
};
|
||||||
|
|
||||||
|
const onItemSelect = (direction: TransferDirection, selectedKey: string, checked: boolean) => {
|
||||||
|
const holder =
|
||||||
|
direction === 'left' ? [...sourceSelectedKeys.value] : [...targetSelectedKeys.value];
|
||||||
const index = holder.indexOf(selectedKey);
|
const index = holder.indexOf(selectedKey);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
holder.splice(index, 1);
|
holder.splice(index, 1);
|
||||||
|
|
@ -284,67 +236,50 @@ const Transfer = defineComponent({
|
||||||
if (checked) {
|
if (checked) {
|
||||||
holder.push(selectedKey);
|
holder.push(selectedKey);
|
||||||
}
|
}
|
||||||
this.handleSelectChange(direction, holder);
|
handleSelectChange(direction, holder);
|
||||||
|
};
|
||||||
|
|
||||||
if (!this.selectedKeys) {
|
const onLeftItemSelect = (selectedKey: string, checked: boolean) => {
|
||||||
this.setState({
|
return onItemSelect('left', selectedKey, checked);
|
||||||
[this.getSelectedKeysName(direction)]: holder,
|
};
|
||||||
});
|
const onRightItemSelect = (selectedKey: string, checked: boolean) => {
|
||||||
}
|
return onItemSelect('right', selectedKey, checked);
|
||||||
},
|
};
|
||||||
|
const onRightItemRemove = (targetedKeys: string[]) => {
|
||||||
|
const { targetKeys = [] } = props;
|
||||||
|
const newTargetKeys = targetKeys.filter(key => !targetedKeys.includes(key));
|
||||||
|
emit('update:targetKeys', newTargetKeys);
|
||||||
|
emit('change', newTargetKeys, 'left', [...targetedKeys]);
|
||||||
|
};
|
||||||
|
|
||||||
// handleSelect(direction, selectedItem, checked) {
|
const handleScroll = (direction: TransferDirection, e: Event) => {
|
||||||
// warning(false, 'Transfer', '`handleSelect` will be removed, please use `onSelect` instead.');
|
emit('scroll', direction, e);
|
||||||
// this.onItemSelect(direction, selectedItem.key, checked);
|
};
|
||||||
// },
|
|
||||||
|
|
||||||
// handleLeftSelect(selectedItem, checked) {
|
const handleLeftScroll = (e: Event) => {
|
||||||
// return this.handleSelect('left', selectedItem, checked);
|
handleScroll('left', e);
|
||||||
// },
|
};
|
||||||
|
const handleRightScroll = (e: Event) => {
|
||||||
// handleRightSelect(selectedItem, checked) {
|
handleScroll('right', e);
|
||||||
// return this.handleSelect('right', selectedItem, checked);
|
};
|
||||||
// },
|
const handleListStyle = (
|
||||||
|
listStyle: ((style: ListStyle) => CSSProperties) | CSSProperties,
|
||||||
onLeftItemSelect(selectedKey, checked) {
|
direction: TransferDirection,
|
||||||
return this.onItemSelect('left', selectedKey, checked);
|
) => {
|
||||||
},
|
|
||||||
onRightItemSelect(selectedKey, checked) {
|
|
||||||
return this.onItemSelect('right', selectedKey, checked);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll(direction, e) {
|
|
||||||
this.$emit('scroll', direction, e);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLeftScroll(e) {
|
|
||||||
this.handleScroll('left', e);
|
|
||||||
},
|
|
||||||
handleRightScroll(e) {
|
|
||||||
this.handleScroll('right', e);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSelectChange(direction: TransferDirection, holder: string[]) {
|
|
||||||
const { sourceSelectedKeys, targetSelectedKeys } = this;
|
|
||||||
|
|
||||||
if (direction === 'left') {
|
|
||||||
this.$emit('selectChange', holder, targetSelectedKeys);
|
|
||||||
} else {
|
|
||||||
this.$emit('selectChange', sourceSelectedKeys, holder);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleListStyle(listStyle, direction) {
|
|
||||||
if (typeof listStyle === 'function') {
|
if (typeof listStyle === 'function') {
|
||||||
return listStyle({ direction });
|
return listStyle({ direction });
|
||||||
}
|
}
|
||||||
return listStyle;
|
return listStyle;
|
||||||
},
|
};
|
||||||
|
|
||||||
separateDataSource() {
|
const leftDataSource = ref([]);
|
||||||
const { dataSource, rowKey, targetKeys = [] } = this.$props;
|
const rightDataSource = ref([]);
|
||||||
|
|
||||||
const leftDataSource = [];
|
watchEffect(() => {
|
||||||
const rightDataSource = new Array(targetKeys.length);
|
const { dataSource, rowKey, targetKeys = [] } = props;
|
||||||
|
|
||||||
|
const ld = [];
|
||||||
|
const rd = new Array(targetKeys.length);
|
||||||
dataSource.forEach(record => {
|
dataSource.forEach(record => {
|
||||||
if (rowKey) {
|
if (rowKey) {
|
||||||
record.key = rowKey(record);
|
record.key = rowKey(record);
|
||||||
|
|
@ -354,132 +289,124 @@ const Transfer = defineComponent({
|
||||||
// leftDataSource should be ordered by dataSource
|
// leftDataSource should be ordered by dataSource
|
||||||
const indexOfKey = targetKeys.indexOf(record.key);
|
const indexOfKey = targetKeys.indexOf(record.key);
|
||||||
if (indexOfKey !== -1) {
|
if (indexOfKey !== -1) {
|
||||||
rightDataSource[indexOfKey] = record;
|
rd[indexOfKey] = record;
|
||||||
} else {
|
} else {
|
||||||
leftDataSource.push(record);
|
ld.push(record);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
leftDataSource.value = ld;
|
||||||
leftDataSource,
|
rightDataSource.value = rd;
|
||||||
rightDataSource,
|
});
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTransfer(transferLocale: TransferLocale) {
|
expose({ handleSelectChange });
|
||||||
const props = getOptionProps(this);
|
|
||||||
|
const renderTransfer = (transferLocale: TransferLocale) => {
|
||||||
const {
|
const {
|
||||||
prefixCls: customizePrefixCls,
|
|
||||||
disabled,
|
disabled,
|
||||||
operations = [],
|
operations = [],
|
||||||
showSearch,
|
showSearch,
|
||||||
listStyle,
|
listStyle,
|
||||||
operationStyle,
|
operationStyle,
|
||||||
filterOption,
|
filterOption,
|
||||||
lazy,
|
|
||||||
showSelectAll,
|
showSelectAll,
|
||||||
|
selectAllLabels = [],
|
||||||
|
oneWay,
|
||||||
|
pagination,
|
||||||
} = props;
|
} = props;
|
||||||
const { class: className, style } = this.$attrs;
|
const { class: className, style } = attrs;
|
||||||
const children = getComponent(this, 'children', {}, false);
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('transfer', customizePrefixCls);
|
|
||||||
|
|
||||||
const renderEmpty = this.configProvider.renderEmpty;
|
const children = slots.children;
|
||||||
const locale = this.getLocale(transferLocale, renderEmpty);
|
const mergedPagination = !children && pagination;
|
||||||
const { sourceSelectedKeys, targetSelectedKeys, $slots } = this;
|
|
||||||
const { body, footer } = $slots;
|
|
||||||
const renderItem = props.render || this.$slots.render;
|
|
||||||
const { leftDataSource, rightDataSource } = this.separateDataSource();
|
|
||||||
const leftActive = targetSelectedKeys.length > 0;
|
|
||||||
const rightActive = sourceSelectedKeys.length > 0;
|
|
||||||
|
|
||||||
const cls = classNames(prefixCls, className, {
|
const renderEmpty = configProvider.renderEmpty;
|
||||||
[`${prefixCls}-disabled`]: disabled,
|
const locale = getLocale(transferLocale, renderEmpty);
|
||||||
[`${prefixCls}-customize-list`]: !!children,
|
const { footer } = slots;
|
||||||
|
const renderItem = props.render || slots.render;
|
||||||
|
const leftActive = targetSelectedKeys.value.length > 0;
|
||||||
|
const rightActive = sourceSelectedKeys.value.length > 0;
|
||||||
|
|
||||||
|
const cls = classNames(prefixCls.value, className, {
|
||||||
|
[`${prefixCls.value}-disabled`]: disabled,
|
||||||
|
[`${prefixCls.value}-customize-list`]: !!children,
|
||||||
});
|
});
|
||||||
const titles = this.getTitles(locale);
|
const titles = props.titles;
|
||||||
|
const leftTitle =
|
||||||
|
(titles && titles[0]) ?? slots.leftTitle?.() ?? (locale.titles || ['', ''])[0];
|
||||||
|
const rightTitle =
|
||||||
|
(titles && titles[1]) ?? slots.rightTitle?.() ?? (locale.titles || ['', ''])[1];
|
||||||
return (
|
return (
|
||||||
<div class={cls} style={style}>
|
<div class={cls} style={style}>
|
||||||
<List
|
<List
|
||||||
key="leftList"
|
key="leftList"
|
||||||
prefixCls={`${prefixCls}-list`}
|
prefixCls={`${prefixCls.value}-list`}
|
||||||
titleText={titles[0]}
|
dataSource={leftDataSource.value}
|
||||||
dataSource={leftDataSource}
|
|
||||||
filterOption={filterOption}
|
filterOption={filterOption}
|
||||||
style={this.handleListStyle(listStyle, 'left')}
|
style={handleListStyle(listStyle, 'left')}
|
||||||
checkedKeys={sourceSelectedKeys}
|
checkedKeys={sourceSelectedKeys.value}
|
||||||
handleFilter={this.handleLeftFilter}
|
handleFilter={handleLeftFilter}
|
||||||
handleClear={this.handleLeftClear}
|
handleClear={handleLeftClear}
|
||||||
// handleSelect={this.handleLeftSelect}
|
onItemSelect={onLeftItemSelect}
|
||||||
handleSelectAll={this.handleLeftSelectAll}
|
onItemSelectAll={onLeftItemSelectAll}
|
||||||
onItemSelect={this.onLeftItemSelect}
|
|
||||||
onItemSelectAll={this.onLeftItemSelectAll}
|
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
showSearch={showSearch}
|
showSearch={showSearch}
|
||||||
body={body}
|
|
||||||
renderList={children}
|
renderList={children}
|
||||||
footer={footer}
|
onScroll={handleLeftScroll}
|
||||||
lazy={lazy}
|
|
||||||
onScroll={this.handleLeftScroll}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
direction="left"
|
direction="left"
|
||||||
showSelectAll={showSelectAll}
|
showSelectAll={showSelectAll}
|
||||||
itemUnit={locale.itemUnit}
|
selectAllLabel={selectAllLabels[0] || slots.leftSelectAllLabel}
|
||||||
itemsUnit={locale.itemsUnit}
|
pagination={mergedPagination}
|
||||||
notFoundContent={locale.notFoundContent}
|
{...locale}
|
||||||
searchPlaceholder={locale.searchPlaceholder}
|
v-slots={{ titleText: () => leftTitle, footer }}
|
||||||
/>
|
/>
|
||||||
<Operation
|
<Operation
|
||||||
key="operation"
|
key="operation"
|
||||||
class={`${prefixCls}-operation`}
|
class={`${prefixCls.value}-operation`}
|
||||||
rightActive={rightActive}
|
rightActive={rightActive}
|
||||||
rightArrowText={operations[0]}
|
rightArrowText={operations[0]}
|
||||||
moveToRight={this.moveToRight}
|
moveToRight={moveToRight}
|
||||||
leftActive={leftActive}
|
leftActive={leftActive}
|
||||||
leftArrowText={operations[1]}
|
leftArrowText={operations[1]}
|
||||||
moveToLeft={this.moveToLeft}
|
moveToLeft={moveToLeft}
|
||||||
style={operationStyle}
|
style={operationStyle}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
direction={direction.value}
|
||||||
|
oneWay={oneWay}
|
||||||
/>
|
/>
|
||||||
<List
|
<List
|
||||||
key="rightList"
|
key="rightList"
|
||||||
prefixCls={`${prefixCls}-list`}
|
prefixCls={`${prefixCls.value}-list`}
|
||||||
titleText={titles[1]}
|
dataSource={rightDataSource.value}
|
||||||
dataSource={rightDataSource}
|
|
||||||
filterOption={filterOption}
|
filterOption={filterOption}
|
||||||
style={this.handleListStyle(listStyle, 'right')}
|
style={handleListStyle(listStyle, 'right')}
|
||||||
checkedKeys={targetSelectedKeys}
|
checkedKeys={targetSelectedKeys.value}
|
||||||
handleFilter={this.handleRightFilter}
|
handleFilter={handleRightFilter}
|
||||||
handleClear={this.handleRightClear}
|
handleClear={handleRightClear}
|
||||||
// handleSelect={this.handleRightSelect}
|
onItemSelect={onRightItemSelect}
|
||||||
handleSelectAll={this.handleRightSelectAll}
|
onItemSelectAll={onRightItemSelectAll}
|
||||||
onItemSelect={this.onRightItemSelect}
|
onItemRemove={onRightItemRemove}
|
||||||
onItemSelectAll={this.onRightItemSelectAll}
|
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
showSearch={showSearch}
|
showSearch={showSearch}
|
||||||
body={body}
|
|
||||||
renderList={children}
|
renderList={children}
|
||||||
footer={footer}
|
onScroll={handleRightScroll}
|
||||||
lazy={lazy}
|
|
||||||
onScroll={this.handleRightScroll}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
direction="right"
|
direction="right"
|
||||||
showSelectAll={showSelectAll}
|
showSelectAll={showSelectAll}
|
||||||
itemUnit={locale.itemUnit}
|
selectAllLabel={selectAllLabels[1] || slots.rightSelectAllLabel}
|
||||||
itemsUnit={locale.itemsUnit}
|
showRemove={oneWay}
|
||||||
notFoundContent={locale.notFoundContent}
|
pagination={mergedPagination}
|
||||||
searchPlaceholder={locale.searchPlaceholder}
|
{...locale}
|
||||||
|
v-slots={{ titleText: () => rightTitle, footer }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
},
|
return () => (
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<LocaleReceiver
|
<LocaleReceiver
|
||||||
componentName="Transfer"
|
componentName="Transfer"
|
||||||
defaultLocale={defaultLocale.Transfer}
|
defaultLocale={defaultLocale.Transfer}
|
||||||
children={this.renderTransfer}
|
children={renderTransfer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type PaginationType =
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
pageSize?: number;
|
||||||
|
};
|
||||||
|
|
@ -1,32 +1,20 @@
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { isValidElement, splitAttrs, findDOMNode, filterEmpty } from '../_util/props-util';
|
import { isValidElement, splitAttrs, filterEmpty } from '../_util/props-util';
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
|
||||||
import Checkbox from '../checkbox';
|
import Checkbox from '../checkbox';
|
||||||
|
import Menu from '../menu';
|
||||||
|
import Dropdown from '../dropdown';
|
||||||
import Search from './search';
|
import Search from './search';
|
||||||
import defaultRenderList from './renderListBody';
|
import ListBody from './ListBody';
|
||||||
import triggerEvent from '../_util/triggerEvent';
|
import type { VNode, VNodeTypes, ExtractPropTypes, PropType } from 'vue';
|
||||||
import type { VNode, VNodeTypes } from 'vue';
|
import { watchEffect, computed } from 'vue';
|
||||||
import { defineComponent, nextTick } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import type { RadioChangeEvent } from '../radio/interface';
|
import type { RadioChangeEvent } from '../radio/interface';
|
||||||
|
import type { TransferItem } from './index';
|
||||||
|
|
||||||
const defaultRender = () => null;
|
const defaultRender = () => null;
|
||||||
|
|
||||||
const TransferItem = {
|
|
||||||
key: PropTypes.string.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
description: PropTypes.string,
|
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface DataSourceItem {
|
|
||||||
key: string;
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRenderResultPlainObject(result: VNode) {
|
function isRenderResultPlainObject(result: VNode) {
|
||||||
return (
|
return (
|
||||||
result &&
|
result &&
|
||||||
|
|
@ -35,241 +23,70 @@ function isRenderResultPlainObject(result: VNode) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TransferListProps = {
|
function getEnabledItemKeys<RecordType extends TransferItem>(items: RecordType[]) {
|
||||||
|
return items.filter(data => !data.disabled).map(data => data.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transferListProps = {
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
titleText: PropTypes.string,
|
dataSource: { type: Array as PropType<TransferItem[]>, default: [] },
|
||||||
dataSource: PropTypes.arrayOf(PropTypes.shape(TransferItem).loose),
|
|
||||||
filter: PropTypes.string,
|
filter: PropTypes.string,
|
||||||
filterOption: PropTypes.func,
|
filterOption: PropTypes.func,
|
||||||
checkedKeys: PropTypes.arrayOf(PropTypes.string),
|
checkedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
handleFilter: PropTypes.func,
|
handleFilter: PropTypes.func,
|
||||||
handleSelect: PropTypes.func,
|
|
||||||
handleSelectAll: PropTypes.func,
|
|
||||||
handleClear: PropTypes.func,
|
handleClear: PropTypes.func,
|
||||||
renderItem: PropTypes.func,
|
renderItem: PropTypes.func,
|
||||||
showSearch: PropTypes.looseBool,
|
showSearch: PropTypes.looseBool.def(false),
|
||||||
searchPlaceholder: PropTypes.string,
|
searchPlaceholder: PropTypes.string,
|
||||||
notFoundContent: PropTypes.any,
|
notFoundContent: PropTypes.any,
|
||||||
itemUnit: PropTypes.string,
|
itemUnit: PropTypes.string,
|
||||||
itemsUnit: PropTypes.string,
|
itemsUnit: PropTypes.string,
|
||||||
body: PropTypes.any,
|
|
||||||
renderList: PropTypes.any,
|
renderList: PropTypes.any,
|
||||||
footer: PropTypes.any,
|
|
||||||
lazy: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object])),
|
|
||||||
disabled: PropTypes.looseBool,
|
disabled: PropTypes.looseBool,
|
||||||
direction: PropTypes.string,
|
direction: PropTypes.string,
|
||||||
showSelectAll: PropTypes.looseBool,
|
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,
|
onItemSelect: PropTypes.func,
|
||||||
onItemSelectAll: PropTypes.func,
|
onItemSelectAll: PropTypes.func,
|
||||||
|
onItemRemove: PropTypes.func,
|
||||||
onScroll: PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderListNode(renderList: Function, props: any) {
|
export type TransferListProps = Partial<ExtractPropTypes<typeof transferListProps>>;
|
||||||
let bodyContent = renderList ? renderList(props) : null;
|
|
||||||
const customize = !!bodyContent && filterEmpty(bodyContent).length > 0;
|
|
||||||
if (!customize) {
|
|
||||||
bodyContent = defaultRenderList(props);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
customize,
|
|
||||||
bodyContent,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TransferList',
|
name: 'TransferList',
|
||||||
mixins: [BaseMixin],
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: initDefaultProps(TransferListProps, {
|
props: transferListProps,
|
||||||
dataSource: [],
|
emits: ['scroll', 'itemSelectAll', 'itemRemove', 'itemSelect'],
|
||||||
titleText: '',
|
slots: ['footer', 'titleText'],
|
||||||
showSearch: false,
|
setup(props, { attrs, slots }) {
|
||||||
lazy: {},
|
const filterValue = ref('');
|
||||||
}),
|
const transferNode = ref();
|
||||||
setup() {
|
const defaultListBodyRef = ref();
|
||||||
return {
|
|
||||||
timer: null,
|
const renderListBody = (renderList: any, props: any) => {
|
||||||
triggerScrollTimer: null,
|
let bodyContent = renderList ? renderList(props) : null;
|
||||||
scrollEvent: null,
|
const customize = !!bodyContent && filterEmpty(bodyContent).length > 0;
|
||||||
|
if (!customize) {
|
||||||
|
bodyContent = <ListBody {...props} ref={defaultListBodyRef} />;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
customize,
|
||||||
|
bodyContent,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
filterValue: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearTimeout(this.triggerScrollTimer);
|
|
||||||
// if (this.scrollEvent) {
|
|
||||||
// this.scrollEvent.remove();
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
nextTick(() => {
|
|
||||||
if (this.scrollEvent) {
|
|
||||||
this.scrollEvent.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleScroll(e: Event) {
|
|
||||||
this.$emit('scroll', e);
|
|
||||||
},
|
|
||||||
getCheckStatus(filteredItems: DataSourceItem[]) {
|
|
||||||
const { checkedKeys } = this.$props;
|
|
||||||
if (checkedKeys.length === 0) {
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
if (filteredItems.every(item => checkedKeys.indexOf(item.key) >= 0 || !!item.disabled)) {
|
|
||||||
return 'all';
|
|
||||||
}
|
|
||||||
return 'part';
|
|
||||||
},
|
|
||||||
|
|
||||||
getFilteredItems(dataSource: DataSourceItem[], filterValue: string) {
|
const renderItemHtml = (item: TransferItem) => {
|
||||||
const filteredItems = [];
|
const { renderItem = defaultRender } = props;
|
||||||
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: string,
|
|
||||||
searchPlaceholder: string,
|
|
||||||
filterValue: string,
|
|
||||||
filteredItems: DataSourceItem[],
|
|
||||||
notFoundContent: unknown,
|
|
||||||
bodyDom: unknown,
|
|
||||||
filteredRenderItems: unknown,
|
|
||||||
checkedKeys: string[],
|
|
||||||
renderList: Function,
|
|
||||||
showSearch: boolean,
|
|
||||||
disabled: boolean,
|
|
||||||
) {
|
|
||||||
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: VNodeTypes;
|
|
||||||
const { onEvents } = splitAttrs(this.$attrs);
|
|
||||||
const { bodyContent, customize } = renderListNode(renderList, {
|
|
||||||
...this.$props,
|
|
||||||
filteredItems,
|
|
||||||
filteredRenderItems,
|
|
||||||
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.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: DataSourceItem[], showSelectAll: boolean, disabled: boolean) {
|
|
||||||
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: DataSourceItem) {
|
|
||||||
const { checkedKeys } = this.$props;
|
|
||||||
const result = checkedKeys.some(key => key === selectedItem.key);
|
|
||||||
this.handleSelect(selectedItem, !result);
|
|
||||||
},
|
|
||||||
_handleFilter(e: RadioChangeEvent) {
|
|
||||||
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
|
|
||||||
// https://github.com/ant-design/ant-design/issues/5631
|
|
||||||
this.triggerScrollTimer = setTimeout(() => {
|
|
||||||
const transferNode = findDOMNode(this);
|
|
||||||
const listNode = transferNode.querySelectorAll('.ant-transfer-list-content')[0];
|
|
||||||
if (listNode) {
|
|
||||||
triggerEvent(listNode, 'scroll');
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
_handleClear(e: Event) {
|
|
||||||
this.setState({ filterValue: '' });
|
|
||||||
this.handleClear(e);
|
|
||||||
},
|
|
||||||
matchFilter(text: string, item: DataSourceItem) {
|
|
||||||
const { filterValue } = this.$data;
|
|
||||||
const { filterOption } = this.$props;
|
|
||||||
if (filterOption) {
|
|
||||||
return filterOption(filterValue, item);
|
|
||||||
}
|
|
||||||
return text.indexOf(filterValue) >= 0;
|
|
||||||
},
|
|
||||||
renderItemHtml(item: DataSourceItem) {
|
|
||||||
const { renderItem = defaultRender } = this.$props;
|
|
||||||
const renderResult = renderItem(item);
|
const renderResult = renderItem(item);
|
||||||
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
|
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
|
||||||
return {
|
return {
|
||||||
|
|
@ -277,82 +94,311 @@ export default defineComponent({
|
||||||
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
|
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
|
||||||
item,
|
item,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
filterNull(arr: unknown[]) {
|
|
||||||
return arr.filter(item => {
|
const filteredItems = ref([]);
|
||||||
return item !== null;
|
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;
|
||||||
|
|
||||||
render() {
|
|
||||||
const { filterValue } = this.$data;
|
|
||||||
const {
|
|
||||||
prefixCls,
|
|
||||||
dataSource,
|
|
||||||
titleText,
|
|
||||||
checkedKeys,
|
|
||||||
disabled,
|
|
||||||
body,
|
|
||||||
footer,
|
|
||||||
showSearch,
|
|
||||||
searchPlaceholder,
|
|
||||||
notFoundContent,
|
|
||||||
itemUnit,
|
|
||||||
itemsUnit,
|
|
||||||
renderList,
|
|
||||||
showSelectAll,
|
|
||||||
} = this.$props;
|
|
||||||
|
|
||||||
// Custom Layout
|
|
||||||
const footerDom = footer && footer({ ...this.$props });
|
|
||||||
const bodyDom = body && body({ ...this.$props });
|
|
||||||
|
|
||||||
const listCls = classNames(prefixCls, {
|
|
||||||
[`${prefixCls}-with-footer`]: !!footerDom,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ====================== Get filtered, checked item list ======================
|
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 { filteredItems, filteredRenderItems } = this.getFilteredItems(dataSource, filterValue);
|
const enabledItemKeys = computed(() => {
|
||||||
|
return getEnabledItemKeys(filteredItems.value);
|
||||||
|
});
|
||||||
|
|
||||||
// ================================= List Body =================================
|
const getNewSelectKeys = (keys, unCheckedKeys) => {
|
||||||
|
return Array.from(new Set([...keys, ...props.checkedKeys])).filter(
|
||||||
|
key => unCheckedKeys.indexOf(key) === -1,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const unit = dataSource.length > 1 ? itemsUnit : itemUnit;
|
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 listBody = this.getListBody(
|
const keys = enabledItemKeys.value;
|
||||||
prefixCls,
|
props.onItemSelectAll(
|
||||||
searchPlaceholder,
|
getNewSelectKeys(!checkedAll ? keys : [], checkedAll ? props.checkedKeys : []),
|
||||||
filterValue,
|
);
|
||||||
filteredItems,
|
}}
|
||||||
notFoundContent,
|
/>
|
||||||
bodyDom,
|
);
|
||||||
filteredRenderItems,
|
|
||||||
checkedKeys,
|
|
||||||
renderList,
|
|
||||||
showSearch,
|
|
||||||
disabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
const listFooter = footerDom ? <div class={`${prefixCls}-footer`}>{footerDom}</div> : null;
|
return checkAllCheckbox;
|
||||||
|
};
|
||||||
|
|
||||||
const checkAllCheckbox = this.getCheckBox(filteredItems, showSelectAll, disabled);
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const getSelectAllLabel = (selectedCount: number, totalCount: number) => {
|
||||||
<div class={listCls} style={this.$attrs.style}>
|
const { itemsUnit, itemUnit, selectAllLabel } = props;
|
||||||
<div class={`${prefixCls}-header`}>
|
if (selectAllLabel) {
|
||||||
{checkAllCheckbox}
|
return typeof selectAllLabel === 'function'
|
||||||
<span class={`${prefixCls}-header-selected`}>
|
? selectAllLabel({ selectedCount, totalCount })
|
||||||
<span>
|
: selectAllLabel;
|
||||||
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + filteredItems.length}{' '}
|
}
|
||||||
{unit}
|
const unit = totalCount > 1 ? itemsUnit : itemUnit;
|
||||||
</span>
|
return (
|
||||||
<span class={`${prefixCls}-header-title`}>{titleText}</span>
|
<>
|
||||||
</span>
|
{(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>
|
</div>
|
||||||
{listBody}
|
) : null;
|
||||||
{listFooter}
|
|
||||||
</div>
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import type { CSSProperties, FunctionalComponent } from 'vue';
|
||||||
import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
|
import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
|
||||||
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
|
import type { Direction } from '../config-provider';
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
export interface TransferOperationProps {
|
export interface TransferOperationProps {
|
||||||
class?: any;
|
class?: string;
|
||||||
leftArrowText?: string;
|
leftArrowText?: string;
|
||||||
rightArrowText?: string;
|
rightArrowText?: string;
|
||||||
moveToLeft?: (e: MouseEvent) => void;
|
moveToLeft?: (e: MouseEvent) => void;
|
||||||
|
|
@ -15,6 +16,8 @@ export interface TransferOperationProps {
|
||||||
rightActive?: boolean;
|
rightActive?: boolean;
|
||||||
style?: CSSProperties | string;
|
style?: CSSProperties | string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
direction?: Direction;
|
||||||
|
oneWay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Operation: FunctionalComponent<TransferOperationProps> = props => {
|
const Operation: FunctionalComponent<TransferOperationProps> = props => {
|
||||||
|
|
@ -28,6 +31,8 @@ const Operation: FunctionalComponent<TransferOperationProps> = props => {
|
||||||
rightActive,
|
rightActive,
|
||||||
class: className,
|
class: className,
|
||||||
style,
|
style,
|
||||||
|
direction,
|
||||||
|
oneWay,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -37,23 +42,25 @@ const Operation: FunctionalComponent<TransferOperationProps> = props => {
|
||||||
size="small"
|
size="small"
|
||||||
disabled={disabled || !rightActive}
|
disabled={disabled || !rightActive}
|
||||||
onClick={moveToRight}
|
onClick={moveToRight}
|
||||||
icon={<RightOutlined />}
|
icon={direction !== 'rtl' ? <RightOutlined /> : <LeftOutlined />}
|
||||||
>
|
>
|
||||||
{rightArrowText}
|
{rightArrowText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{!oneWay && (
|
||||||
type="primary"
|
<Button
|
||||||
size="small"
|
type="primary"
|
||||||
disabled={disabled || !leftActive}
|
size="small"
|
||||||
onClick={moveToLeft}
|
disabled={disabled || !leftActive}
|
||||||
icon={<LeftOutlined />}
|
onClick={moveToLeft}
|
||||||
>
|
icon={direction !== 'rtl' ? <LeftOutlined /> : <RightOutlined />}
|
||||||
{leftArrowText}
|
>
|
||||||
</Button>
|
{leftArrowText}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Operation.displayName = 'Operation';
|
||||||
Operation.inheritAttrs = false;
|
Operation.inheritAttrs = false;
|
||||||
|
|
||||||
export default Operation;
|
export default Operation;
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
import { defineComponent, nextTick } from 'vue';
|
|
||||||
import raf from '../_util/raf';
|
|
||||||
import ListItem from './ListItem';
|
|
||||||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
|
||||||
import { findDOMNode } from '../_util/props-util';
|
|
||||||
import { getTransitionGroupProps, TransitionGroup } from '../_util/transition';
|
|
||||||
import type { DataSourceItem } from './list';
|
|
||||||
const ListBody = defineComponent({
|
|
||||||
name: 'ListBody',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
filteredRenderItems: PropTypes.array.def([]),
|
|
||||||
lazy: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object])),
|
|
||||||
selectedKeys: PropTypes.array,
|
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
onItemSelect: PropTypes.func,
|
|
||||||
onItemSelectAll: PropTypes.func,
|
|
||||||
onScroll: PropTypes.func,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
mountId: null,
|
|
||||||
lazyId: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mounted: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
itemsLength(): number {
|
|
||||||
return this.filteredRenderItems ? this.filteredRenderItems.length : 0;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
itemsLength() {
|
|
||||||
nextTick(() => {
|
|
||||||
const { lazy } = this.$props;
|
|
||||||
if (lazy !== false) {
|
|
||||||
const container = findDOMNode(this);
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount() {
|
|
||||||
raf.cancel(this.mountId);
|
|
||||||
raf.cancel(this.lazyId);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleItemSelect(item: DataSourceItem) {
|
|
||||||
const { selectedKeys } = this.$props;
|
|
||||||
const checked = selectedKeys.indexOf(item.key) >= 0;
|
|
||||||
this.$emit('itemSelect', item.key, !checked);
|
|
||||||
},
|
|
||||||
handleScroll(e: Event) {
|
|
||||||
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 }: any) => {
|
|
||||||
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.handleItemSelect}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const transitionProps = getTransitionGroupProps(
|
|
||||||
mounted ? `${prefixCls}-content-item-highlight` : '',
|
|
||||||
{
|
|
||||||
tag: 'ul',
|
|
||||||
class: `${prefixCls}-content`,
|
|
||||||
onScroll: this.handleScroll,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return <TransitionGroup {...transitionProps}>{items}</TransitionGroup>;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default props => <ListBody {...props} />;
|
|
||||||
|
|
@ -1,62 +1,65 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { getOptionProps } from '../_util/props-util';
|
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||||
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
||||||
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||||
import Input from '../input';
|
import Input from '../input';
|
||||||
|
import type { ExtractPropTypes } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export const TransferSearchProps = {
|
export const transferSearchProps = {
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
value: PropTypes.any,
|
value: PropTypes.string,
|
||||||
handleClear: PropTypes.func,
|
handleClear: PropTypes.func,
|
||||||
disabled: PropTypes.looseBool,
|
disabled: PropTypes.looseBool,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TransferSearchProps = Partial<ExtractPropTypes<typeof transferSearchProps>>;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Search',
|
name: 'Search',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: initDefaultProps(TransferSearchProps, {
|
props: initDefaultProps(transferSearchProps, {
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
}),
|
}),
|
||||||
methods: {
|
emits: ['change'],
|
||||||
handleChange(e: Event) {
|
setup(props, { emit }) {
|
||||||
this.$emit('change', e);
|
const handleChange = (e: Event) => {
|
||||||
},
|
emit('change', e);
|
||||||
handleClear2(e: Event) {
|
};
|
||||||
e.preventDefault();
|
|
||||||
const { handleClear, disabled } = this.$props;
|
const handleClearFn = (e: Event) => {
|
||||||
|
const { handleClear, disabled } = props;
|
||||||
if (!disabled && handleClear) {
|
if (!disabled && handleClear) {
|
||||||
handleClear(e);
|
handleClear(e);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { placeholder, value, prefixCls, disabled } = getOptionProps(this);
|
|
||||||
const icon =
|
|
||||||
value && value.length > 0 ? (
|
|
||||||
<a href="#" class={`${prefixCls}-action`} onClick={this.handleClear2}>
|
|
||||||
<CloseCircleFilled />
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span class={`${prefixCls}-action`}>
|
|
||||||
<SearchOutlined />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return () => {
|
||||||
<>
|
const { placeholder, value, prefixCls, disabled } = props;
|
||||||
<Input
|
const icon =
|
||||||
placeholder={placeholder}
|
value && value.length > 0 ? (
|
||||||
class={prefixCls}
|
<a class={`${prefixCls}-action`} onClick={handleClearFn}>
|
||||||
value={value}
|
<CloseCircleFilled />
|
||||||
onChange={this.handleChange}
|
</a>
|
||||||
disabled={disabled}
|
) : (
|
||||||
/>
|
<span class={`${prefixCls}-action`}>
|
||||||
{icon}
|
<SearchOutlined />
|
||||||
</>
|
</span>
|
||||||
);
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder}
|
||||||
|
class={prefixCls}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
{icon}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,14 @@
|
||||||
@import './index.less';
|
@import './index.less';
|
||||||
|
|
||||||
@table-prefix-cls: ~'@{ant-prefix}-table';
|
@table-prefix-cls: ~'@{ant-prefix}-table';
|
||||||
|
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||||
|
|
||||||
.@{transfer-prefix-cls}-customize-list {
|
.@{transfer-prefix-cls}-customize-list {
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.@{transfer-prefix-cls}-operation {
|
|
||||||
flex: none;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.@{transfer-prefix-cls}-list {
|
.@{transfer-prefix-cls}-list {
|
||||||
flex: auto;
|
flex: 1 1 50%;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: @transfer-list-height;
|
min-height: @transfer-list-height;
|
||||||
|
|
||||||
&-body {
|
|
||||||
&-with-search {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search box in customize mode do not need fix top
|
|
||||||
&-search-wrapper {
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-customize-wrapper {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================== Hook Components ===================
|
// =================== Hook Components ===================
|
||||||
|
|
@ -59,4 +37,9 @@
|
||||||
margin: 16px 0 4px;
|
margin: 16px 0 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.@{input-prefix-cls} {
|
||||||
|
&[disabled] {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
@import '../../style/themes/index';
|
@import '../../style/themes/index';
|
||||||
@import '../../style/mixins/index';
|
@import '../../style/mixins/index';
|
||||||
@import '../../checkbox/style/mixin';
|
@import '../../checkbox/style/mixin';
|
||||||
@import './customize.less';
|
@import './customize';
|
||||||
|
|
||||||
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
|
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
|
||||||
|
|
||||||
@transfer-header-vertical-padding: (
|
@transfer-header-vertical-padding: ceil(
|
||||||
(@transfer-header-height - 1px - (@font-size-base * @line-height-base)) / 2
|
((@transfer-header-height - 1px - @font-size-base * @line-height-base) / 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
.@{transfer-prefix-cls} {
|
.@{transfer-prefix-cls} {
|
||||||
.reset-component();
|
.reset-component();
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
&-disabled {
|
&-disabled {
|
||||||
.@{transfer-prefix-cls}-list {
|
.@{transfer-prefix-cls}-list {
|
||||||
|
|
@ -21,17 +23,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-list {
|
&-list {
|
||||||
position: relative;
|
display: flex;
|
||||||
display: inline-block;
|
flex-direction: column;
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: @transfer-list-height;
|
height: @transfer-list-height;
|
||||||
padding-top: @transfer-header-height;
|
|
||||||
vertical-align: middle;
|
|
||||||
border: @border-width-base @border-style-base @border-color-base;
|
border: @border-width-base @border-style-base @border-color-base;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
&-with-footer {
|
&-with-pagination {
|
||||||
padding-bottom: 34px;
|
width: 250px;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-search {
|
&-search {
|
||||||
|
|
@ -39,13 +40,14 @@
|
||||||
padding-left: @control-padding-horizontal-sm;
|
padding-left: @control-padding-horizontal-sm;
|
||||||
&-action {
|
&-action {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: @transfer-list-search-icon-top;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
line-height: @input-height-base;
|
line-height: @input-height-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.@{iconfont-css-prefix} {
|
.@{iconfont-css-prefix} {
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
|
@ -60,75 +62,128 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 0;
|
flex: none;
|
||||||
left: 0;
|
align-items: center;
|
||||||
width: 100%;
|
height: @transfer-header-height;
|
||||||
// border-top is on the transfer dom. We should minus 1px for this
|
// border-top is on the transfer dom. We should minus 1px for this
|
||||||
padding: (@transfer-header-vertical-padding - 1px) @control-padding-horizontal
|
padding: (@transfer-header-vertical-padding - 1px) @control-padding-horizontal
|
||||||
@transfer-header-vertical-padding;
|
@transfer-header-vertical-padding;
|
||||||
overflow: hidden;
|
|
||||||
color: @text-color;
|
color: @text-color;
|
||||||
background: @component-background;
|
background: @component-background;
|
||||||
border-bottom: @border-width-base @border-style-base @border-color-split;
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
border-radius: @border-radius-base @border-radius-base 0 0;
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
|
||||||
&-title {
|
> *:not(:last-child) {
|
||||||
position: absolute;
|
margin-right: 4px;
|
||||||
right: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{ant-prefix}-checkbox-wrapper + span {
|
> * {
|
||||||
padding-left: 8px;
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
flex: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dropdown {
|
||||||
|
font-size: 10px;
|
||||||
|
transform: translateY(10%);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-body {
|
&-body {
|
||||||
position: relative;
|
display: flex;
|
||||||
height: 100%;
|
flex: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-base;
|
||||||
|
|
||||||
&-search-wrapper {
|
&-search-wrapper {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 0;
|
flex: none;
|
||||||
left: 0;
|
padding: @padding-sm;
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-body-with-search {
|
|
||||||
padding-top: @input-height-base + 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
height: 100%;
|
flex: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
> .LazyLoad {
|
|
||||||
animation: transferHighlightIn 1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
min-height: 32px;
|
display: flex;
|
||||||
padding: 6px @control-padding-horizontal;
|
align-items: center;
|
||||||
overflow: hidden;
|
min-height: @transfer-item-height;
|
||||||
white-space: nowrap;
|
padding: @transfer-item-padding-vertical @control-padding-horizontal;
|
||||||
text-overflow: ellipsis;
|
line-height: @transfer-item-height - 2 * @transfer-item-padding-vertical;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
> span {
|
|
||||||
padding-right: 0;
|
> *:not(:last-child) {
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
padding-left: 8px;
|
flex: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-remove {
|
||||||
|
.operation-unit();
|
||||||
|
position: relative;
|
||||||
|
color: @border-color-base;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: -@transfer-item-padding-vertical;
|
||||||
|
right: -50%;
|
||||||
|
bottom: -@transfer-item-padding-vertical;
|
||||||
|
left: -50%;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @link-hover-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item:not(&-item-disabled):hover {
|
&-item:not(&-item-disabled) {
|
||||||
background-color: @transfer-item-hover-bg;
|
&:hover {
|
||||||
cursor: pointer;
|
background-color: @transfer-item-hover-bg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{transfer-prefix-cls}-list-content-item-checked:hover {
|
||||||
|
background-color: darken(@item-active-bg, 2%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not change hover style when `oneWay` mode
|
||||||
|
&-show-remove &-item:not(&-item-disabled):hover {
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item-checked {
|
||||||
|
background-color: @item-active-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-disabled {
|
&-item-disabled {
|
||||||
|
|
@ -137,35 +192,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
padding: @padding-xs 0;
|
||||||
|
text-align: right;
|
||||||
|
border-top: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
&-body-not-found {
|
&-body-not-found {
|
||||||
position: absolute;
|
flex: none;
|
||||||
top: 50%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 0;
|
margin: auto 0;
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
// with filter should offset the search box height
|
|
||||||
.@{transfer-prefix-cls}-list-body-with-search & {
|
|
||||||
margin-top: (@input-height-base / 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-footer {
|
&-footer {
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
border-top: @border-width-base @border-style-base @border-color-split;
|
border-top: @border-width-base @border-style-base @border-color-split;
|
||||||
border-radius: 0 0 @border-radius-base @border-radius-base;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-operation {
|
&-operation {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
|
flex: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-self: center;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
overflow: hidden;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
.@{ant-prefix}-btn {
|
.@{ant-prefix}-btn {
|
||||||
|
|
@ -180,13 +231,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-empty-image {
|
||||||
|
max-height: (@transfer-header-height / 2) - 22;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes transferHighlightIn {
|
@import './rtl';
|
||||||
0% {
|
|
||||||
background: @primary-2;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ import '../../empty/style';
|
||||||
import '../../checkbox/style';
|
import '../../checkbox/style';
|
||||||
import '../../button/style';
|
import '../../button/style';
|
||||||
import '../../input/style';
|
import '../../input/style';
|
||||||
|
import '../../menu/style';
|
||||||
|
import '../../dropdown/style';
|
||||||
|
import '../../pagination/style';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
@import '../../style/themes/index';
|
||||||
|
@import '../../style/mixins/index';
|
||||||
|
@import '../../checkbox/style/mixin';
|
||||||
|
|
||||||
|
@transfer-prefix-cls: ~'@{ant-prefix}-transfer';
|
||||||
|
|
||||||
|
.@{transfer-prefix-cls} {
|
||||||
|
&-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
&-search {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
padding-right: @control-padding-horizontal-sm;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
&-action {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
right: auto;
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
> *:not(:last-child) {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
&-title {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
&-item {
|
||||||
|
> *:not(:last-child) {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-footer {
|
||||||
|
.@{transfer-prefix-cls}-rtl & {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Content',
|
|
||||||
props: {
|
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
overlay: PropTypes.any,
|
|
||||||
trigger: PropTypes.any,
|
|
||||||
overlayInnerStyle: PropTypes.any,
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
const { trigger } = this;
|
|
||||||
if (trigger) {
|
|
||||||
trigger.forcePopupAlign();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { overlay, prefixCls, overlayInnerStyle } = this;
|
|
||||||
return (
|
|
||||||
<div class={`${prefixCls}-inner`} role="tooltip" style={overlayInnerStyle}>
|
|
||||||
{typeof overlay === 'function' ? overlay() : overlay}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import Trigger from '../vc-trigger';
|
|
||||||
import { placements } from './placements';
|
|
||||||
import Content from './Content';
|
|
||||||
import { hasProp, getComponent, getOptionProps, getSlot } from '../_util/props-util';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
function noop() {}
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Tooltip',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
trigger: PropTypes.any.def(['hover']),
|
|
||||||
defaultVisible: PropTypes.looseBool,
|
|
||||||
visible: PropTypes.looseBool,
|
|
||||||
placement: PropTypes.string.def('right'),
|
|
||||||
transitionName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
|
||||||
animation: PropTypes.any,
|
|
||||||
afterVisibleChange: PropTypes.func.def(() => {}),
|
|
||||||
overlay: PropTypes.any,
|
|
||||||
overlayStyle: PropTypes.object,
|
|
||||||
overlayClassName: PropTypes.string,
|
|
||||||
prefixCls: PropTypes.string.def('rc-tooltip'),
|
|
||||||
mouseEnterDelay: PropTypes.number.def(0),
|
|
||||||
mouseLeaveDelay: PropTypes.number.def(0.1),
|
|
||||||
getTooltipContainer: PropTypes.func,
|
|
||||||
destroyTooltipOnHide: PropTypes.looseBool.def(false),
|
|
||||||
align: PropTypes.object.def(() => ({})),
|
|
||||||
arrowContent: PropTypes.any.def(null),
|
|
||||||
tipId: PropTypes.string,
|
|
||||||
builtinPlacements: PropTypes.object,
|
|
||||||
overlayInnerStyle: PropTypes.style,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getPopupElement() {
|
|
||||||
const { prefixCls, tipId, overlayInnerStyle } = this.$props;
|
|
||||||
return [
|
|
||||||
<div class={`${prefixCls}-arrow`} key="arrow">
|
|
||||||
{getComponent(this, 'arrowContent')}
|
|
||||||
</div>,
|
|
||||||
<Content
|
|
||||||
key="content"
|
|
||||||
trigger={this.$refs.trigger}
|
|
||||||
prefixCls={prefixCls}
|
|
||||||
id={tipId}
|
|
||||||
overlay={getComponent(this, 'overlay')}
|
|
||||||
overlayInnerStyle={overlayInnerStyle}
|
|
||||||
/>,
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
getPopupDomNode() {
|
|
||||||
return this.$refs.trigger.getPopupDomNode();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render(h) {
|
|
||||||
const {
|
|
||||||
overlayClassName,
|
|
||||||
trigger,
|
|
||||||
mouseEnterDelay,
|
|
||||||
mouseLeaveDelay,
|
|
||||||
overlayStyle,
|
|
||||||
prefixCls,
|
|
||||||
afterVisibleChange,
|
|
||||||
transitionName,
|
|
||||||
animation,
|
|
||||||
placement,
|
|
||||||
align,
|
|
||||||
destroyTooltipOnHide,
|
|
||||||
defaultVisible,
|
|
||||||
getTooltipContainer,
|
|
||||||
...restProps
|
|
||||||
} = getOptionProps(this);
|
|
||||||
const extraProps = { ...restProps };
|
|
||||||
if (hasProp(this, 'visible')) {
|
|
||||||
extraProps.popupVisible = this.$props.visible;
|
|
||||||
}
|
|
||||||
const { $attrs } = this;
|
|
||||||
const triggerProps = {
|
|
||||||
popupClassName: overlayClassName,
|
|
||||||
prefixCls,
|
|
||||||
action: trigger,
|
|
||||||
builtinPlacements: placements,
|
|
||||||
popupPlacement: placement,
|
|
||||||
popupAlign: align,
|
|
||||||
getPopupContainer: getTooltipContainer,
|
|
||||||
afterPopupVisibleChange: afterVisibleChange,
|
|
||||||
popupTransitionName: transitionName,
|
|
||||||
popupAnimation: animation,
|
|
||||||
defaultPopupVisible: defaultVisible,
|
|
||||||
destroyPopupOnHide: destroyTooltipOnHide,
|
|
||||||
mouseLeaveDelay,
|
|
||||||
popupStyle: overlayStyle,
|
|
||||||
mouseEnterDelay,
|
|
||||||
...extraProps,
|
|
||||||
...$attrs,
|
|
||||||
onPopupVisibleChange: $attrs.onVisibleChange || noop,
|
|
||||||
onPopupAlign: $attrs.onPopupAlign || noop,
|
|
||||||
ref: 'trigger',
|
|
||||||
popup: this.getPopupElement(),
|
|
||||||
};
|
|
||||||
return <Trigger {...triggerProps}>{getSlot(this)[0]}</Trigger>;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
// based on rc-tooltip 3.7.3
|
|
||||||
import Tooltip from './Tooltip';
|
|
||||||
|
|
||||||
export default Tooltip;
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// base rc-tooltip 5.1.1
|
||||||
|
import Tooltip from './src/Tooltip';
|
||||||
|
|
||||||
|
export default Tooltip;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { ExtractPropTypes } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import PropTypes from '../../_util/vue-types';
|
||||||
|
|
||||||
|
const tooltipContentProps = {
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
overlay: PropTypes.any,
|
||||||
|
id: PropTypes.string,
|
||||||
|
overlayInnerStyle: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TooltipContentProps = Partial<ExtractPropTypes<typeof tooltipContentProps>>;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Content',
|
||||||
|
props: tooltipContentProps,
|
||||||
|
setup(props: TooltipContentProps) {
|
||||||
|
return () => (
|
||||||
|
<div
|
||||||
|
class={`${props.prefixCls}-inner`}
|
||||||
|
id={props.id}
|
||||||
|
role="tooltip"
|
||||||
|
style={props.overlayInnerStyle}
|
||||||
|
>
|
||||||
|
{typeof props.overlay === 'function' ? props.overlay() : props.overlay}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import PropTypes from '../../_util/vue-types';
|
||||||
|
import Trigger from '../../vc-trigger';
|
||||||
|
import { placements } from './placements';
|
||||||
|
import Content from './Content';
|
||||||
|
import { getPropsSlot } from '../../_util/props-util';
|
||||||
|
import { defineComponent, ref, watchEffect } from 'vue';
|
||||||
|
function noop() {}
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Tooltip',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
trigger: PropTypes.any.def(['hover']),
|
||||||
|
defaultVisible: PropTypes.looseBool,
|
||||||
|
visible: PropTypes.looseBool,
|
||||||
|
placement: PropTypes.string.def('right'),
|
||||||
|
transitionName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
animation: PropTypes.any,
|
||||||
|
afterVisibleChange: PropTypes.func.def(() => {}),
|
||||||
|
overlay: PropTypes.any,
|
||||||
|
overlayStyle: PropTypes.object,
|
||||||
|
overlayClassName: PropTypes.string,
|
||||||
|
prefixCls: PropTypes.string.def('rc-tooltip'),
|
||||||
|
mouseEnterDelay: PropTypes.number.def(0.1),
|
||||||
|
mouseLeaveDelay: PropTypes.number.def(0.1),
|
||||||
|
getTooltipContainer: PropTypes.func,
|
||||||
|
destroyTooltipOnHide: PropTypes.looseBool.def(false),
|
||||||
|
align: PropTypes.object.def(() => ({})),
|
||||||
|
arrowContent: PropTypes.any.def(null),
|
||||||
|
tipId: PropTypes.string,
|
||||||
|
builtinPlacements: PropTypes.object,
|
||||||
|
overlayInnerStyle: PropTypes.style,
|
||||||
|
popupVisible: PropTypes.looseBool,
|
||||||
|
},
|
||||||
|
slots: ['arrowContent', 'overlay'],
|
||||||
|
setup(props, { slots, attrs, expose }) {
|
||||||
|
const triggerDOM = ref();
|
||||||
|
|
||||||
|
const getPopupElement = () => {
|
||||||
|
const { prefixCls, tipId, overlayInnerStyle } = props;
|
||||||
|
return [
|
||||||
|
<div class={`${prefixCls}-arrow`} key="arrow">
|
||||||
|
{getPropsSlot(slots, props, 'arrowContent')}
|
||||||
|
</div>,
|
||||||
|
<Content
|
||||||
|
key="content"
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
id={tipId}
|
||||||
|
overlay={getPropsSlot(slots, props, 'overlay')}
|
||||||
|
overlayInnerStyle={overlayInnerStyle}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPopupDomNode = () => {
|
||||||
|
return triggerDOM.value.getPopupDomNode();
|
||||||
|
};
|
||||||
|
|
||||||
|
expose({ getPopupDomNode, triggerDOM });
|
||||||
|
|
||||||
|
const destroyTooltip = ref(false);
|
||||||
|
const autoDestroy = ref(false);
|
||||||
|
watchEffect(() => {
|
||||||
|
const { destroyTooltipOnHide } = props;
|
||||||
|
if (typeof destroyTooltipOnHide === 'boolean') {
|
||||||
|
destroyTooltip.value = destroyTooltipOnHide;
|
||||||
|
} else if (destroyTooltipOnHide && typeof destroyTooltipOnHide === 'object') {
|
||||||
|
const { keepParent } = destroyTooltipOnHide;
|
||||||
|
destroyTooltip.value = keepParent === true;
|
||||||
|
autoDestroy.value = keepParent === false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
overlayClassName,
|
||||||
|
trigger,
|
||||||
|
mouseEnterDelay,
|
||||||
|
mouseLeaveDelay,
|
||||||
|
overlayStyle,
|
||||||
|
prefixCls,
|
||||||
|
afterVisibleChange,
|
||||||
|
transitionName,
|
||||||
|
animation,
|
||||||
|
placement,
|
||||||
|
align,
|
||||||
|
destroyTooltipOnHide,
|
||||||
|
defaultVisible,
|
||||||
|
getTooltipContainer,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
const extraProps = { ...restProps };
|
||||||
|
if (props.visible !== undefined) {
|
||||||
|
extraProps.popupVisible = props.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerProps = {
|
||||||
|
popupClassName: overlayClassName,
|
||||||
|
prefixCls,
|
||||||
|
action: trigger,
|
||||||
|
builtinPlacements: placements,
|
||||||
|
popupPlacement: placement,
|
||||||
|
popupAlign: align,
|
||||||
|
getPopupContainer: getTooltipContainer,
|
||||||
|
afterPopupVisibleChange: afterVisibleChange,
|
||||||
|
popupTransitionName: transitionName,
|
||||||
|
popupAnimation: animation,
|
||||||
|
defaultPopupVisible: defaultVisible,
|
||||||
|
destroyPopupOnHide: destroyTooltip.value,
|
||||||
|
autoDestroy: autoDestroy.value,
|
||||||
|
mouseLeaveDelay,
|
||||||
|
popupStyle: overlayStyle,
|
||||||
|
mouseEnterDelay,
|
||||||
|
...extraProps,
|
||||||
|
...attrs,
|
||||||
|
onPopupVisibleChange: (attrs.onVisibleChange as any) || noop,
|
||||||
|
onPopupAlign: attrs.onPopupAlign || noop,
|
||||||
|
ref: triggerDOM,
|
||||||
|
popup: getPopupElement(),
|
||||||
|
};
|
||||||
|
return <Trigger {...triggerProps}>{slots.default?.()}</Trigger>;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue