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

284 lines
8.2 KiB
Vue
Raw Normal View History

2020-10-07 14:49:01 +00:00
import TransBtn from '../TransBtn';
2021-06-26 01:35:40 +00:00
import type {
2021-06-22 02:47:33 +00:00
LabelValueType,
RawValueType,
CustomTagProps,
DefaultValueType,
DisplayLabelValueType,
} from '../interface/generator';
2021-06-26 01:35:40 +00:00
import type { RenderNode } from '../interface';
import type { InnerSelectorProps } from '.';
2020-10-07 14:49:01 +00:00
import Input from './Input';
2021-06-26 01:35:40 +00:00
import type { VNodeChild, Ref } from 'vue';
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 01:35:40 +00:00
import type { VueNode } from 'ant-design-vue/es/_util/type';
2021-06-22 02:47:33 +00:00
import Overflow from '../../vc-overflow';
2020-10-07 14:49:01 +00:00
interface SelectorProps extends InnerSelectorProps {
// 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?: VNodeChild;
tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => VNodeChild;
2021-06-22 02:47:33 +00:00
onToggleOpen: (open?: boolean) => void;
2020-10-07 14:49:01 +00:00
// Motion
choiceTransitionName?: string;
// Event
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
}
const props = {
id: PropTypes.string,
prefixCls: PropTypes.string,
values: PropTypes.array,
open: PropTypes.looseBool,
2020-10-07 14:49:01 +00:00
searchValue: PropTypes.string,
inputRef: PropTypes.any,
placeholder: PropTypes.any,
disabled: PropTypes.looseBool,
2020-10-07 14:49:01 +00:00
mode: PropTypes.string,
showSearch: PropTypes.looseBool,
autofocus: PropTypes.looseBool,
2020-10-07 14:49:01 +00:00
autocomplete: PropTypes.string,
accessibilityIndex: PropTypes.number,
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
removeIcon: PropTypes.VNodeChild,
2020-10-07 14:49:01 +00:00
choiceTransitionName: PropTypes.string,
2021-06-22 02:47:33 +00:00
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
2020-10-07 14:49:01 +00:00
maxTagTextLength: PropTypes.number,
2021-06-23 15:08:16 +00:00
maxTagPlaceholder: PropTypes.any.def(
() => (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`,
2020-10-07 14:49:01 +00:00
),
tagRender: PropTypes.func,
onSelect: PropTypes.func,
onInputChange: PropTypes.func,
onInputPaste: PropTypes.func,
onInputKeyDown: PropTypes.func,
onInputMouseDown: PropTypes.func,
onInputCompositionStart: PropTypes.func,
onInputCompositionEnd: PropTypes.func,
};
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',
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);
2020-10-07 14:49:01 +00:00
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(
content: VueNode,
itemDisabled: boolean,
closable?: boolean,
onClose?: (e: MouseEvent) => void,
) {
return (
<span
class={classNames(`${selectionPrefixCls.value}-item`, {
[`${selectionPrefixCls.value}-item-disabled`]: itemDisabled,
})}
>
<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: DefaultValueType,
content: VueNode,
itemDisabled: boolean,
closable: boolean,
onClose: (e: MouseEvent) => void,
) {
const onMouseDown = (e: MouseEvent) => {
onPreventMouseDown(e);
props.onToggleOpen(!open);
};
return (
<span onMousedown={onMouseDown}>
{props.tagRender({
label: content,
value,
disabled: itemDisabled,
closable,
onClose,
})}
</span>
);
}
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
function renderItem({ disabled: itemDisabled, label, value }: DisplayLabelValueType) {
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.onSelect(value, { selected: false });
};
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)
: defaultRenderSelector(displayLabel, itemDisabled, closable, onClose);
}
2020-10-07 14:49:01 +00:00
2021-06-22 02:47:33 +00:00
function renderRest(omittedValues: DisplayLabelValueType[]) {
const {
maxTagPlaceholder = (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`,
} = props;
const content =
typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues)
: maxTagPlaceholder;
return defaultRenderSelector(content, false);
}
2020-10-07 14:49:01 +00:00
return () => {
const {
id,
prefixCls,
values,
open,
inputRef,
placeholder,
disabled,
autofocus,
autocomplete,
accessibilityIndex,
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}
accessibilityIndex={accessibilityIndex}
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
)}
</>
);
};
},
});
SelectSelector.inheritAttrs = false;
SelectSelector.props = props;
export default SelectSelector;