@tinkoff/router 0.2.7 → 0.2.8

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.
Files changed (60) hide show
  1. package/lib/components/react/context.browser.js +7 -0
  2. package/lib/components/react/context.es.js +7 -0
  3. package/lib/components/react/context.js +13 -0
  4. package/lib/components/react/provider.browser.js +12 -0
  5. package/lib/components/react/provider.es.js +12 -0
  6. package/lib/components/react/provider.js +16 -0
  7. package/lib/components/react/useNavigate.browser.js +17 -0
  8. package/lib/components/react/useNavigate.es.js +17 -0
  9. package/lib/components/react/useNavigate.js +21 -0
  10. package/lib/components/react/useRoute.browser.js +8 -0
  11. package/lib/components/react/useRoute.es.js +8 -0
  12. package/lib/components/react/useRoute.js +12 -0
  13. package/lib/components/react/useRouter.browser.js +8 -0
  14. package/lib/components/react/useRouter.es.js +8 -0
  15. package/lib/components/react/useRouter.js +12 -0
  16. package/lib/components/react/useUrl.browser.js +8 -0
  17. package/lib/components/react/useUrl.es.js +8 -0
  18. package/lib/components/react/useUrl.js +12 -0
  19. package/lib/history/base.browser.js +11 -0
  20. package/lib/history/base.es.js +11 -0
  21. package/lib/history/base.js +15 -0
  22. package/lib/history/client.browser.js +121 -0
  23. package/lib/history/client.es.js +121 -0
  24. package/lib/history/client.js +125 -0
  25. package/lib/history/server.es.js +15 -0
  26. package/lib/history/server.js +19 -0
  27. package/lib/history/wrapper.browser.js +72 -0
  28. package/lib/history/wrapper.es.js +72 -0
  29. package/lib/history/wrapper.js +80 -0
  30. package/lib/index.browser.js +12 -1169
  31. package/lib/index.es.js +12 -1097
  32. package/lib/index.js +36 -1124
  33. package/lib/logger.browser.js +15 -0
  34. package/lib/logger.es.js +15 -0
  35. package/lib/logger.js +23 -0
  36. package/lib/router/abstract.browser.js +395 -0
  37. package/lib/router/abstract.es.js +395 -0
  38. package/lib/router/abstract.js +404 -0
  39. package/lib/router/browser.browser.js +166 -0
  40. package/lib/router/client.browser.js +116 -0
  41. package/lib/router/client.es.js +116 -0
  42. package/lib/router/client.js +120 -0
  43. package/lib/router/clientNoSpa.browser.js +17 -0
  44. package/lib/router/clientNoSpa.es.js +17 -0
  45. package/lib/router/clientNoSpa.js +21 -0
  46. package/lib/router/server.es.js +85 -0
  47. package/lib/router/server.js +89 -0
  48. package/lib/tree/constants.browser.js +6 -0
  49. package/lib/tree/constants.es.js +6 -0
  50. package/lib/tree/constants.js +13 -0
  51. package/lib/tree/tree.browser.js +148 -0
  52. package/lib/tree/tree.es.js +148 -0
  53. package/lib/tree/tree.js +158 -0
  54. package/lib/tree/utils.browser.js +77 -0
  55. package/lib/tree/utils.es.js +77 -0
  56. package/lib/tree/utils.js +90 -0
  57. package/lib/utils.browser.js +35 -0
  58. package/lib/utils.es.js +35 -0
  59. package/lib/utils.js +48 -0
  60. package/package.json +5 -6
