@weaverse/next 0.1.0-alpha.1 → 0.1.0-alpha.11

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,19 +1,91 @@
1
- import { buildWeaverseNextRequestInfo, generateDataFromSchema } from './chunk-SS6QUKYY.mjs';
2
- export { buildWeaverseNextRequestInfo, runWeaverseComponentLoaders } from './chunk-SS6QUKYY.mjs';
3
- import { WeaverseRoot, Weaverse, WeaverseItemStore } from '@weaverse/react';
1
+ import { buildWeaverseNextRequestInfo, generateDataFromSchema } from './chunk-XXGMPTWH.mjs';
2
+ export { buildWeaverseNextRequestInfo, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, runWeaverseComponentLoaders } from './chunk-XXGMPTWH.mjs';
3
+ import { WeaverseRoot, Weaverse, WeaverseItemStore, useSafeExternalStore } from '@weaverse/react';
4
4
  export { isBrowser, isIframe, useChildInstances, useItemInstance, useParentInstance, useWeaverse } from '@weaverse/react';
5
5
  import { createSchema } from '@weaverse/schema';
6
- export { createSchema } from '@weaverse/schema';
6
+ export * from '@weaverse/schema';
7
7
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
8
- import { createContext, memo, useContext, useMemo, useRef, useEffect, useSyncExternalStore } from 'react';
8
+ import { createContext, memo, useContext, useMemo, useEffect, useLayoutEffect, useRef, useSyncExternalStore } from 'react';
9
+ import { useRouter } from 'next/navigation';
9
10
 
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;
27
+ }
28
+ }
10
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;
11
38
  constructor(initialData, weaverse) {
12
39
  super(initialData, weaverse);
13
- let { data, ...rest } = initialData;
14
40
  let schemaData = generateDataFromSchema(this.Element?.schema);
15
- Object.assign(this._store, schemaData, data, rest);
41
+ Object.assign(this._store, schemaData, flattenItemData(initialData));
16
42
  }
43
+ /**
44
+ * Flatten nested `data` on updates exactly like the constructor does. Core's
45
+ * `initProject()` calls `setData(item)` when a new runtime serves a page
46
+ * whose item instances already exist (e.g. locale/client navigation), and
47
+ * the inherited shallow merge would leave stale top-level props behind.
48
+ * Assigning through the inherited `data` setter swaps the `_store` ref,
49
+ * which invalidates the memoized snapshot below. The subscriber emit is
50
+ * queued instead of fired when a render-phase deferral is active (see
51
+ * `collectDeferredItemUpdates`).
52
+ */
53
+ setData = (update) => {
54
+ this.data = flattenItemData(update);
55
+ if (deferredItemUpdates) {
56
+ deferredItemUpdates.add(this);
57
+ } else {
58
+ this.triggerUpdate();
59
+ }
60
+ return this.data;
61
+ };
62
+ /**
63
+ * Overlay item-level translations onto the base store when the runtime is in
64
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
65
+ * memoized by `_store` ref + translation entry ref so the returned object is
66
+ * referentially stable, and a fast path that returns the base store untouched
67
+ * when the item has no translations.
68
+ */
69
+ getSnapShot = () => {
70
+ let base = this._store;
71
+ let translations = this.weaverse.translationMap[this._id];
72
+ if (!translations) {
73
+ return base;
74
+ }
75
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
76
+ return this._cachedSnapshot;
77
+ }
78
+ let merged = { ...base };
79
+ for (let [key, entry] of Object.entries(translations)) {
80
+ if (entry.translatedValue !== void 0) {
81
+ merged[key] = entry.translatedValue;
82
+ }
83
+ }
84
+ this._cachedSnapshot = merged;
85
+ this._snapshotStoreRef = base;
86
+ this._snapshotTranslationRef = translations;
87
+ return merged;
88
+ };
17
89
  };
