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.
Files changed (113) hide show
  1. package/.babelrc.cjs +17 -0
  2. package/.eslintignore +8 -0
  3. package/.eslintrc.cjs +10 -0
  4. package/.github/workflows/main.yml +39 -0
  5. package/.yarn/install-state.gz +0 -0
  6. package/.yarnrc.yml +1 -0
  7. package/CODE_OF_CONDUCT.md +77 -0
  8. package/LICENSE +21 -0
  9. package/README.md +249 -0
  10. package/codecov.yml +1 -0
  11. package/karma.conf.cjs +63 -0
  12. package/lib/cjs/ActionTypes.js +14 -0
  13. package/lib/cjs/Actions.js +27 -0
  14. package/lib/cjs/LocationStateStorage.js +60 -0
  15. package/lib/cjs/addNavigationBlocker.js +7 -0
  16. package/lib/cjs/basePath.js +58 -0
  17. package/lib/cjs/createMiddlewares.js +43 -0
  18. package/lib/cjs/createSearchFromQuery.js +13 -0
  19. package/lib/cjs/environment/BrowserEnvironment.js +111 -0
  20. package/lib/cjs/environment/MemoryEnvironment.js +150 -0
  21. package/lib/cjs/environment/ServerEnvironment.js +53 -0
  22. package/lib/cjs/getLocationUrl.js +20 -0
  23. package/lib/cjs/index.js +30 -0
  24. package/lib/cjs/isPromise.js +9 -0
  25. package/lib/cjs/locationReducer.js +13 -0
  26. package/lib/cjs/middleware/createBasePathMiddleware.js +24 -0
  27. package/lib/cjs/middleware/createEnvironmentMiddleware.js +58 -0
  28. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +128 -0
  29. package/lib/cjs/middleware/createTransformLocationMiddleware.js +38 -0
  30. package/lib/cjs/middleware/navigationActionMiddleware.js +37 -0
  31. package/lib/cjs/middleware/normalizeInputLocationMiddleware.js +27 -0
  32. package/lib/cjs/navigationBlockers.js +146 -0
  33. package/lib/cjs/normalizeInputLocation.js +46 -0
  34. package/lib/cjs/onlyAllowedOnClientSide.js +10 -0
  35. package/lib/cjs/parseLocationUrl.js +39 -0
  36. package/lib/cjs/parseQueryFromSearch.js +16 -0
  37. package/lib/esm/ActionTypes.js +9 -0
  38. package/lib/esm/Actions.js +21 -0
  39. package/lib/esm/LocationStateStorage.js +53 -0
  40. package/lib/esm/addNavigationBlocker.js +2 -0
  41. package/lib/esm/basePath.js +53 -0
  42. package/lib/esm/createMiddlewares.js +37 -0
  43. package/lib/esm/createSearchFromQuery.js +8 -0
  44. package/lib/esm/environment/BrowserEnvironment.js +104 -0
  45. package/lib/esm/environment/MemoryEnvironment.js +143 -0
  46. package/lib/esm/environment/ServerEnvironment.js +46 -0
  47. package/lib/esm/getLocationUrl.js +15 -0
  48. package/lib/esm/index.js +12 -0
  49. package/lib/esm/isPromise.js +4 -0
  50. package/lib/esm/locationReducer.js +7 -0
  51. package/lib/esm/middleware/createBasePathMiddleware.js +19 -0
  52. package/lib/esm/middleware/createEnvironmentMiddleware.js +52 -0
  53. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +123 -0
  54. package/lib/esm/middleware/createTransformLocationMiddleware.js +33 -0
  55. package/lib/esm/middleware/navigationActionMiddleware.js +32 -0
  56. package/lib/esm/middleware/normalizeInputLocationMiddleware.js +22 -0
  57. package/lib/esm/navigationBlockers.js +138 -0
  58. package/lib/esm/normalizeInputLocation.js +41 -0
  59. package/lib/esm/onlyAllowedOnClientSide.js +5 -0
  60. package/lib/esm/parseLocationUrl.js +33 -0
  61. package/lib/esm/parseQueryFromSearch.js +11 -0
  62. package/lib/index.d.ts +301 -0
  63. package/package.json +100 -0
  64. package/renovate.json +3 -0
  65. package/src/ActionTypes.js +9 -0
  66. package/src/Actions.js +26 -0
  67. package/src/LocationStateStorage.js +59 -0
  68. package/src/addNavigationBlocker.js +2 -0
  69. package/src/basePath.js +65 -0
  70. package/src/createMiddlewares.js +41 -0
  71. package/src/createSearchFromQuery.js +9 -0
  72. package/src/environment/BrowserEnvironment.js +109 -0
  73. package/src/environment/MemoryEnvironment.js +151 -0
  74. package/src/environment/ServerEnvironment.js +54 -0
  75. package/src/getLocationUrl.js +12 -0
  76. package/src/index.js +12 -0
  77. package/src/isPromise.js +8 -0
  78. package/src/locationReducer.js +8 -0
  79. package/src/middleware/createBasePathMiddleware.js +20 -0
  80. package/src/middleware/createEnvironmentMiddleware.js +57 -0
  81. package/src/middleware/createNavigationBlockerMiddleware.js +128 -0
  82. package/src/middleware/createTransformLocationMiddleware.js +29 -0
  83. package/src/middleware/navigationActionMiddleware.js +27 -0
  84. package/src/middleware/normalizeInputLocationMiddleware.js +21 -0
  85. package/src/navigationBlockers.js +158 -0
  86. package/src/normalizeInputLocation.js +44 -0
  87. package/src/onlyAllowedOnClientSide.js +5 -0
  88. package/src/parseLocationUrl.js +40 -0
  89. package/src/parseQueryFromSearch.js +12 -0
  90. package/test/.eslintrc.cjs +17 -0
  91. package/test/Action.test.js +72 -0
  92. package/test/ActionTypes.test.js +13 -0
  93. package/test/LocationStateStorage.test.js +75 -0
  94. package/test/basePath.test.js +158 -0
  95. package/test/createMiddlewares.test.js +62 -0
  96. package/test/environment/BrowserEnvironment.test.js +165 -0
  97. package/test/environment/MemoryEnvironment.test.js +218 -0
  98. package/test/environment/ServerEnvironment.test.js +23 -0
  99. package/test/getLocationUrl.test.js +33 -0
  100. package/test/helpers.js +34 -0
  101. package/test/index.js +44 -0
  102. package/test/index.test.js +20 -0
  103. package/test/locationReducer.test.js +42 -0
  104. package/test/middleware/createBasePathMiddleware.test.js +67 -0
  105. package/test/middleware/createNavigationBlockerMiddleware.test.js +472 -0
  106. package/test/middleware/createTransformLocationMiddleware.test.js +44 -0
  107. package/test/middleware/navigationActionMiddleware.test.js +74 -0
  108. package/test/middleware/normalizeInputLocationMiddleware.test.js +62 -0
  109. package/test/normalizeInputLocation.test.js +81 -0
  110. package/test/parseLocationUrl.test.js +30 -0
  111. package/types/.eslintrc.cjs +3 -0
  112. package/types/index.d.ts +301 -0
  113. package/types/tsconfig.json +14 -0
