@solidjs/router 0.10.9 → 0.11.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isServer, getRequestEvent, createComponent as createComponent$1, delegateEvents, spread, mergeProps as mergeProps$1, template } from 'solid-js/web';
2
- import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, mergeProps, createRoot, Show, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js';
2
+ import { getOwner, runWithOwner, createMemo, createContext, onCleanup, useContext, untrack, createSignal, createRenderEffect, on, startTransition, resetErrorBoundaries, createComponent, children, mergeProps, createRoot, Show, getListener, sharedConfig, $TRACK, splitProps, createResource } from 'solid-js';
3
3
  import { createStore, reconcile } from 'solid-js/store';
4
4
 
5
5
  function createBeforeLeave() {
@@ -36,8 +36,49 @@ function createBeforeLeave() {
36
36
  };
37
37
  }
38
38
 
39
+ // The following supports browser initiated blocking (eg back/forward)
40
+
41
+ let depth;
42
+ function saveCurrentDepth() {
43
+ if (!window.history.state || window.history.state._depth == null) {
44
+ window.history.replaceState({
45
+ ...window.history.state,
46
+ _depth: window.history.length - 1
47
+ }, "");
48
+ }
49
+ depth = window.history.state._depth;
50
+ }
51
+ if (!isServer) {
52
+ saveCurrentDepth();
53
+ }
54
+ function keepDepth(state) {
55
+ return {
56
+ ...state,
57
+ _depth: window.history.state && window.history.state._depth
58
+ };
59
+ }
60
+ function notifyIfNotBlocked(notify, block) {
61
+ let ignore = false;
62
+ return () => {
63
+ const prevDepth = depth;
64
+ saveCurrentDepth();
65
+ const delta = prevDepth == null ? null : depth - prevDepth;
66
+ if (ignore) {
67
+ ignore = false;
68
+ return;
69
+ }
70
+ if (delta && block(delta)) {
71
+ ignore = true;
72
+ window.history.go(-delta);
73
+ } else {
74
+ notify();
75
+ }
76
+ };
77
+ }
78
+
39
79
  const hasSchemeRegex = /^(?:[a-z0-9]+:)?\/\//i;
40
80
  const trimPathRegex = /^\/+|(\/)\/+$/g;
81
+ const mockBase = "http://sr";
41
82
  const redirectStatusCodes = new Set([204, 301, 302, 303, 307, 308]);
42
83
  function normalizePath(path, omitSlash = false) {
43
84
  const s = path.replace(trimPathRegex, "$1");
@@ -257,7 +298,7 @@ function createRoutes(routeDef, base = "") {
257
298
  const path = joinPaths(base, originalPath);
258
299
  let pattern = isLeaf ? path : path.split("/*", 1)[0];
259
300
  pattern = pattern.split("/").map(s => {
260
- return s.startsWith(':') || s.startsWith('*') ? s : encodeURIComponent(s);
301
+ return s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s);
261
302
  }).join("/");
262
303
  acc.push({
263
304
  ...shared,
@@ -327,7 +368,7 @@ function getRouteMatches(branches, location) {
327
368
  return [];
328
369
  }
329
370
  function createLocation(path, state) {
330
- const origin = new URL("http://sar");
371
+ const origin = new URL(mockBase);
331
372
  const url = createMemo(prev => {
332
373
  const path_ = path();
333
374
  try {
@@ -443,7 +484,7 @@ function createRouterContext(integration, getBranches, options = {}) {
443
484
  untrack(() => {
444
485
  if (typeof to === "number") {
445
486
  if (!to) ; else if (utils.go) {
446
- beforeLeave.confirm(to, options) && utils.go(to);
487
+ utils.go(to);
447
488
  } else {
448
489
  console.warn("Router integration does not support relative routing");
449
490
  }
@@ -470,12 +511,12 @@ function createRouterContext(integration, getBranches, options = {}) {
470
511
  if (resolvedTo !== current || nextState !== state()) {
471
512
  if (isServer) {
472
513
  const e = getRequestEvent();
473
- e && (e.response = new Response(null, {
514
+ e && (e.response = {
474
515
  status: 302,
475
- headers: {
516
+ headers: new Headers({
476
517
  Location: resolvedTo
477
- }
478
- }));
518
+ })
519
+ });
479
520
  setSource({
480
521
  value: resolvedTo,
481
522
  replace,
@@ -553,7 +594,7 @@ function createRouterContext(integration, getBranches, options = {}) {
553
594
  }
554
595
  function initFromFlash() {
555
596
  const e = getRequestEvent();
556
- return e && e.initialSubmission ? [e.initialSubmission] : [];
597
+ return e && e.router && e.router.submission ? [e.router.submission] : [];
557
598
  }
558
599
  }
559
600
  function createRouteContext(router, parent, outlet, match, params) {
@@ -622,7 +663,7 @@ function Routes(props) {
622
663
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
623
664
  if (isServer) {
624
665
  const e = getRequestEvent();
625
- e && (e.routerMatches || (e.routerMatches = [])).push(matches().map(({
666
+ e && ((e.router || (e.router = {})).matches || (e.router.matches = matches().map(({
626
667
  route,
627
668
  path,
628
669
  params
@@ -632,7 +673,7 @@ function Routes(props) {
632
673
  match: path,
633
674
  params,
634
675
  metadata: route.metadata
635
- })));
676
+ }))));
636
677
  }
637
678
  const params = createMemoObject(() => {
638
679
  const m = matches();
@@ -779,7 +820,7 @@ if (!isServer) {
779
820
  setInterval(() => {
780
821
  const now = Date.now();
781
822
  for (let [k, v] of cacheMap.entries()) {
782
- if (!v[3].size && now - v[0] > CACHE_TIMEOUT) {
823
+ if (!v[3].count && now - v[0] > CACHE_TIMEOUT) {
783
824
  cacheMap.delete(k);
784
825
  }
785
826
  }
@@ -787,31 +828,29 @@ if (!isServer) {
787
828
  }
788
829
  function getCache() {
789
830
  if (!isServer) return cacheMap;
790
- const req = getRequestEvent() || sharedConfig.context;
831
+ const req = getRequestEvent();
791
832
  if (!req) throw new Error("Cannot find cache context");
792
- return req.routerCache || (req.routerCache = new Map());
833
+ return (req.router || (req.router = {})).cache || (req.router.cache = new Map());
793
834
  }
794
835
  function revalidate(key, force = true) {
795
836
  return startTransition(() => {
796
837
  const now = Date.now();
797
838
  cacheKeyOp(key, entry => {
798
839
  force && (entry[0] = 0); //force cache miss
799
- revalidateSignals(entry[3], now); // retrigger live signals
840
+ entry[3][1](now); // retrigger live signals
800
841
  });
801
842
  });
802
843
  }
803
-
804
844
  function cacheKeyOp(key, fn) {
805
845
  key && !Array.isArray(key) && (key = [key]);
806
846
  for (let k of cacheMap.keys()) {
807
847
  if (key === undefined || matchKey(k, key)) fn(cacheMap.get(k));
808
848
  }
809
849
  }
810
- function revalidateSignals(set, time) {
811
- for (let s of set) s[1](time);
812
- }
813
850
  function cache(fn, name, options) {
814
851
  const [store, setStore] = createStore({});
852
+ // prioritize GET for server functions
853
+ if (fn.GET) fn = fn.GET;
815
854
  const cachedFn = (...args) => {
816
855
  const cache = getCache();
817
856
  const intent = getIntent();
@@ -820,27 +859,24 @@ function cache(fn, name, options) {
820
859
  const now = Date.now();
821
860
  const key = name + hashKey(args);
822
861
  let cached = cache.get(key);
823
- let version;
824
- if (owner && !isServer) {
825
- version = createSignal(now, {
826
- equals: (p, v) => v - p < 50 // margin of error
827
- });
828
-
829
- onCleanup(() => cached[3].delete(version));
830
- version[0](); // track it;
862
+ let tracking;
863
+ if (getListener() && !isServer) {
864
+ tracking = true;
865
+ onCleanup(() => cached[3].count--);
831
866
  }
832
-
833
- if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
834
- version && cached[3].add(version);
867
+ if (cached && (isServer || intent === "native" || cached[0] && cached[3].count || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
868
+ if (tracking) {
869
+ cached[3].count++;
870
+ cached[3][0](); // track
871
+ }
835
872
  if (cached[2] === "preload" && intent !== "preload") {
836
873
  cached[0] = now;
837
874
  }
838
875
  let res = cached[1];
839
876
  if (intent !== "preload") {
840
877
  res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
841
- !isServer && intent === "navigate" && startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
878
+ !isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
842
879
  }
843
-
844
880
  return res;
845
881
  }
846
882
  let res = !isServer && sharedConfig.context && sharedConfig.load ? sharedConfig.load(key) // hydrating
@@ -855,31 +891,38 @@ function cache(fn, name, options) {
855
891
  cached[0] = now;
856
892
  cached[1] = res;
857
893
  cached[2] = intent;
858
- version && cached[3].add(version);
859
- if (!isServer && intent === "navigate") {
860
- startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
861
- }
862
- } else cache.set(key, cached = [now, res, intent, new Set(version ? [version] : [])]);
894
+ !isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
895
+ } else {
896
+ cache.set(key, cached = [now, res, intent, createSignal(now)]);
897
+ cached[3].count = 0;
898
+ }
899
+ if (tracking) {
900
+ cached[3].count++;
901
+ cached[3][0](); // track
902
+ }
863
903
  if (intent !== "preload") {
864
904
  res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res);
865
905
  }
866
906
  return res;
867
907
  function handleResponse(error) {
868
- return v => {
869
- if (v instanceof Response && redirectStatusCodes.has(v.status)) {
870
- if (navigate) {
871
- startTransition(() => {
872
- let url = v.headers.get(LocationHeader);
873
- if (url && url.startsWith("/")) {
874
- navigate(url, {
875
- replace: true
876
- });
877
- } else if (!isServer && url) {
878
- window.location.href = url;
879
- }
880
- });
908
+ return async v => {
909
+ if (v instanceof Response) {
910
+ if (redirectStatusCodes.has(v.status)) {
911
+ if (navigate) {
912
+ startTransition(() => {
913
+ let url = v.headers.get(LocationHeader);
914
+ if (url && url.startsWith("/")) {
915
+ navigate(url, {
916
+ replace: true
917
+ });
918
+ } else if (!isServer && url) {
919
+ window.location.href = url;
920
+ }
921
+ });
922
+ }
923
+ return;
881
924
  }
882
- return;
925
+ if (v.customBody) v = await v.customBody();
883
926
  }
884
927
  if (error) throw v;
885
928
  if (isServer) return v;
@@ -896,20 +939,14 @@ cache.set = (key, value) => {
896
939
  const cache = getCache();
897
940
  const now = Date.now();
898
941
  let cached = cache.get(key);
899
- let version;
900
- if (getOwner()) {
901
- version = createSignal(now, {
902
- equals: (p, v) => v - p < 50 // margin of error
903
- });
904
-
905
- onCleanup(() => cached[3].delete(version));
906
- }
907
942
  if (cached) {
908
943
  cached[0] = now;
909
944
  cached[1] = value;
910
945
  cached[2] = "preload";
911
- version && cached[3].add(version);
912
- } else cache.set(key, cached = [now, value,, new Set(version ? [version] : [])]);
946
+ } else {
947
+ cache.set(key, cached = [now, value,, createSignal(now)]);
948
+ cached[3].count = 0;
949
+ }
913
950
  };
914
951
  cache.clear = () => getCache().clear();
915
952
  function matchKey(key, keys) {
@@ -1003,7 +1040,7 @@ function toAction(fn, url) {
1003
1040
  const newFn = function (...passedArgs) {
1004
1041
  return fn.call(this, ...args, ...passedArgs);
1005
1042
  };
1006
- const uri = new URL(url, "http://sar");
1043
+ const uri = new URL(url, mockBase);
1007
1044
  uri.searchParams.set("args", hashKey(args));
1008
1045
  return toAction(newFn, (uri.origin === "https://action" ? uri.origin : "") + uri.pathname + uri.search);
1009
1046
  };
@@ -1048,7 +1085,7 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
1048
1085
  function handleAnchor(evt) {
1049
1086
  if (evt.defaultPrevented || evt.button !== 0 || evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
1050
1087
  const a = evt.composedPath().find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
1051
- if (!a || explicitLinks && !a.getAttribute("link")) return;
1088
+ if (!a || explicitLinks && !a.hasAttribute("link")) return;
1052
1089
  const svg = isSvg(a);
1053
1090
  const href = svg ? a.href.baseVal : a.href;
1054
1091
  const target = svg ? a.target.baseVal : a.target;
@@ -1099,10 +1136,11 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
1099
1136
  }
1100
1137
  }
1101
1138
  function handleFormSubmit(evt) {
1102
- let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.formAction : evt.target.action;
1139
+ let actionRef = evt.submitter && evt.submitter.hasAttribute("formaction") ? evt.submitter.getAttribute("formaction") : evt.target.getAttribute("action");
1103
1140
  if (!actionRef) return;
1104
1141
  if (!actionRef.startsWith("https://action/")) {
1105
- const url = new URL(actionRef);
1142
+ // normalize server actions
1143
+ const url = new URL(actionRef, mockBase);
1106
1144
  actionRef = router.parsePath(url.pathname + url.search);
1107
1145
  if (!actionRef.startsWith(actionBase)) return;
1108
1146
  }
@@ -1141,11 +1179,13 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
1141
1179
 
1142
1180
  function Router(props) {
1143
1181
  if (isServer) return StaticRouter(props);
1182
+ const getSource = () => ({
1183
+ value: window.location.pathname + window.location.search + window.location.hash,
1184
+ state: window.history.state
1185
+ });
1186
+ const beforeLeave = createBeforeLeave();
1144
1187
  return createRouter({
1145
- get: () => ({
1146
- value: window.location.pathname + window.location.search + window.location.hash,
1147
- state: history.state
1148
- }),
1188
+ get: getSource,
1149
1189
  set({
1150
1190
  value,
1151
1191
  replace,
@@ -1153,16 +1193,27 @@ function Router(props) {
1153
1193
  state
1154
1194
  }) {
1155
1195
  if (replace) {
1156
- window.history.replaceState(state, "", value);
1196
+ window.history.replaceState(keepDepth(state), "", value);
1157
1197
  } else {
1158
1198
  window.history.pushState(state, "", value);
1159
1199
  }
1160
1200
  scrollToHash(window.location.hash.slice(1), scroll);
1201
+ saveCurrentDepth();
1161
1202
  },
1162
- init: notify => bindEvent(window, "popstate", () => notify()),
1203
+ init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
1204
+ if (delta && delta < 0) {
1205
+ return !beforeLeave.confirm(delta);
1206
+ } else {
1207
+ const s = getSource();
1208
+ return !beforeLeave.confirm(s.value, {
1209
+ state: s.state
1210
+ });
1211
+ }
1212
+ })),
1163
1213
  create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
1164
1214
  utils: {
1165
- go: delta => window.history.go(delta)
1215
+ go: delta => window.history.go(delta),
1216
+ beforeLeave
1166
1217
  }
1167
1218
  })(props);
1168
1219
  }
@@ -1179,8 +1230,10 @@ function hashParser(str) {
1179
1230
  return to;
1180
1231
  }
1181
1232
  function HashRouter(props) {
1233
+ const getSource = () => window.location.hash.slice(1);
1234
+ const beforeLeave = createBeforeLeave();
1182
1235
  return createRouter({
1183
- get: () => window.location.hash.slice(1),
1236
+ get: getSource,
1184
1237
  set({
1185
1238
  value,
1186
1239
  replace,
@@ -1188,20 +1241,22 @@ function HashRouter(props) {
1188
1241
  state
1189
1242
  }) {
1190
1243
  if (replace) {
1191
- window.history.replaceState(state, "", "#" + value);
1244
+ window.history.replaceState(keepDepth(state), "", "#" + value);
1192
1245
  } else {
1193
1246
  window.location.hash = value;
1194
1247
  }
1195
1248
  const hashIndex = value.indexOf("#");
1196
1249
  const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
1197
1250
  scrollToHash(hash, scroll);
1251
+ saveCurrentDepth();
1198
1252
  },
1199
- init: notify => bindEvent(window, "hashchange", () => notify()),
1253
+ init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
1200
1254
  create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
1201
1255
  utils: {
1202
1256
  go: delta => window.history.go(delta),
1203
1257
  renderPath: path => `#${path}`,
1204
- parsePath: hashParser
1258
+ parsePath: hashParser,
1259
+ beforeLeave
1205
1260
  }
1206
1261
  })(props);
1207
1262
  }
@@ -1273,10 +1328,10 @@ function A(props) {
1273
1328
  const location = useLocation();
1274
1329
  const isActive = createMemo(() => {
1275
1330
  const to_ = to();
1276
- if (to_ === undefined) return false;
1331
+ if (to_ === undefined) return [false, false];
1277
1332
  const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase();
1278
1333
  const loc = normalizePath(location.pathname).toLowerCase();
1279
- return props.end ? path === loc : loc.startsWith(path);
1334
+ return [props.end ? path === loc : loc.startsWith(path), path === loc];
1280
1335
  });
1281
1336
  return (() => {
1282
1337
  const _el$ = _tmpl$();
@@ -1292,14 +1347,14 @@ function A(props) {
1292
1347
  ...(props.class && {
1293
1348
  [props.class]: true
1294
1349
  }),
1295
- [props.inactiveClass]: !isActive(),
1296
- [props.activeClass]: isActive(),
1350
+ [props.inactiveClass]: !isActive()[0],
1351
+ [props.activeClass]: isActive()[0],
1297
1352
  ...rest.classList
1298
1353
  };
1299
1354
  },
1300
1355
  "link": "",
1301
1356
  get ["aria-current"]() {
1302
- return isActive() ? "page" : undefined;
1357
+ return isActive()[1] ? "page" : undefined;
1303
1358
  }
1304
1359
  }), false, false);
1305
1360
  return _el$;
@@ -1412,5 +1467,15 @@ function reload(init) {
1412
1467
  } : {})
1413
1468
  });
1414
1469
  }
1470
+ function json(data, init) {
1471
+ const headers = new Headers((init || {}).headers);
1472
+ headers.set("Content-Type", "application/json");
1473
+ const response = new Response(JSON.stringify(data), {
1474
+ ...init,
1475
+ headers
1476
+ });
1477
+ response.customBody = () => data;
1478
+ return response;
1479
+ }
1415
1480
 
1416
- export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createMemoryHistory, createRouter, redirect, reload, revalidate, useAction, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
1481
+ export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
package/dist/index.jsx CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./routers";
2
- export * from "./components";
3
- export * from "./lifecycle";
4
- export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
5
- export { mergeSearchString as _mergeSearchString } from "./utils";
6
- export * from "./data";
1
+ export * from "./routers/index.js";
2
+ export * from "./components.jsx";
3
+ export * from "./lifecycle.js";
4
+ export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing.js";
5
+ export { mergeSearchString as _mergeSearchString } from "./utils.js";
6
+ export * from "./data/index.js";
@@ -1,2 +1,5 @@
1
- import { BeforeLeaveLifecycle } from "./types";
1
+ import { BeforeLeaveLifecycle, LocationChange } from "./types.js";
2
2
  export declare function createBeforeLeave(): BeforeLeaveLifecycle;
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,3 +1,4 @@
1
+ import { isServer } from "solid-js/web";
1
2
  export function createBeforeLeave() {
2
3
  let listeners = new Set();
3
4
  function subscribe(listener) {
@@ -30,3 +31,39 @@ export function createBeforeLeave() {
30
31
  confirm
31
32
  };
32
33
  }
34
+ // The following supports browser initiated blocking (eg back/forward)
35
+ let depth;
36
+ export function saveCurrentDepth() {
37
+ if (!window.history.state || window.history.state._depth == null) {
38
+ window.history.replaceState({ ...window.history.state, _depth: window.history.length - 1 }, "");
39
+ }
40
+ depth = window.history.state._depth;
41
+ }
42
+ if (!isServer) {
43
+ saveCurrentDepth();
44
+ }
45
+ export function keepDepth(state) {
46
+ return {
47
+ ...state,
48
+ _depth: window.history.state && window.history.state._depth
49
+ };
50
+ }
51
+ export function notifyIfNotBlocked(notify, block) {
52
+ let ignore = false;
53
+ return () => {
54
+ const prevDepth = depth;
55
+ saveCurrentDepth();
56
+ const delta = prevDepth == null ? null : depth - prevDepth;
57
+ if (ignore) {
58
+ ignore = false;
59
+ return;
60
+ }
61
+ if (delta && block(delta)) {
62
+ ignore = true;
63
+ window.history.go(-delta);
64
+ }
65
+ else {
66
+ notify();
67
+ }
68
+ };
69
+ }
@@ -1,5 +1,5 @@
1
1
  import type { JSX } from "solid-js";
2
- import type { BaseRouterProps } from "./components";
2
+ import type { BaseRouterProps } from "./components.js";
3
3
  export declare function hashParser(str: string): string;
4
4
  export type HashRouterProps = BaseRouterProps & {
5
5
  actionBase?: string;
@@ -1,5 +1,6 @@
1
- import { setupNativeEvents } from "../data/events";
2
- import { createRouter, scrollToHash, bindEvent } from "./createRouter";
1
+ import { setupNativeEvents } from "../data/events.js";
2
+ import { createRouter, scrollToHash, bindEvent } from "./createRouter.js";
3
+ import { createBeforeLeave, keepDepth, notifyIfNotBlocked, saveCurrentDepth } from "../lifecycle.js";
3
4
  export function hashParser(str) {
4
5
  const to = str.replace(/^.*?#/, "");
5
6
  // Hash-only hrefs like `#foo` from plain anchors will come in as `/#foo` whereas a link to
@@ -12,11 +13,13 @@ export function hashParser(str) {
12
13
  return to;
13
14
  }
14
15
  export function HashRouter(props) {
16
+ const getSource = () => window.location.hash.slice(1);
17
+ const beforeLeave = createBeforeLeave();
15
18
  return createRouter({
16
- get: () => window.location.hash.slice(1),
19
+ get: getSource,
17
20
  set({ value, replace, scroll, state }) {
18
21
  if (replace) {
19
- window.history.replaceState(state, "", "#" + value);
22
+ window.history.replaceState(keepDepth(state), "", "#" + value);
20
23
  }
21
24
  else {
22
25
  window.location.hash = value;
@@ -24,13 +27,15 @@ export function HashRouter(props) {
24
27
  const hashIndex = value.indexOf("#");
25
28
  const hash = hashIndex >= 0 ? value.slice(hashIndex + 1) : "";
26
29
  scrollToHash(hash, scroll);
30
+ saveCurrentDepth();
27
31
  },
28
- init: notify => bindEvent(window, "hashchange", () => notify()),
32
+ init: notify => bindEvent(window, "hashchange", notifyIfNotBlocked(notify, delta => !beforeLeave.confirm(delta && delta < 0 ? delta : getSource()))),
29
33
  create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
30
34
  utils: {
31
35
  go: delta => window.history.go(delta),
32
36
  renderPath: path => `#${path}`,
33
- parsePath: hashParser
37
+ parsePath: hashParser,
38
+ beforeLeave
34
39
  }
35
40
  })(props);
36
41
  }
@@ -1,5 +1,5 @@
1
- import type { LocationChange } from "../types";
2
- import type { BaseRouterProps } from "./components";
1
+ import type { LocationChange } from "../types.js";
2
+ import type { BaseRouterProps } from "./components.jsx";
3
3
  import type { JSX } from "solid-js";
4
4
  export type MemoryHistory = {
5
5
  get: () => string;
@@ -1,4 +1,4 @@
1
- import { createRouter, scrollToHash } from "./createRouter";
1
+ import { createRouter, scrollToHash } from "./createRouter.js";
2
2
  export function createMemoryHistory() {
3
3
  const entries = ["/"];
4
4
  let index = 0;
@@ -1,4 +1,4 @@
1
- import type { BaseRouterProps } from "./components";
1
+ import type { BaseRouterProps } from "./components.jsx";
2
2
  import type { JSX } from "solid-js";
3
3
  export type RouterProps = BaseRouterProps & {
4
4
  url?: string;
@@ -1,28 +1,41 @@
1
1
  import { isServer } from "solid-js/web";
2
- import { createRouter, scrollToHash, bindEvent } from "./createRouter";
3
- import { StaticRouter } from "./StaticRouter";
4
- import { setupNativeEvents } from "../data/events";
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";
5
6
  export function Router(props) {
6
7
  if (isServer)
7
8
  return StaticRouter(props);
9
+ const getSource = () => ({
10
+ value: window.location.pathname + window.location.search + window.location.hash,
11
+ state: window.history.state
12
+ });
13
+ const beforeLeave = createBeforeLeave();
8
14
  return createRouter({
9
- get: () => ({
10
- value: window.location.pathname + window.location.search + window.location.hash,
11
- state: history.state
12
- }),
15
+ get: getSource,
13
16
  set({ value, replace, scroll, state }) {
14
17
  if (replace) {
15
- window.history.replaceState(state, "", value);
18
+ window.history.replaceState(keepDepth(state), "", value);
16
19
  }
17
20
  else {
18
21
  window.history.pushState(state, "", value);
19
22
  }
20
23
  scrollToHash(window.location.hash.slice(1), scroll);
24
+ saveCurrentDepth();
21
25
  },
22
- init: notify => bindEvent(window, "popstate", () => notify()),
26
+ init: notify => bindEvent(window, "popstate", notifyIfNotBlocked(notify, delta => {
27
+ if (delta && delta < 0) {
28
+ return !beforeLeave.confirm(delta);
29
+ }
30
+ else {
31
+ const s = getSource();
32
+ return !beforeLeave.confirm(s.value, { state: s.state });
33
+ }
34
+ })),
23
35
  create: setupNativeEvents(props.preload, props.explicitLinks, props.actionBase),
24
36
  utils: {
25
- go: delta => window.history.go(delta)
37
+ go: delta => window.history.go(delta),
38
+ beforeLeave
26
39
  }
27
40
  })(props);
28
41
  }
@@ -1,4 +1,4 @@
1
- import { type BaseRouterProps } from "./components";
1
+ import { type BaseRouterProps } from "./components.jsx";
2
2
  import type { JSX } from "solid-js";
3
3
  export type StaticRouterProps = BaseRouterProps & {
4
4
  url?: string;
@@ -1,5 +1,5 @@
1
1
  import { getRequestEvent } from "solid-js/web";
2
- import { createRouterComponent } from "./components";
2
+ import { createRouterComponent } from "./components.jsx";
3
3
  function getPath(url) {
4
4
  const u = new URL(url);
5
5
  return u.pathname + u.search;