@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.js CHANGED
@@ -4,6 +4,7 @@ var react = require('@weaverse/react');
4
4
  var schema = require('@weaverse/schema');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var react$1 = require('react');
7
+ var navigation = require('next/navigation');
7
8
 
8
9
  // src/index.ts
9
10
 
@@ -26,13 +27,84 @@ function generateDataFromSchema(schema) {
26
27
  }
27
28
 
28
29
  // src/item.ts
30
+ function flattenItemData(update) {
31
+ let { data, ...rest } = update;
32
+ if (!(data && typeof data === "object" && !Array.isArray(data))) {
33
+ return rest;
34
+ }
35
+ return { ...data, ...rest };
36
+ }
37
+ var deferredItemUpdates = null;
38
+ function collectDeferredItemUpdates(create) {
39
+ let previousQueue = deferredItemUpdates;
40
+ let queue = /* @__PURE__ */ new Set();
41
+ deferredItemUpdates = queue;
42
+ try {
43
+ return [create(), [...queue]];
44
+ } finally {
45
+ deferredItemUpdates = previousQueue;
46
+ }
47
+ }
29
48
  var WeaverseNextItem = class extends react.WeaverseItemStore {
49
+ /**
50
+ * Cached merged snapshot (base `_store` + translations). Reset whenever the
51
+ * `_store` ref or the item's translation entry ref changes, so
52
+ * `useSyncExternalStore` sees a stable object between unrelated re-renders.
53
+ */
54
+ _cachedSnapshot = null;
55
+ _snapshotStoreRef = null;
56
+ _snapshotTranslationRef = null;
30
57
  constructor(initialData, weaverse) {
31
58
  super(initialData, weaverse);
32
- let { data, ...rest } = initialData;
33
59
  let schemaData = generateDataFromSchema(this.Element?.schema);
34
- Object.assign(this._store, schemaData, data, rest);
60
+ Object.assign(this._store, schemaData, flattenItemData(initialData));
35
61
  }
62
+ /**
63
+ * Flatten nested `data` on updates exactly like the constructor does. Core's
64
+ * `initProject()` calls `setData(item)` when a new runtime serves a page
65
+ * whose item instances already exist (e.g. locale/client navigation), and
66
+ * the inherited shallow merge would leave stale top-level props behind.
67
+ * Assigning through the inherited `data` setter swaps the `_store` ref,
68
+ * which invalidates the memoized snapshot below. The subscriber emit is
69
+ * queued instead of fired when a render-phase deferral is active (see
70
+ * `collectDeferredItemUpdates`).
71
+ */
72
+ setData = (update) => {
73
+ this.data = flattenItemData(update);
74
+ if (deferredItemUpdates) {
75
+ deferredItemUpdates.add(this);
76
+ } else {
77
+ this.triggerUpdate();
78
+ }
79
+ return this.data;
80
+ };
81
+ /**
82
+ * Overlay item-level translations onto the base store when the runtime is in
83
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
84
+ * memoized by `_store` ref + translation entry ref so the returned object is
85
+ * referentially stable, and a fast path that returns the base store untouched
86
+ * when the item has no translations.
87
+ */
88
+ getSnapShot = () => {
89
+ let base = this._store;
90
+ let translations = this.weaverse.translationMap[this._id];
91
+ if (!translations) {
92
+ return base;
93
+ }
94
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
95
+ return this._cachedSnapshot;
96
+ }
97
+ let merged = { ...base };
98
+ for (let [key, entry] of Object.entries(translations)) {
99
+ if (entry.translatedValue !== void 0) {
100
+ merged[key] = entry.translatedValue;
101
+ }
102
+ }
103
+ this._cachedSnapshot = merged;
104
+ this._snapshotStoreRef = base;
105
+ this._snapshotTranslationRef = translations;
106
+ return merged;
107
+ };
36
108
  };
37
109
  function ensureNextItemConstructor() {
38
110
  if (!react.Weaverse.ItemConstructor || react.Weaverse.ItemConstructor === react.WeaverseItemStore || react.Weaverse.ItemConstructor === WeaverseNextItem) {
@@ -157,30 +229,258 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
157
229
  };
158
230
  return store;
159
231
  }
