feat: add select responsive
parent
501204c543
commit
656d14fc4e
|
@ -20,7 +20,7 @@ export interface LabeledValue {
|
|||
label: VNodeChild;
|
||||
}
|
||||
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'> {
|
||||
suffixIcon?: VNodeChild;
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
background: @input-disabled-bg;
|
||||
cursor: not-allowed;
|
||||
|
||||
.@{select-prefix-cls}-multiple& {
|
||||
background: @select-multiple-disabled-background;
|
||||
}
|
||||
|
||||
input {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
@ -66,7 +70,12 @@
|
|||
display: inline-block;
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -93,6 +102,7 @@
|
|||
color: @input-placeholder-color;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
|
||||
// IE11 css hack. `*::-ms-backdrop,` is a must have
|
||||
@media all and (-ms-high-contrast: none) {
|
||||
|
@ -189,21 +199,21 @@
|
|||
outline: none;
|
||||
box-shadow: @box-shadow-base;
|
||||
|
||||
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,
|
||||
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft {
|
||||
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
|
||||
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
|
||||
animation-name: antSlideUpIn;
|
||||
}
|
||||
|
||||
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
|
||||
&.slide-up-appear.slide-up-appear-active&-placement-topLeft {
|
||||
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
|
||||
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
|
||||
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;
|
||||
}
|
||||
|
||||
&.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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import './index';
|
||||
|
||||
@select-overflow-prefix-cls: ~'@{select-prefix-cls}-selection-overflow';
|
||||
@select-multiple-item-border-width: 1px;
|
||||
|
||||
@select-multiple-padding: max(
|
||||
|
@ -13,13 +14,25 @@
|
|||
* 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} {
|
||||
&-multiple {
|
||||
// ========================= Selector =========================
|
||||
.@{select-prefix-cls}-selector {
|
||||
.select-selector();
|
||||
.select-search-input-without-border();
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
@ -59,9 +72,7 @@
|
|||
|
||||
height: @select-multiple-item-height;
|
||||
margin-top: @select-multiple-item-spacing-half;
|
||||
margin-right: @input-padding-vertical-base;
|
||||
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;
|
||||
background: @select-selection-item-bg;
|
||||
border: 1px solid @select-selection-item-border-color;
|
||||
|
@ -69,6 +80,9 @@
|
|||
cursor: default;
|
||||
transition: font-size 0.3s, line-height 0.3s, height 0.3s;
|
||||
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& {
|
||||
color: @select-multiple-item-disabled-color;
|
||||
|
@ -81,7 +95,7 @@
|
|||
display: inline-block;
|
||||
margin-right: (@padding-xs / 2);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
white-space: pre; // fix whitespace wrapping. custom tags display all whitespace within.
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
@ -90,10 +104,9 @@
|
|||
display: inline-block;
|
||||
color: @text-color-secondary;
|
||||
font-weight: bold;
|
||||
font-size: @font-size-sm;
|
||||
font-size: 10px;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
.iconfont-size-under-12px(10px);
|
||||
|
||||
> .@{iconfont-css-prefix} {
|
||||
vertical-align: -0.2em;
|
||||
|
@ -106,14 +119,24 @@
|
|||
}
|
||||
|
||||
// ========================== 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 {
|
||||
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,
|
||||
&-mirror {
|
||||
height: @select-multiple-item-height;
|
||||
font-family: @font-family;
|
||||
line-height: @line-height-base;
|
||||
line-height: @select-multiple-item-height;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
|
@ -127,14 +150,9 @@
|
|||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
white-space: nowrap;
|
||||
white-space: pre; // fix whitespace wrapping caused width calculation bug
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
&:first-child .@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 6.5px;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================= Placeholder =======================
|
||||
|
@ -166,8 +184,8 @@
|
|||
}
|
||||
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
height: @select-selection-height + @select-multiple-padding;
|
||||
line-height: @select-selection-height + @select-multiple-padding;
|
||||
height: @select-selection-height;
|
||||
line-height: @select-selection-height;
|
||||
|
||||
&-input,
|
||||
&-mirror {
|
||||
|
@ -186,10 +204,9 @@
|
|||
.@{select-prefix-cls}-selection-placeholder {
|
||||
left: @input-padding-horizontal-sm;
|
||||
}
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
.@{select-prefix-cls}-selection-search:first-child
|
||||
.@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 3px;
|
||||
// https://github.com/ant-design/ant-design/issues/29559
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
margin-inline-start: 3px;
|
||||
}
|
||||
}
|
||||
&.@{select-prefix-cls}-lg {
|
||||
|
|
|
@ -66,9 +66,6 @@
|
|||
// ======================== Selections ========================
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
.@{select-prefix-cls}-rtl& {
|
||||
margin-right: 0;
|
||||
margin-left: @input-padding-vertical-base;
|
||||
padding: 0 @padding-xs 0 (@padding-xs / 2);
|
||||
text-align: right;
|
||||
}
|
||||
// It's ok not to do this, but 24px makes bottom narrow in view should adjust
|
||||
|
@ -83,11 +80,6 @@
|
|||
|
||||
// ========================== Input ==========================
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
.@{select-prefix-cls}-rtl& {
|
||||
margin-right: (@select-multiple-padding / 2);
|
||||
margin-left: @input-padding-vertical-base;
|
||||
}
|
||||
|
||||
&-mirror {
|
||||
.@{select-prefix-cls}-rtl& {
|
||||
right: 0;
|
||||
|
@ -119,7 +111,7 @@
|
|||
}
|
||||
|
||||
// single
|
||||
@selection-item-padding: ceil((@font-size-base * 1.25));
|
||||
@selection-item-padding: ceil(@font-size-base * 1.25);
|
||||
|
||||
.@{select-prefix-cls}-single {
|
||||
// ========================= 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 ==
|
||||
// ============================================================
|
||||
|
@ -172,7 +152,7 @@
|
|||
// With arrow should provides `padding-right` to show the arrow
|
||||
&.@{select-prefix-cls}-show-arrow .@{select-prefix-cls}-selection-search {
|
||||
.@{select-prefix-cls}-rtl& {
|
||||
right: 0;
|
||||
right: @input-padding-horizontal-sm - 1px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@import './index';
|
||||
|
||||
@selection-item-padding: ceil((@font-size-base * 1.25));
|
||||
@selection-item-padding: ceil(@font-size-base * 1.25);
|
||||
|
||||
.@{select-prefix-cls}-single {
|
||||
// ========================= Selector =========================
|
||||
|
@ -76,10 +76,7 @@
|
|||
// Not customize
|
||||
&:not(.@{select-prefix-cls}-customize-input) {
|
||||
.@{select-prefix-cls}-selector {
|
||||
.select-selector();
|
||||
.select-search-input-without-border();
|
||||
width: 100%;
|
||||
|
||||
height: @input-height-base;
|
||||
padding: 0 @input-padding-horizontal-base;
|
||||
|
||||
|
|
|
@ -90,19 +90,17 @@ export default defineComponent({
|
|||
</Component>
|
||||
);
|
||||
|
||||
if (responsive) {
|
||||
itemNode = (
|
||||
<ResizeObserver
|
||||
onResize={({ offsetWidth }) => {
|
||||
internalRegisterSize(offsetWidth);
|
||||
}}
|
||||
>
|
||||
{itemNode}
|
||||
</ResizeObserver>
|
||||
);
|
||||
}
|
||||
|
||||
return itemNode;
|
||||
// 使用 disabled 避免结构不一致 导致子组件 rerender
|
||||
return (
|
||||
<ResizeObserver
|
||||
disabled={!responsive}
|
||||
onResize={({ offsetWidth }) => {
|
||||
internalRegisterSize(offsetWidth);
|
||||
}}
|
||||
>
|
||||
{itemNode}
|
||||
</ResizeObserver>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -370,12 +370,12 @@ const Overflow = defineComponent({
|
|||
)}
|
||||
</Component>
|
||||
);
|
||||
|
||||
if (isResponsive.value) {
|
||||
overflowNode = <ResizeObserver onResize={onOverflowResize}>{overflowNode}</ResizeObserver>;
|
||||
}
|
||||
|
||||
return overflowNode;
|
||||
// 使用 disabled 避免结构不一致 导致子组件 rerender
|
||||
return (
|
||||
<ResizeObserver disabled={!isResponsive.value} onResize={onOverflowResize}>
|
||||
{overflowNode}
|
||||
</ResizeObserver>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -237,6 +237,9 @@ const OptionList = defineComponent<OptionListProps, { state?: any }>({
|
|||
// >>> Close
|
||||
case KeyCode.ESC: {
|
||||
props.onToggleOpen(false);
|
||||
if (props.open) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -46,7 +46,6 @@ import generateSelector, { SelectProps } from './generate';
|
|||
import { DefaultValueType } from './interface/generator';
|
||||
import warningProps from './utils/warningPropsUtil';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { getSlot } from '../_util/props-util';
|
||||
import omit from 'lodash-es/omit';
|
||||
|
||||
const RefSelect = generateSelector<SelectOptionsType>({
|
||||
|
@ -69,21 +68,27 @@ export type ExportedSelectProps<
|
|||
> = SelectProps<SelectOptionsType, ValueType>;
|
||||
|
||||
const Select = defineComponent<Omit<ExportedSelectProps, 'children'>>({
|
||||
setup() {
|
||||
setup(props, { attrs, expose, slots }) {
|
||||
const selectRef = ref(null);
|
||||
return {
|
||||
selectRef,
|
||||
expose({
|
||||
focus: () => {
|
||||
selectRef.value?.focus();
|
||||
},
|
||||
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.props = omit(RefSelect.props, ['children']);
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import PropTypes from '../../_util/vue-types';
|
||||
import { RefObject } from '../../_util/createRef';
|
||||
import antInput from '../../_util/antInputDirective';
|
||||
import classNames from 'ant-design-vue/es/_util/classNames';
|
||||
|
||||
interface InputProps {
|
||||
prefixCls: string;
|
||||
|
@ -33,6 +34,8 @@ interface InputProps {
|
|||
onPaste: EventHandlerNonNull;
|
||||
onCompositionstart: EventHandlerNonNull;
|
||||
onCompositionend: EventHandlerNonNull;
|
||||
onFocus: EventHandlerNonNull;
|
||||
onBlur: EventHandlerNonNull;
|
||||
}
|
||||
|
||||
const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTimeout: any }>({
|
||||
|
@ -72,6 +75,8 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
|
|||
onPaste,
|
||||
onCompositionstart,
|
||||
onCompositionend,
|
||||
onFocus,
|
||||
onBlur,
|
||||
open,
|
||||
inputRef,
|
||||
attrs,
|
||||
|
@ -82,6 +87,8 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
|
|||
const {
|
||||
onKeydown: onOriginKeyDown,
|
||||
onInput: onOriginInput,
|
||||
onFocus: onOriginFocus,
|
||||
onBlur: onOriginBlur,
|
||||
onMousedown: onOriginMouseDown,
|
||||
onCompositionstart: onOriginCompositionStart,
|
||||
onCompositionend: onOriginCompositionEnd,
|
||||
|
@ -97,7 +104,7 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
|
|||
tabindex,
|
||||
autocomplete: autocomplete || 'off',
|
||||
autofocus,
|
||||
class: `${prefixCls}-selection-search-input`,
|
||||
class: classNames(`${prefixCls}-selection-search-input`, inputNode?.props?.className),
|
||||
style: { ...style, opacity: editable ? null : 0 },
|
||||
role: 'combobox',
|
||||
'aria-expanded': open,
|
||||
|
@ -143,10 +150,14 @@ const Input = defineComponent<InputProps, { VCSelectContainerEvent: any; blurTim
|
|||
onPaste,
|
||||
onFocus: (...args: any[]) => {
|
||||
clearTimeout(this.blurTimeout);
|
||||
onOriginFocus && onOriginFocus(args[0]);
|
||||
onFocus && onFocus(args[0]);
|
||||
this.VCSelectContainerEvent?.focus(args[0]);
|
||||
},
|
||||
onBlur: (...args: any[]) => {
|
||||
this.blurTimeout = setTimeout(() => {
|
||||
onOriginBlur && onOriginBlur(args[0]);
|
||||
onBlur && onBlur(args[0]);
|
||||
this.VCSelectContainerEvent?.blur(args[0]);
|
||||
}, 200);
|
||||
},
|
||||
|
@ -181,6 +192,8 @@ Input.props = {
|
|||
onPaste: PropTypes.func,
|
||||
onCompositionstart: PropTypes.func,
|
||||
onCompositionend: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
|
|
|
@ -1,35 +1,32 @@
|
|||
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 { InnerSelectorProps } from '.';
|
||||
import Input from './Input';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
ref,
|
||||
VNodeChild,
|
||||
watch,
|
||||
watchEffect,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
import { computed, defineComponent, onMounted, ref, VNodeChild, watch, Ref } from 'vue';
|
||||
import classNames from '../../_util/classNames';
|
||||
import pickAttrs from '../../_util/pickAttrs';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { getTransitionGroupProps, TransitionGroup } from '../../_util/transition';
|
||||
|
||||
const REST_TAG_KEY = '__RC_SELECT_MAX_REST_COUNT__';
|
||||
import { VueNode } from 'ant-design-vue/es/_util/type';
|
||||
import Overflow from '../../vc-overflow';
|
||||
|
||||
interface SelectorProps extends InnerSelectorProps {
|
||||
// Icon
|
||||
removeIcon?: RenderNode;
|
||||
|
||||
// Tags
|
||||
maxTagCount?: number;
|
||||
maxTagCount?: number | 'responsive';
|
||||
maxTagTextLength?: number;
|
||||
maxTagPlaceholder?: VNodeChild;
|
||||
tokenSeparators?: string[];
|
||||
tagRender?: (props: CustomTagProps) => VNodeChild;
|
||||
onToggleOpen: (open?: boolean) => void;
|
||||
|
||||
// Motion
|
||||
choiceTransitionName?: string;
|
||||
|
@ -57,7 +54,7 @@ const props = {
|
|||
removeIcon: PropTypes.VNodeChild,
|
||||
choiceTransitionName: PropTypes.string,
|
||||
|
||||
maxTagCount: PropTypes.number,
|
||||
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
maxTagTextLength: PropTypes.number,
|
||||
maxTagPlaceholder: PropTypes.any.def(() => (omittedValues: LabelValueType[]) =>
|
||||
`+ ${omittedValues.length} ...`,
|
||||
|
@ -73,24 +70,27 @@ const props = {
|
|||
onInputCompositionEnd: PropTypes.func,
|
||||
};
|
||||
|
||||
const onPreventMouseDown = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const SelectSelector = defineComponent<SelectorProps>({
|
||||
name: 'MultipleSelectSelector',
|
||||
setup(props) {
|
||||
let motionAppear = false; // not need use ref, because not need trigger watchEffect
|
||||
const measureRef = ref();
|
||||
const inputWidth = ref(0);
|
||||
const focused = ref(false);
|
||||
|
||||
// ===================== Motion ======================
|
||||
onMounted(() => {
|
||||
motionAppear = true;
|
||||
});
|
||||
const selectionPrefixCls = computed(() => `${props.prefixCls}-selection`);
|
||||
|
||||
// ===================== 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),
|
||||
() =>
|
||||
props.mode === 'tags' || ((props.showSearch && (props.open || focused.value)) as boolean),
|
||||
);
|
||||
|
||||
// We measure width and set to the input immediately
|
||||
|
@ -100,122 +100,99 @@ const SelectSelector = defineComponent<SelectorProps>({
|
|||
() => {
|
||||
inputWidth.value = measureRef.value.scrollWidth;
|
||||
},
|
||||
{ flush: 'post' },
|
||||
{ flush: 'post', immediate: true },
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
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
|
||||
key={mergedKey as string}
|
||||
onMousedown={onMousedown}
|
||||
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>
|
||||
{closable && (
|
||||
<TransBtn
|
||||
class={`${prefixCls}-selection-item-remove`}
|
||||
onMousedown={onMousedown}
|
||||
onClick={onClose}
|
||||
customizeIcon={removeIcon}
|
||||
>
|
||||
×
|
||||
</TransBtn>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
// ===================== 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>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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 () => {
|
||||
const {
|
||||
|
@ -237,40 +214,63 @@ const SelectSelector = defineComponent<SelectorProps>({
|
|||
onInputCompositionStart,
|
||||
onInputCompositionEnd,
|
||||
} = props;
|
||||
|
||||
// >>> 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}
|
||||
</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.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}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
{selectionNode}
|
||||
{!values.length && !inputValue.value && (
|
||||
<span class={`${prefixCls}-selection-placeholder`}>{placeholder}</span>
|
||||
<span class={`${selectionPrefixCls.value}-placeholder`}>{placeholder}</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -63,7 +63,7 @@ export interface SelectorProps {
|
|||
removeIcon?: RenderNode;
|
||||
|
||||
// Tags
|
||||
maxTagCount?: number;
|
||||
maxTagCount?: number | 'responsive';
|
||||
maxTagTextLength?: number;
|
||||
maxTagPlaceholder?: VNodeChild;
|
||||
tagRender?: (props: CustomTagProps) => VNodeChild;
|
||||
|
@ -140,8 +140,12 @@ const Selector = defineComponent<SelectorProps>({
|
|||
compositionStatus = true;
|
||||
};
|
||||
|
||||
const onInputCompositionEnd = () => {
|
||||
const onInputCompositionEnd = (e: InputEvent) => {
|
||||
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 } }) => {
|
||||
|
@ -152,7 +156,10 @@ const Selector = defineComponent<SelectorProps>({
|
|||
// Pasted text should replace back to origin content
|
||||
if (props.tokenWithEnter && pastedText && /[\r\n]/.test(pastedText)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -271,7 +278,7 @@ Selector.props = {
|
|||
removeIcon: PropTypes.any,
|
||||
|
||||
// Tags
|
||||
maxTagCount: PropTypes.number,
|
||||
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
maxTagTextLength: PropTypes.number,
|
||||
maxTagPlaceholder: PropTypes.any,
|
||||
tagRender: PropTypes.func,
|
||||
|
|
|
@ -56,6 +56,7 @@ import createRef from '../_util/createRef';
|
|||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import warning from '../_util/warning';
|
||||
import isMobile from '../vc-util/isMobile';
|
||||
|
||||
const DEFAULT_OMIT_PROPS = [
|
||||
'children',
|
||||
|
@ -67,6 +68,7 @@ const DEFAULT_OMIT_PROPS = [
|
|||
'maxTagPlaceholder',
|
||||
'choiceTransitionName',
|
||||
'onInputKeyDown',
|
||||
'tabindex',
|
||||
];
|
||||
|
||||
export const BaseProps = () => ({
|
||||
|
@ -94,6 +96,7 @@ export const BaseProps = () => ({
|
|||
* It's by design.
|
||||
*/
|
||||
filterOption: PropTypes.any,
|
||||
filterSort: PropTypes.func,
|
||||
showSearch: PropTypes.looseBool,
|
||||
autoClearSearchValue: PropTypes.looseBool,
|
||||
onSearch: PropTypes.func,
|
||||
|
@ -134,7 +137,7 @@ export const BaseProps = () => ({
|
|||
getInputElement: PropTypes.func,
|
||||
optionLabelProp: PropTypes.string,
|
||||
maxTagTextLength: PropTypes.number,
|
||||
maxTagCount: PropTypes.number,
|
||||
maxTagCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
maxTagPlaceholder: PropTypes.any,
|
||||
tokenSeparators: PropTypes.array,
|
||||
tagRender: PropTypes.func,
|
||||
|
@ -195,6 +198,7 @@ export interface SelectProps<OptionsType extends object[], ValueType> {
|
|||
* It's by design.
|
||||
*/
|
||||
filterOption?: boolean | FilterFunc<OptionsType[number]>;
|
||||
filterSort?: (optionA: OptionsType[number], optionB: OptionsType[number]) => number;
|
||||
showSearch?: boolean;
|
||||
autoClearSearchValue?: boolean;
|
||||
onSearch?: (value: string) => void;
|
||||
|
@ -235,7 +239,7 @@ export interface SelectProps<OptionsType extends object[], ValueType> {
|
|||
getInputElement?: () => VNodeChild | JSX.Element;
|
||||
optionLabelProp?: string;
|
||||
maxTagTextLength?: number;
|
||||
maxTagCount?: number;
|
||||
maxTagCount?: number | 'responsive';
|
||||
maxTagPlaceholder?: VNodeChild | ((omittedValues: LabelValueType[]) => VNodeChild);
|
||||
tokenSeparators?: string[];
|
||||
tagRender?: (props: CustomTagProps) => VNodeChild;
|
||||
|
@ -385,6 +389,11 @@ export default function generateSelector<
|
|||
: isMultiple.value || props.mode === 'combobox',
|
||||
);
|
||||
|
||||
const mobile = ref(false);
|
||||
onMounted(() => {
|
||||
mobile.value = isMobile();
|
||||
});
|
||||
|
||||
// ============================== Ref ===============================
|
||||
const selectorDomRef = createRef();
|
||||
|
||||
|
@ -399,12 +408,14 @@ export default function generateSelector<
|
|||
// ============================= Value ==============================
|
||||
|
||||
/** Unique raw values */
|
||||
const mergedRawValue = computed(() =>
|
||||
const mergedRawValueArr = computed(() =>
|
||||
toInnerValue(mergedValue.value, {
|
||||
labelInValue: mergedLabelInValue.value,
|
||||
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 */
|
||||
const rawValues = computed(() => new Set(mergedRawValue.value));
|
||||
|
||||
|
@ -457,7 +468,7 @@ export default function generateSelector<
|
|||
|
||||
const mergedFlattenOptions = computed(() => flattenOptions(mergedOptions.value, props));
|
||||
|
||||
const getValueOption = useCacheOptions(mergedRawValue.value, mergedFlattenOptions);
|
||||
const getValueOption = useCacheOptions(mergedFlattenOptions);
|
||||
|
||||
// Display options for OptionList
|
||||
const displayOptions = computed<OptionsType>(() => {
|
||||
|
@ -484,6 +495,9 @@ export default function generateSelector<
|
|||
key: '__RC_SELECT_TAG_PLACEHOLDER__',
|
||||
});
|
||||
}
|
||||
if (props.filterSort && Array.isArray(filteredOptions)) {
|
||||
return ([...filteredOptions] as OptionsType).sort(props.filterSort);
|
||||
}
|
||||
|
||||
return filteredOptions;
|
||||
});
|
||||
|
@ -507,7 +521,7 @@ export default function generateSelector<
|
|||
const valueOptions = getValueOption([val]);
|
||||
const displayValue = getLabeledValue(val, {
|
||||
options: valueOptions,
|
||||
prevValue: mergedValue.value,
|
||||
prevValueMap: mergedValueMap.value,
|
||||
labelInValue: mergedLabelInValue.value,
|
||||
optionLabelProp: mergedOptionLabelProp.value,
|
||||
});
|
||||
|
@ -542,7 +556,7 @@ export default function generateSelector<
|
|||
const selectValue = (mergedLabelInValue.value
|
||||
? getLabeledValue(newValue, {
|
||||
options: newValueOption,
|
||||
prevValue: mergedValue.value,
|
||||
prevValueMap: mergedValueMap.value,
|
||||
labelInValue: mergedLabelInValue.value,
|
||||
optionLabelProp: mergedOptionLabelProp.value,
|
||||
})
|
||||
|
@ -583,7 +597,7 @@ export default function generateSelector<
|
|||
labelInValue: mergedLabelInValue.value,
|
||||
options: newRawValuesOptions,
|
||||
getLabeledValue,
|
||||
prevValue: mergedValue.value,
|
||||
prevValueMap: mergedValueMap.value,
|
||||
optionLabelProp: mergedOptionLabelProp.value,
|
||||
});
|
||||
|
||||
|
@ -770,6 +784,10 @@ export default function generateSelector<
|
|||
// 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
|
||||
const onSearchSubmit = (searchText: string) => {
|
||||
// prevent empty tags from appearing when you click the Enter button
|
||||
if (!searchText || !searchText.trim()) {
|
||||
return;
|
||||
}
|
||||
const newRawValues = Array.from(
|
||||
new Set<RawValueType>([...mergedRawValue.value, searchText]),
|
||||
);
|
||||
|
@ -815,9 +833,17 @@ export default function generateSelector<
|
|||
const onInternalKeyDown = (event: KeyboardEvent) => {
|
||||
const clearLock = getClearLock();
|
||||
const { which } = event;
|
||||
// We only manage open state here, close logic should handle by list component
|
||||
if (!mergedOpen.value && which === KeyCode.ENTER) {
|
||||
onToggleOpen(true);
|
||||
|
||||
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
|
||||
if (!mergedOpen.value) {
|
||||
onToggleOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
setClearLock(!!mergedSearchValue.value);
|
||||
|
@ -922,7 +948,6 @@ export default function generateSelector<
|
|||
const onInternalMouseDown = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
const popupElement: HTMLDivElement = triggerRef.value && triggerRef.value.getPopupElement();
|
||||
|
||||
// We should give focus back to selector if clicked item is not focusable
|
||||
if (popupElement && popupElement.contains(target as HTMLElement)) {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
|
@ -933,7 +958,7 @@ export default function generateSelector<
|
|||
|
||||
cancelSetMockFocused();
|
||||
|
||||
if (!popupElement.contains(document.activeElement)) {
|
||||
if (!mobile.value && !popupElement.contains(document.activeElement)) {
|
||||
selectorRef.value.focus();
|
||||
}
|
||||
});
|
||||
|
@ -993,6 +1018,7 @@ export default function generateSelector<
|
|||
return {
|
||||
focus,
|
||||
blur,
|
||||
scrollTo: listRef.value?.scrollTo,
|
||||
tokenWithEnter,
|
||||
mockFocused,
|
||||
mergedId,
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function useCacheDisplayValue(
|
|||
|
||||
const resultValues = values.value.map(item => {
|
||||
const cacheLabel = valueLabels.get(item.value);
|
||||
if (item.value === item.label && cacheLabel) {
|
||||
if (item.isCacheable && cacheLabel) {
|
||||
return {
|
||||
...item,
|
||||
label: cacheLabel,
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function useCacheOptions<
|
|||
key?: Key;
|
||||
disabled?: boolean;
|
||||
}[]
|
||||
>(_values: RawValueType[], options: Ref) {
|
||||
>(options: Ref) {
|
||||
const optionMap = computed(() => {
|
||||
const map: Map<RawValueType, FlattenOptionsType<OptionsType>[number]> = new Map();
|
||||
options.value.forEach((item: any) => {
|
||||
|
|
|
@ -6,7 +6,11 @@ export default function useSelectTriggerControl(
|
|||
triggerOpen: (open: boolean) => void,
|
||||
) {
|
||||
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()];
|
||||
if (
|
||||
open.value &&
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { VueNode } from '../../_util/type';
|
||||
import { VNodeChild } from 'vue';
|
||||
|
||||
export type SelectSource = 'option' | 'selection' | 'input';
|
||||
|
||||
|
@ -13,7 +12,8 @@ export type RawValueType = string | number | null;
|
|||
export interface LabelValueType extends Record<string, any> {
|
||||
key?: Key;
|
||||
value?: RawValueType;
|
||||
label?: VNodeChild;
|
||||
label?: VueNode;
|
||||
isCacheable?: boolean;
|
||||
}
|
||||
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 OnClear = () => void;
|
||||
export type OnClear = () => any;
|
||||
|
||||
export type CustomTagProps = {
|
||||
label: DefaultValueType;
|
||||
label: VueNode;
|
||||
value: DefaultValueType;
|
||||
disabled: boolean;
|
||||
onClose: (event?: MouseEvent) => void;
|
||||
|
@ -38,7 +38,7 @@ export type GetLabeledValue<FOT extends FlattenOptionsType> = (
|
|||
value: RawValueType,
|
||||
config: {
|
||||
options: FOT;
|
||||
prevValue: DefaultValueType;
|
||||
prevValueMap: Map<RawValueType, LabelValueType>;
|
||||
labelInValue: boolean;
|
||||
optionLabelProp: string;
|
||||
},
|
||||
|
|
|
@ -19,20 +19,28 @@ export function toArray<T>(value: T | T[]): T[] {
|
|||
export function toInnerValue(
|
||||
value: DefaultValueType,
|
||||
{ labelInValue, combobox }: { labelInValue: boolean; combobox: boolean },
|
||||
): RawValueType[] {
|
||||
): [RawValueType[], Map<RawValueType, LabelValueType>] {
|
||||
const valueMap = new Map<RawValueType, LabelValueType>();
|
||||
if (value === undefined || (value === '' && combobox)) {
|
||||
return [];
|
||||
return [[], valueMap];
|
||||
}
|
||||
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
|
||||
let rawValues = values as RawValueType[];
|
||||
|
||||
if (labelInValue) {
|
||||
return (values as LabelValueType[]).map(({ key, value: val }: LabelValueType) =>
|
||||
val !== undefined ? val : key,
|
||||
);
|
||||
rawValues = (values as LabelValueType[])
|
||||
.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,
|
||||
labelInValue,
|
||||
prevValue,
|
||||
prevValueMap,
|
||||
options,
|
||||
getLabeledValue,
|
||||
}: {
|
||||
|
@ -51,7 +59,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
|
|||
labelInValue: boolean;
|
||||
getLabeledValue: GetLabeledValue<FOT>;
|
||||
options: FOT;
|
||||
prevValue: DefaultValueType;
|
||||
prevValueMap: Map<RawValueType, LabelValueType>;
|
||||
},
|
||||
): RawValueType[] | LabelValueType[] | DefaultValueType {
|
||||
let values: DefaultValueType = valueList;
|
||||
|
@ -60,7 +68,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
|
|||
values = values.map(val =>
|
||||
getLabeledValue(val, {
|
||||
options,
|
||||
prevValue,
|
||||
prevValueMap,
|
||||
labelInValue,
|
||||
optionLabelProp,
|
||||
}),
|
||||
|
|
|
@ -120,24 +120,14 @@ export function findValueOption(
|
|||
|
||||
export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
|
||||
value,
|
||||
{ options, prevValue, labelInValue, optionLabelProp },
|
||||
{ options, prevValueMap, labelInValue, optionLabelProp },
|
||||
) => {
|
||||
const item = findValueOption([value], options)[0];
|
||||
const result: LabelValueType = {
|
||||
value,
|
||||
};
|
||||
|
||||
let prevValItem: LabelValueType;
|
||||
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;
|
||||
}
|
||||
const prevValItem: LabelValueType = labelInValue ? prevValueMap.get(value) : undefined;
|
||||
|
||||
if (prevValItem && typeof prevValItem === 'object' && 'label' in prevValItem) {
|
||||
result.label = prevValItem.label;
|
||||
|
@ -160,6 +150,7 @@ export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
|
|||
}
|
||||
} else {
|
||||
result.label = value;
|
||||
result.isCacheable = true;
|
||||
}
|
||||
|
||||
// Used for motion control
|
||||
|
@ -211,7 +202,7 @@ export function filterOptions(
|
|||
let filterFunc: FilterFunc<SelectOptionsType[number]>;
|
||||
|
||||
if (filterOption === false) {
|
||||
return options;
|
||||
return [...options];
|
||||
}
|
||||
if (typeof filterOption === 'function') {
|
||||
filterFunc = filterOption;
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -37,5 +37,6 @@ declare module 'vue' {
|
|||
onKeydown?: EventHandler;
|
||||
onKeyup?: EventHandler;
|
||||
onDeselect?: EventHandler;
|
||||
onClear?: EventHandler;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue