@solidjs/router
Advanced tools
Comparing version 0.13.5 to 0.13.6
import { JSX } from "solid-js"; | ||
import { Submission } from "../types.js"; | ||
import type { Submission, SubmissionStub } from "../types.js"; | ||
export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<U>) & { | ||
@@ -11,4 +11,4 @@ url: string; | ||
}; | ||
export declare function useSubmission<T extends Array<any>, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>; | ||
export declare function useSubmission<T extends Array<any>, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U> | SubmissionStub; | ||
export declare function useAction<T extends Array<any>, U>(action: Action<T, U>): (...args: Parameters<Action<T, U>>) => Promise<U>; | ||
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>; |
@@ -24,2 +24,4 @@ import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js"; | ||
get(_, property) { | ||
if (submissions.length === 0 && property === "clear" || property === "retry") | ||
return (() => { }); | ||
return submissions[submissions.length - 1]?.[property]; | ||
@@ -26,0 +28,0 @@ } |
@@ -90,3 +90,3 @@ import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js"; | ||
let res = cached[1]; | ||
if (!inLoadFn) { | ||
if (intent !== "preload") { | ||
res = | ||
@@ -98,4 +98,3 @@ "then" in cached[1] | ||
} | ||
else | ||
"then" in res && res.catch(() => { }); | ||
inLoadFn && "then" in res && res.catch(() => { }); | ||
return res; | ||
@@ -125,3 +124,3 @@ } | ||
} | ||
if (!inLoadFn) { | ||
if (intent !== "preload") { | ||
res = | ||
@@ -132,4 +131,3 @@ "then" in res | ||
} | ||
else | ||
"then" in res && res.catch(() => { }); | ||
inLoadFn && "then" in res && res.catch(() => { }); | ||
// serialize on server | ||
@@ -147,15 +145,15 @@ if (isServer && | ||
if (v instanceof Response) { | ||
if (v.headers.has("Location")) { | ||
if (navigate) { | ||
const url = v.headers.get(LocationHeader); | ||
if (url !== null) { | ||
// client + server relative redirect | ||
if (navigate && url.startsWith("/")) | ||
startTransition(() => { | ||
let url = v.headers.get(LocationHeader); | ||
if (url && url.startsWith("/")) { | ||
navigate(url, { | ||
replace: true | ||
}); | ||
} | ||
else if (!isServer && url) { | ||
window.location.href = url; | ||
} | ||
navigate(url, { replace: true }); | ||
}); | ||
else if (!isServer) | ||
window.location.href = url; | ||
else if (isServer) { | ||
const e = getRequestEvent(); | ||
if (e) | ||
e.response = { status: 302, headers: new Headers({ Location: url }) }; | ||
} | ||
@@ -162,0 +160,0 @@ return; |
@@ -64,3 +64,3 @@ import { delegateEvents } from "solid-js/web"; | ||
if (!preloadTimeout[url.pathname]) | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
router.preloadRoute(url, { preloadData: a.getAttribute("preload") !== "false" }); | ||
} | ||
@@ -78,3 +78,3 @@ function handleAnchorIn(evt) { | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
router.preloadRoute(url, { preloadData: a.getAttribute("preload") !== "false" }); | ||
delete preloadTimeout[url.pathname]; | ||
@@ -81,0 +81,0 @@ }, 200); |
import { isServer, getRequestEvent, createComponent as createComponent$1, memo, delegateEvents, spread, mergeProps as mergeProps$1, template } from 'solid-js/web'; | ||
import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, mergeProps, Show, createRoot, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js'; | ||
import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, batch, createComponent, children, mergeProps, Show, createRoot, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js'; | ||
import { createStore, reconcile, unwrap } from 'solid-js/store'; | ||
@@ -436,9 +436,29 @@ | ||
const [isRouting, setIsRouting] = createSignal(false); | ||
const start = async callback => { | ||
setIsRouting(true); | ||
try { | ||
await startTransition(callback); | ||
} finally { | ||
setIsRouting(false); | ||
} | ||
// Keep track of last target, so that last call to transition wins | ||
let lastTransitionTarget; | ||
// Transition the location to a new value | ||
const transition = (newIntent, newTarget) => { | ||
if (newTarget.value === reference() && newTarget.state === state()) return; | ||
if (lastTransitionTarget === undefined) setIsRouting(true); | ||
intent = newIntent; | ||
lastTransitionTarget = newTarget; | ||
startTransition(() => { | ||
if (lastTransitionTarget !== newTarget) return; | ||
setReference(lastTransitionTarget.value); | ||
setState(lastTransitionTarget.state); | ||
resetErrorBoundaries(); | ||
if (!isServer) submissions[1]([]); | ||
}).finally(() => { | ||
if (lastTransitionTarget !== newTarget) return; | ||
// Batch, in order for isRouting and final source update to happen together | ||
batch(() => { | ||
intent = undefined; | ||
if (newIntent === "navigate") navigateEnd(lastTransitionTarget); | ||
setIsRouting(false); | ||
lastTransitionTarget = undefined; | ||
}); | ||
}); | ||
}; | ||
@@ -472,20 +492,7 @@ const [reference, setReference] = createSignal(source().value); | ||
}; | ||
createRenderEffect(() => { | ||
const { | ||
value, | ||
state | ||
} = source(); | ||
// Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved | ||
untrack(() => { | ||
start(() => { | ||
intent = "native"; | ||
if (value !== reference()) setReference(value); | ||
setState(state); | ||
resetErrorBoundaries(); | ||
submissions[1]([]); | ||
}).then(() => { | ||
intent = undefined; | ||
}); | ||
}); | ||
}); | ||
// Create a native transition, when source updates | ||
createRenderEffect(on(source, source => transition("native", source), { | ||
defer: true | ||
})); | ||
return { | ||
@@ -550,3 +557,3 @@ base: baseRoute, | ||
} else if (beforeLeave.confirm(resolvedTo, options)) { | ||
const len = referrers.push({ | ||
referrers.push({ | ||
value: current, | ||
@@ -557,16 +564,5 @@ replace, | ||
}); | ||
start(() => { | ||
intent = "navigate"; | ||
setReference(resolvedTo); | ||
setState(nextState); | ||
resetErrorBoundaries(); | ||
submissions[1]([]); | ||
}).then(() => { | ||
if (referrers.length === len) { | ||
intent = undefined; | ||
navigateEnd({ | ||
value: resolvedTo, | ||
state: nextState | ||
}); | ||
} | ||
transition("navigate", { | ||
value: resolvedTo, | ||
state: nextState | ||
}); | ||
@@ -585,13 +581,11 @@ } | ||
if (first) { | ||
if (next.value !== first.value || next.state !== first.state) { | ||
setSource({ | ||
...next, | ||
replace: first.replace, | ||
scroll: first.scroll | ||
}); | ||
} | ||
setSource({ | ||
...next, | ||
replace: first.replace, | ||
scroll: first.scroll | ||
}); | ||
referrers.length = 0; | ||
} | ||
} | ||
function preloadRoute(url, preloadData) { | ||
function preloadRoute(url, options = {}) { | ||
const matches = getRouteMatches(branches(), url.pathname); | ||
@@ -610,3 +604,3 @@ const prevIntent = intent; | ||
inLoadFn = true; | ||
preloadData && load && runWithOwner(getContext(), () => load({ | ||
options.preloadData && load && runWithOwner(getContext(), () => load({ | ||
params, | ||
@@ -971,6 +965,7 @@ location: { | ||
let res = cached[1]; | ||
if (!inLoadFn) { | ||
if (intent !== "preload") { | ||
res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]); | ||
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version | ||
} else "then" in res && res.catch(() => {}); | ||
} | ||
inLoadFn && "then" in res && res.catch(() => {}); | ||
return res; | ||
@@ -997,5 +992,6 @@ } | ||
} | ||
if (!inLoadFn) { | ||
if (intent !== "preload") { | ||
res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res); | ||
} else "then" in res && res.catch(() => {}); | ||
} | ||
inLoadFn && "then" in res && res.catch(() => {}); | ||
// serialize on server | ||
@@ -1010,14 +1006,17 @@ if (isServer && sharedConfig.context && sharedConfig.context.async && !sharedConfig.context.noHydrate) { | ||
if (v instanceof Response) { | ||
if (v.headers.has("Location")) { | ||
if (navigate) { | ||
startTransition(() => { | ||
let url = v.headers.get(LocationHeader); | ||
if (url && url.startsWith("/")) { | ||
navigate(url, { | ||
replace: true | ||
}); | ||
} else if (!isServer && url) { | ||
window.location.href = url; | ||
} | ||
const url = v.headers.get(LocationHeader); | ||
if (url !== null) { | ||
// client + server relative redirect | ||
if (navigate && url.startsWith("/")) startTransition(() => { | ||
navigate(url, { | ||
replace: true | ||
}); | ||
});else if (!isServer) window.location.href = url;else if (isServer) { | ||
const e = getRequestEvent(); | ||
if (e) e.response = { | ||
status: 302, | ||
headers: new Headers({ | ||
Location: url | ||
}) | ||
}; | ||
} | ||
@@ -1087,2 +1086,3 @@ return; | ||
get(_, property) { | ||
if (submissions.length === 0 && property === "clear" || property === "retry") return () => {}; | ||
return submissions[submissions.length - 1]?.[property]; | ||
@@ -1245,3 +1245,5 @@ } | ||
} | ||
if (!preloadTimeout[url.pathname]) router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
if (!preloadTimeout[url.pathname]) router.preloadRoute(url, { | ||
preloadData: a.getAttribute("preload") !== "false" | ||
}); | ||
} | ||
@@ -1257,3 +1259,5 @@ function handleAnchorIn(evt) { | ||
preloadTimeout[url.pathname] = setTimeout(() => { | ||
router.preloadRoute(url, a.getAttribute("preload") !== "false"); | ||
router.preloadRoute(url, { | ||
preloadData: a.getAttribute("preload") !== "false" | ||
}); | ||
delete preloadTimeout[url.pathname]; | ||
@@ -1343,3 +1347,3 @@ }, 200); | ||
} | ||
scrollToHash(window.location.hash.slice(1), scroll); | ||
scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll); | ||
saveCurrentDepth(); | ||
@@ -1346,0 +1350,0 @@ }, |
@@ -26,3 +26,3 @@ import { isServer } from "solid-js/web"; | ||
} | ||
scrollToHash(window.location.hash.slice(1), scroll); | ||
scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll); | ||
saveCurrentDepth(); | ||
@@ -29,0 +29,0 @@ }, |
@@ -12,2 +12,5 @@ import { JSX, Accessor } from "solid-js"; | ||
export declare const useIsRouting: () => () => boolean; | ||
export declare const usePreloadRoute: () => (url: URL, options: { | ||
preloadData?: boolean | undefined; | ||
}) => void; | ||
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S> | undefined) => Accessor<import("./types.js").PathMatch | undefined>; | ||
@@ -14,0 +17,0 @@ export declare const useCurrentMatches: () => () => RouteMatch[]; |
@@ -1,2 +0,2 @@ | ||
import { runWithOwner } from "solid-js"; | ||
import { runWithOwner, batch } from "solid-js"; | ||
import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js"; | ||
@@ -26,2 +26,3 @@ import { isServer, getRequestEvent } from "solid-js/web"; | ||
export const useIsRouting = () => useRouter().isRouting; | ||
export const usePreloadRoute = () => useRouter().preloadRoute; | ||
export const useMatch = (path, matchFilters) => { | ||
@@ -209,10 +210,32 @@ const location = useLocation(); | ||
const [isRouting, setIsRouting] = createSignal(false); | ||
const start = async (callback) => { | ||
setIsRouting(true); | ||
try { | ||
await startTransition(callback); | ||
} | ||
finally { | ||
setIsRouting(false); | ||
} | ||
// Keep track of last target, so that last call to transition wins | ||
let lastTransitionTarget; | ||
// Transition the location to a new value | ||
const transition = (newIntent, newTarget) => { | ||
if (newTarget.value === reference() && newTarget.state === state()) | ||
return; | ||
if (lastTransitionTarget === undefined) | ||
setIsRouting(true); | ||
intent = newIntent; | ||
lastTransitionTarget = newTarget; | ||
startTransition(() => { | ||
if (lastTransitionTarget !== newTarget) | ||
return; | ||
setReference(lastTransitionTarget.value); | ||
setState(lastTransitionTarget.state); | ||
resetErrorBoundaries(); | ||
if (!isServer) | ||
submissions[1]([]); | ||
}).finally(() => { | ||
if (lastTransitionTarget !== newTarget) | ||
return; | ||
// Batch, in order for isRouting and final source update to happen together | ||
batch(() => { | ||
intent = undefined; | ||
if (newIntent === "navigate") | ||
navigateEnd(lastTransitionTarget); | ||
setIsRouting(false); | ||
lastTransitionTarget = undefined; | ||
}); | ||
}); | ||
}; | ||
@@ -246,18 +269,4 @@ const [reference, setReference] = createSignal(source().value); | ||
}; | ||
createRenderEffect(() => { | ||
const { value, state } = source(); | ||
// Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved | ||
untrack(() => { | ||
start(() => { | ||
intent = "native"; | ||
if (value !== reference()) | ||
setReference(value); | ||
setState(state); | ||
resetErrorBoundaries(); | ||
submissions[1]([]); | ||
}).then(() => { | ||
intent = undefined; | ||
}); | ||
}); | ||
}); | ||
// Create a native transition, when source updates | ||
createRenderEffect(on(source, source => transition("native", source), { defer: true })); | ||
return { | ||
@@ -313,17 +322,6 @@ base: baseRoute, | ||
else if (beforeLeave.confirm(resolvedTo, options)) { | ||
const len = referrers.push({ value: current, replace, scroll, state: state() }); | ||
start(() => { | ||
intent = "navigate"; | ||
setReference(resolvedTo); | ||
setState(nextState); | ||
resetErrorBoundaries(); | ||
submissions[1]([]); | ||
}).then(() => { | ||
if (referrers.length === len) { | ||
intent = undefined; | ||
navigateEnd({ | ||
value: resolvedTo, | ||
state: nextState | ||
}); | ||
} | ||
referrers.push({ value: current, replace, scroll, state: state() }); | ||
transition("navigate", { | ||
value: resolvedTo, | ||
state: nextState | ||
}); | ||
@@ -342,13 +340,11 @@ } | ||
if (first) { | ||
if (next.value !== first.value || next.state !== first.state) { | ||
setSource({ | ||
...next, | ||
replace: first.replace, | ||
scroll: first.scroll | ||
}); | ||
} | ||
setSource({ | ||
...next, | ||
replace: first.replace, | ||
scroll: first.scroll | ||
}); | ||
referrers.length = 0; | ||
} | ||
} | ||
function preloadRoute(url, preloadData) { | ||
function preloadRoute(url, options = {}) { | ||
const matches = getRouteMatches(branches(), url.pathname); | ||
@@ -364,3 +360,3 @@ const prevIntent = intent; | ||
inLoadFn = true; | ||
preloadData && | ||
options.preloadData && | ||
load && | ||
@@ -385,5 +381,3 @@ runWithOwner(getContext(), () => load({ | ||
const e = getRequestEvent(); | ||
return (e && e.router && e.router.submission | ||
? [e.router.submission] | ||
: []); | ||
return (e && e.router && e.router.submission ? [e.router.submission] : []); | ||
} | ||
@@ -390,0 +384,0 @@ } |
@@ -137,3 +137,5 @@ import type { Component, JSX, Signal } from "solid-js"; | ||
beforeLeave: BeforeLeaveLifecycle; | ||
preloadRoute: (url: URL, preloadData: boolean) => void; | ||
preloadRoute: (url: URL, options: { | ||
preloadData?: boolean; | ||
}) => void; | ||
singleFlight: boolean; | ||
@@ -168,2 +170,11 @@ submissions: Signal<Submission<any, any>[]>; | ||
}; | ||
export type SubmissionStub = { | ||
readonly input: undefined; | ||
readonly result: undefined; | ||
readonly error: undefined; | ||
readonly pending: undefined; | ||
readonly url: undefined; | ||
clear: () => void; | ||
retry: () => void; | ||
}; | ||
export interface MaybePreloadableComponent extends Component { | ||
@@ -170,0 +181,0 @@ preload?: () => void; |
@@ -9,3 +9,3 @@ { | ||
"license": "MIT", | ||
"version": "0.13.5", | ||
"version": "0.13.6", | ||
"homepage": "https://github.com/solidjs/solid-router#readme", | ||
@@ -12,0 +12,0 @@ "repository": { |
@@ -375,4 +375,6 @@ <p> | ||
> | ||
<Route path="layer2" | ||
component={() => <div>Innermost layer</div>}> </Route> | ||
<Route | ||
path="layer2" | ||
component={() => <div>Innermost layer</div>} | ||
/> | ||
</Route> | ||
@@ -875,2 +877,12 @@ </Route> | ||
### usePreloadRoute | ||
`usePreloadRoute` returns a function that can be used to preload a route manual. This is what happens automatically with link hovering and similar focus based behavior, but it is available here as an API. | ||
```js | ||
const preload = usePreloadRoute(); | ||
preload(`/users/settings`, { preloadData: true }); | ||
``` | ||
### useBeforeLeave | ||
@@ -877,0 +889,0 @@ |
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
169009
3781
991