@rxdi/router 0.7.220 → 0.7.222
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/README.md +101 -0
- package/dist/decorators.d.ts +1 -2
- package/dist/decorators.js +1 -1
- package/dist/helpers.d.ts +1 -1
- package/dist/helpers.js +10 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/injection.tokens.d.ts +23 -16
- package/dist/injection.tokens.js +3 -1
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +6 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/{slot → lib}/index.js +6 -4
- package/dist/lib/path-to-regexp.d.ts +6 -0
- package/dist/lib/path-to-regexp.js +228 -0
- package/dist/lib/resolver.d.ts +70 -0
- package/dist/lib/resolver.js +426 -0
- package/dist/{vaadin/vaadin-router.d.ts → lib/router.d.ts} +98 -201
- package/dist/lib/router.js +833 -0
- package/dist/lib/triggers.d.ts +6 -0
- package/dist/lib/triggers.js +98 -0
- package/dist/lib/types.d.ts +134 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.js +155 -0
- package/dist/not-found.component.js +3 -3
- package/dist/outlet.d.ts +7 -62
- package/dist/outlet.js +15 -84
- package/dist/router.component.d.ts +3 -3
- package/dist/router.component.js +4 -4
- package/package.json +3 -3
- package/dist/slot/index.d.ts +0 -8
- package/dist/slot/slot.d.ts +0 -11
- package/dist/slot/slot.js +0 -44
- package/dist/vaadin/vaadin-router.js +0 -2065
- package/slot/index.d.ts +0 -8
- package/slot/index.js +0 -8
- package/slot/slot.d.ts +0 -11
- package/slot/slot.js +0 -45
|
@@ -1,2065 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Router = exports.Resolver = void 0;
|
|
4
|
-
function toArray(objectOrArray) {
|
|
5
|
-
objectOrArray = objectOrArray || [];
|
|
6
|
-
return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
|
|
7
|
-
}
|
|
8
|
-
function log(msg) {
|
|
9
|
-
return `[Vaadin.Router] ${msg}`;
|
|
10
|
-
}
|
|
11
|
-
function logValue(value) {
|
|
12
|
-
if (typeof value !== 'object') {
|
|
13
|
-
return String(value);
|
|
14
|
-
}
|
|
15
|
-
const stringType = Object.prototype.toString.call(value).match(/ (.*)\]$/)[1];
|
|
16
|
-
if (stringType === 'Object' || stringType === 'Array') {
|
|
17
|
-
return `${stringType} ${JSON.stringify(value)}`;
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
return stringType;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const MODULE = 'module';
|
|
24
|
-
const NOMODULE = 'nomodule';
|
|
25
|
-
const bundleKeys = [MODULE, NOMODULE];
|
|
26
|
-
function ensureBundle(src) {
|
|
27
|
-
if (!src.match(/.+\.[m]?js$/)) {
|
|
28
|
-
throw new Error(log(`Unsupported type for bundle "${src}": .js or .mjs expected.`));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function ensureRoute(route) {
|
|
32
|
-
if (!route || !isString(route.path)) {
|
|
33
|
-
throw new Error(log(`Expected route config to be an object with a "path" string property, or an array of such objects`));
|
|
34
|
-
}
|
|
35
|
-
const bundle = route.bundle;
|
|
36
|
-
const stringKeys = ['component', 'redirect', 'bundle'];
|
|
37
|
-
if (!isFunction(route.action) &&
|
|
38
|
-
!Array.isArray(route.children) &&
|
|
39
|
-
!isFunction(route.children) &&
|
|
40
|
-
!isObject(bundle) &&
|
|
41
|
-
!stringKeys.some(key => isString(route[key]))) {
|
|
42
|
-
throw new Error(log(`Expected route config "${route.path}" to include either "${stringKeys.join('", "')}" ` +
|
|
43
|
-
`or "action" function but none found.`));
|
|
44
|
-
}
|
|
45
|
-
if (bundle) {
|
|
46
|
-
if (isString(bundle)) {
|
|
47
|
-
ensureBundle(bundle);
|
|
48
|
-
}
|
|
49
|
-
else if (!bundleKeys.some(key => key in bundle)) {
|
|
50
|
-
throw new Error(log('Expected route bundle to include either "' + NOMODULE + '" or "' + MODULE + '" keys, or both'));
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
bundleKeys.forEach(key => key in bundle && ensureBundle(bundle[key]));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (route.redirect) {
|
|
57
|
-
['bundle', 'component'].forEach(overriddenProp => {
|
|
58
|
-
if (overriddenProp in route) {
|
|
59
|
-
console.warn(log(`Route config "${route.path}" has both "redirect" and "${overriddenProp}" properties, ` +
|
|
60
|
-
`and "redirect" will always override the latter. Did you mean to only use "${overriddenProp}"?`));
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function ensureRoutes(routes) {
|
|
66
|
-
toArray(routes).forEach(route => ensureRoute(route));
|
|
67
|
-
}
|
|
68
|
-
function loadScript(src, key) {
|
|
69
|
-
let script = document.head.querySelector('script[src="' + src + '"][async]');
|
|
70
|
-
if (!script) {
|
|
71
|
-
script = document.createElement('script');
|
|
72
|
-
script.setAttribute('src', src);
|
|
73
|
-
if (key === MODULE) {
|
|
74
|
-
script.setAttribute('type', MODULE);
|
|
75
|
-
}
|
|
76
|
-
else if (key === NOMODULE) {
|
|
77
|
-
script.setAttribute(NOMODULE, '');
|
|
78
|
-
}
|
|
79
|
-
script.async = true;
|
|
80
|
-
}
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
script.onreadystatechange = script.onload = e => {
|
|
83
|
-
script.__dynamicImportLoaded = true;
|
|
84
|
-
resolve(e);
|
|
85
|
-
};
|
|
86
|
-
script.onerror = e => {
|
|
87
|
-
if (script.parentNode) {
|
|
88
|
-
script.parentNode.removeChild(script);
|
|
89
|
-
}
|
|
90
|
-
reject(e);
|
|
91
|
-
};
|
|
92
|
-
if (script.parentNode === null) {
|
|
93
|
-
document.head.appendChild(script);
|
|
94
|
-
}
|
|
95
|
-
else if (script.__dynamicImportLoaded) {
|
|
96
|
-
resolve();
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
function loadBundle(bundle) {
|
|
101
|
-
if (isString(bundle)) {
|
|
102
|
-
return loadScript(bundle);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
return Promise.race(bundleKeys
|
|
106
|
-
.filter(key => key in bundle)
|
|
107
|
-
.map(key => loadScript(bundle[key], key)));
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function fireRouterEvent(type, detail) {
|
|
111
|
-
return !window.dispatchEvent(new CustomEvent(`vaadin-router-${type}`, { cancelable: type === 'go', detail }));
|
|
112
|
-
}
|
|
113
|
-
function isObject(o) {
|
|
114
|
-
// guard against null passing the typeof check
|
|
115
|
-
return typeof o === 'object' && !!o;
|
|
116
|
-
}
|
|
117
|
-
function isFunction(f) {
|
|
118
|
-
return typeof f === 'function';
|
|
119
|
-
}
|
|
120
|
-
function isString(s) {
|
|
121
|
-
return typeof s === 'string';
|
|
122
|
-
}
|
|
123
|
-
function getNotFoundError(context) {
|
|
124
|
-
const error = new Error(log(`Page not found (${context.pathname})`));
|
|
125
|
-
error.context = context;
|
|
126
|
-
error.code = 404;
|
|
127
|
-
return error;
|
|
128
|
-
}
|
|
129
|
-
const notFoundResult = new (class NotFoundResult {
|
|
130
|
-
})();
|
|
131
|
-
/* istanbul ignore next: coverage is calculated in Chrome, this code is for IE */
|
|
132
|
-
function getAnchorOrigin(anchor) {
|
|
133
|
-
// IE11: on HTTP and HTTPS the default port is not included into
|
|
134
|
-
// window.location.origin, so won't include it here either.
|
|
135
|
-
const port = anchor.port;
|
|
136
|
-
const protocol = anchor.protocol;
|
|
137
|
-
const defaultHttp = protocol === 'http:' && port === '80';
|
|
138
|
-
const defaultHttps = protocol === 'https:' && port === '443';
|
|
139
|
-
const host = (defaultHttp || defaultHttps)
|
|
140
|
-
? anchor.hostname // does not include the port number (e.g. www.example.org)
|
|
141
|
-
: anchor.host; // does include the port number (e.g. www.example.org:80)
|
|
142
|
-
return `${protocol}//${host}`;
|
|
143
|
-
}
|
|
144
|
-
// The list of checks is not complete:
|
|
145
|
-
// - SVG support is missing
|
|
146
|
-
// - the 'rel' attribute is not considered
|
|
147
|
-
function vaadinRouterGlobalClickHandler(event) {
|
|
148
|
-
// ignore the click if the default action is prevented
|
|
149
|
-
if (event.defaultPrevented) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// ignore the click if not with the primary mouse button
|
|
153
|
-
if (event.button !== 0) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
// ignore the click if a modifier key is pressed
|
|
157
|
-
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
// find the <a> element that the click is at (or within)
|
|
161
|
-
let anchor = event.target;
|
|
162
|
-
const path = event.composedPath
|
|
163
|
-
? event.composedPath()
|
|
164
|
-
: (event.path || []);
|
|
165
|
-
// FIXME(web-padawan): `Symbol.iterator` used by webcomponentsjs is broken for arrays
|
|
166
|
-
// example to check: `for...of` loop here throws the "Not yet implemented" error
|
|
167
|
-
for (let i = 0; i < path.length; i++) {
|
|
168
|
-
const target = path[i];
|
|
169
|
-
if (target.nodeName && target.nodeName.toLowerCase() === 'a') {
|
|
170
|
-
anchor = target;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
while (anchor && anchor.nodeName.toLowerCase() !== 'a') {
|
|
175
|
-
anchor = anchor.parentNode;
|
|
176
|
-
}
|
|
177
|
-
// ignore the click if not at an <a> element
|
|
178
|
-
if (!anchor || anchor.nodeName.toLowerCase() !== 'a') {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
// ignore the click if the <a> element has a non-default target
|
|
182
|
-
if (anchor.target && anchor.target.toLowerCase() !== '_self') {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
// ignore the click if the <a> element has the 'download' attribute
|
|
186
|
-
if (anchor.hasAttribute('download')) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
// ignore the click if the <a> element has the 'router-ignore' attribute
|
|
190
|
-
if (anchor.hasAttribute('router-ignore')) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
// ignore the click if the target URL is a fragment on the current page
|
|
194
|
-
if (anchor.pathname === window.location.pathname && anchor.hash !== '') {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// ignore the click if the target is external to the app
|
|
198
|
-
// In IE11 HTMLAnchorElement does not have the `origin` property
|
|
199
|
-
const origin = anchor.origin || getAnchorOrigin(anchor);
|
|
200
|
-
if (origin !== window.location.origin) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
// if none of the above, convert the click into a navigation event
|
|
204
|
-
const { pathname, search, hash } = anchor;
|
|
205
|
-
if (fireRouterEvent('go', { pathname, search, hash })) {
|
|
206
|
-
event.preventDefault();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* A navigation trigger for Vaadin Router that translated clicks on `<a>` links
|
|
211
|
-
* into Vaadin Router navigation events.
|
|
212
|
-
*
|
|
213
|
-
* Only regular clicks on in-app links are translated (primary mouse button, no
|
|
214
|
-
* modifier keys, the target href is within the app's URL space).
|
|
215
|
-
*
|
|
216
|
-
* @memberOf Router.NavigationTrigger
|
|
217
|
-
* @type {NavigationTrigger}
|
|
218
|
-
*/
|
|
219
|
-
const CLICK = {
|
|
220
|
-
activate() {
|
|
221
|
-
window.document.addEventListener('click', vaadinRouterGlobalClickHandler);
|
|
222
|
-
},
|
|
223
|
-
inactivate() {
|
|
224
|
-
window.document.removeEventListener('click', vaadinRouterGlobalClickHandler);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
// PopStateEvent constructor shim
|
|
228
|
-
const isIE = /Trident/.test(navigator.userAgent);
|
|
229
|
-
/* istanbul ignore next: coverage is calculated in Chrome, this code is for IE */
|
|
230
|
-
if (isIE && !isFunction(window.PopStateEvent)) {
|
|
231
|
-
window.PopStateEvent = function (inType, params) {
|
|
232
|
-
params = params || {};
|
|
233
|
-
var e = document.createEvent('Event');
|
|
234
|
-
e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable));
|
|
235
|
-
e.state = params.state || null;
|
|
236
|
-
return e;
|
|
237
|
-
};
|
|
238
|
-
window.PopStateEvent.prototype = window.Event.prototype;
|
|
239
|
-
}
|
|
240
|
-
function vaadinRouterGlobalPopstateHandler(event) {
|
|
241
|
-
if (event.state === 'vaadin-router-ignore') {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
const { pathname, search, hash } = window.location;
|
|
245
|
-
fireRouterEvent('go', { pathname, search, hash });
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* A navigation trigger for Vaadin Router that translates popstate events into
|
|
249
|
-
* Vaadin Router navigation events.
|
|
250
|
-
*
|
|
251
|
-
* @memberOf Router.NavigationTrigger
|
|
252
|
-
* @type {NavigationTrigger}
|
|
253
|
-
*/
|
|
254
|
-
const POPSTATE = {
|
|
255
|
-
activate() {
|
|
256
|
-
window.addEventListener('popstate', vaadinRouterGlobalPopstateHandler);
|
|
257
|
-
},
|
|
258
|
-
inactivate() {
|
|
259
|
-
window.removeEventListener('popstate', vaadinRouterGlobalPopstateHandler);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
/**
|
|
263
|
-
* Expose `pathToRegexp`.
|
|
264
|
-
*/
|
|
265
|
-
var pathToRegexp_1 = pathToRegexp;
|
|
266
|
-
var parse_1 = parse;
|
|
267
|
-
var compile_1 = compile;
|
|
268
|
-
var tokensToFunction_1 = tokensToFunction;
|
|
269
|
-
var tokensToRegExp_1 = tokensToRegExp;
|
|
270
|
-
/**
|
|
271
|
-
* Default configs.
|
|
272
|
-
*/
|
|
273
|
-
var DEFAULT_DELIMITER = '/';
|
|
274
|
-
var DEFAULT_DELIMITERS = './';
|
|
275
|
-
/**
|
|
276
|
-
* The main path matching regexp utility.
|
|
277
|
-
*
|
|
278
|
-
* @type {RegExp}
|
|
279
|
-
*/
|
|
280
|
-
var PATH_REGEXP = new RegExp([
|
|
281
|
-
// Match escaped characters that would otherwise appear in future matches.
|
|
282
|
-
// This allows the user to escape special characters that won't transform.
|
|
283
|
-
'(\\\\.)',
|
|
284
|
-
// Match Express-style parameters and un-named parameters with a prefix
|
|
285
|
-
// and optional suffixes. Matches appear as:
|
|
286
|
-
//
|
|
287
|
-
// ":test(\\d+)?" => ["test", "\d+", undefined, "?"]
|
|
288
|
-
// "(\\d+)" => [undefined, undefined, "\d+", undefined]
|
|
289
|
-
'(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?'
|
|
290
|
-
].join('|'), 'g');
|
|
291
|
-
/**
|
|
292
|
-
* Parse a string for the raw tokens.
|
|
293
|
-
*
|
|
294
|
-
* @param {string} str
|
|
295
|
-
* @param {Object=} options
|
|
296
|
-
* @return {!Array}
|
|
297
|
-
*/
|
|
298
|
-
function parse(str, options) {
|
|
299
|
-
var tokens = [];
|
|
300
|
-
var key = 0;
|
|
301
|
-
var index = 0;
|
|
302
|
-
var path = '';
|
|
303
|
-
var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER;
|
|
304
|
-
var delimiters = (options && options.delimiters) || DEFAULT_DELIMITERS;
|
|
305
|
-
var pathEscaped = false;
|
|
306
|
-
var res;
|
|
307
|
-
while ((res = PATH_REGEXP.exec(str)) !== null) {
|
|
308
|
-
var m = res[0];
|
|
309
|
-
var escaped = res[1];
|
|
310
|
-
var offset = res.index;
|
|
311
|
-
path += str.slice(index, offset);
|
|
312
|
-
index = offset + m.length;
|
|
313
|
-
// Ignore already escaped sequences.
|
|
314
|
-
if (escaped) {
|
|
315
|
-
path += escaped[1];
|
|
316
|
-
pathEscaped = true;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
var prev = '';
|
|
320
|
-
var next = str[index];
|
|
321
|
-
var name = res[2];
|
|
322
|
-
var capture = res[3];
|
|
323
|
-
var group = res[4];
|
|
324
|
-
var modifier = res[5];
|
|
325
|
-
if (!pathEscaped && path.length) {
|
|
326
|
-
var k = path.length - 1;
|
|
327
|
-
if (delimiters.indexOf(path[k]) > -1) {
|
|
328
|
-
prev = path[k];
|
|
329
|
-
path = path.slice(0, k);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// Push the current path onto the tokens.
|
|
333
|
-
if (path) {
|
|
334
|
-
tokens.push(path);
|
|
335
|
-
path = '';
|
|
336
|
-
pathEscaped = false;
|
|
337
|
-
}
|
|
338
|
-
var partial = prev !== '' && next !== undefined && next !== prev;
|
|
339
|
-
var repeat = modifier === '+' || modifier === '*';
|
|
340
|
-
var optional = modifier === '?' || modifier === '*';
|
|
341
|
-
var delimiter = prev || defaultDelimiter;
|
|
342
|
-
var pattern = capture || group;
|
|
343
|
-
tokens.push({
|
|
344
|
-
name: name || key++,
|
|
345
|
-
prefix: prev,
|
|
346
|
-
delimiter: delimiter,
|
|
347
|
-
optional: optional,
|
|
348
|
-
repeat: repeat,
|
|
349
|
-
partial: partial,
|
|
350
|
-
pattern: pattern ? escapeGroup(pattern) : '[^' + escapeString(delimiter) + ']+?'
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
// Push any remaining characters.
|
|
354
|
-
if (path || index < str.length) {
|
|
355
|
-
tokens.push(path + str.substr(index));
|
|
356
|
-
}
|
|
357
|
-
return tokens;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Compile a string to a template function for the path.
|
|
361
|
-
*
|
|
362
|
-
* @param {string} str
|
|
363
|
-
* @param {Object=} options
|
|
364
|
-
* @return {!function(Object=, Object=)}
|
|
365
|
-
*/
|
|
366
|
-
function compile(str, options) {
|
|
367
|
-
return tokensToFunction(parse(str, options));
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Expose a method for transforming tokens into the path function.
|
|
371
|
-
*/
|
|
372
|
-
function tokensToFunction(tokens) {
|
|
373
|
-
// Compile all the tokens into regexps.
|
|
374
|
-
var matches = new Array(tokens.length);
|
|
375
|
-
// Compile all the patterns before compilation.
|
|
376
|
-
for (var i = 0; i < tokens.length; i++) {
|
|
377
|
-
if (typeof tokens[i] === 'object') {
|
|
378
|
-
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return function (data, options) {
|
|
382
|
-
var path = '';
|
|
383
|
-
var encode = (options && options.encode) || encodeURIComponent;
|
|
384
|
-
for (var i = 0; i < tokens.length; i++) {
|
|
385
|
-
var token = tokens[i];
|
|
386
|
-
if (typeof token === 'string') {
|
|
387
|
-
path += token;
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
var value = data ? data[token.name] : undefined;
|
|
391
|
-
var segment;
|
|
392
|
-
if (Array.isArray(value)) {
|
|
393
|
-
if (!token.repeat) {
|
|
394
|
-
throw new TypeError('Expected "' + token.name + '" to not repeat, but got array');
|
|
395
|
-
}
|
|
396
|
-
if (value.length === 0) {
|
|
397
|
-
if (token.optional)
|
|
398
|
-
continue;
|
|
399
|
-
throw new TypeError('Expected "' + token.name + '" to not be empty');
|
|
400
|
-
}
|
|
401
|
-
for (var j = 0; j < value.length; j++) {
|
|
402
|
-
segment = encode(value[j], token);
|
|
403
|
-
if (!matches[i].test(segment)) {
|
|
404
|
-
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '"');
|
|
405
|
-
}
|
|
406
|
-
path += (j === 0 ? token.prefix : token.delimiter) + segment;
|
|
407
|
-
}
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
411
|
-
segment = encode(String(value), token);
|
|
412
|
-
if (!matches[i].test(segment)) {
|
|
413
|
-
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"');
|
|
414
|
-
}
|
|
415
|
-
path += token.prefix + segment;
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
if (token.optional) {
|
|
419
|
-
// Prepend partial segment prefixes.
|
|
420
|
-
if (token.partial)
|
|
421
|
-
path += token.prefix;
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
throw new TypeError('Expected "' + token.name + '" to be ' + (token.repeat ? 'an array' : 'a string'));
|
|
425
|
-
}
|
|
426
|
-
return path;
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Escape a regular expression string.
|
|
431
|
-
*
|
|
432
|
-
* @param {string} str
|
|
433
|
-
* @return {string}
|
|
434
|
-
*/
|
|
435
|
-
function escapeString(str) {
|
|
436
|
-
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1');
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Escape the capturing group by escaping special characters and meaning.
|
|
440
|
-
*
|
|
441
|
-
* @param {string} group
|
|
442
|
-
* @return {string}
|
|
443
|
-
*/
|
|
444
|
-
function escapeGroup(group) {
|
|
445
|
-
return group.replace(/([=!:$/()])/g, '\\$1');
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Get the flags for a regexp from the options.
|
|
449
|
-
*
|
|
450
|
-
* @param {Object} options
|
|
451
|
-
* @return {string}
|
|
452
|
-
*/
|
|
453
|
-
function flags(options) {
|
|
454
|
-
return options && options.sensitive ? '' : 'i';
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Pull out keys from a regexp.
|
|
458
|
-
*
|
|
459
|
-
* @param {!RegExp} path
|
|
460
|
-
* @param {Array=} keys
|
|
461
|
-
* @return {!RegExp}
|
|
462
|
-
*/
|
|
463
|
-
function regexpToRegexp(path, keys) {
|
|
464
|
-
if (!keys)
|
|
465
|
-
return path;
|
|
466
|
-
// Use a negative lookahead to match only capturing groups.
|
|
467
|
-
var groups = path.source.match(/\((?!\?)/g);
|
|
468
|
-
if (groups) {
|
|
469
|
-
for (var i = 0; i < groups.length; i++) {
|
|
470
|
-
keys.push({
|
|
471
|
-
name: i,
|
|
472
|
-
prefix: null,
|
|
473
|
-
delimiter: null,
|
|
474
|
-
optional: false,
|
|
475
|
-
repeat: false,
|
|
476
|
-
partial: false,
|
|
477
|
-
pattern: null
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return path;
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Transform an array into a regexp.
|
|
485
|
-
*
|
|
486
|
-
* @param {!Array} path
|
|
487
|
-
* @param {Array=} keys
|
|
488
|
-
* @param {Object=} options
|
|
489
|
-
* @return {!RegExp}
|
|
490
|
-
*/
|
|
491
|
-
function arrayToRegexp(path, keys, options) {
|
|
492
|
-
var parts = [];
|
|
493
|
-
for (var i = 0; i < path.length; i++) {
|
|
494
|
-
parts.push(pathToRegexp(path[i], keys, options).source);
|
|
495
|
-
}
|
|
496
|
-
return new RegExp('(?:' + parts.join('|') + ')', flags(options));
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Create a path regexp from string input.
|
|
500
|
-
*
|
|
501
|
-
* @param {string} path
|
|
502
|
-
* @param {Array=} keys
|
|
503
|
-
* @param {Object=} options
|
|
504
|
-
* @return {!RegExp}
|
|
505
|
-
*/
|
|
506
|
-
function stringToRegexp(path, keys, options) {
|
|
507
|
-
return tokensToRegExp(parse(path, options), keys, options);
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Expose a function for taking tokens and returning a RegExp.
|
|
511
|
-
*
|
|
512
|
-
* @param {!Array} tokens
|
|
513
|
-
* @param {Array=} keys
|
|
514
|
-
* @param {Object=} options
|
|
515
|
-
* @return {!RegExp}
|
|
516
|
-
*/
|
|
517
|
-
function tokensToRegExp(tokens, keys, options) {
|
|
518
|
-
options = options || {};
|
|
519
|
-
var strict = options.strict;
|
|
520
|
-
var start = options.start !== false;
|
|
521
|
-
var end = options.end !== false;
|
|
522
|
-
var delimiter = escapeString(options.delimiter || DEFAULT_DELIMITER);
|
|
523
|
-
var delimiters = options.delimiters || DEFAULT_DELIMITERS;
|
|
524
|
-
var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|');
|
|
525
|
-
var route = start ? '^' : '';
|
|
526
|
-
var isEndDelimited = tokens.length === 0;
|
|
527
|
-
// Iterate over the tokens and create our regexp string.
|
|
528
|
-
for (var i = 0; i < tokens.length; i++) {
|
|
529
|
-
var token = tokens[i];
|
|
530
|
-
if (typeof token === 'string') {
|
|
531
|
-
route += escapeString(token);
|
|
532
|
-
isEndDelimited = i === tokens.length - 1 && delimiters.indexOf(token[token.length - 1]) > -1;
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
var capture = token.repeat
|
|
536
|
-
? '(?:' + token.pattern + ')(?:' + escapeString(token.delimiter) + '(?:' + token.pattern + '))*'
|
|
537
|
-
: token.pattern;
|
|
538
|
-
if (keys)
|
|
539
|
-
keys.push(token);
|
|
540
|
-
if (token.optional) {
|
|
541
|
-
if (token.partial) {
|
|
542
|
-
route += escapeString(token.prefix) + '(' + capture + ')?';
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
route += '(?:' + escapeString(token.prefix) + '(' + capture + '))?';
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
route += escapeString(token.prefix) + '(' + capture + ')';
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (end) {
|
|
554
|
-
if (!strict)
|
|
555
|
-
route += '(?:' + delimiter + ')?';
|
|
556
|
-
route += endsWith === '$' ? '$' : '(?=' + endsWith + ')';
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
if (!strict)
|
|
560
|
-
route += '(?:' + delimiter + '(?=' + endsWith + '))?';
|
|
561
|
-
if (!isEndDelimited)
|
|
562
|
-
route += '(?=' + delimiter + '|' + endsWith + ')';
|
|
563
|
-
}
|
|
564
|
-
return new RegExp(route, flags(options));
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Normalize the given path string, returning a regular expression.
|
|
568
|
-
*
|
|
569
|
-
* An empty array can be passed in for the keys, which will hold the
|
|
570
|
-
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
571
|
-
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
572
|
-
*
|
|
573
|
-
* @param {(string|RegExp|Array)} path
|
|
574
|
-
* @param {Array=} keys
|
|
575
|
-
* @param {Object=} options
|
|
576
|
-
* @return {!RegExp}
|
|
577
|
-
*/
|
|
578
|
-
function pathToRegexp(path, keys, options) {
|
|
579
|
-
if (path instanceof RegExp) {
|
|
580
|
-
return regexpToRegexp(path, keys);
|
|
581
|
-
}
|
|
582
|
-
if (Array.isArray(path)) {
|
|
583
|
-
return arrayToRegexp(/** @type {!Array} */ (path), keys, options);
|
|
584
|
-
}
|
|
585
|
-
return stringToRegexp(/** @type {string} */ (path), keys, options);
|
|
586
|
-
}
|
|
587
|
-
pathToRegexp_1.parse = parse_1;
|
|
588
|
-
pathToRegexp_1.compile = compile_1;
|
|
589
|
-
pathToRegexp_1.tokensToFunction = tokensToFunction_1;
|
|
590
|
-
pathToRegexp_1.tokensToRegExp = tokensToRegExp_1;
|
|
591
|
-
/**
|
|
592
|
-
* Universal Router (https://www.kriasoft.com/universal-router/)
|
|
593
|
-
*
|
|
594
|
-
* Copyright (c) 2015-present Kriasoft.
|
|
595
|
-
*
|
|
596
|
-
* This source code is licensed under the MIT license found in the
|
|
597
|
-
* LICENSE.txt file in the root directory of this source tree.
|
|
598
|
-
*/
|
|
599
|
-
const { hasOwnProperty } = Object.prototype;
|
|
600
|
-
const cache = new Map();
|
|
601
|
-
// see https://github.com/pillarjs/path-to-regexp/issues/148
|
|
602
|
-
cache.set('|false', {
|
|
603
|
-
keys: [],
|
|
604
|
-
pattern: /(?:)/
|
|
605
|
-
});
|
|
606
|
-
function decodeParam(val) {
|
|
607
|
-
try {
|
|
608
|
-
return decodeURIComponent(val);
|
|
609
|
-
}
|
|
610
|
-
catch (err) {
|
|
611
|
-
return val;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
function matchPath(routepath, path, exact, parentKeys, parentParams) {
|
|
615
|
-
exact = !!exact;
|
|
616
|
-
const cacheKey = `${routepath}|${exact}`;
|
|
617
|
-
let regexp = cache.get(cacheKey);
|
|
618
|
-
if (!regexp) {
|
|
619
|
-
const keys = [];
|
|
620
|
-
regexp = {
|
|
621
|
-
keys,
|
|
622
|
-
pattern: pathToRegexp_1(routepath, keys, {
|
|
623
|
-
end: exact,
|
|
624
|
-
strict: routepath === ''
|
|
625
|
-
}),
|
|
626
|
-
};
|
|
627
|
-
cache.set(cacheKey, regexp);
|
|
628
|
-
}
|
|
629
|
-
const m = regexp.pattern.exec(path);
|
|
630
|
-
if (!m) {
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
const params = Object.assign({}, parentParams);
|
|
634
|
-
for (let i = 1; i < m.length; i++) {
|
|
635
|
-
const key = regexp.keys[i - 1];
|
|
636
|
-
const prop = key.name;
|
|
637
|
-
const value = m[i];
|
|
638
|
-
if (value !== undefined || !hasOwnProperty.call(params, prop)) {
|
|
639
|
-
if (key.repeat) {
|
|
640
|
-
params[prop] = value ? value.split(key.delimiter).map(decodeParam) : [];
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
params[prop] = value ? decodeParam(value) : value;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return {
|
|
648
|
-
path: m[0],
|
|
649
|
-
keys: (parentKeys || []).concat(regexp.keys),
|
|
650
|
-
params,
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Universal Router (https://www.kriasoft.com/universal-router/)
|
|
655
|
-
*
|
|
656
|
-
* Copyright (c) 2015-present Kriasoft.
|
|
657
|
-
*
|
|
658
|
-
* This source code is licensed under the MIT license found in the
|
|
659
|
-
* LICENSE.txt file in the root directory of this source tree.
|
|
660
|
-
*/
|
|
661
|
-
/**
|
|
662
|
-
* Traverses the routes tree and matches its nodes to the given pathname from
|
|
663
|
-
* the root down to the leaves. Each match consumes a part of the pathname and
|
|
664
|
-
* the matching process continues for as long as there is a matching child
|
|
665
|
-
* route for the remaining part of the pathname.
|
|
666
|
-
*
|
|
667
|
-
* The returned value is a lazily evaluated iterator.
|
|
668
|
-
*
|
|
669
|
-
* The leading "/" in a route path matters only for the root of the routes
|
|
670
|
-
* tree (or if all parent routes are ""). In all other cases a leading "/" in
|
|
671
|
-
* a child route path has no significance.
|
|
672
|
-
*
|
|
673
|
-
* The trailing "/" in a _route path_ matters only for the leaves of the
|
|
674
|
-
* routes tree. A leaf route with a trailing "/" matches only a pathname that
|
|
675
|
-
* also has a trailing "/".
|
|
676
|
-
*
|
|
677
|
-
* The trailing "/" in a route path does not affect matching of child routes
|
|
678
|
-
* in any way.
|
|
679
|
-
*
|
|
680
|
-
* The trailing "/" in a _pathname_ generally does not matter (except for
|
|
681
|
-
* the case of leaf nodes described above).
|
|
682
|
-
*
|
|
683
|
-
* The "" and "/" routes have special treatment:
|
|
684
|
-
* 1. as a single route
|
|
685
|
-
* the "" and "/" routes match only the "" and "/" pathnames respectively
|
|
686
|
-
* 2. as a parent in the routes tree
|
|
687
|
-
* the "" route matches any pathname without consuming any part of it
|
|
688
|
-
* the "/" route matches any absolute pathname consuming its leading "/"
|
|
689
|
-
* 3. as a leaf in the routes tree
|
|
690
|
-
* the "" and "/" routes match only if the entire pathname is consumed by
|
|
691
|
-
* the parent routes chain. In this case "" and "/" are equivalent.
|
|
692
|
-
* 4. several directly nested "" or "/" routes
|
|
693
|
-
* - directly nested "" or "/" routes are 'squashed' (i.e. nesting two
|
|
694
|
-
* "/" routes does not require a double "/" in the pathname to match)
|
|
695
|
-
* - if there are only "" in the parent routes chain, no part of the
|
|
696
|
-
* pathname is consumed, and the leading "/" in the child routes' paths
|
|
697
|
-
* remains significant
|
|
698
|
-
*
|
|
699
|
-
* Side effect:
|
|
700
|
-
* - the routes tree { path: '' } matches only the '' pathname
|
|
701
|
-
* - the routes tree { path: '', children: [ { path: '' } ] } matches any
|
|
702
|
-
* pathname (for the tree root)
|
|
703
|
-
*
|
|
704
|
-
* Prefix matching can be enabled also by `children: true`.
|
|
705
|
-
*/
|
|
706
|
-
function matchRoute(route, pathname, ignoreLeadingSlash, parentKeys, parentParams) {
|
|
707
|
-
let match;
|
|
708
|
-
let childMatches;
|
|
709
|
-
let childIndex = 0;
|
|
710
|
-
let routepath = route.path || '';
|
|
711
|
-
if (routepath.charAt(0) === '/') {
|
|
712
|
-
if (ignoreLeadingSlash) {
|
|
713
|
-
routepath = routepath.substr(1);
|
|
714
|
-
}
|
|
715
|
-
ignoreLeadingSlash = true;
|
|
716
|
-
}
|
|
717
|
-
return {
|
|
718
|
-
next(routeToSkip) {
|
|
719
|
-
if (route === routeToSkip) {
|
|
720
|
-
return { done: true };
|
|
721
|
-
}
|
|
722
|
-
const children = route.__children = route.__children || route.children;
|
|
723
|
-
if (!match) {
|
|
724
|
-
match = matchPath(routepath, pathname, !children, parentKeys, parentParams);
|
|
725
|
-
if (match) {
|
|
726
|
-
return {
|
|
727
|
-
done: false,
|
|
728
|
-
value: {
|
|
729
|
-
route,
|
|
730
|
-
keys: match.keys,
|
|
731
|
-
params: match.params,
|
|
732
|
-
path: match.path
|
|
733
|
-
},
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
if (match && children) {
|
|
738
|
-
while (childIndex < children.length) {
|
|
739
|
-
if (!childMatches) {
|
|
740
|
-
const childRoute = children[childIndex];
|
|
741
|
-
childRoute.parent = route;
|
|
742
|
-
let matchedLength = match.path.length;
|
|
743
|
-
if (matchedLength > 0 && pathname.charAt(matchedLength) === '/') {
|
|
744
|
-
matchedLength += 1;
|
|
745
|
-
}
|
|
746
|
-
childMatches = matchRoute(childRoute, pathname.substr(matchedLength), ignoreLeadingSlash, match.keys, match.params);
|
|
747
|
-
}
|
|
748
|
-
const childMatch = childMatches.next(routeToSkip);
|
|
749
|
-
if (!childMatch.done) {
|
|
750
|
-
return {
|
|
751
|
-
done: false,
|
|
752
|
-
value: childMatch.value,
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
childMatches = null;
|
|
756
|
-
childIndex++;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
return { done: true };
|
|
760
|
-
},
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Universal Router (https://www.kriasoft.com/universal-router/)
|
|
765
|
-
*
|
|
766
|
-
* Copyright (c) 2015-present Kriasoft.
|
|
767
|
-
*
|
|
768
|
-
* This source code is licensed under the MIT license found in the
|
|
769
|
-
* LICENSE.txt file in the root directory of this source tree.
|
|
770
|
-
*/
|
|
771
|
-
function resolveRoute(context) {
|
|
772
|
-
if (isFunction(context.route.action)) {
|
|
773
|
-
return context.route.action(context);
|
|
774
|
-
}
|
|
775
|
-
return undefined;
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Universal Router (https://www.kriasoft.com/universal-router/)
|
|
779
|
-
*
|
|
780
|
-
* Copyright (c) 2015-present Kriasoft.
|
|
781
|
-
*
|
|
782
|
-
* This source code is licensed under the MIT license found in the
|
|
783
|
-
* LICENSE.txt file in the root directory of this source tree.
|
|
784
|
-
*/
|
|
785
|
-
function isChildRoute(parentRoute, childRoute) {
|
|
786
|
-
let route = childRoute;
|
|
787
|
-
while (route) {
|
|
788
|
-
route = route.parent;
|
|
789
|
-
if (route === parentRoute) {
|
|
790
|
-
return true;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return false;
|
|
794
|
-
}
|
|
795
|
-
function generateErrorMessage(currentContext) {
|
|
796
|
-
let errorMessage = `Path '${currentContext.pathname}' is not properly resolved due to an error.`;
|
|
797
|
-
const routePath = (currentContext.route || {}).path;
|
|
798
|
-
if (routePath) {
|
|
799
|
-
errorMessage += ` Resolution had failed on route: '${routePath}'`;
|
|
800
|
-
}
|
|
801
|
-
return errorMessage;
|
|
802
|
-
}
|
|
803
|
-
function updateChainForRoute(context, match) {
|
|
804
|
-
const { route, path } = match;
|
|
805
|
-
if (route && !route.__synthetic) {
|
|
806
|
-
const item = { path, route };
|
|
807
|
-
if (!context.chain) {
|
|
808
|
-
context.chain = [];
|
|
809
|
-
}
|
|
810
|
-
else {
|
|
811
|
-
// Discard old items
|
|
812
|
-
if (route.parent) {
|
|
813
|
-
let i = context.chain.length;
|
|
814
|
-
while (i-- && context.chain[i].route && context.chain[i].route !== route.parent) {
|
|
815
|
-
context.chain.pop();
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
context.chain.push(item);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
*/
|
|
824
|
-
class Resolver {
|
|
825
|
-
constructor(routes, options = {}) {
|
|
826
|
-
if (Object(routes) !== routes) {
|
|
827
|
-
throw new TypeError('Invalid routes');
|
|
828
|
-
}
|
|
829
|
-
this.baseUrl = options.baseUrl || '';
|
|
830
|
-
this.errorHandler = options.errorHandler;
|
|
831
|
-
this.resolveRoute = options.resolveRoute || resolveRoute;
|
|
832
|
-
this.context = Object.assign({ resolver: this }, options.context);
|
|
833
|
-
this.root = Array.isArray(routes) ? { path: '', __children: routes, parent: null, __synthetic: true } : routes;
|
|
834
|
-
this.root.parent = null;
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* Returns the current list of routes (as a shallow copy). Adding / removing
|
|
838
|
-
* routes to / from the returned array does not affect the routing config,
|
|
839
|
-
* but modifying the route objects does.
|
|
840
|
-
*
|
|
841
|
-
* @return {!Array<!Router.Route>}
|
|
842
|
-
*/
|
|
843
|
-
getRoutes() {
|
|
844
|
-
return [...this.root.__children];
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* Sets the routing config (replacing the existing one).
|
|
848
|
-
*
|
|
849
|
-
* @param {!Array<!Router.Route>|!Router.Route} routes a single route or an array of those
|
|
850
|
-
* (the array is shallow copied)
|
|
851
|
-
*/
|
|
852
|
-
setRoutes(routes) {
|
|
853
|
-
ensureRoutes(routes);
|
|
854
|
-
const newRoutes = [...toArray(routes)];
|
|
855
|
-
this.root.__children = newRoutes;
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Appends one or several routes to the routing config and returns the
|
|
859
|
-
* effective routing config after the operation.
|
|
860
|
-
*
|
|
861
|
-
* @param {!Array<!Router.Route>|!Router.Route} routes a single route or an array of those
|
|
862
|
-
* (the array is shallow copied)
|
|
863
|
-
* @return {!Array<!Router.Route>}
|
|
864
|
-
* @protected
|
|
865
|
-
*/
|
|
866
|
-
addRoutes(routes) {
|
|
867
|
-
ensureRoutes(routes);
|
|
868
|
-
this.root.__children.push(...toArray(routes));
|
|
869
|
-
return this.getRoutes();
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Removes all existing routes from the routing config.
|
|
873
|
-
*/
|
|
874
|
-
removeRoutes() {
|
|
875
|
-
this.setRoutes([]);
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* Asynchronously resolves the given pathname, i.e. finds all routes matching
|
|
879
|
-
* the pathname and tries resolving them one after another in the order they
|
|
880
|
-
* are listed in the routes config until the first non-null result.
|
|
881
|
-
*
|
|
882
|
-
* Returns a promise that is fulfilled with the return value of an object that consists of the first
|
|
883
|
-
* route handler result that returns something other than `null` or `undefined` and context used to get this result.
|
|
884
|
-
*
|
|
885
|
-
* If no route handlers return a non-null result, or if no route matches the
|
|
886
|
-
* given pathname the returned promise is rejected with a 'page not found'
|
|
887
|
-
* `Error`.
|
|
888
|
-
*
|
|
889
|
-
* @param {!string|!{pathname: !string}} pathnameOrContext the pathname to
|
|
890
|
-
* resolve or a context object with a `pathname` property and other
|
|
891
|
-
* properties to pass to the route resolver functions.
|
|
892
|
-
* @return {!Promise<any>}
|
|
893
|
-
*/
|
|
894
|
-
resolve(pathnameOrContext) {
|
|
895
|
-
const context = Object.assign({}, this.context, isString(pathnameOrContext) ? { pathname: pathnameOrContext } : pathnameOrContext);
|
|
896
|
-
const match = matchRoute(this.root, this.__normalizePathname(context.pathname), this.baseUrl);
|
|
897
|
-
const resolve = this.resolveRoute;
|
|
898
|
-
let matches = null;
|
|
899
|
-
let nextMatches = null;
|
|
900
|
-
let currentContext = context;
|
|
901
|
-
function next(resume, parent = matches.value.route, prevResult) {
|
|
902
|
-
const routeToSkip = prevResult === null && matches.value.route;
|
|
903
|
-
matches = nextMatches || match.next(routeToSkip);
|
|
904
|
-
nextMatches = null;
|
|
905
|
-
if (!resume) {
|
|
906
|
-
if (matches.done || !isChildRoute(parent, matches.value.route)) {
|
|
907
|
-
nextMatches = matches;
|
|
908
|
-
return Promise.resolve(notFoundResult);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
if (matches.done) {
|
|
912
|
-
return Promise.reject(getNotFoundError(context));
|
|
913
|
-
}
|
|
914
|
-
currentContext = Object.assign(currentContext
|
|
915
|
-
? { chain: (currentContext.chain ? currentContext.chain.slice(0) : []) }
|
|
916
|
-
: {}, context, matches.value);
|
|
917
|
-
updateChainForRoute(currentContext, matches.value);
|
|
918
|
-
return Promise.resolve(resolve(currentContext)).then(resolution => {
|
|
919
|
-
if (resolution !== null && resolution !== undefined && resolution !== notFoundResult) {
|
|
920
|
-
currentContext.result = resolution.result || resolution;
|
|
921
|
-
return currentContext;
|
|
922
|
-
}
|
|
923
|
-
return next(resume, parent, resolution);
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
context.next = next;
|
|
927
|
-
return Promise.resolve()
|
|
928
|
-
.then(() => next(true, this.root))
|
|
929
|
-
.catch((error) => {
|
|
930
|
-
const errorMessage = generateErrorMessage(currentContext);
|
|
931
|
-
if (!error) {
|
|
932
|
-
error = new Error(errorMessage);
|
|
933
|
-
}
|
|
934
|
-
else {
|
|
935
|
-
console.warn(errorMessage);
|
|
936
|
-
}
|
|
937
|
-
error.context = error.context || currentContext;
|
|
938
|
-
// DOMException has its own code which is read-only
|
|
939
|
-
if (!(error instanceof DOMException)) {
|
|
940
|
-
error.code = error.code || 500;
|
|
941
|
-
}
|
|
942
|
-
if (this.errorHandler) {
|
|
943
|
-
currentContext.result = this.errorHandler(error);
|
|
944
|
-
return currentContext;
|
|
945
|
-
}
|
|
946
|
-
throw error;
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* URL constructor polyfill hook. Creates and returns an URL instance.
|
|
951
|
-
*/
|
|
952
|
-
static __createUrl(url, base) {
|
|
953
|
-
return new URL(url, base);
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* If the baseUrl property is set, transforms the baseUrl and returns the full
|
|
957
|
-
* actual `base` string for using in the `new URL(path, base);` and for
|
|
958
|
-
* prepernding the paths with. The returned base ends with a trailing slash.
|
|
959
|
-
*
|
|
960
|
-
* Otherwise, returns empty string.
|
|
961
|
-
*/
|
|
962
|
-
get __effectiveBaseUrl() {
|
|
963
|
-
return this.baseUrl
|
|
964
|
-
? this.constructor.__createUrl(this.baseUrl, document.baseURI || document.URL).href.replace(/[^\/]*$/, '')
|
|
965
|
-
: '';
|
|
966
|
-
}
|
|
967
|
-
/**
|
|
968
|
-
* If the baseUrl is set, matches the pathname with the router’s baseUrl,
|
|
969
|
-
* and returns the local pathname with the baseUrl stripped out.
|
|
970
|
-
*
|
|
971
|
-
* If the pathname does not match the baseUrl, returns undefined.
|
|
972
|
-
*
|
|
973
|
-
* If the `baseUrl` is not set, returns the unmodified pathname argument.
|
|
974
|
-
*/
|
|
975
|
-
__normalizePathname(pathname) {
|
|
976
|
-
if (!this.baseUrl) {
|
|
977
|
-
// No base URL, no need to transform the pathname.
|
|
978
|
-
return pathname;
|
|
979
|
-
}
|
|
980
|
-
const base = this.__effectiveBaseUrl;
|
|
981
|
-
const normalizedUrl = this.constructor.__createUrl(pathname, base).href;
|
|
982
|
-
if (normalizedUrl.slice(0, base.length) === base) {
|
|
983
|
-
return normalizedUrl.slice(base.length);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
exports.Resolver = Resolver;
|
|
988
|
-
/**
|
|
989
|
-
* Universal Router (https://www.kriasoft.com/universal-router/)
|
|
990
|
-
*
|
|
991
|
-
* Copyright (c) 2015-present Kriasoft.
|
|
992
|
-
*
|
|
993
|
-
* This source code is licensed under the MIT license found in the
|
|
994
|
-
* LICENSE.txt file in the root directory of this source tree.
|
|
995
|
-
*/
|
|
996
|
-
const { pathToRegexp: pathToRegexp$1 } = Resolver;
|
|
997
|
-
const cache$1 = new Map();
|
|
998
|
-
function cacheRoutes(routesByName, route, routes) {
|
|
999
|
-
const name = route.name || route.component;
|
|
1000
|
-
if (name) {
|
|
1001
|
-
if (routesByName.has(name)) {
|
|
1002
|
-
routesByName.get(name).push(route);
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
routesByName.set(name, [route]);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (Array.isArray(routes)) {
|
|
1009
|
-
for (let i = 0; i < routes.length; i++) {
|
|
1010
|
-
const childRoute = routes[i];
|
|
1011
|
-
childRoute.parent = route;
|
|
1012
|
-
cacheRoutes(routesByName, childRoute, childRoute.__children || childRoute.children);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
function getRouteByName(routesByName, routeName) {
|
|
1017
|
-
const routes = routesByName.get(routeName);
|
|
1018
|
-
if (routes && routes.length > 1) {
|
|
1019
|
-
throw new Error(`Duplicate route with name "${routeName}".`
|
|
1020
|
-
+ ` Try seting unique 'name' route properties.`);
|
|
1021
|
-
}
|
|
1022
|
-
return routes && routes[0];
|
|
1023
|
-
}
|
|
1024
|
-
function getRoutePath(route) {
|
|
1025
|
-
let path = route.path;
|
|
1026
|
-
path = Array.isArray(path) ? path[0] : path;
|
|
1027
|
-
return path !== undefined ? path : '';
|
|
1028
|
-
}
|
|
1029
|
-
function generateUrls(router, options = {}) {
|
|
1030
|
-
if (!(router instanceof Resolver)) {
|
|
1031
|
-
throw new TypeError('An instance of Resolver is expected');
|
|
1032
|
-
}
|
|
1033
|
-
const routesByName = new Map();
|
|
1034
|
-
return (routeName, params) => {
|
|
1035
|
-
let route = getRouteByName(routesByName, routeName);
|
|
1036
|
-
if (!route) {
|
|
1037
|
-
routesByName.clear(); // clear cache
|
|
1038
|
-
cacheRoutes(routesByName, router.root, router.root.__children);
|
|
1039
|
-
route = getRouteByName(routesByName, routeName);
|
|
1040
|
-
if (!route) {
|
|
1041
|
-
throw new Error(`Route "${routeName}" not found`);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
let regexp = cache$1.get(route.fullPath);
|
|
1045
|
-
if (!regexp) {
|
|
1046
|
-
let fullPath = getRoutePath(route);
|
|
1047
|
-
let rt = route.parent;
|
|
1048
|
-
while (rt) {
|
|
1049
|
-
const path = getRoutePath(rt);
|
|
1050
|
-
if (path) {
|
|
1051
|
-
fullPath = path.replace(/\/$/, '') + '/' + fullPath.replace(/^\//, '');
|
|
1052
|
-
}
|
|
1053
|
-
rt = rt.parent;
|
|
1054
|
-
}
|
|
1055
|
-
const tokens = pathToRegexp$1.parse(fullPath);
|
|
1056
|
-
const toPath = pathToRegexp$1.tokensToFunction(tokens);
|
|
1057
|
-
const keys = Object.create(null);
|
|
1058
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
1059
|
-
if (!isString(tokens[i])) {
|
|
1060
|
-
keys[tokens[i].name] = true;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
regexp = { toPath, keys };
|
|
1064
|
-
cache$1.set(fullPath, regexp);
|
|
1065
|
-
route.fullPath = fullPath;
|
|
1066
|
-
}
|
|
1067
|
-
let url = regexp.toPath(params, options) || '/';
|
|
1068
|
-
if (options.stringifyQueryParams && params) {
|
|
1069
|
-
const queryParams = {};
|
|
1070
|
-
const keys = Object.keys(params);
|
|
1071
|
-
for (let i = 0; i < keys.length; i++) {
|
|
1072
|
-
const key = keys[i];
|
|
1073
|
-
if (!regexp.keys[key]) {
|
|
1074
|
-
queryParams[key] = params[key];
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
const query = options.stringifyQueryParams(queryParams);
|
|
1078
|
-
if (query) {
|
|
1079
|
-
url += query.charAt(0) === '?' ? query : `?${query}`;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return url;
|
|
1083
|
-
};
|
|
1084
|
-
}
|
|
1085
|
-
/**
|
|
1086
|
-
* @typedef NavigationTrigger
|
|
1087
|
-
* @type {object}
|
|
1088
|
-
* @property {function()} activate
|
|
1089
|
-
* @property {function()} inactivate
|
|
1090
|
-
* Configures what triggers Router navigation events:
|
|
1091
|
-
* - `POPSTATE`: popstate events on the current `window`
|
|
1092
|
-
* - `CLICK`: click events on `<a>` links leading to the current page
|
|
1093
|
-
*
|
|
1094
|
-
* This method is invoked with the pre-configured values when creating a new Router instance.
|
|
1095
|
-
* By default, both `POPSTATE` and `CLICK` are enabled. This setup is expected to cover most of the use cases.
|
|
1096
|
-
*
|
|
1097
|
-
* See the `router-config.js` for the default navigation triggers config. Based on it, you can
|
|
1098
|
-
* create the own one and only import the triggers you need, instead of pulling in all the code,
|
|
1099
|
-
* e.g. if you want to handle `click` differently.
|
|
1100
|
-
*
|
|
1101
|
-
* See also **Navigation Triggers** section in [Live Examples](#/classes/Router/demos/demo/index.html).
|
|
1102
|
-
*
|
|
1103
|
-
* @param {...NavigationTrigger} triggers
|
|
1104
|
-
*/
|
|
1105
|
-
/** @type {Array<NavigationTrigger>} */
|
|
1106
|
-
let triggers = [];
|
|
1107
|
-
function setNavigationTriggers(newTriggers) {
|
|
1108
|
-
triggers.forEach((trigger) => trigger.inactivate());
|
|
1109
|
-
newTriggers.forEach((trigger) => trigger.activate());
|
|
1110
|
-
triggers = newTriggers;
|
|
1111
|
-
}
|
|
1112
|
-
const willAnimate = elem => {
|
|
1113
|
-
const name = getComputedStyle(elem).getPropertyValue('animation-name');
|
|
1114
|
-
return name && name !== 'none';
|
|
1115
|
-
};
|
|
1116
|
-
const waitForAnimation = (elem, cb) => {
|
|
1117
|
-
const listener = () => {
|
|
1118
|
-
elem.removeEventListener('animationend', listener);
|
|
1119
|
-
cb();
|
|
1120
|
-
};
|
|
1121
|
-
elem.addEventListener('animationend', listener);
|
|
1122
|
-
};
|
|
1123
|
-
function animate(elem, className) {
|
|
1124
|
-
elem.classList.add(className);
|
|
1125
|
-
return new Promise(resolve => {
|
|
1126
|
-
if (willAnimate(elem)) {
|
|
1127
|
-
const rect = elem.getBoundingClientRect();
|
|
1128
|
-
const size = `height: ${rect.bottom - rect.top}px; width: ${rect.right - rect.left}px`;
|
|
1129
|
-
elem.setAttribute('style', `position: absolute; ${size}`);
|
|
1130
|
-
waitForAnimation(elem, () => {
|
|
1131
|
-
elem.classList.remove(className);
|
|
1132
|
-
elem.removeAttribute('style');
|
|
1133
|
-
resolve();
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
else {
|
|
1137
|
-
elem.classList.remove(className);
|
|
1138
|
-
resolve();
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
const MAX_REDIRECT_COUNT = 256;
|
|
1143
|
-
function isResultNotEmpty(result) {
|
|
1144
|
-
return result !== null && result !== undefined;
|
|
1145
|
-
}
|
|
1146
|
-
function copyContextWithoutNext(context) {
|
|
1147
|
-
const copy = Object.assign({}, context);
|
|
1148
|
-
delete copy.next;
|
|
1149
|
-
return copy;
|
|
1150
|
-
}
|
|
1151
|
-
function createLocation({ pathname = '', search = '', hash = '', chain = [], params = {}, redirectFrom, resolver }, route) {
|
|
1152
|
-
const routes = chain.map(item => item.route);
|
|
1153
|
-
return {
|
|
1154
|
-
baseUrl: resolver && resolver.baseUrl || '',
|
|
1155
|
-
pathname,
|
|
1156
|
-
search,
|
|
1157
|
-
hash,
|
|
1158
|
-
routes,
|
|
1159
|
-
route: route || routes.length && routes[routes.length - 1] || null,
|
|
1160
|
-
params,
|
|
1161
|
-
redirectFrom,
|
|
1162
|
-
getUrl: (userParams = {}) => getPathnameForRouter(pathToRegexp_1.compile(getMatchedPath(routes))(Object.assign({}, params, userParams)), resolver)
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
function createRedirect(context, pathname) {
|
|
1166
|
-
const params = Object.assign({}, context.params);
|
|
1167
|
-
return {
|
|
1168
|
-
redirect: {
|
|
1169
|
-
pathname,
|
|
1170
|
-
from: context.pathname,
|
|
1171
|
-
params
|
|
1172
|
-
}
|
|
1173
|
-
};
|
|
1174
|
-
}
|
|
1175
|
-
function renderElement(context, element) {
|
|
1176
|
-
element.location = createLocation(context);
|
|
1177
|
-
const index = context.chain.map(item => item.route).indexOf(context.route);
|
|
1178
|
-
context.chain[index].element = element;
|
|
1179
|
-
return element;
|
|
1180
|
-
}
|
|
1181
|
-
function runCallbackIfPossible(callback, args, thisArg) {
|
|
1182
|
-
if (isFunction(callback)) {
|
|
1183
|
-
return callback.apply(thisArg, args);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
function amend(amendmentFunction, args, element) {
|
|
1187
|
-
return amendmentResult => {
|
|
1188
|
-
if (amendmentResult && (amendmentResult.cancel || amendmentResult.redirect)) {
|
|
1189
|
-
return amendmentResult;
|
|
1190
|
-
}
|
|
1191
|
-
if (element) {
|
|
1192
|
-
return runCallbackIfPossible(element[amendmentFunction], args, element);
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
function processNewChildren(newChildren, route) {
|
|
1197
|
-
if (!Array.isArray(newChildren) && !isObject(newChildren)) {
|
|
1198
|
-
throw new Error(log(`Incorrect "children" value for the route ${route.path}: expected array or object, but got ${newChildren}`));
|
|
1199
|
-
}
|
|
1200
|
-
route.__children = [];
|
|
1201
|
-
const childRoutes = toArray(newChildren);
|
|
1202
|
-
for (let i = 0; i < childRoutes.length; i++) {
|
|
1203
|
-
ensureRoute(childRoutes[i]);
|
|
1204
|
-
route.__children.push(childRoutes[i]);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
function removeDomNodes(nodes) {
|
|
1208
|
-
if (nodes && nodes.length) {
|
|
1209
|
-
const parent = nodes[0].parentNode;
|
|
1210
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
1211
|
-
parent.removeChild(nodes[i]);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
function getPathnameForRouter(pathname, router) {
|
|
1216
|
-
const base = router.__effectiveBaseUrl;
|
|
1217
|
-
return base
|
|
1218
|
-
? router.constructor.__createUrl(pathname.replace(/^\//, ''), base).pathname
|
|
1219
|
-
: pathname;
|
|
1220
|
-
}
|
|
1221
|
-
function getMatchedPath(chain) {
|
|
1222
|
-
return chain.map(item => item.path).reduce((a, b) => {
|
|
1223
|
-
if (b.length) {
|
|
1224
|
-
return a.replace(/\/$/, '') + '/' + b.replace(/^\//, '');
|
|
1225
|
-
}
|
|
1226
|
-
return a;
|
|
1227
|
-
}, '');
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* A simple client-side router for single-page applications. It uses
|
|
1231
|
-
* express-style middleware and has a first-class support for Web Components and
|
|
1232
|
-
* lazy-loading. Works great in Polymer and non-Polymer apps.
|
|
1233
|
-
*
|
|
1234
|
-
* Use `new Router(outlet, options)` to create a new Router instance.
|
|
1235
|
-
*
|
|
1236
|
-
* * The `outlet` parameter is a reference to the DOM node to render
|
|
1237
|
-
* the content into.
|
|
1238
|
-
*
|
|
1239
|
-
* * The `options` parameter is an optional object with options. The following
|
|
1240
|
-
* keys are supported:
|
|
1241
|
-
* * `baseUrl` — the initial value for [
|
|
1242
|
-
* the `baseUrl` property
|
|
1243
|
-
* ](#/classes/Router#property-baseUrl)
|
|
1244
|
-
*
|
|
1245
|
-
* The Router instance is automatically subscribed to navigation events
|
|
1246
|
-
* on `window`.
|
|
1247
|
-
*
|
|
1248
|
-
* See [Live Examples](#/classes/Router/demos/demo/index.html) for the detailed usage demo and code snippets.
|
|
1249
|
-
*
|
|
1250
|
-
* See also detailed API docs for the following methods, for the advanced usage:
|
|
1251
|
-
*
|
|
1252
|
-
* * [setOutlet](#/classes/Router#method-setOutlet) – should be used to configure the outlet.
|
|
1253
|
-
* * [setTriggers](#/classes/Router#method-setTriggers) – should be used to configure the navigation events.
|
|
1254
|
-
* * [setRoutes](#/classes/Router#method-setRoutes) – should be used to configure the routes.
|
|
1255
|
-
*
|
|
1256
|
-
* Only `setRoutes` has to be called manually, others are automatically invoked when creating a new instance.
|
|
1257
|
-
*
|
|
1258
|
-
* @extends Resolver
|
|
1259
|
-
* @demo demo/index.html
|
|
1260
|
-
* @summary JavaScript class that renders different DOM content depending on
|
|
1261
|
-
* a given path. It can re-render when triggered or automatically on
|
|
1262
|
-
* 'popstate' and / or 'click' events.
|
|
1263
|
-
*/
|
|
1264
|
-
class Router extends Resolver {
|
|
1265
|
-
/**
|
|
1266
|
-
* Creates a new Router instance with a given outlet, and
|
|
1267
|
-
* automatically subscribes it to navigation events on the `window`.
|
|
1268
|
-
* Using a constructor argument or a setter for outlet is equivalent:
|
|
1269
|
-
*
|
|
1270
|
-
* ```
|
|
1271
|
-
* const router = new Router();
|
|
1272
|
-
* router.setOutlet(outlet);
|
|
1273
|
-
* ```
|
|
1274
|
-
* @param {?Node=} outlet
|
|
1275
|
-
* @param {?RouterOptions=} options
|
|
1276
|
-
*/
|
|
1277
|
-
constructor(outlet, options) {
|
|
1278
|
-
const baseElement = document.head.querySelector('base');
|
|
1279
|
-
const baseHref = baseElement && baseElement.getAttribute('href');
|
|
1280
|
-
super([], Object.assign({
|
|
1281
|
-
// Default options
|
|
1282
|
-
baseUrl: baseHref && Resolver.__createUrl(baseHref, document.URL).pathname.replace(/[^\/]*$/, '')
|
|
1283
|
-
}, options));
|
|
1284
|
-
this.resolveRoute = context => this.__resolveRoute(context);
|
|
1285
|
-
setNavigationTriggers([POPSTATE, CLICK]);
|
|
1286
|
-
/**
|
|
1287
|
-
* The base URL for all routes in the router instance. By default,
|
|
1288
|
-
* if the base element exists in the `<head>`, vaadin-router
|
|
1289
|
-
* takes the `<base href>` attribute value, resolves against current `document.URL`
|
|
1290
|
-
* and gets the `pathname` from the result.
|
|
1291
|
-
*
|
|
1292
|
-
* @public
|
|
1293
|
-
* @type {string}
|
|
1294
|
-
*/
|
|
1295
|
-
this.baseUrl;
|
|
1296
|
-
/**
|
|
1297
|
-
* A promise that is settled after the current render cycle completes. If
|
|
1298
|
-
* there is no render cycle in progress the promise is immediately settled
|
|
1299
|
-
* with the last render cycle result.
|
|
1300
|
-
*
|
|
1301
|
-
* @public
|
|
1302
|
-
* @type {!Promise<!RouterLocation>}
|
|
1303
|
-
*/
|
|
1304
|
-
this.ready;
|
|
1305
|
-
this.ready = Promise.resolve(outlet);
|
|
1306
|
-
/**
|
|
1307
|
-
* Contains read-only information about the current router location:
|
|
1308
|
-
* pathname, active routes, parameters. See the
|
|
1309
|
-
* [Location type declaration](#/classes/RouterLocation)
|
|
1310
|
-
* for more details.
|
|
1311
|
-
*
|
|
1312
|
-
* @public
|
|
1313
|
-
* @type {!RouterLocation}
|
|
1314
|
-
*/
|
|
1315
|
-
this.location;
|
|
1316
|
-
this.location = createLocation({ resolver: this });
|
|
1317
|
-
this.__lastStartedRenderId = 0;
|
|
1318
|
-
this.__navigationEventHandler = this.__onNavigationEvent.bind(this);
|
|
1319
|
-
this.setOutlet(outlet);
|
|
1320
|
-
this.subscribe();
|
|
1321
|
-
// Using WeakMap instead of WeakSet because WeakSet is not supported by IE11
|
|
1322
|
-
this.__createdByRouter = new WeakMap();
|
|
1323
|
-
this.__addedByRouter = new WeakMap();
|
|
1324
|
-
}
|
|
1325
|
-
__resolveRoute(context) {
|
|
1326
|
-
const route = context.route;
|
|
1327
|
-
let callbacks = Promise.resolve();
|
|
1328
|
-
if (isFunction(route.children)) {
|
|
1329
|
-
callbacks = callbacks
|
|
1330
|
-
.then(() => route.children(copyContextWithoutNext(context)))
|
|
1331
|
-
.then(children => {
|
|
1332
|
-
// The route.children() callback might have re-written the
|
|
1333
|
-
// route.children property instead of returning a value
|
|
1334
|
-
if (!isResultNotEmpty(children) && !isFunction(route.children)) {
|
|
1335
|
-
children = route.children;
|
|
1336
|
-
}
|
|
1337
|
-
processNewChildren(children, route);
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
const commands = {
|
|
1341
|
-
redirect: path => createRedirect(context, path),
|
|
1342
|
-
component: (component) => {
|
|
1343
|
-
const element = document.createElement(component);
|
|
1344
|
-
this.__createdByRouter.set(element, true);
|
|
1345
|
-
return element;
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
return callbacks
|
|
1349
|
-
.then(() => {
|
|
1350
|
-
if (this.__isLatestRender(context)) {
|
|
1351
|
-
return runCallbackIfPossible(route.action, [context, commands], route);
|
|
1352
|
-
}
|
|
1353
|
-
})
|
|
1354
|
-
.then(result => {
|
|
1355
|
-
if (isResultNotEmpty(result)) {
|
|
1356
|
-
// Actions like `() => import('my-view.js')` are not expected to
|
|
1357
|
-
// end the resolution, despite the result is not empty. Checking
|
|
1358
|
-
// the result with a whitelist of values that end the resolution.
|
|
1359
|
-
if (result instanceof HTMLElement ||
|
|
1360
|
-
result.redirect ||
|
|
1361
|
-
result === notFoundResult) {
|
|
1362
|
-
return result;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
if (isString(route.redirect)) {
|
|
1366
|
-
return commands.redirect(route.redirect);
|
|
1367
|
-
}
|
|
1368
|
-
if (route.bundle) {
|
|
1369
|
-
return loadBundle(route.bundle)
|
|
1370
|
-
.then(() => { }, () => {
|
|
1371
|
-
throw new Error(log(`Bundle not found: ${route.bundle}. Check if the file name is correct`));
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
})
|
|
1375
|
-
.then(result => {
|
|
1376
|
-
if (isResultNotEmpty(result)) {
|
|
1377
|
-
return result;
|
|
1378
|
-
}
|
|
1379
|
-
if (isString(route.component)) {
|
|
1380
|
-
return commands.component(route.component);
|
|
1381
|
-
}
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Sets the router outlet (the DOM node where the content for the current
|
|
1386
|
-
* route is inserted). Any content pre-existing in the router outlet is
|
|
1387
|
-
* removed at the end of each render pass.
|
|
1388
|
-
*
|
|
1389
|
-
* NOTE: this method is automatically invoked first time when creating a new Router instance.
|
|
1390
|
-
*
|
|
1391
|
-
* @param {?Node} outlet the DOM node where the content for the current route
|
|
1392
|
-
* is inserted.
|
|
1393
|
-
*/
|
|
1394
|
-
setOutlet(outlet) {
|
|
1395
|
-
if (outlet) {
|
|
1396
|
-
this.__ensureOutlet(outlet);
|
|
1397
|
-
}
|
|
1398
|
-
this.__outlet = outlet;
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Returns the current router outlet. The initial value is `undefined`.
|
|
1402
|
-
*
|
|
1403
|
-
* @return {?Node} the current router outlet (or `undefined`)
|
|
1404
|
-
*/
|
|
1405
|
-
getOutlet() {
|
|
1406
|
-
return this.__outlet;
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Sets the routing config (replacing the existing one) and triggers a
|
|
1410
|
-
* navigation event so that the router outlet is refreshed according to the
|
|
1411
|
-
* current `window.location` and the new routing config.
|
|
1412
|
-
*
|
|
1413
|
-
* Each route object may have the following properties, listed here in the processing order:
|
|
1414
|
-
* * `path` – the route path (relative to the parent route if any) in the
|
|
1415
|
-
* [express.js syntax](https://expressjs.com/en/guide/routing.html#route-paths").
|
|
1416
|
-
*
|
|
1417
|
-
* * `children` – an array of nested routes or a function that provides this
|
|
1418
|
-
* array at the render time. The function can be synchronous or asynchronous:
|
|
1419
|
-
* in the latter case the render is delayed until the returned promise is
|
|
1420
|
-
* resolved. The `children` function is executed every time when this route is
|
|
1421
|
-
* being rendered. This allows for dynamic route structures (e.g. backend-defined),
|
|
1422
|
-
* but it might have a performance impact as well. In order to avoid calling
|
|
1423
|
-
* the function on subsequent renders, you can override the `children` property
|
|
1424
|
-
* of the route object and save the calculated array there
|
|
1425
|
-
* (via `context.route.children = [ route1, route2, ...];`).
|
|
1426
|
-
* Parent routes are fully resolved before resolving the children. Children
|
|
1427
|
-
* 'path' values are relative to the parent ones.
|
|
1428
|
-
*
|
|
1429
|
-
* * `action` – the action that is executed before the route is resolved.
|
|
1430
|
-
* The value for this property should be a function, accepting `context`
|
|
1431
|
-
* and `commands` parameters described below. If present, this function is
|
|
1432
|
-
* always invoked first, disregarding of the other properties' presence.
|
|
1433
|
-
* The action can return a result directly or within a `Promise`, which
|
|
1434
|
-
* resolves to the result. If the action result is an `HTMLElement` instance,
|
|
1435
|
-
* a `commands.component(name)` result, a `commands.redirect(path)` result,
|
|
1436
|
-
* or a `context.next()` result, the current route resolution is finished,
|
|
1437
|
-
* and other route config properties are ignored.
|
|
1438
|
-
* See also **Route Actions** section in [Live Examples](#/classes/Router/demos/demo/index.html).
|
|
1439
|
-
*
|
|
1440
|
-
* * `redirect` – other route's path to redirect to. Passes all route parameters to the redirect target.
|
|
1441
|
-
* The target route should also be defined.
|
|
1442
|
-
* See also **Redirects** section in [Live Examples](#/classes/Router/demos/demo/index.html).
|
|
1443
|
-
*
|
|
1444
|
-
* * `bundle` – string containing the path to `.js` or `.mjs` bundle to load before resolving the route,
|
|
1445
|
-
* or the object with "module" and "nomodule" keys referring to different bundles.
|
|
1446
|
-
* Each bundle is only loaded once. If "module" and "nomodule" are set, only one bundle is loaded,
|
|
1447
|
-
* depending on whether the browser supports ES modules or not.
|
|
1448
|
-
* The property is ignored when either an `action` returns the result or `redirect` property is present.
|
|
1449
|
-
* Any error, e.g. 404 while loading bundle will cause route resolution to throw.
|
|
1450
|
-
* See also **Code Splitting** section in [Live Examples](#/classes/Router/demos/demo/index.html).
|
|
1451
|
-
*
|
|
1452
|
-
* * `component` – the tag name of the Web Component to resolve the route to.
|
|
1453
|
-
* The property is ignored when either an `action` returns the result or `redirect` property is present.
|
|
1454
|
-
* If route contains the `component` property (or an action that return a component)
|
|
1455
|
-
* and its child route also contains the `component` property, child route's component
|
|
1456
|
-
* will be rendered as a light dom child of a parent component.
|
|
1457
|
-
*
|
|
1458
|
-
* * `name` – the string name of the route to use in the
|
|
1459
|
-
* [`router.urlForName(name, params)`](#/classes/Router#method-urlForName)
|
|
1460
|
-
* navigation helper method.
|
|
1461
|
-
*
|
|
1462
|
-
* For any route function (`action`, `children`) defined, the corresponding `route` object is available inside the callback
|
|
1463
|
-
* through the `this` reference. If you need to access it, make sure you define the callback as a non-arrow function
|
|
1464
|
-
* because arrow functions do not have their own `this` reference.
|
|
1465
|
-
*
|
|
1466
|
-
* `context` object that is passed to `action` function holds the following properties:
|
|
1467
|
-
* * `context.pathname` – string with the pathname being resolved
|
|
1468
|
-
*
|
|
1469
|
-
* * `context.search` – search query string
|
|
1470
|
-
*
|
|
1471
|
-
* * `context.hash` – hash string
|
|
1472
|
-
*
|
|
1473
|
-
* * `context.params` – object with route parameters
|
|
1474
|
-
*
|
|
1475
|
-
* * `context.route` – object that holds the route that is currently being rendered.
|
|
1476
|
-
*
|
|
1477
|
-
* * `context.next()` – function for asynchronously getting the next route
|
|
1478
|
-
* contents from the resolution chain (if any)
|
|
1479
|
-
*
|
|
1480
|
-
* `commands` object that is passed to `action` function has
|
|
1481
|
-
* the following methods:
|
|
1482
|
-
*
|
|
1483
|
-
* * `commands.redirect(path)` – function that creates a redirect data
|
|
1484
|
-
* for the path specified.
|
|
1485
|
-
*
|
|
1486
|
-
* * `commands.component(component)` – function that creates a new HTMLElement
|
|
1487
|
-
* with current context. Note: the component created by this function is reused if visiting the same path twice in row.
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* @param {!Array<!Route>|!Route} routes a single route or an array of those
|
|
1491
|
-
* @param {?boolean} skipRender configure the router but skip rendering the
|
|
1492
|
-
* route corresponding to the current `window.location` values
|
|
1493
|
-
*
|
|
1494
|
-
* @return {!Promise<!Node>}
|
|
1495
|
-
*/
|
|
1496
|
-
setRoutes(routes, skipRender = false) {
|
|
1497
|
-
this.__previousContext = undefined;
|
|
1498
|
-
this.__urlForName = undefined;
|
|
1499
|
-
super.setRoutes(routes);
|
|
1500
|
-
if (!skipRender) {
|
|
1501
|
-
this.__onNavigationEvent();
|
|
1502
|
-
}
|
|
1503
|
-
return this.ready;
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Asynchronously resolves the given pathname and renders the resolved route
|
|
1507
|
-
* component into the router outlet. If no router outlet is set at the time of
|
|
1508
|
-
* calling this method, or at the time when the route resolution is completed,
|
|
1509
|
-
* a `TypeError` is thrown.
|
|
1510
|
-
*
|
|
1511
|
-
* Returns a promise that is fulfilled with the router outlet DOM Node after
|
|
1512
|
-
* the route component is created and inserted into the router outlet, or
|
|
1513
|
-
* rejected if no route matches the given path.
|
|
1514
|
-
*
|
|
1515
|
-
* If another render pass is started before the previous one is completed, the
|
|
1516
|
-
* result of the previous render pass is ignored.
|
|
1517
|
-
*
|
|
1518
|
-
* @param {!string|!{pathname: !string, search: ?string, hash: ?string}} pathnameOrContext
|
|
1519
|
-
* the pathname to render or a context object with a `pathname` property,
|
|
1520
|
-
* optional `search` and `hash` properties, and other properties
|
|
1521
|
-
* to pass to the resolver.
|
|
1522
|
-
* @param {boolean=} shouldUpdateHistory
|
|
1523
|
-
* update browser history with the rendered location
|
|
1524
|
-
* @return {!Promise<!Node>}
|
|
1525
|
-
*/
|
|
1526
|
-
render(pathnameOrContext, shouldUpdateHistory) {
|
|
1527
|
-
const renderId = ++this.__lastStartedRenderId;
|
|
1528
|
-
const context = Object.assign({
|
|
1529
|
-
search: '',
|
|
1530
|
-
hash: ''
|
|
1531
|
-
}, isString(pathnameOrContext)
|
|
1532
|
-
? { pathname: pathnameOrContext }
|
|
1533
|
-
: pathnameOrContext, {
|
|
1534
|
-
__renderId: renderId
|
|
1535
|
-
});
|
|
1536
|
-
// Find the first route that resolves to a non-empty result
|
|
1537
|
-
this.ready = this.resolve(context)
|
|
1538
|
-
// Process the result of this.resolve() and handle all special commands:
|
|
1539
|
-
// (redirect / prevent / component). If the result is a 'component',
|
|
1540
|
-
// then go deeper and build the entire chain of nested components matching
|
|
1541
|
-
// the pathname. Also call all 'on before' callbacks along the way.
|
|
1542
|
-
.then(context => this.__fullyResolveChain(context))
|
|
1543
|
-
.then(context => {
|
|
1544
|
-
if (this.__isLatestRender(context)) {
|
|
1545
|
-
const previousContext = this.__previousContext;
|
|
1546
|
-
// Check if the render was prevented and make an early return in that case
|
|
1547
|
-
if (context === previousContext) {
|
|
1548
|
-
// Replace the history with the previous context
|
|
1549
|
-
// to make sure the URL stays the same.
|
|
1550
|
-
this.__updateBrowserHistory(previousContext, true);
|
|
1551
|
-
return this.location;
|
|
1552
|
-
}
|
|
1553
|
-
this.location = createLocation(context);
|
|
1554
|
-
if (shouldUpdateHistory) {
|
|
1555
|
-
// Replace only if first render redirects, so that we don’t leave
|
|
1556
|
-
// the redirecting record in the history
|
|
1557
|
-
this.__updateBrowserHistory(context, renderId === 1);
|
|
1558
|
-
}
|
|
1559
|
-
fireRouterEvent('location-changed', { router: this, location: this.location });
|
|
1560
|
-
// Skip detaching/re-attaching there are no render changes
|
|
1561
|
-
if (context.__skipAttach) {
|
|
1562
|
-
this.__copyUnchangedElements(context, previousContext);
|
|
1563
|
-
this.__previousContext = context;
|
|
1564
|
-
return this.location;
|
|
1565
|
-
}
|
|
1566
|
-
this.__addAppearingContent(context, previousContext);
|
|
1567
|
-
const animationDone = this.__animateIfNeeded(context);
|
|
1568
|
-
this.__runOnAfterEnterCallbacks(context);
|
|
1569
|
-
this.__runOnAfterLeaveCallbacks(context, previousContext);
|
|
1570
|
-
return animationDone.then(() => {
|
|
1571
|
-
if (this.__isLatestRender(context)) {
|
|
1572
|
-
// If there is another render pass started after this one,
|
|
1573
|
-
// the 'disappearing content' would be removed when the other
|
|
1574
|
-
// render pass calls `this.__addAppearingContent()`
|
|
1575
|
-
this.__removeDisappearingContent();
|
|
1576
|
-
this.__previousContext = context;
|
|
1577
|
-
return this.location;
|
|
1578
|
-
}
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
})
|
|
1582
|
-
.catch(error => {
|
|
1583
|
-
if (renderId === this.__lastStartedRenderId) {
|
|
1584
|
-
if (shouldUpdateHistory) {
|
|
1585
|
-
this.__updateBrowserHistory(context);
|
|
1586
|
-
}
|
|
1587
|
-
removeDomNodes(this.__outlet && this.__outlet.children);
|
|
1588
|
-
this.location = createLocation(Object.assign(context, { resolver: this }));
|
|
1589
|
-
fireRouterEvent('error', Object.assign({ router: this, error }, context));
|
|
1590
|
-
throw error;
|
|
1591
|
-
}
|
|
1592
|
-
});
|
|
1593
|
-
return this.ready;
|
|
1594
|
-
}
|
|
1595
|
-
// `topOfTheChainContextBeforeRedirects` is a context coming from Resolver.resolve().
|
|
1596
|
-
// It would contain a 'redirect' route or the first 'component' route that
|
|
1597
|
-
// matched the pathname. There might be more child 'component' routes to be
|
|
1598
|
-
// resolved and added into the chain. This method would find and add them.
|
|
1599
|
-
// `contextBeforeRedirects` is the context containing such a child component
|
|
1600
|
-
// route. It's only necessary when this method is called recursively (otherwise
|
|
1601
|
-
// it's the same as the 'top of the chain' context).
|
|
1602
|
-
//
|
|
1603
|
-
// Apart from building the chain of child components, this method would also
|
|
1604
|
-
// handle 'redirect' routes, call 'onBefore' callbacks and handle 'prevent'
|
|
1605
|
-
// and 'redirect' callback results.
|
|
1606
|
-
__fullyResolveChain(topOfTheChainContextBeforeRedirects, contextBeforeRedirects = topOfTheChainContextBeforeRedirects) {
|
|
1607
|
-
return this.__findComponentContextAfterAllRedirects(contextBeforeRedirects)
|
|
1608
|
-
// `contextAfterRedirects` is always a context with an `HTMLElement` result
|
|
1609
|
-
// In other cases the promise gets rejected and .then() is not called
|
|
1610
|
-
.then(contextAfterRedirects => {
|
|
1611
|
-
const redirectsHappened = contextAfterRedirects !== contextBeforeRedirects;
|
|
1612
|
-
const topOfTheChainContextAfterRedirects = redirectsHappened ? contextAfterRedirects : topOfTheChainContextBeforeRedirects;
|
|
1613
|
-
const matchedPath = getPathnameForRouter(getMatchedPath(contextAfterRedirects.chain), contextAfterRedirects.resolver);
|
|
1614
|
-
const isFound = (matchedPath === contextAfterRedirects.pathname);
|
|
1615
|
-
// Recursive method to try matching more child and sibling routes
|
|
1616
|
-
const findNextContextIfAny = (context, parent = context.route, prevResult) => {
|
|
1617
|
-
return context.next(undefined, parent, prevResult).then(nextContext => {
|
|
1618
|
-
if (nextContext === null || nextContext === notFoundResult) {
|
|
1619
|
-
// Next context is not found in children, ...
|
|
1620
|
-
if (isFound) {
|
|
1621
|
-
// ...but original context is already fully matching - use it
|
|
1622
|
-
return context;
|
|
1623
|
-
}
|
|
1624
|
-
else if (parent.parent !== null) {
|
|
1625
|
-
// ...and there is no full match yet - step up to check siblings
|
|
1626
|
-
return findNextContextIfAny(context, parent.parent, nextContext);
|
|
1627
|
-
}
|
|
1628
|
-
else {
|
|
1629
|
-
return nextContext;
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
return nextContext;
|
|
1633
|
-
});
|
|
1634
|
-
};
|
|
1635
|
-
return findNextContextIfAny(contextAfterRedirects).then(nextContext => {
|
|
1636
|
-
if (nextContext === null || nextContext === notFoundResult) {
|
|
1637
|
-
throw getNotFoundError(topOfTheChainContextAfterRedirects);
|
|
1638
|
-
}
|
|
1639
|
-
return nextContext
|
|
1640
|
-
&& nextContext !== notFoundResult
|
|
1641
|
-
&& nextContext !== contextAfterRedirects
|
|
1642
|
-
? this.__fullyResolveChain(topOfTheChainContextAfterRedirects, nextContext)
|
|
1643
|
-
: this.__amendWithOnBeforeCallbacks(contextAfterRedirects);
|
|
1644
|
-
});
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
__findComponentContextAfterAllRedirects(context) {
|
|
1648
|
-
const result = context.result;
|
|
1649
|
-
if (result instanceof HTMLElement) {
|
|
1650
|
-
renderElement(context, result);
|
|
1651
|
-
return Promise.resolve(context);
|
|
1652
|
-
}
|
|
1653
|
-
else if (result.redirect) {
|
|
1654
|
-
return this.__redirect(result.redirect, context.__redirectCount, context.__renderId)
|
|
1655
|
-
.then(context => this.__findComponentContextAfterAllRedirects(context));
|
|
1656
|
-
}
|
|
1657
|
-
else if (result instanceof Error) {
|
|
1658
|
-
return Promise.reject(result);
|
|
1659
|
-
}
|
|
1660
|
-
else {
|
|
1661
|
-
return Promise.reject(new Error(log(`Invalid route resolution result for path "${context.pathname}". ` +
|
|
1662
|
-
`Expected redirect object or HTML element, but got: "${logValue(result)}". ` +
|
|
1663
|
-
`Double check the action return value for the route.`)));
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
__amendWithOnBeforeCallbacks(contextWithFullChain) {
|
|
1667
|
-
return this.__runOnBeforeCallbacks(contextWithFullChain).then(amendedContext => {
|
|
1668
|
-
if (amendedContext === this.__previousContext || amendedContext === contextWithFullChain) {
|
|
1669
|
-
return amendedContext;
|
|
1670
|
-
}
|
|
1671
|
-
return this.__fullyResolveChain(amendedContext);
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
__runOnBeforeCallbacks(newContext) {
|
|
1675
|
-
const previousContext = this.__previousContext || {};
|
|
1676
|
-
const previousChain = previousContext.chain || [];
|
|
1677
|
-
const newChain = newContext.chain;
|
|
1678
|
-
let callbacks = Promise.resolve();
|
|
1679
|
-
const prevent = () => ({ cancel: true });
|
|
1680
|
-
const redirect = (pathname) => createRedirect(newContext, pathname);
|
|
1681
|
-
newContext.__divergedChainIndex = 0;
|
|
1682
|
-
newContext.__skipAttach = false;
|
|
1683
|
-
if (previousChain.length) {
|
|
1684
|
-
for (let i = 0; i < Math.min(previousChain.length, newChain.length); i = ++newContext.__divergedChainIndex) {
|
|
1685
|
-
if (previousChain[i].route !== newChain[i].route
|
|
1686
|
-
|| previousChain[i].path !== newChain[i].path && previousChain[i].element !== newChain[i].element
|
|
1687
|
-
|| !this.__isReusableElement(previousChain[i].element, newChain[i].element)) {
|
|
1688
|
-
break;
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
// Skip re-attaching and notifications if element and chain do not change
|
|
1692
|
-
newContext.__skipAttach =
|
|
1693
|
-
// Same route chain
|
|
1694
|
-
newChain.length === previousChain.length && newContext.__divergedChainIndex == newChain.length &&
|
|
1695
|
-
// Same element
|
|
1696
|
-
this.__isReusableElement(newContext.result, previousContext.result);
|
|
1697
|
-
if (newContext.__skipAttach) {
|
|
1698
|
-
// execute onBeforeLeave for changed segment element when skipping attach
|
|
1699
|
-
for (let i = newChain.length - 1; i >= 0; i--) {
|
|
1700
|
-
callbacks = this.__runOnBeforeLeaveCallbacks(callbacks, newContext, { prevent }, previousChain[i]);
|
|
1701
|
-
}
|
|
1702
|
-
// execute onBeforeEnter for changed segment element when skipping attach
|
|
1703
|
-
for (let i = 0; i < newChain.length; i++) {
|
|
1704
|
-
callbacks = this.__runOnBeforeEnterCallbacks(callbacks, newContext, { prevent, redirect }, newChain[i]);
|
|
1705
|
-
previousChain[i].element.location = createLocation(newContext, previousChain[i].route);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
else {
|
|
1709
|
-
// execute onBeforeLeave when NOT skipping attach
|
|
1710
|
-
for (let i = previousChain.length - 1; i >= newContext.__divergedChainIndex; i--) {
|
|
1711
|
-
callbacks = this.__runOnBeforeLeaveCallbacks(callbacks, newContext, { prevent }, previousChain[i]);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
// execute onBeforeEnter when NOT skipping attach
|
|
1716
|
-
if (!newContext.__skipAttach) {
|
|
1717
|
-
for (let i = 0; i < newChain.length; i++) {
|
|
1718
|
-
if (i < newContext.__divergedChainIndex) {
|
|
1719
|
-
if (i < previousChain.length && previousChain[i].element) {
|
|
1720
|
-
previousChain[i].element.location = createLocation(newContext, previousChain[i].route);
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
else {
|
|
1724
|
-
callbacks = this.__runOnBeforeEnterCallbacks(callbacks, newContext, { prevent, redirect }, newChain[i]);
|
|
1725
|
-
if (newChain[i].element) {
|
|
1726
|
-
newChain[i].element.location = createLocation(newContext, newChain[i].route);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
return callbacks.then(amendmentResult => {
|
|
1732
|
-
if (amendmentResult) {
|
|
1733
|
-
if (amendmentResult.cancel) {
|
|
1734
|
-
this.__previousContext.__renderId = newContext.__renderId;
|
|
1735
|
-
return this.__previousContext;
|
|
1736
|
-
}
|
|
1737
|
-
if (amendmentResult.redirect) {
|
|
1738
|
-
return this.__redirect(amendmentResult.redirect, newContext.__redirectCount, newContext.__renderId);
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
return newContext;
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
__runOnBeforeLeaveCallbacks(callbacks, newContext, commands, chainElement) {
|
|
1745
|
-
const location = createLocation(newContext);
|
|
1746
|
-
return callbacks.then(result => {
|
|
1747
|
-
if (this.__isLatestRender(newContext)) {
|
|
1748
|
-
const afterLeaveFunction = amend('onBeforeLeave', [location, commands, this], chainElement.element);
|
|
1749
|
-
return afterLeaveFunction(result);
|
|
1750
|
-
}
|
|
1751
|
-
}).then(result => {
|
|
1752
|
-
if (!(result || {}).redirect) {
|
|
1753
|
-
return result;
|
|
1754
|
-
}
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
__runOnBeforeEnterCallbacks(callbacks, newContext, commands, chainElement) {
|
|
1758
|
-
const location = createLocation(newContext, chainElement.route);
|
|
1759
|
-
return callbacks.then(result => {
|
|
1760
|
-
if (this.__isLatestRender(newContext)) {
|
|
1761
|
-
const beforeEnterFunction = amend('onBeforeEnter', [location, commands, this], chainElement.element);
|
|
1762
|
-
return beforeEnterFunction(result);
|
|
1763
|
-
}
|
|
1764
|
-
});
|
|
1765
|
-
}
|
|
1766
|
-
__isReusableElement(element, otherElement) {
|
|
1767
|
-
if (element && otherElement) {
|
|
1768
|
-
return this.__createdByRouter.get(element) && this.__createdByRouter.get(otherElement)
|
|
1769
|
-
? element.localName === otherElement.localName
|
|
1770
|
-
: element === otherElement;
|
|
1771
|
-
}
|
|
1772
|
-
return false;
|
|
1773
|
-
}
|
|
1774
|
-
__isLatestRender(context) {
|
|
1775
|
-
return context.__renderId === this.__lastStartedRenderId;
|
|
1776
|
-
}
|
|
1777
|
-
__redirect(redirectData, counter, renderId) {
|
|
1778
|
-
if (counter > MAX_REDIRECT_COUNT) {
|
|
1779
|
-
throw new Error(log(`Too many redirects when rendering ${redirectData.from}`));
|
|
1780
|
-
}
|
|
1781
|
-
return this.resolve({
|
|
1782
|
-
pathname: this.urlForPath(redirectData.pathname, redirectData.params),
|
|
1783
|
-
redirectFrom: redirectData.from,
|
|
1784
|
-
__redirectCount: (counter || 0) + 1,
|
|
1785
|
-
__renderId: renderId
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
__ensureOutlet(outlet = this.__outlet) {
|
|
1789
|
-
if (!(outlet instanceof Node)) {
|
|
1790
|
-
throw new TypeError(log(`Expected router outlet to be a valid DOM Node (but got ${outlet})`));
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
__updateBrowserHistory({ pathname, search = '', hash = '' }, replace) {
|
|
1794
|
-
if (window.location.pathname !== pathname
|
|
1795
|
-
|| window.location.search !== search
|
|
1796
|
-
|| window.location.hash !== hash) {
|
|
1797
|
-
const changeState = replace ? 'replaceState' : 'pushState';
|
|
1798
|
-
window.history[changeState](null, document.title, pathname + search + hash);
|
|
1799
|
-
window.dispatchEvent(new PopStateEvent('popstate', { state: 'vaadin-router-ignore' }));
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
__copyUnchangedElements(context, previousContext) {
|
|
1803
|
-
// Find the deepest common parent between the last and the new component
|
|
1804
|
-
// chains. Update references for the unchanged elements in the new chain
|
|
1805
|
-
let deepestCommonParent = this.__outlet;
|
|
1806
|
-
for (let i = 0; i < context.__divergedChainIndex; i++) {
|
|
1807
|
-
const unchangedElement = previousContext && previousContext.chain[i].element;
|
|
1808
|
-
if (unchangedElement) {
|
|
1809
|
-
if (unchangedElement.parentNode === deepestCommonParent) {
|
|
1810
|
-
context.chain[i].element = unchangedElement;
|
|
1811
|
-
deepestCommonParent = unchangedElement;
|
|
1812
|
-
}
|
|
1813
|
-
else {
|
|
1814
|
-
break;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
return deepestCommonParent;
|
|
1819
|
-
}
|
|
1820
|
-
__addAppearingContent(context, previousContext) {
|
|
1821
|
-
this.__ensureOutlet();
|
|
1822
|
-
// If the previous 'entering' animation has not completed yet,
|
|
1823
|
-
// stop it and remove that content from the DOM before adding new one.
|
|
1824
|
-
this.__removeAppearingContent();
|
|
1825
|
-
// Copy reusable elements from the previousContext to current
|
|
1826
|
-
const deepestCommonParent = this.__copyUnchangedElements(context, previousContext);
|
|
1827
|
-
// Keep two lists of DOM elements:
|
|
1828
|
-
// - those that should be removed once the transition animation is over
|
|
1829
|
-
// - and those that should remain
|
|
1830
|
-
this.__appearingContent = [];
|
|
1831
|
-
this.__disappearingContent = Array
|
|
1832
|
-
.from(deepestCommonParent.children)
|
|
1833
|
-
.filter(
|
|
1834
|
-
// Only remove layout content that was added by router
|
|
1835
|
-
e => this.__addedByRouter.get(e) &&
|
|
1836
|
-
// Do not remove the result element to avoid flickering
|
|
1837
|
-
e !== context.result);
|
|
1838
|
-
// Add new elements (starting after the deepest common parent) to the DOM.
|
|
1839
|
-
// That way only the components that are actually different between the two
|
|
1840
|
-
// locations are added to the DOM (and those that are common remain in the
|
|
1841
|
-
// DOM without first removing and then adding them again).
|
|
1842
|
-
let parentElement = deepestCommonParent;
|
|
1843
|
-
for (let i = context.__divergedChainIndex; i < context.chain.length; i++) {
|
|
1844
|
-
const elementToAdd = context.chain[i].element;
|
|
1845
|
-
if (elementToAdd) {
|
|
1846
|
-
parentElement.appendChild(elementToAdd);
|
|
1847
|
-
this.__addedByRouter.set(elementToAdd, true);
|
|
1848
|
-
if (parentElement === deepestCommonParent) {
|
|
1849
|
-
this.__appearingContent.push(elementToAdd);
|
|
1850
|
-
}
|
|
1851
|
-
parentElement = elementToAdd;
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
__removeDisappearingContent() {
|
|
1856
|
-
if (this.__disappearingContent) {
|
|
1857
|
-
removeDomNodes(this.__disappearingContent);
|
|
1858
|
-
}
|
|
1859
|
-
this.__disappearingContent = null;
|
|
1860
|
-
this.__appearingContent = null;
|
|
1861
|
-
}
|
|
1862
|
-
__removeAppearingContent() {
|
|
1863
|
-
if (this.__disappearingContent && this.__appearingContent) {
|
|
1864
|
-
removeDomNodes(this.__appearingContent);
|
|
1865
|
-
this.__disappearingContent = null;
|
|
1866
|
-
this.__appearingContent = null;
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
__runOnAfterLeaveCallbacks(currentContext, targetContext) {
|
|
1870
|
-
if (!targetContext) {
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
// REVERSE iteration: from Z to A
|
|
1874
|
-
for (let i = targetContext.chain.length - 1; i >= currentContext.__divergedChainIndex; i--) {
|
|
1875
|
-
if (!this.__isLatestRender(currentContext)) {
|
|
1876
|
-
break;
|
|
1877
|
-
}
|
|
1878
|
-
const currentComponent = targetContext.chain[i].element;
|
|
1879
|
-
if (!currentComponent) {
|
|
1880
|
-
continue;
|
|
1881
|
-
}
|
|
1882
|
-
try {
|
|
1883
|
-
const location = createLocation(currentContext);
|
|
1884
|
-
runCallbackIfPossible(currentComponent.onAfterLeave, [location, {}, targetContext.resolver], currentComponent);
|
|
1885
|
-
}
|
|
1886
|
-
finally {
|
|
1887
|
-
if (this.__disappearingContent.indexOf(currentComponent) > -1) {
|
|
1888
|
-
removeDomNodes(currentComponent.children);
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
__runOnAfterEnterCallbacks(currentContext) {
|
|
1894
|
-
// forward iteration: from A to Z
|
|
1895
|
-
for (let i = currentContext.__divergedChainIndex; i < currentContext.chain.length; i++) {
|
|
1896
|
-
if (!this.__isLatestRender(currentContext)) {
|
|
1897
|
-
break;
|
|
1898
|
-
}
|
|
1899
|
-
const currentComponent = currentContext.chain[i].element || {};
|
|
1900
|
-
const location = createLocation(currentContext, currentContext.chain[i].route);
|
|
1901
|
-
runCallbackIfPossible(currentComponent.onAfterEnter, [location, {}, currentContext.resolver], currentComponent);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
__animateIfNeeded(context) {
|
|
1905
|
-
const from = (this.__disappearingContent || [])[0];
|
|
1906
|
-
const to = (this.__appearingContent || [])[0];
|
|
1907
|
-
const promises = [];
|
|
1908
|
-
const chain = context.chain;
|
|
1909
|
-
let config;
|
|
1910
|
-
for (let i = chain.length; i > 0; i--) {
|
|
1911
|
-
if (chain[i - 1].route.animate) {
|
|
1912
|
-
config = chain[i - 1].route.animate;
|
|
1913
|
-
break;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
if (from && to && config) {
|
|
1917
|
-
const leave = isObject(config) && config.leave || 'leaving';
|
|
1918
|
-
const enter = isObject(config) && config.enter || 'entering';
|
|
1919
|
-
promises.push(animate(from, leave));
|
|
1920
|
-
promises.push(animate(to, enter));
|
|
1921
|
-
}
|
|
1922
|
-
return Promise.all(promises).then(() => context);
|
|
1923
|
-
}
|
|
1924
|
-
/**
|
|
1925
|
-
* Subscribes this instance to navigation events on the `window`.
|
|
1926
|
-
*
|
|
1927
|
-
* NOTE: beware of resource leaks. For as long as a router instance is
|
|
1928
|
-
* subscribed to navigation events, it won't be garbage collected.
|
|
1929
|
-
*/
|
|
1930
|
-
subscribe() {
|
|
1931
|
-
window.addEventListener('vaadin-router-go', this.__navigationEventHandler);
|
|
1932
|
-
}
|
|
1933
|
-
/**
|
|
1934
|
-
* Removes the subscription to navigation events created in the `subscribe()`
|
|
1935
|
-
* method.
|
|
1936
|
-
*/
|
|
1937
|
-
unsubscribe() {
|
|
1938
|
-
window.removeEventListener('vaadin-router-go', this.__navigationEventHandler);
|
|
1939
|
-
}
|
|
1940
|
-
__onNavigationEvent(event) {
|
|
1941
|
-
const { pathname, search, hash } = event ? event.detail : window.location;
|
|
1942
|
-
if (isString(this.__normalizePathname(pathname))) {
|
|
1943
|
-
if (event && event.preventDefault) {
|
|
1944
|
-
event.preventDefault();
|
|
1945
|
-
}
|
|
1946
|
-
this.render({ pathname, search, hash }, true);
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
/**
|
|
1950
|
-
* Generates a URL for the route with the given name, optionally performing
|
|
1951
|
-
* substitution of parameters.
|
|
1952
|
-
*
|
|
1953
|
-
* The route is searched in all the Router instances subscribed to
|
|
1954
|
-
* navigation events.
|
|
1955
|
-
*
|
|
1956
|
-
* **Note:** For child route names, only array children are considered.
|
|
1957
|
-
* It is not possible to generate URLs using a name for routes set with
|
|
1958
|
-
* a children function.
|
|
1959
|
-
*
|
|
1960
|
-
* @function urlForName
|
|
1961
|
-
* @param {!string} name the route name or the route’s `component` name.
|
|
1962
|
-
* @param {Params=} params Optional object with route path parameters.
|
|
1963
|
-
* Named parameters are passed by name (`params[name] = value`), unnamed
|
|
1964
|
-
* parameters are passed by index (`params[index] = value`).
|
|
1965
|
-
*
|
|
1966
|
-
* @return {string}
|
|
1967
|
-
*/
|
|
1968
|
-
urlForName(name, params) {
|
|
1969
|
-
if (!this.__urlForName) {
|
|
1970
|
-
this.__urlForName = generateUrls(this);
|
|
1971
|
-
}
|
|
1972
|
-
return getPathnameForRouter(this.__urlForName(name, params), this);
|
|
1973
|
-
}
|
|
1974
|
-
/**
|
|
1975
|
-
* Generates a URL for the given route path, optionally performing
|
|
1976
|
-
* substitution of parameters.
|
|
1977
|
-
*
|
|
1978
|
-
* @param {!string} path string route path declared in [express.js syntax](https://expressjs.com/en/guide/routing.html#route-paths").
|
|
1979
|
-
* @param {Params=} params Optional object with route path parameters.
|
|
1980
|
-
* Named parameters are passed by name (`params[name] = value`), unnamed
|
|
1981
|
-
* parameters are passed by index (`params[index] = value`).
|
|
1982
|
-
*
|
|
1983
|
-
* @return {string}
|
|
1984
|
-
*/
|
|
1985
|
-
urlForPath(path, params) {
|
|
1986
|
-
return getPathnameForRouter(pathToRegexp_1.compile(path)(params), this);
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Triggers navigation to a new path. Returns a boolean without waiting until
|
|
1990
|
-
* the navigation is complete. Returns `true` if at least one `Router`
|
|
1991
|
-
* has handled the navigation (was subscribed and had `baseUrl` matching
|
|
1992
|
-
* the `path` argument), otherwise returns `false`.
|
|
1993
|
-
*
|
|
1994
|
-
* @param {!string|!{pathname: !string, search: (string|undefined), hash: (string|undefined)}} path
|
|
1995
|
-
* a new in-app path string, or an URL-like object with `pathname`
|
|
1996
|
-
* string property, and optional `search` and `hash` string properties.
|
|
1997
|
-
* @return {boolean}
|
|
1998
|
-
*/
|
|
1999
|
-
static go(path) {
|
|
2000
|
-
const { pathname, search, hash } = isString(path)
|
|
2001
|
-
? this.__createUrl(path, 'http://a') // some base to omit origin
|
|
2002
|
-
: path;
|
|
2003
|
-
return fireRouterEvent('go', { pathname, search, hash });
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
exports.Router = Router;
|
|
2007
|
-
const DEV_MODE_CODE_REGEXP = /\/\*\*\s+vaadin-dev-mode:start([\s\S]*)vaadin-dev-mode:end\s+\*\*\//i;
|
|
2008
|
-
const FlowClients = window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.clients;
|
|
2009
|
-
function isMinified() {
|
|
2010
|
-
function test() {
|
|
2011
|
-
/** vaadin-dev-mode:start
|
|
2012
|
-
return false;
|
|
2013
|
-
vaadin-dev-mode:end **/
|
|
2014
|
-
return true;
|
|
2015
|
-
}
|
|
2016
|
-
return uncommentAndRun(test);
|
|
2017
|
-
}
|
|
2018
|
-
function isDevelopmentMode() {
|
|
2019
|
-
try {
|
|
2020
|
-
if (isForcedDevelopmentMode()) {
|
|
2021
|
-
return true;
|
|
2022
|
-
}
|
|
2023
|
-
if (!isLocalhost()) {
|
|
2024
|
-
return false;
|
|
2025
|
-
}
|
|
2026
|
-
if (FlowClients) {
|
|
2027
|
-
return !isFlowProductionMode();
|
|
2028
|
-
}
|
|
2029
|
-
return !isMinified();
|
|
2030
|
-
}
|
|
2031
|
-
catch (e) {
|
|
2032
|
-
// Some error in this code, assume production so no further actions will be taken
|
|
2033
|
-
return false;
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
function isForcedDevelopmentMode() {
|
|
2037
|
-
return localStorage.getItem("vaadin.developmentmode.force");
|
|
2038
|
-
}
|
|
2039
|
-
function isLocalhost() {
|
|
2040
|
-
return (["localhost", "127.0.0.1"].indexOf(window.location.hostname) >= 0);
|
|
2041
|
-
}
|
|
2042
|
-
function isFlowProductionMode() {
|
|
2043
|
-
if (FlowClients) {
|
|
2044
|
-
const productionModeApps = Object.keys(FlowClients)
|
|
2045
|
-
.map(key => FlowClients[key])
|
|
2046
|
-
.filter(client => client.productionMode);
|
|
2047
|
-
if (productionModeApps.length > 0) {
|
|
2048
|
-
return true;
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
|
-
return false;
|
|
2052
|
-
}
|
|
2053
|
-
// A guard against polymer-modulizer removing the window.Vaadin
|
|
2054
|
-
// initialization above.
|
|
2055
|
-
window['Vaadin'] = window['Vaadin'] || {};
|
|
2056
|
-
if (window.Vaadin.developmentMode === undefined) {
|
|
2057
|
-
window.Vaadin.developmentMode = isDevelopmentMode();
|
|
2058
|
-
}
|
|
2059
|
-
window.Vaadin = window.Vaadin || {};
|
|
2060
|
-
window.Vaadin.registrations = window.Vaadin.registrations || [];
|
|
2061
|
-
window.Vaadin.registrations.push({
|
|
2062
|
-
is: '@vaadin/router',
|
|
2063
|
-
version: '1.7.2',
|
|
2064
|
-
});
|
|
2065
|
-
//# sourceMappingURL=vaadin-router.js.map
|