@sparrowdesk/react-chat 0.1.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,9 @@
1
+ # @sparrowdesk/react-chat
2
+
3
+ SparrowDesk Chat Widget for React.
4
+
5
+ ```sh
6
+ pnpm add @sparrowdesk/react-chat
7
+ ```
8
+
9
+ See the [main README](../../README.md) for full documentation.
@@ -0,0 +1,151 @@
1
+ import * as React$1 from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/internal/sparrowDeskWidget.d.ts
5
+ interface SparrowDeskApi {
6
+ openWidget?: () => void;
7
+ closeWidget?: () => void;
8
+ hideWidget?: () => void;
9
+ onOpen?: (callback: () => void) => void;
10
+ onClose?: (callback: () => void) => void;
11
+ setTags?: (tags: string[]) => void;
12
+ setConversationFields?: (fields: Record<string, unknown>) => void;
13
+ setContactFields?: (fields: Record<string, unknown>) => void;
14
+ status?: 'open' | 'closed';
15
+ }
16
+ declare global {
17
+ interface Window {
18
+ SD_WIDGET_TOKEN?: string;
19
+ SD_WIDGET_DOMAIN?: string;
20
+ sparrowDesk?: SparrowDeskApi;
21
+ }
22
+ }
23
+ //#endregion
24
+ //#region src/Chat.d.ts
25
+ interface ChatProps {
26
+ /** SparrowDesk domain, e.g. "sparrowdesk7975310.sparrowdesk.com" */
27
+ domain: string;
28
+ /** SparrowDesk widget token */
29
+ token: string;
30
+ /** Optional tags (e.g. user identifiers) for the current session. */
31
+ tags?: string[];
32
+ /**
33
+ * Contact fields to set during init (expects internal_name keys).
34
+ * Invalid internal_names / invalid values are skipped by the widget itself.
35
+ */
36
+ contactFields?: Record<string, unknown>;
37
+ /**
38
+ * Conversation fields to set during init (expects internal_name keys).
39
+ * Invalid internal_names / invalid values are skipped by the widget itself.
40
+ */
41
+ conversationFields?: Record<string, unknown>;
42
+ /** Called once the widget API is available on `window.sparrowDesk`. */
43
+ onReady?: (api: SparrowDeskApi) => void;
44
+ /** Called when the widget opens (registered via `window.sparrowDesk.onOpen`). */
45
+ onOpen?: () => void;
46
+ /** Called when the widget closes (registered via `window.sparrowDesk.onClose`). */
47
+ onClose?: () => void;
48
+ /** If true, calls `window.sparrowDesk.openWidget()` once when ready. */
49
+ openOnInit?: boolean;
50
+ /** If true, calls `window.sparrowDesk.hideWidget()` once when ready. */
51
+ hideOnInit?: boolean;
52
+ /**
53
+ * Controls whether this component should set globals and inject the widget script.
54
+ * Set to `false` if SparrowDesk is loaded elsewhere and you only want to apply
55
+ * fields/tags + register callbacks.
56
+ */
57
+ shouldInitialize?: boolean;
58
+ /**
59
+ * If `false`, defers injecting the widget script + waiting for the API until
60
+ * the visitor interacts (when `initializeOnInteraction` is enabled).
61
+ *
62
+ * This is a performance optimization implemented at the wrapper level by delaying
63
+ * script injection.
64
+ */
65
+ connectOnPageLoad?: boolean;
66
+ /**
67
+ * When `connectOnPageLoad={false}`, if `true`, initialize the widget on the first
68
+ * user interaction (pointer or keyboard), then remove those listeners.
69
+ */
70
+ initializeOnInteraction?: boolean;
71
+ /** If true, removes the injected script tag on unmount. */
72
+ cleanupOnUnmount?: boolean;
73
+ /**
74
+ * How long to wait (ms) for `window.sparrowDesk` to become available after init.
75
+ * Defaults to 10s.
76
+ */
77
+ readyTimeoutMs?: number;
78
+ }
79
+ declare const Chat: React.FC<ChatProps>;
80
+ //#endregion
81
+ //#region src/SparrowDeskProvider.d.ts
82
+ type SparrowDeskProviderProps = {
83
+ /** SparrowDesk domain, e.g. "sparrowdesk7975310.sparrowdesk.com" */
84
+ domain: string;
85
+ /** SparrowDesk widget token */
86
+ token: string;
87
+ children: React$1.ReactNode;
88
+ /**
89
+ * Controls whether this provider should set globals and inject the widget script.
90
+ * Set to `false` if SparrowDesk is loaded elsewhere (e.g. via Segment) and you only
91
+ * want the hook-based API.
92
+ */
93
+ shouldInitialize?: boolean;
94
+ /**
95
+ * If `false`, defers injecting the widget script + waiting for the API until
96
+ * you call `initialize()` (or invoke `openWidget`/`closeWidget`/`hideWidget`/etc),
97
+ * or until the first user interaction when `initializeOnInteraction` is enabled.
98
+ *
99
+ * This is a performance optimization implemented at the wrapper level by delaying
100
+ * script injection until you explicitly initialize.
101
+ */
102
+ connectOnPageLoad?: boolean;
103
+ /**
104
+ * When `connectOnPageLoad={false}`, if `true`, initialize on the first
105
+ * user interaction (pointer or keyboard). Defaults to `true`.
106
+ */
107
+ initializeOnInteraction?: boolean;
108
+ tags?: string[];
109
+ contactFields?: Record<string, unknown>;
110
+ conversationFields?: Record<string, unknown>;
111
+ onReady?: (api: SparrowDeskApi) => void;
112
+ onOpen?: () => void;
113
+ onClose?: () => void;
114
+ openOnInit?: boolean;
115
+ hideOnInit?: boolean;
116
+ cleanupOnUnmount?: boolean;
117
+ readyTimeoutMs?: number;
118
+ };
119
+ type SparrowDeskContextValue = {
120
+ isReady: boolean;
121
+ api: SparrowDeskApi | null;
122
+ /** Ensures the widget script is injected (if enabled) and begins waiting for the API. */
123
+ initialize: () => void;
124
+ openWidget: () => void;
125
+ closeWidget: () => void;
126
+ hideWidget: () => void;
127
+ setTags: (tags: string[]) => void;
128
+ setContactFields: (fields: Record<string, unknown>) => void;
129
+ setConversationFields: (fields: Record<string, unknown>) => void;
130
+ };
131
+ declare function useSparrowDesk(): SparrowDeskContextValue;
132
+ declare function SparrowDeskProvider({
133
+ domain,
134
+ token,
135
+ children,
136
+ shouldInitialize,
137
+ connectOnPageLoad,
138
+ initializeOnInteraction,
139
+ tags,
140
+ contactFields,
141
+ conversationFields,
142
+ onReady,
143
+ onOpen,
144
+ onClose,
145
+ openOnInit,
146
+ hideOnInit,
147
+ cleanupOnUnmount,
148
+ readyTimeoutMs
149
+ }: SparrowDeskProviderProps): react_jsx_runtime0.JSX.Element;
150
+ //#endregion
151
+ export { Chat, type ChatProps, type SparrowDeskApi, type SparrowDeskContextValue, SparrowDeskProvider, type SparrowDeskProviderProps, useSparrowDesk };
package/dist/index.js ADDED
@@ -0,0 +1,400 @@
1
+ import * as React from "react";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/internal/sparrowDeskWidget.ts
6
+ const DEFAULT_SCRIPT_SRC = "https://assets.cdn.sparrowdesk.com/chatbot/bundle/main.js";
7
+ const DEFAULT_READY_TIMEOUT_MS = 1e4;
8
+ const WIDGET_SCRIPT_SELECTOR = "script[data-sd-chat-widget=\"true\"]";
9
+ function isBrowser() {
10
+ return globalThis.document !== void 0;
11
+ }
12
+ function normalizeRequired(value) {
13
+ return value.trim();
14
+ }
15
+ function setWidgetGlobals(domain, token) {
16
+ const w = globalThis;
17
+ w.SD_WIDGET_DOMAIN = domain;
18
+ w.SD_WIDGET_TOKEN = token;
19
+ }
20
+ const scriptEntriesBySrc = /* @__PURE__ */ new Map();
21
+ function removeOtherWidgetScripts(keepSrc) {
22
+ document.querySelectorAll(WIDGET_SCRIPT_SELECTOR).forEach((script) => {
23
+ if (script.src !== keepSrc) script.remove();
24
+ });
25
+ }
26
+ function acquireWidgetScript(src, cleanupOnUnmount) {
27
+ const cached = scriptEntriesBySrc.get(src);
28
+ if (cached) {
29
+ cached.refCount += 1;
30
+ cached.cleanupWhenUnused ||= cleanupOnUnmount;
31
+ return { release() {
32
+ cached.refCount -= 1;
33
+ if (cached.refCount > 0) return;
34
+ if (cached.cleanupWhenUnused) cached.script.remove();
35
+ scriptEntriesBySrc.delete(src);
36
+ } };
37
+ }
38
+ const existing = document.querySelector(WIDGET_SCRIPT_SELECTOR);
39
+ const entry = {
40
+ script: existing?.src === src ? existing : (() => {
41
+ const el = document.createElement("script");
42
+ el.async = true;
43
+ el.src = src;
44
+ el.dataset["sdChatWidget"] = "true";
45
+ document.body.appendChild(el);
46
+ return el;
47
+ })(),
48
+ refCount: 1,
49
+ cleanupWhenUnused: cleanupOnUnmount
50
+ };
51
+ scriptEntriesBySrc.set(src, entry);
52
+ return { release() {
53
+ entry.refCount -= 1;
54
+ if (entry.refCount > 0) return;
55
+ if (entry.cleanupWhenUnused) entry.script.remove();
56
+ scriptEntriesBySrc.delete(src);
57
+ } };
58
+ }
59
+ async function waitForSparrowDeskApi(timeoutMs) {
60
+ const w = globalThis;
61
+ if (w.sparrowDesk) return w.sparrowDesk;
62
+ if (timeoutMs <= 0) return null;
63
+ const startedAt = Date.now();
64
+ while (Date.now() - startedAt < timeoutMs) {
65
+ if (w.sparrowDesk) return w.sparrowDesk;
66
+ await new Promise((r) => setTimeout(r, 50));
67
+ }
68
+ return null;
69
+ }
70
+
71
+ //#endregion
72
+ //#region src/internal/useLatest.ts
73
+ function useLatest(value) {
74
+ const ref = useRef(value);
75
+ useEffect(() => {
76
+ ref.current = value;
77
+ }, [value]);
78
+ return ref;
79
+ }
80
+
81
+ //#endregion
82
+ //#region src/Chat.tsx
83
+ const Chat = ({ domain, token, tags, contactFields, conversationFields, onReady, onOpen, onClose, openOnInit = false, hideOnInit = false, shouldInitialize = true, connectOnPageLoad = true, initializeOnInteraction = true, cleanupOnUnmount = false, readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS }) => {
84
+ const normalized = useMemo(() => {
85
+ return {
86
+ domain: normalizeRequired(domain),
87
+ token: normalizeRequired(token)
88
+ };
89
+ }, [domain, token]);
90
+ const onOpenRef = useLatest(onOpen);
91
+ const onCloseRef = useLatest(onClose);
92
+ const onReadyRef = useLatest(onReady);
93
+ const tagsRef = useLatest(tags);
94
+ const contactFieldsRef = useLatest(contactFields);
95
+ const conversationFieldsRef = useLatest(conversationFields);
96
+ const registeredCallbacksRef = useRef(false);
97
+ const apiRef = useRef(null);
98
+ const didOpenOnceRef = useRef(false);
99
+ const didHideOnceRef = useRef(false);
100
+ const [shouldStart, setShouldStart] = useState(connectOnPageLoad);
101
+ useEffect(() => {
102
+ didOpenOnceRef.current = false;
103
+ didHideOnceRef.current = false;
104
+ apiRef.current = null;
105
+ registeredCallbacksRef.current = false;
106
+ setShouldStart(connectOnPageLoad);
107
+ }, [normalized.domain, normalized.token]);
108
+ useEffect(() => {
109
+ if (!isBrowser()) return;
110
+ if (!normalized.domain || !normalized.token) return;
111
+ setWidgetGlobals(normalized.domain, normalized.token);
112
+ if (!shouldInitialize) return;
113
+ if (!shouldStart) return;
114
+ removeOtherWidgetScripts(DEFAULT_SCRIPT_SRC);
115
+ const handle = acquireWidgetScript(DEFAULT_SCRIPT_SRC, cleanupOnUnmount);
116
+ return () => {
117
+ handle.release();
118
+ };
119
+ }, [
120
+ normalized.domain,
121
+ normalized.token,
122
+ cleanupOnUnmount,
123
+ shouldInitialize,
124
+ shouldStart
125
+ ]);
126
+ useEffect(() => {
127
+ if (!isBrowser()) return;
128
+ if (!normalized.domain || !normalized.token) return;
129
+ if (!shouldStart) return;
130
+ let cancelled = false;
131
+ (async () => {
132
+ const api = await waitForSparrowDeskApi(readyTimeoutMs);
133
+ if (cancelled || !api) return;
134
+ apiRef.current = api;
135
+ if (!registeredCallbacksRef.current) {
136
+ api.onOpen?.(() => onOpenRef.current?.());
137
+ api.onClose?.(() => onCloseRef.current?.());
138
+ registeredCallbacksRef.current = true;
139
+ }
140
+ onReadyRef.current?.(api);
141
+ const latestTags = tagsRef.current;
142
+ const latestContactFields = contactFieldsRef.current;
143
+ const latestConversationFields = conversationFieldsRef.current;
144
+ if (Array.isArray(latestTags) && latestTags.length) api.setTags?.(latestTags);
145
+ if (latestContactFields && Object.keys(latestContactFields).length) api.setContactFields?.(latestContactFields);
146
+ if (latestConversationFields && Object.keys(latestConversationFields).length) api.setConversationFields?.(latestConversationFields);
147
+ if (hideOnInit && !didHideOnceRef.current) {
148
+ api.hideWidget?.();
149
+ didHideOnceRef.current = true;
150
+ }
151
+ if (openOnInit && !didOpenOnceRef.current) {
152
+ api.openWidget?.();
153
+ didOpenOnceRef.current = true;
154
+ }
155
+ })();
156
+ return () => {
157
+ cancelled = true;
158
+ };
159
+ }, [
160
+ normalized.domain,
161
+ normalized.token,
162
+ openOnInit,
163
+ hideOnInit,
164
+ readyTimeoutMs,
165
+ shouldStart
166
+ ]);
167
+ useEffect(() => {
168
+ if (!isBrowser()) return;
169
+ if (connectOnPageLoad) return;
170
+ if (!initializeOnInteraction) return;
171
+ if (shouldStart) return;
172
+ if (!normalized.domain || !normalized.token) return;
173
+ const onFirstInteraction = () => {
174
+ setShouldStart(true);
175
+ cleanup();
176
+ };
177
+ const cleanup = () => {
178
+ document.removeEventListener("pointerdown", onFirstInteraction, true);
179
+ document.removeEventListener("keydown", onFirstInteraction, true);
180
+ };
181
+ document.addEventListener("pointerdown", onFirstInteraction, true);
182
+ document.addEventListener("keydown", onFirstInteraction, true);
183
+ return cleanup;
184
+ }, [
185
+ connectOnPageLoad,
186
+ initializeOnInteraction,
187
+ normalized.domain,
188
+ normalized.token,
189
+ shouldStart
190
+ ]);
191
+ useEffect(() => {
192
+ const api = apiRef.current;
193
+ if (!api) return;
194
+ if (Array.isArray(tags) && tags.length) api.setTags?.(tags);
195
+ if (contactFields && Object.keys(contactFields).length) api.setContactFields?.(contactFields);
196
+ if (conversationFields && Object.keys(conversationFields).length) api.setConversationFields?.(conversationFields);
197
+ }, [
198
+ tags,
199
+ contactFields,
200
+ conversationFields
201
+ ]);
202
+ if (!normalized.domain || !normalized.token) return null;
203
+ return /* @__PURE__ */ jsx("div", { "data-sd-chat-widget-container": "" });
204
+ };
205
+
206
+ //#endregion
207
+ //#region src/SparrowDeskProvider.tsx
208
+ const SparrowDeskContext = React.createContext(null);
209
+ function useSparrowDesk() {
210
+ const value = React.useContext(SparrowDeskContext);
211
+ if (!value) throw new Error("useSparrowDesk must be used within <SparrowDeskProvider />");
212
+ return value;
213
+ }
214
+ function SparrowDeskProvider({ domain, token, children, shouldInitialize = true, connectOnPageLoad = true, initializeOnInteraction = true, tags, contactFields, conversationFields, onReady, onOpen, onClose, openOnInit = false, hideOnInit = false, cleanupOnUnmount = false, readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS }) {
215
+ const normalized = useMemo(() => {
216
+ return {
217
+ domain: normalizeRequired(domain),
218
+ token: normalizeRequired(token)
219
+ };
220
+ }, [domain, token]);
221
+ const onReadyRef = useLatest(onReady);
222
+ const onOpenRef = useLatest(onOpen);
223
+ const onCloseRef = useLatest(onClose);
224
+ const tagsRef = useLatest(tags);
225
+ const contactFieldsRef = useLatest(contactFields);
226
+ const conversationFieldsRef = useLatest(conversationFields);
227
+ const openOnInitRef = useLatest(openOnInit);
228
+ const hideOnInitRef = useLatest(hideOnInit);
229
+ const apiRef = useRef(null);
230
+ const registeredCallbacksRef = useRef(false);
231
+ const didOpenOnceRef = useRef(false);
232
+ const didHideOnceRef = useRef(false);
233
+ const scriptHandleRef = useRef(null);
234
+ const initStartedRef = useRef(false);
235
+ const initCancelRef = useRef(null);
236
+ const pendingCallsRef = useRef([]);
237
+ const [isReady, setIsReady] = useState(false);
238
+ const [shouldStart, setShouldStart] = useState(connectOnPageLoad);
239
+ useEffect(() => {
240
+ setShouldStart(connectOnPageLoad);
241
+ }, [connectOnPageLoad]);
242
+ useEffect(() => {
243
+ didOpenOnceRef.current = false;
244
+ didHideOnceRef.current = false;
245
+ apiRef.current = null;
246
+ registeredCallbacksRef.current = false;
247
+ initStartedRef.current = false;
248
+ initCancelRef.current?.();
249
+ initCancelRef.current = null;
250
+ pendingCallsRef.current = [];
251
+ scriptHandleRef.current?.release();
252
+ scriptHandleRef.current = null;
253
+ setIsReady(false);
254
+ setShouldStart(connectOnPageLoad);
255
+ }, [
256
+ normalized.domain,
257
+ normalized.token,
258
+ connectOnPageLoad
259
+ ]);
260
+ const initialize = React.useCallback(() => {
261
+ if (!isBrowser()) return;
262
+ if (!normalized.domain || !normalized.token) return;
263
+ setWidgetGlobals(normalized.domain, normalized.token);
264
+ if (shouldInitialize && !scriptHandleRef.current) {
265
+ removeOtherWidgetScripts(DEFAULT_SCRIPT_SRC);
266
+ scriptHandleRef.current = acquireWidgetScript(DEFAULT_SCRIPT_SRC, cleanupOnUnmount);
267
+ }
268
+ if (initStartedRef.current) return;
269
+ initStartedRef.current = true;
270
+ let cancelled = false;
271
+ initCancelRef.current = () => {
272
+ cancelled = true;
273
+ };
274
+ (async () => {
275
+ const api = await waitForSparrowDeskApi(readyTimeoutMs);
276
+ if (cancelled || !api) return;
277
+ apiRef.current = api;
278
+ setIsReady(true);
279
+ if (!registeredCallbacksRef.current) {
280
+ api.onOpen?.(() => onOpenRef.current?.());
281
+ api.onClose?.(() => onCloseRef.current?.());
282
+ registeredCallbacksRef.current = true;
283
+ }
284
+ onReadyRef.current?.(api);
285
+ const latestTags = tagsRef.current;
286
+ const latestContactFields = contactFieldsRef.current;
287
+ const latestConversationFields = conversationFieldsRef.current;
288
+ if (Array.isArray(latestTags) && latestTags.length) api.setTags?.(latestTags);
289
+ if (latestContactFields && Object.keys(latestContactFields).length) api.setContactFields?.(latestContactFields);
290
+ if (latestConversationFields && Object.keys(latestConversationFields).length) api.setConversationFields?.(latestConversationFields);
291
+ if (hideOnInitRef.current && !didHideOnceRef.current) {
292
+ api.hideWidget?.();
293
+ didHideOnceRef.current = true;
294
+ }
295
+ if (openOnInitRef.current && !didOpenOnceRef.current) {
296
+ api.openWidget?.();
297
+ didOpenOnceRef.current = true;
298
+ }
299
+ const pending = pendingCallsRef.current;
300
+ pendingCallsRef.current = [];
301
+ pending.forEach((fn) => fn(api));
302
+ })();
303
+ }, [
304
+ cleanupOnUnmount,
305
+ normalized.domain,
306
+ normalized.token,
307
+ readyTimeoutMs,
308
+ shouldInitialize
309
+ ]);
310
+ useEffect(() => {
311
+ if (!isBrowser()) return;
312
+ if (!normalized.domain || !normalized.token) return;
313
+ setWidgetGlobals(normalized.domain, normalized.token);
314
+ if (!shouldStart) return;
315
+ initialize();
316
+ return () => {
317
+ initCancelRef.current?.();
318
+ initCancelRef.current = null;
319
+ if (!apiRef.current) initStartedRef.current = false;
320
+ scriptHandleRef.current?.release();
321
+ scriptHandleRef.current = null;
322
+ };
323
+ }, [
324
+ initialize,
325
+ normalized.domain,
326
+ normalized.token,
327
+ shouldStart
328
+ ]);
329
+ useEffect(() => {
330
+ if (!isBrowser()) return;
331
+ if (connectOnPageLoad) return;
332
+ if (!initializeOnInteraction) return;
333
+ if (shouldStart) return;
334
+ if (!normalized.domain || !normalized.token) return;
335
+ const onFirstInteraction = () => {
336
+ setShouldStart(true);
337
+ cleanup();
338
+ };
339
+ const cleanup = () => {
340
+ document.removeEventListener("pointerdown", onFirstInteraction, true);
341
+ document.removeEventListener("keydown", onFirstInteraction, true);
342
+ };
343
+ document.addEventListener("pointerdown", onFirstInteraction, true);
344
+ document.addEventListener("keydown", onFirstInteraction, true);
345
+ return cleanup;
346
+ }, [
347
+ connectOnPageLoad,
348
+ initializeOnInteraction,
349
+ normalized.domain,
350
+ normalized.token,
351
+ shouldStart
352
+ ]);
353
+ useEffect(() => {
354
+ const api = apiRef.current;
355
+ if (!api) return;
356
+ if (Array.isArray(tags) && tags.length) api.setTags?.(tags);
357
+ if (contactFields && Object.keys(contactFields).length) api.setContactFields?.(contactFields);
358
+ if (conversationFields && Object.keys(conversationFields).length) api.setConversationFields?.(conversationFields);
359
+ }, [
360
+ tags,
361
+ contactFields,
362
+ conversationFields
363
+ ]);
364
+ const methods = useMemo(() => {
365
+ const callOrQueue = (fn) => {
366
+ const api = apiRef.current;
367
+ if (api) {
368
+ fn(api);
369
+ return;
370
+ }
371
+ if (!connectOnPageLoad) {
372
+ initialize();
373
+ pendingCallsRef.current.push(fn);
374
+ }
375
+ };
376
+ return {
377
+ initialize,
378
+ openWidget: () => callOrQueue((api) => api.openWidget?.()),
379
+ closeWidget: () => callOrQueue((api) => api.closeWidget?.()),
380
+ hideWidget: () => callOrQueue((api) => api.hideWidget?.()),
381
+ setTags: (t) => callOrQueue((api) => api.setTags?.(t)),
382
+ setContactFields: (f) => callOrQueue((api) => api.setContactFields?.(f)),
383
+ setConversationFields: (f) => callOrQueue((api) => api.setConversationFields?.(f))
384
+ };
385
+ }, [connectOnPageLoad, initialize]);
386
+ const value = useMemo(() => {
387
+ return {
388
+ ...methods,
389
+ isReady,
390
+ api: apiRef.current
391
+ };
392
+ }, [methods, isReady]);
393
+ return /* @__PURE__ */ jsx(SparrowDeskContext.Provider, {
394
+ value,
395
+ children
396
+ });
397
+ }
398
+
399
+ //#endregion
400
+ export { Chat, SparrowDeskProvider, useSparrowDesk };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@sparrowdesk/react-chat",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "SparrowDesk Chat Widget for React.",
6
+ "keywords": [
7
+ "sparrowdesk",
8
+ "chat",
9
+ "chat-widget",
10
+ "widget",
11
+ "support",
12
+ "customer-support",
13
+ "react"
14
+ ],
15
+ "author": "SparrowDesk",
16
+ "license": "MIT",
17
+ "homepage": "https://github.com/sparrowdesk/sd-chat-widget-react#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/sparrowdesk/sd-chat-widget-react.git",
21
+ "directory": "packages/react-chat"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/sparrowdesk/sd-chat-widget-react/issues"
25
+ },
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": "./dist/index.js",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "main": "./dist/index.js",
32
+ "module": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "dev": "tsdown --watch",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "typecheck": "tsc --noEmit",
46
+ "release": "bumpp && pnpm publish",
47
+ "prepublishOnly": "pnpm run build"
48
+ },
49
+ "peerDependencies": {
50
+ "react": "^19.2.0",
51
+ "react-dom": "^19.2.0"
52
+ },
53
+ "devDependencies": {
54
+ "@tsconfig/strictest": "^2.0.8",
55
+ "@types/node": "^25.0.3",
56
+ "@types/react": "^19.2.7",
57
+ "@types/react-dom": "^19.2.3",
58
+ "@vitest/browser-playwright": "^4.0.16",
59
+ "bumpp": "^10.3.2",
60
+ "playwright": "^1.57.0",
61
+ "react": "^19.2.0",
62
+ "react-dom": "^19.2.0",
63
+ "tsdown": "^0.18.1",
64
+ "typescript": "^5.9.3",
65
+ "vitest": "^4.0.16",
66
+ "vitest-browser-react": "^2.0.2"
67
+ }
68
+ }