restructure project, add claudemd
This commit is contained in:
21
web/runtime/@solidjs/router/LICENSE
Normal file
21
web/runtime/@solidjs/router/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 Ryan Carniato
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1038
web/runtime/@solidjs/router/README.md
Normal file
1038
web/runtime/@solidjs/router/README.md
Normal file
File diff suppressed because it is too large
Load Diff
31
web/runtime/@solidjs/router/dist/components.d.ts
vendored
Normal file
31
web/runtime/@solidjs/router/dist/components.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import type { Location, Navigator } from "./types.js";
|
||||
declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface AnchorHTMLAttributes<T> {
|
||||
state?: string;
|
||||
noScroll?: boolean;
|
||||
replace?: boolean;
|
||||
preload?: boolean;
|
||||
link?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
|
||||
href: string;
|
||||
replace?: boolean | undefined;
|
||||
noScroll?: boolean | undefined;
|
||||
state?: unknown | undefined;
|
||||
inactiveClass?: string | undefined;
|
||||
activeClass?: string | undefined;
|
||||
end?: boolean | undefined;
|
||||
}
|
||||
export declare function A(props: AnchorProps): JSX.Element;
|
||||
export interface NavigateProps {
|
||||
href: ((args: {
|
||||
navigate: Navigator;
|
||||
location: Location;
|
||||
}) => string) | string;
|
||||
state?: unknown;
|
||||
}
|
||||
export declare function Navigate(props: NavigateProps): null;
|
||||
39
web/runtime/@solidjs/router/dist/components.jsx
vendored
Normal file
39
web/runtime/@solidjs/router/dist/components.jsx
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createMemo, mergeProps, splitProps } from "solid-js";
|
||||
import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing.js";
|
||||
import { normalizePath } from "./utils.js";
|
||||
export function A(props) {
|
||||
props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
|
||||
const [, rest] = splitProps(props, [
|
||||
"href",
|
||||
"state",
|
||||
"class",
|
||||
"activeClass",
|
||||
"inactiveClass",
|
||||
"end"
|
||||
]);
|
||||
const to = useResolvedPath(() => props.href);
|
||||
const href = useHref(to);
|
||||
const location = useLocation();
|
||||
const isActive = createMemo(() => {
|
||||
const to_ = to();
|
||||
if (to_ === undefined)
|
||||
return [false, false];
|
||||
const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase();
|
||||
const loc = decodeURI(normalizePath(location.pathname).toLowerCase());
|
||||
return [props.end ? path === loc : loc.startsWith(path + "/") || loc === path, path === loc];
|
||||
});
|
||||
return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
|
||||
...(props.class && { [props.class]: true }),
|
||||
[props.inactiveClass]: !isActive()[0],
|
||||
[props.activeClass]: isActive()[0],
|
||||
...rest.classList
|
||||
}} link aria-current={isActive()[1] ? "page" : undefined}/>);
|
||||
}
|
||||
export function Navigate(props) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { href, state } = props;
|
||||
const path = typeof href === "function" ? href({ navigate, location }) : href;
|
||||
navigate(path, { replace: true, state });
|
||||
return null;
|
||||
}
|
||||
17
web/runtime/@solidjs/router/dist/data/action.d.ts
vendored
Normal file
17
web/runtime/@solidjs/router/dist/data/action.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { JSX } from "solid-js";
|
||||
import type { Submission, SubmissionStub, NarrowResponse } from "../types.js";
|
||||
export type Action<T extends Array<any>, U, V = T> = (T extends [FormData | URLSearchParams] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
||||
url: string;
|
||||
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U, V>;
|
||||
};
|
||||
export declare const actions: Map<string, Action<any, any, any>>;
|
||||
export declare function useSubmissions<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<T, NarrowResponse<U>>[] & {
|
||||
pending: boolean;
|
||||
};
|
||||
export declare function useSubmission<T extends Array<any>, U, V>(fn: Action<T, U, V>, filter?: (input: V) => boolean): Submission<T, NarrowResponse<U>> | SubmissionStub;
|
||||
export declare function useAction<T extends Array<any>, U, V>(action: Action<T, U, V>): (...args: Parameters<Action<T, U, V>>) => Promise<NarrowResponse<U>>;
|
||||
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
|
||||
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, options?: {
|
||||
name?: string;
|
||||
onComplete?: (s: Submission<T, U>) => void;
|
||||
}): Action<T, U>;
|
||||
163
web/runtime/@solidjs/router/dist/data/action.js
vendored
Normal file
163
web/runtime/@solidjs/router/dist/data/action.js
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js";
|
||||
import { isServer } from "solid-js/web";
|
||||
import { useRouter } from "../routing.js";
|
||||
import { mockBase, setFunctionName } from "../utils.js";
|
||||
import { cacheKeyOp, hashKey, revalidate, query } from "./query.js";
|
||||
export const actions = /* #__PURE__ */ new Map();
|
||||
export function useSubmissions(fn, filter) {
|
||||
const router = useRouter();
|
||||
const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.base && (!filter || filter(s.input))));
|
||||
return new Proxy([], {
|
||||
get(_, property) {
|
||||
if (property === $TRACK)
|
||||
return subs();
|
||||
if (property === "pending")
|
||||
return subs().some(sub => !sub.result);
|
||||
return subs()[property];
|
||||
},
|
||||
has(_, property) {
|
||||
return property in subs();
|
||||
}
|
||||
});
|
||||
}
|
||||
export function useSubmission(fn, filter) {
|
||||
const submissions = useSubmissions(fn, filter);
|
||||
return new Proxy({}, {
|
||||
get(_, property) {
|
||||
if ((submissions.length === 0 && property === "clear") || property === "retry")
|
||||
return () => { };
|
||||
return submissions[submissions.length - 1]?.[property];
|
||||
}
|
||||
});
|
||||
}
|
||||
export function useAction(action) {
|
||||
const r = useRouter();
|
||||
return (...args) => action.apply({ r }, args);
|
||||
}
|
||||
export function action(fn, options = {}) {
|
||||
function mutate(...variables) {
|
||||
const router = this.r;
|
||||
const form = this.f;
|
||||
const p = (router.singleFlight && fn.withOptions
|
||||
? fn.withOptions({ headers: { "X-Single-Flight": "true" } })
|
||||
: fn)(...variables);
|
||||
const [result, setResult] = createSignal();
|
||||
let submission;
|
||||
function handler(error) {
|
||||
return async (res) => {
|
||||
const result = await handleResponse(res, error, router.navigatorFactory());
|
||||
let retry = null;
|
||||
o.onComplete?.({
|
||||
...submission,
|
||||
result: result?.data,
|
||||
error: result?.error,
|
||||
pending: false,
|
||||
retry() {
|
||||
return (retry = submission.retry());
|
||||
}
|
||||
});
|
||||
if (retry)
|
||||
return retry;
|
||||
if (!result)
|
||||
return submission.clear();
|
||||
setResult(result);
|
||||
if (result.error && !form)
|
||||
throw result.error;
|
||||
return result.data;
|
||||
};
|
||||
}
|
||||
router.submissions[1](s => [
|
||||
...s,
|
||||
(submission = {
|
||||
input: variables,
|
||||
url,
|
||||
get result() {
|
||||
return result()?.data;
|
||||
},
|
||||
get error() {
|
||||
return result()?.error;
|
||||
},
|
||||
get pending() {
|
||||
return !result();
|
||||
},
|
||||
clear() {
|
||||
router.submissions[1](v => v.filter(i => i !== submission));
|
||||
},
|
||||
retry() {
|
||||
setResult(undefined);
|
||||
const p = fn(...variables);
|
||||
return p.then(handler(), handler(true));
|
||||
}
|
||||
})
|
||||
]);
|
||||
return p.then(handler(), handler(true));
|
||||
}
|
||||
const o = typeof options === "string" ? { name: options } : options;
|
||||
const name = o.name || (!isServer ? String(hashString(fn.toString())) : undefined);
|
||||
const url = fn.url || (name && `https://action/${name}`) || "";
|
||||
mutate.base = url;
|
||||
if (name)
|
||||
setFunctionName(mutate, name);
|
||||
return toAction(mutate, url);
|
||||
}
|
||||
function toAction(fn, url) {
|
||||
fn.toString = () => {
|
||||
if (!url)
|
||||
throw new Error("Client Actions need explicit names if server rendered");
|
||||
return url;
|
||||
};
|
||||
fn.with = function (...args) {
|
||||
const newFn = function (...passedArgs) {
|
||||
return fn.call(this, ...args, ...passedArgs);
|
||||
};
|
||||
newFn.base = fn.base;
|
||||
const uri = new URL(url, mockBase);
|
||||
uri.searchParams.set("args", hashKey(args));
|
||||
return toAction(newFn, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search);
|
||||
};
|
||||
fn.url = url;
|
||||
if (!isServer) {
|
||||
actions.set(url, fn);
|
||||
getOwner() && onCleanup(() => actions.delete(url));
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
|
||||
async function handleResponse(response, error, navigate) {
|
||||
let data;
|
||||
let custom;
|
||||
let keys;
|
||||
let flightKeys;
|
||||
if (response instanceof Response) {
|
||||
if (response.headers.has("X-Revalidate"))
|
||||
keys = response.headers.get("X-Revalidate").split(",");
|
||||
if (response.customBody) {
|
||||
data = custom = await response.customBody();
|
||||
if (response.headers.has("X-Single-Flight")) {
|
||||
data = data._$value;
|
||||
delete custom._$value;
|
||||
flightKeys = Object.keys(custom);
|
||||
}
|
||||
}
|
||||
if (response.headers.has("Location")) {
|
||||
const locationUrl = response.headers.get("Location") || "/";
|
||||
if (locationUrl.startsWith("http")) {
|
||||
window.location.href = locationUrl;
|
||||
}
|
||||
else {
|
||||
navigate(locationUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (error)
|
||||
return { error: response };
|
||||
else
|
||||
data = response;
|
||||
// invalidate
|
||||
cacheKeyOp(keys, entry => (entry[0] = 0));
|
||||
// set cache
|
||||
flightKeys && flightKeys.forEach(k => query.set(k, custom[k]));
|
||||
// trigger revalidation
|
||||
await revalidate(keys, false);
|
||||
return data != null ? { data } : undefined;
|
||||
}
|
||||
32
web/runtime/@solidjs/router/dist/data/createAsync.d.ts
vendored
Normal file
32
web/runtime/@solidjs/router/dist/data/createAsync.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { type ReconcileOptions } from "solid-js/store";
|
||||
/**
|
||||
* As `createAsync` and `createAsyncStore` are wrappers for `createResource`,
|
||||
* this type allows to support `latest` field for these primitives.
|
||||
* It will be removed in the future.
|
||||
*/
|
||||
export type AccessorWithLatest<T> = {
|
||||
(): T;
|
||||
latest: T;
|
||||
};
|
||||
export declare function createAsync<T>(fn: (prev: T) => Promise<T>, options: {
|
||||
name?: string;
|
||||
initialValue: T;
|
||||
deferStream?: boolean;
|
||||
}): AccessorWithLatest<T>;
|
||||
export declare function createAsync<T>(fn: (prev: T | undefined) => Promise<T>, options?: {
|
||||
name?: string;
|
||||
initialValue?: T;
|
||||
deferStream?: boolean;
|
||||
}): AccessorWithLatest<T | undefined>;
|
||||
export declare function createAsyncStore<T>(fn: (prev: T) => Promise<T>, options: {
|
||||
name?: string;
|
||||
initialValue: T;
|
||||
deferStream?: boolean;
|
||||
reconcile?: ReconcileOptions;
|
||||
}): AccessorWithLatest<T>;
|
||||
export declare function createAsyncStore<T>(fn: (prev: T | undefined) => Promise<T>, options?: {
|
||||
name?: string;
|
||||
initialValue?: T;
|
||||
deferStream?: boolean;
|
||||
reconcile?: ReconcileOptions;
|
||||
}): AccessorWithLatest<T | undefined>;
|
||||
96
web/runtime/@solidjs/router/dist/data/createAsync.js
vendored
Normal file
96
web/runtime/@solidjs/router/dist/data/createAsync.js
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
|
||||
*/
|
||||
import { createResource, sharedConfig, untrack, catchError } from "solid-js";
|
||||
import { createStore, reconcile, unwrap } from "solid-js/store";
|
||||
import { isServer } from "solid-js/web";
|
||||
import { setFunctionName } from "../utils.js";
|
||||
export function createAsync(fn, options) {
|
||||
let resource;
|
||||
let prev = () => !resource || resource.state === "unresolved" ? undefined : resource.latest;
|
||||
[resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, options);
|
||||
const resultAccessor = (() => resource());
|
||||
if (options?.name)
|
||||
setFunctionName(resultAccessor, options.name);
|
||||
Object.defineProperty(resultAccessor, "latest", {
|
||||
get() {
|
||||
return resource.latest;
|
||||
}
|
||||
});
|
||||
return resultAccessor;
|
||||
}
|
||||
export function createAsyncStore(fn, options = {}) {
|
||||
let resource;
|
||||
let prev = () => !resource || resource.state === "unresolved"
|
||||
? undefined
|
||||
: unwrap(resource.latest);
|
||||
[resource] = createResource(() => subFetch(fn, catchError(() => untrack(prev), () => undefined)), v => v, {
|
||||
...options,
|
||||
storage: (init) => createDeepSignal(init, options.reconcile)
|
||||
});
|
||||
const resultAccessor = (() => resource());
|
||||
Object.defineProperty(resultAccessor, "latest", {
|
||||
get() {
|
||||
return resource.latest;
|
||||
}
|
||||
});
|
||||
return resultAccessor;
|
||||
}
|
||||
function createDeepSignal(value, options) {
|
||||
const [store, setStore] = createStore({
|
||||
value: structuredClone(value)
|
||||
});
|
||||
return [
|
||||
() => store.value,
|
||||
(v) => {
|
||||
typeof v === "function" && (v = v());
|
||||
setStore("value", reconcile(structuredClone(v), options));
|
||||
return store.value;
|
||||
}
|
||||
];
|
||||
}
|
||||
// mock promise while hydrating to prevent fetching
|
||||
class MockPromise {
|
||||
static all() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static allSettled() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static any() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static race() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static reject() {
|
||||
return new MockPromise();
|
||||
}
|
||||
static resolve() {
|
||||
return new MockPromise();
|
||||
}
|
||||
catch() {
|
||||
return new MockPromise();
|
||||
}
|
||||
then() {
|
||||
return new MockPromise();
|
||||
}
|
||||
finally() {
|
||||
return new MockPromise();
|
||||
}
|
||||
}
|
||||
function subFetch(fn, prev) {
|
||||
if (isServer || !sharedConfig.context)
|
||||
return fn(prev);
|
||||
const ogFetch = fetch;
|
||||
const ogPromise = Promise;
|
||||
try {
|
||||
window.fetch = () => new MockPromise();
|
||||
Promise = MockPromise;
|
||||
return fn(prev);
|
||||
}
|
||||
finally {
|
||||
window.fetch = ogFetch;
|
||||
Promise = ogPromise;
|
||||
}
|
||||
}
|
||||
9
web/runtime/@solidjs/router/dist/data/events.d.ts
vendored
Normal file
9
web/runtime/@solidjs/router/dist/data/events.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { RouterContext } from "../types.js";
|
||||
type NativeEventConfig = {
|
||||
preload?: boolean;
|
||||
explicitLinks?: boolean;
|
||||
actionBase?: string;
|
||||
transformUrl?: (url: string) => string;
|
||||
};
|
||||
export declare function setupNativeEvents({ preload, explicitLinks, actionBase, transformUrl }?: NativeEventConfig): (router: RouterContext) => void;
|
||||
export {};
|
||||
123
web/runtime/@solidjs/router/dist/data/events.js
vendored
Normal file
123
web/runtime/@solidjs/router/dist/data/events.js
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
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);
|
||||
});
|
||||
};
|
||||
}
|
||||
4
web/runtime/@solidjs/router/dist/data/index.d.ts
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { createAsync, createAsyncStore, type AccessorWithLatest } from "./createAsync.js";
|
||||
export { action, useSubmission, useSubmissions, useAction, type Action } from "./action.js";
|
||||
export { query, revalidate, cache, type CachedFunction } from "./query.js";
|
||||
export { redirect, reload, json } from "./response.js";
|
||||
4
web/runtime/@solidjs/router/dist/data/index.js
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/index.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { createAsync, createAsyncStore } from "./createAsync.js";
|
||||
export { action, useSubmission, useSubmissions, useAction } from "./action.js";
|
||||
export { query, revalidate, cache } from "./query.js";
|
||||
export { redirect, reload, json } from "./response.js";
|
||||
23
web/runtime/@solidjs/router/dist/data/query.d.ts
vendored
Normal file
23
web/runtime/@solidjs/router/dist/data/query.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CacheEntry, NarrowResponse } from "../types.js";
|
||||
/**
|
||||
* Revalidates the given cache entry/entries.
|
||||
*/
|
||||
export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
|
||||
export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
|
||||
export type CachedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? ([] extends {
|
||||
[K in keyof A]-?: A[K];
|
||||
} ? (...args: never[]) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R> : (...args: A) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>) & {
|
||||
keyFor: (...args: A) => string;
|
||||
key: string;
|
||||
} : never;
|
||||
export declare function query<T extends (...args: any) => any>(fn: T, name: string): CachedFunction<T>;
|
||||
export declare namespace query {
|
||||
export var get: (key: string) => any;
|
||||
export var set: <T>(key: string, value: T extends Promise<any> ? never : T) => void;
|
||||
var _a: (key: string) => boolean;
|
||||
export var clear: () => void;
|
||||
export { _a as delete };
|
||||
}
|
||||
/** @deprecated use query instead */
|
||||
export declare const cache: typeof query;
|
||||
export declare function hashKey<T extends Array<any>>(args: T): string;
|
||||
232
web/runtime/@solidjs/router/dist/data/query.js
vendored
Normal file
232
web/runtime/@solidjs/router/dist/data/query.js
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
|
||||
import { getRequestEvent, isServer } from "solid-js/web";
|
||||
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
|
||||
const LocationHeader = "Location";
|
||||
const PRELOAD_TIMEOUT = 5000;
|
||||
const CACHE_TIMEOUT = 180000;
|
||||
let cacheMap = new Map();
|
||||
// cleanup forward/back cache
|
||||
if (!isServer) {
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (let [k, v] of cacheMap.entries()) {
|
||||
if (!v[4].count && now - v[0] > CACHE_TIMEOUT) {
|
||||
cacheMap.delete(k);
|
||||
}
|
||||
}
|
||||
}, 300000);
|
||||
}
|
||||
function getCache() {
|
||||
if (!isServer)
|
||||
return cacheMap;
|
||||
const req = getRequestEvent();
|
||||
if (!req)
|
||||
throw new Error("Cannot find cache context");
|
||||
return (req.router || (req.router = {})).cache || (req.router.cache = new Map());
|
||||
}
|
||||
/**
|
||||
* Revalidates the given cache entry/entries.
|
||||
*/
|
||||
export function revalidate(key, force = true) {
|
||||
return startTransition(() => {
|
||||
const now = Date.now();
|
||||
cacheKeyOp(key, entry => {
|
||||
force && (entry[0] = 0); //force cache miss
|
||||
entry[4][1](now); // retrigger live signals
|
||||
});
|
||||
});
|
||||
}
|
||||
export function cacheKeyOp(key, fn) {
|
||||
key && !Array.isArray(key) && (key = [key]);
|
||||
for (let k of cacheMap.keys()) {
|
||||
if (key === undefined || matchKey(k, key))
|
||||
fn(cacheMap.get(k));
|
||||
}
|
||||
}
|
||||
export function query(fn, name) {
|
||||
// prioritize GET for server functions
|
||||
if (fn.GET)
|
||||
fn = fn.GET;
|
||||
const cachedFn = ((...args) => {
|
||||
const cache = getCache();
|
||||
const intent = getIntent();
|
||||
const inPreloadFn = getInPreloadFn();
|
||||
const owner = getOwner();
|
||||
const navigate = owner ? useNavigate() : undefined;
|
||||
const now = Date.now();
|
||||
const key = name + hashKey(args);
|
||||
let cached = cache.get(key);
|
||||
let tracking;
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
if (e) {
|
||||
const dataOnly = (e.router || (e.router = {})).dataOnly;
|
||||
if (dataOnly) {
|
||||
const data = e && (e.router.data || (e.router.data = {}));
|
||||
if (data && key in data)
|
||||
return data[key];
|
||||
if (Array.isArray(dataOnly) && !matchKey(key, dataOnly)) {
|
||||
data[key] = undefined;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getListener() && !isServer) {
|
||||
tracking = true;
|
||||
onCleanup(() => cached[4].count--);
|
||||
}
|
||||
if (cached &&
|
||||
cached[0] &&
|
||||
(isServer ||
|
||||
intent === "native" ||
|
||||
cached[4].count ||
|
||||
Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
|
||||
if (tracking) {
|
||||
cached[4].count++;
|
||||
cached[4][0](); // track
|
||||
}
|
||||
if (cached[3] === "preload" && intent !== "preload") {
|
||||
cached[0] = now;
|
||||
}
|
||||
let res = cached[1];
|
||||
if (intent !== "preload") {
|
||||
res =
|
||||
"then" in cached[1]
|
||||
? cached[1].then(handleResponse(false), handleResponse(true))
|
||||
: handleResponse(false)(cached[1]);
|
||||
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
||||
}
|
||||
inPreloadFn && "then" in res && res.catch(() => { });
|
||||
return res;
|
||||
}
|
||||
let res;
|
||||
if (!isServer && sharedConfig.has && sharedConfig.has(key)) {
|
||||
res = sharedConfig.load(key); // hydrating
|
||||
// @ts-ignore at least until we add a delete method to sharedConfig
|
||||
delete globalThis._$HY.r[key];
|
||||
}
|
||||
else
|
||||
res = fn(...args);
|
||||
if (cached) {
|
||||
cached[0] = now;
|
||||
cached[1] = res;
|
||||
cached[3] = intent;
|
||||
!isServer && intent === "navigate" && startTransition(() => cached[4][1](cached[0])); // update version
|
||||
}
|
||||
else {
|
||||
cache.set(key, (cached = [now, res, , intent, createSignal(now)]));
|
||||
cached[4].count = 0;
|
||||
}
|
||||
if (tracking) {
|
||||
cached[4].count++;
|
||||
cached[4][0](); // track
|
||||
}
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
if (e && e.router.dataOnly)
|
||||
return (e.router.data[key] = res);
|
||||
}
|
||||
if (intent !== "preload") {
|
||||
res =
|
||||
"then" in res
|
||||
? res.then(handleResponse(false), handleResponse(true))
|
||||
: handleResponse(false)(res);
|
||||
}
|
||||
inPreloadFn && "then" in res && res.catch(() => { });
|
||||
// serialize on server
|
||||
if (isServer &&
|
||||
sharedConfig.context &&
|
||||
sharedConfig.context.async &&
|
||||
!sharedConfig.context.noHydrate) {
|
||||
const e = getRequestEvent();
|
||||
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
||||
}
|
||||
return res;
|
||||
function handleResponse(error) {
|
||||
return async (v) => {
|
||||
if (v instanceof Response) {
|
||||
const e = getRequestEvent();
|
||||
if (e) {
|
||||
for (const [key, value] of v.headers) {
|
||||
if (key == "set-cookie")
|
||||
e.response.headers.append("set-cookie", value);
|
||||
else
|
||||
e.response.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
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 (e)
|
||||
e.response.status = 302;
|
||||
return;
|
||||
}
|
||||
if (v.customBody)
|
||||
v = await v.customBody();
|
||||
}
|
||||
if (error)
|
||||
throw v;
|
||||
cached[2] = v;
|
||||
return v;
|
||||
};
|
||||
}
|
||||
});
|
||||
cachedFn.keyFor = (...args) => name + hashKey(args);
|
||||
cachedFn.key = name;
|
||||
return cachedFn;
|
||||
}
|
||||
query.get = (key) => {
|
||||
const cached = getCache().get(key);
|
||||
return cached[2];
|
||||
};
|
||||
query.set = (key, value) => {
|
||||
const cache = getCache();
|
||||
const now = Date.now();
|
||||
let cached = cache.get(key);
|
||||
if (cached) {
|
||||
cached[0] = now;
|
||||
cached[1] = Promise.resolve(value);
|
||||
cached[2] = value;
|
||||
cached[3] = "preload";
|
||||
}
|
||||
else {
|
||||
cache.set(key, (cached = [now, Promise.resolve(value), value, "preload", createSignal(now)]));
|
||||
cached[4].count = 0;
|
||||
}
|
||||
};
|
||||
query.delete = (key) => getCache().delete(key);
|
||||
query.clear = () => getCache().clear();
|
||||
/** @deprecated use query instead */
|
||||
export const cache = query;
|
||||
function matchKey(key, keys) {
|
||||
for (let k of keys) {
|
||||
if (k && key.startsWith(k))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Modified from the amazing Tanstack Query library (MIT)
|
||||
// https://github.com/TanStack/query/blob/main/packages/query-core/src/utils.ts#L168
|
||||
export function hashKey(args) {
|
||||
return JSON.stringify(args, (_, val) => isPlainObject(val)
|
||||
? Object.keys(val)
|
||||
.sort()
|
||||
.reduce((result, key) => {
|
||||
result[key] = val[key];
|
||||
return result;
|
||||
}, {})
|
||||
: val);
|
||||
}
|
||||
function isPlainObject(obj) {
|
||||
let proto;
|
||||
return (obj != null &&
|
||||
typeof obj === "object" &&
|
||||
(!(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype));
|
||||
}
|
||||
4
web/runtime/@solidjs/router/dist/data/response.d.ts
vendored
Normal file
4
web/runtime/@solidjs/router/dist/data/response.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { RouterResponseInit, CustomResponse } from "../types.js";
|
||||
export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
|
||||
export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
|
||||
export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
|
||||
42
web/runtime/@solidjs/router/dist/data/response.js
vendored
Normal file
42
web/runtime/@solidjs/router/dist/data/response.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
export function redirect(url, init = 302) {
|
||||
let responseInit;
|
||||
let revalidate;
|
||||
if (typeof init === "number") {
|
||||
responseInit = { status: init };
|
||||
}
|
||||
else {
|
||||
({ revalidate, ...responseInit } = init);
|
||||
if (typeof responseInit.status === "undefined") {
|
||||
responseInit.status = 302;
|
||||
}
|
||||
}
|
||||
const headers = new Headers(responseInit.headers);
|
||||
headers.set("Location", url);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
const response = new Response(null, {
|
||||
...responseInit,
|
||||
headers: headers
|
||||
});
|
||||
return response;
|
||||
}
|
||||
export function reload(init = {}) {
|
||||
const { revalidate, ...responseInit } = init;
|
||||
const headers = new Headers(responseInit.headers);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
return new Response(null, {
|
||||
...responseInit,
|
||||
headers
|
||||
});
|
||||
}
|
||||
export function json(data, init = {}) {
|
||||
const { revalidate, ...responseInit } = init;
|
||||
const headers = new Headers(responseInit.headers);
|
||||
revalidate !== undefined && headers.set("X-Revalidate", revalidate.toString());
|
||||
headers.set("Content-Type", "application/json");
|
||||
const response = new Response(JSON.stringify(data), {
|
||||
...responseInit,
|
||||
headers
|
||||
});
|
||||
response.customBody = () => data;
|
||||
return response;
|
||||
}
|
||||
7
web/runtime/@solidjs/router/dist/index.d.ts
vendored
Normal file
7
web/runtime/@solidjs/router/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from "./routers/index.js";
|
||||
export * from "./components.js";
|
||||
export * from "./lifecycle.js";
|
||||
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
|
||||
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
||||
export * from "./data/index.js";
|
||||
export type { Location, LocationChange, SearchParams, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, Submission, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
|
||||
1879
web/runtime/@solidjs/router/dist/index.js
vendored
Normal file
1879
web/runtime/@solidjs/router/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
web/runtime/@solidjs/router/dist/index.jsx
vendored
Normal file
6
web/runtime/@solidjs/router/dist/index.jsx
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./routers/index.js";
|
||||
export * from "./components.jsx";
|
||||
export * from "./lifecycle.js";
|
||||
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
|
||||
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
||||
export * from "./data/index.js";
|
||||
5
web/runtime/@solidjs/router/dist/lifecycle.d.ts
vendored
Normal file
5
web/runtime/@solidjs/router/dist/lifecycle.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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;
|
||||
69
web/runtime/@solidjs/router/dist/lifecycle.js
vendored
Normal file
69
web/runtime/@solidjs/router/dist/lifecycle.js
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import { isServer } from "solid-js/web";
|
||||
export function createBeforeLeave() {
|
||||
let listeners = new Set();
|
||||
function subscribe(listener) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
let ignore = false;
|
||||
function confirm(to, options) {
|
||||
if (ignore)
|
||||
return !(ignore = false);
|
||||
const e = {
|
||||
to,
|
||||
options,
|
||||
defaultPrevented: false,
|
||||
preventDefault: () => (e.defaultPrevented = true)
|
||||
};
|
||||
for (const l of listeners)
|
||||
l.listener({
|
||||
...e,
|
||||
from: l.location,
|
||||
retry: (force) => {
|
||||
force && (ignore = true);
|
||||
l.navigate(to, { ...options, resolve: false });
|
||||
}
|
||||
});
|
||||
return !e.defaultPrevented;
|
||||
}
|
||||
return {
|
||||
subscribe,
|
||||
confirm
|
||||
};
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
9
web/runtime/@solidjs/router/dist/routers/HashRouter.d.ts
vendored
Normal file
9
web/runtime/@solidjs/router/dist/routers/HashRouter.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { JSX } from "solid-js";
|
||||
import type { BaseRouterProps } from "./components.js";
|
||||
export declare function hashParser(str: string): string;
|
||||
export type HashRouterProps = BaseRouterProps & {
|
||||
actionBase?: string;
|
||||
explicitLinks?: boolean;
|
||||
preload?: boolean;
|
||||
};
|
||||
export declare function HashRouter(props: HashRouterProps): JSX.Element;
|
||||
41
web/runtime/@solidjs/router/dist/routers/HashRouter.js
vendored
Normal file
41
web/runtime/@solidjs/router/dist/routers/HashRouter.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
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) {
|
||||
const to = str.replace(/^.*?#/, "");
|
||||
// Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
|
||||
// `/foo` will be `/#/foo`. Check if the to starts with a `/` and if not append it as a hash
|
||||
// to the current path so we can handle these in-page anchors correctly.
|
||||
if (!to.startsWith("/")) {
|
||||
const [, path = "/"] = window.location.hash.split("#", 2);
|
||||
return `${path}#${to}`;
|
||||
}
|
||||
return to;
|
||||
}
|
||||
export function HashRouter(props) {
|
||||
const getSource = () => window.location.hash.slice(1);
|
||||
const beforeLeave = createBeforeLeave();
|
||||
return createRouter({
|
||||
get: getSource,
|
||||
set({ value, replace, scroll, state }) {
|
||||
if (replace) {
|
||||
window.history.replaceState(keepDepth(state), "", "#" + value);
|
||||
}
|
||||
else {
|
||||
window.history.pushState(state, "", "#" + value);
|
||||
}
|
||||
const hashIndex = value.indexOf("#");
|
||||
const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
|
||||
scrollToHash(hash, scroll);
|
||||
saveCurrentDepth();
|
||||
},
|
||||
init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
|
||||
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
||||
utils: {
|
||||
go: delta => window.history.go(delta),
|
||||
renderPath: path => `#${path}`,
|
||||
parsePath: hashParser,
|
||||
beforeLeave
|
||||
}
|
||||
})(props);
|
||||
}
|
||||
24
web/runtime/@solidjs/router/dist/routers/MemoryRouter.d.ts
vendored
Normal file
24
web/runtime/@solidjs/router/dist/routers/MemoryRouter.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { LocationChange } from "../types.js";
|
||||
import type { BaseRouterProps } from "./components.js";
|
||||
import type { JSX } from "solid-js";
|
||||
export type MemoryHistory = {
|
||||
get: () => string;
|
||||
set: (change: LocationChange) => void;
|
||||
go: (delta: number) => void;
|
||||
listen: (listener: (value: string) => void) => () => void;
|
||||
};
|
||||
export declare function createMemoryHistory(): {
|
||||
get: () => string;
|
||||
set: ({ value, scroll, replace }: LocationChange) => void;
|
||||
back: () => void;
|
||||
forward: () => void;
|
||||
go: (n: number) => void;
|
||||
listen: (listener: (value: string) => void) => () => void;
|
||||
};
|
||||
export type MemoryRouterProps = BaseRouterProps & {
|
||||
history?: MemoryHistory;
|
||||
actionBase?: string;
|
||||
explicitLinks?: boolean;
|
||||
preload?: boolean;
|
||||
};
|
||||
export declare function MemoryRouter(props: MemoryRouterProps): JSX.Element;
|
||||
57
web/runtime/@solidjs/router/dist/routers/MemoryRouter.js
vendored
Normal file
57
web/runtime/@solidjs/router/dist/routers/MemoryRouter.js
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createRouter, scrollToHash } from "./createRouter.js";
|
||||
import { setupNativeEvents } from "../data/events.js";
|
||||
export function createMemoryHistory() {
|
||||
const entries = ["/"];
|
||||
let index = 0;
|
||||
const listeners = [];
|
||||
const go = (n) => {
|
||||
// https://github.com/remix-run/react-router/blob/682810ca929d0e3c64a76f8d6e465196b7a2ac58/packages/router/history.ts#L245
|
||||
index = Math.max(0, Math.min(index + n, entries.length - 1));
|
||||
const value = entries[index];
|
||||
listeners.forEach(listener => listener(value));
|
||||
};
|
||||
return {
|
||||
get: () => entries[index],
|
||||
set: ({ value, scroll, replace }) => {
|
||||
if (replace) {
|
||||
entries[index] = value;
|
||||
}
|
||||
else {
|
||||
entries.splice(index + 1, entries.length - index, value);
|
||||
index++;
|
||||
}
|
||||
listeners.forEach(listener => listener(value));
|
||||
setTimeout(() => {
|
||||
if (scroll) {
|
||||
scrollToHash(value.split("#")[1] || "", true);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
back: () => {
|
||||
go(-1);
|
||||
},
|
||||
forward: () => {
|
||||
go(1);
|
||||
},
|
||||
go,
|
||||
listen: (listener) => {
|
||||
listeners.push(listener);
|
||||
return () => {
|
||||
const index = listeners.indexOf(listener);
|
||||
listeners.splice(index, 1);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
export function MemoryRouter(props) {
|
||||
const memoryHistory = props.history || createMemoryHistory();
|
||||
return createRouter({
|
||||
get: memoryHistory.get,
|
||||
set: memoryHistory.set,
|
||||
init: memoryHistory.listen,
|
||||
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
|
||||
utils: {
|
||||
go: memoryHistory.go
|
||||
}
|
||||
})(props);
|
||||
}
|
||||
9
web/runtime/@solidjs/router/dist/routers/Router.d.ts
vendored
Normal file
9
web/runtime/@solidjs/router/dist/routers/Router.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { BaseRouterProps } from "./components.js";
|
||||
import type { JSX } from "solid-js";
|
||||
export type RouterProps = BaseRouterProps & {
|
||||
url?: string;
|
||||
actionBase?: string;
|
||||
explicitLinks?: boolean;
|
||||
preload?: boolean;
|
||||
};
|
||||
export declare function Router(props: RouterProps): JSX.Element;
|
||||
45
web/runtime/@solidjs/router/dist/routers/Router.js
vendored
Normal file
45
web/runtime/@solidjs/router/dist/routers/Router.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { isServer } from "solid-js/web";
|
||||
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 = () => {
|
||||
const url = window.location.pathname.replace(/^\/+/, "/") + window.location.search;
|
||||
const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
|
||||
return {
|
||||
value: url + window.location.hash,
|
||||
state
|
||||
};
|
||||
};
|
||||
const beforeLeave = createBeforeLeave();
|
||||
return createRouter({
|
||||
get: getSource,
|
||||
set({ value, replace, scroll, state }) {
|
||||
if (replace) {
|
||||
window.history.replaceState(keepDepth(state), "", value);
|
||||
}
|
||||
else {
|
||||
window.history.pushState(state, "", value);
|
||||
}
|
||||
scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll);
|
||||
saveCurrentDepth();
|
||||
},
|
||||
init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
|
||||
if (delta) {
|
||||
return !beforeLeave.confirm(delta);
|
||||
}
|
||||
else {
|
||||
const s = getSource();
|
||||
return !beforeLeave.confirm(s.value, { state: s.state });
|
||||
}
|
||||
})),
|
||||
create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase, transformUrl: props.transformUrl }),
|
||||
utils: {
|
||||
go: delta => window.history.go(delta),
|
||||
beforeLeave
|
||||
}
|
||||
})(props);
|
||||
}
|
||||
6
web/runtime/@solidjs/router/dist/routers/StaticRouter.d.ts
vendored
Normal file
6
web/runtime/@solidjs/router/dist/routers/StaticRouter.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type BaseRouterProps } from "./components.js";
|
||||
import type { JSX } from "solid-js";
|
||||
export type StaticRouterProps = BaseRouterProps & {
|
||||
url?: string;
|
||||
};
|
||||
export declare function StaticRouter(props: StaticRouterProps): JSX.Element;
|
||||
15
web/runtime/@solidjs/router/dist/routers/StaticRouter.js
vendored
Normal file
15
web/runtime/@solidjs/router/dist/routers/StaticRouter.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { createRouterComponent } from "./components.js";
|
||||
function getPath(url) {
|
||||
const u = new URL(url);
|
||||
return u.pathname + u.search;
|
||||
}
|
||||
export function StaticRouter(props) {
|
||||
let e;
|
||||
const obj = {
|
||||
value: props.url || ((e = getRequestEvent()) && getPath(e.request.url)) || "",
|
||||
};
|
||||
return createRouterComponent({
|
||||
signal: [() => obj, next => Object.assign(obj, next)]
|
||||
})(props);
|
||||
}
|
||||
27
web/runtime/@solidjs/router/dist/routers/components.d.ts
vendored
Normal file
27
web/runtime/@solidjs/router/dist/routers/components.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Component, JSX } from "solid-js";
|
||||
import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionProps } from "../types.js";
|
||||
export type BaseRouterProps = {
|
||||
base?: string;
|
||||
/**
|
||||
* A component that wraps the content of every route.
|
||||
*/
|
||||
root?: Component<RouteSectionProps>;
|
||||
rootPreload?: RoutePreloadFunc;
|
||||
singleFlight?: boolean;
|
||||
children?: JSX.Element | RouteDefinition | RouteDefinition[];
|
||||
transformUrl?: (url: string) => string;
|
||||
/** @deprecated use rootPreload */
|
||||
rootLoad?: RoutePreloadFunc;
|
||||
};
|
||||
export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
|
||||
export type RouteProps<S extends string, T = unknown> = {
|
||||
path?: S | S[];
|
||||
children?: JSX.Element;
|
||||
preload?: RoutePreloadFunc<T>;
|
||||
matchFilters?: MatchFilters<S>;
|
||||
component?: Component<RouteSectionProps<T>>;
|
||||
info?: Record<string, any>;
|
||||
/** @deprecated use preload */
|
||||
load?: RoutePreloadFunc<T>;
|
||||
};
|
||||
export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
|
||||
118
web/runtime/@solidjs/router/dist/routers/components.jsx
vendored
Normal file
118
web/runtime/@solidjs/router/dist/routers/components.jsx
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*@refresh skip*/
|
||||
import { children, createMemo, createRoot, getOwner, mergeProps, on, Show, untrack } from "solid-js";
|
||||
import { getRequestEvent, isServer } from "solid-js/web";
|
||||
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
|
||||
export const createRouterComponent = (router) => (props) => {
|
||||
const { base } = props;
|
||||
const routeDefs = children(() => props.children);
|
||||
const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
|
||||
let context;
|
||||
const routerState = createRouterContext(router, branches, () => context, {
|
||||
base,
|
||||
singleFlight: props.singleFlight,
|
||||
transformUrl: props.transformUrl,
|
||||
});
|
||||
router.create && router.create(routerState);
|
||||
return (<RouterContextObj.Provider value={routerState}>
|
||||
<Root routerState={routerState} root={props.root} preload={props.rootPreload || props.rootLoad}>
|
||||
{(context = getOwner()) && null}
|
||||
<Routes routerState={routerState} branches={branches()}/>
|
||||
</Root>
|
||||
</RouterContextObj.Provider>);
|
||||
};
|
||||
function Root(props) {
|
||||
const location = props.routerState.location;
|
||||
const params = props.routerState.params;
|
||||
const data = createMemo(() => props.preload &&
|
||||
untrack(() => {
|
||||
setInPreloadFn(true);
|
||||
props.preload({ params, location, intent: getIntent() || "initial" });
|
||||
setInPreloadFn(false);
|
||||
}));
|
||||
return (<Show when={props.root} keyed fallback={props.children}>
|
||||
{Root => (<Root params={params} location={location} data={data()}>
|
||||
{props.children}
|
||||
</Root>)}
|
||||
</Show>);
|
||||
}
|
||||
function Routes(props) {
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
if (e && e.router && e.router.dataOnly) {
|
||||
dataOnly(e, props.routerState, props.branches);
|
||||
return;
|
||||
}
|
||||
e &&
|
||||
((e.router || (e.router = {})).matches ||
|
||||
(e.router.matches = props.routerState.matches().map(({ route, path, params }) => ({
|
||||
path: route.originalPath,
|
||||
pattern: route.pattern,
|
||||
match: path,
|
||||
params,
|
||||
info: route.info
|
||||
}))));
|
||||
}
|
||||
const disposers = [];
|
||||
let root;
|
||||
const routeStates = createMemo(on(props.routerState.matches, (nextMatches, prevMatches, prev) => {
|
||||
let equal = prevMatches && nextMatches.length === prevMatches.length;
|
||||
const next = [];
|
||||
for (let i = 0, len = nextMatches.length; i < len; i++) {
|
||||
const prevMatch = prevMatches && prevMatches[i];
|
||||
const nextMatch = nextMatches[i];
|
||||
if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
|
||||
next[i] = prev[i];
|
||||
}
|
||||
else {
|
||||
equal = false;
|
||||
if (disposers[i]) {
|
||||
disposers[i]();
|
||||
}
|
||||
createRoot(dispose => {
|
||||
disposers[i] = dispose;
|
||||
next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => {
|
||||
const routeMatches = props.routerState.matches();
|
||||
return routeMatches[i] ?? routeMatches[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
disposers.splice(nextMatches.length).forEach(dispose => dispose());
|
||||
if (prev && equal) {
|
||||
return prev;
|
||||
}
|
||||
root = next[0];
|
||||
return next;
|
||||
}));
|
||||
return createOutlet(() => routeStates() && root)();
|
||||
}
|
||||
const createOutlet = (child) => {
|
||||
return () => (<Show when={child()} keyed>
|
||||
{child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
|
||||
</Show>);
|
||||
};
|
||||
export const Route = (props) => {
|
||||
const childRoutes = children(() => props.children);
|
||||
return mergeProps(props, {
|
||||
get children() {
|
||||
return childRoutes();
|
||||
}
|
||||
});
|
||||
};
|
||||
// for data only mode with single flight mutations
|
||||
function dataOnly(event, routerState, branches) {
|
||||
const url = new URL(event.request.url);
|
||||
const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
|
||||
const matches = getRouteMatches(branches, url.pathname);
|
||||
for (let match = 0; match < matches.length; match++) {
|
||||
if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
|
||||
event.router.dataOnly = true;
|
||||
const { route, params } = matches[match];
|
||||
route.preload &&
|
||||
route.preload({
|
||||
params,
|
||||
location: routerState.location,
|
||||
intent: "preload"
|
||||
});
|
||||
}
|
||||
}
|
||||
10
web/runtime/@solidjs/router/dist/routers/createRouter.d.ts
vendored
Normal file
10
web/runtime/@solidjs/router/dist/routers/createRouter.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { LocationChange, RouterContext, RouterUtils } from "../types.js";
|
||||
export declare function createRouter(config: {
|
||||
get: () => string | LocationChange;
|
||||
set: (next: LocationChange) => void;
|
||||
init?: (notify: (value?: string | LocationChange) => void) => () => void;
|
||||
create?: (router: RouterContext) => void;
|
||||
utils?: Partial<RouterUtils>;
|
||||
}): (props: import("./components.js").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;
|
||||
41
web/runtime/@solidjs/router/dist/routers/createRouter.js
vendored
Normal file
41
web/runtime/@solidjs/router/dist/routers/createRouter.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createSignal, onCleanup, sharedConfig } from "solid-js";
|
||||
import { createRouterComponent } from "./components.js";
|
||||
function intercept([value, setValue], get, set) {
|
||||
return [get ? () => get(value()) : value, set ? (v) => setValue(set(v)) : setValue];
|
||||
}
|
||||
export function createRouter(config) {
|
||||
let ignore = false;
|
||||
const wrap = (value) => (typeof value === "string" ? { value } : value);
|
||||
const signal = intercept(createSignal(wrap(config.get()), {
|
||||
equals: (a, b) => a.value === b.value && a.state === b.state
|
||||
}), undefined, next => {
|
||||
!ignore && config.set(next);
|
||||
if (sharedConfig.registry && !sharedConfig.done)
|
||||
sharedConfig.done = true;
|
||||
return next;
|
||||
});
|
||||
config.init &&
|
||||
onCleanup(config.init((value = config.get()) => {
|
||||
ignore = true;
|
||||
signal[1](wrap(value));
|
||||
ignore = false;
|
||||
}));
|
||||
return createRouterComponent({
|
||||
signal,
|
||||
create: config.create,
|
||||
utils: config.utils
|
||||
});
|
||||
}
|
||||
export function bindEvent(target, type, handler) {
|
||||
target.addEventListener(type, handler);
|
||||
return () => target.removeEventListener(type, handler);
|
||||
}
|
||||
export function scrollToHash(hash, fallbackTop) {
|
||||
const el = hash && document.getElementById(hash);
|
||||
if (el) {
|
||||
el.scrollIntoView();
|
||||
}
|
||||
else if (fallbackTop) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
11
web/runtime/@solidjs/router/dist/routers/index.d.ts
vendored
Normal file
11
web/runtime/@solidjs/router/dist/routers/index.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export { Route } from "./components.js";
|
||||
export type { BaseRouterProps, RouteProps } from "./components.js";
|
||||
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";
|
||||
6
web/runtime/@solidjs/router/dist/routers/index.js
vendored
Normal file
6
web/runtime/@solidjs/router/dist/routers/index.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export { Route } from "./components.js";
|
||||
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";
|
||||
175
web/runtime/@solidjs/router/dist/routing.d.ts
vendored
Normal file
175
web/runtime/@solidjs/router/dist/routing.d.ts
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
import { JSX, Accessor } from "solid-js";
|
||||
import type { BeforeLeaveEventArgs, Branch, Intent, Location, MatchFilters, NavigateOptions, Navigator, Params, RouteDescription, RouteContext, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SearchParams, SetSearchParams } from "./types.js";
|
||||
/** Consider this API opaque and internal. It is likely to change in the future. */
|
||||
export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
|
||||
export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
|
||||
export declare const useRouter: () => RouterContext;
|
||||
export declare const useRoute: () => RouteContext;
|
||||
export declare const useResolvedPath: (path: () => string) => Accessor<string | undefined>;
|
||||
export declare const useHref: <T extends string | undefined>(to: () => T) => () => string | T;
|
||||
/**
|
||||
* Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
|
||||
*
|
||||
* - resolve (*boolean*, default `true`): resolve the path against the current route
|
||||
* - replace (*boolean*, default `false`): replace the history entry
|
||||
* - scroll (*boolean*, default `true`): scroll to top after navigation
|
||||
* - state (*any*, default `undefined`): pass custom state to `location.state`
|
||||
*
|
||||
* **Note**: The state is serialized using the structured clone algorithm which does not support all object types.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const navigate = useNavigate();
|
||||
*
|
||||
* if (unauthorized) {
|
||||
* navigate("/login", { replace: true });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare const useNavigate: () => Navigator;
|
||||
/**
|
||||
* Retrieves reactive `location` object useful for getting things like `pathname`.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const location = useLocation();
|
||||
*
|
||||
* const pathname = createMemo(() => parsePath(location.pathname));
|
||||
* ```
|
||||
*/
|
||||
export declare const useLocation: <S = unknown>() => Location<S>;
|
||||
/**
|
||||
* Retrieves signal that indicates whether the route is currently in a *Transition*.
|
||||
* Useful for showing stale/pending state when the route resolution is *Suspended* during concurrent rendering.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const isRouting = useIsRouting();
|
||||
*
|
||||
* return (
|
||||
* <div classList={{ "grey-out": isRouting() }}>
|
||||
* <MyAwesomeContent />
|
||||
* </div>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const useIsRouting: () => () => boolean;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const preload = usePreloadRoute();
|
||||
*
|
||||
* preload(`/users/settings`, { preloadData: true });
|
||||
* ```
|
||||
*/
|
||||
export declare const usePreloadRoute: () => (url: string | URL, options?: {
|
||||
preloadData?: boolean;
|
||||
}) => void;
|
||||
/**
|
||||
* `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
|
||||
* Useful for determining if a given path matches the current route.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const match = useMatch(() => props.href);
|
||||
*
|
||||
* return <div classList={{ active: Boolean(match()) }} />;
|
||||
* ```
|
||||
*/
|
||||
export declare const useMatch: <S extends string>(path: () => S, matchFilters?: MatchFilters<S>) => Accessor<import("./types.js").PathMatch | undefined>;
|
||||
/**
|
||||
* `useCurrentMatches` returns all the matches for the current matched route.
|
||||
* Useful for getting all the route information.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const matches = useCurrentMatches();
|
||||
*
|
||||
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
||||
* ```
|
||||
*/
|
||||
export declare const useCurrentMatches: () => () => RouteMatch[];
|
||||
/**
|
||||
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const params = useParams();
|
||||
*
|
||||
* // fetch user based on the id path parameter
|
||||
* const [user] = createResource(() => params.id, fetchUser);
|
||||
* ```
|
||||
*/
|
||||
export declare const useParams: <T extends Params>() => T;
|
||||
/**
|
||||
* Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them.
|
||||
* The object is a proxy so you must access properties to subscribe to reactive updates.
|
||||
* **Note** that values will be strings and property names will retain their casing.
|
||||
*
|
||||
* The setter method accepts an object whose entries will be merged into the current query string.
|
||||
* Values `''`, `undefined` and `null` will remove the key from the resulting query string.
|
||||
* Updates will behave just like a navigation and the setter accepts the same optional second parameter as `navigate` and auto-scrolling is disabled by default.
|
||||
*
|
||||
* @examples
|
||||
* ```js
|
||||
* const [searchParams, setSearchParams] = useSearchParams();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <span>Page: {searchParams.page}</span>
|
||||
* <button
|
||||
* onClick={() =>
|
||||
* setSearchParams({ page: (parseInt(searchParams.page) || 0) + 1 })
|
||||
* }
|
||||
* >
|
||||
* Next Page
|
||||
* </button>
|
||||
* </div>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export declare const useSearchParams: <T extends SearchParams>() => [Partial<T>, (params: SetSearchParams, options?: Partial<NavigateOptions>) => void];
|
||||
/**
|
||||
* useBeforeLeave takes a function that will be called prior to leaving a route.
|
||||
* The function will be called with:
|
||||
*
|
||||
* - from (*Location*): current location (before change).
|
||||
* - to (*string | number*): path passed to `navigate`.
|
||||
* - options (*NavigateOptions*): options passed to navigate.
|
||||
* - preventDefault (*function*): call to block the route change.
|
||||
* - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
|
||||
* - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
||||
* if (form.isDirty && !e.defaultPrevented) {
|
||||
* // preventDefault to block immediately and prompt user async
|
||||
* e.preventDefault();
|
||||
* setTimeout(() => {
|
||||
* if (window.confirm("Discard unsaved changes - are you sure?")) {
|
||||
* // user wants to proceed anyway so retry with force=true
|
||||
* e.retry(true);
|
||||
* }
|
||||
* }, 100);
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
|
||||
export declare function createRoutes(routeDef: RouteDefinition, base?: string): RouteDescription[];
|
||||
export declare function createBranch(routes: RouteDescription[], index?: number): Branch;
|
||||
export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, stack?: RouteDescription[], branches?: Branch[]): Branch[];
|
||||
export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
|
||||
export declare function getIntent(): Intent | undefined;
|
||||
export declare function getInPreloadFn(): boolean;
|
||||
export declare function setInPreloadFn(value: boolean): void;
|
||||
export declare function createRouterContext(integration: RouterIntegration, branches: () => Branch[], getContext?: () => any, options?: {
|
||||
base?: string;
|
||||
singleFlight?: boolean;
|
||||
transformUrl?: (url: string) => string;
|
||||
}): RouterContext;
|
||||
export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch): RouteContext;
|
||||
560
web/runtime/@solidjs/router/dist/routing.js
vendored
Normal file
560
web/runtime/@solidjs/router/dist/routing.js
vendored
Normal file
@@ -0,0 +1,560 @@
|
||||
import { runWithOwner, batch } from "solid-js";
|
||||
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.js";
|
||||
import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js";
|
||||
const MAX_REDIRECTS = 100;
|
||||
/** Consider this API opaque and internal. It is likely to change in the future. */
|
||||
export const RouterContextObj = createContext();
|
||||
export const RouteContextObj = createContext();
|
||||
export const useRouter = () => invariant(useContext(RouterContextObj), "<A> and 'use' router primitives can be only used inside a Route.");
|
||||
let TempRoute;
|
||||
export const useRoute = () => TempRoute || useContext(RouteContextObj) || useRouter().base;
|
||||
export const useResolvedPath = (path) => {
|
||||
const route = useRoute();
|
||||
return createMemo(() => route.resolvePath(path()));
|
||||
};
|
||||
export const useHref = (to) => {
|
||||
const router = useRouter();
|
||||
return createMemo(() => {
|
||||
const to_ = to();
|
||||
return to_ !== undefined ? router.renderPath(to_) : to_;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
|
||||
*
|
||||
* - resolve (*boolean*, default `true`): resolve the path against the current route
|
||||
* - replace (*boolean*, default `false`): replace the history entry
|
||||
* - scroll (*boolean*, default `true`): scroll to top after navigation
|
||||
* - state (*any*, default `undefined`): pass custom state to `location.state`
|
||||
*
|
||||
* **Note**: The state is serialized using the structured clone algorithm which does not support all object types.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const navigate = useNavigate();
|
||||
*
|
||||
* if (unauthorized) {
|
||||
* navigate("/login", { replace: true });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const useNavigate = () => useRouter().navigatorFactory();
|
||||
/**
|
||||
* Retrieves reactive `location` object useful for getting things like `pathname`.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const location = useLocation();
|
||||
*
|
||||
* const pathname = createMemo(() => parsePath(location.pathname));
|
||||
* ```
|
||||
*/
|
||||
export const useLocation = () => useRouter().location;
|
||||
/**
|
||||
* Retrieves signal that indicates whether the route is currently in a *Transition*.
|
||||
* Useful for showing stale/pending state when the route resolution is *Suspended* during concurrent rendering.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const isRouting = useIsRouting();
|
||||
*
|
||||
* return (
|
||||
* <div classList={{ "grey-out": isRouting() }}>
|
||||
* <MyAwesomeContent />
|
||||
* </div>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export const useIsRouting = () => useRouter().isRouting;
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const preload = usePreloadRoute();
|
||||
*
|
||||
* preload(`/users/settings`, { preloadData: true });
|
||||
* ```
|
||||
*/
|
||||
export const usePreloadRoute = () => {
|
||||
const pre = useRouter().preloadRoute;
|
||||
return (url, options = {}) => pre(url instanceof URL ? url : new URL(url, mockBase), options.preloadData);
|
||||
};
|
||||
/**
|
||||
* `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
|
||||
* Useful for determining if a given path matches the current route.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const match = useMatch(() => props.href);
|
||||
*
|
||||
* return <div classList={{ active: Boolean(match()) }} />;
|
||||
* ```
|
||||
*/
|
||||
export const useMatch = (path, matchFilters) => {
|
||||
const location = useLocation();
|
||||
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path, undefined, matchFilters)));
|
||||
return createMemo(() => {
|
||||
for (const matcher of matchers()) {
|
||||
const match = matcher(location.pathname);
|
||||
if (match)
|
||||
return match;
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* `useCurrentMatches` returns all the matches for the current matched route.
|
||||
* Useful for getting all the route information.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const matches = useCurrentMatches();
|
||||
*
|
||||
* const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb))
|
||||
* ```
|
||||
*/
|
||||
export const useCurrentMatches = () => useRouter().matches;
|
||||
/**
|
||||
* Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const params = useParams();
|
||||
*
|
||||
* // fetch user based on the id path parameter
|
||||
* const [user] = createResource(() => params.id, fetchUser);
|
||||
* ```
|
||||
*/
|
||||
export const useParams = () => useRouter().params;
|
||||
/**
|
||||
* Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them.
|
||||
* The object is a proxy so you must access properties to subscribe to reactive updates.
|
||||
* **Note** that values will be strings and property names will retain their casing.
|
||||
*
|
||||
* The setter method accepts an object whose entries will be merged into the current query string.
|
||||
* Values `''`, `undefined` and `null` will remove the key from the resulting query string.
|
||||
* Updates will behave just like a navigation and the setter accepts the same optional second parameter as `navigate` and auto-scrolling is disabled by default.
|
||||
*
|
||||
* @examples
|
||||
* ```js
|
||||
* const [searchParams, setSearchParams] = useSearchParams();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <span>Page: {searchParams.page}</span>
|
||||
* <button
|
||||
* onClick={() =>
|
||||
* setSearchParams({ page: (parseInt(searchParams.page) || 0) + 1 })
|
||||
* }
|
||||
* >
|
||||
* Next Page
|
||||
* </button>
|
||||
* </div>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export const useSearchParams = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const setSearchParams = (params, options) => {
|
||||
const searchString = untrack(() => mergeSearchString(location.search, params) + location.hash);
|
||||
navigate(searchString, {
|
||||
scroll: false,
|
||||
resolve: false,
|
||||
...options
|
||||
});
|
||||
};
|
||||
return [location.query, setSearchParams];
|
||||
};
|
||||
/**
|
||||
* useBeforeLeave takes a function that will be called prior to leaving a route.
|
||||
* The function will be called with:
|
||||
*
|
||||
* - from (*Location*): current location (before change).
|
||||
* - to (*string | number*): path passed to `navigate`.
|
||||
* - options (*NavigateOptions*): options passed to navigate.
|
||||
* - preventDefault (*function*): call to block the route change.
|
||||
* - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
|
||||
* - retry (*function*, force?: boolean ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* useBeforeLeave((e: BeforeLeaveEventArgs) => {
|
||||
* if (form.isDirty && !e.defaultPrevented) {
|
||||
* // preventDefault to block immediately and prompt user async
|
||||
* e.preventDefault();
|
||||
* setTimeout(() => {
|
||||
* if (window.confirm("Discard unsaved changes - are you sure?")) {
|
||||
* // user wants to proceed anyway so retry with force=true
|
||||
* e.retry(true);
|
||||
* }
|
||||
* }, 100);
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const useBeforeLeave = (listener) => {
|
||||
const s = useRouter().beforeLeave.subscribe({
|
||||
listener,
|
||||
location: useLocation(),
|
||||
navigate: useNavigate()
|
||||
});
|
||||
onCleanup(s);
|
||||
};
|
||||
export function createRoutes(routeDef, base = "") {
|
||||
const { component, preload, load, children, info } = routeDef;
|
||||
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
||||
const shared = {
|
||||
key: routeDef,
|
||||
component,
|
||||
preload: preload || load,
|
||||
info
|
||||
};
|
||||
return asArray(routeDef.path).reduce((acc, originalPath) => {
|
||||
for (const expandedPath of expandOptionals(originalPath)) {
|
||||
const path = joinPaths(base, expandedPath);
|
||||
let pattern = isLeaf ? path : path.split("/*", 1)[0];
|
||||
pattern = pattern
|
||||
.split("/")
|
||||
.map((s) => {
|
||||
return s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s);
|
||||
})
|
||||
.join("/");
|
||||
acc.push({
|
||||
...shared,
|
||||
originalPath,
|
||||
pattern,
|
||||
matcher: createMatcher(pattern, !isLeaf, routeDef.matchFilters)
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
export function createBranch(routes, index = 0) {
|
||||
return {
|
||||
routes,
|
||||
score: scoreRoute(routes[routes.length - 1]) * 10000 - index,
|
||||
matcher(location) {
|
||||
const matches = [];
|
||||
for (let i = routes.length - 1; i >= 0; i--) {
|
||||
const route = routes[i];
|
||||
const match = route.matcher(location);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
matches.unshift({
|
||||
...match,
|
||||
route
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
};
|
||||
}
|
||||
function asArray(value) {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
export function createBranches(routeDef, base = "", stack = [], branches = []) {
|
||||
const routeDefs = asArray(routeDef);
|
||||
for (let i = 0, len = routeDefs.length; i < len; i++) {
|
||||
const def = routeDefs[i];
|
||||
if (def && typeof def === "object") {
|
||||
if (!def.hasOwnProperty("path"))
|
||||
def.path = "";
|
||||
const routes = createRoutes(def, base);
|
||||
for (const route of routes) {
|
||||
stack.push(route);
|
||||
const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
|
||||
if (def.children && !isEmptyArray) {
|
||||
createBranches(def.children, route.pattern, stack, branches);
|
||||
}
|
||||
else {
|
||||
const branch = createBranch([...stack], branches.length);
|
||||
branches.push(branch);
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stack will be empty on final return
|
||||
return stack.length ? branches : branches.sort((a, b) => b.score - a.score);
|
||||
}
|
||||
export function getRouteMatches(branches, location) {
|
||||
for (let i = 0, len = branches.length; i < len; i++) {
|
||||
const match = branches[i].matcher(location);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
function createLocation(path, state, queryWrapper) {
|
||||
const origin = new URL(mockBase);
|
||||
const url = createMemo(prev => {
|
||||
const path_ = path();
|
||||
try {
|
||||
return new URL(path_, origin);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Invalid path ${path_}`);
|
||||
return prev;
|
||||
}
|
||||
}, origin, {
|
||||
equals: (a, b) => a.href === b.href
|
||||
});
|
||||
const pathname = createMemo(() => url().pathname);
|
||||
const search = createMemo(() => url().search, true);
|
||||
const hash = createMemo(() => url().hash);
|
||||
const key = () => "";
|
||||
const queryFn = on(search, () => extractSearchParams(url()));
|
||||
return {
|
||||
get pathname() {
|
||||
return pathname();
|
||||
},
|
||||
get search() {
|
||||
return search();
|
||||
},
|
||||
get hash() {
|
||||
return hash();
|
||||
},
|
||||
get state() {
|
||||
return state();
|
||||
},
|
||||
get key() {
|
||||
return key();
|
||||
},
|
||||
query: queryWrapper ? queryWrapper(queryFn) : createMemoObject(queryFn)
|
||||
};
|
||||
}
|
||||
let intent;
|
||||
export function getIntent() {
|
||||
return intent;
|
||||
}
|
||||
let inPreloadFn = false;
|
||||
export function getInPreloadFn() {
|
||||
return inPreloadFn;
|
||||
}
|
||||
export function setInPreloadFn(value) {
|
||||
inPreloadFn = value;
|
||||
}
|
||||
export function createRouterContext(integration, branches, getContext, options = {}) {
|
||||
const { signal: [source, setSource], utils = {} } = integration;
|
||||
const parsePath = utils.parsePath || (p => p);
|
||||
const renderPath = utils.renderPath || (p => p);
|
||||
const beforeLeave = utils.beforeLeave || createBeforeLeave();
|
||||
const basePath = resolvePath("", options.base || "");
|
||||
if (basePath === undefined) {
|
||||
throw new Error(`${basePath} is not a valid base path`);
|
||||
}
|
||||
else if (basePath && !source().value) {
|
||||
setSource({ value: basePath, replace: true, scroll: false });
|
||||
}
|
||||
const [isRouting, setIsRouting] = createSignal(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](subs => subs.filter(s => s.pending));
|
||||
}).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;
|
||||
});
|
||||
});
|
||||
};
|
||||
const [reference, setReference] = createSignal(source().value);
|
||||
const [state, setState] = createSignal(source().state);
|
||||
const location = createLocation(reference, state, utils.queryWrapper);
|
||||
const referrers = [];
|
||||
const submissions = createSignal(isServer ? initFromFlash() : []);
|
||||
const matches = createMemo(() => {
|
||||
if (typeof options.transformUrl === "function") {
|
||||
return getRouteMatches(branches(), options.transformUrl(location.pathname));
|
||||
}
|
||||
return getRouteMatches(branches(), location.pathname);
|
||||
});
|
||||
const buildParams = () => {
|
||||
const m = matches();
|
||||
const params = {};
|
||||
for (let i = 0; i < m.length; i++) {
|
||||
Object.assign(params, m[i].params);
|
||||
}
|
||||
return params;
|
||||
};
|
||||
const params = utils.paramsWrapper
|
||||
? utils.paramsWrapper(buildParams, branches)
|
||||
: createMemoObject(buildParams);
|
||||
const baseRoute = {
|
||||
pattern: basePath,
|
||||
path: () => basePath,
|
||||
outlet: () => null,
|
||||
resolvePath(to) {
|
||||
return resolvePath(basePath, to);
|
||||
}
|
||||
};
|
||||
// Create a native transition, when source updates
|
||||
createRenderEffect(on(source, source => transition("native", source), { defer: true }));
|
||||
return {
|
||||
base: baseRoute,
|
||||
location,
|
||||
params,
|
||||
isRouting,
|
||||
renderPath,
|
||||
parsePath,
|
||||
navigatorFactory,
|
||||
matches,
|
||||
beforeLeave,
|
||||
preloadRoute,
|
||||
singleFlight: options.singleFlight === undefined ? true : options.singleFlight,
|
||||
submissions
|
||||
};
|
||||
function navigateFromRoute(route, to, options) {
|
||||
// Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
|
||||
untrack(() => {
|
||||
if (typeof to === "number") {
|
||||
if (!to) {
|
||||
// A delta of 0 means stay at the current location, so it is ignored
|
||||
}
|
||||
else if (utils.go) {
|
||||
utils.go(to);
|
||||
}
|
||||
else {
|
||||
console.warn("Router integration does not support relative routing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const queryOnly = !to || to[0] === "?";
|
||||
const { replace, resolve, scroll, state: nextState } = {
|
||||
replace: false,
|
||||
resolve: !queryOnly,
|
||||
scroll: true,
|
||||
...options
|
||||
};
|
||||
const resolvedTo = resolve
|
||||
? route.resolvePath(to)
|
||||
: resolvePath((queryOnly && location.pathname) || "", to);
|
||||
if (resolvedTo === undefined) {
|
||||
throw new Error(`Path '${to}' is not a routable path`);
|
||||
}
|
||||
else if (referrers.length >= MAX_REDIRECTS) {
|
||||
throw new Error("Too many redirects");
|
||||
}
|
||||
const current = reference();
|
||||
if (resolvedTo !== current || nextState !== state()) {
|
||||
if (isServer) {
|
||||
const e = getRequestEvent();
|
||||
e && (e.response = { status: 302, headers: new Headers({ Location: resolvedTo }) });
|
||||
setSource({ value: resolvedTo, replace, scroll, state: nextState });
|
||||
}
|
||||
else if (beforeLeave.confirm(resolvedTo, options)) {
|
||||
referrers.push({ value: current, replace, scroll, state: state() });
|
||||
transition("navigate", {
|
||||
value: resolvedTo,
|
||||
state: nextState
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function navigatorFactory(route) {
|
||||
// Workaround for vite issue (https://github.com/vitejs/vite/issues/3803)
|
||||
route = route || useContext(RouteContextObj) || baseRoute;
|
||||
return (to, options) => navigateFromRoute(route, to, options);
|
||||
}
|
||||
function navigateEnd(next) {
|
||||
const first = referrers[0];
|
||||
if (first) {
|
||||
setSource({
|
||||
...next,
|
||||
replace: first.replace,
|
||||
scroll: first.scroll
|
||||
});
|
||||
referrers.length = 0;
|
||||
}
|
||||
}
|
||||
function preloadRoute(url, preloadData) {
|
||||
const matches = getRouteMatches(branches(), url.pathname);
|
||||
const prevIntent = intent;
|
||||
intent = "preload";
|
||||
for (let match in matches) {
|
||||
const { route, params } = matches[match];
|
||||
route.component &&
|
||||
route.component.preload &&
|
||||
route.component.preload();
|
||||
const { preload } = route;
|
||||
inPreloadFn = true;
|
||||
preloadData &&
|
||||
preload &&
|
||||
runWithOwner(getContext(), () => preload({
|
||||
params,
|
||||
location: {
|
||||
pathname: url.pathname,
|
||||
search: url.search,
|
||||
hash: url.hash,
|
||||
query: extractSearchParams(url),
|
||||
state: null,
|
||||
key: ""
|
||||
},
|
||||
intent: "preload"
|
||||
}));
|
||||
inPreloadFn = false;
|
||||
}
|
||||
intent = prevIntent;
|
||||
}
|
||||
function initFromFlash() {
|
||||
const e = getRequestEvent();
|
||||
return (e && e.router && e.router.submission ? [e.router.submission] : []);
|
||||
}
|
||||
}
|
||||
export function createRouteContext(router, parent, outlet, match) {
|
||||
const { base, location, params } = router;
|
||||
const { pattern, component, preload } = match().route;
|
||||
const path = createMemo(() => match().path);
|
||||
component &&
|
||||
component.preload &&
|
||||
component.preload();
|
||||
inPreloadFn = true;
|
||||
const data = preload ? preload({ params, location, intent: intent || "initial" }) : undefined;
|
||||
inPreloadFn = false;
|
||||
const route = {
|
||||
parent,
|
||||
pattern,
|
||||
path,
|
||||
outlet: () => component
|
||||
? createComponent(component, {
|
||||
params,
|
||||
location,
|
||||
data,
|
||||
get children() {
|
||||
return outlet();
|
||||
}
|
||||
})
|
||||
: outlet(),
|
||||
resolvePath(to) {
|
||||
return resolvePath(base.path(), to, path());
|
||||
}
|
||||
};
|
||||
return route;
|
||||
}
|
||||
200
web/runtime/@solidjs/router/dist/types.d.ts
vendored
Normal file
200
web/runtime/@solidjs/router/dist/types.d.ts
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
import type { Component, JSX, Signal } from "solid-js";
|
||||
declare module "solid-js/web" {
|
||||
interface RequestEvent {
|
||||
response: {
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers: Headers;
|
||||
};
|
||||
router?: {
|
||||
matches?: OutputMatch[];
|
||||
cache?: Map<string, CacheEntry>;
|
||||
submission?: {
|
||||
input: any;
|
||||
result: any;
|
||||
url: string;
|
||||
};
|
||||
dataOnly?: boolean | string[];
|
||||
data?: Record<string, any>;
|
||||
previousUrl?: string;
|
||||
};
|
||||
serverOnly?: boolean;
|
||||
}
|
||||
}
|
||||
export type Params = Record<string, string | undefined>;
|
||||
export type SearchParams = Record<string, string | string[] | undefined>;
|
||||
export type SetParams = Record<string, string | number | boolean | null | undefined>;
|
||||
export type SetSearchParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | null | undefined>;
|
||||
export interface Path {
|
||||
pathname: string;
|
||||
search: string;
|
||||
hash: string;
|
||||
}
|
||||
export interface Location<S = unknown> extends Path {
|
||||
query: SearchParams;
|
||||
state: Readonly<Partial<S>> | null;
|
||||
key: string;
|
||||
}
|
||||
export interface NavigateOptions<S = unknown> {
|
||||
resolve: boolean;
|
||||
replace: boolean;
|
||||
scroll: boolean;
|
||||
state: S;
|
||||
}
|
||||
export interface Navigator {
|
||||
(to: string | number, options?: Partial<NavigateOptions>): void;
|
||||
(delta: number): void;
|
||||
}
|
||||
export type NavigatorFactory = (route?: RouteContext) => Navigator;
|
||||
export interface LocationChange<S = unknown> {
|
||||
value: string;
|
||||
replace?: boolean;
|
||||
scroll?: boolean;
|
||||
state?: S;
|
||||
rawPath?: string;
|
||||
}
|
||||
export interface RouterIntegration {
|
||||
signal: Signal<LocationChange>;
|
||||
create?: (router: RouterContext) => void;
|
||||
utils?: Partial<RouterUtils>;
|
||||
}
|
||||
export type Intent = "initial" | "native" | "navigate" | "preload";
|
||||
export interface RoutePreloadFuncArgs {
|
||||
params: Params;
|
||||
location: Location;
|
||||
intent: Intent;
|
||||
}
|
||||
export type RoutePreloadFunc<T = unknown> = (args: RoutePreloadFuncArgs) => T;
|
||||
export interface RouteSectionProps<T = unknown> {
|
||||
params: Params;
|
||||
location: Location;
|
||||
data: T;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
export type RouteDefinition<S extends string | string[] = any, T = unknown> = {
|
||||
path?: S;
|
||||
matchFilters?: MatchFilters<S>;
|
||||
preload?: RoutePreloadFunc<T>;
|
||||
children?: RouteDefinition | RouteDefinition[];
|
||||
component?: Component<RouteSectionProps<T>>;
|
||||
info?: Record<string, any>;
|
||||
/** @deprecated use preload */
|
||||
load?: RoutePreloadFunc;
|
||||
};
|
||||
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
||||
export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];
|
||||
export type MatchFilters<P extends string | readonly string[] = any> = P extends string ? {
|
||||
[K in PathParams<P>[number]]?: MatchFilter;
|
||||
} : Record<string, MatchFilter>;
|
||||
export interface PathMatch {
|
||||
params: Params;
|
||||
path: string;
|
||||
}
|
||||
export interface RouteMatch extends PathMatch {
|
||||
route: RouteDescription;
|
||||
}
|
||||
export interface OutputMatch {
|
||||
path: string;
|
||||
pattern: string;
|
||||
match: string;
|
||||
params: Params;
|
||||
info?: Record<string, any>;
|
||||
}
|
||||
export interface RouteDescription {
|
||||
key: unknown;
|
||||
originalPath: string;
|
||||
pattern: string;
|
||||
component?: Component<RouteSectionProps>;
|
||||
preload?: RoutePreloadFunc;
|
||||
matcher: (location: string) => PathMatch | null;
|
||||
matchFilters?: MatchFilters;
|
||||
info?: Record<string, any>;
|
||||
}
|
||||
export interface Branch {
|
||||
routes: RouteDescription[];
|
||||
score: number;
|
||||
matcher: (location: string) => RouteMatch[] | null;
|
||||
}
|
||||
export interface RouteContext {
|
||||
parent?: RouteContext;
|
||||
child?: RouteContext;
|
||||
pattern: string;
|
||||
path: () => string;
|
||||
outlet: () => JSX.Element;
|
||||
resolvePath(to: string): string | undefined;
|
||||
}
|
||||
export interface RouterUtils {
|
||||
renderPath(path: string): string;
|
||||
parsePath(str: string): string;
|
||||
go(delta: number): void;
|
||||
beforeLeave: BeforeLeaveLifecycle;
|
||||
paramsWrapper: (getParams: () => Params, branches: () => Branch[]) => Params;
|
||||
queryWrapper: (getQuery: () => SearchParams) => SearchParams;
|
||||
}
|
||||
export interface RouterContext {
|
||||
base: RouteContext;
|
||||
location: Location;
|
||||
params: Params;
|
||||
navigatorFactory: NavigatorFactory;
|
||||
isRouting: () => boolean;
|
||||
matches: () => RouteMatch[];
|
||||
renderPath(path: string): string;
|
||||
parsePath(str: string): string;
|
||||
beforeLeave: BeforeLeaveLifecycle;
|
||||
preloadRoute: (url: URL, preloadData?: boolean) => void;
|
||||
singleFlight: boolean;
|
||||
submissions: Signal<Submission<any, any>[]>;
|
||||
}
|
||||
export interface BeforeLeaveEventArgs {
|
||||
from: Location;
|
||||
to: string | number;
|
||||
options?: Partial<NavigateOptions>;
|
||||
readonly defaultPrevented: boolean;
|
||||
preventDefault(): void;
|
||||
retry(force?: boolean): void;
|
||||
}
|
||||
export interface BeforeLeaveListener {
|
||||
listener(e: BeforeLeaveEventArgs): void;
|
||||
location: Location;
|
||||
navigate: Navigator;
|
||||
}
|
||||
export interface BeforeLeaveLifecycle {
|
||||
subscribe(listener: BeforeLeaveListener): () => void;
|
||||
confirm(to: string | number, options?: Partial<NavigateOptions>): boolean;
|
||||
}
|
||||
export type Submission<T, U> = {
|
||||
readonly input: T;
|
||||
readonly result?: U;
|
||||
readonly error: any;
|
||||
readonly pending: boolean;
|
||||
readonly url: string;
|
||||
clear: () => void;
|
||||
retry: () => void;
|
||||
};
|
||||
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 {
|
||||
preload?: () => void;
|
||||
}
|
||||
export type CacheEntry = [number, Promise<any>, any, Intent | undefined, Signal<number> & {
|
||||
count: number;
|
||||
}];
|
||||
export type NarrowResponse<T> = T extends CustomResponse<infer U> ? U : Exclude<T, Response>;
|
||||
export type RouterResponseInit = Omit<ResponseInit, "body"> & {
|
||||
revalidate?: string | string[];
|
||||
};
|
||||
export type CustomResponse<T> = Omit<Response, "clone"> & {
|
||||
customBody: () => T;
|
||||
clone(...args: readonly unknown[]): CustomResponse<T>;
|
||||
};
|
||||
/** @deprecated */
|
||||
export type RouteLoadFunc = RoutePreloadFunc;
|
||||
/** @deprecated */
|
||||
export type RouteLoadFuncArgs = RoutePreloadFuncArgs;
|
||||
1
web/runtime/@solidjs/router/dist/types.js
vendored
Normal file
1
web/runtime/@solidjs/router/dist/types.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
13
web/runtime/@solidjs/router/dist/utils.d.ts
vendored
Normal file
13
web/runtime/@solidjs/router/dist/utils.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { MatchFilters, PathMatch, RouteDescription, SearchParams, SetSearchParams } from "./types.js";
|
||||
export declare const mockBase = "http://sr";
|
||||
export declare function normalizePath(path: string, omitSlash?: boolean): string;
|
||||
export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
|
||||
export declare function invariant<T>(value: T | null | undefined, message: string): T;
|
||||
export declare function joinPaths(from: string, to: string): string;
|
||||
export declare function extractSearchParams(url: URL): SearchParams;
|
||||
export declare function createMatcher<S extends string>(path: S, partial?: boolean, matchFilters?: MatchFilters<S>): (location: string) => PathMatch | null;
|
||||
export declare function scoreRoute(route: RouteDescription): number;
|
||||
export declare function createMemoObject<T extends Record<string | symbol, unknown>>(fn: () => T): T;
|
||||
export declare function mergeSearchString(search: string, params: SetSearchParams): string;
|
||||
export declare function expandOptionals(pattern: string): string[];
|
||||
export declare function setFunctionName<T>(obj: T, value: string): T;
|
||||
185
web/runtime/@solidjs/router/dist/utils.js
vendored
Normal file
185
web/runtime/@solidjs/router/dist/utils.js
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
import { createMemo, getOwner, runWithOwner } from "solid-js";
|
||||
const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
|
||||
const trimPathRegex = /^\/+|(\/)\/+$/g;
|
||||
export const mockBase = "http://sr";
|
||||
export function normalizePath(path, omitSlash = false) {
|
||||
const s = path.replace(trimPathRegex, "$1");
|
||||
return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
|
||||
}
|
||||
export function resolvePath(base, path, from) {
|
||||
if (hasSchemeRegex.test(path)) {
|
||||
return undefined;
|
||||
}
|
||||
const basePath = normalizePath(base);
|
||||
const fromPath = from && normalizePath(from);
|
||||
let result = "";
|
||||
if (!fromPath || path.startsWith("/")) {
|
||||
result = basePath;
|
||||
}
|
||||
else if (fromPath.toLowerCase().indexOf(basePath.toLowerCase()) !== 0) {
|
||||
result = basePath + fromPath;
|
||||
}
|
||||
else {
|
||||
result = fromPath;
|
||||
}
|
||||
return (result || "/") + normalizePath(path, !result);
|
||||
}
|
||||
export function invariant(value, message) {
|
||||
if (value == null) {
|
||||
throw new Error(message);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
export function joinPaths(from, to) {
|
||||
return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to);
|
||||
}
|
||||
export function extractSearchParams(url) {
|
||||
const params = {};
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key in params) {
|
||||
if (Array.isArray(params[key]))
|
||||
params[key].push(value);
|
||||
else
|
||||
params[key] = [params[key], value];
|
||||
}
|
||||
else
|
||||
params[key] = value;
|
||||
});
|
||||
return params;
|
||||
}
|
||||
export function createMatcher(path, partial, matchFilters) {
|
||||
const [pattern, splat] = path.split("/*", 2);
|
||||
const segments = pattern.split("/").filter(Boolean);
|
||||
const len = segments.length;
|
||||
return (location) => {
|
||||
const locSegments = location.split("/").filter(Boolean);
|
||||
const lenDiff = locSegments.length - len;
|
||||
if (lenDiff < 0 || (lenDiff > 0 && splat === undefined && !partial)) {
|
||||
return null;
|
||||
}
|
||||
const match = {
|
||||
path: len ? "" : "/",
|
||||
params: {}
|
||||
};
|
||||
const matchFilter = (s) => matchFilters === undefined ? undefined : matchFilters[s];
|
||||
for (let i = 0; i < len; i++) {
|
||||
const segment = segments[i];
|
||||
const dynamic = segment[0] === ":";
|
||||
const locSegment = dynamic ? locSegments[i] : locSegments[i].toLowerCase();
|
||||
const key = dynamic ? segment.slice(1) : segment.toLowerCase();
|
||||
if (dynamic && matchSegment(locSegment, matchFilter(key))) {
|
||||
match.params[key] = locSegment;
|
||||
}
|
||||
else if (dynamic || !matchSegment(locSegment, key)) {
|
||||
return null;
|
||||
}
|
||||
match.path += `/${locSegment}`;
|
||||
}
|
||||
if (splat) {
|
||||
const remainder = lenDiff ? locSegments.slice(-lenDiff).join("/") : "";
|
||||
if (matchSegment(remainder, matchFilter(splat))) {
|
||||
match.params[splat] = remainder;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
};
|
||||
}
|
||||
function matchSegment(input, filter) {
|
||||
const isEqual = (s) => s === input;
|
||||
if (filter === undefined) {
|
||||
return true;
|
||||
}
|
||||
else if (typeof filter === "string") {
|
||||
return isEqual(filter);
|
||||
}
|
||||
else if (typeof filter === "function") {
|
||||
return filter(input);
|
||||
}
|
||||
else if (Array.isArray(filter)) {
|
||||
return filter.some(isEqual);
|
||||
}
|
||||
else if (filter instanceof RegExp) {
|
||||
return filter.test(input);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function scoreRoute(route) {
|
||||
const [pattern, splat] = route.pattern.split("/*", 2);
|
||||
const segments = pattern.split("/").filter(Boolean);
|
||||
return segments.reduce((score, segment) => score + (segment.startsWith(":") ? 2 : 3), segments.length - (splat === undefined ? 0 : 1));
|
||||
}
|
||||
export function createMemoObject(fn) {
|
||||
const map = new Map();
|
||||
const owner = getOwner();
|
||||
return new Proxy({}, {
|
||||
get(_, property) {
|
||||
if (!map.has(property)) {
|
||||
runWithOwner(owner, () => map.set(property, createMemo(() => fn()[property])));
|
||||
}
|
||||
return map.get(property)();
|
||||
},
|
||||
getOwnPropertyDescriptor() {
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
};
|
||||
},
|
||||
ownKeys() {
|
||||
return Reflect.ownKeys(fn());
|
||||
},
|
||||
has(_, property) {
|
||||
return property in fn();
|
||||
}
|
||||
});
|
||||
}
|
||||
export function mergeSearchString(search, params) {
|
||||
const merged = new URLSearchParams(search);
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value == null || value === "" || (value instanceof Array && !value.length)) {
|
||||
merged.delete(key);
|
||||
}
|
||||
else {
|
||||
if (value instanceof Array) {
|
||||
// Delete all instances of the key before appending
|
||||
merged.delete(key);
|
||||
value.forEach(v => {
|
||||
merged.append(key, String(v));
|
||||
});
|
||||
}
|
||||
else {
|
||||
merged.set(key, String(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
const s = merged.toString();
|
||||
return s ? `?${s}` : "";
|
||||
}
|
||||
export function expandOptionals(pattern) {
|
||||
let match = /(\/?\:[^\/]+)\?/.exec(pattern);
|
||||
if (!match)
|
||||
return [pattern];
|
||||
let prefix = pattern.slice(0, match.index);
|
||||
let suffix = pattern.slice(match.index + match[0].length);
|
||||
const prefixes = [prefix, (prefix += match[1])];
|
||||
// This section handles adjacent optional params. We don't actually want all permuations since
|
||||
// that will lead to equivalent routes which have the same number of params. For example
|
||||
// `/:a?/:b?/:c`? only has the unique expansion: `/`, `/:a`, `/:a/:b`, `/:a/:b/:c` and we can
|
||||
// discard `/:b`, `/:c`, `/:b/:c` by building them up in order and not recursing. This also helps
|
||||
// ensure predictability where earlier params have precidence.
|
||||
while ((match = /^(\/\:[^\/]+)\?/.exec(suffix))) {
|
||||
prefixes.push((prefix += match[1]));
|
||||
suffix = suffix.slice(match[0].length);
|
||||
}
|
||||
return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []);
|
||||
}
|
||||
export function setFunctionName(obj, value) {
|
||||
Object.defineProperty(obj, "name", {
|
||||
value,
|
||||
writable: false,
|
||||
configurable: false
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
61
web/runtime/@solidjs/router/package.json
Normal file
61
web/runtime/@solidjs/router/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@solidjs/router",
|
||||
"description": "Universal router for SolidJS",
|
||||
"author": "Ryan Carniato",
|
||||
"contributors": [
|
||||
"Ryan Turnquist"
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "0.16.1",
|
||||
"homepage": "https://github.com/solidjs/solid-router#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/solidjs/solid-router"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"solid": "./dist/index.jsx",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@changesets/cli": "^2.27.10",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-node-resolve": "15.3.0",
|
||||
"@rollup/plugin-terser": "0.4.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.0",
|
||||
"babel-preset-solid": "^1.9.3",
|
||||
"jsdom": "^25.0.1",
|
||||
"prettier": "^3.4.1",
|
||||
"rollup": "^4.27.4",
|
||||
"solid-js": "^1.9.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-solid": "^2.11.0",
|
||||
"vitest": "^2.1.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"solid-js": "^1.8.6"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc && rollup -c",
|
||||
"test": "vitest run && npm run test:types",
|
||||
"test:watch": "vitest",
|
||||
"test:types": "tsc --project tsconfig.test.json",
|
||||
"pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\"",
|
||||
"release": "pnpm build && changeset publish"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user