@khanglvm/react 1.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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/helpers/createCacheStorage.d.ts +5 -0
- package/dist/helpers/createCacheStorage.js +35 -0
- package/dist/helpers/deepClone.d.ts +1 -0
- package/dist/helpers/deepClone.js +53 -0
- package/dist/helpers/deepEqual.d.ts +8 -0
- package/dist/helpers/deepEqual.js +63 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.js +18 -0
- package/dist/helpers/react.d.ts +26 -0
- package/dist/helpers/react.js +44 -0
- package/dist/helpers/types.d.ts +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/libs/createContextState.d.ts +89 -0
- package/dist/libs/createContextState.js +231 -0
- package/dist/libs/createEventMethod.d.ts +24 -0
- package/dist/libs/createEventMethod.js +51 -0
- package/dist/libs/createStateManager.d.ts +48 -0
- package/dist/libs/createStateManager.js +16 -0
- package/dist/libs/createTranslator.d.ts +120 -0
- package/dist/libs/createTranslator.js +78 -0
- package/docs/helpers.md +127 -0
- package/docs/state.md +169 -0
- package/docs/utilities.md +130 -0
- package/package.json +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Khang Le
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# @khanglvm/react
|
|
2
|
+
|
|
3
|
+
React utilities for shared state, translation, and browser events, with helpers for component props and children.
|
|
4
|
+
|
|
5
|
+
I built these to reuse the setup that kept appearing in my React work. `createContextState` is the main state utility: one factory gives you typed selector hooks, Immer draft updates, and a revert function for each update. The other tools work independently.
|
|
6
|
+
|
|
7
|
+
## Choose a tool
|
|
8
|
+
|
|
9
|
+
| When you need to… | Start here | What it adds |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Share editable state across components | [createContextState](https://github.com/khanglvm/react/blob/master/docs/state.md) | Selectors, draft updates, and per-update rollback in one API |
|
|
12
|
+
| Keep translation keys and language variants together | [defineLocale / createTranslator](https://github.com/khanglvm/react/blob/master/docs/utilities.md#translation) | Typed dictionaries, namespace hooks, and text substitution |
|
|
13
|
+
| Ask mounted components for an async response | [createEventMethod](https://github.com/khanglvm/react/blob/master/docs/utilities.md#browser-events) | Typed event data and collected listener results |
|
|
14
|
+
| Reuse equality, caching, or React child checks | [Helpers](https://github.com/khanglvm/react/blob/master/docs/helpers.md) | Small functions with explicit behavior and limits |
|
|
15
|
+
|
|
16
|
+
For state owned by one component, `useState` is usually enough. These utilities help when several components share the same work. Read the guides for examples and tradeoffs before choosing one.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Use React 18 or 19. The package ships ES modules and TypeScript declarations.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @khanglvm/react
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The [npm package](https://www.npmjs.com/package/@khanglvm/react) includes the
|
|
27
|
+
state utility, translations, browser events, and component helpers. Import
|
|
28
|
+
what you need; the utilities work independently.
|
|
29
|
+
|
|
30
|
+
## Shared state in one file
|
|
31
|
+
|
|
32
|
+
Put this in `App.tsx` in a React TypeScript app. In Next.js App Router, add `'use client'` as the first line.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { createContextState } from '@khanglvm/react'
|
|
36
|
+
|
|
37
|
+
type CounterState = { count: number }
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
Provider,
|
|
41
|
+
useContextStateValue: useCounterValue,
|
|
42
|
+
useSetContextState: useSetCounter,
|
|
43
|
+
} = createContextState<CounterState>()
|
|
44
|
+
|
|
45
|
+
function CounterValue() {
|
|
46
|
+
const count = useCounterValue(state => state.count)
|
|
47
|
+
return <p>Count: {count}</p>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function IncrementButton() {
|
|
51
|
+
const setState = useSetCounter()
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<button onClick={() => setState(draft => { draft.count += 1 })}>
|
|
55
|
+
Add one
|
|
56
|
+
</button>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function App() {
|
|
61
|
+
return (
|
|
62
|
+
<Provider initialState={{ count: 0 }}>
|
|
63
|
+
<CounterValue />
|
|
64
|
+
<IncrementButton />
|
|
65
|
+
</Provider>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Create the state utility outside component renders. Initialize every field your selectors read, keep selectors pure, and use the setter to change state. A hook outside its provider cannot update the store.
|
|
71
|
+
|
|
72
|
+
## More examples
|
|
73
|
+
|
|
74
|
+
- [State API, reverting updates, and binding props](https://github.com/khanglvm/react/blob/master/docs/state.md)
|
|
75
|
+
- [Translation and browser events](https://github.com/khanglvm/react/blob/master/docs/utilities.md)
|
|
76
|
+
- [Every helper and exported type](https://github.com/khanglvm/react/blob/master/docs/helpers.md)
|
|
77
|
+
- [Implementation](https://github.com/khanglvm/react/blob/master/src/libs/createContextState.tsx)
|
|
78
|
+
|
|
79
|
+
`createStateManager` remains available for older hook names. Use `createContextState` in new code.
|
|
80
|
+
|
|
81
|
+
## Using a coding agent
|
|
82
|
+
|
|
83
|
+
Give your agent the repository URL and a small task:
|
|
84
|
+
|
|
85
|
+
> Read the README and the guide for the relevant utility in https://github.com/khanglvm/react. Explain whether it fits this feature before adding it. Install `@khanglvm/react` from npm, preserve the documented API, and check the example against my app's React version. For shared state, use typed selectors and Immer draft updates under the matching provider.
|
|
86
|
+
|
|
87
|
+
By [Khang Le](https://khangle.dev). MIT licensed.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { deepEqual as l } from "./deepEqual.js";
|
|
2
|
+
const M = (c) => {
|
|
3
|
+
const n = /* @__PURE__ */ new Map();
|
|
4
|
+
function o(t, e) {
|
|
5
|
+
return e.length === 0 ? t : (t.has(e[0]) || t.set(e[0], /* @__PURE__ */ new Map()), o(t.get(e[0]), e.slice(1)));
|
|
6
|
+
}
|
|
7
|
+
function f(t, e, ...g) {
|
|
8
|
+
const r = o(n, g);
|
|
9
|
+
if (r.get(e) === void 0)
|
|
10
|
+
return r.set(e, t), t;
|
|
11
|
+
const i = r.get(e);
|
|
12
|
+
return l(i, t) ? i : (r.set(e, t), t);
|
|
13
|
+
}
|
|
14
|
+
function u(t, e) {
|
|
15
|
+
if (e.length !== 0) {
|
|
16
|
+
if (e.length === 1) {
|
|
17
|
+
t.delete(e[0]);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!(t.get(e[0]) === void 0 || !(t.get(e[0]) instanceof Map)))
|
|
21
|
+
return u(t.get(e[0]), e.slice(1));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function a(...t) {
|
|
25
|
+
u(n, t);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
cache: typeof c < "u" ? (t, ...e) => f(t, c, ...e) : f,
|
|
29
|
+
clear: c ? () => a(c) : a,
|
|
30
|
+
uid: () => Symbol()
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export {
|
|
34
|
+
M as createCacheStorage
|
|
35
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepClone<T>(value: T, cache?: WeakMap<object, unknown>): T;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function f(t, n = /* @__PURE__ */ new WeakMap()) {
|
|
2
|
+
const e = Object.prototype.toString.call(t);
|
|
3
|
+
if (t === null || typeof t != "object")
|
|
4
|
+
return t;
|
|
5
|
+
if (n.has(t))
|
|
6
|
+
return n.get(t);
|
|
7
|
+
if (t instanceof Date)
|
|
8
|
+
return new Date(t.getTime());
|
|
9
|
+
if (t instanceof RegExp)
|
|
10
|
+
return new RegExp(t.source, t.flags);
|
|
11
|
+
if (t instanceof AbortSignal || t instanceof FormData)
|
|
12
|
+
return t;
|
|
13
|
+
if (t instanceof Map) {
|
|
14
|
+
const r = /* @__PURE__ */ new Map();
|
|
15
|
+
return n.set(t, r), t.forEach((o, i) => {
|
|
16
|
+
r.set(f(i, n), f(o, n));
|
|
17
|
+
}), r;
|
|
18
|
+
}
|
|
19
|
+
if (t instanceof Set) {
|
|
20
|
+
const r = /* @__PURE__ */ new Set();
|
|
21
|
+
return n.set(t, r), t.forEach((o) => {
|
|
22
|
+
r.add(f(o, n));
|
|
23
|
+
}), r;
|
|
24
|
+
}
|
|
25
|
+
if (t instanceof ArrayBuffer)
|
|
26
|
+
return t.slice(0);
|
|
27
|
+
if (ArrayBuffer.isView(t)) {
|
|
28
|
+
if (!(t.buffer instanceof ArrayBuffer))
|
|
29
|
+
throw new Error(`Unsupported type: ${e}`);
|
|
30
|
+
const r = new t.constructor(
|
|
31
|
+
t.buffer.slice(0)
|
|
32
|
+
);
|
|
33
|
+
return n.set(t, r), r;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(t)) {
|
|
36
|
+
const r = [];
|
|
37
|
+
n.set(t, r);
|
|
38
|
+
for (const o of t)
|
|
39
|
+
r.push(f(o, n));
|
|
40
|
+
return r;
|
|
41
|
+
}
|
|
42
|
+
if (Object.prototype.toString.call(t) === "[object Object]") {
|
|
43
|
+
const r = {};
|
|
44
|
+
n.set(t, r);
|
|
45
|
+
for (const o in t)
|
|
46
|
+
Object.prototype.hasOwnProperty.call(t, o) && (r[o] = f(t[o], n));
|
|
47
|
+
return r;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unsupported type: ${e}`);
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
f as deepClone
|
|
53
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep equality comparison with optimizations
|
|
3
|
+
*
|
|
4
|
+
* Incorporates performance optimizations from fast-deep-equal
|
|
5
|
+
* Credit: https://github.com/epoberezkin/fast-deep-equal
|
|
6
|
+
* Author: Evgeny Poberezkin
|
|
7
|
+
*/
|
|
8
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const p = Array.isArray, y = Object.keys, h = Object.prototype.hasOwnProperty;
|
|
2
|
+
function o(t, e) {
|
|
3
|
+
if (t === e)
|
|
4
|
+
return !0;
|
|
5
|
+
if (t && e && typeof t == "object" && typeof e == "object") {
|
|
6
|
+
const i = p(t), c = p(e);
|
|
7
|
+
if (i && c) {
|
|
8
|
+
const r = t.length;
|
|
9
|
+
if (r !== e.length)
|
|
10
|
+
return !1;
|
|
11
|
+
for (let n = r; n-- !== 0; )
|
|
12
|
+
if (!o(t[n], e[n]))
|
|
13
|
+
return !1;
|
|
14
|
+
return !0;
|
|
15
|
+
}
|
|
16
|
+
if (i !== c)
|
|
17
|
+
return !1;
|
|
18
|
+
const u = t instanceof Date, l = e instanceof Date;
|
|
19
|
+
if (u !== l)
|
|
20
|
+
return !1;
|
|
21
|
+
if (u && l)
|
|
22
|
+
return t.getTime() === e.getTime();
|
|
23
|
+
const a = t instanceof RegExp, g = e instanceof RegExp;
|
|
24
|
+
if (a !== g)
|
|
25
|
+
return !1;
|
|
26
|
+
if (a && g)
|
|
27
|
+
return t.toString() === e.toString();
|
|
28
|
+
if (t.constructor !== e.constructor)
|
|
29
|
+
return !1;
|
|
30
|
+
if (t instanceof Map && e instanceof Map) {
|
|
31
|
+
if (t.size !== e.size)
|
|
32
|
+
return !1;
|
|
33
|
+
for (const [r, n] of t)
|
|
34
|
+
if (!e.has(r) || !o(n, e.get(r)))
|
|
35
|
+
return !1;
|
|
36
|
+
return !0;
|
|
37
|
+
}
|
|
38
|
+
if (t instanceof Set && e instanceof Set) {
|
|
39
|
+
if (t.size !== e.size)
|
|
40
|
+
return !1;
|
|
41
|
+
for (const r of t)
|
|
42
|
+
if (!e.has(r))
|
|
43
|
+
return !1;
|
|
44
|
+
return !0;
|
|
45
|
+
}
|
|
46
|
+
const f = y(t), s = f.length;
|
|
47
|
+
if (s !== y(e).length)
|
|
48
|
+
return !1;
|
|
49
|
+
for (let r = s; r-- !== 0; )
|
|
50
|
+
if (!h.call(e, f[r]))
|
|
51
|
+
return !1;
|
|
52
|
+
for (let r = s; r-- !== 0; ) {
|
|
53
|
+
const n = f[r];
|
|
54
|
+
if (!(n === "_owner" && t.$$typeof) && !o(t[n], e[n]))
|
|
55
|
+
return !1;
|
|
56
|
+
}
|
|
57
|
+
return !0;
|
|
58
|
+
}
|
|
59
|
+
return t !== t && e !== e;
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
o as deepEqual
|
|
63
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { deepClone as o } from "./deepClone.js";
|
|
2
|
+
import { deepEqual as l } from "./deepEqual.js";
|
|
3
|
+
import { createCacheStorage as m } from "./createCacheStorage.js";
|
|
4
|
+
import { compareAllPropsForMemo as p, comparePropsForMemo as a, filterComponentFromChildren as C, isEmptyChildren as d, isHTMLElementChildren as h, isIterableChildren as s, isPrimitiveChildren as c, isReactFragmentChildren as f, isSingleReactElementChildren as x } from "./react.js";
|
|
5
|
+
export {
|
|
6
|
+
p as compareAllPropsForMemo,
|
|
7
|
+
a as comparePropsForMemo,
|
|
8
|
+
m as createCacheStorage,
|
|
9
|
+
o as deepClone,
|
|
10
|
+
l as deepEqual,
|
|
11
|
+
C as filterComponentFromChildren,
|
|
12
|
+
d as isEmptyChildren,
|
|
13
|
+
h as isHTMLElementChildren,
|
|
14
|
+
s as isIterableChildren,
|
|
15
|
+
c as isPrimitiveChildren,
|
|
16
|
+
f as isReactFragmentChildren,
|
|
17
|
+
x as isSingleReactElementChildren
|
|
18
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { JSXElementConstructor, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Use comparePropsForMemo/ compareAllPropsForMemo function to quickly compare prop(s) for React.memo
|
|
5
|
+
* Usage example:
|
|
6
|
+
* memo(Component, compareAllPropsForMemo) // compare all props
|
|
7
|
+
* memo(Component, comparePropsForMemo('title')) // compare only 'title' prop
|
|
8
|
+
* memo(Component, comparePropsForMemo(['title', 'content'])) // compare 'title' and 'content' props
|
|
9
|
+
* */
|
|
10
|
+
type TPropsAreEqualsCallback<P> = (prevProps: P, nextProps: P) => boolean;
|
|
11
|
+
export declare function compareAllPropsForMemo<P extends object>(arg1: P, arg2: P): boolean;
|
|
12
|
+
export declare function comparePropsForMemo<P extends object>(arg: Array<keyof P> | keyof P): TPropsAreEqualsCallback<P>;
|
|
13
|
+
export declare function isIterableChildren<T>(children: T): children is TReactFragment<T>;
|
|
14
|
+
export declare function isSingleReactElementChildren<T>(child: T): child is TReactElement<T>;
|
|
15
|
+
export declare function isReactFragmentChildren<T>(child: T): child is TReactElement<T>;
|
|
16
|
+
export declare function isHTMLElementChildren<T>(child: T): child is TReactElement<T>;
|
|
17
|
+
export declare function isEmptyChildren<T>(children: T): children is TEmptyChildren<T>;
|
|
18
|
+
export declare function isPrimitiveChildren<T>(children: T): children is TPrimitiveChildren<T>;
|
|
19
|
+
type TEmpty = undefined | null | '';
|
|
20
|
+
type TPrimitive = string | number | boolean;
|
|
21
|
+
type TPrimitiveChildren<T> = T extends TPrimitive ? T : never;
|
|
22
|
+
type TEmptyChildren<T> = T extends TEmpty ? T : never;
|
|
23
|
+
type TReactElement<T> = T extends TPrimitiveChildren<T> ? never : T extends TEmptyChildren<T> ? never : T;
|
|
24
|
+
type TReactFragment<T> = T extends Iterable<ReactNode> ? T : never;
|
|
25
|
+
export declare const filterComponentFromChildren: <T extends JSXElementConstructor<any>>(children: ReactNode | ReactNode[], Component: T) => import('react').ReactElement<unknown, string | JSXElementConstructor<any>>[];
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Children as i, isValidElement as f } from "react";
|
|
2
|
+
import { deepEqual as m } from "./deepEqual.js";
|
|
3
|
+
function l(t) {
|
|
4
|
+
return t.length === 1;
|
|
5
|
+
}
|
|
6
|
+
function c(t, e) {
|
|
7
|
+
return o(t, e);
|
|
8
|
+
}
|
|
9
|
+
function s(...t) {
|
|
10
|
+
return l(t) ? (e, n) => o(e, n, Array.isArray(t[0]) ? t[0] : [t[0]]) : o(t[0], t[1]);
|
|
11
|
+
}
|
|
12
|
+
function o(t, e, n) {
|
|
13
|
+
return !(n || Object.keys(t).filter((r) => Object.prototype.hasOwnProperty.call(t, r))).some((r) => !m(t[r], e[r]));
|
|
14
|
+
}
|
|
15
|
+
function C(t) {
|
|
16
|
+
return i.count(t) > 1;
|
|
17
|
+
}
|
|
18
|
+
function u(t) {
|
|
19
|
+
return i.count(t) === 1 && typeof t != "string" && typeof t != "number" && typeof t != "boolean" && t !== void 0 && t !== null;
|
|
20
|
+
}
|
|
21
|
+
function p(t) {
|
|
22
|
+
return u(t) && t.type !== void 0 && t.type.toString() === Symbol("react.fragment").toString();
|
|
23
|
+
}
|
|
24
|
+
function b(t) {
|
|
25
|
+
return u(t) && !p(t);
|
|
26
|
+
}
|
|
27
|
+
function g(t) {
|
|
28
|
+
return t == null;
|
|
29
|
+
}
|
|
30
|
+
function A(t) {
|
|
31
|
+
return typeof t == "string" || typeof t == "number" || typeof t == "boolean";
|
|
32
|
+
}
|
|
33
|
+
const E = (t, e) => i.toArray(t).filter(f).filter((n) => n.type === e);
|
|
34
|
+
export {
|
|
35
|
+
c as compareAllPropsForMemo,
|
|
36
|
+
s as comparePropsForMemo,
|
|
37
|
+
E as filterComponentFromChildren,
|
|
38
|
+
g as isEmptyChildren,
|
|
39
|
+
b as isHTMLElementChildren,
|
|
40
|
+
C as isIterableChildren,
|
|
41
|
+
A as isPrimitiveChildren,
|
|
42
|
+
p as isReactFragmentChildren,
|
|
43
|
+
u as isSingleReactElementChildren
|
|
44
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type TAssertFunction<T> = <StrongTypedEntity extends T>(assertEntity: StrongTypedEntity) => StrongTypedEntity;
|
|
2
|
+
export type MapValueType<Target> = Target extends Map<unknown, infer Type> ? Type : never;
|
|
3
|
+
export type MapKeyType<Target> = Target extends Map<infer Type, unknown> ? Type : never;
|
|
4
|
+
export type ExtractValue<T, V extends T> = V;
|
|
5
|
+
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
6
|
+
type FunctionType = (...args: any[]) => any;
|
|
7
|
+
type BuiltInObjects = Date | RegExp | URL | URLSearchParams | Promise<any> | Error;
|
|
8
|
+
type CollectionTypes = Map<any, any> | Set<any> | WeakMap<object, any> | WeakSet<object> | ArrayBuffer | SharedArrayBuffer | Uint8Array | Uint16Array | Uint32Array | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
|
|
9
|
+
export type TDeepReadonly<T> = T extends Primitive ? T : T extends FunctionType ? T : T extends CollectionTypes ? Omit<T, "set" | "delete" | "clear" | "push" | "pop" | "shift" | "unshift" | "splice" | "sort" | "reverse" | "fill"> : T extends BuiltInObjects ? T : T extends Array<infer U> ? ReadonlyArray<TDeepReadonly<U>> : T extends {} ? {
|
|
10
|
+
readonly [K in keyof T]: TDeepReadonly<T[K]>;
|
|
11
|
+
} : Readonly<T>;
|
|
12
|
+
export type TDeepMutable<T> = T extends Primitive ? T : T extends FunctionType ? T : T extends CollectionTypes ? T : T extends BuiltInObjects ? T : T extends ReadonlyArray<infer U> ? Array<TDeepMutable<U>> : T extends {} ? {
|
|
13
|
+
-readonly [P in keyof T]: TDeepMutable<T[P]>;
|
|
14
|
+
} : T;
|
|
15
|
+
export type DistributiveOmit<T, K extends PropertyKey> = T extends any ? Omit<T, K> : never;
|
|
16
|
+
export type Prettify<T> = {
|
|
17
|
+
[K in keyof T]: T[K];
|
|
18
|
+
} & {};
|
|
19
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContextState as r } from "./libs/createContextState.js";
|
|
3
|
+
import { createStateManager as a } from "./libs/createStateManager.js";
|
|
4
|
+
import { createTranslator as n, defineLocale as f } from "./libs/createTranslator.js";
|
|
5
|
+
import { createEventMethod as m } from "./libs/createEventMethod.js";
|
|
6
|
+
export {
|
|
7
|
+
r as createContextState,
|
|
8
|
+
m as createEventMethod,
|
|
9
|
+
a as createStateManager,
|
|
10
|
+
n as createTranslator,
|
|
11
|
+
f as defineLocale
|
|
12
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ComponentType, PropsWithChildren, PropsWithoutRef } from 'react';
|
|
2
|
+
import { Draft } from 'immer';
|
|
3
|
+
import { TDeepMutable, TDeepReadonly } from '../helpers/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Global window interface extension for state persistence across renders
|
|
7
|
+
* Stores state instances in a Map to prevent recreation on hot reloads
|
|
8
|
+
*/
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
__CONTEXT_STATE__: Map<symbol | string, {
|
|
12
|
+
state: unknown;
|
|
13
|
+
instance: unknown;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Function that modifies state using Immer draft pattern */
|
|
18
|
+
type DraftFunction<T> = (draft: Draft<T>) => void;
|
|
19
|
+
/** Function that modifies state using Immer draft pattern with additional data */
|
|
20
|
+
type DraftFunctionWithData<T, D> = (draft: Draft<T>, data: D) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Brief document of how createContextState works internally:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Context Creation**: Creates a React Context that holds state management functions
|
|
25
|
+
* 2. **State Storage**: Uses different storage strategies for SSR vs client:
|
|
26
|
+
* - Server: Fresh state per request
|
|
27
|
+
* - Client: Persists in window.__CONTEXT_STATE__ for chunking support
|
|
28
|
+
* 3. **Subscription System**: Implements external store pattern with useSyncExternalStore
|
|
29
|
+
* 4. **Immer Integration**: All state updates go through Immer for immutability + patches
|
|
30
|
+
* 5. **Caching Layer**: Uses createCacheStorage for memoization and performance
|
|
31
|
+
* 6. **Pure Function Validation**: Validates compute functions to prevent render loops
|
|
32
|
+
*
|
|
33
|
+
* Flow: Component → useContextState → useSyncExternalStore → subscribers → re-render
|
|
34
|
+
* */
|
|
35
|
+
export declare function createContextState<State extends Record<string | number, unknown>>(instanceId?: string): {
|
|
36
|
+
/** React component to provide state context */
|
|
37
|
+
Provider: import('react').NamedExoticComponent<PropsWithChildren<{
|
|
38
|
+
initialState?: Partial<State>;
|
|
39
|
+
}>>;
|
|
40
|
+
/** Hook for full state access (read + write + computed values) */
|
|
41
|
+
useContextState: <ComputedValue = State>(compute?: (state: TDeepReadonly<State>) => ComputedValue, enableRevert?: boolean) => [TDeepMutable<ComputedValue>, <T extends Partial<State> | DraftFunction<State>>(valueOrUpdater: T) => () => void, {
|
|
42
|
+
<ComputedSnapshotValue>(compute: (input: State) => ComputedSnapshotValue): ComputedSnapshotValue;
|
|
43
|
+
(compute?: undefined): State;
|
|
44
|
+
}];
|
|
45
|
+
/** Hook for read-only state access with computed values */
|
|
46
|
+
useContextStateValue: <ComputedValue = State>(compute?: (state: TDeepReadonly<State>) => ComputedValue) => TDeepMutable<ComputedValue>;
|
|
47
|
+
/** Hook for write-only state access */
|
|
48
|
+
useSetContextState: (enableRevert?: boolean) => <T extends Partial<State> | DraftFunction<State>>(valueOrUpdater: T) => () => void;
|
|
49
|
+
/** Hook for synchronous state snapshot access */
|
|
50
|
+
useStateSnapshotGetter: () => {
|
|
51
|
+
<ComputedSnapshotValue>(compute: (input: State) => ComputedSnapshotValue): ComputedSnapshotValue;
|
|
52
|
+
(compute?: undefined): State;
|
|
53
|
+
};
|
|
54
|
+
/** Hook to register a postFlush callback (runs once per batch before subscribers) */
|
|
55
|
+
useSetPostFlush: () => (callback: (draft: Draft<State>) => void) => void;
|
|
56
|
+
/** Component for syncing external props to state */
|
|
57
|
+
StateSynchronizer: <ComponentProps>({ updateStateOnDataChanged, data }: {
|
|
58
|
+
data?: ComponentProps;
|
|
59
|
+
updateStateOnDataChanged?: DraftFunctionWithData<State, ComponentProps>;
|
|
60
|
+
}) => null;
|
|
61
|
+
/** @deprecated - renamed to withContextProvider */
|
|
62
|
+
withStateProvider: <ComponentProps>(WrappedComponent: ComponentType<ComponentProps>, config?: {
|
|
63
|
+
/**
|
|
64
|
+
* Transform component props to initial state
|
|
65
|
+
* Called once when component mounts
|
|
66
|
+
*/
|
|
67
|
+
initialState?: (props: PropsWithoutRef<ComponentProps>) => Partial<State>;
|
|
68
|
+
/**
|
|
69
|
+
* Sync prop changes to state updates
|
|
70
|
+
* Called whenever props change (except first render)
|
|
71
|
+
*/
|
|
72
|
+
bindPropToState?: DraftFunctionWithData<State, PropsWithoutRef<ComponentProps>>;
|
|
73
|
+
}) => import('react').ForwardRefExoticComponent<PropsWithoutRef<ComponentProps> & import('react').RefAttributes<unknown>>;
|
|
74
|
+
/** HOC to automatically wrap components with state provider */
|
|
75
|
+
withContextProvider: <ComponentProps>(WrappedComponent: ComponentType<ComponentProps>, config?: {
|
|
76
|
+
/**
|
|
77
|
+
* Transform component props to initial state
|
|
78
|
+
* Called once when component mounts
|
|
79
|
+
*/
|
|
80
|
+
initialState?: (props: PropsWithoutRef<ComponentProps>) => Partial<State>;
|
|
81
|
+
/**
|
|
82
|
+
* Sync prop changes to state updates
|
|
83
|
+
* Called whenever props change (except first render)
|
|
84
|
+
*/
|
|
85
|
+
bindPropToState?: DraftFunctionWithData<State, PropsWithoutRef<ComponentProps>>;
|
|
86
|
+
}) => import('react').ForwardRefExoticComponent<PropsWithoutRef<ComponentProps> & import('react').RefAttributes<unknown>>;
|
|
87
|
+
};
|
|
88
|
+
export type TContextState = ReturnType<typeof createContextState>;
|
|
89
|
+
export {};
|