@@ -0,0 +1,81 @@
1
+ import normalizeInputLocation from '../src/normalizeInputLocation';
2
+
3
+ describe('normalizeInputLocation', () => {
4
+ it('should create `query` from `search`', () => {
5
+ expect(
6
+ normalizeInputLocation({
7
+ pathname: '/foo',
8
+ search: '?bar=baz',
9
+ hash: '#qux',
10
+ }),
11
+ ).to.eql({
12
+ pathname: '/foo',
13
+ search: '?bar=baz',
14
+ query: {
15
+ bar: 'baz',
16
+ },
17
+ hash: '#qux',
18
+ });
19
+ });
20
+
21
+ it('should add default `search` and `hash`', () => {
22
+ expect(
23
+ normalizeInputLocation({
24
+ pathname: '/new/pathname',
25
+ }),
26
+ ).to.eql({
27
+ pathname: '/new/pathname',
28
+ search: '',
29
+ hash: '',
30
+ });
31
+ });
32
+
33
+ it('should parse location URL', () => {
34
+ expect(normalizeInputLocation('/foo')).to.eql({
35
+ pathname: '/foo',
36
+ search: '',
37
+ hash: '',
38
+ });
39
+
40
+ expect(normalizeInputLocation('/foo?bar=baz')).to.eql({
41
+ pathname: '/foo',
42
+ search: '?bar=baz',
43
+ query: {
44
+ bar: 'baz',
45
+ },
46
+ hash: '',
47
+ });
48
+
49
+ expect(normalizeInputLocation('/foo#qux')).to.eql({
50
+ pathname: '/foo',
51
+ search: '',
52
+ hash: '#qux',
53
+ });
54
+
55
+ expect(normalizeInputLocation('/foo?bar=baz#qux')).to.eql({
56
+ pathname: '/foo',
57
+ search: '?bar=baz',
58
+ query: {
59
+ bar: 'baz',
60
+ },
61
+ hash: '#qux',
62
+ });
63
+ });
64
+
65
+ it('should create `search` from `query` when `search` is not present', () => {
66
+ expect(
67
+ normalizeInputLocation({
68
+ pathname: '/foo',
69
+ query: { bar: 'baz' },
70
+ hash: '#qux',
71
+ }),
72
+ ).to.eql({
73
+ pathname: '/foo',
74
+ search: '?bar=baz',
75
+ query: {
76
+ bar: 'baz',
77
+ },
78
+ hash: '#qux',
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,30 @@
1
+ import parseLocationUrl from '../src/parseLocationUrl';
2
+
3
+ describe('parseLocationUrl', () => {
4
+ it('should create location from a URL', () => {
5
+ expect(parseLocationUrl('/foo?bar=baz#qux')).to.deep.equal({
6
+ pathname: '/foo',
7
+ search: '?bar=baz',
8
+ query: {
9
+ bar: 'baz',
10
+ },
11
+ hash: '#qux',
12
+ });
13
+ });
14
+
15
+ it('should create location from a URL (`search` is "?")', () => {
16
+ expect(parseLocationUrl('/foo?#qux')).to.deep.equal({
17
+ pathname: '/foo',
18
+ search: '?',
19
+ hash: '#qux',
20
+ });
21
+ });
22
+
23
+ it('should create location from a URL (no `search` and no `hash`)', () => {
24
+ expect(parseLocationUrl('/foo')).to.deep.equal({
25
+ pathname: '/foo',
26
+ search: '',
27
+ hash: '',
28
+ });
29
+ });
30
+ });
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ extends: ['4catalyzer-typescript'],
3
+ };
@@ -0,0 +1,301 @@
1
+ // TypeScript Version: 3.0
2
+
3
+ export {};
4
+
5
+ export type Query = Record<string, string>;
6
+
7
+ // `InputLocationQuery` may specify query parameter values as any type of data.
8
+ // Those values will later be converted to strings.
9
+ export type InputLocationQuery = Record<
10
+ string,
11
+ string | number | boolean | null | undefined
12
+ >;
13
+
14
+ export interface Location<TState = any> {
15
+ /**
16
+ * 'PUSH' or 'REPLACE' if the location was reached via FarceActions.push or
17
+ * FarceActions.replace respectively; 'POP' on the initial location, or if
18
+ * the location was reached via the browser back or forward buttons or
19
+ * via FarceActions.shift
20
+ */
21
+ action: 'PUSH' | 'REPLACE' | 'POP';
22
+ /**
23
+ * the path name; as on window.location e.g. '/foo'
24
+ */
25
+ pathname: string;
26
+ /**
27
+ * map version of search string
28
+ */
29
+ query: Query;
30
+ /**
31
+ * the search string; as on window.location e.g. '?bar=baz'
32
+ */
33
+ search: string;
34
+ /**
35
+ * the location hash; as on window.location e.g. '#qux'
36
+ */
37
+ hash: string;
38
+ /**
39
+ * if present, a unique key identifying the current history entry
40
+ */
41
+ key?: string;
42
+ /**
43
+ * the current index of the history entry, starting at 0 for the initial
44
+ * entry; this increments on FarceActions.push but not on
45
+ * FarceActions.replace
46
+ */
47
+ index: number;
48
+ /**
49
+ * the difference between the current index and the index of the previous
50
+ * location
51
+ */
52
+ delta: number;
53
+ /**
54
+ * additional location state that is not part of the URL
55
+ */
56
+ state: TState;
57
+ }
58
+
59
+ /**
60
+ * Location descriptor object used in #push and #replace.
61
+ */
62
+ export interface InputLocationObject {
63
+ pathname: Location['pathname'];
64
+ search?: Location['search'];
65
+ query?: InputLocationQuery;
66
+ hash?: Location['hash'];
67
+ state?: Location['state'];
68
+ }
69
+
70
+ export interface LocationBase {
71
+ pathname: Location['pathname'];
72
+ search: Location['search'];
73
+ query?: Query;
74
+ hash: Location['hash'];
75
+ state?: Location['state'];
76
+ }
77
+
78
+ /**
79
+ * Location descriptor string:
80
+ * store.dispatch(FarceActions.push('/foo?bar=baz#qux'));
81
+ *
82
+ * Equivalent location descriptor object:
83
+ * store.dispatch(FarceActions.push({
84
+ * pathname: '/foo',
85
+ * search: '?bar=baz',
86
+ * hash: '#qux',
87
+ * }));
88
+ *
89
+ * https://github.com/4Catalyzer/farce#locations-and-location-descriptors
90
+ */
91
+ export type InputLocationString = string;
92
+
93
+ // Using an interface allows consumers to use object merging to add other
94
+ // location descriptor types.
95
+ export interface InputLocationTypes {
96
+ object: InputLocationObject;
97
+ string: InputLocationString;
98
+ }
99
+
100
+ export type InputLocation = InputLocationTypes[keyof InputLocationTypes];
101
+
102
+ export interface CreateMiddlewaresOptions {
103
+ basePath?: string;
104
+ }
105
+
106
+ export type NavigationBlockerSyncResult = boolean | undefined;
107
+ export type NavigationBlockerResult =
108
+ | NavigationBlockerSyncResult
109
+ | Promise<NavigationBlockerSyncResult>;
110
+
111
+ /**
112
+ * The navigation listener function receives the `location` to which the user
113
+ * is attempting to navigate.
114
+ *
115
+ * The `location` argument is `null` when the web browser tab is about to be closed.
116
+ */
117
+ export interface NavigationBlocker {
118
+ (location: Location | LocationBase | null): NavigationBlockerResult;
119
+ }
120
+
121
+ export function addBasePath<L extends InputLocation>(
122
+ location: L,
123
+ basePath?: string,
124
+ ): L;
125
+ export function removeBasePath<L extends InputLocation>(
126
+ location: L,
127
+ basePath?: string,
128
+ ): L;
129
+
130
+ export function getLocationUrl(location: InputLocationObject): string;
131
+ export function parseLocationUrl(locationUrl: string): LocationBase;
132
+
133
+ export function createMiddlewares(
134
+ environment: Environment,
135
+ options?: CreateMiddlewaresOptions,
136
+ ): Middleware[];
137
+
138
+ export function addNavigationBlocker(
139
+ environment: EnvironmentBase,
140
+ blocker: NavigationBlocker,
141
+ ): () => void;
142
+
143
+ export const ActionTypes: {
144
+ INIT: '@@navigation-stack/INIT';
145
+ PUSH: '@@navigation-stack/PUSH';
146
+ REPLACE: '@@navigation-stack/REPLACE';
147
+ NAVIGATE: '@@navigation-stack/NAVIGATE';
148
+ SHIFT: '@@navigation-stack/SHIFT';
149
+ UPDATE: '@@navigation-stack/UPDATE';
150
+ DISPOSE: '@@navigation-stack/DISPOSE';
151
+ };
152
+
153
+ export interface InitAction {
154
+ type: (typeof ActionTypes)['INIT'];
155
+ }
156
+
157
+ export interface PushAction {
158
+ type: (typeof ActionTypes)['PUSH'];
159
+ payload: InputLocation;
160
+ }
161
+
162
+ export interface ReplaceAction {
163
+ type: (typeof ActionTypes)['REPLACE'];
164
+ payload: InputLocation;
165
+ }
166
+
167
+ export interface RewindAction {
168
+ type: (typeof ActionTypes)['SHIFT'];
169
+ payload: number;
170
+ }
171
+
172
+ export interface DisposeAction {
173
+ type: (typeof ActionTypes)['DISPOSE'];
174
+ }
175
+
176
+ export type Action =
177
+ | InitAction
178
+ | PushAction
179
+ | ReplaceAction
180
+ | RewindAction
181
+ | DisposeAction;
182
+
183
+ export const Actions: {
184
+ init(): InitAction;
185
+ push(location: InputLocation): PushAction;
186
+ replace(location: InputLocation): ReplaceAction;
187
+ go(delta: number): RewindAction;
188
+ dispose(): DisposeAction;
189
+ };
190
+
191
+ type BeforeDestroyListener = () => boolean | undefined;
192
+
193
+ export interface Environment {
194
+ init(): void;
195
+
196
+ // Subscribes to changes in location,
197
+ // excluding ones that happened as a result of calling `.navigate()`.
198
+ subscribe(listener: (location: Location) => void): () => void;
199
+
200
+ navigate(location: LocationBase): Location;
201
+
202
+ go(delta: number): void;
203
+
204
+ addBeforeDestroyListener(listener: BeforeDestroyListener): void;
205
+
206
+ getState(key: string): string | null;
207
+ removeState(key: string): void;
208
+ setState(key: string, value: string): void;
209
+ }
210
+
211
+ // This is just a copy-paste of the `Environment` interface above.
212
+ declare abstract class EnvironmentBase implements Environment {
213
+ init(): void;
214
+
215
+ // Subscribes to changes in location,
216
+ // excluding ones that happened as a result of calling `.navigate()`.
217
+ subscribe(listener: (location: Location) => void): () => void;
218
+
219
+ navigate(location: LocationBase): Location;
220
+
221
+ go(delta: number): void;
222
+
223
+ addBeforeDestroyListener(listener: BeforeDestroyListener): void;
224
+
225
+ getState(key: string): string | null;
226
+ removeState(key: string): void;
227
+ setState(key: string, value: string): void;
228
+ }
229
+
230
+ export class BrowserEnvironment extends EnvironmentBase {}
231
+
232
+ export interface MemoryEnvironmentOptions<MemoryEnvironmentState = any> {
233
+ save?: (state: MemoryEnvironmentState) => void;
234
+ load?: () => MemoryEnvironmentState | undefined | null;
235
+ }
236
+
237
+ export class ServerEnvironment extends EnvironmentBase {
238
+ constructor(initialLocation: InputLocation);
239
+ }
240
+
241
+ export class MemoryEnvironment extends EnvironmentBase {
242
+ constructor(
243
+ initialLocation: InputLocation,
244
+ options?: MemoryEnvironmentOptions,
245
+ );
246
+ }
247
+
248
+ export interface QueryMiddlewareOptions {
249
+ stringify(query: InputLocationQuery): string;
250
+ parse(str: string): Query;
251
+ }
252
+
253
+ export function createQueryMiddleware(
254
+ options: QueryMiddlewareOptions,
255
+ ): Middleware;
256
+
257
+ export const queryMiddleware: Middleware;
258
+
259
+ export function createBasePathMiddleware(basePath?: string): Middleware;
260
+
261
+ export const locationReducer: Reducer<Location, Action>;
262
+
263
+ export class LocationStateStorage {
264
+ constructor(environment: Environment, options?: { namespace?: string });
265
+
266
+ get(location: Location, key: string): any;
267
+ set(location: Location, key: string, value: any): void;
268
+ }
269
+
270
+ // The following types are copy-pasted from `redux`.
271
+
272
+ interface ReduxAction<T = any> {
273
+ type: T;
274
+ }
275
+
276
+ interface AnyAction extends ReduxAction {
277
+ // Allows any extra properties to be defined in an action.
278
+ [extraProps: string]: any;
279
+ }
280
+
281
+ interface Dispatch<A extends Action = AnyAction> {
282
+ <T extends A>(action: T): T;
283
+ }
284
+
285
+ interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
286
+ dispatch: D;
287
+ getState(): S;
288
+ }
289
+
290
+ interface Middleware<
291
+ DispatchExt = {},
292
+ S = any,
293
+ D extends Dispatch = Dispatch,
294
+ > {
295
+ (api: MiddlewareAPI<D, S>): (next: Dispatch) => (action: any) => any;
296
+ }
297
+
298
+ type Reducer<S = any, A extends Action = AnyAction> = (
299
+ state: S | undefined,
300
+ action: A,
301
+ ) => S;
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "forceConsistentCasingInFileNames": true,
4
+ "lib": ["es2016"],
5
+ "module": "commonjs",
6
+ "strict": true,
7
+ "types": [],
8
+ "noEmit": true,
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "navigation-stack": ["."]
12
+ }
13
+ }
14
+ }