@weaverse/next 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (107) hide show
  1. package/README.md +586 -123
  2. package/dist/chunk-LH6AAEIK.mjs +326 -0
  3. package/dist/chunk-LH6AAEIK.mjs.map +1 -0
  4. package/dist/index.js +784 -60
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +588 -58
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/server.js +537 -76
  9. package/dist/server.js.map +1 -1
  10. package/dist/server.mjs +363 -27
  11. package/dist/server.mjs.map +1 -1
  12. package/dist/types/client.d.ts +16 -0
  13. package/dist/types/client.d.ts.map +1 -0
  14. package/dist/types/hooks.d.ts +20 -0
  15. package/dist/types/hooks.d.ts.map +1 -0
  16. package/dist/types/index.d.ts +34 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/item.d.ts +60 -0
  19. package/dist/types/item.d.ts.map +1 -0
  20. package/dist/types/loader.d.ts +25 -0
  21. package/dist/types/loader.d.ts.map +1 -0
  22. package/dist/types/main.d.ts +11 -0
  23. package/dist/types/main.d.ts.map +1 -0
  24. package/dist/types/provider.d.ts +82 -0
  25. package/dist/types/provider.d.ts.map +1 -0
  26. package/dist/types/registry.d.ts +15 -0
  27. package/dist/types/registry.d.ts.map +1 -0
  28. package/dist/types/renderer.d.ts +21 -0
  29. package/dist/types/renderer.d.ts.map +1 -0
  30. package/dist/types/request-info.d.ts +19 -0
  31. package/dist/types/request-info.d.ts.map +1 -0
  32. package/dist/types/revalidate-item.d.ts +82 -0
  33. package/dist/types/revalidate-item.d.ts.map +1 -0
  34. package/dist/types/root-provider.d.ts +62 -0
  35. package/dist/types/root-provider.d.ts.map +1 -0
  36. package/dist/types/runtime.d.ts +113 -0
  37. package/dist/types/runtime.d.ts.map +1 -0
  38. package/dist/types/seo.d.ts +55 -0
  39. package/dist/types/seo.d.ts.map +1 -0
  40. package/dist/types/server/configs.d.ts +27 -0
  41. package/dist/types/server/configs.d.ts.map +1 -0
  42. package/dist/types/server/normalize-page-url.d.ts +19 -0
  43. package/dist/types/server/normalize-page-url.d.ts.map +1 -0
  44. package/dist/types/server/revalidate-handler.d.ts +55 -0
  45. package/dist/types/server/revalidate-handler.d.ts.map +1 -0
  46. package/dist/types/server/server-client.d.ts +21 -0
  47. package/dist/types/server/server-client.d.ts.map +1 -0
  48. package/dist/types/server.d.ts +19 -0
  49. package/dist/types/server.d.ts.map +1 -0
  50. package/dist/types/studio-bridge.d.ts +16 -0
  51. package/dist/types/studio-bridge.d.ts.map +1 -0
  52. package/dist/types/studio-connect.d.ts +27 -0
  53. package/dist/types/studio-connect.d.ts.map +1 -0
  54. package/dist/types/studio-router.d.ts +50 -0
  55. package/dist/types/studio-router.d.ts.map +1 -0
  56. package/dist/types/studio-script-src.d.ts +12 -0
  57. package/dist/types/studio-script-src.d.ts.map +1 -0
  58. package/dist/types/theme-settings-store.d.ts +18 -0
  59. package/dist/types/theme-settings-store.d.ts.map +1 -0
  60. package/dist/types/translation-context.d.ts +156 -0
  61. package/dist/types/translation-context.d.ts.map +1 -0
  62. package/dist/types/translation-store.d.ts +44 -0
  63. package/dist/types/translation-store.d.ts.map +1 -0
  64. package/dist/types/types.d.ts +665 -0
  65. package/dist/types/types.d.ts.map +1 -0
  66. package/dist/types/use-weaverse-next-studio.d.ts +34 -0
  67. package/dist/types/use-weaverse-next-studio.d.ts.map +1 -0
  68. package/dist/types/utils.d.ts +12 -0
  69. package/dist/types/utils.d.ts.map +1 -0
  70. package/package.json +12 -8
  71. package/src/client.ts +102 -0
  72. package/src/hooks.ts +50 -0
  73. package/src/index.ts +153 -0
  74. package/src/item.ts +167 -0
  75. package/src/loader.ts +135 -0
  76. package/src/main.tsx +45 -0
  77. package/src/provider.tsx +225 -0
  78. package/src/registry.ts +63 -0
  79. package/src/renderer.tsx +100 -0
  80. package/src/request-info.ts +124 -0
  81. package/src/revalidate-item.ts +211 -0
  82. package/src/root-provider.tsx +182 -0
  83. package/src/runtime.ts +555 -0
  84. package/src/seo.ts +157 -0
  85. package/src/server/configs.ts +178 -0
  86. package/src/server/normalize-page-url.ts +73 -0
  87. package/src/server/revalidate-handler.ts +357 -0
  88. package/src/server/server-client.ts +728 -0
  89. package/src/server.ts +67 -0
  90. package/src/studio-bridge.tsx +82 -0
  91. package/src/studio-connect.tsx +88 -0
  92. package/src/studio-router.ts +85 -0
  93. package/src/studio-script-src.ts +128 -0
  94. package/src/theme-settings-store.ts +47 -0
  95. package/src/translation-context.tsx +299 -0
  96. package/src/translation-store.ts +69 -0
  97. package/src/types.ts +753 -0
  98. package/src/use-weaverse-next-studio.tsx +106 -0
  99. package/src/utils.ts +37 -0
  100. package/dist/chunk-SS6QUKYY.mjs +0 -149
  101. package/dist/chunk-SS6QUKYY.mjs.map +0 -1
  102. package/dist/index.d.mts +0 -250
  103. package/dist/index.d.ts +0 -250
  104. package/dist/server.d.mts +0 -60
  105. package/dist/server.d.ts +0 -60
  106. package/dist/types-DvEeLX6L.d.mts +0 -321
  107. package/dist/types-DvEeLX6L.d.ts +0 -321
