@customerhero/react 2.2.0 → 2.3.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/dist/index.cjs CHANGED
@@ -18,16 +18,16 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
23
  ActionConfirmationCard: () => ActionConfirmationCard,
24
24
  ChatWidget: () => ChatWidget,
25
25
  useChat: () => useChat
26
26
  });
27
- module.exports = __toCommonJS(index_exports);
27
+ module.exports = __toCommonJS(src_exports);
28
28
 
29
29
  // src/components/chat-widget.tsx
30
- var import_react11 = require("react");
30
+ var import_react12 = require("react");
31
31
 
32
32
  // src/context.tsx
33
33
  var import_react = require("react");
@@ -36,6 +36,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
36
36
  var CustomerHeroContext = (0, import_react.createContext)(null);
37
37
  function CustomerHeroProvider({
38
38
  children,
39
+ disableAutoFetch,
39
40
  ...config
40
41
  }) {
41
42
  const clientRef = (0, import_react.useRef)(null);
@@ -43,8 +44,9 @@ function CustomerHeroProvider({
43
44
  clientRef.current = new import_js.CustomerHeroChat(config);
44
45
  }
45
46
  (0, import_react.useEffect)(() => {
47
+ if (disableAutoFetch) return;
46
48
  clientRef.current?.fetchConfig();
47
- }, []);
49
+ }, [disableAutoFetch]);
48
50
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CustomerHeroContext.Provider, { value: clientRef.current, children });
49
51
  }
50
52
  function useCustomerHeroClient() {
@@ -127,7 +129,7 @@ function useChat() {
127
129
  }
128
130
 
129
131
  // src/components/chat-bubble.tsx
130
- var import_react4 = require("react");
132
+ var import_react5 = require("react");
131
133
 
132
134
  // src/use-reduced-motion.ts
133
135
  var import_react3 = require("react");
@@ -145,41 +147,110 @@ function useReducedMotion() {
145
147
  return reduced;
146
148
  }
147
149
 
150
+ // src/use-effective-theme.ts
151
+ var import_js2 = require("@customerhero/js");
152
+
153
+ // src/use-prefers-dark.ts
154
+ var import_react4 = require("react");
155
+ function usePrefersDark() {
156
+ const [prefersDark, setPrefersDark] = (0, import_react4.useState)(false);
157
+ (0, import_react4.useEffect)(() => {
158
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
159
+ return;
160
+ }
161
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
162
+ const update = () => setPrefersDark(mq.matches);
163
+ update();
164
+ if (typeof mq.addEventListener === "function") {
165
+ mq.addEventListener("change", update);
166
+ return () => mq.removeEventListener("change", update);
167
+ }
168
+ mq.addListener(update);
169
+ return () => mq.removeListener(update);
170
+ }, []);
171
+ return prefersDark;
172
+ }
173
+
174
+ // src/use-effective-theme.ts
175
+ function useEffectiveTheme() {
176
+ const { config } = useChat();
177
+ const prefersDark = usePrefersDark();
178
+ const scheme = (0, import_js2.resolveScheme)(config.colorScheme, prefersDark);
179
+ const colors = (0, import_js2.effectiveColors)(config, scheme);
180
+ const isDark = scheme === "dark";
181
+ return {
182
+ scheme,
183
+ primary: colors.primary,
184
+ background: colors.background,
185
+ text: colors.text,
186
+ bubbleBackground: isDark ? "rgba(255,255,255,0.08)" : "#f0f0f0",
187
+ divider: isDark ? "rgba(255,255,255,0.12)" : "#e0e0e0",
188
+ size: (0, import_js2.sizePreset)(config.size),
189
+ radius: (0, import_js2.panelRadius)(config.cornerStyle)
190
+ };
191
+ }
192
+
148
193
  // src/components/chat-bubble.tsx
149
194
  var import_jsx_runtime2 = require("react/jsx-runtime");
