import { css, LitElement } from 'lit'; import { customElement, property, query } from 'lit/decorators.js'; import { nothing, html } from 'lit/html.js'; import { createUUID } from './utils/UniqueId'; import { MonoTextComp } from './MonoTextComp'; import { fromOptionalConverter, spread } from './utils/LitHelper'; import { SpreadController } from './utils/SpreadController'; import { mask } from './utils/InputMask'; import { TailwindStylesController } from './utils/TailwindStylesController'; import { RoundedCorners } from './utils/CommonTypes'; export type InputMaskType = 'number' | 'date' | 'phone'; export type InputMode = | 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'; @customElement('mono-textfield') export class MonoTextFieldComp extends LitElement { static styles = css` ::slotted(p:first-of-type) { margin: 0; } `; private __spreadController: SpreadController = new SpreadController(this); private __stylesController: TailwindStylesController = new TailwindStylesController( this, ); @property({ type: String, reflect: true }) value: string = ''; @property({ type: String, reflect: true }) placeholder: string = ''; @property({ type: String, reflect: true }) id: string = createUUID(); @property({ type: String, reflect: true }) name: string = ''; @property({ type: String, reflect: true, converter: fromOptionalConverter }) error?: string; @property({ type: Boolean, reflect: true }) disabled: boolean = false; @property({ type: Boolean, reflect: true }) required: boolean = false; @property({ type: String, reflect: true, attribute: 'mask-type' }) maskType?: InputMaskType; @property({ type: String, reflect: true, attribute: 'inputmode' }) inputMode: InputMode = 'text'; @property({ type: String, reflect: true }) corners: RoundedCorners = 'none'; @query('input', true) __inputEl!: HTMLInputElement; @query('label', true) __labelEl!: HTMLLabelElement; @query('[id*="-errors"]', true) __errorEl!: MonoTextComp; __computMaskType() { switch (this.maskType) { case 'number': // kind of a hack, need to update input mask // but we aren't using it at the moment return '99999999999999999999999999999999999999999999999999999999999'; case 'phone': return '9999999999'; case 'date': return '99/99/9999'; default: return null; } } private __onInput(_event: Event) { const maskType = this.__computMaskType(); if (maskType) { const newValue = mask(this.__inputEl.value, maskType); this.__inputEl.value = newValue; this.value = newValue; } else { this.value = this.__inputEl.value; } } private __onChange(event: Event) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); const maskType = this.__computMaskType(); if (maskType) { const newValue = mask(this.__inputEl.value, maskType); this.__inputEl.value = newValue; this.value = newValue; } else { this.value = this.__inputEl.value; } const changeEvent = new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true, }); this.dispatchEvent(changeEvent); } private __hasError() { return this.error && this.error.length > 0; } private __renderError(ariaDescribedBy: string) { if (this.__hasError()) { return html` ${this.error} `; } return nothing; } render() { const ariaDescribedBy = this.__hasError() ? `${this.id}-errors` : ''; const attributesToSpread = this.__spreadController.buildSpreadAttributesIgnoring( [ 'as', 'style', 'class', 'slot', 'value', 'placeholder', 'id', 'name', 'error', 'disabled', 'required', 'mask-type', 'aria-describedby', 'corners', ], ); return html`
${this.__renderError(ariaDescribedBy)}
`; } } declare global { interface HTMLElementTagNameMap { 'mono-textfield': MonoTextFieldComp; } }