@agent-html/react 0.0.1
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/package.json +20 -0
- package/src/index.tsx +264 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-html/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"!src/**/*.test.ts",
|
|
10
|
+
"!src/**/*.test.tsx",
|
|
11
|
+
"!src/**/*.spec.ts",
|
|
12
|
+
"!src/**/*.spec.tsx"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/index.tsx"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": "^19.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import type { ReactNode, RefObject } from "react"
|
|
3
|
+
|
|
4
|
+
export const artifactInteractionEventName = "agent-html:state-change"
|
|
5
|
+
|
|
6
|
+
export type ArtifactStateChangeKind =
|
|
7
|
+
| "set"
|
|
8
|
+
| "toggle"
|
|
9
|
+
| "select"
|
|
10
|
+
| "open"
|
|
11
|
+
| "action"
|
|
12
|
+
| "move"
|
|
13
|
+
| "resize"
|
|
14
|
+
| (string & {})
|
|
15
|
+
|
|
16
|
+
export type ArtifactStateChange = {
|
|
17
|
+
after: unknown
|
|
18
|
+
before: unknown
|
|
19
|
+
blockId?: string
|
|
20
|
+
component: string
|
|
21
|
+
controlId: string
|
|
22
|
+
kind: ArtifactStateChangeKind
|
|
23
|
+
label?: string
|
|
24
|
+
semantic?: string
|
|
25
|
+
timestamp: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ArtifactStateChangeInput = Omit<
|
|
29
|
+
ArtifactStateChange,
|
|
30
|
+
"timestamp"
|
|
31
|
+
> & {
|
|
32
|
+
timestamp?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ArtifactInteractionSnapshot = {
|
|
36
|
+
blockId?: string
|
|
37
|
+
currentState: Record<string, unknown>
|
|
38
|
+
recentChanges: ArtifactStateChange[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ArtifactInteractionRuntime = {
|
|
42
|
+
emitChange: (change: ArtifactStateChangeInput) => void
|
|
43
|
+
getSnapshot: (blockId?: string) => ArtifactInteractionSnapshot
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const emptyInteractionSnapshot: ArtifactInteractionSnapshot = {
|
|
47
|
+
currentState: {},
|
|
48
|
+
recentChanges: [],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const noopInteractionRuntime: ArtifactInteractionRuntime = {
|
|
52
|
+
emitChange: dispatchArtifactStateChange,
|
|
53
|
+
getSnapshot: () => emptyInteractionSnapshot,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ArtifactInteractionContext =
|
|
57
|
+
React.createContext<ArtifactInteractionRuntime>(noopInteractionRuntime)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export type ArtifactProps = {
|
|
61
|
+
children?: ReactNode
|
|
62
|
+
title: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type BlockProps = {
|
|
66
|
+
children?: ReactNode
|
|
67
|
+
id: string
|
|
68
|
+
title?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createArtifactStateChange(
|
|
72
|
+
change: ArtifactStateChangeInput
|
|
73
|
+
): ArtifactStateChange {
|
|
74
|
+
return {
|
|
75
|
+
...change,
|
|
76
|
+
timestamp: change.timestamp ?? Date.now(),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function dispatchArtifactStateChange(change: ArtifactStateChangeInput) {
|
|
81
|
+
const detail = createArtifactStateChange(change)
|
|
82
|
+
|
|
83
|
+
if (typeof window === "undefined") {
|
|
84
|
+
return detail
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
window.dispatchEvent(
|
|
88
|
+
new CustomEvent<ArtifactStateChange>(artifactInteractionEventName, {
|
|
89
|
+
detail,
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return detail
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function InteractionProvider({
|
|
97
|
+
children,
|
|
98
|
+
onChange,
|
|
99
|
+
}: {
|
|
100
|
+
children?: ReactNode
|
|
101
|
+
onChange?: (change: ArtifactStateChange) => void
|
|
102
|
+
}) {
|
|
103
|
+
const snapshotsRef = React.useRef(new Map<string, ArtifactInteractionSnapshot>())
|
|
104
|
+
|
|
105
|
+
const emitChange = React.useCallback(
|
|
106
|
+
(input: ArtifactStateChangeInput) => {
|
|
107
|
+
const change = createArtifactStateChange(input)
|
|
108
|
+
const key = change.blockId ?? ""
|
|
109
|
+
const previous = snapshotsRef.current.get(key) ?? {
|
|
110
|
+
blockId: change.blockId,
|
|
111
|
+
currentState: {},
|
|
112
|
+
recentChanges: [],
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
snapshotsRef.current.set(key, {
|
|
116
|
+
blockId: change.blockId,
|
|
117
|
+
currentState: {
|
|
118
|
+
...previous.currentState,
|
|
119
|
+
[change.controlId]: change.after,
|
|
120
|
+
},
|
|
121
|
+
recentChanges: [...previous.recentChanges, change].slice(-20),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
onChange?.(change)
|
|
125
|
+
dispatchArtifactStateChange(change)
|
|
126
|
+
},
|
|
127
|
+
[onChange]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const getSnapshot = React.useCallback((blockId?: string) => {
|
|
131
|
+
return snapshotsRef.current.get(blockId ?? "") ?? emptyInteractionSnapshot
|
|
132
|
+
}, [])
|
|
133
|
+
|
|
134
|
+
const runtime = React.useMemo(
|
|
135
|
+
() => ({
|
|
136
|
+
emitChange,
|
|
137
|
+
getSnapshot,
|
|
138
|
+
}),
|
|
139
|
+
[emitChange, getSnapshot]
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<ArtifactInteractionContext.Provider value={runtime}>
|
|
144
|
+
{children}
|
|
145
|
+
</ArtifactInteractionContext.Provider>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function useArtifactInteraction() {
|
|
150
|
+
return React.useContext(ArtifactInteractionContext)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function findNearestBlockId(element: Element | null | undefined) {
|
|
154
|
+
return (
|
|
155
|
+
element
|
|
156
|
+
?.closest("[data-agent-html-block='true']")
|
|
157
|
+
?.getAttribute("data-agent-html-block-id") ?? undefined
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function useNearestBlockId<T extends Element>(
|
|
162
|
+
ref: RefObject<T | null>
|
|
163
|
+
) {
|
|
164
|
+
const [blockId, setBlockId] = React.useState<string | undefined>()
|
|
165
|
+
|
|
166
|
+
React.useLayoutEffect(() => {
|
|
167
|
+
setBlockId(findNearestBlockId(ref.current))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return blockId
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function useEmitArtifactStateChange({
|
|
174
|
+
blockId,
|
|
175
|
+
elementRef,
|
|
176
|
+
}: {
|
|
177
|
+
blockId?: string
|
|
178
|
+
elementRef?: RefObject<Element | null>
|
|
179
|
+
} = {}) {
|
|
180
|
+
const runtime = useArtifactInteraction()
|
|
181
|
+
|
|
182
|
+
return React.useCallback(
|
|
183
|
+
(change: Omit<ArtifactStateChangeInput, "blockId"> & { blockId?: string }) => {
|
|
184
|
+
runtime.emitChange({
|
|
185
|
+
...change,
|
|
186
|
+
blockId: change.blockId ?? blockId ?? findNearestBlockId(elementRef?.current),
|
|
187
|
+
})
|
|
188
|
+
},
|
|
189
|
+
[blockId, elementRef, runtime]
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function useInstrumentedValueChange<T>({
|
|
194
|
+
blockId,
|
|
195
|
+
component,
|
|
196
|
+
controlId,
|
|
197
|
+
elementRef,
|
|
198
|
+
kind = "set",
|
|
199
|
+
label,
|
|
200
|
+
onChange,
|
|
201
|
+
semantic,
|
|
202
|
+
value,
|
|
203
|
+
}: {
|
|
204
|
+
blockId?: string
|
|
205
|
+
component: string
|
|
206
|
+
controlId: string
|
|
207
|
+
elementRef?: RefObject<Element | null>
|
|
208
|
+
kind?: ArtifactStateChangeKind
|
|
209
|
+
label?: string
|
|
210
|
+
onChange?: (value: T) => void
|
|
211
|
+
semantic?: string
|
|
212
|
+
value: T
|
|
213
|
+
}) {
|
|
214
|
+
const emitChange = useEmitArtifactStateChange({ blockId, elementRef })
|
|
215
|
+
const previousValueRef = React.useRef(value)
|
|
216
|
+
|
|
217
|
+
React.useEffect(() => {
|
|
218
|
+
previousValueRef.current = value
|
|
219
|
+
}, [value])
|
|
220
|
+
|
|
221
|
+
return React.useCallback(
|
|
222
|
+
(nextValue: T) => {
|
|
223
|
+
const before = previousValueRef.current
|
|
224
|
+
previousValueRef.current = nextValue
|
|
225
|
+
onChange?.(nextValue)
|
|
226
|
+
emitChange({
|
|
227
|
+
after: nextValue,
|
|
228
|
+
before,
|
|
229
|
+
component,
|
|
230
|
+
controlId,
|
|
231
|
+
kind,
|
|
232
|
+
label,
|
|
233
|
+
semantic,
|
|
234
|
+
})
|
|
235
|
+
},
|
|
236
|
+
[component, controlId, emitChange, kind, label, onChange, semantic]
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const useInstrumentedCheckedChange = useInstrumentedValueChange
|
|
241
|
+
|
|
242
|
+
export function Artifact({ children, title }: ArtifactProps) {
|
|
243
|
+
return (
|
|
244
|
+
<main
|
|
245
|
+
data-agent-html-artifact="true"
|
|
246
|
+
data-agent-html-title={title}
|
|
247
|
+
className="agent-html-artifact"
|
|
248
|
+
>
|
|
249
|
+
{children}
|
|
250
|
+
</main>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function Block({ children, id, title }: BlockProps) {
|
|
255
|
+
return (
|
|
256
|
+
<section
|
|
257
|
+
data-agent-html-block="true"
|
|
258
|
+
data-agent-html-block-id={id}
|
|
259
|
+
data-agent-html-block-title={title ?? id}
|
|
260
|
+
>
|
|
261
|
+
{children}
|
|
262
|
+
</section>
|
|
263
|
+
)
|
|
264
|
+
}
|