package/dist/index.mjs CHANGED
@@ -1,20 +1,108 @@
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, DEFAULT_REVALIDATE_ENDPOINT, revalidateWeaverseNextItem, generateDataFromSchema } from './chunk-LH6AAEIK.mjs';
2
+ export { DEFAULT_REVALIDATE_ENDPOINT, buildWeaverseNextRequestInfo, buildWeaverseNextRevalidateRouteContext, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, revalidateWeaverseNextItem, runWeaverseComponentLoaders } from './chunk-LH6AAEIK.mjs';
3
+ import { WeaverseRoot, Weaverse, WeaverseItemStore, useSafeExternalStore } from '@weaverse/react';
4
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
9
  import { useRouter } from 'next/navigation';
10
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
+ }
11
29
  var WeaverseNextItem = class extends WeaverseItemStore {
30
+ /**
31
+ * Cached merged snapshot (base `_store` + translations). Reset whenever the
32
+ * `_store` ref or the item's translation entry ref changes, so
33
+ * `useSyncExternalStore` sees a stable object between unrelated re-renders.
34
+ */
35
+ _cachedSnapshot = null;
36
+ _snapshotStoreRef = null;
37
+ _snapshotTranslationRef = null;
38
+ /** Create an item store from serialized component data and its runtime. */
12
39
  constructor(initialData, weaverse) {
13
40
  super(initialData, weaverse);
14
- let { data, ...rest } = initialData;
15
41
  let schemaData = generateDataFromSchema(this.Element?.schema);
16
- Object.assign(this._store, schemaData, data, rest);
42
+ Object.assign(this._store, schemaData, flattenItemData(initialData));
17
43
  }
44
+ /**
45
+ * Flatten nested `data` on updates exactly like the constructor does. Core's
46
+ * `initProject()` calls `setData(item)` when a new runtime serves a page
47
+ * whose item instances already exist (e.g. locale/client navigation).
48
+ *
49
+ * A complete serialized item (it carries `id` and `type`; core's
50
+ * `initProject()` always passes one) replaces the store the way the
51
+ * constructor builds it: the schema defaults of the item's own `type` — the
52
+ * Builder can replace a section in place, keeping its id — then the item's
53
+ * settings. A payload omits a setting — or the whole optional `data` — when
54
+ * its value equals the schema default, so merging into the previous store
55
+ * would keep the previous locale's value visible. Partial updates still
56
+ * merge. Either way the `_store` ref changes, which invalidates the memoized
57
+ * snapshot below. The subscriber emit is queued instead of fired when a
58
+ * render-phase deferral is active (see `collectDeferredItemUpdates`).
59
+ */
60
+ setData = (update) => {
61
+ if ("id" in update && "type" in update) {
62
+ let { data, type } = update;
63
+ let schema = this.weaverse.elementRegistry.get(type)?.schema;
64
+ this._store = {
65
+ ...generateDataFromSchema(schema),
66
+ ...data === void 0 ? {} : { data },
67
+ ...flattenItemData(update)
68
+ };
69
+ } else {
70
+ this.data = flattenItemData(update);
71
+ }
72
+ if (deferredItemUpdates) {
73
+ deferredItemUpdates.add(this);
74
+ } else {
75
+ this.triggerUpdate();
76
+ }
77
+ return this.data;
78
+ };
79
+ /**
80
+ * Overlay item-level translations onto the base store when the runtime is in
81
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
82
+ * memoized by `_store` ref + translation entry ref so the returned object is
83
+ * referentially stable, and a fast path that returns the base store untouched
84
+ * when the item has no translations.
85
+ */
86
+ getSnapShot = () => {
87
+ let base = this._store;
88
+ let translations = this.weaverse.translationMap[this._id];
89
+ if (!translations) {
90
+ return base;
91
+ }
92
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
93
+ return this._cachedSnapshot;
94
+ }
95
+ let merged = { ...base };
96
+ for (let [key, entry] of Object.entries(translations)) {
97
+ if (entry.translatedValue !== void 0) {
98
+ merged[key] = entry.translatedValue;
99
+ }
100
+ }
101
+ this._cachedSnapshot = merged;
102
+ this._snapshotStoreRef = base;
103
+ this._snapshotTranslationRef = translations;
104
+ return merged;
105
+ };
18
106
  };