232
+ function getNestedKey(obj, path, fallback) {
233
+ let keys = path.split(".");
234
+ let current = obj;
235
+ for (let key of keys) {
236
+ if (current == null || typeof current !== "object") {
237
+ return fallback;
238
+ }
239
+ current = current[key];
240
+ }
241
+ if (typeof current === "string") {
242
+ return current;
243
+ }
244
+ return fallback;
245
+ }
246
+ function interpolate(template, variables) {
247
+ if (!variables) {
248
+ return template;
249
+ }
250
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
251
+ let value = variables[key];
252
+ return value === void 0 ? match : String(value);
253
+ });
254
+ }
255
+ var TranslationContext = react$1.createContext(null);
256
+ TranslationContext.displayName = "WeaverseNextTranslationContext";
257
+ function createTranslate(sources) {
258
+ let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
259
+ return (key, variables) => {
260
+ if (externalT) {
261
+ let external = externalT(key, variables);
262
+ if (external && external !== key) {
263
+ return external;
264
+ }
265
+ }
266
+ let design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
267
+ let override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
268
+ let staticValue = getNestedKey(staticContent, key);
269
+ let raw = design ?? override ?? staticValue ?? key;
270
+ return interpolate(raw, variables);
271
+ };
272
+ }
273
+ var EMPTY = {};
274
+ var NOOP_SUBSCRIBE = () => () => {
275
+ };
276
+ var NOOP_SNAPSHOT = () => EMPTY;
277
+ function TranslationProvider({
278
+ staticContent,
279
+ merchantOverrides,
280
+ t: externalT,
281
+ translationStore,
282
+ themeTextStore,
283
+ children
284
+ }) {
285
+ let store = translationStore ?? themeTextStore ?? null;
286
+ let designOverrides = react.useSafeExternalStore(
287
+ store?.subscribe ?? NOOP_SUBSCRIBE,
288
+ store?.getSnapshot ?? NOOP_SNAPSHOT,
289
+ store?.getServerSnapshot ?? NOOP_SNAPSHOT
290
+ );
291
+ let value = react$1.useMemo(
292
+ () => ({
293
+ t: createTranslate({
294
+ staticContent,
295
+ merchantOverrides,
296
+ designOverrides,
297
+ externalT
298
+ }),
299
+ translationStore: store,
300
+ // Deprecated alias — same instance, kept for backward compatibility.
301
+ themeTextStore: store,
302
+ merchantOverrides: merchantOverrides ?? null
303
+ }),
304
+ [staticContent, merchantOverrides, designOverrides, externalT, store]
305
+ );
306
+ return /* @__PURE__ */ jsxRuntime.jsx(TranslationContext.Provider, { value, children });
307
+ }
308
+ function useTranslation() {
309
+ let ctx = react$1.useContext(TranslationContext);
310
+ if (!ctx) {
311
+ throw new Error(
312
+ "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
313
+ );
314
+ }
315
+ return ctx;
316
+ }
317
+ var ThemeTextProvider = TranslationProvider;
318
+ var useThemeText = useTranslation;
319
+
320
+ // src/translation-store.ts
321
+ var TranslationStore = class {
322
+ overrides = {};
323
+ listeners = /* @__PURE__ */ new Set();
324
+ setOverrides = (overrides) => {
325
+ this.overrides = { ...overrides };
326
+ this.emit();
327
+ };
328
+ updateOverrides = (newOverrides) => {
329
+ this.overrides = { ...this.overrides, ...newOverrides };
330
+ this.emit();
331
+ };
332
+ getSnapshot = () => this.overrides;
333
+ getServerSnapshot = () => this.overrides;
334
+ subscribe = (callback) => {
335
+ this.listeners.add(callback);
336
+ return () => {
337
+ this.listeners.delete(callback);
338
+ };
339
+ };
340
+ emit = () => {
341
+ for (const listener of Array.from(this.listeners)) {
342
+ try {
343
+ listener();
344
+ } catch (error) {
345
+ console.error("TranslationStore: Listener error", error);
346
+ }
347
+ }
348
+ };
349
+ };
350
+ var ThemeTextStore = TranslationStore;
351
+ var WeaverseNextRootContext = react$1.createContext(null);
352
+ function WeaverseNextRootProvider(props) {
353
+ let {
354
+ children,
355
+ initialThemeSettings,
356
+ merchantOverrides,
357
+ staticContent,
358
+ t,
359
+ themeSchema,
360
+ publicEnv
361
+ } = props;
362
+ let storeRef = react$1.useRef(null);
363
+ if (!storeRef.current) {
364
+ storeRef.current = createWeaverseNextThemeSettingsStore({
365
+ publicEnv,
366
+ schema: themeSchema,
367
+ settings: initialThemeSettings
368
+ });
369
+ }
370
+ let themeSettingsStore = storeRef.current;
371
+ let translationStoreRef = react$1.useRef(null);
372
+ if (!translationStoreRef.current) {
373
+ translationStoreRef.current = new TranslationStore();
374
+ }
375
+ let translationStore = translationStoreRef.current;
376
+ let appliedThemeSettingsRef = react$1.useRef(initialThemeSettings);
377
+ react$1.useEffect(() => {
378
+ if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
379
+ themeSettingsStore.updateThemeSettings(initialThemeSettings);
380
+ appliedThemeSettingsRef.current = initialThemeSettings;
381
+ }
382
+ }, [initialThemeSettings, themeSettingsStore]);
383
+ let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
384
+ let rootContextValue = react$1.useMemo(
385
+ () => ({
386
+ merchantOverrides,
387
+ staticContent: resolvedStaticContent,
388
+ themeSettingsStore,
389
+ translationStore
390
+ }),
391
+ [
392
+ merchantOverrides,
393
+ resolvedStaticContent,
394
+ themeSettingsStore,
395
+ translationStore
396
+ ]
397
+ );
398
+ let contextValue = react$1.useMemo(
399
+ () => ({
400
+ merchantOverrides,
401
+ themeSettings: themeSettingsStore.getSnapshot(),
402
+ themeSettingsStore,
403
+ translationStore
404
+ }),
405
+ [merchantOverrides, themeSettingsStore, translationStore]
406
+ );
407
+ return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
408
+ TranslationProvider,
409
+ {
410
+ merchantOverrides,
411
+ staticContent: resolvedStaticContent,
412
+ t,
413
+ translationStore,
414
+ children
415
+ }
416
+ ) }) });
417
+ }
160
418
  var EMPTY_THEME_SETTINGS = {};