@@ -0,0 +1,116 @@
1
+ import { parse } from '@tinkoff/url';
2
+ import { AbstractRouter } from './abstract.es.js';
3
+ import { isSameHost } from '../utils.es.js';
4
+ import { logger } from '../logger.es.js';
5
+ import { ClientHistory } from '../history/client.es.js';
6
+
7
+ class ClientRouter extends AbstractRouter {
8
+ constructor(options) {
9
+ super(options);
10
+ this.history = new ClientHistory();
11
+ this.history.listen(async ({ type, url, navigateState, replace, history }) => {
12
+ var _a;
13
+ const currentUrl = this.getCurrentUrl();
14
+ const { pathname, query } = this.resolveUrl({ url });
15
+ const isSameUrlNavigation = currentUrl.pathname === pathname;
16
+ if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
17
+ const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
18
+ await this.internalUpdateCurrentRoute({
19
+ params: route === null || route === void 0 ? void 0 : route.params,
20
+ query,
21
+ replace,
22
+ navigateState,
23
+ }, { history });
24
+ }
25
+ else {
26
+ await this.internalNavigate({
27
+ url,
28
+ replace,
29
+ navigateState,
30
+ }, { history });
31
+ }
32
+ });
33
+ }
34
+ async rehydrate(navigation) {
35
+ // this flag for cases when we don't have initial router state from server - CSR fallback for example
36
+ const fullRehydration = !navigation.to;
37
+ logger.debug({
38
+ event: 'rehydrate',
39
+ navigation,
40
+ });
41
+ const url = parse(window.location.href);
42
+ this.currentNavigation = {
43
+ ...navigation,
44
+ type: 'navigate',
45
+ url,
46
+ };
47
+ this.lastNavigation = this.currentNavigation;
48
+ if (fullRehydration) {
49
+ await this.runHooks('beforeResolve', this.currentNavigation);
50
+ const to = this.resolveRoute({ url }, { wildcard: true });
51
+ this.currentNavigation.to = to;
52
+ }
53
+ // rerun guard check in case it differs from server side
54
+ await this.runGuards(this.currentNavigation);
55
+ // and init any history listeners
56
+ this.history.init(this.currentNavigation);
57
+ if (fullRehydration) {
58
+ this.runSyncHooks('change', this.currentNavigation);
59
+ }
60
+ this.currentNavigation = null;
61
+ // add dehydrated route to tree to prevent its loading
62
+ if (!fullRehydration) {
63
+ this.addRoute({
64
+ name: navigation.to.name,
65
+ path: navigation.to.path,
66
+ config: navigation.to.config,
67
+ // in case we have loaded page from some path-changing proxy
68
+ // save this actual path as alias
69
+ alias: url.pathname,
70
+ });
71
+ }
72
+ }
73
+ resolveRoute(...options) {
74
+ const { url } = options[0];
75
+ // navigation for other hosts should be considered as external navigation
76
+ if (url && !isSameHost(url)) {
77
+ return;
78
+ }
79
+ return super.resolveRoute(...options);
80
+ }
81
+ async notfound(navigation) {
82
+ var _a, _b;
83
+ await super.notfound(navigation);
84
+ // in case we didn't find any matched route just force hard page navigation
85
+ const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
86
+ const nextUrl = navigation.url.href;
87
+ const isNoSpaNavigation = navigation.from && !navigation.to;
88
+ // prevent redirect cycle on the same page,
89
+ // except cases when we run no-spa navigations,
90
+ // because we need hard reload in this cases
91
+ if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
92
+ if (navigation.replace) {
93
+ window.location.replace(nextUrl);
94
+ }
95
+ else {
96
+ window.location.assign(nextUrl);
97
+ }
98
+ }
99
+ // prevent routing from any continues navigation returning promise which will be not resolved
100
+ return new Promise(() => { });
101
+ }
102
+ async block(navigation) {
103
+ return this.notfound(navigation);
104
+ }
105
+ async redirect(navigation, target) {
106
+ await super.redirect(navigation, target);
107
+ return this.internalNavigate({
108
+ ...target,
109
+ replace: target.replace || navigation.replace,
110
+ }, {
111
+ redirect: true,
112
+ });
113
+ }
114
+ }
115
+
116
+ export { ClientRouter };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var url = require('@tinkoff/url');
6
+ var abstract = require('./abstract.js');
7
+ var utils = require('../utils.js');
8
+ var logger = require('../logger.js');
9
+ var client = require('../history/client.js');
10
+
11
+ class ClientRouter extends abstract.AbstractRouter {
12
+ constructor(options) {
13
+ super(options);
14
+ this.history = new client.ClientHistory();
15
+ this.history.listen(async ({ type, url, navigateState, replace, history }) => {
16
+ var _a;
17
+ const currentUrl = this.getCurrentUrl();
18
+ const { pathname, query } = this.resolveUrl({ url });
19
+ const isSameUrlNavigation = currentUrl.pathname === pathname;
20
+ if (type === 'updateCurrentRoute' || (!type && isSameUrlNavigation)) {
21
+ const route = (_a = this.tree) === null || _a === void 0 ? void 0 : _a.getRoute(pathname);
22
+ await this.internalUpdateCurrentRoute({
23
+ params: route === null || route === void 0 ? void 0 : route.params,
24
+ query,
25
+ replace,
26
+ navigateState,
27
+ }, { history });
28
+ }
29
+ else {
30
+ await this.internalNavigate({
31
+ url,
32
+ replace,
33
+ navigateState,
34
+ }, { history });
35
+ }
36
+ });
37
+ }
38
+ async rehydrate(navigation) {
39
+ // this flag for cases when we don't have initial router state from server - CSR fallback for example
40
+ const fullRehydration = !navigation.to;
41
+ logger.logger.debug({
42
+ event: 'rehydrate',
43
+ navigation,
44
+ });
45
+ const url$1 = url.parse(window.location.href);
46
+ this.currentNavigation = {
47
+ ...navigation,
48
+ type: 'navigate',
49
+ url: url$1,
50
+ };
51
+ this.lastNavigation = this.currentNavigation;
52
+ if (fullRehydration) {
53
+ await this.runHooks('beforeResolve', this.currentNavigation);
54
+ const to = this.resolveRoute({ url: url$1 }, { wildcard: true });
55
+ this.currentNavigation.to = to;
56
+ }
57
+ // rerun guard check in case it differs from server side
58
+ await this.runGuards(this.currentNavigation);
59
+ // and init any history listeners
60
+ this.history.init(this.currentNavigation);
61
+ if (fullRehydration) {
62
+ this.runSyncHooks('change', this.currentNavigation);
63
+ }
64
+ this.currentNavigation = null;
65
+ // add dehydrated route to tree to prevent its loading
66
+ if (!fullRehydration) {
67
+ this.addRoute({
68
+ name: navigation.to.name,
69
+ path: navigation.to.path,
70
+ config: navigation.to.config,
71
+ // in case we have loaded page from some path-changing proxy
72
+ // save this actual path as alias
73
+ alias: url$1.pathname,
74
+ });
75
+ }
76
+ }
77
+ resolveRoute(...options) {
78
+ const { url } = options[0];
79
+ // navigation for other hosts should be considered as external navigation
80
+ if (url && !utils.isSameHost(url)) {
81
+ return;
82
+ }
83
+ return super.resolveRoute(...options);
84
+ }
85
+ async notfound(navigation) {
86
+ var _a, _b;
87
+ await super.notfound(navigation);
88
+ // in case we didn't find any matched route just force hard page navigation
89
+ const prevUrl = (_b = (_a = navigation.fromUrl) === null || _a === void 0 ? void 0 : _a.href) !== null && _b !== void 0 ? _b : window.location.href;
90
+ const nextUrl = navigation.url.href;
91
+ const isNoSpaNavigation = navigation.from && !navigation.to;
92
+ // prevent redirect cycle on the same page,
93
+ // except cases when we run no-spa navigations,
94
+ // because we need hard reload in this cases
95
+ if (isNoSpaNavigation ? true : prevUrl !== nextUrl) {
96
+ if (navigation.replace) {
97
+ window.location.replace(nextUrl);
98
+ }
99
+ else {
100
+ window.location.assign(nextUrl);
101
+ }
102
+ }
103
+ // prevent routing from any continues navigation returning promise which will be not resolved
104
+ return new Promise(() => { });
105
+ }
106
+ async block(navigation) {
107
+ return this.notfound(navigation);
108
+ }
109
+ async redirect(navigation, target) {
110
+ await super.redirect(navigation, target);
111
+ return this.internalNavigate({
112
+ ...target,
113
+ replace: target.replace || navigation.replace,
114
+ }, {
115
+ redirect: true,
116
+ });
117
+ }
118
+ }
119
+
120
+ exports.ClientRouter = ClientRouter;
@@ -0,0 +1,17 @@
1
+ import { ClientRouter } from './client.browser.js';
2
+
3
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
4
+ class NoSpaRouter extends ClientRouter {
5
+ run(navigation) {
6
+ const { type, fromUrl, url } = navigation;
7
+ // support only updateCurrentRoute or hash navigations
8
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
9
+ return super.run(navigation);
10
+ }
11
+ return this.notfound(navigation);
12
+ }
13
+ // do not call any hooks as it is only supports url updates with hash
14
+ async runHooks() { }
15
+ }
16
+
17
+ export { NoSpaRouter };
@@ -0,0 +1,17 @@
1
+ import { ClientRouter } from './client.es.js';
2
+
3
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
4
+ class NoSpaRouter extends ClientRouter {
5
+ run(navigation) {
6
+ const { type, fromUrl, url } = navigation;
7
+ // support only updateCurrentRoute or hash navigations
8
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
9
+ return super.run(navigation);
10
+ }
11
+ return this.notfound(navigation);
12
+ }
13
+ // do not call any hooks as it is only supports url updates with hash
14
+ async runHooks() { }
15
+ }
16
+
17
+ export { NoSpaRouter };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var client = require('./client.js');
6
+
7
+ const omitHash = (url) => url.href.replace(/#.*$/, '');
8
+ class NoSpaRouter extends client.ClientRouter {
9
+ run(navigation) {
10
+ const { type, fromUrl, url } = navigation;
11
+ // support only updateCurrentRoute or hash navigations
12
+ if (type === 'updateCurrentRoute' || omitHash(url) === omitHash(fromUrl)) {
13
+ return super.run(navigation);
14
+ }
15
+ return this.notfound(navigation);
16
+ }
17
+ // do not call any hooks as it is only supports url updates with hash
18
+ async runHooks() { }
19
+ }
20
+
21
+ exports.NoSpaRouter = NoSpaRouter;
@@ -0,0 +1,85 @@
1
+ import { isInvalidUrl } from '@tinkoff/url';
2
+ import { AbstractRouter } from './abstract.es.js';
3
+ import { ServerHistory } from '../history/server.es.js';
4
+ import { RouteTree } from '../tree/tree.es.js';
5
+ import { logger } from '../logger.es.js';
6
+
7
+ class Router extends AbstractRouter {
8
+ constructor(options) {
9
+ var _a;
10
+ super(options);
11
+ this.blocked = false;
12
+ this.tree = new RouteTree(options.routes);
13
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
14
+ this.history = new ServerHistory();
15
+ }
16
+ async dehydrate() {
17
+ logger.debug({
18
+ event: 'dehydrate',
19
+ navigation: this.lastNavigation,
20
+ });
21
+ return this.lastNavigation;
22
+ }
23
+ async internalNavigate(navigateOptions, internalOptions) {
24
+ // any navigation after initial should be considered as redirects
25
+ if (this.getCurrentRoute()) {
26
+ return this.redirect(this.lastNavigation, navigateOptions);
27
+ }
28
+ return super.internalNavigate(navigateOptions, internalOptions);
29
+ }
30
+ async run(navigation) {
31
+ if (this.redirectCode) {
32
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
33
+ }
34
+ await super.run(navigation);
35
+ }
36
+ async redirect(navigation, target) {
37
+ logger.debug({
38
+ event: 'redirect',
39
+ navigation,
40
+ target,
41
+ });
42
+ this.blocked = true;
43
+ return this.onRedirect({
44
+ ...navigation,
45
+ from: navigation.to,
46
+ fromUrl: navigation.url,
47
+ to: null,
48
+ url: this.resolveUrl(target),
49
+ code: target.code,
50
+ });
51
+ }
52
+ async notfound(navigation) {
53
+ this.blocked = true;
54
+ return super.notfound(navigation);
55
+ }
56
+ normalizePathname(pathname) {
57
+ const normalized = super.normalizePathname(pathname);
58
+ if (normalized !== pathname) {
59
+ this.redirectCode = this.defaultRedirectCode;
60
+ }
61
+ return normalized;
62
+ }
63
+ resolveUrl(options) {
64
+ if (options.url && isInvalidUrl(options.url)) {
65
+ this.redirectCode = this.defaultRedirectCode;
66
+ }
67
+ return super.resolveUrl(options);
68
+ }
69
+ async runHooks(hookName, navigation) {
70
+ // do not run hooks if another parallel navigation has been called
71
+ if (this.blocked) {
72
+ return;
73
+ }
74
+ return super.runHooks(hookName, navigation);
75
+ }
76
+ async runGuards(navigation) {
77
+ // do not run guards if another parallel navigation has been called
78
+ if (this.blocked) {
79
+ return;
80
+ }
81
+ return super.runGuards(navigation);
82
+ }
83
+ }
84
+
85
+ export { Router };
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var url = require('@tinkoff/url');
6
+ var abstract = require('./abstract.js');
7
+ var server = require('../history/server.js');
8
+ var tree = require('../tree/tree.js');
9
+ var logger = require('../logger.js');
10
+
11
+ class Router extends abstract.AbstractRouter {
12
+ constructor(options) {
13
+ var _a;
14
+ super(options);
15
+ this.blocked = false;
16
+ this.tree = new tree.RouteTree(options.routes);
17
+ this.defaultRedirectCode = (_a = options.defaultRedirectCode) !== null && _a !== void 0 ? _a : 308;
18
+ this.history = new server.ServerHistory();
19
+ }
20
+ async dehydrate() {
21
+ logger.logger.debug({
22
+ event: 'dehydrate',
23
+ navigation: this.lastNavigation,
24
+ });
25
+ return this.lastNavigation;
26
+ }
27
+ async internalNavigate(navigateOptions, internalOptions) {
28
+ // any navigation after initial should be considered as redirects
29
+ if (this.getCurrentRoute()) {
30
+ return this.redirect(this.lastNavigation, navigateOptions);
31
+ }
32
+ return super.internalNavigate(navigateOptions, internalOptions);
33
+ }
34
+ async run(navigation) {
35
+ if (this.redirectCode) {
36
+ return this.redirect(navigation, { url: navigation.url.href, code: this.redirectCode });
37
+ }
38
+ await super.run(navigation);
39
+ }
40
+ async redirect(navigation, target) {
41
+ logger.logger.debug({
42
+ event: 'redirect',
43
+ navigation,
44
+ target,
45
+ });
46
+ this.blocked = true;
47
+ return this.onRedirect({
48
+ ...navigation,
49
+ from: navigation.to,
50
+ fromUrl: navigation.url,
51
+ to: null,
52
+ url: this.resolveUrl(target),
53
+ code: target.code,
54
+ });
55
+ }
56
+ async notfound(navigation) {
57
+ this.blocked = true;
58
+ return super.notfound(navigation);
59
+ }
60
+ normalizePathname(pathname) {
61
+ const normalized = super.normalizePathname(pathname);
62
+ if (normalized !== pathname) {
63
+ this.redirectCode = this.defaultRedirectCode;
64
+ }
65
+ return normalized;
66
+ }
67
+ resolveUrl(options) {
68
+ if (options.url && url.isInvalidUrl(options.url)) {
69
+ this.redirectCode = this.defaultRedirectCode;
70
+ }
71
+ return super.resolveUrl(options);
72
+ }
73
+ async runHooks(hookName, navigation) {
74
+ // do not run hooks if another parallel navigation has been called
75
+ if (this.blocked) {
76
+ return;
77
+ }
78
+ return super.runHooks(hookName, navigation);
79
+ }
80
+ async runGuards(navigation) {
81
+ // do not run guards if another parallel navigation has been called
82
+ if (this.blocked) {
83
+ return;
84
+ }
85
+ return super.runGuards(navigation);
86
+ }
87
+ }
88
+
89
+ exports.Router = Router;
@@ -0,0 +1,6 @@
1
+ const PARAMETER_DELIMITER = ':';
2
+ const WILDCARD_REGEXP = /\*/;
3
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
4
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
5
+
6
+ export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
@@ -0,0 +1,6 @@
1
+ const PARAMETER_DELIMITER = ':';
2
+ const WILDCARD_REGEXP = /\*/;
3
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
4
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
5
+
6
+ export { HISTORY_FALLBACK_REGEXP, PARAMETER_DELIMITER, PARAM_PARSER_REGEXP, WILDCARD_REGEXP };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const PARAMETER_DELIMITER = ':';
6
+ const WILDCARD_REGEXP = /\*/;
7
+ const HISTORY_FALLBACK_REGEXP = /<history-fallback>/;
8
+ const PARAM_PARSER_REGEXP = /([^(?]+)(?:\((.+)\))?(\?)?/;
9
+
10
+ exports.HISTORY_FALLBACK_REGEXP = HISTORY_FALLBACK_REGEXP;
11
+ exports.PARAMETER_DELIMITER = PARAMETER_DELIMITER;
12
+ exports.PARAM_PARSER_REGEXP = PARAM_PARSER_REGEXP;
13
+ exports.WILDCARD_REGEXP = WILDCARD_REGEXP;
@@ -0,0 +1,148 @@
1
+ import each from '@tinkoff/utils/array/each';
2
+ import find from '@tinkoff/utils/array/find';
3
+ import findIndex from '@tinkoff/utils/array/findIndex';
4
+ import { getParts, parse } from './utils.browser.js';
5
+ import { HISTORY_FALLBACK_REGEXP } from './constants.browser.js';
6
+
7
+ const createTree = (route) => {
8
+ return {
9
+ route,
10
+ children: Object.create(null),
11
+ parameters: [],
12
+ };
13
+ };
14
+ const createNavigationRoute = (route, pathname, params) => {
15
+ return {
16
+ ...route,
17
+ actualPath: pathname,
18
+ params: params !== null && params !== void 0 ? params : {},
19
+ };
20
+ };
21
+ class RouteTree {
22
+ constructor(routes) {
23
+ this.tree = createTree();
24
+ each((route) => this.addRoute(route), routes);
25
+ }
26
+ // eslint-disable-next-line max-statements
27
+ addRoute(route) {
28
+ const parts = getParts(route.path);
29
+ let currentTree = this.tree;
30
+ for (let i = 0; i < parts.length; i++) {
31
+ const part = parts[i];
32
+ const parsed = parse(part);
33
+ if (parsed.type === 1 /* PartType.historyFallback */) {
34
+ currentTree.historyFallbackRoute = route;
35
+ return;
36
+ }
37
+ if (parsed.type === 2 /* PartType.wildcard */) {
38
+ currentTree.wildcardRoute = route;
39
+ return;
40
+ }
41
+ if (parsed.type === 3 /* PartType.parameter */) {
42
+ const { paramName, regexp, optional } = parsed;
43
+ // prevent from creating new entries for same route
44
+ const found = find((par) => par.key === part, currentTree.parameters);
45
+ if (found) {
46
+ currentTree = found.tree;
47
+ }
48
+ else {
49
+ const parameter = {
50
+ key: part,
51
+ paramName,
52
+ regexp,
53
+ optional,
54
+ tree: createTree(),
55
+ };
56
+ if (regexp && !optional) {
57
+ // insert parameters with regexp before
58
+ const index = findIndex((par) => !par.regexp, currentTree.parameters);
59
+ currentTree.parameters.splice(index, 0, parameter);
60
+ }
61
+ else {
62
+ currentTree.parameters.push(parameter);
63
+ }
64
+ currentTree = parameter.tree;
65
+ }
66
+ }
67
+ else {
68
+ if (!currentTree.children[part]) {
69
+ currentTree.children[part] = createTree();
70
+ }
71
+ currentTree = currentTree.children[part];
72
+ }
73
+ }
74
+ currentTree.route = route;
75
+ }
76
+ getRoute(pathname) {
77
+ return this.findRoute(pathname, 'route');
78
+ }
79
+ getWildcard(pathname) {
80
+ return this.findRoute(pathname, 'wildcardRoute');
81
+ }
82
+ getHistoryFallback(pathname) {
83
+ const route = this.findRoute(pathname, 'historyFallbackRoute');
84
+ return (route && {
85
+ ...route,
86
+ // remove <history-fallback> from path
87
+ actualPath: route.path.replace(HISTORY_FALLBACK_REGEXP, '') || '/',
88
+ });
89
+ }
90
+ // eslint-disable-next-line max-statements
91
+ findRoute(pathname, propertyName) {
92
+ // we should use exact match only for classic route
93
+ // as special routes (for not-found and history-fallback) are defined for whole subtree
94
+ const exactMatch = propertyName === 'route';
95
+ const parts = getParts(pathname);
96
+ const queue = [
97
+ [this.tree, 0],
98
+ ];
99
+ while (queue.length) {
100
+ const [currentTree, index, params] = queue.pop();
101
+ const { children, parameters } = currentTree;
102
+ // this flag mean we can only check for optional parameters
103
+ // as we didn't find static route for this path, but still may find
104
+ // some inner route inside optional branch
105
+ // the value of parameter will be empty in this case ofc
106
+ let optionalOnly = false;
107
+ if (index >= parts.length) {
108
+ if (currentTree[propertyName]) {
109
+ return createNavigationRoute(currentTree[propertyName], pathname, params);
110
+ }
111
+ // here we cant check any options except for optional parameters
112
+ optionalOnly = true;
113
+ }
114
+ // first add to queue special routes (not-found or history-fallback) to check it after other cases, as it will be processed last
115
+ if (!exactMatch && currentTree[propertyName]) {
116
+ queue.push([currentTree, parts.length, params]);
117
+ }
118
+ const part = parts[index];
119
+ const child = children[part];
120
+ // then add checks for only optional
121
+ for (const param of parameters) {
122
+ const { optional, tree } = param;
123
+ if (optional) {
124
+ queue.push([tree, index, params]);
125
+ }
126
+ }
127
+ if (optionalOnly) {
128
+ continue;
129
+ }
130
+ // for non-optional cases
131
+ for (let i = parameters.length - 1; i >= 0; i--) {
132
+ const param = parameters[i];
133
+ const { paramName, tree, regexp } = param;
134
+ const match = regexp === null || regexp === void 0 ? void 0 : regexp.exec(part);
135
+ const paramValue = regexp ? match === null || match === void 0 ? void 0 : match[1] : part;
136
+ if (paramValue) {
137
+ queue.push([tree, index + 1, { ...params, [paramName]: paramValue }]);
138
+ }
139
+ }
140
+ if (child) {
141
+ // add checks for static child subtree last as it will be processed first after queue.pop
142
+ queue.push([child, index + 1, params]);
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ export { RouteTree };