19
107
  function ensureNextItemConstructor() {
20
108
  if (!Weaverse.ItemConstructor || Weaverse.ItemConstructor === WeaverseItemStore || Weaverse.ItemConstructor === WeaverseNextItem) {
@@ -139,30 +227,264 @@ function createWeaverseNextThemeSettingsStore(options = {}) {
139
227
  };
140
228
  return store;
141
229
  }
230
+ function getNestedKey(obj, path, fallback) {
231
+ let keys = path.split(".");
232
+ let current = obj;
233
+ for (let key of keys) {
234
+ if (current == null || typeof current !== "object") {
235
+ return fallback;
236
+ }
237
+ current = current[key];
238
+ }
239
+ if (typeof current === "string") {
240
+ return current;
241
+ }
242
+ return fallback;
243
+ }
244
+ function interpolate(template, variables) {
245
+ if (!variables) {
246
+ return template;
247
+ }
248
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
249
+ let value = variables[key];
250
+ return value === void 0 ? match : String(value);
251
+ });
252
+ }
253
+ var TranslationContext = createContext(null);
254
+ TranslationContext.displayName = "WeaverseNextTranslationContext";
255
+ function createTranslate(sources) {
256
+ let { staticContent, merchantOverrides, designOverrides, externalT } = sources;
257
+ return (key, variables) => {
258
+ if (externalT) {
259
+ let external = externalT(key, variables);
260
+ if (external && external !== key) {
261
+ return external;
262
+ }
263
+ }
264
+ let design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
265
+ let override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
266
+ let staticValue = getNestedKey(staticContent, key);
267
+ let raw = design ?? override ?? staticValue ?? key;
268
+ return interpolate(raw, variables);
269
+ };
270
+ }
271
+ var EMPTY = {};
272
+ var NOOP_SUBSCRIBE = () => () => {
273
+ };
274
+ var NOOP_SNAPSHOT = () => EMPTY;
275
+ function TranslationProvider({
276
+ staticContent,
277
+ merchantOverrides,
278
+ t: externalT,
279
+ translationStore,
280
+ themeTextStore,
281
+ children
282
+ }) {
283
+ let store = translationStore ?? themeTextStore ?? null;
284
+ let designOverrides = useSafeExternalStore(
285
+ store?.subscribe ?? NOOP_SUBSCRIBE,
286
+ store?.getSnapshot ?? NOOP_SNAPSHOT,
287
+ store?.getServerSnapshot ?? NOOP_SNAPSHOT
288
+ );
289
+ let value = useMemo(
290
+ () => ({
291
+ t: createTranslate({
292
+ staticContent,
293
+ merchantOverrides,
294
+ designOverrides,
295
+ externalT
296
+ }),
297
+ translationStore: store,
298
+ // Deprecated alias — same instance, kept for backward compatibility.
299
+ themeTextStore: store,
300
+ merchantOverrides: merchantOverrides ?? null
301
+ }),
302
+ [staticContent, merchantOverrides, designOverrides, externalT, store]
303
+ );
304
+ return /* @__PURE__ */ jsx(TranslationContext.Provider, { value, children });
305
+ }
306
+ function useTranslation() {
307
+ let ctx = useContext(TranslationContext);
308
+ if (!ctx) {
309
+ throw new Error(
310
+ "useTranslation must be used within <TranslationProvider>. Wrap your app with <WeaverseNextRootProvider>."
311
+ );
312
+ }
313
+ return ctx;
314
+ }
315
+ var ThemeTextProvider = TranslationProvider;
316
+ var useThemeText = useTranslation;
317
+
318
+ // src/translation-store.ts
319
+ var TranslationStore = class {
320
+ /** Current flat translation overrides keyed by dot path. */
321
+ overrides = {};
322
+ listeners = /* @__PURE__ */ new Set();
323
+ /** Replace every override and notify subscribers. */
324
+ setOverrides = (overrides) => {
325
+ this.overrides = { ...overrides };
326
+ this.emit();
327
+ };
328
+ /** Merge changed overrides into the current values and notify subscribers. */
329
+ updateOverrides = (newOverrides) => {
330
+ this.overrides = { ...this.overrides, ...newOverrides };
331
+ this.emit();
332
+ };
333
+ /** Return the current client-side override snapshot. */
334
+ getSnapshot = () => this.overrides;
335
+ /** Return the override snapshot used during server rendering. */
336
+ getServerSnapshot = () => this.overrides;
337
+ /** Subscribe to override changes and return an unsubscribe function. */
338
+ subscribe = (callback) => {
339
+ this.listeners.add(callback);
340
+ return () => {
341
+ this.listeners.delete(callback);
342
+ };
343
+ };
344
+ emit = () => {
345
+ for (const listener of Array.from(this.listeners)) {
346
+ try {
347
+ listener();
348
+ } catch (error) {
349
+ console.error("TranslationStore: Listener error", error);
350
+ }
351
+ }
352
+ };
353
+ };
354
+ var ThemeTextStore = TranslationStore;
355
+ var WeaverseNextRootContext = createContext(null);
356
+ function WeaverseNextRootProvider(props) {
357
+ let {
358
+ children,
359
+ initialThemeSettings,
360
+ merchantOverrides,
361
+ staticContent,
362
+ t,
363
+ themeSchema,
364
+ publicEnv
365
+ } = props;
366
+ let storeRef = useRef(null);
367
+ if (!storeRef.current) {
368
+ storeRef.current = createWeaverseNextThemeSettingsStore({
369
+ publicEnv,
370
+ schema: themeSchema,
371
+ settings: initialThemeSettings
372
+ });
373
+ }
374
+ let themeSettingsStore = storeRef.current;
375
+ let translationStoreRef = useRef(null);
376
+ if (!translationStoreRef.current) {
377
+ translationStoreRef.current = new TranslationStore();
378
+ }
379
+ let translationStore = translationStoreRef.current;
380
+ let appliedThemeSettingsRef = useRef(initialThemeSettings);
381
+ useEffect(() => {
382
+ if (initialThemeSettings && appliedThemeSettingsRef.current !== initialThemeSettings) {
383
+ themeSettingsStore.updateThemeSettings(initialThemeSettings);
384
+ appliedThemeSettingsRef.current = initialThemeSettings;
385
+ }
386
+ }, [initialThemeSettings, themeSettingsStore]);
387
+ let resolvedStaticContent = staticContent ?? getSchemaStaticContent(themeSchema);
388
+ let rootContextValue = useMemo(
389
+ () => ({
390
+ merchantOverrides,
391
+ staticContent: resolvedStaticContent,
392
+ themeSettingsStore,
393
+ translationStore
394
+ }),
395
+ [
396
+ merchantOverrides,
397
+ resolvedStaticContent,
398
+ themeSettingsStore,
399
+ translationStore
400
+ ]
401
+ );
402
+ let contextValue = useMemo(
403
+ () => ({
404
+ merchantOverrides,
405
+ themeSettings: themeSettingsStore.getSnapshot(),
406
+ themeSettingsStore,
407
+ translationStore
408
+ }),
409
+ [merchantOverrides, themeSettingsStore, translationStore]
410
+ );
411
+ return /* @__PURE__ */ jsx(WeaverseNextRootContext.Provider, { value: rootContextValue, children: /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
412
+ TranslationProvider,
413
+ {
414
+ merchantOverrides,
415
+ staticContent: resolvedStaticContent,
416
+ t,
417
+ translationStore,
418
+ children
419
+ }
420
+ ) }) });
421
+ }
142
422
  var EMPTY_THEME_SETTINGS = {};
