@amplifyup/sdk 0.1.23 → 0.1.25

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/react.d.mts CHANGED
@@ -88,6 +88,8 @@ declare function Slot({ name, className, }: {
88
88
  * where field values should appear in their component, with full control
89
89
  * over styling and formatting.
90
90
  *
91
+ * For markdown / rich text fields, use {@link RichText} instead.
92
+ *
91
93
  * @example
92
94
  * ```tsx
93
95
  * function Hero({ title, description }) {
@@ -110,6 +112,23 @@ declare function Field({ name, fallback, className, render, }: {
110
112
  className?: string;
111
113
  render?: (value: any) => ReactNode;
112
114
  }): react_jsx_runtime.JSX.Element | null;
115
+ /**
116
+ * RichText - Renders a markdown (or HTML string) field as formatted HTML.
117
+ *
118
+ * Use for config/data fields of kind `richtext`. Values are stored as Markdown
119
+ * across Composer for portability across CMS platforms.
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * <RichText name="body" className="prose" />
124
+ * ```
125
+ */
126
+ declare function RichText({ name, fallback, className, as: Tag, }: {
127
+ name: string;
128
+ fallback?: ReactNode;
129
+ className?: string;
130
+ as?: keyof JSX.IntrinsicElements;
131
+ }): react_jsx_runtime.JSX.Element | null;
113
132
 
114
133
  interface AmplifyUpContextType {
115
134
  pageConfig: PageConfig | null;
@@ -125,4 +144,4 @@ interface AmplifyUpProviderProps {
125
144
  declare function AmplifyUpProvider({ children, config }: AmplifyUpProviderProps): react_jsx_runtime.JSX.Element;
126
145
  declare function useAmplifyUp(): AmplifyUpContextType;
127
146
 
128
- export { AmplifyContentMeta, AmplifyPageContent, AmplifyUpProvider, ComponentContextProvider, ContentSource, Field, PageConfig, Slot, useAmplifyUp };
147
+ export { AmplifyContentMeta, AmplifyPageContent, AmplifyUpProvider, ComponentContextProvider, ContentSource, Field, PageConfig, RichText, Slot, useAmplifyUp };
package/dist/react.d.ts CHANGED
@@ -88,6 +88,8 @@ declare function Slot({ name, className, }: {
88
88
  * where field values should appear in their component, with full control
89
89
  * over styling and formatting.
90
90
  *
91
+ * For markdown / rich text fields, use {@link RichText} instead.
92
+ *
91
93
  * @example
92
94
  * ```tsx
93
95
  * function Hero({ title, description }) {
@@ -110,6 +112,23 @@ declare function Field({ name, fallback, className, render, }: {
110
112
  className?: string;
111
113
  render?: (value: any) => ReactNode;
112
114
  }): react_jsx_runtime.JSX.Element | null;
115
+ /**
116
+ * RichText - Renders a markdown (or HTML string) field as formatted HTML.
117
+ *
118
+ * Use for config/data fields of kind `richtext`. Values are stored as Markdown
119
+ * across Composer for portability across CMS platforms.
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * <RichText name="body" className="prose" />
124
+ * ```
125
+ */
126
+ declare function RichText({ name, fallback, className, as: Tag, }: {
127
+ name: string;
128
+ fallback?: ReactNode;
129
+ className?: string;
130
+ as?: keyof JSX.IntrinsicElements;
131
+ }): react_jsx_runtime.JSX.Element | null;
113
132
 
114
133
  interface AmplifyUpContextType {
115
134
  pageConfig: PageConfig | null;
@@ -125,4 +144,4 @@ interface AmplifyUpProviderProps {
125
144
  declare function AmplifyUpProvider({ children, config }: AmplifyUpProviderProps): react_jsx_runtime.JSX.Element;
126
145
  declare function useAmplifyUp(): AmplifyUpContextType;
127
146
 
128
- export { AmplifyContentMeta, AmplifyPageContent, AmplifyUpProvider, ComponentContextProvider, ContentSource, Field, PageConfig, Slot, useAmplifyUp };
147
+ export { AmplifyContentMeta, AmplifyPageContent, AmplifyUpProvider, ComponentContextProvider, ContentSource, Field, PageConfig, RichText, Slot, useAmplifyUp };
package/dist/react.js CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var marked = require('marked');
6
7
 
7
8
  /* @amplifyup/sdk - https://amplifyup.ai */
8
9
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -518,6 +519,10 @@ async function fetchContent(options) {
518
519
 
519
520
  // src/index.ts
520
521
  var SESSION_IDLE_GAP_MS = 30 * 60 * 1e3;
522
+ var DWELL_MILESTONES_SEC = [15, 30, 60, 120, 300];
523
+ var DWELL_CHECKPOINT_SEC = 15 * 60;
524
+ var DWELL_ACTIVITY_IDLE_MS = 2 * 60 * 1e3;
525
+ var DWELL_POLL_MS = 5e3;
521
526
  function readPreviewQueryOverrides() {
522
527
  if (typeof window === "undefined") return {};
523
528
  const params = new URLSearchParams(window.location.search);
@@ -588,7 +593,10 @@ var AmplifyUpSDK = class {
588
593
  visibleSince: null,
589
594
  lastDwellSecondsSent: 0,
590
595
  /** When the tab last became hidden — used for idle-resume re-page. */
591
- hiddenAt: null
596
+ hiddenAt: null,
597
+ /** Last user interaction — freezes dwell when stale while visible. */
598
+ lastActivityAt: 0,
599
+ activityIdle: false
592
600
  };
593
601
  this.adapter = adapter || new AWSAdapter();
594
602
  if (typeof window !== "undefined") {
@@ -811,8 +819,10 @@ var AmplifyUpSDK = class {
811
819
  return window.location.pathname || "/";
812
820
  }
813
821
  /**
814
- * Automatic page-level Scroll Depth + Time on Page (dwell heartbeat).
815
- * State lives on the instance so SPA route changes can reset milestones.
822
+ * Automatic page-level Scroll Depth + Time on Page.
823
+ * Dwell uses milestones / sparse checkpoints (not a fixed 15s heartbeat),
824
+ * freezes while the tab is visible but the user is idle, and always flushes
825
+ * on hide / unload / SPA route change.
816
826
  */
817
827
  setupPageEngagement() {
818
828
  if (typeof window === "undefined" || typeof document === "undefined") return;
@@ -825,6 +835,8 @@ var AmplifyUpSDK = class {
825
835
  state.visibleSince = document.visibilityState === "visible" ? Date.now() : null;
826
836
  state.lastDwellSecondsSent = 0;
827
837
  state.hiddenAt = document.visibilityState === "hidden" ? Date.now() : null;
838
+ state.lastActivityAt = Date.now();
839
+ state.activityIdle = false;
828
840
  let scrollRaf = null;
829
841
  const path = () => this.currentPath();
830
842
  const measureScrollPercent = () => {
@@ -856,6 +868,7 @@ var AmplifyUpSDK = class {
856
868
  }
857
869
  };
858
870
  const onScroll = () => {
871
+ noteActivity();
859
872
  if (scrollRaf != null) return;
860
873
  scrollRaf = window.requestAnimationFrame(() => {
861
874
  scrollRaf = null;
@@ -867,19 +880,58 @@ var AmplifyUpSDK = class {
867
880
  state.visibleMs += Date.now() - state.visibleSince;
868
881
  state.visibleSince = Date.now();
869
882
  };
870
- const emitDwell = (_force = false) => {
871
- if (this.isPreviewMode()) return;
883
+ const freezeActivityIdle = () => {
884
+ if (state.activityIdle) return;
872
885
  accumulateVisible();
886
+ state.visibleSince = null;
887
+ state.activityIdle = true;
888
+ };
889
+ const noteActivity = () => {
890
+ state.lastActivityAt = Date.now();
891
+ if (state.activityIdle) {
892
+ state.activityIdle = false;
893
+ if (document.visibilityState === "visible" && state.visibleSince == null) {
894
+ state.visibleSince = Date.now();
895
+ }
896
+ }
897
+ };
898
+ const nextEmitTarget = (lastSent) => {
899
+ for (const m of DWELL_MILESTONES_SEC) {
900
+ if (m > lastSent) return { seconds: m, reason: "milestone" };
901
+ }
902
+ const nextCheckpoint = Math.floor(lastSent / DWELL_CHECKPOINT_SEC) * DWELL_CHECKPOINT_SEC + DWELL_CHECKPOINT_SEC;
903
+ const target = nextCheckpoint > lastSent ? nextCheckpoint : lastSent + DWELL_CHECKPOINT_SEC;
904
+ return { seconds: target, reason: "checkpoint" };
905
+ };
906
+ const emitDwell = (reason, force = false) => {
907
+ if (this.isPreviewMode()) return;
908
+ if (!state.activityIdle) {
909
+ accumulateVisible();
910
+ }
873
911
  const seconds = Math.floor(state.visibleMs / 1e3);
874
912
  if (seconds <= 0) return;
875
913
  if (seconds <= state.lastDwellSecondsSent) return;
914
+ if (!force) {
915
+ const target = nextEmitTarget(state.lastDwellSecondsSent);
916
+ if (!target || seconds < target.seconds) return;
917
+ reason = target.reason;
918
+ }
876
919
  state.lastDwellSecondsSent = seconds;
877
920
  this.track("Time on Page", {
878
921
  seconds,
879
- path: this.lastTrackedPath || path()
922
+ path: this.lastTrackedPath || path(),
923
+ reason
880
924
  });
881
925
  };
882
- this._emitPageDwell = emitDwell;
926
+ const evaluateDwell = () => {
927
+ if (document.visibilityState !== "visible") return;
928
+ if (Date.now() - state.lastActivityAt >= DWELL_ACTIVITY_IDLE_MS) {
929
+ freezeActivityIdle();
930
+ return;
931
+ }
932
+ emitDwell("milestone", false);
933
+ };
934
+ this._emitPageDwell = (force) => emitDwell("route", force !== false);
883
935
  const onVisibility = () => {
884
936
  if (document.visibilityState === "visible") {
885
937
  const idleMs = state.hiddenAt != null ? Date.now() - state.hiddenAt : 0;
@@ -897,35 +949,51 @@ var AmplifyUpSDK = class {
897
949
  );
898
950
  }
899
951
  }
952
+ noteActivity();
900
953
  state.visibleSince = Date.now();
954
+ state.activityIdle = false;
901
955
  } else {
902
- emitDwell(true);
956
+ emitDwell("hidden", true);
903
957
  state.hiddenAt = Date.now();
904
958
  state.visibleSince = null;
959
+ state.activityIdle = false;
905
960
  }
906
961
  };
907
962
  const onPageHide = () => {
908
- emitDwell(true);
963
+ emitDwell("unload", true);
909
964
  if (state.hiddenAt == null) {
910
965
  state.hiddenAt = Date.now();
911
966
  }
912
967
  state.visibleSince = null;
913
968
  };
969
+ const activityEvents = [
970
+ "pointerdown",
971
+ "keydown",
972
+ "scroll",
973
+ "touchstart"
974
+ ];
975
+ const onActivity = () => noteActivity();
914
976
  window.addEventListener("scroll", onScroll, { passive: true });
915
977
  document.addEventListener("visibilitychange", onVisibility);
916
978
  window.addEventListener("pagehide", onPageHide);
979
+ for (const evt of activityEvents) {
980
+ if (evt === "scroll") continue;
981
+ window.addEventListener(evt, onActivity, { passive: true });
982
+ }
917
983
  checkScroll();
918
984
  const heartbeat = setInterval(() => {
919
- if (document.visibilityState === "visible") {
920
- emitDwell(false);
921
- }
922
- }, 15e3);
985
+ evaluateDwell();
986
+ }, DWELL_POLL_MS);
923
987
  this.trackingIntervals.push(heartbeat);
924
988
  const previousCleanup = this.trackingCleanup;
925
989
  this.trackingCleanup = () => {
926
990
  window.removeEventListener("scroll", onScroll);
927
991
  document.removeEventListener("visibilitychange", onVisibility);
928
992
  window.removeEventListener("pagehide", onPageHide);
993
+ for (const evt of activityEvents) {
994
+ if (evt === "scroll") continue;
995
+ window.removeEventListener(evt, onActivity);
996
+ }
929
997
  if (scrollRaf != null) {
930
998
  window.cancelAnimationFrame(scrollRaf);
931
999
  scrollRaf = null;
@@ -951,6 +1019,8 @@ var AmplifyUpSDK = class {
951
1019
  state.visibleMs = 0;
952
1020
  state.lastDwellSecondsSent = 0;
953
1021
  state.hiddenAt = null;
1022
+ state.lastActivityAt = Date.now();
1023
+ state.activityIdle = false;
954
1024
  state.visibleSince = typeof document !== "undefined" && document.visibilityState === "visible" ? Date.now() : null;
955
1025
  this.componentViewSeen.clear();
956
1026
  }
@@ -2661,6 +2731,53 @@ function getSDK(adapter) {
2661
2731
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
2662
2732
  var refreshEventTracking = () => getSDK().refreshEventTracking();
2663
2733
  getSDK();
2734
+ var ComponentRenderBoundary = class extends react.Component {
2735
+ constructor() {
2736
+ super(...arguments);
2737
+ this.state = { error: null };
2738
+ }
2739
+ static getDerivedStateFromError(error) {
2740
+ return { error };
2741
+ }
2742
+ componentDidCatch(error) {
2743
+ console.error(
2744
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
2745
+ error
2746
+ );
2747
+ }
2748
+ render() {
2749
+ if (this.state.error) {
2750
+ if (process.env.NODE_ENV !== "production") {
2751
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2752
+ "div",
2753
+ {
2754
+ "data-amplifyup-render-error": "",
2755
+ "data-component-id": this.props.componentId,
2756
+ "data-node-id": this.props.nodeId,
2757
+ style: {
2758
+ margin: "1rem 0",
2759
+ padding: "0.75rem 1rem",
2760
+ border: "1px solid #fca5a5",
2761
+ borderRadius: "0.5rem",
2762
+ background: "#fef2f2",
2763
+ color: "#991b1b",
2764
+ fontSize: "0.875rem"
2765
+ },
2766
+ children: [
2767
+ 'Component "',
2768
+ this.props.componentId,
2769
+ '" failed to render:',
2770
+ " ",
2771
+ this.state.error.message
2772
+ ]
2773
+ }
2774
+ );
2775
+ }
2776
+ return null;
2777
+ }
2778
+ return this.props.children;
2779
+ }
2780
+ };
2664
2781
  function AmplifyRenderer({
2665
2782
  renderComponent,
2666
2783
  pageConfig: serverPageConfig,
@@ -3017,11 +3134,20 @@ function AmplifyRenderer({
3017
3134
  const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
3018
3135
  console.log(`[AmplifyRenderer] Calling renderComponent for ${node.componentId}`, {
3019
3136
  hasSlots: !!slotsToPass,
3020
- slotKeys: slotsToPass ? Object.keys(slotsToPass) : []
3137
+ slotKeys: slotsToPass ? Object.keys(slotsToPass) : [],
3138
+ propKeys: Object.keys(node.props || {}),
3139
+ props: node.props || {}
3021
3140
  });
3022
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
3023
- layoutNodeId: node.id
3024
- }) });
3141
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: /* @__PURE__ */ jsxRuntime.jsx(
3142
+ ComponentRenderBoundary,
3143
+ {
3144
+ componentId: node.componentId,
3145
+ nodeId: node.id,
3146
+ children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
3147
+ layoutNodeId: node.id
3148
+ })
3149
+ }
3150
+ ) });
3025
3151
  };
3026
3152
  const rawTree = pageConfig.layoutTree;
3027
3153
  const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
@@ -3267,6 +3393,25 @@ function Slot({
3267
3393
  }
3268
3394
  );
3269
3395
  }
3396
+ function formatFieldValue(value) {
3397
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3398
+ return String(value);
3399
+ }
3400
+ if (Array.isArray(value)) {
3401
+ const parts = value.map((item) => {
3402
+ if (typeof item === "string") return item;
3403
+ if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
3404
+ return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
3405
+ }
3406
+ return "";
3407
+ }).filter(Boolean);
3408
+ if (parts.length > 0) return parts.join(" ");
3409
+ }
3410
+ if (value && typeof value === "object") {
3411
+ return "";
3412
+ }
3413
+ return "";
3414
+ }
3270
3415
  function Field({
3271
3416
  name,
3272
3417
  fallback,
@@ -3285,7 +3430,51 @@ function Field({
3285
3430
  if (render) {
3286
3431
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: render(fieldValue) });
3287
3432
  }
3288
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: String(fieldValue) });
3433
+ const text = formatFieldValue(fieldValue);
3434
+ if (!text) {
3435
+ return fallback ? /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: fallback }) : null;
3436
+ }
3437
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: text });
3438
+ }
3439
+ function markdownToHtml(markdown) {
3440
+ try {
3441
+ const result = marked.marked.parse(markdown, { async: false });
3442
+ return typeof result === "string" ? result : "";
3443
+ } catch {
3444
+ return markdown;
3445
+ }
3446
+ }
3447
+ function RichText({
3448
+ name,
3449
+ fallback,
3450
+ className,
3451
+ as: Tag = "div"
3452
+ }) {
3453
+ const context = react.useContext(ComponentContext);
3454
+ if (!context) {
3455
+ console.warn(`[AmplifyUp SDK] RichText "${name}" used outside ComponentContextProvider`);
3456
+ return fallback ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback }) : null;
3457
+ }
3458
+ const fieldValue = context.props?.[name];
3459
+ if (fieldValue === void 0 || fieldValue === null || fieldValue === "") {
3460
+ return fallback ? /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, children: fallback }) : null;
3461
+ }
3462
+ let html = "";
3463
+ if (typeof fieldValue === "string") {
3464
+ const trimmed = fieldValue.trim();
3465
+ if (trimmed.startsWith("<") && trimmed.includes(">")) {
3466
+ html = fieldValue;
3467
+ } else {
3468
+ html = markdownToHtml(fieldValue);
3469
+ }
3470
+ } else {
3471
+ const text = formatFieldValue(fieldValue);
3472
+ html = text ? markdownToHtml(text) : "";
3473
+ }
3474
+ if (!html) {
3475
+ return fallback ? /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, children: fallback }) : null;
3476
+ }
3477
+ return /* @__PURE__ */ jsxRuntime.jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
3289
3478
  }
3290
3479
  var usePathname = null;
3291
3480
  if (typeof window !== "undefined") {
@@ -3449,6 +3638,7 @@ exports.AmplifyRenderer = AmplifyRenderer;
3449
3638
  exports.AmplifyUpProvider = AmplifyUpProvider;
3450
3639
  exports.ComponentContextProvider = ComponentContextProvider;
3451
3640
  exports.Field = Field;
3641
+ exports.RichText = RichText;
3452
3642
  exports.Slot = Slot;
3453
3643
  exports.fetchPageConfigServer = fetchPageConfigServer;
3454
3644
  exports.isComposerPreview = isComposerPreview;