personalize-connect-sdk 1.3.2 → 1.3.4

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.d.mts CHANGED
@@ -140,15 +140,14 @@ declare function resolveContent(options: ResolveContentOptions): Promise<Resolve
140
140
  type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
141
141
  /**
142
142
  * HOC that wraps any JSS component.
143
- * Looks for config in this order:
143
+ *
144
+ * Config lookup order:
144
145
  * 1. props.rendering.personalizeConnect (inline on layout data)
145
146
  * 2. context.configs map (loaded from content tree via Edge)
146
147
  *
147
- * If config is found, renders with defaultKey first, calls Personalize
148
- * asynchronously, and re-renders with personalized content.
149
- *
150
- * In Page Builder, renders a visual indicator (border + badge) on
151
- * components that have personalization configured.
148
+ * Live site: renders with defaultKey first, calls Personalize async, re-renders.
149
+ * Page Builder: shows a preview bar above the component to switch between
150
+ * content variants without running actual personalization.
152
151
  */
153
152
  declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
154
153
 
package/dist/index.d.ts CHANGED
@@ -140,15 +140,14 @@ declare function resolveContent(options: ResolveContentOptions): Promise<Resolve
140
140
  type GetConfigFromProps<P> = (props: P) => PersonalizeConnectConfig | undefined;
141
141
  /**
142
142
  * HOC that wraps any JSS component.
143
- * Looks for config in this order:
143
+ *
144
+ * Config lookup order:
144
145
  * 1. props.rendering.personalizeConnect (inline on layout data)
145
146
  * 2. context.configs map (loaded from content tree via Edge)
146
147
  *
147
- * If config is found, renders with defaultKey first, calls Personalize
148
- * asynchronously, and re-renders with personalized content.
149
- *
150
- * In Page Builder, renders a visual indicator (border + badge) on
151
- * components that have personalization configured.
148
+ * Live site: renders with defaultKey first, calls Personalize async, re-renders.
149
+ * Page Builder: shows a preview bar above the component to switch between
150
+ * content variants without running actual personalization.
152
151
  */
153
152
  declare function withPersonalizeConnect<P extends object>(WrappedComponent: ComponentType<P>, getConfig?: GetConfigFromProps<P>): ComponentType<P>;
154
153
 
package/dist/index.js CHANGED
@@ -164,6 +164,11 @@ var ITEM_QUERY = `
164
164
  }
165
165
  }
166
166
  `;
