vxe-pc-ui
Version:
A vue based PC component library
331 lines (330 loc) • 12.3 kB
JavaScript
import { defineComponent, h, ref, computed, nextTick, watch, reactive, inject } from 'vue';
import XEUtils from 'xe-utils';
import { getConfig, getI18n, createEvent, useSize } from '../../ui';
import { getFuncText } from '../../ui/src/utils';
let autoTxtElem;
export default defineComponent({
name: 'VxeTextarea',
props: {
modelValue: [String, Number],
className: String,
immediate: {
type: Boolean,
default: true
},
name: String,
readonly: {
type: Boolean,
default: null
},
editable: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: null
},
placeholder: String,
maxLength: [String, Number],
rows: {
type: [String, Number],
default: null
},
cols: {
type: [String, Number],
default: null
},
showWordCount: Boolean,
countMethod: Function,
autosize: [Boolean, Object],
form: String,
resize: {
type: String,
default: () => getConfig().textarea.resize
},
size: {
type: String,
default: () => getConfig().textarea.size || getConfig().size
},
// 已废弃
maxlength: [String, Number]
},
emits: [
'update:modelValue',
'input',
'keydown',
'keyup',
'click',
'change',
'focus',
'blur'
],
setup(props, context) {
const { emit } = context;
const $xeForm = inject('$xeForm', null);
const formItemInfo = inject('xeFormItemInfo', null);
const xID = XEUtils.uniqueId();
const { computeSize } = useSize(props);
const reactData = reactive({
inputValue: props.modelValue
});
const refElem = ref();
const refTextarea = ref();
const refMaps = {
refElem,
refTextarea
};
const $xeTextarea = {
xID,
props,
context,
reactData,
getRefMaps: () => refMaps
};
let 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) => {
const value = reactData.inputValue;
$xeTextarea.dispatchEvent(evnt.type, { value }, evnt);
};
const handleChange = (value, evnt) => {
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) => {
const { immediate } = props;
const textElem = evnt.target;
const value = textElem.value;
reactData.inputValue = value;
if (immediate) {
handleChange(value, evnt);
}
$xeTextarea.dispatchEvent('input', { value }, evnt);
handleResize();
};
const changeEvent = (evnt) => {
const { immediate } = props;
if (immediate) {
triggerEvent(evnt);
}
else {
handleChange(reactData.inputValue, evnt);
}
};
const blurEvent = (evnt) => {
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();
}
});