@dharmax/state-router 1.2.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ReadMe.md +24 -29
- package/dist/router.d.ts +16 -16
- package/dist/router.js +51 -64
- package/dist/state-manager.d.ts +2 -5
- package/dist/state-manager.js +9 -20
- package/package.json +5 -6
- package/src/router.ts +67 -73
- package/src/state-manager.ts +2 -2
- package/tsconfig.json +3 -2
package/ReadMe.md
CHANGED
|
@@ -6,48 +6,43 @@ What is a functional router? it's a router that captures a url changes and
|
|
|
6
6
|
per specific pattern of url, it simply triggers a handler. It supports hash notation
|
|
7
7
|
as well as normal url patterns. It also supports contexts (url parameters).
|
|
8
8
|
|
|
9
|
-
Together with the
|
|
10
|
-
to work with, you get a very
|
|
9
|
+
Together with the state manager - which will be the main object you'd need
|
|
10
|
+
to work with, you get a very simple semantic application state management:
|
|
11
11
|
each state can be given a logical name, route and an associated page/component.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Doesn't monopolise the URL
|
|
20
|
-
* Supports state contexts
|
|
13
|
+
The router by default ignores file extensions '.json', '.css', '.js', '.png', '.jpg', '.svg', '.webp','md'
|
|
14
|
+
and you can access the router's staticFilters member to replace or add other rules
|
|
15
|
+
for static serving support.
|
|
16
|
+
|
|
17
|
+
you can use the hash notation or not (set the router's mode to 'history' or 'hash')
|
|
18
|
+
|
|
21
19
|
|
|
22
20
|
# Example
|
|
23
21
|
|
|
24
22
|
## state definitions
|
|
25
23
|
|
|
26
24
|
```javascript
|
|
27
|
-
|
|
28
|
-
// this is how you define the states: logical name, component name, route (as string or regex)
|
|
29
25
|
stateManager.addState('main', 'main-page', /main$/);
|
|
30
26
|
stateManager.addState('login', 'login-box', 'login')
|
|
31
27
|
stateManager.addState('signup', 'signup-box', /signup/)
|
|
32
28
|
stateManager.addState('my-profile') // will assume the page name and the route are the same...
|
|
33
|
-
stateManager.addState('inbox')
|
|
29
|
+
stateManager.addState('inbox')
|
|
34
30
|
stateManager.addState('about')
|
|
35
|
-
stateManager.addState('discussion', 'discussion-page', 'discussion/%')
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// this specific example works with RiotJs, but you get the drift
|
|
40
|
-
this.update({
|
|
41
|
-
currentPage: event.data.pageName
|
|
42
|
-
})
|
|
43
|
-
})
|
|
31
|
+
stateManager.addState('discussion', 'discussion-page', 'discussion/%')
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## usage in the gui
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
In your main component, you write something like that:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
|
|
40
|
+
stateManager.onChange( event => {
|
|
41
|
+
// this specific example works with RiotJs, but you get the drift
|
|
42
|
+
this.update({
|
|
43
|
+
currentPage: event.data.pageName
|
|
51
44
|
})
|
|
52
|
-
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
|
|
53
48
|
```
|
package/dist/router.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
constructor();
|
|
3
|
-
re: RegExp;
|
|
4
|
-
handler: Function;
|
|
5
|
-
}
|
|
1
|
+
type RouteHandler = (...args: string[]) => void;
|
|
6
2
|
export type RoutingMode = 'history' | 'hash';
|
|
7
|
-
|
|
3
|
+
declare class Router {
|
|
8
4
|
mode: RoutingMode;
|
|
9
|
-
routes
|
|
10
|
-
root
|
|
11
|
-
baseLocation
|
|
5
|
+
private routes;
|
|
6
|
+
private root;
|
|
7
|
+
private baseLocation;
|
|
8
|
+
staticFilters: ((url: string) => boolean)[];
|
|
9
|
+
constructor();
|
|
10
|
+
private clearSlashes;
|
|
11
|
+
private clearQuery;
|
|
12
|
+
private isStaticFile;
|
|
12
13
|
resetRoot(root: string): void;
|
|
13
14
|
getLocation(): string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
15
|
+
add(pattern: RegExp | RouteHandler, handler?: RouteHandler): Router;
|
|
16
|
+
process(location?: string): Router;
|
|
17
|
+
listen(): void;
|
|
18
|
+
navigate(path?: string): Router;
|
|
19
|
+
}
|
|
20
|
+
export declare const router: Router;
|
|
21
21
|
export {};
|
package/dist/router.js
CHANGED
|
@@ -1,94 +1,81 @@
|
|
|
1
|
-
class
|
|
1
|
+
class Router {
|
|
2
|
+
mode = 'hash';
|
|
3
|
+
routes = [];
|
|
4
|
+
root = '/';
|
|
5
|
+
baseLocation = null;
|
|
6
|
+
staticFilters = [];
|
|
2
7
|
constructor() {
|
|
3
|
-
this.
|
|
4
|
-
|
|
8
|
+
this.staticFilters.push(url => {
|
|
9
|
+
const staticFileExtensions = ['.json', '.css', '.js', '.png', '.jpg', '.svg', '.webp', 'md'];
|
|
10
|
+
return staticFileExtensions.some(ext => url.endsWith(ext));
|
|
11
|
+
});
|
|
12
|
+
window.addEventListener(this.mode === 'hash' ? 'hashchange' : 'popstate', this.listen.bind(this));
|
|
5
13
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
clearSlashes(path) {
|
|
15
|
+
return path.replace(/\/$/, '').replace(/^\//, '');
|
|
16
|
+
}
|
|
17
|
+
clearQuery(url) {
|
|
18
|
+
const [path, query] = url.split('?');
|
|
19
|
+
if (!query)
|
|
20
|
+
return path;
|
|
21
|
+
const [_, hash] = query.split('#');
|
|
22
|
+
return hash ? `${path}#${hash}` : path;
|
|
23
|
+
}
|
|
24
|
+
isStaticFile(url) {
|
|
25
|
+
return (this.staticFilters || []).some(filter => filter(url));
|
|
13
26
|
}
|
|
14
27
|
resetRoot(root) {
|
|
15
28
|
this.root = '/' + this.clearSlashes(root) + '/';
|
|
16
29
|
}
|
|
17
30
|
getLocation() {
|
|
18
|
-
let fragment = '';
|
|
19
31
|
if (this.mode === 'history') {
|
|
20
|
-
fragment = this.clearSlashes(decodeURI(location.pathname + location.search));
|
|
32
|
+
let fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search));
|
|
21
33
|
fragment = this.clearQuery(fragment);
|
|
22
|
-
|
|
23
|
-
fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment;
|
|
34
|
+
return this.root !== '/' ? fragment.replace(this.root, '') : fragment;
|
|
24
35
|
}
|
|
25
36
|
else {
|
|
26
37
|
const match = window.location.href.match(/#(.*)$/);
|
|
27
|
-
|
|
28
|
-
fragment = match ? match[1] : '';
|
|
38
|
+
return match ? this.clearQuery(match[1]) : '';
|
|
29
39
|
}
|
|
30
|
-
return this.clearSlashes(fragment);
|
|
31
|
-
}
|
|
32
|
-
clearSlashes(path) {
|
|
33
|
-
return path.toString().replace(/\/$/, '').replace(/^\//, '');
|
|
34
|
-
}
|
|
35
|
-
clearQuery(url) {
|
|
36
|
-
if (url.indexOf('?') === -1)
|
|
37
|
-
return url;
|
|
38
|
-
const a = url.split('?');
|
|
39
|
-
const afterHash = a[1].split('#');
|
|
40
|
-
if (!afterHash)
|
|
41
|
-
return a[0];
|
|
42
|
-
return `${a[0]}#${afterHash}`;
|
|
43
40
|
}
|
|
44
|
-
add(
|
|
45
|
-
if (typeof
|
|
46
|
-
handler =
|
|
47
|
-
|
|
41
|
+
add(pattern, handler) {
|
|
42
|
+
if (typeof pattern === 'function') {
|
|
43
|
+
handler = pattern;
|
|
44
|
+
pattern = /^.*$/; // Match any path
|
|
48
45
|
}
|
|
49
|
-
this.routes.push({
|
|
46
|
+
this.routes.push({ pattern, handler: handler });
|
|
50
47
|
return this;
|
|
51
48
|
}
|
|
52
49
|
process(location) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.warn(`No routing found for ${fragment}`);
|
|
64
|
-
return;
|
|
50
|
+
const path = location || this.getLocation();
|
|
51
|
+
if (this.isStaticFile(path))
|
|
52
|
+
return this; // Bypass routing for static files
|
|
53
|
+
for (const route of this.routes) {
|
|
54
|
+
const match = path.match(route.pattern);
|
|
55
|
+
if (match) {
|
|
56
|
+
match.shift(); // Remove the full match element
|
|
57
|
+
route.handler.apply({}, match);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
65
60
|
}
|
|
66
|
-
|
|
67
|
-
longestMatch.match.shift();
|
|
68
|
-
longestMatch.r.handler.apply({}, longestMatch.match);
|
|
61
|
+
console.warn(`No routing found for ${path}`);
|
|
69
62
|
return this;
|
|
70
63
|
}
|
|
71
64
|
listen() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.process(place);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
return this;
|
|
65
|
+
const currentLocation = this.getLocation();
|
|
66
|
+
if (this.baseLocation !== currentLocation) {
|
|
67
|
+
this.baseLocation = currentLocation;
|
|
68
|
+
this.process(currentLocation);
|
|
69
|
+
}
|
|
81
70
|
}
|
|
82
|
-
navigate(path) {
|
|
83
|
-
path = path ? path : '';
|
|
71
|
+
navigate(path = '') {
|
|
84
72
|
if (this.mode === 'history') {
|
|
85
73
|
history.pushState(null, null, this.root + this.clearSlashes(path));
|
|
86
74
|
}
|
|
87
75
|
else {
|
|
88
|
-
|
|
89
|
-
window.location.href = window.location.href.replace(/#(.*)$/, '') + path;
|
|
90
|
-
this.process();
|
|
76
|
+
window.location.hash = '#' + this.clearSlashes(path);
|
|
91
77
|
}
|
|
92
78
|
return this;
|
|
93
79
|
}
|
|
94
|
-
}
|
|
80
|
+
}
|
|
81
|
+
export const router = new Router();
|
package/dist/state-manager.d.ts
CHANGED
|
@@ -7,17 +7,14 @@ export type ApplicationState = {
|
|
|
7
7
|
route: RegExp;
|
|
8
8
|
mode?: string | string[];
|
|
9
9
|
};
|
|
10
|
-
export type ChangeAuthority = (state: ApplicationState) => Promise<boolean>;
|
|
11
10
|
export declare class StateManager {
|
|
12
11
|
private allStates;
|
|
13
12
|
private appState;
|
|
14
13
|
private previousState;
|
|
15
14
|
private stateContext;
|
|
16
|
-
static dispatcher: import("@dharmax/pubsub").
|
|
17
|
-
private changeAuthorities;
|
|
15
|
+
static dispatcher: import("@dharmax/pubsub").Pubsub;
|
|
18
16
|
constructor(mode?: RoutingMode);
|
|
19
17
|
onChange(handler: (event: PubSubEvent, data: any) => void): IPubSubHandle;
|
|
20
|
-
registerChangeAuthority(authorityCallback: (state: ApplicationState) => Promise<boolean>): void;
|
|
21
18
|
getState(): ApplicationState;
|
|
22
19
|
get previous(): ApplicationState;
|
|
23
20
|
get context(): ApplicationState;
|
|
@@ -33,7 +30,7 @@ export declare class StateManager {
|
|
|
33
30
|
* @param stateName state
|
|
34
31
|
* @param context extra context (e.g. sub-state)
|
|
35
32
|
*/
|
|
36
|
-
setState(stateName: ApplicationStateName, context?: any):
|
|
33
|
+
setState(stateName: ApplicationStateName, context?: any): boolean;
|
|
37
34
|
/**
|
|
38
35
|
* Define an application state
|
|
39
36
|
* @param name
|
package/dist/state-manager.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import { router } from "./router";
|
|
2
2
|
import dispatcher from "@dharmax/pubsub";
|
|
3
3
|
export class StateManager {
|
|
4
|
+
allStates = {};
|
|
5
|
+
appState;
|
|
6
|
+
previousState;
|
|
7
|
+
stateContext;
|
|
8
|
+
static dispatcher = dispatcher;
|
|
4
9
|
constructor(mode = 'hash') {
|
|
5
|
-
this.allStates = {};
|
|
6
|
-
this.changeAuthorities = [];
|
|
7
10
|
router.mode = mode;
|
|
8
11
|
router.listen();
|
|
9
12
|
}
|
|
10
13
|
onChange(handler) {
|
|
11
14
|
return StateManager.dispatcher.on('state:changed', handler);
|
|
12
15
|
}
|
|
13
|
-
/*
|
|
14
|
-
Add a hook which enable conditional approval of state change. It can be more than one; when a state
|
|
15
|
-
change is requested, all the registered authorities must return true (asynchronously) otherwise the change
|
|
16
|
-
requested doesn't happen.
|
|
17
|
-
**/
|
|
18
|
-
registerChangeAuthority(authorityCallback) {
|
|
19
|
-
this.changeAuthorities.push(authorityCallback);
|
|
20
|
-
}
|
|
21
16
|
getState() {
|
|
22
17
|
return this.appState || {};
|
|
23
18
|
}
|
|
@@ -40,7 +35,7 @@ export class StateManager {
|
|
|
40
35
|
/** attempts to restore state from current url */
|
|
41
36
|
restoreState(defaultState) {
|
|
42
37
|
let dest = window.location.hash;
|
|
43
|
-
if (dest
|
|
38
|
+
if (dest == '#login' || dest == '')
|
|
44
39
|
dest = '#' + defaultState;
|
|
45
40
|
router.navigate(dest);
|
|
46
41
|
}
|
|
@@ -49,17 +44,12 @@ export class StateManager {
|
|
|
49
44
|
* @param stateName state
|
|
50
45
|
* @param context extra context (e.g. sub-state)
|
|
51
46
|
*/
|
|
52
|
-
|
|
47
|
+
setState(stateName, context) {
|
|
53
48
|
const newState = this.allStates[stateName];
|
|
54
49
|
if (!newState) {
|
|
55
50
|
alert(`Undefined app state ${stateName}`);
|
|
56
51
|
return false;
|
|
57
52
|
}
|
|
58
|
-
// check if the state change was declined by any change authority and if so - don't do it and return false
|
|
59
|
-
const changeConfirmations = await Promise.all(this.changeAuthorities.map(a => a(newState)));
|
|
60
|
-
if (changeConfirmations.includes(false))
|
|
61
|
-
return false;
|
|
62
|
-
// perform the change
|
|
63
53
|
this.previousState = this.appState;
|
|
64
54
|
this.stateContext = context;
|
|
65
55
|
this.appState = newState;
|
|
@@ -89,8 +79,8 @@ export class StateManager {
|
|
|
89
79
|
}
|
|
90
80
|
registerStateByState(state) {
|
|
91
81
|
this.allStates[state.name] = state;
|
|
92
|
-
router.add(state.route,
|
|
93
|
-
if (
|
|
82
|
+
router.add(state.route, (context) => {
|
|
83
|
+
if (this.setState(state.name, context)) {
|
|
94
84
|
// @ts-ignore
|
|
95
85
|
if (window.ga) {
|
|
96
86
|
// @ts-ignore
|
|
@@ -100,5 +90,4 @@ export class StateManager {
|
|
|
100
90
|
});
|
|
101
91
|
}
|
|
102
92
|
}
|
|
103
|
-
StateManager.dispatcher = dispatcher;
|
|
104
93
|
export const stateManager = new StateManager();
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dharmax/state-router",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A cute and tight router and application state controller",
|
|
5
|
-
"main": "dist/
|
|
6
|
-
"types": "dist/
|
|
5
|
+
"main": "dist/router.js",
|
|
6
|
+
"types": "dist/router.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
9
|
"build": "npx tsc",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"router",
|
|
18
|
-
"navigation",
|
|
19
18
|
"state"
|
|
20
19
|
],
|
|
21
20
|
"author": "Avi Tshuva, dharmax",
|
|
@@ -25,9 +24,9 @@
|
|
|
25
24
|
},
|
|
26
25
|
"homepage": "https://github.com/dharmax/state-routerr",
|
|
27
26
|
"devDependencies": {
|
|
28
|
-
"typescript": "^4.9.
|
|
27
|
+
"typescript": "^4.9.5"
|
|
29
28
|
},
|
|
30
29
|
"dependencies": {
|
|
31
|
-
"@dharmax/pubsub": "^1.1
|
|
30
|
+
"@dharmax/pubsub": "^1.0.1"
|
|
32
31
|
}
|
|
33
32
|
}
|
package/src/router.ts
CHANGED
|
@@ -1,108 +1,102 @@
|
|
|
1
|
-
|
|
2
|
-
constructor() {
|
|
3
|
-
}
|
|
1
|
+
type RouteHandler = (...args: string[]) => void;
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
interface Route {
|
|
4
|
+
pattern: RegExp | null;
|
|
5
|
+
handler: RouteHandler;
|
|
7
6
|
}
|
|
8
7
|
|
|
9
|
-
export type RoutingMode = 'history' | 'hash'
|
|
10
|
-
export const router = new class {
|
|
8
|
+
export type RoutingMode = 'history' | 'hash';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
class Router {
|
|
11
|
+
mode: RoutingMode = 'hash';
|
|
12
|
+
private routes: Route[] = [];
|
|
13
|
+
private root: string = '/';
|
|
14
|
+
private baseLocation: string | null = null;
|
|
15
|
+
public staticFilters:((url:string) => boolean)[] = []
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
constructor() {
|
|
18
|
+
this.staticFilters.push( url => {
|
|
19
|
+
const staticFileExtensions = ['.json', '.css', '.js', '.png', '.jpg', '.svg', '.webp','md'];
|
|
20
|
+
return staticFileExtensions.some(ext => url.endsWith(ext));
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
this.
|
|
22
|
+
})
|
|
23
|
+
window.addEventListener(this.mode === 'hash' ? 'hashchange' : 'popstate', this.listen.bind(this));
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
private clearSlashes(path: string): string {
|
|
27
|
+
return path.replace(/\/$/, '').replace(/^\//, '');
|
|
28
|
+
}
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fragment = fragment.replace(/\?(.*)$/, '');
|
|
29
|
-
fragment = this.root != '/' ? fragment.replace(this.root, '') : fragment;
|
|
30
|
-
} else {
|
|
31
|
-
const match = window.location.href.match(/#(.*)$/);
|
|
32
|
-
fragment = this.clearQuery(fragment)
|
|
33
|
-
fragment = match ? match[1] : '';
|
|
34
|
-
}
|
|
35
|
-
return this.clearSlashes(fragment);
|
|
30
|
+
private clearQuery(url: string): string {
|
|
31
|
+
const [path, query] = url.split('?');
|
|
32
|
+
if (!query) return path;
|
|
33
|
+
const [_, hash] = query.split('#');
|
|
34
|
+
return hash ? `${path}#${hash}` : path;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
return
|
|
37
|
+
private isStaticFile(url: string): boolean {
|
|
38
|
+
return (this.staticFilters || []).some( filter => filter(url))
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return url
|
|
45
|
-
const a = url.split('?')
|
|
46
|
-
const afterHash = a[1].split('#')
|
|
47
|
-
if (!afterHash)
|
|
48
|
-
return a[0]
|
|
49
|
-
return `${a[0]}#${afterHash}`
|
|
41
|
+
public resetRoot(root: string): void {
|
|
42
|
+
this.root = '/' + this.clearSlashes(root) + '/';
|
|
50
43
|
}
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
public getLocation(): string {
|
|
46
|
+
if (this.mode === 'history') {
|
|
47
|
+
let fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search));
|
|
48
|
+
fragment = this.clearQuery(fragment);
|
|
49
|
+
return this.root !== '/' ? fragment.replace(this.root, '') : fragment;
|
|
50
|
+
} else {
|
|
51
|
+
const match = window.location.href.match(/#(.*)$/);
|
|
52
|
+
return match ? this.clearQuery(match[1]) : '';
|
|
56
53
|
}
|
|
57
|
-
this.routes.push({re: re as RegExp, handler});
|
|
58
|
-
return this;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.sort((r1, r2) => {
|
|
66
|
-
const [n1, n2] = [r1, r2].map(r => r.re.source.split('/'))
|
|
67
|
-
return n2.length - n1.length
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
.map(r => {
|
|
71
|
-
return {r, match: fragment.match(r.re)}
|
|
72
|
-
});
|
|
73
|
-
if (!matches.length) {
|
|
74
|
-
console.warn(`No routing found for ${fragment}`)
|
|
75
|
-
return
|
|
56
|
+
public add(pattern: RegExp | RouteHandler, handler?: RouteHandler): Router {
|
|
57
|
+
if (typeof pattern === 'function') {
|
|
58
|
+
handler = pattern;
|
|
59
|
+
pattern = /^.*$/; // Match any path
|
|
76
60
|
}
|
|
77
|
-
|
|
78
|
-
const longestMatch = matches[0]
|
|
79
|
-
longestMatch.match.shift()
|
|
80
|
-
longestMatch.r.handler.apply({}, longestMatch.match)
|
|
61
|
+
this.routes.push({ pattern, handler: handler as RouteHandler });
|
|
81
62
|
return this;
|
|
82
63
|
}
|
|
83
64
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
public process(location?: string): Router {
|
|
66
|
+
const path = location || this.getLocation();
|
|
67
|
+
if (this.isStaticFile(path))
|
|
68
|
+
return this; // Bypass routing for static files
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
for (const route of this.routes) {
|
|
72
|
+
const match = path.match(route.pattern);
|
|
73
|
+
if (match) {
|
|
74
|
+
match.shift(); // Remove the full match element
|
|
75
|
+
route.handler.apply({}, match);
|
|
76
|
+
return this;
|
|
91
77
|
}
|
|
92
78
|
}
|
|
79
|
+
|
|
80
|
+
console.warn(`No routing found for ${path}`);
|
|
93
81
|
return this;
|
|
94
82
|
}
|
|
95
83
|
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
listen(): void {
|
|
85
|
+
const currentLocation = this.getLocation();
|
|
86
|
+
if (this.baseLocation !== currentLocation) {
|
|
87
|
+
this.baseLocation = currentLocation;
|
|
88
|
+
this.process(currentLocation);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public navigate(path: string = ''): Router {
|
|
98
93
|
if (this.mode === 'history') {
|
|
99
94
|
history.pushState(null, null, this.root + this.clearSlashes(path));
|
|
100
95
|
} else {
|
|
101
|
-
|
|
102
|
-
window.location.href = window.location.href.replace(/#(.*)$/, '') + path;
|
|
103
|
-
this.process()
|
|
96
|
+
window.location.hash = '#' + this.clearSlashes(path);
|
|
104
97
|
}
|
|
105
98
|
return this;
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
101
|
|
|
102
|
+
export const router = new Router();
|
package/src/state-manager.ts
CHANGED
|
@@ -88,8 +88,8 @@ export class StateManager {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// check if the state change was declined by any change authority and if so - don't do it and return false
|
|
91
|
-
const changeConfirmations = await Promise.all(this.changeAuthorities.map(
|
|
92
|
-
if (changeConfirmations.
|
|
91
|
+
const changeConfirmations = await Promise.all(this.changeAuthorities.map(authority => authority(newState)))
|
|
92
|
+
if (changeConfirmations.includes(false))
|
|
93
93
|
return false
|
|
94
94
|
|
|
95
95
|
// perform the change
|
package/tsconfig.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
3
|
+
"target": "ES2022",
|
|
4
4
|
"lib": [
|
|
5
5
|
"DOM",
|
|
6
|
-
"
|
|
6
|
+
"ES2022"
|
|
7
7
|
],
|
|
8
8
|
"module": "ES6",
|
|
9
9
|
"moduleResolution": "Node",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"exclude": [
|
|
19
|
+
"dist/",
|
|
19
20
|
"node_modules",
|
|
20
21
|
"**/__tests__/*"
|
|
21
22
|
]
|