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

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.
@@ -12,6 +12,7 @@ declare module "solid-js" {
12
12
  }
13
13
  export type RouterProps = {
14
14
  base?: string;
15
+ actionBase?: string;
15
16
  root?: Component<RouteSectionProps>;
16
17
  children: JSX.Element;
17
18
  } & ({
@@ -6,14 +6,16 @@ import { createBranches, createRouteContext, createRouterContext, getRouteMatche
6
6
  import { normalizePath, createMemoObject } from "./utils";
7
7
  export const Router = (props) => {
8
8
  let e;
9
- const { source, url, base } = props;
9
+ const { source, url, base, actionBase } = props;
10
10
  const integration = source ||
11
11
  (isServer
12
- ? staticIntegration({ value: url || ((e = getRequestEvent()) && getPath(e.request.url)) || "" })
12
+ ? staticIntegration({
13
+ value: url || ((e = getRequestEvent()) && getPath(e.request.url)) || ""
14
+ })
13
15
  : pathIntegration());
14
16
  const routeDefs = children(() => props.children);
15
17
  const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
16
- const routerState = createRouterContext(integration, branches, base);
18
+ const routerState = createRouterContext(integration, branches, { base, actionBase });
17
19
  return (<RouterContextObj.Provider value={routerState}>
18
20
  <Routes routerState={routerState} branches={branches()}/>
19
21
  </RouterContextObj.Provider>);
@@ -1,5 +1,6 @@
1
+ import { JSX } from "solid-js";
1
2
  import { Submission } from "../types";
2
- export type Action<T, U> = (vars: T) => Promise<U>;
3
+ export type Action<T, U> = ((vars: T) => Promise<U>) & JSX.SerializableAttributeValue;
3
4
  export declare function useSubmissions<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>[] & {
4
5
  pending: boolean;
5
6
  };
@@ -20,22 +20,22 @@ export function useSubmission(fn, filter) {
20
20
  const submissions = useSubmissions(fn, filter);
21
21
  return {
22
22
  get clear() {
23
- return submissions[0]?.clear;
23
+ return submissions[submissions.length - 1]?.clear;
24
24
  },
25
25
  get retry() {
26
- return submissions[0]?.retry;
26
+ return submissions[submissions.length - 1]?.retry;
27
27
  },
28
28
  get url() {
29
- return submissions[0]?.url;
29
+ return submissions[submissions.length - 1]?.url;
30
30
  },
31
31
  get input() {
32
- return submissions[0]?.input;
32
+ return submissions[submissions.length - 1]?.input;
33
33
  },
34
34
  get result() {
35
- return submissions[0]?.result;
35
+ return submissions[submissions.length - 1]?.result;
36
36
  },
37
37
  get pending() {
38
- return submissions[0]?.pending;
38
+ return submissions[submissions.length - 1]?.pending;
39
39
  }
40
40
  };
41
41
  }
@@ -50,16 +50,15 @@ export function cache(fn, name, options) {
50
50
  version && cached[3].add(version);
51
51
  if (cached[2] === "preload" && intent !== "preload") {
52
52
  cached[0] = now;
53
- cached[1] =
54
- "then" in cached[1]
55
- ? cached[1].then(handleResponse, handleResponse)
56
- : handleResponse(cached[1]);
57
- cached[2] = intent;
58
53
  }
54
+ let res = cached[1];
59
55
  if (!isServer && intent === "navigate") {
56
+ res = "then" in cached[1]
57
+ ? cached[1].then(handleResponse(false), handleResponse(true))
58
+ : handleResponse(false)(cached[1]);
60
59
  startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
61
60
  }
62
- return cached[1];
61
+ return res;
63
62
  }
64
63
  let res = !isServer && sharedConfig.context && sharedConfig.load
65
64
  ? sharedConfig.load(key) // hydrating
@@ -68,12 +67,6 @@ export function cache(fn, name, options) {
68
67
  if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
69
68
  sharedConfig.context && sharedConfig.context.serialize(key, res);
70
69
  }
71
- if (intent !== "preload") {
72
- res =
73
- "then" in res
74
- ? res.then(handleResponse, handleResponse)
75
- : handleResponse(res);
76
- }
77
70
  if (cached) {
78
71
  cached[0] = now;
79
72
  cached[1] = res;
@@ -85,30 +78,37 @@ export function cache(fn, name, options) {
85
78
  }
86
79
  else
87
80
  cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])]));
81
+ if (intent !== "preload") {
82
+ res = "then" in res
83
+ ? res.then(handleResponse(false), handleResponse(true))
84
+ : handleResponse(false)(res);
85
+ }
88
86
  return res;
