@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.
Files changed (142) hide show
  1. package/dist/index.js +5 -6
  2. package/dist/index.mjs +1 -2
  3. package/dist/internal.js +18 -29
  4. package/dist/internal.mjs +3 -14
  5. package/dist/next-client-boundary.js +1088 -0
  6. package/dist/next-client-boundary.js.map +1 -0
  7. package/dist/next-client-boundary.mjs +1038 -0
  8. package/dist/next-client-boundary.mjs.map +1 -0
  9. package/dist/next-server.js +669 -13
  10. package/dist/next-server.js.map +1 -0
  11. package/dist/next-server.mjs +666 -2
  12. package/dist/next-server.mjs.map +1 -0
  13. package/dist/next.js +29 -43
  14. package/dist/next.mjs +5 -3
  15. package/dist/react-router.js +6 -7
  16. package/dist/react-router.mjs +2 -3
  17. package/dist/seo-B8u2r3rX.mjs +205 -0
  18. package/dist/seo-B8u2r3rX.mjs.map +1 -0
  19. package/dist/seo-D9A9uRQ4.js +264 -0
  20. package/dist/seo-D9A9uRQ4.js.map +1 -0
  21. package/dist/translate-Celryv8j.mjs +26 -0
  22. package/dist/translate-Celryv8j.mjs.map +1 -0
  23. package/dist/translate-Kt1BYL1n.js +37 -0
  24. package/dist/translate-Kt1BYL1n.js.map +1 -0
  25. package/dist/{nested-key-CR3-YMFi.js → translation-store-BmGxBALf.js} +46 -137
  26. package/dist/translation-store-BmGxBALf.js.map +1 -0
  27. package/dist/{nested-key-_k9aW_Wd.mjs → translation-store-JFs-4lvf.mjs} +35 -138
  28. package/dist/translation-store-JFs-4lvf.mjs.map +1 -0
  29. package/dist/types/hooks/translation-context.d.ts +2 -36
  30. package/dist/types/hooks/translation-context.d.ts.map +1 -1
  31. package/dist/types/next/client-boundary.d.ts +25 -0
  32. package/dist/types/next/client-boundary.d.ts.map +1 -0
  33. package/dist/types/next/client.d.ts +16 -0
  34. package/dist/types/next/client.d.ts.map +1 -0
  35. package/dist/types/next/hooks.d.ts +20 -0
  36. package/dist/types/next/hooks.d.ts.map +1 -0
  37. package/dist/types/next/index.d.ts +34 -0
  38. package/dist/types/next/index.d.ts.map +1 -0
  39. package/dist/types/next/item.d.ts +60 -0
  40. package/dist/types/next/item.d.ts.map +1 -0
  41. package/dist/types/next/loader.d.ts +25 -0
  42. package/dist/types/next/loader.d.ts.map +1 -0
  43. package/dist/types/next/main.d.ts +11 -0
  44. package/dist/types/next/main.d.ts.map +1 -0
  45. package/dist/types/next/provider.d.ts +85 -0
  46. package/dist/types/next/provider.d.ts.map +1 -0
  47. package/dist/types/next/registry.d.ts +15 -0
  48. package/dist/types/next/registry.d.ts.map +1 -0
  49. package/dist/types/next/renderer.d.ts +21 -0
  50. package/dist/types/next/renderer.d.ts.map +1 -0
  51. package/dist/types/next/request-info.d.ts +19 -0
  52. package/dist/types/next/request-info.d.ts.map +1 -0
  53. package/dist/types/next/revalidate-item.d.ts +82 -0
  54. package/dist/types/next/revalidate-item.d.ts.map +1 -0
  55. package/dist/types/next/root-provider.d.ts +65 -0
  56. package/dist/types/next/root-provider.d.ts.map +1 -0
  57. package/dist/types/next/runtime.d.ts +113 -0
  58. package/dist/types/next/runtime.d.ts.map +1 -0
  59. package/dist/types/next/seo.d.ts +55 -0
  60. package/dist/types/next/seo.d.ts.map +1 -0
  61. package/dist/types/next/server/configs.d.ts +27 -0
  62. package/dist/types/next/server/configs.d.ts.map +1 -0
  63. package/dist/types/next/server/normalize-page-url.d.ts +19 -0
  64. package/dist/types/next/server/normalize-page-url.d.ts.map +1 -0
  65. package/dist/types/next/server/revalidate-handler.d.ts +55 -0
  66. package/dist/types/next/server/revalidate-handler.d.ts.map +1 -0
  67. package/dist/types/next/server/server-client.d.ts +21 -0
  68. package/dist/types/next/server/server-client.d.ts.map +1 -0
  69. package/dist/types/next/server.d.ts +19 -0
  70. package/dist/types/next/server.d.ts.map +1 -0
  71. package/dist/types/next/studio-bridge.d.ts +16 -0
  72. package/dist/types/next/studio-bridge.d.ts.map +1 -0
  73. package/dist/types/next/studio-connect.d.ts +27 -0
  74. package/dist/types/next/studio-connect.d.ts.map +1 -0
  75. package/dist/types/next/studio-router.d.ts +50 -0
  76. package/dist/types/next/studio-router.d.ts.map +1 -0
  77. package/dist/types/next/studio-script-src.d.ts +12 -0
  78. package/dist/types/next/studio-script-src.d.ts.map +1 -0
  79. package/dist/types/next/theme-settings-store.d.ts +18 -0
  80. package/dist/types/next/theme-settings-store.d.ts.map +1 -0
  81. package/dist/types/next/translation-context.d.ts +109 -0
  82. package/dist/types/next/translation-context.d.ts.map +1 -0
  83. package/dist/types/next/types.d.ts +674 -0
  84. package/dist/types/next/types.d.ts.map +1 -0
  85. package/dist/types/next/use-weaverse-next-studio.d.ts +34 -0
  86. package/dist/types/next/use-weaverse-next-studio.d.ts.map +1 -0
  87. package/dist/types/next/utils.d.ts +21 -0
  88. package/dist/types/next/utils.d.ts.map +1 -0
  89. package/dist/types/next-server.d.ts +3 -1
  90. package/dist/types/next-server.d.ts.map +1 -1
  91. package/dist/types/next.d.ts +4 -2
  92. package/dist/types/next.d.ts.map +1 -1
  93. package/dist/types/utils/translate.d.ts +51 -0
  94. package/dist/types/utils/translate.d.ts.map +1 -0
  95. package/dist/{weaverse-client-C9IgIH7O.js → weaverse-client-BmceZc5q.js} +49 -771
  96. package/dist/weaverse-client-BmceZc5q.js.map +1 -0
  97. package/dist/{weaverse-client-D59mBW0j.mjs → weaverse-client-CXnM9ayH.mjs} +4 -720
  98. package/dist/weaverse-client-CXnM9ayH.mjs.map +1 -0
  99. package/package.json +13 -7
  100. package/src/hooks/translation-context.tsx +7 -84
  101. package/src/next/client-boundary.ts +51 -0
  102. package/src/next/client.ts +102 -0
  103. package/src/next/hooks.ts +52 -0
  104. package/src/next/index.ts +145 -0
  105. package/src/next/item.ts +167 -0
  106. package/src/next/loader.ts +135 -0
  107. package/src/next/main.tsx +45 -0
  108. package/src/next/provider.tsx +241 -0
  109. package/src/next/registry.ts +63 -0
  110. package/src/next/renderer.tsx +102 -0
  111. package/src/next/request-info.ts +124 -0
  112. package/src/next/revalidate-item.ts +211 -0
  113. package/src/next/root-provider.tsx +201 -0
  114. package/src/next/runtime.ts +559 -0
  115. package/src/next/seo.ts +157 -0
  116. package/src/next/server/configs.ts +178 -0
  117. package/src/next/server/normalize-page-url.ts +73 -0
  118. package/src/next/server/revalidate-handler.ts +357 -0
  119. package/src/next/server/server-client.ts +728 -0
  120. package/src/next/server.ts +67 -0
  121. package/src/next/studio-bridge.tsx +82 -0
  122. package/src/next/studio-connect.tsx +88 -0
  123. package/src/next/studio-router.ts +85 -0
  124. package/src/next/studio-script-src.ts +128 -0
  125. package/src/next/theme-settings-store.ts +47 -0
  126. package/src/next/translation-context.tsx +182 -0
  127. package/src/next/types.ts +764 -0
  128. package/src/next/use-weaverse-next-studio.tsx +106 -0
  129. package/src/next/utils.ts +48 -0
  130. package/src/next-server.ts +41 -1
  131. package/src/next.ts +48 -12
  132. package/src/utils/translate.ts +101 -0
  133. package/dist/internal.js.map +0 -1
  134. package/dist/internal.mjs.map +0 -1
  135. package/dist/nested-key-CR3-YMFi.js.map +0 -1
  136. package/dist/nested-key-_k9aW_Wd.mjs.map +0 -1
  137. package/dist/translation-store-4fAV771j.js +0 -69
  138. package/dist/translation-store-4fAV771j.js.map +0 -1
  139. package/dist/translation-store-BBrw0FOk.mjs +0 -58
  140. package/dist/translation-store-BBrw0FOk.mjs.map +0 -1
  141. package/dist/weaverse-client-C9IgIH7O.js.map +0 -1
  142. package/dist/weaverse-client-D59mBW0j.mjs.map +0 -1
