From dedd28e6691991cfcf6bb0161ce1b59c01c8d112 Mon Sep 17 00:00:00 2001 From: Mark Murray Date: Tue, 6 Oct 2026 12:26:40 +0100 Subject: [PATCH] Share web checkout overlay markup and lifecycle handling --- platforms/web/src/checkout.ts | 204 +++++++++++----------------------- 1 file changed, 62 insertions(+), 142 deletions(-) diff --git a/platforms/web/src/checkout.ts b/platforms/web/src/checkout.ts index 57b5d5141..e710c157c 100644 --- a/platforms/web/src/checkout.ts +++ b/platforms/web/src/checkout.ts @@ -127,51 +127,41 @@ const CHECKOUT_APPEARANCES = new Map +
+
+ ${safe( + blocked ? "Your browser blocked the checkout window." : "Continue your purchase in the", + )}
+ +
+ +
+ `; +} + const SHADOW_TEMPLATE = createTemplate(html`
-
- -
-
- Continue your purchase in the
- -
- -
-
- -
-
- Your browser blocked the checkout window.
- -
- -
-
+ ${overlaySlot(false)}${overlaySlot(true)}
@@ -423,22 +413,6 @@ export class ShopifyCheckout return this.shadowRoot?.querySelector("#overlay-background") ?? undefined; } - get #dialogCloseButtonElement(): HTMLButtonElement | undefined { - return this.shadowRoot?.querySelector("#overlay-close-button") ?? undefined; - } - - get #dialogButtonElement(): HTMLButtonElement | undefined { - return this.shadowRoot?.querySelector("#overlay-link") ?? undefined; - } - - get #dialogRetryButtonElement(): HTMLButtonElement | undefined { - return this.shadowRoot?.querySelector("#overlay-retry-button") ?? undefined; - } - - get #dialogBlockedCloseButtonElement(): HTMLButtonElement | undefined { - return this.shadowRoot?.querySelector("#overlay-blocked-close-button") ?? undefined; - } - get #targetElement(): HTMLDivElement | undefined { return this.shadowRoot?.querySelector(".Shopify-target") ?? undefined; } @@ -517,53 +491,7 @@ export class ShopifyCheckout const abortController = new AbortController(); - // Opens a dialog element to act as a scrim over the current window while the popup is open. - // The dialog can be closed by the user, or will close itself when the popup is closed. - const dialog = this.#dialogElement; - const dialogCloseButton = this.#dialogCloseButtonElement; - const dialogButton = this.#dialogButtonElement; - - if (dialog && this.#isDialogVisible()) { - delete dialog.dataset.state; - dialog.showModal(); - - dialogCloseButton?.addEventListener( - "click", - () => { - dialog.close(); - }, - { - signal: abortController.signal, - }, - ); - - dialog.addEventListener( - "close", - () => { - // `close` fires asynchronously; ignore it if the dialog has since been re-shown - if (dialog.open) return; - abortController.abort(); - }, - { - signal: abortController.signal, - }, - ); - - dialogButton?.addEventListener( - "click", - (event: MouseEvent) => { - event.preventDefault(); - this.#checkoutWindow?.focus(); - }, - { - signal: abortController.signal, - }, - ); - - abortController.signal.addEventListener("abort", () => { - dialog.close(); - }); - } + this.#showOverlay(abortController); abortController.signal.addEventListener("abort", () => { this.#navigationStartedAt = undefined; @@ -628,50 +556,42 @@ export class ShopifyCheckout return !isElementHidden && !isOverlayHidden; } - #showBlockedOverlay(): void { - const abortController = new AbortController(); + #showOverlay(abortController: AbortController, blocked = false): void { const dialog = this.#dialogElement; + if (!dialog || !this.#isDialogVisible()) return; + + if (blocked) dialog.dataset.state = "blocked"; + else delete dialog.dataset.state; + dialog.showModal(); + + const slot = dialog.querySelector(`slot[name="${blocked ? "overlay-blocked" : "overlay"}"]`); + const options = { signal: abortController.signal }; + slot + ?.querySelector(".overlay-close-button") + ?.addEventListener("click", () => dialog.close(), options); + slot?.querySelector(".overlay-focus-button")?.addEventListener( + "click", + (event) => { + event.preventDefault(); + if (blocked) this.open(); + else this.#checkoutWindow?.focus(); + }, + options, + ); + dialog.addEventListener( + "close", + () => { + // `close` fires asynchronously; ignore it if the dialog has since been re-shown. + if (!dialog.open) abortController.abort(); + }, + options, + ); + abortController.signal.addEventListener("abort", () => dialog.close()); + } - if (dialog && this.#isDialogVisible()) { - dialog.dataset.state = "blocked"; - dialog.showModal(); - - this.#dialogRetryButtonElement?.addEventListener( - "click", - () => { - this.open(); - }, - { - signal: abortController.signal, - }, - ); - - this.#dialogBlockedCloseButtonElement?.addEventListener( - "click", - () => { - dialog.close(); - }, - { - signal: abortController.signal, - }, - ); - - dialog.addEventListener( - "close", - () => { - // `close` fires asynchronously; ignore it if the dialog has since been re-shown - if (dialog.open) return; - abortController.abort(); - }, - { - signal: abortController.signal, - }, - ); - - abortController.signal.addEventListener("abort", () => { - if (dialog.open) dialog.close(); - }); - } + #showBlockedOverlay(): void { + const abortController = new AbortController(); + this.#showOverlay(abortController, true); abortController.signal.addEventListener("abort", () => { this.#blockedOpen = null;