@weaverse/hydrogen 6.1.3 → 6.2.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 +5 -6
- package/dist/index.mjs +1 -2
- package/dist/internal.js +18 -29
- package/dist/internal.mjs +3 -14
- package/dist/next-client-boundary.js +1088 -0
- package/dist/next-client-boundary.js.map +1 -0
- package/dist/next-client-boundary.mjs +1038 -0
- package/dist/next-client-boundary.mjs.map +1 -0
- package/dist/next-server.js +669 -13
- package/dist/next-server.js.map +1 -0
- package/dist/next-server.mjs +666 -2
- package/dist/next-server.mjs.map +1 -0
- package/dist/next.js +29 -43
- package/dist/next.mjs +5 -3
- package/dist/react-router.js +6 -7
- package/dist/react-router.mjs +2 -3
- package/dist/seo-B8u2r3rX.mjs +205 -0
- package/dist/seo-B8u2r3rX.mjs.map +1 -0
- package/dist/seo-D9A9uRQ4.js +264 -0
- package/dist/seo-D9A9uRQ4.js.map +1 -0
- package/dist/translate-Celryv8j.mjs +26 -0
- package/dist/translate-Celryv8j.mjs.map +1 -0
- package/dist/translate-Kt1BYL1n.js +37 -0
- package/dist/translate-Kt1BYL1n.js.map +1 -0
- package/dist/{nested-key-CR3-YMFi.js → translation-store-BmGxBALf.js} +46 -137
- package/dist/translation-store-BmGxBALf.js.map +1 -0
- package/dist/{nested-key-_k9aW_Wd.mjs → translation-store-JFs-4lvf.mjs} +35 -138
- package/dist/translation-store-JFs-4lvf.mjs.map +1 -0
- package/dist/types/hooks/translation-context.d.ts +2 -36
- package/dist/types/hooks/translation-context.d.ts.map +1 -1
- package/dist/types/next/client-boundary.d.ts +25 -0
- package/dist/types/next/client-boundary.d.ts.map +1 -0
- package/dist/types/next/client.d.ts +16 -0
- package/dist/types/next/client.d.ts.map +1 -0
- package/dist/types/next/hooks.d.ts +20 -0
- package/dist/types/next/hooks.d.ts.map +1 -0
- package/dist/types/next/index.d.ts +34 -0
- package/dist/types/next/index.d.ts.map +1 -0
- package/dist/types/next/item.d.ts +60 -0
- package/dist/types/next/item.d.ts.map +1 -0
- package/dist/types/next/loader.d.ts +25 -0
- package/dist/types/next/loader.d.ts.map +1 -0
- package/dist/types/next/main.d.ts +11 -0
- package/dist/types/next/main.d.ts.map +1 -0
- package/dist/types/next/provider.d.ts +85 -0
- package/dist/types/next/provider.d.ts.map +1 -0
- package/dist/types/next/registry.d.ts +15 -0
- package/dist/types/next/registry.d.ts.map +1 -0
- package/dist/types/next/renderer.d.ts +21 -0
- package/dist/types/next/renderer.d.ts.map +1 -0
- package/dist/types/next/request-info.d.ts +19 -0
- package/dist/types/next/request-info.d.ts.map +1 -0
- package/dist/types/next/revalidate-item.d.ts +82 -0
- package/dist/types/next/revalidate-item.d.ts.map +1 -0
- package/dist/types/next/root-provider.d.ts +65 -0
- package/dist/types/next/root-provider.d.ts.map +1 -0
- package/dist/types/next/runtime.d.ts +113 -0
- package/dist/types/next/runtime.d.ts.map +1 -0
- package/dist/types/next/seo.d.ts +55 -0
- package/dist/types/next/seo.d.ts.map +1 -0
- package/dist/types/next/server/configs.d.ts +27 -0
- package/dist/types/next/server/configs.d.ts.map +1 -0
- package/dist/types/next/server/normalize-page-url.d.ts +19 -0
- package/dist/types/next/server/normalize-page-url.d.ts.map +1 -0
- package/dist/types/next/server/revalidate-handler.d.ts +55 -0
- package/dist/types/next/server/revalidate-handler.d.ts.map +1 -0
- package/dist/types/next/server/server-client.d.ts +21 -0
- package/dist/types/next/server/server-client.d.ts.map +1 -0
- package/dist/types/next/server.d.ts +19 -0
- package/dist/types/next/server.d.ts.map +1 -0
- package/dist/types/next/studio-bridge.d.ts +16 -0
- package/dist/types/next/studio-bridge.d.ts.map +1 -0
- package/dist/types/next/studio-connect.d.ts +27 -0
- package/dist/types/next/studio-connect.d.ts.map +1 -0
- package/dist/types/next/studio-router.d.ts +50 -0
- package/dist/types/next/studio-router.d.ts.map +1 -0
- package/dist/types/next/studio-script-src.d.ts +12 -0
- package/dist/types/next/studio-script-src.d.ts.map +1 -0
- package/dist/types/next/theme-settings-store.d.ts +18 -0
- package/dist/types/next/theme-settings-store.d.ts.map +1 -0
- package/dist/types/next/translation-context.d.ts +109 -0
- package/dist/types/next/translation-context.d.ts.map +1 -0
- package/dist/types/next/types.d.ts +674 -0
- package/dist/types/next/types.d.ts.map +1 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts +34 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts.map +1 -0
- package/dist/types/next/utils.d.ts +21 -0
- package/dist/types/next/utils.d.ts.map +1 -0
- package/dist/types/next-server.d.ts +3 -1
- package/dist/types/next-server.d.ts.map +1 -1
- package/dist/types/next.d.ts +4 -2
- package/dist/types/next.d.ts.map +1 -1
- package/dist/types/utils/translate.d.ts +51 -0
- package/dist/types/utils/translate.d.ts.map +1 -0
- package/dist/{weaverse-client-C9IgIH7O.js → weaverse-client-BmceZc5q.js} +49 -771
- package/dist/weaverse-client-BmceZc5q.js.map +1 -0
- package/dist/{weaverse-client-D59mBW0j.mjs → weaverse-client-CXnM9ayH.mjs} +4 -720
- package/dist/weaverse-client-CXnM9ayH.mjs.map +1 -0
- package/package.json +13 -7
- package/src/hooks/translation-context.tsx +7 -84
- package/src/next/client-boundary.ts +51 -0
- package/src/next/client.ts +102 -0
- package/src/next/hooks.ts +52 -0
- package/src/next/index.ts +145 -0
- package/src/next/item.ts +167 -0
- package/src/next/loader.ts +135 -0
- package/src/next/main.tsx +45 -0
- package/src/next/provider.tsx +241 -0
- package/src/next/registry.ts +63 -0
- package/src/next/renderer.tsx +102 -0
- package/src/next/request-info.ts +124 -0
- package/src/next/revalidate-item.ts +211 -0
- package/src/next/root-provider.tsx +201 -0
- package/src/next/runtime.ts +559 -0
- package/src/next/seo.ts +157 -0
- package/src/next/server/configs.ts +178 -0
- package/src/next/server/normalize-page-url.ts +73 -0
- package/src/next/server/revalidate-handler.ts +357 -0
- package/src/next/server/server-client.ts +728 -0
- package/src/next/server.ts +67 -0
- package/src/next/studio-bridge.tsx +82 -0
- package/src/next/studio-connect.tsx +88 -0
- package/src/next/studio-router.ts +85 -0
- package/src/next/studio-script-src.ts +128 -0
- package/src/next/theme-settings-store.ts +47 -0
- package/src/next/translation-context.tsx +182 -0
- package/src/next/types.ts +764 -0
- package/src/next/use-weaverse-next-studio.tsx +106 -0
- package/src/next/utils.ts +48 -0
- package/src/next-server.ts +41 -1
- package/src/next.ts +48 -12
- package/src/utils/translate.ts +101 -0
- package/dist/internal.js.map +0 -1
- package/dist/internal.mjs.map +0 -1
- package/dist/nested-key-CR3-YMFi.js.map +0 -1
- package/dist/nested-key-_k9aW_Wd.mjs.map +0 -1
- package/dist/translation-store-4fAV771j.js +0 -69
- package/dist/translation-store-4fAV771j.js.map +0 -1
- package/dist/translation-store-BBrw0FOk.mjs +0 -58
- package/dist/translation-store-BBrw0FOk.mjs.map +0 -1
- package/dist/weaverse-client-C9IgIH7O.js.map +0 -1
- package/dist/weaverse-client-D59mBW0j.mjs.map +0 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { PageType } from '@weaverse/schema'
|
|
2
|
+
import { PageTypeSchema } from '@weaverse/schema'
|
|
3
|
+
import type {
|
|
4
|
+
WeaverseNextComponentData,
|
|
5
|
+
WeaverseNextI18n,
|
|
6
|
+
WeaverseNextRequestInfo,
|
|
7
|
+
} from './types'
|
|
8
|
+
|
|
9
|
+
/** Default App Router endpoint used for Studio item-loader revalidation. */
|
|
10
|
+
export const DEFAULT_REVALIDATE_ENDPOINT = '/api/weaverse/revalidate'
|
|
11
|
+
|
|
12
|
+
const MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Server-owned and transient controls stripped from the revalidation route
|
|
16
|
+
* context on both sides of the trust boundary. Compared case-insensitively, so
|
|
17
|
+
* every entry is stored lowercased. Client sanitization keeps normal traffic
|
|
18
|
+
* clean; the route handler repeats this deny-list as the security boundary.
|
|
19
|
+
*
|
|
20
|
+
* - Server-owned: a crafted `?weaverseProjectId=`/`?weaverseHost=` must never
|
|
21
|
+
* influence server config resolution (see `getWeaverseNextConfigs`), and
|
|
22
|
+
* credential-shaped params never ride along in route context.
|
|
23
|
+
* - Transient: draft-item and RSC transport params never describe route identity.
|
|
24
|
+
*/
|
|
25
|
+
export const DENIED_ROUTE_CONTEXT_PARAMS = [
|
|
26
|
+
'weaverseprojectid',
|
|
27
|
+
'weaversehost',
|
|
28
|
+
'weaverseapikey',
|
|
29
|
+
'weaverseapibase',
|
|
30
|
+
'weaversepublicapibase',
|
|
31
|
+
'weaverseversion',
|
|
32
|
+
'projectid',
|
|
33
|
+
'weaversedraftitem',
|
|
34
|
+
'__weaversedraftitem',
|
|
35
|
+
'_rsc',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/** i18n fields allowed to cross the browser boundary. */
|
|
39
|
+
export const ROUTE_CONTEXT_I18N_KEYS = [
|
|
40
|
+
'country',
|
|
41
|
+
'label',
|
|
42
|
+
'language',
|
|
43
|
+
'locale',
|
|
44
|
+
'pathPrefix',
|
|
45
|
+
] as const
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* JSON-safe route subset that may cross the browser boundary alongside the
|
|
49
|
+
* draft item. Never carries headers, cookies, auth, env, project ID, Studio
|
|
50
|
+
* host, API keys/bases, commerce clients, the request context, runtime, or
|
|
51
|
+
* client. `search` is canonical (preserves duplicate keys); the derived
|
|
52
|
+
* `queries` record is intentionally omitted.
|
|
53
|
+
*/
|
|
54
|
+
export interface WeaverseNextRevalidateRouteContext {
|
|
55
|
+
/** Route handle used for page assignment. */
|
|
56
|
+
handle?: string
|
|
57
|
+
/** Public locale and market identity allowed across the browser boundary. */
|
|
58
|
+
i18n?: Pick<
|
|
59
|
+
WeaverseNextI18n,
|
|
60
|
+
'country' | 'label' | 'language' | 'locale' | 'pathPrefix'
|
|
61
|
+
>
|
|
62
|
+
/** Weaverse page type resolved by the active route. */
|
|
63
|
+
pageType?: PageType
|
|
64
|
+
/** Canonical same-origin route pathname. */
|
|
65
|
+
pathname: string
|
|
66
|
+
/** Sanitized serialized query string, including the leading `?` when set. */
|
|
67
|
+
search: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Minimal runtime surface the in-place apply needs. Structural (not the
|
|
72
|
+
* concrete `WeaverseNextRuntime` class) so unit tests can drive it with a
|
|
73
|
+
* plain mock. `requestInfo` stays optional: a real `WeaverseNextRuntime`
|
|
74
|
+
* always supplies it, but when absent the client omits `routeContext` and the
|
|
75
|
+
* legacy `{ draftItem }` request is preserved.
|
|
76
|
+
*/
|
|
77
|
+
export interface RevalidateItemRuntimeLike {
|
|
78
|
+
/** Live item stores keyed by Weaverse item ID. */
|
|
79
|
+
itemInstances: Map<
|
|
80
|
+
string,
|
|
81
|
+
{
|
|
82
|
+
/** Merge refreshed loader data into the live item store. */
|
|
83
|
+
setData: (update: Record<string, unknown>) => unknown
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
/** Active route identity used to construct the optional revalidation payload. */
|
|
87
|
+
requestInfo?: WeaverseNextRequestInfo
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Strip denied (server-owned/transient) controls from a `search` string and
|
|
92
|
+
* re-serialize it. Preserves ordinary application queries, duplicate keys,
|
|
93
|
+
* encoded Unicode, and mode controls (`isDesignMode`, `isPreviewMode`,
|
|
94
|
+
* `__revisionId`, `sectionType`). Returns `''` or a `?`-prefixed string.
|
|
95
|
+
*/
|
|
96
|
+
export function sanitizeRouteContextSearch(search: string): string {
|
|
97
|
+
let params = new URLSearchParams(search)
|
|
98
|
+
let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS)
|
|
99
|
+
for (let key of [...new Set(params.keys())]) {
|
|
100
|
+
if (denied.has(key.toLowerCase())) {
|
|
101
|
+
params.delete(key)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let serialized = params.toString()
|
|
105
|
+
return serialized ? `?${serialized}` : ''
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Build the sanitized {@link WeaverseNextRevalidateRouteContext} from the
|
|
110
|
+
* active runtime's request info. Returns `undefined` without a usable pathname
|
|
111
|
+
* so the caller omits `routeContext` and keeps the legacy request. This is not
|
|
112
|
+
* the security boundary — the route handler re-validates and re-sanitizes.
|
|
113
|
+
*/
|
|
114
|
+
export function buildWeaverseNextRevalidateRouteContext(
|
|
115
|
+
requestInfo?: WeaverseNextRequestInfo
|
|
116
|
+
): WeaverseNextRevalidateRouteContext | undefined {
|
|
117
|
+
if (
|
|
118
|
+
!requestInfo ||
|
|
119
|
+
typeof requestInfo.pathname !== 'string' ||
|
|
120
|
+
!requestInfo.pathname
|
|
121
|
+
) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let routeContext: WeaverseNextRevalidateRouteContext = {
|
|
126
|
+
pathname: requestInfo.pathname,
|
|
127
|
+
search: sanitizeRouteContextSearch(requestInfo.search ?? ''),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let { i18n } = requestInfo
|
|
131
|
+
if (i18n && typeof i18n === 'object') {
|
|
132
|
+
let picked: Record<string, string> = {}
|
|
133
|
+
for (let key of ROUTE_CONTEXT_I18N_KEYS) {
|
|
134
|
+
let value = i18n[key]
|
|
135
|
+
if (typeof value === 'string') {
|
|
136
|
+
picked[key] = value
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (Object.keys(picked).length) {
|
|
140
|
+
routeContext.i18n = picked
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
requestInfo.pageType &&
|
|
146
|
+
PageTypeSchema.safeParse(requestInfo.pageType).success
|
|
147
|
+
) {
|
|
148
|
+
routeContext.pageType = requestInfo.pageType
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
typeof requestInfo.handle === 'string' &&
|
|
153
|
+
requestInfo.handle &&
|
|
154
|
+
requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH
|
|
155
|
+
) {
|
|
156
|
+
routeContext.handle = requestInfo.handle
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return routeContext
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Studio per-item loader revalidation: POST the unsaved draft item (and, when
|
|
164
|
+
* the runtime exposes route context, a sanitized `routeContext`) to the app's
|
|
165
|
+
* revalidate route (see `createWeaverseNextRevalidateHandler`) and apply the
|
|
166
|
+
* returned `loaderData` to the live item instance in place.
|
|
167
|
+
*
|
|
168
|
+
* No `router.refresh()` is involved, so the RSC tree is never re-applied — no
|
|
169
|
+
* page remount, no scroll reset, and the draft payload never touches the URL.
|
|
170
|
+
*
|
|
171
|
+
* Throws on any failure (missing route, non-OK response, unknown item) so the
|
|
172
|
+
* Builder Studio bridge can detect the miss and fall back to its legacy
|
|
173
|
+
* refresh-based revalidation for this window.
|
|
174
|
+
*/
|
|
175
|
+
export async function revalidateWeaverseNextItem(
|
|
176
|
+
runtime: RevalidateItemRuntimeLike,
|
|
177
|
+
draftItem: WeaverseNextComponentData,
|
|
178
|
+
endpoint: string = DEFAULT_REVALIDATE_ENDPOINT
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
let instance = runtime.itemInstances.get(draftItem.id)
|
|
181
|
+
if (!instance) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`[weaverse-next] No live item instance for "${draftItem.id}".`
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Snapshot route identity at revalidation start so a later navigation cannot
|
|
188
|
+
// retarget this in-flight request. Omit `routeContext` for legacy runtimes.
|
|
189
|
+
let routeContext = buildWeaverseNextRevalidateRouteContext(
|
|
190
|
+
runtime.requestInfo
|
|
191
|
+
)
|
|
192
|
+
let body = routeContext ? { draftItem, routeContext } : { draftItem }
|
|
193
|
+
|
|
194
|
+
let response = await fetch(endpoint, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify(body),
|
|
198
|
+
})
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`[weaverse-next] Revalidate endpoint responded ${response.status}.`
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let { loaderData } = (await response.json()) as { loaderData?: unknown }
|
|
206
|
+
// The item's settings were already mutated by the Studio edit; only the
|
|
207
|
+
// loader payload needs applying. `setData` merges and notifies subscribers,
|
|
208
|
+
// and the new `loaderData` identity is what Builder's revalidation poll
|
|
209
|
+
// observes for completion.
|
|
210
|
+
instance.setData({ loaderData: loaderData ?? null })
|
|
211
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
StudioSelectionStore,
|
|
5
|
+
StudioSelectionStoreContext,
|
|
6
|
+
} from '@weaverse/react'
|
|
7
|
+
import type { SchemaType } from '@weaverse/schema'
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
useEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
} from 'react'
|
|
16
|
+
import type { TranslateFunction } from '../utils/translate'
|
|
17
|
+
import { TranslationStore } from '../utils/translation-store'
|
|
18
|
+
import {
|
|
19
|
+
getSchemaStaticContent,
|
|
20
|
+
WeaverseNextContext,
|
|
21
|
+
type WeaverseNextContextValue,
|
|
22
|
+
} from './provider'
|
|
23
|
+
import { createWeaverseNextThemeSettingsStore } from './theme-settings-store'
|
|
24
|
+
import { TranslationProvider } from './translation-context'
|
|
25
|
+
import type {
|
|
26
|
+
WeaverseNextThemeSchema,
|
|
27
|
+
WeaverseNextThemeSettingsStore,
|
|
28
|
+
} from './types'
|
|
29
|
+
|
|
30
|
+
/** Root-scoped theme and translation stores adopted by route providers. */
|
|
31
|
+
export interface WeaverseNextRootContextValue {
|
|
32
|
+
/** Active-locale static-text overrides shared with route-level providers. */
|
|
33
|
+
merchantOverrides?: Record<string, unknown>
|
|
34
|
+
/** Theme default locale JSON shared with route-level providers. */
|
|
35
|
+
staticContent: Record<string, unknown>
|
|
36
|
+
/** Root-scoped Studio selection store shared with route-level providers. */
|
|
37
|
+
studioSelectionStore: StudioSelectionStore
|
|
38
|
+
/** Root-scoped external store shared with route-level providers. */
|
|
39
|
+
themeSettingsStore: WeaverseNextThemeSettingsStore
|
|
40
|
+
/** Single live design-mode static-text override store for the whole session. */
|
|
41
|
+
translationStore: TranslationStore
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Ambient root-scoped context. Route-level `WeaverseNextProvider` reads this to
|
|
46
|
+
* adopt the shared theme settings store instead of creating its own.
|
|
47
|
+
*/
|
|
48
|
+
export const WeaverseNextRootContext =
|
|
49
|
+
createContext<WeaverseNextRootContextValue | null>(null)
|
|
50
|
+
|
|
51
|
+
/** Root theme and translation data shared across the App Router tree. */
|
|
52
|
+
export interface WeaverseNextRootProviderProps {
|
|
53
|
+
/** Application subtree, including global and route-level components. */
|
|
54
|
+
children: ReactNode
|
|
55
|
+
/** Root-loaded theme settings (published-mode fetch, no design-mode context). */
|
|
56
|
+
initialThemeSettings?: Record<string, unknown>
|
|
57
|
+
/** Active-locale static-text overrides from `loadThemeSettings().merchantOverrides`. */
|
|
58
|
+
merchantOverrides?: Record<string, unknown>
|
|
59
|
+
/** Public environment values exposed to the theme-settings store. */
|
|
60
|
+
publicEnv?: Record<string, string | undefined>
|
|
61
|
+
/**
|
|
62
|
+
* Theme default locale JSON (`loadThemeSettings().staticContent`). Falls back
|
|
63
|
+
* to `themeSchema.i18n.staticContent` when omitted.
|
|
64
|
+
*/
|
|
65
|
+
staticContent?: Record<string, unknown>
|
|
66
|
+
/** Optional external translation function (host i18n); highest priority in `t()`. */
|
|
67
|
+
t?: TranslateFunction
|
|
68
|
+
/** Theme schema associated with the initial settings. */
|
|
69
|
+
themeSchema?: SchemaType | WeaverseNextThemeSchema
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Root-scoped client boundary that owns the single `WeaverseNextThemeSettingsStore`
|
|
74
|
+
* for the whole app session, matching Hydrogen's `withWeaverse()` root store.
|
|
75
|
+
* Mount once in `app/layout.tsx`, above `Header`/`Footer`/global modules and any
|
|
76
|
+
* route-level `WeaverseNextProvider`.
|
|
77
|
+
*
|
|
78
|
+
* Also renders `WeaverseNextContext` with a minimal value so `useThemeSettings()`
|
|
79
|
+
* works for components mounted directly under this provider, even without a
|
|
80
|
+
* route-level `WeaverseNextProvider` above them.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* <WeaverseNextRootProvider initialThemeSettings={theme.theme} themeSchema={theme.schema}>
|
|
85
|
+
* <Header />
|
|
86
|
+
* {children}
|
|
87
|
+
* <Footer />
|
|
88
|
+
* </WeaverseNextRootProvider>
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function WeaverseNextRootProvider(props: WeaverseNextRootProviderProps) {
|
|
92
|
+
let {
|
|
93
|
+
children,
|
|
94
|
+
initialThemeSettings,
|
|
95
|
+
merchantOverrides,
|
|
96
|
+
staticContent,
|
|
97
|
+
t,
|
|
98
|
+
themeSchema,
|
|
99
|
+
publicEnv,
|
|
100
|
+
} = props
|
|
101
|
+
|
|
102
|
+
// `useRef`, not `useMemo`: a parent re-render must never replace this store —
|
|
103
|
+
// it is the single instance Studio's `updateThemeSettings()` mutates in place.
|
|
104
|
+
let storeRef = useRef<WeaverseNextThemeSettingsStore | null>(null)
|
|
105
|
+
if (!storeRef.current) {
|
|
106
|
+
storeRef.current = createWeaverseNextThemeSettingsStore({
|
|
107
|
+
publicEnv,
|
|
108
|
+
schema: themeSchema,
|
|
109
|
+
settings: initialThemeSettings,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
let themeSettingsStore = storeRef.current
|
|
113
|
+
|
|
114
|
+
// Same `useRef` rationale as the theme store: one live static-text store for
|
|
115
|
+
// the whole session, which Builder's `updateStaticText()` RPC mutates in place
|
|
116
|
+
// via `runtime.internal.translationStore`. A parent re-render must never swap
|
|
117
|
+
// it, or design-mode overrides would be lost.
|
|
118
|
+
let translationStoreRef = useRef<TranslationStore | null>(null)
|
|
119
|
+
if (!translationStoreRef.current) {
|
|
120
|
+
translationStoreRef.current = new TranslationStore()
|
|
121
|
+
}
|
|
122
|
+
let translationStore = translationStoreRef.current
|
|
123
|
+
|
|
124
|
+
// One selection store for the session: the Studio bridge writes it through
|
|
125
|
+
// `runtime.internal.studioSelectionStore`, root UI reads it.
|
|
126
|
+
let [studioSelectionStore] = useState(() => new StudioSelectionStore())
|
|
127
|
+
|
|
128
|
+
// If `initialThemeSettings` changes after mount (e.g. root revalidation),
|
|
129
|
+
// update the existing store in place instead of replacing it. This is an
|
|
130
|
+
// effect because external store updates are side effects; the initial render
|
|
131
|
+
// is already seeded synchronously by store construction above.
|
|
132
|
+
let appliedThemeSettingsRef = useRef(initialThemeSettings)
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (
|
|
135
|
+
initialThemeSettings &&
|
|
136
|
+
appliedThemeSettingsRef.current !== initialThemeSettings
|
|
137
|
+
) {
|
|
138
|
+
themeSettingsStore.updateThemeSettings(initialThemeSettings)
|
|
139
|
+
appliedThemeSettingsRef.current = initialThemeSettings
|
|
140
|
+
}
|
|
141
|
+
}, [initialThemeSettings, themeSettingsStore])
|
|
142
|
+
|
|
143
|
+
// Prefer an explicit `staticContent` prop (from `loadThemeSettings()`); fall
|
|
144
|
+
// back to `themeSchema.i18n.staticContent`. Both resolve to referentially
|
|
145
|
+
// stable objects, so memoizing the context values below stays effective.
|
|
146
|
+
let resolvedStaticContent =
|
|
147
|
+
staticContent ?? getSchemaStaticContent(themeSchema)
|
|
148
|
+
|
|
149
|
+
// `themeSettingsStore` / `translationStore` are `useRef`-stable, so memoizing
|
|
150
|
+
// on them (rather than recreating these plain objects every render) keeps
|
|
151
|
+
// `useThemeSettings()` / `useTranslation()` consumers (Header, Footer, ...)
|
|
152
|
+
// from re-rendering on every root re-render.
|
|
153
|
+
let rootContextValue = useMemo<WeaverseNextRootContextValue>(
|
|
154
|
+
() => ({
|
|
155
|
+
merchantOverrides,
|
|
156
|
+
staticContent: resolvedStaticContent,
|
|
157
|
+
themeSettingsStore,
|
|
158
|
+
studioSelectionStore,
|
|
159
|
+
translationStore,
|
|
160
|
+
}),
|
|
161
|
+
[
|
|
162
|
+
merchantOverrides,
|
|
163
|
+
resolvedStaticContent,
|
|
164
|
+
themeSettingsStore,
|
|
165
|
+
studioSelectionStore,
|
|
166
|
+
translationStore,
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
let contextValue = useMemo<WeaverseNextContextValue>(
|
|
170
|
+
() => ({
|
|
171
|
+
merchantOverrides,
|
|
172
|
+
themeSettings: themeSettingsStore.getSnapshot(),
|
|
173
|
+
themeSettingsStore,
|
|
174
|
+
studioSelectionStore,
|
|
175
|
+
translationStore,
|
|
176
|
+
}),
|
|
177
|
+
[
|
|
178
|
+
merchantOverrides,
|
|
179
|
+
themeSettingsStore,
|
|
180
|
+
studioSelectionStore,
|
|
181
|
+
translationStore,
|
|
182
|
+
]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<WeaverseNextRootContext.Provider value={rootContextValue}>
|
|
187
|
+
<WeaverseNextContext.Provider value={contextValue}>
|
|
188
|
+
<StudioSelectionStoreContext.Provider value={studioSelectionStore}>
|
|
189
|
+
<TranslationProvider
|
|
190
|
+
merchantOverrides={merchantOverrides}
|
|
191
|
+
staticContent={resolvedStaticContent}
|
|
192
|
+
t={t}
|
|
193
|
+
translationStore={translationStore}
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
</TranslationProvider>
|
|
197
|
+
</StudioSelectionStoreContext.Provider>
|
|
198
|
+
</WeaverseNextContext.Provider>
|
|
199
|
+
</WeaverseNextRootContext.Provider>
|
|
200
|
+
)
|
|
201
|
+
}
|