@weaverse/next 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,206 +1,97 @@
1
- import { buildWeaverseNextRequestInfo, DEFAULT_REVALIDATE_ENDPOINT, revalidateWeaverseNextItem, generateDataFromSchema } from './chunk-LH6AAEIK.mjs';
2
- export { DEFAULT_REVALIDATE_ENDPOINT, buildWeaverseNextRequestInfo, buildWeaverseNextRevalidateRouteContext, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, revalidateWeaverseNextItem, runWeaverseComponentLoaders } from './chunk-LH6AAEIK.mjs';
3
- import { WeaverseRoot, Weaverse, WeaverseItemStore, useSafeExternalStore } from '@weaverse/react';
4
- export { isBrowser, isIframe, useChildInstances, useItemInstance, useParentInstance, useWeaverse } from '@weaverse/react';
5
- import { createSchema } from '@weaverse/schema';
1
+ export { DEFAULT_REVALIDATE_ENDPOINT, buildWeaverseNextRequestInfo, buildWeaverseNextRevalidateRouteContext, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, isBrowser, isIframe, revalidateWeaverseNextItem, runWeaverseComponentLoaders } from './chunk-OSC5HR5V.mjs';
6
2
  export * from '@weaverse/schema';
7
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
8
- import { createContext, memo, useContext, useMemo, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';
9
- import { useRouter } from 'next/navigation';
3
+ export { ThemeTextProvider, TranslationProvider, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createWeaverseNextClient, createWeaverseNextRuntime, loadWeaverseNextStudioScript, useChildInstances, useItemInstance, useParentInstance, useThemeSettings, useThemeText, useTranslation, useWeaverse, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData } from './client-boundary.mjs';
10
4
 
11
- function flattenItemData(update) {
12
- let { data, ...rest } = update;
13
- if (!(data && typeof data === "object" && !Array.isArray(data))) {
14
- return rest;
15
- }
16
- return { ...data, ...rest };
17
- }
18
- var deferredItemUpdates = null;
19
- function collectDeferredItemUpdates(create) {
20
- let previousQueue = deferredItemUpdates;
21
- let queue = /* @__PURE__ */ new Set();
22
- deferredItemUpdates = queue;
23
- try {
24
- return [create(), [...queue]];
25
- } finally {
26
- deferredItemUpdates = previousQueue;
5
+ // src/studio-router.ts
6
+ function shouldResetScroll(options) {
7
+ if (!options || typeof options !== "object") {
8
+ return true;
27
9
  }
10
+ return !options.preventScrollReset;
28
11
  }
29
- var WeaverseNextItem = class extends WeaverseItemStore {
30
- /**
31
- * Cached merged snapshot (base `_store` + translations). Reset whenever the
32
- * `_store` ref or the item's translation entry ref changes, so
33
- * `useSyncExternalStore` sees a stable object between unrelated re-renders.
34
- */
35
- _cachedSnapshot = null;
36
- _snapshotStoreRef = null;
37
- _snapshotTranslationRef = null;
38
- /** Create an item store from serialized component data and its runtime. */
39
- constructor(initialData, weaverse) {
40
- super(initialData, weaverse);
41
- let schemaData = generateDataFromSchema(this.Element?.schema);
42
- Object.assign(this._store, schemaData, flattenItemData(initialData));
43
- }
44
- /**
45
- * Flatten nested `data` on updates exactly like the constructor does. Core's
46
- * `initProject()` calls `setData(item)` when a new runtime serves a page
47
- * whose item instances already exist (e.g. locale/client navigation).
48
- *
49
- * A complete serialized item (it carries `id` and `type`; core's
50
- * `initProject()` always passes one) replaces the store the way the
51
- * constructor builds it: the schema defaults of the item's own `type` — the
52
- * Builder can replace a section in place, keeping its id — then the item's
53
- * settings. A payload omits a setting — or the whole optional `data` — when
54
- * its value equals the schema default, so merging into the previous store
55
- * would keep the previous locale's value visible. Partial updates still
56
- * merge. Either way the `_store` ref changes, which invalidates the memoized
57
- * snapshot below. The subscriber emit is queued instead of fired when a
58
- * render-phase deferral is active (see `collectDeferredItemUpdates`).
59
- */
60
- setData = (update) => {
61
- if ("id" in update && "type" in update) {
62
- let { data, type } = update;
63
- let schema = this.weaverse.elementRegistry.get(type)?.schema;
64
- this._store = {
65
- ...generateDataFromSchema(schema),
66
- ...data === void 0 ? {} : { data },
67
- ...flattenItemData(update)
68
- };
69
- } else {
70
- this.data = flattenItemData(update);
71
- }
72
- if (deferredItemUpdates) {
73
- deferredItemUpdates.add(this);
74
- } else {
75
- this.triggerUpdate();
76
- }
77
- return this.data;
78
- };
79
- /**
80
- * Overlay item-level translations onto the base store when the runtime is in
81
- * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
82
- * memoized by `_store` ref + translation entry ref so the returned object is
83
- * referentially stable, and a fast path that returns the base store untouched
84
- * when the item has no translations.
85
- */
86
- getSnapShot = () => {
87
- let base = this._store;
88
- let translations = this.weaverse.translationMap[this._id];
89
- if (!translations) {
90
- return base;
91
- }
92
- if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
93
- return this._cachedSnapshot;
94
- }
95
- let merged = { ...base };
96
- for (let [key, entry] of Object.entries(translations)) {
97
- if (entry.translatedValue !== void 0) {
98
- merged[key] = entry.translatedValue;
12
+ function createWeaverseNextStudioInternals(router, options = {}) {
13
+ return {
14
+ navigate: (to, navigateOptions) => {
15
+ let scroll = shouldResetScroll(navigateOptions);
16
+ if (options.replace) {
17
+ router.replace(to, { scroll });
18
+ } else {
19
+ router.push(to, { scroll });
99
20
  }
21
+ },
22
+ revalidate: () => {
23
+ router.refresh();
100
24
  }
101
- this._cachedSnapshot = merged;
102
- this._snapshotStoreRef = base;
103
- this._snapshotTranslationRef = translations;
104
- return merged;
105
25
  };
106
- };
107
- function ensureNextItemConstructor() {
108
- if (!Weaverse.ItemConstructor || Weaverse.ItemConstructor === WeaverseItemStore || Weaverse.ItemConstructor === WeaverseNextItem) {
109
- Weaverse.ItemConstructor = WeaverseNextItem;
110
- }
111
26
  }
112
- function Main(props) {
113
- let { children, loaderData, ...rest } = props;
114
- return /* @__PURE__ */ jsx("div", { ...rest, children });
115
- }
116
- Main.displayName = "main";
117
- var mainSchema = createSchema({
118
- type: "main",
119
- title: "Main"
120
- });
121
- var defaultMainComponent = {
122
- default: Main,
123
- schema: mainSchema
124
- };
125
27
 
126
- // src/registry.ts
127
- var MAIN_TYPE = "main";
128
- var registeredComponentTypes = /* @__PURE__ */ new Set();
129
- function registerWeaverseNextComponents(components) {
130
- ensureNextItemConstructor();
131
- let hasUserMain = components.some(
132
- (component) => component.schema?.type === MAIN_TYPE
28
+ // src/studio-script-src.ts
29
+ var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
30
+ function isLoopbackHostname(hostname) {
31
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname.endsWith(".localhost");
32
+ }
33
+ function normalizeOrigin(value) {
34
+ if (!value) {
35
+ return null;
36
+ }
37
+ try {
38
+ return new URL(value).origin;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+ function isTrustedStudioOrigin(origin, storefrontHostname) {
44
+ let parsed;
45
+ try {
46
+ parsed = new URL(origin);
47
+ } catch {
48
+ return false;
49
+ }
50
+ let { hostname, protocol } = parsed;
51
+ if (isLoopbackHostname(storefrontHostname) && isLoopbackHostname(hostname)) {
52
+ return protocol === "http:" || protocol === "https:";
53
+ }
54
+ if (protocol !== "https:") {
55
+ return false;
56
+ }
57
+ return TRUSTED_STUDIO_DOMAINS.some(
58
+ (domain) => hostname === domain || hostname.endsWith(`.${domain}`)
133
59
  );
134
- let effectiveComponents = hasUserMain ? components : [defaultMainComponent, ...components];
135
- for (let component of effectiveComponents) {
136
- let type = component.schema.type;
137
- let element = {
138
- type,
139
- Component: component.default,
140
- schema: component.schema,
141
- loader: component.loader
142
- };
143
- if (type === MAIN_TYPE && hasUserMain) {
144
- Weaverse.elementRegistry.set(type, element);
145
- registeredComponentTypes.add(type);
146
- continue;
147
- }
148
- if (type && !registeredComponentTypes.has(type)) {
149
- Weaverse.registerElement(element);
150
- registeredComponentTypes.add(type);
151
- }
60
+ }
61
+ function toSearchParams(context) {
62
+ if (context?.searchParams) {
63
+ return new URLSearchParams(context.searchParams);
152
64
  }
65
+ if (context?.url) {
66
+ let url = typeof context.url === "string" ? new URL(context.url, "http://localhost") : context.url;
67
+ return new URLSearchParams(url.searchParams);
68
+ }
69
+ return new URLSearchParams();
153
70
  }
154
-
155
- // src/client.ts
156
- var NextClient = class {
157
- projectId;
158
- components;
159
- themeSchema;
160
- themeSettings;
161
- requestContext;
162
- commerce;
163
- data = null;
164
- dataContext = {};
165
- _fetchPage;
166
- _fetchThemeSettings;
167
- constructor(config) {
168
- this.projectId = config.projectId;
169
- this.components = config.components;
170
- registerWeaverseNextComponents(this.components);
171
- this.themeSchema = config.themeSchema;
172
- this.themeSettings = {
173
- ...generateDataFromSchema(config.themeSchema),
174
- ...config.themeSettings
175
- };
176
- this.requestContext = config.requestContext;
177
- this.commerce = config.commerce;
178
- this._fetchPage = config.fetchPage;
179
- this._fetchThemeSettings = config.fetchThemeSettings;
71
+ function last(searchParams, key) {
72
+ let values = searchParams.getAll(key);
73
+ return values.length ? values.at(-1) ?? null : null;
74
+ }
75
+ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
76
+ let searchParams = toSearchParams(context);
77
+ let isDesignMode = context?.isDesignMode ?? last(searchParams, "isDesignMode") === "true";
78
+ let isPreviewMode = context?.isPreviewMode ?? last(searchParams, "isPreviewMode") === "true";
79
+ let isRevisionPreview = context?.isRevisionPreview ?? Boolean(last(searchParams, "__revisionId"));
80
+ if (!(isDesignMode || isPreviewMode || isRevisionPreview)) {
81
+ return null;
82
+ }
83
+ let origin = normalizeOrigin(last(searchParams, "weaverseHost"));
84
+ if (!origin) {
85
+ return null;
180
86
  }
181
- /** Compatibility alias so loaders can call `weaverse.storefront.query(...)`. */
182
- get storefront() {
183
- return this.commerce?.storefront;
87
+ let storefrontHostname = options.storefrontHostname ?? "";
88
+ if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
89
+ return null;
184
90
  }
185
- loadPage = (input = {}) => {
186
- if (!this._fetchPage) {
187
- throw new Error(
188
- "[WeaverseNextClient] loadPage requires a `fetchPage` function in client config."
189
- );
190
- }
191
- return this._fetchPage(input);
192
- };
193
- loadThemeSettings = (context) => {
194
- if (!this._fetchThemeSettings) {
195
- throw new Error(
196
- "[WeaverseNextClient] loadThemeSettings requires a `fetchThemeSettings` function in client config."
197
- );
198
- }
199
- return this._fetchThemeSettings(context ?? this.requestContext);
200
- };
201
- };
202
- function createWeaverseNextClient(config) {
203
- return new NextClient(config);
91
+ let bundle = isPreviewMode || isRevisionPreview ? "preview.js" : "index.js";
92
+ let version = last(searchParams, "weaverseVersion");
93
+ let url = `${origin}/static/studio/next/${bundle}`;
94
+ return version ? `${url}?v=${encodeURIComponent(version)}` : url;
204
95
  }
205
96
 
206
97
  // src/theme-settings-store.ts
@@ -227,6 +118,8 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
227
118
  };
228
119
  return store;
229
120
  }
121
+
122
+ // src/translate.ts
230
123
  function getNestedKey(obj, path, fallback) {
231
124
  let keys = path.split(".");
232
125
  let current = obj;
@@ -250,8 +143,6 @@ function interpolate(template, variables) {
250
143
  return value === void 0 ? match : String(value);
251
144
  });
252
145
  }
253
- var TranslationContext = createContext(null);
254
- TranslationContext.displayName = "WeaverseNextTranslationContext";
255
146
  function createTranslate(sources) {
256
147
  let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
257
148
  return (key, variables) => {
@@ -268,52 +159,6 @@ function createTranslate(sources) {
268
159
  return interpolate(raw, variables);
269
160
  };
270
161
  }
271
- var EMPTY = {};
272
- var NOOP_SUBSCRIBE = () => () => {
273
- };
274
- var NOOP_SNAPSHOT = () => EMPTY;
275
- function TranslationProvider({
276
- staticContent,
277
- merchantOverrides,
278
- t: externalT,
279
- translationStore,
280
- themeTextStore,
281
- children
282
- }) {
283
- let store = translationStore ?? themeTextStore ?? null;
284
- let designOverrides = useSafeExternalStore(
285
- store?.subscribe ?? NOOP_SUBSCRIBE,
286
- store?.getSnapshot ?? NOOP_SNAPSHOT,
287
- store?.getServerSnapshot ?? NOOP_SNAPSHOT
288
- );
289
- let value = useMemo(
290
- () => ({
291
- t: createTranslate({
292
- staticContent,
293
- merchantOverrides,
294
- designOverrides,
295
- externalT
296
- }),
297
- translationStore: store,
298
- // Deprecated alias — same instance, kept for backward compatibility.
299
- themeTextStore: store,
300
- merchantOverrides: merchantOverrides ?? null
301
- }),
302
- [staticContent, merchantOverrides, designOverrides, externalT, store]
303
- );
304
- return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
305
- }
306
- function useTranslation() {
307
- let ctx = useContext(TranslationContext);
308
- if (!ctx) {
309
- throw new Error(
310
- "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
311
- );
312
- }
313
- return ctx;
314
- }
315
- var ThemeTextProvider = TranslationProvider;
316
- var useThemeText = useTranslation;
317
162
 
318
163
  // src/translation-store.ts
319
164
  var TranslationStore = class {
@@ -352,767 +197,7 @@ var TranslationStore = class {
352
197
  };
353
198
  };
354
199
  var ThemeTextStore = TranslationStore;
355
- var WeaverseNextRootContext = createContext(null);
356
- function WeaverseNextRootProvider(props) {
357
- let {
358
- children,
359
- initialThemeSettings,
360
- merchantOverrides,
361
- staticContent,
362
- t,
363
- themeSchema,
364
- publicEnv
365
- } = props;
366
- let storeRef = useRef(null);
367
- if (!storeRef.current) {
368
- storeRef.current = createWeaverseNextThemeSettingsStore({
369
- publicEnv,
370
- schema: themeSchema,
371
- settings: initialThemeSettings
372
- });
373
- }
374
- let themeSettingsStore = storeRef.current;
375
- let translationStoreRef = useRef(null);
376
- if (!translationStoreRef.current) {
377
- translationStoreRef.current = new TranslationStore();
378
- }
379
- let translationStore = translationStoreRef.current;
380
- let appliedThemeSettingsRef = useRef(initialThemeSettings);
381
- useEffect(() => {
382
- if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
383
- themeSettingsStore.updateThemeSettings(initialThemeSettings);
384
- appliedThemeSettingsRef.current = initialThemeSettings;
385
- }
386
- }, [initialThemeSettings, themeSettingsStore]);
387
- let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
388
- let rootContextValue = useMemo(
389
- () => ({
390
- merchantOverrides,
391
- staticContent: resolvedStaticContent,
392
- themeSettingsStore,
393
- translationStore
394
- }),
395
- [
396
- merchantOverrides,
397
- resolvedStaticContent,
398
- themeSettingsStore,
399
- translationStore
400
- ]
401
- );
402
- let contextValue = useMemo(
403
- () => ({
404
- merchantOverrides,
405
- themeSettings: themeSettingsStore.getSnapshot(),
406
- themeSettingsStore,
407
- translationStore
408
- }),
409
- [merchantOverrides, themeSettingsStore, translationStore]
410
- );
411
- return /* @__PURE__ */ jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
412
- TranslationProvider,
413
- {
414
- merchantOverrides,
415
- staticContent: resolvedStaticContent,
416
- t,
417
- translationStore,
418
- children
419
- }
420
- ) }) });
421
- }
422
- var EMPTY_THEME_SETTINGS = {};
423
- var EMPTY_STATIC_CONTENT = {};
424
- function getSchemaStaticContent(schema) {
425
- let i18n = schema?.i18n;
426
- return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
427
- }
428
- var WeaverseNextContext = createContext(null);
429
- function WeaverseNextProvider(props) {
430
- let { children, client, rootData, pageData, commerce, themeSettings } = props;
431
- let rootContext = useContext(WeaverseNextRootContext);
432
- let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
433
- let ownTranslationStoreRef = useRef(null);
434
- if (!(rootContext || ownTranslationStoreRef.current)) {
435
- ownTranslationStoreRef.current = new TranslationStore();
436
- }
437
- let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
438
- let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
439
- let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
440
- let ownThemeSettingsStore = useMemo(() => {
441
- if (rootContext) {
442
- return null;
443
- }
444
- return createWeaverseNextThemeSettingsStore({
445
- schema: client?.themeSchema,
446
- settings: themeSettingsValue
447
- });
448
- }, [rootContext, client?.themeSchema, themeSettingsValue]);
449
- let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
450
- let mergedIntoRootRef = useRef(void 0);
451
- useEffect(() => {
452
- if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
453
- rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
454
- mergedIntoRootRef.current = themeSettingsValue;
455
- }
456
- }, [rootContext, themeSettingsValue]);
457
- let value = useMemo(
458
- () => ({
459
- client,
460
- rootData,
461
- pageData,
462
- commerce: commerce ?? client?.commerce,
463
- merchantOverrides,
464
- themeSettings: themeSettingsStore.getSnapshot(),
465
- themeSettingsStore,
466
- translationStore
467
- }),
468
- [
469
- client,
470
- rootData,
471
- pageData,
472
- commerce,
473
- merchantOverrides,
474
- themeSettingsStore,
475
- translationStore
476
- ]
477
- );
478
- return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsx(
479
- TranslationProvider,
480
- {
481
- merchantOverrides,
482
- staticContent,
483
- t: props.t,
484
- translationStore,
485
- children
486
- }
487
- ) });
488
- }
489
-
490
- // src/hooks.ts
491
- var OUTSIDE_PROVIDER_ERROR = "must be used inside a <WeaverseNextProvider>.";
492
- function useWeaverseNextContext(hookName) {
493
- let context = useContext(WeaverseNextContext);
494
- if (!context) {
495
- throw new Error(`[@weaverse/next] ${hookName} ${OUTSIDE_PROVIDER_ERROR}`);
496
- }
497
- return context;
498
- }
499
- function useWeaverseRootData() {
500
- return useWeaverseNextContext("useWeaverseRootData").rootData;
501
- }
502
- function useWeaversePageData() {
503
- return useWeaverseNextContext("useWeaversePageData").pageData;
504
- }
505
- function useWeaverseCommerce() {
506
- return useWeaverseNextContext("useWeaverseCommerce").commerce;
507
- }
508
- function useThemeSettings() {
509
- let { themeSettingsStore } = useWeaverseNextContext("useThemeSettings");
510
- return useSyncExternalStore(
511
- themeSettingsStore.subscribe,
512
- themeSettingsStore.getSnapshot,
513
- themeSettingsStore.getServerSnapshot
514
- );
515
- }
516
- function getRuntimeWindow() {
517
- return typeof window === "undefined" ? void 0 : window;
518
- }
519
- function getRenderablePage(data) {
520
- let page = data.page;
521
- let rootId = page.rootId ?? page.items.find((item) => item.type === "main")?.id ?? page.items[0]?.id ?? page.id;
522
- return { ...page, rootId };
523
- }
524
- function getConfigString(configs, key) {
525
- let value = configs?.[key];
526
- return typeof value === "string" ? value : void 0;
527
- }
528
- function resolveModeFlag(config, key) {
529
- return config.client?.requestContext?.[key] === true || config.data.configs?.[key] === true;
530
- }
531
- function resolveSectionType(config) {
532
- return config.client?.requestContext?.sectionType || getConfigString(config.data.configs, "sectionType") || void 0;
533
- }
534
- function getRecordString(record, key) {
535
- let value = record?.[key];
536
- return typeof value === "string" ? value : void 0;
537
- }
538
- function resolveProjectId(client, data, fallback) {
539
- let projectId = client?.projectId;
540
- if (projectId) {
541
- return projectId;
542
- }
543
- let configProjectId = getConfigString(data.configs, "projectId");
544
- if (configProjectId) {
545
- return configProjectId;
546
- }
547
- return getRecordString(data.project, "id") ?? fallback;
548
- }
549
- function resolveDataContext(config) {
550
- if (config.dataContext) {
551
- return config.dataContext;
552
- }
553
- if (config.client?.dataContext) {
554
- return config.client.dataContext;
555
- }
556
- return {};
557
- }
558
- function getRuntimeKey(pageId, requestInfo) {
559
- return `${pageId}:${requestInfo.pathname}:${requestInfo.search}`;
560
- }
561
- function registerRuntime(runtime) {
562
- let runtimeWindow = getRuntimeWindow();
563
- if (!runtimeWindow) {
564
- return;
565
- }
566
- runtimeWindow.__weaverses ??= {};
567
- runtimeWindow.__weaverses[runtime.pageId] = runtime;
568
- runtimeWindow.__weaverse = runtime;
569
- }
570
- function rebindPageItemsToRuntime(runtime) {
571
- let items = runtime.data?.items;
572
- if (!items) {
573
- return;
574
- }
575
- for (let { id } of items) {
576
- let instance = runtime.itemInstances.get(id);
577
- if (instance && instance.weaverse !== runtime) {
578
- instance.weaverse = runtime;
579
- }
580
- }
581
- }
582
- var WeaverseNextRuntime = class extends Weaverse {
583
- /** Stable ID of the page owned by this runtime. */
584
- pageId;
585
- /** Mutable callbacks and stores consumed by the Studio bridge. */
586
- internal;
587
- /** Path, query, and locale metadata for Studio navigation. */
588
- requestInfo;
589
- /** Whether this runtime renders a component preview. */
590
- isPreviewMode;
591
- /** Whether this runtime renders a saved revision. */
592
- isRevisionPreview;
593
- /** Component type rendered in section preview mode. */
594
- sectionType;
595
- // ─── Item-level translation sidecar (design mode only) ─────────────
596
- /**
597
- * Translation map received from Builder in design mode, keyed by item ID.
598
- * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
599
- * components, so translations render without mutating the base `_store`.
600
- */
601
- translationMap = {};
602
- /** Locale associated with the item-level translation sidecar. */
603
- translationLocale = String();
604
- /** Weaverse language ID used when saving translation changes. */
605
- translationLanguageId = String();
606
- /**
607
- * Fresh page data captured while React was rendering. Runtime reuse happens
608
- * inside the renderer's render phase (`useMemo`), where applying it through
609
- * `setProjectData()` would emit item updates to `useSyncExternalStore`
610
- * subscribers mid-render. The renderer applies it post-commit via
611
- * `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
612
- * Studio tree owns the page data there.
613
- */
614
- pendingProjectData;
615
- /**
616
- * Reused item instances whose data was refreshed while this runtime was
617
- * constructed during render (core `initProject()` calls `setData()` on
618
- * them). Their data is already fresh; only the subscriber notification was
619
- * deferred to `flushRenderPhaseUpdates()`.
620
- */
621
- pendingItemUpdates = [];
622
- /** Create a browser runtime from a loaded page and its request context. */
623
- constructor(config) {
624
- let { client, data } = config;
625
- let page = getRenderablePage(data);
626
- let configs = data.configs;
627
- let requestContext = client?.requestContext;
628
- let isDesignMode = resolveModeFlag(config, "isDesignMode");
629
- let isPreviewMode = resolveModeFlag(config, "isPreviewMode");
630
- let isRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
631
- let sectionType = resolveSectionType(config);
632
- let projectId = resolveProjectId(client, data, page.id);
633
- ensureNextItemConstructor();
634
- super({
635
- projectId,
636
- data: page,
637
- isDesignMode,
638
- weaverseHost: getConfigString(configs, "weaverseHost"),
639
- weaverseVersion: getConfigString(configs, "weaverseVersion")
640
- });
641
- this.pageId = page.id;
642
- this.dataContext = resolveDataContext(config);
643
- this.requestInfo = buildWeaverseNextRequestInfo(requestContext);
644
- this.isDesignMode = isDesignMode;
645
- this.isPreviewMode = isPreviewMode;
646
- this.isRevisionPreview = isRevisionPreview;
647
- this.sectionType = sectionType;
648
- let translationStore = config.translationStore ?? new TranslationStore();
649
- this.internal = {
650
- merchantOverrides: config.merchantOverrides,
651
- navigate: config.navigate,
652
- pageAssignment: data.pageAssignment,
653
- project: data.project,
654
- revalidate: config.revalidate,
655
- themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
656
- schema: client?.themeSchema,
657
- settings: client?.themeSettings
658
- }),
659
- translationStore,
660
- themeTextStore: translationStore
661
- };
662
- this.extractTranslationSidecar();
663
- }
664
- /**
665
- * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
666
- * the current page data (Builder attaches them in design mode). Unlike
667
- * Hydrogen, this also clears the sidecar to defaults when the new page data
668
- * carries none, so a reused runtime / project-data swap can't leave a stale
669
- * map overlaying the fresh tree.
670
- */
671
- extractTranslationSidecar = () => {
672
- let pageData = this.data;
673
- let translationMap = pageData?.translationMap;
674
- if (translationMap) {
675
- this.translationMap = translationMap;
676
- this.translationLocale = pageData?.translationLocale || "";
677
- this.translationLanguageId = pageData?.translationLanguageId || "";
678
- } else {
679
- this.translationMap = {};
680
- this.translationLocale = "";
681
- this.translationLanguageId = "";
682
- }
683
- };
684
- /**
685
- * Replace the translation sidecar (Builder pushes this when translation data
686
- * arrives or the active language changes) and refresh every item so their
687
- * merged snapshots recompute.
688
- */
689
- setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
690
- this.translationMap = map;
691
- this.translationLocale = locale;
692
- this.translationLanguageId = languageId;
693
- this.refreshAllItems();
694
- };
695
- /**
696
- * Update a single translatable field for one item. Assigns a new per-item
697
- * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
698
- * invalidates, then triggers only that item's subscribers.
699
- */
700
- updateTranslation = (itemId, key, originalValue, translatedValue) => {
701
- if (!this.translationMap[itemId]) {
702
- this.translationMap[itemId] = {};
703
- }
704
- this.translationMap[itemId] = {
705
- ...this.translationMap[itemId],
706
- [key]: { originalValue, translatedValue }
707
- };
708
- let instance = this.itemInstances.get(itemId);
709
- if (instance) {
710
- instance.triggerUpdate();
711
- }
712
- };
713
- /**
714
- * Collect translation changes for the save flow. Returns `undefined` unless
715
- * both the locale and language ID are set and at least one entry exists;
716
- * each entry key is namespaced as `data.<field>` to match Builder.
717
- */
718
- getTranslationChanges = () => {
719
- let languageId = this.translationLanguageId;
720
- if (!(this.translationLocale && languageId)) {
721
- return;
722
- }
723
- let entries = [];
724
- for (let [itemId, fields] of Object.entries(this.translationMap)) {
725
- for (let [key, entry] of Object.entries(fields)) {
726
- entries.push({
727
- itemId,
728
- key: `data.${key}`,
729
- originalValue: entry.originalValue,
730
- translatedValue: entry.translatedValue
731
- });
732
- }
733
- }
734
- if (entries.length === 0) {
735
- return;
736
- }
737
- return { languageId, entries };
738
- };
739
- /**
740
- * Re-extract the translation sidecar whenever project data is replaced (route
741
- * change / non-design reuse), before rebuilding item stores. Takes the
742
- * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
743
- * the reuse branch pass in.
744
- */
745
- setProjectData = (data) => {
746
- this.data = data;
747
- this.extractTranslationSidecar();
748
- this.initProject();
749
- };
750
- /**
751
- * Apply updates deferred off the render phase: swap in any pending page data
752
- * captured on runtime reuse, then notify items whose data was refreshed
753
- * during construction. The renderer calls this from a layout effect right
754
- * after commit, so subscribers re-render before paint (no content flash) and
755
- * without React's setState-in-render warning.
756
- */
757
- flushRenderPhaseUpdates = () => {
758
- let pendingData = this.pendingProjectData;
759
- if (pendingData) {
760
- this.pendingProjectData = void 0;
761
- this.setProjectData(pendingData);
762
- }
763
- let pendingItems = this.pendingItemUpdates;
764
- if (pendingItems.length > 0) {
765
- this.pendingItemUpdates = [];
766
- for (let item of pendingItems) {
767
- item.triggerUpdate();
768
- }
769
- }
770
- };
771
- };
772
- function createWeaverseNextRuntime(config) {
773
- let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
774
- let page = getRenderablePage(config.data);
775
- let runtimeWindow = getRuntimeWindow();
776
- let existing = runtimeWindow?.__weaverses?.[page.id];
777
- let requestKey = getRuntimeKey(page.id, requestInfo);
778
- if (existing?.__weaverseNextRequestKey === requestKey) {
779
- let configs = config.data.configs;
780
- let nextIsDesignMode = resolveModeFlag(config, "isDesignMode");
781
- let nextIsPreviewMode = resolveModeFlag(config, "isPreviewMode");
782
- let nextIsRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
783
- let nextSectionType = resolveSectionType(config);
784
- if (existing.isDesignMode || nextIsDesignMode) {
785
- existing.pendingProjectData = void 0;
786
- } else {
787
- existing.pendingProjectData = page;
788
- }
789
- existing.__weaverseNextLatestData = page;
790
- existing.dataContext = resolveDataContext(config);
791
- existing.requestInfo = requestInfo;
792
- existing.projectId = resolveProjectId(config.client, config.data, page.id);
793
- existing.isDesignMode = nextIsDesignMode;
794
- existing.isPreviewMode = nextIsPreviewMode;
795
- existing.isRevisionPreview = nextIsRevisionPreview;
796
- existing.sectionType = nextSectionType;
797
- existing.weaverseHost = getConfigString(configs, "weaverseHost") ?? existing.weaverseHost;
798
- existing.weaverseVersion = getConfigString(configs, "weaverseVersion") ?? existing.weaverseVersion;
799
- existing.internal.pageAssignment = config.data.pageAssignment;
800
- existing.internal.project = config.data.project;
801
- if (config.navigate) {
802
- existing.internal.navigate = config.navigate;
803
- }
804
- if (config.revalidate) {
805
- existing.internal.revalidate = config.revalidate;
806
- }
807
- if (config.themeSettingsStore) {
808
- existing.internal.themeSettingsStore = config.themeSettingsStore;
809
- }
810
- if (config.translationStore) {
811
- existing.internal.translationStore = config.translationStore;
812
- existing.internal.themeTextStore = config.translationStore;
813
- }
814
- existing.internal.merchantOverrides = config.merchantOverrides;
815
- registerRuntime(existing);
816
- return existing;
817
- }
818
- let [runtime, refreshedItems] = collectDeferredItemUpdates(
819
- () => new WeaverseNextRuntime(config)
820
- );
821
- runtime.pendingItemUpdates = refreshedItems;
822
- rebindPageItemsToRuntime(runtime);
823
- runtime.__weaverseNextRequestKey = requestKey;
824
- runtime.__weaverseNextLatestData = page;
825
- registerRuntime(runtime);
826
- return runtime;
827
- }
828
- function bindWeaverseNextStudioRuntime(runtime) {
829
- if (!runtime.isDesignMode) {
830
- return false;
831
- }
832
- let studio = getRuntimeWindow()?.weaverseStudio;
833
- if (!studio) {
834
- return false;
835
- }
836
- let boundRuntime = runtime;
837
- let activeRuntime = studio.weaverse;
838
- let activeRequestInfo = activeRuntime?.requestInfo;
839
- let isActiveUrl = !activeRequestInfo || activeRequestInfo.pathname === runtime.requestInfo.pathname && activeRequestInfo.search === runtime.requestInfo.search;
840
- if (!(boundRuntime.__weaverseNextStudioBound && isActiveUrl)) {
841
- studio.init?.(runtime);
842
- boundRuntime.__weaverseNextStudioBound = true;
843
- return true;
844
- }
845
- studio.refreshStudio?.({
846
- data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
847
- pageId: runtime.pageId,
848
- requestInfo: runtime.requestInfo
849
- });
850
- return true;
851
- }
852
- var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
853
- var STUDIO_BIND_MAX_ATTEMPTS = 100;
854
- function WeaverseNextStudioBridge(props) {
855
- let { runtime, navigate, revalidate, revalidateItem } = props;
856
- let lastRuntimeRef = useRef(runtime);
857
- let lastRuntimeDataRef = useRef(runtime.data);
858
- let lastRequestInfoRef = useRef(runtime.requestInfo);
859
- useEffect(() => {
860
- if (navigate) {
861
- runtime.internal.navigate = navigate;
862
- }
863
- if (revalidate) {
864
- runtime.internal.revalidate = revalidate;
865
- }
866
- if (revalidateItem) {
867
- runtime.internal.revalidateItem = revalidateItem;
868
- }
869
- if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
870
- return;
871
- }
872
- let attempts = 0;
873
- let interval = window.setInterval(() => {
874
- attempts += 1;
875
- if (bindWeaverseNextStudioRuntime(runtime) || attempts >= STUDIO_BIND_MAX_ATTEMPTS) {
876
- window.clearInterval(interval);
877
- }
878
- }, STUDIO_BIND_RETRY_INTERVAL_MS);
879
- return () => window.clearInterval(interval);
880
- }, [runtime, navigate, revalidate, revalidateItem]);
881
- useEffect(() => {
882
- if (lastRuntimeRef.current !== runtime) {
883
- lastRuntimeRef.current = runtime;
884
- lastRuntimeDataRef.current = runtime.data;
885
- lastRequestInfoRef.current = runtime.requestInfo;
886
- return;
887
- }
888
- if (lastRuntimeDataRef.current === runtime.data && lastRequestInfoRef.current === runtime.requestInfo) {
889
- return;
890
- }
891
- lastRuntimeDataRef.current = runtime.data;
892
- lastRequestInfoRef.current = runtime.requestInfo;
893
- bindWeaverseNextStudioRuntime(runtime);
894
- }, [runtime, runtime.data, runtime.requestInfo]);
895
- return null;
896
- }
897
-
898
- // src/studio-router.ts
899
- function shouldResetScroll(options) {
900
- if (!options || typeof options !== "object") {
901
- return true;
902
- }
903
- return !options.preventScrollReset;
904
- }
905
- function createWeaverseNextStudioInternals(router, options = {}) {
906
- return {
907
- navigate: (to, navigateOptions) => {
908
- let scroll = shouldResetScroll(navigateOptions);
909
- if (options.replace) {
910
- router.replace(to, { scroll });
911
- } else {
912
- router.push(to, { scroll });
913
- }
914
- },
915
- revalidate: () => {
916
- router.refresh();
917
- }
918
- };
919
- }
920
- function useWeaverseNextStudioInternals(options) {
921
- let router = useRouter();
922
- let ref = useRef(null);
923
- if (!ref.current || ref.current.router !== router || ref.current.replace !== options?.replace) {
924
- ref.current = {
925
- internals: createWeaverseNextStudioInternals(router, options),
926
- replace: options?.replace,
927
- router
928
- };
929
- }
930
- return ref.current.internals;
931
- }
932
- function WeaverseNextStudio(props) {
933
- let { runtime, replace, revalidateEndpoint } = props;
934
- let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
935
- let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
936
- let revalidateItemRef = useRef(null);
937
- if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
938
- revalidateItemRef.current = {
939
- endpoint,
940
- revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
941
- runtime
942
- };
943
- }
944
- return /* @__PURE__ */ jsx(
945
- WeaverseNextStudioBridge,
946
- {
947
- navigate,
948
- revalidate,
949
- revalidateItem: revalidateItemRef.current.revalidateItem,
950
- runtime
951
- }
952
- );
953
- }
954
- var EMPTY_DATA_CONTEXT = {};
955
- var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
956
- function getRenderablePage2(data) {
957
- let page = data?.page;
958
- if (!page) {
959
- return null;
960
- }
961
- let rootId = page.rootId ?? page.items.find((item) => item.type === "main")?.id ?? page.items[0]?.id ?? page.id;
962
- return { ...page, rootId };
963
- }
964
- var WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(props) {
965
- let context = useContext(WeaverseNextContext);
966
- let client = props.client ?? context?.client;
967
- let data = props.data ?? client?.data ?? null;
968
- let dataContext = props.dataContext ?? client?.dataContext ?? EMPTY_DATA_CONTEXT;
969
- let page = useMemo(() => getRenderablePage2(data), [data]);
970
- let weaverse = useMemo(() => {
971
- if (!page) {
972
- return null;
973
- }
974
- return createWeaverseNextRuntime({
975
- client,
976
- data: { ...data, page },
977
- dataContext,
978
- merchantOverrides: context?.merchantOverrides,
979
- themeSettingsStore: context?.themeSettingsStore,
980
- translationStore: context?.translationStore
981
- });
982
- }, [
983
- client,
984
- page,
985
- data,
986
- dataContext,
987
- context?.merchantOverrides,
988
- context?.themeSettingsStore,
989
- context?.translationStore
990
- ]);
991
- useIsomorphicLayoutEffect(() => {
992
- weaverse?.flushRenderPhaseUpdates();
993
- }, [weaverse]);
994
- if (!weaverse) {
995
- return null;
996
- }
997
- return /* @__PURE__ */ jsxs(Fragment, { children: [
998
- /* @__PURE__ */ jsx(WeaverseRoot, { context: weaverse }),
999
- /* @__PURE__ */ jsx(WeaverseNextStudio, { runtime: weaverse })
1000
- ] });
1001
- });
1002
-
1003
- // src/studio-script-src.ts
1004
- var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
1005
- function isLoopbackHostname(hostname) {
1006
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname.endsWith(".localhost");
1007
- }
1008
- function normalizeOrigin(value) {
1009
- if (!value) {
1010
- return null;
1011
- }
1012
- try {
1013
- return new URL(value).origin;
1014
- } catch {
1015
- return null;
1016
- }
1017
- }
1018
- function isTrustedStudioOrigin(origin, storefrontHostname) {
1019
- let parsed;
1020
- try {
1021
- parsed = new URL(origin);
1022
- } catch {
1023
- return false;
1024
- }
1025
- let { hostname, protocol } = parsed;
1026
- if (isLoopbackHostname(storefrontHostname) && isLoopbackHostname(hostname)) {
1027
- return protocol === "http:" || protocol === "https:";
1028
- }
1029
- if (protocol !== "https:") {
1030
- return false;
1031
- }
1032
- return TRUSTED_STUDIO_DOMAINS.some(
1033
- (domain) => hostname === domain || hostname.endsWith(`.${domain}`)
1034
- );
1035
- }
1036
- function toSearchParams(context) {
1037
- if (context?.searchParams) {
1038
- return new URLSearchParams(context.searchParams);
1039
- }
1040
- if (context?.url) {
1041
- let url = typeof context.url === "string" ? new URL(context.url, "http://localhost") : context.url;
1042
- return new URLSearchParams(url.searchParams);
1043
- }
1044
- return new URLSearchParams();
1045
- }
1046
- function last(searchParams, key) {
1047
- let values = searchParams.getAll(key);
1048
- return values.length ? values.at(-1) ?? null : null;
1049
- }
1050
- function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
1051
- let searchParams = toSearchParams(context);
1052
- let isDesignMode = context?.isDesignMode ?? last(searchParams, "isDesignMode") === "true";
1053
- let isPreviewMode = context?.isPreviewMode ?? last(searchParams, "isPreviewMode") === "true";
1054
- let isRevisionPreview = context?.isRevisionPreview ?? Boolean(last(searchParams, "__revisionId"));
1055
- if (!(isDesignMode || isPreviewMode || isRevisionPreview)) {
1056
- return null;
1057
- }
1058
- let origin = normalizeOrigin(last(searchParams, "weaverseHost"));
1059
- if (!origin) {
1060
- return null;
1061
- }
1062
- let storefrontHostname = options.storefrontHostname ?? "";
1063
- if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
1064
- return null;
1065
- }
1066
- let bundle = isPreviewMode || isRevisionPreview ? "preview.js" : "index.js";
1067
- let version = last(searchParams, "weaverseVersion");
1068
- let url = `${origin}/static/studio/next/${bundle}`;
1069
- return version ? `${url}?v=${encodeURIComponent(version)}` : url;
1070
- }
1071
-
1072
- // src/studio-connect.tsx
1073
- var loadedScriptSrcs = /* @__PURE__ */ new Set();
1074
- function loadScript(src) {
1075
- if (typeof document === "undefined") {
1076
- return;
1077
- }
1078
- if (loadedScriptSrcs.has(src)) {
1079
- return;
1080
- }
1081
- let existing = document.querySelector(`script[src="${src}"]`);
1082
- if (existing) {
1083
- loadedScriptSrcs.add(src);
1084
- return;
1085
- }
1086
- let script = document.createElement("script");
1087
- script.async = true;
1088
- script.src = src;
1089
- document.head.appendChild(script);
1090
- loadedScriptSrcs.add(src);
1091
- }
1092
- function loadWeaverseNextStudioScript(context, options = {}) {
1093
- let resolvedContext = context;
1094
- if (!resolvedContext && typeof window !== "undefined") {
1095
- resolvedContext = {
1096
- searchParams: new URLSearchParams(window.location.search),
1097
- url: window.location.href
1098
- };
1099
- }
1100
- let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1101
- storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1102
- });
1103
- if (src) {
1104
- loadScript(src);
1105
- }
1106
- }
1107
- function WeaverseNextStudioConnect(props) {
1108
- useEffect(() => {
1109
- loadWeaverseNextStudioScript(props.context, {
1110
- storefrontHostname: props.storefrontHostname
1111
- });
1112
- }, [props.context, props.storefrontHostname]);
1113
- return null;
1114
- }
1115
200
 
1116
- export { ThemeTextProvider, ThemeTextStore, TranslationProvider, TranslationStore, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createTranslate, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, getNestedKey, interpolate, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, useThemeSettings, useThemeText, useTranslation, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
201
+ export { ThemeTextStore, TranslationStore, createTranslate, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, getNestedKey, interpolate, resolveWeaverseNextStudioScriptSrc };
1117
202
  //# sourceMappingURL=index.mjs.map
1118
203
  //# sourceMappingURL=index.mjs.map