Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-textarea/cl-textarea.uvue
2025-08-20 17:05:17 +08:00

408 lines
7.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view
class="cl-textarea"
:class="[
pt.className,
{
'is-dark': isDark,
'cl-textarea--border': border,
'cl-textarea--focus': isFocus,
'cl-textarea--disabled': isDisabled,
'cl-textarea--error': isError
}
]"
@tap="onTap"
>
<textarea
class="cl-textarea__inner"
:class="[
{
'is-disabled': isDisabled,
'is-dark': isDark
},
ptClassName
]"
:style="textareaStyle"
:value="value"
:name="name"
:disabled="readonly ?? isDisabled"
:placeholder="placeholder"
:placeholder-class="`text-surface-400 ${placeholderClass}`"
:maxlength="maxlength"
:auto-focus="isFocus"
:cursor="cursor"
:cursor-spacing="cursorSpacing"
:cursor-color="cursorColor"
:show-confirm-bar="showConfirmBar"
:confirm-hold="confirmHold"
:auto-height="autoHeight"
:fixed="fixed"
:adjust-position="adjustPosition"
:hold-keyboard="holdKeyboard"
:inputmode="inputmode"
:disable-default-padding="disableDefaultPadding"
:adjust-keyboard-to="adjustKeyboardTo"
@confirm="onConfirm"
@input="onInput"
@linechange="onLineChange"
@blur="onBlur"
@focus="onFocus"
@keyboardheightchange="onKeyboardheightchange"
/>
<cl-text
:pt="{ className: 'absolute right-2 bottom-2 !text-xs !text-surface-400' }"
v-if="showWordLimit"
>{{ value.length }} / {{ maxlength }}</cl-text
>
</view>
</template>
<script setup lang="ts">
import { computed, nextTick, ref, watch, type PropType } from "vue";
import { parsePt, parseRpx } from "@/cool";
import type { PassThroughProps } from "../../types";
import { isDark } from "@/cool";
import { t } from "@/locale";
import { useForm, useFormItem, useSize } from "../../hooks";
defineOptions({
name: "cl-textarea"
});
// 组件属性定义
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 绑定值
modelValue: {
type: String,
default: ""
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否只读
readonly: {
type: Boolean,
default: null
},
// 是否显示字数统计
showWordLimit: {
type: Boolean,
default: true
},
// 名称
name: {
type: String,
default: ""
},
// 占位符
placeholder: {
type: String,
default: () => t("请输入")
},
// 占位符样式类
placeholderClass: {
type: String,
default: ""
},
// 最大输入长度
maxlength: {
type: Number,
default: 140
},
// 是否自动聚焦
autofocus: {
type: Boolean,
default: false
},
// 设置键盘右下角按钮的文字
confirmType: {
type: String as PropType<"done" | "go" | "next" | "search" | "send">,
default: "done"
},
// 指定focus时的光标位置
cursor: {
type: Number,
default: 0
},
// 点击键盘确认按钮时是否保持键盘不收起
confirmHold: {
type: Boolean,
default: false
},
// 高度
height: {
type: [Number, String],
default: 120
},
// 是否自动增高
autoHeight: {
type: Boolean,
default: false
},
// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
fixed: {
type: Boolean,
default: false
},
// 光标与键盘的距离
cursorSpacing: {
type: Number,
default: 5
},
// 指定光标颜色
cursorColor: {
type: String,
default: ""
},
// 是否显示键盘上方带有”完成“按钮那一栏
showConfirmBar: {
type: Boolean,
default: true
},
// 光标起始位置
selectionStart: {
type: Number,
default: -1
},
// 光标结束位置
selectionEnd: {
type: Number,
default: -1
},
// 盘弹起时,是否自动上推页面
adjustPosition: {
type: Boolean,
default: true
},
// 它提供了用户在编辑元素或其内容时可能输入的数据类型的提示。
inputmode: {
type: String as PropType<
"none" | "text" | "decimal" | "numeric" | "tel" | "search" | "email" | "url"
>,
default: "text"
},
// focus时点击页面的时候不收起键盘
holdKeyboard: {
type: Boolean,
default: false
},
// 是否禁用默认内边距
disableDefaultPadding: {
type: Boolean,
default: true
},
// 键盘对齐位置
adjustKeyboardTo: {
type: String as PropType<"cursor" | "bottom">,
default: "cursor"
}
});
// 事件定义
const emit = defineEmits([
"update:modelValue",
"input",
"change",
"focus",
"blur",
"confirm",
"linechange",
"keyboardheightchange"
]);
// cl-form 上下文
const { disabled } = useForm();
// cl-form-item 上下文
const { isError } = useFormItem();
// 是否禁用
const isDisabled = computed(() => {
return disabled.value || props.disabled;
});
// 透传样式类型定义
type PassThrough = {
className?: string;
inner?: PassThroughProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 文本框样式
const textareaStyle = computed(() => {
const style = {
height: parseRpx(props.height)
};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
// 绑定值
const value = ref(props.modelValue);
// 是否聚焦
const isFocus = ref<boolean>(props.autofocus);
// 获取焦点事件
function onFocus(e: UniTextareaFocusEvent) {
isFocus.value = true;
emit("focus", e);
}
// 失去焦点事件
function onBlur(e: UniTextareaBlurEvent) {
emit("blur", e);
setTimeout(() => {
isFocus.value = false;
}, 0);
}
// 输入事件
function onInput(e: UniInputEvent) {
const v1 = e.detail.value;
const v2 = value.value;
value.value = v1;
emit("update:modelValue", v1);
emit("input", e);
if (v1 != v2) {
emit("change", v1);
}
}
// 点击确认按钮事件
function onConfirm(e: UniInputConfirmEvent) {
emit("confirm", e);
}
// 键盘高度变化事件
function onKeyboardheightchange(e: UniInputKeyboardHeightChangeEvent) {
emit("keyboardheightchange", e);
}
// 行数变化事件
function onLineChange(e: UniTextareaLineChangeEvent) {
emit("linechange", e);
}
// 点击事件
function onTap() {
if (isDisabled.value) {
return;
}
isFocus.value = true;
}
// 聚焦方法
function focus() {
setTimeout(() => {
isFocus.value = false;
nextTick(() => {
isFocus.value = true;
});
}, 0);
}
watch(
computed(() => props.modelValue),
(val: string) => {
value.value = val;
}
);
defineExpose({
isFocus,
focus
});
</script>
<style lang="scss" scoped>
.cl-textarea {
@apply flex flex-row items-center bg-white;
@apply rounded-lg;
padding: 16rpx 20rpx;
transition-property: border-color, background-color;
transition-duration: 0.2s;
:deep(.uni-textarea-compute) {
opacity: 0;
}
&__inner {
@apply h-full text-surface-700;
flex: 1;
font-size: 28rpx;
&.is-dark {
@apply text-white;
}
}
&__icon {
@apply flex items-center justify-center h-full;
padding-left: 20rpx;
}
&--border {
@apply border border-solid border-surface-200;
}
&--focus {
@apply border-primary-500;
}
&--disabled {
@apply bg-surface-100 opacity-70;
}
&--error {
@apply border-red-500;
}
&.is-dark {
@apply bg-surface-800;
&.cl-textarea--border {
@apply border-surface-700;
&.cl-textarea--focus {
@apply border-primary-500;
}
}
&.cl-textarea--disabled {
@apply bg-surface-700;
}
}
}
</style>