419
+ var EMPTY_STATIC_CONTENT = {};
420
+ function getSchemaStaticContent(schema) {
421
+ let i18n = schema?.i18n;
422
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
423
+ }
161
424
  var WeaverseNextContext = react$1.createContext(null);
162
425
  function WeaverseNextProvider(props) {
163
426
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
427
+ let rootContext = react$1.useContext(WeaverseNextRootContext);
164
428
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
165
- let themeSettingsStore = react$1.useMemo(
166
- () => createWeaverseNextThemeSettingsStore({
429
+ let ownTranslationStoreRef = react$1.useRef(null);
430
+ if (!(rootContext || ownTranslationStoreRef.current)) {
431
+ ownTranslationStoreRef.current = new TranslationStore();
432
+ }
433
+ let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
434
+ let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
435
+ let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
436
+ let ownThemeSettingsStore = react$1.useMemo(() => {
437
+ if (rootContext) {
438
+ return null;
439
+ }
440
+ return createWeaverseNextThemeSettingsStore({
167
441
  schema: client?.themeSchema,
168
442
  settings: themeSettingsValue
169
- }),
170
- [client?.themeSchema, themeSettingsValue]
171
- );
443
+ });
444
+ }, [rootContext, client?.themeSchema, themeSettingsValue]);
445
+ let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
446
+ let mergedIntoRootRef = react$1.useRef(void 0);
447
+ react$1.useEffect(() => {
448
+ if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
449
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
450
+ mergedIntoRootRef.current = themeSettingsValue;
451
+ }
452
+ }, [rootContext, themeSettingsValue]);
172
453
  let value = react$1.useMemo(
173
454
  () => ({
174
455
  client,
175
456
  rootData,
176
457
  pageData,
177
458
  commerce: commerce ?? client?.commerce,
459
+ merchantOverrides,
178
460
  themeSettings: themeSettingsStore.getSnapshot(),
179
- themeSettingsStore
461
+ themeSettingsStore,
462
+ translationStore
180
463
  }),
181
- [client, rootData, pageData, commerce, themeSettingsStore]
464
+ [
465
+ client,
466
+ rootData,
467
+ pageData,
468
+ commerce,
469
+ merchantOverrides,
470
+ themeSettingsStore,
471
+ translationStore
472
+ ]
182
473
  );
183
- return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value, children });
474
+ return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(
475
+ TranslationProvider,
476
+ {
477
+ merchantOverrides,
478
+ staticContent,
479
+ t: props.t,
480
+ translationStore,
481
+ children
482
+ }
483
+ ) });
184
484
  }
185
485
 
186
486
  // src/hooks.ts
@@ -327,8 +627,22 @@ function toQueryRecord(searchParams) {
327
627
  }
328
628
  return queries;
329
629
  }
