@weaverse/hydrogen 5.11.1 → 5.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -58,12 +58,39 @@ function interpolate(template, variables) {
58
58
  }
59
59
  var ThemeTextContext = createContext(null);
60
60
  ThemeTextContext.displayName = "WeaverseThemeTextContext";
61
+ function unflattenKeys(flat) {
62
+ const result = {};
63
+ for (const [dotPath, value] of Object.entries(flat)) {
64
+ const keys = dotPath.split(".");
65
+ let current = result;
66
+ for (let i = 0; i < keys.length - 1; i++) {
67
+ const k = keys[i];
68
+ if (!(k in current) || typeof current[k] !== "object") {
69
+ current[k] = {};
70
+ }
71
+ current = current[k];
72
+ }
73
+ current[keys[keys.length - 1]] = value;
74
+ }
75
+ return result;
76
+ }
77
+ var EMPTY = {};
78
+ var NOOP_SUBSCRIBE = () => () => {
79
+ };
80
+ var NOOP_SNAPSHOT = () => EMPTY;
61
81
  function ThemeTextProvider({
62
82
  staticContent,
63
83
  merchantOverrides,
64
84
  t: externalT,
85
+ themeTextStore,
65
86
  children
66
87
  }) {
88
+ const designOverrides = useSafeExternalStore(
89
+ themeTextStore?.subscribe ?? NOOP_SUBSCRIBE,
90
+ themeTextStore?.getSnapshot ?? NOOP_SNAPSHOT,
91
+ themeTextStore?.getServerSnapshot ?? NOOP_SNAPSHOT
92
+ );
93
+ const mergedOverrides = designOverrides !== EMPTY && Object.keys(designOverrides).length > 0 ? { ...merchantOverrides, ...unflattenKeys(designOverrides) } : merchantOverrides;
67
94
  const value = useMemo(() => {
68
95
  const t = (key, variables) => {
69
96
  if (externalT) {
@@ -72,13 +99,23 @@ function ThemeTextProvider({
72
99
  return external;
73
100
  }
74
101
  }
75
- const override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
102
+ const override = mergedOverrides ? getNestedKey(mergedOverrides, key) : void 0;
76
103
  const staticValue = getNestedKey(staticContent, key);
77
104
  const raw = override ?? staticValue ?? key;
78
105
  return interpolate(raw, variables);
79
106
  };
80
- return { t };
81
- }, [staticContent, merchantOverrides, externalT]);
107
+ return {
108
+ t,
109
+ themeTextStore: themeTextStore ?? null,
110
+ merchantOverrides: merchantOverrides ?? null
111
+ };
112
+ }, [
113
+ staticContent,
114
+ merchantOverrides,
115
+ mergedOverrides,
116
+ externalT,
117
+ themeTextStore
118
+ ]);
82
119
  return /* @__PURE__ */ jsx(ThemeTextContext.Provider, { value, children });
83
120
  }
84
121
  function useThemeText() {
@@ -349,6 +386,37 @@ function getPreviewData(type, components, weaverseHost) {
349
386
  }
350
387
  };
351
388
  }
