@solidjs/router 1.0.0-next.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +715 -531
  2. package/dist/components.d.ts +31 -0
  3. package/dist/components.jsx +40 -0
  4. package/dist/data/action.d.ts +11 -40
  5. package/dist/data/action.js +100 -274
  6. package/dist/data/createAsync.d.ts +32 -0
  7. package/dist/data/createAsync.js +96 -0
  8. package/dist/data/events.d.ts +0 -8
  9. package/dist/data/events.js +23 -24
  10. package/dist/data/index.d.ts +4 -2
  11. package/dist/data/index.js +4 -2
  12. package/dist/data/query.d.ts +3 -1
  13. package/dist/data/query.js +22 -39
  14. package/dist/data/response.d.ts +4 -0
  15. package/dist/data/response.js +42 -0
  16. package/dist/index.d.ts +3 -5
  17. package/dist/index.js +1024 -1578
  18. package/dist/index.jsx +2 -2
  19. package/dist/lifecycle.d.ts +4 -29
  20. package/dist/lifecycle.js +37 -40
  21. package/dist/routers/HashRouter.d.ts +9 -0
  22. package/dist/routers/HashRouter.js +41 -0
  23. package/dist/routers/MemoryRouter.d.ts +24 -0
  24. package/dist/routers/MemoryRouter.js +57 -0
  25. package/dist/routers/Router.d.ts +17 -0
  26. package/dist/routers/Router.js +59 -0
  27. package/dist/routers/StaticRouter.d.ts +6 -0
  28. package/dist/routers/StaticRouter.js +15 -0
  29. package/dist/routers/components.d.ts +26 -12
  30. package/dist/routers/components.jsx +54 -68
  31. package/dist/routers/createRouter.d.ts +10 -0
  32. package/dist/routers/createRouter.js +41 -0
  33. package/dist/routers/index.d.ts +11 -4
  34. package/dist/routers/index.js +6 -2
  35. package/dist/routers/scrollRestoration.d.ts +32 -0
  36. package/dist/routers/scrollRestoration.js +96 -0
  37. package/dist/routing.d.ts +52 -88
  38. package/dist/routing.js +166 -381
  39. package/dist/types.d.ts +33 -83
  40. package/dist/utils.d.ts +0 -2
  41. package/dist/utils.js +0 -2
  42. package/package.json +7 -11
  43. package/dist/claims.d.ts +0 -21
  44. package/dist/claims.js +0 -115
  45. package/dist/data/flash.d.ts +0 -21
  46. package/dist/data/flash.js +0 -78
  47. package/dist/data/flashCookie.d.ts +0 -7
  48. package/dist/data/flashCookie.js +0 -20
  49. package/dist/data/serverForms.d.ts +0 -1
  50. package/dist/data/serverForms.js +0 -5
  51. package/dist/paths.d.ts +0 -117
  52. package/dist/paths.js +0 -41
  53. package/dist/routers/factory.d.ts +0 -45
  54. package/dist/routers/factory.jsx +0 -143
  55. package/dist/routers/history.d.ts +0 -24
  56. package/dist/routers/history.js +0 -180
  57. package/dist/server.d.ts +0 -75
  58. package/dist/server.js +0 -264
package/dist/index.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./routers/index.js";
2
+ export * from "./components.jsx";
2
3
  export * from "./lifecycle.js";
3
- export { useHref, useIsRouting, useLinkState, useLocation, useMatch, useNavigate, usePreloadRoute, useParams, useResolvedPath, useRouteMatches, useSearchParams, RouterContextObj as RouterContext } from "./routing.js";
4
+ export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute, RouterContextObj as RouterContext } from "./routing.js";
4
5
  export { mergeSearchString as _mergeSearchString } from "./utils.js";
5
- export { int } from "./paths.js";
6
6
  export * from "./data/index.js";
@@ -1,30 +1,5 @@
1
- import type { BeforeLeaveEventArgs, BeforeLeaveLifecycle } from "./types.js";
1
+ import { BeforeLeaveLifecycle, LocationChange } from "./types.js";
2
2
  export declare function createBeforeLeave(): BeforeLeaveLifecycle;