423
+ var EMPTY_STATIC_CONTENT = {};
424
+ function getSchemaStaticContent(schema) {
425
+ let i18n = schema?.i18n;
426
+ return i18n?.staticContent ?? EMPTY_STATIC_CONTENT;
427
+ }
143
428
  var WeaverseNextContext = createContext(null);
144
429
  function WeaverseNextProvider(props) {
145
430
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
431
+ let rootContext = useContext(WeaverseNextRootContext);
146
432
  let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
147
- let themeSettingsStore = useMemo(
148
- () => createWeaverseNextThemeSettingsStore({
433
+ let ownTranslationStoreRef = useRef(null);
434
+ if (!(rootContext || ownTranslationStoreRef.current)) {
435
+ ownTranslationStoreRef.current = new TranslationStore();
436
+ }
437
+ let translationStore = rootContext?.translationStore ?? ownTranslationStoreRef.current;
438
+ let staticContent = props.staticContent ?? rootContext?.staticContent ?? getSchemaStaticContent(client?.themeSchema);
439
+ let merchantOverrides = props.merchantOverrides ?? rootContext?.merchantOverrides;
440
+ let ownThemeSettingsStore = useMemo(() => {
441
+ if (rootContext) {
442
+ return null;
443
+ }
444
+ return createWeaverseNextThemeSettingsStore({
149
445
  schema: client?.themeSchema,
150
446
  settings: themeSettingsValue
151
- }),
152
- [client?.themeSchema, themeSettingsValue]
153
- );
447
+ });
448
+ }, [rootContext, client?.themeSchema, themeSettingsValue]);
449
+ let themeSettingsStore = rootContext?.themeSettingsStore ?? ownThemeSettingsStore;
450
+ let mergedIntoRootRef = useRef(void 0);
451
+ useEffect(() => {
452
+ if (rootContext && themeSettingsValue !== EMPTY_THEME_SETTINGS && mergedIntoRootRef.current !== themeSettingsValue) {
453
+ rootContext.themeSettingsStore.updateThemeSettings(themeSettingsValue);
454
+ mergedIntoRootRef.current = themeSettingsValue;
455
+ }
456
+ }, [rootContext, themeSettingsValue]);
154
457
  let value = useMemo(
155
458
  () => ({
156
459
  client,
157
460
  rootData,
158
461
  pageData,
159
462
  commerce: commerce ?? client?.commerce,
463
+ merchantOverrides,
160
464
  themeSettings: themeSettingsStore.getSnapshot(),
161
- themeSettingsStore
465
+ themeSettingsStore,
466
+ translationStore
162
467
  }),
163
- [client, rootData, pageData, commerce, themeSettingsStore]
468
+ [
469
+ client,
470
+ rootData,
471
+ pageData,
472
+ commerce,
473
+ merchantOverrides,
474
+ themeSettingsStore,
475
+ translationStore
476
+ ]
164
477
  );
165
- return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children });
478
+ return /* @__PURE__ */ jsx(WeaverseNextContext.Provider, { value, children: /* @__PURE__ */ jsx(
479
+ TranslationProvider,
480
+ {
481
+ merchantOverrides,
482
+ staticContent,
483
+ t: props.t,
484
+ translationStore,
485
+ children
486
+ }
487
+ ) });
166
488
  }
167
489
 
168
490
  // src/hooks.ts
@@ -203,6 +525,12 @@ function getConfigString(configs, key) {
203
525
  let value = configs?.[key];
204
526
  return typeof value === "string" ? value : void 0;
205
527
  }