630
+ var TRANSIENT_STUDIO_PARAMS = /* @__PURE__ */ new Set([
631
+ "weaverseDraftItem",
632
+ // Legacy name from the original design-mode draft-item spike. Keep it out of
633
+ // runtime requestInfo too, in case an older Builder bundle is in front of a
634
+ // newer Next storefront.
635
+ "__weaverseDraftItem"
636
+ ]);
637
+ function getRequestInfoSearchParams(context) {
638
+ let searchParams = new URLSearchParams(getSearchParamsFromContext(context));
639
+ for (let key of TRANSIENT_STUDIO_PARAMS) {
640
+ searchParams.delete(key);
641
+ }
642
+ return searchParams;
643
+ }
330
644
  function buildWeaverseNextRequestInfo(context) {
331
- let searchParams = getSearchParamsFromContext(context);
645
+ let searchParams = getRequestInfoSearchParams(context);
332
646
  let search = searchParams.toString();
333
647
  return {
334
648
  pathname: getPathnameFromContext(context),
@@ -394,9 +708,31 @@ var WeaverseNextRuntime = class extends react.Weaverse {
394
708
  isPreviewMode;
395
709
  isRevisionPreview;
396
710
  sectionType;
711
+ // ─── Item-level translation sidecar (design mode only) ─────────────
712
+ /**
713
+ * Translation map received from Builder in design mode, keyed by item ID.
714
+ * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
715
+ * components, so translations render without mutating the base `_store`.
716
+ */
397
717
  translationMap = {};
398
- translationLocale = "";
399
- translationLanguageId = "";
718
+ translationLocale = String();
719
+ translationLanguageId = String();
720
+ /**
721
+ * Fresh page data captured while React was rendering. Runtime reuse happens
722
+ * inside the renderer's render phase (`useMemo`), where applying it through
723
+ * `setProjectData()` would emit item updates to `useSyncExternalStore`
724
+ * subscribers mid-render. The renderer applies it post-commit via
725
+ * `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
726
+ * Studio tree owns the page data there.
727
+ */
728
+ pendingProjectData;
729
+ /**
730
+ * Reused item instances whose data was refreshed while this runtime was
731
+ * constructed during render (core `initProject()` calls `setData()` on
732
+ * them). Their data is already fresh; only the subscriber notification was
733
+ * deferred to `flushRenderPhaseUpdates()`.
734
+ */
735
+ pendingItemUpdates = [];
400
736
  constructor(config) {
401
737
  let { client, data } = config;
402
738
  let page = getRenderablePage(data);
@@ -418,7 +754,9 @@ var WeaverseNextRuntime = class extends react.Weaverse {
418
754
  this.isPreviewMode = requestContext?.isPreviewMode ?? false;
419
755
  this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
420
756
  this.sectionType = requestContext?.sectionType;
757
+ let translationStore = config.translationStore ?? new TranslationStore();
421
758
  this.internal = {
759
+ merchantOverrides: config.merchantOverrides,
422
760
  navigate: config.navigate,
423
761
  pageAssignment: data.pageAssignment,
424
762
  project: data.project,
@@ -426,17 +764,119 @@ var WeaverseNextRuntime = class extends react.Weaverse {
426
764
  themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
427
765
  schema: client?.themeSchema,
428
766
  settings: client?.themeSettings
429
- })
767
+ }),
768
+ translationStore,
769
+ themeTextStore: translationStore
430
770
  };
771
+ this.extractTranslationSidecar();
431
772
  }
432
- extractTranslationSidecar = () => void 0;
773
+ /**
774
+ * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
775
+ * the current page data (Builder attaches them in design mode). Unlike
776
+ * Hydrogen, this also clears the sidecar to defaults when the new page data
777
+ * carries none, so a reused runtime / project-data swap can't leave a stale
778
+ * map overlaying the fresh tree.
779
+ */
780
+ extractTranslationSidecar = () => {
781
+ let pageData = this.data;
782
+ let translationMap = pageData?.translationMap;
783
+ if (translationMap) {
784
+ this.translationMap = translationMap;
785
+ this.translationLocale = pageData?.translationLocale || "";
786
+ this.translationLanguageId = pageData?.translationLanguageId || "";
787
+ } else {
788
+ this.translationMap = {};
789
+ this.translationLocale = "";
790
+ this.translationLanguageId = "";
791
+ }
792
+ };
793
+ /**
794
+ * Replace the translation sidecar (Builder pushes this when translation data
795
+ * arrives or the active language changes) and refresh every item so their
796
+ * merged snapshots recompute.
797
+ */
433
798
  setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
434
799
  this.translationMap = map;
435
800
  this.translationLocale = locale;
436
801
  this.translationLanguageId = languageId;
802
+ this.refreshAllItems();
803
+ };
804
+ /**
805
+ * Update a single translatable field for one item. Assigns a new per-item
806
+ * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
807
+ * invalidates, then triggers only that item's subscribers.
808
+ */
809
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
810
+ if (!this.translationMap[itemId]) {
811
+ this.translationMap[itemId] = {};
812
+ }
813
+ this.translationMap[itemId] = {
814
+ ...this.translationMap[itemId],
815
+ [key]: { originalValue, translatedValue }
816
+ };
817
+ let instance = this.itemInstances.get(itemId);
818
+ if (instance) {
819
+ instance.triggerUpdate();
820
+ }
821
+ };
822
+ /**
823
+ * Collect translation changes for the save flow. Returns `undefined` unless
824
+ * both the locale and language ID are set and at least one entry exists;
825
+ * each entry key is namespaced as `data.<field>` to match Builder.
826
+ */
827
+ getTranslationChanges = () => {
828
+ let languageId = this.translationLanguageId;
829
+ if (!(this.translationLocale && languageId)) {
830
+ return;
831
+ }
832
+ let entries = [];
833
+ for (let [itemId, fields] of Object.entries(this.translationMap)) {
834
+ for (let [key, entry] of Object.entries(fields)) {
835
+ entries.push({
836
+ itemId,
837
+ key: `data.${key}`,
838
+ originalValue: entry.originalValue,
839
+ translatedValue: entry.translatedValue
840
+ });
841
+ }
842
+ }
843
+ if (entries.length === 0) {
844
+ return;
845
+ }
846
+ return { languageId, entries };
847
+ };
848
+ /**
849
+ * Re-extract the translation sidecar whenever project data is replaced (route
850
+ * change / non-design reuse), before rebuilding item stores. Takes the
851
+ * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
852
+ * the reuse branch pass in.
853
+ */
854
+ setProjectData = (data) => {
855
+ this.data = data;
856
+ this.extractTranslationSidecar();
857
+ this.initProject();
858
+ };
859
+ /**
860
+ * Apply updates deferred off the render phase: swap in any pending page data
861
+ * captured on runtime reuse, then notify items whose data was refreshed
862
+ * during construction. The renderer calls this from a layout effect right
863
+ * after commit, so subscribers re-render before paint (no content flash) and
864
+ * without React's setState-in-render warning.
865
+ */
866
+ flushRenderPhaseUpdates = () => {
867
+ let pendingData = this.pendingProjectData;
868
+ if (pendingData) {
869
+ this.pendingProjectData = void 0;
870
+ this.setProjectData(pendingData);
871
+ }
872
+ let pendingItems = this.pendingItemUpdates;
873
+ if (pendingItems.length > 0) {
874
+ this.pendingItemUpdates = [];
875
+ for (let item of pendingItems) {
876
+ item.triggerUpdate();
877
+ }
878
+ }
437
879
  };
