diff --git a/components.js b/components.js index ce4b1a1..8ff0dd8 100644 --- a/components.js +++ b/components.js @@ -11,7 +11,7 @@ const smSelect = document.createElement("template"); smSelect.innerHTML = '\n { e.value === t ? (e.setAttribute("active", ""), e.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" })) : e.removeAttribute("active") })) } fireEvent() { this.dispatchEvent(new CustomEvent("change", { bubbles: !0, composed: !0, detail: { value: this._value } })) } connectedCallback() { this.setAttribute("role", "listbox"); const t = this.shadowRoot.querySelector("slot"), e = this.shadowRoot.querySelector(".cover--left"), n = this.shadowRoot.querySelector(".cover--right"), i = this.shadowRoot.querySelector(".nav-button--left"), s = this.shadowRoot.querySelector(".nav-button--right"); t.addEventListener("slotchange", o => { this.assignedElements = t.assignedElements(), this.assignedElements.forEach(t => { t.hasAttribute("selected") && (t.setAttribute("active", ""), this._value = t.value) }), this.hasAttribute("multiline") || (this.assignedElements.length > 0 ? (r.observe(this.assignedElements[0]), a.observe(this.assignedElements[this.assignedElements.length - 1])) : (i.classList.add("hide"), s.classList.add("hide"), e.classList.add("hide"), n.classList.add("hide"), r.disconnect(), a.disconnect())) }); const o = new ResizeObserver(t => { t.forEach(t => { if (t.contentBoxSize) { const e = Array.isArray(t.contentBoxSize) ? t.contentBoxSize[0] : t.contentBoxSize; this.scrollDistance = .6 * e.inlineSize } else this.scrollDistance = .6 * t.contentRect.width }) }); o.observe(this), this.stripSelect.addEventListener("option-clicked", t => { this._value !== t.target.value && (this.setSelectedOption(t.target.value), this.fireEvent()) }); const r = new IntersectionObserver(t => { t.forEach(t => { t.isIntersecting ? (i.classList.add("hide"), e.classList.add("hide")) : (i.classList.remove("hide"), e.classList.remove("hide")) }) }, { threshold: .9, root: this }), a = new IntersectionObserver(t => { t.forEach(t => { t.isIntersecting ? (s.classList.add("hide"), n.classList.add("hide")) : (s.classList.remove("hide"), n.classList.remove("hide")) }) }, { threshold: .9, root: this }); i.addEventListener("click", this.scrollLeft), s.addEventListener("click", this.scrollRight) } disconnectedCallback() { navButtonLeft.removeEventListener("click", this.scrollLeft), navButtonRight.removeEventListener("click", this.scrollRight) } }); const stripOption = document.createElement("template"); stripOption.innerHTML = '\n\n\n', customElements.define("strip-option", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(stripOption.content.cloneNode(!0)), this._value = void 0, this.radioButton = this.shadowRoot.querySelector("input"), this.fireEvent = this.fireEvent.bind(this), this.handleKeyDown = this.handleKeyDown.bind(this) } get value() { return this._value } fireEvent() { this.dispatchEvent(new CustomEvent("option-clicked", { bubbles: !0, composed: !0, detail: { value: this._value } })) } handleKeyDown(t) { "Enter" !== t.key && "Space" !== t.key || this.fireEvent() } connectedCallback() { this.setAttribute("role", "option"), this.setAttribute("tabindex", "0"), this._value = this.getAttribute("value"), this.addEventListener("click", this.fireEvent), this.addEventListener("keydown", this.handleKeyDown) } disconnectedCallback() { this.removeEventListener("click", this.fireEvent), this.removeEventListener("keydown", this.handleKeyDown) } }); const smTabHeader = document.createElement("template"); smTabHeader.innerHTML = '\n\n
\n
\n \n
\n
\n
\n', customElements.define("sm-tab-header", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smTabHeader.content.cloneNode(!0)), this.prevTab, this.allTabs, this.activeTab, this.indicator = this.shadowRoot.querySelector(".indicator"), this.tabSlot = this.shadowRoot.querySelector("slot"), this.tabHeader = this.shadowRoot.querySelector(".tab-header"), this.changeTab = this.changeTab.bind(this), this.handleClick = this.handleClick.bind(this), this.handlePanelChange = this.handlePanelChange.bind(this), this.moveIndiactor = this.moveIndiactor.bind(this) } fireEvent(t) { this.dispatchEvent(new CustomEvent(`switchedtab${this.target}`, { bubbles: !0, detail: { index: parseInt(t) } })) } moveIndiactor(t) { this.indicator.setAttribute("style", `width: ${t.width}px; transform: translateX(${t.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`) } changeTab(t) { t !== this.prevTab && t.closest("sm-tab") && (this.prevTab && this.prevTab.classList.remove("active"), t.classList.add("active"), this.tabHeader.scrollTo({ behavior: "smooth", left: t.getBoundingClientRect().left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft }), this.moveIndiactor(t.getBoundingClientRect()), this.prevTab = t, this.activeTab = t) } handleClick(t) { t.target.closest("sm-tab") && (this.changeTab(t.target), this.fireEvent(t.target.dataset.index)) } handlePanelChange(t) { this.changeTab(this.allTabs[t.detail.index]) } connectedCallback() { if (!this.hasAttribute("target") || "" === this.getAttribute("target").value) return; this.target = this.getAttribute("target"), this.tabSlot.addEventListener("slotchange", () => { this.allTabs = this.tabSlot.assignedElements(), this.allTabs.forEach((t, e) => { t.dataset.index = e }) }), this.addEventListener("click", this.handleClick), document.addEventListener(`switchedpanel${this.target}`, this.handlePanelChange); let t = new ResizeObserver(t => { t.forEach(t => { if (this.prevTab) { let t = this.activeTab.getBoundingClientRect(); this.moveIndiactor(t) } }) }); t.observe(this); let e = new IntersectionObserver(t => { t.forEach(t => { if (t.isIntersecting) if (this.indicator.style.transition = "none", this.activeTab) { let t = this.activeTab.getBoundingClientRect(); this.moveIndiactor(t) } else { this.allTabs[0].classList.add("active"); let t = this.allTabs[0].getBoundingClientRect(); this.moveIndiactor(t), this.fireEvent(0), this.prevTab = this.tabSlot.assignedElements()[0], this.activeTab = this.prevTab } }) }, { threshold: 1 }); e.observe(this) } disconnectedCallback() { this.removeEventListener("click", this.handleClick), document.removeEventListener(`switchedpanel${this.target}`, this.handlePanelChange) } }); const smTab = document.createElement("template"); smTab.innerHTML = '\n\n
\n\n
\n', customElements.define("sm-tab", class extends HTMLElement { constructor() { super(), this.shadow = this.attachShadow({ mode: "open" }).append(smTab.content.cloneNode(!0)) } }); const smTabPanels = document.createElement("template"); smTabPanels.innerHTML = '\n\n
\n Nothing to see here.\n
\n', customElements.define("sm-tab-panels", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smTabPanels.content.cloneNode(!0)), this.isTransitioning = !1, this.panelContainer = this.shadowRoot.querySelector(".panel-container"), this.handleTabChange = this.handleTabChange.bind(this) } handleTabChange(t) { this.isTransitioning = !0, this.panelContainer.scrollTo({ left: this.allPanels[t.detail.index].getBoundingClientRect().left - this.panelContainer.getBoundingClientRect().left + this.panelContainer.scrollLeft, behavior: "smooth" }), setTimeout(() => { this.isTransitioning = !1 }, 300) } fireEvent(t) { this.dispatchEvent(new CustomEvent(`switchedpanel${this.id}`, { bubbles: !0, detail: { index: parseInt(t) } })) } connectedCallback() { const t = this.shadowRoot.querySelector("slot"); t.addEventListener("slotchange", t => { this.allPanels = t.target.assignedElements(), this.allPanels.forEach((t, n) => { t.dataset.index = n, e.observe(t) }) }), document.addEventListener(`switchedtab${this.id}`, this.handleTabChange); const e = new IntersectionObserver(t => { t.forEach(t => { !this.isTransitioning && t.isIntersecting && this.fireEvent(t.target.dataset.index) }) }, { threshold: .6 }) } disconnectedCallback() { intersectionObserver.disconnect(), document.removeEventListener(`switchedtab${this.id}`, this.handleTabChange) } }); -const smTextarea = document.createElement("template"); smTextarea.innerHTML = '\n \n \n ', customElements.define("sm-textarea", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smTextarea.content.cloneNode(!0)), this.textarea = this.shadowRoot.querySelector("textarea"), this.textareaBox = this.shadowRoot.querySelector(".textarea"), this.placeholder = this.shadowRoot.querySelector(".placeholder"), this.reflectedAttributes = ["disabled", "required", "readonly", "rows", "minlength", "maxlength"], this.reset = this.reset.bind(this), this.focusIn = this.focusIn.bind(this), this.fireEvent = this.fireEvent.bind(this), this.checkInput = this.checkInput.bind(this) } static get observedAttributes() { return ["disabled", "value", "placeholder", "required", "readonly", "rows", "minlength", "maxlength"] } get value() { return this.textarea.value } set value(e) { this.setAttribute("value", e), this.fireEvent() } get disabled() { return this.hasAttribute("disabled") } set disabled(e) { e ? this.setAttribute("disabled", "") : this.removeAttribute("disabled") } get isValid() { return this.textarea.checkValidity() } reset() { this.setAttribute("value", "") } focusIn() { this.textarea.focus() } fireEvent() { let e = new Event("input", { bubbles: !0, cancelable: !0, composed: !0 }); this.dispatchEvent(e) } checkInput() { this.hasAttribute("placeholder") && "" !== this.getAttribute("placeholder") && ("" !== this.textarea.value ? this.placeholder.classList.add("hide") : this.placeholder.classList.remove("hide")) } connectedCallback() { this.textarea.addEventListener("input", e => { this.textareaBox.dataset.value = this.textarea.value, this.checkInput() }) } attributeChangedCallback(e, t, n) { this.reflectedAttributes.includes(e) ? this.hasAttribute(e) ? this.textarea.setAttribute(e, this.getAttribute(e) ? this.getAttribute(e) : "") : this.textContent.removeAttribute(e) : "placeholder" === e ? this.placeholder.textContent = this.getAttribute("placeholder") : "value" === e && (this.textarea.value = n, this.textareaBox.dataset.value = n, this.checkInput()) } }); +const smTextarea = document.createElement("template"); smTextarea.innerHTML = '\n \n \n ', customElements.define("sm-textarea", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smTextarea.content.cloneNode(!0)), this.textarea = this.shadowRoot.querySelector("textarea"), this.textareaBox = this.shadowRoot.querySelector(".textarea"), this.placeholder = this.shadowRoot.querySelector(".placeholder"), this.reflectedAttributes = ["disabled", "required", "readonly", "rows", "minlength", "maxlength"], this.reset = this.reset.bind(this), this.focusIn = this.focusIn.bind(this), this.fireEvent = this.fireEvent.bind(this), this.checkInput = this.checkInput.bind(this) } static get observedAttributes() { return ["disabled", "value", "placeholder", "required", "readonly", "rows", "minlength", "maxlength"] } get value() { return this.textarea.value } set value(e) { this.setAttribute("value", e), this.fireEvent() } get disabled() { return this.hasAttribute("disabled") } set disabled(e) { e ? this.setAttribute("disabled", "") : this.removeAttribute("disabled") } get isValid() { return this.textarea.checkValidity() } reset() { this.setAttribute("value", "") } focusIn() { this.textarea.focus() } fireEvent() { let e = new Event("input", { bubbles: !0, cancelable: !0, composed: !0 }); this.dispatchEvent(e) } checkInput() { this.hasAttribute("placeholder") && "" !== this.getAttribute("placeholder") && ("" !== this.textarea.value ? this.placeholder.classList.add("hide") : this.placeholder.classList.remove("hide")) } connectedCallback() { this.textarea.addEventListener("input", e => { this.textareaBox.dataset.value = this.textarea.value, this.checkInput() }) } attributeChangedCallback(e, t, n) { this.reflectedAttributes.includes(e) ? this.hasAttribute(e) ? this.textarea.setAttribute(e, this.getAttribute(e) ? this.getAttribute(e) : "") : this.textContent.removeAttribute(e) : "placeholder" === e ? this.placeholder.textContent = this.getAttribute("placeholder") : "value" === e && (this.textarea.value = n, this.textareaBox.dataset.value = n, this.checkInput()) } }); const themeToggle = document.createElement("template"); themeToggle.innerHTML = '\n \n \n'; class ThemeToggle extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(themeToggle.content.cloneNode(!0)), this.isChecked = !1, this.hasTheme = "light", this.toggleState = this.toggleState.bind(this), this.fireEvent = this.fireEvent.bind(this), this.handleThemeChange = this.handleThemeChange.bind(this) } static get observedAttributes() { return ["checked"] } daylight() { this.hasTheme = "light", document.body.dataset.theme = "light", this.setAttribute("aria-checked", "false") } nightlight() { this.hasTheme = "dark", document.body.dataset.theme = "dark", this.setAttribute("aria-checked", "true") } toggleState() { this.toggleAttribute("checked"), this.fireEvent() } handleKeyDown(e) { " " === e.key && this.toggleState() } handleThemeChange(e) { e.detail.theme !== this.hasTheme && ("dark" === e.detail.theme ? this.setAttribute("checked", "") : this.removeAttribute("checked")) } fireEvent() { this.dispatchEvent(new CustomEvent("themechange", { bubbles: !0, composed: !0, detail: { theme: this.hasTheme } })) } connectedCallback() { this.setAttribute("role", "switch"), this.setAttribute("aria-label", "theme toggle"), "dark" === localStorage.getItem(`${window.location.hostname}-theme`) ? (this.nightlight(), this.setAttribute("checked", "")) : "light" === localStorage.getItem(`${window.location.hostname}-theme`) ? (this.daylight(), this.removeAttribute("checked")) : window.matchMedia("(prefers-color-scheme: dark)").matches ? (this.nightlight(), this.setAttribute("checked", "")) : (this.daylight(), this.removeAttribute("checked")), this.addEventListener("click", this.toggleState), this.addEventListener("keydown", this.handleKeyDown), document.addEventListener("themechange", this.handleThemeChange) } disconnectedCallback() { this.removeEventListener("click", this.toggleState), this.removeEventListener("keydown", this.handleKeyDown), document.removeEventListener("themechange", this.handleThemeChange) } attributeChangedCallback(e, t, n) { "checked" === e && (this.hasAttribute("checked") ? (this.nightlight(), localStorage.setItem(`${window.location.hostname}-theme`, "dark")) : (this.daylight(), localStorage.setItem(`${window.location.hostname}-theme`, "light"))) } } window.customElements.define("theme-toggle", ThemeToggle); const cubeLoader = document.createElement("template"); cubeLoader.innerHTML = '\n \n
\n
\n
\n
\n
\n'; class CubeLoader extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(cubeLoader.content.cloneNode(!0)) } } window.customElements.define("cube-loader", CubeLoader); const smCarousel = document.createElement("template"); smCarousel.innerHTML = '\n\n\n', customElements.define("sm-carousel", class extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }).append(smCarousel.content.cloneNode(!0)), this.isAutoPlaying = !1, this.autoPlayInterval = 5e3, this.autoPlayTimeout, this.initialTimeout, this.activeSlideNum = 0, this.carouselItems, this.indicators, this.showIndicator = !1, this.carousel = this.shadowRoot.querySelector(".carousel"), this.carouselContainer = this.shadowRoot.querySelector(".carousel-container"), this.carouselSlot = this.shadowRoot.querySelector("slot"), this.navButtonRight = this.shadowRoot.querySelector(".carousel__button--right"), this.navButtonLeft = this.shadowRoot.querySelector(".carousel__button--left"), this.indicatorsContainer = this.shadowRoot.querySelector(".indicators"), this.scrollLeft = this.scrollLeft.bind(this), this.scrollRight = this.scrollRight.bind(this), this.handleIndicatorClick = this.handleIndicatorClick.bind(this), this.showSlide = this.showSlide.bind(this), this.nextSlide = this.nextSlide.bind(this), this.autoPlay = this.autoPlay.bind(this), this.startAutoPlay = this.startAutoPlay.bind(this), this.stopAutoPlay = this.stopAutoPlay.bind(this) } static get observedAttributes() { return ["indicator", "autoplay", "interval"] } scrollLeft() { this.carousel.scrollBy({ left: -this.scrollDistance, behavior: "smooth" }) } scrollRight() { this.carousel.scrollBy({ left: this.scrollDistance, behavior: "smooth" }) } showSlide(t) { this.carousel.scrollTo({ left: this.carouselItems[t].getBoundingClientRect().left - this.carousel.getBoundingClientRect().left + this.carousel.scrollLeft, behavior: "smooth" }) } nextSlide() { if (!this.carouselItems) return; let t = this.activeSlideNum + 1 < this.carouselItems.length ? this.activeSlideNum + 1 : 0; this.showSlide(t) } autoPlay() { this.nextSlide(), this.isAutoPlaying && (this.autoPlayTimeout = setTimeout(() => { this.autoPlay() }, this.autoPlayInterval)) } startAutoPlay() { this.setAttribute("autoplay", "") } stopAutoPlay() { this.removeAttribute("autoplay") } createIndicator(t) { let n = document.createElement("div"); return n.classList.add("indicator"), n.dataset.rank = t, n } handleIndicatorClick(t) { if (t.target.closest(".indicator")) { const n = parseInt(t.target.closest(".indicator").dataset.rank); this.activeSlideNum !== n && this.showSlide(n) } } handleKeyDown(t) { "ArrowLeft" === t.code ? this.scrollRight() : "ArrowRight" === t.code && this.scrollRight() } connectedCallback() { let t = document.createDocumentFragment(); this.carouselSlot.addEventListener("slotchange", n => { this.carouselItems = this.carouselSlot.assignedElements(), this.carouselItems.forEach(t => i.observe(t)), this.carouselItems.length > 0 ? (e.observe(this.carouselItems[0]), o.observe(this.carouselItems[this.carouselItems.length - 1])) : (navButtonLeft.classList.add("hide"), navButtonRight.classList.add("hide"), e.disconnect(), o.disconnect()), this.showIndicator && (this.indicatorsContainer.innerHTML = "", this.carouselItems.forEach((n, i) => { t.append(this.createIndicator(i)), n.dataset.rank = i }), this.indicatorsContainer.append(t), this.indicators = this.indicatorsContainer.children) }); const n = { threshold: .9, root: this }, i = new IntersectionObserver(t => { t.forEach(t => { if (this.showIndicator) { const n = parseInt(t.target.dataset.rank); t.isIntersecting ? (this.indicators[n].classList.add("active"), this.activeSlideNum = n) : this.indicators[n].classList.remove("active") } }) }, n), e = new IntersectionObserver(t => { t.forEach(t => { t.isIntersecting ? this.navButtonLeft.classList.add("hide") : this.navButtonLeft.classList.remove("hide") }) }, n), o = new IntersectionObserver(t => { t.forEach(t => { t.isIntersecting ? this.navButtonRight.classList.add("hide") : this.navButtonRight.classList.remove("hide") }) }, n), s = new ResizeObserver(t => { t.forEach(t => { if (t.contentBoxSize) { const n = Array.isArray(t.contentBoxSize) ? t.contentBoxSize[0] : t.contentBoxSize; this.scrollDistance = .6 * n.inlineSize } else this.scrollDistance = .6 * t.contentRect.width }) }); s.observe(this), this.addEventListener("keydown", this.handleKeyDown), this.navButtonRight.addEventListener("click", this.scrollRight), this.navButtonLeft.addEventListener("click", this.scrollLeft), this.indicatorsContainer.addEventListener("click", this.handleIndicatorClick) } attributeChangedCallback(t, n, i) { n !== i && ("indicator" === t && (this.showIndicator = this.hasAttribute("indicator")), "autoplay" === t && (this.hasAttribute("autoplay") ? this.initialTimeout = setTimeout(() => { this.isAutoPlaying = !0, this.autoPlay() }, this.autoPlayInterval) : (this.isAutoPlaying = !1, clearTimeout(this.autoPlayTimeout), clearTimeout(this.initialTimeout))), "interval" === t && (this.hasAttribute("interval") && "" !== this.getAttribute("interval").trim() ? this.autoPlayInterval = Math.abs(parseInt(this.getAttribute("interval").trim())) : this.autoPlayInterval = 5e3)) } disconnectedCallback() { this.navButtonRight.removeEventListener("click", this.scrollRight), this.navButtonLeft.removeEventListener("click", this.scrollLeft), this.indicatorsContainer.removeEventListener("click", this.handleIndicatorClick) } }); diff --git a/index.html b/index.html index eb25a46..068dd7c 100644 --- a/index.html +++ b/index.html @@ -344,12 +344,11 @@