import { createTextVNode as _createTextVNode, mergeProps as _mergeProps, createVNode as _createVNode } from "vue"; import { ref, watch, provide, computed, nextTick, reactive, onMounted, defineComponent } from "vue"; import { isDef, extend, addUnit, toArray, FORM_KEY, numericProp, unknownProp, resetScroll, formatNumber, preventDefault, makeStringProp, makeNumericProp, createNamespace } from "../utils/index.mjs"; import { cutString, runSyncRule, endComposing, mapInputType, isEmptyValue, startComposing, getRuleMessage, resizeTextarea, getStringLength, runRuleValidator } from "./utils.mjs"; import { cellSharedProps } from "../cell/Cell.mjs"; import { useParent, useEventListener, CUSTOM_FIELD_INJECTION_KEY } from "@vant/use"; import { useId } from "../composables/use-id.mjs"; import { useExpose } from "../composables/use-expose.mjs"; import { Icon } from "../icon/index.mjs"; import { Cell } from "../cell/index.mjs"; const [name, bem] = createNamespace("field"); const fieldSharedProps = { id: String, name: String, leftIcon: String, rightIcon: String, autofocus: Boolean, clearable: Boolean, maxlength: numericProp, formatter: Function, clearIcon: makeStringProp("clear"), modelValue: makeNumericProp(""), inputAlign: String, placeholder: String, autocomplete: String, errorMessage: String, enterkeyhint: String, clearTrigger: makeStringProp("focus"), formatTrigger: makeStringProp("onChange"), error: { type: Boolean, default: null }, disabled: { type: Boolean, default: null }, readonly: { type: Boolean, default: null } }; const fieldProps = extend({}, cellSharedProps, fieldSharedProps, { rows: numericProp, type: makeStringProp("text"), rules: Array, autosize: [Boolean, Object], labelWidth: numericProp, labelClass: unknownProp, labelAlign: String, showWordLimit: Boolean, errorMessageAlign: String, colon: { type: Boolean, default: null } }); var stdin_default = defineComponent({ name, props: fieldProps, emits: ["blur", "focus", "clear", "keypress", "click-input", "end-validate", "start-validate", "click-left-icon", "click-right-icon", "update:modelValue"], setup(props, { emit, slots }) { const id = useId(); const state = reactive({ status: "unvalidated", focused: false, validateMessage: "" }); const inputRef = ref(); const clearIconRef = ref(); const customValue = ref(); const { parent: form } = useParent(FORM_KEY); const getModelValue = () => { var _a; return String((_a = props.modelValue) != null ? _a : ""); }; const getProp = (key) => { if (isDef(props[key])) { return props[key]; } if (form && isDef(form.props[key])) { return form.props[key]; } }; const showClear = computed(() => { const readonly = getProp("readonly"); if (props.clearable && !readonly) { const hasValue = getModelValue() !== ""; const trigger = props.clearTrigger === "always" || props.clearTrigger === "focus" && state.focused; return hasValue && trigger; } return false; }); const formValue = computed(() => { if (customValue.value && slots.input) { return customValue.value(); } return props.modelValue; }); const runRules = (rules) => rules.reduce((promise, rule) => promise.then(() => { if (state.status === "failed") { return; } let { value } = formValue; if (rule.formatter) { value = rule.formatter(value, rule); } if (!runSyncRule(value, rule)) { state.status = "failed"; state.validateMessage = getRuleMessage(value, rule); return; } if (rule.validator) { if (isEmptyValue(value) && rule.validateEmpty === false) { return; } return runRuleValidator(value, rule).then((result) => { if (result && typeof result === "string") { state.status = "failed"; state.validateMessage = result; } else if (result === false) { state.status = "failed"; state.validateMessage = getRuleMessage(value, rule); } }); } }), Promise.resolve()); const resetValidation = () => { state.status = "unvalidated"; state.validateMessage = ""; }; const endValidate = () => emit("end-validate", { status: state.status }); const validate = (rules = props.rules) => new Promise((resolve) => { resetValidation(); if (rules) { emit("start-validate"); runRules(rules).then(() => { if (state.status === "failed") { resolve({ name: props.name, message: state.validateMessage }); endValidate(); } else { state.status = "passed"; resolve(); endValidate(); } }); } else { resolve(); } }); const validateWithTrigger = (trigger) => { if (form && props.rules) { const { validateTrigger } = form.props; const defaultTrigger = toArray(validateTrigger).includes(trigger); const rules = props.rules.filter((rule) => { if (rule.trigger) { return toArray(rule.trigger).includes(trigger); } return defaultTrigger; }); if (rules.length) { validate(rules); } } }; const limitValueLength = (value) => { const { maxlength } = props; if (isDef(maxlength) && getStringLength(value) > maxlength) { const modelValue = getModelValue(); if (modelValue && getStringLength(modelValue) === +maxlength) { return modelValue; } return cutString(value, +maxlength); } return value; }; const updateValue = (value, trigger = "onChange") => { const originalValue = value; value = limitValueLength(value); const isExceedLimit = value !== originalValue; if (props.type === "number" || props.type === "digit") { const isNumber = props.type === "number"; value = formatNumber(value, isNumber, isNumber); } if (props.formatter && trigger === props.formatTrigger) { value = props.formatter(value); } if (inputRef.value && inputRef.value.value !== value) { if (state.focused && isExceedLimit) { const { selectionStart, selectionEnd } = inputRef.value; inputRef.value.value = value; inputRef.value.setSelectionRange(selectionStart - 1, selectionEnd - 1); } else { inputRef.value.value = value; } } if (value !== props.modelValue) { emit("update:modelValue", value); } }; const onInput = (event) => { if (!event.target.composing) { updateValue(event.target.value); } }; const blur = () => { var _a; return (_a = inputRef.value) == null ? void 0 : _a.blur(); }; const focus = () => { var _a; return (_a = inputRef.value) == null ? void 0 : _a.focus(); }; const adjustTextareaSize = () => { const input = inputRef.value; if (props.type === "textarea" && props.autosize && input) { resizeTextarea(input, props.autosize); } }; const onFocus = (event) => { state.focused = true; emit("focus", event); nextTick(adjustTextareaSize); if (getProp("readonly")) { blur(); } }; const onBlur = (event) => { if (getProp("readonly")) { return; } state.focused = false; updateValue(getModelValue(), "onBlur"); emit("blur", event); validateWithTrigger("onBlur"); nextTick(adjustTextareaSize); resetScroll(); }; const onClickInput = (event) => emit("click-input", event); const onClickLeftIcon = (event) => emit("click-left-icon", event); const onClickRightIcon = (event) => emit("click-right-icon", event); const onClear = (event) => { preventDefault(event); emit("update:modelValue", ""); emit("clear", event); }; const showError = computed(() => { if (typeof props.error === "boolean") { return props.error; } if (form && form.props.showError && state.status === "failed") { return true; } }); const labelStyle = computed(() => { const labelWidth = getProp("labelWidth"); if (labelWidth) { return { width: addUnit(labelWidth) }; } }); const onKeypress = (event) => { const ENTER_CODE = 13; if (event.keyCode === ENTER_CODE) { const submitOnEnter = form && form.props.submitOnEnter; if (!submitOnEnter && props.type !== "textarea") { preventDefault(event); } if (props.type === "search") { blur(); } } emit("keypress", event); }; const getInputId = () => props.id || `${id}-input`; const getValidationStatus = () => state.status; const renderInput = () => { const controlClass = bem("control", [getProp("inputAlign"), { error: showError.value, custom: !!slots.input, "min-height": props.type === "textarea" && !props.autosize }]); if (slots.input) { return _createVNode("div", { "class": controlClass, "onClick": onClickInput }, [slots.input()]); } const inputAttrs = { id: getInputId(), ref: inputRef, name: props.name, rows: props.rows !== void 0 ? +props.rows : void 0, class: controlClass, disabled: getProp("disabled"), readonly: getProp("readonly"), autofocus: props.autofocus, placeholder: props.placeholder, autocomplete: props.autocomplete, enterkeyhint: props.enterkeyhint, "aria-labelledby": props.label ? `${id}-label` : void 0, onBlur, onFocus, onInput, onClick: onClickInput, onChange: endComposing, onKeypress, onCompositionend: endComposing, onCompositionstart: startComposing }; if (props.type === "textarea") { return _createVNode("textarea", inputAttrs, null); } return _createVNode("input", _mergeProps(mapInputType(props.type), inputAttrs), null); }; const renderLeftIcon = () => { const leftIconSlot = slots["left-icon"]; if (props.leftIcon || leftIconSlot) { return _createVNode("div", { "class": bem("left-icon"), "onClick": onClickLeftIcon }, [leftIconSlot ? leftIconSlot() : _createVNode(Icon, { "name": props.leftIcon, "classPrefix": props.iconPrefix }, null)]); } }; const renderRightIcon = () => { const rightIconSlot = slots["right-icon"]; if (props.rightIcon || rightIconSlot) { return _createVNode("div", { "class": bem("right-icon"), "onClick": onClickRightIcon }, [rightIconSlot ? rightIconSlot() : _createVNode(Icon, { "name": props.rightIcon, "classPrefix": props.iconPrefix }, null)]); } }; const renderWordLimit = () => { if (props.showWordLimit && props.maxlength) { const count = getStringLength(getModelValue()); return _createVNode("div", { "class": bem("word-limit") }, [_createVNode("span", { "class": bem("word-num") }, [count]), _createTextVNode("/"), props.maxlength]); } }; const renderMessage = () => { if (form && form.props.showErrorMessage === false) { return; } const message = props.errorMessage || state.validateMessage; if (message) { const slot = slots["error-message"]; const errorMessageAlign = getProp("errorMessageAlign"); return _createVNode("div", { "class": bem("error-message", errorMessageAlign) }, [slot ? slot({ message }) : message]); } }; const renderLabel = () => { const colon = getProp("colon") ? ":" : ""; if (slots.label) { return [slots.label(), colon]; } if (props.label) { return _createVNode("label", { "id": `${id}-label`, "for": getInputId() }, [props.label + colon]); } }; const renderFieldBody = () => [_createVNode("div", { "class": bem("body") }, [renderInput(), showClear.value && _createVNode(Icon, { "ref": clearIconRef, "name": props.clearIcon, "class": bem("clear") }, null), renderRightIcon(), slots.button && _createVNode("div", { "class": bem("button") }, [slots.button()])]), renderWordLimit(), renderMessage()]; useExpose({ blur, focus, validate, formValue, resetValidation, getValidationStatus }); provide(CUSTOM_FIELD_INJECTION_KEY, { customValue, resetValidation, validateWithTrigger }); watch(() => props.modelValue, () => { updateValue(getModelValue()); resetValidation(); validateWithTrigger("onChange"); nextTick(adjustTextareaSize); }); onMounted(() => { updateValue(getModelValue(), props.formatTrigger); nextTick(adjustTextareaSize); }); useEventListener("touchstart", onClear, { target: computed(() => { var _a; return (_a = clearIconRef.value) == null ? void 0 : _a.$el; }) }); return () => { const disabled = getProp("disabled"); const labelAlign = getProp("labelAlign"); const Label = renderLabel(); const LeftIcon = renderLeftIcon(); return _createVNode(Cell, { "size": props.size, "icon": props.leftIcon, "class": bem({ error: showError.value, disabled, [`label-${labelAlign}`]: labelAlign }), "center": props.center, "border": props.border, "isLink": props.isLink, "clickable": props.clickable, "titleStyle": labelStyle.value, "valueClass": bem("value"), "titleClass": [bem("label", [labelAlign, { required: props.required }]), props.labelClass], "arrowDirection": props.arrowDirection }, { icon: LeftIcon ? () => LeftIcon : null, title: Label ? () => Label : null, value: renderFieldBody, extra: slots.extra }); }; } }); export { stdin_default as default, fieldSharedProps };