@mapsight/lib-redux 2.1.0 → 2.2.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/README.md +16 -13
- package/dist/README.md +16 -13
- package/dist/package.json +6 -4
- package/package.json +7 -5
- package/src/js/clone-action.ts +0 -36
- package/src/js/combine-sub-path-reducers.ts +0 -45
- package/src/js/create-filtered-reducer-for-path.ts +0 -52
- package/src/js/create-immutable-path-reducer.ts +0 -41
- package/src/js/create-prefixed-async-action-middleware.ts +0 -50
- package/src/js/createSelectorUsingOwnProps.ts +0 -84
- package/src/js/deep-change-state.ts +0 -37
- package/src/js/enable-async-dispatch.ts +0 -61
- package/src/js/enable-controlled-dispatch-and-observe.ts +0 -128
- package/src/js/enable-initialization.ts +0 -36
- package/src/js/flatten-actions.ts +0 -22
- package/src/js/index.ts +0 -1
- package/src/js/local-storage.ts +0 -96
- package/src/js/matchPath.ts +0 -78
- package/src/js/matchesPath.ts +0 -23
- package/src/js/observe-state.ts +0 -109
- package/src/js/reducers/immutable/add-to.ts +0 -6
- package/src/js/reducers/immutable/index.ts +0 -17
- package/src/js/reducers/immutable/merge.ts +0 -18
- package/src/js/reducers/immutable/noop.ts +0 -5
- package/src/js/reducers/immutable/remove-from.ts +0 -8
- package/src/js/reducers/immutable/set.ts +0 -5
- package/src/js/reducers/immutable-path/add-to.ts +0 -4
- package/src/js/reducers/immutable-path/index.ts +0 -17
- package/src/js/reducers/immutable-path/merge.ts +0 -4
- package/src/js/reducers/immutable-path/noop.ts +0 -3
- package/src/js/reducers/immutable-path/remove-from.ts +0 -4
- package/src/js/reducers/immutable-path/set.ts +0 -4
- package/src/js/reducers/reduce-by-keys.ts +0 -19
package/README.md
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# @mapsight/lib-redux
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared **Redux utilities** for Mapsight — immutable path reducers, controlled dispatch, async action middleware, state observation, and local-storage helpers. Used by [`@mapsight/core`](https://github.com/open-mapsight/mapsight/blob/main/packages/core/README.md) and [`@mapsight/ui`](https://github.com/open-mapsight/mapsight/blob/main/packages/ui/README.md) plugins; not intended as a general-purpose Redux toolkit for host apps.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See the [documentation hub](https://github.com/open-mapsight/mapsight/blob/main/docs/README.md) for architecture context. For GIS state patterns, start with [Mapsight Redux Architecture](https://github.com/open-mapsight/mapsight/blob/main/packages/core/docs/REDUX_ARCHITECTURE.md) (`@mapsight/core`).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Common imports
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| Module | Purpose |
|
|
10
|
+
| ------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
11
|
+
| `@mapsight/lib-redux/reducers/immutable-path` | Path-based `set`, `merge`, `add-to`, `remove-from` reducers |
|
|
12
|
+
| `@mapsight/lib-redux/reducers/immutable` | Immutable collection reducers |
|
|
13
|
+
| `@mapsight/lib-redux/observe-state` | Subscribe to derived store slices (`observeState`, `getAndObserveState`) |
|
|
14
|
+
| `@mapsight/lib-redux/local-storage` | Persist slice to `localStorage` |
|
|
15
|
+
| `@mapsight/lib-redux/matchesPath`, `matchPath` | Action path matching |
|
|
16
|
+
| `@mapsight/lib-redux/enable-controlled-dispatch-and-observe` | Controlled vs uncontrolled store loop prevention |
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
## Related packages
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
npm publish
|
|
18
|
-
git push
|
|
19
|
-
git push --tags
|
|
20
|
-
```
|
|
20
|
+
| Package | Role |
|
|
21
|
+
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
22
|
+
| [`@mapsight/core`](https://github.com/open-mapsight/mapsight/blob/main/packages/core/README.md) | Primary consumer — GIS store and controllers |
|
|
23
|
+
| [`@mapsight/ui`](https://github.com/open-mapsight/mapsight/blob/main/packages/ui/README.md) | Browser plugins (share link, geolocation, analytics, etc.) |
|
package/dist/README.md
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# @mapsight/lib-redux
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared **Redux utilities** for Mapsight — immutable path reducers, controlled dispatch, async action middleware, state observation, and local-storage helpers. Used by [`@mapsight/core`](https://github.com/open-mapsight/mapsight/blob/main/packages/core/README.md) and [`@mapsight/ui`](https://github.com/open-mapsight/mapsight/blob/main/packages/ui/README.md) plugins; not intended as a general-purpose Redux toolkit for host apps.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See the [documentation hub](https://github.com/open-mapsight/mapsight/blob/main/docs/README.md) for architecture context. For GIS state patterns, start with [Mapsight Redux Architecture](https://github.com/open-mapsight/mapsight/blob/main/packages/core/docs/REDUX_ARCHITECTURE.md) (`@mapsight/core`).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Common imports
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| Module | Purpose |
|
|
10
|
+
| ------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
11
|
+
| `@mapsight/lib-redux/reducers/immutable-path` | Path-based `set`, `merge`, `add-to`, `remove-from` reducers |
|
|
12
|
+
| `@mapsight/lib-redux/reducers/immutable` | Immutable collection reducers |
|
|
13
|
+
| `@mapsight/lib-redux/observe-state` | Subscribe to derived store slices (`observeState`, `getAndObserveState`) |
|
|
14
|
+
| `@mapsight/lib-redux/local-storage` | Persist slice to `localStorage` |
|
|
15
|
+
| `@mapsight/lib-redux/matchesPath`, `matchPath` | Action path matching |
|
|
16
|
+
| `@mapsight/lib-redux/enable-controlled-dispatch-and-observe` | Controlled vs uncontrolled store loop prevention |
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
## Related packages
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
npm publish
|
|
18
|
-
git push
|
|
19
|
-
git push --tags
|
|
20
|
-
```
|
|
20
|
+
| Package | Role |
|
|
21
|
+
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
22
|
+
| [`@mapsight/core`](https://github.com/open-mapsight/mapsight/blob/main/packages/core/README.md) | Primary consumer — GIS store and controllers |
|
|
23
|
+
| [`@mapsight/ui`](https://github.com/open-mapsight/mapsight/blob/main/packages/ui/README.md) | Browser plugins (share link, geolocation, analytics, etc.) |
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapsight/lib-redux",
|
|
3
3
|
"description": "Mapsight Redux Library",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@mapsight/lib-js": "workspace:^",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"import": "./dist/reducers/immutable-path/index.js"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
33
36
|
"license": "UNLICENSED",
|
|
34
37
|
"main": "index.js",
|
|
35
38
|
"repository": {
|
|
@@ -43,8 +46,7 @@
|
|
|
43
46
|
"clean": "rimraf dist/*",
|
|
44
47
|
"clean-build": "run-s clean build",
|
|
45
48
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"watch": "run-
|
|
47
|
-
"watch:
|
|
48
|
-
"watch:modules": "pnpm run build:modules --watch"
|
|
49
|
+
"watch": "run-s build:setup build:modules watch:build",
|
|
50
|
+
"watch:build": "tsc --watch"
|
|
49
51
|
}
|
|
50
52
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapsight/lib-redux",
|
|
3
3
|
"description": "Mapsight Redux Library",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@reduxjs/toolkit": "^2.11.2",
|
|
8
8
|
"@types/lodash": "^4.17.24",
|
|
9
9
|
"lodash": "^4.18.1",
|
|
10
|
-
"@mapsight/lib-js": "^3.0.
|
|
10
|
+
"@mapsight/lib-js": "^3.0.4"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"cpx2": "^8.0.2"
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"import": "./dist/reducers/immutable-path/index.js"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
33
36
|
"license": "UNLICENSED",
|
|
34
37
|
"main": "index.js",
|
|
35
38
|
"repository": {
|
|
@@ -43,8 +46,7 @@
|
|
|
43
46
|
"clean": "rimraf dist/*",
|
|
44
47
|
"clean-build": "run-s clean build",
|
|
45
48
|
"typecheck": "tsc --noEmit",
|
|
46
|
-
"watch": "run-
|
|
47
|
-
"watch:
|
|
48
|
-
"watch:modules": "pnpm run build:modules --watch"
|
|
49
|
+
"watch": "run-s build:setup build:modules watch:build",
|
|
50
|
+
"watch:build": "tsc --watch"
|
|
49
51
|
}
|
|
50
52
|
}
|
package/src/js/clone-action.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type {AnyAction} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
function shallowCloneObject<T>(o: T): T {
|
|
4
|
-
return {...o};
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a new action (object or function) with the same content but new reference.
|
|
9
|
-
*
|
|
10
|
-
* @param action action
|
|
11
|
-
* @returns cloned action
|
|
12
|
-
*/
|
|
13
|
-
export default function cloneAction<T = AnyAction>(action: T): T {
|
|
14
|
-
// special case for redux-thunk where the action is a function
|
|
15
|
-
if (typeof action === "function") {
|
|
16
|
-
const newAction: T = action.bind({}) as T;
|
|
17
|
-
|
|
18
|
-
if ("meta" in action) {
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
22
|
-
newAction.meta = shallowCloneObject(action.meta);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (action.name) {
|
|
26
|
-
Object.defineProperty(newAction, "name", {
|
|
27
|
-
value: action.name + "__cloned",
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return newAction;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// otherwise just make a shallow copy of the plan object action
|
|
35
|
-
return shallowCloneObject(action);
|
|
36
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
import {combineReducers} from "@reduxjs/toolkit";
|
|
3
|
-
|
|
4
|
-
import cloneAction from "./clone-action";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Combines the given reducers, so they only get actions without a path or
|
|
8
|
-
* a path matching the map key.
|
|
9
|
-
*
|
|
10
|
-
* @param reducerMap a map of reducers and their keys representing their part of the store
|
|
11
|
-
* @param pathKey key of the action parameter that contains the path array
|
|
12
|
-
* @returns combined reducer
|
|
13
|
-
*/
|
|
14
|
-
export default function combineSubPathReducers(
|
|
15
|
-
reducerMap: Record<string, Reducer>,
|
|
16
|
-
pathKey = "path",
|
|
17
|
-
) {
|
|
18
|
-
const reducerEntries = Object.entries(reducerMap).map(([name, reducer]) => {
|
|
19
|
-
const wrapperReducer = <T>(state: T, action: AnyAction): any => {
|
|
20
|
-
if (typeof action.meta === "object") {
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
22
|
-
const path = action.meta[pathKey];
|
|
23
|
-
|
|
24
|
-
if (Array.isArray(path) && path.length) {
|
|
25
|
-
if (path[0] !== name) {
|
|
26
|
-
return state;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const controllerAction = cloneAction(action);
|
|
30
|
-
controllerAction[pathKey] = path.slice(1);
|
|
31
|
-
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
33
|
-
return reducer(state, controllerAction);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
38
|
-
return reducer(state, action);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return [name, wrapperReducer] as const;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return combineReducers(Object.fromEntries(reducerEntries));
|
|
45
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
import cloneAction from "./clone-action";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Filters the actions, so the reducer only gets actions without a path or
|
|
7
|
-
* a path matching the given key.
|
|
8
|
-
*
|
|
9
|
-
* @param reducer the base reducer that should get filtered actions only
|
|
10
|
-
* @param key key of the action parameter that contains the path array
|
|
11
|
-
* @param [statePathMetaKey] name of the meta field that contains the path array (action.meta.path), default: 'path'
|
|
12
|
-
* @returns resulting reducer
|
|
13
|
-
*/
|
|
14
|
-
function createFilteredReducerForPath(
|
|
15
|
-
reducer: Reducer,
|
|
16
|
-
key: string,
|
|
17
|
-
statePathMetaKey = "path",
|
|
18
|
-
): Reducer {
|
|
19
|
-
const filteredReducer: Reducer = <T extends Record<string, unknown>>(
|
|
20
|
-
state: T,
|
|
21
|
-
action: AnyAction,
|
|
22
|
-
) => {
|
|
23
|
-
if (typeof action.meta === "object") {
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
|
|
25
|
-
const path = action.meta[statePathMetaKey];
|
|
26
|
-
if (Array.isArray(path) && path.length) {
|
|
27
|
-
if (path[0] !== key) {
|
|
28
|
-
return state;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const slicedAction = cloneAction(action);
|
|
32
|
-
slicedAction[statePathMetaKey] = path.slice(1);
|
|
33
|
-
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
35
|
-
return reducer(state, slicedAction);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
40
|
-
return reducer(state, action);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (reducer.name) {
|
|
44
|
-
Object.defineProperty(filteredReducer, "name", {
|
|
45
|
-
value: reducer.name + "__filtered-by-path",
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return filteredReducer;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default createFilteredReducerForPath;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
import get from "lodash/get";
|
|
3
|
-
|
|
4
|
-
import deepChangeState from "./deep-change-state";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates a new reducer, that replaces an value at the given path using the given reducer,
|
|
8
|
-
* replacing all parent objects, instead of mutating the existing object(s).
|
|
9
|
-
*
|
|
10
|
-
* The given reducer has to work immutable as well to get the full desired effect!
|
|
11
|
-
*
|
|
12
|
-
* @param reducer reducer to reduce the value at the path
|
|
13
|
-
* @param [pathKey] path in the object to reduce, default: 'path'
|
|
14
|
-
* @returns resulting immutable path reducer
|
|
15
|
-
*/
|
|
16
|
-
export default function createImmutablePathReducer(
|
|
17
|
-
reducer: Reducer,
|
|
18
|
-
pathKey = "path",
|
|
19
|
-
): Reducer {
|
|
20
|
-
// noinspection UnnecessaryLocalVariableJS
|
|
21
|
-
const immutablePathReducer: Reducer = <T>(
|
|
22
|
-
state: T,
|
|
23
|
-
action: AnyAction,
|
|
24
|
-
): T => {
|
|
25
|
-
const path = action[pathKey] as Array<string>;
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
27
|
-
const oldValue = (path?.length ? get(state, path) : state) as T;
|
|
28
|
-
const newValue = reducer(oldValue, action) as T;
|
|
29
|
-
if (newValue === oldValue) {
|
|
30
|
-
return state;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return deepChangeState(state, path, newValue);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
//if (reducer.name) {
|
|
37
|
-
// Object.defineProperty(aImmutablePathReducer, 'name', {value: reducer.name + '__immutable-path'});
|
|
38
|
-
//}
|
|
39
|
-
|
|
40
|
-
return immutablePathReducer;
|
|
41
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type {Middleware, MiddlewareAPI} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a redux middleware that allows dispatching of functions as actions, when the action
|
|
5
|
-
* exposes the defined actionFlag. If a prefix is provided, the action function will receive a
|
|
6
|
-
* prefixed getState, meaning it will retrieve the sub store with the prefix key (store.getState()[prefix]).
|
|
7
|
-
*
|
|
8
|
-
* Works similar to thunk, but with additional flag and optional sub store getState.
|
|
9
|
-
*
|
|
10
|
-
* @param [actionFlag] action flag, default: 'isAsync'
|
|
11
|
-
* @param [prefix] prefix state prefix
|
|
12
|
-
* @param [extraArgument] extra argument to be passed to action function
|
|
13
|
-
*
|
|
14
|
-
* @returns {function(*=): function(*): Function} resulting middleware
|
|
15
|
-
*/
|
|
16
|
-
export default function createPrefixedAsyncActionMiddleware<
|
|
17
|
-
Prefix extends string | undefined,
|
|
18
|
-
>(
|
|
19
|
-
actionFlag = "isAsync",
|
|
20
|
-
prefix?: Prefix,
|
|
21
|
-
extraArgument: unknown = undefined,
|
|
22
|
-
): Middleware {
|
|
23
|
-
return function prefixedAsyncActionMiddleware(api: MiddlewareAPI) {
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
25
|
-
let getState = api.getState;
|
|
26
|
-
if (typeof prefix === "string") {
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
28
|
-
const baseGetState = api.getState as () => Record<string, unknown>;
|
|
29
|
-
getState = function getStateWithPrefix() {
|
|
30
|
-
return baseGetState()[prefix];
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return function (next) {
|
|
35
|
-
return function prefixedAsyncActionMiddlewareWithAction(action) {
|
|
36
|
-
if (
|
|
37
|
-
typeof action === "function" &&
|
|
38
|
-
"meta" in action &&
|
|
39
|
-
typeof action.meta === "object" &&
|
|
40
|
-
(action.meta as Record<string, unknown>)[actionFlag]
|
|
41
|
-
) {
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return
|
|
43
|
-
return action(api.dispatch, getState, extraArgument);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return next(action);
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
function strictEqualityComparison<T>(a: T, b: T): boolean {
|
|
2
|
-
return a === b;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export type SelectorUsingPropsOptions = {
|
|
6
|
-
equals?: typeof strictEqualityComparison;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
type OwnPropsSelector<OwnProps, T = unknown> = (ownProps: OwnProps) => T;
|
|
10
|
-
|
|
11
|
-
type SelectorWithOwnProps<
|
|
12
|
-
OwnProps extends Array<unknown> = Array<unknown>,
|
|
13
|
-
State = unknown,
|
|
14
|
-
Value = unknown,
|
|
15
|
-
> = (state: State, ownProps: OwnProps) => Value;
|
|
16
|
-
type SelectorFactory<
|
|
17
|
-
OwnProps extends Array<unknown> = Array<unknown>,
|
|
18
|
-
State = unknown,
|
|
19
|
-
Value = unknown,
|
|
20
|
-
> = (
|
|
21
|
-
...selectedValues: unknown[]
|
|
22
|
-
) => SelectorWithOwnProps<OwnProps, State, Value>;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create a selector whenever some (own) properties change. To be used with react-redux's connect() mapState.
|
|
26
|
-
*
|
|
27
|
-
* @param ownPropsSelectorOrSelectors one selector function or an array of selector functions that receive
|
|
28
|
-
* the ownProps and should return one property value that will be used to determine changes
|
|
29
|
-
* @param selectorFactory factory function receives the selected props as arguments followed by the state and
|
|
30
|
-
* ownProps at the time of invalidation
|
|
31
|
-
* @param [options] optional options
|
|
32
|
-
* @param [options.equals] function that check equality, defaults to strict equality (===)
|
|
33
|
-
* @returns selector function to be used as mapState in react-redux's connect() function.
|
|
34
|
-
* May be combined with createSelector or any other function as
|
|
35
|
-
* long as the selector is called with state as first and own props as second argument.
|
|
36
|
-
*/
|
|
37
|
-
export default function createSelectorUsingOwnProps<
|
|
38
|
-
State = unknown,
|
|
39
|
-
Value = unknown,
|
|
40
|
-
OwnProps extends Array<unknown> = Array<unknown>,
|
|
41
|
-
>(
|
|
42
|
-
ownPropsSelectorOrSelectors:
|
|
43
|
-
| OwnPropsSelector<OwnProps>
|
|
44
|
-
| Array<OwnPropsSelector<OwnProps>>,
|
|
45
|
-
selectorFactory: SelectorFactory<OwnProps, State, Value>,
|
|
46
|
-
options: SelectorUsingPropsOptions = {},
|
|
47
|
-
) {
|
|
48
|
-
const equals = options.equals || strictEqualityComparison;
|
|
49
|
-
let propsCache: Array<unknown>;
|
|
50
|
-
let cachedSelector: SelectorWithOwnProps<OwnProps, State, Value>;
|
|
51
|
-
|
|
52
|
-
const ownPropsSelectors = Array.isArray(ownPropsSelectorOrSelectors)
|
|
53
|
-
? ownPropsSelectorOrSelectors
|
|
54
|
-
: [ownPropsSelectorOrSelectors];
|
|
55
|
-
|
|
56
|
-
function updateSelector(state: State, ownProps: OwnProps) {
|
|
57
|
-
let hasChanged = false;
|
|
58
|
-
|
|
59
|
-
if (!propsCache) {
|
|
60
|
-
propsCache = new Array(ownPropsSelectorOrSelectors.length);
|
|
61
|
-
hasChanged = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
ownPropsSelectors.forEach(function updateProps(ownPropsSelector, i) {
|
|
65
|
-
const prev = propsCache[i];
|
|
66
|
-
const next = ownPropsSelector(ownProps);
|
|
67
|
-
|
|
68
|
-
if (!hasChanged && !equals(prev, next)) {
|
|
69
|
-
hasChanged = true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
propsCache[i] = next;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
if (hasChanged || !cachedSelector) {
|
|
76
|
-
cachedSelector = selectorFactory(...propsCache, state, ownProps);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return function selector(state: State, ownProps: OwnProps): Value {
|
|
81
|
-
updateSelector(state, ownProps);
|
|
82
|
-
return cachedSelector(state, ownProps);
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import setWith from "lodash/setWith";
|
|
2
|
-
|
|
3
|
-
function shallowCloneObject<T>(o: T): T {
|
|
4
|
-
return {...o};
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a new state from {state} with the value at the {path} changed to the given {value} and
|
|
9
|
-
* reassigns all objects down along the path of the change so they aren't strictly equal anymore
|
|
10
|
-
* and can be easily observed as changed by redux/react.
|
|
11
|
-
*
|
|
12
|
-
* **Note:** Returns a new state object! Does NOT mutate the original state object!
|
|
13
|
-
*
|
|
14
|
-
* @param {object} state The original state
|
|
15
|
-
* @param {*[]|string} [path] The path at which the change gets applied.
|
|
16
|
-
* @param {*} [value] The new value to be set (or undefined).
|
|
17
|
-
* @returns {*} The new state with changed value and new objects along the path.
|
|
18
|
-
*/
|
|
19
|
-
export default function deepChangeState<State = unknown, Value = unknown>(
|
|
20
|
-
state: State,
|
|
21
|
-
path: Array<string>,
|
|
22
|
-
value: Value,
|
|
23
|
-
) {
|
|
24
|
-
const hasPath = path?.length;
|
|
25
|
-
if (!hasPath) {
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const newState = shallowCloneObject(state);
|
|
30
|
-
|
|
31
|
-
if (newState && typeof newState === "object") {
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
33
|
-
setWith(newState, path, value, shallowCloneObject);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return newState;
|
|
37
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Store} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
function runAsync(fn: () => void) {
|
|
4
|
-
return setTimeout(() => fn(), 10);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function isRecord(val: unknown): val is Record<keyof any, unknown> {
|
|
8
|
-
return typeof val === "object" && val !== null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Enhances the stores dispatch function, so you can dispatch actions flagged as async.
|
|
13
|
-
* This allows "dispatching while dispatching" by queueing actions that are dispatched while dispatching is already
|
|
14
|
-
* in progress and dispatching them after finishing with the current action.
|
|
15
|
-
*
|
|
16
|
-
* The action is guaranteed to be dispatched asynchronously in any case (even when dispatching is possible immediately).
|
|
17
|
-
*
|
|
18
|
-
* @param store redux store to be enhanced (having dispatch)
|
|
19
|
-
* @param asyncActionFlag flag that async actions expose (action[actionFlag] == true)
|
|
20
|
-
*/
|
|
21
|
-
export default function enableAsyncDispatch(
|
|
22
|
-
store: Store,
|
|
23
|
-
asyncActionFlag: string | symbol = "isAsync",
|
|
24
|
-
) {
|
|
25
|
-
// This function will override the store.dispatch method, deferring the actual dispatch of actions flagged as
|
|
26
|
-
// async by queueing them and only dispatching them in the next microtask by scheduling them with setTimeout.
|
|
27
|
-
let isAsyncScheduled = false;
|
|
28
|
-
const actionQueue: Array<AnyAction> = [];
|
|
29
|
-
|
|
30
|
-
const baseDispatch = store.dispatch;
|
|
31
|
-
|
|
32
|
-
function isActionAsync(action: AnyAction) {
|
|
33
|
-
return isRecord(action.meta) && !!action.meta[asyncActionFlag];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function runScheduleAsyncDispatch() {
|
|
37
|
-
isAsyncScheduled = false;
|
|
38
|
-
|
|
39
|
-
while (actionQueue.length) {
|
|
40
|
-
const action = actionQueue.shift() as AnyAction;
|
|
41
|
-
baseDispatch(action);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function scheduleAsyncDispatch() {
|
|
46
|
-
if (!isAsyncScheduled) {
|
|
47
|
-
isAsyncScheduled = true;
|
|
48
|
-
runAsync(runScheduleAsyncDispatch);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
store.dispatch = (action) => {
|
|
53
|
-
if (isActionAsync(action)) {
|
|
54
|
-
actionQueue.push(action);
|
|
55
|
-
scheduleAsyncDispatch();
|
|
56
|
-
return action;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return baseDispatch(action);
|
|
60
|
-
};
|
|
61
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Dispatch, Store} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
type CompareFunction = (a: unknown, b: unknown) => boolean;
|
|
4
|
-
|
|
5
|
-
const strictEqualCompare: CompareFunction = (a, b) => a === b;
|
|
6
|
-
|
|
7
|
-
type UncontrolledActionListener<State = unknown> = (
|
|
8
|
-
previousState: State,
|
|
9
|
-
state: State,
|
|
10
|
-
) => void;
|
|
11
|
-
type SubscribeUncontrolled<State = unknown> = (
|
|
12
|
-
listener: UncontrolledActionListener<State>,
|
|
13
|
-
) => () => void;
|
|
14
|
-
|
|
15
|
-
export type ObserveHandler<State = unknown, Value = unknown> = (
|
|
16
|
-
newValue?: Value,
|
|
17
|
-
previousValue?: Value,
|
|
18
|
-
state?: State,
|
|
19
|
-
) => void;
|
|
20
|
-
|
|
21
|
-
export type ObserveUncontrolled<State = unknown, Value = unknown> = (
|
|
22
|
-
selector: (state: State) => Value,
|
|
23
|
-
onChange: ObserveHandler<State, Value>,
|
|
24
|
-
compare?: CompareFunction,
|
|
25
|
-
) => () => void;
|
|
26
|
-
|
|
27
|
-
export type StoreExtControlledActions<TState = unknown> = {
|
|
28
|
-
subscribeUncontrolled: SubscribeUncontrolled<TState>;
|
|
29
|
-
observeUncontrolled: ObserveUncontrolled<TState>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type StoreWithControlledActions<
|
|
33
|
-
S extends Store = Store,
|
|
34
|
-
TState = unknown,
|
|
35
|
-
> = S & StoreExtControlledActions<TState>;
|
|
36
|
-
|
|
37
|
-
function isActionWithMeta(
|
|
38
|
-
action: AnyAction,
|
|
39
|
-
): action is AnyAction & {meta: Record<string, unknown>} {
|
|
40
|
-
return "meta" in action;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isControlledAction(
|
|
44
|
-
action: AnyAction,
|
|
45
|
-
controlledActionFlag: string,
|
|
46
|
-
): boolean {
|
|
47
|
-
return (
|
|
48
|
-
isActionWithMeta(action) && action.meta?.[controlledActionFlag] === true
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Enhances the store. Allows actions to be flagged as controlled and adds a new function
|
|
54
|
-
* store.observeUncontrolled(selector, onChange, compare) to the store.
|
|
55
|
-
*
|
|
56
|
-
* @param {object} store redux store
|
|
57
|
-
* @param {string|symbol} controlledActionFlag flag that controlled actions expose (action[controlledActionFlag] == true)
|
|
58
|
-
*/
|
|
59
|
-
export default function enableControlledDispatchAndObserve<State = unknown>(
|
|
60
|
-
store: Store<State>,
|
|
61
|
-
controlledActionFlag = "isControlled",
|
|
62
|
-
) {
|
|
63
|
-
let listeners: Array<UncontrolledActionListener<State>> = [];
|
|
64
|
-
|
|
65
|
-
function removeListener(listener: UncontrolledActionListener<State>) {
|
|
66
|
-
listeners = listeners.filter((l) => l !== listener);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function addListener(listener: UncontrolledActionListener<State>) {
|
|
70
|
-
listeners.push(listener);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const subscribeUncontrolled: SubscribeUncontrolled<State> = (listener) => {
|
|
74
|
-
addListener(listener);
|
|
75
|
-
|
|
76
|
-
return function removeListenerBound() {
|
|
77
|
-
removeListener(listener);
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const observeUncontrolled: ObserveUncontrolled<State> = (
|
|
82
|
-
selector,
|
|
83
|
-
onChange,
|
|
84
|
-
compare = strictEqualCompare,
|
|
85
|
-
) =>
|
|
86
|
-
subscribeUncontrolled(
|
|
87
|
-
function handleUncontrolledChange(previousState, state) {
|
|
88
|
-
const previousValue = selector(previousState);
|
|
89
|
-
const newValue = selector(state);
|
|
90
|
-
|
|
91
|
-
if (!compare(previousValue, newValue)) {
|
|
92
|
-
onChange(newValue, previousValue, state);
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
let wasControlled = false;
|
|
98
|
-
const baseDispatch = store.dispatch;
|
|
99
|
-
|
|
100
|
-
const enhancedDispatch: Dispatch = (action) => {
|
|
101
|
-
wasControlled = isControlledAction(action, controlledActionFlag);
|
|
102
|
-
return baseDispatch(action);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
let previousState = store.getState();
|
|
106
|
-
|
|
107
|
-
function listenForUncontrolledActions() {
|
|
108
|
-
const state = store.getState();
|
|
109
|
-
|
|
110
|
-
if (!wasControlled) {
|
|
111
|
-
listeners.forEach(
|
|
112
|
-
function callUncontrolledChangeListener(listener) {
|
|
113
|
-
listener(previousState, state);
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
previousState = state;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
store.subscribe(listenForUncontrolledActions);
|
|
122
|
-
|
|
123
|
-
Object.assign(store, {
|
|
124
|
-
dispatch: enhancedDispatch,
|
|
125
|
-
subscribeUncontrolled: subscribeUncontrolled,
|
|
126
|
-
observeUncontrolled: observeUncontrolled,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Add initialization reducer to existing reducer
|
|
5
|
-
*
|
|
6
|
-
* @deprecated Use redux preloadedState or mergeAll actions instead.
|
|
7
|
-
*
|
|
8
|
-
* @param reducer base reducer to enhance
|
|
9
|
-
* @param [actionName] name of action, default: 'INITIALIZE'
|
|
10
|
-
* @returns enhanced reducer
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export default function enableInitialization(
|
|
14
|
-
reducer: Reducer,
|
|
15
|
-
actionName = "INITIALIZE",
|
|
16
|
-
) {
|
|
17
|
-
const aReducerWithInitialization: Reducer = <T>(
|
|
18
|
-
state: T,
|
|
19
|
-
action: AnyAction,
|
|
20
|
-
) => {
|
|
21
|
-
if (action.type === actionName) {
|
|
22
|
-
return action.value as T;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
26
|
-
return reducer(state, action);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (reducer.name) {
|
|
30
|
-
Object.defineProperty(aReducerWithInitialization, "name", {
|
|
31
|
-
value: reducer.name + "__with-initialization",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return aReducerWithInitialization;
|
|
36
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type {AnyAction} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Flattens (deeply) batched actions into a flat array of actions
|
|
5
|
-
*
|
|
6
|
-
* @param {object} action the root action
|
|
7
|
-
* @returns {Array<object>} flattened array of actions
|
|
8
|
-
*/
|
|
9
|
-
export default function flattenActions(action: AnyAction): Array<AnyAction> {
|
|
10
|
-
if (
|
|
11
|
-
"meta" in action &&
|
|
12
|
-
"payload" in action &&
|
|
13
|
-
Array.isArray(action.payload) &&
|
|
14
|
-
typeof action.meta === "object" &&
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
16
|
-
action.meta.batch === true
|
|
17
|
-
) {
|
|
18
|
-
return action.payload.flatMap(flattenActions);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return [action];
|
|
22
|
-
}
|
package/src/js/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//
|
package/src/js/local-storage.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type {Store} from "@reduxjs/toolkit";
|
|
2
|
-
import get from "lodash/get";
|
|
3
|
-
import set from "lodash/set";
|
|
4
|
-
|
|
5
|
-
import {observeState} from "./observe-state";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a storage object to access local storage.
|
|
9
|
-
*
|
|
10
|
-
* @param [storageKey=null] key to be used in local storage
|
|
11
|
-
* @returns storage adapter
|
|
12
|
-
*/
|
|
13
|
-
export function createStorage(storageKey: string | null = null) {
|
|
14
|
-
function setStorageKey(k: string | null) {
|
|
15
|
-
storageKey = k;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function getLocalStorageState(): unknown {
|
|
19
|
-
if (!storageKey) {
|
|
20
|
-
throw Error(
|
|
21
|
-
"@mapsight/lib-redux: Cannot access local storage without a key. Please set a storage key using the `string: storageKey` property on `createStorage(string?: storageKey)` or using the `setStorageKey(string: storageKey)` method on an existing storage object.",
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (typeof window !== "undefined") {
|
|
26
|
-
const value = window.localStorage.getItem(storageKey);
|
|
27
|
-
return window.localStorage && value ? JSON.parse(value) : null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function setLocalStorageState(state: unknown) {
|
|
34
|
-
if (!storageKey) {
|
|
35
|
-
throw Error(
|
|
36
|
-
"@mapsight/lib-redux: Cannot access local storage without a key. Please set a storage key using the `string: storageKey` property on `createStorage(string?: storageKey)` or using the `setStorageKey(string: storageKey)` method on an existing storage object.",
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (window.localStorage) {
|
|
41
|
-
window.localStorage.setItem(storageKey, JSON.stringify(state));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function updateLocalStorageState(
|
|
46
|
-
path: Array<string> | string,
|
|
47
|
-
value: unknown,
|
|
48
|
-
) {
|
|
49
|
-
if (!storageKey) {
|
|
50
|
-
throw Error(
|
|
51
|
-
"@mapsight/lib-redux: Cannot access local storage without a key. Please set a storage key using the `string: storageKey` property on `createStorage(string?: storageKey)` or using the `setStorageKey(string: storageKey)` method on an existing storage object.",
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
setLocalStorageState(set<any>(getLocalStorageState(), path, value));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Observes and synchronizes the state of the given store with local storage.
|
|
60
|
-
*
|
|
61
|
-
* @see lodash/get for path definition
|
|
62
|
-
*
|
|
63
|
-
* @param store the store to observe
|
|
64
|
-
* @param paths array of path strings (string[]) or path arrays (string[][])
|
|
65
|
-
*/
|
|
66
|
-
function synchronizePathsToLocalStorage(
|
|
67
|
-
store: Store,
|
|
68
|
-
paths: Array<string | Array<string>>,
|
|
69
|
-
) {
|
|
70
|
-
if (!storageKey) {
|
|
71
|
-
throw Error(
|
|
72
|
-
"@mapsight/lib-redux: Cannot access local storage without a key. Please set a storage key using the `string: storageKey` property on `createStorage(string?: storageKey)` or using the `setStorageKey(string: storageKey)` method on an existing storage object.",
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (window.localStorage) {
|
|
77
|
-
paths.forEach(function synchronizePathToLocalStorage(path) {
|
|
78
|
-
observeState(
|
|
79
|
-
store,
|
|
80
|
-
(state): unknown =>
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
82
|
-
get(state, path),
|
|
83
|
-
(value) => updateLocalStorageState(path, value),
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
setStorageKey: setStorageKey,
|
|
91
|
-
getLocalStorageState: getLocalStorageState,
|
|
92
|
-
setLocalStorageState: setLocalStorageState,
|
|
93
|
-
updateLocalStorageState: updateLocalStorageState,
|
|
94
|
-
synchronizePathsToLocalStorage: synchronizePathsToLocalStorage,
|
|
95
|
-
};
|
|
96
|
-
}
|
package/src/js/matchPath.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {zip} from "@mapsight/lib-js/array";
|
|
2
|
-
|
|
3
|
-
type PatternSegment = {isParam: boolean; name: string};
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Parses a pattern segment.
|
|
7
|
-
*
|
|
8
|
-
* @param patternSegment segment string
|
|
9
|
-
* @returns parsed segment
|
|
10
|
-
*/
|
|
11
|
-
function parsePatternSegment(patternSegment: string): PatternSegment {
|
|
12
|
-
if (patternSegment.startsWith(":")) {
|
|
13
|
-
return {
|
|
14
|
-
isParam: true,
|
|
15
|
-
name: patternSegment.slice(1),
|
|
16
|
-
};
|
|
17
|
-
} else {
|
|
18
|
-
return {
|
|
19
|
-
isParam: false,
|
|
20
|
-
name: patternSegment,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// TODO: lru/limit cache?
|
|
26
|
-
const parsePathPatternCache: Record<string, Array<PatternSegment>> = {};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Parses a path pattern
|
|
30
|
-
*
|
|
31
|
-
* @param pattern path pattern
|
|
32
|
-
* @returns parsed pattern
|
|
33
|
-
*/
|
|
34
|
-
function parsePathPattern(pattern: string): Array<PatternSegment> {
|
|
35
|
-
const fromCache = parsePathPatternCache[pattern];
|
|
36
|
-
if (fromCache !== undefined) {
|
|
37
|
-
return fromCache;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const patternSegments = pattern.split("/").map(parsePatternSegment);
|
|
41
|
-
parsePathPatternCache[pattern] = patternSegments;
|
|
42
|
-
return patternSegments;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* This function matches the given path array to a path pattern. The pattern consists of / delimited names and/or
|
|
47
|
-
* named parameters defined as `:name` where name may be any string which will be used in the returned object to
|
|
48
|
-
* access the parameter value.
|
|
49
|
-
*
|
|
50
|
-
* If the path matches the pattern, the return value will be an object containing any present
|
|
51
|
-
* named parameter values that may be in the pattern, otherwise it will return false if any path segment does not
|
|
52
|
-
* match the specified pattern.
|
|
53
|
-
*
|
|
54
|
-
* @param pathArr path array
|
|
55
|
-
* @param pattern pattern to match
|
|
56
|
-
* @returns object containing matched named parameter values or false if the path did not patch the pattern
|
|
57
|
-
*/
|
|
58
|
-
export default function matchPath(
|
|
59
|
-
pathArr: Array<string>,
|
|
60
|
-
pattern: string,
|
|
61
|
-
): false | Record<string, string> {
|
|
62
|
-
const patternSegments = parsePathPattern(pattern);
|
|
63
|
-
if (patternSegments.length !== pathArr.length) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const params: Record<string, string> = {};
|
|
68
|
-
|
|
69
|
-
for (const [pathValue, patternSegment] of zip(pathArr, patternSegments)) {
|
|
70
|
-
if (patternSegment.isParam) {
|
|
71
|
-
params[patternSegment.name] = pathValue;
|
|
72
|
-
} else if (pathValue !== patternSegment.name) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return params;
|
|
78
|
-
}
|
package/src/js/matchesPath.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import matchPath from "./matchPath";
|
|
2
|
-
|
|
3
|
-
const noMatch: Record<string, string> = {};
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* This function matches the given path array to a path pattern. The pattern consists of / delimited names and/or
|
|
7
|
-
* named parameters defined as `:name` where name may be any string which will be used in the returned object to
|
|
8
|
-
* access the parameter value.
|
|
9
|
-
*
|
|
10
|
-
* If the path matches the pattern it will return an object containing any present
|
|
11
|
-
* named parameter values that may be in the pattern otherwise an empty object is returned.
|
|
12
|
-
*
|
|
13
|
-
* @param pathArr path array
|
|
14
|
-
* @param pattern pattern to match
|
|
15
|
-
* @returns 2-ary array with boolean that is true if the path did match the pattern and an object containing matched named parameter values
|
|
16
|
-
*/
|
|
17
|
-
export default function matchesPath(
|
|
18
|
-
pathArr: Array<string>,
|
|
19
|
-
pattern: string,
|
|
20
|
-
): [boolean, Record<string, string>] {
|
|
21
|
-
const match = matchPath(pathArr, pattern);
|
|
22
|
-
return match ? [true, match] : [false, noMatch];
|
|
23
|
-
}
|
package/src/js/observe-state.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import type {Store} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
function strictEqualCompare<T>(a: T, b: T): boolean {
|
|
4
|
-
return a === b;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
//const jsonCompare = (a, b) => JSON.stringify(a) !== JSON.stringify(b);
|
|
8
|
-
|
|
9
|
-
export const AbortObserving = Symbol(); // TODO: Use Symbol()
|
|
10
|
-
|
|
11
|
-
function internalObserveState<State = unknown, Value = unknown>(
|
|
12
|
-
store: Store<State>,
|
|
13
|
-
selector: (state: State) => Value,
|
|
14
|
-
listener: (
|
|
15
|
-
newValue: Value,
|
|
16
|
-
oldValue: Value | null,
|
|
17
|
-
state: State,
|
|
18
|
-
) => void | typeof AbortObserving,
|
|
19
|
-
compare = strictEqualCompare,
|
|
20
|
-
initialValue: Value | null = null,
|
|
21
|
-
) {
|
|
22
|
-
let currentValue = initialValue;
|
|
23
|
-
const unsubscribe = store.subscribe(function onStateChange() {
|
|
24
|
-
const state = store.getState();
|
|
25
|
-
const newValue = selector(state);
|
|
26
|
-
|
|
27
|
-
if (!compare(currentValue, newValue)) {
|
|
28
|
-
const oldValue = currentValue;
|
|
29
|
-
currentValue = newValue;
|
|
30
|
-
|
|
31
|
-
const returnValue = listener(newValue, oldValue, state);
|
|
32
|
-
if (returnValue === AbortObserving) {
|
|
33
|
-
unsubscribe();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
return unsubscribe;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function observeState<State = unknown, Value = unknown>(
|
|
42
|
-
store: Store<State>,
|
|
43
|
-
selector: (state: State) => Value,
|
|
44
|
-
listener: (
|
|
45
|
-
newValue: Value,
|
|
46
|
-
oldValue: Value | null,
|
|
47
|
-
state: State,
|
|
48
|
-
) => void | typeof AbortObserving,
|
|
49
|
-
compare = strictEqualCompare,
|
|
50
|
-
) {
|
|
51
|
-
const initialValue = selector(store.getState());
|
|
52
|
-
return internalObserveState(
|
|
53
|
-
store,
|
|
54
|
-
selector,
|
|
55
|
-
listener,
|
|
56
|
-
compare,
|
|
57
|
-
initialValue,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function observeStateOnce<State = unknown, Value = unknown>(
|
|
62
|
-
store: Store,
|
|
63
|
-
selector: (state: State) => Value,
|
|
64
|
-
listener: (
|
|
65
|
-
newValue: Value,
|
|
66
|
-
oldValue: Value | null,
|
|
67
|
-
state: State,
|
|
68
|
-
) => void | typeof AbortObserving,
|
|
69
|
-
compare = strictEqualCompare,
|
|
70
|
-
) {
|
|
71
|
-
const unsubscribe = observeState(
|
|
72
|
-
store,
|
|
73
|
-
selector,
|
|
74
|
-
function handleChange(newValue, oldValue, state: State) {
|
|
75
|
-
listener(newValue, oldValue, state);
|
|
76
|
-
unsubscribe();
|
|
77
|
-
},
|
|
78
|
-
compare,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
return unsubscribe;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function getAndObserveState<State = unknown, Value = unknown>(
|
|
85
|
-
store: Store<State>,
|
|
86
|
-
selector: (state: State) => Value,
|
|
87
|
-
listener: (
|
|
88
|
-
newValue: Value,
|
|
89
|
-
oldValue: Value | null,
|
|
90
|
-
state: State,
|
|
91
|
-
) => void | typeof AbortObserving,
|
|
92
|
-
compare = strictEqualCompare,
|
|
93
|
-
) {
|
|
94
|
-
const initialState = store.getState();
|
|
95
|
-
const initialValue = selector(initialState);
|
|
96
|
-
|
|
97
|
-
const initialReturnValue = listener(initialValue, null, initialState);
|
|
98
|
-
if (initialReturnValue === AbortObserving) {
|
|
99
|
-
return () => undefined;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return internalObserveState(
|
|
103
|
-
store,
|
|
104
|
-
selector,
|
|
105
|
-
listener,
|
|
106
|
-
compare,
|
|
107
|
-
initialValue,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type {Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
import addTo from "./add-to";
|
|
4
|
-
import merge from "./merge";
|
|
5
|
-
import noop from "./noop";
|
|
6
|
-
import removeFrom from "./remove-from";
|
|
7
|
-
import set from "./set";
|
|
8
|
-
|
|
9
|
-
const reducers: Record<string, Reducer> = {
|
|
10
|
-
addTo: addTo,
|
|
11
|
-
merge: merge,
|
|
12
|
-
noop: noop,
|
|
13
|
-
removeFrom: removeFrom,
|
|
14
|
-
set: set,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default reducers;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type {AnyAction, Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
import merge from "lodash/merge";
|
|
3
|
-
|
|
4
|
-
const mergeReducer: Reducer = <
|
|
5
|
-
T extends Array<unknown> | Record<string | number | symbol, unknown>,
|
|
6
|
-
>(
|
|
7
|
-
state: T,
|
|
8
|
-
action: AnyAction,
|
|
9
|
-
): T => {
|
|
10
|
-
const result: T = (Array.isArray(state) ? [] : {}) as T;
|
|
11
|
-
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
13
|
-
merge(result, state, action.value);
|
|
14
|
-
|
|
15
|
-
return result;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default mergeReducer;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type {Reducer} from "@reduxjs/toolkit";
|
|
2
|
-
|
|
3
|
-
import addTo from "./add-to";
|
|
4
|
-
import merge from "./merge";
|
|
5
|
-
import noop from "./noop";
|
|
6
|
-
import removeFrom from "./remove-from";
|
|
7
|
-
import set from "./set";
|
|
8
|
-
|
|
9
|
-
const reducers = {
|
|
10
|
-
addTo: addTo,
|
|
11
|
-
merge: merge,
|
|
12
|
-
noop: noop,
|
|
13
|
-
removeFrom: removeFrom,
|
|
14
|
-
set: set,
|
|
15
|
-
} satisfies Record<string, Reducer>;
|
|
16
|
-
|
|
17
|
-
export default reducers;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import unique from "lodash/uniq";
|
|
2
|
-
|
|
3
|
-
export default function reduceByKeys(
|
|
4
|
-
keys: Array<string>,
|
|
5
|
-
state: Record<string, unknown>,
|
|
6
|
-
) {
|
|
7
|
-
if (keys && state) {
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
9
|
-
const uniqueKeys: Array<string> = unique(keys);
|
|
10
|
-
|
|
11
|
-
return uniqueKeys.reduce(function reduceByKey(newState, key) {
|
|
12
|
-
return state[key] !== undefined
|
|
13
|
-
? {...newState, [key]: state[key]}
|
|
14
|
-
: newState;
|
|
15
|
-
}, {});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return state;
|
|
19
|
-
}
|