UNPKG

vxe-pc-ui

Version:
365 lines (329 loc) 11.8 kB
import { defineComponent, h, ref, Ref, computed, nextTick, watch, PropType, reactive, inject } from 'vue' import XEUtils from 'xe-utils' import { getConfig, getI18n, createEvent, useSize } from '../../ui' import { getFuncText } from '../../ui/src/utils' import type { VxeTextareaPropTypes, TextareaReactData, TextareaMethods, VxeTextareaConstructor, VxeTextareaEmits, TextareaPrivateRef, VxeFormConstructor, VxeFormPrivateMethods, VxeFormDefines } from '../../../types' let autoTxtElem: HTMLDivElement export default defineComponent({ name: 'VxeTextarea', props: { modelValue: [String, Number] as PropType<VxeTextareaPropTypes.ModelValue>, className: String as PropType<VxeTextareaPropTypes.ClassName>, immediate: { type: Boolean as PropType<VxeTextareaPropTypes.Immediate>, default: true }, name: String as PropType<VxeTextareaPropTypes.Name>, readonly: { type: Boolean as PropType<VxeTextareaPropTypes.Readonly>, default: null }, editable: { type: Boolean as PropType<VxeTextareaPropTypes.Readonly>, default: true }, disabled: { type: Boolean as PropType<VxeTextareaPropTypes.Disabled>, default: null }, placeholder: String as PropType<VxeTextareaPropTypes.Placeholder>, maxLength: [String, Number] as PropType<VxeTextareaPropTypes.MaxLength>, rows: { type: [String, Number] as PropType<VxeTextareaPropTypes.Rows>, default: null }, cols: { type: [String, Number] as PropType<VxeTextareaPropTypes.Cols>, default: null }, showWordCount: Boolean as PropType<VxeTextareaPropTypes.ShowWordCount>, countMethod: Function as PropType<VxeTextareaPropTypes.CountMethod>, autosize: [Boolean, Object] as PropType<VxeTextareaPropTypes.Autosize>, form: String as PropType<VxeTextareaPropTypes.Form>, resize: { type: String as PropType<VxeTextareaPropTypes.Resize>, default: () => getConfig().textarea.resize }, size: { type: String as PropType<VxeTextareaPropTypes.Size>, default: () => getConfig().textarea.size || getConfig().size }, // 已废弃 maxlength: [String, Number] as PropType<VxeTextareaPropTypes.Maxlength> }, emits: [ 'update:modelValue', 'input', 'keydown', 'keyup', 'click', 'change', 'focus', 'blur' ] as VxeTextareaEmits, setup (props, context) { const { emit } = context const $xeForm = inject<VxeFormConstructor & VxeFormPrivateMethods | null>('$xeForm', null) const formItemInfo = inject<VxeFormDefines.ProvideItemInfo | null>('xeFormItemInfo', null) const xID = XEUtils.uniqueId() const { computeSize } = useSize(props) const reactData = reactive<TextareaReactData>({ inputValue: props.modelValue }) const refElem = ref() as Ref<HTMLDivElement> const refTextarea = ref() as Ref<HTMLTextAreaElement> const refMaps: TextareaPrivateRef = { refElem, refTextarea } const $xeTextarea = { xID, props, context, reactData, getRefMaps: () => refMaps } as unknown as VxeTextareaConstructor let textareaMethods = {} as TextareaMethods const computeFormReadonly = computed(() => { const { readonly } = props if (readonly === null) { if ($xeForm) { return $xeForm.props.readonly } return false } return readonly }) const computeIsDisabled = computed(() => { const { disabled } = props if (disabled === null) { if ($xeForm) { return $xeForm.props.disabled } return false } return disabled }) const computeInputReadonly = computed(() => { const { editable } = props const formReadonly = computeFormReadonly.value return formReadonly || !editable }) const computeInpPlaceholder = computed(() => { const { placeholder } = props if (placeholder) { return getFuncText(placeholder) } const globalPlaceholder = getConfig().textarea.placeholder if (globalPlaceholder) { return getFuncText(globalPlaceholder) } return getI18n('vxe.base.pleaseInput') }) const computeInpMaxLength = computed(() => { const { maxLength, maxlength } = props return maxLength || maxlength }) const computeInputCount = computed(() => { return XEUtils.getSize(reactData.inputValue) }) const computeIsCountError = computed(() => { const inputCount = computeInputCount.value const inpMaxLength = computeInpMaxLength.value return inpMaxLength && inputCount > XEUtils.toNumber(inpMaxLength) }) const computeSizeOpts = computed(() => { return Object.assign({ minRows: 1, maxRows: 10 }, getConfig().textarea.autosize, props.autosize) }) const updateAutoTxt = () => { const { size, autosize } = props const { inputValue } = reactData if (autosize) { if (!autoTxtElem) { autoTxtElem = document.createElement('div') } if (!autoTxtElem.parentNode) { document.body.appendChild(autoTxtElem) } const textElem = refTextarea.value if (!textElem) { return } const textStyle = getComputedStyle(textElem) autoTxtElem.className = ['vxe-textarea--autosize', size ? `size--${size}` : ''].join(' ') autoTxtElem.style.width = `${textElem.clientWidth}px` autoTxtElem.style.padding = textStyle.padding autoTxtElem.innerText = ('' + (inputValue || ' ')).replace(/\n$/, '\n ') } } const handleResize = () => { if (props.autosize) { nextTick(() => { const sizeOpts = computeSizeOpts.value const { minRows, maxRows } = sizeOpts const textElem = refTextarea.value if (!textElem) { return } const sizeHeight = autoTxtElem.clientHeight const textStyle = getComputedStyle(textElem) const lineHeight = XEUtils.toNumber(textStyle.lineHeight) const paddingTop = XEUtils.toNumber(textStyle.paddingTop) const paddingBottom = XEUtils.toNumber(textStyle.paddingBottom) const borderTopWidth = XEUtils.toNumber(textStyle.borderTopWidth) const borderBottomWidth = XEUtils.toNumber(textStyle.borderBottomWidth) const intervalHeight = paddingTop + paddingBottom + borderTopWidth + borderBottomWidth const rowNum = (sizeHeight - intervalHeight) / lineHeight const textRows = rowNum && /[0-9]/.test('' + rowNum) ? rowNum : Math.floor(rowNum) + 1 let vaildRows = textRows if (textRows < minRows) { vaildRows = minRows } else if (textRows > maxRows) { vaildRows = maxRows } textElem.style.height = `${(vaildRows * lineHeight) + intervalHeight}px` }) } } const triggerEvent = (evnt: Event & { type: 'focus' | 'blur' | 'change' }) => { const value = reactData.inputValue $xeTextarea.dispatchEvent(evnt.type, { value }, evnt) } const handleChange = (value: string, evnt: Event) => { reactData.inputValue = value emit('update:modelValue', value) if (XEUtils.toValueString(props.modelValue) !== value) { textareaMethods.dispatchEvent('change', { value }, evnt) // 自动更新校验状态 if ($xeForm && formItemInfo) { $xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, value) } } } const inputEvent = (evnt: InputEvent) => { const { immediate } = props const textElem = evnt.target as HTMLTextAreaElement const value = textElem.value reactData.inputValue = value if (immediate) { handleChange(value, evnt) } $xeTextarea.dispatchEvent('input', { value }, evnt) handleResize() } const changeEvent = (evnt: Event & { type: 'change' }) => { const { immediate } = props if (immediate) { triggerEvent(evnt) } else { handleChange(reactData.inputValue, evnt) } } const blurEvent = (evnt: Event & { type: 'blur' }) => { const { immediate } = props const { inputValue } = reactData if (!immediate) { handleChange(inputValue, evnt) } $xeTextarea.dispatchEvent('blur', { value: inputValue }, evnt) } textareaMethods = { dispatchEvent (type, params, evnt) { emit(type, createEvent(evnt, { $textarea: $xeTextarea }, params)) }, focus () { const textElem = refTextarea.value textElem.focus() return nextTick() }, blur () { const textElem = refTextarea.value textElem.blur() return nextTick() } } Object.assign($xeTextarea, textareaMethods) watch(() => props.modelValue, (val) => { reactData.inputValue = val updateAutoTxt() }) watch(computeSizeOpts, () => { updateAutoTxt() handleResize() }) nextTick(() => { const { autosize } = props if (autosize) { updateAutoTxt() handleResize() } }) const renderVN = () => { const { className, resize, autosize, showWordCount, countMethod, rows, cols } = props const { inputValue } = reactData const vSize = computeSize.value const isDisabled = computeIsDisabled.value const isCountError = computeIsCountError.value const inputCount = computeInputCount.value const inputReadonly = computeInputReadonly.value const formReadonly = computeFormReadonly.value const inpPlaceholder = computeInpPlaceholder.value const inpMaxLength = computeInpMaxLength.value if (formReadonly) { return h('div', { ref: refElem, class: ['vxe-textarea--readonly', className] }, inputValue) } return h('div', { ref: refElem, class: ['vxe-textarea', className, { [`size--${vSize}`]: vSize, 'is--autosize': autosize, 'is--count': showWordCount, 'is--disabled': isDisabled, 'is--rows': !XEUtils.eqNull(rows), 'is--cols': !XEUtils.eqNull(cols) }], spellcheck: false }, [ h('textarea', { ref: refTextarea, class: 'vxe-textarea--inner', value: inputValue, name: props.name, placeholder: inpPlaceholder, maxlength: inpMaxLength, readonly: inputReadonly, disabled: isDisabled, rows, cols, style: resize ? { resize } : null, onInput: inputEvent, onChange: changeEvent, onKeydown: triggerEvent, onKeyup: triggerEvent, onClick: triggerEvent, onFocus: triggerEvent, onBlur: blurEvent }), showWordCount ? h('span', { class: ['vxe-textarea--count', { 'is--error': isCountError }] }, countMethod ? `${countMethod({ value: inputValue })}` : `${inputCount}${inpMaxLength ? `/${inpMaxLength}` : ''}`) : null ]) } $xeTextarea.renderVN = renderVN return $xeTextarea }, render () { return this.renderVN() } })