@arco-design/web-vue
Version:
Arco Design Vue 2.0: A Vue.js 3 UI Library
392 lines (391 loc) • 13.8 kB
JavaScript
import { defineComponent, toRefs, ref, computed, watch, onMounted, nextTick, resolveComponent, openBlock, createElementBlock, mergeProps, normalizeClass, normalizeStyle, toDisplayString, createCommentVNode, createVNode, withCtx, createElementVNode, renderSlot } from "vue";
import ResizeObserver from "../_components/resize-observer.js";
import IconHover from "../_components/icon-hover.js";
import IconClose from "../icon/icon-close/index.js";
import { getPrefixCls } from "../_utils/global-config.js";
import { getSizeStyles } from "./utils.js";
import { isUndefined, isNull, isObject, isFunction } from "../_utils/is.js";
import { omit } from "../_utils/omit.js";
import { INPUT_EVENTS } from "../_utils/constant.js";
import pick from "../_utils/pick.js";
import { useFormItem } from "../_hooks/use-form-item.js";
import { useCursor } from "../_hooks/use-cursor.js";
import _export_sfc from "../_virtual/plugin-vue_export-helper.js";
const _sfc_main = defineComponent({
name: "Textarea",
components: { ResizeObserver, IconHover, IconClose },
inheritAttrs: false,
props: {
modelValue: String,
defaultValue: {
type: String,
default: ""
},
placeholder: String,
disabled: {
type: Boolean,
default: false
},
error: {
type: Boolean,
default: false
},
maxLength: {
type: [Number, Object],
default: 0
},
showWordLimit: {
type: Boolean,
default: false
},
allowClear: {
type: Boolean,
default: false
},
autoSize: {
type: [Boolean, Object],
default: false
},
wordLength: {
type: Function
},
wordSlice: {
type: Function
},
textareaAttrs: {
type: Object
}
},
emits: {
"update:modelValue": (value) => true,
"input": (value, ev) => true,
"change": (value, ev) => true,
"clear": (ev) => true,
"focus": (ev) => true,
"blur": (ev) => true
},
setup(props, { emit, attrs }) {
const { disabled, error, modelValue } = toRefs(props);
const prefixCls = getPrefixCls("textarea");
const {
mergedDisabled,
mergedError: _mergedError,
eventHandlers
} = useFormItem({ disabled, error });
const textareaRef = ref();
const textareaStyle = ref();
const mirrorRef = ref();
const mirrorStyle = ref();
const _value = ref(props.defaultValue);
const computedValue = computed(() => {
var _a;
return (_a = modelValue.value) != null ? _a : _value.value;
});
const [recordCursor, setCursor] = useCursor(textareaRef);
watch(modelValue, (value) => {
if (isUndefined(value) || isNull(value)) {
_value.value = "";
}
});
const maxLengthErrorOnly = computed(
() => isObject(props.maxLength) && Boolean(props.maxLength.errorOnly)
);
const computedMaxLength = computed(() => {
if (isObject(props.maxLength)) {
return props.maxLength.length;
}
return props.maxLength;
});
const getValueLength = (value) => {
var _a;
if (isFunction(props.wordLength)) {
return props.wordLength(value);
}
return (_a = value.length) != null ? _a : 0;
};
const valueLength = computed(() => getValueLength(computedValue.value));
const mergedError = computed(
() => _mergedError.value || Boolean(
computedMaxLength.value && maxLengthErrorOnly.value && valueLength.value > computedMaxLength.value
)
);
const isScroll = ref(false);
const focused = ref(false);
const showClearBtn = computed(
() => props.allowClear && !mergedDisabled.value && computedValue.value
);
const isComposition = ref(false);
const compositionValue = ref("");
const keepControl = () => {
recordCursor();
nextTick(() => {
if (textareaRef.value && computedValue.value !== textareaRef.value.value) {
textareaRef.value.value = computedValue.value;
setCursor();
}
});
};
const updateValue = (value, inner = true) => {
var _a, _b;
if (computedMaxLength.value && !maxLengthErrorOnly.value && getValueLength(value) > computedMaxLength.value) {
value = (_b = (_a = props.wordSlice) == null ? void 0 : _a.call(props, value, computedMaxLength.value)) != null ? _b : value.slice(0, computedMaxLength.value);
}
_value.value = value;
if (inner) {
emit("update:modelValue", value);
}
keepControl();
};
let preValue = computedValue.value;
const emitChange = (value, ev) => {
var _a, _b;
if (value !== preValue) {
preValue = value;
emit("change", value, ev);
(_b = (_a = eventHandlers.value) == null ? void 0 : _a.onChange) == null ? void 0 : _b.call(_a, ev);
}
};
const handleFocus = (ev) => {
var _a, _b;
focused.value = true;
preValue = computedValue.value;
emit("focus", ev);
(_b = (_a = eventHandlers.value) == null ? void 0 : _a.onFocus) == null ? void 0 : _b.call(_a, ev);
};
const handleBlur = (ev) => {
var _a, _b;
focused.value = false;
emit("blur", ev);
(_b = (_a = eventHandlers.value) == null ? void 0 : _a.onBlur) == null ? void 0 : _b.call(_a, ev);
emitChange(computedValue.value, ev);
};
const handleComposition = (e) => {
var _a, _b;
const { value } = e.target;
if (e.type === "compositionend") {
isComposition.value = false;
compositionValue.value = "";
if (computedMaxLength.value && !maxLengthErrorOnly.value && computedValue.value.length >= computedMaxLength.value && getValueLength(value) > computedMaxLength.value) {
keepControl();
return;
}
emit("input", value, e);
updateValue(value);
(_b = (_a = eventHandlers.value) == null ? void 0 : _a.onInput) == null ? void 0 : _b.call(_a, e);
} else {
isComposition.value = true;
}
};
const handleInput = (e) => {
var _a, _b;
const { value } = e.target;
if (!isComposition.value) {
if (computedMaxLength.value && !maxLengthErrorOnly.value && computedValue.value.length >= computedMaxLength.value && getValueLength(value) > computedMaxLength.value && e.inputType === "insertText") {
keepControl();
return;
}
emit("input", value, e);
updateValue(value);
(_b = (_a = eventHandlers.value) == null ? void 0 : _a.onInput) == null ? void 0 : _b.call(_a, e);
} else {
compositionValue.value = value;
}
};
const handleClear = (ev) => {
updateValue("");
emitChange("", ev);
emit("clear", ev);
};
watch(modelValue, (value) => {
if (value !== computedValue.value) {
updateValue(value != null ? value : "", false);
}
});
const getWrapperAttrs = (attr) => omit(attrs, INPUT_EVENTS);
const getTextareaAttrs = (attr) => pick(attrs, INPUT_EVENTS);
const textareaAttrs = getTextareaAttrs();
const mergeTextareaAttrs = computed(() => {
const attrs2 = {
...textareaAttrs,
...props.textareaAttrs
};
if (mergedError.value) {
attrs2["aria-invalid"] = true;
}
return attrs2;
});
const wrapperCls = computed(() => [
`${prefixCls}-wrapper`,
{
[`${prefixCls}-focus`]: focused.value,
[`${prefixCls}-disabled`]: mergedDisabled.value,
[`${prefixCls}-error`]: mergedError.value,
[`${prefixCls}-scroll`]: isScroll.value
}
]);
let styleDeclaration;
const lineHeight = ref(0);
const outerHeight = ref(0);
const minHeight = computed(() => {
if (!isObject(props.autoSize) || !props.autoSize.minRows) {
return 0;
}
return props.autoSize.minRows * lineHeight.value + outerHeight.value;
});
const maxHeight = computed(() => {
if (!isObject(props.autoSize) || !props.autoSize.maxRows) {
return 0;
}
return props.autoSize.maxRows * lineHeight.value + outerHeight.value;
});
const getMirrorStyle = () => {
const styles = getSizeStyles(styleDeclaration);
lineHeight.value = Number.parseInt(styles["line-height"] || 0, 10);
outerHeight.value = Number.parseInt(styles["border-width"] || 0, 10) * 2 + Number.parseInt(styles["padding-top"] || 0, 10) + Number.parseInt(styles["padding-bottom"] || 0, 10);
mirrorStyle.value = styles;
nextTick(() => {
var _a;
const mirrorHeight = (_a = mirrorRef.value) == null ? void 0 : _a.offsetHeight;
let height = mirrorHeight != null ? mirrorHeight : 0;
let overflow = "hidden";
if (minHeight.value && height < minHeight.value) {
height = minHeight.value;
}
if (maxHeight.value && height > maxHeight.value) {
height = maxHeight.value;
overflow = "auto";
}
textareaStyle.value = {
height: `${height}px`,
resize: "none",
overflow
};
});
};
onMounted(() => {
if (textareaRef.value) {
styleDeclaration = window.getComputedStyle(textareaRef.value);
if (props.autoSize) {
getMirrorStyle();
}
}
computeIsScroll();
});
const handleResize = () => {
if (props.autoSize && mirrorRef.value) {
getMirrorStyle();
}
computeIsScroll();
};
const handleMousedown = (e) => {
if (textareaRef.value && e.target !== textareaRef.value) {
e.preventDefault();
textareaRef.value.focus();
}
};
const computeIsScroll = () => {
if (textareaRef.value) {
if (textareaRef.value.scrollHeight > textareaRef.value.offsetHeight) {
if (!isScroll.value)
isScroll.value = true;
} else if (isScroll.value) {
isScroll.value = false;
}
}
};
watch(computedValue, () => {
if (props.autoSize && mirrorRef.value) {
getMirrorStyle();
}
computeIsScroll();
});
return {
prefixCls,
wrapperCls,
textareaRef,
textareaStyle,
mirrorRef,
mirrorStyle,
computedValue,
showClearBtn,
valueLength,
computedMaxLength,
mergedDisabled,
mergeTextareaAttrs,
getWrapperAttrs,
getTextareaAttrs,
handleInput,
handleFocus,
handleBlur,
handleComposition,
handleClear,
handleResize,
handleMousedown
};
},
methods: {
focus() {
var _a;
(_a = this.$refs.textareaRef) == null ? void 0 : _a.focus();
},
blur() {
var _a;
(_a = this.$refs.textareaRef) == null ? void 0 : _a.blur();
}
}
});
const _hoisted_1 = ["disabled", "value", "placeholder"];
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_resize_observer = resolveComponent("resize-observer");
const _component_icon_close = resolveComponent("icon-close");
const _component_icon_hover = resolveComponent("icon-hover");
return openBlock(), createElementBlock("div", mergeProps(_ctx.getWrapperAttrs(_ctx.$attrs), {
class: _ctx.wrapperCls,
onMousedown: _cache[7] || (_cache[7] = (...args) => _ctx.handleMousedown && _ctx.handleMousedown(...args))
}), [
_ctx.autoSize ? (openBlock(), createElementBlock("div", {
key: 0,
ref: "mirrorRef",
class: normalizeClass(`${_ctx.prefixCls}-mirror`),
style: normalizeStyle(_ctx.mirrorStyle)
}, toDisplayString(`${_ctx.computedValue}
`), 7)) : createCommentVNode("v-if", true),
createVNode(_component_resize_observer, { onResize: _ctx.handleResize }, {
default: withCtx(() => [
createElementVNode("textarea", mergeProps({ ref: "textareaRef" }, _ctx.mergeTextareaAttrs, {
disabled: _ctx.mergedDisabled,
class: _ctx.prefixCls,
style: _ctx.textareaStyle,
value: _ctx.computedValue,
placeholder: _ctx.placeholder,
onInput: _cache[0] || (_cache[0] = (...args) => _ctx.handleInput && _ctx.handleInput(...args)),
onFocus: _cache[1] || (_cache[1] = (...args) => _ctx.handleFocus && _ctx.handleFocus(...args)),
onBlur: _cache[2] || (_cache[2] = (...args) => _ctx.handleBlur && _ctx.handleBlur(...args)),
onCompositionstart: _cache[3] || (_cache[3] = (...args) => _ctx.handleComposition && _ctx.handleComposition(...args)),
onCompositionupdate: _cache[4] || (_cache[4] = (...args) => _ctx.handleComposition && _ctx.handleComposition(...args)),
onCompositionend: _cache[5] || (_cache[5] = (...args) => _ctx.handleComposition && _ctx.handleComposition(...args))
}), null, 16, _hoisted_1)
]),
_: 1
}, 8, ["onResize"]),
renderSlot(_ctx.$slots, "suffix"),
_ctx.computedMaxLength && _ctx.showWordLimit ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass(`${_ctx.prefixCls}-word-limit`)
}, toDisplayString(_ctx.valueLength) + "/" + toDisplayString(_ctx.computedMaxLength), 3)) : createCommentVNode("v-if", true),
_ctx.showClearBtn ? (openBlock(), createElementBlock("div", {
key: 2,
class: normalizeClass(`${_ctx.prefixCls}-clear-btn`),
onClick: _cache[6] || (_cache[6] = (...args) => _ctx.handleClear && _ctx.handleClear(...args))
}, [
createVNode(_component_icon_hover, null, {
default: withCtx(() => [
createVNode(_component_icon_close)
]),
_: 1
})
], 2)) : createCommentVNode("v-if", true)
], 16);
}
var _Textarea = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
export { _Textarea as default };