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.
Files changed (77) hide show
  1. package/.github/workflows/main.yml +39 -39
  2. package/README.md +128 -27
  3. package/lib/cjs/{LocationStateStorage.js → LocationDataStorage.js} +5 -5
  4. package/lib/cjs/addBeforeLocationChangeListener.js +7 -0
  5. package/lib/cjs/beforeLocationChangeListeners.js +51 -0
  6. package/lib/cjs/createMiddlewares.js +21 -17
  7. package/lib/cjs/index.js +9 -9
  8. package/lib/cjs/middleware/createBasePathMiddleware.js +2 -2
  9. package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +39 -0
  10. package/lib/cjs/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  11. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +62 -29
  12. package/lib/cjs/middleware/createTransformLocationMiddleware.js +2 -2
  13. package/lib/cjs/navigationBlockers.js +55 -47
  14. package/lib/cjs/normalizeInputLocation.js +1 -0
  15. package/lib/cjs/parseLocationUrl.js +2 -0
  16. package/lib/cjs/parseQueryFromSearch.js +1 -1
  17. package/lib/cjs/session/BrowserSession.js +235 -0
  18. package/lib/cjs/session/MemorySession.js +223 -0
  19. package/lib/cjs/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -16
  20. package/lib/esm/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  21. package/lib/esm/addBeforeLocationChangeListener.js +2 -0
  22. package/lib/esm/beforeLocationChangeListeners.js +44 -0
  23. package/lib/esm/createMiddlewares.js +21 -17
  24. package/lib/esm/index.js +4 -4
  25. package/lib/esm/middleware/createBasePathMiddleware.js +2 -2
  26. package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +34 -0
  27. package/lib/esm/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +11 -13
  28. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +62 -29
  29. package/lib/esm/middleware/createTransformLocationMiddleware.js +2 -2
  30. package/lib/esm/navigationBlockers.js +55 -47
  31. package/lib/esm/normalizeInputLocation.js +1 -0
  32. package/lib/esm/parseLocationUrl.js +2 -0
  33. package/lib/esm/parseQueryFromSearch.js +1 -1
  34. package/lib/esm/session/BrowserSession.js +229 -0
  35. package/lib/esm/session/MemorySession.js +217 -0
  36. package/lib/esm/{environment/ServerEnvironment.js → session/ServerSession.js} +27 -15
  37. package/lib/index.d.ts +66 -65
  38. package/package.json +2 -7
  39. package/src/{LocationStateStorage.js → LocationDataStorage.js} +4 -4
  40. package/src/addBeforeLocationChangeListener.js +2 -0
  41. package/src/beforeLocationChangeListeners.js +54 -0
  42. package/src/createMiddlewares.js +21 -17
  43. package/src/index.js +4 -4
  44. package/src/middleware/createBasePathMiddleware.js +2 -2
  45. package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +40 -0
  46. package/src/middleware/{createEnvironmentMiddleware.js → createLocationMiddleware.js} +12 -14
  47. package/src/middleware/createNavigationBlockerMiddleware.js +68 -28
  48. package/src/middleware/createTransformLocationMiddleware.js +2 -2
  49. package/src/navigationBlockers.js +68 -49
  50. package/src/normalizeInputLocation.js +1 -0
  51. package/src/parseLocationUrl.js +2 -0
  52. package/src/parseQueryFromSearch.js +1 -1
  53. package/src/session/BrowserSession.js +235 -0
  54. package/src/session/MemorySession.js +219 -0
  55. package/src/{environment/ServerEnvironment.js → session/ServerSession.js} +28 -15
  56. package/test/{LocationStateStorage.test.js → LocationDataStorage.test.js} +6 -6
  57. package/test/createMiddlewares.test.js +2 -2
  58. package/test/helpers.js +1 -1
  59. package/test/index.test.js +3 -3
  60. package/test/middleware/createBasePathMiddleware.test.js +7 -7
  61. package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +141 -0
  62. package/test/middleware/createNavigationBlockerMiddleware.test.js +100 -101
  63. package/test/middleware/createTransformLocationMiddleware.test.js +1 -1
  64. package/test/normalizeInputLocation.test.js +3 -0
  65. package/test/parseLocationUrl.test.js +2 -0
  66. package/test/{environment/BrowserEnvironment.test.js → session/BrowserSession.test.js} +38 -21
  67. package/test/session/MemorySession.test.js +244 -0
  68. package/test/session/ServerSession.test.js +23 -0
  69. package/types/index.d.ts +66 -65
  70. package/lib/cjs/environment/BrowserEnvironment.js +0 -111
  71. package/lib/cjs/environment/MemoryEnvironment.js +0 -150
  72. package/lib/esm/environment/BrowserEnvironment.js +0 -104
  73. package/lib/esm/environment/MemoryEnvironment.js +0 -143
  74. package/src/environment/BrowserEnvironment.js +0 -109
  75. package/src/environment/MemoryEnvironment.js +0 -151
  76. package/test/environment/MemoryEnvironment.test.js +0 -218
  77. 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
- }