1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
6 |
|
7 | var interactions = require('@curi/interactions');
|
8 | var PathToRegexp = _interopDefault(require('path-to-regexp'));
|
9 |
|
10 | function isAsyncRoute(route) {
|
11 | return typeof route.methods.resolve !== "undefined";
|
12 | }
|
13 | function isExternalRedirect(redirect) {
|
14 | return "externalURL" in redirect;
|
15 | }
|
16 | function isRedirectLocation(redirect) {
|
17 | return "url" in redirect;
|
18 | }
|
19 |
|
20 | function finishResponse(route, match, resolvedResults, router, external) {
|
21 | var _a = resolvedResults || {}, _b = _a.resolved, resolved = _b === void 0 ? null : _b, _c = _a.error, error = _c === void 0 ? null : _c;
|
22 | var response = {
|
23 | data: undefined,
|
24 | body: undefined,
|
25 | meta: undefined
|
26 | };
|
27 | for (var key in match) {
|
28 | response[key] = match[key];
|
29 | }
|
30 | if (!route.methods.respond) {
|
31 | return response;
|
32 | }
|
33 | var results = route.methods.respond({
|
34 | resolved: resolved,
|
35 | error: error,
|
36 | match: match,
|
37 | external: external
|
38 | });
|
39 | if (!results) {
|
40 | if (process.env.NODE_ENV !== "production") {
|
41 | console.warn("\"" + match.name + "\"'s response function did not return anything. Did you forget to include a return statement?");
|
42 | }
|
43 | return response;
|
44 | }
|
45 | if (process.env.NODE_ENV !== "production") {
|
46 | var validProperties_1 = {
|
47 | meta: true,
|
48 | body: true,
|
49 | data: true,
|
50 | redirect: true
|
51 | };
|
52 | Object.keys(results).forEach(function (property) {
|
53 | if (!validProperties_1.hasOwnProperty(property)) {
|
54 | console.warn("\"" + property + "\" is not a valid response property. The valid properties are:\n\n " + Object.keys(validProperties_1).join(", "));
|
55 | }
|
56 | });
|
57 | }
|
58 | response["meta"] = results["meta"];
|
59 | response["body"] = results["body"];
|
60 | response["data"] = results["data"];
|
61 | if (results["redirect"]) {
|
62 | response["redirect"] = createRedirect(results["redirect"], router);
|
63 | }
|
64 | return response;
|
65 | }
|
66 | function createRedirect(redirect, router) {
|
67 | if (isExternalRedirect(redirect)) {
|
68 | return redirect;
|
69 | }
|
70 | var name = redirect.name, params = redirect.params, query = redirect.query, hash = redirect.hash, state = redirect.state;
|
71 | var url = isRedirectLocation(redirect)
|
72 | ? redirect.url
|
73 | : router.url({ name: name, params: params, query: query, hash: hash });
|
74 | return {
|
75 | name: name,
|
76 | params: params,
|
77 | query: query,
|
78 | hash: hash,
|
79 | state: state,
|
80 | url: url
|
81 | };
|
82 | }
|
83 |
|
84 | function createRouter(historyConstructor, routes, options) {
|
85 | if (options === void 0) { options = {}; }
|
86 | var latestResponse;
|
87 | var latestNavigation;
|
88 | var history = historyConstructor(function (pendingNav) {
|
89 | var navigation = {
|
90 | action: pendingNav.action,
|
91 | previous: latestResponse
|
92 | };
|
93 | var matched = routes.match(pendingNav.location);
|
94 | if (!matched) {
|
95 | if (process.env.NODE_ENV !== "production") {
|
96 | console.warn("The current location (" + pendingNav.location.pathname + ") has no matching route, " +
|
97 | 'so a response could not be emitted. A catch-all route ({ path: "(.*)" }) ' +
|
98 | "can be used to match locations with no other matching route.");
|
99 | }
|
100 | pendingNav.finish();
|
101 | finishAndResetNavCallbacks();
|
102 | return;
|
103 | }
|
104 | var route = matched.route, match = matched.match;
|
105 | if (!isAsyncRoute(route)) {
|
106 | finalizeResponseAndEmit(route, match, pendingNav, navigation, null);
|
107 | }
|
108 | else {
|
109 | announceAsyncNav();
|
110 | route.methods
|
111 | .resolve(match, options.external)
|
112 | .then(function (resolved) { return ({ resolved: resolved, error: null }); }, function (error) { return ({ error: error, resolved: null }); })
|
113 | .then(function (resolved) {
|
114 | if (pendingNav.cancelled) {
|
115 | return;
|
116 | }
|
117 | finalizeResponseAndEmit(route, match, pendingNav, navigation, resolved);
|
118 | });
|
119 | }
|
120 | }, options.history || {});
|
121 | function finalizeResponseAndEmit(route, match, pending, navigation, resolved) {
|
122 | asyncNavComplete();
|
123 | pending.finish();
|
124 | var response = finishResponse(route, match, resolved, router, options.external);
|
125 | finishAndResetNavCallbacks();
|
126 | emitImmediate(response, navigation);
|
127 | }
|
128 | var _a = options.invisibleRedirects, invisibleRedirects = _a === void 0 ? false : _a;
|
129 | function emitImmediate(response, navigation) {
|
130 | if (!response.redirect ||
|
131 | !invisibleRedirects ||
|
132 | isExternalRedirect(response.redirect)) {
|
133 | latestResponse = response;
|
134 | latestNavigation = navigation;
|
135 | var emit = { response: response, navigation: navigation, router: router };
|
136 | callObservers(emit);
|
137 | callOneTimersAndSideEffects(emit);
|
138 | }
|
139 | if (response.redirect !== undefined &&
|
140 | !isExternalRedirect(response.redirect)) {
|
141 | history.navigate(response.redirect, "replace");
|
142 | }
|
143 | }
|
144 | function callObservers(emitted) {
|
145 | observers.forEach(function (fn) {
|
146 | fn(emitted);
|
147 | });
|
148 | }
|
149 | function callOneTimersAndSideEffects(emitted) {
|
150 | oneTimers.splice(0).forEach(function (fn) {
|
151 | fn(emitted);
|
152 | });
|
153 | if (options.sideEffects) {
|
154 | options.sideEffects.forEach(function (fn) {
|
155 | fn(emitted);
|
156 | });
|
157 | }
|
158 | }
|
159 |
|
160 | var observers = [];
|
161 | var oneTimers = [];
|
162 | function observe(fn, options) {
|
163 | var _a = (options || {}).initial, initial = _a === void 0 ? true : _a;
|
164 | observers.push(fn);
|
165 | if (latestResponse && initial) {
|
166 | fn({
|
167 | response: latestResponse,
|
168 | navigation: latestNavigation,
|
169 | router: router
|
170 | });
|
171 | }
|
172 | return function () {
|
173 | observers = observers.filter(function (obs) {
|
174 | return obs !== fn;
|
175 | });
|
176 | };
|
177 | }
|
178 | function once(fn, options) {
|
179 | var _a = (options || {}).initial, initial = _a === void 0 ? true : _a;
|
180 | if (latestResponse && initial) {
|
181 | fn({
|
182 | response: latestResponse,
|
183 | navigation: latestNavigation,
|
184 | router: router
|
185 | });
|
186 | }
|
187 | else {
|
188 | oneTimers.push(fn);
|
189 | }
|
190 | }
|
191 |
|
192 | function url(details) {
|
193 | var name = details.name, params = details.params, hash = details.hash, query = details.query;
|
194 | var pathname;
|
195 | if (name) {
|
196 | var route = router.route(name);
|
197 | if (route) {
|
198 | pathname = interactions.pathname(route, params);
|
199 | }
|
200 | }
|
201 | return history.url({ pathname: pathname, hash: hash, query: query });
|
202 | }
|
203 |
|
204 | var cancelCallback;
|
205 | var finishCallback;
|
206 | function navigate(details) {
|
207 | cancelAndResetNavCallbacks();
|
208 | var url = details.url, state = details.state, method = details.method;
|
209 | history.navigate({ url: url, state: state }, method);
|
210 | if (details.cancelled || details.finished) {
|
211 | cancelCallback = details.cancelled;
|
212 | finishCallback = details.finished;
|
213 | return resetCallbacks;
|
214 | }
|
215 | }
|
216 | function cancelAndResetNavCallbacks() {
|
217 | if (cancelCallback) {
|
218 | cancelCallback();
|
219 | }
|
220 | resetCallbacks();
|
221 | }
|
222 | function finishAndResetNavCallbacks() {
|
223 | if (finishCallback) {
|
224 | finishCallback();
|
225 | }
|
226 | resetCallbacks();
|
227 | }
|
228 | function resetCallbacks() {
|
229 | cancelCallback = undefined;
|
230 | finishCallback = undefined;
|
231 | }
|
232 |
|
233 | var cancelWith;
|
234 | var asyncNavNotifiers = [];
|
235 | function cancel(fn) {
|
236 | asyncNavNotifiers.push(fn);
|
237 | return function () {
|
238 | asyncNavNotifiers = asyncNavNotifiers.filter(function (can) {
|
239 | return can !== fn;
|
240 | });
|
241 | };
|
242 | }
|
243 |
|
244 |
|
245 | function announceAsyncNav() {
|
246 | if (asyncNavNotifiers.length && cancelWith === undefined) {
|
247 | cancelWith = function () {
|
248 | history.cancel();
|
249 | asyncNavComplete();
|
250 | cancelAndResetNavCallbacks();
|
251 | };
|
252 | asyncNavNotifiers.forEach(function (fn) {
|
253 | fn(cancelWith);
|
254 | });
|
255 | }
|
256 | }
|
257 | function asyncNavComplete() {
|
258 | if (cancelWith) {
|
259 | cancelWith = undefined;
|
260 | asyncNavNotifiers.forEach(function (fn) {
|
261 | fn();
|
262 | });
|
263 | }
|
264 | }
|
265 | var router = {
|
266 | route: routes.route,
|
267 | history: history,
|
268 | external: options.external,
|
269 | observe: observe,
|
270 | once: once,
|
271 | cancel: cancel,
|
272 | url: url,
|
273 | navigate: navigate,
|
274 | current: function () {
|
275 | return {
|
276 | response: latestResponse,
|
277 | navigation: latestNavigation
|
278 | };
|
279 | },
|
280 | destroy: function () {
|
281 | history.destroy();
|
282 | }
|
283 | };
|
284 | history.current();
|
285 | return router;
|
286 | }
|
287 |
|
288 | var withLeadingSlash = function (path) {
|
289 | return path.charAt(0) === "/" ? path : "/" + path;
|
290 | };
|
291 | var withTrailingSlash = function (path) {
|
292 | return path.charAt(path.length - 1) === "/" ? path : path + "/";
|
293 | };
|
294 | var join = function (beginning, end) {
|
295 | return withTrailingSlash(beginning) + end;
|
296 | };
|
297 |
|
298 | function createRoute(props, map, parent) {
|
299 | if (parent === void 0) { parent = {
|
300 | path: "",
|
301 | keys: []
|
302 | }; }
|
303 | if (process.env.NODE_ENV !== "production") {
|
304 | if (props.name in map) {
|
305 | throw new Error("Multiple routes have the name \"" + props.name + "\". Route names must be unique.");
|
306 | }
|
307 | if (props.path.charAt(0) === "/") {
|
308 | throw new Error("Route paths cannot start with a forward slash (/). (Received \"" + props.path + "\")");
|
309 | }
|
310 | }
|
311 | var fullPath = withLeadingSlash(join(parent.path, props.path));
|
312 | var _a = props.pathOptions || {}, _b = _a.match, matchOptions = _b === void 0 ? {} : _b, _c = _a.compile, compileOptions = _c === void 0 ? {} : _c;
|
313 |
|
314 | var exact = matchOptions.end == null || matchOptions.end;
|
315 | if (props.children && props.children.length) {
|
316 | matchOptions.end = false;
|
317 | }
|
318 | var keys = [];
|
319 | var re = PathToRegexp(withLeadingSlash(props.path), keys, matchOptions);
|
320 | var keyNames = keys.map(function (key) { return key.name; });
|
321 | if (parent.keys.length) {
|
322 | keyNames = parent.keys.concat(keyNames);
|
323 | }
|
324 | var childRoutes = [];
|
325 | var children = [];
|
326 | if (props.children && props.children.length) {
|
327 | childRoutes = props.children.map(function (child) {
|
328 | return createRoute(child, map, {
|
329 | path: fullPath,
|
330 | keys: keyNames
|
331 | });
|
332 | });
|
333 | children = childRoutes.map(function (child) { return child.public; });
|
334 | }
|
335 | var compiled = PathToRegexp.compile(fullPath);
|
336 | var route = {
|
337 | public: {
|
338 | name: props.name,
|
339 | keys: keyNames,
|
340 | parent: undefined,
|
341 | children: children,
|
342 | methods: {
|
343 | resolve: props.resolve,
|
344 | respond: props.respond,
|
345 | pathname: function (params) {
|
346 | return compiled(params, compileOptions);
|
347 | }
|
348 | },
|
349 | extra: props.extra
|
350 | },
|
351 | matching: {
|
352 | re: re,
|
353 | keys: keys,
|
354 | exact: exact,
|
355 | parsers: props.params || {},
|
356 | children: childRoutes
|
357 | }
|
358 | };
|
359 | map[props.name] = route.public;
|
360 | if (childRoutes.length) {
|
361 | childRoutes.forEach(function (child) {
|
362 | child.public.parent = route.public;
|
363 | });
|
364 | }
|
365 | return route;
|
366 | }
|
367 |
|
368 | function matchLocation(location, routes) {
|
369 | for (var i = 0, len = routes.length; i < len; i++) {
|
370 | var routeMatches = matchRoute(routes[i], location.pathname);
|
371 | if (routeMatches.length) {
|
372 | return createMatch(routeMatches, location);
|
373 | }
|
374 | }
|
375 | }
|
376 | function matchRoute(route, pathname) {
|
377 | var _a = route.matching, re = _a.re, children = _a.children, exact = _a.exact;
|
378 | var regExpMatch = re.exec(pathname);
|
379 | if (!regExpMatch) {
|
380 | return [];
|
381 | }
|
382 | var matchedSegment = regExpMatch[0], parsed = regExpMatch.slice(1);
|
383 | var matches = [{ route: route, parsed: parsed }];
|
384 | var remainder = pathname.slice(matchedSegment.length);
|
385 | if (!children.length || remainder === "") {
|
386 | return matches;
|
387 | }
|
388 |
|
389 | var fullSegments = withLeadingSlash(remainder);
|
390 | for (var i = 0, length_1 = children.length; i < length_1; i++) {
|
391 | var matched = matchRoute(children[i], fullSegments);
|
392 | if (matched.length) {
|
393 | return matches.concat(matched);
|
394 | }
|
395 | }
|
396 | return exact ? [] : matches;
|
397 | }
|
398 | function createMatch(routeMatches, location) {
|
399 | var route = routeMatches[routeMatches.length - 1].route.public;
|
400 | return {
|
401 | route: route,
|
402 | match: {
|
403 | location: location,
|
404 | name: route.name,
|
405 | params: routeMatches.reduce(function (params, _a) {
|
406 | var route = _a.route, parsed = _a.parsed;
|
407 | parsed.forEach(function (param, index) {
|
408 | var name = route.matching.keys[index].name;
|
409 | var fn = route.matching.parsers[name] || decodeURIComponent;
|
410 | params[name] = fn(param);
|
411 | });
|
412 | return params;
|
413 | }, {})
|
414 | }
|
415 | };
|
416 | }
|
417 |
|
418 | function prepareRoutes(routes) {
|
419 | var mappedRoutes = {};
|
420 | var prepared = routes.map(function (route) { return createRoute(route, mappedRoutes); });
|
421 | return {
|
422 | match: function (location) {
|
423 | return matchLocation(location, prepared);
|
424 | },
|
425 | route: function (name) {
|
426 | if (process.env.NODE_ENV !== "production" && !(name in mappedRoutes)) {
|
427 | console.warn("Attempting to use route \"" + name + "\", but no route with that name exists.");
|
428 | }
|
429 | return mappedRoutes[name];
|
430 | }
|
431 | };
|
432 | }
|
433 |
|
434 | function announce(fmt, mode) {
|
435 | if (mode === void 0) { mode = "assertive"; }
|
436 | var announcer = document.createElement("div");
|
437 | announcer.setAttribute("aria-live", mode);
|
438 |
|
439 | announcer.setAttribute("style", [
|
440 | "border: 0 !important;",
|
441 | "clip: rect(1px, 1px, 1px, 1px) !important;",
|
442 | "-webkit-clip-path: inset(50%) !important;",
|
443 | "clip-path: inset(50%) !important;",
|
444 | "height: 1px !important;",
|
445 | "overflow: hidden !important;",
|
446 | "padding: 0 !important;",
|
447 | "position: absolute !important;",
|
448 | "width: 1px !important;",
|
449 | "white-space: nowrap !important;",
|
450 | "top: 0;"
|
451 | ].join(" "));
|
452 | document.body.appendChild(announcer);
|
453 | return function (emitted) {
|
454 | announcer.textContent = fmt(emitted);
|
455 | };
|
456 | }
|
457 |
|
458 | function scroll() {
|
459 | return function (_a) {
|
460 | var response = _a.response, navigation = _a.navigation;
|
461 | if (navigation.action === "pop") {
|
462 | return;
|
463 | }
|
464 |
|
465 | setTimeout(function () {
|
466 | var hash = response.location.hash;
|
467 | if (hash !== "") {
|
468 | var element = document.getElementById(hash);
|
469 | if (element && element.scrollIntoView) {
|
470 | element.scrollIntoView();
|
471 | return;
|
472 | }
|
473 | }
|
474 |
|
475 |
|
476 |
|
477 | window.scrollTo(0, 0);
|
478 | }, 0);
|
479 | };
|
480 | }
|
481 |
|
482 | function title(callback) {
|
483 | return function (emitted) {
|
484 | document.title = callback(emitted);
|
485 | };
|
486 | }
|
487 |
|
488 | exports.announce = announce;
|
489 | exports.createRouter = createRouter;
|
490 | exports.prepareRoutes = prepareRoutes;
|
491 | exports.scroll = scroll;
|
492 | exports.title = title;
|