@the-link/react 0.1.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 +20 -0
- package/README.md +4 -0
- package/dist/property-hook.d.ts +21 -0
- package/dist/property-hook.js +25 -0
- package/dist/react-tunnel.d.ts +60 -0
- package/dist/react-tunnel.js +80 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2026 Zohayr SLILEH
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
20
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Property from "@the-link/core/property";
|
|
2
|
+
/**
|
|
3
|
+
* React hook for reading a Property value reactively.
|
|
4
|
+
*
|
|
5
|
+
* The Property remains the source of truth. React reads its current snapshot
|
|
6
|
+
* and subscribes only for invalidation, so there is no mirrored state that can
|
|
7
|
+
* fall out of step between rendering and effect setup.
|
|
8
|
+
*
|
|
9
|
+
* @param property Property instance to synchronize with React state
|
|
10
|
+
* @returns Current property value, updated when the property publishes `change`
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* function ThemeLabel({ theme }: { theme: Property<string> }) {
|
|
15
|
+
* const value = useProperty(theme)
|
|
16
|
+
*
|
|
17
|
+
* return <span>{value}</span>
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export default function useProperty<Value>(property: Property<Value>): Value;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* React hook for reading a Property value reactively.
|
|
4
|
+
*
|
|
5
|
+
* The Property remains the source of truth. React reads its current snapshot
|
|
6
|
+
* and subscribes only for invalidation, so there is no mirrored state that can
|
|
7
|
+
* fall out of step between rendering and effect setup.
|
|
8
|
+
*
|
|
9
|
+
* @param property Property instance to synchronize with React state
|
|
10
|
+
* @returns Current property value, updated when the property publishes `change`
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* function ThemeLabel({ theme }: { theme: Property<string> }) {
|
|
15
|
+
* const value = useProperty(theme)
|
|
16
|
+
*
|
|
17
|
+
* return <span>{value}</span>
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export default function useProperty(property) {
|
|
22
|
+
const subscribe = useCallback((changed) => property.tunnel.subscribe("change", changed), [property]);
|
|
23
|
+
const snapshot = useCallback(() => property.value, [property]);
|
|
24
|
+
return useSyncExternalStore(subscribe, snapshot, snapshot);
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Tunnel, { Subscriber } from "@the-link/core/tunnel";
|
|
2
|
+
/**
|
|
3
|
+
* React hook helper for a Tunnel instance.
|
|
4
|
+
*
|
|
5
|
+
* Provides React-friendly subscription and state helpers around Tunnel events.
|
|
6
|
+
* Subscriptions are registered in effects and cleaned up automatically, while
|
|
7
|
+
* event payloads can be mirrored into React state for rendering.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* function StatusView({ tunnel }: { tunnel: Tunnel }) {
|
|
12
|
+
* const reactTunnel = ReactTunnel.useFactory(tunnel)
|
|
13
|
+
* const status = reactTunnel.useFirstState("status:update", "idle")
|
|
14
|
+
*
|
|
15
|
+
* return <span>{status}</span>
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default class ReactTunnel {
|
|
20
|
+
/**
|
|
21
|
+
* Tunnel instance used by the React hook helpers.
|
|
22
|
+
*/
|
|
23
|
+
readonly tunnel: Tunnel;
|
|
24
|
+
/**
|
|
25
|
+
* Initialize a ReactTunnel wrapper around a Tunnel.
|
|
26
|
+
*
|
|
27
|
+
* @param tunnel Tunnel instance to integrate with React components
|
|
28
|
+
*/
|
|
29
|
+
constructor(tunnel: Tunnel);
|
|
30
|
+
/**
|
|
31
|
+
* Create a memoized ReactTunnel instance for a Tunnel.
|
|
32
|
+
*
|
|
33
|
+
* @param tunnel Tunnel instance to wrap
|
|
34
|
+
* @returns Stable ReactTunnel wrapper while the Tunnel reference is unchanged
|
|
35
|
+
*/
|
|
36
|
+
static useFactory(tunnel: Tunnel): ReactTunnel;
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe to a Tunnel event with React lifecycle cleanup.
|
|
39
|
+
*
|
|
40
|
+
* @param event Event identifier to subscribe to
|
|
41
|
+
* @param subscriber Handler invoked when the event is published
|
|
42
|
+
*/
|
|
43
|
+
useSubscribe(event: string, subscriber: Subscriber): void;
|
|
44
|
+
/**
|
|
45
|
+
* Mirror a Tunnel event's payload values into React state.
|
|
46
|
+
*
|
|
47
|
+
* @param event Event identifier used for state updates
|
|
48
|
+
* @param defaultResults Initial state values before the event is published
|
|
49
|
+
* @returns Latest event payload values
|
|
50
|
+
*/
|
|
51
|
+
useState<Results extends unknown[]>(event: string, defaultResults: Results): Results;
|
|
52
|
+
/**
|
|
53
|
+
* Mirror the first value of a Tunnel event payload into React state.
|
|
54
|
+
*
|
|
55
|
+
* @param event Event identifier used for state updates
|
|
56
|
+
* @param defaultResult Initial value before the event is published
|
|
57
|
+
* @returns Latest first event payload value
|
|
58
|
+
*/
|
|
59
|
+
useFirstState<Result>(event: string, defaultResult: Result): Result;
|
|
60
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* React hook helper for a Tunnel instance.
|
|
4
|
+
*
|
|
5
|
+
* Provides React-friendly subscription and state helpers around Tunnel events.
|
|
6
|
+
* Subscriptions are registered in effects and cleaned up automatically, while
|
|
7
|
+
* event payloads can be mirrored into React state for rendering.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* function StatusView({ tunnel }: { tunnel: Tunnel }) {
|
|
12
|
+
* const reactTunnel = ReactTunnel.useFactory(tunnel)
|
|
13
|
+
* const status = reactTunnel.useFirstState("status:update", "idle")
|
|
14
|
+
*
|
|
15
|
+
* return <span>{status}</span>
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export default class ReactTunnel {
|
|
20
|
+
/**
|
|
21
|
+
* Tunnel instance used by the React hook helpers.
|
|
22
|
+
*/
|
|
23
|
+
tunnel;
|
|
24
|
+
/**
|
|
25
|
+
* Initialize a ReactTunnel wrapper around a Tunnel.
|
|
26
|
+
*
|
|
27
|
+
* @param tunnel Tunnel instance to integrate with React components
|
|
28
|
+
*/
|
|
29
|
+
constructor(tunnel) {
|
|
30
|
+
// Store the tunnel used by all helper hooks.
|
|
31
|
+
this.tunnel = tunnel;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a memoized ReactTunnel instance for a Tunnel.
|
|
35
|
+
*
|
|
36
|
+
* @param tunnel Tunnel instance to wrap
|
|
37
|
+
* @returns Stable ReactTunnel wrapper while the Tunnel reference is unchanged
|
|
38
|
+
*/
|
|
39
|
+
static useFactory(tunnel) {
|
|
40
|
+
return useMemo(() => new ReactTunnel(tunnel), [tunnel]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Subscribe to a Tunnel event with React lifecycle cleanup.
|
|
44
|
+
*
|
|
45
|
+
* @param event Event identifier to subscribe to
|
|
46
|
+
* @param subscriber Handler invoked when the event is published
|
|
47
|
+
*/
|
|
48
|
+
useSubscribe(event, subscriber) {
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
// Register on mount or when the event/subscriber pair changes.
|
|
51
|
+
this.tunnel.subscribe(event, subscriber);
|
|
52
|
+
// Remove the exact subscriber when React cleans up the effect.
|
|
53
|
+
return () => this.tunnel.unsubscribe(event, subscriber);
|
|
54
|
+
}, [event, subscriber]);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Mirror a Tunnel event's payload values into React state.
|
|
58
|
+
*
|
|
59
|
+
* @param event Event identifier used for state updates
|
|
60
|
+
* @param defaultResults Initial state values before the event is published
|
|
61
|
+
* @returns Latest event payload values
|
|
62
|
+
*/
|
|
63
|
+
useState(event, defaultResults) {
|
|
64
|
+
const [results, setResults] = useState(defaultResults);
|
|
65
|
+
// Store each event publication payload as the next React state value.
|
|
66
|
+
const subscriber = useCallback((...results) => setResults(results), []);
|
|
67
|
+
this.useSubscribe(event, subscriber);
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Mirror the first value of a Tunnel event payload into React state.
|
|
72
|
+
*
|
|
73
|
+
* @param event Event identifier used for state updates
|
|
74
|
+
* @param defaultResult Initial value before the event is published
|
|
75
|
+
* @returns Latest first event payload value
|
|
76
|
+
*/
|
|
77
|
+
useFirstState(event, defaultResult) {
|
|
78
|
+
return this.useState(event, [defaultResult])[0];
|
|
79
|
+
}
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@the-link/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React bindings for The Link.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "dist/react-tunnel.js",
|
|
8
|
+
"types": "dist/react-tunnel.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/react-tunnel.d.ts",
|
|
12
|
+
"default": "./dist/react-tunnel.js"
|
|
13
|
+
},
|
|
14
|
+
"./property": {
|
|
15
|
+
"types": "./dist/property-hook.d.ts",
|
|
16
|
+
"default": "./dist/property-hook.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": ["dist", "LICENSE", "README.md"],
|
|
20
|
+
"author": "Zohayr SLILEH",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/the-l-ink/react.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://the-l.ink",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"provenance": true
|
|
30
|
+
},
|
|
31
|
+
"packageManager": "bun@1.3.14",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
34
|
+
"check": "tsc --noEmit",
|
|
35
|
+
"build": "node --run clean && tsc",
|
|
36
|
+
"verify:package": "npm pack --dry-run --ignore-scripts",
|
|
37
|
+
"verify": "node --run check && node --run build && node --run verify:package",
|
|
38
|
+
"prepack": "node --run build"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@the-link/core": "^0.1.0",
|
|
42
|
+
"react": "^19.2.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@the-link/core": "^0.1.0",
|
|
46
|
+
"@types/react": "^19.2.18",
|
|
47
|
+
"react": "^19.2.8",
|
|
48
|
+
"typescript": "^6.0.3"
|
|
49
|
+
}
|
|
50
|
+
}
|