UNPKG

@nysds/nys-textinput

Version:

The Textinput component from the NYS Design System.

348 lines (347 loc) 18.8 kB
import { LitElement as h, unsafeCSS as c, html as y } from "lit"; import { property as o, query as _, state as v } from "lit/decorators.js"; import { ifDefined as u } from "lit/directives/if-defined.js"; /*! * █▄ █ █ █ █▀▀▀█ █▀▀▄ █▀▀▀█ * █ █ █ █▄▄▄█ ▀▀▀▄▄ █ █ ▀▀▀▄▄ * █ ▀█ █ █▄▄▄█ █▄▄▀ █▄▄▄█ * * Textinput Component v1.18.1 * Part of the New York State Design System * Repository: https://github.com/its-hcd/nysds * License: MIT */ const f = ':host{--_nys-textinput-width: 100%;--_nys-textinput-height: var(--nys-size-500, 40px);--_nys-textinput-border-radius: var(--nys-radius-md, 4px);--_nys-textinput-border-width: var(--nys-border-width-sm, 1px);--_nys-textinput-border-color: var(--nys-color-neutral-400, #909395);--_nys-textinput-color: var(--nys-color-text, var(--nys-color-neutral-900, #1b1b1b));--_nys-textinput-color--placeholder: var(--nys-color-text-weaker, var(--nys-color-neutral-500, #797c7f));--_nys-textinput-padding: var(--nys-space-100, 8px);--_nys-textinput-gap: var(--nys-space-50, 4px);--_nys-textinput-background-color: var(--nys-color-ink-reverse, var(--nys-color-white, #ffffff));--_nys-textinput-outline-color--hover: var(--nys-color-neutral-900, #1b1b1b);--_nys-textinput-outline-width: var(--nys-border-width-sm, 1px);--_nys-textinput-outline-color--focus: var(--nys-color-focus, #004dd1);--_nys-textinput-background-color--disabled: var(--nys-color-neutral-10, #f6f6f6);--_nys-textinput-border-color--disabled: var(--nys-color-neutral-200, #bec0c1);--_nys-textinput-color--disabled: var(--nys-color-text-disabled, var(--nys-color-neutral-200, #bec0c1));--_nys-textinput-font-family: var(--nys-font-family-ui, var(--nys-font-family-sans, "Proxima Nova", "Helvetica Neue", "Helvetica", "Arial", sans-serif));--_nys-textinput-font-size: var(--nys-font-size-ui-md, 16px);--_nys-textinput-font-weight: var(--nys-font-weight-regular, 400);--_nys-textinput-line-height: var(--nys-font-lineheight-ui-md, 24px);--_nys-textinput-letter-spacing: var(--nys-font-letterspacing-ui-md, var(--nys-font-letterspacing-400, .044px))}:host([width=sm]){--_nys-textinput-width: var(--nys-form-width-sm, 88px)}:host([width=md]){--_nys-textinput-width: var(--nys-form-width-md, 200px)}:host([width=lg]){--_nys-textinput-width: var(--nys-form-width-lg, 384px)}:host([width=full]){--_nys-textinput-width: 100%;flex:1}:host([showError]){--_nys-textinput-border-color: var(--nys-color-danger, #b52c2c)}:host([inverted]){--_nys-textinput-outline-color--focus: var(--nys-color-focus-reverse, #7aa5e7)}.nys-textinput{font-weight:var(--_nys-textinput-font-weight);font-family:var(--_nys-textinput-font-family);font-size:var(--_nys-textinput-font-size);line-height:var(--_nys-textinput-line-height);letter-spacing:var(--_nys-textinput-letter-spacing);color:var(--_nys-textinput-color);gap:var(--_nys-textinput-gap);display:flex;flex-direction:column}.nys-textinput__mask-overlay{position:absolute;margin:calc(var(--_nys-textinput-padding) + var(--_nys-textinput-border-width));color:var(--nys-color-text-weaker, #797c7f);display:inline;overflow:hidden;white-space:nowrap;font:inherit;letter-spacing:normal}.nys-textinput__input{color:var(--_nys-textinput-color);border-radius:var(--_nys-textinput-border-radius);border:solid var(--_nys-textinput-border-color) var(--_nys-textinput-border-width);outline:transparent solid var(--_nys-textinput-outline-width);padding:var(--_nys-textinput-padding);width:100%;height:var(--_nys-textinput-height);box-sizing:border-box;background-color:transparent;position:relative;font:inherit}.nys-textinput__input::placeholder{color:var(--_nys-textinput-color--placeholder)}.nys-textinput__buttoncontainer{width:var(--_nys-textinput-width);max-width:100%;display:flex}.nys-textinput__buttoncontainer.has-end-button .nys-textinput__input{border-start-end-radius:0;border-end-end-radius:0;border-inline-end:none}.nys-textinput__buttoncontainer.has-start-button .nys-textinput__input{border-start-start-radius:0;border-end-start-radius:0;border-inline-start:none}.nys-textinput__container{position:relative;display:flex;align-items:center;width:100%;background-color:var(--_nys-textinput-background-color);border-radius:var(--_nys-textinput-border-radius)}::slotted(nys-button){--_nys-button-height: var(--_nys-textinput-height);--_nys-button-border-radius: var(--_nys-textinput-border-radius);--_nys-button-background-color--disabled: var(--_nys-textinput-background-color--disabled);--_nys-button-border-color--disabled: var(--_nys-textinput-color--disabled);--_nys-button-color--disabled: var(--_nys-textinput-color--disabled);--_nys-button-border-width: var(--_nys-textinput-border-width);z-index:1}.nys-textinput__buttoncontainer.has-start-button ::slotted(nys-button){--_nys-button-border-radius: var(--_nys-textinput-border-radius) 0 0 var(--_nys-textinput-border-radius)}.nys-textinput__buttoncontainer.has-end-button ::slotted(nys-button){--_nys-button-border-radius: 0 var(--_nys-textinput-border-radius) var(--_nys-textinput-border-radius) 0}.eye-icon{position:absolute;right:var(--nys-space-50, 4px);top:50%;transform:translateY(-50%);cursor:pointer;color:var(--_nys-textinput-color--icon);--nys-button-background-color: var(--_nys-textinput-background-color);--nys-button-background-color--hover: var(--_nys-textinput-background-color);--nys-button-background-color--active: var(--_nys-textinput-background-color);--_nys-button-outline-focus: calc(var(--_nys-button-outline-width) * -1);--_nys-button-padding--y: var(--nys-space-50, 4px);--_nys-button-padding--x: var(--nys-space-50, 4px);--_nys-button-height: var(--nys-size-300, 32px);--_nys-button-width: var(--nys-size-400, 32px)}.nys-textinput__input:hover:not(:disabled):not(:focus):not([readonly]){outline-color:var(--_nys-textinput-outline-color--hover);border-color:var(--_nys-textinput-outline-color--hover)}.nys-textinput__input:focus:not([readonly]){outline-color:var(--_nys-textinput-outline-color--focus);border-color:var(--_nys-textinput-outline-color--focus);caret-color:var(--_nys-textinput-outline-color--focus)}.nys-textinput__input:disabled,.nys-textinput__input:disabled::placeholder,.nys-textinput__input:disabled+.eye-icon{background-color:var(--_nys-textinput-background-color--disabled);border-color:var(--_nys-textinput-border-color--disabled);color:var(--_nys-textinput-color--disabled);cursor:not-allowed}'; var b = Object.defineProperty, r = (p, e, s, t) => { for (var n = void 0, l = p.length - 1, a; l >= 0; l--) (a = p[l]) && (n = a(e, s, n) || n); return n && b(e, s, n), n; }; let x = 0; const d = class d extends h { // allows use of elementInternals' API constructor() { super(), this.id = "", this.name = "", this.type = "text", this.label = "", this.description = "", this.placeholder = "", this.value = "", this.disabled = !1, this.readonly = !1, this.required = !1, this.optional = !1, this.tooltip = "", this.form = null, this.pattern = "", this.maxlength = null, this.ariaLabel = "", this.width = "full", this.step = null, this.min = null, this.max = null, this.inverted = !1, this.showError = !1, this.errorMessage = "", this.showPassword = !1, this._originalErrorMessage = "", this._hasUserInteracted = !1, this._maskPatterns = { tel: "(___) ___-____" }, this._internals = this.attachInternals(); } // Generate a unique ID if one is not provided connectedCallback() { super.connectedCallback(), this.id || (this.id = `nys-textinput-${Date.now()}-${x++}`), this._originalErrorMessage = this.errorMessage ?? "", this.addEventListener("invalid", this._handleInvalid); } disconnectedCallback() { super.disconnectedCallback(), this.removeEventListener("invalid", this._handleInvalid); } async firstUpdated() { this._setValue(); } // Ensure the "width" property is valid after updates async updated(e) { if (e.has("value") && this._setValue(), e.has("disabled") && (this._validateButtonSlot("startButton"), this._validateButtonSlot("endButton")), e.has("type")) { const s = this._maskPatterns[this.type], t = this._inputEl; if (t) if (s) this.maxlength === null && (t.maxLength = s.length), this._updateOverlay(t.value, s); else { this.maxlength === null && t.removeAttribute("maxLength"); const n = this.shadowRoot?.querySelector( ".nys-textinput__mask-overlay" ); n && (n.textContent = ""); } } if (e.has("readonly") || e.has("required")) { const s = this._inputEl; s && (s.required = this.required && !this.readonly); } } /** * Form Integration * -------------------------------------------------------------------------- */ _setValue() { this._internals.setFormValue(this.value), this._manageRequire(); } _manageRequire() { const e = this._inputEl; if (!e) return; const s = this.errorMessage || "This field is required"; this.required && (!this.value || this.value?.trim() === "") ? (this._internals.ariaInvalid = "true", this._internals.setValidity({ valueMissing: !0 }, s, e)) : (this._internals.ariaInvalid = "false", this._internals.setValidity({}), this._hasUserInteracted = !1); } _setValidityMessage(e = "") { const s = this._inputEl; if (!s) return; this.showError = !!e, this._originalErrorMessage?.trim() && e !== "" ? this.errorMessage = this._originalErrorMessage : this.errorMessage = e, this._internals.ariaInvalid = this.showError ? "true" : "false"; const t = e ? { customError: !0 } : {}; this._internals.setValidity(t, this.errorMessage, s); } _validate() { const e = this._inputEl; if (!e) return; const s = e.validity; let t = ""; s.valueMissing ? t = "This field is required" : s.typeMismatch ? t = "Invalid format for this type" : s.patternMismatch ? t = "Invalid format" : s.tooShort ? t = `Value is too short. Minimum length is ${e.minLength}` : s.tooLong ? t = `Value is too long. Maximum length is ${e.maxLength}` : s.rangeUnderflow ? t = `Value must be at least ${e.min}` : s.rangeOverflow ? t = `Value must be at most ${e.max}` : s.stepMismatch ? t = "Invalid step value" : t = e.validationMessage, this._setValidityMessage(t); } // This callback is automatically called when the parent form is reset. formResetCallback() { this.value = ""; const e = this._inputEl; e && (e.value = ""), this._internals.setFormValue(""), this.showError = !1, this.errorMessage = "", this._internals.setValidity({}), this._internals.ariaInvalid = "false", this.showPassword = !1, this.requestUpdate(); } /** * Functions * -------------------------------------------------------------------------- */ // This helper function is called to perform the element's native validation. checkValidity() { const e = this._inputEl; return e ? e.checkValidity() : !0; } _handleInvalid(e) { e.preventDefault(), this._hasUserInteracted = !0, this._validate(); const s = this._inputEl; if (s) { const t = this._internals.form; t ? Array.from(t.elements).find( (a) => typeof a.checkValidity == "function" && !a.checkValidity() ) === this && s.focus() : s.focus(); } } _togglePasswordVisibility() { this.showPassword = !this.showPassword; } _updateOverlay(e, s) { const t = this.shadowRoot?.querySelector( ".nys-textinput__mask-overlay" ); if (!t) return; const n = e, l = s.slice(n.length); t.textContent = n + l; } _applyMask(e, s) { const t = e.replace(/\D/g, ""); let n = ""; if (this.type === "tel") return t.length > 0 && (n = "(" + t.substring(0, 3)), t.length >= 4 && (n += ") " + t.substring(3, 6)), t.length > 6 && (n += "-" + t.substring(6, 10)), n; let l = 0; for (let a = 0; a < s.length; a++) if (s[a] === "_" || s[a].match(/[d9]/i)) if (l < t.length) n += t[l++]; else break; else n += s[a]; return n; } /** * Event Handlers * -------------------------------------------------------------------------- */ // Handle input event to check pattern validity _handleInput(e) { const s = e.target; let t = s.value; const n = this._maskPatterns[this.type]; n && (t = this._applyMask(t, n), s.value = t, this._updateOverlay(t, n)), this.value = t, this._internals.setFormValue(this.value), this._hasUserInteracted && this._validate(), this.dispatchEvent( new CustomEvent("nys-input", { detail: { id: this.id, value: this.value }, bubbles: !0, composed: !0 }) ); } // Handle focus event _handleFocus() { this.dispatchEvent( new Event("nys-focus", { bubbles: !0, composed: !0 }) ); } // Handle blur event _handleBlur() { this._hasUserInteracted || (this._hasUserInteracted = !0), this._validate(), this.dispatchEvent( new Event("nys-blur", { bubbles: !0, composed: !0 }) ); } _validateButtonSlot(e) { const s = this.shadowRoot?.querySelector( 'slot[name="' + e + '"]' ), t = this.shadowRoot?.querySelector( ".nys-textinput__buttoncontainer" ); if (!s || !t) return; const n = s.assignedElements(); let l = !1; n.forEach((a) => { a instanceof HTMLElement && a.tagName.toLowerCase() === "nys-button" && !l ? (l = !0, a.setAttribute("size", "sm"), a.setAttribute("variant", "primary"), this.disabled ? a.setAttribute("disabled", "true") : a.removeAttribute("disabled")) : (console.warn( "The '" + e + "' slot only accepts a single <nys-button> element. Removing invalid or extra node:", a ), a.remove()); }), e === "startButton" ? t.classList.toggle("has-start-button", l) : e === "endButton" && t.classList.toggle("has-end-button", l); } render() { return y` <div class="nys-textinput"> <nys-label id="${this.id}--label" label=${this.label} description=${this.description} flag=${this.required && !this.readonly ? "required" : this.optional ? "optional" : ""} tooltip=${this.tooltip} ?inverted=${this.inverted} > <slot name="description" slot="description">${this.description}</slot> </nys-label> <div class="nys-textinput__buttoncontainer"> <slot name="startButton" @slotchange=${() => this._validateButtonSlot("startButton")} ></slot> <div class="nys-textinput__container"> <span class="nys-textinput__mask-overlay"></span> <input class="nys-textinput__input" type=${this.type === "password" ? this.showPassword ? "text" : "password" : this.type} name=${this.name} id=${this.id + "--native"} ?disabled=${this.disabled} ?required=${this.required && !this.readonly} ?readonly=${this.readonly} aria-label=${u( [this.label, this.description].filter(Boolean).join(" ") || this.ariaLabel || void 0 )} aria-required=${this.required} aria-disabled="${this.disabled}" aria-invalid=${this.showError ? "true" : "false"} aria-errormessage=${this.id + "--error"} .value=${this.value} placeholder=${u( this.placeholder ? this.placeholder : void 0 )} pattern=${u(this.pattern ? this.pattern : void 0)} min=${u(this.min !== null ? this.min : void 0)} maxlength=${u( this.maxlength !== null ? this.maxlength : void 0 )} step=${u(this.step !== null ? this.step : void 0)} max=${u(this.max !== null ? this.max : void 0)} form=${u(this.form || void 0)} @input=${this._handleInput} @focus="${this._handleFocus}" @blur="${this._handleBlur}" /> ${this.type === "password" ? y` <nys-button class="eye-icon" id="password-toggle" suffixIcon="slotted" ariaLabel="password toggle" variant="ghost" size="sm" @nys-click=${() => !this.disabled && this._togglePasswordVisibility()} > <nys-icon slot="suffix-icon" size="2xl" name=${this.showPassword ? "visibility_off" : "visibility"} ></nys-icon> </nys-button>` : ""} </div> <slot name="endButton" @slotchange=${() => this._validateButtonSlot("endButton")} ></slot> </div> <nys-errormessage id=${this.id + "--error"} ?showError=${this.showError} errorMessage=${this.errorMessage} ></nys-errormessage> </div> `; } }; d.styles = c(f), d.shadowRootOptions = { ...h.shadowRootOptions, delegatesFocus: !0 }, d.formAssociated = !0; let i = d; r([ o({ type: String, reflect: !0 }) ], i.prototype, "id"); r([ o({ type: String, reflect: !0 }) ], i.prototype, "name"); r([ o({ type: String, reflect: !0 }) ], i.prototype, "type"); r([ o({ type: String }) ], i.prototype, "label"); r([ o({ type: String }) ], i.prototype, "description"); r([ o({ type: String }) ], i.prototype, "placeholder"); r([ o({ type: String }) ], i.prototype, "value"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "disabled"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "readonly"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "required"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "optional"); r([ o({ type: String }) ], i.prototype, "tooltip"); r([ o({ type: String, reflect: !0 }) ], i.prototype, "form"); r([ o({ type: String }) ], i.prototype, "pattern"); r([ o({ type: Number }) ], i.prototype, "maxlength"); r([ o({ type: String }) ], i.prototype, "ariaLabel"); r([ o({ type: String, reflect: !0 }) ], i.prototype, "width"); r([ o({ type: Number }) ], i.prototype, "step"); r([ o({ type: Number }) ], i.prototype, "min"); r([ o({ type: Number }) ], i.prototype, "max"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "inverted"); r([ o({ type: Boolean, reflect: !0 }) ], i.prototype, "showError"); r([ o({ type: String }) ], i.prototype, "errorMessage"); r([ _("input") ], i.prototype, "_inputEl"); r([ v() ], i.prototype, "showPassword"); customElements.get("nys-textinput") || customElements.define("nys-textinput", i); export { i as NysTextinput }; //# sourceMappingURL=nys-textinput.js.map