react-state-custom 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -1
  3. package/dist/index.es.js +616 -384
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +21 -1
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/state-utils/createAutoCtx.d.ts +5 -4
  8. package/dist/state-utils/createRootCtx.d.ts +6 -1
  9. package/dist/state-utils/ctx.d.ts +1 -0
  10. package/dist/state-utils/paramsToId.d.ts +1 -0
  11. package/package.json +21 -5
  12. package/.github/copilot-instructions.md +0 -57
  13. package/.github/workflows/deploy.yml +0 -56
  14. package/.vscode/extensions.json +0 -5
  15. package/.vscode/settings.json +0 -8
  16. package/API_DOCUMENTATION.md +0 -1012
  17. package/dist/dev.d.ts +0 -0
  18. package/dist/examples/Playground.d.ts +0 -1
  19. package/dist/examples/cart/app.d.ts +0 -1
  20. package/dist/examples/cart/index.d.ts +0 -3
  21. package/dist/examples/cart/state.d.ts +0 -23
  22. package/dist/examples/cart/view.d.ts +0 -4
  23. package/dist/examples/counter/app.d.ts +0 -1
  24. package/dist/examples/counter/index.d.ts +0 -2
  25. package/dist/examples/counter/state.d.ts +0 -6
  26. package/dist/examples/counter/view.d.ts +0 -2
  27. package/dist/examples/form/app.d.ts +0 -1
  28. package/dist/examples/form/index.d.ts +0 -3
  29. package/dist/examples/form/state.d.ts +0 -16
  30. package/dist/examples/form/view.d.ts +0 -4
  31. package/dist/examples/timer/app.d.ts +0 -1
  32. package/dist/examples/timer/index.d.ts +0 -2
  33. package/dist/examples/timer/state.d.ts +0 -11
  34. package/dist/examples/timer/view.d.ts +0 -4
  35. package/dist/examples/todo/app.d.ts +0 -1
  36. package/dist/examples/todo/index.d.ts +0 -3
  37. package/dist/examples/todo/state.d.ts +0 -17
  38. package/dist/examples/todo/view.d.ts +0 -4
  39. package/fix-vscode-yarn-pnp.sh +0 -26
  40. package/index.html +0 -14
  41. package/src/dev-tool/DataViewComponent.tsx +0 -17
  42. package/src/dev-tool/DevTool.css +0 -134
  43. package/src/dev-tool/DevTool.tsx +0 -20
  44. package/src/dev-tool/DevToolState.tsx +0 -78
  45. package/src/dev-tool/StateLabelRender.tsx +0 -38
  46. package/src/dev-tool/useHighlight.tsx +0 -56
  47. package/src/dev.tsx +0 -7
  48. package/src/examples/Playground.tsx +0 -180
  49. package/src/examples/cart/app.tsx +0 -16
  50. package/src/examples/cart/index.ts +0 -3
  51. package/src/examples/cart/state.ts +0 -67
  52. package/src/examples/cart/view.tsx +0 -62
  53. package/src/examples/counter/app.tsx +0 -14
  54. package/src/examples/counter/index.ts +0 -2
  55. package/src/examples/counter/state.ts +0 -22
  56. package/src/examples/counter/state.tsx?raw +0 -0
  57. package/src/examples/counter/view.tsx +0 -20
  58. package/src/examples/form/app.tsx +0 -16
  59. package/src/examples/form/index.ts +0 -3
  60. package/src/examples/form/state.ts +0 -58
  61. package/src/examples/form/view.tsx +0 -53
  62. package/src/examples/timer/app.tsx +0 -16
  63. package/src/examples/timer/index.ts +0 -2
  64. package/src/examples/timer/state.ts +0 -43
  65. package/src/examples/timer/view.tsx +0 -26
  66. package/src/examples/todo/app.tsx +0 -16
  67. package/src/examples/todo/index.ts +0 -3
  68. package/src/examples/todo/state.ts +0 -54
  69. package/src/examples/todo/view.tsx +0 -47
  70. package/src/index.ts +0 -22
  71. package/src/state-utils/createAutoCtx.tsx +0 -191
  72. package/src/state-utils/createRootCtx.tsx +0 -117
  73. package/src/state-utils/ctx.ts +0 -346
  74. package/src/state-utils/useArrayHash.ts +0 -53
  75. package/src/state-utils/useObjectHash.ts +0 -53
  76. package/src/state-utils/useQuickSubscribe.ts +0 -110
  77. package/src/state-utils/useRefValue.ts +0 -8
  78. package/src/state-utils/utils.ts +0 -43
  79. package/src/vite-env.d.ts +0 -6
  80. package/tsconfig.json +0 -27
  81. package/vite.config.dev.ts +0 -16
  82. package/vite.config.ts +0 -39
