@solidjs/router
Advanced tools
Comparing version 0.10.10 to 0.11.0
import type { JSX } from "solid-js"; | ||
import type { Location, Navigator } from "./types"; | ||
import type { Location, Navigator } from "./types.js"; | ||
declare module "solid-js" { | ||
@@ -4,0 +4,0 @@ namespace JSX { |
import { createMemo, mergeProps, splitProps } from "solid-js"; | ||
import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing"; | ||
import { normalizePath } from "./utils"; | ||
import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing.js"; | ||
import { normalizePath } from "./utils.js"; | ||
export function A(props) { | ||
@@ -20,13 +20,13 @@ props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props); | ||
if (to_ === undefined) | ||
return false; | ||
return [false, false]; | ||
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase(); | ||
const loc = normalizePath(location.pathname).toLowerCase(); | ||
return props.end ? path === loc : loc.startsWith(path); | ||
return [props.end ? path === loc : loc.startsWith(path), path === loc]; | ||
}); | ||
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{ | ||
...(props.class && { [props.class]: true }), | ||
[props.inactiveClass]: !isActive(), | ||
[props.activeClass]: isActive(), | ||
[props.inactiveClass]: !isActive()[0], | ||
[props.activeClass]: isActive()[0], | ||
...rest.classList | ||
}} link aria-current={isActive() ? "page" : undefined}/>); | ||
}} link aria-current={isActive()[1] ? "page" : undefined}/>); | ||
} | ||
@@ -33,0 +33,0 @@ export function Navigate(props) { |
import { JSX } from "solid-js"; | ||
import { Submission } from "../types"; | ||
import { Submission } from "../types.js"; | ||
export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<U>) & { | ||
@@ -4,0 +4,0 @@ url: string; |
import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js"; | ||
import { isServer } from "solid-js/web"; | ||
import { useRouter } from "../routing"; | ||
import { redirectStatusCodes, mockBase } from "../utils"; | ||
import { cacheKeyOp, hashKey, revalidate } from "./cache"; | ||
import { useRouter } from "../routing.js"; | ||
import { redirectStatusCodes, mockBase } from "../utils.js"; | ||
import { cacheKeyOp, hashKey, revalidate } from "./cache.js"; | ||
export const actions = /* #__PURE__ */ new Map(); | ||
@@ -7,0 +7,0 @@ export function useSubmissions(fn, filter) { |
@@ -1,4 +0,3 @@ | ||
import { type Signal } from "solid-js"; | ||
import { type ReconcileOptions } from "solid-js/store"; | ||
type CacheEntry = [number, any, string, Set<Signal<number>>]; | ||
import { CacheEntry } from "../types.js"; | ||
export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>; | ||
@@ -13,5 +12,4 @@ export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void; | ||
var set: (key: string, value: any) => void; | ||
var clear: () => any; | ||
var clear: () => void; | ||
} | ||
export declare function hashKey<T extends Array<any>>(args: T): string; | ||
export {}; |
@@ -1,6 +0,6 @@ | ||
import { createSignal, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js"; | ||
import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js"; | ||
import { createStore, reconcile } from "solid-js/store"; | ||
import { getRequestEvent, isServer } from "solid-js/web"; | ||
import { useNavigate, getIntent } from "../routing"; | ||
import { redirectStatusCodes } from "../utils"; | ||
import { useNavigate, getIntent } from "../routing.js"; | ||
import { redirectStatusCodes } from "../utils.js"; | ||
const LocationHeader = "Location"; | ||
@@ -15,3 +15,3 @@ const PRELOAD_TIMEOUT = 5000; | ||
for (let [k, v] of cacheMap.entries()) { | ||
if (!v[3].size && now - v[0] > CACHE_TIMEOUT) { | ||
if (!v[3].count && now - v[0] > CACHE_TIMEOUT) { | ||
cacheMap.delete(k); | ||
@@ -25,6 +25,6 @@ } | ||
return cacheMap; | ||
const req = getRequestEvent() || sharedConfig.context; | ||
const req = getRequestEvent(); | ||
if (!req) | ||
throw new Error("Cannot find cache context"); | ||
return req.routerCache || (req.routerCache = new Map()); | ||
return (req.router || (req.router = {})).cache || (req.router.cache = new Map()); | ||
} | ||
@@ -36,3 +36,3 @@ export function revalidate(key, force = true) { | ||
force && (entry[0] = 0); //force cache miss | ||
revalidateSignals(entry[3], now); // retrigger live signals | ||
entry[3][1](now); // retrigger live signals | ||
}); | ||
@@ -48,6 +48,2 @@ }); | ||
} | ||
function revalidateSignals(set, time) { | ||
for (let s of set) | ||
s[1](time); | ||
} | ||
export function cache(fn, name, options) { | ||
@@ -66,12 +62,16 @@ const [store, setStore] = createStore({}); | ||
let cached = cache.get(key); | ||
let version; | ||
if (owner && !isServer) { | ||
version = createSignal(now, { | ||
equals: (p, v) => v - p < 50 // margin of error | ||
}); | ||
onCleanup(() => cached[3].delete(version)); | ||
version[0](); // track it; | ||
let tracking; | ||
if (getListener() && !isServer) { | ||
tracking = true; | ||
onCleanup(() => cached[3].count--); | ||
} | ||
if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) { | ||
version && cached[3].add(version); | ||
if (cached && | ||
(isServer || | ||
intent === "native" || | ||
(cached[0] && cached[3].count) || | ||
Date.now() - cached[0] < PRELOAD_TIMEOUT)) { | ||
if (tracking) { | ||
cached[3].count++; | ||
cached[3][0](); // track | ||
} | ||
if (cached[2] === "preload" && intent !== "preload") { | ||
@@ -86,5 +86,3 @@ cached[0] = now; | ||
: handleResponse(false)(cached[1]); | ||
!isServer && | ||
intent === "navigate" && | ||
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version | ||
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version | ||
} | ||
@@ -105,9 +103,12 @@ return res; | ||
cached[2] = intent; | ||
version && cached[3].add(version); | ||
if (!isServer && intent === "navigate") { | ||
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version | ||
} | ||
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version | ||
} | ||
else | ||
cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])])); | ||
else { | ||
cache.set(key, (cached = [now, res, intent, createSignal(now)])); | ||
cached[3].count = 0; | ||
} | ||
if (tracking) { | ||
cached[3].count++; | ||
cached[3][0](); // track | ||
} | ||
if (intent !== "preload") { | ||
@@ -159,9 +160,2 @@ res = | ||
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) { | ||
@@ -171,6 +165,7 @@ cached[0] = now; | ||
cached[2] = "preload"; | ||
version && cached[3].add(version); | ||
} | ||
else | ||
cache.set(key, (cached = [now, value, , new Set(version ? [version] : [])])); | ||
else { | ||
cache.set(key, (cached = [now, value, , createSignal(now)])); | ||
cached[3].count = 0; | ||
} | ||
}; | ||
@@ -177,0 +172,0 @@ cache.clear = () => getCache().clear(); |
@@ -1,2 +0,2 @@ | ||
import type { RouterContext } from "../types"; | ||
import type { RouterContext } from "../types.js"; | ||
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"; | ||
import { mockBase } from "../utils"; | ||
import { actions } from "./action.js"; | ||
import { mockBase } from "../utils.js"; | ||
export function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "/_server") { | ||
@@ -6,0 +6,0 @@ return (router) => { |
@@ -1,4 +0,4 @@ | ||
export { createAsync } from "./createAsync"; | ||
export { action, useSubmission, useSubmissions, useAction, type Action } from "./action"; | ||
export { cache, revalidate, type CachedFunction } from "./cache"; | ||
export { redirect, reload, json } from "./response"; | ||
export { createAsync } from "./createAsync.js"; | ||
export { action, useSubmission, useSubmissions, useAction, type Action } from "./action.js"; | ||
export { cache, revalidate, type CachedFunction } from "./cache.js"; | ||
export { redirect, reload, json } from "./response.js"; |
@@ -1,4 +0,4 @@ | ||
export { createAsync } from "./createAsync"; | ||
export { action, useSubmission, useSubmissions, useAction } from "./action"; | ||
export { cache, revalidate } from "./cache"; | ||
export { redirect, reload, json } from "./response"; | ||
export { createAsync } from "./createAsync.js"; | ||
export { action, useSubmission, useSubmissions, useAction } from "./action.js"; | ||
export { cache, revalidate } from "./cache.js"; | ||
export { redirect, reload, json } from "./response.js"; |
@@ -1,7 +0,7 @@ | ||
export * from "./routers"; | ||
export * from "./components"; | ||
export * from "./lifecycle"; | ||
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing"; | ||
export { mergeSearchString as _mergeSearchString } from "./utils"; | ||
export * from "./data"; | ||
export type { Location, LocationChange, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types"; | ||
export * from "./routers/index.js"; | ||
export * from "./components.jsx"; | ||
export * from "./lifecycle.js"; | ||
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing.js"; | ||
export { mergeSearchString as _mergeSearchString } from "./utils.js"; | ||
export * from "./data/index.js"; | ||
export type { Location, LocationChange, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types.js"; |
import { isServer, getRequestEvent, createComponent as createComponent$1, 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, createRoot, Show, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js'; | ||
import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, mergeProps, createRoot, Show, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js'; | ||
import { createStore, reconcile } from 'solid-js/store'; | ||
@@ -39,2 +39,42 @@ | ||
// The following supports browser initiated blocking (eg back/forward) | ||
let depth; | ||
function saveCurrentDepth() { | ||
if (!window.history.state || window.history.state._depth == null) { | ||
window.history.replaceState({ | ||
...window.history.state, | ||
_depth: window.history.length - 1 | ||
}, ""); | ||
} | ||
depth = window.history.state._depth; | ||
} | ||
if (!isServer) { | ||
saveCurrentDepth(); | ||
} | ||
function keepDepth(state) { | ||
return { | ||
...state, | ||
_depth: window.history.state && window.history.state._depth | ||
}; | ||
} | ||
function notifyIfNotBlocked(notify, block) { | ||
let ignore = false; | ||
return () => { | ||
const prevDepth = depth; | ||
saveCurrentDepth(); | ||
const delta = prevDepth == null ? null : depth - prevDepth; | ||
if (ignore) { | ||
ignore = false; | ||
return; | ||
} | ||
if (delta && block(delta)) { | ||
ignore = true; | ||
window.history.go(-delta); | ||
} else { | ||
notify(); | ||
} | ||
}; | ||
} | ||
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i; | ||
@@ -262,3 +302,3 @@ const trimPathRegex = /^\/+|(\/)\/+$/g; | ||
pattern = pattern.split("/").map(s => { | ||
return s.startsWith(':') || s.startsWith('*') ? s : encodeURIComponent(s); | ||
return s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s); | ||
}).join("/"); | ||
@@ -449,3 +489,3 @@ acc.push({ | ||
if (!to) ; else if (utils.go) { | ||
beforeLeave.confirm(to, options) && utils.go(to); | ||
utils.go(to); | ||
} else { | ||
@@ -477,8 +517,8 @@ console.warn("Router integration does not support relative routing"); | ||
const e = getRequestEvent(); | ||
e && (e.response = new Response(null, { | ||
e && (e.response = { | ||
status: 302, | ||
headers: { | ||
headers: new Headers({ | ||
Location: resolvedTo | ||
} | ||
})); | ||
}) | ||
}); | ||
setSource({ | ||
@@ -561,3 +601,3 @@ value: resolvedTo, | ||
const e = getRequestEvent(); | ||
return e && e.initialSubmission ? [e.initialSubmission] : []; | ||
return e && e.router && e.router.submission ? [e.router.submission] : []; | ||
} | ||
@@ -631,3 +671,3 @@ } | ||
const e = getRequestEvent(); | ||
e && (e.routerMatches || (e.routerMatches = [])).push(matches().map(({ | ||
e && ((e.router || (e.router = {})).matches || (e.router.matches = matches().map(({ | ||
route, | ||
@@ -642,3 +682,3 @@ path, | ||
metadata: route.metadata | ||
}))); | ||
})))); | ||
} | ||
@@ -790,3 +830,3 @@ const params = createMemoObject(() => { | ||
for (let [k, v] of cacheMap.entries()) { | ||
if (!v[3].size && now - v[0] > CACHE_TIMEOUT) { | ||
if (!v[3].count && now - v[0] > CACHE_TIMEOUT) { | ||
cacheMap.delete(k); | ||
@@ -799,5 +839,5 @@ } | ||
if (!isServer) return cacheMap; | ||
const req = getRequestEvent() || sharedConfig.context; | ||
const req = getRequestEvent(); | ||
if (!req) throw new Error("Cannot find cache context"); | ||
return req.routerCache || (req.routerCache = new Map()); | ||
return (req.router || (req.router = {})).cache || (req.router.cache = new Map()); | ||
} | ||
@@ -809,7 +849,6 @@ function revalidate(key, force = true) { | ||
force && (entry[0] = 0); //force cache miss | ||
revalidateSignals(entry[3], now); // retrigger live signals | ||
entry[3][1](now); // retrigger live signals | ||
}); | ||
}); | ||
} | ||
function cacheKeyOp(key, fn) { | ||
@@ -821,5 +860,2 @@ key && !Array.isArray(key) && (key = [key]); | ||
} | ||
function revalidateSignals(set, time) { | ||
for (let s of set) s[1](time); | ||
} | ||
function cache(fn, name, options) { | ||
@@ -837,14 +873,12 @@ const [store, setStore] = createStore({}); | ||
let cached = cache.get(key); | ||
let version; | ||
if (owner && !isServer) { | ||
version = createSignal(now, { | ||
equals: (p, v) => v - p < 50 // margin of error | ||
}); | ||
onCleanup(() => cached[3].delete(version)); | ||
version[0](); // track it; | ||
let tracking; | ||
if (getListener() && !isServer) { | ||
tracking = true; | ||
onCleanup(() => cached[3].count--); | ||
} | ||
if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) { | ||
version && cached[3].add(version); | ||
if (cached && (isServer || intent === "native" || cached[0] && cached[3].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) { | ||
if (tracking) { | ||
cached[3].count++; | ||
cached[3][0](); // track | ||
} | ||
if (cached[2] === "preload" && intent !== "preload") { | ||
@@ -856,5 +890,4 @@ cached[0] = now; | ||
res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]); | ||
!isServer && intent === "navigate" && startTransition(() => revalidateSignals(cached[3], cached[0])); // update version | ||
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version | ||
} | ||
return res; | ||
@@ -874,7 +907,11 @@ } | ||
cached[2] = intent; | ||
version && cached[3].add(version); | ||
if (!isServer && intent === "navigate") { | ||
startTransition(() => revalidateSignals(cached[3], cached[0])); // update version | ||
} | ||
} else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]); | ||
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version | ||
} else { | ||
cache.set(key, cached = [now, res, intent, createSignal(now)]); | ||
cached[3].count = 0; | ||
} | ||
if (tracking) { | ||
cached[3].count++; | ||
cached[3][0](); // track | ||
} | ||
if (intent !== "preload") { | ||
@@ -919,10 +956,2 @@ res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res); | ||
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) { | ||
@@ -932,4 +961,6 @@ cached[0] = now; | ||
cached[2] = "preload"; | ||
version && cached[3].add(version); | ||
} else cache.set(key, cached = [now, value,, new Set(version ? [version] : [])]); | ||
} else { | ||
cache.set(key, cached = [now, value,, createSignal(now)]); | ||
cached[3].count = 0; | ||
} | ||
}; | ||
@@ -1167,7 +1198,9 @@ cache.clear = () => getCache().clear(); | ||
if (isServer) return StaticRouter(props); | ||
const getSource = () => ({ | ||
value: window.location.pathname + window.location.search + window.location.hash, | ||
state: window.history.state | ||
}); | ||
const beforeLeave = createBeforeLeave(); | ||
return createRouter({ | ||
get: () => ({ | ||
value: window.location.pathname + window.location.search + window.location.hash, | ||
state: history.state | ||
}), | ||
get: getSource, | ||
set({ | ||
@@ -1180,3 +1213,3 @@ value, | ||
if (replace) { | ||
window.history.replaceState(state, "", value); | ||
window.history.replaceState(keepDepth(state), "", value); | ||
} else { | ||
@@ -1186,7 +1219,18 @@ window.history.pushState(state, "", value); | ||
scrollToHash(window.location.hash.slice(1), scroll); | ||
saveCurrentDepth(); | ||
}, | ||
init: notify => bindEvent(window, "popstate", () => notify()), | ||
init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => { | ||
if (delta && delta < 0) { | ||
return !beforeLeave.confirm(delta); | ||
} else { | ||
const s = getSource(); | ||
return !beforeLeave.confirm(s.value, { | ||
state: s.state | ||
}); | ||
} | ||
})), | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
go: delta => window.history.go(delta) | ||
go: delta => window.history.go(delta), | ||
beforeLeave | ||
} | ||
@@ -1208,4 +1252,6 @@ })(props); | ||
function HashRouter(props) { | ||
const getSource = () => window.location.hash.slice(1); | ||
const beforeLeave = createBeforeLeave(); | ||
return createRouter({ | ||
get: () => window.location.hash.slice(1), | ||
get: getSource, | ||
set({ | ||
@@ -1218,3 +1264,3 @@ value, | ||
if (replace) { | ||
window.history.replaceState(state, "", "#" + value); | ||
window.history.replaceState(keepDepth(state), "", "#" + value); | ||
} else { | ||
@@ -1226,4 +1272,5 @@ window.location.hash = value; | ||
scrollToHash(hash, scroll); | ||
saveCurrentDepth(); | ||
}, | ||
init: notify => bindEvent(window, "hashchange", () => notify()), | ||
init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))), | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
@@ -1233,3 +1280,4 @@ utils: { | ||
renderPath: path => `#${path}`, | ||
parsePath: hashParser | ||
parsePath: hashParser, | ||
beforeLeave | ||
} | ||
@@ -1306,6 +1354,6 @@ })(props); | ||
const to_ = to(); | ||
if (to_ === undefined) return false; | ||
if (to_ === undefined) return [false, false]; | ||
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase(); | ||
const loc = normalizePath(location.pathname).toLowerCase(); | ||
return props.end ? path === loc : loc.startsWith(path); | ||
return [props.end ? path === loc : loc.startsWith(path), path === loc]; | ||
}); | ||
@@ -1326,4 +1374,4 @@ return (() => { | ||
}), | ||
[props.inactiveClass]: !isActive(), | ||
[props.activeClass]: isActive(), | ||
[props.inactiveClass]: !isActive()[0], | ||
[props.activeClass]: isActive()[0], | ||
...rest.classList | ||
@@ -1334,3 +1382,3 @@ }; | ||
get ["aria-current"]() { | ||
return isActive() ? "page" : undefined; | ||
return isActive()[1] ? "page" : undefined; | ||
} | ||
@@ -1459,2 +1507,2 @@ }), false, false); | ||
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createMemoryHistory, createRouter, json, redirect, reload, revalidate, useAction, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions }; | ||
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions }; |
@@ -1,6 +0,6 @@ | ||
export * from "./routers"; | ||
export * from "./components"; | ||
export * from "./lifecycle"; | ||
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing"; | ||
export { mergeSearchString as _mergeSearchString } from "./utils"; | ||
export * from "./data"; | ||
export * from "./routers/index.js"; | ||
export * from "./components.jsx"; | ||
export * from "./lifecycle.js"; | ||
export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing.js"; | ||
export { mergeSearchString as _mergeSearchString } from "./utils.js"; | ||
export * from "./data/index.js"; |
@@ -1,2 +0,5 @@ | ||
import { BeforeLeaveLifecycle } from "./types"; | ||
import { BeforeLeaveLifecycle, LocationChange } from "./types.js"; | ||
export declare function createBeforeLeave(): BeforeLeaveLifecycle; | ||
export declare function saveCurrentDepth(): void; | ||
export declare function keepDepth(state: any): any; | ||
export declare function notifyIfNotBlocked(notify: (value?: string | LocationChange) => void, block: (delta: number | null) => boolean): () => void; |
@@ -0,1 +1,2 @@ | ||
import { isServer } from "solid-js/web"; | ||
export function createBeforeLeave() { | ||
@@ -33,1 +34,37 @@ let listeners = new Set(); | ||
} | ||
// The following supports browser initiated blocking (eg back/forward) | ||
let depth; | ||
export function saveCurrentDepth() { | ||
if (!window.history.state || window.history.state._depth == null) { | ||
window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, ""); | ||
} | ||
depth = window.history.state._depth; | ||
} | ||
if (!isServer) { | ||
saveCurrentDepth(); | ||
} | ||
export function keepDepth(state) { | ||
return { | ||
...state, | ||
_depth: window.history.state && window.history.state._depth | ||
}; | ||
} | ||
export function notifyIfNotBlocked(notify, block) { | ||
let ignore = false; | ||
return () => { | ||
const prevDepth = depth; | ||
saveCurrentDepth(); | ||
const delta = prevDepth == null ? null : depth - prevDepth; | ||
if (ignore) { | ||
ignore = false; | ||
return; | ||
} | ||
if (delta && block(delta)) { | ||
ignore = true; | ||
window.history.go(-delta); | ||
} | ||
else { | ||
notify(); | ||
} | ||
}; | ||
} |
import type { Component, JSX } from "solid-js"; | ||
import type { MatchFilters, RouteLoadFunc, RouteDefinition, RouterIntegration, RouteSectionProps } from "../types"; | ||
import type { MatchFilters, RouteLoadFunc, RouteDefinition, RouterIntegration, RouteSectionProps } from "../types.ts"; | ||
export type BaseRouterProps = { | ||
@@ -4,0 +4,0 @@ base?: string; |
/*@refresh skip*/ | ||
import { getRequestEvent, isServer } from "solid-js/web"; | ||
import { children, createMemo, createRoot, mergeProps, on, Show } from "solid-js"; | ||
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing"; | ||
import { createMemoObject } from "../utils"; | ||
import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing.js"; | ||
import { createMemoObject } from "../utils.js"; | ||
export const createRouterComponent = (router) => (props) => { | ||
@@ -21,9 +21,10 @@ const { base } = props; | ||
e && | ||
(e.routerMatches || (e.routerMatches = [])).push(matches().map(({ route, path, params }) => ({ | ||
path: route.originalPath, | ||
pattern: route.pattern, | ||
match: path, | ||
params, | ||
metadata: route.metadata | ||
}))); | ||
((e.router || (e.router = {})).matches || | ||
(e.router.matches = matches().map(({ route, path, params }) => ({ | ||
path: route.originalPath, | ||
pattern: route.pattern, | ||
match: path, | ||
params, | ||
metadata: route.metadata | ||
})))); | ||
} | ||
@@ -30,0 +31,0 @@ const params = createMemoObject(() => { |
@@ -1,2 +0,2 @@ | ||
import type { LocationChange, RouterContext, RouterUtils } from "../types"; | ||
import type { LocationChange, RouterContext, RouterUtils } from "../types.ts"; | ||
export declare function createRouter(config: { | ||
@@ -8,4 +8,4 @@ get: () => string | LocationChange; | ||
utils?: Partial<RouterUtils>; | ||
}): (props: import("./components").BaseRouterProps) => import("solid-js").JSX.Element; | ||
}): (props: import("./components.jsx").BaseRouterProps) => import("solid-js").JSX.Element; | ||
export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void; | ||
export declare function scrollToHash(hash: string, fallbackTop?: boolean): void; |
import { createSignal, onCleanup } from "solid-js"; | ||
import { createRouterComponent } from "./components"; | ||
import { createRouterComponent } from "./components.jsx"; | ||
function intercept([value, setValue], get, set) { | ||
@@ -4,0 +4,0 @@ return [get ? () => get(value()) : value, set ? (v) => setValue(set(v)) : setValue]; |
import type { JSX } from "solid-js"; | ||
import type { BaseRouterProps } from "./components"; | ||
import type { BaseRouterProps } from "./components.js"; | ||
export declare function hashParser(str: string): string; | ||
@@ -4,0 +4,0 @@ export type HashRouterProps = BaseRouterProps & { |
@@ -1,3 +0,4 @@ | ||
import { setupNativeEvents } from "../data/events"; | ||
import { createRouter, scrollToHash, bindEvent } from "./createRouter"; | ||
import { setupNativeEvents } from "../data/events.js"; | ||
import { createRouter, scrollToHash, bindEvent } from "./createRouter.js"; | ||
import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js"; | ||
export function hashParser(str) { | ||
@@ -15,7 +16,9 @@ const to = str.replace(/^.*?#/, ""); | ||
export function HashRouter(props) { | ||
const getSource = () => window.location.hash.slice(1); | ||
const beforeLeave = createBeforeLeave(); | ||
return createRouter({ | ||
get: () => window.location.hash.slice(1), | ||
get: getSource, | ||
set({ value, replace, scroll, state }) { | ||
if (replace) { | ||
window.history.replaceState(state, "", "#" + value); | ||
window.history.replaceState(keepDepth(state), "", "#" + value); | ||
} | ||
@@ -28,4 +31,5 @@ else { | ||
scrollToHash(hash, scroll); | ||
saveCurrentDepth(); | ||
}, | ||
init: notify => bindEvent(window, "hashchange", () => notify()), | ||
init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))), | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
@@ -35,5 +39,6 @@ utils: { | ||
renderPath: path => `#${path}`, | ||
parsePath: hashParser | ||
parsePath: hashParser, | ||
beforeLeave | ||
} | ||
})(props); | ||
} |
@@ -1,11 +0,11 @@ | ||
export { Route } from "./components"; | ||
export type { BaseRouterProps, RouteProps } from "./components"; | ||
export { createRouter } from "./createRouter"; | ||
export { Router } from "./Router"; | ||
export type { RouterProps } from "./Router"; | ||
export { HashRouter } from "./HashRouter"; | ||
export type { HashRouterProps } from "./HashRouter"; | ||
export { MemoryRouter, createMemoryHistory } from "./MemoryRouter"; | ||
export type { MemoryRouterProps, MemoryHistory } from "./MemoryRouter"; | ||
export { StaticRouter } from "./StaticRouter"; | ||
export type { StaticRouterProps } from "./StaticRouter"; | ||
export { Route } from "./components.jsx"; | ||
export type { BaseRouterProps, RouteProps } from "./components.jsx"; | ||
export { createRouter } from "./createRouter.js"; | ||
export { Router } from "./Router.js"; | ||
export type { RouterProps } from "./Router.js"; | ||
export { HashRouter } from "./HashRouter.js"; | ||
export type { HashRouterProps } from "./HashRouter.js"; | ||
export { MemoryRouter, createMemoryHistory } from "./MemoryRouter.js"; | ||
export type { MemoryRouterProps, MemoryHistory } from "./MemoryRouter.js"; | ||
export { StaticRouter } from "./StaticRouter.js"; | ||
export type { StaticRouterProps } from "./StaticRouter.js"; |
@@ -1,6 +0,6 @@ | ||
export { Route } from "./components"; | ||
export { createRouter } from "./createRouter"; | ||
export { Router } from "./Router"; | ||
export { HashRouter } from "./HashRouter"; | ||
export { MemoryRouter, createMemoryHistory } from "./MemoryRouter"; | ||
export { StaticRouter } from "./StaticRouter"; | ||
export { Route } from "./components.jsx"; | ||
export { createRouter } from "./createRouter.js"; | ||
export { Router } from "./Router.js"; | ||
export { HashRouter } from "./HashRouter.js"; | ||
export { MemoryRouter, createMemoryHistory } from "./MemoryRouter.js"; | ||
export { StaticRouter } from "./StaticRouter.js"; |
@@ -1,3 +0,3 @@ | ||
import type { LocationChange } from "../types"; | ||
import type { BaseRouterProps } from "./components"; | ||
import type { LocationChange } from "../types.js"; | ||
import type { BaseRouterProps } from "./components.jsx"; | ||
import type { JSX } from "solid-js"; | ||
@@ -4,0 +4,0 @@ export type MemoryHistory = { |
@@ -1,2 +0,2 @@ | ||
import { createRouter, scrollToHash } from "./createRouter"; | ||
import { createRouter, scrollToHash } from "./createRouter.js"; | ||
export function createMemoryHistory() { | ||
@@ -3,0 +3,0 @@ const entries = ["/"]; |
@@ -1,2 +0,2 @@ | ||
import type { BaseRouterProps } from "./components"; | ||
import type { BaseRouterProps } from "./components.jsx"; | ||
import type { JSX } from "solid-js"; | ||
@@ -3,0 +3,0 @@ export type RouterProps = BaseRouterProps & { |
import { isServer } from "solid-js/web"; | ||
import { createRouter, scrollToHash, bindEvent } from "./createRouter"; | ||
import { StaticRouter } from "./StaticRouter"; | ||
import { setupNativeEvents } from "../data/events"; | ||
import { createRouter, scrollToHash, bindEvent } from "./createRouter.js"; | ||
import { StaticRouter } from "./StaticRouter.js"; | ||
import { setupNativeEvents } from "../data/events.js"; | ||
import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js"; | ||
export function Router(props) { | ||
if (isServer) | ||
return StaticRouter(props); | ||
const getSource = () => ({ | ||
value: window.location.pathname + window.location.search + window.location.hash, | ||
state: window.history.state | ||
}); | ||
const beforeLeave = createBeforeLeave(); | ||
return createRouter({ | ||
get: () => ({ | ||
value: window.location.pathname + window.location.search + window.location.hash, | ||
state: history.state | ||
}), | ||
get: getSource, | ||
set({ value, replace, scroll, state }) { | ||
if (replace) { | ||
window.history.replaceState(state, "", value); | ||
window.history.replaceState(keepDepth(state), "", value); | ||
} | ||
@@ -21,9 +24,19 @@ else { | ||
scrollToHash(window.location.hash.slice(1), scroll); | ||
saveCurrentDepth(); | ||
}, | ||
init: notify => bindEvent(window, "popstate", () => notify()), | ||
init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => { | ||
if (delta && delta < 0) { | ||
return !beforeLeave.confirm(delta); | ||
} | ||
else { | ||
const s = getSource(); | ||
return !beforeLeave.confirm(s.value, { state: s.state }); | ||
} | ||
})), | ||
create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase), | ||
utils: { | ||
go: delta => window.history.go(delta) | ||
go: delta => window.history.go(delta), | ||
beforeLeave | ||
} | ||
})(props); | ||
} |
@@ -1,2 +0,2 @@ | ||
import { type BaseRouterProps } from "./components"; | ||
import { type BaseRouterProps } from "./components.jsx"; | ||
import type { JSX } from "solid-js"; | ||
@@ -3,0 +3,0 @@ export type StaticRouterProps = BaseRouterProps & { |
import { getRequestEvent } from "solid-js/web"; | ||
import { createRouterComponent } from "./components"; | ||
import { createRouterComponent } from "./components.jsx"; | ||
function getPath(url) { | ||
@@ -4,0 +4,0 @@ const u = new URL(url); |
import { JSX, Accessor } from "solid-js"; | ||
import type { BeforeLeaveEventArgs, Branch, Intent, Location, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types"; | ||
import type { BeforeLeaveEventArgs, Branch, Intent, Location, MatchFilters, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types.js"; | ||
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>; | ||
@@ -12,3 +12,3 @@ export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>; | ||
export declare const useIsRouting: () => () => boolean; | ||
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S> | undefined) => Accessor<import("./types").PathMatch | undefined>; | ||
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S> | undefined) => Accessor<import("./types.js").PathMatch | undefined>; | ||
export declare const useParams: <T extends Params>() => T; | ||
@@ -15,0 +15,0 @@ export declare const useSearchParams: <T extends Params>() => [Partial<T>, (params: SetParams, options?: Partial<NavigateOptions>) => void]; |
import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js"; | ||
import { isServer, getRequestEvent } from "solid-js/web"; | ||
import { createBeforeLeave } from "./lifecycle"; | ||
import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils"; | ||
import { createBeforeLeave } from "./lifecycle.js"; | ||
import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js"; | ||
const MAX_REDIRECTS = 100; | ||
@@ -71,5 +71,8 @@ export const RouterContextObj = createContext(); | ||
let pattern = isLeaf ? path : path.split("/*", 1)[0]; | ||
pattern = pattern.split("/").map((s) => { | ||
return (s.startsWith(':') || s.startsWith('*')) ? s : encodeURIComponent(s); | ||
}).join("/"); | ||
pattern = pattern | ||
.split("/") | ||
.map((s) => { | ||
return s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s); | ||
}) | ||
.join("/"); | ||
acc.push({ | ||
@@ -256,3 +259,3 @@ ...shared, | ||
else if (utils.go) { | ||
beforeLeave.confirm(to, options) && utils.go(to); | ||
utils.go(to); | ||
} | ||
@@ -281,4 +284,3 @@ else { | ||
const e = getRequestEvent(); | ||
e && | ||
(e.response = new Response(null, { status: 302, headers: { Location: resolvedTo } })); | ||
e && (e.response = { status: 302, headers: new Headers({ Location: resolvedTo }) }); | ||
setSource({ value: resolvedTo, replace, scroll, state: nextState }); | ||
@@ -353,3 +355,3 @@ } | ||
const e = getRequestEvent(); | ||
return e && e.initialSubmission ? [e.initialSubmission] : []; | ||
return e && e.router && e.router.submission ? [e.router.submission] : []; | ||
} | ||
@@ -356,0 +358,0 @@ } |
import type { Component, JSX, Signal } from "solid-js"; | ||
declare module "solid-js/web" { | ||
interface RequestEvent { | ||
response?: Response; | ||
routerMatches?: OutputMatch[][]; | ||
routerCache?: Map<any, any>; | ||
initialSubmission?: Submission<any, any>; | ||
response?: { | ||
status?: number; | ||
statusText?: string; | ||
headers: Headers; | ||
}; | ||
router?: { | ||
matches?: OutputMatch[]; | ||
cache?: Map<string, CacheEntry>; | ||
submission?: Submission<any, any>; | ||
}; | ||
serverOnly?: boolean; | ||
@@ -154,1 +160,4 @@ } | ||
} | ||
export type CacheEntry = [number, any, Intent | undefined, Signal<number> & { | ||
count: number; | ||
}]; |
@@ -1,2 +0,2 @@ | ||
import type { MatchFilters, Params, PathMatch, Route, SetParams } from "./types"; | ||
import type { MatchFilters, Params, PathMatch, Route, SetParams } from "./types.ts"; | ||
export declare const mockBase = "http://sr"; | ||
@@ -3,0 +3,0 @@ export declare const redirectStatusCodes: Set<number>; |
@@ -9,3 +9,3 @@ { | ||
"license": "MIT", | ||
"version": "0.10.10", | ||
"version": "0.11.0", | ||
"homepage": "https://github.com/solidjs/solid-router#readme", | ||
@@ -33,18 +33,19 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/core": "^7.18.13", | ||
"@babel/preset-typescript": "^7.18.6", | ||
"@rollup/plugin-babel": "6.0.3", | ||
"@rollup/plugin-node-resolve": "15.0.1", | ||
"@rollup/plugin-terser": "0.2.0", | ||
"@types/jest": "^29.0.0", | ||
"@types/node": "^20.9.0", | ||
"babel-jest": "^29.0.1", | ||
"@babel/core": "^7.23.9", | ||
"@babel/preset-typescript": "^7.23.3", | ||
"@changesets/cli": "^2.27.1", | ||
"@rollup/plugin-babel": "6.0.4", | ||
"@rollup/plugin-node-resolve": "15.2.3", | ||
"@rollup/plugin-terser": "0.4.4", | ||
"@types/jest": "^29.5.11", | ||
"@types/node": "^20.11.14", | ||
"babel-preset-solid": "^1.8.6", | ||
"jest": "^29.0.1", | ||
"jest-environment-jsdom": "^29.2.1", | ||
"prettier": "^2.7.1", | ||
"rollup": "^3.7.5", | ||
"solid-jest": "^0.2.0", | ||
"jsdom": "^24.0.0", | ||
"prettier": "^2.7.0", | ||
"rollup": "^4.9.6", | ||
"solid-js": "^1.8.7", | ||
"typescript": "^5.2.2" | ||
"typescript": "^5.3.3", | ||
"vite": "^5.0.12", | ||
"vite-plugin-solid": "^2.9.1", | ||
"vitest": "^1.2.2" | ||
}, | ||
@@ -54,10 +55,6 @@ "peerDependencies": { | ||
}, | ||
"jest": { | ||
"preset": "solid-jest/preset/browser" | ||
}, | ||
"scripts": { | ||
"build": "tsc && rollup -c", | ||
"test": "jest && npm run test:types", | ||
"test:watch": "jest --watch", | ||
"test:coverage": "jest --coverage && npm run test:types", | ||
"test": "vitest run && npm run test:types", | ||
"test:watch": "vitest", | ||
"test:types": "tsc --project tsconfig.test.json", | ||
@@ -64,0 +61,0 @@ "pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"" |
@@ -568,9 +568,9 @@ <p> | ||
type Submission<T, U> = { | ||
input: T; | ||
result: U; | ||
error: any; | ||
pending: boolean | ||
clear: () => {} | ||
retry: () => {} | ||
} | ||
readonly input: T; | ||
readonly result?: U; | ||
readonly pending: boolean; | ||
readonly url: string; | ||
clear: () => void; | ||
retry: () => void; | ||
}; | ||
@@ -725,3 +725,3 @@ const submissions = useSubmissions(action, (input) => filter(input)); | ||
Like the `<a>` tag but supports relative paths and active class styling (requires client side JavaScript). | ||
Like the `<a>` tag but supports automatic apply of base path + relative paths and active class styling (requires client side JavaScript). | ||
@@ -728,0 +728,0 @@ 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. |
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
154390
3422
17