@nysds/nys-textinput
Version:
The Textinput component from the NYS Design System.
348 lines (347 loc) • 18.8 kB
JavaScript
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"
=${() => 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)}
=${this._handleInput}
="${this._handleFocus}"
="${this._handleBlur}"
/>
${this.type === "password" ? y` <nys-button
class="eye-icon"
id="password-toggle"
suffixIcon="slotted"
ariaLabel="password toggle"
variant="ghost"
size="sm"
-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"
=${() => 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