Created
September 4, 2024 16:03
-
-
Save sajanbasnet75/80e95830a05a2b1291116775e03a1153 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { FocusTrap, Delegate } from "vendor"; | |
| var DialogElement = class _DialogElement extends HTMLElement { | |
| static get observedAttributes() { | |
| return ["id", "open"]; | |
| } | |
| static #lockLayerCount = 0; | |
| #isLocked = false; | |
| constructor() { | |
| super(); | |
| this.addEventListener("dialog:force-close", (event) => { | |
| this.hide(); | |
| event.stopPropagation(); | |
| }); | |
| } | |
| connectedCallback() { | |
| if (this.id) { | |
| this.delegate.off().on("click", `[aria-controls="${this.id}"]`, this._onToggleClicked.bind(this)); | |
| } | |
| this._abortController = new AbortController(); | |
| this.setAttribute("role", "dialog"); | |
| if (Shopify.designMode) { | |
| this.addEventListener("shopify:block:select", (event) => this.show(!event.detail.load), { signal: this._abortController.signal }); | |
| this.addEventListener("shopify:block:deselect", this.hide, { signal: this._abortController.signal }); | |
| this._shopifySection = this._shopifySection || this.closest(".shopify-section"); | |
| if (this._shopifySection) { | |
| if (this.hasAttribute("handle-section-events")) { | |
| this._shopifySection.addEventListener("shopify:section:select", (event) => this.show(!event.detail.load), { signal: this._abortController.signal }); | |
| this._shopifySection.addEventListener("shopify:section:deselect", this.hide.bind(this), { signal: this._abortController.signal }); | |
| } | |
| this._shopifySection.addEventListener("shopify:section:unload", () => this.remove(), { signal: this._abortController.signal }); | |
| } | |
| } | |
| } | |
| disconnectedCallback() { | |
| this._abortController.abort(); | |
| this.delegate.off(); | |
| this.focusTrap?.deactivate({ onDeactivate: () => { | |
| } }); | |
| if (this.#isLocked) { | |
| this.#isLocked = false; | |
| document.documentElement.classList.toggle("lock", --_DialogElement.#lockLayerCount > 0); | |
| } | |
| } | |
| show(animate19 = true) { | |
| if (this.open) { | |
| return; | |
| } | |
| this.setAttribute("open", animate19 ? "" : "immediate"); | |
| return waitForEvent(this, "dialog:after-show"); | |
| } | |
| hide() { | |
| if (!this.open) { | |
| return; | |
| } | |
| this.removeAttribute("open"); | |
| return waitForEvent(this, "dialog:after-hide"); | |
| } | |
| get delegate() { | |
| return this._delegate = this._delegate || new Delegate(document.body); | |
| } | |
| get controls() { | |
| return Array.from(this.getRootNode().querySelectorAll(`[aria-controls="${this.id}"]`)); | |
| } | |
| get open() { | |
| return this.hasAttribute("open"); | |
| } | |
| get shouldTrapFocus() { | |
| return true; | |
| } | |
| get shouldLock() { | |
| return false; | |
| } | |
| /** | |
| * Sometimes (especially for drawer) we need to ensure that an element is on top of everything else. To do that, | |
| * we need to move the element to the body. We are doing that on open, and then restore the initial position on | |
| * close | |
| */ | |
| get shouldAppendToBody() { | |
| return false; | |
| } | |
| get initialFocus() { | |
| return this.hasAttribute("initial-focus") ? this.getAttribute("initial-focus") : this.hasAttribute("tabindex") ? this : this.querySelector('input:not([type="hidden"])') || false; | |
| } | |
| get preventScrollWhenTrapped() { | |
| return true; | |
| } | |
| get focusTrap() { | |
| return this._focusTrap = this._focusTrap || new FocusTrap.createFocusTrap([this, this.shadowRoot], { | |
| onDeactivate: this.hide.bind(this), | |
| allowOutsideClick: this._allowOutsideClick.bind(this), | |
| initialFocus: window.matchMedia("screen and (pointer: fine)").matches ? this.initialFocus : false, | |
| fallbackFocus: this, | |
| preventScroll: this.preventScrollWhenTrapped | |
| }); | |
| } | |
| attributeChangedCallback(name, oldValue, newValue) { | |
| switch (name) { | |
| case "id": | |
| if (newValue) { | |
| this.delegate.off().on("click", `[aria-controls="${this.id}"]`, this._onToggleClicked.bind(this)); | |
| } | |
| break; | |
| case "open": | |
| this.controls.forEach((toggle) => toggle.setAttribute("aria-expanded", newValue === null ? "false" : "true")); | |
| if (oldValue === null && (newValue === "" || newValue === "immediate")) { | |
| this.removeAttribute("inert"); | |
| this._originalParentBeforeAppend = null; | |
| if (this.shouldAppendToBody && this.parentElement !== document.body) { | |
| this._originalParentBeforeAppend = this.parentElement; | |
| document.body.append(this); | |
| } | |
| const showTransitionPromise = this._showTransition(newValue !== "immediate") || Promise.resolve(); | |
| showTransitionPromise.then(() => { | |
| this.dispatchEvent(new CustomEvent("dialog:after-show", { bubbles: true })); | |
| }); | |
| if (this.shouldTrapFocus) { | |
| this.focusTrap.activate({ | |
| checkCanFocusTrap: () => showTransitionPromise | |
| }); | |
| } | |
| if (this.shouldLock) { | |
| _DialogElement.#lockLayerCount += 1; | |
| this.#isLocked = true; | |
| document.documentElement.classList.add("lock"); | |
| } | |
| } else if (oldValue !== null && newValue === null) { | |
| this.setAttribute("inert", ""); | |
| const hideTransitionPromise = this._hideTransition() || Promise.resolve(); | |
| hideTransitionPromise.then(() => { | |
| if (this.parentElement === document.body && this._originalParentBeforeAppend) { | |
| this._originalParentBeforeAppend.appendChild(this); | |
| this._originalParentBeforeAppend = null; | |
| } | |
| this.dispatchEvent(new CustomEvent("dialog:after-hide", { bubbles: true })); | |
| }); | |
| this.focusTrap?.deactivate({ | |
| checkCanReturnFocus: () => hideTransitionPromise | |
| }); | |
| if (this.shouldLock) { | |
| this.#isLocked = false; | |
| document.documentElement.classList.toggle("lock", --_DialogElement.#lockLayerCount > 0); | |
| } | |
| } | |
| this.dispatchEvent(new CustomEvent("toggle", { bubbles: true })); | |
| break; | |
| } | |
| } | |
| /* Those methods are used to perform custom show/hide transition, and must return a promise */ | |
| _showTransition(animate19 = true) { | |
| } | |
| _hideTransition() { | |
| } | |
| /* By default, a focus element is deactivated when you click outside it */ | |
| _allowOutsideClick(event) { | |
| if ("TouchEvent" in window && event instanceof TouchEvent) { | |
| return this._allowOutsideClickTouch(event); | |
| } else { | |
| return this._allowOutsideClickMouse(event); | |
| } | |
| } | |
| _allowOutsideClickTouch(event) { | |
| event.target.addEventListener("touchend", (subEvent) => { | |
| const endTarget = document.elementFromPoint(subEvent.changedTouches.item(0).clientX, subEvent.changedTouches.item(0).clientY); | |
| if (!this.contains(endTarget)) { | |
| this.hide(); | |
| } | |
| }, { once: true }); | |
| return false; | |
| } | |
| _allowOutsideClickMouse(event) { | |
| if (event.type !== "click") { | |
| return false; | |
| } | |
| if (!this.contains(event.target)) { | |
| this.hide(); | |
| } | |
| let target = event.target, closestControl = event.target.closest("[aria-controls]"); | |
| if (closestControl && closestControl.getAttribute("aria-controls") === this.id) { | |
| target = closestControl; | |
| } | |
| return this.id !== target.getAttribute("aria-controls"); | |
| } | |
| _onToggleClicked(event) { | |
| event?.preventDefault(); | |
| this.open ? this.hide() : this.show(); | |
| } | |
| }; | |
| var CloseButton = class extends HTMLButtonElement { | |
| constructor() { | |
| super(); | |
| this.addEventListener("click", () => this.dispatchEvent(new CustomEvent("dialog:force-close", { bubbles: true, cancelable: true, composed: true }))); | |
| } | |
| }; | |
| if (!window.customElements.get("dialog-element")) { | |
| window.customElements.define("dialog-element", DialogElement); | |
| } | |
| if (!window.customElements.get("close-button")) { | |
| window.customElements.define("close-button", CloseButton, { extends: "button" }); | |
| } | |
| import { animate as motionAnimate, timeline as motionTimeline } from "vendor"; | |
| var reduceDrawerAnimation = window.matchMedia("(prefers-reduced-motion: reduce)").matches || window.themeVariables.settings.reduceDrawerAnimation; | |
| var Drawer = class extends DialogElement { | |
| constructor() { | |
| super(); | |
| const template2 = document.getElementById(this.template).content.cloneNode(true); | |
| this.attachShadow({ mode: "open" }).appendChild(template2); | |
| this.shadowRoot.addEventListener("slotchange", (event) => this._updateSlotVisibility(event.target)); | |
| } | |
| connectedCallback() { | |
| super.connectedCallback(); | |
| this.setAttribute("aria-modal", "true"); | |
| this.shadowRoot.querySelector('[part="overlay"]')?.addEventListener("click", this.hide.bind(this), { signal: this._abortController.signal }); | |
| Array.from(this.shadowRoot.querySelectorAll("slot")).forEach((slot) => this._updateSlotVisibility(slot)); | |
| } | |
| get template() { | |
| return this.getAttribute("template") || "drawer-default-template"; | |
| } | |
| get shouldLock() { | |
| return true; | |
| } | |
| get shouldAppendToBody() { | |
| return true; | |
| } | |
| get openFrom() { | |
| return window.matchMedia(`${window.themeVariables.breakpoints["sm-max"]}`).matches ? "bottom" : this.getAttribute("open-from") || "right"; | |
| } | |
| _getClipPathProperties() { | |
| switch (this.openFrom) { | |
| case "left": | |
| return document.dir === "ltr" ? ["inset(0 100% 0 0 round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm))"] : ["inset(0 0 0 100% round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm))"]; | |
| case "right": | |
| return document.dir === "ltr" ? ["inset(0 0 0 100% round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm)"] : ["inset(0 100% 0 0 round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm))"]; | |
| case "bottom": | |
| return ["inset(100% 0 0 0 round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm))"]; | |
| case "top": | |
| return ["inset(0 0 100% 0 round var(--rounded-sm))", "inset(0 0 0 0 round var(--rounded-sm))"]; | |
| } | |
| } | |
| _setInitialPosition() { | |
| this.style.left = document.dir === "ltr" && this.openFrom === "left" || document.dir === "rtl" && this.openFrom === "right" ? "0px" : "auto"; | |
| this.style.right = this.style.left === "auto" ? "0px" : "auto"; | |
| this.style.bottom = this.openFrom === "bottom" ? "0px" : null; | |
| this.style.top = this.style.bottom === "" ? "0px" : null; | |
| } | |
| _showTransition(animate19 = true) { | |
| let animationControls; | |
| this._setInitialPosition(); | |
| if (reduceDrawerAnimation) { | |
| animationControls = motionAnimate(this, { opacity: [0, 1], visibility: ["hidden", "visible"] }, { duration: 0.2 }); | |
| } else { | |
| let content = this.shadowRoot.querySelector('[part="content"]'), closeButton = this.shadowRoot.querySelector('[part="outside-close-button"]'); | |
| animationControls = motionTimeline([ | |
| [this, { opacity: [0, 1], visibility: ["hidden", "visible"] }, { duration: 0.15 }], | |
| [content, { clipPath: this._getClipPathProperties() }, { duration: 0.4, easing: [0.86, 0, 0.07, 1] }], | |
| [content.children, { opacity: [0, 1] }, { duration: 0.15 }], | |
| [closeButton, { opacity: [0, 1] }, { at: "<", duration: 0.15 }] | |
| ]); | |
| } | |
| animate19 ? animationControls.play() : animationControls.finish(); | |
| return animationControls.finished.then(() => this.classList.add("show-close-cursor")); | |
| } | |
| _hideTransition() { | |
| let animationControls; | |
| if (reduceDrawerAnimation) { | |
| animationControls = motionAnimate(this, { opacity: [1, 0], visibility: ["visibility", "hidden"] }, { duration: 0.2 }); | |
| } else { | |
| let content = this.shadowRoot.querySelector('[part="content"]'), closeButton = this.shadowRoot.querySelector('[part="outside-close-button"]'); | |
| animationControls = motionTimeline([ | |
| [closeButton, { opacity: [null, 0] }, { duration: 0.15 }], | |
| [content.children, { opacity: [null, 0] }, { at: "<", duration: 0.15 }], | |
| [content, { clipPath: this._getClipPathProperties().reverse() }, { duration: 0.4, easing: [0.86, 0, 0.07, 1] }], | |
| [this, { opacity: [null, 0], visibility: ["visible", "hidden"] }, { duration: 0.15 }] | |
| ]); | |
| } | |
| return animationControls.finished.then(() => this.classList.remove("show-close-cursor")); | |
| } | |
| _updateSlotVisibility(slot) { | |
| if (!["header", "footer", "body"].includes(slot.name)) { | |
| return; | |
| } | |
| slot.parentElement.hidden = slot.assignedElements({ flatten: true }).length === 0; | |
| } | |
| }; | |
| var CartDrawer = class extends Drawer { | |
| constructor() { | |
| super(); | |
| this._onPrepareBundledSectionsListener = this._onPrepareBundledSections.bind(this); | |
| this._onCartChangedListener = this._onCartChanged.bind(this); | |
| this._onCartRefreshListener = this._onCartRefresh.bind(this); | |
| this._onVariantAddedListener = this._onVariantAdded.bind(this); | |
| window.addEventListener("pageshow", this._onPageShow.bind(this)); | |
| } | |
| connectedCallback() { | |
| super.connectedCallback(); | |
| document.addEventListener("cart:prepare-bundled-sections", this._onPrepareBundledSectionsListener); | |
| document.addEventListener("cart:change", this._onCartChangedListener); | |
| document.addEventListener("cart:refresh", this._onCartRefreshListener); | |
| document.addEventListener("variant:add", this._onVariantAddedListener); | |
| } | |
| disconnectedCallback() { | |
| super.disconnectedCallback(); | |
| document.removeEventListener("cart:prepare-bundled-sections", this._onPrepareBundledSectionsListener); | |
| document.removeEventListener("cart:change", this._onCartChangedListener); | |
| document.removeEventListener("cart:refresh", this._onCartRefreshListener); | |
| document.removeEventListener("variant:add", this._onVariantAddedListener); | |
| } | |
| get shouldAppendToBody() { | |
| return false; | |
| } | |
| get openFrom() { | |
| return "right"; | |
| } | |
| _onPrepareBundledSections(event) { | |
| event.detail.sections.push(extractSectionId(this)); | |
| } | |
| /** | |
| * Update the cart drawer content when cart content changes. | |
| */ | |
| async _onCartChanged(event) { | |
| const updatedDrawerContent = new DOMParser().parseFromString(event.detail.cart["sections"][extractSectionId(this)], "text/html"); | |
| if (event.detail.cart["item_count"] > 0) { | |
| const currentInner = this.querySelector(".cart-drawer__inner"), updatedInner = updatedDrawerContent.querySelector(".cart-drawer__inner"); | |
| if (!currentInner) { | |
| this.replaceChildren(document.createRange().createContextualFragment(updatedDrawerContent.querySelector(".cart-drawer").innerHTML)); | |
| } else { | |
| setTimeout(() => { | |
| currentInner.innerHTML = updatedInner.innerHTML; | |
| // Fetch product recommendations and update it. | |
| const cartDrawerRecommendations = new CartDrawerRecommendations(); | |
| cartDrawerRecommendations.displayCartRecommendations() | |
| }, event.detail.baseEvent === "variant:add" ? 0 : 1250); | |
| this.querySelector('[slot="footer"]').replaceChildren(...updatedDrawerContent.querySelector('[slot="footer"]').childNodes); | |
| } | |
| } else { | |
| await animate4(this.children, { opacity: 0 }, { duration: 0.15 }).finished; | |
| this.replaceChildren(...updatedDrawerContent.querySelector(".cart-drawer").childNodes); | |
| animate4(this.querySelector(".empty-state"), { opacity: [0, 1], transform: ["translateY(20px)", "translateY(0)"] }, { duration: 0.15 }); | |
| } | |
| } | |
| /** | |
| * Handle the case when the page is served from BF cache | |
| */ | |
| _onPageShow(event) { | |
| if (!event.persisted) { | |
| return; | |
| } | |
| this._onCartRefresh(); | |
| } | |
| /** | |
| * Listeners called when a new variant has been added | |
| */ | |
| _onVariantAdded(event) { | |
| if (window.themeVariables.settings.cartType !== "drawer" || event.detail?.blockCartDrawerOpening) { | |
| return; | |
| } | |
| this.show(); | |
| } | |
| /** | |
| * Force a complete refresh of the cart drawer (this is called by dispatching the 'cart:refresh' on the document) | |
| */ | |
| async _onCartRefresh() { | |
| const tempDiv = document.createElement("div"); | |
| tempDiv.innerHTML = await (await fetch(`${window.Shopify.routes.root}?section_id=${extractSectionId(this)}`)).text(); | |
| this.replaceChildren(...tempDiv.querySelector("#cart-drawer").children); | |
| } | |
| }; | |
| // the cart icon in in the header navigation | |
| <a href="{{ routes.cart_url }}" data-no-instant class="relative tap-area" {% if settings.cart_type != 'page' and request.page_type != 'cart' %}aria-controls="cart-drawer"{% endif %}> | |
| <span class="sr-only">{{ 'header.general.open_cart' | t }}</span> | |
| {%- render 'icon' with 'cart' -%} | |
| <div class="header__cart-count"> | |
| <cart-count class="count-bubble {% if cart.item_count == 0 %}opacity-0{% endif %}" aria-hidden="true"> | |
| {{- cart.item_count -}} | |
| </cart-count> | |
| </div> | |
| </a> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment