UNPKG

3.76 kBJavaScriptView Raw
1import queryString from 'query-string';
2import Router from 'abstract-nested-router';
3import { writable } from 'svelte/store';
4
5import {
6 ROOT_URL, hashchangeEnable, navigateTo, isActive, router,
7} from './utils';
8
9export const baseRouter = new Router();
10export const routeInfo = writable({});
11
12// private registries
13const onError = {};
14const shared = {};
15
16let errors = [];
17let routers = 0;
18let interval;
19
20// take snapshot from current state...
21router.subscribe(value => { shared.router = value; });
22routeInfo.subscribe(value => { shared.routeInfo = value; });
23
24export function doFallback(failure, fallback) {
25 routeInfo.update(defaults => ({
26 ...defaults,
27 [fallback]: {
28 ...shared.router,
29 failure,
30 },
31 }));
32}
33
34export function handleRoutes(map, params) {
35 const keys = [];
36
37 map.some(x => {
38 if (x.key && x.matches && !x.fallback && !shared.routeInfo[x.key]) {
39 if (x.redirect && (x.condition === null || x.condition(shared.router) !== true)) {
40 if (x.exact && shared.router.path !== x.path) return false;
41 navigateTo(x.redirect);
42 return true;
43 }
44
45 if (x.exact) {
46 keys.push(x.key);
47 }
48
49 // extend shared params...
50 Object.assign(params, x.params);
51
52 // upgrade matching routes!
53 routeInfo.update(defaults => ({
54 ...defaults,
55 [x.key]: {
56 ...shared.router,
57 ...x,
58 },
59 }));
60 }
61
62 return false;
63 });
64
65 return keys;
66}
67
68export function evtHandler() {
69 let baseUri = !hashchangeEnable() ? window.location.href.replace(window.location.origin, '') : window.location.hash || '/';
70 let failure;
71
72 // unprefix active URL
73 if (ROOT_URL !== '/') {
74 baseUri = baseUri.replace(ROOT_URL, '');
75 }
76
77 const [fullpath, qs] = baseUri.replace('/#', '#').replace(/^#\//, '/').split('?');
78 const query = queryString.parse(qs);
79 const params = {};
80 const keys = [];
81
82 // reset current state
83 routeInfo.set({});
84 router.set({
85 query,
86 params,
87 path: fullpath,
88 });
89
90 // load all matching routes...
91 baseRouter.resolve(fullpath, (err, result) => {
92 if (err) {
93 failure = err;
94 return;
95 }
96
97 // save exact-keys for deletion after failures!
98 keys.push(...handleRoutes(result, params));
99 });
100
101 const toDelete = {};
102
103 if (failure) {
104 keys.reduce((prev, cur) => {
105 prev[cur] = null;
106 return prev;
107 }, toDelete);
108 }
109
110 // clear previously failed handlers
111 errors.forEach(cb => cb());
112 errors = [];
113
114 try {
115 // clear routes that not longer matches!
116 baseRouter.find(fullpath).forEach(sub => {
117 if (sub.exact && !sub.matches) {
118 toDelete[sub.key] = null;
119 }
120 });
121 } catch (e) {
122 // this is fine
123 }
124
125 // drop unwanted routes...
126 routeInfo.update(defaults => ({
127 ...defaults,
128 ...toDelete,
129 }));
130
131 let fallback;
132
133 // invoke error-handlers to clear out previous state!
134 Object.keys(onError).forEach(root => {
135 if (isActive(root, fullpath, false)) {
136 const fn = onError[root].callback;
137
138 fn(failure);
139 errors.push(fn);
140 }
141
142 if (!fallback && onError[root].fallback) {
143 fallback = onError[root].fallback;
144 }
145 });
146
147 // handle unmatched fallbacks
148 if (failure && fallback) {
149 doFallback(failure, fallback);
150 }
151}
152
153export function findRoutes() {
154 clearTimeout(interval);
155 interval = setTimeout(evtHandler);
156}
157
158export function addRouter(root, fallback, callback) {
159 if (!routers) {
160 window.addEventListener('popstate', findRoutes, false);
161 }
162
163 // register error-handlers
164 onError[root] = { fallback, callback };
165 routers += 1;
166
167 return () => {
168 delete onError[root];
169 routers -= 1;
170
171 if (!routers) {
172 window.removeEventListener('popstate', findRoutes, false);
173 }
174 };
175}