131 lines
3.2 KiB
Vue
131 lines
3.2 KiB
Vue
<script lang="ts">
|
|
export default {
|
|
name: "LayTextarea",
|
|
};
|
|
</script>
|
|
|
|
<script setup lang="ts">
|
|
import { LayIcon } from "@layui/icons-vue";
|
|
import { computed, ref, watch } from "vue";
|
|
import { isObject } from "@vueuse/shared";
|
|
import "./index.less";
|
|
|
|
export interface TextareaProps {
|
|
name?: string;
|
|
modelValue?: string;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
showCount?: boolean;
|
|
allowClear?: boolean;
|
|
maxlength?: number;
|
|
autosize?: boolean | { minHeight: number; maxHeight: number };
|
|
}
|
|
|
|
const props = defineProps<TextareaProps>();
|
|
|
|
interface TextareaEmits {
|
|
(e: "blur", event: Event): void;
|
|
(e: "input", value: string): void;
|
|
(e: "update:modelValue", value: string): void;
|
|
(e: "change", value: string): void;
|
|
(e: "focus", event: Event): void;
|
|
(e: "clear"): void;
|
|
}
|
|
|
|
const emit = defineEmits<TextareaEmits>();
|
|
const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
|
const composing = ref(false);
|
|
|
|
const onInput = function (event: Event) {
|
|
const inputElement = event.target as HTMLInputElement;
|
|
emit("input", inputElement.value);
|
|
if (composing.value) {
|
|
return;
|
|
}
|
|
emit("update:modelValue", inputElement.value);
|
|
};
|
|
|
|
const onFocus = function (event: Event) {
|
|
emit("focus", event);
|
|
};
|
|
|
|
const onBlur = function (event: Event) {
|
|
emit("blur", event);
|
|
};
|
|
|
|
const onChange = (event: Event) => {
|
|
const inputElement = event.target as HTMLInputElement;
|
|
emit("change", inputElement.value);
|
|
};
|
|
|
|
const onClear = function () {
|
|
emit("update:modelValue", "");
|
|
emit("clear");
|
|
};
|
|
|
|
const onCompositionstart = () => {
|
|
composing.value = true;
|
|
};
|
|
|
|
const onCompositionend = (event: Event) => {
|
|
composing.value = false;
|
|
onInput(event);
|
|
};
|
|
|
|
const hasContent = computed(() => (props.modelValue as string)?.length > 0);
|
|
|
|
const wordCount = computed(() => {
|
|
let count = String(props.modelValue?.length ?? 0);
|
|
if (props.maxlength) {
|
|
count += "/" + props.maxlength;
|
|
}
|
|
return count;
|
|
});
|
|
|
|
watch(
|
|
[() => props.modelValue, textareaRef],
|
|
() => {
|
|
if (!textareaRef.value || !props.autosize) return;
|
|
const height: number = textareaRef.value?.scrollHeight + 2; // 边框
|
|
if (isObject(props.autosize)) {
|
|
const { minHeight, maxHeight } = props.autosize;
|
|
if (height < minHeight || height > maxHeight) return;
|
|
}
|
|
textareaRef.value!.style.height = "1px";
|
|
textareaRef.value!.style.height = `${
|
|
textareaRef.value?.scrollHeight + 2
|
|
}px`;
|
|
},
|
|
{
|
|
immediate: true,
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="layui-textarea-wrapper">
|
|
<textarea
|
|
ref="textareaRef"
|
|
class="layui-textarea"
|
|
:value="modelValue"
|
|
:placeholder="placeholder"
|
|
:name="name"
|
|
:disabled="disabled"
|
|
:maxlength="maxlength"
|
|
:class="{ 'layui-textarea-disabled': disabled }"
|
|
@compositionstart="onCompositionstart"
|
|
@compositionend="onCompositionend"
|
|
@input="onInput"
|
|
@focus="onFocus"
|
|
@change="onChange"
|
|
@blur="onBlur"
|
|
></textarea>
|
|
<span class="layui-textarea-clear" v-if="allowClear && hasContent">
|
|
<lay-icon type="layui-icon-close-fill" @click="onClear"></lay-icon>
|
|
</span>
|
|
<div v-if="showCount" class="layui-texterea-count">
|
|
{{ wordCount }}
|
|
</div>
|
|
</div>
|
|
</template>
|