navigation-stack 0.1.3 → 0.3.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/.github/workflows/main.yml +39 -39
- package/README.md +128 -27
- package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
- package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
- package/lib/cjs/beforeLocationChangeListeners.js +51 -0
- package/lib/cjs/createMiddlewares.js +21 -17
- package/lib/cjs/index.js +9 -9
- package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
- package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
- package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
- package/lib/cjs/navigationBlockers.js +55 -47
- package/lib/cjs/normalizeInputLocation.js +1 -0
- package/lib/cjs/parseLocationUrl.js +2 -0
- package/lib/cjs/parseQueryFromSearch.js +1 -1
- package/lib/cjs/session/BrowserSession.js +235 -0
- package/lib/cjs/session/MemorySession.js +223 -0
- package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
- package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
- package/lib/esm/addBeforeLocationChangeListener.js +2 -0
- package/lib/esm/beforeLocationChangeListeners.js +44 -0
- package/lib/esm/createMiddlewares.js +21 -17
- package/lib/esm/index.js +4 -4
- package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
- package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
- package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
- package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
- package/lib/esm/navigationBlockers.js +55 -47
- package/lib/esm/normalizeInputLocation.js +1 -0
- package/lib/esm/parseLocationUrl.js +2 -0
- package/lib/esm/parseQueryFromSearch.js +1 -1
- package/lib/esm/session/BrowserSession.js +229 -0
- package/lib/esm/session/MemorySession.js +217 -0
- package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
- package/lib/index.d.ts +66 -65
- package/package.json +2 -7
- package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
- package/src/addBeforeLocationChangeListener.js +2 -0
- package/src/beforeLocationChangeListeners.js +54 -0
- package/src/createMiddlewares.js +21 -17
- package/src/index.js +4 -4
- package/src/middleware/createBasePathMiddleware.js +2 -2
- package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
- package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
- package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
- package/src/middleware/createTransformLocationMiddleware.js +2 -2
- package/src/navigationBlockers.js +68 -49
- package/src/normalizeInputLocation.js +1 -0
- package/src/parseLocationUrl.js +2 -0
- package/src/parseQueryFromSearch.js +1 -1
- package/src/session/BrowserSession.js +235 -0
- package/src/session/MemorySession.js +219 -0
- package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
- package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
- package/test/createMiddlewares.test.js +2 -2
- package/test/helpers.js +1 -1
- package/test/index.test.js +3 -3
- package/test/middleware/createBasePathMiddleware.test.js +7 -7
- package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
- package/test/middleware/createNavigationBlockerMiddleware.test.js +100 -101
- package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
- package/test/normalizeInputLocation.test.js +3 -0
- package/test/parseLocationUrl.test.js +2 -0
- package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +38 -21
- package/test/session/MemorySession.test.js +244 -0
- package/test/session/ServerSession.test.js +23 -0
- package/types/index.d.ts +66 -65
- package/lib/cjs/environment/BrowserEnvironment.js +0 -111
- package/lib/cjs/environment/MemoryEnvironment.js +0 -150
- package/lib/esm/environment/BrowserEnvironment.js +0 -104
- package/lib/esm/environment/MemoryEnvironment.js +0 -143
- package/src/environment/BrowserEnvironment.js +0 -109
- package/src/environment/MemoryEnvironment.js +0 -151
- package/test/environment/MemoryEnvironment.test.js +0 -218
- package/test/environment/ServerEnvironment.test.js +0 -23
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.__esModule = true;
|
|
4
|
-
exports.default = void 0;
|
|
5
|
-
var _getLocationUrl = _interopRequireDefault(require("../getLocationUrl"));
|
|
6
|
-
var _parseQueryFromSearch = _interopRequireDefault(require("../parseQueryFromSearch"));
|
|
7
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
-
class BrowserEnvironment {
|
|
9
|
-
constructor() {
|
|
10
|
-
this._keyPrefix = Math.random().toString(36).slice(2, 8);
|
|
11
|
-
this._keyIndex = 0;
|
|
12
|
-
this._index = null;
|
|
13
|
-
}
|
|
14
|
-
init() {
|
|
15
|
-
const {
|
|
16
|
-
pathname,
|
|
17
|
-
search,
|
|
18
|
-
hash
|
|
19
|
-
} = window.location;
|
|
20
|
-
const {
|
|
21
|
-
key,
|
|
22
|
-
index = 0,
|
|
23
|
-
state
|
|
24
|
-
} = window.history.state || {};
|
|
25
|
-
const delta = this._index != null ? index - this._index : 0;
|
|
26
|
-
this._index = index;
|
|
27
|
-
return {
|
|
28
|
-
action: 'POP',
|
|
29
|
-
pathname,
|
|
30
|
-
search,
|
|
31
|
-
query: search && (0, _parseQueryFromSearch.default)(search),
|
|
32
|
-
hash,
|
|
33
|
-
key,
|
|
34
|
-
index,
|
|
35
|
-
delta,
|
|
36
|
-
state
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Subscribes to changes in location,
|
|
41
|
-
// excluding ones that happened as a result of calling `.navigate()`.
|
|
42
|
-
subscribe(listener) {
|
|
43
|
-
const onPopState = () => {
|
|
44
|
-
listener(this.init());
|
|
45
|
-
};
|
|
46
|
-
window.addEventListener('popstate', onPopState);
|
|
47
|
-
return () => {
|
|
48
|
-
window.removeEventListener('popstate', onPopState);
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
navigate(location) {
|
|
52
|
-
const {
|
|
53
|
-
action,
|
|
54
|
-
state
|
|
55
|
-
} = location;
|
|
56
|
-
const push = action === 'PUSH';
|
|
57
|
-
if (!push && action !== 'REPLACE') {
|
|
58
|
-
throw Error(`Unrecognized browser environment action: ${action}`);
|
|
59
|
-
}
|
|
60
|
-
const delta = push ? 1 : 0;
|
|
61
|
-
const extraState = this._createExtraState(delta);
|
|
62
|
-
const browserState = Object.assign({
|
|
63
|
-
state
|
|
64
|
-
}, extraState);
|
|
65
|
-
const url = (0, _getLocationUrl.default)(location);
|
|
66
|
-
if (push) {
|
|
67
|
-
window.history.pushState(browserState, null, url);
|
|
68
|
-
} else {
|
|
69
|
-
window.history.replaceState(browserState, null, url);
|
|
70
|
-
}
|
|
71
|
-
return Object.assign({}, location, extraState, {
|
|
72
|
-
delta
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
shift(delta) {
|
|
76
|
-
window.history.go(delta);
|
|
77
|
-
}
|
|
78
|
-
addBeforeDestroyListener(onBeforeDestroy) {
|
|
79
|
-
const onBeforeUnload = event => {
|
|
80
|
-
if (onBeforeDestroy()) {
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
window.addEventListener('beforeunload', onBeforeUnload);
|
|
85
|
-
return () => {
|
|
86
|
-
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
_createExtraState(delta) {
|
|
90
|
-
const keyIndex = this._keyIndex++;
|
|
91
|
-
this._index += delta;
|
|
92
|
-
return {
|
|
93
|
-
key: `${this._keyPrefix}:${keyIndex.toString(36)}`,
|
|
94
|
-
index: this._index
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Returns either a `string` value or `null` if the key doesn't exist.
|
|
99
|
-
getState(key) {
|
|
100
|
-
// FYI: `sessionStorage` persists across page reloads.
|
|
101
|
-
return window.sessionStorage.getItem(key);
|
|
102
|
-
}
|
|
103
|
-
removeState(key) {
|
|
104
|
-
window.sessionStorage.removeItem(key);
|
|
105
|
-
}
|
|
106
|
-
setState(key, value) {
|
|
107
|
-
window.sessionStorage.setItem(key, value);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
exports.default = BrowserEnvironment;
|
|
111
|
-
module.exports = exports.default;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.__esModule = true;
|
|
4
|
-
exports.default = void 0;
|
|
5
|
-
var _normalizeInputLocation = _interopRequireDefault(require("../normalizeInputLocation"));
|
|
6
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
-
class MemoryEnvironment {
|
|
8
|
-
constructor(initialLocation, {
|
|
9
|
-
save,
|
|
10
|
-
load
|
|
11
|
-
} = {}) {
|
|
12
|
-
this._save = save;
|
|
13
|
-
this._load = load;
|
|
14
|
-
const initialState = load ? this._loadState() : null;
|
|
15
|
-
if (initialState) {
|
|
16
|
-
this._stack = initialState.stack;
|
|
17
|
-
this._index = initialState.index;
|
|
18
|
-
this._state = initialState.state;
|
|
19
|
-
} else {
|
|
20
|
-
this._stack = [(0, _normalizeInputLocation.default)(initialLocation)];
|
|
21
|
-
this._index = 0;
|
|
22
|
-
this._state = {};
|
|
23
|
-
}
|
|
24
|
-
this._keyPrefix = Math.random().toString(36).slice(2, 8);
|
|
25
|
-
this._keyIndex = 0;
|
|
26
|
-
this._listener = null;
|
|
27
|
-
}
|
|
28
|
-
_loadState() {
|
|
29
|
-
try {
|
|
30
|
-
const {
|
|
31
|
-
stack,
|
|
32
|
-
index,
|
|
33
|
-
state
|
|
34
|
-
} = JSON.parse(this._load());
|
|
35
|
-
|
|
36
|
-
// Check that the stack and index at least seem reasonable before using
|
|
37
|
-
// them as state. This isn't foolproof, but it might prevent mistakes.
|
|
38
|
-
// Also perform a basic validation of `state`.
|
|
39
|
-
if (Array.isArray(stack) && typeof index === 'number' && stack[index] && typeof state === 'object' && state !== null) {
|
|
40
|
-
return {
|
|
41
|
-
stack,
|
|
42
|
-
index,
|
|
43
|
-
state
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
} catch (error) {} // eslint-disable-line no-empty
|
|
47
|
-
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
init(delta = 0) {
|
|
51
|
-
return Object.assign({}, this._stack[this._index], {
|
|
52
|
-
action: 'POP',
|
|
53
|
-
index: this._index,
|
|
54
|
-
delta
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
subscribe(listener) {
|
|
58
|
-
this._listener = listener;
|
|
59
|
-
return () => {
|
|
60
|
-
this._listener = null;
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
navigate(location) {
|
|
64
|
-
const {
|
|
65
|
-
action,
|
|
66
|
-
pathname,
|
|
67
|
-
search,
|
|
68
|
-
query,
|
|
69
|
-
hash,
|
|
70
|
-
state
|
|
71
|
-
} = location;
|
|
72
|
-
const push = action === 'PUSH';
|
|
73
|
-
if (!push && action !== 'REPLACE') throw Error(`Unrecognized browser environment action: ${action}`);
|
|
74
|
-
const delta = push ? 1 : 0;
|
|
75
|
-
this._index += delta;
|
|
76
|
-
const keyIndex = this._keyIndex++;
|
|
77
|
-
const key = `${this._keyPrefix}:${keyIndex.toString(36)}`;
|
|
78
|
-
this._stack[this._index] = {
|
|
79
|
-
pathname,
|
|
80
|
-
search,
|
|
81
|
-
query,
|
|
82
|
-
hash,
|
|
83
|
-
state,
|
|
84
|
-
key
|
|
85
|
-
};
|
|
86
|
-
if (push) {
|
|
87
|
-
this._stack.length = this._index + 1;
|
|
88
|
-
}
|
|
89
|
-
if (this._save) {
|
|
90
|
-
this._saveState();
|
|
91
|
-
}
|
|
92
|
-
return Object.assign({}, location, {
|
|
93
|
-
key,
|
|
94
|
-
index: this._index,
|
|
95
|
-
delta
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
shift(delta) {
|
|
99
|
-
const prevIndex = this._index;
|
|
100
|
-
this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
|
|
101
|
-
if (this._index === prevIndex) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (this._save) {
|
|
105
|
-
this._saveState();
|
|
106
|
-
}
|
|
107
|
-
if (this._listener) {
|
|
108
|
-
this._listener(this.init(this._index - prevIndex));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// "Before destroy" listeners are currently ignored.
|
|
113
|
-
// If required, one could implement a `_destroy()` method
|
|
114
|
-
// and there check that the listeners actually do get called.
|
|
115
|
-
addBeforeDestroyListener(listener) {
|
|
116
|
-
return () => {
|
|
117
|
-
this._removeBeforeDestroyListener(listener);
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// This method is used in tests.
|
|
122
|
-
_removeBeforeDestroyListener() {}
|
|
123
|
-
_saveState() {
|
|
124
|
-
try {
|
|
125
|
-
this._save(JSON.stringify({
|
|
126
|
-
stack: this._stack,
|
|
127
|
-
index: this._index,
|
|
128
|
-
state: this._state
|
|
129
|
-
}));
|
|
130
|
-
} catch (error) {} // eslint-disable-line no-empty
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Returns either a `string` value or `null` if the key doesn't exist.
|
|
134
|
-
getState(key) {
|
|
135
|
-
if (key in this._state) {
|
|
136
|
-
return this._state[key];
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
removeState(key) {
|
|
141
|
-
if (key in this._state) {
|
|
142
|
-
delete this._state[key];
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
setState(key, value) {
|
|
146
|
-
this._state[key] = value;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
exports.default = MemoryEnvironment;
|
|
150
|
-
module.exports = exports.default;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import getLocationUrl from '../getLocationUrl';
|
|
2
|
-
import parseQueryFromSearch from '../parseQueryFromSearch';
|
|
3
|
-
export default class BrowserEnvironment {
|
|
4
|
-
constructor() {
|
|
5
|
-
this._keyPrefix = Math.random().toString(36).slice(2, 8);
|
|
6
|
-
this._keyIndex = 0;
|
|
7
|
-
this._index = null;
|
|
8
|
-
}
|
|
9
|
-
init() {
|
|
10
|
-
const {
|
|
11
|
-
pathname,
|
|
12
|
-
search,
|
|
13
|
-
hash
|
|
14
|
-
} = window.location;
|
|
15
|
-
const {
|
|
16
|
-
key,
|
|
17
|
-
index = 0,
|
|
18
|
-
state
|
|
19
|
-
} = window.history.state || {};
|
|
20
|
-
const delta = this._index != null ? index - this._index : 0;
|
|
21
|
-
this._index = index;
|
|
22
|
-
return {
|
|
23
|
-
action: 'POP',
|
|
24
|
-
pathname,
|
|
25
|
-
search,
|
|
26
|
-
query: search && parseQueryFromSearch(search),
|
|
27
|
-
hash,
|
|
28
|
-
key,
|
|
29
|
-
index,
|
|
30
|
-
delta,
|
|
31
|
-
state
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Subscribes to changes in location,
|
|
36
|
-
// excluding ones that happened as a result of calling `.navigate()`.
|
|
37
|
-
subscribe(listener) {
|
|
38
|
-
const onPopState = () => {
|
|
39
|
-
listener(this.init());
|
|
40
|
-
};
|
|
41
|
-
window.addEventListener('popstate', onPopState);
|
|
42
|
-
return () => {
|
|
43
|
-
window.removeEventListener('popstate', onPopState);
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
navigate(location) {
|
|
47
|
-
const {
|
|
48
|
-
action,
|
|
49
|
-
state
|
|
50
|
-
} = location;
|
|
51
|
-
const push = action === 'PUSH';
|
|
52
|
-
if (!push && action !== 'REPLACE') {
|
|
53
|
-
throw Error(`Unrecognized browser environment action: ${action}`);
|
|
54
|
-
}
|
|
55
|
-
const delta = push ? 1 : 0;
|
|
56
|
-
const extraState = this._createExtraState(delta);
|
|
57
|
-
const browserState = Object.assign({
|
|
58
|
-
state
|
|
59
|
-
}, extraState);
|
|
60
|
-
const url = getLocationUrl(location);
|
|
61
|
-
if (push) {
|
|
62
|
-
window.history.pushState(browserState, null, url);
|
|
63
|
-
} else {
|
|
64
|
-
window.history.replaceState(browserState, null, url);
|
|
65
|
-
}
|
|
66
|
-
return Object.assign({}, location, extraState, {
|
|
67
|
-
delta
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
shift(delta) {
|
|
71
|
-
window.history.go(delta);
|
|
72
|
-
}
|
|
73
|
-
addBeforeDestroyListener(onBeforeDestroy) {
|
|
74
|
-
const onBeforeUnload = event => {
|
|
75
|
-
if (onBeforeDestroy()) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
window.addEventListener('beforeunload', onBeforeUnload);
|
|
80
|
-
return () => {
|
|
81
|
-
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
_createExtraState(delta) {
|
|
85
|
-
const keyIndex = this._keyIndex++;
|
|
86
|
-
this._index += delta;
|
|
87
|
-
return {
|
|
88
|
-
key: `${this._keyPrefix}:${keyIndex.toString(36)}`,
|
|
89
|
-
index: this._index
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Returns either a `string` value or `null` if the key doesn't exist.
|
|
94
|
-
getState(key) {
|
|
95
|
-
// FYI: `sessionStorage` persists across page reloads.
|
|
96
|
-
return window.sessionStorage.getItem(key);
|
|
97
|
-
}
|
|
98
|
-
removeState(key) {
|
|
99
|
-
window.sessionStorage.removeItem(key);
|
|
100
|
-
}
|
|
101
|
-
setState(key, value) {
|
|
102
|
-
window.sessionStorage.setItem(key, value);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import normalizeInputLocation from '../normalizeInputLocation';
|
|
2
|
-
export default class MemoryEnvironment {
|
|
3
|
-
constructor(initialLocation, {
|
|
4
|
-
save,
|
|
5
|
-
load
|
|
6
|
-
} = {}) {
|
|
7
|
-
this._save = save;
|
|
8
|
-
this._load = load;
|
|
9
|
-
const initialState = load ? this._loadState() : null;
|
|
10
|
-
if (initialState) {
|
|
11
|
-
this._stack = initialState.stack;
|
|
12
|
-
this._index = initialState.index;
|
|
13
|
-
this._state = initialState.state;
|
|
14
|
-
} else {
|
|
15
|
-
this._stack = [normalizeInputLocation(initialLocation)];
|
|
16
|
-
this._index = 0;
|
|
17
|
-
this._state = {};
|
|
18
|
-
}
|
|
19
|
-
this._keyPrefix = Math.random().toString(36).slice(2, 8);
|
|
20
|
-
this._keyIndex = 0;
|
|
21
|
-
this._listener = null;
|
|
22
|
-
}
|
|
23
|
-
_loadState() {
|
|
24
|
-
try {
|
|
25
|
-
const {
|
|
26
|
-
stack,
|
|
27
|
-
index,
|
|
28
|
-
state
|
|
29
|
-
} = JSON.parse(this._load());
|
|
30
|
-
|
|
31
|
-
// Check that the stack and index at least seem reasonable before using
|
|
32
|
-
// them as state. This isn't foolproof, but it might prevent mistakes.
|
|
33
|
-
// Also perform a basic validation of `state`.
|
|
34
|
-
if (Array.isArray(stack) && typeof index === 'number' && stack[index] && typeof state === 'object' && state !== null) {
|
|
35
|
-
return {
|
|
36
|
-
stack,
|
|
37
|
-
index,
|
|
38
|
-
state
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {} // eslint-disable-line no-empty
|
|
42
|
-
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
init(delta = 0) {
|
|
46
|
-
return Object.assign({}, this._stack[this._index], {
|
|
47
|
-
action: 'POP',
|
|
48
|
-
index: this._index,
|
|
49
|
-
delta
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
subscribe(listener) {
|
|
53
|
-
this._listener = listener;
|
|
54
|
-
return () => {
|
|
55
|
-
this._listener = null;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
navigate(location) {
|
|
59
|
-
const {
|
|
60
|
-
action,
|
|
61
|
-
pathname,
|
|
62
|
-
search,
|
|
63
|
-
query,
|
|
64
|
-
hash,
|
|
65
|
-
state
|
|
66
|
-
} = location;
|
|
67
|
-
const push = action === 'PUSH';
|
|
68
|
-
if (!push && action !== 'REPLACE') throw Error(`Unrecognized browser environment action: ${action}`);
|
|
69
|
-
const delta = push ? 1 : 0;
|
|
70
|
-
this._index += delta;
|
|
71
|
-
const keyIndex = this._keyIndex++;
|
|
72
|
-
const key = `${this._keyPrefix}:${keyIndex.toString(36)}`;
|
|
73
|
-
this._stack[this._index] = {
|
|
74
|
-
pathname,
|
|
75
|
-
search,
|
|
76
|
-
query,
|
|
77
|
-
hash,
|
|
78
|
-
state,
|
|
79
|
-
key
|
|
80
|
-
};
|
|
81
|
-
if (push) {
|
|
82
|
-
this._stack.length = this._index + 1;
|
|
83
|
-
}
|
|
84
|
-
if (this._save) {
|
|
85
|
-
this._saveState();
|
|
86
|
-
}
|
|
87
|
-
return Object.assign({}, location, {
|
|
88
|
-
key,
|
|
89
|
-
index: this._index,
|
|
90
|
-
delta
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
shift(delta) {
|
|
94
|
-
const prevIndex = this._index;
|
|
95
|
-
this._index = Math.min(Math.max(this._index + delta, 0), this._stack.length - 1);
|
|
96
|
-
if (this._index === prevIndex) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (this._save) {
|
|
100
|
-
this._saveState();
|
|
101
|
-
}
|
|
102
|
-
if (this._listener) {
|
|
103
|
-
this._listener(this.init(this._index - prevIndex));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// "Before destroy" listeners are currently ignored.
|
|
108
|
-
// If required, one could implement a `_destroy()` method
|
|
109
|
-
// and there check that the listeners actually do get called.
|
|
110
|
-
addBeforeDestroyListener(listener) {
|
|
111
|
-
return () => {
|
|
112
|
-
this._removeBeforeDestroyListener(listener);
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// This method is used in tests.
|
|
117
|
-
_removeBeforeDestroyListener() {}
|
|
118
|
-
_saveState() {
|
|
119
|
-
try {
|
|
120
|
-
this._save(JSON.stringify({
|
|
121
|
-
stack: this._stack,
|
|
122
|
-
index: this._index,
|
|
123
|
-
state: this._state
|
|
124
|
-
}));
|
|
125
|
-
} catch (error) {} // eslint-disable-line no-empty
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Returns either a `string` value or `null` if the key doesn't exist.
|
|
129
|
-
getState(key) {
|
|
130
|
-
if (key in this._state) {
|
|
131
|
-
return this._state[key];
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
removeState(key) {
|
|
136
|
-
if (key in this._state) {
|
|
137
|
-
delete this._state[key];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
setState(key, value) {
|
|
141
|
-
this._state[key] = value;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import getLocationUrl from '../getLocationUrl';
|
|
2
|
-
import parseQueryFromSearch from '../parseQueryFromSearch';
|
|
3
|
-
|
|
4
|
-
export default class BrowserEnvironment {
|
|
5
|
-
constructor() {
|
|
6
|
-
this._keyPrefix = Math.random().toString(36).slice(2, 8);
|
|
7
|
-
this._keyIndex = 0;
|
|
8
|
-
|
|
9
|
-
this._index = null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
init() {
|
|
13
|
-
const { pathname, search, hash } = window.location;
|
|
14
|
-
|
|
15
|
-
const { key, index = 0, state } = window.history.state || {};
|
|
16
|
-
const delta = this._index != null ? index - this._index : 0;
|
|
17
|
-
this._index = index;
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
action: 'POP',
|
|
21
|
-
pathname,
|
|
22
|
-
search,
|
|
23
|
-
query: search && parseQueryFromSearch(search),
|
|
24
|
-
hash,
|
|
25
|
-
key,
|
|
26
|
-
index,
|
|
27
|
-
delta,
|
|
28
|
-
state,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Subscribes to changes in location,
|
|
33
|
-
// excluding ones that happened as a result of calling `.navigate()`.
|
|
34
|
-
subscribe(listener) {
|
|
35
|
-
const onPopState = () => {
|
|
36
|
-
listener(this.init());
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
window.addEventListener('popstate', onPopState);
|
|
40
|
-
return () => {
|
|
41
|
-
window.removeEventListener('popstate', onPopState);
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
navigate(location) {
|
|
46
|
-
const { action, state } = location;
|
|
47
|
-
|
|
48
|
-
const push = action === 'PUSH';
|
|
49
|
-
|
|
50
|
-
if (!push && action !== 'REPLACE') {
|
|
51
|
-
throw Error(`Unrecognized browser environment action: ${action}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const delta = push ? 1 : 0;
|
|
55
|
-
const extraState = this._createExtraState(delta);
|
|
56
|
-
|
|
57
|
-
const browserState = { state, ...extraState };
|
|
58
|
-
const url = getLocationUrl(location);
|
|
59
|
-
|
|
60
|
-
if (push) {
|
|
61
|
-
window.history.pushState(browserState, null, url);
|
|
62
|
-
} else {
|
|
63
|
-
window.history.replaceState(browserState, null, url);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { ...location, ...extraState, delta };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
shift(delta) {
|
|
70
|
-
window.history.go(delta);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
addBeforeDestroyListener(onBeforeDestroy) {
|
|
74
|
-
const onBeforeUnload = (event) => {
|
|
75
|
-
if (onBeforeDestroy()) {
|
|
76
|
-
event.preventDefault();
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
window.addEventListener('beforeunload', onBeforeUnload);
|
|
81
|
-
return () => {
|
|
82
|
-
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
_createExtraState(delta) {
|
|
87
|
-
const keyIndex = this._keyIndex++;
|
|
88
|
-
this._index += delta;
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
key: `${this._keyPrefix}:${keyIndex.toString(36)}`,
|
|
92
|
-
index: this._index,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Returns either a `string` value or `null` if the key doesn't exist.
|
|
97
|
-
getState(key) {
|
|
98
|
-
// FYI: `sessionStorage` persists across page reloads.
|
|
99
|
-
return window.sessionStorage.getItem(key);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
removeState(key) {
|
|
103
|
-
window.sessionStorage.removeItem(key);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
setState(key, value) {
|
|
107
|
-
window.sessionStorage.setItem(key, value);
|
|
108
|
-
}
|
|
109
|
-
}
|