@solidjs/router 0.4.3 → 0.5.1

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/dist/index.jsx CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./components";
2
2
  export * from "./integration";
3
- export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams } from "./routing";
3
+ export * from "./lifecycle";
4
+ export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
4
5
  export { mergeSearchString as _mergeSearchString } from "./utils";
@@ -0,0 +1,2 @@
1
+ import { BeforeLeaveLifecycle } from "./types";
2
+ export declare function createBeforeLeave(): BeforeLeaveLifecycle;
@@ -0,0 +1,32 @@
1
+ export function createBeforeLeave() {
2
+ let listeners = new Set();
3
+ function subscribe(listener) {
4
+ listeners.add(listener);
5
+ return () => listeners.delete(listener);
6
+ }
7
+ let ignore = false;
8
+ function confirm(to, options) {
9
+ if (ignore)
10
+ return !(ignore = false);
11
+ const e = {
12
+ to,
13
+ options,
14
+ defaultPrevented: false,
15
+ preventDefault: () => (e.defaultPrevented = true)
16
+ };
17
+ for (const l of listeners)
18
+ l.listener({
19
+ ...e,
20
+ from: l.location,
21
+ retry: (force) => {
22
+ force && (ignore = true);
23
+ l.navigate(to, options);
24
+ }
25
+ });
26
+ return !e.defaultPrevented;
27
+ }
28
+ return {
29
+ subscribe,
30
+ confirm
31
+ };
32
+ }
package/dist/routing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Component, Accessor } from "solid-js";
2
- import type { Branch, Location, LocationChangeSignal, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
2
+ import type { BeforeLeaveEventArgs, Branch, Location, LocationChangeSignal, NavigateOptions, Navigator, Params, Route, RouteContext, RouteDataFunc, RouteDefinition, RouteMatch, RouterContext, RouterIntegration, SetParams } from "./types";
3
3
  export declare const RouterContextObj: import("solid-js").Context<RouterContext | undefined>;
4
4
  export declare const RouteContextObj: import("solid-js").Context<RouteContext | undefined>;
5
5
  export declare const useRouter: () => RouterContext;
@@ -13,7 +13,8 @@ export declare const useMatch: (path: () => string) => Accessor<import("./types"
13
13
  export declare const useParams: <T extends Params>() => T;
14
14
  declare type MaybeReturnType<T> = T extends (...args: any) => infer R ? R : T;
15
15
  export declare const useRouteData: <T>() => MaybeReturnType<T>;
16
- export declare const useSearchParams: <T extends Params>() => [T, (params: SetParams, options?: Partial<NavigateOptions<unknown>> | undefined) => void];
16
+ export declare const useSearchParams: <T extends Params>() => [T, (params: SetParams, options?: Partial<NavigateOptions>) => void];
17
+ export declare const useBeforeLeave: (listener: (e: BeforeLeaveEventArgs) => void) => void;
17
18
  export declare function createRoutes(routeDef: RouteDefinition, base?: string, fallback?: Component): Route[];
18
19
  export declare function createBranch(routes: Route[], index?: number): Branch;
19
20
  export declare function createBranches(routeDef: RouteDefinition | RouteDefinition[], base?: string, fallback?: Component, stack?: Route[], branches?: Branch[]): Branch[];
package/dist/routing.js CHANGED
@@ -1,6 +1,7 @@
1
- import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, useTransition, resetErrorBoundaries } from "solid-js";
1
+ import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js";
2
2
  import { isServer, delegateEvents } from "solid-js/web";
3
3
  import { normalizeIntegration } from "./integration";
4
+ import { createBeforeLeave } from "./lifecycle";
4
5
  import { createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, urlDecode, expandOptionals } from "./utils";
5
6
  const MAX_REDIRECTS = 100;
6
7
  export const RouterContextObj = createContext();
@@ -34,10 +35,14 @@ export const useSearchParams = () => {
34
35
  const navigate = useNavigate();
35
36
  const setSearchParams = (params, options) => {
36
37
  const searchString = untrack(() => mergeSearchString(location.search, params));
37
- navigate(searchString, { scroll: false, ...options, resolve: true });
38
+ navigate(location.pathname + searchString + location.hash, { scroll: false, resolve: false, ...options });
38
39
  };
39
40
  return [location.query, setSearchParams];
40
41
  };
42
+ export const useBeforeLeave = (listener) => {
43
+ const s = useRouter().beforeLeave.subscribe({ listener, location: useLocation(), navigate: useNavigate() });
44
+ onCleanup(s);
45
+ };
41
46
  export function createRoutes(routeDef, base = "", fallback) {
42
47
  const { component, data, children } = routeDef;
43
48
  const isLeaf = !children || (Array.isArray(children) && !children.length);
@@ -102,7 +107,8 @@ export function createBranches(routeDef, base = "", fallback, stack = [], branch
102
107
  const routes = createRoutes(def, base, fallback);
103
108
  for (const route of routes) {
104
109
  stack.push(route);
105
- if (def.children) {
110
+ const isEmptyArray = Array.isArray(def.children) && def.children.length === 0;
111
+ if (def.children && !isEmptyArray) {
106
112
  createBranches(def.children, route.pattern, fallback, stack, branches);
107
113
  }
108
114
  else {
@@ -166,6 +172,7 @@ export function createRouterContext(integration, base = "", data, out) {
166
172
  const { signal: [source, setSource], utils = {} } = normalizeIntegration(integration);
167
173
  const parsePath = utils.parsePath || (p => p);
168
174
  const renderPath = utils.renderPath || (p => p);
175
+ const beforeLeave = utils.beforeLeave || createBeforeLeave();
169
176
  const basePath = resolvePath("", base);
170
177
  const output = isServer && out
171
178
  ? Object.assign(out, {
@@ -179,7 +186,16 @@ export function createRouterContext(integration, base = "", data, out) {
179
186
  else if (basePath && !source().value) {
180
187
  setSource({ value: basePath, replace: true, scroll: false });
181
188
  }
182
- const [isRouting, start] = useTransition();
189
+ const [isRouting, setIsRouting] = createSignal(false);
190
+ const start = async (callback) => {
191
+ setIsRouting(true);
192
+ try {
193
+ await startTransition(callback);
194
+ }
195
+ finally {
196
+ setIsRouting(false);
197
+ }
198
+ };
183
199
  const [reference, setReference] = createSignal(source().value);
184
200
  const [state, setState] = createSignal(source().state);
185
201
  const location = createLocation(reference, state);
@@ -215,7 +231,7 @@ export function createRouterContext(integration, base = "", data, out) {
215
231
  // A delta of 0 means stay at the current location, so it is ignored
216
232
  }
217
233
  else if (utils.go) {
218
- utils.go(to);
234
+ beforeLeave.confirm(to, options) && utils.go(to);
219
235
  }
220
236
  else {
221
237
  console.warn("Router integration does not support relative routing");
@@ -243,7 +259,7 @@ export function createRouterContext(integration, base = "", data, out) {
243
259
  }
244
260
  setSource({ value: resolvedTo, replace, scroll, state: nextState });
245
261
  }
246
- else {
262
+ else if (beforeLeave.confirm(resolvedTo, options)) {
247
263
  const len = referrers.push({ value: current, replace, scroll, state: state() });
248
264
  start(() => {
249
265
  setReference(resolvedTo);
@@ -292,9 +308,6 @@ export function createRouterContext(integration, base = "", data, out) {
292
308
  });
293
309
  });
294
310
  if (!isServer) {
295
- function isSvg(el) {
296
- return el.namespaceURI === "http://www.w3.org/2000/svg";
297
- }
298
311
  function handleAnchorClick(evt) {
299
312
  if (evt.defaultPrevented ||
300
313
  evt.button !== 0 ||
@@ -306,22 +319,20 @@ export function createRouterContext(integration, base = "", data, out) {
306
319
  const a = evt
307
320
  .composedPath()
308
321
  .find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
309
- if (!a)
322
+ if (!a || !a.hasAttribute("link"))
310
323
  return;
311
- const svg = isSvg(a);
312
- const href = svg ? a.href.baseVal : a.href;
313
- const target = svg ? a.target.baseVal : a.target;
314
- if (target || (!href && !a.hasAttribute("state")))
324
+ const href = a.href;
325
+ if (a.target || (!href && !a.hasAttribute("state")))
315
326
  return;
316
327
  const rel = (a.getAttribute("rel") || "").split(/\s+/);
317
328
  if (a.hasAttribute("download") || (rel && rel.includes("external")))
318
329
  return;
319
- const url = svg ? new URL(href, document.baseURI) : new URL(href);
330
+ const url = new URL(href);
320
331
  const pathname = urlDecode(url.pathname);
321
332
  if (url.origin !== window.location.origin ||
322
333
  (basePath && pathname && !pathname.toLowerCase().startsWith(basePath.toLowerCase())))
323
334
  return;
324
- const to = parsePath(pathname + urlDecode(url.search, true) + urlDecode(url.hash));
335
+ const to = parsePath(url.pathname + url.search + url.hash);
325
336
  const state = a.getAttribute("state");
326
337
  evt.preventDefault();
327
338
  navigateFromRoute(baseRoute, to, {
@@ -343,7 +354,8 @@ export function createRouterContext(integration, base = "", data, out) {
343
354
  isRouting,
344
355
  renderPath,
345
356
  parsePath,
346
- navigatorFactory
357
+ navigatorFactory,
358
+ beforeLeave
347
359
  };
348
360
  }
349
361
  export function createRouteContext(router, parent, child, match) {
package/dist/types.d.ts CHANGED
@@ -93,6 +93,7 @@ export interface RouterUtils {
93
93
  renderPath(path: string): string;
94
94
  parsePath(str: string): string;
95
95
  go(delta: number): void;
96
+ beforeLeave: BeforeLeaveLifecycle;
96
97
  }
97
98
  export interface OutputMatch {
98
99
  originalPath: string;
@@ -112,4 +113,22 @@ export interface RouterContext {
112
113
  isRouting: () => boolean;
113
114
  renderPath(path: string): string;
114
115
  parsePath(str: string): string;
116
+ beforeLeave: BeforeLeaveLifecycle;
117
+ }
118
+ export interface BeforeLeaveEventArgs {
119
+ from: Location;
120
+ to: string | number;
121
+ options?: Partial<NavigateOptions>;
122
+ readonly defaultPrevented: boolean;
123
+ preventDefault(): void;
124
+ retry(force?: boolean): void;
125
+ }
126
+ export interface BeforeLeaveListener {
127
+ listener(e: BeforeLeaveEventArgs): void;
128
+ location: Location;
129
+ navigate: Navigator;
130
+ }
131
+ export interface BeforeLeaveLifecycle {
132
+ subscribe(listener: BeforeLeaveListener): () => void;
133
+ confirm(to: string | number, options?: Partial<NavigateOptions>): boolean;
115
134
  }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Params, PathMatch, Route, SetParams } from "./types";
2
+ export declare function normalizePath(path: string, omitSlash?: boolean): string;
2
3
  export declare function resolvePath(base: string, path: string, from?: string): string | undefined;
3
4
  export declare function invariant<T>(value: T | null | undefined, message: string): T;
4
5
  export declare function joinPaths(from: string, to: string): string;
package/dist/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createMemo, getOwner, runWithOwner } from "solid-js";
2
2
  const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
3
3
  const trimPathRegex = /^\/+|\/+$/g;
4
- function normalize(path, omitSlash = false) {
4
+ export function normalizePath(path, omitSlash = false) {
5
5
  const s = path.replace(trimPathRegex, "");
6
6
  return s ? (omitSlash || /^[?#]/.test(s) ? s : "/" + s) : "";
7
7
  }
@@ -9,8 +9,8 @@ export function resolvePath(base, path, from) {
9
9
  if (hasSchemeRegex.test(path)) {
10
10
  return undefined;
11
11
  }
12
- const basePath = normalize(base);
13
- const fromPath = from && normalize(from);
12
+ const basePath = normalizePath(base);
13
+ const fromPath = from && normalizePath(from);
14
14
  let result = "";
15
15
  if (!fromPath || path.startsWith("/")) {
16
16
  result = basePath;
@@ -21,7 +21,7 @@ export function resolvePath(base, path, from) {
21
21
  else {
22
22
  result = fromPath;
23
23
  }
24
- return (result || "/") + normalize(path, !result);
24
+ return (result || "/") + normalizePath(path, !result);
25
25
  }
26
26
  export function invariant(value, message) {
27
27
  if (value == null) {
@@ -30,7 +30,7 @@ export function invariant(value, message) {
30
30
  return value;
31
31
  }
32
32
  export function joinPaths(from, to) {
33
- return normalize(from).replace(/\/*(\*.*)?$/g, "") + normalize(to);
33
+ return normalizePath(from).replace(/\/*(\*.*)?$/g, "") + normalizePath(to);
34
34
  }
35
35
  export function extractSearchParams(url) {
36
36
  const params = {};
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.4.3",
9
+ "version": "0.5.1",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -38,23 +38,24 @@
38
38
  "pretty": "prettier --write \"{src,test}/**/*.{ts,tsx}\""
39
39
  },
40
40
  "devDependencies": {
41
- "@babel/core": "^7.15.8",
42
- "@babel/preset-typescript": "^7.15.0",
43
- "@rollup/plugin-babel": "5.3.0",
44
- "@rollup/plugin-node-resolve": "13.0.5",
45
- "@types/jest": "^27.0.2",
46
- "@types/node": "^16.10.3",
47
- "babel-preset-solid": "^1.4.4",
48
- "jest": "^27.2.5",
49
- "prettier": "^2.5.1",
50
- "rollup": "^2.52.1",
41
+ "@babel/core": "^7.18.13",
42
+ "@babel/preset-typescript": "^7.18.6",
43
+ "@rollup/plugin-babel": "5.3.1",
44
+ "@rollup/plugin-node-resolve": "13.3.0",
45
+ "@types/jest": "^29.0.0",
46
+ "@types/node": "^18.7.14",
47
+ "babel-preset-solid": "^1.5.3",
48
+ "jest": "^29.0.1",
49
+ "jest-environment-jsdom": "^29.2.1",
50
+ "prettier": "^2.7.1",
51
+ "rollup": "^2.79.0",
51
52
  "rollup-plugin-terser": "^7.0.2",
52
53
  "solid-jest": "^0.2.0",
53
- "solid-js": "^1.4.4",
54
- "typescript": "^4.5.4"
54
+ "solid-js": "^1.5.3",
55
+ "typescript": "^4.8.2"
55
56
  },
56
57
  "peerDependencies": {
57
- "solid-js": "^1.3.5"
58
+ "solid-js": "^1.5.3"
58
59
  },
59
60
  "jest": {
60
61
  "preset": "solid-jest/preset/browser"