UNPKG

@feedal/embed

Version:

Feedal embed script to load feedback forms via JS or NPM

1,401 lines (1,395 loc) 80.4 kB
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(