@shuvi/router 0.0.1-pre.1 → 0.0.1-pre.2
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/esm/createRoutesFromArray.d.ts +2 -0
- package/esm/createRoutesFromArray.js +9 -0
- package/esm/getRedirectFromRoutes.d.ts +2 -0
- package/esm/getRedirectFromRoutes.js +8 -0
- package/esm/history/base.d.ts +53 -0
- package/esm/history/base.js +80 -0
- package/esm/history/browser.d.ts +12 -0
- package/esm/history/browser.js +104 -0
- package/esm/history/hash.d.ts +13 -0
- package/esm/history/hash.js +135 -0
- package/esm/history/index.d.ts +7 -0
- package/esm/history/index.js +13 -0
- package/esm/history/memory.d.ts +22 -0
- package/esm/history/memory.js +75 -0
- package/esm/index.d.ts +7 -0
- package/esm/index.js +7 -0
- package/esm/matchPathname.d.ts +15 -0
- package/esm/matchPathname.js +54 -0
- package/esm/matchRoutes.d.ts +6 -0
- package/esm/matchRoutes.js +92 -0
- package/esm/pathParserRanker.d.ts +65 -0
- package/esm/pathParserRanker.js +223 -0
- package/esm/pathTokenizer.d.ts +23 -0
- package/esm/pathTokenizer.js +149 -0
- package/esm/router.d.ts +10 -0
- package/esm/router.js +207 -0
- package/esm/types/history.d.ts +176 -0
- package/esm/types/history.js +0 -0
- package/esm/types/index.d.ts +2 -0
- package/esm/types/index.js +1 -0
- package/esm/types/router.d.ts +78 -0
- package/esm/types/router.js +0 -0
- package/esm/utils/async.d.ts +2 -0
- package/esm/utils/async.js +18 -0
- package/esm/utils/createRedirector.d.ts +8 -0
- package/esm/utils/createRedirector.js +30 -0
- package/esm/utils/dom.d.ts +1 -0
- package/esm/utils/dom.js +1 -0
- package/esm/utils/error.d.ts +1 -0
- package/esm/utils/error.js +3 -0
- package/esm/utils/extract-hooks.d.ts +3 -0
- package/esm/utils/extract-hooks.js +16 -0
- package/esm/utils/history.d.ts +12 -0
- package/esm/utils/history.js +51 -0
- package/esm/utils/index.d.ts +4 -0
- package/esm/utils/index.js +4 -0
- package/esm/utils/misc.d.ts +11 -0
- package/esm/utils/misc.js +41 -0
- package/esm/utils/path.d.ts +13 -0
- package/esm/utils/path.js +101 -0
- package/package.json +5 -4
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function createRoutesFromArray(array) {
|
|
2
|
+
return array.map(partialRoute => {
|
|
3
|
+
let route = Object.assign(Object.assign({}, partialRoute), { caseSensitive: !!partialRoute.caseSensitive, path: partialRoute.path || '/' });
|
|
4
|
+
if (partialRoute.children) {
|
|
5
|
+
route.children = createRoutesFromArray(partialRoute.children);
|
|
6
|
+
}
|
|
7
|
+
return route;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { State, HistoryState, Blocker, Location, ResolvedPath, PathRecord, Action } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* A POP indicates a change to an arbitrary index in the history stack, such
|
|
4
|
+
* as a back or forward navigation. It does not describe the direction of the
|
|
5
|
+
* navigation, only that the current index changed.
|
|
6
|
+
*
|
|
7
|
+
* Note: This is the default action for newly created history objects.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ACTION_POP: Action;
|
|
10
|
+
/**
|
|
11
|
+
* A Push indicates a new entry being added to the history stack, such as when
|
|
12
|
+
* a link is clicked and a new page loads. When this happens, all subsequent
|
|
13
|
+
* entries in the stack are lost.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ACTION_PUSH: Action;
|
|
16
|
+
/**
|
|
17
|
+
* A REPLACE indicates the entry at the current index in the history stack
|
|
18
|
+
* being replaced by a new one.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ACTION_REPLACE: Action;
|
|
21
|
+
export interface TransitionOptions {
|
|
22
|
+
state?: State;
|
|
23
|
+
action?: Action;
|
|
24
|
+
redirectedFrom?: PathRecord;
|
|
25
|
+
onTransition(event: {
|
|
26
|
+
location: Location;
|
|
27
|
+
state: HistoryState;
|
|
28
|
+
url: string;
|
|
29
|
+
}): void;
|
|
30
|
+
onAbort?(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface PushOptions {
|
|
33
|
+
state?: object | null | undefined;
|
|
34
|
+
redirectedFrom?: PathRecord;
|
|
35
|
+
}
|
|
36
|
+
export default abstract class BaseHistory {
|
|
37
|
+
action: Action;
|
|
38
|
+
location: Location;
|
|
39
|
+
doTransition: (to: PathRecord, onComplete: Function, onAbort?: Function) => void;
|
|
40
|
+
protected _index: number;
|
|
41
|
+
protected _blockers: import("../utils").Events<Blocker<State>>;
|
|
42
|
+
protected abstract getIndexAndLocation(): [number, Location];
|
|
43
|
+
abstract setup(): void;
|
|
44
|
+
abstract push(to: PathRecord, options: PushOptions): void;
|
|
45
|
+
abstract replace(to: PathRecord, options?: PushOptions): void;
|
|
46
|
+
abstract go(delta: number): void;
|
|
47
|
+
abstract block(blocker: Blocker<State>): () => void;
|
|
48
|
+
back(): void;
|
|
49
|
+
forward(): void;
|
|
50
|
+
resolve(to: any, from?: any): ResolvedPath;
|
|
51
|
+
transitionTo(to: PathRecord, { onTransition, onAbort, action, state, redirectedFrom }: TransitionOptions): void;
|
|
52
|
+
private _updateState;
|
|
53
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createLocation, createEvents, resolvePath, pathToString } from '../utils';
|
|
2
|
+
/**
|
|
3
|
+
* A POP indicates a change to an arbitrary index in the history stack, such
|
|
4
|
+
* as a back or forward navigation. It does not describe the direction of the
|
|
5
|
+
* navigation, only that the current index changed.
|
|
6
|
+
*
|
|
7
|
+
* Note: This is the default action for newly created history objects.
|
|
8
|
+
*/
|
|
9
|
+
export const ACTION_POP = 'POP';
|
|
10
|
+
/**
|
|
11
|
+
* A Push indicates a new entry being added to the history stack, such as when
|
|
12
|
+
* a link is clicked and a new page loads. When this happens, all subsequent
|
|
13
|
+
* entries in the stack are lost.
|
|
14
|
+
*/
|
|
15
|
+
export const ACTION_PUSH = 'PUSH';
|
|
16
|
+
/**
|
|
17
|
+
* A REPLACE indicates the entry at the current index in the history stack
|
|
18
|
+
* being replaced by a new one.
|
|
19
|
+
*/
|
|
20
|
+
export const ACTION_REPLACE = 'REPLACE';
|
|
21
|
+
export default class BaseHistory {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.action = ACTION_POP;
|
|
24
|
+
this.location = createLocation('/');
|
|
25
|
+
this.doTransition = () => void 0;
|
|
26
|
+
this._index = 0;
|
|
27
|
+
this._blockers = createEvents();
|
|
28
|
+
}
|
|
29
|
+
back() {
|
|
30
|
+
this.go(-1);
|
|
31
|
+
}
|
|
32
|
+
forward() {
|
|
33
|
+
this.go(1);
|
|
34
|
+
}
|
|
35
|
+
resolve(to, from) {
|
|
36
|
+
const toPath = resolvePath(to, from);
|
|
37
|
+
return {
|
|
38
|
+
path: toPath,
|
|
39
|
+
href: pathToString(toPath)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
transitionTo(to, { onTransition, onAbort, action = ACTION_PUSH, state = null, redirectedFrom }) {
|
|
43
|
+
const { path } = this.resolve(to, this.location.pathname);
|
|
44
|
+
const nextLocation = createLocation(path, { state, redirectedFrom });
|
|
45
|
+
// check transition
|
|
46
|
+
if (this._blockers.length) {
|
|
47
|
+
this._blockers.call({
|
|
48
|
+
action,
|
|
49
|
+
location: nextLocation,
|
|
50
|
+
retry: () => {
|
|
51
|
+
this.transitionTo(to, {
|
|
52
|
+
onTransition,
|
|
53
|
+
onAbort,
|
|
54
|
+
action,
|
|
55
|
+
state,
|
|
56
|
+
redirectedFrom
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.doTransition(to, () => {
|
|
63
|
+
onTransition({
|
|
64
|
+
location: nextLocation,
|
|
65
|
+
state: {
|
|
66
|
+
usr: nextLocation.state,
|
|
67
|
+
key: nextLocation.key,
|
|
68
|
+
idx: this._index + 1
|
|
69
|
+
},
|
|
70
|
+
url: this.resolve(nextLocation).href
|
|
71
|
+
});
|
|
72
|
+
this._updateState(action);
|
|
73
|
+
}, onAbort);
|
|
74
|
+
}
|
|
75
|
+
_updateState(nextAction) {
|
|
76
|
+
// update state
|
|
77
|
+
this.action = nextAction;
|
|
78
|
+
[this._index, this.location] = this.getIndexAndLocation();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PathRecord, Location, Blocker } from '../types';
|
|
2
|
+
import BaseHistory, { PushOptions } from './base';
|
|
3
|
+
export default class BrowserHistory extends BaseHistory {
|
|
4
|
+
private _history;
|
|
5
|
+
constructor();
|
|
6
|
+
push(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
7
|
+
replace(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
8
|
+
go(delta: number): void;
|
|
9
|
+
block(blocker: Blocker): () => void;
|
|
10
|
+
setup(): void;
|
|
11
|
+
protected getIndexAndLocation(): [number, Location];
|
|
12
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createLocation, pushState, replaceState, addBlocker, warning } from '../utils';
|
|
2
|
+
import BaseHistory, { ACTION_POP, ACTION_REPLACE } from './base';
|
|
3
|
+
export default class BrowserHistory extends BaseHistory {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this._history = window.history;
|
|
7
|
+
[this._index, this.location] = this.getIndexAndLocation();
|
|
8
|
+
if (this._index == null) {
|
|
9
|
+
this._index = 0;
|
|
10
|
+
this._history.replaceState(Object.assign(Object.assign({}, this._history.state), { idx: this._index }), '');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
push(to, { state, redirectedFrom } = {}) {
|
|
14
|
+
return this.transitionTo(to, {
|
|
15
|
+
state,
|
|
16
|
+
redirectedFrom,
|
|
17
|
+
onTransition({ state, url }) {
|
|
18
|
+
pushState(state, url);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
replace(to, { state, redirectedFrom } = {}) {
|
|
23
|
+
return this.transitionTo(to, {
|
|
24
|
+
state,
|
|
25
|
+
action: ACTION_REPLACE,
|
|
26
|
+
redirectedFrom,
|
|
27
|
+
onTransition({ state, url }) {
|
|
28
|
+
replaceState(state, url);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
go(delta) {
|
|
33
|
+
this._history.go(delta);
|
|
34
|
+
}
|
|
35
|
+
block(blocker) {
|
|
36
|
+
return addBlocker(this._blockers, blocker);
|
|
37
|
+
}
|
|
38
|
+
setup() {
|
|
39
|
+
let blockedPopTx = null;
|
|
40
|
+
const handlePop = () => {
|
|
41
|
+
const index = this._index;
|
|
42
|
+
const blockers = this._blockers;
|
|
43
|
+
if (blockedPopTx) {
|
|
44
|
+
blockers.call(blockedPopTx);
|
|
45
|
+
blockedPopTx = null;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
let nextAction = ACTION_POP;
|
|
49
|
+
let [nextIndex, nextLocation] = this.getIndexAndLocation();
|
|
50
|
+
if (blockers.length) {
|
|
51
|
+
if (nextIndex != null) {
|
|
52
|
+
let delta = index - nextIndex;
|
|
53
|
+
if (delta) {
|
|
54
|
+
// Revert the POP
|
|
55
|
+
blockedPopTx = {
|
|
56
|
+
action: nextAction,
|
|
57
|
+
location: nextLocation,
|
|
58
|
+
retry: () => {
|
|
59
|
+
this.go(delta * -1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
this.go(delta);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Trying to POP to a location with no index. We did not create
|
|
67
|
+
// this location, so we can't effectively block the navigation.
|
|
68
|
+
warning(false,
|
|
69
|
+
// TODO: Write up a doc that explains our blocking strategy in
|
|
70
|
+
// detail and link to it here so people can understand better what
|
|
71
|
+
// is going on and how to avoid it.
|
|
72
|
+
`You are trying to block a POP navigation to a location that was not ` +
|
|
73
|
+
`created by the history library. The block will fail silently in ` +
|
|
74
|
+
`production, but in general you should do all navigation with the ` +
|
|
75
|
+
`history library (instead of using window.history.pushState directly) ` +
|
|
76
|
+
`to avoid this situation.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.transitionTo(nextLocation, {
|
|
81
|
+
onTransition: () => { },
|
|
82
|
+
action: nextAction
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
window.addEventListener('popstate', handlePop);
|
|
88
|
+
}
|
|
89
|
+
getIndexAndLocation() {
|
|
90
|
+
const { pathname, search, hash } = window.location;
|
|
91
|
+
const state = this._history.state || {};
|
|
92
|
+
return [
|
|
93
|
+
state.idx,
|
|
94
|
+
createLocation({
|
|
95
|
+
pathname,
|
|
96
|
+
search,
|
|
97
|
+
hash
|
|
98
|
+
}, {
|
|
99
|
+
state: state.usr || null,
|
|
100
|
+
key: state.key || 'default'
|
|
101
|
+
})
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PathRecord, Location, Blocker, ResolvedPath } from '../types';
|
|
2
|
+
import BaseHistory, { PushOptions } from './base';
|
|
3
|
+
export default class HashHistory extends BaseHistory {
|
|
4
|
+
private _history;
|
|
5
|
+
constructor();
|
|
6
|
+
push(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
7
|
+
replace(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
8
|
+
go(delta: number): void;
|
|
9
|
+
block(blocker: Blocker): () => void;
|
|
10
|
+
resolve(to: any, from?: any): ResolvedPath;
|
|
11
|
+
setup(): void;
|
|
12
|
+
protected getIndexAndLocation(): [number, Location];
|
|
13
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createLocation, pushState, replaceState, addBlocker, resolvePath, pathToString, warning } from '../utils';
|
|
2
|
+
import BaseHistory, { ACTION_POP, ACTION_REPLACE } from './base';
|
|
3
|
+
function getBaseHref() {
|
|
4
|
+
let base = document.querySelector('base');
|
|
5
|
+
let href = '';
|
|
6
|
+
if (base && base.getAttribute('href')) {
|
|
7
|
+
let url = window.location.href;
|
|
8
|
+
let hashIndex = url.indexOf('#');
|
|
9
|
+
href = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
10
|
+
}
|
|
11
|
+
return href;
|
|
12
|
+
}
|
|
13
|
+
function createHref(to) {
|
|
14
|
+
return (getBaseHref() +
|
|
15
|
+
'#' +
|
|
16
|
+
(typeof to === 'string' ? to : pathToString(resolvePath(to))));
|
|
17
|
+
}
|
|
18
|
+
export default class HashHistory extends BaseHistory {
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this._history = window.history;
|
|
22
|
+
[this._index, this.location] = this.getIndexAndLocation();
|
|
23
|
+
if (this._index == null) {
|
|
24
|
+
this._index = 0;
|
|
25
|
+
this._history.replaceState(Object.assign(Object.assign({}, this._history.state), { idx: this._index }), '');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
push(to, { state, redirectedFrom } = {}) {
|
|
29
|
+
return this.transitionTo(to, {
|
|
30
|
+
state,
|
|
31
|
+
redirectedFrom,
|
|
32
|
+
onTransition({ state, url }) {
|
|
33
|
+
pushState(state, url);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
replace(to, { state, redirectedFrom } = {}) {
|
|
38
|
+
return this.transitionTo(to, {
|
|
39
|
+
state,
|
|
40
|
+
action: ACTION_REPLACE,
|
|
41
|
+
redirectedFrom,
|
|
42
|
+
onTransition({ state, url }) {
|
|
43
|
+
replaceState(state, url);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
go(delta) {
|
|
48
|
+
this._history.go(delta);
|
|
49
|
+
}
|
|
50
|
+
block(blocker) {
|
|
51
|
+
return addBlocker(this._blockers, blocker);
|
|
52
|
+
}
|
|
53
|
+
resolve(to, from) {
|
|
54
|
+
const toPath = resolvePath(to, from);
|
|
55
|
+
return {
|
|
56
|
+
path: toPath,
|
|
57
|
+
href: createHref(toPath)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
setup() {
|
|
61
|
+
let blockedPopTx = null;
|
|
62
|
+
const handlePop = () => {
|
|
63
|
+
const index = this._index;
|
|
64
|
+
const blockers = this._blockers;
|
|
65
|
+
if (blockedPopTx) {
|
|
66
|
+
blockers.call(blockedPopTx);
|
|
67
|
+
blockedPopTx = null;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
let nextAction = ACTION_POP;
|
|
71
|
+
let [nextIndex, nextLocation] = this.getIndexAndLocation();
|
|
72
|
+
if (blockers.length) {
|
|
73
|
+
if (nextIndex != null) {
|
|
74
|
+
let delta = index - nextIndex;
|
|
75
|
+
if (delta) {
|
|
76
|
+
// Revert the POP
|
|
77
|
+
blockedPopTx = {
|
|
78
|
+
action: nextAction,
|
|
79
|
+
location: nextLocation,
|
|
80
|
+
retry: () => {
|
|
81
|
+
this.go(delta * -1);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
this.go(delta);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Trying to POP to a location with no index. We did not create
|
|
89
|
+
// this location, so we can't effectively block the navigation.
|
|
90
|
+
warning(false,
|
|
91
|
+
// TODO: Write up a doc that explains our blocking strategy in
|
|
92
|
+
// detail and link to it here so people can understand better what
|
|
93
|
+
// is going on and how to avoid it.
|
|
94
|
+
`You are trying to block a POP navigation to a location that was not ` +
|
|
95
|
+
`created by the history library. The block will fail silently in ` +
|
|
96
|
+
`production, but in general you should do all navigation with the ` +
|
|
97
|
+
`history library (instead of using window.history.pushState directly) ` +
|
|
98
|
+
`to avoid this situation.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.transitionTo(nextLocation, {
|
|
103
|
+
onTransition: () => { },
|
|
104
|
+
action: nextAction
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener('popstate', handlePop);
|
|
110
|
+
// popstate does not fire on hashchange in IE 11 and old (trident) Edge
|
|
111
|
+
// https://developer.mozilla.org/de/docs/Web/API/Window/popstate_event
|
|
112
|
+
window.addEventListener('hashchange', () => {
|
|
113
|
+
const [, nextLocation] = this.getIndexAndLocation();
|
|
114
|
+
// Ignore extraneous hashchange events.
|
|
115
|
+
if (pathToString(nextLocation) !== pathToString(this.location)) {
|
|
116
|
+
handlePop();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
getIndexAndLocation() {
|
|
121
|
+
const { pathname, search, hash } = resolvePath(window.location.hash.substr(1));
|
|
122
|
+
const state = this._history.state || {};
|
|
123
|
+
return [
|
|
124
|
+
state.idx,
|
|
125
|
+
createLocation({
|
|
126
|
+
pathname,
|
|
127
|
+
search,
|
|
128
|
+
hash
|
|
129
|
+
}, {
|
|
130
|
+
state: state.usr || null,
|
|
131
|
+
key: state.key || 'default'
|
|
132
|
+
})
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import MemoryHistory, { MemoryHistoryOptions, InitialEntry } from './memory';
|
|
2
|
+
import BrowserHistory from './browser';
|
|
3
|
+
import HashHistory from './hash';
|
|
4
|
+
export { MemoryHistory, MemoryHistoryOptions, InitialEntry };
|
|
5
|
+
export declare function createBrowserHistory(): BrowserHistory;
|
|
6
|
+
export declare function createHashHistory(): HashHistory;
|
|
7
|
+
export declare function createMemoryHistory(options?: MemoryHistoryOptions): MemoryHistory;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import MemoryHistory from './memory';
|
|
2
|
+
import BrowserHistory from './browser';
|
|
3
|
+
import HashHistory from './hash';
|
|
4
|
+
export { MemoryHistory };
|
|
5
|
+
export function createBrowserHistory() {
|
|
6
|
+
return new BrowserHistory();
|
|
7
|
+
}
|
|
8
|
+
export function createHashHistory() {
|
|
9
|
+
return new HashHistory();
|
|
10
|
+
}
|
|
11
|
+
export function createMemoryHistory(options = {}) {
|
|
12
|
+
return new MemoryHistory(options);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PathRecord, Location, Blocker, PartialLocation, ResolvedPath } from '../types';
|
|
2
|
+
import BaseHistory, { PushOptions } from './base';
|
|
3
|
+
/**
|
|
4
|
+
* A user-supplied object that describes a location. Used when providing
|
|
5
|
+
* entries to `createMemoryHistory` via its `initialEntries` option.
|
|
6
|
+
*/
|
|
7
|
+
export declare type InitialEntry = string | PartialLocation;
|
|
8
|
+
export declare type MemoryHistoryOptions = {
|
|
9
|
+
initialEntries?: InitialEntry[];
|
|
10
|
+
initialIndex?: number;
|
|
11
|
+
};
|
|
12
|
+
export default class MemoryHistory extends BaseHistory {
|
|
13
|
+
private _entries;
|
|
14
|
+
constructor({ initialEntries, initialIndex }?: MemoryHistoryOptions);
|
|
15
|
+
setup(): void;
|
|
16
|
+
push(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
17
|
+
replace(to: PathRecord, { state, redirectedFrom }?: PushOptions): void;
|
|
18
|
+
go(delta: number): void;
|
|
19
|
+
block(blocker: Blocker): () => void;
|
|
20
|
+
resolve(to: PathRecord, from?: string): ResolvedPath;
|
|
21
|
+
protected getIndexAndLocation(): [number, Location];
|
|
22
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createLocation, resolvePath, pathToString, warning } from '../utils';
|
|
2
|
+
import BaseHistory, { ACTION_POP, ACTION_REPLACE } from './base';
|
|
3
|
+
function clamp(n, lowerBound, upperBound) {
|
|
4
|
+
return Math.min(Math.max(n, lowerBound), upperBound);
|
|
5
|
+
}
|
|
6
|
+
export default class MemoryHistory extends BaseHistory {
|
|
7
|
+
constructor({ initialEntries = ['/'], initialIndex } = {}) {
|
|
8
|
+
super();
|
|
9
|
+
this._entries = [];
|
|
10
|
+
this._entries = initialEntries.map(entry => {
|
|
11
|
+
let location = createLocation(Object.assign({ pathname: '/', search: '', hash: '' }, (typeof entry === 'string' ? resolvePath(entry) : entry)));
|
|
12
|
+
warning(location.pathname.charAt(0) === '/', `Relative pathnames are not supported in createMemoryHistory({ initialEntries }) (invalid entry: ${JSON.stringify(entry)})`);
|
|
13
|
+
return location;
|
|
14
|
+
});
|
|
15
|
+
this._index = clamp(initialIndex == null ? this._entries.length - 1 : initialIndex, 0, this._entries.length - 1);
|
|
16
|
+
this.location = this._entries[this._index];
|
|
17
|
+
}
|
|
18
|
+
setup() {
|
|
19
|
+
// do nothing
|
|
20
|
+
}
|
|
21
|
+
push(to, { state, redirectedFrom } = {}) {
|
|
22
|
+
return this.transitionTo(to, {
|
|
23
|
+
state,
|
|
24
|
+
redirectedFrom,
|
|
25
|
+
onTransition: ({ location }) => {
|
|
26
|
+
this._index += 1;
|
|
27
|
+
this._entries.splice(this._index, this._entries.length, location);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
replace(to, { state, redirectedFrom } = {}) {
|
|
32
|
+
return this.transitionTo(to, {
|
|
33
|
+
state,
|
|
34
|
+
action: ACTION_REPLACE,
|
|
35
|
+
redirectedFrom,
|
|
36
|
+
onTransition: ({ location }) => {
|
|
37
|
+
this._entries[this._index] = location;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
go(delta) {
|
|
42
|
+
const { _index: index, _entries: entries } = this;
|
|
43
|
+
let nextIndex = clamp(index + delta, 0, entries.length - 1);
|
|
44
|
+
let nextAction = ACTION_POP;
|
|
45
|
+
let nextLocation = entries[nextIndex];
|
|
46
|
+
// check transition
|
|
47
|
+
if (this._blockers.length) {
|
|
48
|
+
this._blockers.call({
|
|
49
|
+
action: nextAction,
|
|
50
|
+
location: nextLocation,
|
|
51
|
+
retry: () => {
|
|
52
|
+
this.go(delta);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.transitionTo(nextLocation.pathname, Object.assign(Object.assign({}, nextLocation), { action: nextAction, onTransition: ({ location }) => {
|
|
58
|
+
this._index = nextIndex;
|
|
59
|
+
} }));
|
|
60
|
+
}
|
|
61
|
+
block(blocker) {
|
|
62
|
+
return this._blockers.push(blocker);
|
|
63
|
+
}
|
|
64
|
+
resolve(to, from) {
|
|
65
|
+
const toPath = resolvePath(to, from);
|
|
66
|
+
return {
|
|
67
|
+
path: toPath,
|
|
68
|
+
href: pathToString(toPath)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
getIndexAndLocation() {
|
|
72
|
+
const index = this._index;
|
|
73
|
+
return [index, this._entries[index]];
|
|
74
|
+
}
|
|
75
|
+
}
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createRoutesFromArray } from './createRoutesFromArray';
|
|
2
|
+
export { matchPathname, matchStringify } from './matchPathname';
|
|
3
|
+
export { matchRoutes, IRouteBaseObject, rankRouteBranches } from './matchRoutes';
|
|
4
|
+
export { pathToString, parseQuery, resolvePath, createLocation, createRedirector } from './utils';
|
|
5
|
+
export * from './types';
|
|
6
|
+
export * from './history';
|
|
7
|
+
export * from './router';
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createRoutesFromArray } from './createRoutesFromArray';
|
|
2
|
+
export { matchPathname, matchStringify } from './matchPathname';
|
|
3
|
+
export { matchRoutes, rankRouteBranches } from './matchRoutes';
|
|
4
|
+
export { pathToString, parseQuery, resolvePath, createLocation, createRedirector } from './utils';
|
|
5
|
+
export * from './types';
|
|
6
|
+
export * from './history';
|
|
7
|
+
export * from './router';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IPathPattern, IPathMatch, IParams } from './types';
|
|
2
|
+
import { PathParserOptions } from './pathParserRanker';
|
|
3
|
+
/**
|
|
4
|
+
* match pathname, online link https://paths.esm.dev/?p=AAMeJSyAwR4UbFDAFxAcAGAIJnMCo0SmCHGYBdyBsATSBUQBsAPABAwxsAHeGVJwuLlARA..#
|
|
5
|
+
* @param pattern
|
|
6
|
+
* @param pathname
|
|
7
|
+
*/
|
|
8
|
+
export declare function matchPathname(pattern: IPathPattern, pathname: string): IPathMatch | null;
|
|
9
|
+
/**
|
|
10
|
+
* stringify path to string by params and options
|
|
11
|
+
* @param path
|
|
12
|
+
* @param params
|
|
13
|
+
* @param options
|
|
14
|
+
*/
|
|
15
|
+
export declare function matchStringify(path: string, params: IParams, options?: PathParserOptions): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { tokensToParser } from './pathParserRanker';
|
|
2
|
+
import { tokenizePath } from './pathTokenizer';
|
|
3
|
+
function safelyDecodeURIComponent(value, paramName, optional) {
|
|
4
|
+
try {
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value.map((item) => {
|
|
7
|
+
return decodeURIComponent(item.replace(/\+/g, ' '));
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
return decodeURIComponent(value.replace(/\+/g, ' '));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (!optional) {
|
|
14
|
+
console.warn(`The value for the URL param "${paramName}" will not be decoded because` +
|
|
15
|
+
` the string "${value}" is a malformed URL segment. This is probably` +
|
|
16
|
+
` due to a bad percent encoding (${error}).`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* match pathname, online link https://paths.esm.dev/?p=AAMeJSyAwR4UbFDAFxAcAGAIJnMCo0SmCHGYBdyBsATSBUQBsAPABAwxsAHeGVJwuLlARA..#
|
|
23
|
+
* @param pattern
|
|
24
|
+
* @param pathname
|
|
25
|
+
*/
|
|
26
|
+
export function matchPathname(pattern, pathname) {
|
|
27
|
+
if (typeof pattern === 'string') {
|
|
28
|
+
pattern = { path: pattern };
|
|
29
|
+
}
|
|
30
|
+
const { path, caseSensitive = false, end = true } = pattern;
|
|
31
|
+
const pathParser = tokensToParser(tokenizePath(path), { end, sensitive: caseSensitive });
|
|
32
|
+
;
|
|
33
|
+
const matchResult = pathParser.parse(pathname);
|
|
34
|
+
if (!matchResult)
|
|
35
|
+
return null;
|
|
36
|
+
const { keys = [] } = pathParser;
|
|
37
|
+
const { match, params } = matchResult;
|
|
38
|
+
const safelyDecodetParams = (keys).reduce((memo, key, index) => {
|
|
39
|
+
const keyName = key.name;
|
|
40
|
+
memo[keyName] = safelyDecodeURIComponent(params[keyName], String(keyName), key.optional);
|
|
41
|
+
return memo;
|
|
42
|
+
}, {});
|
|
43
|
+
return { path, pathname: match, params: safelyDecodetParams };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* stringify path to string by params and options
|
|
47
|
+
* @param path
|
|
48
|
+
* @param params
|
|
49
|
+
* @param options
|
|
50
|
+
*/
|
|
51
|
+
export function matchStringify(path, params, options) {
|
|
52
|
+
const pathParser = tokensToParser(tokenizePath(path), options);
|
|
53
|
+
return pathParser.stringify(params);
|
|
54
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IRouteRecord, IRouteMatch, PartialLocation } from './types';
|
|
2
|
+
export interface IRouteBaseObject<Element = any> extends Omit<IRouteRecord<Element>, 'children' | 'element' | 'filepath'> {
|
|
3
|
+
children?: IRouteBaseObject<Element>[];
|
|
4
|
+
}
|
|
5
|
+
export declare function rankRouteBranches<T extends [string, ...any[]]>(branches: T[]): T[];
|
|
6
|
+
export declare function matchRoutes<T extends IRouteBaseObject>(routes: T[], location: string | PartialLocation, basename?: string): IRouteMatch<T>[] | null;
|