438
- updateTranslation = () => void 0;
439
- getTranslationChanges = () => void 0;
440
880
  };
441
881
  function createWeaverseNextRuntime(config) {
442
882
  let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
@@ -446,9 +886,12 @@ function createWeaverseNextRuntime(config) {
446
886
  let requestKey = getRuntimeKey(page.id, requestInfo);
447
887
  if (existing?.__weaverseNextRequestKey === requestKey) {
448
888
  let nextIsDesignMode = config.client?.requestContext?.isDesignMode ?? false;
449
- if (!(existing.isDesignMode || nextIsDesignMode)) {
450
- existing.setProjectData(page);
889
+ if (existing.isDesignMode || nextIsDesignMode) {
890
+ existing.pendingProjectData = void 0;
891
+ } else {
892
+ existing.pendingProjectData = page;
451
893
  }
894
+ existing.__weaverseNextLatestData = page;
452
895
  existing.dataContext = resolveDataContext(config);
453
896
  existing.requestInfo = requestInfo;
454
897
  existing.projectId = resolveProjectId(config.client, config.data, page.id);
@@ -469,11 +912,20 @@ function createWeaverseNextRuntime(config) {
469
912
  if (config.themeSettingsStore) {
470
913
  existing.internal.themeSettingsStore = config.themeSettingsStore;
471
914
  }
915
+ if (config.translationStore) {
916
+ existing.internal.translationStore = config.translationStore;
917
+ existing.internal.themeTextStore = config.translationStore;
918
+ }
919
+ existing.internal.merchantOverrides = config.merchantOverrides;
472
920
  registerRuntime(existing);
473
921
  return existing;
474
922
  }
475
- let runtime = new WeaverseNextRuntime(config);
923
+ let [runtime, refreshedItems] = collectDeferredItemUpdates(
924
+ () => new WeaverseNextRuntime(config)
925
+ );
926
+ runtime.pendingItemUpdates = refreshedItems;
476
927
  runtime.__weaverseNextRequestKey = requestKey;
928
+ runtime.__weaverseNextLatestData = page;
477
929
  registerRuntime(runtime);
478
930
  return runtime;
479
931
  }
@@ -492,16 +944,39 @@ function bindWeaverseNextStudioRuntime(runtime) {
492
944
  return true;
493
945
  }
494
946
  studio.refreshStudio?.({
495
- data: runtime.data,
947
+ data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
496
948
  pageId: runtime.pageId,
497
949
  requestInfo: runtime.requestInfo
498
950
  });
499
951
  return true;
500
952
  }
953
+
954
+ // src/revalidate-item.ts
955
+ var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
956
+ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
957
+ let instance = runtime.itemInstances.get(draftItem.id);
958
+ if (!instance) {
959
+ throw new Error(
960
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
961
+ );
962
+ }
963
+ let response = await fetch(endpoint, {
964
+ method: "POST",
965
+ headers: { "Content-Type": "application/json" },
966
+ body: JSON.stringify({ draftItem })
967
+ });
968
+ if (!response.ok) {
969
+ throw new Error(
970
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
971
+ );
972
+ }
973
+ let { loaderData } = await response.json();
974
+ instance.setData({ loaderData: loaderData ?? null });
975
+ }
501
976
  var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
502
977
  var STUDIO_BIND_MAX_ATTEMPTS = 100;
503
978
  function WeaverseNextStudioBridge(props) {
504
- let { runtime, navigate, revalidate } = props;
979
+ let { runtime, navigate, revalidate, revalidateItem } = props;
505
980
  let lastRuntimeRef = react$1.useRef(runtime);
506
981
  let lastRuntimeDataRef = react$1.useRef(runtime.data);
507
982
  let lastRequestInfoRef = react$1.useRef(runtime.requestInfo);
@@ -512,6 +987,9 @@ function WeaverseNextStudioBridge(props) {
512
987
  if (revalidate) {
513
988
  runtime.internal.revalidate = revalidate;
514
989
  }
990
+ if (revalidateItem) {
991
+ runtime.internal.revalidateItem = revalidateItem;
992
+ }
515
993
  if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
516
994
  return;
517
995
  }
@@ -523,7 +1001,7 @@ function WeaverseNextStudioBridge(props) {
523
1001
  }
524
1002
  }, STUDIO_BIND_RETRY_INTERVAL_MS);
