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

284 lines
8.4 KiB
TypeScript
Raw Normal View History

2020-10-07 14:49:01 +00:00
import TransBtn from '../TransBtn';
import { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator';
import { RenderNode } from '../interface';
import { InnerSelectorProps } from '.';
import Input from './Input';
import {
computed,
defineComponent,
onMounted,
ref,
TransitionGroup,
VNodeChild,
watch,
watchEffect,
Ref,
} from 'vue';
import classNames from '../../_util/classNames';
import pickAttrs from '../../_util/pickAttrs';
import PropTypes from '../../_util/vue-types';
2020-10-09 09:37:04 +00:00
import getTransitionGroupProps from '../../_util/getTransitionGroupProps';
2020-10-07 14:49:01 +00:00
const REST_TAG_KEY = '__RC_SELECT_MAX_REST_COUNT__';
interface SelectorProps extends InnerSelectorProps {
// Icon
removeIcon?: RenderNode;
// Tags
maxTagCount?: number;
maxTagTextLength?: number;
maxTagPlaceholder?: VNodeChild;
tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => VNodeChild;
// 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,
tabindex: PropTypes.number,
removeIcon: PropTypes.VNodeChild,
2020-10-07 14:49:01 +00:00
choiceTransitionName: PropTypes.string,
maxTagCount: PropTypes.number,
maxTagTextLength: PropTypes.number,
maxTagPlaceholder: PropTypes.any.def(
(omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`,
),
tagRender: PropTypes.func,
onSelect: PropTypes.func,
onInputChange: PropTypes.func,
onInputPaste: PropTypes.func,
onInputKeyDown: PropTypes.func,
onInputMouseDown: PropTypes.func,
onInputCompositionStart: PropTypes.func,
onInputCompositionEnd: PropTypes.func,
};
const SelectSelector = defineComponent<SelectorProps>({
name: 'SelectSelector',
setup(props) {
let motionAppear = false; // not need use ref, because not need trigger watchEffect
const measureRef = ref();
const inputWidth = ref(0);
// ===================== Motion ======================
onMounted(() => {
motionAppear = true;
});
// ===================== Search ======================
const inputValue = computed(() =>
props.open || props.mode === 'tags' ? props.searchValue : '',
);
const inputEditable: Ref<boolean> = computed(
() => props.mode === 'tags' || ((props.open && props.showSearch) as boolean),
);
// 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;
},
{ flush: 'post' },
);
});
2020-10-07 14:49:01 +00:00
const selectionNode = ref();
watchEffect(() => {
const {
values,
prefixCls,
removeIcon,
choiceTransitionName,
maxTagCount,
maxTagTextLength,
maxTagPlaceholder = (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`,
tagRender,
onSelect,
} = props;
// ==================== Selection ====================
let displayValues: LabelValueType[] = values;
// Cut by `maxTagCount`
let restCount: number;
if (typeof maxTagCount === 'number') {
restCount = values.length - maxTagCount;
displayValues = values.slice(0, maxTagCount);
}
// Update by `maxTagTextLength`
if (typeof maxTagTextLength === 'number') {
displayValues = displayValues.map(({ label, ...rest }) => {
let displayLabel = label;
if (typeof label === 'string' || typeof label === 'number') {
const strLabel = String(displayLabel);
if (strLabel.length > maxTagTextLength) {
displayLabel = `${strLabel.slice(0, maxTagTextLength)}...`;
}
}
return {
...rest,
label: displayLabel,
};
});
}
// Fill rest
if (restCount > 0) {
displayValues.push({
key: REST_TAG_KEY,
label:
typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(values.slice(maxTagCount))
: maxTagPlaceholder,
});
}
2020-10-20 09:41:11 +00:00
const transitionProps = getTransitionGroupProps(choiceTransitionName, {
appear: motionAppear,
})
2020-10-07 14:49:01 +00:00
selectionNode.value = (
<TransitionGroup {...transitionProps}>
2020-10-09 09:37:04 +00:00
{...displayValues.map(
2020-10-07 14:49:01 +00:00
({ key, label, value, disabled: itemDisabled, class: className, style }) => {
const mergedKey = key || value;
const closable = key !== REST_TAG_KEY && !itemDisabled;
const onMousedown = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
const onClose = (event?: MouseEvent) => {
if (event) event.stopPropagation();
onSelect(value as RawValueType, { selected: false });
};
return typeof tagRender === 'function' ? (
<span
key={mergedKey as string}
onMousedown={onMousedown}
2020-10-09 09:37:04 +00:00
class={classNames(className)}
2020-10-07 14:49:01 +00:00
style={style}
>
{tagRender({
label,
value,
disabled: itemDisabled,
closable,
onClose,
} as CustomTagProps)}
</span>
) : (
<span
key={mergedKey as string}
class={classNames(className, `${prefixCls}-selection-item`, {
[`${prefixCls}-selection-item-disabled`]: itemDisabled,
})}
style={style}
>
<span class={`${prefixCls}-selection-item-content`}>{label}</span>
{closable && (
<TransBtn
class={`${prefixCls}-selection-item-remove`}
onMousedown={onMousedown}
onClick={onClose}
customizeIcon={removeIcon}
>
×
</TransBtn>
)}
</span>
);
},
)}
</TransitionGroup>
);
});
return () => {
const {
id,
prefixCls,
values,
open,
inputRef,
placeholder,
disabled,
autofocus,
autocomplete,
accessibilityIndex,
tabindex,
onInputChange,
onInputPaste,
onInputKeyDown,
onInputMouseDown,
onInputCompositionStart,
onInputCompositionEnd,
} = props;
return (
<>
{selectionNode.value}
<span class={`${prefixCls}-selection-search`} style={{ width: inputWidth.value + 'px' }}>
<Input
inputRef={inputRef}
open={open}
prefixCls={prefixCls}
id={id}
inputElement={null}
disabled={disabled}
autofocus={autofocus}
autocomplete={autocomplete}
editable={inputEditable.value as boolean}
accessibilityIndex={accessibilityIndex}
value={inputValue.value}
onKeydown={onInputKeyDown}
onMousedown={onInputMouseDown}
onChange={onInputChange}
onPaste={onInputPaste}
onCompositionstart={onInputCompositionStart}
onCompositionend={onInputCompositionEnd}
tabindex={tabindex}
attrs={pickAttrs(props, true)}
/>
{/* Measure Node */}
<span ref={measureRef} class={`${prefixCls}-selection-search-mirror`} aria-hidden>
{inputValue.value}&nbsp;
</span>
</span>
{!values.length && !inputValue.value && (
<span class={`${prefixCls}-selection-placeholder`}>{placeholder}</span>
)}
</>
);
};
},
});
SelectSelector.inheritAttrs = false;
SelectSelector.props = props;
export default SelectSelector;