150
- function ChatBubble() {
195
+ function ChatBubble({ embedded } = {}) {
151
196
  const { toggle, config, t, isRtl } = useChat();
152
197
  const reduced = useReducedMotion();
153
- const [mounted, setMounted] = (0, import_react4.useState)(false);
154
- (0, import_react4.useEffect)(() => {
198
+ const theme = useEffectiveTheme();
199
+ const [mounted, setMounted] = (0, import_react5.useState)(false);
200
+ (0, import_react5.useEffect)(() => {
155
201
  const id = requestAnimationFrame(() => setMounted(true));
156
202
  return () => cancelAnimationFrame(id);
157
203
  }, []);
158
204
  const visible = mounted;
159
205
  const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
206
+ const colors = theme;
207
+ const preset = theme.size;
208
+ const hasLabel = !!config.launcher.label;
209
+ const width = hasLabel ? "auto" : preset.bubble;
210
+ const height = preset.bubble;
211
+ const borderRadius = hasLabel ? preset.bubble / 2 : "50%";
160
212
  const style = {
161
- position: "fixed",
162
- bottom: 20,
163
- [effectivePosition === "bottom-left" ? "left" : "right"]: 20,
164
- width: 56,
165
- height: 56,
166
- borderRadius: "50%",
167
- background: config.primaryColor,
168
- color: "white",
213
+ position: embedded ? "absolute" : "fixed",
214
+ bottom: config.offset.bottom,
215
+ [effectivePosition === "bottom-left" ? "left" : "right"]: config.offset.side,
216
+ width,
217
+ height,
218
+ minWidth: preset.bubble,
219
+ paddingInline: hasLabel ? Math.round(preset.bubble * 0.35) : 0,
220
+ borderRadius,
221
+ background: colors.primary,
222
+ color: "#FFFFFF",
169
223
  display: "flex",
170
224
  alignItems: "center",
171
225
  justifyContent: "center",
226
+ gap: hasLabel ? 8 : 0,
172
227
  cursor: "pointer",
173
- boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
174
- zIndex: 99999,
228
+ boxShadow: theme.scheme === "dark" ? "0 6px 20px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.06)" : "0 4px 20px rgba(0,0,0,0.15)",
229
+ zIndex: config.zIndex,
175
230
  border: "none",
176
- padding: 0,
231
+ fontSize: preset.fontSize,
232
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
233
+ fontWeight: 500,
177
234
  opacity: visible ? 1 : 0,
178
235
  transform: visible ? "scale(1)" : "scale(0.6)",
179
236
  transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease",
180
237
  pointerEvents: visible ? "auto" : "none"
181
238
  };
182
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
239
+ const iconSize = Math.round(preset.bubble * 0.43);
240
+ const dotSize = Math.max(10, Math.round(preset.bubble * 0.22));
241
+ const dotOffset = Math.round(preset.bubble * 0.08);
242
+ const dotStyle = {
243
+ position: "absolute",
244
+ top: dotOffset,
245
+ [effectivePosition === "bottom-left" ? "left" : "right"]: dotOffset,
246
+ width: dotSize,
247
+ height: dotSize,
248
+ borderRadius: "50%",
249
+ background: "#22c55e",
250
+ border: "2px solid rgba(255,255,255,0.9)",
251
+ pointerEvents: "none"
252
+ };
253
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
183
254
  "button",
184
255
  {
185
256
  onClick: toggle,
@@ -187,41 +258,55 @@ function ChatBubble() {
187
258
  dir: isRtl ? "rtl" : "ltr",
188
259
  "aria-label": t("open_chat"),
189
260
  onMouseEnter: (e) => {
190
- if (!reduced) e.currentTarget.style.transform = "scale(1.1)";
261
+ if (!reduced) e.currentTarget.style.transform = "scale(1.06)";
191
262
  },
192
263
  onMouseLeave: (e) => {
193
264
  if (!reduced) e.currentTarget.style.transform = "scale(1)";
194
265
  },
195
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
196
- "svg",
197
- {
198
- width: "24",
199
- height: "24",
200
- viewBox: "0 0 24 24",
201
- fill: "none",
202
- stroke: "currentColor",
203
- strokeWidth: "2",
204
- strokeLinecap: "round",
205
- strokeLinejoin: "round",
206
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
207
- }
208
- )
266
+ children: [
267
+ config.launcher.iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
268
+ "img",
269
+ {
270
+ src: config.launcher.iconUrl,
271
+ alt: "",
272
+ width: iconSize,
273
+ height: iconSize,
274
+ style: { display: "block" }
275
+ }
276
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
277
+ "svg",
278
+ {
279
+ width: iconSize,
280
+ height: iconSize,
281
+ viewBox: "0 0 24 24",
282
+ fill: "none",
283
+ stroke: "currentColor",
284
+ strokeWidth: "2",
285
+ strokeLinecap: "round",
286
+ strokeLinejoin: "round",
287
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
288
+ }
289
+ ),
290
+ hasLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: config.launcher.label }),
291
+ config.launcher.showOnlineDot && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: dotStyle, "aria-hidden": true })
292
+ ]
209
293
  }
210
294
  );
211
295
  }
212
296
 
213
297
  // src/components/chat-window.tsx
214
- var import_react10 = require("react");
298
+ var import_react11 = require("react");
215
299
 
216
300
  // src/components/chat-header.tsx
217
- var import_react5 = require("react");
301
+ var import_react6 = require("react");
218
302
  var import_jsx_runtime3 = require("react/jsx-runtime");