525
1003
  return () => window.clearInterval(interval);
526
- }, [runtime, navigate, revalidate]);
1004
+ }, [runtime, navigate, revalidate, revalidateItem]);
527
1005
  react$1.useEffect(() => {
528
1006
  if (lastRuntimeRef.current !== runtime) {
529
1007
  lastRuntimeRef.current = runtime;
@@ -540,7 +1018,65 @@ function WeaverseNextStudioBridge(props) {
540
1018
  }, [runtime, runtime.data, runtime.requestInfo]);
541
1019
  return null;
542
1020
  }
1021
+
1022
+ // src/studio-router.ts
1023
+ function shouldResetScroll(options) {
1024
+ if (!options || typeof options !== "object") {
1025
+ return true;
1026
+ }
1027
+ return !options.preventScrollReset;
1028
+ }
1029
+ function createWeaverseNextStudioInternals(router, options = {}) {
1030
+ return {
1031
+ navigate: (to, navigateOptions) => {
1032
+ let scroll = shouldResetScroll(navigateOptions);
1033
+ if (options.replace) {
1034
+ router.replace(to, { scroll });
1035
+ } else {
1036
+ router.push(to, { scroll });
1037
+ }
1038
+ },
1039
+ revalidate: () => {
1040
+ router.refresh();
1041
+ }
1042
+ };
1043
+ }
1044
+ function useWeaverseNextStudioInternals(options) {
1045
+ let router = navigation.useRouter();
1046
+ let ref = react$1.useRef(null);
1047
+ if (!ref.current || ref.current.router !== router || ref.current.replace !== options?.replace) {
1048
+ ref.current = {
1049
+ internals: createWeaverseNextStudioInternals(router, options),
1050
+ replace: options?.replace,
1051
+ router
1052
+ };
1053
+ }
1054
+ return ref.current.internals;
1055
+ }
1056
+ function WeaverseNextStudio(props) {
1057
+ let { runtime, replace, revalidateEndpoint } = props;
1058
+ let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
1059
+ let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
1060
+ let revalidateItemRef = react$1.useRef(null);
1061
+ if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
1062
+ revalidateItemRef.current = {
1063
+ endpoint,
1064
+ revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
1065
+ runtime
1066
+ };
1067
+ }
1068
+ return /* @__PURE__ */ jsxRuntime.jsx(
1069
+ WeaverseNextStudioBridge,
1070
+ {
1071
+ navigate,
1072
+ revalidate,
1073
+ revalidateItem: revalidateItemRef.current.revalidateItem,
1074
+ runtime
1075
+ }
1076
+ );
1077
+ }
543
1078
  var EMPTY_DATA_CONTEXT = {};