89
- function handleResponse(v) {
90
- if (v instanceof Response && redirectStatusCodes.has(v.status)) {
91
- if (navigate) {
92
- startTransition(() => {
93
- let url = v.headers.get(LocationHeader);
94
- if (url && url.startsWith("/")) {
95
- navigate(url, {
96
- replace: true
97
- });
98
- }
99
- else if (!isServer && url) {
100
- window.location.href = url;
101
- }
102
- });
87
+ function handleResponse(error) {
88
+ return (v) => {
89
+ if (v instanceof Response && redirectStatusCodes.has(v.status)) {
90
+ if (navigate) {
91
+ startTransition(() => {
92
+ let url = v.headers.get(LocationHeader);
93
+ if (url && url.startsWith("/")) {
94
+ navigate(url, {
95
+ replace: true
96
+ });
97
+ }
98
+ else if (!isServer && url) {
99
+ window.location.href = url;
100
+ }
101
+ });
102
+ }
103
+ return;
103
104
  }
104
- return;
105
- }
106
- if (v instanceof Error)
107
- throw v;
108
- if (isServer)
109
- return v;
110
- setStore(key, reconcile(v, options));
111
- return store[key];
105
+ if (error)
106
+ throw error;
107
+ if (isServer)
108
+ return v;
109
+ setStore(key, reconcile(v, options));
110
+ return store[key];
111
+ };
112
112
  }
113
113
  });
114
114
  }
@@ -1,4 +1,4 @@
1
1
  export { createAsync } from "./createAsync";
2
- export { action, useSubmission, useSubmissions } from "./action";
2
+ export { action, useSubmission, useSubmissions, useAction } from "./action";
3
3
  export { cache, revalidate } from "./cache";
4
4
  export { redirect } from "./response";
@@ -1,4 +1,4 @@
1
1
  export { createAsync } from "./createAsync";
2
- export { action, useSubmission, useSubmissions } from "./action";
2
+ export { action, useSubmission, useSubmissions, useAction } from "./action";
3
3
  export { cache, revalidate } from "./cache";
4
4
  export { redirect } from "./response";
package/dist/index.js CHANGED
@@ -529,7 +529,7 @@ let intent;
529
529
  function getIntent() {
530
530
  return intent;
531
531
  }
