✨(component): [tagInput]添加事件
This commit is contained in:
parent
4e9604377b
commit
af86d77e17
@ -16,12 +16,7 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
nextTick,
|
nextTick,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import {
|
import { isObject, reactiveOmit, useResizeObserver } from "@vueuse/core";
|
||||||
isObject,
|
|
||||||
reactiveOmit,
|
|
||||||
useResizeObserver,
|
|
||||||
useVModel,
|
|
||||||
} from "@vueuse/core";
|
|
||||||
import { LayIcon } from "@layui/icons-vue";
|
import { LayIcon } from "@layui/icons-vue";
|
||||||
|
|
||||||
export interface TagData {
|
export interface TagData {
|
||||||
@ -31,7 +26,7 @@ export interface TagData {
|
|||||||
[other: string]: any;
|
[other: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayInputTagProps {
|
export interface LayTagInputProps {
|
||||||
modelValue?: (string | number | TagData)[];
|
modelValue?: (string | number | TagData)[];
|
||||||
inputValue?: string;
|
inputValue?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -46,13 +41,23 @@ export interface LayInputTagProps {
|
|||||||
disabledInput?: boolean;
|
disabledInput?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<LayInputTagProps>(), {
|
const props = withDefaults(defineProps<LayTagInputProps>(), {
|
||||||
placeholder: undefined,
|
placeholder: undefined,
|
||||||
minCollapsedNum: 0,
|
minCollapsedNum: 0,
|
||||||
size: "md",
|
size: "md",
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "update:inputValue"]);
|
const emit = defineEmits([
|
||||||
|
"update:modelValue",
|
||||||
|
"update:inputValue",
|
||||||
|
"change",
|
||||||
|
"inputValueChange",
|
||||||
|
"remove",
|
||||||
|
"clear",
|
||||||
|
"focus",
|
||||||
|
"blur",
|
||||||
|
"pressEnter",
|
||||||
|
]);
|
||||||
|
|
||||||
const mirrorRefEl = shallowRef<HTMLElement | undefined>(undefined);
|
const mirrorRefEl = shallowRef<HTMLElement | undefined>(undefined);
|
||||||
const inputRefEl = shallowRef<HTMLInputElement | undefined>(undefined);
|
const inputRefEl = shallowRef<HTMLInputElement | undefined>(undefined);
|
||||||
@ -60,13 +65,27 @@ const oldInputValue = ref<string>("");
|
|||||||
const compositionValue = ref<string>("");
|
const compositionValue = ref<string>("");
|
||||||
const isComposing = ref(false);
|
const isComposing = ref(false);
|
||||||
const inputStyle = reactive({ width: "15px" });
|
const inputStyle = reactive({ width: "15px" });
|
||||||
const inputValue = useVModel(props, "inputValue", emit, { defaultValue: "" });
|
|
||||||
const tagData = useVModel(props, "modelValue", emit, {
|
|
||||||
deep: true,
|
|
||||||
defaultValue: [] as TagData[],
|
|
||||||
});
|
|
||||||
const _tagProps = reactive(props.tagProps ?? {});
|
const _tagProps = reactive(props.tagProps ?? {});
|
||||||
const tagProps = reactiveOmit(_tagProps, "closable", "size", "disabled");
|
const tagProps = reactiveOmit(_tagProps, "closable", "size", "disabled");
|
||||||
|
const inputValue = computed({
|
||||||
|
get() {
|
||||||
|
return props.inputValue;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit("update:inputValue", val);
|
||||||
|
emit("inputValueChange", val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tagData = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue;
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit("update:modelValue", val);
|
||||||
|
emit("change", val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const normalizedTags = computed(() => normalizedTagData(tagData.value ?? []));
|
const normalizedTags = computed(() => normalizedTagData(tagData.value ?? []));
|
||||||
|
|
||||||
@ -85,26 +104,26 @@ const collapsedTagData = computed(() => {
|
|||||||
: [];
|
: [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleInput = function (event: Event) {
|
const handleInput = function (e: Event) {
|
||||||
if (isComposing.value) {
|
if (isComposing.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
inputValue.value = (event.target as HTMLInputElement).value;
|
inputValue.value = (e.target as HTMLInputElement).value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleComposition = (event: CompositionEvent) => {
|
const handleComposition = (e: CompositionEvent) => {
|
||||||
if (event.type === "compositionend") {
|
if (e.type === "compositionend") {
|
||||||
isComposing.value = false;
|
isComposing.value = false;
|
||||||
compositionValue.value = "";
|
compositionValue.value = "";
|
||||||
handleInput(event);
|
handleInput(e);
|
||||||
} else {
|
} else {
|
||||||
isComposing.value = true;
|
isComposing.value = true;
|
||||||
compositionValue.value = inputValue.value + (event.data ?? "");
|
compositionValue.value = inputValue.value + (e.data ?? "");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnter = (event: KeyboardEvent) => {
|
const handleEnter = (e: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
e.preventDefault();
|
||||||
const valueStr = inputValue.value ? String(inputValue.value).trim() : "";
|
const valueStr = inputValue.value ? String(inputValue.value).trim() : "";
|
||||||
if (!valueStr || !tagData.value) return;
|
if (!valueStr || !tagData.value) return;
|
||||||
const isLimit = props.max && tagData.value?.length >= props.max;
|
const isLimit = props.max && tagData.value?.length >= props.max;
|
||||||
@ -115,21 +134,25 @@ const handleEnter = (event: KeyboardEvent) => {
|
|||||||
: [valueStr];
|
: [valueStr];
|
||||||
inputValue.value = "";
|
inputValue.value = "";
|
||||||
}
|
}
|
||||||
|
emit("pressEnter", inputValue.value, e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackspaceKeyUp = (event: KeyboardEvent) => {
|
const handleBackspaceKeyUp = (e: KeyboardEvent) => {
|
||||||
if (!tagData.value || !tagData.value.length) return;
|
if (!tagData.value || !tagData.value.length) return;
|
||||||
if (!oldInputValue.value && ["Backspace", "Delete"].includes(event.code)) {
|
if (!oldInputValue.value && ["Backspace", "Delete"].includes(e.code)) {
|
||||||
tagData.value = tagData.value.slice(0, -1);
|
const lastIndex = normalizedTags.value.length - 1;
|
||||||
|
handleClose(normalizedTags.value[lastIndex].value, lastIndex, e);
|
||||||
}
|
}
|
||||||
oldInputValue.value = inputValue.value ?? "";
|
oldInputValue.value = inputValue.value ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocus = () => {
|
const handleFocus = (e: FocusEvent) => {
|
||||||
|
emit("focus", e);
|
||||||
(inputRefEl.value as HTMLInputElement)?.focus();
|
(inputRefEl.value as HTMLInputElement)?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = (e: FocusEvent) => {
|
||||||
|
emit("blur", e);
|
||||||
(inputRefEl.value as HTMLInputElement)?.blur();
|
(inputRefEl.value as HTMLInputElement)?.blur();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,13 +161,26 @@ const handleClearClick = (e: MouseEvent) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tagData.value = [];
|
tagData.value = [];
|
||||||
|
emit("clear", e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (index: number) => {
|
const handleClose = (
|
||||||
|
value: string | number | undefined,
|
||||||
|
index: number,
|
||||||
|
e: Event
|
||||||
|
) => {
|
||||||
if (!tagData.value) return;
|
if (!tagData.value) return;
|
||||||
const arr = [...tagData.value];
|
const arr = [...tagData.value];
|
||||||
arr.splice(index, 1);
|
arr.splice(index, 1);
|
||||||
tagData.value = arr;
|
tagData.value = arr;
|
||||||
|
emit("remove", value, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
if (inputRefEl.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
inputRefEl.value.focus();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setInputWidth = (width: number) => {
|
const setInputWidth = (width: number) => {
|
||||||
@ -210,7 +246,7 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div :class="cls" @click="handleFocus">
|
<div :class="cls" @mousedown="handleMouseDown">
|
||||||
<span ref="mirrorRefEl" class="layui-tag-input-mirror">
|
<span ref="mirrorRefEl" class="layui-tag-input-mirror">
|
||||||
{{ compositionValue || inputValue || placeholder }}
|
{{ compositionValue || inputValue || placeholder }}
|
||||||
</span>
|
</span>
|
||||||
@ -226,7 +262,7 @@ defineExpose({
|
|||||||
v-bind="tagProps"
|
v-bind="tagProps"
|
||||||
:closable="!readonly && !disabled && item.closable"
|
:closable="!readonly && !disabled && item.closable"
|
||||||
:size="size"
|
:size="size"
|
||||||
@close="handleClose(index)"
|
@close="handleClose(item.value, index, $event)"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</LayTag>
|
</LayTag>
|
||||||
@ -249,7 +285,13 @@ defineExpose({
|
|||||||
v-bind="tagProps"
|
v-bind="tagProps"
|
||||||
:closable="!readonly && !disabled && item.closable"
|
:closable="!readonly && !disabled && item.closable"
|
||||||
:size="size"
|
:size="size"
|
||||||
@close="handleClose(index + (minCollapsedNum ?? 0))"
|
@close="
|
||||||
|
handleClose(
|
||||||
|
item.value,
|
||||||
|
index + (minCollapsedNum ?? 0),
|
||||||
|
$event
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</LayTag>
|
</LayTag>
|
||||||
@ -268,6 +310,8 @@ defineExpose({
|
|||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
@keyup="handleBackspaceKeyUp"
|
@keyup="handleBackspaceKeyUp"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
|
@focus="handleFocus"
|
||||||
|
@blur="handleBlur"
|
||||||
@compositionstart="handleComposition"
|
@compositionstart="handleComposition"
|
||||||
@compositionupdate="handleComposition"
|
@compositionupdate="handleComposition"
|
||||||
@compositionend="handleComposition"
|
@compositionend="handleComposition"
|
||||||
|
@ -13,7 +13,14 @@
|
|||||||
::: demo 使用 `lay-tag-input` 标签, 创建标签输入框。
|
::: demo 使用 `lay-tag-input` 标签, 创建标签输入框。
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<lay-tag-input v-model="data1" v-model:inputValue="inputValue1" placeholder="请输入" style="width:320px"></lay-tag-input>
|
<lay-tag-input
|
||||||
|
v-model="data1"
|
||||||
|
v-model:inputValue="inputValue1"
|
||||||
|
allowClear
|
||||||
|
placeholder="请输入"
|
||||||
|
style="width:320px"
|
||||||
|
>
|
||||||
|
</lay-tag-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -220,7 +227,7 @@ export default {
|
|||||||
| size | 输入框大小 |`string` | `md` | `lg` `md` `sm` `xs`|
|
| size | 输入框大小 |`string` | `md` | `lg` `md` `sm` `xs`|
|
||||||
| inputValue | 输入框的值 | `string` | -| - |
|
| inputValue | 输入框的值 | `string` | -| - |
|
||||||
| placeholder | 占位符 | `string` | - | - |
|
| placeholder | 占位符 | `string` | - | - |
|
||||||
| readonly | 是否可关闭 | `boolean` | `false` | `true` `false`|
|
| readonly | 是否可只读 | `boolean` | `false` | `true` `false`|
|
||||||
| allowClear | 允许清空 | `boolean` | `false` | `true` `false`|
|
| allowClear | 允许清空 | `boolean` | `false` | `true` `false`|
|
||||||
| disabled | 是否禁用 | `boolean` | `false` | `true` `false`|
|
| disabled | 是否禁用 | `boolean` | `false` | `true` `false`|
|
||||||
| max | 最大输入标签数量 | `number` | -|-|
|
| max | 最大输入标签数量 | `number` | -|-|
|
||||||
@ -231,6 +238,21 @@ export default {
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::title TagInput 事件
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::table
|
||||||
|
| 名称 | 描述 | 参数 |
|
||||||
|
|------ |----------|-----------|
|
||||||
|
| change | 值改变时触发 | (value: (string | number | TagData)[]) |
|
||||||
|
| inputValueChange |输入框值改变时触发 | (inputValue: string) |
|
||||||
|
| remove|删除标签时触发| (value: string | number, e: Event) |
|
||||||
|
| clear | 点击清除按钮时触发| (e: Event) |
|
||||||
|
| focus | 获得焦点时触发| (e: Event) |
|
||||||
|
| blur | 失去焦点时触发| (e: Event) |
|
||||||
|
| pressEnter| 按下 Enter 键时触发| (inputValue: string, e: Event) |
|
||||||
|
:::
|
||||||
|
|
||||||
:::title TagInput 方法
|
:::title TagInput 方法
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user