@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
509 lines (508 loc) • 16.6 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 { 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