navigation-stack 0.1.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/.babelrc.cjs +17 -0
- package/.eslintignore +8 -0
- package/.eslintrc.cjs +10 -0
- package/.github/workflows/main.yml +39 -0
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +1 -0
- package/CODE_OF_CONDUCT.md +77 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/codecov.yml +1 -0
- package/karma.conf.cjs +63 -0
- package/lib/cjs/ActionTypes.js +14 -0
- package/lib/cjs/Actions.js +27 -0
- package/lib/cjs/LocationStateStorage.js +60 -0
- package/lib/cjs/addNavigationBlocker.js +7 -0
- package/lib/cjs/basePath.js +58 -0
- package/lib/cjs/createMiddlewares.js +43 -0
- package/lib/cjs/createSearchFromQuery.js +13 -0
- package/lib/cjs/environment/BrowserEnvironment.js +111 -0
- package/lib/cjs/environment/MemoryEnvironment.js +150 -0
- package/lib/cjs/environment/ServerEnvironment.js +53 -0
- package/lib/cjs/getLocationUrl.js +20 -0
- package/lib/cjs/index.js +30 -0
- package/lib/cjs/isPromise.js +9 -0
- package/lib/cjs/locationReducer.js +13 -0
- package/lib/cjs/middleware/createBasePathMiddleware.js +24 -0
- package/lib/cjs/middleware/createEnvironmentMiddleware.js +58 -0
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +128 -0
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +38 -0
- package/lib/cjs/middleware/navigationActionMiddleware.js +37 -0
- package/lib/cjs/middleware/normalizeInputLocationMiddleware.js +27 -0
- package/lib/cjs/navigationBlockers.js +146 -0
- package/lib/cjs/normalizeInputLocation.js +46 -0
- package/lib/cjs/onlyAllowedOnClientSide.js +10 -0
- package/lib/cjs/parseLocationUrl.js +39 -0
- package/lib/cjs/parseQueryFromSearch.js +16 -0
- package/lib/esm/ActionTypes.js +9 -0
- package/lib/esm/Actions.js +21 -0
- package/lib/esm/LocationStateStorage.js +53 -0
- package/lib/esm/addNavigationBlocker.js +2 -0
- package/lib/esm/basePath.js +53 -0
- package/lib/esm/createMiddlewares.js +37 -0
- package/lib/esm/createSearchFromQuery.js +8 -0
- package/lib/esm/environment/BrowserEnvironment.js +104 -0
- package/lib/esm/environment/MemoryEnvironment.js +143 -0
- package/lib/esm/environment/ServerEnvironment.js +46 -0
- package/lib/esm/getLocationUrl.js +15 -0
- package/lib/esm/index.js +12 -0
- package/lib/esm/isPromise.js +4 -0
- package/lib/esm/locationReducer.js +7 -0
- package/lib/esm/middleware/createBasePathMiddleware.js +19 -0
- package/lib/esm/middleware/createEnvironmentMiddleware.js +52 -0
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +123 -0
- package/lib/esm/middleware/createTransformLocationMiddleware.js +33 -0
- package/lib/esm/middleware/navigationActionMiddleware.js +32 -0
- package/lib/esm/middleware/normalizeInputLocationMiddleware.js +22 -0
- package/lib/esm/navigationBlockers.js +138 -0
- package/lib/esm/normalizeInputLocation.js +41 -0
- package/lib/esm/onlyAllowedOnClientSide.js +5 -0
- package/lib/esm/parseLocationUrl.js +33 -0
- package/lib/esm/parseQueryFromSearch.js +11 -0
- package/lib/index.d.ts +301 -0
- package/package.json +100 -0
- package/renovate.json +3 -0
- package/src/ActionTypes.js +9 -0
- package/src/Actions.js +26 -0
- package/src/LocationStateStorage.js +59 -0
- package/src/addNavigationBlocker.js +2 -0
- package/src/basePath.js +65 -0
- package/src/createMiddlewares.js +41 -0
- package/src/createSearchFromQuery.js +9 -0
- package/src/environment/BrowserEnvironment.js +109 -0
- package/src/environment/MemoryEnvironment.js +151 -0
- package/src/environment/ServerEnvironment.js +54 -0
- package/src/getLocationUrl.js +12 -0
- package/src/index.js +12 -0
- package/src/isPromise.js +8 -0
- package/src/locationReducer.js +8 -0
- package/src/middleware/createBasePathMiddleware.js +20 -0
- package/src/middleware/createEnvironmentMiddleware.js +57 -0
- package/src/middleware/createNavigationBlockerMiddleware.js +128 -0
- package/src/middleware/createTransformLocationMiddleware.js +29 -0
- package/src/middleware/navigationActionMiddleware.js +27 -0
- package/src/middleware/normalizeInputLocationMiddleware.js +21 -0
- package/src/navigationBlockers.js +158 -0
- package/src/normalizeInputLocation.js +44 -0
- package/src/onlyAllowedOnClientSide.js +5 -0
- package/src/parseLocationUrl.js +40 -0
- package/src/parseQueryFromSearch.js +12 -0
- package/test/.eslintrc.cjs +17 -0
- package/test/Action.test.js +72 -0
- package/test/ActionTypes.test.js +13 -0
- package/test/LocationStateStorage.test.js +75 -0
- package/test/basePath.test.js +158 -0
- package/test/createMiddlewares.test.js +62 -0
- package/test/environment/BrowserEnvironment.test.js +165 -0
- package/test/environment/MemoryEnvironment.test.js +218 -0
- package/test/environment/ServerEnvironment.test.js +23 -0
- package/test/getLocationUrl.test.js +33 -0
- package/test/helpers.js +34 -0
- package/test/index.js +44 -0
- package/test/index.test.js +20 -0
- package/test/locationReducer.test.js +42 -0
- package/test/middleware/createBasePathMiddleware.test.js +67 -0
- package/test/middleware/createNavigationBlockerMiddleware.test.js +472 -0
- package/test/middleware/createTransformLocationMiddleware.test.js +44 -0
- package/test/middleware/navigationActionMiddleware.test.js +74 -0
- package/test/middleware/normalizeInputLocationMiddleware.test.js +62 -0
- package/test/normalizeInputLocation.test.js +81 -0
- package/test/parseLocationUrl.test.js +30 -0
- package/types/.eslintrc.cjs +3 -0
- package/types/index.d.ts +301 -0
- package/types/tsconfig.json +14 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = createMiddlewares;
|
|
5
|
+
var _createBasePathMiddleware = _interopRequireDefault(require("./middleware/createBasePathMiddleware"));
|
|
6
|
+
var _createEnvironmentMiddleware = _interopRequireDefault(require("./middleware/createEnvironmentMiddleware"));
|
|
7
|
+
var _createNavigationBlockerMiddleware = _interopRequireDefault(require("./middleware/createNavigationBlockerMiddleware"));
|
|
8
|
+
var _navigationActionMiddleware = _interopRequireDefault(require("./middleware/navigationActionMiddleware"));
|
|
9
|
+
var _normalizeInputLocationMiddleware = _interopRequireDefault(require("./middleware/normalizeInputLocationMiddleware"));
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
function createMiddlewares(environment, options) {
|
|
12
|
+
// Allows temporarily ignoring certain environment location updates.
|
|
13
|
+
let shouldIgnoreEnvironmentLocationUpdates = false;
|
|
14
|
+
const ignoreEnvironmentLocationUpdates = func => {
|
|
15
|
+
shouldIgnoreEnvironmentLocationUpdates = true;
|
|
16
|
+
func();
|
|
17
|
+
shouldIgnoreEnvironmentLocationUpdates = false;
|
|
18
|
+
};
|
|
19
|
+
return [
|
|
20
|
+
// Validates that the action "payload" (input location) is a proper `NormalizedInputLocation`.
|
|
21
|
+
_normalizeInputLocationMiddleware.default,
|
|
22
|
+
// Transforms a "PUSH" / "REPLACE" action into a "NAVIGATE" action.
|
|
23
|
+
_navigationActionMiddleware.default,
|
|
24
|
+
// If a website is hosted under a certain path (`basePath`)
|
|
25
|
+
// then this middleware will automatically strip that starting segment from the `pathname` of `location`s.
|
|
26
|
+
(0, _createBasePathMiddleware.default)(options && options.basePath),
|
|
27
|
+
// Allows blocking navigation.
|
|
28
|
+
// Handles `NAVIGATE` actions dispatched by the application itself.
|
|
29
|
+
(0, _createNavigationBlockerMiddleware.default)(environment, {
|
|
30
|
+
ignoreEnvironmentLocationUpdates
|
|
31
|
+
}),
|
|
32
|
+
// This "middleware" performs the actual navigation according to the `environment` being used.
|
|
33
|
+
// For example, when `BrowserEnvironment` is used, it calls methods of the `history` object.
|
|
34
|
+
(0, _createEnvironmentMiddleware.default)(environment, {
|
|
35
|
+
shouldIgnoreEnvironmentLocationUpdates: () => shouldIgnoreEnvironmentLocationUpdates
|
|
36
|
+
}),
|
|
37
|
+
// Allows blocking navigation.
|
|
38
|
+
// Handles location `UPDATE` actions dispatched by the environment.
|
|
39
|
+
(0, _createNavigationBlockerMiddleware.default)(environment, {
|
|
40
|
+
ignoreEnvironmentLocationUpdates
|
|
41
|
+
})];
|
|
42
|
+
}
|
|
43
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = createSearchFromQuery;
|
|
5
|
+
var _queryString = require("query-string");
|
|
6
|
+
function createSearchFromQuery(query) {
|
|
7
|
+
const queryString = (0, _queryString.stringify)(query);
|
|
8
|
+
if (queryString) {
|
|
9
|
+
return `?${queryString}`;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,111 @@
|
|
|
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;
|
|
@@ -0,0 +1,150 @@
|
|
|
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;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
function noop() {}
|
|
8
|
+
function serverSideNavigationNotPossible() {
|
|
9
|
+
throw new Error('Server-side navigation is not possible');
|
|
10
|
+
}
|
|
11
|
+
class ServerEnvironment {
|
|
12
|
+
constructor(initialLocation) {
|
|
13
|
+
this._location = (0, _normalizeInputLocation.default)(initialLocation);
|
|
14
|
+
}
|
|
15
|
+
init() {
|
|
16
|
+
return Object.assign({
|
|
17
|
+
action: 'POP'
|
|
18
|
+
}, this._location);
|
|
19
|
+
}
|
|
20
|
+
subscribe() {
|
|
21
|
+
// Server environment emits no location events.
|
|
22
|
+
return noop;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Navigation methods are not implemented, because `ServerPEnvironment` instances
|
|
26
|
+
// cannot navigate.
|
|
27
|
+
navigate() {
|
|
28
|
+
serverSideNavigationNotPossible();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Navigation methods are not implemented, because `ServerEnvironment` instances
|
|
32
|
+
// cannot navigate.
|
|
33
|
+
shift() {
|
|
34
|
+
serverSideNavigationNotPossible();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// "Before destroy" listeners are currently ignored.
|
|
38
|
+
// If required, one could implement a `_destroy()` method
|
|
39
|
+
// and there check that the listeners actually do get called.
|
|
40
|
+
addBeforeDestroyListener() {
|
|
41
|
+
return () => {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// It doesn't seem to make any sense to store anything on server side.
|
|
45
|
+
// Hence, state management methods are "no op" stubs.
|
|
46
|
+
getState() {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
removeState() {}
|
|
50
|
+
setState() {}
|
|
51
|
+
}
|
|
52
|
+
exports.default = ServerEnvironment;
|
|
53
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = getLocationUrl;
|
|
5
|
+
var _queryString = require("query-string");
|
|
6
|
+
function getLocationUrl({
|
|
7
|
+
pathname,
|
|
8
|
+
search,
|
|
9
|
+
query,
|
|
10
|
+
hash
|
|
11
|
+
}) {
|
|
12
|
+
if (!search && query) {
|
|
13
|
+
const queryString = (0, _queryString.stringify)(query);
|
|
14
|
+
if (queryString) {
|
|
15
|
+
search = `?${queryString}`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return `${pathname}${search || ''}${hash || ''}`;
|
|
19
|
+
}
|
|
20
|
+
module.exports = exports.default;
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.removeBasePath = exports.parseLocationUrl = exports.locationReducer = exports.getLocationUrl = exports.createMiddlewares = exports.addNavigationBlocker = exports.addBasePath = exports.ServerEnvironment = exports.MemoryEnvironment = exports.LocationStateStorage = exports.BrowserEnvironment = exports.Actions = exports.ActionTypes = void 0;
|
|
5
|
+
var _Actions = _interopRequireDefault(require("./Actions"));
|
|
6
|
+
exports.Actions = _Actions.default;
|
|
7
|
+
var _ActionTypes = _interopRequireDefault(require("./ActionTypes"));
|
|
8
|
+
exports.ActionTypes = _ActionTypes.default;
|
|
9
|
+
var _basePath = require("./basePath");
|
|
10
|
+
exports.addBasePath = _basePath.addBasePath;
|
|
11
|
+
exports.removeBasePath = _basePath.removeBasePath;
|
|
12
|
+
var _addNavigationBlocker = _interopRequireDefault(require("./addNavigationBlocker"));
|
|
13
|
+
exports.addNavigationBlocker = _addNavigationBlocker.default;
|
|
14
|
+
var _getLocationUrl = _interopRequireDefault(require("./getLocationUrl"));
|
|
15
|
+
exports.getLocationUrl = _getLocationUrl.default;
|
|
16
|
+
var _parseLocationUrl = _interopRequireDefault(require("./parseLocationUrl"));
|
|
17
|
+
exports.parseLocationUrl = _parseLocationUrl.default;
|
|
18
|
+
var _createMiddlewares = _interopRequireDefault(require("./createMiddlewares"));
|
|
19
|
+
exports.createMiddlewares = _createMiddlewares.default;
|
|
20
|
+
var _locationReducer = _interopRequireDefault(require("./locationReducer"));
|
|
21
|
+
exports.locationReducer = _locationReducer.default;
|
|
22
|
+
var _LocationStateStorage = _interopRequireDefault(require("./LocationStateStorage"));
|
|
23
|
+
exports.LocationStateStorage = _LocationStateStorage.default;
|
|
24
|
+
var _BrowserEnvironment = _interopRequireDefault(require("./environment/BrowserEnvironment"));
|
|
25
|
+
exports.BrowserEnvironment = _BrowserEnvironment.default;
|
|
26
|
+
var _MemoryEnvironment = _interopRequireDefault(require("./environment/MemoryEnvironment"));
|
|
27
|
+
exports.MemoryEnvironment = _MemoryEnvironment.default;
|
|
28
|
+
var _ServerEnvironment = _interopRequireDefault(require("./environment/ServerEnvironment"));
|
|
29
|
+
exports.ServerEnvironment = _ServerEnvironment.default;
|
|
30
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = isPromise;
|
|
5
|
+
function isPromise(anything) {
|
|
6
|
+
// Weirdly, `typeof null` is "object".
|
|
7
|
+
return typeof anything === 'object' && anything !== null && typeof anything.then === 'function';
|
|
8
|
+
}
|
|
9
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = locationReducer;
|
|
5
|
+
var _ActionTypes = _interopRequireDefault(require("./ActionTypes"));
|
|
6
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
function locationReducer(state, action) {
|
|
8
|
+
if (action.type === _ActionTypes.default.UPDATE) {
|
|
9
|
+
return action.payload;
|
|
10
|
+
}
|
|
11
|
+
return state;
|
|
12
|
+
}
|
|
13
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = createBasePathMiddleware;
|
|
5
|
+
var _basePath = require("../basePath");
|
|
6
|
+
var _createTransformLocationMiddleware = _interopRequireDefault(require("./createTransformLocationMiddleware"));
|
|
7
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
// Creates a "middleware" that, when a website is hosted under a certain path (`basePath`),
|
|
9
|
+
// automatically strips that starting segment from the `pathname` of `location`s.
|
|
10
|
+
function createBasePathMiddleware(basePath) {
|
|
11
|
+
return (0, _createTransformLocationMiddleware.default)({
|
|
12
|
+
// Transforms input `Location`:
|
|
13
|
+
// prepends `basePath` to the URL.
|
|
14
|
+
transformInputLocation: location => {
|
|
15
|
+
return (0, _basePath.addBasePath)(location, basePath);
|
|
16
|
+
},
|
|
17
|
+
// Transforms environment `Location` object:
|
|
18
|
+
// removes `basePath` from the URL.
|
|
19
|
+
transformEnvironmentLocation: location => {
|
|
20
|
+
return (0, _basePath.removeBasePath)(location, basePath);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = createEnvironmentMiddleware;
|
|
5
|
+
var _ActionTypes = _interopRequireDefault(require("../ActionTypes"));
|
|
6
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
function updateLocation(location) {
|
|
8
|
+
return {
|
|
9
|
+
type: _ActionTypes.default.UPDATE,
|
|
10
|
+
payload: location
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Creates a "middleware" that performs the actual navigation according to the `environment` being used.
|
|
15
|
+
// For example, when `BrowserProtocol` is used, it calls methods of the `history` object.
|
|
16
|
+
// A better name for this function could be something like `createProtocolMiddleware(environment)`.
|
|
17
|
+
// A better name for "environment" could be something like "environment".
|
|
18
|
+
function createEnvironmentMiddleware(environment, {
|
|
19
|
+
shouldIgnoreEnvironmentLocationUpdates
|
|
20
|
+
}) {
|
|
21
|
+
return function environmentMiddleware() {
|
|
22
|
+
return next => {
|
|
23
|
+
// Whenever browser location changes,
|
|
24
|
+
// perform the same changes with the internal `location` object.
|
|
25
|
+
const unsubscribe = environment.subscribe(location => {
|
|
26
|
+
if (!shouldIgnoreEnvironmentLocationUpdates()) {
|
|
27
|
+
next(updateLocation(location));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return action => {
|
|
31
|
+
const {
|
|
32
|
+
type,
|
|
33
|
+
payload
|
|
34
|
+
} = action;
|
|
35
|
+
switch (type) {
|
|
36
|
+
case _ActionTypes.default.INIT:
|
|
37
|
+
return next(updateLocation(environment.init()));
|
|
38
|
+
case _ActionTypes.default.NAVIGATE:
|
|
39
|
+
// `environment.navigate()` doesn't trigger the `subscribe()` listener.
|
|
40
|
+
return next(updateLocation(environment.navigate(payload)));
|
|
41
|
+
case _ActionTypes.default.SHIFT:
|
|
42
|
+
// `shift()` will trigger the `subscribe()` listener,
|
|
43
|
+
// which will call `updateLocation()`.
|
|
44
|
+
environment.shift(payload);
|
|
45
|
+
// eslint-disable-next-line consistent-return
|
|
46
|
+
return;
|
|
47
|
+
case _ActionTypes.default.DISPOSE:
|
|
48
|
+
unsubscribe();
|
|
49
|
+
// eslint-disable-next-line consistent-return
|
|
50
|
+
return;
|
|
51
|
+
default:
|
|
52
|
+
return next(action);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
module.exports = exports.default;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = createNavigationBlockerMiddleware;
|
|
5
|
+
var _ActionTypes = _interopRequireDefault(require("../ActionTypes"));
|
|
6
|
+
var _isPromise = _interopRequireDefault(require("../isPromise"));
|
|
7
|
+
var _navigationBlockers = require("../navigationBlockers");
|
|
8
|
+
var _onlyAllowedOnClientSide = _interopRequireDefault(require("../onlyAllowedOnClientSide"));
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
// Creates a "middleware" that applies navigation blockers.
|
|
11
|
+
function createNavigationBlockerMiddleware(environment, {
|
|
12
|
+
ignoreEnvironmentLocationUpdates
|
|
13
|
+
}) {
|
|
14
|
+
// A "dummy" initial value that will be ignored.
|
|
15
|
+
let navigationBlockersEvaluationStatus = {
|
|
16
|
+
cancelled: false
|
|
17
|
+
};
|
|
18
|
+
function createNavigationBlockersEvaluationStatus() {
|
|
19
|
+
(0, _onlyAllowedOnClientSide.default)();
|
|
20
|
+
navigationBlockersEvaluationStatus.cancelled = true;
|
|
21
|
+
navigationBlockersEvaluationStatus = {
|
|
22
|
+
cancelled: false
|
|
23
|
+
};
|
|
24
|
+
return navigationBlockersEvaluationStatus;
|
|
25
|
+
}
|
|
26
|
+
return function navigationListenerMiddleware() {
|
|
27
|
+
return next => action => {
|
|
28
|
+
const {
|
|
29
|
+
type,
|
|
30
|
+
payload
|
|
31
|
+
} = action;
|
|
32
|
+
|
|
33
|
+
// Declaring `result` variable here fixes ESLint error:
|
|
34
|
+
// "Unexpected lexical declaration in case block".
|
|
35
|
+
let result;
|
|
36
|
+
switch (type) {
|
|
37
|
+
// Prevent or allow navigation that was initiated by the application.
|
|
38
|
+
case _ActionTypes.default.NAVIGATE:
|
|
39
|
+
// `resultValue` variable name works around a stupid javascript error:
|
|
40
|
+
// "Cannot redeclare block-scoped variable 'result'".
|
|
41
|
+
result = (0, _navigationBlockers.runNavigationBlockers)((0, _navigationBlockers.getNavigationBlockers)(), payload);
|
|
42
|
+
if ((0, _isPromise.default)(result)) {
|
|
43
|
+
const status = createNavigationBlockersEvaluationStatus();
|
|
44
|
+
// eslint-disable-next-line consistent-return
|
|
45
|
+
result.then(resultValue => {
|
|
46
|
+
if (!status.cancelled) {
|
|
47
|
+
if (!resultValue) {
|
|
48
|
+
return next(action);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
} else if (!result) {
|
|
53
|
+
return next(action);
|
|
54
|
+
}
|
|
55
|
+
// eslint-disable-next-line consistent-return
|
|
56
|
+
return;
|
|
57
|
+
|
|
58
|
+
// Prevent or allow navigation that was initiated by the environment itself.
|
|
59
|
+
// For example, in a web browser, it could happen when the user clicks the "Back"/"Forward" buttons.
|
|
60
|
+
//
|
|
61
|
+
// In this scenario, the web browser is already at the new location, i.e. the navigation has already happened.
|
|
62
|
+
// It could be "prevented" by rewinding back to the previous location.
|
|
63
|
+
//
|
|
64
|
+
case _ActionTypes.default.UPDATE:
|
|
65
|
+
// If no navigation blockers to run, don't do anything.
|
|
66
|
+
if ((0, _navigationBlockers.getNavigationBlockers)().length === 0) {
|
|
67
|
+
return next(action);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If it was the initial page load or a redirect,
|
|
71
|
+
// it's not really a navigation that could be rolled back.
|
|
72
|
+
if (payload.delta === 0) {
|
|
73
|
+
return next(action);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// It's not really possible for a location to not have a `delta` property in a web browser environment.
|
|
77
|
+
// So this case is not something that's supposed to happen in real life.
|
|
78
|
+
// Rather, it's a guard against an unsupported or incorrectly-implemented environment or something like that.
|
|
79
|
+
// If there's no `delta` property on the location, it means that the previous location can't be rewound to,
|
|
80
|
+
// so it can't really "prevent" the navigation that has just happened.
|
|
81
|
+
if (payload.delta === null) {
|
|
82
|
+
return next(action);
|
|
83
|
+
}
|
|
84
|
+
result = (0, _navigationBlockers.runNavigationBlockers)((0, _navigationBlockers.getNavigationBlockers)(), payload);
|
|
85
|
+
if ((0, _isPromise.default)(result)) {
|
|
86
|
+
const status = createNavigationBlockersEvaluationStatus();
|
|
87
|
+
|
|
88
|
+
// While location blockers are running, rewind to the previous location.
|
|
89
|
+
ignoreEnvironmentLocationUpdates(() => {
|
|
90
|
+
environment.shift(-payload.delta);
|
|
91
|
+
});
|
|
92
|
+
result.then(promiseResult => {
|
|
93
|
+
if (promiseResult) {
|
|
94
|
+
// Navigation blocked.
|
|
95
|
+
// Already rewound to a previous location.
|
|
96
|
+
} else if (!status.cancelled) {
|
|
97
|
+
// Navigation not blocked.
|
|
98
|
+
// Rewind back to the new location.
|
|
99
|
+
ignoreEnvironmentLocationUpdates(() => {
|
|
100
|
+
environment.shift(payload.delta);
|
|
101
|
+
});
|
|
102
|
+
// Update the location.
|
|
103
|
+
next(action);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
} else if (result) {
|
|
107
|
+
// Prevent the navigation: rewind to the previous location.
|
|
108
|
+
ignoreEnvironmentLocationUpdates(() => {
|
|
109
|
+
environment.shift(-payload.delta);
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
// Update the location.
|
|
113
|
+
return next(action);
|
|
114
|
+
}
|
|
115
|
+
// eslint-disable-next-line consistent-return
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
// Remove any navigation blockers on `DISPOSE` event.
|
|
119
|
+
case _ActionTypes.default.DISPOSE:
|
|
120
|
+
(0, _navigationBlockers.removeAllNavigationBlockers)();
|
|
121
|
+
return next(action);
|
|
122
|
+
default:
|
|
123
|
+
return next(action);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
module.exports = exports.default;
|