3 lines
15 KiB
JavaScript
3 lines
15 KiB
JavaScript
const smInput = document.createElement("template"); smInput.innerHTML = '<style>*{padding: 0;margin: 0;-webkit-box-sizing: border-box; box-sizing: border-box;}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-results-button,input[type="search"]::-webkit-search-results-decoration { display: none; }input[type=number] {-moz-appearance:textfield;}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button {-webkit-appearance: none;-moz-appearance: none;appearance: none;margin: 0;}input::-ms-reveal,input::-ms-clear { display: none;}input:invalid{outline: none;-webkit-box-shadow: none; box-shadow: none;}::-moz-focus-inner{border: none;}:host{display: flex;--success-color: #00C853;--danger-color: red;--width: 100%;--icon-gap: 0.5rem;--min-height: 3.2rem;--background: rgba(var(--text-color, (17,17,17)), 0.06);}.hidden{ display: none !important;}button{display: flex;border: none;background: none;padding: 0;border-radius: 1rem;min-width: 0;cursor: pointer;}button:focus{outline: var(--accent-color, teal) solid medium;}.icon {height: 1.2rem;width: 1.2rem;fill: rgba(var(--text-color, (17,17,17)), 0.6);}:host(.round) .input{border-radius: 10rem;}.input {display: flex;cursor: text;min-width: 0;text-align: left; align-items: center;position: relative;gap: var(--icon-gap);padding: var(--padding, 0.6rem 0.8rem);border-radius: var(--border-radius,0.3rem);transition: opacity 0.3s, box-shadow 0.2s;background: var(--background);width: 100%;outline: none;min-height: var(--min-height);}.input.readonly .clear{opacity: 0 !important;margin-right: -2rem;pointer-events: none !important;}.clear{visibility: hidden;}.readonly{pointer-events: none;}.input:focus-within:not(.readonly){box-shadow: 0 0 0 0.1rem var(--accent-color,teal) inset !important;}.disabled{pointer-events: none;opacity: 0.6;}.label {grid-area: 1/1/2/2;font-size: inherit;opacity: .7;font-weight: 400;transition: -webkit-transform 0.3s;transition: transform 0.3s;transition: transform 0.3s, -webkit-transform 0.3s, color .03; transform-origin: left;pointer-events: none;white-space: nowrap;overflow: hidden;width: 100%;user-select: none;will-change: transform;}.outer-container{position: relative;width: var(--width);}.container{width: 100%;display: grid;grid-template-columns: 1fr auto;position: relative;align-items: center;}input{grid-area: 1/1/2/2;font-size: inherit;border: none;background: transparent;outline: none;color: inherit;font-family: inherit;width: 100%;caret-color: var(--accent-color, teal);}:host([animate]) .input:focus-within .container input,.animate-placeholder .container input {-webkit-transform: translateY(0.6rem); -ms-transform: translateY(0.6rem); transform: translateY(0.6rem);}:host([animate]) .input:focus-within .label,.animate-placeholder .label {-webkit-transform: translateY(-0.7em) scale(0.8); -ms-transform: translateY(-0.7em) scale(0.8); transform: translateY(-0.7em) scale(0.8);opacity: 1;color: var(--accent-color,teal)}:host([variant="outlined"]) .input {box-shadow: 0 0 0 1px var(--border-color, rgba(var(--text-color, (17,17,17)), 0.3)) inset;background: rgba(var(--background-color, (255,255,255)), 1);}.animate-placeholder:focus-within:not(.readonly) .label{color: var(--accent-color,teal)}.feedback-text:not(:empty){display: flex;width: 100%;text-align: left;font-size: 0.9rem;align-items: center;padding: 0.8rem 0;color: rgba(var(--text-color, (17,17,17)), 0.8);}.success{color: var(--success-color);}.error{color: var(--danger-color);}.status-icon{margin-right: 0.2rem;}.status-icon--error{fill: var(--danger-color);}.status-icon--success{fill: var(--success-color);}.datalist{position: absolute;top: 100%;left: 0;width: 100%;z-index: 100;background: rgba(var(--foreground-color, (255,255,255)), 1);border-radius: 0 0 var(--border-radius,0.5rem) var(--border-radius,0.5rem);box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.1);max-height: 20rem;overflow-y: auto;overflow-x: hidden;padding: 0.3rem;}.datalist-item{padding: 0.8rem 1rem;cursor: pointer;transition: background 0.2s;border-radius: 0.5rem;content-visibility: auto;}.datalist-item:focus{outline: none;}.datalist-item:focus-visible{outline: var(--accent-color, teal) solid medium;}@media (any-hover: hover){.icon:hover{ background: rgba(var(--text-color, (17,17,17)), 0.1);}.datalist-item:hover{ background: rgba(var(--text-color, (17,17,17)), 0.06);}}</style><div class="outer-container"><label part="input" class="input"> <slot name="icon"></slot> <div class="container"> <input type="text"/> <div part="placeholder" class="label"></div> <button class="clear" title="Clear" tabindex="-1"><svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg> </button> </div> <slot name="right"></slot></label><ul class="datalist hidden"></ul><p class="feedback-text"></p></div>', customElements.define("sm-input", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smInput.content.cloneNode(!0)), this.inputParent = this.shadowRoot.querySelector(".input"), this.input = this.shadowRoot.querySelector("input"), this.clearBtn = this.shadowRoot.querySelector(".clear"), this.label = this.shadowRoot.querySelector(".label"), this.feedbackText = this.shadowRoot.querySelector(".feedback-text"), this.outerContainer = this.shadowRoot.querySelector(".outer-container"), this.optionList = this.shadowRoot.querySelector(".datalist"), this._helperText = "", this._errorText = "", this.isRequired = !1, this.datalist = [], this.validationFunction = void 0, this.reflectedAttributes = ["value", "required", "disabled", "type", "inputmode", "readonly", "min", "max", "pattern", "minlength", "maxlength", "step", "list", "autocomplete"], this.reset = this.reset.bind(this), this.clear = this.clear.bind(this), this.focusIn = this.focusIn.bind(this), this.focusOut = this.focusOut.bind(this), this.fireEvent = this.fireEvent.bind(this), this.checkInput = this.checkInput.bind(this), this.allowOnlyNum = this.allowOnlyNum.bind(this), this.vibrate = this.vibrate.bind(this), this.handleOptionClick = this.handleOptionClick.bind(this), this.handleInputNavigation = this.handleInputNavigation.bind(this), this.handleDatalistNavigation = this.handleDatalistNavigation.bind(this), this.handleFocus = this.handleFocus.bind(this), this.handleBlur = this.handleBlur.bind(this) } static get observedAttributes() { return ["value", "placeholder", "required", "disabled", "type", "inputmode", "readonly", "min", "max", "pattern", "minlength", "maxlength", "step", "helper-text", "error-text", "list"] } get value() { return this.input.value } set value(t) { t !== this.input.value && (this.input.value = t, this.checkInput()) } get placeholder() { return this.getAttribute("placeholder") } set placeholder(t) { this.setAttribute("placeholder", t) } get type() { return this.getAttribute("type") } set type(t) { this.setAttribute("type", t) } get validity() { return this.input.validity } get disabled() { return this.hasAttribute("disabled") } set disabled(t) { t ? this.inputParent.classList.add("disabled") : this.inputParent.classList.remove("disabled") } get readOnly() { return this.hasAttribute("readonly") } set readOnly(t) { t ? this.setAttribute("readonly", "") : this.removeAttribute("readonly") } set customValidation(t) { this.validationFunction = t } set errorText(t) { this._errorText = t } set helperText(t) { this._helperText = t } get isValid() { if ("" !== this.input.value) { const t = this.input.checkValidity(); let e = !0; return this.validationFunction && (e = Boolean(this.validationFunction(this.input.value))), t && e ? (this.feedbackText.classList.remove("error"), this.feedbackText.classList.add("success"), this.feedbackText.textContent = "") : this._errorText && (this.feedbackText.classList.add("error"), this.feedbackText.classList.remove("success"), this.feedbackText.innerHTML = `<svg class="status-icon status-icon--error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>${this._errorText}`), t && e } } reset() { this.value = "" } clear() { this.value = "", this.input.focus(), this.fireEvent() } focusIn() { this.input.focus() } focusOut() { this.input.blur() } fireEvent() { let t = new Event("input", { bubbles: !0, cancelable: !0, composed: !0 }); this.dispatchEvent(t) } searchDatalist(t) { const e = this.datalist.filter(e => e.toLowerCase().includes(t.toLowerCase())); if (e.sort((e, n) => { const i = e.toLowerCase().indexOf(t.toLowerCase()), s = n.toLowerCase().indexOf(t.toLowerCase()); return i - s }), e.length) { if (this.optionList.children.length > e.length) { const t = this.optionList.children.length - e.length; for (let e = 0; e < t; e++)this.optionList.removeChild(this.optionList.lastChild) } e.forEach((t, e) => { if (this.optionList.children[e]) this.optionList.children[e].textContent = t; else { const e = document.createElement("li"); e.textContent = t, e.classList.add("datalist-item"), e.setAttribute("tabindex", "0"), this.optionList.appendChild(e) } }), this.optionList.classList.remove("hidden") } else this.optionList.classList.add("hidden") } checkInput(t) { this.hasAttribute("readonly") || (this.clearBtn.style.visibility = "" !== this.input.value ? "visible" : "hidden"), this.hasAttribute("placeholder") && "" !== this.getAttribute("placeholder").trim() && ("" !== this.input.value ? (this.animate ? this.inputParent.classList.add("animate-placeholder") : this.label.classList.add("hidden"), this.datalist.length && (this.searchTimeout && clearTimeout(this.searchTimeout), this.searchTimeout = setTimeout(() => { this.searchDatalist(this.input.value.trim()) }, 100))) : (this.animate ? this.inputParent.classList.remove("animate-placeholder") : this.label.classList.remove("hidden"), this.feedbackText.textContent = "", this.datalist.length && (this.optionList.innerHTML = "", this.optionList.classList.add("hidden")))) } allowOnlyNum(t) { 1 === t.key.length && (("." !== t.key || !t.target.value.includes(".") && 0 !== t.target.value.length) && ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."].includes(t.key) || t.preventDefault()) } vibrate() { this.outerContainer.animate([{ transform: "translateX(-1rem)" }, { transform: "translateX(1rem)" }, { transform: "translateX(-0.5rem)" }, { transform: "translateX(0.5rem)" }, { transform: "translateX(0)" }], { duration: 300, easing: "ease" }) } handleOptionClick(t) { this.input.value = t.target.textContent, this.optionList.classList.add("hidden"), this.input.focus() } handleInputNavigation(t) { "ArrowDown" === t.key ? (t.preventDefault(), this.optionList.children.length && this.optionList.children[0].focus()) : "ArrowUp" === t.key && (t.preventDefault(), this.optionList.children.length && this.optionList.children[this.optionList.children.length - 1].focus()) } handleDatalistNavigation(t) { "ArrowUp" === t.key ? (t.preventDefault(), this.shadowRoot.activeElement.previousElementSibling ? this.shadowRoot.activeElement.previousElementSibling.focus() : this.input.focus()) : "ArrowDown" === t.key ? (t.preventDefault(), this.shadowRoot.activeElement.nextElementSibling ? this.shadowRoot.activeElement.nextElementSibling.focus() : this.input.focus()) : "Enter" !== t.key && " " !== t.key || (t.preventDefault(), this.input.value = t.target.textContent, this.optionList.classList.add("hidden"), this.input.focus()) } handleFocus(t) { this.datalist.length && this.searchDatalist(this.input.value.trim()) } handleBlur(t) { this.datalist.length && this.optionList.classList.add("hidden") } connectedCallback() { this.animate = this.hasAttribute("animate"), this.setAttribute("role", "textbox"), this.input.addEventListener("input", this.checkInput), this.clearBtn.addEventListener("click", this.clear), this.datalist.length && (this.optionList.addEventListener("click", this.handleOptionClick), this.input.addEventListener("keydown", this.handleInputNavigation), this.optionList.addEventListener("keydown", this.handleDatalistNavigation)), this.input.addEventListener("focusin", this.handleFocus), this.addEventListener("focusout", this.handleBlur) } attributeChangedCallback(t, e, n) { e !== n && (this.reflectedAttributes.includes(t) && (this.hasAttribute(t) ? this.input.setAttribute(t, this.getAttribute(t) ? this.getAttribute(t) : "") : this.input.removeAttribute(t)), "placeholder" === t ? (this.label.textContent = n, this.setAttribute("aria-label", n)) : this.hasAttribute("value") ? this.checkInput() : "type" === t ? this.hasAttribute("type") && "number" === this.getAttribute("type") ? (this.input.setAttribute("inputmode", "decimal"), this.input.addEventListener("keydown", this.allowOnlyNum)) : this.input.removeEventListener("keydown", this.allowOnlyNum) : "helper-text" === t ? this._helperText = this.getAttribute("helper-text") : "error-text" === t ? this._errorText = this.getAttribute("error-text") : "required" === t ? (this.isRequired = this.hasAttribute("required"), this.isRequired ? this.setAttribute("aria-required", "true") : this.setAttribute("aria-required", "false")) : "readonly" === t ? this.hasAttribute("readonly") ? this.inputParent.classList.add("readonly") : this.inputParent.classList.remove("readonly") : "disabled" === t ? this.hasAttribute("disabled") ? this.inputParent.classList.add("disabled") : this.inputParent.classList.remove("disabled") : "list" === t && this.hasAttribute("list") && "" !== this.getAttribute("list").trim() && (this.datalist = this.getAttribute("list").split(","))) } disconnectedCallback() { this.input.removeEventListener("input", this.checkInput), this.clearBtn.removeEventListener("click", this.clear), this.input.removeEventListener("keydown", this.allowOnlyNum), this.optionList.removeEventListener("click", this.handleOptionClick), this.input.removeEventListener("keydown", this.handleInputNavigation), this.optionList.removeEventListener("keydown", this.handleDatalistNavigation), this.input.removeEventListener("focusin", this.handleFocus), this.removeEventListener("focusout", this.handleBlur) } });
|
|
const spinner = document.createElement("template"); spinner.innerHTML = '<style>*{padding: 0;margin: 0;-webkit-box-sizing: border-box; box-sizing: border-box;}.loader {height: var(--size, 1.5rem);width: var(--size, 1.5rem);stroke-width: 8;overflow: visible;stroke: var(--accent-color, teal);fill: none;stroke-dashoffset: 180;stroke-dasharray: 180;animation: load 2s infinite, spin 1s linear infinite;}@keyframes load {50% {stroke-dashoffset: 0;}100%{stroke-dashoffset: -180;}}@keyframes spin {100% {transform: rotate(360deg);}}</style><svg viewBox="0 0 64 64" class="loader"><circle cx="32" cy="32" r="32" /></svg>'; class SpinnerLoader extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(spinner.content.cloneNode(!0)) } } window.customElements.define("sm-spinner", SpinnerLoader);
|