personalize-connect-sdk 1.3.3 → 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
@@ -750,35 +750,100 @@ function getRenderingUid(props) {
750
750
  const rendering = props.rendering;
751
751
  return rendering?.uid;
752
752
  }
753
- var INDICATOR_BORDER = {
754
- position: "relative",
755
- border: "2px dashed #6B5CE7",
756
- borderRadius: "4px"
757
- };
758
- var INDICATOR_BADGE = {
759
- position: "absolute",
760
- top: "-10px",
761
- 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",
762
764
  zIndex: 9999,
765
+ flexWrap: "wrap"
766
+ };
767
+ var LABEL_STYLE = {
763
768
  display: "flex",
764
769
  alignItems: "center",
765
770
  gap: "4px",
766
- 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",
767
778
  fontSize: "11px",
768
779
  fontWeight: 600,
769
- lineHeight: "16px",
770
- color: "#fff",
771
- backgroundColor: "#6B5CE7",
772
- borderRadius: "10px",
780
+ border: "1px solid rgba(255,255,255,0.4)",
781
+ borderRadius: "12px",
782
+ cursor: "pointer",
783
+ transition: "all 0.15s ease",
773
784
  whiteSpace: "nowrap",
774
- pointerEvents: "none"
785
+ lineHeight: "16px"
775
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"
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
+ }
776
837
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
777
838
  const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
778
839
  function PersonalizeConnectWrapper(props) {
779
840
  const context = usePersonalizeContext();
780
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);
781
845
  const mountedRef = (0, import_react2.useRef)(true);
846
+ const previewCacheRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
782
847
  let config = getConfig(props);