18
90
  function ensureNextItemConstructor() {
19
91
  if (!Weaverse.ItemConstructor || Weaverse.ItemConstructor === WeaverseItemStore || Weaverse.ItemConstructor === WeaverseNextItem) {
@@ -138,30 +210,258 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
138
210
  };
139
211
  return store;
140
212
  }
213
+ function getNestedKey(obj, path, fallback) {
214
+ let keys = path.split(".");
215
+ let current = obj;
216
+ for (let key of keys) {
217
+ if (current == null || typeof current !== "object") {
218
+ return fallback;
219
+ }
220
+ current = current[key];
221
+ }
222
+ if (typeof current === "string") {
223
+ return current;
224
+ }
225
+ return fallback;
226
+ }
227
+ function interpolate(template, variables) {
228
+ if (!variables) {
229
+ return template;
230
+ }
231
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
232
+ let value = variables[key];
233
+ return value === void 0 ? match : String(value);
234
+ });
235
+ }
236
+ var TranslationContext = createContext(null);
237
+ TranslationContext.displayName = "WeaverseNextTranslationContext";
238
+ function createTranslate(sources) {
239
+ let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
240
+ return (key, variables) => {
241
+ if (externalT) {
242
+ let external = externalT(key, variables);
243
+ if (external && external !== key) {
244
+ return external;
245
+ }
246
+ }
247
+ let design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
248
+ let override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
249
+ let staticValue = getNestedKey(staticContent, key);
250
+ let raw = design ?? override ?? staticValue ?? key;
251
+ return interpolate(raw, variables);
252
+ };
253
+ }
254
+ var EMPTY = {};
255
+ var NOOP_SUBSCRIBE = () => () => {
256
+ };
257
+ var NOOP_SNAPSHOT = () => EMPTY;
258
+ function TranslationProvider({
259
+ staticContent,
260
+ merchantOverrides,
261
+ t: externalT,
262
+ translationStore,
263
+ themeTextStore,
264
+ children
265
+ }) {
266
+ let store = translationStore ?? themeTextStore ?? null;
267
+ let designOverrides = useSafeExternalStore(
268
+ store?.subscribe ?? NOOP_SUBSCRIBE,
269
+ store?.getSnapshot ?? NOOP_SNAPSHOT,
270
+ store?.getServerSnapshot ?? NOOP_SNAPSHOT
271
+ );
272
+ let value = useMemo(
273
+ () => ({
274
+ t: createTranslate({
275
+ staticContent,
276
+ merchantOverrides,
277
+ designOverrides,
278
+ externalT
279
+ }),
280
+ translationStore: store,
281
+ // Deprecated alias — same instance, kept for backward compatibility.
282
+ themeTextStore: store,
283
+ merchantOverrides: merchantOverrides ?? null
284
+ }),
285
+ [staticContent, merchantOverrides, designOverrides, externalT, store]
286
+ );
287
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
288
+ }
289
+ function useTranslation() {
290
+ let ctx = useContext(TranslationContext);
291
+ if (!ctx) {
292
+ throw new Error(
293
+ "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
294
+ );
295
+ }
296
+ return ctx;
297
+ }
298
+ var ThemeTextProvider = TranslationProvider;
299
+ var useThemeText = useTranslation;
300
+
301
+ // src/translation-store.ts
302
+ var TranslationStore = class {
303
+ overrides = {};
304
+ listeners = /* @__PURE__ */ new Set();
305
+ setOverrides = (overrides) => {
306
+ this.overrides = { ...overrides };
307
+ this.emit();
308
+ };
309
+ updateOverrides = (newOverrides) => {
310
+ this.overrides = { ...this.overrides, ...newOverrides };
311
+ this.emit();
312
+ };
313
+ getSnapshot = () => this.overrides;
314
+ getServerSnapshot = () => this.overrides;
315
+ subscribe = (callback) => {
316
+ this.listeners.add(callback);
317
+ return () => {
318
+ this.listeners.delete(callback);
319
+ };
320
+ };
321
+ emit = () => {
322
+ for (const listener of Array.from(this.listeners)) {
323
+ try {
324
+ listener();
325
+ } catch (error) {
326
+ console.error("TranslationStore: Listener error", error);
327
+ }
328
+ }
329
+ };
330
+ };
331
+ var ThemeTextStore = TranslationStore;
332
+ var WeaverseNextRootContext = createContext(null);
333
+ function WeaverseNextRootProvider(props) {
334
+ let {
335
+ children,
336
+ initialThemeSettings,
337
+ merchantOverrides,
338
+ staticContent,
339
+ t,
340
+ themeSchema,
341
+ publicEnv
342
+ } = props;
343
+ let storeRef = useRef(null);
344
+ if (!storeRef.current) {
345
+ storeRef.current = createWeaverseNextThemeSettingsStore({
346
+ publicEnv,
347
+ schema: themeSchema,
348
+ settings: initialThemeSettings
349
+ });
350
+ }
351
+ let themeSettingsStore = storeRef.current;
352
+ let translationStoreRef = useRef(null);
353
+ if (!translationStoreRef.current) {
354
+ translationStoreRef.current = new TranslationStore();
355
+ }
356
+ let translationStore = translationStoreRef.current;
357
+ let appliedThemeSettingsRef = useRef(initialThemeSettings);
358
+ useEffect(() => {
359
+ if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
360
+ themeSettingsStore.updateThemeSettings(initialThemeSettings);
361
+ appliedThemeSettingsRef.current = initialThemeSettings;
362
+ }
363
+ }, [initialThemeSettings, themeSettingsStore]);
364
+ let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
365
+ let rootContextValue = useMemo(
366
+ () => ({
367
+ merchantOverrides,
368
+ staticContent: resolvedStaticContent,
369
+ themeSettingsStore,
370
+ translationStore
371
+ }),
372
+ [
373
+ merchantOverrides,
374
+ resolvedStaticContent,
375
+ themeSettingsStore,
376
+ translationStore
377
+ ]
378
+ );
379
+ let contextValue = useMemo(
380
+ () => ({
381
+ merchantOverrides,
382
+ themeSettings: themeSettingsStore.getSnapshot(),
383
+ themeSettingsStore,
384
+ translationStore
385
+ }),
386
+ [merchantOverrides, themeSettingsStore, translationStore]
387
+ );
388
+ return /* @__PURE__ */ jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
389
+ TranslationProvider,
390
+ {
391
+ merchantOverrides,
392
+ staticContent: resolvedStaticContent,
393
+ t,
394
+ translationStore,
395
+ children
396
+ }
397
+ ) }) });
398
+ }
141
399
  var EMPTY_THEME_SETTINGS = {};