528
+ function resolveModeFlag(config, key) {
529
+ return config.client?.requestContext?.[key] === true || config.data.configs?.[key] === true;
530
+ }
531
+ function resolveSectionType(config) {
532
+ return config.client?.requestContext?.sectionType || getConfigString(config.data.configs, "sectionType") || void 0;
533
+ }
206
534
  function getRecordString(record, key) {
207
535
  let value = record?.[key];
208
536
  return typeof value === "string" ? value : void 0;
@@ -239,38 +567,87 @@ function registerRuntime(runtime) {
239
567
  runtimeWindow.__weaverses[runtime.pageId] = runtime;
240
568
  runtimeWindow.__weaverse = runtime;
241
569
  }
570
+ function rebindPageItemsToRuntime(runtime) {
571
+ let items = runtime.data?.items;
572
+ if (!items) {
573
+ return;
574
+ }
575
+ for (let { id } of items) {
576
+ let instance = runtime.itemInstances.get(id);
577
+ if (instance && instance.weaverse !== runtime) {
578
+ instance.weaverse = runtime;
579
+ }
580
+ }
581
+ }
242
582
  var WeaverseNextRuntime = class extends Weaverse {
583
+ /** Stable ID of the page owned by this runtime. */
243
584
  pageId;
585
+ /** Mutable callbacks and stores consumed by the Studio bridge. */
244
586
  internal;
587
+ /** Path, query, and locale metadata for Studio navigation. */
245
588
  requestInfo;
589
+ /** Whether this runtime renders a component preview. */
246
590
  isPreviewMode;
591
+ /** Whether this runtime renders a saved revision. */
247
592
  isRevisionPreview;
593
+ /** Component type rendered in section preview mode. */
248
594
  sectionType;
595
+ // ─── Item-level translation sidecar (design mode only) ─────────────
596
+ /**
597
+ * Translation map received from Builder in design mode, keyed by item ID.
598
+ * `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
599
+ * components, so translations render without mutating the base `_store`.
600
+ */
249
601
  translationMap = {};
250
- translationLocale = "";
251
- translationLanguageId = "";
602
+ /** Locale associated with the item-level translation sidecar. */
603
+ translationLocale = String();
604
+ /** Weaverse language ID used when saving translation changes. */
605
+ translationLanguageId = String();
606
+ /**
607
+ * Fresh page data captured while React was rendering. Runtime reuse happens
608
+ * inside the renderer's render phase (`useMemo`), where applying it through
609
+ * `setProjectData()` would emit item updates to `useSyncExternalStore`
610
+ * subscribers mid-render. The renderer applies it post-commit via
611
+ * `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
612
+ * Studio tree owns the page data there.
613
+ */
614
+ pendingProjectData;
615
+ /**
616
+ * Reused item instances whose data was refreshed while this runtime was
617
+ * constructed during render (core `initProject()` calls `setData()` on
618
+ * them). Their data is already fresh; only the subscriber notification was
619
+ * deferred to `flushRenderPhaseUpdates()`.
620
+ */
621
+ pendingItemUpdates = [];
622
+ /** Create a browser runtime from a loaded page and its request context. */
252
623
  constructor(config) {
253
624
  let { client, data } = config;
254
625
  let page = getRenderablePage(data);
255
626
  let configs = data.configs;
256
627
  let requestContext = client?.requestContext;
628
+ let isDesignMode = resolveModeFlag(config, "isDesignMode");
629
+ let isPreviewMode = resolveModeFlag(config, "isPreviewMode");
630
+ let isRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
631
+ let sectionType = resolveSectionType(config);
257
632
  let projectId = resolveProjectId(client, data, page.id);
258
633
  ensureNextItemConstructor();
259
634
  super({
260
635
  projectId,
261
636
  data: page,
262
- isDesignMode: requestContext?.isDesignMode ?? false,
637
+ isDesignMode,
263
638
  weaverseHost: getConfigString(configs, "weaverseHost"),
264
639
  weaverseVersion: getConfigString(configs, "weaverseVersion")
265
640
  });
266
641
  this.pageId = page.id;
267
642
  this.dataContext = resolveDataContext(config);
268
643
  this.requestInfo = buildWeaverseNextRequestInfo(requestContext);
269
- this.isDesignMode = requestContext?.isDesignMode ?? false;
270
- this.isPreviewMode = requestContext?.isPreviewMode ?? false;
271
- this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
272
- this.sectionType = requestContext?.sectionType;
644
+ this.isDesignMode = isDesignMode;
645
+ this.isPreviewMode = isPreviewMode;
646
+ this.isRevisionPreview = isRevisionPreview;
647
+ this.sectionType = sectionType;
648
+ let translationStore = config.translationStore ?? new TranslationStore();
273
649
  this.internal = {
650
+ merchantOverrides: config.merchantOverrides,
274
651
  navigate: config.navigate,
275
652
  pageAssignment: data.pageAssignment,
276
653
  project: data.project,
@@ -278,17 +655,119 @@ var WeaverseNextRuntime = class extends Weaverse {
278
655
  themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
279
656
  schema: client?.themeSchema,
280
657
  settings: client?.themeSettings
281
- })
658
+ }),
659
+ translationStore,
660
+ themeTextStore: translationStore
282
661
  };
662
+ this.extractTranslationSidecar();
283
663
  }
