Files
workspace/code/base-project/base-vue/packages/ao-design/src/textarea/index.tsx
T
2025-09-03 17:31:30 +08:00

132 lines
3.7 KiB
TypeScript

import type { PropType } from 'vue'
import { computed, defineComponent, nextTick, onMounted, ref, watch } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Icon from '../icon'
export default defineComponent({
name: 'Textarea',
props: {
modelValue: { type: String, default: '222' },
autosize: { type: [Boolean, Object] as PropType<boolean | { minRows?: number, maxRows?: number }>, default: false },
disabled: Boolean,
readonly: Boolean,
clearable: Boolean,
showCount: Boolean,
maxlength: Number,
autoFocus: Boolean,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const prefixCls = getPrefixCls('textarea')
const textareaRef = ref<HTMLTextAreaElement | null>(null)
const inputValue = ref(props.modelValue || '')
/** textarea 高度自适应 */
const resizeTextarea = () => {
const el = textareaRef.value
if (!el || !props.autosize)
return
el.style.height = 'auto'
const style = getComputedStyle(el)
const border = Number.parseInt(style.borderTopWidth) + Number.parseInt(style.borderBottomWidth)
let height = el.scrollHeight + border
if (typeof props.autosize === 'object') {
const lineHeight = Number.parseInt(style.lineHeight || '20', 10)
const { minRows, maxRows } = props.autosize
if (minRows)
height = Math.max(height, lineHeight * minRows + border)
if (maxRows)
height = Math.min(height, lineHeight * maxRows + border)
}
el.style.height = `${height}px`
}
/** 同步父组件 modelValue */
watch(
() => props.modelValue,
(val) => {
inputValue.value = val || ''
nextTick(resizeTextarea)
},
)
/** 动态 autoFocus */
watch(
() => props.autoFocus,
(val) => {
if (val && textareaRef.value) {
nextTick(() => textareaRef.value!.focus())
}
},
{ immediate: true },
)
/** 更新值 */
const updateValue = (val: string) => {
if (props.maxlength)
val = val.slice(0, props.maxlength)
inputValue.value = val
emit('update:modelValue', val)
nextTick(resizeTextarea)
}
/** 清空 */
const clearValue = () => {
updateValue('')
if (textareaRef.value)
textareaRef.value.focus()
}
const countText = computed(() => {
const len = inputValue.value.length
return props.maxlength ? `${len} / ${props.maxlength}` : `${len}`
})
onMounted(() => nextTick(() => {
resizeTextarea()
if (props.autoFocus && textareaRef.value)
textareaRef.value.focus()
}))
return () => {
const value = inputValue.value
return (
<div class={`${prefixCls}-wrapper`}>
<textarea
ref={textareaRef}
class={[
`${prefixCls}-inner`,
props.disabled && `${prefixCls}-disabled`,
props.readonly && `${prefixCls}-readonly`,
]}
value={value}
onInput={(e: Event) => updateValue((e.target as HTMLTextAreaElement).value)}
disabled={props.disabled}
readonly={props.readonly}
maxlength={props.maxlength}
/>
{/* Clear 按钮 */}
{props.clearable && value && !props.disabled && !props.readonly && (
<span class={`${prefixCls}-clear`} onClick={clearValue}>
<Icon icon="ic:round-clear" />
</span>
)}
{/* 字数统计 */}
{props.showCount && (
<span class={`${prefixCls}-count`}>
{countText.value}
</span>
)}
</div>
)
}
},
})