400
+ var EMPTY_STATIC_CONTENT = {};
401
+ function getSchemaStaticContent(schema) {
402
+ let i18n = schema?.i18n;
403
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
404
+ }
142
405
  var WeaverseNextContext = createContext(null);
143
406
  function WeaverseNextProvider(props) {
144
407
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
408
+ let rootContext = useContext(WeaverseNextRootContext);
145
409
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
146
- let themeSettingsStore = useMemo(
147
- () => createWeaverseNextThemeSettingsStore({
410
+ let ownTranslationStoreRef = useRef(null);
411
+ if (!(rootContext || ownTranslationStoreRef.current)) {
412
+ ownTranslationStoreRef.current = new TranslationStore();
413
+ }
414
+ let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
415
+ let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
416
+ let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
417
+ let ownThemeSettingsStore = useMemo(() => {
418
+ if (rootContext) {
419
+ return null;
420
+ }
421
+ return createWeaverseNextThemeSettingsStore({
148
422
  schema: client?.themeSchema,
149
423
  settings: themeSettingsValue
150
- }),
151
- [client?.themeSchema, themeSettingsValue]
152
- );
424
+ });
425
+ }, [rootContext, client?.themeSchema, themeSettingsValue]);
426
+ let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
427
+ let mergedIntoRootRef = useRef(void 0);
428
+ useEffect(() => {
429
+ if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
430
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
431
+ mergedIntoRootRef.current = themeSettingsValue;
432
+ }
433
+ }, [rootContext, themeSettingsValue]);
153
434
  let value = useMemo(
154
435
  () => ({
155
436
  client,
156
437
  rootData,
157
438
  pageData,
158
439
  commerce: commerce ?? client?.commerce,
440
+ merchantOverrides,
159
441
  themeSettings: themeSettingsStore.getSnapshot(),
160
- themeSettingsStore
442
+ themeSettingsStore,
443
+ translationStore
161
444
  }),
162
- [client, rootData, pageData, commerce, themeSettingsStore]
445
+ [
446
+ client,
447
+ rootData,
448
+ pageData,
449
+ commerce,
450
+ merchantOverrides,
451
+ themeSettingsStore,
452
+ translationStore
453
+ ]
163
454
  );
