UNPKG

@supportai.it/chat-widget

Version:

Chat widget web component for supportAI.it

493 lines (474 loc) 19.6 kB
import { defineComponent as d, ref as c, h as b } from "vue"; const r = '<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 535.62 369.81"><defs><linearGradient id="b" x1="218.11" y1="-120.74" x2="475.29" y2="-123.48" gradientTransform="translate(0 145.8) scale(1 -1)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ededed"/><stop offset="1" stop-color="#fafafa"/></linearGradient></defs><path d="M473.62,329.58c-.42,4.74-1.75,13.63-8.33,20.69-6.33,6.82-15.33,8.39-24.9,10.31-3.91.75-10.66,1.83-23.57,1.66-7.5-.08-18.99-.33-34.48-3.16-33.06-6.07-61.55-20.11-61.55-20.11-48.06-20.11-81.79-48.87-102.69-70.48,4.25-4.16,8.58-8.31,12.83-12.38.5-.42.92-.91,1.5-1.41.67-.67,1.42-1.33,2.17-2.08,14.41-13.63,28.73-26.51,42.98-36.9,16.24-10.89,32.4-21.78,48.56-32.66,4.33-2.83,8.66-5.9,13.08-8.81,3.25,4.74,6.75,9.22,10.66,13.55,32.65,35.9,85.45,48.2,103.61,50.78,2.58.42,9.41,1.25,14.58,6.4,6.41,6.32,6.83,15.71,6.91,21.19.67,26.26-.75,52.61-1.33,63.41Z" fill="url(#b)"/><path d="M525.99,19.32c-5.16,13.8-25.99,76.05-45.64,105.63-1.5,2.33-4.25,6.23-8.75,10.47-2.17,2.08-6.83,6.57-13.58,9.56-11.66,5.24-22.57,3.74-25.57,3.24-18.82-2.99-29.9-17.04-35.98-24.68-16.49-20.69-23.32-48.87-16.74-54.27,2.66-2.16,8.08-2.99,20.9,3.66,1.33.75,2.58,1.33,4,2.16,5.41,2.91,9.58,5.15,13.66,7.56.92.5,2.08,1.16,3.25,1.83.5-.33.92-.66,1.33-1,15.33-11.22,30.48-22.52,45.64-33.74,9.24-6.82,18.41-13.63,27.57-20.45,2.5-1.83,14.33-11.22,19.49-15.04,9.75-7.15,11.66-7.31,12.24-7.06h.08c2.17,1.41-.83,9.14-1.92,12.22v-.08Z" fill="#fff"/><path d="M339.2,174.25c-4.41,2.91-8.75,5.98-13.08,8.81-16.16,10.89-32.32,21.78-48.56,32.66-14.24,10.39-28.57,23.27-42.98,36.9-.75.75-1.5,1.41-2.17,2.08-.58.5-1,1-1.5,1.41-4.25,4.07-8.58,8.23-12.83,12.38-5.75,5.57-13.08,12.55-20.74,19.61-14.16,13.21-31.15,28.92-49.89,41.39-9.83,6.48-32.73,21.36-66.05,28.76-29.65,6.65-55.72,4.65-74.13,1.33,0-53.27-.17-106.55-.25-159.82.75.08,1.5.08,2.42.08,38.9,1.75,67.21-19.78,93.28-42.14,4.33-3.82,8.66-7.56,12.91-11.22,21.4-18.45,41.06-38.31,61.47-57.6,18.07-17.04,36.65-39.14,64.05-43.63.42-.08.75-.17,1.17-.08,1.58-.33,3.25-.5,4.83-.58,12.41-1.16,26.15.5,36.81,5.82,10.66,5.32,16.41,15.87,20.24,27.68,2.42,7.48,4,15.54,5.66,22.94,1.83,8.06,3.17,13.8,3.33,14.54,2.08,9.31,8.16,26.01,25.99,58.59v.08Z" fill="#fff"/></svg>', g = typeof window < "u" ? window.HTMLElement : class { }; let u = class extends g { constructor() { super(), this._isOpen = !1, this._isMobile = !1, this._fullChatUrl = "", this._baseUrl = "https://www.supportai.it", this._isFirstLoad = !0, this._firstChatOpen = !1, this._messageBubbleVisible = !0, this._cookieName = "chat-widget-opened", this._cookieExpDays = 7, this._chatModal = null, this._chatButton = null, this._messageContainer = null, this._connected = !1, this._lastRenderedState = { isOpen: !1, isFirstLoad: !0, firstChatOpen: !1, isMobile: !1, chatUrl: "" }, this._config = { chatId: "", apiKey: "", buttonColor: "#582975", buttonHoverColor: "#7b2ba6", buttonSize: "64px", messageBubble: "Ciao, sono l'assistente virtuale di <b>supportAI</b>, se hai bisogno di assistenza apri la chat!", chatAlign: "right", svgIcon: r, chatWidth: "400px", chatHeight: "650px", style: "" }, this.attachShadow({ mode: "open" }), this._firstChatOpen = this.getCookie(this._cookieName) === "true"; } setCookie(e, s, t) { const a = /* @__PURE__ */ new Date(); a.setTime(a.getTime() + t * 24 * 60 * 60 * 1e3); const o = `expires=${a.toUTCString()}`; document.cookie = `${e}=${s};${o};path=/`; } getCookie(e) { if (console.log(`Getting cookie: ${e}`), typeof document > "u") return ""; const s = document.cookie.split(";"); for (let t = 0; t < s.length; t++) { const a = s[t].trim(); if (a.startsWith(e + "=")) return a.substring(e.length + 1); } return ""; } updateChatUrl() { const e = new URL(`${this._baseUrl}/chat/${this._config.chatId}`); e.searchParams.set("apiKey", this._config.apiKey), e.searchParams.set("mobile", this._isMobile.toString()), this._fullChatUrl = e.toString(); } static get observedAttributes() { return [ "base-url", "chat-id", "api-key", "button-color", "button-hover-color", "button-size", "message-bubble", "chat-align", "svg-icon", "chat-width", "chat-height", "style" ]; } attributeChangedCallback(e, s, t) { if (s !== t) { switch (e) { case "base-url": this._baseUrl = t, this.updateChatUrl(); break; case "chat-id": this._config.chatId = t, this.updateChatUrl(); break; case "api-key": this._config.apiKey = t, this.updateChatUrl(); break; case "button-color": this._config.buttonColor = t; break; case "button-hover-color": this._config.buttonHoverColor = t; break; case "button-size": this._config.buttonSize = t; break; case "message-bubble": this._config.messageBubble = t === "false" ? !1 : t || "Ciao, sono l'assistente virtuale di supportAI, se hai bisogno di assistenza apri la chat!"; break; case "chat-align": this._config.chatAlign = t === "left" ? "left" : "right"; break; case "svg-icon": this._config.svgIcon = t || r; break; case "chat-width": this._config.chatWidth = t; break; case "chat-height": this._config.chatHeight = t; break; case "style": this._config.style = t; break; } this._connected && (this._chatModal = null, this.render()); } } /** * it's part of the Custom Elements API. * https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements * * Called once the element is inserted into the DOM */ connectedCallback() { this._connected = !0, this._isMobile = window.innerWidth <= 768, this.updateChatUrl(), this.render(), this.setupEventListeners(); } // Called once the element is removed from the DOM disconnectedCallback() { this.removeEventListeners(); } setupEventListeners() { this.handleResize = this.handleResize.bind(this), this.updateContext = this.updateContext.bind(this), this.handlePostMessage = this.handlePostMessage.bind(this), window.addEventListener("resize", this.handleResize), window.addEventListener("chat-widget/updateContext", this.updateContext), window.addEventListener("message", this.handlePostMessage); } removeEventListeners() { window.removeEventListener("resize", this.handleResize), window.removeEventListener("chat-widget/updateContext", this.updateContext), window.removeEventListener("message", this.handlePostMessage); } handleResize() { const e = this._isMobile; this._isMobile = window.innerWidth <= 768, e !== this._isMobile && (this.updateChatUrl(), this._chatModal && (this._isMobile ? this._chatModal.classList.add("mobile-fullscreen") : this._chatModal.classList.remove("mobile-fullscreen")), this.updateMessageBubble(), this._lastRenderedState.isMobile = this._isMobile, this._lastRenderedState.chatUrl = this._fullChatUrl); } toggleChat() { this._firstChatOpen || (this._firstChatOpen = !0, this.setBubbleCookie()), this._isOpen = !this._isOpen, this.updateChatState(); } closeBubble() { this.setBubbleCookie(), this._messageBubbleVisible = !1, this.updateMessageBubble(); } setBubbleCookie() { this.setCookie(this._cookieName, "true", this._cookieExpDays); } updateChatState() { this.shadowRoot && (this._chatModal && (this._chatModal.classList.toggle("chat-hidden", !this._isOpen), this._isMobile ? this._chatModal.classList.add("mobile-fullscreen") : this._chatModal.classList.remove("mobile-fullscreen")), this._chatButton && (this._chatButton.innerHTML = this._isOpen ? '<svg viewBox="0 0 24 24" fill="white"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>' : this._config.svgIcon, this._chatButton.setAttribute( "aria-label", this._isOpen ? "Close Chat" : "Open Chat" )), this.updateMessageBubble()); } updateMessageBubble() { if (!this._messageContainer) return; if (!(this._config.messageBubble && typeof this._config.messageBubble == "string" && !this._isMobile && !this._isOpen && !this._firstChatOpen && this._messageBubbleVisible)) { this._messageContainer.innerHTML = ""; return; } if (this._isFirstLoad) this._messageContainer.innerHTML = ` <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> `, setTimeout(() => { this._isFirstLoad = !1, this.updateMessageBubble(); }, 2500); else { this._messageContainer.innerHTML = ` <div class="message-bubble"> <button class="bubble-close" aria-label="Close message"> <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"> <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/> </svg> </button> <div class="bubble-content">${this._config.messageBubble}</div> </div> `; const s = this._messageContainer.querySelector(".bubble-close"); s && s.addEventListener("click", (t) => { t.stopPropagation(), this.closeBubble(); }); } } updateContext(e) { const s = this.shadowRoot?.getElementById("chat-iframe"); s && s.contentWindow?.postMessage( { type: "updateContext", context: e.detail, apiKey: this._config.apiKey }, new URL(this._baseUrl).origin ); } handlePostMessage(e) { const s = new URL(this._baseUrl).origin; e.origin === s && e.data?.namespace === "chat-widget/close" && this.closeChat(); } closeChat() { this._isOpen && (this._isOpen = !1, this.updateChatState()); } render() { const e = !this._chatModal || !this._chatButton || !this._messageContainer || this._lastRenderedState.isMobile !== this._isMobile || this._lastRenderedState.chatUrl !== this._fullChatUrl; if (!e && this._lastRenderedState.isOpen !== this._isOpen) { this.updateChatState(), this._lastRenderedState.isOpen = this._isOpen; return; } if (!e && (this._lastRenderedState.isFirstLoad !== this._isFirstLoad || this._lastRenderedState.firstChatOpen !== this._firstChatOpen)) { this.updateMessageBubble(), this._lastRenderedState.isFirstLoad = this._isFirstLoad, this._lastRenderedState.firstChatOpen = this._firstChatOpen; return; } if (!e) return; const s = ` <style> .chat-widget { position: fixed; bottom: 20px; ${this._config.chatAlign}: 20px; z-index: 9999; font-family: Arial, sans-serif; } .chat-button-container { position: relative; display: flex; flex-direction: column; align-items: ${this._config.chatAlign === "right" ? "flex-end" : "flex-start"}; } .message-bubble, .typing-indicator { background-color: white; border: 1px solid #e0e0e0; border-radius: ${this._config.chatAlign === "right" ? "15px 15px 0 15px" : "15px 15px 15px 0"}; padding: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-${this._config.chatAlign}: 40px; text-align: left; } .message-bubble { max-width: 250px; font-size: 14px; line-height: 1.4; padding-right: 20px; margin-right: 5px; } .bubble-close { position: absolute; top: 5px; right: 8px; background: none; border: none; cursor: pointer; padding: 2px; color: #666; border-radius: 50%; display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; transition: background-color 0.2s ease, color 0.2s ease; } .bubble-close:hover { background-color: #f0f0f0; color: #333; } .typing-indicator { display: flex; align-items: center; justify-content: center; } .typing-dot { background-color: #888; border-radius: 50%; width: 8px; height: 8px; margin: 0 4px; animation: typing 1.4s infinite ease-in-out; } .typing-dot:nth-child(1) { animation-delay: -0.32s; } .typing-dot:nth-child(2) { animation-delay: -0.16s; } @keyframes typing { 0%, 80%, 100% { opacity: 0.5; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } } .chat-button { border: none; border-radius: 50%; cursor: pointer; margin-top: 5px; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); transition: background-color 0.3s ease; background-color: ${this._config.buttonColor}; width: ${this._config.buttonSize}; height: ${this._config.buttonSize}; } .chat-button svg { width: 75%; height: 75%; } .chat-button:hover { background-color: ${this._config.buttonHoverColor}; } .chat-modal { position: fixed; bottom: 100px; ${this._config.chatAlign}: 20px; width: ${this._config.chatWidth}; height: ${this._config.chatHeight}; background: white; border-radius: 10px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); overflow: hidden; z-index: 10000; } .chat-modal.mobile-fullscreen { position: fixed; top: 0; left: 0; width: 100%; height: 100%; bottom: auto; right: auto; border-radius: 0; } .close-button { background: none; border: none; color: #333; font-size: 1.2rem; cursor: pointer; } #chat-iframe { width: 100%; height: 100%; border: none; } .chat-hidden { display: none; } </style> `, a = ` <div class="chat-button-container"> <div class="message-container">${this._config.messageBubble && typeof this._config.messageBubble == "string" && !this._isMobile && !this._isOpen && !this._firstChatOpen && this._messageBubbleVisible ? this._isFirstLoad ? ` <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> ` : ` <div class="message-bubble"> <button class="bubble-close" aria-label="Close message"> <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"> <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/> </svg> </button> <div class="bubble-content">${this._config.messageBubble}</div> </div> ` : ""}</div> <button class="chat-button" aria-label="${this._isOpen ? "Close Chat" : "Open Chat"}"> ${this._isOpen ? '<svg viewBox="0 0 24 24" fill="white"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>' : this._config.svgIcon} </button> </div> `, o = ` <div class="chat-modal ${this._isMobile ? "mobile-fullscreen" : ""} ${this._isOpen ? "" : "chat-hidden"}"> <iframe id="chat-iframe" src="${this._fullChatUrl}" frameborder="0" allow="microphone" > </iframe> </div> `; if (this.shadowRoot) { this.shadowRoot.innerHTML = ` ${s} <div class="chat-widget" style="${this._config.style}"> ${a} ${o} </div> `, this._chatButton = this.shadowRoot.querySelector(".chat-button"), this._chatModal = this.shadowRoot.querySelector(".chat-modal"), this._messageContainer = this.shadowRoot.querySelector(".message-container"), this._chatButton && this._chatButton.addEventListener("click", () => this.toggleChat()); const n = this.shadowRoot.querySelector(".close-button"); n && n.addEventListener("click", () => this.toggleChat()); const h = this.shadowRoot.querySelector(".bubble-close"); h && h.addEventListener("click", (l) => { l.stopPropagation(), this.closeBubble(); }), this._lastRenderedState = { isOpen: this._isOpen, isFirstLoad: this._isFirstLoad, firstChatOpen: this._firstChatOpen, isMobile: this._isMobile, chatUrl: this._fullChatUrl }, this._isFirstLoad && !this._isOpen && !this._isMobile && this.shadowRoot.querySelector(".typing-indicator") && setTimeout(() => { this._isFirstLoad = !1, this.updateMessageBubble(), this._lastRenderedState.isFirstLoad = !1; }, 2500); } } }; typeof window < "u" && window.customElements && customElements.define("chat-widget", u); const _ = d({ name: "ChatWidget", props: { chatId: { type: String, required: !0 }, apiKey: { type: String, required: !0 }, buttonColor: { type: String, default: "#582975" }, buttonHoverColor: { type: String, default: "#7b2ba6" }, buttonSize: { type: String, default: "64px" }, messageBubble: { type: [String, Boolean], default: "Ciao, sono l'assistente virtuale di supportAI, se hai bisogno di assistenza apri la chat!" }, chatAlign: { type: String, default: "right", validator: (i) => ["left", "right"].includes(i) }, svgIcon: { type: String }, chatWidth: { type: String, default: "400px" }, chatHeight: { type: String, default: "600px" }, baseUrl: { type: String } }, setup(i, { expose: e }) { const s = c(null); return e({ $el: s }), () => { const t = { "chat-id": i.chatId, "api-key": i.apiKey }; return i.buttonColor && (t["button-color"] = i.buttonColor), i.buttonHoverColor && (t["button-hover-color"] = i.buttonHoverColor), i.buttonSize && (t["button-size"] = i.buttonSize), i.messageBubble !== void 0 && (t["message-bubble"] = i.messageBubble.toString()), i.chatAlign && (t["chat-align"] = i.chatAlign), i.svgIcon && (t["svg-icon"] = i.svgIcon), i.chatWidth && (t["chat-width"] = i.chatWidth), i.chatHeight && (t["chat-height"] = i.chatHeight), i.baseUrl && (t["base-url"] = i.baseUrl), b("chat-widget", { ...t, ref: s }); }; } }), m = () => ({ updateContext: (e) => { window.dispatchEvent( new CustomEvent("chat-widget/updateContext", { detail: e }) ); } }); export { _ as ChatWidget, _ as default, m as useChatContext };