dunefox-chatbot 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # dunefox-chatbot
2
2
 
3
- Official SDK for embedding the [Dunefox](https://dunefox.io) AI chatbot widget on any website.
3
+ Official SDK for embedding the [Dunefox AI chatbot](https://dunefox.io) widget on any website.
4
4
 
5
- Get your **Tenant ID** from **Console → Integrations**.
5
+ [![npm version](https://img.shields.io/npm/v/dunefox-chatbot)](https://www.npmjs.com/package/dunefox-chatbot)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Website](https://img.shields.io/badge/website-dunefox.io-blue)](https://dunefox.io)
8
+
9
+ **[Dunefox](https://dunefox.io)** is an AI-powered customer support platform that lets you build and deploy intelligent chatbots. Get your **Business ID** from the [Dunefox Console → Integrations](https://dunefox.io).
6
10
 
7
11
  ---
8
12
 
@@ -92,7 +96,7 @@ onBeforeUnmount(async () => {
92
96
 
93
97
  | Option | Type | Default | Description |
94
98
  |--------|------|---------|-------------|
95
- | `tenantId` | `string` | **required** | Your Dunefox tenant ID |
99
+ | `tenantId` | `string` | **required** | Your [Dunefox](https://dunefox.io) tenant ID |
96
100
  | `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Widget anchor position |
97
101
  | `defaultOpen` | `boolean` | `false` | Open the panel on load |
98
102
  | `baseUrl` | `string` | `https://app.dunefox.io` | Override for staging/self-hosted |
@@ -128,6 +132,19 @@ npm run build
128
132
 
129
133
  ---
130
134
 
135
+ ## About Dunefox
136
+
137
+ [Dunefox](https://dunefox.io) is a modern AI customer support platform for businesses of all sizes. With Dunefox you can:
138
+
139
+ - 🤖 Deploy an [AI chatbot](https://dunefox.io) on your website in minutes
140
+ - 💬 Manage conversations from a unified inbox
141
+ - 📊 Gain insights with real-time analytics
142
+ - 🔗 Integrate with your existing tools via webhooks and APIs
143
+
144
+ Learn more and get started for free at **[dunefox.io](https://dunefox.io)**.
145
+
146
+ ---
147
+
131
148
  ## License
132
149
 
133
150
  MIT © [Dunefox](https://dunefox.io)
@@ -1,4 +1,4 @@
1
- "use strict";var DunefoxChat=(()=>{var m=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var L=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of S(t))!D.call(e,a)&&a!==n&&m(e,a,{get:()=>t[a],enumerable:!(i=I(t,a))||i.enumerable});return e};var C=e=>O(m({},"__esModule",{value:!0}),e);var T={};L(T,{close:()=>v,destroy:()=>E,init:()=>b,open:()=>y});var B="https://app.dunefox.io",U="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",g="dunefox_chat_uuid";function $(){var e,t;try{let n=localStorage.getItem(g);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(g,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function k(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),i=e.endsWith("left")?"left:16px":"right:16px",p=`
1
+ "use strict";var DunefoxChat=(()=>{var m=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var O=(e,t)=>{for(var n in t)m(e,n,{get:t[n],enumerable:!0})},D=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of S(t))!L.call(e,a)&&a!==n&&m(e,a,{get:()=>t[a],enumerable:!(i=I(t,a))||i.enumerable});return e};var C=e=>D(m({},"__esModule",{value:!0}),e);var T={};O(T,{close:()=>v,destroy:()=>w,init:()=>g,open:()=>y});var B="https://app.dunefox.io",U="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",b="dunefox_chat_uuid";function k(){var e,t;try{let n=localStorage.getItem(b);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(b,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function $(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),i=e.endsWith("left")?"left:16px":"right:16px",p=`
2
2
  #dunefox-btn {
3
3
  position: fixed; ${t?"top:16px":"bottom:16px"}; ${i};
4
4
  width: 60px; height: 60px; border-radius: 50%;
@@ -22,9 +22,14 @@
22
22
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
23
  }
24
24
  @media (max-width: 768px) {
25
- #dunefox-btn {
25
+ /* Only move button to top-right when chat is open on mobile */
26
+ body.df-chat-open #dunefox-btn {
26
27
  bottom: unset !important; top: 16px !important;
27
28
  right: 16px !important; left: unset !important;
29
+ /* Strip the circular button style \u2014 show just the X icon */
30
+ background: transparent !important;
31
+ box-shadow: none !important;
32
+ width: 36px !important; height: 36px !important;
28
33
  }
29
34
  #dunefox-frame {
30
35
  width: 100% !important; height: 100% !important; max-height: none !important;
@@ -34,4 +39,4 @@
34
39
  }
35
40
  #dunefox-frame.df-open { transform: translateY(0) !important; }
36
41
  }
37
- `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=p,document.head.appendChild(o)}var A='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',u=!1;function b(e){let t=typeof e=="string"?{tenantId:e}:e;if(u){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:i="bottom-right",defaultOpen:a=!1,baseUrl:f=B,iconUrl:x=U}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(l=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",l,{once:!0}):l())(()=>{k(i);let l=$(),p=`${f}/api/${n}?uuid=${l}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=p;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let s=document.createElement("img");s.src=x,s.alt="",s.width=60,s.height=60,s.style.cssText="display:block;border-radius:50%;object-fit:cover;";let c=document.createElement("span");c.innerHTML=A,c.style.display="none",r.append(s,c);let d=a,h=()=>{o.classList.toggle("df-open",d),s.style.display=d?"none":"block",c.style.display=d?"block":"none",r.setAttribute("aria-expanded",String(d))};r.addEventListener("click",()=>{d=!d,h()}),document.body.append(o,r),h(),u=!0})}function y(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"))}function v(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"))}function E(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),u=!1}return C(T);})();
42
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=p,document.head.appendChild(o)}var A='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',u=!1;function g(e){let t=typeof e=="string"?{tenantId:e}:e;if(u){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:i="bottom-right",defaultOpen:a=!1,baseUrl:f=B,iconUrl:x=U}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(l=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",l,{once:!0}):l())(()=>{$(i);let l=k(),p=`${f}/api/${n}?uuid=${l}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=p;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let s=document.createElement("img");s.src=x,s.alt="",s.width=60,s.height=60,s.style.cssText="display:block;border-radius:50%;object-fit:cover;";let c=document.createElement("span");c.innerHTML=A,c.style.display="none",r.append(s,c);let d=a,h=()=>{o.classList.toggle("df-open",d),document.body.classList.toggle("df-chat-open",d),s.style.display=d?"none":"block",c.style.display=d?"block":"none",r.setAttribute("aria-expanded",String(d))};r.addEventListener("click",()=>{d=!d,h()}),document.body.append(o,r),h(),u=!0})}function y(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"))}function v(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"))}function w(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),document.body.classList.remove("df-chat-open"),u=!1}return C(T);})();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var b="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function v(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function E(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
1
+ 'use strict';var g="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function v(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function w(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
2
2
  #dunefox-btn {
3
3
  position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
4
  width: 60px; height: 60px; border-radius: 50%;
@@ -22,9 +22,14 @@
22
22
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
23
  }
24
24
  @media (max-width: 768px) {
25
- #dunefox-btn {
25
+ /* Only move button to top-right when chat is open on mobile */
26
+ body.df-chat-open #dunefox-btn {
26
27
  bottom: unset !important; top: 16px !important;
27
28
  right: 16px !important; left: unset !important;
29
+ /* Strip the circular button style \u2014 show just the X icon */
30
+ background: transparent !important;
31
+ box-shadow: none !important;
32
+ width: 36px !important; height: 36px !important;
28
33
  }
29
34
  #dunefox-frame {
30
35
  width: 100% !important; height: 100% !important; max-height: none !important;
@@ -34,6 +39,6 @@
34
39
  }
35
40
  #dunefox-frame.df-open { transform: translateY(0) !important; }
36
41
  }
37
- `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var w='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:m=false,baseUrl:u=b,iconUrl:f=y}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{E(d);let s=v(),c=`${u}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let a=document.createElement("button");a.id="dunefox-btn",a.setAttribute("aria-label","Open chat"),a.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=w,l.style.display="none",a.append(i,l);let r=m,x=()=>{o.classList.toggle("df-open",r),i.style.display=r?"none":"block",l.style.display=r?"block":"none",a.setAttribute("aria-expanded",String(r));};a.addEventListener("click",()=>{r=!r,x();}),document.body.append(o,a),x(),p=true;});}function S(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"));}function D(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"));}function O(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),p=false;}
38
- exports.close=D;exports.destroy=O;exports.init=I;exports.open=S;//# sourceMappingURL=index.js.map
42
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var E='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:m=false,baseUrl:u=g,iconUrl:f=y}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{w(d);let s=v(),c=`${u}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let a=document.createElement("button");a.id="dunefox-btn",a.setAttribute("aria-label","Open chat"),a.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=E,l.style.display="none",a.append(i,l);let r=m,x=()=>{o.classList.toggle("df-open",r),document.body.classList.toggle("df-chat-open",r),i.style.display=r?"none":"block",l.style.display=r?"block":"none",a.setAttribute("aria-expanded",String(r));};a.addEventListener("click",()=>{r=!r,x();}),document.body.append(o,a),x(),p=true;});}function S(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"));}function L(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"));}function O(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),document.body.classList.remove("df-chat-open"),p=false;}
43
+ exports.close=L;exports.destroy=O;exports.init=I;exports.open=S;//# sourceMappingURL=index.js.map
39
44
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","close","destroy","_c"],"mappings":"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","file":"index.js","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n _initialised = false;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/core.ts"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","close","destroy","_c"],"mappings":"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","file":"index.js","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n /* Only move button to top-right when chat is open on mobile */\r\n body.df-chat-open #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n /* Strip the circular button style — show just the X icon */\r\n background: transparent !important;\r\n box-shadow: none !important;\r\n width: 36px !important; height: 36px !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n // Toggle body class so mobile CSS can move button to top-right only when open\r\n document.body.classList.toggle('df-chat-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n document.body.classList.remove('df-chat-open');\r\n _initialised = false;\r\n}\r\n"]}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var b="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function v(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function E(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
1
+ var g="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function v(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function w(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
2
2
  #dunefox-btn {