164
- return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children });
455
+ return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsx(
456
+ TranslationProvider,
457
+ {
458
+ merchantOverrides,
459
+ staticContent,
460
+ t: props.t,
461
+ translationStore,
462
+ children
463
+ }
464
+ ) });
165
465
  }
166
466
 
167
467
  // src/hooks.ts
@@ -245,9 +545,31 @@ var WeaverseNextRuntime = class extends Weaverse {
245
545
  isPreviewMode;
246
546
  isRevisionPreview;
247
547
  sectionType;
548
+ // ─── Item-level translation sidecar (design mode only) ─────────────
549
+ /**
550
+ * Translation map received from Builder in design mode, keyed by item ID.
551
+ * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
552
+ * components, so translations render without mutating the base `_store`.
553
+ */
248
554
  translationMap = {};
249
- translationLocale = "";
250
- translationLanguageId = "";
555
+ translationLocale = String();
556
+ translationLanguageId = String();
557
+ /**
558
+ * Fresh page data captured while React was rendering. Runtime reuse happens
559
+ * inside the renderer's render phase (`useMemo`), where applying it through
560
+ * `setProjectData()` would emit item updates to `useSyncExternalStore`
561
+ * subscribers mid-render. The renderer applies it post-commit via
562
+ * `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
563
+ * Studio tree owns the page data there.
564
+ */
565
+ pendingProjectData;
566
+ /**
567
+ * Reused item instances whose data was refreshed while this runtime was
568
+ * constructed during render (core `initProject()` calls `setData()` on
569
+ * them). Their data is already fresh; only the subscriber notification was
570
+ * deferred to `flushRenderPhaseUpdates()`.
571
+ */
572
+ pendingItemUpdates = [];
251
573
  constructor(config) {
252
574
  let { client, data } = config;
253
575
  let page = getRenderablePage(data);
@@ -269,7 +591,9 @@ var WeaverseNextRuntime = class extends Weaverse {
269
591
  this.isPreviewMode = requestContext?.isPreviewMode ?? false;
270
592
  this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
271
593
  this.sectionType = requestContext?.sectionType;
594
+ let translationStore = config.translationStore ?? new TranslationStore();
272
595
  this.internal = {
596
+ merchantOverrides: config.merchantOverrides,
273
597
  navigate: config.navigate,
274
598
  pageAssignment: data.pageAssignment,
275
599
  project: data.project,
@@ -277,17 +601,119 @@ var WeaverseNextRuntime = class extends Weaverse {
277
601
  themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
278
602
  schema: client?.themeSchema,
279
603
  settings: client?.themeSettings
280
- })
604
+ }),
605
+ translationStore,
606
+ themeTextStore: translationStore
281
607
  };
608
+ this.extractTranslationSidecar();
282
609
  }