1079
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? react$1.useEffect : react$1.useLayoutEffect;
544
1080
  function getRenderablePage2(data) {
545
1081
  let page = data?.page;
546
1082
  if (!page) {
@@ -563,18 +1099,87 @@ var WeaverseNextRenderer = react$1.memo(function WeaverseNextRendererComponent(p
563
1099
  client,
564
1100
  data: { ...data, page },
565
1101
  dataContext,
566
- themeSettingsStore: context?.themeSettingsStore
1102
+ merchantOverrides: context?.merchantOverrides,
1103
+ themeSettingsStore: context?.themeSettingsStore,
1104
+ translationStore: context?.translationStore
567
1105
  });
568
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
1106
+ }, [
1107
+ client,
1108
+ page,
1109
+ data,
1110
+ dataContext,
1111
+ context?.merchantOverrides,
1112
+ context?.themeSettingsStore,
1113
+ context?.translationStore
1114
+ ]);
1115
+ useIsomorphicLayoutEffect(() => {
1116
+ weaverse?.flushRenderPhaseUpdates();
1117
+ }, [weaverse]);
569
1118
  if (!weaverse) {
570
1119
  return null;
571
1120
  }
572
1121
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
573
1122
  /* @__PURE__ */ jsxRuntime.jsx(react.WeaverseRoot, { context: weaverse }),
574
- /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextStudioBridge, { runtime: weaverse })
1123
+ /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextStudio, { runtime: weaverse })
575
1124
  ] });
576
1125
  });
577
1126
 
1127
+ // src/seo.ts
1128
+ function hasOpenGraphContent(seo) {
1129
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.type);
1130
+ }
1131
+ function hasTwitterContent(seo) {
1132
+ return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType);
1133
+ }
1134
+ function formatWeaverseNextSeoMetadata(seo) {
1135
+ let index = seo?.robots?.index !== false;
1136
+ let follow = seo?.robots?.follow !== false;
1137
+ let metadata = {
1138
+ robots: { index, follow }
1139
+ };
1140
+ if (!seo) {
1141
+ return metadata;
1142
+ }
1143
+ if (seo.title) {
1144
+ metadata.title = seo.title;
1145
+ }
1146
+ if (seo.description) {
1147
+ metadata.description = seo.description;
1148
+ }
1149
+ if (seo.keywords) {
1150
+ metadata.keywords = seo.keywords;
1151
+ }
1152
+ if (seo.canonicalUrl) {
1153
+ metadata.alternates = { canonical: seo.canonicalUrl };
1154
+ }
1155
+ let openGraph = seo.openGraph;
1156
+ if (hasOpenGraphContent(openGraph)) {
1157
+ metadata.openGraph = {
1158
+ title: openGraph?.title,
1159
+ description: openGraph?.description,
1160
+ images: openGraph?.image ? [openGraph.image] : void 0,
1161
+ type: openGraph?.type || "website"
1162
+ };
1163
+ }
1164
+ let twitter = seo.twitter;
1165
+ if (hasTwitterContent(twitter)) {
1166
+ metadata.twitter = {
1167
+ card: twitter?.cardType || "summary",
1168
+ title: twitter?.title,
1169
+ description: twitter?.description,
1170
+ images: twitter?.image ? [twitter.image] : void 0
1171
+ };
1172
+ }
1173
+ return metadata;
1174
+ }
1175
+ function getWeaverseNextSeoMetadata(data) {
1176
+ if (!data) {
1177
+ return formatWeaverseNextSeoMetadata(null);
1178
+ }
1179
+ let page = "items" in data && "id" in data ? data : data.page ?? null;
1180
+ return formatWeaverseNextSeoMetadata(page?.seo ?? null);
1181
+ }
1182
+
578
1183
  // src/studio-script-src.ts
579
1184
  var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
580
1185
  function isLoopbackHostname(hostname) {
@@ -638,10 +1243,9 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
638
1243
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
639
1244
  return null;
640
1245
  }
641
- let framework = options.framework ?? "hydrogen";
642
1246
  let bundle = isDesignMode ? "index.js" : "preview.js";
643
1247
  let version = last(searchParams, "weaverseVersion");
644
- let url = `${origin}/static/studio/${framework}/${bundle}`;
1248
+ let url = `${origin}/static/studio/next/${bundle}`;
645
1249
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
646
1250
  }
647
1251
 
@@ -665,21 +1269,26 @@ function loadScript(src) {
665
1269
  document.head.appendChild(script);
666
1270
  loadedScriptSrcs.add(src);
667
1271
  }
