@pixotope/react-context-store 0.5.0 → 0.7.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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-test.log +4 -4
- package/CHANGELOG.md +14 -0
- package/README.md +69 -0
- package/dist/index.cjs +42 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -2
- package/dist/index.d.ts +114 -2
- package/dist/index.js +42 -96
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/store.tsx +182 -23
- package/typedoc.json +3 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pixotope/react-context-store@0.
|
|
2
|
+
> @pixotope/react-context-store@0.7.0 build /home/circleci/project/packages/react-context-store
|
|
3
3
|
> tsup src/index.ts --format cjs,esm --dts --sourcemap --external react
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mCJS[39m Build start
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
12
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[
|
|
13
|
-
[32mCJS[39m ⚡️ Build success in
|
|
14
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
15
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
16
|
-
[32mESM[39m ⚡️ Build success in
|
|
11
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m6.98 KB[39m
|
|
12
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m21.74 KB[39m
|
|
13
|
+
[32mCJS[39m ⚡️ Build success in 40ms
|
|
14
|
+
[32mESM[39m [1mdist/index.js [22m[32m5.24 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m21.67 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 39ms
|
|
17
17
|
[34mDTS[39m Build start
|
|
18
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
+
[32mDTS[39m ⚡️ Build success in 3895ms
|
|
19
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m7.51 KB[39m
|
|
20
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.51 KB[39m
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
> @pixotope/react-context-store@0.
|
|
2
|
+
> @pixotope/react-context-store@0.7.0 test /home/circleci/project/packages/react-context-store
|
|
3
3
|
> vitest
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/home/circleci/project/packages/react-context-store[39m
|
|
7
7
|
|
|
8
|
-
[32m✓[39m src/store.test.tsx [2m([22m[2m14 tests[22m[2m)[22m[32m
|
|
8
|
+
[32m✓[39m src/store.test.tsx [2m([22m[2m14 tests[22m[2m)[22m[32m 131[2mms[22m[39m
|
|
9
9
|
|
|
10
10
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
11
11
|
[2m Tests [22m [1m[32m14 passed[39m[22m[90m (14)[39m
|
|
12
|
-
[2m Start at [22m
|
|
13
|
-
[2m Duration [22m 7.
|
|
12
|
+
[2m Start at [22m 13:44:03
|
|
13
|
+
[2m Duration [22m 7.50s[2m (transform 446ms, setup 380ms, collect 927ms, tests 131ms, environment 2.09s, prepare 1.29s)[22m
|
|
14
14
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @pixotope/react-context-store
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8a28444: Include detailed documentation for every package and setup `typedoc`
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 20804c6: @pixotope/react-context-store
|
|
14
|
+
|
|
15
|
+
- remove selector hook
|
|
16
|
+
|
|
3
17
|
## 0.5.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @pixotope/react-context-store
|
|
2
|
+
|
|
3
|
+
A factory for React context-based state stores, with selector hooks, typed actions, and `useSyncExternalStore` under the hood.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @pixotope/react-context-store
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package depends on `@pixotope/utils` internally (for shallow-equality comparisons). As a workspace dependency it is resolved automatically — no extra install step is needed.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { createContextStore, ActionablePayload } from "@pixotope/react-context-store";
|
|
17
|
+
|
|
18
|
+
const initialState = {
|
|
19
|
+
count: 0,
|
|
20
|
+
user: { name: "John", age: 20 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Create a store with its initial state and a map of named actions.
|
|
24
|
+
const store = createContextStore(initialState, {
|
|
25
|
+
increment: ({ set, get }) => {
|
|
26
|
+
set({ ...get(), count: get().count + 1 });
|
|
27
|
+
},
|
|
28
|
+
incrementBy: ({ set, get }, action: ActionablePayload<number>) => {
|
|
29
|
+
set({ ...get(), count: get().count + action.payload });
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function Counter() {
|
|
34
|
+
// Select only the state this component needs; it only re-renders when
|
|
35
|
+
// the selected value changes.
|
|
36
|
+
const { state: count } = store.useStore((state) => state.count);
|
|
37
|
+
const { increment, incrementBy } = store.useActions();
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<p>{count}</p>
|
|
42
|
+
<button onClick={() => increment()}>+1</button>
|
|
43
|
+
<button onClick={() => incrementBy(5)}>+5</button>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function App() {
|
|
49
|
+
// Wrap any component tree that uses the store's hooks in its Provider.
|
|
50
|
+
return (
|
|
51
|
+
<store.Provider>
|
|
52
|
+
<Counter />
|
|
53
|
+
</store.Provider>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API overview
|
|
59
|
+
|
|
60
|
+
- `createContextStore(initialState, actions?, options?)` — Factory function that creates a store, returning the object described below.
|
|
61
|
+
- `Provider` — React component that wraps a component tree and gives it access to the store.
|
|
62
|
+
- `useStore(selector?, compare?)` — Hook that reads (a selection of) the store state, re-rendering the component when the selected state changes.
|
|
63
|
+
- `useActions()` — Hook that returns the actions passed to `createContextStore`, bound to the current store.
|
|
64
|
+
- `useSetStore()` — Hook that returns a function to update the store state without subscribing to updates.
|
|
65
|
+
- `subscribe(selector, callback, compare?)` / `unsubscribe(callback)` — Subscribe to store updates from outside the React component tree.
|
|
66
|
+
- `ActionablePayload<Payload>` — Type used on an action's second parameter to give it a typed payload argument (see `incrementBy` above).
|
|
67
|
+
- `ContextOptions` — Options passed as the third argument to `createContextStore` (`global`, `compare`).
|
|
68
|
+
|
|
69
|
+
See the generated API reference (`pnpm typedoc`) for full type signatures.
|
package/dist/index.cjs
CHANGED
|
@@ -5,9 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
8
|
var __export = (target, all) => {
|
|
12
9
|
for (var name in all)
|
|
13
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -30,71 +27,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
27
|
));
|
|
31
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
29
|
|
|
33
|
-
// ../utils/dist/comparison/index.js
|
|
34
|
-
var require_comparison = __commonJS({
|
|
35
|
-
"../utils/dist/comparison/index.js"(exports2, module2) {
|
|
36
|
-
"use strict";
|
|
37
|
-
var __defProp2 = Object.defineProperty;
|
|
38
|
-
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
39
|
-
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
40
|
-
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
41
|
-
var __export2 = (target, all) => {
|
|
42
|
-
for (var name in all)
|
|
43
|
-
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
44
|
-
};
|
|
45
|
-
var __copyProps2 = (to, from, except, desc) => {
|
|
46
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
47
|
-
for (let key of __getOwnPropNames2(from))
|
|
48
|
-
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
49
|
-
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
50
|
-
}
|
|
51
|
-
return to;
|
|
52
|
-
};
|
|
53
|
-
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
54
|
-
var comparison_exports = {};
|
|
55
|
-
__export2(comparison_exports, {
|
|
56
|
-
shallowEqual: () => shallowEqual2
|
|
57
|
-
});
|
|
58
|
-
module2.exports = __toCommonJS2(comparison_exports);
|
|
59
|
-
function shallowEqual2(objA, objB) {
|
|
60
|
-
if (Object.is(objA, objB)) {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
if (objA instanceof Map && objB instanceof Map) {
|
|
67
|
-
if (objA.size !== objB.size) return false;
|
|
68
|
-
for (const [key, value] of objA) {
|
|
69
|
-
if (!Object.is(value, objB.get(key))) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
if (objA instanceof Set && objB instanceof Set) {
|
|
76
|
-
if (objA.size !== objB.size) return false;
|
|
77
|
-
for (const value of objA) {
|
|
78
|
-
if (!objB.has(value)) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
const keysA = Object.keys(objA);
|
|
85
|
-
if (keysA.length !== Object.keys(objB).length) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
for (let i = 0; i < keysA.length; i++) {
|
|
89
|
-
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
30
|
// src/index.ts
|
|
99
31
|
var index_exports = {};
|
|
100
32
|
__export(index_exports, {
|
|
@@ -104,7 +36,46 @@ module.exports = __toCommonJS(index_exports);
|
|
|
104
36
|
|
|
105
37
|
// src/store.tsx
|
|
106
38
|
var import_react = __toESM(require("react"), 1);
|
|
107
|
-
|
|
39
|
+
|
|
40
|
+
// ../utils/dist/chunk-5QIHMQPV.js
|
|
41
|
+
function shallowEqual(objA, objB) {
|
|
42
|
+
if (Object.is(objA, objB)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (objA instanceof Map && objB instanceof Map) {
|
|
49
|
+
if (objA.size !== objB.size) return false;
|
|
50
|
+
for (const [key, value] of objA) {
|
|
51
|
+
if (!Object.is(value, objB.get(key))) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (objA instanceof Set && objB instanceof Set) {
|
|
58
|
+
if (objA.size !== objB.size) return false;
|
|
59
|
+
for (const value of objA) {
|
|
60
|
+
if (!objB.has(value)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const keysA = Object.keys(objA);
|
|
67
|
+
if (keysA.length !== Object.keys(objB).length) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
71
|
+
if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/store.tsx
|
|
108
79
|
var LIB_NAME = "@pixotope/react-context-store";
|
|
109
80
|
function isFunction(value) {
|
|
110
81
|
return typeof value === "function";
|
|
@@ -147,7 +118,7 @@ function createContextStore(initialState, actions = {}, options = {}) {
|
|
|
147
118
|
);
|
|
148
119
|
}
|
|
149
120
|
const observers = /* @__PURE__ */ new Set();
|
|
150
|
-
function subscribeExternal(selector = (store) => store, callback, compare =
|
|
121
|
+
function subscribeExternal(selector = (store) => store, callback, compare = shallowEqual) {
|
|
151
122
|
let lastSelectedState;
|
|
152
123
|
const shouldSendUpdates = (newSelectedState) => {
|
|
153
124
|
if (lastSelectedState === void 0 || !compare(lastSelectedState, newSelectedState)) {
|
|
@@ -175,7 +146,7 @@ function createContextStore(initialState, actions = {}, options = {}) {
|
|
|
175
146
|
observers.forEach((callback) => callback(state));
|
|
176
147
|
}
|
|
177
148
|
};
|
|
178
|
-
function useStore(selector = (store) => store, compare = ((_a) => (_a = options.compare) != null ? _a :
|
|
149
|
+
function useStore(selector = (store) => store, compare = ((_a) => (_a = options.compare) != null ? _a : shallowEqual)()) {
|
|
179
150
|
const store = (0, import_react.useContext)(StoreContext);
|
|
180
151
|
const lastSelectedState = (0, import_react.useRef)(void 0);
|
|
181
152
|
if (!store) {
|
|
@@ -235,13 +206,11 @@ function createContextStore(initialState, actions = {}, options = {}) {
|
|
|
235
206
|
return actionProxy;
|
|
236
207
|
}
|
|
237
208
|
const useSetStore = () => useStore(() => false).set;
|
|
238
|
-
const useStoreSelector = () => useStore(() => false).selector;
|
|
239
209
|
return {
|
|
240
210
|
Provider,
|
|
241
211
|
useStore,
|
|
242
212
|
useSetStore,
|
|
243
213
|
useActions,
|
|
244
|
-
useStoreSelector,
|
|
245
214
|
subscribe: observable.subscribe,
|
|
246
215
|
unsubscribe: observable.unsubscribe
|
|
247
216
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../utils/dist/comparison/index.js","../src/index.ts","../src/store.tsx"],"sourcesContent":["\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/comparison/index.ts\nvar comparison_exports = {};\n__export(comparison_exports, {\n shallowEqual: () => shallowEqual\n});\nmodule.exports = __toCommonJS(comparison_exports);\n\n// src/comparison/shallowEqual.ts\nfunction shallowEqual(objA, objB) {\n if (Object.is(objA, objB)) {\n return true;\n }\n if (typeof objA !== \"object\" || objA === null || typeof objB !== \"object\" || objB === null) {\n return false;\n }\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false;\n for (const [key, value] of objA) {\n if (!Object.is(value, objB.get(key))) {\n return false;\n }\n }\n return true;\n }\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false;\n for (const value of objA) {\n if (!objB.has(value)) {\n return false;\n }\n }\n return true;\n }\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) {\n return false;\n }\n for (let i = 0; i < keysA.length; i++) {\n if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {\n return false;\n }\n }\n return true;\n}\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n shallowEqual\n});\n","export * from \"./store\";\n","import React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport { shallowEqual } from \"@pixotope/utils/comparison\";\n\nconst LIB_NAME = \"@pixotope/react-context-store\";\n\nexport type SelectorOptions<Selected> = (\n first: Selected,\n second: Selected\n) => boolean;\n\nexport type ContextOptions = {\n /**\n * If true, the store state will be preserved across mounts and un-mounts\n * of the Provider.\n * @default false\n */\n global?: boolean;\n /**\n * A function that will be used to compare the selected state.\n * @default shallowEqual\n */\n compare?: SelectorOptions<unknown>;\n};\n\nexport type SetterArgs<Store> = Store | ((prev: Store) => Store);\ntype ExtractActionKeys<T> = {\n [K in keyof T]: T[K] extends (\n stateProps: never,\n action: infer A\n ) => void | Promise<void>\n ? A extends ActionablePayload<infer Payload>\n ? Payload extends undefined\n ? () => void\n : (payload: Payload) => void\n : () => void\n : never;\n};\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\nexport type ActionablePayload<Payload = any> = {\n payload: Payload;\n};\n\ntype StateActionProps<Store> = {\n set: (value: SetterArgs<Store>) => void;\n get: () => Store;\n};\n\ntype Actions<Store> = {\n [key: string]: (\n stateProps: StateActionProps<Store>,\n action: ActionablePayload\n ) => void | Promise<void>;\n};\n\ntype UseStoreReturnType<Store, SelectorOutput> = {\n state: SelectorOutput;\n get: () => SelectorOutput;\n set: (value: SetterArgs<Store>) => void;\n selector: () => Store;\n};\n\ntype ContextReturnType<Store, A extends Actions<Store>> = {\n Provider: React.FC<React.PropsWithChildren>;\n useStore: <SelectorOutput = Store>(\n selector?: (store: Store) => SelectorOutput,\n options?: SelectorOptions<SelectorOutput>\n ) => UseStoreReturnType<Store, SelectorOutput>;\n useActions: () => Prettify<ExtractActionKeys<A>>;\n useSetStore: () => (value: SetterArgs<Store>) => void;\n useStoreSelector: () => () => Store;\n subscribe: <SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput,\n callback: (state: SelectorOutput) => void,\n options?: SelectorOptions<SelectorOutput>\n ) => () => void;\n unsubscribe: (callback: () => void) => void;\n};\n\nfunction isFunction(value: any): value is (prev: any) => any {\n return typeof value === \"function\";\n}\n\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n ...args: Extract<A, { payload: A }> extends { payload: infer Payload }\n ? [initialState: Store, actions?: A, options?: ContextOptions]\n : [initialState: Store, options?: ContextOptions]\n): ContextReturnType<Store, A>;\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n initialState: Store,\n actions: A = {} as A,\n options: ContextOptions = {}\n): ContextReturnType<Store, A> {\n let globalStore: Store | undefined = options.global\n ? initialState\n : undefined;\n\n function useStoreData({\n defaultSubscriber = [],\n }: {\n defaultSubscriber?: ((state: Store) => void)[];\n } = {}): {\n get: () => Store;\n set: (value: SetterArgs<Store>) => void;\n subscribe: (callback: (state: Store) => void) => () => void;\n } {\n const store = useRef<Store>(globalStore ?? initialState);\n\n const get = useCallback(() => store.current, []);\n\n const subscribers = useRef(\n new Set<(state: Store) => void>(defaultSubscriber)\n );\n\n const set = useCallback((value: SetterArgs<Store>) => {\n store.current = isFunction(value) ? value(store.current) : value;\n\n if (options.global) {\n globalStore = store.current;\n }\n\n subscribers.current.forEach((callback) => callback(store.current));\n }, []);\n\n const subscribe = useCallback((callback: (state: Store) => void) => {\n subscribers.current.add(callback);\n\n return () => subscribers.current.delete(callback);\n }, []);\n\n return {\n get,\n set,\n subscribe,\n };\n }\n\n type UseStoreDataReturnType = ReturnType<typeof useStoreData>;\n\n const StoreContext = createContext<UseStoreDataReturnType | null>(null);\n\n /**\n * This is the provider that will be used to wrap the react component tree\n * to provide the store to all the components in the tree.\n */\n function Provider({ children }: React.PropsWithChildren) {\n return (\n <StoreContext.Provider\n value={useStoreData({ defaultSubscriber: [observable.broadcast] })}\n >\n {children}\n </StoreContext.Provider>\n );\n }\n\n /**\n * This holds all the observables that are subscribed to the store\n * but not being actively used by any component. Helps to broadcast\n * store updates to all the observables that are interested in reacting\n * to state outside the react component tree.\n */\n const observers = new Set<(state: Store) => void>();\n\n function subscribeExternal<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n callback: (state: SelectorOutput) => void,\n compare: SelectorOptions<SelectorOutput> = shallowEqual\n ): () => void {\n let lastSelectedState: SelectorOutput | undefined;\n\n const shouldSendUpdates = (newSelectedState: SelectorOutput) => {\n if (\n lastSelectedState === undefined ||\n !compare(lastSelectedState, newSelectedState)\n ) {\n lastSelectedState = newSelectedState;\n\n return true;\n }\n\n return false;\n };\n\n const selectedCb = (store: Store) => {\n const selected = selector(store);\n const shouldSend = shouldSendUpdates(selected);\n\n if (shouldSend) {\n callback(selector(store));\n }\n };\n observers.add(selectedCb);\n\n return () => observers.delete(selectedCb);\n }\n\n /**\n * A simple observable that proxy store updates to all the subscribers\n * that are interested in reacting to state outside the react component tree.\n */\n const observable = {\n subscribe: subscribeExternal,\n unsubscribe: (callback: (state: Store) => void) => {\n observers.delete(callback);\n },\n broadcast: (state: Store) => {\n observers.forEach((callback) => callback(state));\n },\n } as const;\n\n /**\n * This is the hook that will be used to access the store from any component\n * in the react component tree.\n * @param selector A function that will be used to select the part of the store\n * that is needed by the component.\n * @param options Options to customize the behavior of the hook.\n * @returns An object with the selected state, a function to update the store\n * and a function to get the entire store.\n * @example\n * ```tsx\n * const { get, set } = useStore(store => store.user);\n * const { get, set } = useStore(store => store.user, { deepEqual: false });\n * const { get, set } = useStore(store => store.user, {\n * compare: (first, second) => first.id === second.id\n * });\n * ```\n */\n function useStore<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n compare: SelectorOptions<SelectorOutput> = options.compare ?? shallowEqual\n ): UseStoreReturnType<Store, SelectorOutput> {\n const store = useContext(StoreContext);\n const lastSelectedState = useRef<SelectorOutput | undefined>(undefined);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const state = useSyncExternalStore(\n store.subscribe,\n () => {\n const selectedState = selector(store.get());\n\n if (\n lastSelectedState.current === undefined ||\n !compare(lastSelectedState.current, selectedState)\n ) {\n lastSelectedState.current = selectedState;\n }\n\n return lastSelectedState.current;\n },\n () => selector(initialState)\n );\n\n return {\n state,\n get: () => state,\n set: store.set,\n selector: () => store.get(),\n };\n }\n\n function useActions() {\n const store = useContext(StoreContext);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const actionProxy = useMemo(\n () =>\n new Proxy(actions, {\n get: (target, prop) => {\n const action = target[prop as string];\n\n if (action) {\n return (args: Parameters<typeof action>[\"1\"]) => {\n action(\n {\n set: store.set,\n get: store.get,\n },\n {\n payload: args,\n }\n );\n };\n }\n },\n set: () => {\n throw new Error(`[${LIB_NAME}] Actions cannot be updated`);\n },\n }),\n []\n );\n\n return actionProxy as unknown as ExtractActionKeys<typeof actions>;\n }\n\n const useSetStore = () => useStore(() => false).set;\n const useStoreSelector = () => useStore(() => false).selector;\n\n return {\n Provider,\n useStore,\n useSetStore,\n useActions,\n useStoreSelector,\n subscribe: observable.subscribe,\n unsubscribe: observable.unsubscribe,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,sCAAAA,UAAAC,SAAA;AAAA;AACA,QAAIC,aAAY,OAAO;AACvB,QAAIC,oBAAmB,OAAO;AAC9B,QAAIC,qBAAoB,OAAO;AAC/B,QAAIC,gBAAe,OAAO,UAAU;AACpC,QAAIC,YAAW,CAAC,QAAQ,QAAQ;AAC9B,eAAS,QAAQ;AACf,QAAAJ,WAAU,QAAQ,MAAM,EAAE,KAAK,IAAI,IAAI,GAAG,YAAY,KAAK,CAAC;AAAA,IAChE;AACA,QAAIK,eAAc,CAAC,IAAI,MAAM,QAAQ,SAAS;AAC5C,UAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,SAAS,YAAY;AAClE,iBAAS,OAAOH,mBAAkB,IAAI;AACpC,cAAI,CAACC,cAAa,KAAK,IAAI,GAAG,KAAK,QAAQ;AACzC,YAAAH,WAAU,IAAI,KAAK,EAAE,KAAK,MAAM,KAAK,GAAG,GAAG,YAAY,EAAE,OAAOC,kBAAiB,MAAM,GAAG,MAAM,KAAK,WAAW,CAAC;AAAA,MACvH;AACA,aAAO;AAAA,IACT;AACA,QAAIK,gBAAe,CAAC,QAAQD,aAAYL,WAAU,CAAC,GAAG,cAAc,EAAE,OAAO,KAAK,CAAC,GAAG,GAAG;AAGzF,QAAI,qBAAqB,CAAC;AAC1B,IAAAI,UAAS,oBAAoB;AAAA,MAC3B,cAAc,MAAMG;AAAA,IACtB,CAAC;AACD,IAAAR,QAAO,UAAUO,cAAa,kBAAkB;AAGhD,aAASC,cAAa,MAAM,MAAM;AAChC,UAAI,OAAO,GAAG,MAAM,IAAI,GAAG;AACzB,eAAO;AAAA,MACT;AACA,UAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,OAAO,SAAS,YAAY,SAAS,MAAM;AAC1F,eAAO;AAAA,MACT;AACA,UAAI,gBAAgB,OAAO,gBAAgB,KAAK;AAC9C,YAAI,KAAK,SAAS,KAAK,KAAM,QAAO;AACpC,mBAAW,CAAC,KAAK,KAAK,KAAK,MAAM;AAC/B,cAAI,CAAC,OAAO,GAAG,OAAO,KAAK,IAAI,GAAG,CAAC,GAAG;AACpC,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO;AAAA,MACT;AACA,UAAI,gBAAgB,OAAO,gBAAgB,KAAK;AAC9C,YAAI,KAAK,SAAS,KAAK,KAAM,QAAO;AACpC,mBAAW,SAAS,MAAM;AACxB,cAAI,CAAC,KAAK,IAAI,KAAK,GAAG;AACpB,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO;AAAA,MACT;AACA,YAAM,QAAQ,OAAO,KAAK,IAAI;AAC9B,UAAI,MAAM,WAAW,OAAO,KAAK,IAAI,EAAE,QAAQ;AAC7C,eAAO;AAAA,MACT;AACA,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI,CAAC,OAAO,UAAU,eAAe,KAAK,MAAM,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,MAAM,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG;AACvG,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA;AAAA;;;AC9DA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAOO;AACP,wBAA6B;AAE7B,IAAM,WAAW;AA+EjB,SAAS,WAAW,OAAyC;AAC3D,SAAO,OAAO,UAAU;AAC1B;AAOO,SAAS,mBACd,cACA,UAAa,CAAC,GACd,UAA0B,CAAC,GACE;AAC7B,MAAI,cAAiC,QAAQ,SACzC,eACA;AAEJ,WAAS,aAAa;AAAA,IACpB,oBAAoB,CAAC;AAAA,EACvB,IAEI,CAAC,GAIH;AACA,UAAM,YAAQ,qBAAc,oCAAe,YAAY;AAEvD,UAAM,UAAM,0BAAY,MAAM,MAAM,SAAS,CAAC,CAAC;AAE/C,UAAM,kBAAc;AAAA,MAClB,IAAI,IAA4B,iBAAiB;AAAA,IACnD;AAEA,UAAM,UAAM,0BAAY,CAAC,UAA6B;AACpD,YAAM,UAAU,WAAW,KAAK,IAAI,MAAM,MAAM,OAAO,IAAI;AAE3D,UAAI,QAAQ,QAAQ;AAClB,sBAAc,MAAM;AAAA,MACtB;AAEA,kBAAY,QAAQ,QAAQ,CAAC,aAAa,SAAS,MAAM,OAAO,CAAC;AAAA,IACnE,GAAG,CAAC,CAAC;AAEL,UAAM,gBAAY,0BAAY,CAAC,aAAqC;AAClE,kBAAY,QAAQ,IAAI,QAAQ;AAEhC,aAAO,MAAM,YAAY,QAAQ,OAAO,QAAQ;AAAA,IAClD,GAAG,CAAC,CAAC;AAEL,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAIA,QAAM,mBAAe,4BAA6C,IAAI;AAMtE,WAAS,SAAS,EAAE,SAAS,GAA4B;AACvD,WACE,6BAAAC,QAAA;AAAA,MAAC,aAAa;AAAA,MAAb;AAAA,QACC,OAAO,aAAa,EAAE,mBAAmB,CAAC,WAAW,SAAS,EAAE,CAAC;AAAA;AAAA,MAEhE;AAAA,IACH;AAAA,EAEJ;AAQA,QAAM,YAAY,oBAAI,IAA4B;AAElD,WAAS,kBACP,WAA6C,CAAC,UAC5C,OACF,UACA,UAA2C,gCAC/B;AACZ,QAAI;AAEJ,UAAM,oBAAoB,CAAC,qBAAqC;AAC9D,UACE,sBAAsB,UACtB,CAAC,QAAQ,mBAAmB,gBAAgB,GAC5C;AACA,4BAAoB;AAEpB,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,CAAC,UAAiB;AACnC,YAAM,WAAW,SAAS,KAAK;AAC/B,YAAM,aAAa,kBAAkB,QAAQ;AAE7C,UAAI,YAAY;AACd,iBAAS,SAAS,KAAK,CAAC;AAAA,MAC1B;AAAA,IACF;AACA,cAAU,IAAI,UAAU;AAExB,WAAO,MAAM,UAAU,OAAO,UAAU;AAAA,EAC1C;AAMA,QAAM,aAAa;AAAA,IACjB,WAAW;AAAA,IACX,aAAa,CAAC,aAAqC;AACjD,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,IACA,WAAW,CAAC,UAAiB;AAC3B,gBAAU,QAAQ,CAAC,aAAa,SAAS,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AAmBA,WAAS,SACP,WAA6C,CAAC,UAC5C,OACF,WAA2C,sBAAQ,YAAR,YAAmB,mCACnB;AAC3C,UAAM,YAAQ,yBAAW,YAAY;AACrC,UAAM,wBAAoB,qBAAmC,MAAS;AAEtE,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,IAAI,QAAQ,8DAA8D,QAAQ;AAAA,MACpF;AAAA,IACF;AAEA,UAAM,YAAQ;AAAA,MACZ,MAAM;AAAA,MACN,MAAM;AACJ,cAAM,gBAAgB,SAAS,MAAM,IAAI,CAAC;AAE1C,YACE,kBAAkB,YAAY,UAC9B,CAAC,QAAQ,kBAAkB,SAAS,aAAa,GACjD;AACA,4BAAkB,UAAU;AAAA,QAC9B;AAEA,eAAO,kBAAkB;AAAA,MAC3B;AAAA,MACA,MAAM,SAAS,YAAY;AAAA,IAC7B;AAEA,WAAO;AAAA,MACL;AAAA,MACA,KAAK,MAAM;AAAA,MACX,KAAK,MAAM;AAAA,MACX,UAAU,MAAM,MAAM,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,WAAS,aAAa;AACpB,UAAM,YAAQ,yBAAW,YAAY;AAErC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,IAAI,QAAQ,8DAA8D,QAAQ;AAAA,MACpF;AAAA,IACF;AAEA,UAAM,kBAAc;AAAA,MAClB,MACE,IAAI,MAAM,SAAS;AAAA,QACjB,KAAK,CAAC,QAAQ,SAAS;AACrB,gBAAM,SAAS,OAAO,IAAc;AAEpC,cAAI,QAAQ;AACV,mBAAO,CAAC,SAAyC;AAC/C;AAAA,gBACE;AAAA,kBACE,KAAK,MAAM;AAAA,kBACX,KAAK,MAAM;AAAA,gBACb;AAAA,gBACA;AAAA,kBACE,SAAS;AAAA,gBACX;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA,KAAK,MAAM;AACT,gBAAM,IAAI,MAAM,IAAI,QAAQ,6BAA6B;AAAA,QAC3D;AAAA,MACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM,SAAS,MAAM,KAAK,EAAE;AAChD,QAAM,mBAAmB,MAAM,SAAS,MAAM,KAAK,EAAE;AAErD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,WAAW;AAAA,IACtB,aAAa,WAAW;AAAA,EAC1B;AACF;","names":["exports","module","__defProp","__getOwnPropDesc","__getOwnPropNames","__hasOwnProp","__export","__copyProps","__toCommonJS","shallowEqual","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/store.tsx","../../utils/dist/chunk-5QIHMQPV.js"],"sourcesContent":["export * from \"./store\";\n","import React, {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport { shallowEqual } from \"@pixotope/utils/comparison\";\n\nconst LIB_NAME = \"@pixotope/react-context-store\";\n\n/**\n * A function used to compare two versions of selected state, to decide\n * whether subscribers should be notified of a change.\n * @param first - The previously selected state.\n * @param second - The newly selected state.\n * @returns `true` if the two values should be treated as equal (no update triggered), `false` otherwise.\n */\nexport type SelectorOptions<Selected> = (\n first: Selected,\n second: Selected\n) => boolean;\n\n/**\n * Options that configure the behavior of a store created with {@link createContextStore}.\n */\nexport type ContextOptions = {\n /**\n * If true, the store state will be preserved across mounts and un-mounts\n * of the Provider.\n * @default false\n */\n global?: boolean;\n /**\n * A function that will be used to compare the selected state.\n * @default shallowEqual\n */\n compare?: SelectorOptions<unknown>;\n};\n\n/**\n * The value accepted by a store's `set` function: either the next state\n * directly, or an updater function that receives the previous state and\n * returns the next state.\n */\nexport type SetterArgs<Store> = Store | ((prev: Store) => Store);\n\n/** Maps an actions map (as passed to {@link createContextStore}) to the shape of the object returned by `useActions`. */\nexport type ExtractActionKeys<T> = {\n [K in keyof T]: T[K] extends (\n stateProps: never,\n action: infer A\n ) => void | Promise<void>\n ? A extends ActionablePayload<infer Payload>\n ? Payload extends undefined\n ? () => void\n : (payload: Payload) => void\n : () => void\n : never;\n};\n\n/** Flattens an intersection/mapped type into a single object type, for cleaner hover tooltips. */\nexport type Prettify<T> = {\n [K in keyof T]: T[K];\n} & {};\n\n/**\n * The action argument passed to an action handler, wrapping its payload.\n * Use this as the type of an action's second parameter to give the\n * corresponding {@link createContextStore} action a typed payload, which is\n * then reflected in the function returned by `useActions`.\n * @typeParam Payload - The type of the action's payload. Defaults to `any`.\n * @example\n * ```tsx\n * const store = createContextStore(initialState, {\n * incrementBy: ({ set, get }, action: ActionablePayload<number>) => {\n * set((prev) => ({ ...prev, count: get().count + action.payload }));\n * },\n * });\n * ```\n */\nexport type ActionablePayload<Payload = any> = {\n payload: Payload;\n};\n\n/** The state accessors passed as the first argument to every action handler. */\nexport type StateActionProps<Store> = {\n /** Updates the store state. */\n set: (value: SetterArgs<Store>) => void;\n /** Returns the current store state. */\n get: () => Store;\n};\n\n/**\n * A map of action names to action handlers, as passed to {@link createContextStore}.\n * Each handler receives {@link StateActionProps} for reading/writing the\n * store, plus an {@link ActionablePayload} carrying any payload passed by the caller.\n */\nexport type Actions<Store> = {\n [key: string]: (\n stateProps: StateActionProps<Store>,\n action: ActionablePayload\n ) => void | Promise<void>;\n};\n\n/** The object returned by a store's `useStore` hook. */\nexport type UseStoreReturnType<Store, SelectorOutput> = {\n /** The currently selected state. Reading this subscribes the component to updates. */\n state: SelectorOutput;\n /** Returns the currently selected state. Equivalent to reading `state`. */\n get: () => SelectorOutput;\n /** Updates the store state. */\n set: (value: SetterArgs<Store>) => void;\n /** Returns the entire store state, not just the selected slice. */\n selector: () => Store;\n};\n\n/**\n * The object returned by {@link createContextStore}: the `Provider`\n * component plus the hooks and functions used to read, update and\n * subscribe to the store.\n */\nexport type ContextReturnType<Store, A extends Actions<Store>> = {\n /** Wraps the part of the component tree that should have access to the store. */\n Provider: React.FC<React.PropsWithChildren>;\n /**\n * Reads (a selection of) the store state from the nearest `Provider`,\n * re-rendering the component whenever the selected state changes.\n * @param selector - Selects the part of the store the component needs. Defaults to the entire store.\n * @param options - A function used to compare the previous and next selected state. Defaults to the store's `compare` option, or shallow equality.\n * @returns The selected state, plus functions to read and update the store.\n */\n useStore: <SelectorOutput = Store>(\n selector?: (store: Store) => SelectorOutput,\n options?: SelectorOptions<SelectorOutput>\n ) => UseStoreReturnType<Store, SelectorOutput>;\n /**\n * Returns the actions passed to {@link createContextStore}, bound to the\n * current store. Actions declared with an {@link ActionablePayload}\n * parameter take a single payload argument; other actions take none.\n */\n useActions: () => Prettify<ExtractActionKeys<A>>;\n /**\n * Returns a function to update the store state from the nearest\n * `Provider`, without subscribing the component to store updates.\n */\n useSetStore: () => (value: SetterArgs<Store>) => void;\n /**\n * Subscribes to store updates from outside the React component tree.\n * @param selector - Selects the part of the store to watch.\n * @param callback - Called with the selected state whenever it changes.\n * @param options - A function used to compare the previous and next selected state. Defaults to shallow equality.\n * @returns A function to remove the subscription.\n */\n subscribe: <SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput,\n callback: (state: SelectorOutput) => void,\n options?: SelectorOptions<SelectorOutput>\n ) => () => void;\n /** Removes a subscription created with `subscribe`. */\n unsubscribe: (callback: () => void) => void;\n};\n\nfunction isFunction(value: any): value is (prev: any) => any {\n return typeof value === \"function\";\n}\n\n/**\n * Creates a React context-based store: a `Provider` component plus hooks to\n * read, update and subscribe to its state.\n *\n * Each call creates an independent store with its own React context, so the\n * `Provider` and hooks returned by one call must always be used together.\n * @typeParam Store - The shape of the store state.\n * @typeParam A - The map of named actions available to update the store.\n * @param args - Positional arguments: the `initialState` the store is initialized with, an\n * optional `actions` map of named action handlers used to update the store (see\n * {@link ActionablePayload} for actions that take a payload), and optional `options` to\n * customize the store's behavior.\n * @returns An object with a `Provider` component and hooks to read, update and subscribe to the store's state. See {@link ContextReturnType}.\n * @example\n * ```tsx\n * const store = createContextStore(\n * { count: 0 },\n * {\n * increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),\n * }\n * );\n *\n * function Counter() {\n * const { state: count } = store.useStore((state) => state.count);\n * const { increment } = store.useActions();\n *\n * return <button onClick={() => increment()}>{count}</button>;\n * }\n *\n * function App() {\n * return (\n * <store.Provider>\n * <Counter />\n * </store.Provider>\n * );\n * }\n * ```\n */\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n ...args: Extract<A, { payload: A }> extends { payload: infer Payload }\n ? [initialState: Store, actions?: A, options?: ContextOptions]\n : [initialState: Store, options?: ContextOptions]\n): ContextReturnType<Store, A>;\nexport function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(\n initialState: Store,\n actions: A = {} as A,\n options: ContextOptions = {}\n): ContextReturnType<Store, A> {\n let globalStore: Store | undefined = options.global\n ? initialState\n : undefined;\n\n function useStoreData({\n defaultSubscriber = [],\n }: {\n defaultSubscriber?: ((state: Store) => void)[];\n } = {}): {\n get: () => Store;\n set: (value: SetterArgs<Store>) => void;\n subscribe: (callback: (state: Store) => void) => () => void;\n } {\n const store = useRef<Store>(globalStore ?? initialState);\n\n const get = useCallback(() => store.current, []);\n\n const subscribers = useRef(\n new Set<(state: Store) => void>(defaultSubscriber)\n );\n\n const set = useCallback((value: SetterArgs<Store>) => {\n store.current = isFunction(value) ? value(store.current) : value;\n\n if (options.global) {\n globalStore = store.current;\n }\n\n subscribers.current.forEach((callback) => callback(store.current));\n }, []);\n\n const subscribe = useCallback((callback: (state: Store) => void) => {\n subscribers.current.add(callback);\n\n return () => subscribers.current.delete(callback);\n }, []);\n\n return {\n get,\n set,\n subscribe,\n };\n }\n\n type UseStoreDataReturnType = ReturnType<typeof useStoreData>;\n\n const StoreContext = createContext<UseStoreDataReturnType | null>(null);\n\n /**\n * Wraps the part of the component tree that should have access to the\n * store. Must wrap any component using `useStore`, `useActions` or `useSetStore`.\n * @remarks\n * If the store was created with `{ global: true }`, state changes made\n * while a `Provider` is mounted are preserved and restored the next time\n * a `Provider` for this store mounts.\n */\n function Provider({ children }: React.PropsWithChildren) {\n return (\n <StoreContext.Provider\n value={useStoreData({ defaultSubscriber: [observable.broadcast] })}\n >\n {children}\n </StoreContext.Provider>\n );\n }\n\n /**\n * This holds all the observables that are subscribed to the store\n * but not being actively used by any component. Helps to broadcast\n * store updates to all the observables that are interested in reacting\n * to state outside the react component tree.\n */\n const observers = new Set<(state: Store) => void>();\n\n /**\n * Subscribes to store updates from outside the React component tree.\n * @param selector - Selects the part of the store to watch. Defaults to the whole store.\n * @param callback - Called with the selected state whenever it changes.\n * @param compare - A function used to compare the previous and next selected state. Defaults to shallow equality.\n * @returns A function to remove the subscription.\n */\n function subscribeExternal<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n callback: (state: SelectorOutput) => void,\n compare: SelectorOptions<SelectorOutput> = shallowEqual\n ): () => void {\n let lastSelectedState: SelectorOutput | undefined;\n\n const shouldSendUpdates = (newSelectedState: SelectorOutput) => {\n if (\n lastSelectedState === undefined ||\n !compare(lastSelectedState, newSelectedState)\n ) {\n lastSelectedState = newSelectedState;\n\n return true;\n }\n\n return false;\n };\n\n const selectedCb = (store: Store) => {\n const selected = selector(store);\n const shouldSend = shouldSendUpdates(selected);\n\n if (shouldSend) {\n callback(selector(store));\n }\n };\n observers.add(selectedCb);\n\n return () => observers.delete(selectedCb);\n }\n\n /**\n * A simple observable that proxy store updates to all the subscribers\n * that are interested in reacting to state outside the react component tree.\n */\n const observable = {\n subscribe: subscribeExternal,\n unsubscribe: (callback: (state: Store) => void) => {\n observers.delete(callback);\n },\n broadcast: (state: Store) => {\n observers.forEach((callback) => callback(state));\n },\n } as const;\n\n /**\n * Reads (a selection of) the store state from the nearest `Provider`,\n * re-rendering the component whenever the selected state changes.\n * @param selector - A function that selects the part of the store that is\n * needed by the component. Defaults to the entire store.\n * @param compare - A function used to compare the previous and next\n * selected state; the component only re-renders when it returns `false`.\n * Defaults to the store's `compare` option (see {@link ContextOptions}),\n * or `shallowEqual` from `@pixotope/utils/comparison`.\n * @returns An object containing the selected `state`, a `set` function to\n * update the store, a `get` function equivalent to reading `state`, and a\n * `selector` function to read the entire store without selecting.\n * @example\n * ```tsx\n * const { state, set } = store.useStore((store) => store.user);\n * const { state, set } = store.useStore(\n * (store) => store.user,\n * (first, second) => first.id === second.id\n * );\n * ```\n */\n function useStore<SelectorOutput = Store>(\n selector: (store: Store) => SelectorOutput = (store) =>\n store as unknown as SelectorOutput,\n compare: SelectorOptions<SelectorOutput> = options.compare ?? shallowEqual\n ): UseStoreReturnType<Store, SelectorOutput> {\n const store = useContext(StoreContext);\n const lastSelectedState = useRef<SelectorOutput | undefined>(undefined);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const state = useSyncExternalStore(\n store.subscribe,\n () => {\n const selectedState = selector(store.get());\n\n if (\n lastSelectedState.current === undefined ||\n !compare(lastSelectedState.current, selectedState)\n ) {\n lastSelectedState.current = selectedState;\n }\n\n return lastSelectedState.current;\n },\n () => selector(initialState)\n );\n\n return {\n state,\n get: () => state,\n set: store.set,\n selector: () => store.get(),\n };\n }\n\n /**\n * Returns the actions passed to {@link createContextStore}, bound to the\n * current store from the nearest `Provider`. Calling an action does not\n * itself cause a re-render; only the state changes it makes (via `set`)\n * do, and only for components subscribed to the affected state.\n * @returns An object with one bound function per action. Actions declared\n * with an {@link ActionablePayload} parameter take a single payload\n * argument; other actions take no arguments.\n * @example\n * ```tsx\n * const store = createContextStore(initialState, {\n * increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),\n * incrementBy: ({ set, get }, action: ActionablePayload<number>) =>\n * set({ ...get(), count: get().count + action.payload }),\n * });\n *\n * function Counter() {\n * const { incrementBy } = store.useActions();\n * return <button onClick={() => incrementBy(5)}>+5</button>;\n * }\n * ```\n */\n function useActions() {\n const store = useContext(StoreContext);\n\n if (!store) {\n throw new Error(\n `[${LIB_NAME}] Store not found. Make sure the component is wrapped in a ${Provider} component.`\n );\n }\n\n const actionProxy = useMemo(\n () =>\n new Proxy(actions, {\n get: (target, prop) => {\n const action = target[prop as string];\n\n if (action) {\n return (args: Parameters<typeof action>[\"1\"]) => {\n action(\n {\n set: store.set,\n get: store.get,\n },\n {\n payload: args,\n }\n );\n };\n }\n },\n set: () => {\n throw new Error(`[${LIB_NAME}] Actions cannot be updated`);\n },\n }),\n []\n );\n\n return actionProxy as unknown as ExtractActionKeys<typeof actions>;\n }\n\n /**\n * Returns a function to update the store state from the nearest\n * `Provider`, without subscribing the component to store updates.\n * @returns A function that sets the store state, accepting either the\n * next state directly or an updater function that receives the previous state.\n * @example\n * ```tsx\n * const setStore = store.useSetStore();\n * setStore((prev) => ({ ...prev, count: prev.count + 1 }));\n * ```\n */\n const useSetStore = () => useStore(() => false).set;\n\n return {\n Provider,\n useStore,\n useSetStore,\n useActions,\n subscribe: observable.subscribe,\n unsubscribe: observable.unsubscribe,\n };\n}\n","// src/comparison/shallowEqual.ts\nfunction shallowEqual(objA, objB) {\n if (Object.is(objA, objB)) {\n return true;\n }\n if (typeof objA !== \"object\" || objA === null || typeof objB !== \"object\" || objB === null) {\n return false;\n }\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false;\n for (const [key, value] of objA) {\n if (!Object.is(value, objB.get(key))) {\n return false;\n }\n }\n return true;\n }\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false;\n for (const value of objA) {\n if (!objB.has(value)) {\n return false;\n }\n }\n return true;\n }\n const keysA = Object.keys(objA);\n if (keysA.length !== Object.keys(objB).length) {\n return false;\n }\n for (let i = 0; i < keysA.length; i++) {\n if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) {\n return false;\n }\n }\n return true;\n}\n\nexport {\n shallowEqual\n};\n"],"mappings":"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","names":["React"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A function used to compare two versions of selected state, to decide
|
|
5
|
+
* whether subscribers should be notified of a change.
|
|
6
|
+
* @param first - The previously selected state.
|
|
7
|
+
* @param second - The newly selected state.
|
|
8
|
+
* @returns `true` if the two values should be treated as equal (no update triggered), `false` otherwise.
|
|
9
|
+
*/
|
|
3
10
|
type SelectorOptions<Selected> = (first: Selected, second: Selected) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Options that configure the behavior of a store created with {@link createContextStore}.
|
|
13
|
+
*/
|
|
4
14
|
type ContextOptions = {
|
|
5
15
|
/**
|
|
6
16
|
* If true, the store state will be preserved across mounts and un-mounts
|
|
@@ -14,42 +24,144 @@ type ContextOptions = {
|
|
|
14
24
|
*/
|
|
15
25
|
compare?: SelectorOptions<unknown>;
|
|
16
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* The value accepted by a store's `set` function: either the next state
|
|
29
|
+
* directly, or an updater function that receives the previous state and
|
|
30
|
+
* returns the next state.
|
|
31
|
+
*/
|
|
17
32
|
type SetterArgs<Store> = Store | ((prev: Store) => Store);
|
|
33
|
+
/** Maps an actions map (as passed to {@link createContextStore}) to the shape of the object returned by `useActions`. */
|
|
18
34
|
type ExtractActionKeys<T> = {
|
|
19
35
|
[K in keyof T]: T[K] extends (stateProps: never, action: infer A) => void | Promise<void> ? A extends ActionablePayload<infer Payload> ? Payload extends undefined ? () => void : (payload: Payload) => void : () => void : never;
|
|
20
36
|
};
|
|
37
|
+
/** Flattens an intersection/mapped type into a single object type, for cleaner hover tooltips. */
|
|
21
38
|
type Prettify<T> = {
|
|
22
39
|
[K in keyof T]: T[K];
|
|
23
40
|
} & {};
|
|
41
|
+
/**
|
|
42
|
+
* The action argument passed to an action handler, wrapping its payload.
|
|
43
|
+
* Use this as the type of an action's second parameter to give the
|
|
44
|
+
* corresponding {@link createContextStore} action a typed payload, which is
|
|
45
|
+
* then reflected in the function returned by `useActions`.
|
|
46
|
+
* @typeParam Payload - The type of the action's payload. Defaults to `any`.
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* const store = createContextStore(initialState, {
|
|
50
|
+
* incrementBy: ({ set, get }, action: ActionablePayload<number>) => {
|
|
51
|
+
* set((prev) => ({ ...prev, count: get().count + action.payload }));
|
|
52
|
+
* },
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
24
56
|
type ActionablePayload<Payload = any> = {
|
|
25
57
|
payload: Payload;
|
|
26
58
|
};
|
|
59
|
+
/** The state accessors passed as the first argument to every action handler. */
|
|
27
60
|
type StateActionProps<Store> = {
|
|
61
|
+
/** Updates the store state. */
|
|
28
62
|
set: (value: SetterArgs<Store>) => void;
|
|
63
|
+
/** Returns the current store state. */
|
|
29
64
|
get: () => Store;
|
|
30
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* A map of action names to action handlers, as passed to {@link createContextStore}.
|
|
68
|
+
* Each handler receives {@link StateActionProps} for reading/writing the
|
|
69
|
+
* store, plus an {@link ActionablePayload} carrying any payload passed by the caller.
|
|
70
|
+
*/
|
|
31
71
|
type Actions<Store> = {
|
|
32
72
|
[key: string]: (stateProps: StateActionProps<Store>, action: ActionablePayload) => void | Promise<void>;
|
|
33
73
|
};
|
|
74
|
+
/** The object returned by a store's `useStore` hook. */
|
|
34
75
|
type UseStoreReturnType<Store, SelectorOutput> = {
|
|
76
|
+
/** The currently selected state. Reading this subscribes the component to updates. */
|
|
35
77
|
state: SelectorOutput;
|
|
78
|
+
/** Returns the currently selected state. Equivalent to reading `state`. */
|
|
36
79
|
get: () => SelectorOutput;
|
|
80
|
+
/** Updates the store state. */
|
|
37
81
|
set: (value: SetterArgs<Store>) => void;
|
|
82
|
+
/** Returns the entire store state, not just the selected slice. */
|
|
38
83
|
selector: () => Store;
|
|
39
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* The object returned by {@link createContextStore}: the `Provider`
|
|
87
|
+
* component plus the hooks and functions used to read, update and
|
|
88
|
+
* subscribe to the store.
|
|
89
|
+
*/
|
|
40
90
|
type ContextReturnType<Store, A extends Actions<Store>> = {
|
|
91
|
+
/** Wraps the part of the component tree that should have access to the store. */
|
|
41
92
|
Provider: React.FC<React.PropsWithChildren>;
|
|
93
|
+
/**
|
|
94
|
+
* Reads (a selection of) the store state from the nearest `Provider`,
|
|
95
|
+
* re-rendering the component whenever the selected state changes.
|
|
96
|
+
* @param selector - Selects the part of the store the component needs. Defaults to the entire store.
|
|
97
|
+
* @param options - A function used to compare the previous and next selected state. Defaults to the store's `compare` option, or shallow equality.
|
|
98
|
+
* @returns The selected state, plus functions to read and update the store.
|
|
99
|
+
*/
|
|
42
100
|
useStore: <SelectorOutput = Store>(selector?: (store: Store) => SelectorOutput, options?: SelectorOptions<SelectorOutput>) => UseStoreReturnType<Store, SelectorOutput>;
|
|
101
|
+
/**
|
|
102
|
+
* Returns the actions passed to {@link createContextStore}, bound to the
|
|
103
|
+
* current store. Actions declared with an {@link ActionablePayload}
|
|
104
|
+
* parameter take a single payload argument; other actions take none.
|
|
105
|
+
*/
|
|
43
106
|
useActions: () => Prettify<ExtractActionKeys<A>>;
|
|
107
|
+
/**
|
|
108
|
+
* Returns a function to update the store state from the nearest
|
|
109
|
+
* `Provider`, without subscribing the component to store updates.
|
|
110
|
+
*/
|
|
44
111
|
useSetStore: () => (value: SetterArgs<Store>) => void;
|
|
45
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Subscribes to store updates from outside the React component tree.
|
|
114
|
+
* @param selector - Selects the part of the store to watch.
|
|
115
|
+
* @param callback - Called with the selected state whenever it changes.
|
|
116
|
+
* @param options - A function used to compare the previous and next selected state. Defaults to shallow equality.
|
|
117
|
+
* @returns A function to remove the subscription.
|
|
118
|
+
*/
|
|
46
119
|
subscribe: <SelectorOutput = Store>(selector: (store: Store) => SelectorOutput, callback: (state: SelectorOutput) => void, options?: SelectorOptions<SelectorOutput>) => () => void;
|
|
120
|
+
/** Removes a subscription created with `subscribe`. */
|
|
47
121
|
unsubscribe: (callback: () => void) => void;
|
|
48
122
|
};
|
|
123
|
+
/**
|
|
124
|
+
* Creates a React context-based store: a `Provider` component plus hooks to
|
|
125
|
+
* read, update and subscribe to its state.
|
|
126
|
+
*
|
|
127
|
+
* Each call creates an independent store with its own React context, so the
|
|
128
|
+
* `Provider` and hooks returned by one call must always be used together.
|
|
129
|
+
* @typeParam Store - The shape of the store state.
|
|
130
|
+
* @typeParam A - The map of named actions available to update the store.
|
|
131
|
+
* @param args - Positional arguments: the `initialState` the store is initialized with, an
|
|
132
|
+
* optional `actions` map of named action handlers used to update the store (see
|
|
133
|
+
* {@link ActionablePayload} for actions that take a payload), and optional `options` to
|
|
134
|
+
* customize the store's behavior.
|
|
135
|
+
* @returns An object with a `Provider` component and hooks to read, update and subscribe to the store's state. See {@link ContextReturnType}.
|
|
136
|
+
* @example
|
|
137
|
+
* ```tsx
|
|
138
|
+
* const store = createContextStore(
|
|
139
|
+
* { count: 0 },
|
|
140
|
+
* {
|
|
141
|
+
* increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),
|
|
142
|
+
* }
|
|
143
|
+
* );
|
|
144
|
+
*
|
|
145
|
+
* function Counter() {
|
|
146
|
+
* const { state: count } = store.useStore((state) => state.count);
|
|
147
|
+
* const { increment } = store.useActions();
|
|
148
|
+
*
|
|
149
|
+
* return <button onClick={() => increment()}>{count}</button>;
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* function App() {
|
|
153
|
+
* return (
|
|
154
|
+
* <store.Provider>
|
|
155
|
+
* <Counter />
|
|
156
|
+
* </store.Provider>
|
|
157
|
+
* );
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
49
161
|
declare function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(...args: Extract<A, {
|
|
50
162
|
payload: A;
|
|
51
163
|
}> extends {
|
|
52
164
|
payload: infer Payload;
|
|
53
165
|
} ? [initialState: Store, actions?: A, options?: ContextOptions] : [initialState: Store, options?: ContextOptions]): ContextReturnType<Store, A>;
|
|
54
166
|
|
|
55
|
-
export { type ActionablePayload, type ContextOptions, type SelectorOptions, type SetterArgs, createContextStore };
|
|
167
|
+
export { type ActionablePayload, type Actions, type ContextOptions, type ContextReturnType, type ExtractActionKeys, type Prettify, type SelectorOptions, type SetterArgs, type StateActionProps, type UseStoreReturnType, createContextStore };
|