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

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
  }
@@ -343,16 +781,39 @@ function bindWeaverseNextStudioRuntime(runtime) {
343
781
  return true;
344
782
  }
345
783
  studio.refreshStudio?.({
346
- data: runtime.data,
784
+ data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
347
785
  pageId: runtime.pageId,
348
786
  requestInfo: runtime.requestInfo
349
787
  });
350
788
  return true;
351
789
  }
790
+
791
+ // src/revalidate-item.ts
792
+ var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
793
+ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
794
+ let instance = runtime.itemInstances.get(draftItem.id);
795
+ if (!instance) {
796
+ throw new Error(
797
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
798
+ );
799
+ }
800
+ let response = await fetch(endpoint, {
801
+ method: "POST",
802
+ headers: { "Content-Type": "application/json" },
803
+ body: JSON.stringify({ draftItem })
804
+ });
805
+ if (!response.ok) {
806
+ throw new Error(
807
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
808
+ );
809
+ }
810
+ let { loaderData } = await response.json();
811
+ instance.setData({ loaderData: loaderData ?? null });
812
+ }
352
813
  var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
353
814
  var STUDIO_BIND_MAX_ATTEMPTS = 100;
354
815
  function WeaverseNextStudioBridge(props) {
355
- let { runtime, navigate, revalidate } = props;
816
+ let { runtime, navigate, revalidate, revalidateItem } = props;
356
817
  let lastRuntimeRef = useRef(runtime);
357
818
  let lastRuntimeDataRef = useRef(runtime.data);
358
819
  let lastRequestInfoRef = useRef(runtime.requestInfo);
@@ -363,6 +824,9 @@ function WeaverseNextStudioBridge(props) {
363
824
  if (revalidate) {
364
825
  runtime.internal.revalidate = revalidate;
365
826
  }
827
+ if (revalidateItem) {
828
+ runtime.internal.revalidateItem = revalidateItem;
829
+ }
366
830
  if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
367
831
  return;
368
832
  }
@@ -374,7 +838,7 @@ function WeaverseNextStudioBridge(props) {
374
838
  }
375
839
  }, STUDIO_BIND_RETRY_INTERVAL_MS);
376
840
  return () => window.clearInterval(interval);
377
- }, [runtime, navigate, revalidate]);
841
+ }, [runtime, navigate, revalidate, revalidateItem]);
378
842
  useEffect(() => {
379
843
  if (lastRuntimeRef.current !== runtime) {
380
844
  lastRuntimeRef.current = runtime;
@@ -391,7 +855,65 @@ function WeaverseNextStudioBridge(props) {
391
855
  }, [runtime, runtime.data, runtime.requestInfo]);
392
856
  return null;
393
857
  }
858
+
859
+ // src/studio-router.ts
860
+ function shouldResetScroll(options) {
861
+ if (!options || typeof options !== "object") {
862
+ return true;
863
+ }
864
+ return !options.preventScrollReset;
865
+ }
866
+ function createWeaverseNextStudioInternals(router, options = {}) {
867
+ return {
868
+ navigate: (to, navigateOptions) => {
869
+ let scroll = shouldResetScroll(navigateOptions);
870
+ if (options.replace) {
871
+ router.replace(to, { scroll });
872
+ } else {
873
+ router.push(to, { scroll });
874
+ }
875
+ },
876
+ revalidate: () => {
877
+ router.refresh();
878
+ }
879
+ };
880
+ }
881
+ function useWeaverseNextStudioInternals(options) {
882
+ let router = useRouter();
883
+ let ref = useRef(null);
884
+ if (!ref.current || ref.current.router !== router || ref.current.replace !== options?.replace) {
885
+ ref.current = {
886
+ internals: createWeaverseNextStudioInternals(router, options),
887
+ replace: options?.replace,
888
+ router
889
+ };
890
+ }
891
+ return ref.current.internals;
892
+ }
893
+ function WeaverseNextStudio(props) {
894
+ let { runtime, replace, revalidateEndpoint } = props;
895
+ let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
896
+ let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
897
+ let revalidateItemRef = useRef(null);
898
+ if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
899
+ revalidateItemRef.current = {
900
+ endpoint,
901
+ revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
902
+ runtime
903
+ };
904
+ }
905
+ return /* @__PURE__ */ jsx(
906
+ WeaverseNextStudioBridge,
907
+ {
908
+ navigate,
909
+ revalidate,
910
+ revalidateItem: revalidateItemRef.current.revalidateItem,
911
+ runtime
912
+ }
913
+ );
914
+ }
394
915
  var EMPTY_DATA_CONTEXT = {};