783
848
  if (!config && context) {
784
849
  const uid = getRenderingUid(props);
@@ -807,8 +872,43 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
807
872
  if (config) {
808
873
  log(`[${componentName}] Config active:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
809
874
  }
810
- const runPersonalization = (0, import_react2.useCallback)(async () => {
875
+ const isEditing = context?.isEditing ?? false;
876
+ const handlePreviewSelect = (0, import_react2.useCallback)(async (key) => {
811
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;
812
912
  group(`[${componentName}] personalization flow`);
813
913
  log("Calling Personalize for experience:", config.friendlyId);
814
914
  const contentKey = await callPersonalize({ config, context });
@@ -836,29 +936,37 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
836
936
  warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
837
937
  }
838
938
  groupEnd();
839
- }, [config, context]);
939
+ }, [config, context, isEditing]);
840
940
  (0, import_react2.useEffect)(() => {
841
941
  mountedRef.current = true;
842
- if (config && context && context.browserId) {
942
+ if (config && context && context.browserId && !isEditing) {
843
943
  runPersonalization();
844
944
  }
845
945
  return () => {
846
946
  mountedRef.current = false;
847
947
  };
848
- }, [config?.friendlyId, context?.browserId, context?.configsLoaded, runPersonalization]);
948
+ }, [config?.friendlyId, context?.browserId, context?.configsLoaded, isEditing, runPersonalization]);
849
949
  if (!config || !context) {
850
950
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...props });
851
951
  }
852
- const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
853
- const component = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
854
- if (context.isEditing) {
855
- log(`[${componentName}] Editing mode \u2014 rendering indicator badge for ${config.friendlyId}`);
856
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
857
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
858
- 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 })
859
966
  ] });
860
967
  }
861
- return component;
968
+ const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
969
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(WrappedComponent, { ...mergedProps });
862
970
  }
863
971
  PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
864
972
  return PersonalizeConnectWrapper;
package/dist/index.mjs CHANGED
@@ -699,7 +699,7 @@ async function resolveContent(options) {
699
699
  }
700
700
 
701
701
  // src/withPersonalizeConnect.tsx
702
- 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";
703
703
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
704
704
  var DEFAULT_GET_CONFIG = (props) => props.rendering?.personalizeConnect;
705
705
  function normalizeGuid2(id) {
@@ -709,35 +709,100 @@ function getRenderingUid(props) {
709
709
  const rendering = props.rendering;
710
710
  return rendering?.uid;
711
711
  }
712
- var INDICATOR_BORDER = {
713
- position: "relative",
714
- border: "2px dashed #6B5CE7",
715
- borderRadius: "4px"
716
- };
717
- var INDICATOR_BADGE = {
718
- position: "absolute",
719
- top: "-10px",
720
- 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",
721
723
  zIndex: 9999,
724
+ flexWrap: "wrap"
725
+ };
726
+ var LABEL_STYLE = {
722
727
  display: "flex",
723
728
  alignItems: "center",
724
729
  gap: "4px",
725
- 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",
726
737
  fontSize: "11px",
727
738
  fontWeight: 600,
728
- lineHeight: "16px",
729
- color: "#fff",
730
- backgroundColor: "#6B5CE7",
731
- borderRadius: "10px",
739
+ border: "1px solid rgba(255,255,255,0.4)",
740
+ borderRadius: "12px",
741
+ cursor: "pointer",
742
+ transition: "all 0.15s ease",
732
743
  whiteSpace: "nowrap",
733
- pointerEvents: "none"
744
+ lineHeight: "16px"
734
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"
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
+ }
735
796
  function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG) {
736
797
  const componentName = WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
737
798
  function PersonalizeConnectWrapper(props) {
738
799
  const context = usePersonalizeContext();
739
800
  const [resolvedFields, setResolvedFields] = useState2(null);
801
+ const [previewKey, setPreviewKey] = useState2(null);
802
+ const [previewFields, setPreviewFields] = useState2(null);
803
+ const [previewLoading, setPreviewLoading] = useState2(false);
740
804
  const mountedRef = useRef(true);
805
+ const previewCacheRef = useRef(/* @__PURE__ */ new Map());
741
806
  let config = getConfig(props);
742
807
  if (!config && context) {
743
808
  const uid = getRenderingUid(props);
@@ -766,8 +831,43 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
766
831
  if (config) {
767
832
  log(`[${componentName}] Config active:`, { friendlyId: config.friendlyId, defaultKey: config.defaultKey, keys: Object.keys(config.contentMap) });
768
833
  }
769
- const runPersonalization = useCallback2(async () => {
834
+ const isEditing = context?.isEditing ?? false;
835
+ const handlePreviewSelect = useCallback2(async (key) => {
770
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;
771
871
  group(`[${componentName}] personalization flow`);
772
872
  log("Calling Personalize for experience:", config.friendlyId);
773
873
  const contentKey = await callPersonalize({ config, context });
@@ -795,29 +895,37 @@ function withPersonalizeConnect(WrappedComponent, getConfig = DEFAULT_GET_CONFIG
795
895
  warn(`[${componentName}] Content resolution returned null \u2014 component keeps original fields`);
796
896
  }
797
897
  groupEnd();
798
- }, [config, context]);
898
+ }, [config, context, isEditing]);
799
899
  useEffect2(() => {
800
900
  mountedRef.current = true;
801
- if (config && context && context.browserId) {
901
+ if (config && context && context.browserId && !isEditing) {
802
902
  runPersonalization();
803
903
  }
804
904
  return () => {
805
905
  mountedRef.current = false;
806
906
  };
807
- }, [config?.friendlyId, context?.browserId, context?.configsLoaded, runPersonalization]);
907
+ }, [config?.friendlyId, context?.browserId, context?.configsLoaded, isEditing, runPersonalization]);
808
908
  if (!config || !context) {
809
909
  return /* @__PURE__ */ jsx2(WrappedComponent, { ...props });
810
910
  }
811
- const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
812
- const component = /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
813
- if (context.isEditing) {
814
- log(`[${componentName}] Editing mode \u2014 rendering indicator badge for ${config.friendlyId}`);
815
- return /* @__PURE__ */ jsxs("div", { style: INDICATOR_BORDER, "data-personalize-connect": config.friendlyId, children: [
816
- /* @__PURE__ */ jsx2("span", { style: INDICATOR_BADGE, title: `Personalize: ${config.friendlyId}`, children: "\u26A1 Personalized" }),
817
- 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 })
818
925
  ] });
819
926
  }
820
- return component;
927
+ const mergedProps = resolvedFields ? { ...props, fields: resolvedFields } : props;
928
+ return /* @__PURE__ */ jsx2(WrappedComponent, { ...mergedProps });
821
929
  }
822
930
  PersonalizeConnectWrapper.displayName = `WithPersonalizeConnect(${componentName})`;
823
931
  return PersonalizeConnectWrapper;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "personalize-connect-sdk",
3
- "version": "1.3.3",
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": [