Created
August 9, 2025 02:21
-
-
Save stefanvermaas/d377e34d6a381c5a6dae448f7fc84bef to your computer and use it in GitHub Desktop.
A polyfill to make Turbo work with s-link, s-clickable, and s-button components that function as links
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
| (function patchTurboClickObserver() { | |
| if (window.__turboClickObserverPatched) return; | |
| window.__turboClickObserverPatched = true; | |
| const applyPatch = () => { | |
| const observer = Turbo.session.linkClickObserver; | |
| const expandURL = (locatable) => | |
| new URL(locatable.toString(), document.baseURI); | |
| const getLocationForLink = (link) => | |
| expandURL(link.getAttribute("href") || ""); | |
| const doesNotTargetIFrame = (name) => { | |
| if (!name) return true; | |
| if (name === "_blank") return false; | |
| return ![...document.getElementsByName(name)].some( | |
| (el) => el instanceof HTMLIFrameElement, | |
| ); | |
| }; | |
| const closestCrossShadow = (el, selector) => { | |
| while (el) { | |
| if (el.closest) { | |
| const found = el.closest(selector); | |
| if (found) return found; | |
| } | |
| const root = el.getRootNode?.(); | |
| if (root?.host) { | |
| el = root.host; | |
| } else { | |
| el = el.parentElement; | |
| } | |
| } | |
| return null; | |
| }; | |
| const findCustomOrNativeLink = (target) => | |
| closestCrossShadow( | |
| target, | |
| "a[href], s-link[href], s-clickable[href], s-button[href]", | |
| ); | |
| const clickEventIsSignificant = (event) => | |
| !( | |
| event.target?.isContentEditable || | |
| event.which > 1 || | |
| event.altKey || | |
| event.ctrlKey || | |
| event.metaKey || | |
| event.shiftKey | |
| ); | |
| observer.clickBubbled = (event) => { | |
| if (!(event instanceof MouseEvent) || !clickEventIsSignificant(event)) | |
| return; | |
| const target = event.composedPath?.()[0] || event.target; | |
| const link = findCustomOrNativeLink(target); | |
| if (link && doesNotTargetIFrame(link.target)) { | |
| const location = getLocationForLink(link); | |
| if (observer.delegate.willFollowLinkToLocation(link, location, event)) { | |
| event.preventDefault(); | |
| observer.delegate.followedLinkToLocation(link, location); | |
| } | |
| } | |
| }; | |
| }; | |
| const waitForTurbo = () => { | |
| if (window.Turbo?.session?.linkClickObserver) { | |
| applyPatch(); | |
| } else { | |
| requestAnimationFrame(waitForTurbo); | |
| } | |
| }; | |
| waitForTurbo(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment