@weaverse/react 5.17.1 → 5.18.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/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/types/context.d.ts +13 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/hooks.d.ts +32 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/renderer.d.ts +18 -0
- package/dist/types/renderer.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +24 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/data-connector.d.ts +33 -0
- package/dist/types/utils/data-connector.d.ts.map +1 -0
- package/package.json +6 -5
- package/src/context.ts +16 -0
- package/src/hooks.ts +69 -0
- package/src/index.ts +10 -0
- package/src/renderer.tsx +204 -0
- package/src/types/index.ts +26 -0
- package/src/utils/data-connector.ts +544 -0
- package/dist/index.d.mts +0 -67
- package/dist/index.d.ts +0 -67
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Weaverse, type WeaverseItemStore } from '@weaverse/core'
|
|
2
|
+
import { useContext } from 'react'
|
|
3
|
+
import { WeaverseContext, WeaverseItemContext } from './context'
|
|
4
|
+
|
|
5
|
+
export type { DataContext } from './utils/data-connector'
|
|
6
|
+
// Re-export the data connector utilities
|
|
7
|
+
export {
|
|
8
|
+
replaceContentDataConnectors,
|
|
9
|
+
replaceContentDataConnectorsDeep,
|
|
10
|
+
} from './utils/data-connector'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the active Weaverse runtime from React context.
|
|
14
|
+
*
|
|
15
|
+
* @typeParam T - Runtime subtype expected by the integration.
|
|
16
|
+
*/
|
|
17
|
+
export function useWeaverse<T = Weaverse>() {
|
|
18
|
+
let weaverse = useContext(WeaverseContext)
|
|
19
|
+
return weaverse as T
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns a Weaverse item instance by ID, or the current rendered item when no
|
|
24
|
+
* ID is provided.
|
|
25
|
+
*
|
|
26
|
+
* @param id - Optional item ID to look up.
|
|
27
|
+
* @returns The matching item instance, or `null` when it is unavailable.
|
|
28
|
+
*/
|
|
29
|
+
export let useItemInstance = (id?: string) => {
|
|
30
|
+
let { id: currentId } = useContext(WeaverseItemContext)
|
|
31
|
+
let instance = Weaverse.itemInstances.get(id || currentId)
|
|
32
|
+
if (!instance) {
|
|
33
|
+
console.log(`Item instance ${id} not found`)
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
return instance as WeaverseItemStore
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns the parent instance of the current rendered item.
|
|
41
|
+
*
|
|
42
|
+
* @returns The parent item instance. At the root, the empty parent ID falls
|
|
43
|
+
* back to the current item instance; returns `null` only when that lookup is
|
|
44
|
+
* unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export let useParentInstance = () => {
|
|
47
|
+
let { parentId } = useContext(WeaverseItemContext)
|
|
48
|
+
return useItemInstance(parentId || '')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the direct child instances of an item.
|
|
53
|
+
*
|
|
54
|
+
* @param id - Optional parent item ID; defaults to the current rendered item.
|
|
55
|
+
*/
|
|
56
|
+
export let useChildInstances = (id?: string) => {
|
|
57
|
+
let currentInstance = useItemInstance(id)
|
|
58
|
+
if (!currentInstance) {
|
|
59
|
+
return []
|
|
60
|
+
}
|
|
61
|
+
let { itemInstances } = Weaverse
|
|
62
|
+
|
|
63
|
+
let {
|
|
64
|
+
data: { children },
|
|
65
|
+
} = currentInstance
|
|
66
|
+
return children
|
|
67
|
+
.map(({ id }: { id: string }) => itemInstances.get(id))
|
|
68
|
+
.filter(Boolean) as WeaverseItemStore[]
|
|
69
|
+
}
|
package/src/index.ts
ADDED
package/src/renderer.tsx
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Weaverse } from '@weaverse/core'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import React, { memo, useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import { WeaverseContextProvider, WeaverseItemContext } from './context'
|
|
5
|
+
import { useItemInstance, useWeaverse } from './hooks'
|
|
6
|
+
import type { ItemComponentProps, WeaverseRootPropsType } from './types'
|
|
7
|
+
import { replaceContentDataConnectorsDeep } from './utils/data-connector'
|
|
8
|
+
|
|
9
|
+
const REACT_VERSION_THRESHOLD = 18
|
|
10
|
+
const reactVersion = Number(React.version?.split('.')[0]) || 0
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Reads an external store in React while remaining safe during server rendering
|
|
14
|
+
* and on React versions without `useSyncExternalStore`.
|
|
15
|
+
*
|
|
16
|
+
* @param subscribe - Registers a callback for store changes.
|
|
17
|
+
* @param getSnapshot - Returns the current client snapshot.
|
|
18
|
+
* @param getServerSnapshot - Returns the server snapshot; defaults to `getSnapshot`.
|
|
19
|
+
* @returns The current external-store snapshot.
|
|
20
|
+
*/
|
|
21
|
+
export const useSafeExternalStore = (
|
|
22
|
+
subscribe: any,
|
|
23
|
+
getSnapshot: any,
|
|
24
|
+
getServerSnapshot: any = getSnapshot
|
|
25
|
+
) => {
|
|
26
|
+
// In server environment or during SSR, just return the snapshot directly
|
|
27
|
+
if (typeof window === 'undefined') {
|
|
28
|
+
return getServerSnapshot()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// In client environment, use React.useSyncExternalStore if available (React 18+)
|
|
32
|
+
if (React.useSyncExternalStore) {
|
|
33
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: <$>
|
|
34
|
+
return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fallback for older React versions - just return the current snapshot
|
|
38
|
+
return getSnapshot()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Determines the root element ID for the Weaverse component tree.
|
|
43
|
+
* Falls back through multiple strategies to ensure a valid root is always found:
|
|
44
|
+
* 1. Use explicit rootId from context.data if available
|
|
45
|
+
* 2. Find the first element with type 'main' in items array
|
|
46
|
+
* 3. Use projectId as final fallback
|
|
47
|
+
*/
|
|
48
|
+
const getRootId = (context: Weaverse): string => {
|
|
49
|
+
// Strategy 1: Explicit rootId
|
|
50
|
+
if (context.data.rootId) {
|
|
51
|
+
return context.data.rootId
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Strategy 2: Find main element
|
|
55
|
+
let mainItem = context.data?.items?.find((item) => item.type === 'main')
|
|
56
|
+
if (mainItem?.id) {
|
|
57
|
+
return mainItem.id
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Strategy 3: Fallback to projectId
|
|
61
|
+
return context.projectId
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Renders a Weaverse content tree and provides its runtime to descendant
|
|
65
|
+
* components.
|
|
66
|
+
*/
|
|
67
|
+
export const WeaverseRoot = memo(({ context }: WeaverseRootPropsType) => {
|
|
68
|
+
const data = useSafeExternalStore(
|
|
69
|
+
context.subscribe,
|
|
70
|
+
context.getSnapShot,
|
|
71
|
+
context.getSnapShot
|
|
72
|
+
)
|
|
73
|
+
const rootRef = useRef<HTMLElement>(null)
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
context.contentRootElement = rootRef.current
|
|
77
|
+
}, [context])
|
|
78
|
+
|
|
79
|
+
const eventHandlers = context?.studioBridge?.eventHandlers || {}
|
|
80
|
+
|
|
81
|
+
if (context.projectId) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
className="weaverse-content-root"
|
|
85
|
+
{...eventHandlers}
|
|
86
|
+
data-weaverse-project-id={context.projectId}
|
|
87
|
+
data-weaverse-template-id={data.id}
|
|
88
|
+
ref={rootRef}
|
|
89
|
+
>
|
|
90
|
+
<WeaverseContextProvider value={context}>
|
|
91
|
+
<ItemInstance id={getRootId(context)} parentId={''} />
|
|
92
|
+
</WeaverseContextProvider>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
return null
|
|
97
|
+
})
|
|
98
|
+
WeaverseRoot.displayName = 'WeaverseRoot'
|
|
99
|
+
const ItemComponent = memo(({ instance }: ItemComponentProps) => {
|
|
100
|
+
const context = useWeaverse()
|
|
101
|
+
const { elementRegistry } = context
|
|
102
|
+
const data = useSafeExternalStore(
|
|
103
|
+
instance.subscribe,
|
|
104
|
+
instance.getSnapShot,
|
|
105
|
+
instance.getSnapShot
|
|
106
|
+
)
|
|
107
|
+
const {
|
|
108
|
+
id,
|
|
109
|
+
type,
|
|
110
|
+
childIds = [],
|
|
111
|
+
children = [],
|
|
112
|
+
|
|
113
|
+
createdAt,
|
|
114
|
+
updatedAt,
|
|
115
|
+
deletedAt,
|
|
116
|
+
css,
|
|
117
|
+
__hidden,
|
|
118
|
+
// Need to get rid of this `data` property
|
|
119
|
+
data: _,
|
|
120
|
+
...rest
|
|
121
|
+
} = data
|
|
122
|
+
|
|
123
|
+
// Replace data connectors in all component data (handles strings, objects, and arrays recursively)
|
|
124
|
+
// IMMUTABLE: Process the entire rest object to avoid mutating original content
|
|
125
|
+
// Memoized per snapshot: `data` identity changes exactly when the item's
|
|
126
|
+
// store or translation entry changes (see WeaverseHydrogenItem.getSnapShot),
|
|
127
|
+
// and context-only updates swap the store reference via `setData({})` in
|
|
128
|
+
// syncReusedInstance — so re-renders with an unchanged snapshot skip the
|
|
129
|
+
// deep placeholder scan + clone entirely.
|
|
130
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: `rest` is a pure projection of `data`
|
|
131
|
+
const processedRest = useMemo(
|
|
132
|
+
() => replaceContentDataConnectorsDeep(rest, context.dataContext),
|
|
133
|
+
[data, context.dataContext]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!instance.ref.current) {
|
|
138
|
+
// Fallback `ref` if the element isn't created by `React.forwardRef`
|
|
139
|
+
Object.assign(instance.ref, {
|
|
140
|
+
current: document.querySelector(`[data-wv-id="${id}"]`),
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}, [instance, id])
|
|
144
|
+
|
|
145
|
+
if (__hidden) {
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const Element = elementRegistry.get(type)
|
|
150
|
+
|
|
151
|
+
if (Element?.Component) {
|
|
152
|
+
const Component = Element.Component
|
|
153
|
+
if (Component.displayName !== type) {
|
|
154
|
+
// Devtools nicety — assign once, not on every render.
|
|
155
|
+
Component.displayName = type
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
Component.$$typeof === Symbol.for('react.forward_ref') ||
|
|
159
|
+
reactVersion > REACT_VERSION_THRESHOLD
|
|
160
|
+
) {
|
|
161
|
+
processedRest.ref = instance.ref
|
|
162
|
+
}
|
|
163
|
+
const childItems = children?.length
|
|
164
|
+
? children
|
|
165
|
+
: childIds?.length
|
|
166
|
+
? childIds.map((cid: string) => ({ id: cid }))
|
|
167
|
+
: []
|
|
168
|
+
|
|
169
|
+
const renderChildren = childItems.map(
|
|
170
|
+
(item: { id: string }, index: number) => (
|
|
171
|
+
<ItemInstance id={item.id} key={`${item.id}-${index}`} parentId={id} />
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Component
|
|
177
|
+
{...processedRest}
|
|
178
|
+
children={renderChildren.length ? renderChildren : undefined}
|
|
179
|
+
className={clsx(processedRest.data?.className)}
|
|
180
|
+
data-wv-id={id}
|
|
181
|
+
data-wv-type={type}
|
|
182
|
+
key={id}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
instance._store.deleted = true
|
|
187
|
+
return null
|
|
188
|
+
})
|
|
189
|
+
ItemComponent.displayName = 'WeaverseComponent'
|
|
190
|
+
|
|
191
|
+
const ItemInstance = memo(
|
|
192
|
+
({ id, parentId }: { id: string; parentId: string }) => {
|
|
193
|
+
const instance = useItemInstance(id)
|
|
194
|
+
if (!instance) {
|
|
195
|
+
return <div style={{ display: 'none' }}>Item instance {id} not found</div>
|
|
196
|
+
}
|
|
197
|
+
return (
|
|
198
|
+
<WeaverseItemContext.Provider value={{ parentId, id }}>
|
|
199
|
+
<ItemComponent instance={instance} key={id} />
|
|
200
|
+
</WeaverseItemContext.Provider>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
ItemInstance.displayName = 'WeaverseInstanceProvider'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ElementCSS, Weaverse, WeaverseItemStore } from '@weaverse/core'
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
/** Common props accepted by React components rendered by Weaverse. */
|
|
5
|
+
export interface WeaverseElementProps extends Partial<ReactElement> {
|
|
6
|
+
/** React content rendered inside the element. */
|
|
7
|
+
children?: ReactElement | ReactElement[] | ReactNode | ReactNode[]
|
|
8
|
+
/** CSS class names applied to the element. */
|
|
9
|
+
className?: string
|
|
10
|
+
/** Responsive CSS values configured for the element. */
|
|
11
|
+
css?: ElementCSS
|
|
12
|
+
/** Weaverse item ID associated with the rendered element. */
|
|
13
|
+
'data-wv-id'?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Props for the root Weaverse React renderer. */
|
|
17
|
+
export type WeaverseRootPropsType = {
|
|
18
|
+
/** Weaverse runtime that owns the rendered content tree. */
|
|
19
|
+
context: Weaverse
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Props used to render a registered component from its item store. */
|
|
23
|
+
export type ItemComponentProps = {
|
|
24
|
+
/** Store instance containing the item's data and subscriptions. */
|
|
25
|
+
instance: InstanceType<typeof WeaverseItemStore>
|
|
26
|
+
}
|