feat: add select responsive

refactor-list
tangjinzhou 2021-06-22 10:47:33 +08:00
parent 501204c543
commit 656d14fc4e
21 changed files with 371 additions and 293 deletions

View File

@ -20,7 +20,7 @@ export interface LabeledValue {
label: VNodeChild; label: VNodeChild;
} }
export type SizeType = 'small' | 'middle' | 'large' | undefined; export type SizeType = 'small' | 'middle' | 'large' | undefined;
export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[]; export type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[] | undefined;
export interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> { export interface InternalSelectProps<VT> extends Omit<RcSelectProps<VT>, 'mode'> {
suffixIcon?: VNodeChild; suffixIcon?: VNodeChild;

View File

@ -37,6 +37,10 @@
background: @input-disabled-bg; background: @input-disabled-bg;
cursor: not-allowed; cursor: not-allowed;
.@{select-prefix-cls}-multiple& {
background: @select-multiple-disabled-background;
}
input { input {
cursor: not-allowed; cursor: not-allowed;
} }
@ -66,7 +70,12 @@
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
&:not(.@{select-prefix-cls}-disabled):hover &-selector { &:not(&-customize-input) &-selector {
.select-selector();
.select-search-input-without-border();
}
&:not(&-disabled):hover &-selector {
.hover(); .hover();
} }
@ -93,6 +102,7 @@
color: @input-placeholder-color; color: @input-placeholder-color;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
pointer-events: none;
// IE11 css hack. `*::-ms-backdrop,` is a must have // IE11 css hack. `*::-ms-backdrop,` is a must have
@media all and (-ms-high-contrast: none) { @media all and (-ms-high-contrast: none) {
@ -189,21 +199,21 @@
outline: none; outline: none;
box-shadow: @box-shadow-base; box-shadow: @box-shadow-base;
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft, &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft { &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
animation-name: antSlideUpIn; animation-name: antSlideUpIn;
} }
&.slide-up-enter.slide-up-enter-active&-placement-topLeft, &.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft { &.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
animation-name: antSlideDownIn; animation-name: antSlideDownIn;
} }
&.slide-up-leave.slide-up-leave-active&-placement-bottomLeft { &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft {
animation-name: antSlideUpOut; animation-name: antSlideUpOut;
} }
&.slide-up-leave.slide-up-leave-active&-placement-topLeft { &.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft {
animation-name: antSlideDownOut; animation-name: antSlideDownOut;
} }

View File

@ -1,5 +1,6 @@
@import './index'; @import './index';
@select-overflow-prefix-cls: ~'@{select-prefix-cls}-selection-overflow';
@select-multiple-item-border-width: 1px; @select-multiple-item-border-width: 1px;
@select-multiple-padding: max( @select-multiple-padding: max(
@ -13,13 +14,25 @@
* since chrome may update to redesign with its align logic. * since chrome may update to redesign with its align logic.
*/ */
// =========================== Overflow ===========================
.@{select-overflow-prefix-cls} {
position: relative;
display: flex;
flex: auto;
flex-wrap: wrap;
max-width: 100%;
&-item {
flex: none;
align-self: center;
max-width: 100%;
}
}
.@{select-prefix-cls} { .@{select-prefix-cls} {
&-multiple { &-multiple {
// ========================= Selector ========================= // ========================= Selector =========================
.@{select-prefix-cls}-selector { .@{select-prefix-cls}-selector {
.select-selector();
.select-search-input-without-border();
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
@ -59,9 +72,7 @@
height: @select-multiple-item-height; height: @select-multiple-item-height;
margin-top: @select-multiple-item-spacing-half; margin-top: @select-multiple-item-spacing-half;
margin-right: @input-padding-vertical-base;
margin-bottom: @select-multiple-item-spacing-half; margin-bottom: @select-multiple-item-spacing-half;
padding: 0 (@padding-xs / 2) 0 @padding-xs;
line-height: @select-multiple-item-height - @select-multiple-item-border-width * 2; line-height: @select-multiple-item-height - @select-multiple-item-border-width * 2;
background: @select-selection-item-bg; background: @select-selection-item-bg;
border: 1px solid @select-selection-item-border-color; border: 1px solid @select-selection-item-border-color;
@ -69,6 +80,9 @@
cursor: default; cursor: default;
transition: font-size 0.3s, line-height 0.3s, height 0.3s; transition: font-size 0.3s, line-height 0.3s, height 0.3s;
user-select: none; user-select: none;
margin-inline-end: @input-padding-vertical-base;
padding-inline-start: @padding-xs;
padding-inline-end: (@padding-xs / 2);
.@{select-prefix-cls}-disabled& { .@{select-prefix-cls}-disabled& {
color: @select-multiple-item-disabled-color; color: @select-multiple-item-disabled-color;
@ -81,7 +95,7 @@
display: inline-block; display: inline-block;
margin-right: (@padding-xs / 2); margin-right: (@padding-xs / 2);
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: pre; // fix whitespace wrapping. custom tags display all whitespace within.
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@ -90,10 +104,9 @@
display: inline-block; display: inline-block;
color: @text-color-secondary; color: @text-color-secondary;
font-weight: bold; font-weight: bold;
font-size: @font-size-sm; font-size: 10px;
line-height: inherit; line-height: inherit;
cursor: pointer; cursor: pointer;
.iconfont-size-under-12px(10px);
> .@{iconfont-css-prefix} { > .@{iconfont-css-prefix} {
vertical-align: -0.2em; vertical-align: -0.2em;
@ -106,14 +119,24 @@
} }
// ========================== Input ========================== // ========================== Input ==========================
.@{select-overflow-prefix-cls}-item + .@{select-overflow-prefix-cls}-item {
.@{select-prefix-cls}-selection-search {
margin-inline-start: 0;
}
}
.@{select-prefix-cls}-selection-search { .@{select-prefix-cls}-selection-search {
position: relative; position: relative;
margin-left: (@select-multiple-padding / 2); max-width: 100%;
margin-top: @select-multiple-item-spacing-half;
margin-bottom: @select-multiple-item-spacing-half;
margin-inline-start: @input-padding-horizontal-base - @input-padding-vertical-base;
&-input, &-input,
&-mirror { &-mirror {
height: @select-multiple-item-height;
font-family: @font-family; font-family: @font-family;
line-height: @line-height-base; line-height: @select-multiple-item-height;
transition: all 0.3s; transition: all 0.3s;
} }
@ -127,14 +150,9 @@
top: 0; top: 0;
left: 0; left: 0;
z-index: 999; z-index: 999;
white-space: nowrap; white-space: pre; // fix whitespace wrapping caused width calculation bug
visibility: hidden; visibility: hidden;
} }
// https://github.com/ant-design/ant-design/issues/22906
&:first-child .@{select-prefix-cls}-selection-search-input {
margin-left: 6.5px;
}
} }
// ======================= Placeholder ======================= // ======================= Placeholder =======================
@ -166,8 +184,8 @@
} }
.@{select-prefix-cls}-selection-search { .@{select-prefix-cls}-selection-search {
height: @select-selection-height + @select-multiple-padding; height: @select-selection-height;
line-height: @select-selection-height + @select-multiple-padding; line-height: @select-selection-height;
&-input, &-input,
&-mirror { &-mirror {
@ -186,10 +204,9 @@
.@{select-prefix-cls}-selection-placeholder { .@{select-prefix-cls}-selection-placeholder {
left: @input-padding-horizontal-sm; left: @input-padding-horizontal-sm;
} }
// https://github.com/ant-design/ant-design/issues/22906 // https://github.com/ant-design/ant-design/issues/29559
.@{select-prefix-cls}-selection-search:first-child .@{select-prefix-cls}-selection-search {
.@{select-prefix-cls}-selection-search-input { margin-inline-start: 3px;
margin-left: 3px;
} }
} }
&.@{select-prefix-cls}-lg { &.@{select-prefix-cls}-lg {

View File

@ -66,9 +66,6 @@
// ======================== Selections ======================== // ======================== Selections ========================
.@{select-prefix-cls}-selection-item { .@{select-prefix-cls}-selection-item {
.@{select-prefix-cls}-rtl& { .@{select-prefix-cls}-rtl& {
margin-right: 0;
margin-left: @input-padding-vertical-base;
padding: 0 @padding-xs 0 (@padding-xs / 2);
text-align: right; text-align: right;
} }
// It's ok not to do this, but 24px makes bottom narrow in view should adjust // It's ok not to do this, but 24px makes bottom narrow in view should adjust
@ -83,11 +80,6 @@
// ========================== Input ========================== // ========================== Input ==========================
.@{select-prefix-cls}-selection-search { .@{select-prefix-cls}-selection-search {
.@{select-prefix-cls}-rtl& {
margin-right: (@select-multiple-padding / 2);
margin-left: @input-padding-vertical-base;
}
&-mirror { &-mirror {
.@{select-prefix-cls}-rtl& { .@{select-prefix-cls}-rtl& {
right: 0; right: 0;
@ -119,7 +111,7 @@
} }
// single // single
@selection-item-padding: ceil((@font-size-base * 1.25)); @selection-item-padding: ceil(@font-size-base * 1.25);
.@{select-prefix-cls}-single { .@{select-prefix-cls}-single {
// ========================= Selector ========================= // ========================= Selector =========================
@ -150,18 +142,6 @@
} }
} }
// ========================== Input ==========================
// We only change the style of non-customize input which is only support by `combobox` mode.
// Not customize
&:not(.@{select-prefix-cls}-customize-input) {
.@{select-prefix-cls}-selector {
.@{select-prefix-cls}-rtl& {
padding: 0 @input-padding-horizontal-base;
}
}
}
// ============================================================ // ============================================================
// == Size == // == Size ==
// ============================================================ // ============================================================
@ -172,7 +152,7 @@
// With arrow should provides `padding-right` to show the arrow // With arrow should provides `padding-right` to show the arrow
&.@{select-prefix-cls}-show-arrow .@{select-prefix-cls}-selection-search { &.@{select-prefix-cls}-show-arrow .@{select-prefix-cls}-selection-search {
.@{select-prefix-cls}-rtl& { .@{select-prefix-cls}-rtl& {
right: 0; right: @input-padding-horizontal-sm - 1px;
} }
} }

View File

@ -1,6 +1,6 @@
@import './index'; @import './index';
@selection-item-padding: ceil((@font-size-base * 1.25)); @selection-item-padding: ceil(@font-size-base * 1.25);
.@{select-prefix-cls}-single { .@{select-prefix-cls}-single {
// ========================= Selector ========================= // ========================= Selector =========================
@ -76,10 +76,7 @@
// Not customize // Not customize
&:not(.@{select-prefix-cls}-customize-input) { &:not(.@{select-prefix-cls}-customize-input) {
.@{select-prefix-cls}-selector { .@{select-prefix-cls}-selector {
.select-selector();
.select-search-input-without-border();
width: 100%; width: 100%;
height: @input-height-base; height: @input-height-base;
padding: 0 @input-padding-horizontal-base; padding: 0 @input-padding-horizontal-base;

View File

@ -90,9 +90,10 @@ export default defineComponent({
</Component> </Component>
); );
if (responsive) { // 使 disabled rerender
itemNode = ( return (
<ResizeObserver <ResizeObserver
disabled={!responsive}
onResize={({ offsetWidth }) => { onResize={({ offsetWidth }) => {
internalRegisterSize(offsetWidth); internalRegisterSize(offsetWidth);
}} }}
@ -100,9 +101,6 @@ export default defineComponent({
{itemNode} {itemNode}
</ResizeObserver> </ResizeObserver>
); );
}
return itemNode;
}; };
}, },
}); });

View File

@ -370,12 +370,12 @@ const Overflow = defineComponent({
)} )}
</Component> </Component>
); );
// 使 disabled rerender
if (isResponsive.value) { return (
overflowNode = <ResizeObserver onResize={onOverflowResize}>{overflowNode}</ResizeObserver>; <ResizeObserver disabled={!isResponsive.value} onResize={onOverflowResize}>
} {overflowNode}
</ResizeObserver>
return overflowNode; );
}; };
}, },
}); });

View File

@ -237,6 +237,9 @@ const OptionList = defineComponent<OptionListProps, { state?: any }>({
// >>> Close // >>> Close
case KeyCode.ESC: { case KeyCode.ESC: {
props.onToggleOpen(false); props.onToggleOpen(false);
if (props.open) {
event.stopPropagation();
}
} }
} }
}, },

View File

@ -46,7 +46,6 @@ import generateSelector, { SelectProps } from './generate';
import { DefaultValueType } from './interface/generator'; import { DefaultValueType } from './interface/generator';
import warningProps from './utils/warningPropsUtil'; import warningProps from './utils/warningPropsUtil';
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
import { getSlot } from '../_util/props-util';
import omit from 'lodash-es/omit'; import omit from 'lodash-es/omit';
const RefSelect = generateSelector<SelectOptionsType>({ const RefSelect = generateSelector<SelectOptionsType>({
@ -69,21 +68,27 @@ export type ExportedSelectProps<
> = SelectProps<SelectOptionsType, ValueType>; > = SelectProps<SelectOptionsType, ValueType>;
const Select = defineComponent<Omit<ExportedSelectProps, 'children'>>({ const Select = defineComponent<Omit<ExportedSelectProps, 'children'>>({
setup() { setup(props, { attrs, expose, slots }) {
const selectRef = ref(null); const selectRef = ref(null);
return { expose({
selectRef,
focus: () => { focus: () => {
selectRef.value?.focus(); selectRef.value?.focus();
}, },
blur: () => { blur: () => {
selectRef.value?.blur(); selectRef.value?.blur();
}, },
});
return () => {
return (
<RefSelect
ref={selectRef}
{...(props as any)}
{...attrs}
children={slots.default?.() || []}
/>
);
}; };
}, },
render() {
return <RefSelect ref="selectRef" {...this.$props} {...this.$attrs} children={getSlot(this)} />;
},
}); });
Select.inheritAttrs = false; Select.inheritAttrs = false;
Select.props = omit(RefSelect.props, ['children']); Select.props = omit(RefSelect.props, ['children']);

View File

@ -11,6 +11,7 @@ import {
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import { RefObject } from '../../_util/createRef'; import { RefObject } from '../../_util/createRef';
import antInput from '../../_util/antInputDirective'; import antInput from '../../_util/antInputDirective';
import classNames from 'ant-design-vue/es/_util/classNames';
interface InputProps { interface InputProps {
prefixCls: string; prefixCls: string;
@ -33,6 +34,8 @@ interface InputProps {
onPaste: EventHandlerNonNull; onPaste: EventHandlerNonNull;
onCompositionstart: EventHandlerNonNull; onCompositionstart: EventHandlerNonNull;
onCompositionend: EventHandlerNonNull; onCompositionend: EventHandlerNonNull;
onFocus: EventHandlerNonNull;
onBlur: EventHandlerNonNull;
} }
const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTimeout: any }>({ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTimeout: any }>({
@ -72,6 +75,8 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
onPaste, onPaste,
onCompositionstart, onCompositionstart,
onCompositionend, onCompositionend,
onFocus,
onBlur,
open, open,
inputRef, inputRef,
attrs, attrs,
@ -82,6 +87,8 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
const { const {
onKeydown: onOriginKeyDown, onKeydown: onOriginKeyDown,
onInput: onOriginInput, onInput: onOriginInput,
onFocus: onOriginFocus,
onBlur: onOriginBlur,
onMousedown: onOriginMouseDown, onMousedown: onOriginMouseDown,
onCompositionstart: onOriginCompositionStart, onCompositionstart: onOriginCompositionStart,
onCompositionend: onOriginCompositionEnd, onCompositionend: onOriginCompositionEnd,
@ -97,7 +104,7 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
tabindex, tabindex,
autocomplete: autocomplete || 'off', autocomplete: autocomplete || 'off',
autofocus, autofocus,
class: `${prefixCls}-selection-search-input`, class: classNames(`${prefixCls}-selection-search-input`, inputNode?.props?.className),
style: { ...style, opacity: editable ? null : 0 }, style: { ...style, opacity: editable ? null : 0 },
role: 'combobox', role: 'combobox',
'aria-expanded': open, 'aria-expanded': open,
@ -143,10 +150,14 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
onPaste, onPaste,
onFocus: (...args: any[]) => { onFocus: (...args: any[]) => {
clearTimeout(this.blurTimeout); clearTimeout(this.blurTimeout);
onOriginFocus && onOriginFocus(args[0]);
onFocus && onFocus(args[0]);
this.VCSelectContainerEvent?.focus(args[0]); this.VCSelectContainerEvent?.focus(args[0]);
}, },
onBlur: (...args: any[]) => { onBlur: (...args: any[]) => {
this.blurTimeout = setTimeout(() => { this.blurTimeout = setTimeout(() => {
onOriginBlur && onOriginBlur(args[0]);
onBlur && onBlur(args[0]);
this.VCSelectContainerEvent?.blur(args[0]); this.VCSelectContainerEvent?.blur(args[0]);
}, 200); }, 200);
}, },
@ -181,6 +192,8 @@ Input.props = {
onPaste: PropTypes.func, onPaste: PropTypes.func,
onCompositionstart: PropTypes.func, onCompositionstart: PropTypes.func,
onCompositionend: PropTypes.func, onCompositionend: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
}; };
export default Input; export default Input;

View File

@ -1,35 +1,32 @@
import TransBtn from '../TransBtn'; import TransBtn from '../TransBtn';
import { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator'; import {
LabelValueType,
RawValueType,
CustomTagProps,
DefaultValueType,
DisplayLabelValueType,
} from '../interface/generator';
import { RenderNode } from '../interface'; import { RenderNode } from '../interface';
import { InnerSelectorProps } from '.'; import { InnerSelectorProps } from '.';
import Input from './Input'; import Input from './Input';
import { import { computed, defineComponent, onMounted, ref, VNodeChild, watch, Ref } from 'vue';
computed,
defineComponent,
onMounted,
ref,
VNodeChild,
watch,
watchEffect,
Ref,
} from 'vue';
import classNames from '../../_util/classNames'; import classNames from '../../_util/classNames';
import pickAttrs from '../../_util/pickAttrs'; import pickAttrs from '../../_util/pickAttrs';
import PropTypes from '../../_util/vue-types'; import PropTypes from '../../_util/vue-types';
import { getTransitionGroupProps, TransitionGroup } from '../../_util/transition'; import { VueNode } from 'ant-design-vue/es/_util/type';
import Overflow from '../../vc-overflow';
const REST_TAG_KEY = '__RC_SELECT_MAX_REST_COUNT__';
interface SelectorProps extends InnerSelectorProps { interface SelectorProps extends InnerSelectorProps {
// Icon // Icon
removeIcon?: RenderNode; removeIcon?: RenderNode;
// Tags // Tags
maxTagCount?: number; maxTagCount?: number | 'responsive';
maxTagTextLength?: number; maxTagTextLength?: number;
maxTagPlaceholder?: VNodeChild; maxTagPlaceholder?: VNodeChild;
tokenSeparators?: string[]; tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => VNodeChild; tagRender?: (props: CustomTagProps) => VNodeChild;
onToggleOpen: (open?: boolean) => void;
// Motion // Motion
choiceTransitionName?: string; choiceTransitionName?: string;
@ -57,7 +54,7 @@ const props = {
removeIcon: PropTypes.VNodeChild, removeIcon: PropTypes.VNodeChild,
choiceTransitionName: PropTypes.string, choiceTransitionName: PropTypes.string,
maxTagCount: PropTypes.number, maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
maxTagTextLength: PropTypes.number, maxTagTextLength: PropTypes.number,
maxTagPlaceholder: PropTypes.any.def(() => (omittedValues: LabelValueType[]) => maxTagPlaceholder: PropTypes.any.def(() => (omittedValues: LabelValueType[]) =>
`+ ${omittedValues.length} ...`, `+ ${omittedValues.length} ...`,
@ -73,24 +70,27 @@ const props = {
onInputCompositionEnd: PropTypes.func, onInputCompositionEnd: PropTypes.func,
}; };
const onPreventMouseDown = (event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
const SelectSelector = defineComponent<SelectorProps>({ const SelectSelector = defineComponent<SelectorProps>({
name: 'MultipleSelectSelector', name: 'MultipleSelectSelector',
setup(props) { setup(props) {
let motionAppear = false; // not need use ref, because not need trigger watchEffect
const measureRef = ref(); const measureRef = ref();
const inputWidth = ref(0); const inputWidth = ref(0);
const focused = ref(false);
// ===================== Motion ====================== const selectionPrefixCls = computed(() => `${props.prefixCls}-selection`);
onMounted(() => {
motionAppear = true;
});
// ===================== Search ====================== // ===================== Search ======================
const inputValue = computed(() => const inputValue = computed(() =>
props.open || props.mode === 'tags' ? props.searchValue : '', props.open || props.mode === 'tags' ? props.searchValue : '',
); );
const inputEditable: Ref<boolean> = computed( const inputEditable: Ref<boolean> = computed(
() => props.mode === 'tags' || ((props.open && props.showSearch) as boolean), () =>
props.mode === 'tags' || ((props.showSearch && (props.open || focused.value)) as boolean),
); );
// We measure width and set to the input immediately // We measure width and set to the input immediately
@ -100,122 +100,99 @@ const SelectSelector = defineComponent<SelectorProps>({
() => { () => {
inputWidth.value = measureRef.value.scrollWidth; inputWidth.value = measureRef.value.scrollWidth;
}, },
{ flush: 'post' }, { flush: 'post', immediate: true },
); );
}); });
const selectionNode = ref(); // ===================== Render ======================
watchEffect(() => { // >>> Render Selector Node. Includes Item & Rest
const { function defaultRenderSelector(
values, content: VueNode,
prefixCls, itemDisabled: boolean,
removeIcon, closable?: boolean,
choiceTransitionName, onClose?: (e: MouseEvent) => void,
maxTagCount, ) {
maxTagTextLength, return (
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,
});
}
const transitionProps = getTransitionGroupProps(choiceTransitionName, {
appear: motionAppear,
});
selectionNode.value = (
<TransitionGroup {...transitionProps}>
{...displayValues.map(
({ 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 <span
key={mergedKey as string} class={classNames(`${selectionPrefixCls.value}-item`, {
onMousedown={onMousedown} [`${selectionPrefixCls.value}-item-disabled`]: itemDisabled,
class={classNames(className)}
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> <span class={`${selectionPrefixCls.value}-item-content`}>{content}</span>
{closable && ( {closable && (
<TransBtn <TransBtn
class={`${prefixCls}-selection-item-remove`} class={`${selectionPrefixCls.value}-item-remove`}
onMousedown={onMousedown} onMousedown={onPreventMouseDown}
onClick={onClose} onClick={onClose}
customizeIcon={removeIcon} customizeIcon={props.removeIcon}
> >
× ×
</TransBtn> </TransBtn>
)} )}
</span> </span>
); );
}, }
)}
</TransitionGroup> 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>
); );
}); }
function renderItem({ disabled: itemDisabled, label, value }: DisplayLabelValueType) {
const closable = !props.disabled && !itemDisabled;
let displayLabel = label;
if (typeof props.maxTagTextLength === 'number') {
if (typeof label === 'string' || typeof label === 'number') {
const strLabel = String(displayLabel);
if (strLabel.length > props.maxTagTextLength) {
displayLabel = `${strLabel.slice(0, props.maxTagTextLength)}...`;
}
}
}
const onClose = (event?: MouseEvent) => {
if (event) event.stopPropagation();
props.onSelect(value, { selected: false });
};
return typeof props.tagRender === 'function'
? customizeRenderSelector(value, displayLabel, itemDisabled, closable, onClose)
: defaultRenderSelector(displayLabel, itemDisabled, closable, onClose);
}
function renderRest(omittedValues: DisplayLabelValueType[]) {
const {
maxTagPlaceholder = (omittedValues: LabelValueType[]) => `+ ${omittedValues.length} ...`,
} = props;
const content =
typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues)
: maxTagPlaceholder;
return defaultRenderSelector(content, false);
}
return () => { return () => {
const { const {
@ -237,10 +214,14 @@ const SelectSelector = defineComponent<SelectorProps>({
onInputCompositionStart, onInputCompositionStart,
onInputCompositionEnd, onInputCompositionEnd,
} = props; } = props;
return (
<> // >>> Input Node
{selectionNode.value} const inputNode = (
<span class={`${prefixCls}-selection-search`} style={{ width: inputWidth.value + 'px' }}> <div
class={`${selectionPrefixCls.value}-search`}
style={{ width: inputWidth.value + 'px' }}
key="input"
>
<Input <Input
inputRef={inputRef} inputRef={inputRef}
open={open} open={open}
@ -250,7 +231,7 @@ const SelectSelector = defineComponent<SelectorProps>({
disabled={disabled} disabled={disabled}
autofocus={autofocus} autofocus={autofocus}
autocomplete={autocomplete} autocomplete={autocomplete}
editable={inputEditable.value as boolean} editable={inputEditable.value}
accessibilityIndex={accessibilityIndex} accessibilityIndex={accessibilityIndex}
value={inputValue.value} value={inputValue.value}
onKeydown={onInputKeyDown} onKeydown={onInputKeyDown}
@ -261,16 +242,35 @@ const SelectSelector = defineComponent<SelectorProps>({
onCompositionend={onInputCompositionEnd} onCompositionend={onInputCompositionEnd}
tabindex={tabindex} tabindex={tabindex}
attrs={pickAttrs(props, true)} attrs={pickAttrs(props, true)}
onFocus={() => (focused.value = true)}
onBlur={() => (focused.value = false)}
/> />
{/* Measure Node */} {/* Measure Node */}
<span ref={measureRef} class={`${prefixCls}-selection-search-mirror`} aria-hidden> <span ref={measureRef} class={`${selectionPrefixCls.value}-search-mirror`} aria-hidden>
{inputValue.value}&nbsp; {inputValue.value}&nbsp;
</span> </span>
</span> </div>
);
// >>> 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}
{!values.length && !inputValue.value && ( {!values.length && !inputValue.value && (
<span class={`${prefixCls}-selection-placeholder`}>{placeholder}</span> <span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span>
)} )}
</> </>
); );

View File

@ -63,7 +63,7 @@ export interface SelectorProps {
removeIcon?: RenderNode; removeIcon?: RenderNode;
// Tags // Tags
maxTagCount?: number; maxTagCount?: number | 'responsive';
maxTagTextLength?: number; maxTagTextLength?: number;
maxTagPlaceholder?: VNodeChild; maxTagPlaceholder?: VNodeChild;
tagRender?: (props: CustomTagProps) => VNodeChild; tagRender?: (props: CustomTagProps) => VNodeChild;
@ -140,8 +140,12 @@ const Selector = defineComponent<SelectorProps>({
compositionStatus = true; compositionStatus = true;
}; };
const onInputCompositionEnd = () => { const onInputCompositionEnd = (e: InputEvent) => {
compositionStatus = false; compositionStatus = false;
// Trigger search again to support `tokenSeparators` with typewriting
if (props.mode !== 'combobox') {
triggerOnSearch((e.target as HTMLInputElement).value);
}
}; };
const onInputChange = (event: { target: { value: any } }) => { const onInputChange = (event: { target: { value: any } }) => {
@ -152,7 +156,10 @@ const Selector = defineComponent<SelectorProps>({
// Pasted text should replace back to origin content // Pasted text should replace back to origin content
if (props.tokenWithEnter && pastedText && /[\r\n]/.test(pastedText)) { if (props.tokenWithEnter && pastedText && /[\r\n]/.test(pastedText)) {
// CRLF will be treated as a single space for input element // CRLF will be treated as a single space for input element
const replacedText = pastedText.replace(/\r\n/g, ' ').replace(/[\r\n]/g, ' '); const replacedText = pastedText
.replace(/[\r\n]+$/, '')
.replace(/\r\n/g, ' ')
.replace(/[\r\n]/g, ' ');
value = value.replace(replacedText, pastedText); value = value.replace(replacedText, pastedText);
} }
@ -271,7 +278,7 @@ Selector.props = {
removeIcon: PropTypes.any, removeIcon: PropTypes.any,
// Tags // Tags
maxTagCount: PropTypes.number, maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
maxTagTextLength: PropTypes.number, maxTagTextLength: PropTypes.number,
maxTagPlaceholder: PropTypes.any, maxTagPlaceholder: PropTypes.any,
tagRender: PropTypes.func, tagRender: PropTypes.func,

View File

@ -56,6 +56,7 @@ import createRef from '../_util/createRef';
import PropTypes, { withUndefined } from '../_util/vue-types'; import PropTypes, { withUndefined } from '../_util/vue-types';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import warning from '../_util/warning'; import warning from '../_util/warning';
import isMobile from '../vc-util/isMobile';
const DEFAULT_OMIT_PROPS = [ const DEFAULT_OMIT_PROPS = [
'children', 'children',
@ -67,6 +68,7 @@ const DEFAULT_OMIT_PROPS = [
'maxTagPlaceholder', 'maxTagPlaceholder',
'choiceTransitionName', 'choiceTransitionName',
'onInputKeyDown', 'onInputKeyDown',
'tabindex',
]; ];
export const BaseProps = () => ({ export const BaseProps = () => ({
@ -94,6 +96,7 @@ export const BaseProps = () => ({
* It's by design. * It's by design.
*/ */
filterOption: PropTypes.any, filterOption: PropTypes.any,
filterSort: PropTypes.func,
showSearch: PropTypes.looseBool, showSearch: PropTypes.looseBool,
autoClearSearchValue: PropTypes.looseBool, autoClearSearchValue: PropTypes.looseBool,
onSearch: PropTypes.func, onSearch: PropTypes.func,
@ -134,7 +137,7 @@ export const BaseProps = () => ({
getInputElement: PropTypes.func, getInputElement: PropTypes.func,
optionLabelProp: PropTypes.string, optionLabelProp: PropTypes.string,
maxTagTextLength: PropTypes.number, maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.number, maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
maxTagPlaceholder: PropTypes.any, maxTagPlaceholder: PropTypes.any,
tokenSeparators: PropTypes.array, tokenSeparators: PropTypes.array,
tagRender: PropTypes.func, tagRender: PropTypes.func,
@ -195,6 +198,7 @@ export interface SelectProps<OptionsType extends object[], ValueType> {
* It's by design. * It's by design.
*/ */
filterOption?: boolean | FilterFunc<OptionsType[number]>; filterOption?: boolean | FilterFunc<OptionsType[number]>;
filterSort?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number;
showSearch?: boolean; showSearch?: boolean;
autoClearSearchValue?: boolean; autoClearSearchValue?: boolean;
onSearch?: (value: string) => void; onSearch?: (value: string) => void;
@ -235,7 +239,7 @@ export interface SelectProps<OptionsType extends object[], ValueType> {
getInputElement?: () => VNodeChild | JSX.Element; getInputElement?: () => VNodeChild | JSX.Element;
optionLabelProp?: string; optionLabelProp?: string;
maxTagTextLength?: number; maxTagTextLength?: number;
maxTagCount?: number; maxTagCount?: number | 'responsive';
maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild); maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild);
tokenSeparators?: string[]; tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => VNodeChild; tagRender?: (props: CustomTagProps) => VNodeChild;
@ -385,6 +389,11 @@ export default function generateSelector<
: isMultiple.value || props.mode === 'combobox', : isMultiple.value || props.mode === 'combobox',
); );
const mobile = ref(false);
onMounted(() => {
mobile.value = isMobile();
});
// ============================== Ref =============================== // ============================== Ref ===============================
const selectorDomRef = createRef(); const selectorDomRef = createRef();
@ -399,12 +408,14 @@ export default function generateSelector<
// ============================= Value ============================== // ============================= Value ==============================
/** Unique raw values */ /** Unique raw values */
const mergedRawValue = computed(() => const mergedRawValueArr = computed(() =>
toInnerValue(mergedValue.value, { toInnerValue(mergedValue.value, {
labelInValue: mergedLabelInValue.value, labelInValue: mergedLabelInValue.value,
combobox: props.mode === 'combobox', combobox: props.mode === 'combobox',
}), }),
); );
const mergedRawValue = computed(() => mergedRawValueArr.value[0]);
const mergedValueMap = computed(() => mergedRawValueArr.value[1]);
/** We cache a set of raw values to speed up check */ /** We cache a set of raw values to speed up check */
const rawValues = computed(() => new Set(mergedRawValue.value)); const rawValues = computed(() => new Set(mergedRawValue.value));
@ -457,7 +468,7 @@ export default function generateSelector<
const mergedFlattenOptions = computed(() => flattenOptions(mergedOptions.value, props)); const mergedFlattenOptions = computed(() => flattenOptions(mergedOptions.value, props));
const getValueOption = useCacheOptions(mergedRawValue.value, mergedFlattenOptions); const getValueOption = useCacheOptions(mergedFlattenOptions);
// Display options for OptionList // Display options for OptionList
const displayOptions = computed<OptionsType>(() => { const displayOptions = computed<OptionsType>(() => {
@ -484,6 +495,9 @@ export default function generateSelector<
key: '__RC_SELECT_TAG_PLACEHOLDER__', key: '__RC_SELECT_TAG_PLACEHOLDER__',
}); });
} }
if (props.filterSort && Array.isArray(filteredOptions)) {
return ([...filteredOptions] as OptionsType).sort(props.filterSort);
}
return filteredOptions; return filteredOptions;
}); });
@ -507,7 +521,7 @@ export default function generateSelector<
const valueOptions = getValueOption([val]); const valueOptions = getValueOption([val]);
const displayValue = getLabeledValue(val, { const displayValue = getLabeledValue(val, {
options: valueOptions, options: valueOptions,
prevValue: mergedValue.value, prevValueMap: mergedValueMap.value,
labelInValue: mergedLabelInValue.value, labelInValue: mergedLabelInValue.value,
optionLabelProp: mergedOptionLabelProp.value, optionLabelProp: mergedOptionLabelProp.value,
}); });
@ -542,7 +556,7 @@ export default function generateSelector<
const selectValue = (mergedLabelInValue.value const selectValue = (mergedLabelInValue.value
? getLabeledValue(newValue, { ? getLabeledValue(newValue, {
options: newValueOption, options: newValueOption,
prevValue: mergedValue.value, prevValueMap: mergedValueMap.value,
labelInValue: mergedLabelInValue.value, labelInValue: mergedLabelInValue.value,
optionLabelProp: mergedOptionLabelProp.value, optionLabelProp: mergedOptionLabelProp.value,
}) })
@ -583,7 +597,7 @@ export default function generateSelector<
labelInValue: mergedLabelInValue.value, labelInValue: mergedLabelInValue.value,
options: newRawValuesOptions, options: newRawValuesOptions,
getLabeledValue, getLabeledValue,
prevValue: mergedValue.value, prevValueMap: mergedValueMap.value,
optionLabelProp: mergedOptionLabelProp.value, optionLabelProp: mergedOptionLabelProp.value,
}); });
@ -770,6 +784,10 @@ export default function generateSelector<
// If menu is open, OptionList will take charge // If menu is open, OptionList will take charge
// If mode isn't tags, press enter is not meaningful when you can't see any option // If mode isn't tags, press enter is not meaningful when you can't see any option
const onSearchSubmit = (searchText: string) => { const onSearchSubmit = (searchText: string) => {
// prevent empty tags from appearing when you click the Enter button
if (!searchText || !searchText.trim()) {
return;
}
const newRawValues = Array.from( const newRawValues = Array.from(
new Set<RawValueType>([...mergedRawValue.value, searchText]), new Set<RawValueType>([...mergedRawValue.value, searchText]),
); );
@ -815,10 +833,18 @@ export default function generateSelector<
const onInternalKeyDown = (event: KeyboardEvent) => { const onInternalKeyDown = (event: KeyboardEvent) => {
const clearLock = getClearLock(); const clearLock = getClearLock();
const { which } = event; const { which } = event;
if (which === KeyCode.ENTER) {
// Do not submit form when type in the input
if (props.mode !== 'combobox') {
event.preventDefault();
}
// We only manage open state here, close logic should handle by list component // We only manage open state here, close logic should handle by list component
if (!mergedOpen.value && which === KeyCode.ENTER) { if (!mergedOpen.value) {
onToggleOpen(true); onToggleOpen(true);
} }
}
setClearLock(!!mergedSearchValue.value); setClearLock(!!mergedSearchValue.value);
@ -922,7 +948,6 @@ export default function generateSelector<
const onInternalMouseDown = (event: MouseEvent) => { const onInternalMouseDown = (event: MouseEvent) => {
const { target } = event; const { target } = event;
const popupElement: HTMLDivElement = triggerRef.value && triggerRef.value.getPopupElement(); const popupElement: HTMLDivElement = triggerRef.value && triggerRef.value.getPopupElement();
// We should give focus back to selector if clicked item is not focusable // We should give focus back to selector if clicked item is not focusable
if (popupElement && popupElement.contains(target as HTMLElement)) { if (popupElement && popupElement.contains(target as HTMLElement)) {
const timeoutId = window.setTimeout(() => { const timeoutId = window.setTimeout(() => {
@ -933,7 +958,7 @@ export default function generateSelector<
cancelSetMockFocused(); cancelSetMockFocused();
if (!popupElement.contains(document.activeElement)) { if (!mobile.value && !popupElement.contains(document.activeElement)) {
selectorRef.value.focus(); selectorRef.value.focus();
} }
}); });
@ -993,6 +1018,7 @@ export default function generateSelector<
return { return {
focus, focus,
blur, blur,
scrollTo: listRef.value?.scrollTo,
tokenWithEnter, tokenWithEnter,
mockFocused, mockFocused,
mergedId, mergedId,

View File

@ -17,7 +17,7 @@ export default function useCacheDisplayValue(
const resultValues = values.value.map(item => { const resultValues = values.value.map(item => {
const cacheLabel = valueLabels.get(item.value); const cacheLabel = valueLabels.get(item.value);
if (item.value === item.label && cacheLabel) { if (item.isCacheable && cacheLabel) {
return { return {
...item, ...item,
label: cacheLabel, label: cacheLabel,

View File

@ -8,7 +8,7 @@ export default function useCacheOptions<
key?: Key; key?: Key;
disabled?: boolean; disabled?: boolean;
}[] }[]
>(_values: RawValueType[], options: Ref) { >(options: Ref) {
const optionMap = computed(() => { const optionMap = computed(() => {
const map: Map<RawValueType, FlattenOptionsType<OptionsType>[number]> = new Map(); const map: Map<RawValueType, FlattenOptionsType<OptionsType>[number]> = new Map();
options.value.forEach((item: any) => { options.value.forEach((item: any) => {

View File

@ -6,7 +6,11 @@ export default function useSelectTriggerControl(
triggerOpen: (open: boolean) => void, triggerOpen: (open: boolean) => void,
) { ) {
function onGlobalMouseDown(event: MouseEvent) { function onGlobalMouseDown(event: MouseEvent) {
const target = event.target as HTMLElement; let target = event.target as HTMLElement;
if (target.shadowRoot && event.composed) {
target = (event.composedPath()[0] || target) as HTMLElement;
}
const elements = [refs[0]?.value, refs[1]?.value?.getPopupElement()]; const elements = [refs[0]?.value, refs[1]?.value?.getPopupElement()];
if ( if (
open.value && open.value &&

View File

@ -1,5 +1,4 @@
import { VueNode } from '../../_util/type'; import { VueNode } from '../../_util/type';
import { VNodeChild } from 'vue';
export type SelectSource = 'option' | 'selection' | 'input'; export type SelectSource = 'option' | 'selection' | 'input';
@ -13,7 +12,8 @@ export type RawValueType = string | number | null;
export interface LabelValueType extends Record<string, any> { export interface LabelValueType extends Record<string, any> {
key?: Key; key?: Key;
value?: RawValueType; value?: RawValueType;
label?: VNodeChild; label?: VueNode;
isCacheable?: boolean;
} }
export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[]; export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];
@ -23,10 +23,10 @@ export interface DisplayLabelValueType extends LabelValueType {
export type SingleType<MixType> = MixType extends (infer Single)[] ? Single : MixType; export type SingleType<MixType> = MixType extends (infer Single)[] ? Single : MixType;
export type OnClear = () => void; export type OnClear = () => any;
export type CustomTagProps = { export type CustomTagProps = {
label: DefaultValueType; label: VueNode;
value: DefaultValueType; value: DefaultValueType;
disabled: boolean; disabled: boolean;
onClose: (event?: MouseEvent) => void; onClose: (event?: MouseEvent) => void;
@ -38,7 +38,7 @@ export type GetLabeledValue<FOT extends FlattenOptionsType> = (
value: RawValueType, value: RawValueType,
config: { config: {
options: FOT; options: FOT;
prevValue: DefaultValueType; prevValueMap: Map<RawValueType, LabelValueType>;
labelInValue: boolean; labelInValue: boolean;
optionLabelProp: string; optionLabelProp: string;
}, },

View File

@ -19,20 +19,28 @@ export function toArray<T>(value: T | T[]): T[] {
export function toInnerValue( export function toInnerValue(
value: DefaultValueType, value: DefaultValueType,
{ labelInValue, combobox }: { labelInValue: boolean; combobox: boolean }, { labelInValue, combobox }: { labelInValue: boolean; combobox: boolean },
): RawValueType[] { ): [RawValueType[], Map<RawValueType, LabelValueType>] {
const valueMap = new Map<RawValueType, LabelValueType>();
if (value === undefined || (value === '' && combobox)) { if (value === undefined || (value === '' && combobox)) {
return []; return [[], valueMap];
} }
const values = Array.isArray(value) ? value : [value]; const values = Array.isArray(value) ? value : [value];
let rawValues = values as RawValueType[];
if (labelInValue) { if (labelInValue) {
return (values as LabelValueType[]).map(({ key, value: val }: LabelValueType) => rawValues = (values as LabelValueType[])
val !== undefined ? val : key, .filter(item => item !== null)
); .map((itemValue: LabelValueType) => {
const { key, value: val } = itemValue;
const finalVal = val !== undefined ? val : key;
valueMap.set(finalVal, itemValue);
return finalVal;
});
} }
return values as RawValueType[]; return [rawValues, valueMap];
} }
/** /**
@ -43,7 +51,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
{ {
optionLabelProp, optionLabelProp,
labelInValue, labelInValue,
prevValue, prevValueMap,
options, options,
getLabeledValue, getLabeledValue,
}: { }: {
@ -51,7 +59,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
labelInValue: boolean; labelInValue: boolean;
getLabeledValue: GetLabeledValue<FOT>; getLabeledValue: GetLabeledValue<FOT>;
options: FOT; options: FOT;
prevValue: DefaultValueType; prevValueMap: Map<RawValueType, LabelValueType>;
}, },
): RawValueType[] | LabelValueType[] | DefaultValueType { ): RawValueType[] | LabelValueType[] | DefaultValueType {
let values: DefaultValueType = valueList; let values: DefaultValueType = valueList;
@ -60,7 +68,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
values = values.map(val => values = values.map(val =>
getLabeledValue(val, { getLabeledValue(val, {
options, options,
prevValue, prevValueMap,
labelInValue, labelInValue,
optionLabelProp, optionLabelProp,
}), }),

View File

@ -120,24 +120,14 @@ export function findValueOption(
export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = ( export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
value, value,
{ options, prevValue, labelInValue, optionLabelProp }, { options, prevValueMap, labelInValue, optionLabelProp },
) => { ) => {
const item = findValueOption([value], options)[0]; const item = findValueOption([value], options)[0];
const result: LabelValueType = { const result: LabelValueType = {
value, value,
}; };
let prevValItem: LabelValueType; const prevValItem: LabelValueType = labelInValue ? prevValueMap.get(value) : undefined;
const prevValues = toArray<LabelValueType>(prevValue as LabelValueType);
if (labelInValue) {
prevValItem = prevValues.find((prevItem: LabelValueType) => {
if (typeof prevItem === 'object' && 'value' in prevItem) {
return prevItem.value === value;
}
// [Legacy] Support `key` as `value`
return prevItem.key === value;
}) as LabelValueType;
}
if (prevValItem && typeof prevValItem === 'object' && 'label' in prevValItem) { if (prevValItem && typeof prevValItem === 'object' && 'label' in prevValItem) {
result.label = prevValItem.label; result.label = prevValItem.label;
@ -160,6 +150,7 @@ export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
} }
} else { } else {
result.label = value; result.label = value;
result.isCacheable = true;
} }
// Used for motion control // Used for motion control
@ -211,7 +202,7 @@ export function filterOptions(
let filterFunc: FilterFunc<SelectOptionsType[number]>; let filterFunc: FilterFunc<SelectOptionsType[number]>;
if (filterOption === false) { if (filterOption === false) {
return options; return [...options];
} }
if (typeof filterOption === 'function') { if (typeof filterOption === 'function') {
filterFunc = filterOption; filterFunc = filterOption;

View File

@ -0,0 +1,18 @@
export default () => {
if (typeof navigator === 'undefined' || typeof window === 'undefined') {
return false;
}
const agent = navigator.userAgent || navigator.vendor || (window as any).opera;
if (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
agent,
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
agent?.substr(0, 4),
)
) {
return true;
}
return false;
};

View File

@ -37,5 +37,6 @@ declare module 'vue' {
onKeydown?: EventHandler; onKeydown?: EventHandler;
onKeyup?: EventHandler; onKeyup?: EventHandler;
onDeselect?: EventHandler; onDeselect?: EventHandler;
onClear?: EventHandler;
} }
} }