dunefox-chatbot 1.0.0

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 ADDED
@@ -0,0 +1,133 @@
1
+ # dunefox-chatbot
2
+
3
+ Official SDK for embedding the [Dunefox](https://dunefox.io) AI chatbot widget on any website.
4
+
5
+ Get your **Tenant ID** from **Console → Install Widget**.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install dunefox-chatbot
13
+ # or: yarn add dunefox-chatbot | pnpm add dunefox-chatbot
14
+ ```
15
+
16
+ ---
17
+
18
+ ## Platform Guides
19
+
20
+ ### HTML / Laravel / WordPress / Any site
21
+
22
+ No npm required. Paste this before `</body>`:
23
+
24
+ ```html
25
+ <script src="https://app.dunefox.io/api/sdk/chatbot.js"></script>
26
+ <script>
27
+ DunefoxChat.init({ tenantId: 'YOUR_TENANT_ID' });
28
+ </script>
29
+ ```
30
+
31
+ ---
32
+
33
+ ### Next.js (App Router)
34
+
35
+ ```tsx
36
+ // app/layout.tsx
37
+ import { DunefoxChatbot } from 'dunefox-chatbot/react';
38
+
39
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
40
+ return (
41
+ <html lang="en">
42
+ <body>
43
+ {children}
44
+ <DunefoxChatbot tenantId="YOUR_TENANT_ID" />
45
+ </body>
46
+ </html>
47
+ );
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ### React (Vite / CRA)
54
+
55
+ ```tsx
56
+ import { DunefoxChatbot } from 'dunefox-chatbot/react';
57
+
58
+ export default function App() {
59
+ return (
60
+ <>
61
+ <YourApp />
62
+ <DunefoxChatbot tenantId="YOUR_TENANT_ID" />
63
+ </>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ### Vue 3 / Nuxt
71
+
72
+ ```html
73
+ <script setup>
74
+ import { onMounted, onBeforeUnmount } from 'vue';
75
+
76
+ onMounted(async () => {
77
+ const { init } = await import('dunefox-chatbot');
78
+ init({ tenantId: 'YOUR_TENANT_ID' });
79
+ });
80
+ onBeforeUnmount(async () => {
81
+ const { destroy } = await import('dunefox-chatbot');
82
+ destroy();
83
+ });
84
+ </script>
85
+ ```
86
+
87
+ ---
88
+
89
+ ## API Reference
90
+
91
+ ### `init(options)`
92
+
93
+ | Option | Type | Default | Description |
94
+ |--------|------|---------|-------------|
95
+ | `tenantId` | `string` | **required** | Your Dunefox tenant ID |
96
+ | `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Widget anchor position |
97
+ | `defaultOpen` | `boolean` | `false` | Open the panel on load |
98
+ | `baseUrl` | `string` | `https://app.dunefox.io` | Override for staging/self-hosted |
99
+ | `iconUrl` | `string` | Dunefox default | Custom toggle button icon |
100
+
101
+ ### `open()` / `close()`
102
+
103
+ Programmatically show or hide the chat panel.
104
+
105
+ ```js
106
+ DunefoxChat.open();
107
+ DunefoxChat.close();
108
+ ```
109
+
110
+ ### `destroy()`
111
+
112
+ Completely remove the widget from the page.
113
+
114
+ ```js
115
+ DunefoxChat.destroy();
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Building from source
121
+
122
+ ```bash
123
+ cd packages/chatbot-sdk
124
+ npm install
125
+ npm run build
126
+ # Outputs: dist/index.js (ESM), dist/index.cjs (CJS), dist/chatbot.umd.js (browser)
127
+ ```
128
+
129
+ ---
130
+
131
+ ## License
132
+
133
+ MIT © [Dunefox](https://dunefox.io)
@@ -0,0 +1,32 @@
1
+ "use strict";var DunefoxChat=(()=>{var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var L=(e,t)=>{for(var n in t)f(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&&f(e,a,{get:()=>t[a],enumerable:!(i=I(t,a))||i.enumerable});return e};var C=e=>O(f({},"__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",u=`
2
+ #dunefox-btn {
3
+ position: fixed; ${t?"top:16px":"bottom:16px"}; ${i};
4
+ width: 60px; height: 60px; border-radius: 50%;
5
+ background: #1a1a1a; border: none; cursor: pointer;
6
+ display: flex; align-items: center; justify-content: center;
7
+ z-index: 2147483646;
8
+ box-shadow: 0 4px 20px rgba(0,0,0,.25);
9
+ transition: transform .25s ease;
10
+ }
11
+ #dunefox-btn:hover { transform: ${t?"translateY(2px) scale(1.05)":"translateY(-2px) scale(1.05)"}; }
12
+ #dunefox-frame {
13
+ position: fixed; ${t?"top:88px":"bottom:88px"}; ${i};
14
+ width: 350px; height: calc(100vh - 160px); max-height: 600px;
15
+ border-radius: 16px; border: none; z-index: 2147483645;
16
+ box-shadow: 0 8px 32px rgba(0,0,0,.12);
17
+ opacity: 0; visibility: hidden;
18
+ transform: ${t?"translateY(-24px) scale(.96)":"translateY(24px) scale(.96)"};
19
+ transition: all .4s cubic-bezier(.4,0,.2,1);
20
+ }
21
+ #dunefox-frame.df-open {
22
+ opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
+ }
24
+ @media (max-width: 768px) {
25
+ #dunefox-frame {
26
+ width: 100vw; height: 100vh; max-height: none;
27
+ inset: 0; border-radius: 0; transform: ${t?"translateY(-100%)":"translateY(100%)"};
28
+ bottom: unset; right: unset; left: unset; top: unset;
29
+ }
30
+ #dunefox-frame.df-open { transform: translateY(0); }
31
+ }
32
+ `,o=document.createElement("style");o.id="dunefox-styles",o.textContent=u,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>',p=!1;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:i="bottom-right",defaultOpen:a=!1,baseUrl:m=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=$(),u=`${m}/api/${n}?uuid=${l}`,o=document.createElement("iframe");o.id="dunefox-frame",o.title="Dunefox Chat",o.loading="lazy",o.src=u;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(),p=!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(),p=!1}return C(T);})();
@@ -0,0 +1,39 @@
1
+ /** Options accepted by DunefoxChat.init() */
2
+ interface DunefoxChatOptions {
3
+ /** Your Dunefox Tenant ID — found in Settings > Install Widget */
4
+ tenantId: string;
5
+ /**
6
+ * Where to anchor the widget. Defaults to "bottom-right".
7
+ * Supports: "bottom-right" | "bottom-left" | "top-right" | "top-left"
8
+ */
9
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
10
+ /** Open the chat panel on load. Defaults to false. */
11
+ defaultOpen?: boolean;
12
+ /** Override the base URL (for self-hosted / staging). */
13
+ baseUrl?: string;
14
+ /** Icon image URL override. Falls back to Dunefox CDN asset. */
15
+ iconUrl?: string;
16
+ }
17
+ /**
18
+ * Initialise the Dunefox chatbot widget.
19
+ * Call once on DOMContentLoaded or after your framework has mounted.
20
+ *
21
+ * @example
22
+ * import { init } from 'dunefox-chatbot';
23
+ * init({ tenantId: 'YOUR_TENANT_ID' });
24
+ */
25
+ declare function init(options: DunefoxChatOptions | string): void;
26
+ /**
27
+ * Programmatically open the chat panel.
28
+ */
29
+ declare function open(): void;
30
+ /**
31
+ * Programmatically close the chat panel.
32
+ */
33
+ declare function close(): void;
34
+ /**
35
+ * Remove the widget entirely.
36
+ */
37
+ declare function destroy(): void;
38
+
39
+ export { type DunefoxChatOptions, close, destroy, init, open };
@@ -0,0 +1,39 @@
1
+ /** Options accepted by DunefoxChat.init() */
2
+ interface DunefoxChatOptions {
3
+ /** Your Dunefox Tenant ID — found in Settings > Install Widget */
4
+ tenantId: string;
5
+ /**
6
+ * Where to anchor the widget. Defaults to "bottom-right".
7
+ * Supports: "bottom-right" | "bottom-left" | "top-right" | "top-left"
8
+ */
9
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
10
+ /** Open the chat panel on load. Defaults to false. */
11
+ defaultOpen?: boolean;
12
+ /** Override the base URL (for self-hosted / staging). */
13
+ baseUrl?: string;
14
+ /** Icon image URL override. Falls back to Dunefox CDN asset. */
15
+ iconUrl?: string;
16
+ }
17
+ /**
18
+ * Initialise the Dunefox chatbot widget.
19
+ * Call once on DOMContentLoaded or after your framework has mounted.
20
+ *
21
+ * @example
22
+ * import { init } from 'dunefox-chatbot';
23
+ * init({ tenantId: 'YOUR_TENANT_ID' });
24
+ */
25
+ declare function init(options: DunefoxChatOptions | string): void;
26
+ /**
27
+ * Programmatically open the chat panel.
28
+ */
29
+ declare function open(): void;
30
+ /**
31
+ * Programmatically close the chat panel.
32
+ */
33
+ declare function close(): void;
34
+ /**
35
+ * Remove the widget entirely.
36
+ */
37
+ declare function destroy(): void;
38
+
39
+ export { type DunefoxChatOptions, close, destroy, init, open };
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
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=`
2
+ #dunefox-btn {
3
+ position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
+ width: 60px; height: 60px; border-radius: 50%;
5
+ background: #1a1a1a; border: none; cursor: pointer;
6
+ display: flex; align-items: center; justify-content: center;
7
+ z-index: 2147483646;
8
+ box-shadow: 0 4px 20px rgba(0,0,0,.25);
9
+ transition: transform .25s ease;
10
+ }
11
+ #dunefox-btn:hover { transform: ${t?"translateY(2px) scale(1.05)":"translateY(-2px) scale(1.05)"}; }
12
+ #dunefox-frame {
13
+ position: fixed; ${t?"top:88px":"bottom:88px"}; ${d};
14
+ width: 350px; height: calc(100vh - 160px); max-height: 600px;
15
+ border-radius: 16px; border: none; z-index: 2147483645;
16
+ box-shadow: 0 8px 32px rgba(0,0,0,.12);
17
+ opacity: 0; visibility: hidden;
18
+ transform: ${t?"translateY(-24px) scale(.96)":"translateY(24px) scale(.96)"};
19
+ transition: all .4s cubic-bezier(.4,0,.2,1);
20
+ }
21
+ #dunefox-frame.df-open {
22
+ opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
+ }
24
+ @media (max-width: 768px) {
25
+ #dunefox-frame {
26
+ width: 100vw; height: 100vh; max-height: none;
27
+ inset: 0; border-radius: 0; transform: ${t?"translateY(-100%)":"translateY(100%)"};
28
+ bottom: unset; right: unset; left: unset; top: unset;
29
+ }
30
+ #dunefox-frame.df-open { transform: translateY(0); }
31
+ }
32
+ `,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>',u=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(u){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:p=false,baseUrl:f=b,iconUrl:m=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=`${f}/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 r=document.createElement("img");r.src=m,r.alt="",r.width=60,r.height=60,r.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=w,l.style.display="none",a.append(r,l);let i=p,x=()=>{o.classList.toggle("df-open",i),r.style.display=i?"none":"block",l.style.display=i?"block":"none",a.setAttribute("aria-expanded",String(i));};a.addEventListener("click",()=>{i=!i,x();}),document.body.append(o,a),x(),u=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(),u=false;}
33
+ exports.close=D;exports.destroy=O;exports.init=I;exports.open=S;//# sourceMappingURL=index.js.map
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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() */\nexport interface DunefoxChatOptions {\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\n tenantId: string;\n /**\n * Where to anchor the widget. Defaults to \"bottom-right\".\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\n */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Open the chat panel on load. Defaults to false. */\n defaultOpen?: boolean;\n /** Override the base URL (for self-hosted / staging). */\n baseUrl?: string;\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\n iconUrl?: string;\n}\n\nconst DEFAULT_BASE = 'https://app.dunefox.io';\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\nconst UUID_KEY = 'dunefox_chat_uuid';\n\n// ─── UUID generation ──────────────────────────────────────────────────────────\nfunction getOrCreateUUID(): string {\n try {\n let id = localStorage.getItem(UUID_KEY);\n if (!id) {\n id = crypto?.randomUUID?.()\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\n localStorage.setItem(UUID_KEY, id);\n }\n return id;\n } catch {\n // Incognito / storage blocked — generate ephemeral ID\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n }\n}\n\n// ─── CSS injector ─────────────────────────────────────────────────────────────\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\n if (document.getElementById('dunefox-styles')) return;\n\n const isTop = position.startsWith('top');\n const isLeft = position.endsWith('left');\n const hSide = isLeft ? 'left:16px' : 'right:16px';\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\n // Panel opens away from the button edge\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\n // Slide direction: panels below slide up, panels above slide down\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\n // Hover nudge direction\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\n // Mobile slide direction\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\n\n const css = `\n #dunefox-btn {\n position: fixed; ${vBtn}; ${hSide};\n width: 60px; height: 60px; border-radius: 50%;\n background: #1a1a1a; border: none; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483646;\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\n transition: transform .25s ease;\n }\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\n #dunefox-frame {\n position: fixed; ${vFrame}; ${hSide};\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\n border-radius: 16px; border: none; z-index: 2147483645;\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\n opacity: 0; visibility: hidden;\n transform: ${slideOut};\n transition: all .4s cubic-bezier(.4,0,.2,1);\n }\n #dunefox-frame.df-open {\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\n }\n @media (max-width: 768px) {\n #dunefox-frame {\n width: 100vw; height: 100vh; max-height: none;\n inset: 0; border-radius: 0; transform: ${mobileSlideOut};\n bottom: unset; right: unset; left: unset; top: unset;\n }\n #dunefox-frame.df-open { transform: translateY(0); }\n }\n `;\n const tag = document.createElement('style');\n tag.id = 'dunefox-styles';\n tag.textContent = css;\n document.head.appendChild(tag);\n}\n\n// ─── SVG icons ────────────────────────────────────────────────────────────────\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>`;\n\n// ─── Main init ────────────────────────────────────────────────────────────────\nlet _initialised = false;\n\n/**\n * Initialise the Dunefox chatbot widget.\n * Call once on DOMContentLoaded or after your framework has mounted.\n *\n * @example\n * import { init } from 'dunefox-chatbot';\n * init({ tenantId: 'YOUR_TENANT_ID' });\n */\nexport function init(options: DunefoxChatOptions | string): void {\n // Allow shorthand: init('tenantId')\n const opts: DunefoxChatOptions =\n typeof options === 'string' ? { tenantId: options } : options;\n\n if (_initialised) {\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\n return;\n }\n\n const {\n tenantId,\n position = 'bottom-right',\n defaultOpen = false,\n baseUrl = DEFAULT_BASE,\n iconUrl = DEFAULT_ICON,\n } = opts;\n\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\n\n // Wait for DOM if we're running during <head> parse\n const ready = (cb: () => void) =>\n document.readyState === 'loading'\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\n : cb();\n\n ready(() => {\n injectStyles(position);\n\n const uuid = getOrCreateUUID();\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\n\n // ── iframe ──\n const iframe = document.createElement('iframe');\n iframe.id = 'dunefox-frame';\n iframe.title = 'Dunefox Chat';\n iframe.loading = 'lazy';\n iframe.src = src;\n\n // ── toggle button ──\n const btn = document.createElement('button');\n btn.id = 'dunefox-btn';\n btn.setAttribute('aria-label', 'Open chat');\n btn.setAttribute('aria-expanded', 'false');\n\n const img = document.createElement('img');\n img.src = iconUrl;\n img.alt = '';\n img.width = 60;\n img.height = 60;\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\n\n const closeSpan = document.createElement('span');\n closeSpan.innerHTML = CLOSE_SVG;\n closeSpan.style.display = 'none';\n\n btn.append(img, closeSpan);\n\n // ── state ──\n let open = defaultOpen;\n const applyState = () => {\n iframe.classList.toggle('df-open', open);\n img.style.display = open ? 'none' : 'block';\n closeSpan.style.display = open ? 'block' : 'none';\n btn.setAttribute('aria-expanded', String(open));\n };\n\n btn.addEventListener('click', () => {\n open = !open;\n applyState();\n });\n\n document.body.append(iframe, btn);\n applyState();\n _initialised = true;\n });\n}\n\n/**\n * Programmatically open the chat panel.\n */\nexport function open(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.add('df-open');\n btn.setAttribute('aria-expanded', 'true');\n}\n\n/**\n * Programmatically close the chat panel.\n */\nexport function close(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.remove('df-open');\n btn.setAttribute('aria-expanded', 'false');\n}\n\n/**\n * Remove the widget entirely.\n */\nexport function destroy(): void {\n document.getElementById('dunefox-frame')?.remove();\n document.getElementById('dunefox-btn')?.remove();\n document.getElementById('dunefox-styles')?.remove();\n _initialised = false;\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,34 @@
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=`
2
+ #dunefox-btn {
3
+ position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
+ width: 60px; height: 60px; border-radius: 50%;
5
+ background: #1a1a1a; border: none; cursor: pointer;
6
+ display: flex; align-items: center; justify-content: center;
7
+ z-index: 2147483646;
8
+ box-shadow: 0 4px 20px rgba(0,0,0,.25);
9
+ transition: transform .25s ease;
10
+ }
11
+ #dunefox-btn:hover { transform: ${t?"translateY(2px) scale(1.05)":"translateY(-2px) scale(1.05)"}; }
12
+ #dunefox-frame {
13
+ position: fixed; ${t?"top:88px":"bottom:88px"}; ${d};
14
+ width: 350px; height: calc(100vh - 160px); max-height: 600px;
15
+ border-radius: 16px; border: none; z-index: 2147483645;
16
+ box-shadow: 0 8px 32px rgba(0,0,0,.12);
17
+ opacity: 0; visibility: hidden;
18
+ transform: ${t?"translateY(-24px) scale(.96)":"translateY(24px) scale(.96)"};
19
+ transition: all .4s cubic-bezier(.4,0,.2,1);
20
+ }
21
+ #dunefox-frame.df-open {
22
+ opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
+ }
24
+ @media (max-width: 768px) {
25
+ #dunefox-frame {
26
+ width: 100vw; height: 100vh; max-height: none;
27
+ inset: 0; border-radius: 0; transform: ${t?"translateY(-100%)":"translateY(100%)"};
28
+ bottom: unset; right: unset; left: unset; top: unset;
29
+ }
30
+ #dunefox-frame.df-open { transform: translateY(0); }
31
+ }
32
+ `,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>',u=false;function I(e){let t=typeof e=="string"?{tenantId:e}:e;if(u){console.warn("[DunefoxChat] Already initialised. Call destroy() first.");return}let{tenantId:n,position:d="bottom-right",defaultOpen:p=false,baseUrl:f=b,iconUrl:m=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=`${f}/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 r=document.createElement("img");r.src=m,r.alt="",r.width=60,r.height=60,r.style.cssText="display:block;border-radius:50%;object-fit:cover;";let l=document.createElement("span");l.innerHTML=w,l.style.display="none",a.append(r,l);let i=p,x=()=>{o.classList.toggle("df-open",i),r.style.display=i?"none":"block",l.style.display=i?"block":"none",a.setAttribute("aria-expanded",String(i));};a.addEventListener("click",()=>{i=!i,x();}),document.body.append(o,a),x(),u=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(),u=false;}
33
+ export{D as close,O as destroy,I as init,S as open};//# sourceMappingURL=index.mjs.map
34
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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() */\nexport interface DunefoxChatOptions {\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\n tenantId: string;\n /**\n * Where to anchor the widget. Defaults to \"bottom-right\".\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\n */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Open the chat panel on load. Defaults to false. */\n defaultOpen?: boolean;\n /** Override the base URL (for self-hosted / staging). */\n baseUrl?: string;\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\n iconUrl?: string;\n}\n\nconst DEFAULT_BASE = 'https://app.dunefox.io';\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\nconst UUID_KEY = 'dunefox_chat_uuid';\n\n// ─── UUID generation ──────────────────────────────────────────────────────────\nfunction getOrCreateUUID(): string {\n try {\n let id = localStorage.getItem(UUID_KEY);\n if (!id) {\n id = crypto?.randomUUID?.()\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\n localStorage.setItem(UUID_KEY, id);\n }\n return id;\n } catch {\n // Incognito / storage blocked — generate ephemeral ID\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n }\n}\n\n// ─── CSS injector ─────────────────────────────────────────────────────────────\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\n if (document.getElementById('dunefox-styles')) return;\n\n const isTop = position.startsWith('top');\n const isLeft = position.endsWith('left');\n const hSide = isLeft ? 'left:16px' : 'right:16px';\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\n // Panel opens away from the button edge\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\n // Slide direction: panels below slide up, panels above slide down\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\n // Hover nudge direction\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\n // Mobile slide direction\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\n\n const css = `\n #dunefox-btn {\n position: fixed; ${vBtn}; ${hSide};\n width: 60px; height: 60px; border-radius: 50%;\n background: #1a1a1a; border: none; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483646;\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\n transition: transform .25s ease;\n }\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\n #dunefox-frame {\n position: fixed; ${vFrame}; ${hSide};\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\n border-radius: 16px; border: none; z-index: 2147483645;\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\n opacity: 0; visibility: hidden;\n transform: ${slideOut};\n transition: all .4s cubic-bezier(.4,0,.2,1);\n }\n #dunefox-frame.df-open {\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\n }\n @media (max-width: 768px) {\n #dunefox-frame {\n width: 100vw; height: 100vh; max-height: none;\n inset: 0; border-radius: 0; transform: ${mobileSlideOut};\n bottom: unset; right: unset; left: unset; top: unset;\n }\n #dunefox-frame.df-open { transform: translateY(0); }\n }\n `;\n const tag = document.createElement('style');\n tag.id = 'dunefox-styles';\n tag.textContent = css;\n document.head.appendChild(tag);\n}\n\n// ─── SVG icons ────────────────────────────────────────────────────────────────\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>`;\n\n// ─── Main init ────────────────────────────────────────────────────────────────\nlet _initialised = false;\n\n/**\n * Initialise the Dunefox chatbot widget.\n * Call once on DOMContentLoaded or after your framework has mounted.\n *\n * @example\n * import { init } from 'dunefox-chatbot';\n * init({ tenantId: 'YOUR_TENANT_ID' });\n */\nexport function init(options: DunefoxChatOptions | string): void {\n // Allow shorthand: init('tenantId')\n const opts: DunefoxChatOptions =\n typeof options === 'string' ? { tenantId: options } : options;\n\n if (_initialised) {\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\n return;\n }\n\n const {\n tenantId,\n position = 'bottom-right',\n defaultOpen = false,\n baseUrl = DEFAULT_BASE,\n iconUrl = DEFAULT_ICON,\n } = opts;\n\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\n\n // Wait for DOM if we're running during <head> parse\n const ready = (cb: () => void) =>\n document.readyState === 'loading'\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\n : cb();\n\n ready(() => {\n injectStyles(position);\n\n const uuid = getOrCreateUUID();\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\n\n // ── iframe ──\n const iframe = document.createElement('iframe');\n iframe.id = 'dunefox-frame';\n iframe.title = 'Dunefox Chat';\n iframe.loading = 'lazy';\n iframe.src = src;\n\n // ── toggle button ──\n const btn = document.createElement('button');\n btn.id = 'dunefox-btn';\n btn.setAttribute('aria-label', 'Open chat');\n btn.setAttribute('aria-expanded', 'false');\n\n const img = document.createElement('img');\n img.src = iconUrl;\n img.alt = '';\n img.width = 60;\n img.height = 60;\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\n\n const closeSpan = document.createElement('span');\n closeSpan.innerHTML = CLOSE_SVG;\n closeSpan.style.display = 'none';\n\n btn.append(img, closeSpan);\n\n // ── state ──\n let open = defaultOpen;\n const applyState = () => {\n iframe.classList.toggle('df-open', open);\n img.style.display = open ? 'none' : 'block';\n closeSpan.style.display = open ? 'block' : 'none';\n btn.setAttribute('aria-expanded', String(open));\n };\n\n btn.addEventListener('click', () => {\n open = !open;\n applyState();\n });\n\n document.body.append(iframe, btn);\n applyState();\n _initialised = true;\n });\n}\n\n/**\n * Programmatically open the chat panel.\n */\nexport function open(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.add('df-open');\n btn.setAttribute('aria-expanded', 'true');\n}\n\n/**\n * Programmatically close the chat panel.\n */\nexport function close(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.remove('df-open');\n btn.setAttribute('aria-expanded', 'false');\n}\n\n/**\n * Remove the widget entirely.\n */\nexport function destroy(): void {\n document.getElementById('dunefox-frame')?.remove();\n document.getElementById('dunefox-btn')?.remove();\n document.getElementById('dunefox-styles')?.remove();\n _initialised = false;\n}\n"]}
@@ -0,0 +1,41 @@
1
+ /** Options accepted by DunefoxChat.init() */
2
+ interface DunefoxChatOptions {
3
+ /** Your Dunefox Tenant ID — found in Settings > Install Widget */
4
+ tenantId: string;
5
+ /**
6
+ * Where to anchor the widget. Defaults to "bottom-right".
7
+ * Supports: "bottom-right" | "bottom-left" | "top-right" | "top-left"
8
+ */
9
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
10
+ /** Open the chat panel on load. Defaults to false. */
11
+ defaultOpen?: boolean;
12
+ /** Override the base URL (for self-hosted / staging). */
13
+ baseUrl?: string;
14
+ /** Icon image URL override. Falls back to Dunefox CDN asset. */
15
+ iconUrl?: string;
16
+ }
17
+
18
+ interface DunefoxChatbotProps extends DunefoxChatOptions {
19
+ }
20
+ /**
21
+ * React component wrapper for the Dunefox Chatbot widget.
22
+ *
23
+ * Drop it anywhere in your component tree — typically at the root layout level.
24
+ * SSR-safe: the widget is only mounted in the browser via useEffect.
25
+ *
26
+ * @example
27
+ * // Next.js App Router (app/layout.tsx)
28
+ * import { DunefoxChatbot } from 'dunefox-chatbot/react';
29
+ *
30
+ * export default function RootLayout({ children }) {
31
+ * return (
32
+ * <html><body>
33
+ * {children}
34
+ * <DunefoxChatbot tenantId="YOUR_TENANT_ID" />
35
+ * </body></html>
36
+ * );
37
+ * }
38
+ */
39
+ declare function DunefoxChatbot(props: DunefoxChatbotProps): null;
40
+
41
+ export { DunefoxChatbot, type DunefoxChatbotProps };
@@ -0,0 +1,41 @@
1
+ /** Options accepted by DunefoxChat.init() */
2
+ interface DunefoxChatOptions {
3
+ /** Your Dunefox Tenant ID — found in Settings > Install Widget */
4
+ tenantId: string;
5
+ /**
6
+ * Where to anchor the widget. Defaults to "bottom-right".
7
+ * Supports: "bottom-right" | "bottom-left" | "top-right" | "top-left"
8
+ */
9
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
10
+ /** Open the chat panel on load. Defaults to false. */
11
+ defaultOpen?: boolean;
12
+ /** Override the base URL (for self-hosted / staging). */
13
+ baseUrl?: string;
14
+ /** Icon image URL override. Falls back to Dunefox CDN asset. */
15
+ iconUrl?: string;
16
+ }
17
+
18
+ interface DunefoxChatbotProps extends DunefoxChatOptions {
19
+ }
20
+ /**
21
+ * React component wrapper for the Dunefox Chatbot widget.
22
+ *
23
+ * Drop it anywhere in your component tree — typically at the root layout level.
24
+ * SSR-safe: the widget is only mounted in the browser via useEffect.
25
+ *
26
+ * @example
27
+ * // Next.js App Router (app/layout.tsx)
28
+ * import { DunefoxChatbot } from 'dunefox-chatbot/react';
29
+ *
30
+ * export default function RootLayout({ children }) {
31
+ * return (
32
+ * <html><body>
33
+ * {children}
34
+ * <DunefoxChatbot tenantId="YOUR_TENANT_ID" />
35
+ * </body></html>
36
+ * );
37
+ * }
38
+ */
39
+ declare function DunefoxChatbot(props: DunefoxChatbotProps): null;
40
+
41
+ export { DunefoxChatbot, type DunefoxChatbotProps };
package/dist/react.js ADDED
@@ -0,0 +1,34 @@
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",c=`
2
+ #dunefox-btn {
3
+ position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
+ width: 60px; height: 60px; border-radius: 50%;
5
+ background: #1a1a1a; border: none; cursor: pointer;
6
+ display: flex; align-items: center; justify-content: center;
7
+ z-index: 2147483646;
8
+ box-shadow: 0 4px 20px rgba(0,0,0,.25);
9
+ transition: transform .25s ease;
10
+ }
11
+ #dunefox-btn:hover { transform: ${t?"translateY(2px) scale(1.05)":"translateY(-2px) scale(1.05)"}; }
12
+ #dunefox-frame {
13
+ position: fixed; ${t?"top:88px":"bottom:88px"}; ${d};
14
+ width: 350px; height: calc(100vh - 160px); max-height: 600px;
15
+ border-radius: 16px; border: none; z-index: 2147483645;
16
+ box-shadow: 0 8px 32px rgba(0,0,0,.12);
17
+ opacity: 0; visibility: hidden;
18
+ transform: ${t?"translateY(-24px) scale(.96)":"translateY(24px) scale(.96)"};
19
+ transition: all .4s cubic-bezier(.4,0,.2,1);
20
+ }
21
+ #dunefox-frame.df-open {
22
+ opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
+ }
24
+ @media (max-width: 768px) {
25
+ #dunefox-frame {
26
+ width: 100vw; height: 100vh; max-height: none;
27
+ inset: 0; border-radius: 0; transform: ${t?"translateY(-100%)":"translateY(100%)"};
28
+ bottom: unset; right: unset; left: unset; top: unset;
29
+ }
30
+ #dunefox-frame.df-open { transform: translateY(0); }
31
+ }
32
+ `,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>',u=false;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:d="bottom-right",defaultOpen:f=false,baseUrl:p=v,iconUrl:m=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(),c=`${p}/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=f,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(),u=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(),u=false;}function U(e){let t=react.useRef(e);return react.useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
33
+ exports.DunefoxChatbot=U;//# sourceMappingURL=react.js.map
34
+ //# sourceMappingURL=react.js.map
@@ -0,0 +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() */\nexport interface DunefoxChatOptions {\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\n tenantId: string;\n /**\n * Where to anchor the widget. Defaults to \"bottom-right\".\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\n */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Open the chat panel on load. Defaults to false. */\n defaultOpen?: boolean;\n /** Override the base URL (for self-hosted / staging). */\n baseUrl?: string;\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\n iconUrl?: string;\n}\n\nconst DEFAULT_BASE = 'https://app.dunefox.io';\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\nconst UUID_KEY = 'dunefox_chat_uuid';\n\n// ─── UUID generation ──────────────────────────────────────────────────────────\nfunction getOrCreateUUID(): string {\n try {\n let id = localStorage.getItem(UUID_KEY);\n if (!id) {\n id = crypto?.randomUUID?.()\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\n localStorage.setItem(UUID_KEY, id);\n }\n return id;\n } catch {\n // Incognito / storage blocked — generate ephemeral ID\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n }\n}\n\n// ─── CSS injector ─────────────────────────────────────────────────────────────\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\n if (document.getElementById('dunefox-styles')) return;\n\n const isTop = position.startsWith('top');\n const isLeft = position.endsWith('left');\n const hSide = isLeft ? 'left:16px' : 'right:16px';\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\n // Panel opens away from the button edge\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\n // Slide direction: panels below slide up, panels above slide down\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\n // Hover nudge direction\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\n // Mobile slide direction\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\n\n const css = `\n #dunefox-btn {\n position: fixed; ${vBtn}; ${hSide};\n width: 60px; height: 60px; border-radius: 50%;\n background: #1a1a1a; border: none; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483646;\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\n transition: transform .25s ease;\n }\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\n #dunefox-frame {\n position: fixed; ${vFrame}; ${hSide};\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\n border-radius: 16px; border: none; z-index: 2147483645;\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\n opacity: 0; visibility: hidden;\n transform: ${slideOut};\n transition: all .4s cubic-bezier(.4,0,.2,1);\n }\n #dunefox-frame.df-open {\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\n }\n @media (max-width: 768px) {\n #dunefox-frame {\n width: 100vw; height: 100vh; max-height: none;\n inset: 0; border-radius: 0; transform: ${mobileSlideOut};\n bottom: unset; right: unset; left: unset; top: unset;\n }\n #dunefox-frame.df-open { transform: translateY(0); }\n }\n `;\n const tag = document.createElement('style');\n tag.id = 'dunefox-styles';\n tag.textContent = css;\n document.head.appendChild(tag);\n}\n\n// ─── SVG icons ────────────────────────────────────────────────────────────────\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>`;\n\n// ─── Main init ────────────────────────────────────────────────────────────────\nlet _initialised = false;\n\n/**\n * Initialise the Dunefox chatbot widget.\n * Call once on DOMContentLoaded or after your framework has mounted.\n *\n * @example\n * import { init } from 'dunefox-chatbot';\n * init({ tenantId: 'YOUR_TENANT_ID' });\n */\nexport function init(options: DunefoxChatOptions | string): void {\n // Allow shorthand: init('tenantId')\n const opts: DunefoxChatOptions =\n typeof options === 'string' ? { tenantId: options } : options;\n\n if (_initialised) {\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\n return;\n }\n\n const {\n tenantId,\n position = 'bottom-right',\n defaultOpen = false,\n baseUrl = DEFAULT_BASE,\n iconUrl = DEFAULT_ICON,\n } = opts;\n\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\n\n // Wait for DOM if we're running during <head> parse\n const ready = (cb: () => void) =>\n document.readyState === 'loading'\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\n : cb();\n\n ready(() => {\n injectStyles(position);\n\n const uuid = getOrCreateUUID();\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\n\n // ── iframe ──\n const iframe = document.createElement('iframe');\n iframe.id = 'dunefox-frame';\n iframe.title = 'Dunefox Chat';\n iframe.loading = 'lazy';\n iframe.src = src;\n\n // ── toggle button ──\n const btn = document.createElement('button');\n btn.id = 'dunefox-btn';\n btn.setAttribute('aria-label', 'Open chat');\n btn.setAttribute('aria-expanded', 'false');\n\n const img = document.createElement('img');\n img.src = iconUrl;\n img.alt = '';\n img.width = 60;\n img.height = 60;\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\n\n const closeSpan = document.createElement('span');\n closeSpan.innerHTML = CLOSE_SVG;\n closeSpan.style.display = 'none';\n\n btn.append(img, closeSpan);\n\n // ── state ──\n let open = defaultOpen;\n const applyState = () => {\n iframe.classList.toggle('df-open', open);\n img.style.display = open ? 'none' : 'block';\n closeSpan.style.display = open ? 'block' : 'none';\n btn.setAttribute('aria-expanded', String(open));\n };\n\n btn.addEventListener('click', () => {\n open = !open;\n applyState();\n });\n\n document.body.append(iframe, btn);\n applyState();\n _initialised = true;\n });\n}\n\n/**\n * Programmatically open the chat panel.\n */\nexport function open(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.add('df-open');\n btn.setAttribute('aria-expanded', 'true');\n}\n\n/**\n * Programmatically close the chat panel.\n */\nexport function close(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.remove('df-open');\n btn.setAttribute('aria-expanded', 'false');\n}\n\n/**\n * Remove the widget entirely.\n */\nexport function destroy(): void {\n document.getElementById('dunefox-frame')?.remove();\n document.getElementById('dunefox-btn')?.remove();\n document.getElementById('dunefox-styles')?.remove();\n _initialised = false;\n}\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 ADDED
@@ -0,0 +1,34 @@
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",c=`
2
+ #dunefox-btn {
3
+ position: fixed; ${t?"top:16px":"bottom:16px"}; ${d};
4
+ width: 60px; height: 60px; border-radius: 50%;
5
+ background: #1a1a1a; border: none; cursor: pointer;
6
+ display: flex; align-items: center; justify-content: center;
7
+ z-index: 2147483646;
8
+ box-shadow: 0 4px 20px rgba(0,0,0,.25);
9
+ transition: transform .25s ease;
10
+ }
11
+ #dunefox-btn:hover { transform: ${t?"translateY(2px) scale(1.05)":"translateY(-2px) scale(1.05)"}; }
12
+ #dunefox-frame {
13
+ position: fixed; ${t?"top:88px":"bottom:88px"}; ${d};
14
+ width: 350px; height: calc(100vh - 160px); max-height: 600px;
15
+ border-radius: 16px; border: none; z-index: 2147483645;
16
+ box-shadow: 0 8px 32px rgba(0,0,0,.12);
17
+ opacity: 0; visibility: hidden;
18
+ transform: ${t?"translateY(-24px) scale(.96)":"translateY(24px) scale(.96)"};
19
+ transition: all .4s cubic-bezier(.4,0,.2,1);
20
+ }
21
+ #dunefox-frame.df-open {
22
+ opacity: 1; visibility: visible; transform: translateY(0) scale(1);
23
+ }
24
+ @media (max-width: 768px) {
25
+ #dunefox-frame {
26
+ width: 100vw; height: 100vh; max-height: none;
27
+ inset: 0; border-radius: 0; transform: ${t?"translateY(-100%)":"translateY(100%)"};
28
+ bottom: unset; right: unset; left: unset; top: unset;
29
+ }
30
+ #dunefox-frame.df-open { transform: translateY(0); }
31
+ }
32
+ `,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>',u=false;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:d="bottom-right",defaultOpen:f=false,baseUrl:p=v,iconUrl:m=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(),c=`${p}/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=f,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(),u=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(),u=false;}function U(e){let t=useRef(e);return useEffect(()=>{if(typeof window!="undefined")return b(t.current),()=>{g();}},[]),null}
33
+ export{U as DunefoxChatbot};//# sourceMappingURL=react.mjs.map
34
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +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() */\nexport interface DunefoxChatOptions {\n /** Your Dunefox Tenant ID — found in Settings > Install Widget */\n tenantId: string;\n /**\n * Where to anchor the widget. Defaults to \"bottom-right\".\n * Supports: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\n */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Open the chat panel on load. Defaults to false. */\n defaultOpen?: boolean;\n /** Override the base URL (for self-hosted / staging). */\n baseUrl?: string;\n /** Icon image URL override. Falls back to Dunefox CDN asset. */\n iconUrl?: string;\n}\n\nconst DEFAULT_BASE = 'https://app.dunefox.io';\nconst DEFAULT_ICON = 'https://dunefoxx.s3.ap-south-1.amazonaws.com/chatbot.png';\nconst UUID_KEY = 'dunefox_chat_uuid';\n\n// ─── UUID generation ──────────────────────────────────────────────────────────\nfunction getOrCreateUUID(): string {\n try {\n let id = localStorage.getItem(UUID_KEY);\n if (!id) {\n id = crypto?.randomUUID?.()\n ?? Math.random().toString(36).slice(2) + Date.now().toString(36);\n localStorage.setItem(UUID_KEY, id);\n }\n return id;\n } catch {\n // Incognito / storage blocked — generate ephemeral ID\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n }\n}\n\n// ─── CSS injector ─────────────────────────────────────────────────────────────\nfunction injectStyles(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'): void {\n if (document.getElementById('dunefox-styles')) return;\n\n const isTop = position.startsWith('top');\n const isLeft = position.endsWith('left');\n const hSide = isLeft ? 'left:16px' : 'right:16px';\n const vBtn = isTop ? 'top:16px' : 'bottom:16px';\n // Panel opens away from the button edge\n const vFrame = isTop ? 'top:88px' : 'bottom:88px';\n // Slide direction: panels below slide up, panels above slide down\n const slideOut = isTop ? 'translateY(-24px) scale(.96)' : 'translateY(24px) scale(.96)';\n // Hover nudge direction\n const hoverTranslate = isTop ? 'translateY(2px) scale(1.05)' : 'translateY(-2px) scale(1.05)';\n // Mobile slide direction\n const mobileSlideOut = isTop ? 'translateY(-100%)' : 'translateY(100%)';\n\n const css = `\n #dunefox-btn {\n position: fixed; ${vBtn}; ${hSide};\n width: 60px; height: 60px; border-radius: 50%;\n background: #1a1a1a; border: none; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483646;\n box-shadow: 0 4px 20px rgba(0,0,0,.25);\n transition: transform .25s ease;\n }\n #dunefox-btn:hover { transform: ${hoverTranslate}; }\n #dunefox-frame {\n position: fixed; ${vFrame}; ${hSide};\n width: 350px; height: calc(100vh - 160px); max-height: 600px;\n border-radius: 16px; border: none; z-index: 2147483645;\n box-shadow: 0 8px 32px rgba(0,0,0,.12);\n opacity: 0; visibility: hidden;\n transform: ${slideOut};\n transition: all .4s cubic-bezier(.4,0,.2,1);\n }\n #dunefox-frame.df-open {\n opacity: 1; visibility: visible; transform: translateY(0) scale(1);\n }\n @media (max-width: 768px) {\n #dunefox-frame {\n width: 100vw; height: 100vh; max-height: none;\n inset: 0; border-radius: 0; transform: ${mobileSlideOut};\n bottom: unset; right: unset; left: unset; top: unset;\n }\n #dunefox-frame.df-open { transform: translateY(0); }\n }\n `;\n const tag = document.createElement('style');\n tag.id = 'dunefox-styles';\n tag.textContent = css;\n document.head.appendChild(tag);\n}\n\n// ─── SVG icons ────────────────────────────────────────────────────────────────\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>`;\n\n// ─── Main init ────────────────────────────────────────────────────────────────\nlet _initialised = false;\n\n/**\n * Initialise the Dunefox chatbot widget.\n * Call once on DOMContentLoaded or after your framework has mounted.\n *\n * @example\n * import { init } from 'dunefox-chatbot';\n * init({ tenantId: 'YOUR_TENANT_ID' });\n */\nexport function init(options: DunefoxChatOptions | string): void {\n // Allow shorthand: init('tenantId')\n const opts: DunefoxChatOptions =\n typeof options === 'string' ? { tenantId: options } : options;\n\n if (_initialised) {\n console.warn('[DunefoxChat] Already initialised. Call destroy() first.');\n return;\n }\n\n const {\n tenantId,\n position = 'bottom-right',\n defaultOpen = false,\n baseUrl = DEFAULT_BASE,\n iconUrl = DEFAULT_ICON,\n } = opts;\n\n if (!tenantId) throw new Error('[DunefoxChat] tenantId is required.');\n\n // Wait for DOM if we're running during <head> parse\n const ready = (cb: () => void) =>\n document.readyState === 'loading'\n ? document.addEventListener('DOMContentLoaded', cb, { once: true })\n : cb();\n\n ready(() => {\n injectStyles(position);\n\n const uuid = getOrCreateUUID();\n const src = `${baseUrl}/api/${tenantId}?uuid=${uuid}`;\n\n // ── iframe ──\n const iframe = document.createElement('iframe');\n iframe.id = 'dunefox-frame';\n iframe.title = 'Dunefox Chat';\n iframe.loading = 'lazy';\n iframe.src = src;\n\n // ── toggle button ──\n const btn = document.createElement('button');\n btn.id = 'dunefox-btn';\n btn.setAttribute('aria-label', 'Open chat');\n btn.setAttribute('aria-expanded', 'false');\n\n const img = document.createElement('img');\n img.src = iconUrl;\n img.alt = '';\n img.width = 60;\n img.height = 60;\n img.style.cssText = 'display:block;border-radius:50%;object-fit:cover;';\n\n const closeSpan = document.createElement('span');\n closeSpan.innerHTML = CLOSE_SVG;\n closeSpan.style.display = 'none';\n\n btn.append(img, closeSpan);\n\n // ── state ──\n let open = defaultOpen;\n const applyState = () => {\n iframe.classList.toggle('df-open', open);\n img.style.display = open ? 'none' : 'block';\n closeSpan.style.display = open ? 'block' : 'none';\n btn.setAttribute('aria-expanded', String(open));\n };\n\n btn.addEventListener('click', () => {\n open = !open;\n applyState();\n });\n\n document.body.append(iframe, btn);\n applyState();\n _initialised = true;\n });\n}\n\n/**\n * Programmatically open the chat panel.\n */\nexport function open(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.add('df-open');\n btn.setAttribute('aria-expanded', 'true');\n}\n\n/**\n * Programmatically close the chat panel.\n */\nexport function close(): void {\n const iframe = document.getElementById('dunefox-frame') as HTMLIFrameElement | null;\n const btn = document.getElementById('dunefox-btn');\n if (!iframe || !btn) return;\n iframe.classList.remove('df-open');\n btn.setAttribute('aria-expanded', 'false');\n}\n\n/**\n * Remove the widget entirely.\n */\nexport function destroy(): void {\n document.getElementById('dunefox-frame')?.remove();\n document.getElementById('dunefox-btn')?.remove();\n document.getElementById('dunefox-styles')?.remove();\n _initialised = false;\n}\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 ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "dunefox-chatbot",
3
+ "version": "1.0.0",
4
+ "description": "Official Dunefox chatbot widget — add AI chat to any website in seconds",
5
+ "keywords": [
6
+ "dunefox",
7
+ "chatbot",
8
+ "widget",
9
+ "ai",
10
+ "chat"
11
+ ],
12
+ "homepage": "https://dunefox.io",
13
+ "license": "MIT",
14
+ "author": "Dunefox",
15
+ "sideEffects": false,
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./react": {
26
+ "types": "./dist/react.d.ts",
27
+ "import": "./dist/react.js",
28
+ "require": "./dist/react.cjs"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^18.2.0",
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.0.0"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=17",
46
+ "react-dom": ">=17"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "react": {
50
+ "optional": true
51
+ },
52
+ "react-dom": {
53
+ "optional": true
54
+ }
55
+ }
56
+ }