@@ -0,0 +1,241 @@
1
+ 'use client'
2
+
3
+ import {
4
+ StudioSelectionStore,
5
+ StudioSelectionStoreContext,
6
+ } from '@weaverse/react'
7
+ import {
8
+ createContext,
9
+ type ReactNode,
10
+ useContext,
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 { WeaverseNextRootContext } from './root-provider'
19
+ import { createWeaverseNextThemeSettingsStore } from './theme-settings-store'
20
+ import { TranslationProvider } from './translation-context'
21
+ import type {
22
+ WeaverseNextClient,
23
+ WeaverseNextCommerceContext,
24
+ WeaverseNextThemeSettingsStore,
25
+ } from './types'
26
+
27
+ const EMPTY_THEME_SETTINGS: Record<string, unknown> = {}
28
+ const EMPTY_STATIC_CONTENT: Record<string, unknown> = {}
29
+
30
+ /** Read `themeSchema.i18n.staticContent` for the fallback (no-root) provider. */
31
+ export function getSchemaStaticContent(
32
+ schema: unknown
33
+ ): Record<string, unknown> {
34
+ let i18n = (schema as { i18n?: { staticContent?: Record<string, unknown> } })
35
+ ?.i18n
36
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT
37
+ }
38
+
39
+ /**
40
+ * Value carried by {@link WeaverseNextContext}. Holds the explicit data
41
+ * boundaries that replace React Router's `useLoaderData` /
42
+ * `useRouteLoaderData` inside sections.
43
+ */
44
+ export interface WeaverseNextContextValue {
45
+ /** Client used by the renderer and component-loader compatibility hooks. */
46
+ client?: WeaverseNextClient
47
+ /** App-provided commerce context for rendered components. */
48
+ commerce?: WeaverseNextCommerceContext
49
+ /** Active-locale static-text overrides, threaded into the runtime. */
50
+ merchantOverrides?: Record<string, unknown>
51
+ /** Page-scoped data exposed to rendered components. */
52
+ pageData?: unknown
53
+ /** Root-scoped data exposed to rendered components. */
54
+ rootData?: unknown
55
+ /** Studio selection store read by `useStudioSelection`, threaded into the runtime. */
56
+ studioSelectionStore: StudioSelectionStore
57
+ /** Current merged theme-setting values. */
58
+ themeSettings: Record<string, unknown>
59
+ /** External store that publishes live theme-setting updates. */
60
+ themeSettingsStore: WeaverseNextThemeSettingsStore
61
+ /** Live design-mode static-text override store, threaded into the runtime. */
62
+ translationStore: TranslationStore
63
+ }
64
+
65
+ /**
66
+ * Null sentinel default lets hooks detect usage outside the provider and throw
67
+ * a clear error instead of silently returning empty data.
68
+ */
69
+ export const WeaverseNextContext =
70
+ createContext<WeaverseNextContextValue | null>(null)
71
+
72
+ /** Explicit page, commerce, theme, and translation data for Next components. */
73
+ export interface WeaverseNextProviderProps {
74
+ /** React subtree that consumes the Weaverse Next contexts. */
75
+ children: ReactNode
76
+ /** Client used by the renderer and component-loader compatibility hooks. */
77
+ client?: WeaverseNextClient
78
+ /** App-provided commerce context (storefront/cart/customer). */
79
+ commerce?: WeaverseNextCommerceContext
80
+ /**
81
+ * Active-locale static-text overrides (nested, from `loadThemeSettings()`).
82
+ * Adopts the root provider's `merchantOverrides` when omitted.
83
+ */
84
+ merchantOverrides?: Record<string, unknown>
85
+ /** Explicit page/route commerce data (replaces section-level `useLoaderData`). */
86
+ pageData?: unknown
87
+ /** Explicit root/global data (replaces `useRouteLoaderData('root')`). */
88
+ rootData?: unknown
89
+ /**
90
+ * Theme default locale JSON (`loadThemeSettings().staticContent`). Adopts the
91
+ * root provider's `staticContent` when omitted, then falls back to
92
+ * `client.themeSchema.i18n.staticContent`.
93
+ */
94
+ staticContent?: Record<string, unknown>
95
+ /** Optional external translation function (host i18n); highest priority in `t()`. */
96
+ t?: TranslateFunction
97
+ /** Theme settings override; falls back to `client.themeSettings`. */
98
+ themeSettings?: Record<string, unknown>
99
+ }
100
+
101
+ /**
102
+ * Client boundary that supplies explicit root/page/commerce data to Weaverse
103
+ * sections. Next-native replacement for `withWeaverse` + route loader data —
104
+ * no React Router route matches are emulated.
105
+ *
106
+ * In App Router, create/pass `client` only from a client component wrapper. A
107
+ * `WeaverseNextClient` contains functions and component references, so it is
108
+ * not serializable across a Server Component → Client Component boundary.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * 'use client'
113
+ *
114
+ * <WeaverseNextProvider client={client} rootData={rootData} pageData={pageData} commerce={commerce}>
115
+ * <WeaverseNextRenderer />
116
+ * </WeaverseNextProvider>
117
+ * ```
118
+ */
119
+ export function WeaverseNextProvider(props: WeaverseNextProviderProps) {
120
+ let { children, client, rootData, pageData, commerce, themeSettings } = props
121
+ let rootContext = useContext(WeaverseNextRootContext)
122
+
123
+ let themeSettingsValue =
124
+ themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS
125
+
126
+ // Fallback translation store when no `WeaverseNextRootProvider` is mounted.
127
+ // Otherwise the ambient root store is adopted, so exactly one live static-text
128
+ // store backs Builder's `updateStaticText()` RPC per page load — mirroring the
129
+ // theme-settings store adoption below. `useRef`, not `useMemo`: React may
130
+ // evict memoized values, but this store must survive every re-render so
131
+ // design-mode overrides already applied by Builder are never dropped.
132
+ let ownTranslationStoreRef = useRef<TranslationStore | null>(null)
133
+ if (!(rootContext || ownTranslationStoreRef.current)) {
134
+ ownTranslationStoreRef.current = new TranslationStore()
135
+ }
136
+ let translationStore =
137
+ rootContext?.translationStore ??
138
+ (ownTranslationStoreRef.current as TranslationStore)
139
+
140
+ // Same adoption rule as the translation store: one selection store per page
141
+ // load, so the Studio bridge and root-level UI observe the same instance.
142
+ // The route's own store is only used when no root provider is mounted.
143
+ let [ownSelectionStore] = useState(() => new StudioSelectionStore())
144
+ let studioSelectionStore =
145
+ rootContext?.studioSelectionStore ?? ownSelectionStore
146
+
147
+ // Static content / merchant overrides feeding `useTranslation()`. Precedence:
148
+ // explicit prop → ambient root value → (static content only) schema default.
149
+ let staticContent =
150
+ props.staticContent ??
151
+ rootContext?.staticContent ??
152
+ getSchemaStaticContent(client?.themeSchema)
153
+ let merchantOverrides =
154
+ props.merchantOverrides ?? rootContext?.merchantOverrides
155
+
156
+ // Fallback store for apps that don't mount `WeaverseNextRootProvider`. Not
157
+ // created at all when a root store is present, so exactly one
158
+ // `WeaverseNextThemeSettingsStore` exists per page load either way.
159
+ let ownThemeSettingsStore = useMemo(() => {
160
+ if (rootContext) {
161
+ return null
162
+ }
163
+ return createWeaverseNextThemeSettingsStore({
164
+ schema: client?.themeSchema,
165
+ settings: themeSettingsValue,
166
+ })
167
+ }, [rootContext, client?.themeSchema, themeSettingsValue])
168
+
169
+ let themeSettingsStore =
170
+ rootContext?.themeSettingsStore ??
171
+ (ownThemeSettingsStore as WeaverseNextThemeSettingsStore)
172
+
173
+ // When adopting the root store, route-supplied theme data augments it in
174
+ // place (merge), never replaces it — Studio and root `Header`/`Footer` must
175
+ // keep observing the same instance. Guarded so a stable `themeSettingsValue`
176
+ // across re-renders doesn't re-merge/re-notify every render.
177
+ //
178
+ // This runs in an effect, not render: mutating a store shared outside this
179
+ // component's subtree is a side effect, and render functions must stay
180
+ // pure under concurrent React (retried/discarded renders, StrictMode
181
+ // double-invocation). The root's initial theme is authoritative for SSR;
182
+ // a route's `themeSettings`/`client.themeSettings` override applies after
183
+ // client mount instead of racing a render-phase store mutation. Apps that
184
+ // don't mount `WeaverseNextRootProvider` are unaffected — `themeSettings`
185
+ // still renders synchronously on SSR via `ownThemeSettingsStore` below.
186
+ let mergedIntoRootRef = useRef<Record<string, unknown> | undefined>(undefined)
187
+ useEffect(() => {
188
+ if (
189
+ rootContext &&
190
+ themeSettingsValue !== EMPTY_THEME_SETTINGS &&
191
+ mergedIntoRootRef.current !== themeSettingsValue
192
+ ) {
193
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue)
194
+ mergedIntoRootRef.current = themeSettingsValue
195
+ }
196
+ }, [rootContext, themeSettingsValue])
197
+
198
+ // Do not depend on `themeSettingsValue` here. In fallback mode it is already
199
+ // represented by a new `themeSettingsStore`; in root-adoption mode the
200
+ // post-mount merge effect notifies `useThemeSettings()` subscribers. Including
201
+ // it would recreate this context with a stale root snapshot before the effect
202
+ // fires, causing an avoidable extra render.
203
+ let value = useMemo<WeaverseNextContextValue>(
204
+ () => ({
205
+ client,
206
+ rootData,
207
+ pageData,
208
+ commerce: commerce ?? client?.commerce,
209
+ merchantOverrides,
210
+ themeSettings: themeSettingsStore.getSnapshot(),
211
+ themeSettingsStore,
212
+ studioSelectionStore,
213
+ translationStore,
214
+ }),
215
+ [
216
+ client,
217
+ rootData,
218
+ pageData,
219
+ commerce,
220
+ merchantOverrides,
221
+ themeSettingsStore,
222
+ studioSelectionStore,
223
+ translationStore,
224
+ ]
225
+ )
226
+
227
+ return (
228
+ <WeaverseNextContext.Provider value={value}>
229
+ <StudioSelectionStoreContext.Provider value={studioSelectionStore}>
230
+ <TranslationProvider
231
+ merchantOverrides={merchantOverrides}
232
+ staticContent={staticContent}
233
+ t={props.t}
234
+ translationStore={translationStore}
235
+ >
236
+ {children}
237
+ </TranslationProvider>
238
+ </StudioSelectionStoreContext.Provider>
239
+ </WeaverseNextContext.Provider>
240
+ )
241
+ }
@@ -0,0 +1,63 @@
1
+ import { Weaverse } from '@weaverse/react'
2
+ import { ensureNextItemConstructor } from './item'
3
+ import { defaultMainComponent } from './main'
4
+ import type { WeaverseNextComponent } from './types'
5
+
6
+ const MAIN_TYPE = 'main'
7
+
8
+ /**
9
+ * Component types already pushed into the element registry. Registration is
10
+ * idempotent by `schema.type`, so callers may pass fresh arrays per request
11
+ * without causing repeated global registry writes.
12
+ */
13
+ const registeredComponentTypes = new Set<string>()
14
+
15
+ /**
16
+ * Register Next adapter components with the shared Weaverse element registry.
17
+ *
18
+ * This mutates global Weaverse runtime state, so callers should run it while
19
+ * creating their app/client wiring, not from a React render body.
20
+ *
21
+ * A default `main` root component is always registered first so consumers don't
22
+ * have to register one for basic page-tree rendering. If the consumer registers
23
+ * their own `main`, that one is preferred: we only prepend the default when the
24
+ * provided components don't already include a `main`, so a user-provided `main`
25
+ * never collides with (or is shadowed by) the default.
26
+ */
27
+ export function registerWeaverseNextComponents(
28
+ components: WeaverseNextComponent[]
29
+ ) {
30
+ ensureNextItemConstructor()
31
+
32
+ let hasUserMain = components.some(
33
+ (component) => component.schema?.type === MAIN_TYPE
34
+ )
35
+ let effectiveComponents = hasUserMain
36
+ ? components
37
+ : [defaultMainComponent, ...components]
38
+
39
+ for (let component of effectiveComponents) {
40
+ let type = component.schema.type
41
+ let element = {
42
+ type,
43
+ Component: component.default,
44
+ schema: component.schema,
45
+ loader: component.loader,
46
+ }
47
+
48
+ if (type === MAIN_TYPE && hasUserMain) {
49
+ // `main` is the only built-in default. If a consumer registers their own
50
+ // root component after the default was already installed by another
51
+ // client/test/request, prefer the consumer version by replacing the global
52
+ // registry entry explicitly.
53
+ Weaverse.elementRegistry.set(type, element)
54
+ registeredComponentTypes.add(type)
55
+ continue
56
+ }
57
+
58
+ if (type && !registeredComponentTypes.has(type)) {
59
+ Weaverse.registerElement(element)
60
+ registeredComponentTypes.add(type)
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,102 @@
1
+ 'use client'
2
+
3
+ import { WeaverseRoot } from '@weaverse/react'
4
+ import { memo, useContext, useEffect, useLayoutEffect, useMemo } from 'react'
5
+ import type { JSX } from 'react/jsx-runtime'
6
+ import { WeaverseNextContext } from './provider'
7
+ import { createWeaverseNextRuntime } from './runtime'
8
+ import type {
9
+ WeaverseNextClient,
10
+ WeaverseNextLoaderData,
11
+ WeaverseNextPageData,
12
+ } from './types'
13
+ import { WeaverseNextStudio } from './use-weaverse-next-studio'
14
+
15
+ const EMPTY_DATA_CONTEXT: Record<string, unknown> = {}
16
+ const useIsomorphicLayoutEffect =
17
+ typeof window === 'undefined' ? useEffect : useLayoutEffect
18
+
19
+ function getRenderablePage(
20
+ data: WeaverseNextLoaderData | null | undefined
21
+ ): (WeaverseNextPageData & { rootId: string }) | null {
22
+ let page = data?.page
23
+ if (!page) {
24
+ return null
25
+ }
26
+ let rootId =
27
+ page.rootId ??
28
+ page.items.find((item) => item.type === 'main')?.id ??
29
+ page.items[0]?.id ??
30
+ page.id
31
+ return { ...page, rootId }
32
+ }
33
+
34
+ /** Page payload, client, and connector data accepted by the Next renderer. */
35
+ export interface WeaverseNextRendererProps {
36
+ /** Client to render from. Defaults to the provider's client. */
37
+ client?: WeaverseNextClient
38
+ /** Page payload to render. Defaults to `client.data`. */
39
+ data?: WeaverseNextLoaderData | null
40
+ /** Data-connector context for `{{...}}` template replacement. */
41
+ dataContext?: Record<string, unknown>
42
+ }
43
+
44
+ /**
45
+ * Render a serialized Weaverse page tree through `@weaverse/react` primitives.
46
+ *
47
+ * Reads the client/page data supplied by {@link WeaverseNextProvider} when no
48
+ * props are passed, or accepts an explicit `client` + `data` for standalone use. Builds a
49
+ * framework-neutral `Weaverse` core instance (no React Router hooks) and hands
50
+ * it to the shared `WeaverseRoot`.
51
+ */
52
+ export const WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(
53
+ props: WeaverseNextRendererProps
54
+ ): JSX.Element | null {
55
+ let context = useContext(WeaverseNextContext)
56
+ let client = props.client ?? context?.client
57
+ let data = props.data ?? client?.data ?? null
58
+ let dataContext =
59
+ props.dataContext ?? client?.dataContext ?? EMPTY_DATA_CONTEXT
60
+
61
+ let page = useMemo(() => getRenderablePage(data), [data])
62
+
63
+ let weaverse = useMemo(() => {
64
+ if (!page) {
65
+ return null
66
+ }
67
+
68
+ return createWeaverseNextRuntime({
69
+ client,
70
+ data: { ...(data as WeaverseNextLoaderData), page },
71
+ dataContext,
72
+ merchantOverrides: context?.merchantOverrides,
73
+ themeSettingsStore: context?.themeSettingsStore,
74
+ studioSelectionStore: context?.studioSelectionStore,
75
+ translationStore: context?.translationStore,
76
+ })
77
+ }, [
78
+ client,
79
+ page,
80
+ data,
81
+ dataContext,
82
+ context?.merchantOverrides,
83
+ context?.themeSettingsStore,
84
+ context?.studioSelectionStore,
85
+ context?.translationStore,
86
+ ])
87
+
88
+ useIsomorphicLayoutEffect(() => {
89
+ weaverse?.flushRenderPhaseUpdates()
90
+ }, [weaverse])
91
+
92
+ if (!weaverse) {
93
+ return null
94
+ }
95
+
96
+ return (
97
+ <>
98
+ <WeaverseRoot context={weaverse} />
99
+ <WeaverseNextStudio runtime={weaverse} />
100
+ </>
101
+ )
102
+ })
@@ -0,0 +1,124 @@
1
+ import type {
2
+ WeaverseNextRequestContext,
3
+ WeaverseNextRequestInfo,
4
+ } from './types'
5
+
6
+ function toUrl(value: string | URL): URL {
7
+ return typeof value === 'string' ? new URL(value, 'http://localhost') : value
8
+ }
9
+
10
+ function getSearchParamsFromContext(
11
+ context?: WeaverseNextRequestContext
12
+ ): URLSearchParams {
13
+ if (context?.searchParams) {
14
+ return new URLSearchParams(context.searchParams)
15
+ }
16
+
17
+ if (context?.url) {
18
+ let url = toUrl(context.url)
19
+ return new URLSearchParams(url.searchParams)
20
+ }
21
+
22
+ return new URLSearchParams()
23
+ }
24
+
25
+ /**
26
+ * Put the request's market prefix (`i18n.pathPrefix`, e.g. `/de-de`) on a
27
+ * pathname that does not carry it yet.
28
+ *
29
+ * A Next app builds its request context from the route path, and with
30
+ * locale-segmented routes that path is usually unprefixed. Studio's address
31
+ * bar follows `requestInfo.pathname`, so without the prefix it snaps back to
32
+ * the default market after every navigation. A path that already carries the
33
+ * prefix, and the default market's empty prefix, pass through unchanged.
34
+ */
35
+ export function withPathPrefix(
36
+ pathname: string,
37
+ i18n?: WeaverseNextRequestContext['i18n']
38
+ ): string {
39
+ let raw = typeof i18n?.pathPrefix === 'string' ? i18n.pathPrefix : ''
40
+ // Split rather than regex-trim: `pathPrefix` is caller input, and an anchored
41
+ // `/\/+$/` backtracks polynomially on long runs of slashes.
42
+ let segments = raw.split('/').filter(Boolean)
43
+ let prefix = segments.length > 0 ? `/${segments.join('/')}` : ''
44
+ if (
45
+ prefix === '' ||
46
+ pathname === prefix ||
47
+ pathname.startsWith(`${prefix}/`)
48
+ ) {
49
+ return pathname
50
+ }
51
+ return pathname === '/' ? prefix : `${prefix}${pathname}`
52
+ }
53
+
54
+ function getPathnameFromContext(context?: WeaverseNextRequestContext): string {
55
+ if (context?.pathname) {
56
+ return withPathPrefix(context.pathname, context.i18n)
57
+ }
58
+
59
+ if (context?.url) {
60
+ let url = toUrl(context.url)
61
+ return withPathPrefix(url.pathname || '/', context.i18n)
62
+ }
63
+
64
+ return '/'
65
+ }
66
+
67
+ function coerceQueryValue(value: string): string | boolean {
68
+ if (value === 'true') {
69
+ return true
70
+ }
71
+ if (value === 'false') {
72
+ return false
73
+ }
74
+ return value
75
+ }
76
+
77
+ function toQueryRecord(
78
+ searchParams: URLSearchParams
79
+ ): Record<string, string | boolean> {
80
+ let queries: Record<string, string | boolean> = {}
81
+ for (let [key, value] of searchParams.entries()) {
82
+ queries[key] = coerceQueryValue(value)
83
+ }
84
+ return queries
85
+ }
86
+
87
+ const TRANSIENT_STUDIO_PARAMS = new Set([
88
+ 'weaverseDraftItem',
89
+ // Legacy name from the original design-mode draft-item spike. Keep it out of
90
+ // runtime requestInfo too, in case an older Builder bundle is in front of a
91
+ // newer Next storefront.
92
+ '__weaverseDraftItem',
93
+ ])
94
+
95
+ function getRequestInfoSearchParams(
96
+ context?: WeaverseNextRequestContext
97
+ ): URLSearchParams {
98
+ let searchParams = new URLSearchParams(getSearchParamsFromContext(context))
99
+ for (let key of TRANSIENT_STUDIO_PARAMS) {
100
+ searchParams.delete(key)
101
+ }
102
+ return searchParams
103
+ }
104
+
105
+ /**
106
+ * Build the Hydrogen-like request info shape consumed by the Studio bridge.
107
+ * Duplicate query keys keep the last value, matching `URLSearchParams` iteration
108
+ * assignment and Hydrogen's practical query-object behavior.
109
+ */
110
+ export function buildWeaverseNextRequestInfo(
111
+ context?: WeaverseNextRequestContext
112
+ ): WeaverseNextRequestInfo {
113
+ let searchParams = getRequestInfoSearchParams(context)
114
+ let search = searchParams.toString()
115
+
116
+ return {
117
+ pathname: getPathnameFromContext(context),
118
+ search: search ? `?${search}` : '',
119
+ queries: toQueryRecord(searchParams),
120
+ ...(context?.i18n ? { i18n: context.i18n } : {}),
121
+ ...(context?.pageType ? { pageType: context.pageType } : {}),
122
+ ...(context?.handle ? { handle: context.handle } : {}),
123
+ }
124
+ }