284
- extractTranslationSidecar = () => void 0;
664
+ /**
665
+ * Pull `translationMap` / `translationLocale` / `translationLanguageId` off
666
+ * the current page data (Builder attaches them in design mode). Unlike
667
+ * Hydrogen, this also clears the sidecar to defaults when the new page data
668
+ * carries none, so a reused runtime / project-data swap can't leave a stale
669
+ * map overlaying the fresh tree.
670
+ */
671
+ extractTranslationSidecar = () => {
672
+ let pageData = this.data;
673
+ let translationMap = pageData?.translationMap;
674
+ if (translationMap) {
675
+ this.translationMap = translationMap;
676
+ this.translationLocale = pageData?.translationLocale || "";
677
+ this.translationLanguageId = pageData?.translationLanguageId || "";
678
+ } else {
679
+ this.translationMap = {};
680
+ this.translationLocale = "";
681
+ this.translationLanguageId = "";
682
+ }
683
+ };
684
+ /**
685
+ * Replace the translation sidecar (Builder pushes this when translation data
686
+ * arrives or the active language changes) and refresh every item so their
687
+ * merged snapshots recompute.
688
+ */
285
689
  setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
286
690
  this.translationMap = map;
287
691
  this.translationLocale = locale;
288
692
  this.translationLanguageId = languageId;
693
+ this.refreshAllItems();
694
+ };
695
+ /**
696
+ * Update a single translatable field for one item. Assigns a new per-item
697
+ * entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
698
+ * invalidates, then triggers only that item's subscribers.
699
+ */
700
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
701
+ if (!this.translationMap[itemId]) {
702
+ this.translationMap[itemId] = {};
703
+ }
704
+ this.translationMap[itemId] = {
705
+ ...this.translationMap[itemId],
706
+ [key]: { originalValue, translatedValue }
707
+ };
708
+ let instance = this.itemInstances.get(itemId);
709
+ if (instance) {
710
+ instance.triggerUpdate();
711
+ }
712
+ };
713
+ /**
714
+ * Collect translation changes for the save flow. Returns `undefined` unless
715
+ * both the locale and language ID are set and at least one entry exists;
716
+ * each entry key is namespaced as `data.<field>` to match Builder.
717
+ */
718
+ getTranslationChanges = () => {
719
+ let languageId = this.translationLanguageId;
720
+ if (!(this.translationLocale && languageId)) {
721
+ return;
722
+ }
723
+ let entries = [];
724
+ for (let [itemId, fields] of Object.entries(this.translationMap)) {
725
+ for (let [key, entry] of Object.entries(fields)) {
726
+ entries.push({
727
+ itemId,
728
+ key: `data.${key}`,
729
+ originalValue: entry.originalValue,
730
+ translatedValue: entry.translatedValue
731
+ });
732
+ }
733
+ }
734
+ if (entries.length === 0) {
735
+ return;
736
+ }
737
+ return { languageId, entries };
738
+ };
739
+ /**
740
+ * Re-extract the translation sidecar whenever project data is replaced (route
741
+ * change / non-design reuse), before rebuilding item stores. Takes the
742
+ * renderable page shape (`rootId` resolved) that `getRenderablePage()` and
743
+ * the reuse branch pass in.
744
+ */
745
+ setProjectData = (data) => {
746
+ this.data = data;
747
+ this.extractTranslationSidecar();
748
+ this.initProject();
749
+ };
750
+ /**
751
+ * Apply updates deferred off the render phase: swap in any pending page data
752
+ * captured on runtime reuse, then notify items whose data was refreshed
753
+ * during construction. The renderer calls this from a layout effect right
754
+ * after commit, so subscribers re-render before paint (no content flash) and
755
+ * without React's setState-in-render warning.
756
+ */
757
+ flushRenderPhaseUpdates = () => {
758
+ let pendingData = this.pendingProjectData;
759
+ if (pendingData) {
760
+ this.pendingProjectData = void 0;
761
+ this.setProjectData(pendingData);
762
+ }
763
+ let pendingItems = this.pendingItemUpdates;
764
+ if (pendingItems.length > 0) {
765
+ this.pendingItemUpdates = [];
766
+ for (let item of pendingItems) {
767
+ item.triggerUpdate();
768
+ }
769
+ }
289
770
  };
290
- updateTranslation = () => void 0;
291
- getTranslationChanges = () => void 0;
292
771
  };
