UNPKG

@solidjs/router

Version:
124 lines (123 loc) 5.47 kB
import { delegateEvents } from "solid-js/web"; import { onCleanup } from "solid-js"; import { actions } from "./action.js"; import { mockBase } from "../utils.js"; export function setupNativeEvents({ preload = true, explicitLinks = false, actionBase = "/_server", transformUrl } = {}) { return (router) => { const basePath = router.base.path(); const navigateFromRoute = router.navigatorFactory(router.base); let preloadTimeout; let lastElement; function isSvg(el) { return el.namespaceURI === "http://www.w3.org/2000/svg"; } function handleAnchor(evt) { if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return; const a = evt .composedPath() .find(el => el instanceof Node && el.nodeName.toUpperCase() === "A"); if (!a || (explicitLinks && !a.hasAttribute("link"))) return; const svg = isSvg(a); const href = svg ? a.href.baseVal : a.href; const target = svg ? a.target.baseVal : a.target; if (target || (!href && !a.hasAttribute("state"))) return; const rel = (a.getAttribute("rel") || "").split(/\s+/); if (a.hasAttribute("download") || (rel && rel.includes("external"))) return; const url = svg ? new URL(href, document.baseURI) : new URL(href); if (url.origin !== window.location.origin || (basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))) return; return [a, url]; } function handleAnchorClick(evt) { const res = handleAnchor(evt); if (!res) return; const [a, url] = res; const to = router.parsePath(url.pathname + url.search + url.hash); const state = a.getAttribute("state"); evt.preventDefault(); navigateFromRoute(to, { resolve: false, replace: a.hasAttribute("replace"), scroll: !a.hasAttribute("noscroll"), state: state ? JSON.parse(state) : undefined }); } function handleAnchorPreload(evt) { const res = handleAnchor(evt); if (!res) return; const [a, url] = res; transformUrl && (url.pathname = transformUrl(url.pathname)); router.preloadRoute(url, a.getAttribute("preload") !== "false"); } function handleAnchorMove(evt) { clearTimeout(preloadTimeout); const res = handleAnchor(evt); if (!res) return (lastElement = null); const [a, url] = res; if (lastElement === a) return; transformUrl && (url.pathname = transformUrl(url.pathname)); preloadTimeout = setTimeout(() => { router.preloadRoute(url, a.getAttribute("preload") !== "false"); lastElement = a; }, 20); } function handleFormSubmit(evt) { if (evt.defaultPrevented) return; let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.getAttribute("formaction") : evt.target.getAttribute("action"); if (!actionRef) return; if (!actionRef.startsWith("https://action/")) { // normalize server actions const url = new URL(actionRef, mockBase); actionRef = router.parsePath(url.pathname + url.search); if (!actionRef.startsWith(actionBase)) return; } if (evt.target.method.toUpperCase() !== "POST") throw new Error("Only POST forms are supported for Actions"); const handler = actions.get(actionRef); if (handler) { evt.preventDefault(); const data = new FormData(evt.target, evt.submitter); handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data" ? data : new URLSearchParams(data)); } } // ensure delegated event run first delegateEvents(["click", "submit"]); document.addEventListener("click", handleAnchorClick); if (preload) { document.addEventListener("mousemove", handleAnchorMove, { passive: true }); document.addEventListener("focusin", handleAnchorPreload, { passive: true }); document.addEventListener("touchstart", handleAnchorPreload, { passive: true }); } document.addEventListener("submit", handleFormSubmit); onCleanup(() => { document.removeEventListener("click", handleAnchorClick); if (preload) { document.removeEventListener("mousemove", handleAnchorMove); document.removeEventListener("focusin", handleAnchorPreload); document.removeEventListener("touchstart", handleAnchorPreload); } document.removeEventListener("submit", handleFormSubmit); }); }; }