916
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
395
917
  function getRenderablePage2(data) {
396
918
  let page = data?.page;
397
919
  if (!page) {
@@ -414,15 +936,28 @@ var WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(props) {
414
936
  client,
415
937
  data: { ...data, page },
416
938
  dataContext,
417
- themeSettingsStore: context?.themeSettingsStore
939
+ merchantOverrides: context?.merchantOverrides,
940
+ themeSettingsStore: context?.themeSettingsStore,
941
+ translationStore: context?.translationStore
418
942
  });
419
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
943
+ }, [
944
+ client,
945
+ page,
946
+ data,
947
+ dataContext,
948
+ context?.merchantOverrides,
949
+ context?.themeSettingsStore,
950
+ context?.translationStore
951
+ ]);
952
+ useIsomorphicLayoutEffect(() => {
953
+ weaverse?.flushRenderPhaseUpdates();
954
+ }, [weaverse]);
420
955
  if (!weaverse) {
421
956
  return null;
422
957
  }
423
958
  return /* @__PURE__ */ jsxs(Fragment, { children: [
424
959
  /* @__PURE__ */ jsx(WeaverseRoot, { context: weaverse }),
425
- /* @__PURE__ */ jsx(WeaverseNextStudioBridge, { runtime: weaverse })
960
+ /* @__PURE__ */ jsx(WeaverseNextStudio, { runtime: weaverse })
426
961
  ] });
427
962
  });
428
963
 
@@ -489,10 +1024,9 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
489
1024
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
490
1025
  return null;
491
1026
  }
492
- let framework = options.framework ?? "hydrogen";
493
1027
  let bundle = isDesignMode ? "index.js" : "preview.js";
494
1028
  let version = last(searchParams, "weaverseVersion");
495
- let url = `${origin}/static/studio/${framework}/${bundle}`;
1029
+ let url = `${origin}/static/studio/next/${bundle}`;
496
1030
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
497
1031
  }
498
1032
 
@@ -516,25 +1050,30 @@ function loadScript(src) {
516
1050
  document.head.appendChild(script);
517
1051
  loadedScriptSrcs.add(src);
518
1052
  }
1053
+ function loadWeaverseNextStudioScript(context, options = {}) {
1054
+ let resolvedContext = context;
1055
+ if (!resolvedContext && typeof window !== "undefined") {
1056
+ resolvedContext = {
1057
+ searchParams: new URLSearchParams(window.location.search),
1058
+ url: window.location.href
1059
+ };
1060
+ }
1061
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1062
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1063
+ });
1064
+ if (src) {
1065
+ loadScript(src);
1066
+ }
1067
+ }
519
1068
  function WeaverseNextStudioConnect(props) {
520
1069
  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)
1070
+ loadWeaverseNextStudioScript(props.context, {
1071
+ storefrontHostname: props.storefrontHostname
530
1072
  });
531
- if (src) {
532
- loadScript(src);
533
- }
534
1073
  }, [props.context, props.storefrontHostname]);
535
1074
  return null;
536
1075
  }
537
1076
 
538
- export { WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRuntime, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextThemeSettingsStore, resolveWeaverseNextStudioScriptSrc, useThemeSettings, useWeaverseCommerce, useWeaversePageData, useWeaverseRootData };
1077
+ 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
1078
  //# sourceMappingURL=index.mjs.map
540
1079
  //# sourceMappingURL=index.mjs.map