293
772
  function createWeaverseNextRuntime(config) {
294
773
  let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
@@ -297,19 +776,26 @@ function createWeaverseNextRuntime(config) {
297
776
  let existing = runtimeWindow?.__weaverses?.[page.id];
298
777
  let requestKey = getRuntimeKey(page.id, requestInfo);
299
778
  if (existing?.__weaverseNextRequestKey === requestKey) {
300
- let nextIsDesignMode = config.client?.requestContext?.isDesignMode ?? false;
301
- if (!(existing.isDesignMode || nextIsDesignMode)) {
302
- existing.setProjectData(page);
779
+ let configs = config.data.configs;
780
+ let nextIsDesignMode = resolveModeFlag(config, "isDesignMode");
781
+ let nextIsPreviewMode = resolveModeFlag(config, "isPreviewMode");
782
+ let nextIsRevisionPreview = resolveModeFlag(config, "isRevisionPreview");
783
+ let nextSectionType = resolveSectionType(config);
784
+ if (existing.isDesignMode || nextIsDesignMode) {
785
+ existing.pendingProjectData = void 0;
786
+ } else {
787
+ existing.pendingProjectData = page;
303
788
  }
789
+ existing.__weaverseNextLatestData = page;
304
790
  existing.dataContext = resolveDataContext(config);
305
791
  existing.requestInfo = requestInfo;
306
792
  existing.projectId = resolveProjectId(config.client, config.data, page.id);
307
793
  existing.isDesignMode = nextIsDesignMode;
308
- existing.isPreviewMode = config.client?.requestContext?.isPreviewMode ?? false;
309
- existing.isRevisionPreview = config.client?.requestContext?.isRevisionPreview ?? false;
310
- existing.sectionType = config.client?.requestContext?.sectionType;
311
- existing.weaverseHost = getConfigString(config.data.configs, "weaverseHost") ?? existing.weaverseHost;
312
- existing.weaverseVersion = getConfigString(config.data.configs, "weaverseVersion") ?? existing.weaverseVersion;
794
+ existing.isPreviewMode = nextIsPreviewMode;
795
+ existing.isRevisionPreview = nextIsRevisionPreview;
796
+ existing.sectionType = nextSectionType;
797
+ existing.weaverseHost = getConfigString(configs, "weaverseHost") ?? existing.weaverseHost;
798
+ existing.weaverseVersion = getConfigString(configs, "weaverseVersion") ?? existing.weaverseVersion;
313
799
  existing.internal.pageAssignment = config.data.pageAssignment;
314
800
  existing.internal.project = config.data.project;
315
801
  if (config.navigate) {
@@ -321,11 +807,21 @@ function createWeaverseNextRuntime(config) {
321
807
  if (config.themeSettingsStore) {
322
808
  existing.internal.themeSettingsStore = config.themeSettingsStore;
323
809
  }
810
+ if (config.translationStore) {
811
+ existing.internal.translationStore = config.translationStore;
812
+ existing.internal.themeTextStore = config.translationStore;
813
+ }
814
+ existing.internal.merchantOverrides = config.merchantOverrides;
324
815
  registerRuntime(existing);
325
816
  return existing;
326
817
  }
327
- let runtime = new WeaverseNextRuntime(config);
818
+ let [runtime, refreshedItems] = collectDeferredItemUpdates(
819
+ () => new WeaverseNextRuntime(config)
820
+ );
821
+ runtime.pendingItemUpdates = refreshedItems;
822
+ rebindPageItemsToRuntime(runtime);
328
823
  runtime.__weaverseNextRequestKey = requestKey;
824
+ runtime.__weaverseNextLatestData = page;
329
825
  registerRuntime(runtime);
330
826
  return runtime;
331
827
  }
@@ -338,13 +834,16 @@ function bindWeaverseNextStudioRuntime(runtime) {
338
834
  return false;
339
835
  }
340
836
  let boundRuntime = runtime;
341
- if (!boundRuntime.__weaverseNextStudioBound) {
837
+ let activeRuntime = studio.weaverse;
838
+ let activeRequestInfo = activeRuntime?.requestInfo;
839
+ let isActiveUrl = !activeRequestInfo || activeRequestInfo.pathname === runtime.requestInfo.pathname && activeRequestInfo.search === runtime.requestInfo.search;
840
+ if (!(boundRuntime.__weaverseNextStudioBound && isActiveUrl)) {
342
841
  studio.init?.(runtime);
343
842
  boundRuntime.__weaverseNextStudioBound = true;
344
843
  return true;
345
844
  }
346
845
  studio.refreshStudio?.({
347
- data: runtime.data,
846
+ data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
348
847
  pageId: runtime.pageId,
349
848
  requestInfo: runtime.requestInfo
350
849
  });
@@ -353,7 +852,7 @@ function bindWeaverseNextStudioRuntime(runtime) {
353
852
  var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
354
853
  var STUDIO_BIND_MAX_ATTEMPTS = 100;
355
854
  function WeaverseNextStudioBridge(props) {
356
- let { runtime, navigate, revalidate } = props;
855
+ let { runtime, navigate, revalidate, revalidateItem } = props;
357
856
  let lastRuntimeRef = useRef(runtime);
358
857
  let lastRuntimeDataRef = useRef(runtime.data);
359
858
  let lastRequestInfoRef = useRef(runtime.requestInfo);
@@ -364,6 +863,9 @@ function WeaverseNextStudioBridge(props) {
364
863
  if (revalidate) {
365
864
  runtime.internal.revalidate = revalidate;
366
865
  }
866
+ if (revalidateItem) {
867
+ runtime.internal.revalidateItem = revalidateItem;
868
+ }
367
869
  if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
368
870
  return;
369
871
  }
@@ -375,7 +877,7 @@ function WeaverseNextStudioBridge(props) {
375
877
  }
376
878
  }, STUDIO_BIND_RETRY_INTERVAL_MS);
377
879
  return () => window.clearInterval(interval);
378
- }, [runtime, navigate, revalidate]);
880
+ }, [runtime, navigate, revalidate, revalidateItem]);
379
881
  useEffect(() => {
380
882
  if (lastRuntimeRef.current !== runtime) {
381
883
  lastRuntimeRef.current = runtime;
@@ -428,18 +930,29 @@ function useWeaverseNextStudioInternals(options) {
428
930
  return ref.current.internals;
429
931
  }
430
932
  function WeaverseNextStudio(props) {
431
- let { runtime, replace } = props;
933
+ let { runtime, replace, revalidateEndpoint } = props;
432
934
  let { navigate, revalidate } = useWeaverseNextStudioInternals({ replace });
935
+ let endpoint = revalidateEndpoint ?? DEFAULT_REVALIDATE_ENDPOINT;
936
+ let revalidateItemRef = useRef(null);
937
+ if (revalidateItemRef.current?.endpoint !== endpoint || revalidateItemRef.current.runtime !== runtime) {
938
+ revalidateItemRef.current = {
939
+ endpoint,
940
+ revalidateItem: (draftItem) => revalidateWeaverseNextItem(runtime, draftItem, endpoint),
941
+ runtime
942
+ };
943
+ }
433
944
  return /* @__PURE__ */ jsx(
434
945
  WeaverseNextStudioBridge,
435
946
  {
436
947
  navigate,
437
948
  revalidate,
949
+ revalidateItem: revalidateItemRef.current.revalidateItem,
438
950
  runtime
439
951
  }
440
952
  );
441
953
  }
442
954
  var EMPTY_DATA_CONTEXT = {};
955
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
443
956
  function getRenderablePage2(data) {
444
957
  let page = data?.page;
445
958
  if (!page) {
@@ -462,9 +975,22 @@ var WeaverseNextRenderer = memo(function WeaverseNextRendererComponent(props) {
462
975
  client,
463
976
  data: { ...data, page },
464
977
  dataContext,
465
- themeSettingsStore: context?.themeSettingsStore
978
+ merchantOverrides: context?.merchantOverrides,
979
+ themeSettingsStore: context?.themeSettingsStore,
980
+ translationStore: context?.translationStore
466
981
  });
467
- }, [client, page, data, dataContext, context?.themeSettingsStore]);
982
+ }, [
983
+ client,
984
+ page,
985
+ data,
986
+ dataContext,
987
+ context?.merchantOverrides,
988
+ context?.themeSettingsStore,
989
+ context?.translationStore
990
+ ]);
991
+ useIsomorphicLayoutEffect(() => {
992
+ weaverse?.flushRenderPhaseUpdates();
993
+ }, [weaverse]);
468
994
  if (!weaverse) {
469
995
  return null;
470
996
  }
@@ -537,10 +1063,9 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
537
1063
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
538
1064
  return null;
539
1065
  }
540
- let framework = options.framework ?? "hydrogen";
541
- let bundle = isDesignMode ? "index.js" : "preview.js";
1066
+ let bundle = isPreviewMode || isRevisionPreview ? "preview.js" : "index.js";
542
1067
  let version = last(searchParams, "weaverseVersion");
543
- let url = `${origin}/static/studio/${framework}/${bundle}`;
1068
+ let url = `${origin}/static/studio/next/${bundle}`;
544
1069
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
545
1070
  }
