@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
412 lines (411 loc) • 14 kB
JavaScript
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