From b4838a50767471d86a92eeea0e5f87dfd7c11a39 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Wed, 28 Dec 2022 03:46:38 +0530 Subject: [PATCH] performance improvements --- components/dist/form.js | 27 ++++++++++++++++++++++----- components/dist/form.min.js | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/components/dist/form.js b/components/dist/form.js index 700b63a..d489fde 100644 --- a/components/dist/form.js +++ b/components/dist/form.js @@ -30,12 +30,21 @@ customElements.define('sm-form', class extends HTMLElement { this.form = this.shadowRoot.querySelector('form'); this.invalidFields = false; + this.skipSubmit = false; + this.isFormValid = false; + this.supportedElements = new Set(['INPUT', 'SM-INPUT', 'SM-TEXTAREA', 'SM-CHECKBOX', 'TAGS-INPUT', 'FILE-INPUT', 'SM-SWITCH', 'SM-RADIO']); this.debounce = this.debounce.bind(this); this._checkValidity = this._checkValidity.bind(this); this.handleKeydown = this.handleKeydown.bind(this); this.reset = this.reset.bind(this); this.elementsChanged = this.elementsChanged.bind(this); } + static get observedAttributes() { + return ['skip-submit']; + } + get validity() { + return this.isFormValid; + } debounce(callback, wait) { let timeoutId = null; return (...args) => { @@ -48,14 +57,16 @@ customElements.define('sm-form', class extends HTMLElement { _checkValidity() { if (!this.submitButton) return; this.invalidFields = this._requiredElements.filter(([elem, isWC]) => isWC ? !elem.isValid : !elem.checkValidity()); - this.submitButton.disabled = this.invalidFields.length > 0; - if (this.invalidFields.length > 0) - this.dispatchEvent(new CustomEvent('invalid', { + this.isFormValid = this.invalidFields.length === 0; + if (!this.skipSubmit) + this.submitButton.disabled = !this.isFormValid; + if (this.isFormValid) + this.dispatchEvent(new CustomEvent('valid', { bubbles: true, composed: true, })); else - this.dispatchEvent(new CustomEvent('valid', { + this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, composed: true, })); @@ -117,13 +128,19 @@ customElements.define('sm-form', class extends HTMLElement { this.addEventListener('keydown', this.debounce(this.handleKeydown, 100)); this.mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { - if (mutation.type === 'childList') { + if (mutation.type === 'childList' && [...mutation.addedNodes].some(node => this.supportedElements.has(node.tagName)) || [...mutation.removedNodes].some(node => this.supportedElements.has(node.tagName))) { updateFormDecedents(); } }); }); this.mutationObserver.observe(this, { childList: true, subtree: true }); } + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'skip-submit') { + this.skipSubmit = newValue !== null; + } + } + disconnectedCallback() { this.removeEventListener('input', this.debounce(this._checkValidity, 100)); this.removeEventListener('keydown', this.debounce(this.handleKeydown, 100)); diff --git a/components/dist/form.min.js b/components/dist/form.min.js index c868406..17cff14 100644 --- a/components/dist/form.min.js +++ b/components/dist/form.min.js @@ -1 +1 @@ -const smForm = document.createElement("template"); smForm.innerHTML = `
`, customElements.define("sm-form", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smForm.content.cloneNode(!0)), this.form = this.shadowRoot.querySelector("form"), this.invalidFields = !1, this.debounce = this.debounce.bind(this), this._checkValidity = this._checkValidity.bind(this), this.handleKeydown = this.handleKeydown.bind(this), this.reset = this.reset.bind(this), this.elementsChanged = this.elementsChanged.bind(this) } debounce(e, t) { let i = null; return (...s) => { window.clearTimeout(i), i = window.setTimeout(() => { e.apply(null, s) }, t) } } _checkValidity() { this.submitButton && (this.invalidFields = this._requiredElements.filter(([e, t]) => t ? !e.isValid : !e.checkValidity()), this.submitButton.disabled = this.invalidFields.length > 0, this.invalidFields.length > 0 ? this.dispatchEvent(new CustomEvent("invalid", { bubbles: !0, composed: !0 })) : this.dispatchEvent(new CustomEvent("valid", { bubbles: !0, composed: !0 }))) } handleKeydown(e) { if ("Enter" === e.key && e.target.tagName.includes("INPUT")) { if (this.invalidFields.length) for (let [t, i] of this._requiredElements) { let s = i ? !t.isValid : !t.checkValidity(); if (s) { (t?.shadowRoot?.lastElementChild || t).animate([{ transform: "translateX(-1rem)" }, { transform: "translateX(1rem)" }, { transform: "translateX(-0.5rem)" }, { transform: "translateX(0.5rem)" }, { transform: "translateX(0)" },], { duration: 300, easing: "ease" }), i ? t.focusIn() : t.focus(); break } } else this.submitButton && this.submitButton.click(), this.dispatchEvent(new CustomEvent("submit", { bubbles: !0, composed: !0 })) } } reset() { this.formElements.forEach(([e, t]) => { t ? e.reset() : e.value = "" }) } elementsChanged() { this.formElements = [...this.querySelectorAll("input, sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio")].map(e => [e, e.tagName.includes("-")]), this._requiredElements = this.formElements.filter(([e]) => e.hasAttribute("required")), this.submitButton = this.querySelector('[variant="primary"], [type="submit"]'), this.resetButton = this.querySelector('[type="reset"]'), this.resetButton && this.resetButton.addEventListener("click", this.reset), this._checkValidity() } connectedCallback() { let e = this.debounce(this.elementsChanged, 100); this.shadowRoot.querySelector("slot").addEventListener("slotchange", e), this.addEventListener("input", this.debounce(this._checkValidity, 100)), this.addEventListener("keydown", this.debounce(this.handleKeydown, 100)), this.mutationObserver = new MutationObserver(t => { t.forEach(t => { "childList" === t.type && e() }) }), this.mutationObserver.observe(this, { childList: !0, subtree: !0 }) } disconnectedCallback() { this.removeEventListener("input", this.debounce(this._checkValidity, 100)), this.removeEventListener("keydown", this.debounce(this.handleKeydown, 100)), this.mutationObserver.disconnect() } }); \ No newline at end of file +const smForm = document.createElement("template"); smForm.innerHTML = `
`, customElements.define("sm-form", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smForm.content.cloneNode(!0)), this.form = this.shadowRoot.querySelector("form"), this.invalidFields = !1, this.skipSubmit = !1, this.isFormValid = !1, this.supportedElements = new Set(["INPUT", "SM-INPUT", "SM-TEXTAREA", "SM-CHECKBOX", "TAGS-INPUT", "FILE-INPUT", "SM-SWITCH", "SM-RADIO"]), this.debounce = this.debounce.bind(this), this._checkValidity = this._checkValidity.bind(this), this.handleKeydown = this.handleKeydown.bind(this), this.reset = this.reset.bind(this), this.elementsChanged = this.elementsChanged.bind(this) } static get observedAttributes() { return ["skip-submit"] } get validity() { return this.isFormValid } debounce(t, e) { let i = null; return (...s) => { window.clearTimeout(i), i = window.setTimeout(() => { t.apply(null, s) }, e) } } _checkValidity() { this.submitButton && (this.invalidFields = this._requiredElements.filter(([t, e]) => e ? !t.isValid : !t.checkValidity()), this.isFormValid = 0 === this.invalidFields.length, this.skipSubmit || (this.submitButton.disabled = !this.isFormValid), this.isFormValid ? this.dispatchEvent(new CustomEvent("valid", { bubbles: !0, composed: !0 })) : this.dispatchEvent(new CustomEvent("invalid", { bubbles: !0, composed: !0 }))) } handleKeydown(t) { if ("Enter" === t.key && t.target.tagName.includes("INPUT")) { if (this.invalidFields.length) for (let [e, i] of this._requiredElements) { let s = i ? !e.isValid : !e.checkValidity(); if (s) { (e?.shadowRoot?.lastElementChild || e).animate([{ transform: "translateX(-1rem)" }, { transform: "translateX(1rem)" }, { transform: "translateX(-0.5rem)" }, { transform: "translateX(0.5rem)" }, { transform: "translateX(0)" },], { duration: 300, easing: "ease" }), i ? e.focusIn() : e.focus(); break } } else this.submitButton && this.submitButton.click(), this.dispatchEvent(new CustomEvent("submit", { bubbles: !0, composed: !0 })) } } reset() { this.formElements.forEach(([t, e]) => { e ? t.reset() : t.value = "" }) } elementsChanged() { this.formElements = [...this.querySelectorAll("input, sm-input, sm-textarea, sm-checkbox, tags-input, file-input, sm-switch, sm-radio")].map(t => [t, t.tagName.includes("-")]), this._requiredElements = this.formElements.filter(([t]) => t.hasAttribute("required")), this.submitButton = this.querySelector('[variant="primary"], [type="submit"]'), this.resetButton = this.querySelector('[type="reset"]'), this.resetButton && this.resetButton.addEventListener("click", this.reset), this._checkValidity() } connectedCallback() { let t = this.debounce(this.elementsChanged, 100); this.shadowRoot.querySelector("slot").addEventListener("slotchange", t), this.addEventListener("input", this.debounce(this._checkValidity, 100)), this.addEventListener("keydown", this.debounce(this.handleKeydown, 100)), this.mutationObserver = new MutationObserver(e => { e.forEach(e => { ("childList" === e.type && [...e.addedNodes].some(t => this.supportedElements.has(t.tagName)) || [...e.removedNodes].some(t => this.supportedElements.has(t.tagName))) && t() }) }), this.mutationObserver.observe(this, { childList: !0, subtree: !0 }) } attributeChangedCallback(t, e, i) { "skip-submit" === t && (this.skipSubmit = null !== i) } disconnectedCallback() { this.removeEventListener("input", this.debounce(this._checkValidity, 100)), this.removeEventListener("keydown", this.debounce(this.handleKeydown, 100)), this.mutationObserver.disconnect() } }); \ No newline at end of file