@@ -1,54 +0,0 @@
1
- import { createRootCtx, createAutoCtx } from '../../index'
2
- import { useCallback, useState } from 'react'
3
-
4
- export interface Todo {
5
- id: string
6
- text: string
7
- completed: boolean
8
- }
9
-
10
- export const { useCtxState: useTodoCtx } = createAutoCtx(
11
- createRootCtx(
12
- "todos",
13
- ({ listId }: { listId: string }) => {
14
- const [todos, setTodos] = useState<Todo[]>([])
15
- const [input, setInput] = useState('')
16
-
17
- const addTodo = useCallback(() => {
18
- if (input.trim()) {
19
- setTodos(prev => [...prev, {
20
- id: Date.now().toString(),
21
- text: input.trim(),
22
- completed: false
23
- }])
24
- setInput('')
25
- }
26
- }, [input])
27
-
28
- const toggleTodo = useCallback((id: string) => {
29
- setTodos(prev => prev.map(t =>
30
- t.id === id ? { ...t, completed: !t.completed } : t
31
- ))
32
- }, [])
33
-
34
- const removeTodo = useCallback((id: string) => {
35
- setTodos(prev => prev.filter(t => t.id !== id))
36
- }, [])
37
-
38
- const clearCompleted = useCallback(() => {
39
- setTodos(prev => prev.filter(t => !t.completed))
40
- }, [])
41
-
42
- return {
43
- listId,
44
- todos,
45
- input,
46
- setInput,
47
- addTodo,
48
- toggleTodo,
49
- removeTodo,
50
- clearCompleted,
51
- }
52
- }
53
- )
54
- )
@@ -1,47 +0,0 @@
1
- import { useQuickSubscribe } from '../../index'
2
- import { useTodoCtx } from './state'
3
-
4
- export const TodoExample = ({ listId = "main" }: { listId?: string }) => {
5
- const { todos, input, setInput, addTodo, toggleTodo, removeTodo, clearCompleted } =
6
- useQuickSubscribe(useTodoCtx({ listId }))
7
-
8
- return (
9
- <div style={{ padding: '1rem', border: '1px solid #ccc', marginBottom: '1rem' }}>
10
- <h3>Todo List ({listId})</h3>
11
- <div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
12
- <input
13
- value={input ?? ''}
14
- onChange={(e) => setInput?.(e.target.value)}
15
- onKeyDown={(e) => e.key === 'Enter' && addTodo?.()}
16
- placeholder="Add todo..."
17
- style={{ flex: 1, padding: '0.25rem' }}
18
- />
19
- <button onClick={addTodo}>Add</button>
20
- </div>
21
- <ul style={{ listStyle: 'none', padding: 0 }}>
22
- {todos?.map(todo => (
23
- <li key={todo.id} style={{ display: 'flex', gap: '0.5rem', marginBottom: '0.5rem' }}>
24
- <input
25
- type="checkbox"
26
- checked={todo.completed}
27
- onChange={() => toggleTodo?.(todo.id)}
28
- />
29
- <span style={{
30
- flex: 1,
31
- textDecoration: todo.completed ? 'line-through' : 'none',
32
- opacity: todo.completed ? 0.6 : 1
33
- }}>
34
- {todo.text}
35
- </span>
36
- <button onClick={() => removeTodo?.(todo.id)}>×</button>
37
- </li>
38
- ))}
39
- </ul>
40
- {todos?.some(t => t.completed) && (
41
- <button onClick={clearCompleted}>Clear Completed</button>
42
- )}
43
- </div>
44
- )
45
- }
46
-
47
- export default TodoExample
package/src/index.ts DELETED
@@ -1,22 +0,0 @@
1
- // export { default as MyComponent } from './components/MyComponent';
2
-
3
- export {
4
- Context,
5
- getContext,
6
- useDataContext,
7
- useDataSource,
8
- useDataSourceMultiple,
9
- useDataSubscribe,
10
- useDataSubscribeMultiple,
11
- useDataSubscribeMultipleWithDebounce,
12
- useDataSubscribeWithTransform
13
- } from "./state-utils/ctx"
14
-
15
- export { createRootCtx } from "./state-utils/createRootCtx"
16
- export { AutoRootCtx, createAutoCtx } from "./state-utils/createAutoCtx"
17
- export { useArrayHash } from "./state-utils/useArrayHash"
18
-
19
- export { useQuickSubscribe } from "./state-utils/useQuickSubscribe"
20
-
21
- export { DevToolContainer } from "./dev-tool/DevTool"
22
- export type { DataViewComponent } from "./dev-tool/DataViewComponent"
@@ -1,191 +0,0 @@
1
- import { useEffect, useState, Fragment, useCallback } from "react"
2
- import { useDataContext, useDataSourceMultiple, useDataSubscribe, type Context } from "./ctx"
3
- import { createRootCtx } from "./createRootCtx"
4
-
5
-
6
-
7
-
8
-
9
-
10
- const weakmapName = (function () {
11
- const weakmap = new WeakMap()
12
-
13
- return (e: any): string => {
14
- let result = weakmap.get(e);
15
- if (!result) {
16
- weakmap.set(e, result = (e?.name ?? "") + Math.random().toString())
17
- }
18
- return result
19
- }
20
- })()
21
-
22
-
23
- const resolveName = (e: any) => [
24
- ...Object
25
- .entries(e ?? {})
26
- .sort((e, f) => e[0].localeCompare(f[0]))
27
- .flat()
28
- ].join("-")
29
-
30
- /**
31
- * Inline docs: createAutoCtx + AutoRootCtx
32
- *
33
- * Quick start
34
- * 1) Mount <AutoRootCtx /> ONCE near your app root. Provide a Wrapper that acts like an ErrorBoundary to isolate and log errors.
35
- * Example: <AutoRootCtx Wrapper={MyErrorBoundary} />
36
- *
37
- * 2) Create auto contexts from your root context factories:
38
- * ```
39
- * const { useCtxState: useTestCtxState } = createAutoCtx(createRootCtx('test-state', stateFn))
40
- * const { useCtxState: useOtherCtxState } = createAutoCtx(createRootCtx('other-state', otherFn))
41
- * ```
42
- * 3) Use them in components:
43
- * ```
44
- * const ctx = useTestCtxState({ userId })
45
- * const { property1, property2 } = useDataSubscribeMultiple(ctx,'property1','property2')
46
- * // No need to mount the Root returned by createRootCtx directly — AutoRootCtx manages it for you.
47
- * ```
48
- * Notes
49
- * - AutoRootCtx must be mounted before any useCtxState hooks created by createAutoCtx run.
50
- * - Wrapper should be an ErrorBoundary-like component that simply renders {children}; no extra providers or layout required.
51
- * - For each unique params object (by stable stringified key), AutoRootCtx ensures a corresponding Root instance is rendered.
52
- */
53
-
54
- export const AutoRootCtx = ({ Wrapper = Fragment }) => {
55
-
56
- const ctx = useDataContext<any>("auto-ctx")
57
-
58
-
59
- const [state, setState] = useState<Record<string, { Component: React.FC, subState: Record<string, { params: any, counter: number }> }>>({})
60
-
61
-
62
- const subscribeRoot = useCallback(
63
- (Comp: any, params: any) => {
64
- const weakName = weakmapName(Comp);
65
- const key = resolveName(params);
66
-
67
- setState(({
68
- [weakName]: {
69
- Component = Comp,
70
- subState: {
71
- [key]: preState = { params, counter: 0 },
72
- ...subState
73
- } = {}
74
- } = {},
75
- ...state
76
- }) => ({
77
- ...state,
78
- [weakName]: {
79
- Component,
80
- subState: {
81
- ...subState,
82
- [key]: {
83
- ...preState,
84
- counter: preState.counter + 1,
85
- },
86
- },
87
- }
88
- }));
89
-
90
- return () => setState(({
91
- [weakName]: {
92
- Component = Comp,
93
- subState: {
94
- [key]: preState = { params, counter: 0 },
95
- ...subState
96
- } = {}
97
- } = {},
98
- ...state
99
- }) => ({
100
- ...state,
101
- [weakName]: {
102
- Component,
103
- subState: {
104
- ...subState,
105
- ...preState.counter > 1 ? {
106
- [key]: {
107
- ...preState,
108
- counter: preState.counter - 1,
109
- },
110
- } : {},
111
- },
112
- }
113
- }))
114
-
115
- },
116
- []
117
- )
118
-
119
- useDataSourceMultiple(ctx,
120
- ["subscribe", subscribeRoot],
121
- ["state", state],
122
- )
123
-
124
-
125
- return <>
126
- {Object.entries(state)
127
- .flatMap(([k1, { Component, subState }]) => Object
128
- .entries(subState)
129
- .map(([k2, { counter, params }]) => ({ key: k1 + k2, Component, params, counter }))
130
- .filter(e => e.counter > 0)
131
- .map(({ key, params, Component }) => <Wrapper key={key} >
132
- <Component {...params} />
133
- </Wrapper>)
134
- )
135
- }
136
- </>
137
-
138
- }
139
-
140
- /**
141
- * createAutoCtx
142
- *
143
- * Bridges a Root context (from createRootCtx) to the global AutoRootCtx renderer.
144
- * You do NOT mount the Root component yourself — just mount <AutoRootCtx /> once at the app root.
145
- *
146
- * Usage:
147
- * ```
148
- * const { useCtxState: useTestCtxState } = createAutoCtx(createRootCtx(
149
- * 'test-state',
150
- * stateFn
151
- * ))
152
- * const { useCtxState: useOtherCtxState } = createAutoCtx(createRootCtx(
153
- * 'other-state',
154
- * otherFn
155
- * ))
156
- * ```
157
- *
158
- * Then inside components:
159
- * ```
160
- * const ctxState = useTestCtxState({ any: 'params' })
161
- * ```
162
- * AutoRootCtx will subscribe/unsubscribe instances per unique params and render the appropriate Root under the hood.
163
- */
164
- export const createAutoCtx = <U extends object, V extends object,>(
165
- { Root, useCtxState, useCtxStateStrict, resolveCtxName }: ReturnType<typeof createRootCtx<U, V>>,
166
- unmountTime = 0
167
- ) => {
168
-
169
- return {
170
-
171
- useCtxState: (e: U): Context<V> => {
172
-
173
- const ctxName = resolveCtxName(e)
174
-
175
- const subscribe = useDataSubscribe(useDataContext<any>("auto-ctx"), "subscribe")
176
-
177
- useEffect(() => {
178
- // Subscribe this component to an AutoRootCtx-managed Root instance keyed by e.
179
- // AutoRootCtx handles instance ref-counting and cleanup on unmount.
180
- if (unmountTime == 0) {
181
- return subscribe?.(Root, e)
182
- } else {
183
- let unsub = subscribe?.(Root, e)
184
- return () => setTimeout(unsub, unmountTime)
185
- }
186
- }, [subscribe, ctxName])
187
-
188
- return useDataContext<V>(ctxName)
189
- }
190
- }
191
- }
@@ -1,117 +0,0 @@
1
- import { useEffect, useMemo } from "react"
2
- import { useDataContext, useDataSourceMultiple, type Context } from "./ctx"
3
-
4
-
5
-
6
- /**
7
- * createRootCtx
8
- *
9
- * Factory that creates a headless "Root" component and companion hooks for a context namespace.
10
- * It derives a unique context name from a base `name` and a props object `U`, then publishes
11
- * a computed state `V` (from `useFn`) to that context.
12
- *
13
- * Usage (manual mounting):
14
- * ```
15
- * const { Root, useCtxState } = createRootCtx('user-state', useUserState)
16
- * ...
17
- * // Mount exactly one Root per unique props combination
18
- * <Root userId={id} />
19
- * ...
20
- * // Read anywhere ,using the same props shape
21
- * const user = useCtxState({ userId: id })
22
- *```
23
- * Strict vs lenient consumers:
24
- * - useCtxStateStrict(props) throws if a matching Root is not mounted.
25
- * - useCtxState(props) logs an error (after 1s) instead of throwing.
26
- *
27
- * Multiple instances safety:
28
- * - Mounting more than one Root with the same resolved context name throws (guards accidental duplicates).
29
- *
30
- * Name resolution notes:
31
- * - The context name is built from `name` + sorted key/value pairs of `props` (U), joined by "-".
32
- * - Prefer stable, primitive props to avoid collisions; if you need automation, pair with `createAutoCtx` and
33
- * mount a single <AutoRootCtx Wrapper={ErrorBoundary} /> at the app root so you don't manually mount `Root`.
34
- */
35
- export const createRootCtx = <U extends object, V extends object>(name: string, useFn: (e: U) => V) => {
36
-
37
- const resolveCtxName = (e: U) => [
38
- name,
39
- ...Object
40
- .entries(e ?? {})
41
- .sort((e, f) => e[0].localeCompare(f[0]))
42
- .flat()
43
- ].join("-")
44
-
45
- let ctxMountedCheck = new Set<string>()
46
-
47
-
48
- const RootState: React.FC<U> = (e: U) => {
49
- const state = useFn(e)
50
- const ctxName = resolveCtxName(e)
51
- const ctx = useDataContext<V>(ctxName)
52
- const stack = useMemo(() => new Error().stack, [])
53
-
54
- useDataSourceMultiple(
55
- ctx,
56
- ...Object.entries(state) as any
57
- )
58
-
59
- useEffect(() => {
60
- if (ctxMountedCheck.has(ctxName)) {
61
- const err = new Error("RootContext " + ctxName + " are mounted more than once")
62
- err.stack = stack;
63
- throw err
64
- }
65
- ctxMountedCheck.add(ctxName)
66
- return () => { ctxMountedCheck.delete(ctxName) };
67
- })
68
-
69
- return <></>
70
- }
71
-
72
- RootState.displayName = `State[${useFn?.name??'??'}]`
73
-
74
- return {
75
- resolveCtxName,
76
- Root: RootState,
77
- /**
78
- * Strict consumer: throws if the corresponding Root for these props isn't mounted.
79
- * Use in development/tests to fail fast when wiring is incorrect.
80
- */
81
- useCtxStateStrict: (e: U): Context<V> => {
82
- const ctxName = resolveCtxName(e)
83
-
84
- const stack = useMemo(() => new Error().stack, [])
85
-
86
- useEffect(() => {
87
- if (!ctxMountedCheck.has(ctxName)) {
88
- const err = new Error("RootContext [" + ctxName + "] is not mounted")
89
- err.stack = stack;
90
- throw err
91
- }
92
- }, [ctxName])
93
-
94
- return useDataContext<V>(ctxName)
95
- },
96
- /**
97
- * Lenient consumer: schedules a console.error if the Root isn't mounted instead of throwing.
98
- * Useful in production to avoid hard crashes while still surfacing misconfiguration.
99
- */
100
- useCtxState: (e: U): Context<V> => {
101
- const ctxName = resolveCtxName(e)
102
-
103
- const stack = useMemo(() => new Error().stack, [])
104
-
105
- useEffect(() => {
106
- if (!ctxMountedCheck.has(ctxName)) {
107
- const err = new Error("RootContext [" + ctxName + "] is not mounted")
108
- err.stack = stack;
109
- let timeout = setTimeout(() => console.error(err), 1000)
110
- return () => clearTimeout(timeout)
111
- }
112
- }, [ctxMountedCheck.has(ctxName)])
113
-
114
- return useDataContext<V>(ctxName)
115
- }
116
- }
117
- }