219
303
  function ChatHeader() {
220
304
  const { config, close, reset, t } = useChat();
221
305
  const reduced = useReducedMotion();
222
- const [menuOpen, setMenuOpen] = (0, import_react5.useState)(false);
223
- const menuRef = (0, import_react5.useRef)(null);
224
- (0, import_react5.useEffect)(() => {
306
+ const theme = useEffectiveTheme();
307
+ const [menuOpen, setMenuOpen] = (0, import_react6.useState)(false);
308
+ const menuRef = (0, import_react6.useRef)(null);
309
+ (0, import_react6.useEffect)(() => {
225
310
  if (!menuOpen) return;
226
311
  const handleClick = (e) => {
227
312
  if (menuRef.current && !menuRef.current.contains(e.target)) {
@@ -232,7 +317,7 @@ function ChatHeader() {
232
317
  return () => document.removeEventListener("mousedown", handleClick);
233
318
  }, [menuOpen]);
234
319
  const headerStyle = {
235
- background: config.primaryColor,
320
+ background: theme.primary,
236
321
  padding: 16,
237
322
  display: "flex",
238
323
  alignItems: "center",
@@ -267,14 +352,16 @@ function ChatHeader() {
267
352
  opacity: 0.7,
268
353
  padding: 4
269
354
  };
355
+ const isDark = theme.scheme === "dark";
270
356
  const menuStyle = {
271
357
  position: "absolute",
272
358
  top: "100%",
273
359
  right: 0,
274
360
  marginTop: 4,
275
- background: "white",
361
+ background: theme.background,
362
+ border: `1px solid ${theme.divider}`,
276
363
  borderRadius: 8,
277
- boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
364
+ boxShadow: isDark ? "0 4px 16px rgba(0,0,0,0.5)" : "0 4px 16px rgba(0,0,0,0.15)",
278
365
  minWidth: 180,
279
366
  overflow: "hidden",
280
367
  zIndex: 10,
@@ -291,7 +378,7 @@ function ChatHeader() {
291
378
  background: "none",
292
379
  cursor: "pointer",
293
380
  fontSize: 13,
294
- color: "#333",
381
+ color: theme.text,
295
382
  textAlign: "left",
296
383
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
297
384
  };
@@ -368,7 +455,7 @@ function ChatHeader() {
368
455
  reset();
369
456
  },
370
457
  onMouseEnter: (e) => {
371
- e.currentTarget.style.background = "#f5f5f5";
458
+ e.currentTarget.style.background = theme.bubbleBackground;
372
459
  },
373
460
  onMouseLeave: (e) => {
374
461
  e.currentTarget.style.background = "none";
@@ -430,7 +517,7 @@ function ChatHeader() {
430
517
  }
431
518
 
432
519
  // src/components/chat-messages.tsx
433
- var import_react7 = require("react");
520
+ var import_react8 = require("react");
434
521
 
435
522
  // src/markdown/render.tsx
436
523
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -797,7 +884,7 @@ function renderMarkdown(source, opts = {}) {
797
884
  }
798
885
 
799
886
  // src/components/action-confirmation-card.tsx
800
- var import_react6 = require("react");
887
+ var import_react7 = require("react");
801
888
  var import_jsx_runtime5 = require("react/jsx-runtime");
802
889
  function ActionConfirmationCard({
803
890
  block,
@@ -806,10 +893,10 @@ function ActionConfirmationCard({
806
893
  onApprove,
807
894
  onCancel
808
895
  }) {
809
- const [chosen, setChosen] = (0, import_react6.useState)(null);
810
- const [showSpinner, setShowSpinner] = (0, import_react6.useState)(false);
811
- const [error, setError] = (0, import_react6.useState)(null);
812
- const [showSummary, setShowSummary] = (0, import_react6.useState)(false);
896
+ const [chosen, setChosen] = (0, import_react7.useState)(null);
897
+ const [showSpinner, setShowSpinner] = (0, import_react7.useState)(false);
898
+ const [error, setError] = (0, import_react7.useState)(null);
899
+ const [showSummary, setShowSummary] = (0, import_react7.useState)(false);
813
900
  const handle = async (choice) => {
814
901
  if (chosen) return;
815
902
  setChosen(choice);
@@ -1037,7 +1124,7 @@ function MessageRatingButtons({
1037
1124
  t,
1038
1125
  reduced
1039
1126
  }) {
1040
- const [rated, setRated] = (0, import_react7.useState)(null);
1127
+ const [rated, setRated] = (0, import_react8.useState)(null);
1041
1128
  const handleRate = (rating) => {
1042
1129
  setRated(rating);
1043
1130
  onRate(messageId, rating);
@@ -1118,8 +1205,8 @@ function AnimatedMessage({
1118
1205
  animate,
1119
1206
  reduced
1120
1207
  }) {
1121
- const [visible, setVisible] = (0, import_react7.useState)(!animate);
1122
- (0, import_react7.useEffect)(() => {
1208
+ const [visible, setVisible] = (0, import_react8.useState)(!animate);
1209
+ (0, import_react8.useEffect)(() => {
1123
1210
  if (!animate || reduced) {
1124
1211
  setVisible(true);
1125
1212
  return;
@@ -1264,7 +1351,7 @@ function Message({
1264
1351
  color: "white",
1265
1352
  borderBottomRightRadius: 4
1266
1353
  } : {
1267
- background: "#f0f0f0",
1354
+ background: config.bubbleBackground,
1268
1355
  color: config.textColor,
1269
1356
  borderBottomLeftRadius: 4
1270
1357
  }
@@ -1360,7 +1447,6 @@ function ChatMessages() {
1360
1447
  messages,
1361
1448
  isLoading,
1362
1449
  error,
1363
- config,
1364
1450
  conversationId,
1365
1451
  rateMessage,
1366
1452
  sendMessage,
@@ -1368,13 +1454,19 @@ function ChatMessages() {
1368
1454
  cancelAction,
1369
1455
  t
1370
1456
  } = useChat();
1457
+ const theme = useEffectiveTheme();
1458
+ const themedConfig = {
1459
+ primaryColor: theme.primary,
1460
+ textColor: theme.text,
1461
+ bubbleBackground: theme.bubbleBackground
1462
+ };
1371
1463
  const reduced = useReducedMotion();
1372
- const containerRef = (0, import_react7.useRef)(null);
1373
- const messagesEndRef = (0, import_react7.useRef)(null);
1374
- const isFirstRender = (0, import_react7.useRef)(true);
1375
- const prevMessageCount = (0, import_react7.useRef)(0);
1376
- const stickRef = (0, import_react7.useRef)(true);
1377
- const suppressScrollRef = (0, import_react7.useRef)(false);
1464
+ const containerRef = (0, import_react8.useRef)(null);
1465
+ const messagesEndRef = (0, import_react8.useRef)(null);
1466
+ const isFirstRender = (0, import_react8.useRef)(true);
1467
+ const prevMessageCount = (0, import_react8.useRef)(0);
1468
+ const stickRef = (0, import_react8.useRef)(true);
1469
+ const suppressScrollRef = (0, import_react8.useRef)(false);
1378
1470
  const autoScrollTo = (top) => {
1379
1471
  const el = containerRef.current;
1380
1472
  if (!el) return;
@@ -1391,7 +1483,7 @@ function ChatMessages() {
1391
1483
  const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
1392
1484
  stickRef.current = distFromBottom < 60;
1393
1485
  };
1394
- (0, import_react7.useEffect)(() => {
1486
+ (0, import_react8.useEffect)(() => {
1395
1487
  const el = containerRef.current;
1396
1488
  if (!el) return;
1397
1489
  const lastMsg2 = messages[messages.length - 1];
@@ -1416,7 +1508,7 @@ function ChatMessages() {
1416
1508
  isFirstRender.current = false;
1417
1509
  }, [messages, isLoading, reduced]);
1418
1510
  const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
1419
- (0, import_react7.useEffect)(() => {
1511
+ (0, import_react8.useEffect)(() => {
1420
1512
  prevMessageCount.current = messages.length;
1421
1513
  }, [messages.length]);
1422
1514
  let lastBotIndex = -1;
@@ -1441,7 +1533,7 @@ function ChatMessages() {
1441
1533
  Message,
1442
1534
  {
1443
1535
  message: msg,
1444
- config,
1536
+ config: themedConfig,
1445
1537
  onRate: rateMessage,
1446
1538
  onSend: sendMessage,
1447
1539
  onApproveAction: approveAction,
@@ -1480,10 +1572,13 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
1480
1572
  function ChatSuggestions() {
1481
1573
  const { messages, isLoading, config, sendMessage } = useChat();
1482
1574
  const reduced = useReducedMotion();
1575
+ const theme = useEffectiveTheme();
1483
1576
  const hasUserMessage = messages.some((m) => m.role === "user");
1484
1577
  if (config.suggestedMessages.length === 0 || hasUserMessage || isLoading) {
1485
1578
  return null;
1486
1579
  }
1580
+ const isDark = theme.scheme === "dark";
1581
+ const idleBorder = isDark ? "rgba(255,255,255,0.18)" : "#e0e0e0";
1487
1582
  const containerStyle = {
1488
1583
  padding: "8px 16px",
1489
1584
  display: "flex",
@@ -1493,11 +1588,11 @@ function ChatSuggestions() {
1493
1588
  };
1494
1589
  const chipStyle = {
1495
1590
  background: "none",
1496
- border: "1px solid #e0e0e0",
1591
+ border: `1px solid ${idleBorder}`,
1497
1592
  borderRadius: 20,
1498
1593
  padding: "7px 14px",
1499
1594
  fontSize: 13,
1500
- color: "#333",
1595
+ color: theme.text,
1501
1596
  cursor: "pointer",
1502
1597
  textAlign: "left",
1503
1598
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
@@ -1509,11 +1604,11 @@ function ChatSuggestions() {
1509
1604
  style: chipStyle,
1510
1605
  onClick: () => sendMessage(text),
1511
1606
  onMouseEnter: (e) => {
1512
- e.currentTarget.style.borderColor = config.primaryColor;
1513
- e.currentTarget.style.background = `${config.primaryColor}08`;
1607
+ e.currentTarget.style.borderColor = theme.primary;
1608
+ e.currentTarget.style.background = `${theme.primary}14`;
1514
1609
  },
1515
1610
  onMouseLeave: (e) => {
1516
- e.currentTarget.style.borderColor = "#e0e0e0";
1611
+ e.currentTarget.style.borderColor = idleBorder;
1517
1612
  e.currentTarget.style.background = "none";
1518
1613
  },
1519
1614
  children: text
@@ -1523,8 +1618,8 @@ function ChatSuggestions() {
1523
1618
  }
1524
1619
 
1525
1620
  // src/components/chat-input.tsx
1526
- var import_react8 = require("react");
1527
- var import_js2 = require("@customerhero/js");
1621
+ var import_react9 = require("react");
1622
+ var import_js3 = require("@customerhero/js");
1528
1623
  var import_jsx_runtime8 = require("react/jsx-runtime");
1529
1624
  var MAX_ATTACHMENTS = 3;
1530
1625
  var ALLOWED_MIME_TYPES = [
@@ -1542,47 +1637,50 @@ function ChatInput() {
1542
1637
  sendMessage,
1543
1638
  uploadAttachment,
1544
1639
  isLoading,
1640
+ readOnly,
1545
1641
  config,
1546
1642
  t,
1547
1643
  consumePendingPrefill,
1548
1644
  pendingPrefill
1549
1645
  } = useChat();
1646
+ const theme = useEffectiveTheme();
1647
+ const isDark = theme.scheme === "dark";
1550
1648
  const reduced = useReducedMotion();
1551
- const [value, setValue] = (0, import_react8.useState)("");
1552
- const [attachments, setAttachments] = (0, import_react8.useState)([]);
1553
- const [captureSupported, setCaptureSupported] = (0, import_react8.useState)(false);
1554
- const [menuOpen, setMenuOpen] = (0, import_react8.useState)(false);
1555
- const [dragActive, setDragActive] = (0, import_react8.useState)(false);
1556
- const [transientError, setTransientError] = (0, import_react8.useState)(null);
1557
- const fileInputRef = (0, import_react8.useRef)(null);
1558
- const textInputRef = (0, import_react8.useRef)(null);
1559
- const menuRef = (0, import_react8.useRef)(null);
1560
- const menuButtonRef = (0, import_react8.useRef)(null);
1561
- const dragCounterRef = (0, import_react8.useRef)(0);
1562
- (0, import_react8.useEffect)(() => {
1563
- setCaptureSupported((0, import_js2.canCaptureScreenshot)());
1649
+ const [value, setValue] = (0, import_react9.useState)("");
1650
+ const [attachments, setAttachments] = (0, import_react9.useState)([]);
1651
+ const [captureSupported, setCaptureSupported] = (0, import_react9.useState)(false);
1652
+ const [menuOpen, setMenuOpen] = (0, import_react9.useState)(false);
1653
+ const [dragActive, setDragActive] = (0, import_react9.useState)(false);
1654
+ const [transientError, setTransientError] = (0, import_react9.useState)(null);
1655
+ const fileInputRef = (0, import_react9.useRef)(null);
1656
+ const textInputRef = (0, import_react9.useRef)(null);
1657
+ const menuRef = (0, import_react9.useRef)(null);
1658
+ const menuButtonRef = (0, import_react9.useRef)(null);
1659
+ const dragCounterRef = (0, import_react9.useRef)(0);
1660
+ (0, import_react9.useEffect)(() => {
1661
+ setCaptureSupported((0, import_js3.canCaptureScreenshot)());
1564
1662
  }, []);
1565
- (0, import_react8.useEffect)(() => {
1663
+ (0, import_react9.useEffect)(() => {
1566
1664
  const id = requestAnimationFrame(() => textInputRef.current?.focus());
1567
1665
  return () => cancelAnimationFrame(id);
1568
1666
  }, []);
1569
- (0, import_react8.useLayoutEffect)(() => {
1667
+ (0, import_react9.useLayoutEffect)(() => {
1570
1668
  const el = textInputRef.current;
1571
1669
  if (!el) return;
1572
1670
  el.style.height = "auto";
1573
1671
  el.style.height = `${el.scrollHeight}px`;
1574
1672
  }, [value]);
1575
- (0, import_react8.useEffect)(() => {
1673
+ (0, import_react9.useEffect)(() => {
1576
1674
  if (pendingPrefill === null) return;
1577
1675
  const text = consumePendingPrefill();
1578
1676
  if (text !== null) setValue(text);
1579
1677
  }, [pendingPrefill, consumePendingPrefill]);
1580
- (0, import_react8.useEffect)(() => {
1678
+ (0, import_react9.useEffect)(() => {
1581
1679
  return () => {
1582
1680
  for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
1583
1681
  };
1584
1682
  }, []);
1585
- (0, import_react8.useEffect)(() => {
1683
+ (0, import_react9.useEffect)(() => {
1586
1684
  if (!menuOpen) return;
1587
1685
  const onClick = (e) => {
1588
1686
  const target = e.target;
@@ -1601,7 +1699,7 @@ function ChatInput() {
1601
1699
  document.removeEventListener("keydown", onKey);
1602
1700
  };
1603
1701
  }, [menuOpen]);
1604
- (0, import_react8.useEffect)(() => {
1702
+ (0, import_react9.useEffect)(() => {
1605
1703
  if (!transientError) return;
1606
1704
  const id = window.setTimeout(() => setTransientError(null), 4e3);
1607
1705
  return () => window.clearTimeout(id);
@@ -1657,10 +1755,10 @@ function ChatInput() {
1657
1755
  const handleCapture = async () => {
1658
1756
  setMenuOpen(false);
1659
1757
  try {
1660
- const blob = await (0, import_js2.captureScreenshot)();
1758
+ const blob = await (0, import_js3.captureScreenshot)();
1661
1759
  await startUpload(blob);
1662
1760
  } catch (e) {
1663
- if (e instanceof import_js2.ScreenshotCancelled) return;
1761
+ if (e instanceof import_js3.ScreenshotCancelled) return;
1664
1762
  }
1665
1763
  };
1666
1764
  const handlePickFile = () => {
@@ -1742,7 +1840,7 @@ function ChatInput() {
1742
1840
  const containerStyle = {
1743
1841
  position: "relative",
1744
1842
  padding: "12px 16px",
1745
- borderTop: "1px solid #eee",
1843
+ borderTop: `1px solid ${theme.divider}`,
1746
1844
  display: "flex",
1747
1845
  flexDirection: "column",
1748
1846
  gap: 8
@@ -1757,14 +1855,14 @@ function ChatInput() {
1757
1855
  const TEXTAREA_MAX_HEIGHT = 140;
1758
1856
  const inputStyle = {
1759
1857
  flex: 1,
1760
- border: "1px solid #e0e0e0",
1858
+ border: `1px solid ${theme.divider}`,
1761
1859
  borderRadius: 18,
1762
1860
  padding: "10px 16px",
1763
1861
  fontSize: 14,
1764
1862
  lineHeight: 1.4,
1765
1863
  outline: "none",
1766
- background: "#fafafa",
1767
- color: config.textColor,
1864
+ background: isDark ? "rgba(255,255,255,0.06)" : "#fafafa",
1865
+ color: theme.text,
1768
1866
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
1769
1867
  resize: "none",
1770
1868
  overflowY: "auto",
@@ -1775,7 +1873,7 @@ function ChatInput() {
1775
1873
  width: 36,
1776
1874
  height: 36,
1777
1875
  borderRadius: "50%",
1778
- background: config.primaryColor,
1876
+ background: theme.primary,
1779
1877
  border: "none",
1780
1878
  color: "white",
1781
1879
  cursor: isLoading ? "not-allowed" : "pointer",
@@ -1805,10 +1903,10 @@ function ChatInput() {
1805
1903
  position: "absolute",
1806
1904
  bottom: "calc(100% + 4px)",
1807
1905
  left: 0,
1808
- background: "white",
1809
- border: "1px solid #e0e0e0",
1906
+ background: theme.background,
1907
+ border: `1px solid ${theme.divider}`,
1810
1908
  borderRadius: 8,
1811
- boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
1909
+ boxShadow: isDark ? "0 4px 16px rgba(0,0,0,0.5)" : "0 4px 16px rgba(0,0,0,0.12)",
1812
1910
  padding: 4,
1813
1911
  minWidth: 180,
1814
1912
  zIndex: 10,
@@ -1825,7 +1923,7 @@ function ChatInput() {
1825
1923
  borderRadius: 4,
1826
1924
  cursor: "pointer",
1827
1925
  fontSize: 14,
1828
- color: "#333",
1926
+ color: theme.text,
1829
1927
  textAlign: "left",
1830
1928
  whiteSpace: "nowrap",
1831
1929
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
@@ -1833,13 +1931,13 @@ function ChatInput() {
1833
1931
  const dropOverlayStyle = {
1834
1932
  position: "absolute",
1835
1933
  inset: 0,
1836
- background: "rgba(255,255,255,0.92)",
1837
- border: `2px dashed ${config.primaryColor}`,
1934
+ background: isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.92)",
1935
+ border: `2px dashed ${theme.primary}`,
1838
1936
  borderRadius: 4,
1839
1937
  display: "flex",
1840
1938
  alignItems: "center",
1841
1939
  justifyContent: "center",
1842
- color: config.primaryColor,
1940
+ color: theme.primary,
1843
1941
  fontSize: 14,
1844
1942
  fontWeight: 500,
1845
1943
  pointerEvents: "none",
@@ -1854,7 +1952,7 @@ function ChatInput() {
1854
1952
  padding: "4px 10px",
1855
1953
  fontSize: 12
1856
1954
  };
1857
- const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
1955
+ const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading || readOnly;
1858
1956
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1859
1957
  "div",
1860
1958
  {
@@ -1883,7 +1981,7 @@ function ChatInput() {
1883
1981
  }
1884
1982
  ),
1885
1983
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
1886
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { position: "relative" }, children: [
1984
+ config.allowAttachments !== false && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { position: "relative" }, children: [
1887
1985
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1888
1986
  "button",
1889
1987
  {
@@ -1908,7 +2006,7 @@ function ChatInput() {
1908
2006
  onClick: handlePickFile,
1909
2007
  style: menuItemStyle,
1910
2008
  onMouseEnter: (e) => {
1911
- e.currentTarget.style.background = "#f5f5f5";
2009
+ e.currentTarget.style.background = theme.bubbleBackground;
1912
2010
  },
1913
2011
  onMouseLeave: (e) => {
1914
2012
  e.currentTarget.style.background = "transparent";
@@ -1927,7 +2025,7 @@ function ChatInput() {
1927
2025
  onClick: handleCapture,
1928
2026
  style: menuItemStyle,
1929
2027
  onMouseEnter: (e) => {
1930
- e.currentTarget.style.background = "#f5f5f5";
2028
+ e.currentTarget.style.background = theme.bubbleBackground;
1931
2029
  },
1932
2030
  onMouseLeave: (e) => {
1933
2031
  e.currentTarget.style.background = "transparent";
@@ -1964,14 +2062,14 @@ function ChatInput() {
1964
2062
  onPaste: handlePaste,
1965
2063
  placeholder: config.placeholderText,
1966
2064
  style: inputStyle,
1967
- disabled: isLoading
2065
+ disabled: isLoading || readOnly
1968
2066
  }
1969
2067
  ),
1970
2068
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1971
2069
  "button",
1972
2070
  {
1973
2071
  onClick: handleSend,
1974
- disabled: isLoading || !value.trim(),
2072
+ disabled: isLoading || readOnly || !value.trim(),
1975
2073
  style: sendButtonStyle,
1976
2074
  "aria-label": t("send_message"),
1977
2075
  onMouseEnter: (e) => {
@@ -2231,7 +2329,7 @@ function Spinner2() {
2231
2329
  }
2232
2330
 
2233
2331
  // src/components/incident-banner.tsx
2234
- var import_react9 = require("react");
2332
+ var import_react10 = require("react");
2235
2333
  var import_jsx_runtime9 = require("react/jsx-runtime");
2236
2334
  var PALETTE = {
2237
2335
  info: {
@@ -2296,8 +2394,8 @@ function SeverityIcon({
2296
2394
  }
2297
2395
  function IncidentBanner() {
2298
2396
  const { incidentBanner, incidentBannerDismissed, dismissIncidentBanner, t } = useChat();
2299
- const [linkHover, setLinkHover] = (0, import_react9.useState)(false);
2300
- const [closeHover, setCloseHover] = (0, import_react9.useState)(false);
2397
+ const [linkHover, setLinkHover] = (0, import_react10.useState)(false);
2398
+ const [closeHover, setCloseHover] = (0, import_react10.useState)(false);
2301
2399
  if (!incidentBanner || incidentBannerDismissed) return null;
2302
2400
  const palette = PALETTE[incidentBanner.severity];
2303
2401
  const wrap = {
@@ -2513,9 +2611,9 @@ function validateField(field, value) {
2513
2611
  }
2514
2612
  function PreChatFormView() {
2515
2613
  const { preChatForm, submitPreChatForm, cancelPreChatForm, config, t } = useChat();
2516
- const [values, setValues] = (0, import_react10.useState)({});
2517
- const [errors, setErrors] = (0, import_react10.useState)({});
2518
- const [submitting, setSubmitting] = (0, import_react10.useState)(false);
2614
+ const [values, setValues] = (0, import_react11.useState)({});
2615
+ const [errors, setErrors] = (0, import_react11.useState)({});
2616
+ const [submitting, setSubmitting] = (0, import_react11.useState)(false);
2519
2617
  if (!preChatForm) return null;
2520
2618
  function setValue(key, value) {
2521
2619
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -2744,12 +2842,13 @@ function PreChatFormView() {
2744
2842
  }
2745
2843
  );
2746
2844
  }
2747
- function ChatWindow() {
2845
+ function ChatWindow({ embedded } = {}) {
2748
2846
  const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
2749
2847
  const reduced = useReducedMotion();
2750
- const [visible, setVisible] = (0, import_react10.useState)(false);
2751
- const [shouldRender, setShouldRender] = (0, import_react10.useState)(false);
2752
- (0, import_react10.useEffect)(() => {
2848
+ const theme = useEffectiveTheme();
2849
+ const [visible, setVisible] = (0, import_react11.useState)(false);
2850
+ const [shouldRender, setShouldRender] = (0, import_react11.useState)(false);
2851
+ (0, import_react11.useEffect)(() => {
2753
2852
  if (isOpen) {
2754
2853
  setShouldRender(true);
2755
2854
  requestAnimationFrame(() => {
@@ -2767,34 +2866,44 @@ function ChatWindow() {
2767
2866
  }, [isOpen, reduced]);
2768
2867
  if (!shouldRender) return null;
2769
2868
  const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
2869
+ const colors = theme;
2870
+ const preset = theme.size;
2871
+ const radius = theme.radius;
2872
+ const panelBottom = config.offset.bottom + preset.bubble + 14;
2770
2873
  const style = {
2771
- position: "fixed",
2772
- bottom: 90,
2773
- [effectivePosition === "bottom-left" ? "left" : "right"]: 20,
2774
- width: 380,
2775
- maxWidth: "calc(100vw - 40px)",
2776
- height: 520,
2777
- maxHeight: "calc(100vh - 120px)",
2778
- borderRadius: 16,
2874
+ position: embedded ? "absolute" : "fixed",
2875
+ bottom: panelBottom,
2876
+ [effectivePosition === "bottom-left" ? "left" : "right"]: config.offset.side,
2877
+ width: preset.width,
2878
+ maxWidth: embedded ? "calc(100% - 16px)" : "calc(100vw - 40px)",
2879
+ height: preset.height,
2880
+ maxHeight: embedded ? `calc(100% - ${panelBottom + 16}px)` : `calc(100vh - ${panelBottom + 30}px)`,
2881
+ borderRadius: radius,
2779
2882
  overflow: "hidden",
2780
2883
  display: "flex",
2781
2884
  flexDirection: "column",
2782
- boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
2783
- zIndex: 99999,
2784
- background: config.backgroundColor,
2885
+ // In dark mode the default 0.15 opacity black shadow is invisible
2886
+ // against a dark page; switch to a stronger shadow plus a subtle 1px
2887
+ // light outline so the panel still reads as a separated surface.
2888
+ boxShadow: theme.scheme === "dark" ? "0 12px 40px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.06)" : "0 8px 40px rgba(0,0,0,0.15)",
2889
+ zIndex: config.zIndex,
2890
+ background: colors.background,
2891
+ color: colors.text,
2892
+ fontSize: preset.fontSize,
2785
2893
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2786
2894
  opacity: visible ? 1 : 0,
2787
2895
  transform: visible ? "translateY(0) scale(1)" : "translateY(16px) scale(0.97)",
2788
2896
  transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease"
2789
2897
  };
2898
+ const isDark = theme.scheme === "dark";
2790
2899
  const poweredStyle = {
2791
2900
  textAlign: "center",
2792
2901
  padding: 6,
2793
2902
  fontSize: 10,
2794
- color: "#aaa"
2903
+ color: isDark ? "rgba(255,255,255,0.45)" : "#aaa"
2795
2904
  };
2796
2905
  const linkStyle = {
2797
- color: "#888",
2906
+ color: isDark ? "rgba(255,255,255,0.6)" : "#888",
2798
2907
  textDecoration: "underline",
2799
2908
  textUnderlineOffset: 2
2800
2909
  };
@@ -2825,11 +2934,14 @@ function ChatWindow() {
2825
2934
 
2826
2935
  // src/components/chat-widget.tsx
2827
2936
  var import_jsx_runtime11 = require("react/jsx-runtime");
2828
- function ChatWidgetInner({ identity }) {
2937
+ function ChatWidgetInner({
2938
+ identity,
2939
+ embedded
2940
+ }) {
2829
2941
  const client = useCustomerHeroClient();
2830
2942
  const { configLoaded, configError } = useChat();
2831
- const prevIdentityRef = (0, import_react11.useRef)(void 0);
2832
- (0, import_react11.useEffect)(() => {
2943
+ const prevIdentityRef = (0, import_react12.useRef)(void 0);
2944
+ (0, import_react12.useEffect)(() => {
2833
2945
  const key = identity ? JSON.stringify(identity) : void 0;
2834
2946
  if (key !== prevIdentityRef.current) {
2835
2947
  prevIdentityRef.current = key;
@@ -2840,12 +2952,12 @@ function ChatWidgetInner({ identity }) {
2840
2952
  }, [identity, client]);
2841
2953
  if (!configLoaded || configError) return null;
2842
2954
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2843
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubble, {}),
2844
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWindow, {})
2955
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubble, { embedded }),
2956
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWindow, { embedded })
2845
2957
  ] });
2846
2958
  }
2847
- function ChatWidget({ identity, ...config }) {
2848
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWidgetInner, { identity }) });
2959
+ function ChatWidget({ identity, embedded, ...config }) {
2960
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWidgetInner, { identity, embedded }) });
2849
2961
  }
2850
2962
  // Annotate the CommonJS export names for ESM import in node:
2851
2963
  0 && (module.exports = {