532
- function createRouterContext(integration, getBranches, base = "") {
532
+ function createRouterContext(integration, getBranches, options = {}) {
533
533
  const {
534
534
  signal: [source, setSource],
535
535
  utils = {}
@@ -537,8 +537,8 @@ function createRouterContext(integration, getBranches, base = "") {
537
537
  const parsePath = utils.parsePath || (p => p);
538
538
  const renderPath = utils.renderPath || (p => p);
539
539
  const beforeLeave = utils.beforeLeave || createBeforeLeave();
540
- let submissions = [];
541
- const basePath = resolvePath("", base);
540
+ const basePath = resolvePath("", options.base || "");
541
+ const actionBase = options.actionBase || "/_server";
542
542
  if (basePath === undefined) {
543
543
  throw new Error(`${basePath} is not a valid base path`);
544
544
  } else if (basePath && !source().value) {
@@ -578,7 +578,7 @@ function createRouterContext(integration, getBranches, base = "") {
578
578
  parsePath,
579
579
  navigatorFactory,
580
580
  beforeLeave,
581
- submissions: createSignal(submissions)
581
+ submissions: createSignal(initFromFlash(location.query))
582
582
  };
583
583
  function navigateFromRoute(route, to, options) {
584
584
  // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
@@ -667,6 +667,16 @@ function createRouterContext(integration, getBranches, base = "") {
667
667
  referrers.length = 0;
668
668
  }
669
669
  }
670
+ function initFromFlash(params) {
671
+ let param = params.form ? JSON.parse(params.form) : null;
672
+ if (!param || !param.result) return [];
673
+ const input = new Map(param.entries);
674
+ return [{
675
+ url: param.url,
676
+ result: param.error ? new Error(param.result.message) : param.result,
677
+ input: input
678
+ }];
679
+ }
670
680
  createRenderEffect(() => {
671
681
  const {
672
682
  value,
@@ -771,10 +781,17 @@ function createRouterContext(integration, getBranches, base = "") {
771
781
  }
772
782
  function handleFormSubmit(evt) {
773
783
  let actionRef = evt.submitter && evt.submitter.getAttribute("formaction") || evt.target.action;
774
- if (actionRef && actionRef.startsWith("action:")) {
775
- const data = new FormData(evt.target);
776
- actions.get(actionRef).call(router, data);
784
+ if (!actionRef) return;
785
+ if (!actionRef.startsWith("action:")) {
786
+ const url = new URL(actionRef);
787
+ actionRef = parsePath(url.pathname + url.search);
788
+ if (!actionRef.startsWith(actionBase)) return;
789
+ }
790
+ const handler = actions.get(actionRef);
791
+ if (handler) {
777
792
  evt.preventDefault();
793
+ const data = new FormData(evt.target);
794
+ handler.call(router, data);
778
795
  }
779
796
  }
780
797
 
@@ -794,20 +811,6 @@ function createRouterContext(integration, getBranches, base = "") {
794
811
  document.removeEventListener("touchstart", handleAnchorPreload);
795
812
  document.removeEventListener("submit", handleFormSubmit);
796
813
  });
797
- } else {
798
- function initFromFlash(params) {
799
- let param = params.form ? JSON.parse(params.form) : null;
800
- if (!param || !param.result) {
801
- return [];
802
- }
803
- const input = new Map(param.entries);
804
- return [{
805
- url: param.url,
806
- result: param.error ? new Error(param.result.message) : param.result,
807
- input: input
808
- }];
809
- }
810
- submissions = initFromFlash(location.query);
811
814
  }
812
815
  return router;
813
816
  }
@@ -847,13 +850,14 @@ function createRouteContext(router, parent, outlet, match, params) {
847
850
  return route;
848
851
  }
849
852
 
850
- const _tmpl$ = /*#__PURE__*/template(`<a></a>`, 2);
853
+ const _tmpl$ = /*#__PURE__*/template(`<a>`);
851
854
  const Router = props => {
852
855
  let e;
853
856
  const {
854
857
  source,
855
858
  url,
856
- base
859
+ base,
860
+ actionBase
857
861
  } = props;
858
862
  const integration = source || (isServer ? staticIntegration({
859
863
  value: url || (e = getRequestEvent()) && getPath(e.request.url) || ""
@@ -863,7 +867,10 @@ const Router = props => {
863
867
  component: props.root,
864
868
  children: routeDefs()
865
869
  } : routeDefs(), props.base || ""));
866
- const routerState = createRouterContext(integration, branches, base);
870
+ const routerState = createRouterContext(integration, branches, {
871
+ base,
872
+ actionBase
873
+ });
867
874
  return createComponent$1(RouterContextObj.Provider, {
868
875
  value: routerState,
869
876
  get children() {
@@ -970,7 +977,7 @@ function A(props) {
970
977
  return props.end ? path === loc : loc.startsWith(path);
971
978
  });
972
979
  return (() => {
973
- const _el$ = _tmpl$.cloneNode(true);
980
+ const _el$ = _tmpl$();
974
981
  spread(_el$, mergeProps$1(rest, {
975
982
  get href() {
976
983
  return href() || props.href;
@@ -1112,14 +1119,14 @@ function cache(fn, name, options) {
1112
1119
  version && cached[3].add(version);
1113
1120
  if (cached[2] === "preload" && intent !== "preload") {
1114
1121
  cached[0] = now;
1115
- cached[1] = "then" in cached[1] ? cached[1].then(handleResponse, handleResponse) : handleResponse(cached[1]);
1116
- cached[2] = intent;
1117
1122
  }
1123
+ let res = cached[1];
1118
1124
  if (!isServer && intent === "navigate") {
1125
+ res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
1119
1126
  startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
1120
1127
  }
1121
1128
 
1122
- return cached[1];
1129
+ return res;
1123
1130
  }
1124
1131
  let res = !isServer && sharedConfig.context && sharedConfig.load ? sharedConfig.load(key) // hydrating
1125
1132
  : fn(...args);
@@ -1128,9 +1135,6 @@ function cache(fn, name, options) {
1128
1135
  if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
1129
1136
  sharedConfig.context && sharedConfig.context.serialize(key, res);
1130
1137
  }
1131
- if (intent !== "preload") {
1132
- res = "then" in res ? res.then(handleResponse, handleResponse) : handleResponse(res);
1133
- }
1134
1138
  if (cached) {
1135
1139
  cached[0] = now;
1136
1140
  cached[1] = res;
@@ -1140,27 +1144,32 @@ function cache(fn, name, options) {
1140
1144
  startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
1141
1145
  }
1142
1146
  } else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]);
1147
+ if (intent !== "preload") {
1148
+ res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res);
1149
+ }
1143
1150
  return res;
1144
- function handleResponse(v) {
1145
- if (v instanceof Response && redirectStatusCodes.has(v.status)) {
1146
- if (navigate) {
1147
- startTransition(() => {
1148
- let url = v.headers.get(LocationHeader);
1149
- if (url && url.startsWith("/")) {
1150
- navigate(url, {
1151
- replace: true
1152
- });
1153
- } else if (!isServer && url) {
1154
- window.location.href = url;
1155
- }
1156
- });
1151
+ function handleResponse(error) {
1152
+ return v => {
1153
+ if (v instanceof Response && redirectStatusCodes.has(v.status)) {
1154
+ if (navigate) {
1155
+ startTransition(() => {
1156
+ let url = v.headers.get(LocationHeader);
1157
+ if (url && url.startsWith("/")) {
1158
+ navigate(url, {
1159
+ replace: true
1160
+ });
1161
+ } else if (!isServer && url) {
1162
+ window.location.href = url;
1163
+ }
1164
+ });
1165
+ }
1166
+ return;
1157
1167
  }
1158
- return;
1159
- }
1160
- if (v instanceof Error) throw v;
1161
- if (isServer) return v;
1162
- setStore(key, reconcile(v, options));
1163
- return store[key];
1168
+ if (error) throw error;
1169
+ if (isServer) return v;
1170
+ setStore(key, reconcile(v, options));
1171
+ return store[key];
1172
+ };
1164
1173
  }
1165
1174
  };
1166
1175
  }
@@ -1180,25 +1189,29 @@ function useSubmission(fn, filter) {
1180
1189
  const submissions = useSubmissions(fn, filter);
1181
1190
  return {
1182
1191
  get clear() {
1183
- return submissions[0]?.clear;
1192
+ return submissions[submissions.length - 1]?.clear;
1184
1193
  },
1185
1194
  get retry() {
1186
- return submissions[0]?.retry;
1195
+ return submissions[submissions.length - 1]?.retry;
1187
1196
  },
1188
1197
  get url() {
1189
- return submissions[0]?.url;
1198
+ return submissions[submissions.length - 1]?.url;
1190
1199
  },
1191
1200
  get input() {
1192
- return submissions[0]?.input;
1201
+ return submissions[submissions.length - 1]?.input;
1193
1202
  },
1194
1203
  get result() {
1195
- return submissions[0]?.result;
1204
+ return submissions[submissions.length - 1]?.result;
1196
1205
  },
1197
1206
  get pending() {
1198
- return submissions[0]?.pending;
1207
+ return submissions[submissions.length - 1]?.pending;
1199
1208
  }
1200
1209
  };
1201
1210
  }
1211
+ function useAction(action) {
1212
+ const router = useRouter();
1213
+ return action.bind(router);
1214
+ }
1202
1215
  function action(fn, name) {
1203
1216
  function mutate(variables) {
1204
1217
  const p = fn(variables);
@@ -1275,4 +1288,4 @@ function redirect(url, init = 302) {
1275
1288
  return response;
1276
1289
  }
1277
1290
 
1278
- export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, redirect, revalidate, staticIntegration, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
1291
+ export { A, A as Link, A as NavLink, Navigate, Route, Router, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createIntegration, createMemoryHistory, hashIntegration, memoryIntegration, normalizeIntegration, pathIntegration, redirect, revalidate, staticIntegration, useAction, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
package/dist/routing.d.ts CHANGED
@@ -20,5 +20,8 @@ export declare function getRouteMatches(branches: Branch[], location: string): R
20
20
  export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
21
21
  export declare function registerAction(url: string, fn: Function): void;
22
22
  export declare function getIntent(): Intent | undefined;
23
- export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, getBranches?: () => Branch[], base?: string): RouterContext;
23
+ export declare function createRouterContext(integration?: RouterIntegration | LocationChangeSignal, getBranches?: () => Branch[], options?: {
24
+ base?: string;
25
+ actionBase?: string;
26
+ }): RouterContext;
24
27
  export declare function createRouteContext(router: RouterContext, parent: RouteContext, outlet: () => JSX.Element, match: () => RouteMatch, params: Params): RouteContext;
package/dist/routing.js CHANGED
@@ -181,13 +181,13 @@ let intent;
181
181
  export function getIntent() {
182
182
  return intent;
183
183
  }
184
- export function createRouterContext(integration, getBranches, base = "") {
184
+ export function createRouterContext(integration, getBranches, options = {}) {
185
185
  const { signal: [source, setSource], utils = {} } = normalizeIntegration(integration);
186
186
  const parsePath = utils.parsePath || (p => p);
187
187
  const renderPath = utils.renderPath || (p => p);
188
188
  const beforeLeave = utils.beforeLeave || createBeforeLeave();
189
- let submissions = [];
190
- const basePath = resolvePath("", base);
189
+ const basePath = resolvePath("", options.base || "");
190
+ const actionBase = options.actionBase || "/_server";
191
191
  if (basePath === undefined) {
192
192
  throw new Error(`${basePath} is not a valid base path`);
193
193
  }
@@ -225,7 +225,7 @@ export function createRouterContext(integration, getBranches, base = "") {
225
225
  parsePath,
226
226
  navigatorFactory,
227
227
  beforeLeave,
228
- submissions: createSignal(submissions)
228
+ submissions: createSignal(initFromFlash(location.query))
229
229
  };
230
230
  function navigateFromRoute(route, to, options) {
231
231
  // Untrack in case someone navigates in an effect - don't want to track `reference` or route paths
@@ -301,6 +301,19 @@ export function createRouterContext(integration, getBranches, base = "") {
301
301
  referrers.length = 0;
302
302
  }
303
303
  }
304
+ function initFromFlash(params) {
305
+ let param = params.form ? JSON.parse(params.form) : null;
306
+ if (!param || !param.result)
307
+ return [];
308
+ const input = new Map(param.entries);
309
+ return [
310
+ {
311
+ url: param.url,
312
+ result: param.error ? new Error(param.result.message) : param.result,
313
+ input: input
314
+ }
315
+ ];
316
+ }
304
317
  createRenderEffect(() => {
305
318
  const { value, state } = source();
306
319
  // Untrack this whole block so `start` doesn't cause Solid's Listener to be preserved
@@ -422,10 +435,19 @@ export function createRouterContext(integration, getBranches, base = "") {
422
435
  }
423
436
  function handleFormSubmit(evt) {
424
437
  let actionRef = (evt.submitter && evt.submitter.getAttribute("formaction")) || evt.target.action;
425
- if (actionRef && actionRef.startsWith("action:")) {
426
- const data = new FormData(evt.target);
427
- actions.get(actionRef).call(router, data);
438
+ if (!actionRef)
439
+ return;
440
+ if (!actionRef.startsWith("action:")) {
441
+ const url = new URL(actionRef);
442
+ actionRef = parsePath(url.pathname + url.search);
443
+ if (!actionRef.startsWith(actionBase))
444
+ return;
445
+ }
446
+ const handler = actions.get(actionRef);
447
+ if (handler) {
428
448
  evt.preventDefault();
449
+ const data = new FormData(evt.target);
450
+ handler.call(router, data);
429
451
  }
430
452
  }
431
453
  // ensure delegated event run first
@@ -445,23 +467,6 @@ export function createRouterContext(integration, getBranches, base = "") {
445
467
  document.removeEventListener("submit", handleFormSubmit);
446
468
  });
447
469
  }
448
- else {
449
- function initFromFlash(params) {
450
- let param = params.form ? JSON.parse(params.form) : null;
451
- if (!param || !param.result) {
452
- return [];
453
- }
454
- const input = new Map(param.entries);
455
- return [
456
- {
457
- url: param.url,
458
- result: param.error ? new Error(param.result.message) : param.result,
459
- input: input
460
- }
461
- ];
462
- }
463
- submissions = initFromFlash(location.query);
464
- }
465
470
  return router;
466
471
  }
467
472
  export function createRouteContext(router, parent, outlet, match, params) {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.10.0-beta.2",
9
+ "version": "0.10.0-beta.4",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -37,17 +37,17 @@
37
37
  "@types/jest": "^29.0.0",
38
38
  "@types/node": "^20.9.0",
39
39
  "babel-jest": "^29.0.1",
40
- "babel-preset-solid": "^1.6.6",
40
+ "babel-preset-solid": "^1.8.6",
41
41
  "jest": "^29.0.1",
42
42
  "jest-environment-jsdom": "^29.2.1",
43
43
  "prettier": "^2.7.1",
44
44
  "rollup": "^3.7.5",
45
45
  "solid-jest": "^0.2.0",
46
- "solid-js": "^1.8.4",
46
+ "solid-js": "^1.8.6",
47
47
  "typescript": "^5.2.2"
48
48
  },
49
49
  "peerDependencies": {
50
- "solid-js": "^1.8.4"
50
+ "solid-js": "^1.8.6"
51
51
  },
52
52
  "jest": {
53
53
  "preset": "solid-jest/preset/browser"