@feedal/embed
Version:
Feedal embed script to load feedback forms via JS or NPM
1,401 lines (1,395 loc) • 80.4 kB
JavaScript
var M = Object.defineProperty;
var L = (i, e, t) => e in i ? M(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
var l = (i, e, t) => L(i, typeof e != "symbol" ? e + "" : e, t);
const C = {
host: "https://fedl.io/f/",
mode: "popup",
theme: "light",
trigger: "manual",
triggerCooldown: 5,
// Updated: 5 minutes (defaults to minutes) - matches UI expectation
autoClose: !0,
// Updated: true = 3 seconds (default), false = disabled, number = custom seconds
showCloseButton: !0,
closeOnOverlayClick: !1,
// Updated: false by default for better UX
responsive: !0,
// width and height removed to enable responsive sizing by default
// position removed to allow each mode to use its own sensible fallback
animation: "fade",
zIndex: 9999,
overlay: !1,
// Updated: false by default for cleaner look
useCard: !1,
ariaLabel: "Feedback form",
// Added: Default accessibility label
focusTrap: !0,
// Enable focus trap by default for accessibility
lazyLoad: !0,
// Enable lazy loading by default
preloadResources: !0,
// Enable resource preloading
performanceMonitoring: !1,
// Disabled by default to avoid overhead
// Performance & Animation Defaults
disableFormAnimations: !0,
// Updated: true by default for better performance
// Loading & User Experience Defaults
showLoadingIndicator: !1,
// Added: Disabled by default to avoid UI clutter
loadingSpinner: !0,
// Added: Show spinner when loading indicator is enabled
// Submission Persistence Defaults
rememberSubmission: !0,
// Enable submission persistence by default
submissionExpiry: 30,
// 30 days (defaults to days)
storageType: "local",
// Use localStorage by default
// Button-specific Defaults (Keep these - they're different from form positioning)
buttonPosition: "bottom-right",
buttonSize: "medium",
buttonColor: "#007bff",
buttonIcon: "👍",
buttonText: "",
// Enhanced Behavioral Defaults
draggable: !1,
// Disabled by default
resizable: !1,
// Disabled by default
collapsible: !1,
// Disabled by default
persistent: !1,
// Disabled by default
blurBackground: !1
// Disabled by default for performance
};
function R(i) {
return /^[a-zA-Z0-9\-_]+$/.test(i);
}
function h(i) {
return typeof i == "number" ? `${i}px` : i;
}
function A(i, e) {
const t = new URLSearchParams({
s: "e"
// source: embed
});
return i.theme && t.set("theme", i.theme), i.customCssUrl && t.set("css", i.customCssUrl), i.autoClose && t.set("autoClose", String(i.autoClose)), i.mode && t.set("mode", i.mode), i.token && t.set("token", i.token), i.useCard && t.set("useCard", String(i.useCard)), i.disableFormAnimations && t.set("disableAnimations", "1"), i.metadata && t.set("metadata", JSON.stringify(i.metadata)), i.prefill && t.set("prefill", JSON.stringify(i.prefill)), t;
}
function $() {
return window.innerWidth <= 768;
}
function O(i, e) {
const t = {}, s = (e == null ? void 0 : e.x) || 0, r = (e == null ? void 0 : e.y) || 0;
switch (i) {
case "top-left":
t.top = `${20 + r}px`, t.left = `${20 + s}px`;
break;
case "top-center":
t.top = `${20 + r}px`, t.left = "50%", t.transform = `translateX(calc(-50% + ${s}px))`;
break;
case "top-right":
t.top = `${20 + r}px`, t.right = `${20 - s}px`;
break;
case "bottom-left":
t.bottom = `${20 - r}px`, t.left = `${20 + s}px`;
break;
case "bottom-center":
t.bottom = `${20 - r}px`, t.left = "50%", t.transform = `translateX(calc(-50% + ${s}px))`;
break;
case "bottom-right":
t.bottom = `${20 - r}px`, t.right = `${20 - s}px`;
break;
case "left":
t.top = "50%", t.left = `${20 + s}px`, t.transform = `translateY(calc(-50% + ${r}px))`;
break;
case "right":
t.top = "50%", t.right = `${20 - s}px`, t.transform = `translateY(calc(-50% + ${r}px))`;
break;
case "center":
default:
t.top = "50%", t.left = "50%", t.transform = `translate(calc(-50% + ${s}px), calc(-50% + ${r}px))`;
break;
}
return t;
}
function T(i, e, t) {
return new Promise((s) => {
if (e === "none") {
s();
return;
}
const r = D(e), o = z(e), a = [], n = i.style.transform || "", c = n.includes("translate") || n.includes("calc");
switch (e) {
case "fade":
a.push(
{ opacity: t === "enter" ? 0 : 1 },
{ opacity: t === "enter" ? 1 : 0 }
);
break;
case "slide":
c ? a.push(
{ transform: `${n} translateY(${t === "enter" ? "100%" : "0"})` },
{ transform: `${n} translateY(${t === "enter" ? "0" : "100%"})` }
) : a.push(
{ transform: `translateY(${t === "enter" ? "100%" : "0"})` },
{ transform: `translateY(${t === "enter" ? "0" : "100%"})` }
);
break;
case "scale":
c ? a.push(
{ transform: `${n} scale(${t === "enter" ? 0.8 : 1})`, opacity: t === "enter" ? 0 : 1 },
{ transform: `${n} scale(${t === "enter" ? 1 : 0.8})`, opacity: t === "enter" ? 1 : 0 }
) : a.push(
{ transform: `scale(${t === "enter" ? 0.8 : 1})`, opacity: t === "enter" ? 0 : 1 },
{ transform: `scale(${t === "enter" ? 1 : 0.8})`, opacity: t === "enter" ? 1 : 0 }
);
break;
case "bounce":
t === "enter" ? c ? a.push(
{ transform: `${n} scale(0.3)`, opacity: 0 },
{ transform: `${n} scale(1.05)`, opacity: 0.8, offset: 0.5 },
{ transform: `${n} scale(0.9)`, opacity: 0.9, offset: 0.7 },
{ transform: `${n} scale(1.03)`, opacity: 0.95, offset: 0.8 },
{ transform: `${n} scale(0.97)`, opacity: 0.98, offset: 0.9 },
{ transform: `${n} scale(1)`, opacity: 1 }
) : a.push(
{ transform: "scale(0.3)", opacity: 0 },
{ transform: "scale(1.05)", opacity: 0.8, offset: 0.5 },
{ transform: "scale(0.9)", opacity: 0.9, offset: 0.7 },
{ transform: "scale(1.03)", opacity: 0.95, offset: 0.8 },
{ transform: "scale(0.97)", opacity: 0.98, offset: 0.9 },
{ transform: "scale(1)", opacity: 1 }
) : c ? a.push(
{ transform: `${n} scale(1)`, opacity: 1 },
{ transform: `${n} scale(1.02)`, opacity: 0.9, offset: 0.3 },
{ transform: `${n} scale(0.95)`, opacity: 0.7, offset: 0.6 },
{ transform: `${n} scale(0.3)`, opacity: 0 }
) : a.push(
{ transform: "scale(1)", opacity: 1 },
{ transform: "scale(1.02)", opacity: 0.9, offset: 0.3 },
{ transform: "scale(0.95)", opacity: 0.7, offset: 0.6 },
{ transform: "scale(0.3)", opacity: 0 }
);
break;
case "flip":
c ? a.push(
{ transform: `${n} rotateY(${t === "enter" ? "90deg" : "0deg"})`, opacity: t === "enter" ? 0 : 1 },
{ transform: `${n} rotateY(${t === "enter" ? "0deg" : "90deg"})`, opacity: t === "enter" ? 1 : 0 }
) : a.push(
{ transform: `rotateY(${t === "enter" ? "90deg" : "0deg"})`, opacity: t === "enter" ? 0 : 1 },
{ transform: `rotateY(${t === "enter" ? "0deg" : "90deg"})`, opacity: t === "enter" ? 1 : 0 }
);
break;
case "elastic":
t === "enter" ? c ? a.push(
{ transform: `${n} scale(0)`, opacity: 0 },
{ transform: `${n} scale(1.2)`, opacity: 0.8, offset: 0.6 },
{ transform: `${n} scale(0.9)`, opacity: 0.9, offset: 0.8 },
{ transform: `${n} scale(1.05)`, opacity: 0.95, offset: 0.9 },
{ transform: `${n} scale(1)`, opacity: 1 }
) : a.push(
{ transform: "scale(0)", opacity: 0 },
{ transform: "scale(1.2)", opacity: 0.8, offset: 0.6 },
{ transform: "scale(0.9)", opacity: 0.9, offset: 0.8 },
{ transform: "scale(1.05)", opacity: 0.95, offset: 0.9 },
{ transform: "scale(1)", opacity: 1 }
) : c ? a.push(
{ transform: `${n} scale(1)`, opacity: 1 },
{ transform: `${n} scale(1.1)`, opacity: 0.8, offset: 0.4 },
{ transform: `${n} scale(0)`, opacity: 0 }
) : a.push(
{ transform: "scale(1)", opacity: 1 },
{ transform: "scale(1.1)", opacity: 0.8, offset: 0.4 },
{ transform: "scale(0)", opacity: 0 }
);
break;
}
if (a.length > 0) {
const d = i.animate(a, { duration: r, easing: o });
d.onfinish = () => s();
} else
s();
});
}
function D(i) {
switch (i) {
case "bounce":
case "elastic":
return 600;
case "flip":
return 400;
default:
return 300;
}
}
function z(i) {
switch (i) {
case "bounce":
return "cubic-bezier(0.68, -0.55, 0.265, 1.55)";
case "elastic":
return "cubic-bezier(0.175, 0.885, 0.32, 1.275)";
case "flip":
return "cubic-bezier(0.4, 0, 0.2, 1)";
default:
return "ease-out";
}
}
function P(i) {
const e = i.position || "bottom-right";
let t = "200px", s = "150px", r = "400px", o = "400px", a = "300px";
i.height ? t = h(i.height) : (t = "200px", s = "150px", r = "400px"), i.width ? (o = h(i.width), a = h(i.width)) : (o = "400px", a = "300px");
const n = {
position: "fixed",
zIndex: String(i.zIndex),
background: "#fff",
borderRadius: "8px",
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
border: "1px solid #e5e7eb",
overflow: "hidden",
maxWidth: o,
minWidth: a,
height: t,
minHeight: s,
maxHeight: r
};
switch (e) {
case "top-left":
return { ...n, top: "20px", left: "20px" };
case "top-center":
return { ...n, top: "20px", left: "50%", transform: "translateX(-50%)" };
case "top-right":
return { ...n, top: "20px", right: "20px" };
case "bottom-left":
return { ...n, bottom: "20px", left: "20px" };
case "bottom-center":
return { ...n, bottom: "20px", left: "50%", transform: "translateX(-50%)" };
case "bottom-right":
default:
return { ...n, bottom: "20px", right: "20px" };
}
}
function K(i) {
const e = i.position || "center", t = i.offset;
let s = "400px", r = "90vw", o = "auto", a = "200px", n = "90vh";
i.width ? s = h(i.width) : (s = "400px", r = "90vw"), i.height ? o = h(i.height) : (o = "auto", a = "200px", n = "90vh");
const c = {
position: "fixed",
zIndex: String(i.zIndex),
background: "#fff",
borderRadius: "12px",
boxShadow: "0 10px 25px rgba(0, 0, 0, 0.15)",
overflow: "hidden",
width: s,
maxWidth: r,
height: o,
minHeight: a,
maxHeight: n
}, d = O(e, t);
return i.responsive && $() ? (c.width = "calc(100% - 40px)", c.maxWidth = "400px", {
...c,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
}) : {
...c,
...d
};
}
function F(i) {
return {
position: "fixed",
top: "0",
left: "0",
width: "100%",
height: "100%",
zIndex: String(i.zIndex)
};
}
function H(i) {
let e = "100%", t = "auto", s = "100px", r = "100%", o = "100%";
return i.width && (e = h(i.width), r = h(i.width), o = h(i.width)), i.height ? t = h(i.height) : (t = "auto", s = "100px"), i.maxWidth && (r = h(i.maxWidth)), i.minWidth && (o = h(i.minWidth)), {
position: "relative",
top: "auto",
left: "auto",
width: e,
height: t,
minHeight: s,
maxWidth: r,
minWidth: o,
border: "1px solid #e5e7eb",
borderRadius: "8px",
display: "block",
margin: "10px 0"
};
}
function W(i) {
const e = i.buttonPosition || "bottom-right", t = i.buttonSize || "medium", s = i.buttonColor || "#007bff";
let r = "60px";
t === "custom" ? r = i.width ? String(i.width) : "60px" : r = { small: "40px", medium: "60px", large: "80px" }[t] || "60px";
let o = "auto", a = "auto", n = "auto", c = "auto", d = "";
switch (e) {
case "top-left":
o = "20px", n = "20px";
break;
case "top-center":
o = "20px", n = "50%", d = "translateX(-50%)";
break;
case "top-right":
o = "20px", c = "20px";
break;
case "center-left":
o = "50%", n = "20px", d = "translateY(-50%)";
break;
case "center":
o = "50%", n = "50%", d = "translate(-50%, -50%)";
break;
case "center-right":
o = "50%", c = "20px", d = "translateY(-50%)";
break;
case "bottom-left":
a = "20px", n = "20px";
break;
case "bottom-center":
a = "20px", n = "50%", d = "translateX(-50%)";
break;
case "bottom-right":
default:
a = "20px", c = "20px";
break;
}
return {
position: "fixed",
top: o,
bottom: a,
left: n,
right: c,
transform: d,
width: r,
height: r,
borderRadius: "50%",
border: "none",
background: s,
color: "white",
fontSize: t === "small" ? "16px" : t === "large" ? "32px" : "24px",
cursor: "pointer",
zIndex: String((i.zIndex || 9999) - 1),
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
transition: "all 0.3s ease"
};
}
function B(i) {
const e = i.position || "right";
let t;
switch (e) {
case "left":
case "center-left":
case "top-left":
case "bottom-left":
t = "left";
break;
case "right":
case "center-right":
case "top-right":
case "bottom-right":
default:
t = "right";
break;
}
let s = "500px", r = "90vw";
return i.width ? s = h(i.width) : (s = "500px", r = "90vw"), {
position: "fixed",
top: "0",
[t]: "0",
height: "100vh",
width: s,
maxWidth: r,
zIndex: String(i.zIndex),
background: "#fff",
boxShadow: t === "left" ? "4px 0 30px rgba(0, 0, 0, 0.2)" : "-4px 0 30px rgba(0, 0, 0, 0.2)",
overflowY: "auto",
borderTopLeftRadius: t === "right" ? "12px" : "0",
borderBottomLeftRadius: t === "right" ? "12px" : "0",
borderTopRightRadius: t === "left" ? "12px" : "0",
borderBottomRightRadius: t === "left" ? "12px" : "0"
};
}
function N(i) {
const e = i.position || "center";
let t = "90vw", s = "600px", r = "auto", o = "200px", a = "80vh";
i.width ? t = h(i.width) : (t = "500px", s = "90vw"), i.height ? r = h(i.height) : (r = "auto", o = "300px", a = "90vh");
const n = {
position: "fixed",
zIndex: String(i.zIndex),
background: "#fff",
borderRadius: "12px",
boxShadow: "0 20px 40px rgba(0, 0, 0, 0.2)",
overflow: "hidden",
width: t,
maxWidth: s,
height: r,
minHeight: o,
maxHeight: a
};
switch (e) {
case "top":
case "top-center":
return {
...n,
top: "10%",
left: "50%",
transform: "translateX(-50%)"
};
case "bottom":
case "bottom-center":
return {
...n,
bottom: "10%",
left: "50%",
transform: "translateX(-50%)"
};
case "left":
case "center-left":
return {
...n,
top: "50%",
left: "10%",
transform: "translateY(-50%)"
};
case "right":
case "center-right":
return {
...n,
top: "50%",
right: "10%",
transform: "translateY(-50%)"
};
case "center":
default:
return {
...n,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
};
}
}
function U(i) {
const e = i.position || "right";
let t;
switch (e) {
case "left":
case "center-left":
case "top-left":
case "bottom-left":
t = "left";
break;
case "right":
case "center-right":
case "top-right":
case "bottom-right":
t = "right";
break;
case "center":
case "top-center":
case "bottom-center":
default:
t = "right";
break;
}
let s = "400px", r = "80vw";
return i.width ? s = h(i.width) : (s = "400px", r = "80vw"), {
position: "fixed",
[t]: "0",
top: "0",
height: "100vh",
width: s,
maxWidth: r,
borderTopLeftRadius: t === "right" ? "12px" : "0",
borderBottomLeftRadius: t === "right" ? "12px" : "0",
borderTopRightRadius: t === "left" ? "12px" : "0",
borderBottomRightRadius: t === "left" ? "12px" : "0",
zIndex: String(i.zIndex),
background: "#fff",
boxShadow: t === "right" ? "-4px 0 20px rgba(0, 0, 0, 0.15)" : "4px 0 20px rgba(0, 0, 0, 0.15)",
overflowY: "auto"
};
}
function Y(i) {
const e = i.position || "bottom";
console.log("🎨 [DRAWER STYLES] Generating styles for position:", e);
let t;
switch (e) {
case "center":
case "top-center":
case "bottom-center":
t = "bottom";
break;
case "center-left":
t = "left";
break;
case "center-right":
t = "right";
break;
case "top-left":
case "top-right":
t = "top";
break;
case "bottom-left":
case "bottom-right":
t = "bottom";
break;
default:
t = e;
}
console.log("🎨 [DRAWER STYLES] Mapped position:", t);
const s = t === "left" || t === "right";
console.log("🎨 [DRAWER STYLES] Is horizontal:", s);
let r = "400px", o = "400px", a = "80vh", n = "80vw";
i.width ? r = h(i.width) : r = t === "left" || t === "right" ? "400px" : "100vw", i.height ? o = h(i.height) : o = t === "bottom" || t === "top" ? "400px" : "100vh", a = t === "bottom" || t === "top" ? "80vh" : "100vh", n = t === "left" || t === "right" ? "80vw" : "100vw";
const c = {
position: "fixed",
[t]: "0",
width: r,
height: o,
maxWidth: n,
maxHeight: a,
...s ? {
top: "0",
borderTopLeftRadius: t === "right" ? "12px" : "0",
borderBottomLeftRadius: t === "right" ? "12px" : "0",
borderTopRightRadius: t === "left" ? "12px" : "0",
borderBottomRightRadius: t === "left" ? "12px" : "0"
} : {
left: "0",
right: "0",
borderTopLeftRadius: t === "bottom" ? "12px" : "0",
borderTopRightRadius: t === "bottom" ? "12px" : "0",
borderBottomLeftRadius: t === "top" ? "12px" : "0",
borderBottomRightRadius: t === "top" ? "12px" : "0"
},
zIndex: String(i.zIndex),
background: "#fff",
boxShadow: {
bottom: "0 -4px 20px rgba(0, 0, 0, 0.15)",
top: "0 4px 20px rgba(0, 0, 0, 0.15)",
left: "4px 0 20px rgba(0, 0, 0, 0.15)",
right: "-4px 0 20px rgba(0, 0, 0, 0.15)"
}[t]
};
return console.log("🎨 [DRAWER STYLES] Generated styles:", c), c;
}
function V() {
const i = window.innerWidth;
return i <= 768 ? "mobile" : i <= 1024 ? "tablet" : "desktop";
}
function E() {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function y(i, e) {
Object.assign(i.style, e);
}
function q() {
return {
width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0),
height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
};
}
function J(i, e) {
const t = q(), s = i.getBoundingClientRect();
return {
x: Math.max(0, Math.min(e.x, t.width - s.width)),
y: Math.max(0, Math.min(e.y, t.height - s.height))
};
}
function b(i, e) {
return typeof i == "boolean" ? i ? 3e3 : -1 : i === void 0 ? -1 : typeof i == "string" ? X(i) : j(i, e);
}
function X(i) {
const e = i.match(/^(\d+(?:\.\d+)?)([smhd])$/);
if (!e)
throw new Error(`Invalid time format: ${i}. Expected format: "5s", "10m", "2h", "30d"`);
const [, t, s] = e, r = parseFloat(t);
switch (s) {
case "s":
return Math.round(r * 1e3);
// seconds
case "m":
return Math.round(r * 60 * 1e3);
// minutes
case "h":
return Math.round(r * 3600 * 1e3);
// hours
case "d":
return Math.round(r * 86400 * 1e3);
// days
default:
throw new Error(`Unknown time unit: ${s}. Supported: s, m, h, d`);
}
}
function j(i, e) {
switch (e) {
case "seconds":
return i * 1e3;
case "minutes":
return i * 60 * 1e3;
case "hours":
return i * 3600 * 1e3;
case "days":
return i * 86400 * 1e3;
default:
throw new Error(`Unknown default unit: ${e}`);
}
}
const w = {
autoClose: "seconds",
triggerDelay: "seconds",
triggerCooldown: "minutes",
submissionExpiry: "days"
};
class G {
constructor(e, t = {}) {
l(this, "element");
l(this, "options");
l(this, "originalFocus", null);
l(this, "focusableElements", []);
l(this, "isTrappingFocus", !1);
l(this, "isHandlingFocus", !1);
this.element = e, this.options = t, this.setupAccessibility();
}
setupAccessibility() {
this.options.ariaLabel && this.element.setAttribute("aria-label", this.options.ariaLabel), this.options.ariaDescribedBy && this.element.setAttribute("aria-describedby", this.options.ariaDescribedBy), this.element.setAttribute("role", this.options.role || "dialog"), this.element.setAttribute("aria-modal", "true"), this.element.setAttribute("tabindex", "-1"), this.respectUserPreferences();
}
respectUserPreferences() {
window.matchMedia("(prefers-reduced-motion: reduce)").matches && (this.element.style.setProperty("--feedal-animation-duration", "0s"), this.element.classList.add("feedal-reduced-motion")), window.matchMedia("(prefers-contrast: high)").matches && this.element.classList.add("feedal-high-contrast"), window.matchMedia("(prefers-color-scheme: dark)").matches && this.element.classList.add("feedal-dark-preference");
}
enableFocusTrap() {
this.options.focusTrap && (this.originalFocus = document.activeElement, this.updateFocusableElements(), this.element.addEventListener("keydown", this.handleKeyDown.bind(this)), document.addEventListener("focusin", this.handleFocusIn.bind(this)), this.focusableElements.length > 0 ? this.focusableElements[0].focus() : this.element.focus(), this.isTrappingFocus = !0);
}
disableFocusTrap() {
this.isTrappingFocus && (this.element.removeEventListener("keydown", this.handleKeyDown.bind(this)), document.removeEventListener("focusin", this.handleFocusIn.bind(this)), this.originalFocus && document.contains(this.originalFocus) && this.originalFocus.focus(), this.isTrappingFocus = !1);
}
updateFocusableElements() {
const e = [
"button:not([disabled])",
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])",
"a[href]",
'[tabindex]:not([tabindex="-1"])',
"iframe"
].join(", ");
this.focusableElements = Array.from(
this.element.querySelectorAll(e)
);
}
handleKeyDown(e) {
if (e.key === "Escape") {
this.handleEscapeKey(e);
return;
}
e.key === "Tab" && this.handleTabKey(e);
}
handleEscapeKey(e) {
e.preventDefault(), this.element.dispatchEvent(new CustomEvent("feedal:escape", { bubbles: !0 }));
}
handleTabKey(e) {
if (this.focusableElements.length === 0) {
e.preventDefault();
return;
}
const t = this.focusableElements[0], s = this.focusableElements[this.focusableElements.length - 1], r = this.focusableElements.indexOf(document.activeElement);
e.shiftKey ? (document.activeElement === t || r === -1) && (e.preventDefault(), s.focus()) : (document.activeElement === s || r === -1) && (e.preventDefault(), t.focus());
}
handleFocusIn(e) {
if (this.isHandlingFocus) return;
const t = e.target;
this.element.contains(t) || (e.preventDefault(), this.isHandlingFocus = !0, this.focusableElements.length > 0 ? this.focusableElements[0].focus() : this.element.focus(), setTimeout(() => {
this.isHandlingFocus = !1;
}, 10));
}
announceToScreenReader(e, t = "polite") {
const s = document.createElement("div");
s.setAttribute("aria-live", t), s.setAttribute("aria-atomic", "true"), s.style.position = "absolute", s.style.left = "-10000px", s.style.width = "1px", s.style.height = "1px", s.style.overflow = "hidden", document.body.appendChild(s), setTimeout(() => {
s.textContent = e, setTimeout(() => {
document.body.contains(s) && document.body.removeChild(s);
}, 1e3);
}, 100);
}
addKeyboardShortcuts() {
this.element.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
const t = new CustomEvent("feedal:submit-shortcut", { bubbles: !0 });
this.element.dispatchEvent(t);
}
});
}
destroy() {
this.disableFocusTrap();
}
}
const Q = `
/* Accessibility styles */
.feedal-reduced-motion * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.feedal-high-contrast {
border: 2px solid #000 !important;
background: #fff !important;
color: #000 !important;
}
.feedal-high-contrast button {
border: 2px solid #000 !important;
background: #fff !important;
color: #000 !important;
}
.feedal-high-contrast button:hover,
.feedal-high-contrast button:focus {
background: #000 !important;
color: #fff !important;
}
/* Focus indicators */
.feedal-widget *:focus {
outline: 2px solid #4A90E2;
outline-offset: 2px;
}
.feedal-widget *:focus:not(:focus-visible) {
outline: none;
}
/* Screen reader only text */
.feedal-sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
`;
class Z {
constructor(e = {}) {
l(this, "options");
l(this, "intersectionObserver");
l(this, "performanceObserver");
l(this, "metrics", /* @__PURE__ */ new Map());
this.options = {
lazyLoad: !0,
preloadDelay: 100,
intersectionThreshold: 0.1,
performanceMonitoring: !1,
criticalPath: !1,
...e
}, this.options.performanceMonitoring && this.initPerformanceMonitoring();
}
// Lazy loading with Intersection Observer
setupLazyLoading(e, t) {
if (!this.options.lazyLoad || !("IntersectionObserver" in window)) {
t();
return;
}
this.intersectionObserver = new IntersectionObserver(
(s) => {
s.forEach((r) => {
var o;
r.isIntersecting && (t(), (o = this.intersectionObserver) == null || o.unobserve(e));
});
},
{
threshold: this.options.intersectionThreshold,
// Add some margin to trigger before fully visible
rootMargin: "50px"
}
), this.intersectionObserver.observe(e);
}
// Preload critical resources
preloadResources(e, t) {
if (!this.options.criticalPath) return;
const s = document.createElement("link");
s.rel = "dns-prefetch", s.href = new URL(e).origin, document.head.appendChild(s);
const r = document.createElement("link");
r.rel = "preconnect", r.href = e, document.head.appendChild(r), console.log("Preconnect added for:", r.href);
const o = document.createElement("link");
o.rel = "prefetch", o.href = `${e}${t}?preload=true&s=e`, document.head.appendChild(o);
}
// Optimized scroll handling
createOptimizedScrollHandler(e) {
let t = !1;
return () => {
t || (requestAnimationFrame(() => {
e(), t = !1;
}), t = !0);
};
}
// Passive event listeners for better scroll performance
addPassiveEventListener(e, t, s) {
const r = {
passive: !0,
capture: !1
};
e.addEventListener(t, s, r);
}
// Debounced resize handler with performance optimization
createOptimizedResizeHandler(e, t = 250) {
let s, r = window.innerWidth, o = window.innerHeight;
return () => {
const a = window.innerWidth, n = window.innerHeight;
(a !== r || n !== o) && (clearTimeout(s), s = setTimeout(() => {
e(), r = a, o = n;
}, t));
};
}
// Performance monitoring
initPerformanceMonitoring() {
if ("PerformanceObserver" in window) {
this.performanceObserver = new PerformanceObserver((e) => {
e.getEntries().forEach((t) => {
if (t.entryType === "navigation") {
const s = t;
this.metrics.set("domContentLoaded", s.domContentLoadedEventEnd - s.domContentLoadedEventStart), this.metrics.set("loadComplete", s.loadEventEnd - s.loadEventStart);
}
t.entryType === "paint" && this.metrics.set(t.name, t.startTime), t.entryType === "largest-contentful-paint" && this.metrics.set("lcp", t.startTime);
});
});
try {
this.performanceObserver.observe({ entryTypes: ["navigation", "paint", "largest-contentful-paint"] });
} catch {
console.warn("Some performance metrics not available");
}
}
}
// Mark performance timestamps
markPerformance(e) {
"performance" in window && "mark" in performance && performance.mark(`feedal-${e}`), this.metrics.set(e, Date.now());
}
// Measure time between marks
measurePerformance(e, t, s) {
if ("performance" in window && "measure" in performance) {
const a = `feedal-${e}`, n = `feedal-${t}`, c = s ? `feedal-${s}` : void 0;
try {
return performance.measure(a, n, c), performance.getEntriesByName(a)[0].duration;
} catch {
const g = this.metrics.get(t) || 0;
return (s && this.metrics.get(s) || Date.now()) - g;
}
}
const r = this.metrics.get(t) || 0;
return (s && this.metrics.get(s) || Date.now()) - r;
}
// Get performance metrics
getMetrics() {
const e = {};
if (this.metrics.forEach((t, s) => {
e[s] = t;
}), "performance" in window)
try {
const t = performance.getEntriesByType("navigation")[0];
t && (e.ttfb = t.responseStart - t.requestStart, e.domInteractive = t.domInteractive - t.fetchStart, e.domComplete = t.domComplete - t.fetchStart);
} catch {
}
return e;
}
// Optimize iframe loading
optimizeIframeLoading(e) {
e.loading = "lazy", e.setAttribute("importance", "low"), e.setAttribute("referrerpolicy", "no-referrer-when-downgrade"), e.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture");
}
// Memory management
trackMemoryUsage() {
if ("memory" in performance) {
const e = performance.memory;
this.metrics.set("memoryUsed", e.usedJSHeapSize), this.metrics.set("memoryTotal", e.totalJSHeapSize), this.metrics.set("memoryLimit", e.jsHeapSizeLimit);
}
}
// Clean up resources
destroy() {
this.intersectionObserver && this.intersectionObserver.disconnect(), this.performanceObserver && this.performanceObserver.disconnect(), this.metrics.clear();
}
}
function _(i) {
const e = document.createElement("link");
e.rel = "dns-prefetch", e.href = new URL(i).origin, document.head.appendChild(e);
const t = document.createElement("link");
t.rel = "preconnect", t.href = new URL(i).origin, document.head.appendChild(t);
}
function ee(i) {
const e = document.createElement("style");
e.textContent = i, e.setAttribute("data-feedal-critical", "true");
const t = document.head.querySelector('link[rel="stylesheet"], style');
t ? document.head.insertBefore(e, t) : document.head.appendChild(e);
}
class te {
constructor(e, t, s) {
l(this, "options");
l(this, "performanceManager");
l(this, "state");
l(this, "listeners", []);
l(this, "timers", /* @__PURE__ */ new Set());
l(this, "onTrigger");
this.options = e, this.performanceManager = t, this.onTrigger = s, this.state = {
sessionStartTime: Date.now(),
lastActivityTime: Date.now(),
hasTriggered: !1,
cooldownUntil: 0,
exitIntentTriggered: !1
};
}
setupTrigger() {
const { trigger: e } = this.options;
switch (e) {
case "exit-intent":
this.setupExitIntentTrigger();
break;
case "element-visible":
this.setupElementVisibleTrigger();
break;
case "session-duration":
this.setupSessionDurationTrigger();
break;
case "idle":
this.setupIdleTrigger();
break;
}
}
// Exit Intent Trigger
setupExitIntentTrigger() {
let e = !1;
const t = b(this.options.triggerCooldown, w.triggerCooldown), s = (r) => {
if (r.clientY <= 0 && !e && this.canTrigger()) {
e = !0, this.state.exitIntentTriggered = !0, this.state.cooldownUntil = Date.now() + t, this.performanceManager.markPerformance("exit-intent-detected"), this.trigger();
const o = setTimeout(() => {
e = !1;
}, t);
this.timers.add(o);
}
};
this.isMobile() || (document.addEventListener("mouseleave", s), this.listeners.push(() => document.removeEventListener("mouseleave", s)));
}
// Element Visible Trigger
setupElementVisibleTrigger() {
if (!this.options.triggerElement) {
console.warn("triggerElement is required for element-visible trigger");
return;
}
const e = typeof this.options.triggerElement == "string" ? document.querySelector(this.options.triggerElement) : this.options.triggerElement;
if (!e) {
console.warn(`Element not found: ${this.options.triggerElement}`);
return;
}
const t = this.options.triggerThreshold || 0.5, s = b(this.options.triggerDelay, w.triggerDelay);
let r;
const o = new IntersectionObserver(
(a) => {
a.forEach((n) => {
n.isIntersecting && n.intersectionRatio >= t && this.canTrigger() ? (r = setTimeout(() => {
this.canTrigger() && (this.performanceManager.markPerformance("element-visible-triggered"), this.trigger());
}, s), this.timers.add(r)) : r && (clearTimeout(r), this.timers.delete(r));
});
},
{ threshold: t }
);
o.observe(e), this.listeners.push(() => o.disconnect());
}
// Session Duration Trigger
setupSessionDurationTrigger() {
const e = b(this.options.triggerDelay, w.triggerDelay), t = () => {
Date.now() - this.state.sessionStartTime >= e && this.canTrigger() && (this.performanceManager.markPerformance("session-duration-reached"), this.trigger());
}, s = setInterval(t, 3e4);
this.listeners.push(() => clearInterval(s));
const r = () => {
this.canTrigger() && t();
};
this.performanceManager.addPassiveEventListener(document, "click", r), this.performanceManager.addPassiveEventListener(document, "scroll", r), this.performanceManager.addPassiveEventListener(document, "keydown", r), this.listeners.push(() => {
document.removeEventListener("click", r), document.removeEventListener("scroll", r), document.removeEventListener("keydown", r);
});
}
// Idle Trigger
setupIdleTrigger() {
const e = b(this.options.triggerDelay, w.triggerDelay);
let t;
const s = () => {
this.state.lastActivityTime = Date.now(), t && (clearTimeout(t), this.timers.delete(t)), t = setTimeout(() => {
this.canTrigger() && (this.performanceManager.markPerformance("idle-state-detected"), this.trigger());
}, e), this.timers.add(t);
};
["mousedown", "mousemove", "keypress", "scroll", "touchstart", "click"].forEach((o) => {
this.performanceManager.addPassiveEventListener(document, o, s), this.listeners.push(() => document.removeEventListener(o, s));
}), s();
}
// Utility methods
canTrigger() {
return !this.state.hasTriggered && Date.now() > this.state.cooldownUntil;
}
trigger() {
this.canTrigger() && (this.state.hasTriggered = !0, this.onTrigger());
}
isMobile() {
return window.innerWidth <= 768 || /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// Public methods
reset() {
this.state.hasTriggered = !1, this.state.exitIntentTriggered = !1, this.state.lastActivityTime = Date.now(), this.state.cooldownUntil = 0;
}
getState() {
return { ...this.state };
}
destroy() {
this.timers.forEach((e) => clearTimeout(e)), this.timers.clear(), this.listeners.forEach((e) => e()), this.listeners = [];
}
}
const p = class p {
constructor(e, t = {}) {
l(this, "options");
l(this, "formId");
this.formId = e, this.options = {
rememberSubmission: !0,
submissionExpiry: 30 * 24 * 60 * 60 * 1e3,
// 30 days in milliseconds
storageType: "local",
...t
};
}
/**
* Check if the form has been submitted before
* @returns boolean True if the form has been submitted and should not be shown
*/
hasSubmitted() {
if (!this.options.rememberSubmission)
return console.log("Submission checking disabled in options"), !1;
const e = this.getSubmittedSessionKey();
if (!e)
return console.log("No submitted session key found"), !1;
const t = Date.now();
if ((this.options.submissionExpiry || 0) > 0)
try {
const r = JSON.parse(e);
if (r.expiresAt && t > r.expiresAt)
return console.log("Submitted session key expired, removing"), this.clearSubmittedSessionKey(), !1;
} catch {
return console.warn("Failed to parse submitted session key, removing corrupted data"), this.clearSubmittedSessionKey(), !1;
}
return console.log("Form has been submitted with valid session key"), !0;
}
/**
* Record a form submission
*/
recordSubmission() {
if (!this.options.rememberSubmission) {
console.log("Submission recording disabled in options");
return;
}
const e = this.getRenderedSessionKey();
if (e) {
const t = `${p.submittedKeyPrefix}${this.formId}`, s = this.options.submissionExpiry;
if (s && Number(s) > 0) {
const r = Date.now() + Number(s), o = {
sessionKey: e,
expiresAt: r
};
switch (this.options.storageType) {
case "local":
typeof localStorage < "u" ? (localStorage.setItem(t, JSON.stringify(o)), console.log("Saved to localStorage:", t, "=", JSON.stringify(o))) : console.warn("localStorage not available");
break;
case "session":
typeof sessionStorage < "u" ? (sessionStorage.setItem(t, JSON.stringify(o)), console.log("Saved to sessionStorage:", t, "=", JSON.stringify(o))) : console.warn("sessionStorage not available");
break;
case "cookie":
this.setCookie(t, JSON.stringify(o), this.options.submissionExpiry), console.log("Saved to cookie:", t, "=", JSON.stringify(o));
break;
}
console.log(`Stored submitted session key with expiration: ${new Date(r).toISOString()}`);
} else {
switch (this.options.storageType) {
case "local":
typeof localStorage < "u" ? (localStorage.setItem(t, e), console.log("Saved to localStorage:", t, "=", e)) : console.warn("localStorage not available");
break;
case "session":
typeof sessionStorage < "u" ? (sessionStorage.setItem(t, e), console.log("Saved to sessionStorage:", t, "=", e)) : console.warn("sessionStorage not available");
break;
case "cookie":
this.setCookie(t, e, this.options.submissionExpiry), console.log("Saved to cookie:", t, "=", e);
break;
}
console.log("Stored submitted session key without expiration (persistent)");
}
this.clearRenderedSessionKey(), console.log("Cleared rendered session key");
} else
console.warn("No rendered session key found to record submission");
}
/**
* Clear submission records for this form
*/
clearSubmission() {
this.clearSubmittedSessionKey(), this.clearRenderedSessionKey(), console.log("Cleared all submission records for form:", this.formId);
}
/**
* Clear all submission records across all forms
*/
static resetAllSubmissions() {
if (typeof localStorage < "u") {
const e = [];
for (let t = 0; t < localStorage.length; t++) {
const s = localStorage.key(t);
s && (s.startsWith(p.storageKeyPrefix) || s.startsWith(p.renderedKeyPrefix) || s.startsWith(p.submittedKeyPrefix)) && e.push(s);
}
e.forEach((t) => {
localStorage.removeItem(t), console.log("Removed localStorage key:", t);
});
}
if (typeof sessionStorage < "u") {
const e = [];
for (let t = 0; t < sessionStorage.length; t++) {
const s = sessionStorage.key(t);
s && (s.startsWith(p.storageKeyPrefix) || s.startsWith(p.renderedKeyPrefix) || s.startsWith(p.submittedKeyPrefix)) && e.push(s);
}
e.forEach((t) => {
sessionStorage.removeItem(t), console.log("Removed sessionStorage key:", t);
});
}
document.cookie.split(";").forEach((e) => {
const t = e.trim().split("=")[0];
(t.startsWith(p.storageKeyPrefix) || t.startsWith(p.renderedKeyPrefix) || t.startsWith(p.submittedKeyPrefix)) && (document.cookie = t + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;", console.log("Removed cookie:", t));
}), console.log("All submission records cleared from all storage types");
}
/**
* Store the rendered session key (form is ready to be submitted)
*/
setRenderedSessionKey(e) {
if (!this.options.rememberSubmission)
return;
const t = `${p.renderedKeyPrefix}${this.formId}`;
this.setStorageValue(t, e), console.log("Stored rendered session key:", e, "for form:", this.formId);
}
/**
* Get the rendered session key
*/
getRenderedSessionKey() {
if (!this.options.rememberSubmission)
return null;
const e = `${p.renderedKeyPrefix}${this.formId}`;
return this.getStorageValue(e);
}
/**
* Clear the rendered session key
*/
clearRenderedSessionKey() {
const e = `${p.renderedKeyPrefix}${this.formId}`;
this.removeStorageValue(e), console.log("Cleared rendered session key for form:", this.formId);
}
/**
* Store the submitted session key (form has been submitted)
*/
setSubmittedSessionKey(e) {
if (!this.options.rememberSubmission)
return;
const t = `${p.submittedKeyPrefix}${this.formId}`, s = this.options.submissionExpiry;
if (s && Number(s) > 0) {
const r = Date.now() + Number(s), o = {
sessionKey: e,
expiresAt: r
};
this.setStorageValue(t, JSON.stringify(o)), console.log(`Stored submitted session key with expiration: ${new Date(r).toISOString()}`);
} else
this.setStorageValue(t, e), console.log("Stored submitted session key without expiration (persistent)");
}
/**
* Get the submitted session key
*/
getSubmittedSessionKey() {
if (!this.options.rememberSubmission)
return null;
const e = `${p.submittedKeyPrefix}${this.formId}`, t = this.getStorageValue(e);
if (!t)
return null;
try {
const s = JSON.parse(t);
if (!s || typeof s != "object" || !s.sessionKey)
return console.warn(`Invalid stored session key data for form ${this.formId}. Removing.`), this.removeStorageValue(e), null;
const r = s.expiresAt;
return r && Date.now() > r ? (this.removeStorageValue(e), console.log(`Submitted session key expired for form ${this.formId}. Removing.`), null) : s.sessionKey;
} catch (s) {
return console.warn(`Failed to parse stored session key for form ${this.formId}. Removing corrupted data.`, s), this.removeStorageValue(e), null;
}
}
/**
* Clear the submitted session key
*/
clearSubmittedSessionKey() {
const e = `${p.submittedKeyPrefix}${this.formId}`;
this.removeStorageValue(e), console.log("Cleared submitted session key for form:", this.formId);
}
/**
* Clean up expired keys for this form
*/
cleanupExpiredKeys() {
if (!this.options.rememberSubmission)
return;
this.getSubmittedSessionKey() && console.log("Expired keys cleanup completed for form:", this.formId);
}
/**
* Clear all form-related keys (rendered and submitted)
*/
clearAllFormKeys() {
this.clearRenderedSessionKey(), this.clearSubmittedSessionKey(), console.log("Cleared all form keys for form:", this.formId);
}
/**
* Update the session key - this invalidates all previous submissions
*/
updateSessionKey(e) {
console.log("Updating session key from", this.options.sessionKey, "to", e), this.options.sessionKey = e;
}
/**
* Check if a specific session key is still valid (not expired)
*/
isSessionKeyValid(e) {
if (!this.options.rememberSubmission)
return !1;
if (this.options.sessionKey && this.options.sessionKey !== e)
return console.log("Session key changed, stored key is invalid"), !1;
const t = this.getSubmittedSessionKey();
if (!t)
return !1;
const s = Date.now();
if ((this.options.submissionExpiry || 0) > 0)
try {
const o = JSON.parse(t);
if (o.expiresAt && s > o.expiresAt)
return console.log("Session key validation - expired"), !1;
} catch {
return console.warn("Failed to parse session key for validation, removing corrupted data"), this.clearSubmittedSessionKey(), !1;
}
return console.log("Session key validation - valid"), !0;
}
// Private helper methods for storage abstraction
setStorageValue(e, t) {
switch (this.options.storageType) {
case "local":
typeof localStorage < "u" && localStorage.setItem(e, t);
break;
case "session":
typeof sessionStorage < "u" && sessionStorage.setItem(e, t);
break;
case "cookie":
this.setCookie(e, t, this.options.submissionExpiry);
break;
}
}
getStorageValue(e) {
switch (this.options.storageType) {
case "local":
if (typeof localStorage < "u")
return localStorage.getItem(e);
break;
case "session":
if (typeof sessionStorage < "u")
return sessionStorage.getItem(e);
break;
case "cookie":
return this.getCookie(e);
}
return null;
}
removeStorageValue(e) {
switch (this.options.storageType) {
case "local":
typeof localStorage < "u" && localStorage.removeItem(e);
break;
case "session":
typeof sessionStorage < "u" && sessionStorage.removeItem(e);
break;
case "cookie":
this.deleteCookie(e);
break;
}
}
/**
* Set a cookie with expiration
*/
setCookie(e, t, s) {
let r = `${e}=${t}; path=/`;
if (s) {
const o = /* @__PURE__ */ new Date();
o.setTime(o.getTime() + s), r += `; expires=${o.toUTCString()}`;
}
document.cookie = r;
}
/**
* Get a cookie value by name
*/
getCookie(e) {
const t = document.cookie.split(";");
for (let s = 0; s < t.length; s++) {
const r = t[s].trim();
if (r.startsWith(e + "="))
return r.substring(e.length + 1);
}
return null;
}
/**
* Delete a cookie by name
*/
deleteCookie(e) {
document.cookie = `${e}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
};
l(p, "storageKeyPrefix", "feedal-submission-"), l(p, "renderedKeyPrefix", "feedal-rendered-"), l(p, "submittedKeyPrefix", "feedal-submitted-");
let S = p;
const m = class m {
constructor(e) {
l(this, "options");
l(this, "iframe");
l(this, "wrapper");
l(this, "overlay");
l(this, "isOpen", !1);
l(this, "messageReceived", !1);
// Track if we've received any message
l(this, "messageListener");
l(this, "resizeListener");
l(this, "accessibilityManager");
l(this, "performanceManager");
l(this, "triggerManager");
l(this, "submissionManager");
l(this, "dragHandlers");
l(this, "resizeHandlers");
l(this, "focusTrapCleanup");
l(this, "buttonElement");
this.options = { ...C, ...e }, this.validateOptions(), this.performanceManager = new Z({
lazyLoad: !0,
performanceMonitoring: !0,
criticalPath: !0
}), this.submissionManager = new S(this.options.formId, {
rememberSubmission: this.options.rememberSubmission,
submissionExpiry: b(this.options.submissionExpiry, w.submissionExpiry),
storageType: this.options.storageType,
sessionKey: this.options.sessionKey
}), this.performanceManager.markPerformance("widget-created"), m.stylesInjected || (ee(Q), m.stylesInjected = !0), this.options.host && (_(this.options.host), this.performanceManager.preloadResources(this.options.host, this.options.formId)), this.setupEventListeners(), this.options.formId && m.instances.set(this.options.formId, this);
}
// Public API methods
open() {
this.isOpen || (console.log("Opening widget for form:", this.options.formId), this.isOpen = !0, this.createIframe());
}
close() {
this.isOpen && (this.performanceManager.markPerformance("widget-close-start"), this.accessibilityManager && this.accessibilityManager.announceToScreenReader("Feedback form closed"), this.cleanupWidget().then(() => {
var e, t;
this.isOpen = !1, this.accessibilityManager && (this.accessibilityManager.destroy(), this.accessibilityManager = void 0), this.cleanupEnhancedFeatures(), this.performanceManager.markPerformance("widget-close-end"), (t = (e = this.options).onClose) == null || t.call(e);
}));
}
toggle() {
this.isOpen ? this.close() : this.open();
}
destroy() {
this.performanceManager.markPerformance("widget-destroy"), this.cleanupWidget(), this.cleanup(), this.accessibilityManager && this.accessibilityManager.destroy(), this.triggerManager && this.triggerManager.destroy(), this.cleanupEnhancedFeatures(), this.performanceManager.destroy(), this.options.formId && m.instances.delete(this.options.formId);
}
updateOptions(e) {
this.options = { ...this.options, ...e }, this.validateOptions(), e.trigger && this.triggerManager && (this.triggerManager.destroy(), this.setupTriggers()), this.isOpen && (this.close(), setTimeout(() => this.open(), 100));
}
// Performance metrics access
getPerformanceMetrics() {
const e = this.performanceManager.getMetrics();
if (this.triggerManager) {
const t = this.triggerManager.getState();
e.sessionDuration = Date.now() - t.sessionStartTime, e.timeSinceLastActivity = Date.now(