167
+ function formatGuidForEdge(id) {
168
+ const clean = id.replace(/[{}\-\s]/g, "").toLowerCase();
169
+ if (clean.length !== 32 || !/^[0-9a-f]+$/.test(clean)) return id;
170
+ return `{${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}}`;
171
+ }
167
172
  function mapFields(fields) {
168
173
  const result = {};
169
174
  for (const field of fields) {
@@ -174,13 +179,14 @@ function mapFields(fields) {
174
179
  return result;
175
180
  }
176
181
  async function queryEdge(url, headers, datasourceId, language) {
177
- log("Edge GraphQL request:", { url, datasourceId, language });
182
+ const formattedId = formatGuidForEdge(datasourceId);
183
+ log("Edge GraphQL request:", { url, datasourceId, formattedId, language });
178
184
  const res = await fetch(url, {
179
185
  method: "POST",
180
186
  headers: { "Content-Type": "application/json", ...headers },
181
187
  body: JSON.stringify({
182
188
  query: ITEM_QUERY,
183
- variables: { itemId: datasourceId, language }
189
+ variables: { itemId: formattedId, language }
184
190
  })
185
191
  });
186
192
  log("Edge GraphQL response status:", res.status);
@@ -312,9 +318,12 @@ function parseConfigJson(json, renderingId) {
312
318
  if (!contentMap || typeof contentMap !== "object" || !friendlyId) return null;
313
319
  const keys = Object.keys(contentMap);
314
320
  return {
315
- friendlyId,
316
- contentMap,
317
- defaultKey: raw.defaultKey ?? keys[0] ?? ""
321
+ config: {
322
+ friendlyId,
323
+ contentMap,
324
+ defaultKey: raw.defaultKey ?? keys[0] ?? ""
325
+ },
326
+ instanceId: raw.instanceId ?? void 0
318
327
  };
319
328
  } catch {
320
329
  warn("Failed to parse config JSON for rendering", renderingId);
@@ -388,8 +397,13 @@ async function loadPageConfigs(edgeUrl, pageItemId, language, headers = {}, site
388
397
  const normalizedRid = normalizeGuid(renderingId);
389
398
  const parsed = parseConfigJson(configJson, normalizedRid);
390
399
  if (parsed) {
391
- configs.set(normalizedRid, parsed);
392
- log("Config loader: loaded config for rendering", normalizedRid, "\u2192 experience", parsed.friendlyId);
400
+ configs.set(normalizedRid, parsed.config);
401
+ log("Config loader: stored under renderingId", normalizedRid, "\u2192", parsed.config.friendlyId);
402
+ if (parsed.instanceId) {
403
+ const normalizedIid = normalizeGuid(parsed.instanceId);
404
+ configs.set(normalizedIid, parsed.config);
405
+ log("Config loader: also stored under instanceId", normalizedIid);
406
+ }
393
407
  }
394
408
  }
395
409
  log("Config loader: total configs loaded:", configs.size);
@@ -736,35 +750,100 @@ function getRenderingUid(props) {
736
750
  const rendering = props.rendering;
737
751
  return rendering?.uid;
738
752
  }
739
- var INDICATOR_BORDER = {
740
- position: "relative",
741
- border: "2px dashed #6B5CE7",
742
- borderRadius: "4px"
743
- };
744
- var INDICATOR_BADGE = {
745
- position: "absolute",
746
- top: "-10px",
747
- right: "-10px",
753
+ var BAR_STYLE = {
754
+ display: "flex",
755
+ alignItems: "center",
756
+ gap: "6px",
757
+ padding: "6px 12px",
758
+ background: "linear-gradient(135deg, #6B5CE7 0%, #8B7CF7 100%)",
759
+ color: "#fff",
760
+ fontSize: "12px",
761
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
762
+ fontWeight: 500,
763
+ borderRadius: "6px 6px 0 0",
748
764
  zIndex: 9999,
765
+ flexWrap: "wrap"
766
+ };
767
+ var LABEL_STYLE = {
749
768
  display: "flex",
750
769
  alignItems: "center",
751
770
  gap: "4px",
752
- padding: "2px 8px",
771
+ marginRight: "6px",
772
+ fontSize: "11px",
773
+ opacity: 0.85,
774
+ whiteSpace: "nowrap"
775
+ };
776
+ var TAB_BASE = {
777
+ padding: "3px 10px",
753
778
  fontSize: "11px",
754
779
  fontWeight: 600,
755
- lineHeight: "16px",
756
- color: "#fff",
757
- backgroundColor: "#6B5CE7",
758
- borderRadius: "10px",
780
+ border: "1px solid rgba(255,255,255,0.4)",
781
+ borderRadius: "12px",
782
+ cursor: "pointer",
783
+ transition: "all 0.15s ease",
759
784
  whiteSpace: "nowrap",
760
- pointerEvents: "none"
785
+ lineHeight: "16px"
786
+ };
787
+ var TAB_ACTIVE = {
788
+ ...TAB_BASE,
789
+ background: "#fff",
790
+ color: "#6B5CE7",
791
+ border: "1px solid #fff"
792
+ };
793
+ var TAB_INACTIVE = {
794
+ ...TAB_BASE,
795
+ background: "rgba(255,255,255,0.15)",
796
+ color: "#fff"
761
797
  };
798
+ var LOADING_STYLE = {
799
+ marginLeft: "auto",
800
+ fontSize: "10px",
801
+ opacity: 0.7
802
+ };
803
+ function PreviewBar({
804
+ config,
805
+ activeKey,
806
+ isLoading,
807
+ onSelect
808
+ }) {
809
+ const keys = Object.keys(config.contentMap);
810
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: BAR_STYLE, "data-personalize-connect-bar": config.friendlyId, children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: LABEL_STYLE, children: [
812
+ "\u26A1 ",
813
+ config.friendlyId
814
+ ] }),
815
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
816
+ "button",
817
+ {
818
+ type: "button",
819
+ style: activeKey === null ? TAB_ACTIVE : TAB_INACTIVE,
820
+ onClick: () => onSelect(null),
821
+ children: "Original"
822
+ }
823
+ ),
824
+ keys.map((key) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
825
+ "button",
826
+ {
827
+ type: "button",
828
+ style: activeKey === key ? TAB_ACTIVE : TAB_INACTIVE,
829
+ onClick: () => onSelect(key),
830
+ children: key
831
+ },
832
+ key
833
+ )),
834
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: LOADING_STYLE, children: "loading..." })
835
+ ] });
836
+ }
762
837
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
763
838
  const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