3
3
  position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
4
  width: 60px; height: 60px; border-radius: 50%;
@@ -22,9 +22,14 @@ var b="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/c
22
22
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
23
  }
24
24
  @media (max-width: 768px) {
25
- #dunefox-btn {
25
+ /* Only move button to top-right when chat is open on mobile */
26
+ body.df-chat-open #dunefox-btn {
26
27
  bottom: unset !important; top: 16px !important;
27
28
  right: 16px !important; left: unset !important;
29
+ /* Strip the circular button style \u2014 show just the X icon */
30
+ background: transparent !important;
31
+ box-shadow: none !important;
32
+ width: 36px !important; height: 36px !important;
28
33
  }
29
34
  #dunefox-frame {
30
35
  width: 100% !important; height: 100% !important; max-height: none !important;
@@ -34,6 +39,6 @@ var b="https://app.dunefox.io",y="https://dunefoxx.s3.ap-south-1.amazonaws.com/c
34
39
  }
35
40
  #dunefox-frame.df-open { transform: translateY(0) !important; }
36
41
  }
37
- `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var w='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:m=false,baseUrl:u=b,iconUrl:f=y}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{E(d);let s=v(),c=`${u}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let a=document.createElement("button");a.id="dunefox-btn",a.setAttribute("aria-label","Open chat"),a.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=w,l.style.display="none",a.append(i,l);let r=m,x=()=>{o.classList.toggle("df-open",r),i.style.display=r?"none":"block",l.style.display=r?"block":"none",a.setAttribute("aria-expanded",String(r));};a.addEventListener("click",()=>{r=!r,x();}),document.body.append(o,a),x(),p=true;});}function S(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"));}function D(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"));}function O(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),p=false;}
38
- export{D as close,O as destroy,I as init,S as open};//# sourceMappingURL=index.mjs.map
42
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var E='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:m=false,baseUrl:u=g,iconUrl:f=y}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{w(d);let s=v(),c=`${u}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let a=document.createElement("button");a.id="dunefox-btn",a.setAttribute("aria-label","Open chat"),a.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=E,l.style.display="none",a.append(i,l);let r=m,x=()=>{o.classList.toggle("df-open",r),document.body.classList.toggle("df-chat-open",r),i.style.display=r?"none":"block",l.style.display=r?"block":"none",a.setAttribute("aria-expanded",String(r));};a.addEventListener("click",()=>{r=!r,x();}),document.body.append(o,a),x(),p=true;});}function S(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.add("df-open"),t.setAttribute("aria-expanded","true"));}function L(){let e=document.getElementById("dunefox-frame"),t=document.getElementById("dunefox-btn");!e||!t||(e.classList.remove("df-open"),t.setAttribute("aria-expanded","false"));}function O(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),document.body.classList.remove("df-chat-open"),p=false;}
43
+ export{L as close,O as destroy,I as init,S as open};//# sourceMappingURL=index.mjs.map
39
44
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","close","destroy","_c"],"mappings":"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","file":"index.mjs","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n _initialised = false;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/core.ts"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","close","destroy","_c"],"mappings":"AAiBA,IAAMA,CAAAA,CAAe,yBACfC,CAAAA,CAAe,0DAAA,CACfC,EAAW,mBAAA,CAGjB,SAASC,GAA0B,CAtBnC,IAAAC,EAAAC,CAAAA,CAuBE,GAAI,CACF,IAAIC,CAAAA,CAAK,aAAa,OAAA,CAAQJ,CAAQ,CAAA,CACtC,OAAKI,CAAAA,GACHA,CAAAA,CAAAA,CAAKD,GAAAD,CAAAA,CAAA,MAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAA,MAAA,CAAQ,aAAR,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAA,oBAAAC,CAAAA,CACA,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,CAAI,IAAA,CAAK,KAAI,CAAE,QAAA,CAAS,EAAE,CAAA,CACjE,YAAA,CAAa,OAAA,CAAQH,EAAUI,CAAE,CAAA,CAAA,CAE5BA,CACT,CAAA,MAAQC,CAAAA,CAAA,CAEN,OAAO,IAAA,CAAK,QAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,CAAI,IAAA,CAAK,KAAI,CAAE,QAAA,CAAS,EAAE,CACrE,CACF,CAGA,SAASC,CAAAA,CAAaC,CAAAA,CAA2E,CAC/F,GAAI,QAAA,CAAS,eAAe,gBAAgB,CAAA,CAAG,OAE/C,IAAMC,CAAAA,CAAUD,EAAS,UAAA,CAAW,KAAK,EAEnCE,CAAAA,CADUF,CAAAA,CAAS,SAAS,MAAM,CAAA,CACd,WAAA,CAAgB,YAAA,CAWpCG,CAAAA,CAAM;AAAA;AAAA,uBAAA,EAVIF,CAAAA,CAAU,UAAA,CAAgB,aAYf,CAAA,EAAA,EAAKC,CAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAAA,EANdD,CAAAA,CAAQ,8BAAgC,8BAcb,CAAA;AAAA;AAAA,uBAAA,EAlBlCA,CAAAA,CAAU,UAAA,CAAgB,aAoBb,CAAA,EAAA,EAAKC,CAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAA,EAlBtBD,CAAAA,CAAS,+BAAiC,6BAuBlC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAA,EAnBDA,CAAAA,CAAQ,oBAAsB,kBAuCrB,CAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA,CAK3BG,EAAM,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA,CAC1CA,EAAI,EAAA,CAAK,gBAAA,CACTA,CAAAA,CAAI,WAAA,CAAcD,EAClB,QAAA,CAAS,IAAA,CAAK,YAAYC,CAAG,EAC/B,CAGA,IAAMC,CAAAA,CAAY,kOAAA,CAGdC,CAAAA,CAAe,MAUZ,SAASC,CAAAA,CAAKC,CAAAA,CAA4C,CAE/D,IAAMC,CAAAA,CACJ,OAAOD,CAAAA,EAAY,QAAA,CAAW,CAAE,QAAA,CAAUA,CAAQ,EAAIA,CAAAA,CAExD,GAAIF,EAAc,CAChB,OAAA,CAAQ,IAAA,CAAK,0DAA0D,EACvE,MACF,CAEA,GAAM,CACJ,SAAAI,CAAAA,CACA,QAAA,CAAAV,CAAAA,CAAW,cAAA,CACX,YAAAW,CAAAA,CAAc,KAAA,CACd,QAAAC,CAAAA,CAAUrB,CAAAA,CACV,QAAAsB,CAAAA,CAAUrB,CACZ,CAAA,CAAIiB,CAAAA,CAEJ,GAAI,CAACC,CAAAA,CAAU,MAAM,IAAI,KAAA,CAAM,qCAAqC,CAAA,CAAA,CAGrDI,CAAAA,EACb,QAAA,CAAS,UAAA,GAAe,UACpB,QAAA,CAAS,gBAAA,CAAiB,mBAAoBA,CAAAA,CAAI,CAAE,KAAM,IAAK,CAAC,CAAA,CAChEA,CAAAA,IAEA,IAAM,CACVf,CAAAA,CAAaC,CAAQ,EAErB,IAAMe,CAAAA,CAAOrB,CAAAA,EAAgB,CACvBsB,EAAM,CAAA,EAAGJ,CAAO,QAAQF,CAAQ,CAAA,MAAA,EAASK,CAAI,CAAA,CAAA,CAG7CE,CAAAA,CAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA,CAC9CA,CAAAA,CAAO,GAAK,eAAA,CACZA,CAAAA,CAAO,MAAQ,cAAA,CACfA,CAAAA,CAAO,OAAA,CAAU,MAAA,CACjBA,EAAO,GAAA,CAAMD,CAAAA,CAGb,IAAME,CAAAA,CAAM,QAAA,CAAS,cAAc,QAAQ,CAAA,CAC3CA,CAAAA,CAAI,EAAA,CAAK,cACTA,CAAAA,CAAI,YAAA,CAAa,YAAA,CAAc,WAAW,EAC1CA,CAAAA,CAAI,YAAA,CAAa,eAAA,CAAiB,OAAO,EAEzC,IAAMC,CAAAA,CAAM,SAAS,aAAA,CAAc,KAAK,EACxCA,CAAAA,CAAI,GAAA,CAAMN,CAAAA,CACVM,CAAAA,CAAI,IAAM,EAAA,CACVA,CAAAA,CAAI,MAAQ,EAAA,CACZA,CAAAA,CAAI,OAAS,EAAA,CACbA,CAAAA,CAAI,KAAA,CAAM,OAAA,CAAU,oDAEpB,IAAMC,CAAAA,CAAY,SAAS,aAAA,CAAc,MAAM,EAC/CA,CAAAA,CAAU,SAAA,CAAYf,CAAAA,CACtBe,CAAAA,CAAU,MAAM,OAAA,CAAU,MAAA,CAE1BF,CAAAA,CAAI,MAAA,CAAOC,EAAKC,CAAS,CAAA,CAGzB,IAAIC,CAAAA,CAAOV,EACLW,CAAAA,CAAa,IAAM,CACvBL,CAAAA,CAAO,SAAA,CAAU,OAAO,SAAA,CAAWI,CAAI,CAAA,CAEvC,QAAA,CAAS,KAAK,SAAA,CAAU,MAAA,CAAO,eAAgBA,CAAI,CAAA,CACnDF,EAAI,KAAA,CAAM,OAAA,CAAUE,CAAAA,CAAO,MAAA,CAAS,QACpCD,CAAAA,CAAU,KAAA,CAAM,QAAUC,CAAAA,CAAO,OAAA,CAAU,OAC3CH,CAAAA,CAAI,YAAA,CAAa,eAAA,CAAiB,MAAA,CAAOG,CAAI,CAAC,EAChD,CAAA,CAEAH,CAAAA,CAAI,iBAAiB,OAAA,CAAS,IAAM,CAClCG,CAAAA,CAAO,CAACA,CAAAA,CACRC,CAAAA,GACF,CAAC,CAAA,CAED,SAAS,IAAA,CAAK,MAAA,CAAOL,CAAAA,CAAQC,CAAG,EAChCI,CAAAA,EAAW,CACXhB,EAAe,KACjB,CAAC,EACH,CAKO,SAASe,CAAAA,EAAa,CAC3B,IAAMJ,CAAAA,CAAS,QAAA,CAAS,eAAe,eAAe,CAAA,CAChDC,EAAM,QAAA,CAAS,cAAA,CAAe,aAAa,CAAA,CAC7C,CAACD,CAAAA,EAAU,CAACC,CAAAA,GAChBD,CAAAA,CAAO,UAAU,GAAA,CAAI,SAAS,CAAA,CAC9BC,CAAAA,CAAI,aAAa,eAAA,CAAiB,MAAM,GAC1C,CAKO,SAASK,GAAc,CAC5B,IAAMN,CAAAA,CAAS,QAAA,CAAS,eAAe,eAAe,CAAA,CAChDC,EAAM,QAAA,CAAS,cAAA,CAAe,aAAa,CAAA,CAC7C,CAACD,CAAAA,EAAU,CAACC,IAChBD,CAAAA,CAAO,SAAA,CAAU,OAAO,SAAS,CAAA,CACjCC,EAAI,YAAA,CAAa,eAAA,CAAiB,OAAO,CAAA,EAC3C,CAKO,SAASM,CAAAA,EAAgB,CA7NhC,IAAA7B,EAAAC,CAAAA,CAAA6B,CAAAA,CAAAA,CA8NE9B,CAAAA,CAAA,QAAA,CAAS,eAAe,eAAe,CAAA,GAAvC,MAAAA,CAAAA,CAA0C,MAAA,EAAA,CAAA,CAC1CC,EAAA,QAAA,CAAS,cAAA,CAAe,aAAa,CAAA,GAArC,MAAAA,CAAAA,CAAwC,MAAA,EAAA,CAAA,CACxC6B,EAAA,QAAA,CAAS,cAAA,CAAe,gBAAgB,CAAA,GAAxC,IAAA,EAAAA,CAAAA,CAA2C,MAAA,EAAA,CAC3C,SAAS,IAAA,CAAK,SAAA,CAAU,OAAO,cAAc,CAAA,CAC7CnB,EAAe,MACjB","file":"index.mjs","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n /* Only move button to top-right when chat is open on mobile */\r\n body.df-chat-open #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n /* Strip the circular button style — show just the X icon */\r\n background: transparent !important;\r\n box-shadow: none !important;\r\n width: 36px !important; height: 36px !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n // Toggle body class so mobile CSS can move button to top-right only when open\r\n document.body.classList.toggle('df-chat-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n document.body.classList.remove('df-chat-open');\r\n _initialised = false;\r\n}\r\n"]}
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var react=require('react');var v="https://app.dunefox.io",E="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function w(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function I(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
1
+ 'use strict';var react=require('react');var v="https://app.dunefox.io",w="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function E(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function I(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",p=`
2
2
  #dunefox-btn {
3
3
  position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
4
  width: 60px; height: 60px; border-radius: 50%;
@@ -22,9 +22,14 @@
22
22
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
23
  }
24
24
  @media (max-width: 768px) {
25
- #dunefox-btn {
25
+ /* Only move button to top-right when chat is open on mobile */
26
+ body.df-chat-open #dunefox-btn {
26
27
  bottom: unset !important; top: 16px !important;
27
28
  right: 16px !important; left: unset !important;
29
+ /* Strip the circular button style \u2014 show just the X icon */
30
+ background: transparent !important;
31
+ box-shadow: none !important;
32
+ width: 36px !important; height: 36px !important;
28
33
  }
29
34
  #dunefox-frame {
30
35
  width: 100% !important; height: 100% !important; max-height: none !important;
@@ -34,6 +39,6 @@
34
39
  }
35
40
  #dunefox-frame.df-open { transform: translateY(0) !important; }
36
41
  }
37
- `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var D='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function b(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:u=false,baseUrl:f=v,iconUrl:m=E}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{I(d);let s=w(),c=`${f}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=m,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=D,l.style.display="none",r.append(i,l);let a=u,x=()=>{o.classList.toggle("df-open",a),i.style.display=a?"none":"block",l.style.display=a?"block":"none",r.setAttribute("aria-expanded",String(a));};r.addEventListener("click",()=>{a=!a,x();}),document.body.append(o,r),x(),p=true;});}function g(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),p=false;}function U(e){let t=react.useRef(e);return react.useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
42
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=p,document.head.appendChild(o);}var D='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',c=false;function b(e){let t=typeof e=="string"?{tenantId:e}:e;if(c){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:u=false,baseUrl:m=v,iconUrl:f=w}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{I(d);let s=E(),p=`${m}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=p;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=D,l.style.display="none",r.append(i,l);let a=u,x=()=>{o.classList.toggle("df-open",a),document.body.classList.toggle("df-chat-open",a),i.style.display=a?"none":"block",l.style.display=a?"block":"none",r.setAttribute("aria-expanded",String(a));};r.addEventListener("click",()=>{a=!a,x();}),document.body.append(o,r),x(),c=true;});}function g(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),document.body.classList.remove("df-chat-open"),c=false;}function U(e){let t=react.useRef(e);return react.useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
38
43
  exports.DunefoxChatbot=U;//# sourceMappingURL=react.js.map
39
44
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/react.tsx"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","destroy","_c","DunefoxChatbot","props","optsRef","useRef","useEffect"],"mappings":"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","file":"react.js","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n _initialised = false;\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { init, destroy } from './core';\nimport type { DunefoxChatOptions } from './core';\n\nexport interface DunefoxChatbotProps extends DunefoxChatOptions {}\n\n/**\n * React component wrapper for the Dunefox Chatbot widget.\n *\n * Drop it anywhere in your component tree — typically at the root layout level.\n * SSR-safe: the widget is only mounted in the browser via useEffect.\n *\n * @example\n * // Next.js App Router (app/layout.tsx)\n * import { DunefoxChatbot } from 'dunefox-chatbot/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * {children}\n * <DunefoxChatbot tenantId=\"YOUR_TENANT_ID\" />\n * </body></html>\n * );\n * }\n */\nexport function DunefoxChatbot(props: DunefoxChatbotProps) {\n // Capture props at mount time — we intentionally don't re-init on prop changes\n const optsRef = useRef(props);\n\n useEffect(() => {\n // Prevent running during SSR (window check is a safety net)\n if (typeof window === 'undefined') return;\n\n init(optsRef.current);\n\n return () => {\n destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // This component renders nothing to the React tree — the widget is DOM-injected\n return null;\n}\n"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/react.tsx"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","destroy","_c","DunefoxChatbot","props","optsRef","useRef","useEffect"],"mappings":"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","file":"react.js","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n /* Only move button to top-right when chat is open on mobile */\r\n body.df-chat-open #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n /* Strip the circular button style — show just the X icon */\r\n background: transparent !important;\r\n box-shadow: none !important;\r\n width: 36px !important; height: 36px !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n // Toggle body class so mobile CSS can move button to top-right only when open\r\n document.body.classList.toggle('df-chat-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n document.body.classList.remove('df-chat-open');\r\n _initialised = false;\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { init, destroy } from './core';\nimport type { DunefoxChatOptions } from './core';\n\nexport interface DunefoxChatbotProps extends DunefoxChatOptions {}\n\n/**\n * React component wrapper for the Dunefox Chatbot widget.\n *\n * Drop it anywhere in your component tree — typically at the root layout level.\n * SSR-safe: the widget is only mounted in the browser via useEffect.\n *\n * @example\n * // Next.js App Router (app/layout.tsx)\n * import { DunefoxChatbot } from 'dunefox-chatbot/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * {children}\n * <DunefoxChatbot tenantId=\"YOUR_TENANT_ID\" />\n * </body></html>\n * );\n * }\n */\nexport function DunefoxChatbot(props: DunefoxChatbotProps) {\n // Capture props at mount time — we intentionally don't re-init on prop changes\n const optsRef = useRef(props);\n\n useEffect(() => {\n // Prevent running during SSR (window check is a safety net)\n if (typeof window === 'undefined') return;\n\n init(optsRef.current);\n\n return () => {\n destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // This component renders nothing to the React tree — the widget is DOM-injected\n return null;\n}\n"]}
package/dist/react.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import {useRef,useEffect}from'react';var v="https://app.dunefox.io",E="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function w(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function I(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",c=`
1
+ import {useRef,useEffect}from'react';var v="https://app.dunefox.io",w="https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png",h="dunefox_chat_uuid";function E(){var e,t;try{let n=localStorage.getItem(h);return n||(n=(t=(e=crypto==null?void 0:crypto.randomUUID)==null?void 0:e.call(crypto))!=null?t:Math.random().toString(36).slice(2)+Date.now().toString(36),localStorage.setItem(h,n)),n}catch(n){return Math.random().toString(36).slice(2)+Date.now().toString(36)}}function I(e){if(document.getElementById("dunefox-styles"))return;let t=e.startsWith("top"),d=e.endsWith("left")?"left:16px":"right:16px",p=`
2
2
  #dunefox-btn {
3
3
  position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
4
  width: 60px; height: 60px; border-radius: 50%;
@@ -22,9 +22,14 @@ import {useRef,useEffect}from'react';var v="https://app.dunefox.io",E="https://d
22
22
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
23
  }
24
24
  @media (max-width: 768px) {
25
- #dunefox-btn {
25
+ /* Only move button to top-right when chat is open on mobile */
26
+ body.df-chat-open #dunefox-btn {
26
27
  bottom: unset !important; top: 16px !important;
27
28
  right: 16px !important; left: unset !important;
29
+ /* Strip the circular button style \u2014 show just the X icon */
30
+ background: transparent !important;
31
+ box-shadow: none !important;
32
+ width: 36px !important; height: 36px !important;
28
33
  }
29
34
  #dunefox-frame {
30
35
  width: 100% !important; height: 100% !important; max-height: none !important;
@@ -34,6 +39,6 @@ import {useRef,useEffect}from'react';var v="https://app.dunefox.io",E="https://d
34
39
  }
35
40
  #dunefox-frame.df-open { transform: translateY(0) !important; }
36
41
  }
37
- `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=c,document.head.appendChild(o);}var D='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',p=false;function b(e){let t=typeof e=="string"?{tenantId:e}:e;if(p){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:u=false,baseUrl:f=v,iconUrl:m=E}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{I(d);let s=w(),c=`${f}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=c;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=m,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=D,l.style.display="none",r.append(i,l);let a=u,x=()=>{o.classList.toggle("df-open",a),i.style.display=a?"none":"block",l.style.display=a?"block":"none",r.setAttribute("aria-expanded",String(a));};r.addEventListener("click",()=>{a=!a,x();}),document.body.append(o,r),x(),p=true;});}function g(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),p=false;}function U(e){let t=useRef(e);return useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
42
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=p,document.head.appendChild(o);}var D='<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',c=false;function b(e){let t=typeof e=="string"?{tenantId:e}:e;if(c){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:u=false,baseUrl:m=v,iconUrl:f=w}=t;if(!n)throw new Error("[DunefoxChat] tenantId is required.");(s=>document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s,{once:true}):s())(()=>{I(d);let s=E(),p=`${m}/api/${n}?uuid=${s}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=p;let r=document.createElement("button");r.id="dunefox-btn",r.setAttribute("aria-label","Open chat"),r.setAttribute("aria-expanded","false");let i=document.createElement("img");i.src=f,i.alt="",i.width=60,i.height=60,i.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=D,l.style.display="none",r.append(i,l);let a=u,x=()=>{o.classList.toggle("df-open",a),document.body.classList.toggle("df-chat-open",a),i.style.display=a?"none":"block",l.style.display=a?"block":"none",r.setAttribute("aria-expanded",String(a));};r.addEventListener("click",()=>{a=!a,x();}),document.body.append(o,r),x(),c=true;});}function g(){var e,t,n;(e=document.getElementById("dunefox-frame"))==null||e.remove(),(t=document.getElementById("dunefox-btn"))==null||t.remove(),(n=document.getElementById("dunefox-styles"))==null||n.remove(),document.body.classList.remove("df-chat-open"),c=false;}function U(e){let t=useRef(e);return useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
38
43
  export{U as DunefoxChatbot};//# sourceMappingURL=react.mjs.map
39
44
  //# sourceMappingURL=react.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/react.tsx"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","destroy","_c","DunefoxChatbot","props","optsRef","useRef","useEffect"],"mappings":"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","file":"react.mjs","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n _initialised = false;\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { init, destroy } from './core';\nimport type { DunefoxChatOptions } from './core';\n\nexport interface DunefoxChatbotProps extends DunefoxChatOptions {}\n\n/**\n * React component wrapper for the Dunefox Chatbot widget.\n *\n * Drop it anywhere in your component tree — typically at the root layout level.\n * SSR-safe: the widget is only mounted in the browser via useEffect.\n *\n * @example\n * // Next.js App Router (app/layout.tsx)\n * import { DunefoxChatbot } from 'dunefox-chatbot/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * {children}\n * <DunefoxChatbot tenantId=\"YOUR_TENANT_ID\" />\n * </body></html>\n * );\n * }\n */\nexport function DunefoxChatbot(props: DunefoxChatbotProps) {\n // Capture props at mount time — we intentionally don't re-init on prop changes\n const optsRef = useRef(props);\n\n useEffect(() => {\n // Prevent running during SSR (window check is a safety net)\n if (typeof window === 'undefined') return;\n\n init(optsRef.current);\n\n return () => {\n destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // This component renders nothing to the React tree — the widget is DOM-injected\n return null;\n}\n"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/react.tsx"],"names":["DEFAULT_BASE","DEFAULT_ICON","UUID_KEY","getOrCreateUUID","_a","_b","id","e","injectStyles","position","isTop","hSide","css","tag","CLOSE_SVG","_initialised","init","options","opts","tenantId","defaultOpen","baseUrl","iconUrl","cb","uuid","src","iframe","btn","img","closeSpan","open","applyState","destroy","_c","DunefoxChatbot","props","optsRef","useRef","useEffect"],"mappings":"qCAiBA,IAAMA,CAAAA,CAAe,wBAAA,CACfC,CAAAA,CAAe,0DAAA,CACfC,CAAAA,CAAW,oBAGjB,SAASC,CAAAA,EAA0B,CAtBnC,IAAAC,CAAAA,CAAAC,EAuBE,GAAI,CACF,IAAIC,CAAAA,CAAK,YAAA,CAAa,OAAA,CAAQJ,CAAQ,CAAA,CACtC,OAAKI,IACHA,CAAAA,CAAAA,CAAKD,CAAAA,CAAAA,CAAAD,EAAA,MAAA,EAAA,IAAA,CAAA,KAAA,CAAA,CAAA,MAAA,CAAQ,UAAA,GAAR,IAAA,CAAA,KAAA,CAAA,CAAAA,CAAAA,CAAA,IAAA,CAAA,MAAA,CAAA,GAAA,IAAA,CAAAC,CAAAA,CACA,KAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,CAAI,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,EACjE,YAAA,CAAa,OAAA,CAAQH,EAAUI,CAAE,CAAA,CAAA,CAE5BA,CACT,CAAA,MAAQC,CAAAA,CAAA,CAEN,OAAO,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAC,EAAI,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CACrE,CACF,CAGA,SAASC,EAAaC,CAAAA,CAA2E,CAC/F,GAAI,QAAA,CAAS,cAAA,CAAe,gBAAgB,CAAA,CAAG,OAE/C,IAAMC,EAAUD,CAAAA,CAAS,UAAA,CAAW,KAAK,CAAA,CAEnCE,CAAAA,CADUF,CAAAA,CAAS,SAAS,MAAM,CAAA,CACd,WAAA,CAAgB,YAAA,CAWpCG,CAAAA,CAAM;AAAA;AAAA,uBAAA,EAVIF,CAAAA,CAAU,UAAA,CAAgB,aAYf,CAAA,EAAA,EAAKC,CAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAAA,EANdD,CAAAA,CAAQ,8BAAgC,8BAcb,CAAA;AAAA;AAAA,uBAAA,EAlBlCA,CAAAA,CAAU,UAAA,CAAgB,aAoBb,CAAA,EAAA,EAAKC,CAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAA,EAlBtBD,CAAAA,CAAS,+BAAiC,6BAuBlC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAA,EAnBDA,CAAAA,CAAQ,oBAAsB,kBAuCrB,CAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA,CAK3BG,CAAAA,CAAM,QAAA,CAAS,aAAA,CAAc,OAAO,EAC1CA,CAAAA,CAAI,EAAA,CAAK,gBAAA,CACTA,CAAAA,CAAI,YAAcD,CAAAA,CAClB,QAAA,CAAS,IAAA,CAAK,WAAA,CAAYC,CAAG,EAC/B,CAGA,IAAMC,CAAAA,CAAY,mOAGdC,CAAAA,CAAe,KAAA,CAUZ,SAASC,CAAAA,CAAKC,EAA4C,CAE/D,IAAMC,CAAAA,CACJ,OAAOD,GAAY,QAAA,CAAW,CAAE,QAAA,CAAUA,CAAQ,EAAIA,CAAAA,CAExD,GAAIF,CAAAA,CAAc,CAChB,QAAQ,IAAA,CAAK,0DAA0D,CAAA,CACvE,MACF,CAEA,GAAM,CACJ,QAAA,CAAAI,CAAAA,CACA,SAAAV,CAAAA,CAAW,cAAA,CACX,WAAA,CAAAW,CAAAA,CAAc,MACd,OAAA,CAAAC,CAAAA,CAAUrB,CAAAA,CACV,OAAA,CAAAsB,EAAUrB,CACZ,CAAA,CAAIiB,CAAAA,CAEJ,GAAI,CAACC,CAAAA,CAAU,MAAM,IAAI,KAAA,CAAM,qCAAqC,CAAA,CAAA,CAGrDI,CAAAA,EACb,QAAA,CAAS,UAAA,GAAe,UACpB,QAAA,CAAS,gBAAA,CAAiB,kBAAA,CAAoBA,CAAAA,CAAI,CAAE,IAAA,CAAM,IAAK,CAAC,CAAA,CAChEA,GAAG,EAEH,IAAM,CACVf,CAAAA,CAAaC,CAAQ,CAAA,CAErB,IAAMe,CAAAA,CAAOrB,CAAAA,GACPsB,CAAAA,CAAM,CAAA,EAAGJ,CAAO,CAAA,KAAA,EAAQF,CAAQ,CAAA,MAAA,EAASK,CAAI,CAAA,CAAA,CAG7CE,CAAAA,CAAS,SAAS,aAAA,CAAc,QAAQ,CAAA,CAC9CA,CAAAA,CAAO,GAAK,eAAA,CACZA,CAAAA,CAAO,KAAA,CAAQ,cAAA,CACfA,EAAO,OAAA,CAAU,MAAA,CACjBA,CAAAA,CAAO,GAAA,CAAMD,EAGb,IAAME,CAAAA,CAAM,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA,CAC3CA,CAAAA,CAAI,EAAA,CAAK,aAAA,CACTA,EAAI,YAAA,CAAa,YAAA,CAAc,WAAW,CAAA,CAC1CA,EAAI,YAAA,CAAa,eAAA,CAAiB,OAAO,CAAA,CAEzC,IAAMC,CAAAA,CAAM,QAAA,CAAS,aAAA,CAAc,KAAK,EACxCA,CAAAA,CAAI,GAAA,CAAMN,CAAAA,CACVM,CAAAA,CAAI,IAAM,EAAA,CACVA,CAAAA,CAAI,KAAA,CAAQ,EAAA,CACZA,EAAI,MAAA,CAAS,EAAA,CACbA,CAAAA,CAAI,KAAA,CAAM,OAAA,CAAU,mDAAA,CAEpB,IAAMC,CAAAA,CAAY,SAAS,aAAA,CAAc,MAAM,CAAA,CAC/CA,CAAAA,CAAU,UAAYf,CAAAA,CACtBe,CAAAA,CAAU,KAAA,CAAM,OAAA,CAAU,OAE1BF,CAAAA,CAAI,MAAA,CAAOC,CAAAA,CAAKC,CAAS,EAGzB,IAAIC,CAAAA,CAAOV,CAAAA,CACLW,CAAAA,CAAa,IAAM,CACvBL,CAAAA,CAAO,SAAA,CAAU,MAAA,CAAO,UAAWI,CAAI,CAAA,CAEvC,QAAA,CAAS,IAAA,CAAK,UAAU,MAAA,CAAO,cAAA,CAAgBA,CAAI,CAAA,CACnDF,EAAI,KAAA,CAAM,OAAA,CAAUE,CAAAA,CAAO,MAAA,CAAS,QACpCD,CAAAA,CAAU,KAAA,CAAM,OAAA,CAAUC,CAAAA,CAAO,QAAU,MAAA,CAC3CH,CAAAA,CAAI,YAAA,CAAa,eAAA,CAAiB,OAAOG,CAAI,CAAC,EAChD,CAAA,CAEAH,EAAI,gBAAA,CAAiB,OAAA,CAAS,IAAM,CAClCG,EAAO,CAACA,CAAAA,CACRC,CAAAA,GACF,CAAC,CAAA,CAED,QAAA,CAAS,IAAA,CAAK,MAAA,CAAOL,EAAQC,CAAG,CAAA,CAChCI,CAAAA,EAAW,CACXhB,EAAe,KACjB,CAAC,EACH,CA2BO,SAASiB,CAAAA,EAAgB,CA7NhC,IAAA5B,CAAAA,CAAAC,EAAA4B,CAAAA,CAAAA,CA8NE7B,CAAAA,CAAA,QAAA,CAAS,cAAA,CAAe,eAAe,CAAA,GAAvC,IAAA,EAAAA,CAAAA,CAA0C,MAAA,EAAA,CAAA,CAC1CC,EAAA,QAAA,CAAS,cAAA,CAAe,aAAa,CAAA,GAArC,MAAAA,CAAAA,CAAwC,MAAA,EAAA,CAAA,CACxC4B,CAAAA,CAAA,QAAA,CAAS,eAAe,gBAAgB,CAAA,GAAxC,IAAA,EAAAA,CAAAA,CAA2C,SAC3C,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,cAAc,CAAA,CAC7ClB,CAAAA,CAAe,MACjB,CC1MO,SAASmB,CAAAA,CAAeC,CAAAA,CAA4B,CAEzD,IAAMC,EAAUC,MAAAA,CAAOF,CAAK,CAAA,CAE5B,OAAAG,UAAU,IAAM,CAEd,GAAI,OAAO,QAAW,WAAA,CAEtB,OAAAtB,CAAAA,CAAKoB,CAAAA,CAAQ,OAAO,CAAA,CAEb,IAAM,CACXJ,CAAAA,GACF,CACF,CAAA,CAAG,EAAE,EAGE,IACT","file":"react.mjs","sourcesContent":["/** Options accepted by DunefoxChat.init() */\r\nexport interface DunefoxChatOptions {\r\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\r\n tenantId: string;\r\n /**\r\n * Where to anchor the widget. Defaults to \"bottom-right\".\r\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\r\n */\r\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\r\n /** Open the chat panel on load. Defaults to false. */\r\n defaultOpen?: boolean;\r\n /** Override the base URL (for self-hosted / staging). */\r\n baseUrl?: string;\r\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\r\n iconUrl?: string;\r\n}\r\n\r\nconst DEFAULT_BASE = 'https://app.dunefox.io';\r\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\r\nconst UUID_KEY = 'dunefox_chat_uuid';\r\n\r\n// ─── UUID generation ──────────────────────────────────────────────────────────\r\nfunction getOrCreateUUID(): string {\r\n try {\r\n let id = localStorage.getItem(UUID_KEY);\r\n if (!id) {\r\n id = crypto?.randomUUID?.()\r\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n localStorage.setItem(UUID_KEY, id);\r\n }\r\n return id;\r\n } catch {\r\n // Incognito / storage blocked — generate ephemeral ID\r\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\r\n }\r\n}\r\n\r\n// ─── CSS injector ─────────────────────────────────────────────────────────────\r\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\r\n if (document.getElementById('dunefox-styles')) return;\r\n\r\n const isTop = position.startsWith('top');\r\n const isLeft = position.endsWith('left');\r\n const hSide = isLeft ? 'left:16px' : 'right:16px';\r\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\r\n // Panel opens away from the button edge\r\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\r\n // Slide direction: panels below slide up, panels above slide down\r\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\r\n // Hover nudge direction\r\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\r\n // Mobile slide direction\r\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\r\n\r\n const css = `\r\n #dunefox-btn {\r\n position: fixed; ${vBtn}; ${hSide};\r\n width: 60px; height: 60px; border-radius: 50%;\r\n background: #1a1a1a; border: none; cursor: pointer;\r\n display: flex; align-items: center; justify-content: center;\r\n z-index: 2147483646;\r\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\r\n transition: transform .25s ease;\r\n }\r\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\r\n #dunefox-frame {\r\n position: fixed; ${vFrame}; ${hSide};\r\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\r\n border-radius: 16px; border: none; z-index: 2147483645;\r\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\r\n opacity: 0; visibility: hidden;\r\n transform: ${slideOut};\r\n transition: all .4s cubic-bezier(.4,0,.2,1);\r\n }\r\n #dunefox-frame.df-open {\r\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\r\n }\r\n @media (max-width: 768px) {\r\n /* Only move button to top-right when chat is open on mobile */\r\n body.df-chat-open #dunefox-btn {\r\n bottom: unset !important; top: 16px !important;\r\n right: 16px !important; left: unset !important;\r\n /* Strip the circular button style — show just the X icon */\r\n background: transparent !important;\r\n box-shadow: none !important;\r\n width: 36px !important; height: 36px !important;\r\n }\r\n #dunefox-frame {\r\n width: 100% !important; height: 100% !important; max-height: none !important;\r\n position: fixed !important; inset: 0 !important;\r\n border-radius: 0 !important;\r\n transform: ${mobileSlideOut} !important;\r\n }\r\n #dunefox-frame.df-open { transform: translateY(0) !important; }\r\n }\r\n `;\r\n const tag = document.createElement('style');\r\n tag.id = 'dunefox-styles';\r\n tag.textContent = css;\r\n document.head.appendChild(tag);\r\n}\r\n\r\n// ─── SVG icons ────────────────────────────────────────────────────────────────\r\nconst CLOSE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>`;\r\n\r\n// ─── Main init ────────────────────────────────────────────────────────────────\r\nlet _initialised = false;\r\n\r\n/**\r\n * Initialise the Dunefox chatbot widget.\r\n * Call once on DOMContentLoaded or after your framework has mounted.\r\n *\r\n * @example\r\n * import { init } from 'dunefox-chatbot';\r\n * init({ tenantId: 'YOUR_TENANT_ID' });\r\n */\r\nexport function init(options: DunefoxChatOptions | string): void {\r\n // Allow shorthand: init('tenantId')\r\n const opts: DunefoxChatOptions =\r\n typeof options === 'string' ? { tenantId: options } : options;\r\n\r\n if (_initialised) {\r\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\r\n return;\r\n }\r\n\r\n const {\r\n tenantId,\r\n position = 'bottom-right',\r\n defaultOpen = false,\r\n baseUrl = DEFAULT_BASE,\r\n iconUrl = DEFAULT_ICON,\r\n } = opts;\r\n\r\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\r\n\r\n // Wait for DOM if we're running during <head> parse\r\n const ready = (cb: () => void) =>\r\n document.readyState === 'loading'\r\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\r\n : cb();\r\n\r\n ready(() => {\r\n injectStyles(position);\r\n\r\n const uuid = getOrCreateUUID();\r\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\r\n\r\n // ── iframe ──\r\n const iframe = document.createElement('iframe');\r\n iframe.id = 'dunefox-frame';\r\n iframe.title = 'Dunefox Chat';\r\n iframe.loading = 'lazy';\r\n iframe.src = src;\r\n\r\n // ── toggle button ──\r\n const btn = document.createElement('button');\r\n btn.id = 'dunefox-btn';\r\n btn.setAttribute('aria-label', 'Open chat');\r\n btn.setAttribute('aria-expanded', 'false');\r\n\r\n const img = document.createElement('img');\r\n img.src = iconUrl;\r\n img.alt = '';\r\n img.width = 60;\r\n img.height = 60;\r\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\r\n\r\n const closeSpan = document.createElement('span');\r\n closeSpan.innerHTML = CLOSE_SVG;\r\n closeSpan.style.display = 'none';\r\n\r\n btn.append(img, closeSpan);\r\n\r\n // ── state ──\r\n let open = defaultOpen;\r\n const applyState = () => {\r\n iframe.classList.toggle('df-open', open);\r\n // Toggle body class so mobile CSS can move button to top-right only when open\r\n document.body.classList.toggle('df-chat-open', open);\r\n img.style.display = open ? 'none' : 'block';\r\n closeSpan.style.display = open ? 'block' : 'none';\r\n btn.setAttribute('aria-expanded', String(open));\r\n };\r\n\r\n btn.addEventListener('click', () => {\r\n open = !open;\r\n applyState();\r\n });\r\n\r\n document.body.append(iframe, btn);\r\n applyState();\r\n _initialised = true;\r\n });\r\n}\r\n\r\n/**\r\n * Programmatically open the chat panel.\r\n */\r\nexport function open(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.add('df-open');\r\n btn.setAttribute('aria-expanded', 'true');\r\n}\r\n\r\n/**\r\n * Programmatically close the chat panel.\r\n */\r\nexport function close(): void {\r\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\r\n const btn = document.getElementById('dunefox-btn');\r\n if (!iframe || !btn) return;\r\n iframe.classList.remove('df-open');\r\n btn.setAttribute('aria-expanded', 'false');\r\n}\r\n\r\n/**\r\n * Remove the widget entirely.\r\n */\r\nexport function destroy(): void {\r\n document.getElementById('dunefox-frame')?.remove();\r\n document.getElementById('dunefox-btn')?.remove();\r\n document.getElementById('dunefox-styles')?.remove();\r\n document.body.classList.remove('df-chat-open');\r\n _initialised = false;\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { init, destroy } from './core';\nimport type { DunefoxChatOptions } from './core';\n\nexport interface DunefoxChatbotProps extends DunefoxChatOptions {}\n\n/**\n * React component wrapper for the Dunefox Chatbot widget.\n *\n * Drop it anywhere in your component tree — typically at the root layout level.\n * SSR-safe: the widget is only mounted in the browser via useEffect.\n *\n * @example\n * // Next.js App Router (app/layout.tsx)\n * import { DunefoxChatbot } from 'dunefox-chatbot/react';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html><body>\n * {children}\n * <DunefoxChatbot tenantId=\"YOUR_TENANT_ID\" />\n * </body></html>\n * );\n * }\n */\nexport function DunefoxChatbot(props: DunefoxChatbotProps) {\n // Capture props at mount time — we intentionally don't re-init on prop changes\n const optsRef = useRef(props);\n\n useEffect(() => {\n // Prevent running during SSR (window check is a safety net)\n if (typeof window === 'undefined') return;\n\n init(optsRef.current);\n\n return () => {\n destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n // This component renders nothing to the React tree — the widget is DOM-injected\n return null;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dunefox-chatbot",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Official Dunefox chatbot widget — add AI chat to any website in seconds",
5
5
  "keywords": [
6
6
  "dunefox",