283
- extractTranslationSidecar = () => void 0;
610
+ /**
611
+ * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
612
+ * the current page data (Builder attaches them in design mode). Unlike
613
+ * Hydrogen, this also clears the sidecar to defaults when the new page data
614
+ * carries none, so a reused runtime / project-data swap can't leave a stale
615
+ * map overlaying the fresh tree.
616
+ */
617
+ extractTranslationSidecar = () => {
618
+ let pageData = this.data;
619
+ let translationMap = pageData?.translationMap;
620
+ if (translationMap) {
621
+ this.translationMap = translationMap;
622
+ this.translationLocale = pageData?.translationLocale || "";
623
+ this.translationLanguageId = pageData?.translationLanguageId || "";
624
+ } else {
625
+ this.translationMap = {};
626
+ this.translationLocale = "";
627
+ this.translationLanguageId = "";
628
+ }
629
+ };
630
+ /**
631
+ * Replace the translation sidecar (Builder pushes this when translation data
632
+ * arrives or the active language changes) and refresh every item so their
633
+ * merged snapshots recompute.
634
+ */
284
635
  setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
285
636
  this.translationMap = map;
286
637
  this.translationLocale = locale;
287
638
  this.translationLanguageId = languageId;
639
+ this.refreshAllItems();
640
+ };
641
+ /**
642
+ * Update a single translatable field for one item. Assigns a new per-item
643
+ * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
644
+ * invalidates, then triggers only that item's subscribers.
645
+ */
646
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
647
+ if (!this.translationMap[itemId]) {
648
+ this.translationMap[itemId] = {};
649
+ }
650
+ this.translationMap[itemId] = {
651
+ ...this.translationMap[itemId],
652
+ [key]: { originalValue, translatedValue }
653
+ };
654
+ let instance = this.itemInstances.get(itemId);
655
+ if (instance) {
656
+ instance.triggerUpdate();
657
+ }
658
+ };
659
+ /**
660
+ * Collect translation changes for the save flow. Returns `undefined` unless
661
+ * both the locale and language ID are set and at least one entry exists;
662
+ * each entry key is namespaced as `data.<field>` to match Builder.
663
+ */
664
+ getTranslationChanges = () => {
665
+ let languageId = this.translationLanguageId;
666
+ if (!(this.translationLocale && languageId)) {
667
+ return;
668
+ }
669
+ let entries = [];
670
+ for (let [itemId, fields] of Object.entries(this.translationMap)) {
671
+ for (let [key, entry] of Object.entries(fields)) {
672
+ entries.push({
673
+ itemId,
674
+ key: `data.${key}`,
675
+ originalValue: entry.originalValue,
676
+ translatedValue: entry.translatedValue
677
+ });
678
+ }
679
+ }
680
+ if (entries.length === 0) {
681
+ return;
682
+ }
683
+ return { languageId, entries };
684
+ };
685
+ /**
686
+ * Re-extract the translation sidecar whenever project data is replaced (route
687
+ * change / non-design reuse), before rebuilding item stores. Takes the
688
+ * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
689
+ * the reuse branch pass in.
690
+ */
691
+ setProjectData = (data) => {
692
+ this.data = data;
693
+ this.extractTranslationSidecar();
694
+ this.initProject();
695
+ };
696
+ /**
697
+ * Apply updates deferred off the render phase: swap in any pending page data
698
+ * captured on runtime reuse, then notify items whose data was refreshed
699
+ * during construction. The renderer calls this from a layout effect right
700
+ * after commit, so subscribers re-render before paint (no content flash) and
701
+ * without React's setState-in-render warning.
702
+ */
703
+ flushRenderPhaseUpdates = () => {
704
+ let pendingData = this.pendingProjectData;
705
+ if (pendingData) {
706
+ this.pendingProjectData = void 0;
707
+ this.setProjectData(pendingData);
708
+ }
709
+ let pendingItems = this.pendingItemUpdates;
710
+ if (pendingItems.length > 0) {
711
+ this.pendingItemUpdates = [];
712
+ for (let item of pendingItems) {
713
+ item.triggerUpdate();
714
+ }
715
+ }
288
716
  };
