ant-design-vue/components/vc-select/Selector/MultipleSelector.tsx

290 lines
8.6 KiB
Vue
Raw Normal View History

2020-10-07 14:49:01 +00:00
import TransBtn from '../TransBtn';
2021-08-10 06:36:28 +00:00
import type { InnerSelectorProps } from './interface';
2020-10-07 14:49:01 +00:00
import Input from './Input';
2021-12-16 09:20:18 +00:00
import type { Ref, PropType } from 'vue';
2021-06-26 01:35:40 +00:00
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
2020-10-07 14:49:01 +00:00
import classNames from '../../_util/classNames';
import pickAttrs from '../../_util/pickAttrs';
import PropTypes from '../../_util/vue-types';
2021-06-26 06:27:26 +00:00
import type { VueNode } from '../../_util/type';
2021-06-22 02:47:33 +00:00
import Overflow from '../../vc-overflow';
import type { DisplayValueType, RenderNode, CustomTagProps, RawValueType } from '../BaseSelect';
import type { BaseOptionType } from '../Select';
import useInjectLegacySelectContext from '../../vc-tree-select/LegacyContext';
2020-10-07 14:49:01 +00:00
2021-08-10 06:36:28 +00:00
type SelectorProps = InnerSelectorProps & {
2020-10-07 14:49:01 +00:00
// Icon
removeIcon?: RenderNode;
// Tags
2021-06-22 02:47:33 +00:00
maxTagCount?: number | 'responsive';
2020-10-07 14:49:01 +00:00
maxTagTextLength?: number;
maxTagPlaceholder?: VueNode | ((omittedValues: DisplayValueType[]) => VueNode);
2020-10-07 14:49:01 +00:00
tokenSeparators?: string[];
2021-12-16 09:20:18 +00:00
tagRender?: (props: CustomTagProps) => VueNode;
2021-08-10 06:36:28 +00:00
onToggleOpen: any;
2020-10-07 14:49:01 +00:00
// Motion
choiceTransitionName?: string;
// Event
onRemove: (value: DisplayValueType) => void;
2021-08-10 06:36:28 +00:00
};
2020-10-07 14:49:01 +00:00
const props = {
id: String,
prefixCls: String,
2020-10-07 14:49:01 +00:00
values: PropTypes.array,
open: { type: Boolean, default: undefined },
searchValue: String,
2020-10-07 14:49:01 +00:00
inputRef: PropTypes.any,
placeholder: PropTypes.any,
disabled: { type: Boolean, default: undefined },
mode: String,
showSearch: { type: Boolean, default: undefined },
autofocus: { type: Boolean, default: undefined },
autocomplete: String,
activeDescendantId: String,
refactor: Anchor、Alert、Avatar、Badge、BackTop、Col、Form、Layout、Menu、Space、Spin、Switch、Row、Result、Rate (#4171) * chore: remove resize-observer-polyfill * refactor: align * refactor(v3/avatar): refactor using composition api (#4052) * refactor(avatar): refactor using composition api * refactor: update props define * fix: avatar src scale not update * refactor: resizeObserver * refactor: divider * refactor: localeProvider * refactor(v3/back-top): use composition api (#4060) * refactor: backtop * refactor: empty * refactor: transButton * feat(v3/avatar): add avatar group (#4062) * feat(avatar): add avatar group * refactor: update * refactor: update Co-authored-by: tangjinzhou <415800467@qq.com> * refactor: avatar * refactor: avatar * style: rename useProvide * refactor: menu (#4110) * fix: menu * refactor: menu * refactor: remove rc-menu * fix: menu rtl error * style: lint * refactor(Anchor): use composition api (#4054) * refactor: anchor * refactor: anchor * refactor: anchor * feat: update * fix: icon class lose * refactor(v3/badge): use composition api (#4076) * refactor: badge * fix: badge inheritAttrs * refactor: grid * refactor: layout * fix: menu not close * refactor: space * refactor: result * refactor: affix * refactor: comment * refactor: form * feat: spin add rtl * feat: export spin type * refactor: pageHeader * refactor: page-header * refactor: skeleton * refactor: typography * refactor(v3/rate): use composition api * fix: add useRef hook * refactor: form * fix: menu not update * refactor: form * refactor: form * fix: slide animate not work * fix: menu mode error * fix: menu icon * refactor: rate * perf: remove rate * feat: add vc-overflow * refactor: menu * fix: remove flex check (#4165) * fix: dist locale file lose #3684 * release 2.2.0-beta.1 * dcos: update changelog * chore: update type * docs: update changelog Co-authored-by: John <John60676@qq.com> Co-authored-by: 言肆 <18x@loacg.com> Co-authored-by: zkwolf <chenhao5866@gmail.com>
2021-06-07 09:35:03 +00:00
tabindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
2020-10-07 14:49:01 +00:00
2021-12-16 09:20:18 +00:00
removeIcon: PropTypes.any,
choiceTransitionName: String,
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
maxTagTextLength: Number,
2021-06-23 15:08:16 +00:00
maxTagPlaceholder: PropTypes.any.def(
() => (omittedValues: DisplayValueType[]) => `+ ${omittedValues.length} ...`,
2020-10-07 14:49:01 +00:00
),
tagRender: Function,
2020-10-07 14:49:01 +00:00
2021-08-10 06:36:28 +00:00
onToggleOpen: { type: Function as PropType<(open?: boolean) => void> },
onRemove: Function,
onInputChange: Function,
onInputPaste: Function,
onInputKeyDown: Function,
onInputMouseDown: Function,
onInputCompositionStart: Function,
onInputCompositionEnd: Function,
2020-10-07 14:49:01 +00:00
};
2021-06-22 02:47:33 +00:00
const onPreventMouseDown = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
2020-10-07 14:49:01 +00:00
const SelectSelector = defineComponent<SelectorProps>({
2020-10-26 10:31:50 +00:00
name: 'MultipleSelectSelector',
2021-08-10 06:36:28 +00:00
inheritAttrs: false,
props: props as any,
2020-10-07 14:49:01 +00:00
setup(props) {
const measureRef = ref();
const inputWidth = ref(0);
2021-06-22 02:47:33 +00:00
const focused = ref(false);
const legacyTreeSelectContext = useInjectLegacySelectContext();
2021-06-22 02:47:33 +00:00
const selectionPrefixCls = computed(() => `${props.prefixCls}-selection`);
2020-10-07 14:49:01 +00:00
// ===================== Search ======================
const inputValue = computed(() =>
props.open || props.mode === 'tags' ? props.searchValue : '',
);
const inputEditable: Ref<boolean> = computed(
2021-06-22 02:47:33 +00:00
() =>
props.mode === 'tags' || ((props.showSearch && (props.open || focused.value)) as boolean),
2020-10-07 14:49:01 +00:00
);
// We measure width and set to the input immediately
2020-10-08 14:51:09 +00:00
onMounted(() => {
watch(
inputValue,
() => {
inputWidth.value = measureRef.value.scrollWidth;
},
2021-06-22 02:47:33 +00:00
{ flush: 'post', immediate: true },
2020-10-08 14:51:09 +00:00
);
});
2021-06-22 02:47:33 +00:00
// ===================== Render ======================
// >>> Render Selector Node. Includes Item & Rest
function defaultRenderSelector(
title: VueNode,
2021-06-22 02:47:33 +00:00
content: VueNode,
itemDisabled: boolean,
closable?: boolean,
onClose?: (e: MouseEvent) => void,
) {
return (
<span
class={classNames(`${selectionPrefixCls.value}-item`, {
[`${selectionPrefixCls.value}-item-disabled`]: itemDisabled,
})}
2021-11-03 08:22:02 +00:00
title={
typeof title === 'string' || typeof title === 'number' ? title.toString() : undefined
2021-11-03 08:22:02 +00:00
}
2021-06-22 02:47:33 +00:00
>
<span class={`${selectionPrefixCls.value}-item-content`}>{content}</span>
{closable && (
<TransBtn
class={`${selectionPrefixCls.value}-item-remove`}
onMousedown={onPreventMouseDown}
onClick={onClose}
customizeIcon={props.removeIcon}
>
×
</TransBtn>
)}
</span>
);
}
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
function customizeRenderSelector(
value: RawValueType,
2021-06-22 02:47:33 +00:00
content: VueNode,
itemDisabled: boolean,
closable: boolean,
onClose: (e: MouseEvent) => void,
option: BaseOptionType,
2021-06-22 02:47:33 +00:00
) {
const onMouseDown = (e: MouseEvent) => {
onPreventMouseDown(e);
props.onToggleOpen(!open);
};
let originData = option;
// For TreeSelect
if (legacyTreeSelectContext.keyEntities) {
originData = legacyTreeSelectContext.keyEntities[value]?.node || {};
}
2021-06-22 02:47:33 +00:00
return (
<span key={value} onMousedown={onMouseDown}>
2021-06-22 02:47:33 +00:00
{props.tagRender({
label: content,
value,
disabled: itemDisabled,
closable,
onClose,
option: originData,
2021-06-22 02:47:33 +00:00
})}
</span>
);
}
2020-10-07 14:49:01 +00:00
function renderItem(valueItem: DisplayValueType) {
const { disabled: itemDisabled, label, value, option } = valueItem;
2021-06-22 02:47:33 +00:00
const closable = !props.disabled && !itemDisabled;
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
let displayLabel = label;
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
if (typeof props.maxTagTextLength === 'number') {
if (typeof label === 'string' || typeof label === 'number') {
const strLabel = String(displayLabel);
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
if (strLabel.length > props.maxTagTextLength) {
displayLabel = `${strLabel.slice(0, props.maxTagTextLength)}...`;
}
}
2020-10-07 14:49:01 +00:00
}
2021-06-22 02:47:33 +00:00
const onClose = (event?: MouseEvent) => {
if (event) event.stopPropagation();
props.onRemove?.(valueItem);
2021-06-22 02:47:33 +00:00
};
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
return typeof props.tagRender === 'function'
? customizeRenderSelector(value, displayLabel, itemDisabled, closable, onClose, option)
: defaultRenderSelector(label, displayLabel, itemDisabled, closable, onClose);
2021-06-22 02:47:33 +00:00
}
2020-10-07 14:49:01 +00:00
function renderRest(omittedValues: DisplayValueType[]) {
const { maxTagPlaceholder = omittedValues => `+ ${omittedValues.length} ...` } = props;
2021-06-22 02:47:33 +00:00
const content =
typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues)
: maxTagPlaceholder;
return defaultRenderSelector(content, content, false);
2021-06-22 02:47:33 +00:00
}
2020-10-07 14:49:01 +00:00
return () => {
const {
id,
prefixCls,
values,
open,
inputRef,
placeholder,
disabled,
autofocus,
autocomplete,
activeDescendantId,
2020-10-07 14:49:01 +00:00
tabindex,
onInputChange,
onInputPaste,
onInputKeyDown,
onInputMouseDown,
onInputCompositionStart,
onInputCompositionEnd,
} = props;
2021-06-22 02:47:33 +00:00
// >>> Input Node
const inputNode = (
<div
class={`${selectionPrefixCls.value}-search`}
style={{ width: inputWidth.value + 'px' }}
key="input"
>
<Input
inputRef={inputRef}
open={open}
prefixCls={prefixCls}
id={id}
inputElement={null}
disabled={disabled}
autofocus={autofocus}
autocomplete={autocomplete}
editable={inputEditable.value}
activeDescendantId={activeDescendantId}
2021-06-22 02:47:33 +00:00
value={inputValue.value}
onKeydown={onInputKeyDown}
onMousedown={onInputMouseDown}
onChange={onInputChange}
onPaste={onInputPaste}
onCompositionstart={onInputCompositionStart}
onCompositionend={onInputCompositionEnd}
tabindex={tabindex}
attrs={pickAttrs(props, true)}
onFocus={() => (focused.value = true)}
onBlur={() => (focused.value = false)}
/>
{/* Measure Node */}
<span ref={measureRef} class={`${selectionPrefixCls.value}-search-mirror`} aria-hidden>
{inputValue.value}&nbsp;
2020-10-07 14:49:01 +00:00
</span>
2021-06-22 02:47:33 +00:00
</div>
);
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
// >>> Selections
const selectionNode = (
<Overflow
prefixCls={`${selectionPrefixCls.value}-overflow`}
data={values}
renderItem={renderItem}
renderRest={renderRest}
suffix={inputNode}
itemKey="key"
maxCount={props.maxTagCount}
key="overflow"
/>
);
return (
<>
{selectionNode}
2020-10-07 14:49:01 +00:00
{!values.length && !inputValue.value && (
2021-06-22 02:47:33 +00:00
<span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span>
2020-10-07 14:49:01 +00:00
)}
</>
);
};
},
});
2021-08-10 06:36:28 +00:00
2020-10-07 14:49:01 +00:00
export default SelectSelector;