@solidjs/router 0.10.0-beta.4 → 0.10.0-beta.5

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.
package/README.md CHANGED
@@ -456,12 +456,12 @@ function loadUser({params, location}) {
456
456
  <Route path="/users/:id" component={User} load={loadUser} />;
457
457
  ```
458
458
 
459
- The load function is called when the Route is loaded or eagerly when links are hovered. Inside your page component you
459
+ The load function is called when the Route is loaded or eagerly when links are hovered. Inside your page component you:
460
460
 
461
461
  ```jsx
462
462
  // pages/users/[id].js
463
463
  import { getUser } from ... // the cache function
464
-
464
+
465
465
  export default function User(props) {
466
466
  const user = createAsync(() => getUser(props.params.id));
467
467
  return <h1>{user().name}</h1>;
@@ -542,9 +542,9 @@ render(() =>
542
542
  By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through the `source` property on `<Router>` component.
543
543
 
544
544
  ```jsx
545
- import { Router, hashIntegration } from "@solidjs/router";
545
+ import { HashRouter } from "@solidjs/router";
546
546
 
547
- <Router source={hashIntegration()} />;
547
+ <HashRouter />;
548
548
  ```
549
549
 
550
550
  ### Memory Mode Router
@@ -552,11 +552,23 @@ import { Router, hashIntegration } from "@solidjs/router";
552
552
  You can also use memory mode router for testing purpose.
553
553
 
554
554
  ```jsx
555
- import { Router, memoryIntegration } from "@solidjs/router";
555
+ import { MemoryRouter } from "@solidjs/router";
556
556
 
557
- <Router source={memoryIntegration()} />;
557
+ <MemoryRouter />;
558
558
  ```
559
559
 
560
+ ### SSR Routing
561
+
562
+ For SSR you can use the static router directly or the browser Router defaults to it on the server, just pass in the url.
563
+
564
+ ```jsx
565
+ import { isServer } from "solid-js/web";
566
+ import { Router } from "@solidjs/router";
567
+
568
+ <Router url={isServer ? req.url : ""} />;
569
+ ```
570
+
571
+
560
572
  ## Components
561
573
 
562
574
  ### `<A>`
@@ -1,5 +1,5 @@
1
- import type { Component, JSX } from "solid-js";
2
- import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "./types";
1
+ import type { JSX } from "solid-js";
2
+ import type { Location, Navigator } from "./types";
3
3
  declare module "solid-js" {
4
4
  namespace JSX {
5
5
  interface AnchorHTMLAttributes<T> {
@@ -10,27 +10,6 @@ declare module "solid-js" {
10
10
  }
11
11
  }
12
12
  }
13
- export type RouterProps = {
14
- base?: string;
15
- actionBase?: string;
16
- root?: Component<RouteSectionProps>;
17
- children: JSX.Element;
18
- } & ({
19
- url?: never;
20
- source?: RouterIntegration | LocationChangeSignal;
21
- } | {
22
- source?: never;
23
- url: string;
24
- });
25
- export declare const Router: (props: RouterProps) => JSX.Element;
26
- export type RouteProps<S extends string> = {
27
- path?: S | S[];
28
- children?: JSX.Element;
29
- load?: RouteLoadFunc;
30
- matchFilters?: MatchFilters<S>;
31
- component?: Component;
32
- };
33
- export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
34
13
  export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
35
14
  href: string;
36
15
  replace?: boolean | undefined;
@@ -41,7 +20,6 @@ export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorEle
41
20
  end?: boolean | undefined;
42
21
  }
43
22
  export declare function A(props: AnchorProps): JSX.Element;
44
- export { A as Link, A as NavLink, AnchorProps as LinkProps, AnchorProps as NavLinkProps };
45
23
  export interface NavigateProps {
46
24
  href: ((args: {
47
25
  navigate: Navigator;
@@ -1,85 +1,8 @@
1
1
  /*@refresh skip*/
2
- import { children, createMemo, createRoot, mergeProps, on, Show, splitProps } from "solid-js";
3
- import { isServer, getRequestEvent } from "solid-js/web";
4
- import { pathIntegration, staticIntegration } from "./integration";
5
- import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath } from "./routing";
6
- import { normalizePath, createMemoObject } from "./utils";
7
- export const Router = (props) => {
8
- let e;
9
- const { source, url, base, actionBase } = props;
10
- const integration = source ||
11
- (isServer
12
- ? staticIntegration({
13
- value: url || ((e = getRequestEvent()) && getPath(e.request.url)) || ""
14
- })
15
- : pathIntegration());
16
- const routeDefs = children(() => props.children);
17
- const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
18
- const routerState = createRouterContext(integration, branches, { base, actionBase });
19
- return (<RouterContextObj.Provider value={routerState}>
20
- <Routes routerState={routerState} branches={branches()}/>
21
- </RouterContextObj.Provider>);
22
- };
23
- function getPath(url) {
24
- const u = new URL(url);
25
- return u.pathname + u.search;
26
- }
27
- function Routes(props) {
28
- const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
29
- const params = createMemoObject(() => {
30
- const m = matches();
31
- const params = {};
32
- for (let i = 0; i < m.length; i++) {
33
- Object.assign(params, m[i].params);
34
- }
35
- return params;
36
- });
37
- const disposers = [];
38
- let root;
39
- const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
40
- let equal = prevMatches && nextMatches.length === prevMatches.length;
41
- const next = [];
42
- for (let i = 0, len = nextMatches.length; i < len; i++) {
43
- const prevMatch = prevMatches && prevMatches[i];
44
- const nextMatch = nextMatches[i];
45
- if (prev && prevMatch && nextMatch.route.key === prevMatch.route.key) {
46
- next[i] = prev[i];
47
- }
48
- else {
49
- equal = false;
50
- if (disposers[i]) {
51
- disposers[i]();
52
- }
53
- createRoot(dispose => {
54
- disposers[i] = dispose;
55
- next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => matches()[i], params);
56
- });
57
- }
58
- }
59
- disposers.splice(nextMatches.length).forEach(dispose => dispose());
60
- if (prev && equal) {
61
- return prev;
62
- }
63
- root = next[0];
64
- return next;
65
- }));
66
- return (<Show when={routeStates() && root} keyed>
67
- {route => <RouteContextObj.Provider value={route}>{route.outlet()}</RouteContextObj.Provider>}
68
- </Show>);
69
- }
70
- const createOutlet = (child) => {
71
- return () => (<Show when={child()} keyed>
72
- {child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
73
- </Show>);
74
- };
75
- export const Route = (props) => {
76
- const childRoutes = children(() => props.children);
77
- return mergeProps(props, {
78
- get children() {
79
- return childRoutes();
80
- }
81
- });
82
- };
2
+ "use client";
3
+ import { createMemo, mergeProps, splitProps } from "solid-js";
4
+ import { useHref, useLocation, useNavigate, useResolvedPath } from "./routing";
5
+ import { normalizePath } from "./utils";
83
6
  export function A(props) {
84
7
  props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
85
8
  const [, rest] = splitProps(props, [
@@ -108,8 +31,6 @@ export function A(props) {
108
31
  ...rest.classList
109
32
  }} aria-current={isActive() ? "page" : undefined}/>);
110
33
  }
111
- // deprecated alias exports
112
- export { A as Link, A as NavLink };
113
34
  export function Navigate(props) {
114
35
  const navigate = useNavigate();
115
36
  const location = useLocation();
@@ -1,6 +1,7 @@
1
1
  import { JSX } from "solid-js";
2
2
  import { Submission } from "../types";
3
3
  export type Action<T, U> = ((vars: T) => Promise<U>) & JSX.SerializableAttributeValue;
4
+ export declare const actions: Map<string, Function>;
4
5
  export declare function useSubmissions<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>[] & {
5
6
  pending: boolean;
6
7
  };
@@ -1,8 +1,9 @@
1
1
  import { $TRACK, createMemo, createSignal } from "solid-js";
2
2
  import { isServer } from "solid-js/web";
3
- import { registerAction, useRouter } from "../routing";
3
+ import { useRouter } from "../routing";
4
4
  import { redirectStatusCodes } from "../utils";
5
5
  import { revalidate } from "./cache";
6
+ export const actions = /* #__PURE__ */ new Map();
6
7
  export function useSubmissions(fn, filter) {
7
8
  const router = useRouter();
8
9
  const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.toString() && (!filter || filter(s.input))));
@@ -18,26 +19,11 @@ export function useSubmissions(fn, filter) {
18
19
  }
19
20
  export function useSubmission(fn, filter) {
20
21
  const submissions = useSubmissions(fn, filter);
21
- return {
22
- get clear() {
23
- return submissions[submissions.length - 1]?.clear;
24
- },
25
- get retry() {
26
- return submissions[submissions.length - 1]?.retry;
27
- },
28
- get url() {
29
- return submissions[submissions.length - 1]?.url;
30
- },
31
- get input() {
32
- return submissions[submissions.length - 1]?.input;
33
- },
34
- get result() {
35
- return submissions[submissions.length - 1]?.result;
36
- },
37
- get pending() {
38
- return submissions[submissions.length - 1]?.pending;
22
+ return new Proxy({}, {
23
+ get(_, property) {
24
+ return submissions[submissions.length - 1]?.[property];
39
25
  }
40
- };
26
+ });
41
27
  }
42
28
  export function useAction(action) {
43
29
  const router = useRouter();
@@ -86,7 +72,7 @@ export function action(fn, name) {
86
72
  return url;
87
73
  };
88
74
  if (!isServer)
89
- registerAction(url, mutate);
75
+ actions.set(url, mutate);
90
76
  return mutate;
91
77
  }
92
78
  async function handleResponse(response, navigate) {
@@ -0,0 +1,2 @@
1
+ import type { RouterContext } from "../types";
2
+ export declare function setupNativeEvents(router: RouterContext): void;
@@ -0,0 +1,116 @@
1
+ import { delegateEvents } from "solid-js/web";
2
+ import { onCleanup } from "solid-js";
3
+ import { actions } from "./action";
4
+ export function setupNativeEvents(router) {
5
+ const basePath = router.base.path();
6
+ const navigateFromRoute = router.navigatorFactory(router.base);
7
+ let preloadTimeout = {};
8
+ function isSvg(el) {
9
+ return el.namespaceURI === "http://www.w3.org/2000/svg";
10
+ }
11
+ function handleAnchor(evt) {
12
+ if (evt.defaultPrevented ||
13
+ evt.button !== 0 ||
14
+ evt.metaKey ||
15
+ evt.altKey ||
16
+ evt.ctrlKey ||
17
+ evt.shiftKey)
18
+ return;
19
+ const a = evt
20
+ .composedPath()
21
+ .find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
22
+ if (!a)
23
+ return;
24
+ const svg = isSvg(a);
25
+ const href = svg ? a.href.baseVal : a.href;
26
+ const target = svg ? a.target.baseVal : a.target;
27
+ if (target || (!href && !a.hasAttribute("state")))
28
+ return;
29
+ const rel = (a.getAttribute("rel") || "").split(/\s+/);
30
+ if (a.hasAttribute("download") || (rel && rel.includes("external")))
31
+ return;
32
+ const url = svg ? new URL(href, document.baseURI) : new URL(href);
33
+ if (url.origin !== window.location.origin ||
34
+ (basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase())))
35
+ return;
36
+ return [a, url];
37
+ }
38
+ function handleAnchorClick(evt) {
39
+ const res = handleAnchor(evt);
40
+ if (!res)
41
+ return;
42
+ const [a, url] = res;
43
+ const to = router.parsePath(url.pathname + url.search + url.hash);
44
+ const state = a.getAttribute("state");
45
+ evt.preventDefault();
46
+ navigateFromRoute(to, {
47
+ resolve: false,
48
+ replace: a.hasAttribute("replace"),
49
+ scroll: !a.hasAttribute("noscroll"),
50
+ state: state && JSON.parse(state)
51
+ });
52
+ }
53
+ function handleAnchorPreload(evt) {
54
+ const res = handleAnchor(evt);
55
+ if (!res)
56
+ return;
57
+ const [a, url] = res;
58
+ if (!preloadTimeout[url.pathname])
59
+ router.preloadRoute(url, a.getAttribute("preload") !== "false");
60
+ }
61
+ function handleAnchorIn(evt) {
62
+ const res = handleAnchor(evt);
63
+ if (!res)
64
+ return;
65
+ const [a, url] = res;
66
+ if (preloadTimeout[url.pathname])
67
+ return;
68
+ preloadTimeout[url.pathname] = setTimeout(() => {
69
+ router.preloadRoute(url, a.getAttribute("preload") !== "false");
70
+ delete preloadTimeout[url.pathname];
71
+ }, 200);
72
+ }
73
+ function handleAnchorOut(evt) {
74
+ const res = handleAnchor(evt);
75
+ if (!res)
76
+ return;
77
+ const [, url] = res;
78
+ if (preloadTimeout[url.pathname]) {
79
+ clearTimeout(preloadTimeout[url.pathname]);
80
+ delete preloadTimeout[url.pathname];
81
+ }
82
+ }
83
+ function handleFormSubmit(evt) {
84
+ let actionRef = (evt.submitter && evt.submitter.getAttribute("formaction")) || evt.target.action;
85
+ if (!actionRef)
86
+ return;
87
+ if (!actionRef.startsWith("action:")) {
88
+ const url = new URL(actionRef);
89
+ actionRef = router.parsePath(url.pathname + url.search);
90
+ if (!actionRef.startsWith(router.actionBase))
91
+ return;
92
+ }
93
+ const handler = actions.get(actionRef);
94
+ if (handler) {
95
+ evt.preventDefault();
96
+ const data = new FormData(evt.target);
97
+ handler.call(router, data);
98
+ }
99
+ }
100
+ // ensure delegated event run first
101
+ delegateEvents(["click", "submit"]);
102
+ document.addEventListener("click", handleAnchorClick);
103
+ document.addEventListener("mouseover", handleAnchorIn);
104
+ document.addEventListener("mouseout", handleAnchorOut);
105
+ document.addEventListener("focusin", handleAnchorPreload);
106
+ document.addEventListener("touchstart", handleAnchorPreload);
107
+ document.addEventListener("submit", handleFormSubmit);
108
+ onCleanup(() => {
109
+ document.removeEventListener("click", handleAnchorClick);
110
+ document.removeEventListener("mouseover", handleAnchorIn);
111
+ document.removeEventListener("mouseout", handleAnchorOut);
112
+ document.removeEventListener("focusin", handleAnchorPreload);
113
+ document.removeEventListener("touchstart", handleAnchorPreload);
114
+ document.removeEventListener("submit", handleFormSubmit);
115
+ });
116
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
+ export * from "./routers";
1
2
  export * from "./components";
2
- export * from "./integration";
3
3
  export * from "./lifecycle";
4
4
  export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils";
6
6
  export * from "./data";
7
- export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
7
+ export type { Location, LocationChange, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";