289
- updateTranslation = () => void 0;
290
- getTranslationChanges = () => void 0;
291
717
  };
292
718
  function createWeaverseNextRuntime(config) {
293
719
  let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
@@ -297,9 +723,12 @@ function createWeaverseNextRuntime(config) {
297
723
  let requestKey = getRuntimeKey(page.id, requestInfo);
298
724
  if (existing?.__weaverseNextRequestKey === requestKey) {
299
725
  let nextIsDesignMode = config.client?.requestContext?.isDesignMode ?? false;
300
- if (!(existing.isDesignMode || nextIsDesignMode)) {
301
- existing.setProjectData(page);
726
+ if (existing.isDesignMode || nextIsDesignMode) {
727
+ existing.pendingProjectData = void 0;
728
+ } else {
729
+ existing.pendingProjectData = page;
302
730
  }
731
+ existing.__weaverseNextLatestData = page;
303
732
  existing.dataContext = resolveDataContext(config);
304
733
  existing.requestInfo = requestInfo;
305
734
  existing.projectId = resolveProjectId(config.client, config.data, page.id);
@@ -320,11 +749,20 @@ function createWeaverseNextRuntime(config) {
320
749
  if (config.themeSettingsStore) {
321
750
  existing.internal.themeSettingsStore = config.themeSettingsStore;
322
751
  }
752
+ if (config.translationStore) {
753
+ existing.internal.translationStore = config.translationStore;
754
+ existing.internal.themeTextStore = config.translationStore;
755
+ }
756
+ existing.internal.merchantOverrides = config.merchantOverrides;
323
757
  registerRuntime(existing);
324
758
  return existing;
325
759
  }
326
- let runtime = new WeaverseNextRuntime(config);
760
+ let [runtime, refreshedItems] = collectDeferredItemUpdates(
761
+ () => new WeaverseNextRuntime(config)
762
+ );
763
+ runtime.pendingItemUpdates = refreshedItems;
327
764
  runtime.__weaverseNextRequestKey = requestKey;
765
+ runtime.__weaverseNextLatestData = page;
328
766
  registerRuntime(runtime);
329
767
  return runtime;
330
768
  }
@@ -337,22 +775,48 @@ function bindWeaverseNextStudioRuntime(runtime) {
337
775
  return false;
338
776
  }
339
777
  let boundRuntime = runtime;
340
- if (!boundRuntime.__weaverseNextStudioBound) {
778
+ let activeRuntime = studio.weaverse;
779
+ let activeRequestInfo = activeRuntime?.requestInfo;
780
+ let isActiveUrl = !activeRequestInfo || activeRequestInfo.pathname === runtime.requestInfo.pathname && activeRequestInfo.search === runtime.requestInfo.search;
781
+ if (!(boundRuntime.__weaverseNextStudioBound && isActiveUrl)) {
341
782
  studio.init?.(runtime);
342
783
  boundRuntime.__weaverseNextStudioBound = true;
343
784
  return true;
344
785
  }
345
786
  studio.refreshStudio?.({
346
- data: runtime.data,
787
+ data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
347
788
  pageId: runtime.pageId,
348
789
  requestInfo: runtime.requestInfo
349
790
  });
350
791
  return true;
351
792
  }
793
+
794
+ // src/revalidate-item.ts
795
+ var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
796
+ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
797
+ let instance = runtime.itemInstances.get(draftItem.id);
798
+ if (!instance) {
799
+ throw new Error(
800
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
801
+ );
802
+ }
803
+ let response = await fetch(endpoint, {
804
+ method: "POST",
805
+ headers: { "Content-Type": "application/json" },
806
+ body: JSON.stringify({ draftItem })
807
+ });
808
+ if (!response.ok) {
809
+ throw new Error(
810
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
811
+ );
812
+ }
813
+ let { loaderData } = await response.json();
814
+ instance.setData({ loaderData: loaderData ?? null });
815
+ }
352
816
  var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
353
817
  var STUDIO_BIND_MAX_ATTEMPTS = 100;
354
818
  function WeaverseNextStudioBridge(props) {
355
- let { runtime, navigate, revalidate } = props;
819
+ let { runtime, navigate, revalidate, revalidateItem } = props;
356
820
  let lastRuntimeRef = useRef(runtime);
357
821
  let lastRuntimeDataRef = useRef(runtime.data);
358
822
  let lastRequestInfoRef = useRef(runtime.requestInfo);
@@ -363,6 +827,9 @@ function WeaverseNextStudioBridge(props) {
363
827
  if (revalidate) {
364
828
  runtime.internal.revalidate = revalidate;
365
829
  }
830
+ if (revalidateItem) {
831
+ runtime.internal.revalidateItem = revalidateItem;
832
+ }
366
833
  if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
367
834
  return;
368
835
  }
@@ -374,7 +841,7 @@ function WeaverseNextStudioBridge(props) {
374
841
  }
375
842
  }, STUDIO_BIND_RETRY_INTERVAL_MS);
376
843
  return () => window.clearInterval(interval);
377
- }, [runtime, navigate, revalidate]);
844
+ }, [runtime, navigate, revalidate, revalidateItem]);
378
845
  useEffect(() => {
379
846
  if (lastRuntimeRef.current !== runtime) {
380
847
  lastRuntimeRef.current = runtime;
@@ -391,7 +858,65 @@ function WeaverseNextStudioBridge(props) {
391
858
  }, [runtime, runtime.data, runtime.requestInfo]);
392
859
  return null;
393
860
  }
861
+
862
+ // src/studio-router.ts
863
+ function shouldResetScroll(options) {
864
+ if (!options || typeof options !== "object") {
865
+ return true;
866
+ }
867
+ return !options.preventScrollReset;
868
+ }
869
+ function createWeaverseNextStudioInternals(router, options = {}) {
870
+ return {
871
+ navigate: (to, navigateOptions) => {
872
+ let scroll = shouldResetScroll(navigateOptions);
873
+ if (options.replace) {
874
+ router.replace(to, { scroll });
875
+ } else {
876
+ router.push(to, { scroll });
877
+ }
878
+ },
879
+ revalidate: () => {
880
+ router.refresh();
881
+ }
882
+ };
883
+ }
884
+ function useWeaverseNextStudioInternals(options) {
885
+ let router = useRouter();
886
+ let ref = useRef(null);
887
+ if (!ref.current || ref.current.router !== router || ref.current.replace !== options?.replace) {
888
+ ref.current = {
889
+ internals: createWeaverseNextStudioInternals(router, options),
890
+ replace: options?.replace,
891
+ router
892
+ };
893
+ }
894
+ return ref.current.internals;
895
+ }
896
+ function WeaverseNextStudio(props) {
897
+ let { runtime, replace, revalidateEndpoint } = props;
898
+ let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
899
+ let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
900
+ let revalidateItemRef = useRef(null);
901
+ if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
902
+ revalidateItemRef.current = {
903
+ endpoint,
904
+ revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
905
+ runtime
906
+ };
907
+ }
908
+ return /* @__PURE__ */ jsx(
909
+ WeaverseNextStudioBridge,
910
+ {
911
+ navigate,
912
+ revalidate,
913
+ revalidateItem: revalidateItemRef.current.revalidateItem,
914
+ runtime
915
+ }
916
+ );
917
+ }
394
918
  var EMPTY_DATA_CONTEXT = {};
