@rune-hub/react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +336 -0
  3. package/context/HubContext/HubContext.d.ts +27 -0
  4. package/context/HubContext/HubContext.es6.js +31 -0
  5. package/context/HubContext/HubContext.js +36 -0
  6. package/context/HubContext/index.d.ts +1 -0
  7. package/context/HubContext/index.es6.js +1 -0
  8. package/context/HubContext/index.js +10 -0
  9. package/context/index.d.ts +1 -0
  10. package/context/index.es6.js +1 -0
  11. package/context/index.js +4 -0
  12. package/hooks/index.d.ts +5 -0
  13. package/hooks/index.es6.js +5 -0
  14. package/hooks/index.js +8 -0
  15. package/hooks/useAction/index.d.ts +1 -0
  16. package/hooks/useAction/index.es6.js +1 -0
  17. package/hooks/useAction/index.js +9 -0
  18. package/hooks/useAction/useAction.d.ts +24 -0
  19. package/hooks/useAction/useAction.es6.js +39 -0
  20. package/hooks/useAction/useAction.js +43 -0
  21. package/hooks/useHub/index.d.ts +1 -0
  22. package/hooks/useHub/index.es6.js +1 -0
  23. package/hooks/useHub/index.js +9 -0
  24. package/hooks/useHub/useHub.d.ts +20 -0
  25. package/hooks/useHub/useHub.es6.js +28 -0
  26. package/hooks/useHub/useHub.js +32 -0
  27. package/hooks/useOn/index.d.ts +1 -0
  28. package/hooks/useOn/index.es6.js +1 -0
  29. package/hooks/useOn/index.js +9 -0
  30. package/hooks/useOn/useOn.d.ts +23 -0
  31. package/hooks/useOn/useOn.es6.js +31 -0
  32. package/hooks/useOn/useOn.js +35 -0
  33. package/hooks/useRune/index.d.ts +1 -0
  34. package/hooks/useRune/index.es6.js +1 -0
  35. package/hooks/useRune/index.js +9 -0
  36. package/hooks/useRune/useRune.d.ts +24 -0
  37. package/hooks/useRune/useRune.es6.js +37 -0
  38. package/hooks/useRune/useRune.js +41 -0
  39. package/hooks/useSlot/index.d.ts +1 -0
  40. package/hooks/useSlot/index.es6.js +1 -0
  41. package/hooks/useSlot/index.js +9 -0
  42. package/hooks/useSlot/useSlot.d.ts +34 -0
  43. package/hooks/useSlot/useSlot.es6.js +43 -0
  44. package/hooks/useSlot/useSlot.js +47 -0
  45. package/index.d.ts +2 -0
  46. package/index.es6.js +8 -0
  47. package/index.js +22 -0
  48. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mike Lysikov
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,336 @@
1
+ <h1 align="center">
2
+ <a href="https://github.com/d8corp/rune-hub">
3
+ <img src="https://cdn.jsdelivr.net/gh/d8corp/rune-hub@main/logo.svg" width="80">
4
+ </a>
5
+ <br>
6
+ @rune-hub/react
7
+ </h1>
8
+
9
+ <div align="center">
10
+ <a href="https://www.npmjs.com/package/@rune-hub/react" target="_blank">
11
+ <img src="https://img.shields.io/npm/v/@rune-hub/react.svg" alt="rune-hub npm">
12
+ </a>
13
+ <a href="https://github.com/d8corp/rune-hub-react" target="_blank">
14
+ <img src="https://img.shields.io/badge/-GitHub-181717?logo=github&logoColor=white" alt="rune-hub source code">
15
+ </a>
16
+ <a href="https://www.npmtrends.com/@rune-hub/react" target="_blank">
17
+ <img src="https://img.shields.io/npm/dm/@rune-hub/react.svg" alt="rune-hub downloads">
18
+ </a>
19
+ <a href="https://github.com/d8corp/rune-hub-react/tree/main/release" target="_blank">
20
+ <img src="https://packagephobia.com/badge?p=@rune-hub/react" alt="rune-hub install size">
21
+ </a>
22
+ <a href="https://www.typescriptlang.org" target="_blank">
23
+ <img src="https://img.shields.io/npm/types/@rune-hub/react" alt="TypeScript">
24
+ </a>
25
+ <a href="https://github.com/d8corp/rune-hub-react/blob/main/LICENSE" target="_blank">
26
+ <img src="https://img.shields.io/npm/l/@rune-hub/react" alt="rune-hub license">
27
+ </a>
28
+ <a href="https://github.com/d8corp/rune-hub-react/blob/main/CHANGELOG.md" target="_blank">
29
+ <img src="https://img.shields.io/badge/Changelog-⋮-brightgreen" alt="rune-hub changelog">
30
+ </a>
31
+ <a href="https://d8corp.github.io/rune-hub-react/coverage/lcov-report" target="_blank">
32
+ <img src="https://github.com/d8corp/rune-hub-react/actions/workflows/tests.yml/badge.svg" alt="rune-hub tests">
33
+ </a>
34
+ <a href="https://github.com/d8corp/rune-hub-react/issues" target="_blank">
35
+ <img src="https://img.shields.io/github/issues-raw/d8corp/rune-hub-react" alt="Open issues">
36
+ </a>
37
+ <a href="https://github.com/d8corp/rune-hub-react/pulls" target="_blank">
38
+ <img src="https://img.shields.io/github/issues-pr-raw/d8corp/rune-hub-react" alt="Pull requests">
39
+ </a>
40
+ </div>
41
+ <br>
42
+
43
+ `@rune-hub/react` provides [React](https://www.npmjs.com/package/react) integration for the reactive [rune-hub](https://www.npmjs.com/package/rune-hub) store, implementing a flexible subscription mechanism for state changes and mutation calls via specialized hooks.
44
+ The [HubProvider](#hubprovider) component injects the store instance into the React context, while the [useRune](#userune) and [useAction](#useaction) hooks ensure reactive data synchronization and stable references to action functions, minimizing unnecessary re-renders.
45
+ With full TypeScript support and straightforward integration, the library enables predictable state management architecture without boilerplate, while maintaining high performance and compatibility with modern build tools.
46
+
47
+ [![stars](https://img.shields.io/github/stars/d8corp/rune-hub-react?style=social)](https://github.com/d8corp/rune-hub-react/stargazers)
48
+ [![watchers](https://img.shields.io/github/watchers/d8corp/rune-hub-react?style=social)](https://github.com/d8corp/rune-hub-react/watchers)
49
+
50
+ ## Index
51
+
52
+ <sup>**[ [Install](#install) ]**</sup>
53
+ <sup>**[ [Examples](#examples) ]** [Basic Counter](#basic-counter) • [Todo List](#todo-list)</sup>
54
+ <sup>**[ [API](#api) ]** [HubProvider](#hubprovider) • [useRune](#userune) • [useOn](#useon) • [useAction](#useaction) • [useHub](#usehub) • [useSlot](#useslot)</sup>
55
+ <sup>**[ [Links](#links) ]**</sup>
56
+
57
+ ## Install
58
+ ###### [🏠︎](#index) / Install [↓](#examples)
59
+
60
+ **Requires [React 18+](https://www.npmjs.com/package/react) and [rune-hub 1.0+](https://www.npmjs.com/package/rune-hub).**
61
+
62
+ Use with any modern bundler (Vite, Webpack, Rollup, etc.) or framework (Next.js, Remix, etc.).
63
+
64
+ ```shell
65
+ npm i rune-hub @rune-hub/react
66
+ ```
67
+
68
+ ## Examples
69
+ ###### [🏠︎](#index) / Examples [↑](#install) [↓](#api)
70
+
71
+ <sup>[Basic Counter](#basic-counter) • [Todo List](#todo-list)</sup>
72
+
73
+ ### Basic Counter
74
+ ###### [🏠︎](#index) / [Examples](#examples) / Basic Counter [↓](#todo-list)
75
+
76
+ A simple counter demonstrating.
77
+
78
+ ```tsx
79
+ import { slot } from 'rune-hub'
80
+ import { useRune } from '@rune-hub/react'
81
+
82
+ const count = () => 0
83
+ const increment = () => slot(count).value++
84
+ const decrement = () => slot(count).value--
85
+
86
+ function Counter () {
87
+ const value = useRune(count)
88
+
89
+ return (
90
+ <div>
91
+ <button onClick={decrement}>-</button>
92
+ <span>{value}</span>
93
+ <button onClick={increment}>+</button>
94
+ </div>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ### Todo List
100
+ ###### [🏠︎](#index) / [Examples](#examples) / Todo List [↑](#basic-counter)
101
+
102
+ A todo list showcasing rune-hub features.
103
+
104
+ Store:
105
+ ```ts
106
+ import { get, set, update } from 'rune-hub'
107
+
108
+ export interface Todo {
109
+ id: number
110
+ text: string
111
+ done: boolean
112
+ }
113
+
114
+ let nextId = 1
115
+ export const todos = (): Todo[] => []
116
+
117
+ export const addTodo = (text: string) => {
118
+ get(todos).push({ id: nextId++, text, done: false })
119
+ update(todos)
120
+ }
121
+
122
+ export const toggleTodo = (todoId: number) => {
123
+ set(todos, get(todos).map(todo =>
124
+ todoId === todo.id ? { ...todo, done: !todo.done } : todo,
125
+ ))
126
+ }
127
+ ```
128
+
129
+ Component:
130
+ ```tsx
131
+ import { useState } from 'react'
132
+ import { useRune } from '@rune-hub/react'
133
+ import { todos, addTodo, toggleTodo } from './store.ts'
134
+
135
+ function TodoList () {
136
+ const todoList = useRune(todos)
137
+ const [text, setText] = useState('')
138
+
139
+ const handleSubmit = (e: any) => {
140
+ e.preventDefault()
141
+
142
+ if (text.trim()) {
143
+ addTodo(text.trim())
144
+ setText('')
145
+ }
146
+ }
147
+
148
+ return (
149
+ <div>
150
+ <form onSubmit={handleSubmit}>
151
+ <input
152
+ value={text}
153
+ onChange={e => setText(e.target.value)}
154
+ placeholder='What needs to be done?'
155
+ />
156
+ <button type='submit'>Add</button>
157
+ </form>
158
+ <ul>
159
+ {todoList.map(({ id, done, text }) => (
160
+ <li
161
+ key={id}
162
+ onClick={() => toggleTodo(id)}
163
+ style={{ textDecoration: done ? 'line-through' : 'none' }}
164
+ >
165
+ {text}
166
+ </li>
167
+ ))}
168
+ </ul>
169
+ </div>
170
+ )
171
+ }
172
+ ```
173
+
174
+ ## API
175
+ ###### [🏠︎](#index) / API [↑](#examples) [↓](#links)
176
+
177
+ <sup>[HubProvider](#hubprovider) • [useRune](#userune) • [useOn](#useon) • [useAction](#useaction) • [useHub](#usehub) • [useSlot](#useslot)</sup>
178
+
179
+ ### HubProvider
180
+ ###### [🏠︎](#index) / [API](#api) / HubProvider [↓](#userune)
181
+
182
+ The `HubProvider` wraps your component tree and makes a Hub instance available to all child components via [useHub](#usehub) hook.
183
+
184
+ ```tsx
185
+ import { Hub } from 'rune-hub'
186
+ import { HubProvider } from '@rune-hub/react'
187
+
188
+ const myHub = new Hub()
189
+
190
+ function App () {
191
+ return (
192
+ <HubProvider value={myHub}>
193
+ <YourComponents />
194
+ </HubProvider>
195
+ )
196
+ }
197
+ ```
198
+
199
+ If you don't provide a Hub, the default `Hub.root` will be used.
200
+
201
+ ### useRune
202
+ ###### [🏠︎](#index) / [API](#api) / useRune [↑](#hubprovider) [↓](#useon)
203
+
204
+ Uses `useSyncExternalStore` for proper synchronization with React's rendering cycle.
205
+ Automatically subscribes to the Rune's slot and unsubscribes on unmount.
206
+
207
+ ```tsx
208
+ import { rune, get } from 'rune-hub'
209
+ import { useRune } from '@rune-hub/react'
210
+
211
+ const count = () => 0
212
+ const log = () => console.log(get(count))
213
+
214
+ function Counter () {
215
+ const value = useRune(count)
216
+ // Subscribe to count changes
217
+
218
+ useRune(log)
219
+ // Activate log effect
220
+
221
+ return <div>Count: {value}</div>
222
+ }
223
+ ```
224
+
225
+ ### useOn
226
+ ###### [🏠︎](#index) / [API](#api) / useOn [↑](#userune) [↓](#useaction)
227
+
228
+ Use `useOn` when you need to run side effects (like logging, analytics, or synchronization) that depend on other Runes, but don't need the return value in your component.
229
+
230
+ ```tsx
231
+ import { rune, get } from 'rune-hub'
232
+ import { useRune, useOn } from '@rune-hub/react'
233
+
234
+ const count = () => 0
235
+ const log = () => console.log(get(count))
236
+
237
+ function Counter () {
238
+ const value = useRune(count)
239
+ // Subscribe to count changes
240
+
241
+ useOn(log)
242
+ // Activate log effect
243
+
244
+ return <div>Count: {value}</div>
245
+ }
246
+ ```
247
+
248
+ ### useAction
249
+ ###### [🏠︎](#index) / [API](#api) / useAction [↑](#useon) [↓](#usehub)
250
+
251
+ The hook automatically binds your action to context Hub.
252
+
253
+ ```tsx
254
+ import { slot } from 'rune-hub'
255
+ import { useRune, useAction } from '@rune-hub/react'
256
+
257
+ const count = () => 0
258
+ const increment = () => slot(count).value++
259
+ const decrement = () => slot(count).value--
260
+
261
+ function Counter () {
262
+ const value = useRune(count)
263
+ const inc = useAction(increment)
264
+ const dec = useAction(decrement)
265
+
266
+ return (
267
+ <div>
268
+ <button onClick={dec}>-</button>
269
+ <span>{value}</span>
270
+ <button onClick={inc}>+</button>
271
+ </div>
272
+ )
273
+ }
274
+ ```
275
+
276
+ ### useHub
277
+ ###### [🏠︎](#index) / [API](#api) / useHub [↑](#useaction) [↓](#useslot)
278
+
279
+ Returns the current Hub instance from context.
280
+
281
+ ```tsx
282
+ import { useHub } from '@rune-hub/react'
283
+
284
+ function MyComponent () {
285
+ const hub = useHub()
286
+
287
+ console.log(hub)
288
+
289
+ return <h1>Hello World!</h1>
290
+ }
291
+ ```
292
+
293
+ ### useSlot
294
+ ###### [🏠︎](#index) / [API](#api) / useSlot [↑](#usehub)
295
+
296
+ **Returns a Slot instance for a given Rune within the current Hub context.**
297
+
298
+ A Slot is a Hub-scoped reactive container that tracks changes to a Rune. Use this when you need direct access to the [Slot API](https://github.com/d8corp/rune-hub#slot-api) rather than just the value.
299
+
300
+ ```tsx
301
+ import { useEffect } from 'react'
302
+ import { useSlot } from '@rune-hub/react'
303
+
304
+ const count = () => 0
305
+
306
+ function Counter () {
307
+ const slot = useSlot(count)
308
+
309
+ useEffect(() => {
310
+ // Access raw value without subscribing
311
+ console.log(slot.raw)
312
+
313
+ // Manually listen to changes
314
+ return slot.on('change', () => {
315
+ console.log('Count changed:', slot.raw)
316
+ })
317
+ }, [slot])
318
+
319
+ return <div>Count: {slot.raw}</div>
320
+ }
321
+ ```
322
+
323
+ The Slot instance is memoized and stable for the Rune and Hub combination.
324
+
325
+ ## Links
326
+ ###### [🏠︎](#index) / Links [↑](#examples)
327
+
328
+ - **Creator**: [Mike Lysikov](http://github.com/d8corp)
329
+ - **Source Code**: [GitHub](https://github.com/d8corp/rune-hub-react)
330
+ - **Repository**: [npm](https://www.npmjs.com/package/@rune-hub/react) • [npmx](https://npmx.dev/package/@rune-hub/react)
331
+ - **Utils**: [@rune-hub/utils](https://github.com/d8corp/rune-hub-utils)
332
+
333
+ **Contributions are welcome!** Please feel free to submit [issues](https://github.com/d8corp/rune-hub-react/issues) and [pull requests](https://github.com/d8corp/rune-hub-react/pulls).
334
+
335
+ [![issues](https://img.shields.io/github/issues-raw/d8corp/rune-hub-react)](https://github.com/d8corp/rune-hub-react/issues)
336
+ [![pulls](https://img.shields.io/github/issues-pr-raw/d8corp/rune-hub-react)](https://github.com/d8corp/rune-hub-react/pulls)
@@ -0,0 +1,27 @@
1
+ import { Hub } from 'rune-hub';
2
+ export declare const HubContext: import("react").Context<Hub>;
3
+ /**
4
+ * Wraps your component tree and makes a Hub instance available to all child components.
5
+ *
6
+ * If you don't provide a Hub via the `value` prop, the default `Hub.root` will be used.
7
+ * All hooks like `useRune`, `useAction`, and `useHub` will use the Hub from this context.
8
+ *
9
+ * @see {@link https://github.com/d8corp/rune-hub-react#hubprovider}
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { Hub } from 'rune-hub'
14
+ * import { HubProvider } from '@rune-hub/react'
15
+ *
16
+ * const myHub = new Hub()
17
+ *
18
+ * function App () {
19
+ * return (
20
+ * <HubProvider value={myHub}>
21
+ * <YourComponents />
22
+ * </HubProvider>
23
+ * )
24
+ * }
25
+ * ```
26
+ */
27
+ export declare const HubProvider: import("react").Provider<Hub>;
@@ -0,0 +1,31 @@
1
+ import { createContext } from 'react';
2
+ import { Hub } from 'rune-hub';
3
+
4
+ var HubContext = createContext(Hub.root);
5
+ /**
6
+ * Wraps your component tree and makes a Hub instance available to all child components.
7
+ *
8
+ * If you don't provide a Hub via the `value` prop, the default `Hub.root` will be used.
9
+ * All hooks like `useRune`, `useAction`, and `useHub` will use the Hub from this context.
10
+ *
11
+ * @see {@link https://github.com/d8corp/rune-hub-react#hubprovider}
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { Hub } from 'rune-hub'
16
+ * import { HubProvider } from '@rune-hub/react'
17
+ *
18
+ * const myHub = new Hub()
19
+ *
20
+ * function App () {
21
+ * return (
22
+ * <HubProvider value={myHub}>
23
+ * <YourComponents />
24
+ * </HubProvider>
25
+ * )
26
+ * }
27
+ * ```
28
+ */
29
+ var HubProvider = HubContext.Provider;
30
+
31
+ export { HubContext, HubProvider };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var runeHub = require('rune-hub');
7
+
8
+ var HubContext = react.createContext(runeHub.Hub.root);
9
+ /**
10
+ * Wraps your component tree and makes a Hub instance available to all child components.
11
+ *
12
+ * If you don't provide a Hub via the `value` prop, the default `Hub.root` will be used.
13
+ * All hooks like `useRune`, `useAction`, and `useHub` will use the Hub from this context.
14
+ *
15
+ * @see {@link https://github.com/d8corp/rune-hub-react#hubprovider}
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * import { Hub } from 'rune-hub'
20
+ * import { HubProvider } from '@rune-hub/react'
21
+ *
22
+ * const myHub = new Hub()
23
+ *
24
+ * function App () {
25
+ * return (
26
+ * <HubProvider value={myHub}>
27
+ * <YourComponents />
28
+ * </HubProvider>
29
+ * )
30
+ * }
31
+ * ```
32
+ */
33
+ var HubProvider = HubContext.Provider;
34
+
35
+ exports.HubContext = HubContext;
36
+ exports.HubProvider = HubProvider;
@@ -0,0 +1 @@
1
+ export * from './HubContext';
@@ -0,0 +1 @@
1
+ export { HubContext, HubProvider } from './HubContext.es6.js';
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var HubContext = require('./HubContext.js');
6
+
7
+
8
+
9
+ exports.HubContext = HubContext.HubContext;
10
+ exports.HubProvider = HubContext.HubProvider;
@@ -0,0 +1 @@
1
+ export * from './HubContext';
@@ -0,0 +1 @@
1
+ import './HubContext/index.es6.js';
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ require('./HubContext/index.js');
4
+
@@ -0,0 +1,5 @@
1
+ export * from './useAction';
2
+ export * from './useHub';
3
+ export * from './useOn';
4
+ export * from './useRune';
5
+ export * from './useSlot';
@@ -0,0 +1,5 @@
1
+ import './useAction/index.es6.js';
2
+ import './useHub/index.es6.js';
3
+ import './useOn/index.es6.js';
4
+ import './useRune/index.es6.js';
5
+ import './useSlot/index.es6.js';
package/hooks/index.js ADDED
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ require('./useAction/index.js');
4
+ require('./useHub/index.js');
5
+ require('./useOn/index.js');
6
+ require('./useRune/index.js');
7
+ require('./useSlot/index.js');
8
+
@@ -0,0 +1 @@
1
+ export * from './useAction';
@@ -0,0 +1 @@
1
+ export { useAction } from './useAction.es6.js';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var useAction = require('./useAction.js');
6
+
7
+
8
+
9
+ exports.useAction = useAction.useAction;
@@ -0,0 +1,24 @@
1
+ import type { Fn } from 'rune-hub';
2
+ /**
3
+ * The hook automatically binds your action to context Hub, ensuring all Rune mutations
4
+ * triggered inside the action are tracked within the same Hub instance.
5
+ *
6
+ * @template T - The action function type
7
+ * @param action - The action function to bind to the Hub
8
+ * @returns A stable callback that executes the action in the Hub context
9
+ * @see {@link https://github.com/d8corp/rune-hub-react#useaction}
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * const count = () => 0
14
+ * const increment = () => slot(count).value++
15
+ *
16
+ * function Counter () {
17
+ * const value = useRune(count)
18
+ * const inc = useAction(increment)
19
+ *
20
+ * return <button onClick={inc}>Count: {value}</button>
21
+ * }
22
+ * ```
23
+ */
24
+ export declare function useAction<T extends Fn>(action: T): T;
@@ -0,0 +1,39 @@
1
+ import { __spreadArray, __read } from 'tslib';
2
+ import { useCallback } from 'react';
3
+ import '../useHub/index.es6.js';
4
+ import { useHub } from '../useHub/useHub.es6.js';
5
+
6
+ /**
7
+ * The hook automatically binds your action to context Hub, ensuring all Rune mutations
8
+ * triggered inside the action are tracked within the same Hub instance.
9
+ *
10
+ * @template T - The action function type
11
+ * @param action - The action function to bind to the Hub
12
+ * @returns A stable callback that executes the action in the Hub context
13
+ * @see {@link https://github.com/d8corp/rune-hub-react#useaction}
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const count = () => 0
18
+ * const increment = () => slot(count).value++
19
+ *
20
+ * function Counter () {
21
+ * const value = useRune(count)
22
+ * const inc = useAction(increment)
23
+ *
24
+ * return <button onClick={inc}>Count: {value}</button>
25
+ * }
26
+ * ```
27
+ */
28
+ function useAction(action) {
29
+ var hub = useHub();
30
+ return useCallback((function () {
31
+ var args = [];
32
+ for (var _i = 0; _i < arguments.length; _i++) {
33
+ args[_i] = arguments[_i];
34
+ }
35
+ return hub.use(function () { return action.apply(void 0, __spreadArray([], __read(args), false)); });
36
+ }), [hub, action]);
37
+ }
38
+
39
+ export { useAction };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var tslib = require('tslib');
6
+ var react = require('react');
7
+ require('../useHub/index.js');
8
+ var useHub = require('../useHub/useHub.js');
9
+
10
+ /**
11
+ * The hook automatically binds your action to context Hub, ensuring all Rune mutations
12
+ * triggered inside the action are tracked within the same Hub instance.
13
+ *
14
+ * @template T - The action function type
15
+ * @param action - The action function to bind to the Hub
16
+ * @returns A stable callback that executes the action in the Hub context
17
+ * @see {@link https://github.com/d8corp/rune-hub-react#useaction}
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const count = () => 0
22
+ * const increment = () => slot(count).value++
23
+ *
24
+ * function Counter () {
25
+ * const value = useRune(count)
26
+ * const inc = useAction(increment)
27
+ *
28
+ * return <button onClick={inc}>Count: {value}</button>
29
+ * }
30
+ * ```
31
+ */
32
+ function useAction(action) {
33
+ var hub = useHub.useHub();
34
+ return react.useCallback((function () {
35
+ var args = [];
36
+ for (var _i = 0; _i < arguments.length; _i++) {
37
+ args[_i] = arguments[_i];
38
+ }
39
+ return hub.use(function () { return action.apply(void 0, tslib.__spreadArray([], tslib.__read(args), false)); });
40
+ }), [hub, action]);
41
+ }
42
+
43
+ exports.useAction = useAction;
@@ -0,0 +1 @@
1
+ export * from './useHub';
@@ -0,0 +1 @@
1
+ export { useHub } from './useHub.es6.js';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var useHub = require('./useHub.js');
6
+
7
+
8
+
9
+ exports.useHub = useHub.useHub;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Returns the current Hub instance from context.
3
+ *
4
+ * If no Hub is provided via HubProvider, returns the default `Hub.root` instance.
5
+ *
6
+ * @returns The current Hub instance
7
+ * @see {@link https://github.com/d8corp/rune-hub-react#usehub}
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * function MyComponent () {
12
+ * const hub = useHub()
13
+ *
14
+ * console.log(hub)
15
+ *
16
+ * return <h1>Hello World!</h1>
17
+ * }
18
+ * ```
19
+ */
20
+ export declare function useHub(): import("rune-hub").Hub;
@@ -0,0 +1,28 @@
1
+ import { useContext } from 'react';
2
+ import '../../context/index.es6.js';
3
+ import { HubContext } from '../../context/HubContext/HubContext.es6.js';
4
+
5
+ /**
6
+ * Returns the current Hub instance from context.
7
+ *
8
+ * If no Hub is provided via HubProvider, returns the default `Hub.root` instance.
9
+ *
10
+ * @returns The current Hub instance
11
+ * @see {@link https://github.com/d8corp/rune-hub-react#usehub}
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function MyComponent () {
16
+ * const hub = useHub()
17
+ *
18
+ * console.log(hub)
19
+ *
20
+ * return <h1>Hello World!</h1>
21
+ * }
22
+ * ```
23
+ */
24
+ function useHub() {
25
+ return useContext(HubContext);
26
+ }
27
+
28
+ export { useHub };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ require('../../context/index.js');
7
+ var HubContext = require('../../context/HubContext/HubContext.js');
8
+
9
+ /**
10
+ * Returns the current Hub instance from context.
11
+ *
12
+ * If no Hub is provided via HubProvider, returns the default `Hub.root` instance.
13
+ *
14
+ * @returns The current Hub instance
15
+ * @see {@link https://github.com/d8corp/rune-hub-react#usehub}
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * function MyComponent () {
20
+ * const hub = useHub()
21
+ *
22
+ * console.log(hub)
23
+ *
24
+ * return <h1>Hello World!</h1>
25
+ * }
26
+ * ```
27
+ */
28
+ function useHub() {
29
+ return react.useContext(HubContext.HubContext);
30
+ }
31
+
32
+ exports.useHub = useHub;
@@ -0,0 +1 @@
1
+ export * from './useOn';
@@ -0,0 +1 @@
1
+ export { useOn } from './useOn.es6.js';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var useOn = require('./useOn.js');
6
+
7
+
8
+
9
+ exports.useOn = useOn.useOn;
@@ -0,0 +1,23 @@
1
+ import type { Rune } from 'rune-hub';
2
+ /**
3
+ * Activates a Rune effect without subscribing to its value.
4
+ * The hook automatically subscribes to the Rune's slot on mount and unsubscribes on unmount.
5
+ *
6
+ * @param rune - The Rune to activate
7
+ * @see {@link https://github.com/d8corp/rune-hub-react#useon}
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const count = () => 0
12
+ * const log = () => console.log(get(count))
13
+ *
14
+ * function Counter () {
15
+ * const value = useRune(count)
16
+ *
17
+ * useOn(log)
18
+ *
19
+ * return <div>Count: {value}</div>
20
+ * }
21
+ * ```
22
+ */
23
+ export declare function useOn(rune: Rune): void;
@@ -0,0 +1,31 @@
1
+ import { useEffect } from 'react';
2
+ import '../useSlot/index.es6.js';
3
+ import { useSlot } from '../useSlot/useSlot.es6.js';
4
+
5
+ /**
6
+ * Activates a Rune effect without subscribing to its value.
7
+ * The hook automatically subscribes to the Rune's slot on mount and unsubscribes on unmount.
8
+ *
9
+ * @param rune - The Rune to activate
10
+ * @see {@link https://github.com/d8corp/rune-hub-react#useon}
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const count = () => 0
15
+ * const log = () => console.log(get(count))
16
+ *
17
+ * function Counter () {
18
+ * const value = useRune(count)
19
+ *
20
+ * useOn(log)
21
+ *
22
+ * return <div>Count: {value}</div>
23
+ * }
24
+ * ```
25
+ */
26
+ function useOn(rune) {
27
+ var slot = useSlot(rune);
28
+ useEffect(function () { return slot.on(); }, [slot]);
29
+ }
30
+
31
+ export { useOn };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ require('../useSlot/index.js');
7
+ var useSlot = require('../useSlot/useSlot.js');
8
+
9
+ /**
10
+ * Activates a Rune effect without subscribing to its value.
11
+ * The hook automatically subscribes to the Rune's slot on mount and unsubscribes on unmount.
12
+ *
13
+ * @param rune - The Rune to activate
14
+ * @see {@link https://github.com/d8corp/rune-hub-react#useon}
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const count = () => 0
19
+ * const log = () => console.log(get(count))
20
+ *
21
+ * function Counter () {
22
+ * const value = useRune(count)
23
+ *
24
+ * useOn(log)
25
+ *
26
+ * return <div>Count: {value}</div>
27
+ * }
28
+ * ```
29
+ */
30
+ function useOn(rune) {
31
+ var slot = useSlot.useSlot(rune);
32
+ react.useEffect(function () { return slot.on(); }, [slot]);
33
+ }
34
+
35
+ exports.useOn = useOn;
@@ -0,0 +1 @@
1
+ export * from './useRune';
@@ -0,0 +1 @@
1
+ export { useRune } from './useRune.es6.js';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var useRune = require('./useRune.js');
6
+
7
+
8
+
9
+ exports.useRune = useRune.useRune;
@@ -0,0 +1,24 @@
1
+ import type { Rune } from 'rune-hub';
2
+ /**
3
+ * Subscribes to a Rune and returns its current value.
4
+ *
5
+ * Uses `useSyncExternalStore` for proper synchronization with React's rendering cycle.
6
+ * Automatically subscribes to the Rune's slot and unsubscribes on unmount.
7
+ *
8
+ * @template T - The Rune type
9
+ * @param rune - The Rune to subscribe to
10
+ * @returns The current value of the Rune
11
+ * @see {@link https://github.com/d8corp/rune-hub-react#userune}
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const count = () => 0
16
+ *
17
+ * function Counter () {
18
+ * const value = useRune(count)
19
+ *
20
+ * return <div>Count: {value}</div>
21
+ * }
22
+ * ```
23
+ */
24
+ export declare function useRune<T extends Rune>(rune: T): ReturnType<T>;
@@ -0,0 +1,37 @@
1
+ import { useMemo, useEffect, useCallback, useSyncExternalStore } from 'react';
2
+ import '../useSlot/index.es6.js';
3
+ import { useSlot } from '../useSlot/useSlot.es6.js';
4
+
5
+ /**
6
+ * Subscribes to a Rune and returns its current value.
7
+ *
8
+ * Uses `useSyncExternalStore` for proper synchronization with React's rendering cycle.
9
+ * Automatically subscribes to the Rune's slot and unsubscribes on unmount.
10
+ *
11
+ * @template T - The Rune type
12
+ * @param rune - The Rune to subscribe to
13
+ * @returns The current value of the Rune
14
+ * @see {@link https://github.com/d8corp/rune-hub-react#userune}
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const count = () => 0
19
+ *
20
+ * function Counter () {
21
+ * const value = useRune(count)
22
+ *
23
+ * return <div>Count: {value}</div>
24
+ * }
25
+ * ```
26
+ */
27
+ function useRune(rune) {
28
+ var slot = useSlot(rune);
29
+ var stop = useMemo(function () { return slot.on(); }, [slot]);
30
+ useEffect(function () { return stop; }, [stop]);
31
+ var subscribe = useCallback(function (callback) {
32
+ return slot.on('change', callback);
33
+ }, [slot]);
34
+ return useSyncExternalStore(subscribe, function () { return slot.raw; });
35
+ }
36
+
37
+ export { useRune };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ require('../useSlot/index.js');
7
+ var useSlot = require('../useSlot/useSlot.js');
8
+
9
+ /**
10
+ * Subscribes to a Rune and returns its current value.
11
+ *
12
+ * Uses `useSyncExternalStore` for proper synchronization with React's rendering cycle.
13
+ * Automatically subscribes to the Rune's slot and unsubscribes on unmount.
14
+ *
15
+ * @template T - The Rune type
16
+ * @param rune - The Rune to subscribe to
17
+ * @returns The current value of the Rune
18
+ * @see {@link https://github.com/d8corp/rune-hub-react#userune}
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const count = () => 0
23
+ *
24
+ * function Counter () {
25
+ * const value = useRune(count)
26
+ *
27
+ * return <div>Count: {value}</div>
28
+ * }
29
+ * ```
30
+ */
31
+ function useRune(rune) {
32
+ var slot = useSlot.useSlot(rune);
33
+ var stop = react.useMemo(function () { return slot.on(); }, [slot]);
34
+ react.useEffect(function () { return stop; }, [stop]);
35
+ var subscribe = react.useCallback(function (callback) {
36
+ return slot.on('change', callback);
37
+ }, [slot]);
38
+ return react.useSyncExternalStore(subscribe, function () { return slot.raw; });
39
+ }
40
+
41
+ exports.useRune = useRune;
@@ -0,0 +1 @@
1
+ export * from './useSlot';
@@ -0,0 +1 @@
1
+ export { useSlot } from './useSlot.es6.js';
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var useSlot = require('./useSlot.js');
6
+
7
+
8
+
9
+ exports.useSlot = useSlot.useSlot;
@@ -0,0 +1,34 @@
1
+ import type { Rune, Slot } from 'rune-hub';
2
+ /**
3
+ * Returns a Slot instance for a given Rune within the current Hub context.
4
+ *
5
+ * A Slot is a Hub-scoped reactive container that tracks changes to a Rune.
6
+ * The Slot instance is memoized and stable for the Rune and Hub combination.
7
+ *
8
+ * @template T - The Rune value type
9
+ * @param rune - The Rune to get a Slot for
10
+ * @returns A memoized Slot instance for the Rune
11
+ * @see {@link https://github.com/d8corp/rune-hub-react#useslot}
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const count = () => 0
16
+ *
17
+ * function Counter () {
18
+ * const slot = useSlot(count)
19
+ *
20
+ * useEffect(() => {
21
+ * // Access raw value without subscribing
22
+ * console.log(slot.raw)
23
+ *
24
+ * // Manually listen to changes
25
+ * return slot.on('change', () => {
26
+ * console.log('Count changed:', slot.raw)
27
+ * })
28
+ * }, [slot])
29
+ *
30
+ * return <div>Count: {slot.raw}</div>
31
+ * }
32
+ * ```
33
+ */
34
+ export declare function useSlot<T>(rune: Rune<T>): Slot<T>;
@@ -0,0 +1,43 @@
1
+ import { useMemo } from 'react';
2
+ import { slot } from 'rune-hub';
3
+ import '../useHub/index.es6.js';
4
+ import { useHub } from '../useHub/useHub.es6.js';
5
+
6
+ /**
7
+ * Returns a Slot instance for a given Rune within the current Hub context.
8
+ *
9
+ * A Slot is a Hub-scoped reactive container that tracks changes to a Rune.
10
+ * The Slot instance is memoized and stable for the Rune and Hub combination.
11
+ *
12
+ * @template T - The Rune value type
13
+ * @param rune - The Rune to get a Slot for
14
+ * @returns A memoized Slot instance for the Rune
15
+ * @see {@link https://github.com/d8corp/rune-hub-react#useslot}
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const count = () => 0
20
+ *
21
+ * function Counter () {
22
+ * const slot = useSlot(count)
23
+ *
24
+ * useEffect(() => {
25
+ * // Access raw value without subscribing
26
+ * console.log(slot.raw)
27
+ *
28
+ * // Manually listen to changes
29
+ * return slot.on('change', () => {
30
+ * console.log('Count changed:', slot.raw)
31
+ * })
32
+ * }, [slot])
33
+ *
34
+ * return <div>Count: {slot.raw}</div>
35
+ * }
36
+ * ```
37
+ */
38
+ function useSlot(rune) {
39
+ var hub = useHub();
40
+ return useMemo(function () { return hub.use(function () { return slot(rune); }); }, [rune, hub]);
41
+ }
42
+
43
+ export { useSlot };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var runeHub = require('rune-hub');
7
+ require('../useHub/index.js');
8
+ var useHub = require('../useHub/useHub.js');
9
+
10
+ /**
11
+ * Returns a Slot instance for a given Rune within the current Hub context.
12
+ *
13
+ * A Slot is a Hub-scoped reactive container that tracks changes to a Rune.
14
+ * The Slot instance is memoized and stable for the Rune and Hub combination.
15
+ *
16
+ * @template T - The Rune value type
17
+ * @param rune - The Rune to get a Slot for
18
+ * @returns A memoized Slot instance for the Rune
19
+ * @see {@link https://github.com/d8corp/rune-hub-react#useslot}
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const count = () => 0
24
+ *
25
+ * function Counter () {
26
+ * const slot = useSlot(count)
27
+ *
28
+ * useEffect(() => {
29
+ * // Access raw value without subscribing
30
+ * console.log(slot.raw)
31
+ *
32
+ * // Manually listen to changes
33
+ * return slot.on('change', () => {
34
+ * console.log('Count changed:', slot.raw)
35
+ * })
36
+ * }, [slot])
37
+ *
38
+ * return <div>Count: {slot.raw}</div>
39
+ * }
40
+ * ```
41
+ */
42
+ function useSlot(rune) {
43
+ var hub = useHub.useHub();
44
+ return react.useMemo(function () { return hub.use(function () { return runeHub.slot(rune); }); }, [rune, hub]);
45
+ }
46
+
47
+ exports.useSlot = useSlot;
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './context';
2
+ export * from './hooks';
package/index.es6.js ADDED
@@ -0,0 +1,8 @@
1
+ import './context/index.es6.js';
2
+ import './hooks/index.es6.js';
3
+ export { HubContext, HubProvider } from './context/HubContext/HubContext.es6.js';
4
+ export { useAction } from './hooks/useAction/useAction.es6.js';
5
+ export { useHub } from './hooks/useHub/useHub.es6.js';
6
+ export { useOn } from './hooks/useOn/useOn.es6.js';
7
+ export { useRune } from './hooks/useRune/useRune.es6.js';
8
+ export { useSlot } from './hooks/useSlot/useSlot.es6.js';
package/index.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ require('./context/index.js');
6
+ require('./hooks/index.js');
7
+ var HubContext = require('./context/HubContext/HubContext.js');
8
+ var useAction = require('./hooks/useAction/useAction.js');
9
+ var useHub = require('./hooks/useHub/useHub.js');
10
+ var useOn = require('./hooks/useOn/useOn.js');
11
+ var useRune = require('./hooks/useRune/useRune.js');
12
+ var useSlot = require('./hooks/useSlot/useSlot.js');
13
+
14
+
15
+
16
+ exports.HubContext = HubContext.HubContext;
17
+ exports.HubProvider = HubContext.HubProvider;
18
+ exports.useAction = useAction.useAction;
19
+ exports.useHub = useHub.useHub;
20
+ exports.useOn = useOn.useOn;
21
+ exports.useRune = useRune.useRune;
22
+ exports.useSlot = useSlot.useSlot;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@rune-hub/react",
3
+ "version": "1.0.0",
4
+ "description": "Integrating RuneHub with React",
5
+ "main": "index.js",
6
+ "module": "index.es6.js",
7
+ "jsnext:main": "index.es6.js",
8
+ "types": "index.d.ts",
9
+ "scripts": {
10
+ "build": "innetjs release",
11
+ "test": "jest",
12
+ "test:coverage": "jest --coverage",
13
+ "speed": "innetjs run speed.test.ts",
14
+ "release": "innetjs release -p",
15
+ "patch": "innetjs patch",
16
+ "minor": "innetjs minor",
17
+ "major": "innetjs major",
18
+ "lint": "eslint",
19
+ "lint:fix": "eslint --fix"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/d8corp/rune-hub-react.git"
24
+ },
25
+ "keywords": [
26
+ "watch",
27
+ "state",
28
+ "stateful",
29
+ "reaction",
30
+ "reactions",
31
+ "react",
32
+ "reactive",
33
+ "observe",
34
+ "observable",
35
+ "front-end"
36
+ ],
37
+ "author": "Mikhail Lysikov <d8corp@mail.ru>",
38
+ "license": "MIT",
39
+ "bugs": {
40
+ "url": "https://github.com/d8corp/rune-hub-react/issues"
41
+ },
42
+ "homepage": "https://github.com/d8corp/rune-hub-react",
43
+ "dependencies": {
44
+ "react": ">=18",
45
+ "rune-hub": "^1.0.0"
46
+ }
47
+ }