764
839
  function PersonalizeConnectWrapper(props) {
765
840
  const context = usePersonalizeContext();
766
841
  const [resolvedFields, setResolvedFields] = (0, import_react2.useState)(null);
842
+ const [previewKey, setPreviewKey] = (0, import_react2.useState)(null);
843
+ const [previewFields, setPreviewFields] = (0, import_react2.useState)(null);
844
+ const [previewLoading, setPreviewLoading] = (0, import_react2.useState)(false);
767
845
  const mountedRef = (0, import_react2.useRef)(true);
846
+ const previewCacheRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
768
847
  let config = getConfig(props);
769
848
  if (!config && context) {
770
849
  const uid = getRenderingUid(props);
@@ -793,8 +872,43 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
793
872
  if (config) {
794
873
  log(`[${componentName}] Config active:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
795
874
  }
796
- const runPersonalization = (0, import_react2.useCallback)(async () => {
875
+ const isEditing = context?.isEditing ?? false;
876
+ const handlePreviewSelect = (0, import_react2.useCallback)(async (key) => {
797
877
  if (!config || !context) return;
878
+ setPreviewKey(key);
879
+ if (key === null) {
880
+ setPreviewFields(null);
881
+ return;
882
+ }
883
+ const cached = previewCacheRef.current.get(key);
884
+ if (cached) {
885
+ setPreviewFields(cached);
886
+ return;
887
+ }
888
+ const datasourceId = config.contentMap[key];
889
+ if (!datasourceId) {
890
+ warn(`[${componentName}] Preview: no datasource for key "${key}"`);
891
+ setPreviewFields(null);
892
+ return;
893
+ }
894
+ setPreviewLoading(true);
895
+ log(`[${componentName}] Preview: resolving datasource for key "${key}" \u2192`, datasourceId);
896
+ try {
897
+ const fields = await context.resolveDatasource(datasourceId);
898
+ if (mountedRef.current) {
899
+ previewCacheRef.current.set(key, fields);
900
+ setPreviewFields(fields);
901
+ log(`[${componentName}] Preview: resolved fields for "${key}":`, Object.keys(fields));
902
+ }
903
+ } catch (e) {
904
+ warn(`[${componentName}] Preview: failed to resolve datasource for "${key}"`, e);
905
+ if (mountedRef.current) setPreviewFields(null);
906
+ } finally {
907
+ if (mountedRef.current) setPreviewLoading(false);
908
+ }
909
+ }, [config, context]);
910
+ const runPersonalization = (0, import_react2.useCallback)(async () => {
911
+ if (!config || !context || isEditing) return;
798
912
  group(`[${componentName}] personalization flow`);
799
913
  log("Calling Personalize for experience:", config.friendlyId);
800
914
  const contentKey = await callPersonalize({ config, context });
@@ -822,29 +936,37 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
822
936
  warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
823
937
  }
824
938
  groupEnd();
825
- }, [config, context]);
939
+ }, [config, context, isEditing]);
826
940
  (0, import_react2.useEffect)(() => {
827
941
  mountedRef.current = true;
828
- if (config && context && context.browserId) {
942
+ if (config && context && context.browserId && !isEditing) {
829
943
  runPersonalization();
830
944
  }
831
945
  return () => {
832
946
  mountedRef.current = false;
833
947
  };
834
- }, [config?.friendlyId, context?.browserId, context?.configsLoaded, runPersonalization]);
948
+ }, [config?.friendlyId, context?.browserId, context?.configsLoaded, isEditing, runPersonalization]);
835
949
  if (!config || !context) {
836
950
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...props });
837
951
  }
838
- const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
839
- const component = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
840
- if (context.isEditing) {
841
- log(`[${componentName}] Editing mode \u2014 rendering indicator badge for ${config.friendlyId}`);
842
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
843
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
844
- component
952
+ if (isEditing) {
953
+ const editingFields = previewKey !== null ? previewFields : null;
954
+ const editingProps = editingFields ? { ...props, fields: editingFields } : props;
955
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react2.Fragment, { children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
957
+ PreviewBar,
958
+ {
959
+ config,
960
+ activeKey: previewKey,
961
+ isLoading: previewLoading,
962
+ onSelect: handlePreviewSelect
963
+ }
964
+ ),
965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...editingProps })
845
966
  ] });
846
967
  }
847
- return component;
968
+ const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
969
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
848
970
  }
849
971
  PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
850
972
  return PersonalizeConnectWrapper;
package/dist/index.mjs CHANGED
@@ -123,6 +123,11 @@ var ITEM_QUERY = `
123
123
  }
124
124
  }
125
125
  `;
126
+ function formatGuidForEdge(id) {
127
+ const clean = id.replace(/[{}\-\s]/g, "").toLowerCase();
128
+ if (clean.length !== 32 || !/^[0-9a-f]+$/.test(clean)) return id;
129
+ return `{${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}}`;
130
+ }
126
131
  function mapFields(fields) {
127
132
  const result = {};
128
133
  for (const field of fields) {
@@ -133,13 +138,14 @@ function mapFields(fields) {
133
138
  return result;
134
139
  }
135
140
  async function queryEdge(url, headers, datasourceId, language) {
136
- log("Edge GraphQL request:", { url, datasourceId, language });
141
+ const formattedId = formatGuidForEdge(datasourceId);
142
+ log("Edge GraphQL request:", { url, datasourceId, formattedId, language });
137
143
  const res = await fetch(url, {
138
144
  method: "POST",
139
145
  headers: { "Content-Type": "application/json", ...headers },
140
146
  body: JSON.stringify({
141
147
  query: ITEM_QUERY,
142
- variables: { itemId: datasourceId, language }
148
+ variables: { itemId: formattedId, language }
143
149
  })
144
150
  });
145
151
  log("Edge GraphQL response status:", res.status);
@@ -271,9 +277,12 @@ function parseConfigJson(json, renderingId) {
271
277
  if (!contentMap || typeof contentMap !== "object" || !friendlyId) return null;
272
278
  const keys = Object.keys(contentMap);
273
279
  return {
274
- friendlyId,
275
- contentMap,
276
- defaultKey: raw.defaultKey ?? keys[0] ?? ""
280
+ config: {
281
+ friendlyId,
282
+ contentMap,
283
+ defaultKey: raw.defaultKey ?? keys[0] ?? ""
284
+ },
285
+ instanceId: raw.instanceId ?? void 0
277
286
  };
278
287
  } catch {
279
288
  warn("Failed to parse config JSON for rendering", renderingId);
@@ -347,8 +356,13 @@ async function loadPageConfigs(edgeUrl, pageItemId, language, headers = {}, site
347
356
  const normalizedRid = normalizeGuid(renderingId);
348
357
  const parsed = parseConfigJson(configJson, normalizedRid);
349
358
  if (parsed) {
350
- configs.set(normalizedRid, parsed);
351
- log("Config loader: loaded config for rendering", normalizedRid, "\u2192 experience", parsed.friendlyId);
359
+ configs.set(normalizedRid, parsed.config);
360
+ log("Config loader: stored under renderingId", normalizedRid, "\u2192", parsed.config.friendlyId);
361
+ if (parsed.instanceId) {
362
+ const normalizedIid = normalizeGuid(parsed.instanceId);
363
+ configs.set(normalizedIid, parsed.config);
364
+ log("Config loader: also stored under instanceId", normalizedIid);
365
+ }
352
366
  }
353
367
  }
354
368
  log("Config loader: total configs loaded:", configs.size);
@@ -685,7 +699,7 @@ async function resolveContent(options) {
685
699
  }
686
700
 
687
701
  // src/withPersonalizeConnect.tsx
688
- import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
702
+ import { Fragment, useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
689
703
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
690
704
  var DEFAULT_GET_CONFIG = (props) => props.rendering?.personalizeConnect;
691
705
  function normalizeGuid2(id) {
@@ -695,35 +709,100 @@ function getRenderingUid(props) {
695
709
  const rendering = props.rendering;
696
710
  return rendering?.uid;
697
711
  }
698
- var INDICATOR_BORDER = {
699
- position: "relative",
700
- border: "2px dashed #6B5CE7",
701
- borderRadius: "4px"
702
- };
703
- var INDICATOR_BADGE = {
704
- position: "absolute",
705
- top: "-10px",
706
- right: "-10px",
712
+ var BAR_STYLE = {
713
+ display: "flex",
714
+ alignItems: "center",
715
+ gap: "6px",
716
+ padding: "6px 12px",
717
+ background: "linear-gradient(135deg, #6B5CE7 0%, #8B7CF7 100%)",
718
+ color: "#fff",
719
+ fontSize: "12px",
720
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
721
+ fontWeight: 500,
722
+ borderRadius: "6px 6px 0 0",
707
723
  zIndex: 9999,
724
+ flexWrap: "wrap"
725
+ };
726
+ var LABEL_STYLE = {
708
727
  display: "flex",
709
728
  alignItems: "center",
710
729
  gap: "4px",
711
- padding: "2px 8px",
730
+ marginRight: "6px",
731
+ fontSize: "11px",
732
+ opacity: 0.85,
733
+ whiteSpace: "nowrap"
734
+ };
735
+ var TAB_BASE = {
736
+ padding: "3px 10px",
712
737
  fontSize: "11px",
713
738
  fontWeight: 600,
714
- lineHeight: "16px",
715
- color: "#fff",
716
- backgroundColor: "#6B5CE7",
717
- borderRadius: "10px",
739
+ border: "1px solid rgba(255,255,255,0.4)",
740
+ borderRadius: "12px",
741
+ cursor: "pointer",
742
+ transition: "all 0.15s ease",
718
743
  whiteSpace: "nowrap",
719
- pointerEvents: "none"
744
+ lineHeight: "16px"
745
+ };
746
+ var TAB_ACTIVE = {
747
+ ...TAB_BASE,
748
+ background: "#fff",
749
+ color: "#6B5CE7",
750
+ border: "1px solid #fff"
751
+ };
752
+ var TAB_INACTIVE = {
753
+ ...TAB_BASE,
754
+ background: "rgba(255,255,255,0.15)",
755
+ color: "#fff"
720
756
  };
757
+ var LOADING_STYLE = {
758
+ marginLeft: "auto",
759
+ fontSize: "10px",
760
+ opacity: 0.7
761
+ };
762
+ function PreviewBar({
763
+ config,
764
+ activeKey,
765
+ isLoading,
766
+ onSelect
767
+ }) {
768
+ const keys = Object.keys(config.contentMap);
769
+ return /* @__PURE__ */ jsxs("div", { style: BAR_STYLE, "data-personalize-connect-bar": config.friendlyId, children: [
770
+ /* @__PURE__ */ jsxs("span", { style: LABEL_STYLE, children: [
771
+ "\u26A1 ",
772
+ config.friendlyId
773
+ ] }),
774
+ /* @__PURE__ */ jsx2(
775
+ "button",
776
+ {
777
+ type: "button",
778
+ style: activeKey === null ? TAB_ACTIVE : TAB_INACTIVE,
779
+ onClick: () => onSelect(null),
780
+ children: "Original"
781
+ }
782
+ ),
783
+ keys.map((key) => /* @__PURE__ */ jsx2(
784
+ "button",
785
+ {
786
+ type: "button",
787
+ style: activeKey === key ? TAB_ACTIVE : TAB_INACTIVE,
788
+ onClick: () => onSelect(key),
789
+ children: key
790
+ },
791
+ key
792
+ )),
793
+ isLoading && /* @__PURE__ */ jsx2("span", { style: LOADING_STYLE, children: "loading..." })
794
+ ] });
795
+ }
721
796
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
722
797
  const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
723
798
  function PersonalizeConnectWrapper(props) {
724
799
  const context = usePersonalizeContext();
725
800
  const [resolvedFields, setResolvedFields] = useState2(null);
801
+ const [previewKey, setPreviewKey] = useState2(null);
802
+ const [previewFields, setPreviewFields] = useState2(null);
803
+ const [previewLoading, setPreviewLoading] = useState2(false);
726
804
  const mountedRef = useRef(true);
805
+ const previewCacheRef = useRef(/* @__PURE__ */ new Map());
727
806
  let config = getConfig(props);
728
807
  if (!config && context) {
729
808
  const uid = getRenderingUid(props);
@@ -752,8 +831,43 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
752
831
  if (config) {
753
832
  log(`[${componentName}] Config active:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
754
833
  }
755
- const runPersonalization = useCallback2(async () => {
834
+ const isEditing = context?.isEditing ?? false;
835
+ const handlePreviewSelect = useCallback2(async (key) => {
756
836
  if (!config || !context) return;
837
+ setPreviewKey(key);
838
+ if (key === null) {
839
+ setPreviewFields(null);
840
+ return;
841
+ }
842
+ const cached = previewCacheRef.current.get(key);
843
+ if (cached) {
844
+ setPreviewFields(cached);
845
+ return;
846
+ }
847
+ const datasourceId = config.contentMap[key];
848
+ if (!datasourceId) {
849
+ warn(`[${componentName}] Preview: no datasource for key "${key}"`);
850
+ setPreviewFields(null);
851
+ return;
852
+ }
853
+ setPreviewLoading(true);
854
+ log(`[${componentName}] Preview: resolving datasource for key "${key}" \u2192`, datasourceId);
855
+ try {
856
+ const fields = await context.resolveDatasource(datasourceId);
857
+ if (mountedRef.current) {
858
+ previewCacheRef.current.set(key, fields);
859
+ setPreviewFields(fields);
860
+ log(`[${componentName}] Preview: resolved fields for "${key}":`, Object.keys(fields));
861
+ }
862
+ } catch (e) {
863
+ warn(`[${componentName}] Preview: failed to resolve datasource for "${key}"`, e);
864
+ if (mountedRef.current) setPreviewFields(null);
865
+ } finally {
866
+ if (mountedRef.current) setPreviewLoading(false);
867
+ }
868
+ }, [config, context]);
869
+ const runPersonalization = useCallback2(async () => {
870
+ if (!config || !context || isEditing) return;
757
871
  group(`[${componentName}] personalization flow`);
758
872
  log("Calling Personalize for experience:", config.friendlyId);
759
873
  const contentKey = await callPersonalize({ config, context });
@@ -781,29 +895,37 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
781
895
  warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
782
896
  }
783
897
  groupEnd();
784
- }, [config, context]);
898
+ }, [config, context, isEditing]);
785
899
  useEffect2(() => {
786
900
  mountedRef.current = true;
787
- if (config && context && context.browserId) {
901
+ if (config && context && context.browserId && !isEditing) {
788
902
  runPersonalization();
789
903
  }
790
904
  return () => {
791
905
  mountedRef.current = false;
792
906
  };
793
- }, [config?.friendlyId, context?.browserId, context?.configsLoaded, runPersonalization]);
907
+ }, [config?.friendlyId, context?.browserId, context?.configsLoaded, isEditing, runPersonalization]);
794
908
  if (!config || !context) {
795
909
  return /* @__PURE__ */ jsx2(WrappedComponent, { ...props });
796
910
  }
797
- const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
798
- const component = /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
799
- if (context.isEditing) {
800
- log(`[${componentName}] Editing mode \u2014 rendering indicator badge for ${config.friendlyId}`);
801
- return /* @__PURE__ */ jsxs("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
802
- /* @__PURE__ */ jsx2("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
803
- component
911
+ if (isEditing) {
912
+ const editingFields = previewKey !== null ? previewFields : null;
913
+ const editingProps = editingFields ? { ...props, fields: editingFields } : props;
914
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
915
+ /* @__PURE__ */ jsx2(
916
+ PreviewBar,
917
+ {
918
+ config,
919
+ activeKey: previewKey,
920
+ isLoading: previewLoading,
921
+ onSelect: handlePreviewSelect
922
+ }
923
+ ),
924
+ /* @__PURE__ */ jsx2(WrappedComponent, { ...editingProps })
804
925
  ] });
805
926
  }
806
- return component;
927
+ const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
928
+ return /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
807
929
  }
808
930
  PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
809
931
  return PersonalizeConnectWrapper;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "personalize-connect-sdk",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Runtime SDK for Personalize Connect - resolves active experience outcomes and datasources in your rendering host",
5
5
  "author": "Dylan Young",
6
6
  "keywords": [