132 lines
3.7 KiB
TypeScript
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>
|
|
)
|
|
}
|
|
},
|
|
})
|