3
- /**
4
- * useBeforeLeave takes a function that will be called prior to leaving a route.
5
- * The function will be called with:
6
- *
7
- * - from (*Location*): current location (before change).
8
- * - to (*string | number*): path passed to `navigate`.
9
- * - options (*NavigateOptions*): options passed to navigate.
10
- * - preventDefault (*function*): call to block the route change.
11
- * - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
12
- * - 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).
13
- *
14
- * @example
15
- * ```js
16
- * useBeforeLeave((e: BeforeLeaveEventArgs) => {
17
- * if (form.isDirty && !e.defaultPrevented) {
18
- * // preventDefault to block immediately and prompt user async
19
- * e.preventDefault();
20
- * setTimeout(() => {
21
- * if (window.confirm("Discard unsaved changes - are you sure?")) {
22
- * // user wants to proceed anyway so retry with force=true
23
- * e.retry(true);
24
- * }
25
- * }, 100);
26
- * }
27
- * });
28
- * ```
29
- */
30
- export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
3
+ export declare function saveCurrentDepth(): void;
4
+ export declare function keepDepth(state: any): any;
5
+ export declare function notifyIfNotBlocked(notify: (value?: string | LocationChange) => void, block: (delta: number | null) => boolean): () => void;
package/dist/lifecycle.js CHANGED
@@ -1,5 +1,4 @@
1
- import { onCleanup } from "solid-js";
2
- import { useLocation, useNavigate, useRouter } from "./routing.js";
1
+ import { isServer } from "solid-js/web";
3
2
  export function createBeforeLeave() {
4
3
  let listeners = new Set();
5
4
  function subscribe(listener) {
@@ -39,41 +38,39 @@ export function createBeforeLeave() {
39
38
  confirm
40
39
  };
41
40
  }
42
- /**
43
- * useBeforeLeave takes a function that will be called prior to leaving a route.
44
- * The function will be called with:
45
- *
46
- * - from (*Location*): current location (before change).
47
- * - to (*string | number*): path passed to `navigate`.
48
- * - options (*NavigateOptions*): options passed to navigate.
49
- * - preventDefault (*function*): call to block the route change.
50
- * - defaultPrevented (*readonly boolean*): `true` if any previously called leave handlers called `preventDefault`.
51
- * - 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).
52
- *
53
- * @example
54
- * ```js
55
- * useBeforeLeave((e: BeforeLeaveEventArgs) => {
56
- * if (form.isDirty && !e.defaultPrevented) {
57
- * // preventDefault to block immediately and prompt user async
58
- * e.preventDefault();
59
- * setTimeout(() => {
60
- * if (window.confirm("Discard unsaved changes - are you sure?")) {
61
- * // user wants to proceed anyway so retry with force=true
62
- * e.retry(true);
63
- * }
64
- * }, 100);
65
- * }
66
- * });
67
- * ```
68
- */
69
- export const useBeforeLeave = (listener) => {
70
- // Installing the guard here (instead of eagerly in the router/history) keeps
71
- // all of createBeforeLeave tree-shakeable for apps that never block leaves.
72
- const slot = useRouter().beforeLeave;
73
- const s = (slot.current || (slot.current = createBeforeLeave())).subscribe({
74
- listener,
75
- location: useLocation(),
76
- navigate: useNavigate()
77
- });
78
- onCleanup(s);
79
- };
41
+ // The following supports browser initiated blocking (eg back/forward)
42
+ let depth;
43
+ export function saveCurrentDepth() {
44
+ if (!window.history.state || window.history.state._depth == null) {
45
+ window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
46
+ }
47
+ depth = window.history.state._depth;
48
+ }
49
+ if (!isServer) {
50
+ saveCurrentDepth();
51
+ }
52
+ export function keepDepth(state) {
53
+ return {
54
+ ...state,
55
+ _depth: window.history.state && window.history.state._depth
56
+ };
57
+ }
58
+ export function notifyIfNotBlocked(notify, block) {
59
+ let ignore = false;
60
+ return () => {
61
+ const prevDepth = depth;
62
+ saveCurrentDepth();
63
+ const delta = prevDepth == null ? null : depth - prevDepth;
64
+ if (ignore) {
65
+ ignore = false;
66
+ return;
67
+ }
68
+ if (delta && block(delta)) {
69
+ ignore = true;
70
+ window.history.go(-delta);
71
+ }
72
+ else {
73
+ notify();
74
+ }
75
+ };
76
+ }
@@ -0,0 +1,9 @@
1
+ import type { JSX } from "solid-js";
2
+ import type { BaseRouterProps } from "./components.jsx";
3
+ export declare function hashParser(str: string): string;
4
+ export type HashRouterProps = BaseRouterProps & {
5
+ actionBase?: string;
6
+ explicitLinks?: boolean;
7
+ preload?: boolean;
8
+ };
9
+ export declare function HashRouter(props: HashRouterProps): JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { setupNativeEvents } from "../data/events.js";
2
+ import { createRouter, scrollToHash, bindEvent } from "./createRouter.js";
3
+ import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js";
4
+ export function hashParser(str) {
5
+ const to = str.replace(/^.*?#/, "");
6
+ // Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
7
+ // `/foo` will be `/#/foo`. Check if the to starts with a `/` and if not append it as a hash
8
+ // to the current path so we can handle these in-page anchors correctly.
9
+ if (!to.startsWith("/")) {
10
+ const [, path = "/"] = window.location.hash.split("#", 2);
11
+ return `${path}#${to}`;
12
+ }
13
+ return to;
14
+ }
15
+ export function HashRouter(props) {
16
+ const getSource = () => window.location.hash.slice(1);
17
+ const beforeLeave = createBeforeLeave();
18
+ return createRouter({
19
+ get: getSource,
20
+ set({ value, replace, scroll, state }) {
21
+ if (replace) {
22
+ window.history.replaceState(keepDepth(state), "", "#" + value);
23
+ }
24
+ else {
25
+ window.history.pushState(state, "", "#" + value);
26
+ }
27
+ const hashIndex = value.indexOf("#");
28
+ const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
29
+ scrollToHash(hash, scroll);
30
+ saveCurrentDepth();
31
+ },
32
+ init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
33
+ create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
34
+ utils: {
35
+ go: delta => window.history.go(delta),
36
+ renderPath: path => `#${path}`,
37
+ parsePath: hashParser,
38
+ beforeLeave
39
+ }
40
+ })(props);
41
+ }
@@ -0,0 +1,24 @@
1
+ import type { LocationChange } from "../types.js";
2
+ import type { BaseRouterProps } from "./components.jsx";
3
+ import type { JSX } from "solid-js";
4
+ export type MemoryHistory = {
5
+ get: () => string;
6
+ set: (change: LocationChange) => void;
7
+ go: (delta: number) => void;
8
+ listen: (listener: (value: string) => void) => () => void;
9
+ };
10
+ export declare function createMemoryHistory(): {
11
+ get: () => string;
12
+ set: ({ value, scroll, replace }: LocationChange) => void;
13
+ back: () => void;
14
+ forward: () => void;
15
+ go: (n: number) => void;
16
+ listen: (listener: (value: string) => void) => () => void;
17
+ };
18
+ export type MemoryRouterProps = BaseRouterProps & {
19
+ history?: MemoryHistory;
20
+ actionBase?: string;
21
+ explicitLinks?: boolean;
22
+ preload?: boolean;
23
+ };
24
+ export declare function MemoryRouter(props: MemoryRouterProps): JSX.Element;
@@ -0,0 +1,57 @@
1
+ import { createRouter, scrollToHash } from "./createRouter.js";
2
+ import { setupNativeEvents } from "../data/events.js";
3
+ export function createMemoryHistory() {
4
+ const entries = ["/"];
5
+ let index = 0;
6
+ const listeners = [];
7
+ const go = (n) => {
8
+ // https://github.com/remix-run/react-router/blob/682810ca929d0e3c64a76f8d6e465196b7a2ac58/packages/router/history.ts#L245
9
+ index = Math.max(0, Math.min(index + n, entries.length - 1));
10
+ const value = entries[index];
11
+ listeners.forEach(listener => listener(value));
12
+ };
13
+ return {
14
+ get: () => entries[index],
15
+ set: ({ value, scroll, replace }) => {
16
+ if (replace) {
17
+ entries[index] = value;
18
+ }
19
+ else {
20
+ entries.splice(index + 1, entries.length - index, value);
21
+ index++;
22
+ }
23
+ listeners.forEach(listener => listener(value));
24
+ setTimeout(() => {
25
+ if (scroll) {
26
+ scrollToHash(value.split("#")[1] || "", true);
27
+ }
28
+ }, 0);
29
+ },
30
+ back: () => {
31
+ go(-1);
32
+ },
33
+ forward: () => {
34
+ go(1);
35
+ },
36
+ go,
37
+ listen: (listener) => {
38
+ listeners.push(listener);
39
+ return () => {
40
+ const index = listeners.indexOf(listener);
41
+ listeners.splice(index, 1);
42
+ };
43
+ }
44
+ };
45
+ }
46
+ export function MemoryRouter(props) {
47
+ const memoryHistory = props.history || createMemoryHistory();
48
+ return createRouter({
49
+ get: memoryHistory.get,
50
+ set: memoryHistory.set,
51
+ init: memoryHistory.listen,
52
+ create: setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase }),
53
+ utils: {
54
+ go: memoryHistory.go
55
+ }
56
+ })(props);
57
+ }
@@ -0,0 +1,17 @@
1
+ import type { BaseRouterProps } from "./components.jsx";
2
+ import type { JSX } from "solid-js";
3
+ export type RouterProps = BaseRouterProps & {
4
+ url?: string;
5
+ actionBase?: string;
6
+ explicitLinks?: boolean;
7
+ preload?: boolean;
8
+ /**
9
+ * Opt-in explicit scroll restoration for back/forward navigation (read
10
+ * once, not reactive). Sets `history.scrollRestoration = "manual"` and
11
+ * restores saved positions after the navigation settles, replacing the
12
+ * browser heuristic that loses offsets when the destination route forces
13
+ * a layout while rendering (#577).
14
+ */
15
+ scrollRestoration?: boolean;
16
+ };
17
+ export declare function Router(props: RouterProps): JSX.Element;
@@ -0,0 +1,59 @@
1
+ import { isServer } from "solid-js/web";
2
+ import { createRouter, scrollToHash, bindEvent } from "./createRouter.js";
3
+ import { StaticRouter } from "./StaticRouter.js";
4
+ import { setupNativeEvents } from "../data/events.js";
5
+ import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js";
6
+ import { createScrollRestoration } from "./scrollRestoration.js";
7
+ export function Router(props) {
8
+ if (isServer)
9
+ return StaticRouter(props);
10
+ const restoration = props.scrollRestoration ? createScrollRestoration() : undefined;
11
+ const getSource = () => {
12
+ const url = window.location.pathname + window.location.search;
13
+ const state = window.history.state && window.history.state._depth && Object.keys(window.history.state).length === 1 ? undefined : window.history.state;
14
+ return {
15
+ value: url + window.location.hash,
16
+ state
17
+ };
18
+ };
19
+ const beforeLeave = createBeforeLeave();
20
+ return createRouter({
21
+ get: getSource,
22
+ set({ value, replace, scroll, state }) {
23
+ if (replace) {
24
+ window.history.replaceState(keepDepth(state), "", value);
25
+ }
26
+ else {
27
+ window.history.pushState(state, "", value);
28
+ }
29
+ scrollToHash(decodeURIComponent(window.location.hash.slice(1)), scroll);
30
+ saveCurrentDepth();
31
+ restoration && !replace && restoration.onPush();
32
+ },
33
+ init: notify => {
34
+ const handler = notifyIfNotBlocked(notify, delta => {
35
+ if (delta) {
36
+ return !beforeLeave.confirm(delta);
37
+ }
38
+ else {
39
+ const s = getSource();
40
+ return !beforeLeave.confirm(s.value, { state: s.state });
41
+ }
42
+ });
43
+ return bindEvent(window, "popstate", restoration
44
+ ? () => {
45
+ restoration.onPop();
46
+ handler();
47
+ }
48
+ : handler);
49
+ },
50
+ create: router => {
51
+ setupNativeEvents({ preload: props.preload, explicitLinks: props.explicitLinks, actionBase: props.actionBase, transformUrl: props.transformUrl })(router);
52
+ restoration && restoration.create(router);
53
+ },
54
+ utils: {
55
+ go: delta => window.history.go(delta),
56
+ beforeLeave
57
+ }
58
+ })(props);
59
+ }
@@ -0,0 +1,6 @@
1
+ import { type BaseRouterProps } from "./components.jsx";
2
+ import type { JSX } from "solid-js";
3
+ export type StaticRouterProps = BaseRouterProps & {
4
+ url?: string;
5
+ };
6
+ export declare function StaticRouter(props: StaticRouterProps): JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { getRequestEvent } from "solid-js/web";
2
+ import { createRouterComponent } from "./components.jsx";
3
+ function getPath(url) {
4
+ const u = new URL(url);
5
+ return u.pathname + u.search;
6
+ }
7
+ export function StaticRouter(props) {
8
+ let e;
9
+ const obj = {
10
+ value: props.url || ((e = getRequestEvent()) && getPath(e.request.url)) || "",
11
+ };
12
+ return createRouterComponent({
13
+ signal: [() => obj, next => Object.assign(obj, next)]
14
+ })(props);
15
+ }
@@ -1,13 +1,27 @@
1
- import type { Component } from "solid-js";
2
- import type { JSX } from "@solidjs/web";
3
- import type { Branch, RoutePreloadFunc, RouterContext, RouteSectionProps } from "../types.js";
4
- export declare function Root(props: {
5
- routerState: RouterContext;
1
+ import type { Component, JSX } from "solid-js";
2
+ import type { MatchFilters, RouteDefinition, RoutePreloadFunc, RouterIntegration, RouteSectionComponent, RouteSectionProps } from "../types.js";
3
+ export type BaseRouterProps = {
4
+ base?: string;
5
+ /**
6
+ * A component that wraps the content of every route.
7
+ */
6
8
  root?: Component<RouteSectionProps>;
7
- preload?: RoutePreloadFunc;
8
- children: JSX.Element;
9
- }): JSX.Element;
10
- export declare function Routes(props: {
11
- routerState: RouterContext;
12
- branches: () => Branch[];
13
- }): JSX.Element;
9
+ rootPreload?: RoutePreloadFunc;
10
+ singleFlight?: boolean;
11
+ children?: JSX.Element | RouteDefinition | RouteDefinition[];
12
+ transformUrl?: (url: string) => string;
13
+ /** @deprecated use rootPreload */
14
+ rootLoad?: RoutePreloadFunc;
15
+ };
16
+ export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
17
+ export type RouteProps<S extends string, T = unknown> = {
18
+ path?: S | S[];
19
+ children?: JSX.Element;
20
+ preload?: RoutePreloadFunc<T>;
21
+ matchFilters?: MatchFilters<S>;
22
+ component?: RouteSectionComponent<T>;
23
+ info?: Record<string, any>;
24
+ /** @deprecated use preload */
25
+ load?: RoutePreloadFunc<T>;
26
+ };
27
+ export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
@@ -1,72 +1,67 @@
1
1
  /*@refresh skip*/