919
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
395
920
  function getRenderablePage2(data) {
396
921
  let page = data?.page;
397
922
  if (!page) {
@@ -414,15 +939,28 @@ var WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(props) {
414
939
  client,
415
940
  data: { ...data, page },
416
941
  dataContext,
417
- themeSettingsStore: context?.themeSettingsStore
942
+ merchantOverrides: context?.merchantOverrides,
943
+ themeSettingsStore: context?.themeSettingsStore,
944
+ translationStore: context?.translationStore
418
945
  });
419
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
946
+ }, [
947
+ client,
948
+ page,
949
+ data,
950
+ dataContext,
951
+ context?.merchantOverrides,
952
+ context?.themeSettingsStore,
953
+ context?.translationStore
954
+ ]);
955
+ useIsomorphicLayoutEffect(() => {
956
+ weaverse?.flushRenderPhaseUpdates();
957
+ }, [weaverse]);
420
958
  if (!weaverse) {
421
959
  return null;
422
960
  }
423
961
  return /* @__PURE__ */ jsxs(Fragment, { children: [
424
962
  /* @__PURE__ */ jsx(WeaverseRoot, { context: weaverse }),
425
- /* @__PURE__ */ jsx(WeaverseNextStudioBridge, { runtime: weaverse })
963
+ /* @__PURE__ */ jsx(WeaverseNextStudio, { runtime: weaverse })
426
964
  ] });
