UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

509 lines (508 loc) 16.6 kB
import { DESCRIPTION_SIZE_TYPES, VALIDATION_MESSAGE_TYPES } from "../../common/constants.js"; import { INPUT_TYPES, INPUT_SIZES, INPUT_ICON_SIZES, INPUT_SIZE_CLASSES, INPUT_STATE_CLASSES, DESCRIPTION_SIZE_CLASSES, LABEL_SIZE_CLASSES } from "./input_constants.js"; import { hasSlotContent, getUniqueString, getValidationState, removeClassStyleAttrs, addClassStyleAttrs } from "../../common/utils.js"; import { MessagesMixin } from "../../common/mixins/input.js"; import { resolveComponent, openBlock, createElementBlock, mergeProps, createElementVNode, renderSlot, normalizeClass, toDisplayString, createCommentVNode, createTextVNode, toHandlers, createVNode } from "vue"; import _export_sfc from "../../_virtual/_plugin-vue_export-helper.js"; import DtValidationMessages from "../validation_messages/validation_messages.vue.js"; const _sfc_main = { compatConfig: { MODE: 3 }, name: "DtInput", components: { DtValidationMessages }, mixins: [MessagesMixin], inheritAttrs: false, props: { /** * Name property of the input element */ name: { type: String, default: "" }, /** * Type of the input. * When `textarea` a `<textarea>` element will be rendered instead of an `<input>` element. * @values text, password, email, number, textarea, date, time, file, tel, search * @default 'text' */ type: { type: String, default: INPUT_TYPES.TEXT, validator: (t) => Object.values(INPUT_TYPES).includes(t) }, /** * Value of the input */ modelValue: { type: [String, Number], default: "" }, /** * Disables the input * @values true, false */ disabled: { type: Boolean, default: false }, /** * Label for the input */ label: { type: String, default: "" }, /** * Determines visibility of input label. * @values true, false */ labelVisible: { type: Boolean, default: true }, /** * Description for the input */ description: { type: String, default: "" }, /** * Size of the input, one of `xs`, `sm`, `md`, `lg`, `xl` * @values xs, sm, md, lg, xl */ size: { type: String, default: "md", validator: (t) => Object.values(INPUT_SIZES).includes(t) }, /** * Additional class name for the input element. * Can accept String, Object, and Array, i.e. has the * same API as Vue's built-in handling of the class attribute. */ inputClass: { type: [String, Object, Array], default: "" }, /** * Additional class name for the input wrapper element. * Can accept all of String, Object, and Array, i.e. has the * same api as Vue's built-in handling of the class attribute. */ inputWrapperClass: { type: [String, Object, Array], default: "" }, /** * The current character length that the user has entered into the input. * This will only need to be used if you are using `validate.length` and * the string contains abnormal characters. * For example, an emoji could take up many characters in the input, but should only count as 1 character. * If no number is provided, a built-in length calculation will be used for the length validation. */ currentLength: { type: Number, default: null }, /** * Whether the input will continue to display a warning validation message even if the input has lost focus. */ retainWarning: { type: Boolean, default: false }, /** * Validation for the input. Supports maximum length validation with the structure: * `{ "length": {"description": string, "max": number, "warn": number, "message": string, * "limitMaxLength": boolean }}` */ validate: { type: Object, default: null }, /** * hidden allows to use input without the element visually present in DOM */ hidden: { type: Boolean, default: false } }, emits: [ /** * Native input event * * @event input * @type {String} */ "input", /** * Native input blur event * * @event blur * @type {FocusEvent} */ "blur", /** * Input clear event * * @event clear */ "clear", /** * Native input focus event * * @event focus * @type {FocusEvent} */ "focus", /** * Native input focusin event * * @event focusin * @type {FocusEvent} */ "focusin", /** * Native input focusout event * * @event focusout * @type {FocusEvent} */ "focusout", /** * Event fired to sync the modelValue prop with the parent component * @event update:modelValue */ "update:modelValue", /** * Length of the input when currentLength prop is not passed * * @event update:length * @type {Number} */ "update:length", /** * Result of the input validation * * @event update:invalid * @type {Boolean} */ "update:invalid" ], data() { return { isInputFocused: false, isInvalid: false, defaultLength: 0, hasSlotContent }; }, computed: { isTextarea() { return this.type === INPUT_TYPES.TEXTAREA; }, isDefaultSize() { return this.size === INPUT_SIZES.DEFAULT; }, iconSize() { return INPUT_ICON_SIZES[this.size]; }, isValidSize() { return Object.values(INPUT_SIZES).includes(this.size); }, isValidDescriptionSize() { return Object.values(DESCRIPTION_SIZE_TYPES).includes(this.size); }, inputComponent() { if (this.isTextarea) { return "textarea"; } return "input"; }, inputListeners() { return { input: async (event) => { let val = event.target.value; if (this.type === INPUT_TYPES.FILE) { const files = Array.from(event.target.files); val = files.map((file) => file.name); } this.$emit("input", val); this.$emit("update:modelValue", val); }, blur: (event) => { this.isInputFocused = false; this.onBlur(event); }, focus: (event) => { this.isInputFocused = true; this.$emit("focus", event); }, focusin: (event) => this.$emit("focusin", event), focusout: (event) => this.$emit("focusout", event) }; }, descriptionKey() { return `input-description-${getUniqueString()}`; }, inputState() { return getValidationState(this.validationMessages); }, defaultLengthCalculation() { return this.calculateLength(this.modelValue); }, validationProps() { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; return { length: { description: (_b = (_a = this == null ? void 0 : this.validate) == null ? void 0 : _a.length) == null ? void 0 : _b.description, max: (_d = (_c = this == null ? void 0 : this.validate) == null ? void 0 : _c.length) == null ? void 0 : _d.max, warn: (_f = (_e = this == null ? void 0 : this.validate) == null ? void 0 : _e.length) == null ? void 0 : _f.warn, message: (_h = (_g = this == null ? void 0 : this.validate) == null ? void 0 : _g.length) == null ? void 0 : _h.message, limitMaxLength: ((_j = (_i = this == null ? void 0 : this.validate) == null ? void 0 : _i.length) == null ? void 0 : _j.limitMaxLength) ? this.validate.length.limitMaxLength : false } }; }, validationMessages() { if (this.showLengthLimitValidation) { return this.formattedMessages.concat([this.inputLengthErrorMessage()]); } return this.formattedMessages; }, showInputState() { return this.showMessages && this.inputState; }, inputLength() { return this.currentLength ? this.currentLength : this.defaultLengthCalculation; }, inputLengthState() { if (this.inputLength < this.validationProps.length.warn) { return null; } else if (this.inputLength <= this.validationProps.length.max) { return this.validationProps.length.warn ? VALIDATION_MESSAGE_TYPES.WARNING : null; } else { return VALIDATION_MESSAGE_TYPES.ERROR; } }, shouldValidateLength() { return !!(this.validationProps.length.description && this.validationProps.length.max); }, shouldLimitMaxLength() { return this.shouldValidateLength && this.validationProps.length.limitMaxLength; }, // eslint-disable-next-line complexity showLengthLimitValidation() { return this.shouldValidateLength && this.inputLengthState !== null && this.validationProps.length.message && (this.retainWarning || this.isInputFocused || this.isInvalid); }, sizeModifierClass() { if (this.isDefaultSize || !this.isValidSize) { return ""; } return INPUT_SIZE_CLASSES[this.inputComponent][this.size]; }, stateClass() { return [INPUT_STATE_CLASSES[this.inputState]]; } }, watch: { isInvalid(val) { this.$emit("update:invalid", val); }, modelValue: { immediate: true, handler(newValue) { if (this.shouldValidateLength) { this.validateLength(this.inputLength); } if (this.currentLength == null) { this.$emit("update:length", this.calculateLength(newValue)); } } } }, beforeMount() { this.descriptionSizeClasses = DESCRIPTION_SIZE_CLASSES; this.labelSizeClasses = LABEL_SIZE_CLASSES; }, methods: { removeClassStyleAttrs, addClassStyleAttrs, inputClasses() { return [ "d-input__input", this.inputComponent === "input" ? "d-input" : "d-textarea", { [this.stateClass]: this.showInputState, "d-input-icon--left": this.$slots.leftIcon, "d-input-icon--right": this.$slots.rightIcon }, this.sizeModifierClass, this.inputClass ]; }, inputWrapperClasses() { if (this.hidden) { return []; } return [ "d-input__wrapper", { [this.stateClass]: this.showInputState }, this.inputWrapperClass ]; }, calculateLength(value) { if (typeof value !== "string") { return 0; } return [...value].length; }, inputLengthErrorMessage() { return { message: this.validationProps.length.message, type: this.inputLengthState }; }, onBlur(e) { var _a; if (!((_a = this.$refs.container) == null ? void 0 : _a.contains(e.relatedTarget))) { this.$emit("blur", e); } }, emitClearEvents() { this.$emit("input", ""); this.$emit("clear"); this.$emit("update:modelValue", ""); }, blur() { this.$refs.input.blur(); }, focus() { this.$refs.input.focus(); }, select() { this.$refs.input.select(); }, getMessageKey(type, index) { return `message-${type}-${index}`; }, validateLength(length) { this.isInvalid = length > this.validationProps.length.max; }, clearInput() { this.$refs.input.value = ""; this.$refs.input.focus(); this.emitClearEvents(); } } }; const _hoisted_1 = ["aria-details"]; const _hoisted_2 = ["id"]; const _hoisted_3 = { key: 0 }; const _hoisted_4 = { key: 1, "data-qa": "dt-input-length-description", class: "d-input__length-description" }; const _hoisted_5 = ["read-only"]; const _hoisted_6 = ["value", "name", "disabled", "autocomplete", "maxlength"]; const _hoisted_7 = ["value", "name", "type", "disabled", "autocomplete", "maxlength"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_dt_validation_messages = resolveComponent("dt-validation-messages"); return openBlock(), createElementBlock("div", mergeProps({ ref: "container", class: ["d-input__root", { "d-input--hidden": $props.hidden }] }, $options.addClassStyleAttrs(_ctx.$attrs), { "data-qa": "dt-input" }), [ createElementVNode("label", { class: "d-input__label", "aria-details": _ctx.$slots.description || $props.description ? $options.descriptionKey : void 0, "data-qa": "dt-input-label-wrapper" }, [ renderSlot(_ctx.$slots, "labelSlot", {}, () => [ $props.labelVisible && $props.label ? (openBlock(), createElementBlock("div", { key: 0, ref: "label", "data-qa": "dt-input-label", class: normalizeClass([ "d-input__label-text", "d-label", _ctx.labelSizeClasses[$props.size] ]) }, toDisplayString($props.label), 3)) : createCommentVNode("", true) ]), $data.hasSlotContent(_ctx.$slots.description) || $props.description || $options.shouldValidateLength ? (openBlock(), createElementBlock("div", { key: 0, id: $options.descriptionKey, ref: "description", class: normalizeClass([ "d-input__description", "d-description", _ctx.descriptionSizeClasses[$props.size] ]), "data-qa": "dt-input-description" }, [ $data.hasSlotContent(_ctx.$slots.description) || $props.description ? (openBlock(), createElementBlock("div", _hoisted_3, [ renderSlot(_ctx.$slots, "description", {}, () => [ createTextVNode(toDisplayString($props.description), 1) ]) ])) : createCommentVNode("", true), $options.shouldValidateLength ? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString($options.validationProps.length.description), 1)) : createCommentVNode("", true) ], 10, _hoisted_2)) : createCommentVNode("", true), createElementVNode("div", { class: normalizeClass($options.inputWrapperClasses()), "read-only": $props.disabled === true ? true : void 0 }, [ createElementVNode("span", { class: "d-input-icon d-input-icon--left", "data-qa": "dt-input-left-icon-wrapper", onFocusout: _cache[0] || (_cache[0] = (...args) => $options.onBlur && $options.onBlur(...args)) }, [ renderSlot(_ctx.$slots, "leftIcon", { iconSize: $options.iconSize }) ], 32), $options.isTextarea ? (openBlock(), createElementBlock("textarea", mergeProps({ key: 0, ref: "input", value: $props.modelValue, name: $props.name, disabled: $props.disabled, autocomplete: _ctx.$attrs.autocomplete ?? "off", class: $options.inputClasses(), maxlength: $options.shouldLimitMaxLength ? $options.validationProps.length.max : null, "data-qa": "dt-input-input" }, $options.removeClassStyleAttrs(_ctx.$attrs), toHandlers($options.inputListeners, true)), null, 16, _hoisted_6)) : (openBlock(), createElementBlock("input", mergeProps({ key: 1, ref: "input", value: $props.modelValue, name: $props.name, type: $props.type, disabled: $props.disabled, autocomplete: _ctx.$attrs.autocomplete ?? "off", class: $options.inputClasses(), maxlength: $options.shouldLimitMaxLength ? $options.validationProps.length.max : null, "data-qa": "dt-input-input" }, $options.removeClassStyleAttrs(_ctx.$attrs), toHandlers($options.inputListeners, true)), null, 16, _hoisted_7)), createElementVNode("span", { class: "d-input-icon d-input-icon--right", "data-qa": "dt-input-right-icon-wrapper", onFocusout: _cache[1] || (_cache[1] = (...args) => $options.onBlur && $options.onBlur(...args)) }, [ renderSlot(_ctx.$slots, "rightIcon", { iconSize: $options.iconSize, clear: $options.clearInput }) ], 32) ], 10, _hoisted_5) ], 8, _hoisted_1), createVNode(_component_dt_validation_messages, mergeProps({ "validation-messages": $options.validationMessages, "show-messages": _ctx.showMessages, class: _ctx.messagesClass }, _ctx.messagesChildProps, { "data-qa": "dt-input-messages" }), null, 16, ["validation-messages", "show-messages", "class"]) ], 16); } const DtInput = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); export { DtInput as default }; //# sourceMappingURL=input.vue.js.map