2
- import { createMemo, createRoot, getOwner, onCleanup, runWithOwner, untrack } from "solid-js";
3
- import { getRequestEvent, isServer } from "@solidjs/web";
4
- import { createRouteContext, getIntent, getRouteMatches, resolveLazySubtree, RouteContextObj, setInPreloadFn, unresolvedLazyMatches } from "../routing.js";
5
- export function Root(props) {
2
+ import { children, createMemo, createRoot, getOwner, mergeProps, on, onCleanup, Show, untrack } from "solid-js";
3
+ import { getRequestEvent, isServer } from "solid-js/web";
4
+ import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
5
+ export const createRouterComponent = (router) => (props) => {
6
+ const { base } = props;
7
+ const routeDefs = children(() => props.children);
8
+ const branches = createMemo(() => createBranches(routeDefs(), props.base || ""));
9
+ let context;
10
+ const routerState = createRouterContext(router, branches, () => context, {
11
+ base,
12
+ singleFlight: props.singleFlight,
13
+ transformUrl: props.transformUrl,
14
+ });
15
+ router.create && router.create(routerState);
16
+ return (<RouterContextObj.Provider value={routerState}>
17
+ <Root routerState={routerState} root={props.root} preload={props.rootPreload || props.rootLoad}>
18
+ {(context = getOwner()) && null}
19
+ <Routes routerState={routerState} branches={branches()}/>
20
+ </Root>
21
+ </RouterContextObj.Provider>);
22
+ };
23
+ function Root(props) {
6
24
  const location = props.routerState.location;
7
25
  const params = props.routerState.params;
8
26
  const data = createMemo(() => props.preload &&
9
27
  untrack(() => {
10
28
  setInPreloadFn(true);
11
- try {
12
- return props.preload({ params, location, intent: getIntent() || "initial" });
13
- }
14
- finally {
15
- setInPreloadFn(false);
16
- }
29
+ props.preload({ params, location, intent: getIntent() || "initial" });
30
+ setInPreloadFn(false);
17
31
  }));
18
- const RootComp = props.root;
19
- if (RootComp) {
20
- return (<RootComp params={params} location={location} data={data()}>
21
- {props.children}
22
- </RootComp>);
23
- }
24
- return props.children;
32
+ return (<Show when={props.root} keyed fallback={props.children}>
33
+ {Root => (<Root params={params} location={location} data={data()}>
34
+ {props.children}
35
+ </Root>)}
36
+ </Show>);
25
37
  }
26
- export function Routes(props) {
38
+ function Routes(props) {
27
39
  if (isServer) {
28
40
  const e = getRequestEvent();
29
41
  if (e && e.router && e.router.dataOnly) {
30
- dataOnly(e, props.routerState, props.branches());
42
+ dataOnly(e, props.routerState, props.branches);
31
43
  return;
32
44
  }
33
- if (e && !(e.router && e.router.matches)) {
34
- // A lazy getter rather than a snapshot: unresolved lazy subtrees mean
35
- // the match chain can still improve while the render streams, and
36
- // frameworks read this after the render settles.
37
- Object.defineProperty((e.router || (e.router = {})), "matches", {
38
- configurable: true,
39
- enumerable: true,
40
- get: () => props.routerState.matches().map(({ route, path, params }) => ({
45
+ e &&
46
+ ((e.router || (e.router = {})).matches ||
47
+ (e.router.matches = props.routerState.matches().map(({ route, path, params }) => ({
41
48
  path: route.originalPath,
42
49
  pattern: route.pattern,
43
50
  match: path,
44
51
  params,
45
52
  info: route.info
46
- }))
47
- });
48
- }
53
+ }))));
49
54
  }
50
55
  const disposers = [];
51
56
  let root;
52
- let prevMatches;
53
57
  // dispose the detached per-route roots when this component unmounts, otherwise
54
58
  // they stay subscribed to `matches` and crash on a later navigation (#451)
55
59
  onCleanup(() => disposers.forEach(dispose => dispose()));
56
- // Route roots must outlive re-runs of the `routeStates` memo below, so they
57
- // are created under the owner of this component rather than the memo's
58
- // computation (which disposes its children every time it re-runs).
59
- const owner = getOwner();
60
- const routeStates = createMemo((prev) => {
61
- // While a lazy subtree resolves, `matches()` is not ready and this
62
- // computation parks with it — no route contexts are created against
63
- // placeholder matches.
64
- const nextMatches = props.routerState.matches();
65
- const previousMatches = prevMatches;
66
- let equal = previousMatches && nextMatches.length === previousMatches.length;
60
+ const routeStates = createMemo(on(props.routerState.matches, (nextMatches, prevMatches, prev) => {
61
+ let equal = prevMatches && nextMatches.length === prevMatches.length;
67
62
  const next = [];
68
63
  for (let i = 0, len = nextMatches.length; i < len; i++) {
69
- const prevMatch = previousMatches && previousMatches[i];
64
+ const prevMatch = prevMatches && prevMatches[i];
70
65
  const nextMatch = nextMatches[i];
71
66
  if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
72
67
  next[i] = prev[i];
@@ -76,51 +71,42 @@ export function Routes(props) {
76
71
  if (disposers[i]) {
77
72
  disposers[i]();
78
73
  }
79
- runWithOwner(owner, () => createRoot(dispose => {
74
+ createRoot(dispose => {
80
75
  disposers[i] = dispose;
81
- const routeKey = nextMatch.route.key;
82
- // Retain the last matches in which this route participated so
83
- // that its components and preloads never observe another
84
- // route's params/path while this route is being torn down.
85
- const matchesAtLevel = createMemo((prev) => {
76
+ next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => {
86
77
  const routeMatches = props.routerState.matches();
87
- const m = routeMatches[i];
88
- return m && m.route.key === routeKey ? routeMatches : prev || nextMatches;
78
+ return routeMatches[i] ?? routeMatches[0];
89
79
  });
90
- next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()?.[i + 1]), () => matchesAtLevel()[i], matchesAtLevel);
91
- }));
80
+ });
92
81
  }
93
82
  }
94
83
  disposers.splice(nextMatches.length).forEach(dispose => dispose());
95
84
  if (prev && equal) {
96
- prevMatches = nextMatches;
97
85
  return prev;
98
86
  }
99
87
  root = next[0];
100
- prevMatches = nextMatches;
101
88
  return next;
102
- });
103
- const outlet = createOutlet(() => routeStates() && root);
104
- return <>{outlet()}</>;
89
+ }));
90
+ return createOutlet(() => routeStates() && root)();
105
91
  }
106
92
  const createOutlet = (child) => {
107
- return () => {
108
- const c = child();
109
- if (c) {
110
- return <RouteContextObj value={c}>{c.outlet()}</RouteContextObj>;
93
+ return () => (<Show when={child()} keyed>
94
+ {child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
95
+ </Show>);
96
+ };
97
+ export const Route = (props) => {
98
+ const childRoutes = children(() => props.children);
99
+ return mergeProps(props, {
100
+ get children() {
101
+ return childRoutes();
111
102
  }
112
- return undefined;
113
- };
103
+ });
114
104
  };
115
105
  // for data only mode with single flight mutations
116
106
  function dataOnly(event, routerState, branches) {
117
107
  const url = new URL(event.request.url);
118
108
  const prevMatches = getRouteMatches(branches, new URL(event.router.previousUrl || event.request.url).pathname);
119
109
  const matches = getRouteMatches(branches, url.pathname);
120
- // This pass is synchronous — an unresolved lazy subtree can only be
121
- // kicked, not awaited (the flight collector's own runner awaits; see
122
- // src/server.ts). Best effort so a subsequent pass sees the table.
123
- unresolvedLazyMatches([...prevMatches, ...matches]).forEach(resolveLazySubtree);
124
110
  for (let match = 0; match < matches.length; match++) {
125
111
  if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
126
112
  event.router.dataOnly = true;
@@ -0,0 +1,10 @@
1
+ import type { LocationChange, RouterContext, RouterUtils } from "../types.js";
2
+ export declare function createRouter(config: {
3
+ get: () => string | LocationChange;
4
+ set: (next: LocationChange) => void;
5
+ init?: (notify: (value?: string | LocationChange) => void) => () => void;
6
+ create?: (router: RouterContext) => void;
7
+ utils?: Partial<RouterUtils>;
8
+ }): (props: import("./components.jsx").BaseRouterProps) => import("solid-js").JSX.Element;
9
+ export declare function bindEvent(target: EventTarget, type: string, handler: EventListener): () => void;
10
+ export declare function scrollToHash(hash: string, fallbackTop?: boolean): void;
@@ -0,0 +1,41 @@
1
+ import { createSignal, onCleanup, sharedConfig } from "solid-js";
2
+ import { createRouterComponent } from "./components.jsx";
3
+ function intercept([value, setValue], get, set) {
4
+ return [get ? () => get(value()) : value, set ? (v) => setValue(set(v)) : setValue];
5
+ }
6
+ export function createRouter(config) {
7
+ let ignore = false;
8
+ const wrap = (value) => (typeof value === "string" ? { value } : value);
9
+ const signal = intercept(createSignal(wrap(config.get()), {
10
+ equals: (a, b) => a.value === b.value && a.state === b.state
11
+ }), undefined, next => {
12
+ !ignore && config.set(next);
13
+ if (sharedConfig.registry && !sharedConfig.done)
14
+ sharedConfig.done = true;
15
+ return next;
16
+ });
17
+ config.init &&
18
+ onCleanup(config.init((value = config.get()) => {
19
+ ignore = true;
20
+ signal[1](wrap(value));
21
+ ignore = false;
22
+ }));
23
+ return createRouterComponent({
24
+ signal,
25
+ create: config.create,
26
+ utils: config.utils
27
+ });
28
+ }
29
+ export function bindEvent(target, type, handler) {
30
+ target.addEventListener(type, handler);
31
+ return () => target.removeEventListener(type, handler);
32
+ }
33
+ export function scrollToHash(hash, fallbackTop) {
34
+ const el = hash && document.getElementById(hash);
35
+ if (el) {
36
+ el.scrollIntoView();
37
+ }
38
+ else if (fallbackTop) {
39
+ window.scrollTo(0, 0);
40
+ }
41
+ }