UNPKG

@dialpad/dialtone

Version:

Dialpad's Dialtone design system monorepo

412 lines (411 loc) 14 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 { getUniqueString, getValidationState } from "../../common/utils.js"; import { MessagesMixin } from "../../common/mixins/input.js"; import normalizeComponent from "../../_virtual/_plugin-vue2_normalizer.js"; import DtValidationMessages from "../validation_messages/validation_messages.vue.js"; const _sfc_main = { 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 */ value: { 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", /** * 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 }; }, 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 { /* TODO Check if any usages of this component leverage $listeners and either remove if unused or scope the removal and migration prior to upgrading to Vue 3.x */ ...this.$listeners, input: (event) => this.$emit("input", event.target.value), focus: (event) => { this.isInputFocused = true; this.$emit("focus", event); }, blur: (event) => { this.isInputFocused = false; this.onBlur(event); } }; }, descriptionKey() { return `input-description-${getUniqueString()}`; }, inputState() { return getValidationState(this.validationMessages); }, defaultLengthCalculation() { return this.calculateLength(this.value); }, 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); }, value: { 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: { 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(); } } }; var _sfc_render = function render() { var _vm = this, _c = _vm._self._c; return _c("div", { ref: "container", class: ["d-input__root", { "d-input--hidden": _vm.hidden }], attrs: { "data-qa": "dt-input" } }, [_c("label", { staticClass: "d-input__label", attrs: { "aria-details": _vm.$slots.description || _vm.description ? _vm.descriptionKey : void 0, "data-qa": "dt-input-label-wrapper" } }, [_vm._t("labelSlot", function() { return [_vm.labelVisible && _vm.label ? _c("div", { ref: "label", class: [ "d-input__label-text", "d-label", _vm.labelSizeClasses[_vm.size] ], attrs: { "data-qa": "dt-input-label" } }, [_vm._v(" " + _vm._s(_vm.label) + " ")]) : _vm._e()]; }), _vm.$slots.description || _vm.description || _vm.shouldValidateLength ? _c("div", { ref: "description", class: [ "d-input__description", "d-description", _vm.descriptionSizeClasses[_vm.size] ], attrs: { "id": _vm.descriptionKey, "data-qa": "dt-input-description" } }, [_vm.$slots.description || _vm.description ? _c("div", [_vm._t("description", function() { return [_vm._v(_vm._s(_vm.description))]; })], 2) : _vm._e(), _vm.shouldValidateLength ? _c("div", { staticClass: "d-input__length-description", attrs: { "data-qa": "dt-input-length-description" } }, [_vm._v(" " + _vm._s(_vm.validationProps.length.description) + " ")]) : _vm._e()]) : _vm._e(), _c("div", { class: _vm.inputWrapperClasses(), attrs: { "read-only": _vm.disabled } }, [_c("span", { staticClass: "d-input-icon d-input-icon--left", attrs: { "data-qa": "dt-input-left-icon-wrapper" }, on: { "focusout": _vm.onBlur } }, [_vm._t("leftIcon", null, { "iconSize": _vm.iconSize })], 2), _vm.isTextarea ? _c("textarea", _vm._g(_vm._b({ ref: "input", class: _vm.inputClasses(), attrs: { "name": _vm.name, "disabled": _vm.disabled, "autocomplete": _vm.$attrs.autocomplete ?? "off", "maxlength": _vm.shouldLimitMaxLength ? _vm.validationProps.length.max : null, "data-qa": _vm.$attrs["data-qa"] ?? "dt-input-input" }, domProps: { "value": _vm.value } }, "textarea", _vm.$attrs, false), _vm.inputListeners)) : _c("input", _vm._g(_vm._b({ ref: "input", class: _vm.inputClasses(), attrs: { "name": _vm.name, "type": _vm.type, "disabled": _vm.disabled, "autocomplete": _vm.$attrs.autocomplete ?? "off", "maxlength": _vm.shouldLimitMaxLength ? _vm.validationProps.length.max : null, "data-qa": _vm.$attrs["data-qa"] ?? "dt-input-input" }, domProps: { "value": _vm.value } }, "input", _vm.$attrs, false), _vm.inputListeners)), _c("span", { staticClass: "d-input-icon d-input-icon--right", attrs: { "data-qa": "dt-input-right-icon-wrapper" }, on: { "focusout": _vm.onBlur } }, [_vm._t("rightIcon", null, { "iconSize": _vm.iconSize, "clear": _vm.clearInput })], 2)])], 2), _c("dt-validation-messages", _vm._b({ class: _vm.messagesClass, attrs: { "validation-messages": _vm.validationMessages, "show-messages": _vm.showMessages, "data-qa": "dt-input-messages" } }, "dt-validation-messages", _vm.messagesChildProps, false))], 1); }; var _sfc_staticRenderFns = []; var __component__ = /* @__PURE__ */ normalizeComponent( _sfc_main, _sfc_render, _sfc_staticRenderFns ); const DtInput = __component__.exports; export { DtInput as default }; //# sourceMappingURL=input.vue.js.map