1272
+ function loadWeaverseNextStudioScript(context, options = {}) {
1273
+ let resolvedContext = context;
1274
+ if (!resolvedContext && typeof window !== "undefined") {
1275
+ resolvedContext = {
1276
+ searchParams: new URLSearchParams(window.location.search),
1277
+ url: window.location.href
1278
+ };
1279
+ }
1280
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1281
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1282
+ });
1283
+ if (src) {
1284
+ loadScript(src);
1285
+ }
1286
+ }
668
1287
  function WeaverseNextStudioConnect(props) {
669
1288
  react$1.useEffect(() => {
670
- let context = props.context;
671
- if (!context && typeof window !== "undefined") {
672
- context = {
673
- searchParams: new URLSearchParams(window.location.search),
674
- url: window.location.href
675
- };
676
- }
677
- let src = resolveWeaverseNextStudioScriptSrc(context, {
678
- storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1289
+ loadWeaverseNextStudioScript(props.context, {
1290
+ storefrontHostname: props.storefrontHostname
679
1291
  });
680
- if (src) {
681
- loadScript(src);
682
- }
683
1292
  }, [props.context, props.storefrontHostname]);
684
1293
  return null;
685
1294
  }
@@ -708,25 +1317,46 @@ Object.defineProperty(exports, "useWeaverse", {
708
1317
  enumerable: true,
709
1318
  get: function () { return react.useWeaverse; }
710
1319
  });
711
- Object.defineProperty(exports, "createSchema", {
712
- enumerable: true,
713
- get: function () { return schema.createSchema; }
714
- });
1320
+ exports.DEFAULT_REVALIDATE_ENDPOINT = DEFAULT_REVALIDATE_ENDPOINT;
1321
+ exports.ThemeTextProvider = ThemeTextProvider;
1322
+ exports.ThemeTextStore = ThemeTextStore;
1323
+ exports.TranslationProvider = TranslationProvider;
1324
+ exports.TranslationStore = TranslationStore;
715
1325
  exports.WeaverseNextProvider = WeaverseNextProvider;
716
1326
  exports.WeaverseNextRenderer = WeaverseNextRenderer;
1327
+ exports.WeaverseNextRootProvider = WeaverseNextRootProvider;
717
1328
  exports.WeaverseNextRuntime = WeaverseNextRuntime;
1329
+ exports.WeaverseNextStudio = WeaverseNextStudio;
718
1330
  exports.WeaverseNextStudioBridge = WeaverseNextStudioBridge;
719
1331
  exports.WeaverseNextStudioConnect = WeaverseNextStudioConnect;
720
1332
  exports.bindWeaverseNextStudioRuntime = bindWeaverseNextStudioRuntime;
721
1333
  exports.buildWeaverseNextRequestInfo = buildWeaverseNextRequestInfo;
1334
+ exports.createTranslate = createTranslate;
722
1335
  exports.createWeaverseNextClient = createWeaverseNextClient;
723
1336
  exports.createWeaverseNextRuntime = createWeaverseNextRuntime;
1337
+ exports.createWeaverseNextStudioInternals = createWeaverseNextStudioInternals;
724
1338
  exports.createWeaverseNextThemeSettingsStore = createWeaverseNextThemeSettingsStore;
1339
+ exports.formatWeaverseNextSeoMetadata = formatWeaverseNextSeoMetadata;
1340
+ exports.generateDataFromSchema = generateDataFromSchema;
1341
+ exports.getNestedKey = getNestedKey;
1342
+ exports.getWeaverseNextSeoMetadata = getWeaverseNextSeoMetadata;
1343
+ exports.interpolate = interpolate;
1344
+ exports.loadWeaverseNextStudioScript = loadWeaverseNextStudioScript;
725
1345
  exports.resolveWeaverseNextStudioScriptSrc = resolveWeaverseNextStudioScriptSrc;
1346
+ exports.revalidateWeaverseNextItem = revalidateWeaverseNextItem;
726
1347
  exports.runWeaverseComponentLoaders = runWeaverseComponentLoaders;
727
1348
  exports.useThemeSettings = useThemeSettings;
1349
+ exports.useThemeText = useThemeText;
1350
+ exports.useTranslation = useTranslation;
728
1351
  exports.useWeaverseCommerce = useWeaverseCommerce;
1352
+ exports.useWeaverseNextStudioInternals = useWeaverseNextStudioInternals;
729
1353
  exports.useWeaversePageData = useWeaversePageData;
730
1354
  exports.useWeaverseRootData = useWeaverseRootData;
1355
+ Object.keys(schema).forEach(function (k) {
1356
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
1357
+ enumerable: true,
1358
+ get: function () { return schema[k]; }
1359
+ });
1360
+ });
731
1361
  //# sourceMappingURL=index.js.map
732
1362
  //# sourceMappingURL=index.js.map