546
1071
 
@@ -564,25 +1089,30 @@ function loadScript(src) {
564
1089
  document.head.appendChild(script);
565
1090
  loadedScriptSrcs.add(src);
566
1091
  }
1092
+ function loadWeaverseNextStudioScript(context, options = {}) {
1093
+ let resolvedContext = context;
1094
+ if (!resolvedContext && typeof window !== "undefined") {
1095
+ resolvedContext = {
1096
+ searchParams: new URLSearchParams(window.location.search),
1097
+ url: window.location.href
1098
+ };
1099
+ }
1100
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
1101
+ storefrontHostname: options.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1102
+ });
1103
+ if (src) {
1104
+ loadScript(src);
1105
+ }
1106
+ }
567
1107
  function WeaverseNextStudioConnect(props) {
568
1108
  useEffect(() => {
569
- let context = props.context;
570
- if (!context && typeof window !== "undefined") {
571
- context = {
572
- searchParams: new URLSearchParams(window.location.search),
573
- url: window.location.href
574
- };
575
- }
576
- let src = resolveWeaverseNextStudioScriptSrc(context, {
577
- storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
1109
+ loadWeaverseNextStudioScript(props.context, {
1110
+ storefrontHostname: props.storefrontHostname
578
1111
  });
579
- if (src) {
580
- loadScript(src);
581
- }
582
1112
  }, [props.context, props.storefrontHostname]);
583
1113
  return null;
584
1114
  }
585
1115
 
586
- export { WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, resolveWeaverseNextStudioScriptSrc, useThemeSettings, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
1116
+ export { ThemeTextProvider, ThemeTextStore, TranslationProvider, TranslationStore, WeaverseNextProvider, WeaverseNextRenderer, WeaverseNextRootProvider, WeaverseNextRuntime, WeaverseNextStudio, WeaverseNextStudioBridge, WeaverseNextStudioConnect, bindWeaverseNextStudioRuntime, createTranslate, createWeaverseNextClient, createWeaverseNextRuntime, createWeaverseNextStudioInternals, createWeaverseNextThemeSettingsStore, getNestedKey, interpolate, loadWeaverseNextStudioScript, resolveWeaverseNextStudioScriptSrc, useThemeSettings, useThemeText, useTranslation, useWeaverseCommerce, useWeaverseNextStudioInternals, useWeaversePageData, useWeaverseRootData };
587
1117
  //# sourceMappingURL=index.mjs.map
588
1118
  //# sourceMappingURL=index.mjs.map