@amplifyup/sdk 0.1.25 → 0.1.27

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,5 +1,5 @@
1
1
  'use client';
2
- import { createContext, useState, useEffect, useContext, Fragment as Fragment$1, Component } from 'react';
2
+ import { createContext, useState, useEffect, useContext, Fragment as Fragment$1, useRef, Component } from 'react';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import { marked } from 'marked';
5
5
 
@@ -58,7 +58,10 @@ var AWSAdapter = class {
58
58
  headers: {
59
59
  "Content-Type": "application/json"
60
60
  },
61
- body: JSON.stringify(eventData)
61
+ body: JSON.stringify(eventData),
62
+ // Keep the request alive through tab close so the single Time on Page
63
+ // flush on pagehide is not cancelled with the document.
64
+ keepalive: true
62
65
  });
63
66
  if (!response.ok) {
64
67
  if (this.config.debug) {
@@ -517,8 +520,6 @@ async function fetchContent(options) {
517
520
 
518
521
  // src/index.ts
519
522
  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
523
  var DWELL_ACTIVITY_IDLE_MS = 2 * 60 * 1e3;
523
524
  var DWELL_POLL_MS = 5e3;
524
525
  function readPreviewQueryOverrides() {
@@ -589,7 +590,8 @@ var AmplifyUpSDK = class {
589
590
  reached: /* @__PURE__ */ new Set(),
590
591
  visibleMs: 0,
591
592
  visibleSince: null,
592
- lastDwellSecondsSent: 0,
593
+ /** True after Time on Page has been sent for the current visit. */
594
+ dwellEmitted: false,
593
595
  /** When the tab last became hidden — used for idle-resume re-page. */
594
596
  hiddenAt: null,
595
597
  /** Last user interaction — freezes dwell when stale while visible. */
@@ -818,9 +820,9 @@ var AmplifyUpSDK = class {
818
820
  }
819
821
  /**
820
822
  * 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.
823
+ * Time on Page fires once per visit when the visit ends (route change,
824
+ * tab close, or 30-minute idle resume). Hidden tabs pause the clock;
825
+ * visible-but-idle tabs freeze after DWELL_ACTIVITY_IDLE_MS.
824
826
  */
825
827
  setupPageEngagement() {
826
828
  if (typeof window === "undefined" || typeof document === "undefined") return;
@@ -831,7 +833,7 @@ var AmplifyUpSDK = class {
831
833
  state.reached = /* @__PURE__ */ new Set();
832
834
  state.visibleMs = 0;
833
835
  state.visibleSince = document.visibilityState === "visible" ? Date.now() : null;
834
- state.lastDwellSecondsSent = 0;
836
+ state.dwellEmitted = false;
835
837
  state.hiddenAt = document.visibilityState === "hidden" ? Date.now() : null;
836
838
  state.lastActivityAt = Date.now();
837
839
  state.activityIdle = false;
@@ -893,48 +895,34 @@ var AmplifyUpSDK = class {
893
895
  }
894
896
  }
895
897
  };
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) => {
898
+ const emitDwell = (reason) => {
905
899
  if (this.isPreviewMode()) return;
900
+ if (state.dwellEmitted) return;
906
901
  if (!state.activityIdle) {
907
902
  accumulateVisible();
908
903
  }
909
904
  const seconds = Math.floor(state.visibleMs / 1e3);
910
905
  if (seconds <= 0) return;
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
- }
917
- state.lastDwellSecondsSent = seconds;
906
+ state.dwellEmitted = true;
918
907
  this.track("Time on Page", {
919
908
  seconds,
920
909
  path: this.lastTrackedPath || path(),
921
910
  reason
922
911
  });
923
912
  };
924
- const evaluateDwell = () => {
913
+ const evaluateIdle = () => {
925
914
  if (document.visibilityState !== "visible") return;
926
915
  if (Date.now() - state.lastActivityAt >= DWELL_ACTIVITY_IDLE_MS) {
927
916
  freezeActivityIdle();
928
- return;
929
917
  }
930
- emitDwell("milestone", false);
931
918
  };
932
- this._emitPageDwell = (force) => emitDwell("route", force !== false);
919
+ this._emitPageDwell = () => emitDwell("route");
933
920
  const onVisibility = () => {
934
921
  if (document.visibilityState === "visible") {
935
922
  const idleMs = state.hiddenAt != null ? Date.now() - state.hiddenAt : 0;
936
923
  state.hiddenAt = null;
937
924
  if (idleMs >= SESSION_IDLE_GAP_MS) {
925
+ emitDwell("idle-resume");
938
926
  this.resetEngagementQuiet();
939
927
  const resumePath = path();
940
928
  this.page(resumePath, { resumed: true });
@@ -951,14 +939,14 @@ var AmplifyUpSDK = class {
951
939
  state.visibleSince = Date.now();
952
940
  state.activityIdle = false;
953
941
  } else {
954
- emitDwell("hidden", true);
942
+ accumulateVisible();
955
943
  state.hiddenAt = Date.now();
956
944
  state.visibleSince = null;
957
945
  state.activityIdle = false;
958
946
  }
959
947
  };
960
948
  const onPageHide = () => {
961
- emitDwell("unload", true);
949
+ emitDwell("unload");
962
950
  if (state.hiddenAt == null) {
963
951
  state.hiddenAt = Date.now();
964
952
  }
@@ -979,10 +967,10 @@ var AmplifyUpSDK = class {
979
967
  window.addEventListener(evt, onActivity, { passive: true });
980
968
  }
981
969
  checkScroll();
982
- const heartbeat = setInterval(() => {
983
- evaluateDwell();
970
+ const idlePoll = setInterval(() => {
971
+ evaluateIdle();
984
972
  }, DWELL_POLL_MS);
985
- this.trackingIntervals.push(heartbeat);
973
+ this.trackingIntervals.push(idlePoll);
986
974
  const previousCleanup = this.trackingCleanup;
987
975
  this.trackingCleanup = () => {
988
976
  window.removeEventListener("scroll", onScroll);
@@ -1010,12 +998,12 @@ var AmplifyUpSDK = class {
1010
998
  emit?.(true);
1011
999
  this.resetEngagementQuiet();
1012
1000
  }
1013
- /** Reset dwell/scroll state without emitting (idle resume already flushed on hide). */
1001
+ /** Reset dwell/scroll state without emitting (idle resume already flushed). */
1014
1002
  resetEngagementQuiet() {
1015
1003
  const state = this.pageEngagementState;
1016
1004
  state.reached = /* @__PURE__ */ new Set();
1017
1005
  state.visibleMs = 0;
1018
- state.lastDwellSecondsSent = 0;
1006
+ state.dwellEmitted = false;
1019
1007
  state.hiddenAt = null;
1020
1008
  state.lastActivityAt = Date.now();
1021
1009
  state.activityIdle = false;
@@ -1793,6 +1781,12 @@ Available Console Commands:
1793
1781
  return;
1794
1782
  }
1795
1783
  try {
1784
+ if (event.data?.type === "AMPLIFYUP_FIELD_PATCH") {
1785
+ window.dispatchEvent(
1786
+ new CustomEvent("amplifyup:field-patch", { detail: event.data })
1787
+ );
1788
+ return;
1789
+ }
1796
1790
  if (event.data?.type === "COMPONENT_DELETABLE_STATUS") {
1797
1791
  const { componentId, deletable, nodeId } = event.data;
1798
1792
  const element = document.querySelector(`[data-component-id="${componentId}"]`);
@@ -1979,6 +1973,10 @@ Available Console Commands:
1979
1973
  setEventSelectionMode(enabled) {
1980
1974
  if (typeof document === "undefined") return;
1981
1975
  this.eventSelectionMode = enabled;
1976
+ if (typeof window !== "undefined") {
1977
+ window.__AMPLIFYUP_EVENT_SELECTION__ = enabled;
1978
+ window.dispatchEvent(new CustomEvent("amplifyup:composer-edit"));
1979
+ }
1982
1980
  if (this.eventSelectionClickHandler) {
1983
1981
  document.removeEventListener("click", this.eventSelectionClickHandler, true);
1984
1982
  this.eventSelectionClickHandler = null;
@@ -2216,6 +2214,14 @@ Available Console Commands:
2216
2214
  el.classList.add("amplifyup-selected");
2217
2215
  }
2218
2216
  });
2217
+ if (typeof window !== "undefined") {
2218
+ window.__AMPLIFYUP_COMPOSER_EDIT__ = highlightComponents;
2219
+ window.dispatchEvent(
2220
+ new CustomEvent("amplifyup:composer-edit", {
2221
+ detail: { enabled: highlightComponents }
2222
+ })
2223
+ );
2224
+ }
2219
2225
  }
2220
2226
  /** Document-level drag/drop for cross-iframe drags from Composer sidebar. */
2221
2227
  setupComposerDragDrop() {
@@ -2524,6 +2530,12 @@ Available Console Commands:
2524
2530
  const clearSelection = opts?.clearSelection !== false;
2525
2531
  this.highlightingEnabled = false;
2526
2532
  this.teardownComposerDragDrop();
2533
+ if (opts?.clearSelection !== false && typeof window !== "undefined") {
2534
+ window.__AMPLIFYUP_COMPOSER_EDIT__ = false;
2535
+ window.dispatchEvent(
2536
+ new CustomEvent("amplifyup:composer-edit", { detail: { enabled: false } })
2537
+ );
2538
+ }
2527
2539
  if (this.mutationObserver) {
2528
2540
  this.mutationObserver.disconnect();
2529
2541
  this.mutationObserver = null;
@@ -3391,6 +3403,152 @@ function Slot({
3391
3403
  }
3392
3404
  );
3393
3405
  }
3406
+ function isComposerEditMode() {
3407
+ if (typeof window === "undefined") return false;
3408
+ return isComposerPreview() && window.__AMPLIFYUP_COMPOSER_EDIT__ === true && window.__AMPLIFYUP_EVENT_SELECTION__ !== true;
3409
+ }
3410
+ function isLikelyNonTextFieldName(name) {
3411
+ const n = name.toLowerCase();
3412
+ return n.includes("href") || n.includes("url") || n.includes("src") || n.includes("image") || n.includes("icon") || n.includes("overlay") || n.includes("color");
3413
+ }
3414
+ function canInlineEdit(name, value) {
3415
+ if (isLikelyNonTextFieldName(name)) return false;
3416
+ if (value !== void 0 && value !== null && typeof value === "object") {
3417
+ return false;
3418
+ }
3419
+ if (typeof value === "boolean") return false;
3420
+ return true;
3421
+ }
3422
+ function useComposerEditMode() {
3423
+ const [enabled, setEnabled] = useState(false);
3424
+ useEffect(() => {
3425
+ const sync = () => setEnabled(isComposerEditMode());
3426
+ sync();
3427
+ window.addEventListener("amplifyup:composer-edit", sync);
3428
+ return () => window.removeEventListener("amplifyup:composer-edit", sync);
3429
+ }, []);
3430
+ return enabled;
3431
+ }
3432
+ function useFieldOverlay(layoutNodeId, name, source) {
3433
+ const [overlay, setOverlay] = useState(void 0);
3434
+ useEffect(() => {
3435
+ const onPatch = (event) => {
3436
+ const detail = event.detail || {};
3437
+ if (detail.fieldName !== name) return;
3438
+ if (detail.layoutNodeId && layoutNodeId && String(detail.layoutNodeId) !== String(layoutNodeId)) {
3439
+ return;
3440
+ }
3441
+ setOverlay(detail.value);
3442
+ };
3443
+ window.addEventListener("amplifyup:field-patch", onPatch);
3444
+ return () => window.removeEventListener("amplifyup:field-patch", onPatch);
3445
+ }, [layoutNodeId, name]);
3446
+ return overlay !== void 0 ? overlay : source;
3447
+ }
3448
+ function commitComposerField(layoutNodeId, componentId, name, value) {
3449
+ if (typeof window === "undefined" || window.parent === window) return;
3450
+ window.parent.postMessage(
3451
+ {
3452
+ type: "AMPLIFYUP_FIELD_COMMIT",
3453
+ layoutNodeId,
3454
+ componentId,
3455
+ fieldName: name,
3456
+ value
3457
+ },
3458
+ "*"
3459
+ );
3460
+ }
3461
+ function ComposerInlineField({
3462
+ name,
3463
+ value,
3464
+ fallback,
3465
+ className,
3466
+ render,
3467
+ layoutNodeId,
3468
+ componentId
3469
+ }) {
3470
+ const [editing, setEditing] = useState(false);
3471
+ const elRef = useRef(null);
3472
+ const text = formatFieldValue(value);
3473
+ const fallbackText = typeof fallback === "string" || typeof fallback === "number" ? String(fallback) : "";
3474
+ const display = text || fallbackText;
3475
+ const showRendered = !!render && !editing;
3476
+ useEffect(() => {
3477
+ const el = elRef.current;
3478
+ if (!el || showRendered) return;
3479
+ if (document.activeElement === el) return;
3480
+ const next = display || "";
3481
+ if (el.textContent !== next) el.textContent = next;
3482
+ }, [display, showRendered]);
3483
+ useEffect(() => {
3484
+ if (!editing || !elRef.current) return;
3485
+ const el = elRef.current;
3486
+ el.textContent = display || "";
3487
+ el.focus();
3488
+ const range = document.createRange();
3489
+ range.selectNodeContents(el);
3490
+ const sel = window.getSelection();
3491
+ sel?.removeAllRanges();
3492
+ sel?.addRange(range);
3493
+ }, [editing]);
3494
+ const finishEdit = () => {
3495
+ const raw = (elRef.current?.innerText ?? "").replace(/\u00a0/g, " ").replace(/\n+$/g, "");
3496
+ const next = raw.trim() === "" ? "" : raw;
3497
+ setEditing(false);
3498
+ if (next === text) return;
3499
+ if (!text && next === fallbackText) return;
3500
+ commitComposerField(layoutNodeId, componentId, name, next);
3501
+ };
3502
+ return /* @__PURE__ */ jsx(
3503
+ "span",
3504
+ {
3505
+ ref: elRef,
3506
+ className,
3507
+ "data-amplifyup-field": name,
3508
+ "data-amplifyup-node-id": layoutNodeId || void 0,
3509
+ contentEditable: !showRendered,
3510
+ suppressContentEditableWarning: true,
3511
+ role: "textbox",
3512
+ tabIndex: 0,
3513
+ onMouseDown: (event) => {
3514
+ event.stopPropagation();
3515
+ },
3516
+ onClick: (event) => {
3517
+ const link = event.target?.closest?.("a");
3518
+ if (link) event.preventDefault();
3519
+ if (render && isComposerEditMode()) {
3520
+ setEditing(true);
3521
+ }
3522
+ },
3523
+ onFocus: () => {
3524
+ if (!isComposerEditMode()) return;
3525
+ if (render) setEditing(true);
3526
+ },
3527
+ onBlur: finishEdit,
3528
+ onKeyDown: (event) => {
3529
+ event.stopPropagation();
3530
+ if (event.key === "Enter" && !event.shiftKey) {
3531
+ event.preventDefault();
3532
+ event.currentTarget.blur();
3533
+ }
3534
+ if (event.key === "Escape") {
3535
+ event.preventDefault();
3536
+ if (elRef.current) elRef.current.textContent = display || "";
3537
+ setEditing(false);
3538
+ event.currentTarget.blur();
3539
+ }
3540
+ },
3541
+ style: {
3542
+ cursor: "text",
3543
+ outline: "1px dashed color-mix(in srgb, currentColor 40%, transparent)",
3544
+ outlineOffset: "2px",
3545
+ minWidth: "1ch",
3546
+ display: render ? void 0 : "inline"
3547
+ },
3548
+ children: showRendered ? value != null && value !== "" ? render(value) : fallback ?? display : null
3549
+ }
3550
+ );
3551
+ }
3394
3552
  function formatFieldValue(value) {
3395
3553
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3396
3554
  return String(value);
@@ -3417,11 +3575,30 @@ function Field({
3417
3575
  render
3418
3576
  }) {
3419
3577
  const context = useContext(ComponentContext);
3578
+ const composerEdit = useComposerEditMode();
3579
+ const fieldValue = useFieldOverlay(
3580
+ context?.layoutNodeId,
3581
+ name,
3582
+ context?.props?.[name]
3583
+ );
3420
3584
  if (!context) {
3421
3585
  console.warn(`[AmplifyUp SDK] Field "${name}" used outside ComponentContextProvider`);
3422
3586
  return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
3423
3587
  }
3424
- const fieldValue = context.props?.[name];
3588
+ if (composerEdit && canInlineEdit(name, fieldValue)) {
3589
+ return /* @__PURE__ */ jsx(
3590
+ ComposerInlineField,
3591
+ {
3592
+ name,
3593
+ value: fieldValue,
3594
+ fallback,
3595
+ className,
3596
+ render,
3597
+ layoutNodeId: context.layoutNodeId,
3598
+ componentId: context.componentId
3599
+ }
3600
+ );
3601
+ }
3425
3602
  if (fieldValue === void 0 || fieldValue === null) {
3426
3603
  return fallback ? /* @__PURE__ */ jsx("span", { className, children: fallback }) : null;
3427
3604
  }
@@ -3449,11 +3626,29 @@ function RichText({
3449
3626
  as: Tag = "div"
3450
3627
  }) {
3451
3628
  const context = useContext(ComponentContext);
3629
+ const composerEdit = useComposerEditMode();
3630
+ const fieldValue = useFieldOverlay(
3631
+ context?.layoutNodeId,
3632
+ name,
3633
+ context?.props?.[name]
3634
+ );
3452
3635
  if (!context) {
3453
3636
  console.warn(`[AmplifyUp SDK] RichText "${name}" used outside ComponentContextProvider`);
3454
3637
  return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
3455
3638
  }
3456
- const fieldValue = context.props?.[name];
3639
+ if (composerEdit && canInlineEdit(name, fieldValue)) {
3640
+ return /* @__PURE__ */ jsx(
3641
+ ComposerInlineField,
3642
+ {
3643
+ name,
3644
+ value: fieldValue,
3645
+ fallback,
3646
+ className,
3647
+ layoutNodeId: context.layoutNodeId,
3648
+ componentId: context.componentId
3649
+ }
3650
+ );
3651
+ }
3457
3652
  if (fieldValue === void 0 || fieldValue === null || fieldValue === "") {
3458
3653
  return fallback ? /* @__PURE__ */ jsx(Tag, { className, children: fallback }) : null;
3459
3654
  }