427
965
  });
428
966
 
@@ -489,10 +1027,9 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
489
1027
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
490
1028
  return null;
491
1029
  }
492
- let framework = options.framework ?? "hydrogen";
493
1030
  let bundle = isDesignMode ? "index.js" : "preview.js";
494
1031
  let version = last(searchParams, "weaverseVersion");
495
- let url = `${origin}/static/studio/${framework}/${bundle}`;
1032
+ let url = `${origin}/static/studio/next/${bundle}`;
496
1033
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
497
1034
  }
498
1035
 
@@ -516,25 +1053,30 @@ function loadScript(src) {
516
1053
  document.head.appendChild(script);
517
1054
  loadedScriptSrcs.add(src);
518
1055
  }
1056
+ function loadWeaverseNextStudioScript(context, options = {}) {
1057
+ let resolvedContext = context;
1058
+ if (!resolvedContext && typeof window !== "undefined") {
1059
+ resolvedContext = {
1060
+ searchParams: new URLSearchParams(window.location.search),
1061
+ url: window.location.href
1062
+ };
1063
+ }
1064
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1065
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1066
+ });
1067
+ if (src) {
1068
+ loadScript(src);
1069
+ }
1070
+ }
519
1071
  function WeaverseNextStudioConnect(props) {
520
1072
  useEffect(() => {
521
- let context = props.context;
522
- if (!context && typeof window !== "undefined") {
523
- context = {
524
- searchParams: new URLSearchParams(window.location.search),
525
- url: window.location.href
526
- };
527
- }
528
- let src = resolveWeaverseNextStudioScriptSrc(context, {
529
- storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1073
+ loadWeaverseNextStudioScript(props.context, {
1074
+ storefrontHostname: props.storefrontHostname
530
1075
  });
531
- if (src) {
532
- loadScript(src);
533
- }
534
1076
  }, [props.context, props.storefrontHostname]);
535
1077
  return null;
536
1078
  }
537
1079
 
538
- export { WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRuntime, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextThemeSettingsStore, resolveWeaverseNextStudioScriptSrc, useThemeSettings, useWeaverseCommerce, useWeaversePageData, useWeaverseRootData };
1080
+ export { DEFAULT_REVALIDATE_ENDPOINT, ThemeTextProvider, ThemeTextStore, TranslationProvider, TranslationStore, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createTranslate, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, getNestedKey, interpolate, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, revalidateWeaverseNextItem, useThemeSettings, useThemeText, useTranslation, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
539
1081
  //# sourceMappingURL=index.mjs.map
540
1082
  //# sourceMappingURL=index.mjs.map