@solidjs/router
Advanced tools
Comparing version 0.10.0-beta.8 to 0.10.0-beta.9
@@ -10,2 +10,3 @@ import type { JSX } from "solid-js"; | ||
preload?: boolean; | ||
link?: boolean; | ||
} | ||
@@ -12,0 +13,0 @@ } |
@@ -30,3 +30,3 @@ import { createMemo, mergeProps, splitProps } from "solid-js"; | ||
...rest.classList | ||
}} aria-current={isActive() ? "page" : undefined}/>); | ||
}} link aria-current={isActive() ? "page" : undefined}/>); | ||
} | ||
@@ -33,0 +33,0 @@ export function Navigate(props) { |
import { JSX } from "solid-js"; | ||
import { Submission } from "../types"; | ||
export type Action<T extends Array<any>, U> = ((...vars: T) => Promise<U>) & JSX.SerializableAttributeValue & { | ||
export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<U>) & { | ||
url: string; | ||
@@ -5,0 +5,0 @@ with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => U, ...args: A): Action<B, U>; |
@@ -8,2 +8,5 @@ import { type ReconcileOptions } from "solid-js/store"; | ||
export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options?: ReconcileOptions): CachedFunction<T, U>; | ||
export declare namespace cache { | ||
var set: (key: string, value: any) => void; | ||
} | ||
export declare function hashKey<T extends Array<any>>(args: T): string; |
@@ -25,2 +25,4 @@ import { createSignal, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js"; | ||
const req = getRequestEvent() || sharedConfig.context; | ||
if (!req) | ||
throw new Error("Cannot find cache context"); | ||
return req.routerCache || (req.routerCache = new Map()); | ||
@@ -82,4 +84,5 @@ } | ||
// serialize on server | ||
if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) { | ||
sharedConfig.context && sharedConfig.context.serialize(key, res); | ||
if (isServer && (sharedConfig.context && !sharedConfig.context.noHydrate)) { | ||
const e = getRequestEvent(); | ||
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res); | ||
} | ||
@@ -135,2 +138,23 @@ if (cached) { | ||
} | ||
; | ||
cache.set = (key, value) => { | ||
const cache = getCache(); | ||
const now = Date.now(); | ||
let cached = cache.get(key); | ||
let version; | ||
if (getOwner()) { | ||
version = createSignal(now, { | ||
equals: (p, v) => v - p < 50 // margin of error | ||
}); | ||
onCleanup(() => cached[3].delete(version)); | ||
} | ||
if (cached) { | ||
cached[0] = now; | ||
cached[1] = value; | ||
cached[2] = "preload"; | ||
version && cached[3].add(version); | ||
} | ||
else | ||
cache.set(key, (cached = [now, value, , new Set(version ? [version] : [])])); | ||
}; | ||
function matchKey(key, keys) { | ||
@@ -137,0 +161,0 @@ for (let k of keys) { |
import type { RouterContext } from "../types"; | ||
export declare function setupNativeEvents(router: RouterContext): void; | ||
export declare function setupNativeEvents(preload?: boolean, explicitLinks?: boolean, actionBase?: string): (router: RouterContext) => void; |
import { delegateEvents } from "solid-js/web"; | ||
import { onCleanup } from "solid-js"; | ||
import { actions } from "./action"; | ||
export function setupNativeEvents(router) { | ||
const basePath = router.base.path(); | ||
const navigateFromRoute = router.navigatorFactory(router.base); | ||
let preloadTimeout = {}; | ||
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) | ||
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) | ||
}); | ||
} | ||
function handleAnchorPreload(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [a, url] = res; | ||
if (!preloadTimeout[url.pathname]) | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
} | ||
function handleAnchorIn(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [a, url] = res; | ||
if (preloadTimeout[url.pathname]) | ||
return; | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
delete preloadTimeout[url.pathname]; | ||
}, 200); | ||
} | ||
function handleAnchorOut(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [, url] = res; | ||
if (preloadTimeout[url.pathname]) { | ||
clearTimeout(preloadTimeout[url.pathname]); | ||
delete preloadTimeout[url.pathname]; | ||
export function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") { | ||
return (router) => { | ||
const basePath = router.base.path(); | ||
const navigateFromRoute = router.navigatorFactory(router.base); | ||
let preloadTimeout = {}; | ||
function isSvg(el) { | ||
return el.namespaceURI === "http://www.w3.org/2000/svg"; | ||
} | ||
} | ||
function handleFormSubmit(evt) { | ||
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") | ||
? evt.submitter.formAction | ||
: evt.target.action; | ||
if (!actionRef) | ||
return; | ||
if (!actionRef.startsWith("action:")) { | ||
const url = new URL(actionRef); | ||
actionRef = router.parsePath(url.pathname + url.search); | ||
if (!actionRef.startsWith(router.actionBase)) | ||
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.getAttribute("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]; | ||
} | ||
const handler = actions.get(actionRef); | ||
if (handler) { | ||
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(); | ||
const data = new FormData(evt.target); | ||
handler.call(router, data); | ||
navigateFromRoute(to, { | ||
resolve: false, | ||
replace: a.hasAttribute("replace"), | ||
scroll: !a.hasAttribute("noscroll"), | ||
state: state && JSON.parse(state) | ||
}); | ||
} | ||
} | ||
// ensure delegated event run first | ||
delegateEvents(["click", "submit"]); | ||
document.addEventListener("click", handleAnchorClick); | ||
document.addEventListener("mouseover", handleAnchorIn); | ||
document.addEventListener("mouseout", handleAnchorOut); | ||
document.addEventListener("focusin", handleAnchorPreload); | ||
document.addEventListener("touchstart", handleAnchorPreload); | ||
document.addEventListener("submit", handleFormSubmit); | ||
onCleanup(() => { | ||
document.removeEventListener("click", handleAnchorClick); | ||
document.removeEventListener("mouseover", handleAnchorIn); | ||
document.removeEventListener("mouseout", handleAnchorOut); | ||
document.removeEventListener("focusin", handleAnchorPreload); | ||
document.removeEventListener("touchstart", handleAnchorPreload); | ||
document.removeEventListener("submit", handleFormSubmit); | ||
}); | ||
function handleAnchorPreload(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [a, url] = res; | ||
if (!preloadTimeout[url.pathname]) | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
} | ||
function handleAnchorIn(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [a, url] = res; | ||
if (preloadTimeout[url.pathname]) | ||
return; | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
delete preloadTimeout[url.pathname]; | ||
}, 200); | ||
} | ||
function handleAnchorOut(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) | ||
return; | ||
const [, url] = res; | ||
if (preloadTimeout[url.pathname]) { | ||
clearTimeout(preloadTimeout[url.pathname]); | ||
delete preloadTimeout[url.pathname]; | ||
} | ||
} | ||
function handleFormSubmit(evt) { | ||
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") | ||
? evt.submitter.formAction | ||
: evt.target.action; | ||
if (!actionRef) | ||
return; | ||
if (!actionRef.startsWith("action:")) { | ||
const url = new URL(actionRef); | ||
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); | ||
handler.call(router, data); | ||
} | ||
} | ||
// ensure delegated event run first | ||
delegateEvents(["click", "submit"]); | ||
document.addEventListener("click", handleAnchorClick); | ||
if (preload) { | ||
document.addEventListener("mouseover", handleAnchorIn); | ||
document.addEventListener("mouseout", handleAnchorOut); | ||
document.addEventListener("focusin", handleAnchorPreload); | ||
document.addEventListener("touchstart", handleAnchorPreload); | ||
} | ||
document.addEventListener("submit", handleFormSubmit); | ||
onCleanup(() => { | ||
document.removeEventListener("click", handleAnchorClick); | ||
if (preload) { | ||
document.removeEventListener("mouseover", handleAnchorIn); | ||
document.removeEventListener("mouseout", handleAnchorOut); | ||
document.removeEventListener("focusin", handleAnchorPreload); | ||
document.removeEventListener("touchstart", handleAnchorPreload); | ||
} | ||
document.removeEventListener("submit", handleFormSubmit); | ||
}); | ||
}; | ||
} |
@@ -424,3 +424,2 @@ import { isServer, getRequestEvent, createComponent as createComponent$1, delegateEvents, spread, mergeProps as mergeProps$1, template } from 'solid-js/web'; | ||
base: baseRoute, | ||
actionBase: options.actionBase || "/_server", | ||
location, | ||
@@ -591,4 +590,3 @@ isRouting, | ||
const { | ||
base, | ||
actionBase | ||
base | ||
} = props; | ||
@@ -601,4 +599,3 @@ const routeDefs = children(() => props.children); | ||
const routerState = createRouterContext(router, branches, { | ||
base, | ||
actionBase | ||
base | ||
}); | ||
@@ -774,2 +771,3 @@ router.create && router.create(routerState); | ||
const req = getRequestEvent() || sharedConfig.context; | ||
if (!req) throw new Error("Cannot find cache context"); | ||
return req.routerCache || (req.routerCache = new Map()); | ||
@@ -832,3 +830,4 @@ } | ||
if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) { | ||
sharedConfig.context && sharedConfig.context.serialize(key, res); | ||
const e = getRequestEvent(); | ||
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res); | ||
} | ||
@@ -876,2 +875,21 @@ if (cached) { | ||
} | ||
cache.set = (key, value) => { | ||
const cache = getCache(); | ||
const now = Date.now(); | ||
let cached = cache.get(key); | ||
let version; | ||
if (getOwner()) { | ||
version = createSignal(now, { | ||
equals: (p, v) => v - p < 50 // margin of error | ||
}); | ||
onCleanup(() => cached[3].delete(version)); | ||
} | ||
if (cached) { | ||
cached[0] = now; | ||
cached[1] = value; | ||
cached[2] = "preload"; | ||
version && cached[3].add(version); | ||
} else cache.set(key, cached = [now, value,, new Set(version ? [version] : [])]); | ||
}; | ||
function matchKey(key, keys) { | ||
@@ -1000,94 +1018,101 @@ for (let k of keys) { | ||
function setupNativeEvents(router) { | ||
const basePath = router.base.path(); | ||
const navigateFromRoute = router.navigatorFactory(router.base); | ||
let preloadTimeout = {}; | ||
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) 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) | ||
}); | ||
} | ||
function handleAnchorPreload(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [a, url] = res; | ||
if (!preloadTimeout[url.pathname]) router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
} | ||
function handleAnchorIn(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [a, url] = res; | ||
if (preloadTimeout[url.pathname]) return; | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
delete preloadTimeout[url.pathname]; | ||
}, 200); | ||
} | ||
function handleAnchorOut(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [, url] = res; | ||
if (preloadTimeout[url.pathname]) { | ||
clearTimeout(preloadTimeout[url.pathname]); | ||
delete preloadTimeout[url.pathname]; | ||
function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") { | ||
return router => { | ||
const basePath = router.base.path(); | ||
const navigateFromRoute = router.navigatorFactory(router.base); | ||
let preloadTimeout = {}; | ||
function isSvg(el) { | ||
return el.namespaceURI === "http://www.w3.org/2000/svg"; | ||
} | ||
} | ||
function handleFormSubmit(evt) { | ||
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.formAction : evt.target.action; | ||
if (!actionRef) return; | ||
if (!actionRef.startsWith("action:")) { | ||
const url = new URL(actionRef); | ||
actionRef = router.parsePath(url.pathname + url.search); | ||
if (!actionRef.startsWith(router.actionBase)) return; | ||
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.getAttribute("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]; | ||
} | ||
const handler = actions.get(actionRef); | ||
if (handler) { | ||
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(); | ||
const data = new FormData(evt.target); | ||
handler.call(router, data); | ||
navigateFromRoute(to, { | ||
resolve: false, | ||
replace: a.hasAttribute("replace"), | ||
scroll: !a.hasAttribute("noscroll"), | ||
state: state && JSON.parse(state) | ||
}); | ||
} | ||
} | ||
function handleAnchorPreload(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [a, url] = res; | ||
if (!preloadTimeout[url.pathname]) router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
} | ||
function handleAnchorIn(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [a, url] = res; | ||
if (preloadTimeout[url.pathname]) return; | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
delete preloadTimeout[url.pathname]; | ||
}, 200); | ||
} | ||
function handleAnchorOut(evt) { | ||
const res = handleAnchor(evt); | ||
if (!res) return; | ||
const [, url] = res; | ||
if (preloadTimeout[url.pathname]) { | ||
clearTimeout(preloadTimeout[url.pathname]); | ||
delete preloadTimeout[url.pathname]; | ||
} | ||
} | ||
function handleFormSubmit(evt) { | ||
let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.formAction : evt.target.action; | ||
if (!actionRef) return; | ||
if (!actionRef.startsWith("action:")) { | ||
const url = new URL(actionRef); | ||
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); | ||
handler.call(router, data); | ||
} | ||
} | ||
// ensure delegated event run first | ||
delegateEvents(["click", "submit"]); | ||
document.addEventListener("click", handleAnchorClick); | ||
document.addEventListener("mouseover", handleAnchorIn); | ||
document.addEventListener("mouseout", handleAnchorOut); | ||
document.addEventListener("focusin", handleAnchorPreload); | ||
document.addEventListener("touchstart", handleAnchorPreload); | ||
document.addEventListener("submit", handleFormSubmit); | ||
onCleanup(() => { | ||
document.removeEventListener("click", handleAnchorClick); | ||
document.removeEventListener("mouseover", handleAnchorIn); | ||
document.removeEventListener("mouseout", handleAnchorOut); | ||
document.removeEventListener("focusin", handleAnchorPreload); | ||
document.removeEventListener("touchstart", handleAnchorPreload); | ||
document.removeEventListener("submit", handleFormSubmit); | ||
}); | ||
// ensure delegated event run first | ||
delegateEvents(["click", "submit"]); | ||
document.addEventListener("click", handleAnchorClick); | ||
if (preload) { | ||
document.addEventListener("mouseover", handleAnchorIn); | ||
document.addEventListener("mouseout", handleAnchorOut); | ||
document.addEventListener("focusin", handleAnchorPreload); | ||
document.addEventListener("touchstart", handleAnchorPreload); | ||
} | ||
document.addEventListener("submit", handleFormSubmit); | ||
onCleanup(() => { | ||
document.removeEventListener("click", handleAnchorClick); | ||
if (preload) { | ||
document.removeEventListener("mouseover", handleAnchorIn); | ||
document.removeEventListener("mouseout", handleAnchorOut); | ||
document.removeEventListener("focusin", handleAnchorPreload); | ||
document.removeEventListener("touchstart", handleAnchorPreload); | ||
} | ||
document.removeEventListener("submit", handleFormSubmit); | ||
}); | ||
}; | ||
} | ||
@@ -1116,3 +1141,3 @@ | ||
init: notify => bindEvent(window, "popstate", () => notify()), | ||
create: setupNativeEvents, | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
@@ -1154,3 +1179,3 @@ go: delta => window.history.go(delta) | ||
init: notify => bindEvent(window, "hashchange", () => notify()), | ||
create: setupNativeEvents, | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
@@ -1255,2 +1280,3 @@ go: delta => window.history.go(delta), | ||
}, | ||
"link": "", | ||
get ["aria-current"]() { | ||
@@ -1257,0 +1283,0 @@ return isActive() ? "page" : undefined; |
@@ -5,3 +5,2 @@ import type { Component, JSX } from "solid-js"; | ||
base?: string; | ||
actionBase?: string; | ||
root?: Component<RouteSectionProps>; | ||
@@ -8,0 +7,0 @@ children?: JSX.Element; |
@@ -6,6 +6,6 @@ /*@refresh skip*/ | ||
export const createRouterComponent = (router) => (props) => { | ||
const { base, actionBase } = props; | ||
const { base } = props; | ||
const routeDefs = children(() => props.children); | ||
const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || "")); | ||
const routerState = createRouterContext(router, branches, { base, actionBase }); | ||
const routerState = createRouterContext(router, branches, { base }); | ||
router.create && router.create(routerState); | ||
@@ -12,0 +12,0 @@ return (<RouterContextObj.Provider value={routerState}> |
import type { JSX } from "solid-js"; | ||
import type { BaseRouterProps } from "./components"; | ||
export declare function hashParser(str: string): string; | ||
export type HashRouterProps = BaseRouterProps; | ||
export type HashRouterProps = BaseRouterProps & { | ||
actionBase?: string; | ||
explicitLinks?: boolean; | ||
preload?: boolean; | ||
}; | ||
export declare function HashRouter(props: HashRouterProps): JSX.Element; |
@@ -29,3 +29,3 @@ import { setupNativeEvents } from "../data/events"; | ||
init: notify => bindEvent(window, "hashchange", () => notify()), | ||
create: setupNativeEvents, | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
@@ -32,0 +32,0 @@ go: delta => window.history.go(delta), |
@@ -5,3 +5,6 @@ import type { BaseRouterProps } from "./components"; | ||
url?: string; | ||
actionBase?: string; | ||
explicitLinks?: boolean; | ||
preload?: boolean; | ||
}; | ||
export declare function Router(props: RouterProps): JSX.Element; |
@@ -23,3 +23,3 @@ import { isServer } from "solid-js/web"; | ||
init: notify => bindEvent(window, "popstate", () => notify()), | ||
create: setupNativeEvents, | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
@@ -26,0 +26,0 @@ go: delta => window.history.go(delta) |
@@ -24,4 +24,3 @@ import { JSX, Accessor } from "solid-js"; | ||
base?: string; | ||
actionBase?: string; | ||
}): RouterContext; | ||
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext; |
@@ -234,3 +234,2 @@ import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js"; | ||
base: baseRoute, | ||
actionBase: options.actionBase || "/_server", | ||
location, | ||
@@ -237,0 +236,0 @@ isRouting, |
@@ -7,2 +7,3 @@ import type { Component, JSX, Signal } from "solid-js"; | ||
initialSubmission?: Submission<any, any>; | ||
serverOnly?: boolean; | ||
} | ||
@@ -122,3 +123,2 @@ } | ||
base: RouteContext; | ||
actionBase: string; | ||
location: Location; | ||
@@ -125,0 +125,0 @@ navigatorFactory: NavigatorFactory; |
@@ -9,3 +9,3 @@ { | ||
"license": "MIT", | ||
"version": "0.10.0-beta.8", | ||
"version": "0.10.0-beta.9", | ||
"homepage": "https://github.com/solidjs/solid-router#readme", | ||
@@ -12,0 +12,0 @@ "repository": { |
@@ -371,2 +371,10 @@ <p> | ||
Cached function has a few useful methods for getting the key that are useful for invalidation. | ||
```ts | ||
let id = 5; | ||
getUser.key // returns "users" | ||
getUser.keyFor(id) // returns "users[5]" | ||
``` | ||
This cache can be defined anywhere and then used inside your components with: | ||
@@ -382,2 +390,4 @@ | ||
Using `cache` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly. | ||
### `action` | ||
@@ -396,3 +406,3 @@ | ||
// in component | ||
<form action={myAction} /> | ||
<form action={myAction} method="post" /> | ||
@@ -403,2 +413,28 @@ //or | ||
Actions only work with post requests, so make sure to put `method="post"` on your form. | ||
Sometimes it might be easier to deal with typed data instead of `FormData` and adding additional hidden fields. For that reason Actions have a with method. That works similar to `bind` which applies the arguments in order. | ||
Picture an action that deletes Todo Item: | ||
```js | ||
const deleteTodo = action(async (formData: FormData) => { | ||
const id = Number(formData.get("id")) | ||
await api.deleteTodo(id) | ||
}) | ||
<form action={deleteUser} method="post"> | ||
<input type="hidden" name="id" value={todo.id} /> | ||
<button type="submit">Delete</button> | ||
</form> | ||
``` | ||
Instead with `with` you can write this: | ||
```js | ||
const deleteUser = action(api.deleteUser) | ||
<form action={deleteUser.with(todo.id)} method="post"> | ||
<button type="submit">Delete</button> | ||
</form> | ||
``` | ||
#### Notes of `<form>` implementation and SSR | ||
@@ -578,5 +614,18 @@ This requires stable references as you can only serialize a string as an attribute, and across SSR they'd need to match. The solution is providing a unique name. | ||
### `<Router>` | ||
This is the main Router component for the browser. | ||
| prop | type | description | | ||
|-----|----|----| | ||
| children | `JSX.Element` or `RouteDefinition[]` | The route definitions | | ||
| root | Component | Top level layout comoponent | | ||
| base | string | Base url to use for matching routes | | ||
| actionBase | string | Root url for server actions, default: `/_server` | | ||
| preload | boolean | Enables/disables preloads globally, default: `true` | | ||
| explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. default: `false` | | ||
### `<A>` | ||
Like the `<a>` tag but supports relative paths and active class styling. | ||
Like the `<a>` tag but supports relative paths and active class styling (requires client side JavaScript). | ||
@@ -737,5 +786,5 @@ The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything. | ||
## Migrations from 0.8.x | ||
## Migrations from 0.9.x | ||
v0.9.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application. | ||
v0.10.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application. | ||
@@ -754,3 +803,3 @@ The biggest changes are around removed APIs that need to be replaced. | ||
These have been replaced by a load mechanism. This allows link hover preloads (as the load function can be run as much as wanted without worry about reactivity). It support deduping/cache APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks. | ||
These have been replaced by a load mechanism. This allows link hover preloads (as the load function can be run as much as wanted without worry about reactivity). It support deduping/cache APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks. | ||
@@ -757,0 +806,0 @@ ## SPAs in Deployed Environments |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
141169
3193
821