Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions components/_util/use/useModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const useNormalModel = <
> = ModelValuePropKey,
EventName extends string = string,
>(
props: Props,
emit: (eventName: EventName, ...args: any[]) => void,
config: UseNormalModelOptions<Props, Key> = {},
): [WritableComputedRef<Props[Key]>, (val: Props[Key]) => void] => {
props: Props,
emit: (eventName: EventName, ...args: any[]) => void,
config: UseNormalModelOptions<Props, Key> = {},
): [WritableComputedRef<Props[Key]>, (val: Props[Key]) => void] => {
const {
prop = 'modelValue',
deep = false,
Expand Down Expand Up @@ -106,13 +106,13 @@ export const useArrayModel = <
> = Extract<ModelValuePropKey, GetKeysIsArrayType<Props>>,
EventName extends string = string,
>(
props: Props,
emit: (eventName: EventName, ...args: any[]) => void,
config: UseNormalModelOptions<Props, Key> = {},
): [
WritableComputedRef<Props[Key]>,
(val: ArrayOrItem<Props[Key]>) => void,
] => {
props: Props,
emit: (eventName: EventName, ...args: any[]) => void,
config: UseNormalModelOptions<Props, Key> = {},
): [
WritableComputedRef<Props[Key]>,
(val: ArrayOrItem<Props[Key]>) => void,
] => {
const [computedValue, updateCurrentValue] = useNormalModel(props, emit, {
...config,
defaultValue: [] as Props[Key],
Expand Down
4 changes: 4 additions & 0 deletions components/select/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export const selectProps = {
type: Boolean,
default: false,
},
passive: {
type: Boolean,
default: true,
},
popperClass: [String, Array, Object] as PropType<string | [] | object>,
triggerClass: [String, Array, Object] as PropType<string | [] | object>,
triggerStyle: [Object, String] as PropType<CSSProperties | string>,
Expand Down
17 changes: 8 additions & 9 deletions components/select/select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@
</template>

<script lang="ts">
import { type CSSProperties, computed, defineComponent, provide, ref, unref, watch } from 'vue';
import { computed, defineComponent, provide, ref, unref, watch } from 'vue';
import type { CSSProperties } from 'vue';
import { isNil } from 'lodash-es';
import { useTheme } from '../_theme/useTheme';
import { type UseArrayModelReturn, useArrayModel, useNormalModel } from '../_util/use/useModel';
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../_util/constants';
import useFormAdaptor from '../_util/use/useFormAdaptor';
import Popper from '../popper';
Expand All @@ -88,6 +88,7 @@ import OptionList from './optionList';
import { selectProps } from './props';
import useOptions from './useOptions';
import type { SelectOption, SelectValue } from './interface';
import { useCurrentValue } from './useCurrentValue';

export default defineComponent({
name: 'FSelect',
Expand All @@ -106,7 +107,7 @@ export default defineComponent({
const innerDisabled = computed(() => props.disabled === true || isFormDisabled.value);
const isOpenedRef = ref(false);
// 与 props 中 modelValue 类型保持一致
const [currentValue, updateCurrentValue] = props.multiple ? (useArrayModel(props, emit) as unknown as UseArrayModelReturn<SelectValue[]>) : useNormalModel(props, emit);
const { currentValue, updateCurrentValue } = useCurrentValue(props, emit);

const triggerRef = ref();
const triggerWidth = ref(0);
Expand All @@ -123,16 +124,15 @@ export default defineComponent({
}
});

const handleChange = () => {
emit(CHANGE_EVENT, unref(currentValue));
const handleChange = (value: SelectValue | SelectValue[]) => {
emit(CHANGE_EVENT, value);
validate(CHANGE_EVENT);
};

const handleClear = () => {
const value: null | [] = props.multiple ? [] : null;
if (props.multiple ? ((currentValue.value as SelectValue[]) || []).length : currentValue.value !== null) {
updateCurrentValue(value);
handleChange();
handleChange(updateCurrentValue(value));
}
filterText.value = '';
cacheOptions.value = [];
Expand Down Expand Up @@ -260,8 +260,7 @@ export default defineComponent({
}
}
}
updateCurrentValue(unref(value));
handleChange();
handleChange(updateCurrentValue(unref(value)));
};

// select-trigger 选择项展示,只在 currentValue 改变时才改变
Expand Down
59 changes: 59 additions & 0 deletions components/select/useCurrentValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useVModel } from '@vueuse/core';
import type { SelectValue } from './interface';
import type { SelectProps } from './props';

export function useCurrentValue(props: SelectProps, emit: (event: 'filter' | 'update:modelValue' | 'focus' | 'blur' | 'change' | 'clear' | 'search' | 'scroll' | 'removeTag' | 'visibleChange', ...args: any[]) => void) {
const currentValue = useVModel(props, 'modelValue', emit, {
passive: props.passive,
deep: true,
defaultValue: props.multiple ? [] : undefined,
});

function updateCurrentValue(value: SelectValue | SelectValue[]): SelectValue | SelectValue[] {
if (!props.multiple) {
currentValue.value = value;
return value;
} else {
if (Array.isArray(value)) {
currentValue.value = value;
return value;
}

// 兼容重复赋值为不符合预期数据类型的场景
let val: SelectValue[] = [];
if (!Array.isArray(currentValue.value)) {
console.warn(
'[useArrayModel] 绑定值类型不匹配, 仅支持数组类型, value:',
currentValue.value,
);
val = [];
} else {
val = [...currentValue.value];
}

const index = val.indexOf(value);
if (index !== -1) {
val.splice(index, 1);
} else {
val.push(value);
}
currentValue.value = val;
return val;
}
};

if (props.multiple) {
if (!Array.isArray(currentValue.value)) {
console.warn(
'[useArrayModel] 绑定值类型不匹配, 仅支持数组类型, value:',
props.modelValue,
);
currentValue.value = [];
}
}

return {
currentValue,
updateCurrentValue,
};
}
7 changes: 7 additions & 0 deletions docs/.vitepress/components/select/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ addon.vue
selectGroupOption.vue
:::

### 受控模式

:::demo
passive.vue
:::

## Select Props

| 属性 | 说明 | 类型 | 默认值 |
Expand All @@ -156,6 +162,7 @@ selectGroupOption.vue
| tag | 是否可以创建新的选项,需要和 `filterable` 一起使用 | boolean | `false` |
| remote | 是否远程搜索,当输入内容时触发`search`事件 | boolean | `false` |
| options | 选项配置 | array\<SelectOption\> | `[]` |
| passive | 是否受控模式,true-非受控,false-受控 | boolean | `true` |
| virtualScroll | 虚拟滚动 | boolean / number | `true` |
| valueField | 替代 `SelectOption` 中的 `value` 字段名 | string | `value` |
| labelField | 替代 `SelectOption` 中的 `label` 字段名 | string | `label` |
Expand Down
52 changes: 52 additions & 0 deletions docs/.vitepress/components/select/passive.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<FSpace vertical>
<FSelect :modelValue="singleSelect" :passive="false" @change="changeSingle">
<FOption
v-for="(item, index) in optionList"
:key="index"
:value="item.value"
:label="item.label"
/>
</FSelect>
</FSpace>
</template>

<script setup>
import { reactive, ref } from 'vue';

const optionList = reactive([
{
value: 'HuNan',
label: '湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南湖南',
},
{
value: 'HuBei',
label: '湖北',
disabled: true,
},
{
value: 'ZheJiang',
label: '浙江',
},
{
value: 'GuangDong',
label: '广东',
},
{
value: 'JiangSu',
label: '江苏',
},
]);

const singleSelect = ref();
function changeSingle(value) {
console.log(value);
singleSelect.value = value;
}
</script>

<style scoped>
.fes-select {
width: 200px;
}
</style>