@weaverse/hydrogen 6.1.2 → 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 +33 -41
- 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 -3
- 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 +51 -15
- 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,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only Weaverse APIs for Next.js App Router server components and route
|
|
3
|
+
* handlers.
|
|
4
|
+
*
|
|
5
|
+
* When migrating from Hydrogen's `context.weaverse`, create a request-scoped
|
|
6
|
+
* client with {@link createWeaverseNextServerClient}, then call `loadPage()` or
|
|
7
|
+
* `loadThemeSettings()`. Keep rendering providers and components imported from
|
|
8
|
+
* the root `@weaverse/hydrogen/next` entrypoint.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type { WeaverseNextRevalidateRouteContext } from './revalidate-item'
|
|
14
|
+
export {
|
|
15
|
+
formatWeaverseNextSeoMetadata,
|
|
16
|
+
getWeaverseNextSeoMetadata,
|
|
17
|
+
type NextOpenGraph,
|
|
18
|
+
type NextTwitter,
|
|
19
|
+
type WeaverseNextSeoInput,
|
|
20
|
+
type WeaverseNextSeoMetadata,
|
|
21
|
+
type WeaverseNextSeoPageContainer,
|
|
22
|
+
} from './seo'
|
|
23
|
+
export {
|
|
24
|
+
getWeaverseNextConfigs,
|
|
25
|
+
type ResolveConfigsOptions,
|
|
26
|
+
} from './server/configs'
|
|
27
|
+
export {
|
|
28
|
+
normalizeNextPageUrl,
|
|
29
|
+
resolveRequestUrl,
|
|
30
|
+
} from './server/normalize-page-url'
|
|
31
|
+
export {
|
|
32
|
+
createWeaverseNextRevalidateHandler,
|
|
33
|
+
type WeaverseNextRevalidateHandler,
|
|
34
|
+
type WeaverseNextRevalidateHandlerConfig,
|
|
35
|
+
type WeaverseNextRevalidateRequestBody,
|
|
36
|
+
} from './server/revalidate-handler'
|
|
37
|
+
export { createWeaverseNextServerClient } from './server/server-client'
|
|
38
|
+
export type {
|
|
39
|
+
WeaverseNextBaseConfigs,
|
|
40
|
+
WeaverseNextCacheConfig,
|
|
41
|
+
WeaverseNextClient,
|
|
42
|
+
WeaverseNextCommerceContext,
|
|
43
|
+
WeaverseNextComponent,
|
|
44
|
+
WeaverseNextComponentData,
|
|
45
|
+
WeaverseNextComponentLoaderArgs,
|
|
46
|
+
WeaverseNextComponentProps,
|
|
47
|
+
WeaverseNextConfigs,
|
|
48
|
+
WeaverseNextCustomPageEntry,
|
|
49
|
+
WeaverseNextFetchCustomPagesOptions,
|
|
50
|
+
WeaverseNextFetchOptions,
|
|
51
|
+
WeaverseNextI18n,
|
|
52
|
+
WeaverseNextLoaderData,
|
|
53
|
+
WeaverseNextLoadPageInput,
|
|
54
|
+
WeaverseNextPageAssignment,
|
|
55
|
+
WeaverseNextPageData,
|
|
56
|
+
WeaverseNextProjectId,
|
|
57
|
+
WeaverseNextRequestContext,
|
|
58
|
+
WeaverseNextServerClient,
|
|
59
|
+
WeaverseNextServerClientConfig,
|
|
60
|
+
WeaverseNextServerLoadPageInput,
|
|
61
|
+
WeaverseNextStorefront,
|
|
62
|
+
WeaverseNextThemeSchema,
|
|
63
|
+
WeaverseNextThemeSchemaGroup,
|
|
64
|
+
WeaverseNextThemeSchemaInput,
|
|
65
|
+
WeaverseNextThemeSettingsOptions,
|
|
66
|
+
WeaverseNextThemeSettingsResponse,
|
|
67
|
+
} from './types'
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
bindWeaverseNextStudioRuntime,
|
|
6
|
+
type WeaverseNextRuntime,
|
|
7
|
+
} from './runtime'
|
|
8
|
+
import type { WeaverseNextRuntimeInternal } from './types'
|
|
9
|
+
|
|
10
|
+
const STUDIO_BIND_RETRY_INTERVAL_MS = 50
|
|
11
|
+
const STUDIO_BIND_MAX_ATTEMPTS = 100
|
|
12
|
+
|
|
13
|
+
/** Runtime and host callbacks attached to the Builder Studio bridge. */
|
|
14
|
+
export interface WeaverseNextStudioBridgeProps {
|
|
15
|
+
/** Preview navigation callback. */
|
|
16
|
+
navigate?: WeaverseNextRuntimeInternal['navigate']
|
|
17
|
+
/** Whole-route refresh callback used as a legacy fallback. */
|
|
18
|
+
revalidate?: WeaverseNextRuntimeInternal['revalidate']
|
|
19
|
+
/** Preferred per-item loader revalidation callback. */
|
|
20
|
+
revalidateItem?: WeaverseNextRuntimeInternal['revalidateItem']
|
|
21
|
+
/** Browser runtime to initialize or refresh in Studio. */
|
|
22
|
+
runtime: WeaverseNextRuntime
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Page-level Studio runtime binder. */
|
|
26
|
+
export function WeaverseNextStudioBridge(props: WeaverseNextStudioBridgeProps) {
|
|
27
|
+
let { runtime, navigate, revalidate, revalidateItem } = props
|
|
28
|
+
let lastRuntimeRef = useRef(runtime)
|
|
29
|
+
let lastRuntimeDataRef = useRef(runtime.data)
|
|
30
|
+
let lastRequestInfoRef = useRef(runtime.requestInfo)
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (navigate) {
|
|
34
|
+
runtime.internal.navigate = navigate
|
|
35
|
+
}
|
|
36
|
+
if (revalidate) {
|
|
37
|
+
runtime.internal.revalidate = revalidate
|
|
38
|
+
}
|
|
39
|
+
if (revalidateItem) {
|
|
40
|
+
runtime.internal.revalidateItem = revalidateItem
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let attempts = 0
|
|
48
|
+
let interval = window.setInterval(() => {
|
|
49
|
+
attempts += 1
|
|
50
|
+
if (
|
|
51
|
+
bindWeaverseNextStudioRuntime(runtime) ||
|
|
52
|
+
attempts >= STUDIO_BIND_MAX_ATTEMPTS
|
|
53
|
+
) {
|
|
54
|
+
window.clearInterval(interval)
|
|
55
|
+
}
|
|
56
|
+
}, STUDIO_BIND_RETRY_INTERVAL_MS)
|
|
57
|
+
|
|
58
|
+
return () => window.clearInterval(interval)
|
|
59
|
+
}, [runtime, navigate, revalidate, revalidateItem])
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (lastRuntimeRef.current !== runtime) {
|
|
63
|
+
lastRuntimeRef.current = runtime
|
|
64
|
+
lastRuntimeDataRef.current = runtime.data
|
|
65
|
+
lastRequestInfoRef.current = runtime.requestInfo
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
lastRuntimeDataRef.current === runtime.data &&
|
|
71
|
+
lastRequestInfoRef.current === runtime.requestInfo
|
|
72
|
+
) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lastRuntimeDataRef.current = runtime.data
|
|
77
|
+
lastRequestInfoRef.current = runtime.requestInfo
|
|
78
|
+
bindWeaverseNextStudioRuntime(runtime)
|
|
79
|
+
}, [runtime, runtime.data, runtime.requestInfo])
|
|
80
|
+
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { resolveWeaverseNextStudioScriptSrc } from './studio-script-src'
|
|
5
|
+
import type { WeaverseNextRequestContext } from './types'
|
|
6
|
+
|
|
7
|
+
const loadedScriptSrcs = new Set<string>()
|
|
8
|
+
|
|
9
|
+
function loadScript(src: string) {
|
|
10
|
+
if (typeof document === 'undefined') {
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (loadedScriptSrcs.has(src)) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let existing = document.querySelector(`script[src="${src}"]`)
|
|
19
|
+
if (existing) {
|
|
20
|
+
loadedScriptSrcs.add(src)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let script = document.createElement('script')
|
|
25
|
+
script.async = true
|
|
26
|
+
script.src = src
|
|
27
|
+
document.head.appendChild(script)
|
|
28
|
+
loadedScriptSrcs.add(src)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Trust context used while loading the Studio bridge script. */
|
|
32
|
+
export interface LoadWeaverseNextStudioScriptOptions {
|
|
33
|
+
/** Storefront hostname; required to permit loopback Studio origins. */
|
|
34
|
+
storefrontHostname?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve and append the Next Studio bridge script for the given request
|
|
39
|
+
* context, deduping by src. SSR-safe (no-op without `document`) so it can run
|
|
40
|
+
* unconditionally from a root layout/error boundary before any page tree or
|
|
41
|
+
* runtime exists — e.g. App Router `not-found.tsx` / `error.tsx` routes.
|
|
42
|
+
*/
|
|
43
|
+
export function loadWeaverseNextStudioScript(
|
|
44
|
+
context?: WeaverseNextRequestContext,
|
|
45
|
+
options: LoadWeaverseNextStudioScriptOptions = {}
|
|
46
|
+
) {
|
|
47
|
+
let resolvedContext = context
|
|
48
|
+
if (!resolvedContext && typeof window !== 'undefined') {
|
|
49
|
+
resolvedContext = {
|
|
50
|
+
searchParams: new URLSearchParams(window.location.search),
|
|
51
|
+
url: window.location.href,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
|
|
56
|
+
storefrontHostname:
|
|
57
|
+
options.storefrontHostname ??
|
|
58
|
+
(typeof window === 'undefined' ? undefined : window.location.hostname),
|
|
59
|
+
})
|
|
60
|
+
if (src) {
|
|
61
|
+
loadScript(src)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Props for the root-level Studio script connector. */
|
|
66
|
+
export interface WeaverseNextStudioConnectProps {
|
|
67
|
+
/** Explicit request context used to resolve Studio mode and host. */
|
|
68
|
+
context?: WeaverseNextRequestContext
|
|
69
|
+
/** Storefront hostname; required to permit loopback Studio origins. */
|
|
70
|
+
storefrontHostname?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Root-level Studio script connector for Next client boundaries. Mount once
|
|
75
|
+
* near the app root (e.g. root layout) so it also covers routes that never
|
|
76
|
+
* render `WeaverseNextRenderer`, such as `not-found.tsx` / `error.tsx`.
|
|
77
|
+
*/
|
|
78
|
+
export function WeaverseNextStudioConnect(
|
|
79
|
+
props: WeaverseNextStudioConnectProps
|
|
80
|
+
) {
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
loadWeaverseNextStudioScript(props.context, {
|
|
83
|
+
storefrontHostname: props.storefrontHostname,
|
|
84
|
+
})
|
|
85
|
+
}, [props.context, props.storefrontHostname])
|
|
86
|
+
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { WeaverseNextRuntimeInternal } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural shape of the Next App Router returned by `useRouter()`
|
|
5
|
+
* (`next/navigation`) that the Studio bridge needs. Declared locally — rather
|
|
6
|
+
* than importing `AppRouterInstance` from `next` — so the adapter stays a pure
|
|
7
|
+
* function that unit tests can drive with a plain mock and never pulls
|
|
8
|
+
* `next/navigation` into the test graph.
|
|
9
|
+
*/
|
|
10
|
+
export interface WeaverseNextRouterLike {
|
|
11
|
+
/** Add a route to browser history. */
|
|
12
|
+
push: (
|
|
13
|
+
href: string,
|
|
14
|
+
options?: {
|
|
15
|
+
/** Whether to scroll to the top after navigation. */
|
|
16
|
+
scroll?: boolean
|
|
17
|
+
}
|
|
18
|
+
) => void
|
|
19
|
+
/** Refresh the current App Router route and server data. */
|
|
20
|
+
refresh: () => void
|
|
21
|
+
/** Replace the current browser-history entry. */
|
|
22
|
+
replace: (
|
|
23
|
+
href: string,
|
|
24
|
+
options?: {
|
|
25
|
+
/** Whether to scroll to the top after navigation. */
|
|
26
|
+
scroll?: boolean
|
|
27
|
+
}
|
|
28
|
+
) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Resolved Studio navigation/revalidation callbacks for a Next runtime. */
|
|
32
|
+
export interface WeaverseNextStudioInternals {
|
|
33
|
+
/** Navigate Studio's preview with React Router-compatible options. */
|
|
34
|
+
navigate: NonNullable<WeaverseNextRuntimeInternal['navigate']>
|
|
35
|
+
/** Refresh the current App Router route. */
|
|
36
|
+
revalidate: NonNullable<WeaverseNextRuntimeInternal['revalidate']>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Controls how Studio navigation maps to the Next App Router. */
|
|
40
|
+
export interface CreateWeaverseNextStudioInternalsOptions {
|
|
41
|
+
/** Use `router.replace` instead of `router.push` for navigation. */
|
|
42
|
+
replace?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function shouldResetScroll(
|
|
46
|
+
options?: { preventScrollReset?: boolean } | Record<string, unknown>
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!options || typeof options !== 'object') {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
return !(options as { preventScrollReset?: boolean }).preventScrollReset
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build Studio `navigate` / `revalidate` callbacks from a Next App Router.
|
|
56
|
+
*
|
|
57
|
+
* Maps Hydrogen's React Router semantics onto Next App Router:
|
|
58
|
+
* - `navigate(to, { preventScrollReset })` → `router.push(to, { scroll })`,
|
|
59
|
+
* where `scroll` is the inverse of `preventScrollReset`. Studio passes
|
|
60
|
+
* `preventScrollReset` for in-place edits so the preview viewport is kept,
|
|
61
|
+
* matching React Router's `navigate({ preventScrollReset })`.
|
|
62
|
+
* - `revalidate()` → `router.refresh()`, the App Router's loader-backed refresh
|
|
63
|
+
* and the closest equivalent to React Router's `useRevalidator`.
|
|
64
|
+
*
|
|
65
|
+
* Pure (no React, no `next/navigation`) so it is unit-testable with a mock
|
|
66
|
+
* router; the {@link WeaverseNextRouterLike} contract is the only coupling.
|
|
67
|
+
*/
|
|
68
|
+
export function createWeaverseNextStudioInternals(
|
|
69
|
+
router: WeaverseNextRouterLike,
|
|
70
|
+
options: CreateWeaverseNextStudioInternalsOptions = {}
|
|
71
|
+
): WeaverseNextStudioInternals {
|
|
72
|
+
return {
|
|
73
|
+
navigate: (to, navigateOptions) => {
|
|
74
|
+
let scroll = shouldResetScroll(navigateOptions)
|
|
75
|
+
if (options.replace) {
|
|
76
|
+
router.replace(to, { scroll })
|
|
77
|
+
} else {
|
|
78
|
+
router.push(to, { scroll })
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
revalidate: () => {
|
|
82
|
+
router.refresh()
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { WeaverseNextRequestContext } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Weaverse-controlled apex domains the Studio bridge may be served from
|
|
5
|
+
* (matched on the host itself and any subdomain), mirroring Hydrogen's
|
|
6
|
+
* `TRUSTED_STUDIO_DOMAINS`.
|
|
7
|
+
*/
|
|
8
|
+
const TRUSTED_STUDIO_DOMAINS = ['weaverse.io', 'weaverse.dev']
|
|
9
|
+
|
|
10
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
11
|
+
return (
|
|
12
|
+
hostname === 'localhost' ||
|
|
13
|
+
hostname === '127.0.0.1' ||
|
|
14
|
+
hostname.endsWith('.localhost')
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeOrigin(value: string | null): string | null {
|
|
19
|
+
if (!value) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
return new URL(value).origin
|
|
25
|
+
} catch {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether `origin` is one we trust to serve the Studio bridge script. Matches
|
|
32
|
+
* Hydrogen's `isTrustedStudioHost`: trusts `weaverse.io` / `weaverse.dev` and
|
|
33
|
+
* their subdomains over https, plus loopback (`localhost` / `*.localhost` /
|
|
34
|
+
* `127.0.0.1`) — but only when the storefront itself is loopback (genuine
|
|
35
|
+
* local-builder dev). Loopback is the only place http is allowed; every other
|
|
36
|
+
* trusted host must be https. Arbitrary origins are rejected.
|
|
37
|
+
*/
|
|
38
|
+
function isTrustedStudioOrigin(
|
|
39
|
+
origin: string,
|
|
40
|
+
storefrontHostname: string
|
|
41
|
+
): boolean {
|
|
42
|
+
let parsed: URL
|
|
43
|
+
try {
|
|
44
|
+
parsed = new URL(origin)
|
|
45
|
+
} catch {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let { hostname, protocol } = parsed
|
|
50
|
+
if (isLoopbackHostname(storefrontHostname) && isLoopbackHostname(hostname)) {
|
|
51
|
+
return protocol === 'http:' || protocol === 'https:'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (protocol !== 'https:') {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return TRUSTED_STUDIO_DOMAINS.some(
|
|
59
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toSearchParams(context?: WeaverseNextRequestContext): URLSearchParams {
|
|
64
|
+
if (context?.searchParams) {
|
|
65
|
+
return new URLSearchParams(context.searchParams)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (context?.url) {
|
|
69
|
+
let url =
|
|
70
|
+
typeof context.url === 'string'
|
|
71
|
+
? new URL(context.url, 'http://localhost')
|
|
72
|
+
: context.url
|
|
73
|
+
return new URLSearchParams(url.searchParams)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new URLSearchParams()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function last(searchParams: URLSearchParams, key: string): string | null {
|
|
80
|
+
let values = searchParams.getAll(key)
|
|
81
|
+
return values.length ? (values.at(-1) ?? null) : null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Trust context used when resolving a Studio bridge script URL. */
|
|
85
|
+
export interface ResolveWeaverseNextStudioScriptSrcOptions {
|
|
86
|
+
/** Storefront hostname; required to permit loopback Studio origins. */
|
|
87
|
+
storefrontHostname?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the Studio script URL from request/search context. `@weaverse/hydrogen/next`
|
|
92
|
+
* uses the dedicated Next bridge path served by Builder.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveWeaverseNextStudioScriptSrc(
|
|
95
|
+
context?: WeaverseNextRequestContext,
|
|
96
|
+
options: ResolveWeaverseNextStudioScriptSrcOptions = {}
|
|
97
|
+
): string | null {
|
|
98
|
+
let searchParams = toSearchParams(context)
|
|
99
|
+
|
|
100
|
+
let isDesignMode =
|
|
101
|
+
context?.isDesignMode ?? last(searchParams, 'isDesignMode') === 'true'
|
|
102
|
+
let isPreviewMode =
|
|
103
|
+
context?.isPreviewMode ?? last(searchParams, 'isPreviewMode') === 'true'
|
|
104
|
+
let isRevisionPreview =
|
|
105
|
+
context?.isRevisionPreview ?? Boolean(last(searchParams, '__revisionId'))
|
|
106
|
+
|
|
107
|
+
if (!(isDesignMode || isPreviewMode || isRevisionPreview)) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let origin = normalizeOrigin(last(searchParams, 'weaverseHost'))
|
|
112
|
+
if (!origin) {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let storefrontHostname = options.storefrontHostname ?? ''
|
|
117
|
+
if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Builder section-preview URLs carry both isDesignMode and isPreviewMode;
|
|
122
|
+
// the preview iframe waits for `preview-size`, which only preview.js emits,
|
|
123
|
+
// so preview/revision mode takes precedence (matching Hydrogen).
|
|
124
|
+
let bundle = isPreviewMode || isRevisionPreview ? 'preview.js' : 'index.js'
|
|
125
|
+
let version = last(searchParams, 'weaverseVersion')
|
|
126
|
+
let url = `${origin}/static/studio/next/${bundle}`
|
|
127
|
+
return version ? `${url}?v=${encodeURIComponent(version)}` : url
|
|
128
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SchemaType } from '@weaverse/schema'
|
|
2
|
+
import type {
|
|
3
|
+
WeaverseNextThemeSchema,
|
|
4
|
+
WeaverseNextThemeSettingsStore,
|
|
5
|
+
} from './types'
|
|
6
|
+
|
|
7
|
+
/** Initial values for a root-scoped theme-settings store. */
|
|
8
|
+
export interface CreateWeaverseNextThemeSettingsStoreOptions {
|
|
9
|
+
/** Public environment values exposed alongside the store. */
|
|
10
|
+
publicEnv?: Record<string, string | undefined>
|
|
11
|
+
/** Theme schema associated with the settings. */
|
|
12
|
+
schema?: SchemaType | WeaverseNextThemeSchema
|
|
13
|
+
/** Initial setting values. */
|
|
14
|
+
settings?: Record<string, unknown>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Small external store shape compatible with the Studio bridge and React's
|
|
19
|
+
* `useSyncExternalStore` contract. The bridge calls `updateThemeSettings` during
|
|
20
|
+
* live theme edits, so keep that method name stable.
|
|
21
|
+
*/
|
|
22
|
+
export function createWeaverseNextThemeSettingsStore(
|
|
23
|
+
options: CreateWeaverseNextThemeSettingsStoreOptions = {}
|
|
24
|
+
): WeaverseNextThemeSettingsStore {
|
|
25
|
+
let listeners = new Set<() => void>()
|
|
26
|
+
let store: WeaverseNextThemeSettingsStore = {
|
|
27
|
+
publicEnv: options.publicEnv,
|
|
28
|
+
schema: options.schema,
|
|
29
|
+
settings: { ...(options.settings ?? {}) },
|
|
30
|
+
getServerSnapshot: () => store.settings,
|
|
31
|
+
getSnapshot: () => store.settings,
|
|
32
|
+
subscribe: (listener: () => void) => {
|
|
33
|
+
listeners.add(listener)
|
|
34
|
+
return () => {
|
|
35
|
+
listeners.delete(listener)
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
updateThemeSettings: (next: Record<string, unknown>) => {
|
|
39
|
+
store.settings = { ...store.settings, ...next }
|
|
40
|
+
for (let listener of listeners) {
|
|
41
|
+
listener()
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return store
|
|
47
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSafeExternalStore } from '@weaverse/react'
|
|
4
|
+
import { createContext, type ReactNode, useContext, useMemo } from 'react'
|
|
5
|
+
import { createTranslate, type TranslateFunction } from '../utils/translate'
|
|
6
|
+
import type { TranslationStore } from '../utils/translation-store'
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Context & Provider
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** Translation function and live override stores exposed by `useTranslation`. */
|
|
13
|
+
export interface TranslationValue {
|
|
14
|
+
/** Active-locale translated keys loaded from the storefront API. */
|
|
15
|
+
merchantOverrides: Record<string, unknown> | null
|
|
16
|
+
/**
|
|
17
|
+
* Translate a key with optional variable interpolation.
|
|
18
|
+
*
|
|
19
|
+
* Priority chain (highest wins):
|
|
20
|
+
* 1. External `t` (passed into `WeaverseNextRootProvider` / `TranslationProvider`)
|
|
21
|
+
* 2. Design overrides (live Builder edits from `TranslationStore`)
|
|
22
|
+
* 3. `merchantOverrides` (DB/API overrides for the current locale)
|
|
23
|
+
* 4. `staticContent` (theme default JSON)
|
|
24
|
+
* 5. The key itself (fallback)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* t('cart.title') // => "Cart"
|
|
28
|
+
* t('badge.sale', { percentage: 15 }) // => "-15% Off"
|
|
29
|
+
*/
|
|
30
|
+
t: TranslateFunction
|
|
31
|
+
/**
|
|
32
|
+
* Legacy name for the live design-mode translation store.
|
|
33
|
+
*
|
|
34
|
+
* @deprecated Migrate to {@link TranslationValue.translationStore}. This
|
|
35
|
+
* alias points at the same store instance.
|
|
36
|
+
*/
|
|
37
|
+
themeTextStore: TranslationStore | null
|
|
38
|
+
/** Exposed so the runtime/Studio bridge can attach it to `internal`. */
|
|
39
|
+
translationStore: TranslationStore | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const TranslationContext = createContext<TranslationValue | null>(null)
|
|
43
|
+
TranslationContext.displayName = 'WeaverseNextTranslationContext'
|
|
44
|
+
|
|
45
|
+
/** Sources and content rendered by {@link TranslationProvider}. */
|
|
46
|
+
export interface TranslationProviderProps {
|
|
47
|
+
/** React subtree that can call `useTranslation`. */
|
|
48
|
+
children: ReactNode
|
|
49
|
+
/** Active-locale static-text overrides loaded from Weaverse. */
|
|
50
|
+
merchantOverrides?: Record<string, unknown>
|
|
51
|
+
/** Default-locale static content from the theme schema. */
|
|
52
|
+
staticContent: Record<string, unknown>
|
|
53
|
+
/**
|
|
54
|
+
* Optional external translation function.
|
|
55
|
+
* When provided it becomes the **highest-priority** source:
|
|
56
|
+
* if it returns a non-empty string that differs from the raw key,
|
|
57
|
+
* that value is used; otherwise the internal resolution chain continues.
|
|
58
|
+
*/
|
|
59
|
+
t?: TranslateFunction
|
|
60
|
+
/**
|
|
61
|
+
* Legacy prop name for the live translation store.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated Migrate to {@link TranslationProviderProps.translationStore}.
|
|
64
|
+
* Still accepted for backward compatibility; ignored when `translationStore`
|
|
65
|
+
* is also provided.
|
|
66
|
+
*/
|
|
67
|
+
themeTextStore?: TranslationStore | null
|
|
68
|
+
/** Optional store for live design-mode text overrides from the builder. */
|
|
69
|
+
translationStore?: TranslationStore | null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// No-op fallback for useSafeExternalStore when store is null (production).
|
|
73
|
+
// Module-level constants — stable references, zero allocation per render.
|
|
74
|
+
const EMPTY: Record<string, string> = {}
|
|
75
|
+
const NOOP_SUBSCRIBE = () => () => {
|
|
76
|
+
// No-op unsubscribe for production/static renders without a translation store.
|
|
77
|
+
}
|
|
78
|
+
const NOOP_SNAPSHOT = () => EMPTY
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Provider that wraps the app to expose the `t()` function.
|
|
82
|
+
*
|
|
83
|
+
* - `staticContent` – the theme's default locale JSON (from themeSchema.i18n)
|
|
84
|
+
* - `merchantOverrides` – locale-specific overrides fetched from the API
|
|
85
|
+
* - `translationStore` – optional store for live design-mode text overrides
|
|
86
|
+
* (the deprecated `themeTextStore` prop is accepted as a fallback)
|
|
87
|
+
*/
|
|
88
|
+
export function TranslationProvider({
|
|
89
|
+
staticContent,
|
|
90
|
+
merchantOverrides,
|
|
91
|
+
t: externalT,
|
|
92
|
+
translationStore,
|
|
93
|
+
themeTextStore,
|
|
94
|
+
children,
|
|
95
|
+
}: TranslationProviderProps) {
|
|
96
|
+
// Canonical store, falling back to the deprecated `themeTextStore` prop.
|
|
97
|
+
let store = translationStore ?? themeTextStore ?? null
|
|
98
|
+
|
|
99
|
+
// Subscribe to design-mode overrides. No-op when store is null (production).
|
|
100
|
+
let designOverrides = useSafeExternalStore(
|
|
101
|
+
store?.subscribe ?? NOOP_SUBSCRIBE,
|
|
102
|
+
store?.getSnapshot ?? NOOP_SNAPSHOT,
|
|
103
|
+
store?.getServerSnapshot ?? NOOP_SNAPSHOT
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
let value = useMemo<TranslationValue>(
|
|
107
|
+
() => ({
|
|
108
|
+
t: createTranslate({
|
|
109
|
+
staticContent,
|
|
110
|
+
merchantOverrides,
|
|
111
|
+
designOverrides,
|
|
112
|
+
externalT,
|
|
113
|
+
}),
|
|
114
|
+
translationStore: store,
|
|
115
|
+
// Deprecated alias — same instance, kept for backward compatibility.
|
|
116
|
+
themeTextStore: store,
|
|
117
|
+
merchantOverrides: merchantOverrides ?? null,
|
|
118
|
+
}),
|
|
119
|
+
[staticContent, merchantOverrides, designOverrides, externalT, store]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<TranslationContext.Provider value={value}>
|
|
124
|
+
{children}
|
|
125
|
+
</TranslationContext.Provider>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Hook to access the translation `t()` function.
|
|
131
|
+
*
|
|
132
|
+
* Must be used inside a component wrapped by `WeaverseNextRootProvider` or
|
|
133
|
+
* `TranslationProvider`.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```tsx
|
|
137
|
+
* function CartTitle() {
|
|
138
|
+
* let { t } = useTranslation()
|
|
139
|
+
* return <h2>{t('cart.title')}</h2>
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function useTranslation(): TranslationValue {
|
|
144
|
+
let ctx = useContext(TranslationContext)
|
|
145
|
+
if (!ctx) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>.'
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
return ctx
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Deprecated aliases (backward compatibility — kept exported indefinitely)
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Legacy name for {@link TranslationProvider}.
|
|
159
|
+
*
|
|
160
|
+
* @deprecated Replace `ThemeTextProvider` with {@link TranslationProvider};
|
|
161
|
+
* the props and runtime behavior are unchanged.
|
|
162
|
+
*/
|
|
163
|
+
export const ThemeTextProvider = TranslationProvider
|
|
164
|
+
/**
|
|
165
|
+
* Legacy name for {@link useTranslation}.
|
|
166
|
+
*
|
|
167
|
+
* @deprecated Replace `useThemeText` with {@link useTranslation}; the return
|
|
168
|
+
* value is unchanged.
|
|
169
|
+
*/
|
|
170
|
+
export const useThemeText = useTranslation
|
|
171
|
+
/**
|
|
172
|
+
* Legacy name for {@link TranslationValue}.
|
|
173
|
+
*
|
|
174
|
+
* @deprecated Import {@link TranslationValue} instead.
|
|
175
|
+
*/
|
|
176
|
+
export type ThemeTextValue = TranslationValue
|
|
177
|
+
/**
|
|
178
|
+
* Legacy name for {@link TranslationProviderProps}.
|
|
179
|
+
*
|
|
180
|
+
* @deprecated Import {@link TranslationProviderProps} instead.
|
|
181
|
+
*/
|
|
182
|
+
export type ThemeTextProviderProps = TranslationProviderProps
|