389
+
390
+ // src/utils/theme-text-store.ts
391
+ var ThemeTextStore = class {
392
+ overrides = {};
393
+ listeners = /* @__PURE__ */ new Set();
394
+ setOverrides = (overrides) => {
395
+ this.overrides = { ...overrides };
396
+ this.emit();
397
+ };
398
+ updateOverrides = (newOverrides) => {
399
+ this.overrides = { ...this.overrides, ...newOverrides };
400
+ this.emit();
401
+ };
402
+ getSnapshot = () => this.overrides;
403
+ getServerSnapshot = () => this.overrides;
404
+ subscribe = (callback) => {
405
+ this.listeners.add(callback);
406
+ return () => {
407
+ this.listeners.delete(callback);
408
+ };
409
+ };
410
+ emit = () => {
411
+ for (const listener of Array.from(this.listeners)) {
412
+ try {
413
+ listener();
414
+ } catch (error) {
415
+ console.error("ThemeTextStore: Listener error", error);
416
+ }
417
+ }
418
+ };
419
+ };
352
420
  var ThemeSettingsStore = class {
353
421
  settings = {};
354
422
  schema;
@@ -459,6 +527,7 @@ function useStudio(weaverse) {
459
527
  let { pathname, search } = useLocation();
460
528
  let navigate = useNavigate();
461
529
  let themeSettingsStore = useThemeSettingsStore();
530
+ let { themeTextStore, merchantOverrides } = useThemeText();
462
531
  let {
463
532
  isDesignMode,
464
533
  weaverseHost,
@@ -476,7 +545,9 @@ function useStudio(weaverse) {
476
545
  ...weaverse.internal,
477
546
  navigate,
478
547
  revalidate,
479
- themeSettingsStore
548
+ merchantOverrides,
549
+ themeSettingsStore,
550
+ themeTextStore
480
551
  };
481
552
  let studioSrc = `${weaverseHost}/static/studio/hydrogen/index.js?v=${weaverseVersion}`;
482
553
  loadScript(studioSrc).then(() => {
@@ -501,6 +572,13 @@ function usePixel(context) {
501
572
  }, []);
502
573
  }
503
574
  var WeaverseHydrogenItem = class extends WeaverseItemStore {
575
+ /**
576
+ * Cached merged snapshot (base + translations).
577
+ * Reset to `null` whenever `_store` or the item's translation entry changes.
578
+ */
579
+ _cachedSnapshot = null;
580
+ _snapshotStoreRef = null;
581
+ _snapshotTranslationRef = null;
504
582
  constructor(initialData, weaverse) {
505
583
  super(initialData, weaverse);
506
584
  const { data, ...rest } = initialData;
@@ -514,6 +592,32 @@ var WeaverseHydrogenItem = class extends WeaverseItemStore {
514
592
  get Element() {
515
593
  return super.Element;
516
594
  }
595
+ /**
596
+ * Override getSnapShot to merge translation data when in translation mode.
597
+ * Memoizes the result: returns the same object reference as long as
598
+ * both `_store` and the translation entry haven't changed.
599
+ * This is critical for `useSyncExternalStore` stability.
600
+ */
601
+ getSnapShot = () => {
602
+ const base = this._store;
603
+ const translations = this.weaverse.translationMap?.[this._id];
604
+ if (!translations) {
605
+ return base;
606
+ }
607
+ if (this._cachedSnapshot && this._snapshotStoreRef === base && this._snapshotTranslationRef === translations) {
608
+ return this._cachedSnapshot;
609
+ }
610
+ const merged = { ...base };
611
+ for (const [key, entry] of Object.entries(translations)) {
612
+ if (entry.translatedValue !== void 0) {
613
+ merged[key] = entry.translatedValue;
614
+ }
615
+ }
616
+ this._cachedSnapshot = merged;
617
+ this._snapshotStoreRef = base;
618
+ this._snapshotTranslationRef = translations;
619
+ return merged;
620
+ };
517
621
  };
518
622
  Weaverse.ItemConstructor = WeaverseHydrogenItem;
519
623
  var WeaverseHydrogen = class extends Weaverse {
@@ -529,8 +633,16 @@ var WeaverseHydrogen = class extends Weaverse {
529
633
  isPreviewMode;
530
634
  isRevisionPreview;
531
635
  sectionType;
532
- static itemInstances;
533
- static elementRegistry;
636
+ // ─── Translation sidecar (design mode only) ────────────────────────
637
+ /**
638
+ * Translation map received from the builder in design mode.
639
+ * Keyed by item ID, each entry maps field names to translated values.
640
+ * `WeaverseHydrogenItem.getSnapShot()` merges these into the data
641
+ * returned to components, so translations render without monkey-patching.
642
+ */
643
+ translationMap = {};
644
+ translationLocale = "";
645
+ translationLanguageId = "";
534
646
  constructor(params) {
535
647
  const { internal, pageId, requestInfo, ...coreParams } = params;
536
648
  super({ ...coreParams });
@@ -546,7 +658,82 @@ var WeaverseHydrogen = class extends Weaverse {
546
658
  this.isPreviewMode = params.isPreviewMode ?? false;
547
659
  this.isRevisionPreview = params.isRevisionPreview ?? false;
548
660
  this.sectionType = params.sectionType || "";
661
+ this.extractTranslationSidecar();
549
662
  }
663
+ /**
664
+ * Extract translation sidecar data from `this.data` (the page data).
665
+ * The builder attaches `translationMap`, `translationLocale`, and
666
+ * `translationLanguageId` to the page data in design mode.
667
+ */
668
+ extractTranslationSidecar = () => {
669
+ const pageData = this.data;
670
+ if (pageData?.translationMap) {
671
+ this.translationMap = pageData.translationMap;
672
+ this.translationLocale = pageData.translationLocale || "";
673
+ this.translationLanguageId = pageData.translationLanguageId || "";
674
+ }
675
+ };
676
+ /**
677
+ * Set the translation sidecar data.
678
+ * Called by the studio bridge when translation data arrives or changes.
679
+ * Triggers a re-render of all items so translated snapshots are recalculated.
680
+ */
681
+ setTranslationSidecar = (translationMap, locale, languageId) => {
682
+ this.translationMap = translationMap;
683
+ this.translationLocale = locale;
684
+ this.translationLanguageId = languageId;
685
+ this.refreshAllItems();
686
+ };
687
+ /**
688
+ * Update a single translation entry for a specific item field.
689
+ * Used by the studio bridge when the user edits a translatable field.
690
+ */
691
+ updateTranslation = (itemId, key, originalValue, translatedValue) => {
692
+ if (!this.translationMap[itemId]) {
693
+ this.translationMap[itemId] = {};
694
+ }
695
+ this.translationMap[itemId] = {
696
+ ...this.translationMap[itemId],
697
+ [key]: { originalValue, translatedValue }
698
+ };
699
+ const instance = this.itemInstances.get(itemId);
700
+ if (instance) {
701
+ instance.triggerUpdate();
702
+ }
703
+ };
704
+ /**
705
+ * Collect all translation changes across all weaverse instances.
706
+ * Used by the save flow to persist translation edits.
707
+ */
708
+ getTranslationChanges = () => {
709
+ const entries = [];
710
+ const languageId = this.translationLanguageId;
711
+ if (!(this.translationLocale && languageId)) {
712
+ return void 0;
713
+ }
714
+ for (const [itemId, fields] of Object.entries(this.translationMap)) {
715
+ for (const [key, entry] of Object.entries(fields)) {
716
+ entries.push({
717
+ itemId,
718
+ key: `data.${key}`,
719
+ originalValue: entry.originalValue,
720
+ translatedValue: entry.translatedValue
721
+ });
722
+ }
723
+ }
724
+ if (entries.length === 0) {
725
+ return void 0;
726
+ }
727
+ return { languageId, entries };
728
+ };
729
+ /**
730
+ * Override setProjectData to re-extract translation sidecar from new page data.
731
+ */
732
+ setProjectData = (data) => {
733
+ this.data = data;
734
+ this.extractTranslationSidecar();
735
+ this.initProject();
736
+ };
550
737
  };
551
738
  function createWeaverseInstance(params) {
552
739
  if (isBrowser) {
@@ -645,12 +832,17 @@ function withWeaverse(Component, options) {
645
832
  return (props) => {
646
833
  const themeSettingsStore = useCreateThemeSettingsStore();
647
834
  const themeTextData = useThemeTextData();
835
+ const themeTextStoreRef = useRef(null);
836
+ if (!themeTextStoreRef.current) {
837
+ themeTextStoreRef.current = new ThemeTextStore();
838
+ }
648
839
  return /* @__PURE__ */ jsx(ThemeSettingsStoreContext.Provider, { value: themeSettingsStore, children: /* @__PURE__ */ jsx(
649
840
  ThemeTextProvider,
650
841
  {
651
842
  merchantOverrides: themeTextData.merchantOverrides,
652
843
  staticContent: themeTextData.staticContent,
653
844
  t: options?.t,
845
+ themeTextStore: themeTextStoreRef.current,
654
846
  children: /* @__PURE__ */ jsx(Component, { ...props })
655
847
  }
656
848
  ) });
@@ -1442,6 +1634,6 @@ var IMAGES_PLACEHOLDERS = {
1442
1634
  product_18: "https://cdn.shopify.com/s/files/1/0838/0052/3057/files/fpo-product-watch-2-1024x1024.svg"
1443
1635
  };
1444
1636
 
1445
- export { IMAGES_PLACEHOLDERS, ThemeTextProvider, WeaverseClient, WeaverseError, WeaverseHydrogen, WeaverseHydrogenItem, WeaverseHydrogenRoot, createWeaverseDataContext, defaultComponents, generateDataFromSchema, getNestedKey, getPreviewData, getRequestQueries, getSelectedProductOptions, getWeaverseConfigs, hasError, hasWeaverseStudio, interpolate, isFetchProjectPayload, isProjectIdFunction, isThemeSettingsResponse, isValidHydrogenSchema, isWithCacheFetchResponse, registerComponent, useThemeSettings, useThemeText, useWeaverseDataContext, withWeaverse };
1637
+ export { IMAGES_PLACEHOLDERS, ThemeTextProvider, ThemeTextStore, WeaverseClient, WeaverseError, WeaverseHydrogen, WeaverseHydrogenItem, WeaverseHydrogenRoot, createWeaverseDataContext, defaultComponents, generateDataFromSchema, getNestedKey, getPreviewData, getRequestQueries, getSelectedProductOptions, getWeaverseConfigs, hasError, hasWeaverseStudio, interpolate, isFetchProjectPayload, isProjectIdFunction, isThemeSettingsResponse, isValidHydrogenSchema, isWithCacheFetchResponse, registerComponent, useThemeSettings, useThemeText, useWeaverseDataContext, withWeaverse };
1446
1638
  //# sourceMappingURL=index.mjs.map
1447
1639
  //# sourceMappingURL=index.mjs.map