@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.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use client';
2
- import { createContext, useState, useEffect, useContext, Fragment as Fragment$1 } from 'react';
2
+ import { createContext, useState, useEffect, useContext, Fragment as Fragment$1, Component } from 'react';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
+ import { marked } from 'marked';
4
5
 
5
6
  /* @amplifyup/sdk - https://amplifyup.ai */
6
7
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -516,6 +517,10 @@ async function fetchContent(options) {
516
517
 
517
518
  // src/index.ts
518
519
  var SESSION_IDLE_GAP_MS = 30 * 60 * 1e3;
520
+ var DWELL_MILESTONES_SEC = [15, 30, 60, 120, 300];
521
+ var DWELL_CHECKPOINT_SEC = 15 * 60;
522
+ var DWELL_ACTIVITY_IDLE_MS = 2 * 60 * 1e3;
523
+ var DWELL_POLL_MS = 5e3;
519
524
  function readPreviewQueryOverrides() {
520
525
  if (typeof window === "undefined") return {};
521
526
  const params = new URLSearchParams(window.location.search);
@@ -586,7 +591,10 @@ var AmplifyUpSDK = class {
586
591
  visibleSince: null,
587
592
  lastDwellSecondsSent: 0,
588
593
  /** When the tab last became hidden — used for idle-resume re-page. */
589
- hiddenAt: null
594
+ hiddenAt: null,
595
+ /** Last user interaction — freezes dwell when stale while visible. */
596
+ lastActivityAt: 0,
597
+ activityIdle: false
590
598
  };
591
599
  this.adapter = adapter || new AWSAdapter();
592
600
  if (typeof window !== "undefined") {
@@ -809,8 +817,10 @@ var AmplifyUpSDK = class {
809
817
  return window.location.pathname || "/";
810
818
  }
811
819
  /**
812
- * Automatic page-level Scroll Depth + Time on Page (dwell heartbeat).
813
- * State lives on the instance so SPA route changes can reset milestones.
820
+ * Automatic page-level Scroll Depth + Time on Page.
821
+ * Dwell uses milestones / sparse checkpoints (not a fixed 15s heartbeat),
822
+ * freezes while the tab is visible but the user is idle, and always flushes
823
+ * on hide / unload / SPA route change.
814
824
  */
815
825
  setupPageEngagement() {
816
826
  if (typeof window === "undefined" || typeof document === "undefined") return;
@@ -823,6 +833,8 @@ var AmplifyUpSDK = class {
823
833
  state.visibleSince = document.visibilityState === "visible" ? Date.now() : null;
824
834
  state.lastDwellSecondsSent = 0;
825
835
  state.hiddenAt = document.visibilityState === "hidden" ? Date.now() : null;
836
+ state.lastActivityAt = Date.now();
837
+ state.activityIdle = false;
826
838
  let scrollRaf = null;
827
839
  const path = () => this.currentPath();
828
840
  const measureScrollPercent = () => {
@@ -854,6 +866,7 @@ var AmplifyUpSDK = class {
854
866
  }
855
867
  };
856
868
  const onScroll = () => {
869
+ noteActivity();
857
870
  if (scrollRaf != null) return;
858
871
  scrollRaf = window.requestAnimationFrame(() => {
859
872
  scrollRaf = null;
@@ -865,19 +878,58 @@ var AmplifyUpSDK = class {
865
878
  state.visibleMs += Date.now() - state.visibleSince;
866
879
  state.visibleSince = Date.now();
867
880
  };
868
- const emitDwell = (_force = false) => {
869
- if (this.isPreviewMode()) return;
881
+ const freezeActivityIdle = () => {
882
+ if (state.activityIdle) return;
870
883
  accumulateVisible();
884
+ state.visibleSince = null;
885
+ state.activityIdle = true;
886
+ };
887
+ const noteActivity = () => {
888
+ state.lastActivityAt = Date.now();
889
+ if (state.activityIdle) {
890
+ state.activityIdle = false;
891
+ if (document.visibilityState === "visible" && state.visibleSince == null) {
892
+ state.visibleSince = Date.now();
893
+ }
894
+ }
895
+ };
896
+ const nextEmitTarget = (lastSent) => {
897
+ for (const m of DWELL_MILESTONES_SEC) {
898
+ if (m > lastSent) return { seconds: m, reason: "milestone" };
899
+ }
900
+ const nextCheckpoint = Math.floor(lastSent / DWELL_CHECKPOINT_SEC) * DWELL_CHECKPOINT_SEC + DWELL_CHECKPOINT_SEC;
901
+ const target = nextCheckpoint > lastSent ? nextCheckpoint : lastSent + DWELL_CHECKPOINT_SEC;
902
+ return { seconds: target, reason: "checkpoint" };
903
+ };
904
+ const emitDwell = (reason, force = false) => {
905
+ if (this.isPreviewMode()) return;
906
+ if (!state.activityIdle) {
907
+ accumulateVisible();
908
+ }
871
909
  const seconds = Math.floor(state.visibleMs / 1e3);
872
910
  if (seconds <= 0) return;
873
911
  if (seconds <= state.lastDwellSecondsSent) return;
912
+ if (!force) {
913
+ const target = nextEmitTarget(state.lastDwellSecondsSent);
914
+ if (!target || seconds < target.seconds) return;
915
+ reason = target.reason;
916
+ }
874
917
  state.lastDwellSecondsSent = seconds;
875
918
  this.track("Time on Page", {
876
919
  seconds,
877
- path: this.lastTrackedPath || path()
920
+ path: this.lastTrackedPath || path(),
921
+ reason
878
922
  });
879
923
  };
880
- this._emitPageDwell = emitDwell;
924
+ const evaluateDwell = () => {
925
+ if (document.visibilityState !== "visible") return;
926
+ if (Date.now() - state.lastActivityAt >= DWELL_ACTIVITY_IDLE_MS) {
927
+ freezeActivityIdle();
928
+ return;
929
+ }
930
+ emitDwell("milestone", false);
931
+ };
932
+ this._emitPageDwell = (force) => emitDwell("route", force !== false);
881
933
  const onVisibility = () => {
882
934
  if (document.visibilityState === "visible") {
883
935
  const idleMs = state.hiddenAt != null ? Date.now() - state.hiddenAt : 0;
@@ -895,35 +947,51 @@ var AmplifyUpSDK = class {
895
947
  );
896
948
  }
897
949
  }
950
+ noteActivity();
898
951
  state.visibleSince = Date.now();
952
+ state.activityIdle = false;
899
953
  } else {
900
- emitDwell(true);
954
+ emitDwell("hidden", true);
901
955
  state.hiddenAt = Date.now();
902
956
  state.visibleSince = null;
957
+ state.activityIdle = false;
903
958
  }
904
959
  };
905
960
  const onPageHide = () => {
906
- emitDwell(true);
961
+ emitDwell("unload", true);
907
962
  if (state.hiddenAt == null) {
908
963
  state.hiddenAt = Date.now();
909
964
  }
910
965
  state.visibleSince = null;
911
966
  };
967
+ const activityEvents = [
968
+ "pointerdown",
969
+ "keydown",
970
+ "scroll",
971
+ "touchstart"
972
+ ];
973
+ const onActivity = () => noteActivity();
912
974
  window.addEventListener("scroll", onScroll, { passive: true });
913
975
  document.addEventListener("visibilitychange", onVisibility);
914
976
  window.addEventListener("pagehide", onPageHide);
977
+ for (const evt of activityEvents) {
978
+ if (evt === "scroll") continue;
979
+ window.addEventListener(evt, onActivity, { passive: true });
980
+ }
915
981
  checkScroll();
916
982
  const heartbeat = setInterval(() => {
917
- if (document.visibilityState === "visible") {
918
- emitDwell(false);
919
- }
920
- }, 15e3);
983
+ evaluateDwell();
984
+ }, DWELL_POLL_MS);
921
985
  this.trackingIntervals.push(heartbeat);
922
986
  const previousCleanup = this.trackingCleanup;
923
987
  this.trackingCleanup = () => {
924
988
  window.removeEventListener("scroll", onScroll);
925
989
  document.removeEventListener("visibilitychange", onVisibility);
926
990
  window.removeEventListener("pagehide", onPageHide);
991
+ for (const evt of activityEvents) {
992
+ if (evt === "scroll") continue;
993
+ window.removeEventListener(evt, onActivity);
994
+ }
927
995
  if (scrollRaf != null) {
928
996
  window.cancelAnimationFrame(scrollRaf);
929
997
  scrollRaf = null;
@@ -949,6 +1017,8 @@ var AmplifyUpSDK = class {
949
1017
  state.visibleMs = 0;
950
1018
  state.lastDwellSecondsSent = 0;
951
1019
  state.hiddenAt = null;
1020
+ state.lastActivityAt = Date.now();
1021
+ state.activityIdle = false;
952
1022
  state.visibleSince = typeof document !== "undefined" && document.visibilityState === "visible" ? Date.now() : null;
953
1023
  this.componentViewSeen.clear();
954
1024
  }
@@ -2659,6 +2729,53 @@ function getSDK(adapter) {
2659
2729
  var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
2660
2730
  var refreshEventTracking = () => getSDK().refreshEventTracking();
2661
2731
  getSDK();
2732
+ var ComponentRenderBoundary = class extends Component {
2733
+ constructor() {
2734
+ super(...arguments);
2735
+ this.state = { error: null };
2736
+ }
2737
+ static getDerivedStateFromError(error) {
2738
+ return { error };
2739
+ }
2740
+ componentDidCatch(error) {
2741
+ console.error(
2742
+ `[AmplifyRenderer] "${this.props.componentId}" (${this.props.nodeId}) threw while rendering:`,
2743
+ error
2744
+ );
2745
+ }
2746
+ render() {
2747
+ if (this.state.error) {
2748
+ if (process.env.NODE_ENV !== "production") {
2749
+ return /* @__PURE__ */ jsxs(
2750
+ "div",
2751
+ {
2752
+ "data-amplifyup-render-error": "",
2753
+ "data-component-id": this.props.componentId,
2754
+ "data-node-id": this.props.nodeId,
2755
+ style: {
2756
+ margin: "1rem 0",
2757
+ padding: "0.75rem 1rem",
2758
+ border: "1px solid #fca5a5",
2759
+ borderRadius: "0.5rem",
2760
+ background: "#fef2f2",
2761
+ color: "#991b1b",
2762
+ fontSize: "0.875rem"
2763
+ },
2764
+ children: [
2765
+ 'Component "',
2766
+ this.props.componentId,
2767
+ '" failed to render:',
2768
+ " ",
2769
+ this.state.error.message
2770
+ ]
2771
+ }
2772
+ );
2773
+ }
2774
+ return null;
2775
+ }
2776
+ return this.props.children;
2777
+ }
2778
+ };
2662
2779
  function AmplifyRenderer({
2663
2780
  renderComponent,
2664
2781
  pageConfig: serverPageConfig,
@@ -3015,11 +3132,20 @@ function AmplifyRenderer({
3015
3132
  const slotsToPass = Object.keys(slotChildren).length > 0 ? slotChildren : void 0;
3016
3133
  console.log(`[AmplifyRenderer] Calling renderComponent for ${node.componentId}`, {
3017
3134
  hasSlots: !!slotsToPass,
3018
- slotKeys: slotsToPass ? Object.keys(slotsToPass) : []
3135
+ slotKeys: slotsToPass ? Object.keys(slotsToPass) : [],
3136
+ propKeys: Object.keys(node.props || {}),
3137
+ props: node.props || {}
3019
3138
  });
3020
- return /* @__PURE__ */ jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
3021
- layoutNodeId: node.id
3022
- }) });
3139
+ return /* @__PURE__ */ jsx("div", { "data-node-id": node.id, "data-amplifyup-node": "", children: /* @__PURE__ */ jsx(
3140
+ ComponentRenderBoundary,
3141
+ {
3142
+ componentId: node.componentId,
3143
+ nodeId: node.id,
3144
+ children: renderComponent(node.componentId, node.props || {}, slotsToPass, {
3145
+ layoutNodeId: node.id
3146
+ })
3147
+ }
3148
+ ) });
3023
3149
  };
3024
3150
  const rawTree = pageConfig.layoutTree;
3025
3151
  const layoutTree = Array.isArray(rawTree) ? rawTree : rawTree && typeof rawTree === "object" && rawTree.componentId ? [rawTree] : [];
@@ -3265,6 +3391,25 @@ function Slot({
3265
3391
  }
3266
3392
  );
3267
3393
  }
3394
+ function formatFieldValue(value) {
3395
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3396
+ return String(value);
3397
+ }
3398
+ if (Array.isArray(value)) {
3399
+ const parts = value.map((item) => {
3400
+ if (typeof item === "string") return item;
3401
+ if (item && typeof item === "object" && "children" in item && Array.isArray(item.children)) {
3402
+ return item.children.map((c) => typeof c?.text === "string" ? c.text : "").join("");
3403
+ }
3404
+ return "";
3405
+ }).filter(Boolean);
3406
+ if (parts.length > 0) return parts.join(" ");
3407
+ }
3408
+ if (value && typeof value === "object") {
3409
+ return "";
3410
+ }
3411
+ return "";
3412
+ }
3268
3413
  function Field({
3269
3414
  name,
3270
3415
  fallback,
@@ -3283,7 +3428,51 @@ function Field({
3283
3428
  if (render) {
3284
3429
  return /* @__PURE__ */ jsx("span", { className, children: render(fieldValue) });
3285
3430
  }
3286
- return /* @__PURE__ */ jsx("span", { className, children: String(fieldValue) });
3431
+ const text = formatFieldValue(fieldValue);
3432
+ if (!text) {
3433
+ return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
3434
+ }
3435
+ return /* @__PURE__ */ jsx("span", { className, children: text });
3436
+ }
3437
+ function markdownToHtml(markdown) {
3438
+ try {
3439
+ const result = marked.parse(markdown, { async: false });
3440
+ return typeof result === "string" ? result : "";
3441
+ } catch {
3442
+ return markdown;
3443
+ }
3444
+ }
3445
+ function RichText({
3446
+ name,
3447
+ fallback,
3448
+ className,
3449
+ as: Tag = "div"
3450
+ }) {
3451
+ const context = useContext(ComponentContext);
3452
+ if (!context) {
3453
+ console.warn(`[AmplifyUp SDK] RichText "${name}" used outside ComponentContextProvider`);
3454
+ return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
3455
+ }
3456
+ const fieldValue = context.props?.[name];
3457
+ if (fieldValue === void 0 || fieldValue === null || fieldValue === "") {
3458
+ return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
3459
+ }
3460
+ let html = "";
3461
+ if (typeof fieldValue === "string") {
3462
+ const trimmed = fieldValue.trim();
3463
+ if (trimmed.startsWith("<") && trimmed.includes(">")) {
3464
+ html = fieldValue;
3465
+ } else {
3466
+ html = markdownToHtml(fieldValue);
3467
+ }
3468
+ } else {
3469
+ const text = formatFieldValue(fieldValue);
3470
+ html = text ? markdownToHtml(text) : "";
3471
+ }
3472
+ if (!html) {
3473
+ return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
3474
+ }
3475
+ return /* @__PURE__ */ jsx(Tag, { className, dangerouslySetInnerHTML: { __html: html } });
3287
3476
  }
3288
3477
  var usePathname = null;
3289
3478
  if (typeof window !== "undefined") {
@@ -3442,6 +3631,6 @@ function useAmplifyUp() {
3442
3631
  return context;
3443
3632
  }
3444
3633
 
3445
- export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, Field, Slot, fetchPageConfigServer, isComposerPreview, isPreviewRequest, useAmplifyUp };
3634
+ export { AmplifyPageContent, AmplifyRenderer, AmplifyUpProvider, ComponentContextProvider, Field, RichText, Slot, fetchPageConfigServer, isComposerPreview, isPreviewRequest, useAmplifyUp };
3446
3635
  //# sourceMappingURL=react.mjs.map
3447
3636
  //# sourceMappingURL=react.mjs.map