@customerhero/react 2.1.1 → 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_react10 = 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() {
@@ -118,12 +120,16 @@ function useChat() {
118
120
  consumePendingPrefill: (0, import_react2.useCallback)(
119
121
  () => client.consumePendingPrefill(),
120
122
  [client]
123
+ ),
124
+ dismissIncidentBanner: (0, import_react2.useCallback)(
125
+ () => client.dismissIncidentBanner(),
126
+ [client]
121
127
  )
122
128
  };
123
129
  }
124
130
 
125
131
  // src/components/chat-bubble.tsx
126
- var import_react4 = require("react");
132
+ var import_react5 = require("react");
127
133
 
128
134
  // src/use-reduced-motion.ts
129
135
  var import_react3 = require("react");
@@ -141,41 +147,110 @@ function useReducedMotion() {
141
147
  return reduced;
142
148
  }
143
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
+
144
193
  // src/components/chat-bubble.tsx
145
194
  var import_jsx_runtime2 = require("react/jsx-runtime");
146
- function ChatBubble() {
195
+ function ChatBubble({ embedded } = {}) {
147
196
  const { toggle, config, t, isRtl } = useChat();
148
197
  const reduced = useReducedMotion();
149
- const [mounted, setMounted] = (0, import_react4.useState)(false);
150
- (0, import_react4.useEffect)(() => {
198
+ const theme = useEffectiveTheme();
199
+ const [mounted, setMounted] = (0, import_react5.useState)(false);
200
+ (0, import_react5.useEffect)(() => {
151
201
  const id = requestAnimationFrame(() => setMounted(true));
152
202
  return () => cancelAnimationFrame(id);
153
203
  }, []);
154
204
  const visible = mounted;
155
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%";
156
212
  const style = {
157
- position: "fixed",
158
- bottom: 20,
159
- [effectivePosition === "bottom-left" ? "left" : "right"]: 20,
160
- width: 56,
161
- height: 56,
162
- borderRadius: "50%",
163
- background: config.primaryColor,
164
- 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",
165
223
  display: "flex",
166
224
  alignItems: "center",
167
225
  justifyContent: "center",
226
+ gap: hasLabel ? 8 : 0,
168
227
  cursor: "pointer",
169
- boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
170
- 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,
171
230
  border: "none",
172
- padding: 0,
231
+ fontSize: preset.fontSize,
232
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
233
+ fontWeight: 500,
173
234
  opacity: visible ? 1 : 0,
174
235
  transform: visible ? "scale(1)" : "scale(0.6)",
175
236
  transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease",
176
237
  pointerEvents: visible ? "auto" : "none"
177
238
  };
178
- 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)(
179
254
  "button",
180
255
  {
181
256
  onClick: toggle,
@@ -183,41 +258,55 @@ function ChatBubble() {
183
258
  dir: isRtl ? "rtl" : "ltr",
184
259
  "aria-label": t("open_chat"),
185
260
  onMouseEnter: (e) => {
186
- if (!reduced) e.currentTarget.style.transform = "scale(1.1)";
261
+ if (!reduced) e.currentTarget.style.transform = "scale(1.06)";
187
262
  },
188
263
  onMouseLeave: (e) => {
189
264
  if (!reduced) e.currentTarget.style.transform = "scale(1)";
190
265
  },
191
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
192
- "svg",
193
- {
194
- width: "24",
195
- height: "24",
196
- viewBox: "0 0 24 24",
197
- fill: "none",
198
- stroke: "currentColor",
199
- strokeWidth: "2",
200
- strokeLinecap: "round",
201
- strokeLinejoin: "round",
202
- 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" })
203
- }
204
- )
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
+ ]
205
293
  }
206
294
  );
207
295
  }
208
296
 
209
297
  // src/components/chat-window.tsx
210
- var import_react9 = require("react");
298
+ var import_react11 = require("react");
211
299
 
212
300
  // src/components/chat-header.tsx
213
- var import_react5 = require("react");
301
+ var import_react6 = require("react");
214
302
  var import_jsx_runtime3 = require("react/jsx-runtime");
215
303
  function ChatHeader() {
216
304
  const { config, close, reset, t } = useChat();
217
305
  const reduced = useReducedMotion();
218
- const [menuOpen, setMenuOpen] = (0, import_react5.useState)(false);
219
- const menuRef = (0, import_react5.useRef)(null);
220
- (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)(() => {
221
310
  if (!menuOpen) return;
222
311
  const handleClick = (e) => {
223
312
  if (menuRef.current && !menuRef.current.contains(e.target)) {
@@ -228,7 +317,7 @@ function ChatHeader() {
228
317
  return () => document.removeEventListener("mousedown", handleClick);
229
318
  }, [menuOpen]);
230
319
  const headerStyle = {
231
- background: config.primaryColor,
320
+ background: theme.primary,
232
321
  padding: 16,
233
322
  display: "flex",
234
323
  alignItems: "center",
@@ -263,14 +352,16 @@ function ChatHeader() {
263
352
  opacity: 0.7,
264
353
  padding: 4
265
354
  };
355
+ const isDark = theme.scheme === "dark";
266
356
  const menuStyle = {
267
357
  position: "absolute",
268
358
  top: "100%",
269
359
  right: 0,
270
360
  marginTop: 4,
271
- background: "white",
361
+ background: theme.background,
362
+ border: `1px solid ${theme.divider}`,
272
363
  borderRadius: 8,
273
- 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)",
274
365
  minWidth: 180,
275
366
  overflow: "hidden",
276
367
  zIndex: 10,
@@ -287,7 +378,7 @@ function ChatHeader() {
287
378
  background: "none",
288
379
  cursor: "pointer",
289
380
  fontSize: 13,
290
- color: "#333",
381
+ color: theme.text,
291
382
  textAlign: "left",
292
383
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
293
384
  };
@@ -364,7 +455,7 @@ function ChatHeader() {
364
455
  reset();
365
456
  },
366
457
  onMouseEnter: (e) => {
367
- e.currentTarget.style.background = "#f5f5f5";
458
+ e.currentTarget.style.background = theme.bubbleBackground;
368
459
  },
369
460
  onMouseLeave: (e) => {
370
461
  e.currentTarget.style.background = "none";
@@ -426,7 +517,7 @@ function ChatHeader() {
426
517
  }
427
518
 
428
519
  // src/components/chat-messages.tsx
429
- var import_react7 = require("react");
520
+ var import_react8 = require("react");
430
521
 
431
522
  // src/markdown/render.tsx
432
523
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -793,7 +884,7 @@ function renderMarkdown(source, opts = {}) {
793
884
  }
794
885
 
795
886
  // src/components/action-confirmation-card.tsx
796
- var import_react6 = require("react");
887
+ var import_react7 = require("react");
797
888
  var import_jsx_runtime5 = require("react/jsx-runtime");
798
889
  function ActionConfirmationCard({
799
890
  block,
@@ -802,10 +893,10 @@ function ActionConfirmationCard({
802
893
  onApprove,
803
894
  onCancel
804
895
  }) {
805
- const [chosen, setChosen] = (0, import_react6.useState)(null);
806
- const [showSpinner, setShowSpinner] = (0, import_react6.useState)(false);
807
- const [error, setError] = (0, import_react6.useState)(null);
808
- 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);
809
900
  const handle = async (choice) => {
810
901
  if (chosen) return;
811
902
  setChosen(choice);
@@ -1033,7 +1124,7 @@ function MessageRatingButtons({
1033
1124
  t,
1034
1125
  reduced
1035
1126
  }) {
1036
- const [rated, setRated] = (0, import_react7.useState)(null);
1127
+ const [rated, setRated] = (0, import_react8.useState)(null);
1037
1128
  const handleRate = (rating) => {
1038
1129
  setRated(rating);
1039
1130
  onRate(messageId, rating);
@@ -1114,8 +1205,8 @@ function AnimatedMessage({
1114
1205
  animate,
1115
1206
  reduced
1116
1207
  }) {
1117
- const [visible, setVisible] = (0, import_react7.useState)(!animate);
1118
- (0, import_react7.useEffect)(() => {
1208
+ const [visible, setVisible] = (0, import_react8.useState)(!animate);
1209
+ (0, import_react8.useEffect)(() => {
1119
1210
  if (!animate || reduced) {
1120
1211
  setVisible(true);
1121
1212
  return;
@@ -1260,7 +1351,7 @@ function Message({
1260
1351
  color: "white",
1261
1352
  borderBottomRightRadius: 4
1262
1353
  } : {
1263
- background: "#f0f0f0",
1354
+ background: config.bubbleBackground,
1264
1355
  color: config.textColor,
1265
1356
  borderBottomLeftRadius: 4
1266
1357
  }
@@ -1356,7 +1447,6 @@ function ChatMessages() {
1356
1447
  messages,
1357
1448
  isLoading,
1358
1449
  error,
1359
- config,
1360
1450
  conversationId,
1361
1451
  rateMessage,
1362
1452
  sendMessage,
@@ -1364,13 +1454,19 @@ function ChatMessages() {
1364
1454
  cancelAction,
1365
1455
  t
1366
1456
  } = useChat();
1457
+ const theme = useEffectiveTheme();
1458
+ const themedConfig = {
1459
+ primaryColor: theme.primary,
1460
+ textColor: theme.text,
1461
+ bubbleBackground: theme.bubbleBackground
1462
+ };
1367
1463
  const reduced = useReducedMotion();
1368
- const containerRef = (0, import_react7.useRef)(null);
1369
- const messagesEndRef = (0, import_react7.useRef)(null);
1370
- const isFirstRender = (0, import_react7.useRef)(true);
1371
- const prevMessageCount = (0, import_react7.useRef)(0);
1372
- const stickRef = (0, import_react7.useRef)(true);
1373
- 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);
1374
1470
  const autoScrollTo = (top) => {
1375
1471
  const el = containerRef.current;
1376
1472
  if (!el) return;
@@ -1387,7 +1483,7 @@ function ChatMessages() {
1387
1483
  const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
1388
1484
  stickRef.current = distFromBottom < 60;
1389
1485
  };
1390
- (0, import_react7.useEffect)(() => {
1486
+ (0, import_react8.useEffect)(() => {
1391
1487
  const el = containerRef.current;
1392
1488
  if (!el) return;
1393
1489
  const lastMsg2 = messages[messages.length - 1];
@@ -1412,7 +1508,7 @@ function ChatMessages() {
1412
1508
  isFirstRender.current = false;
1413
1509
  }, [messages, isLoading, reduced]);
1414
1510
  const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
1415
- (0, import_react7.useEffect)(() => {
1511
+ (0, import_react8.useEffect)(() => {
1416
1512
  prevMessageCount.current = messages.length;
1417
1513
  }, [messages.length]);
1418
1514
  let lastBotIndex = -1;
@@ -1437,7 +1533,7 @@ function ChatMessages() {
1437
1533
  Message,
1438
1534
  {
1439
1535
  message: msg,
1440
- config,
1536
+ config: themedConfig,
1441
1537
  onRate: rateMessage,
1442
1538
  onSend: sendMessage,
1443
1539
  onApproveAction: approveAction,
@@ -1476,10 +1572,13 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
1476
1572
  function ChatSuggestions() {
1477
1573
  const { messages, isLoading, config, sendMessage } = useChat();
1478
1574
  const reduced = useReducedMotion();
1575
+ const theme = useEffectiveTheme();
1479
1576
  const hasUserMessage = messages.some((m) => m.role === "user");
1480
1577
  if (config.suggestedMessages.length === 0 || hasUserMessage || isLoading) {
1481
1578
  return null;
1482
1579
  }
1580
+ const isDark = theme.scheme === "dark";
1581
+ const idleBorder = isDark ? "rgba(255,255,255,0.18)" : "#e0e0e0";
1483
1582
  const containerStyle = {
1484
1583
  padding: "8px 16px",
1485
1584
  display: "flex",
@@ -1489,11 +1588,11 @@ function ChatSuggestions() {
1489
1588
  };
1490
1589
  const chipStyle = {
1491
1590
  background: "none",
1492
- border: "1px solid #e0e0e0",
1591
+ border: `1px solid ${idleBorder}`,
1493
1592
  borderRadius: 20,
1494
1593
  padding: "7px 14px",
1495
1594
  fontSize: 13,
1496
- color: "#333",
1595
+ color: theme.text,
1497
1596
  cursor: "pointer",
1498
1597
  textAlign: "left",
1499
1598
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
@@ -1505,11 +1604,11 @@ function ChatSuggestions() {
1505
1604
  style: chipStyle,
1506
1605
  onClick: () => sendMessage(text),
1507
1606
  onMouseEnter: (e) => {
1508
- e.currentTarget.style.borderColor = config.primaryColor;
1509
- e.currentTarget.style.background = `${config.primaryColor}08`;
1607
+ e.currentTarget.style.borderColor = theme.primary;
1608
+ e.currentTarget.style.background = `${theme.primary}14`;
1510
1609
  },
1511
1610
  onMouseLeave: (e) => {
1512
- e.currentTarget.style.borderColor = "#e0e0e0";
1611
+ e.currentTarget.style.borderColor = idleBorder;
1513
1612
  e.currentTarget.style.background = "none";
1514
1613
  },
1515
1614
  children: text
@@ -1519,8 +1618,8 @@ function ChatSuggestions() {
1519
1618
  }
1520
1619
 
1521
1620
  // src/components/chat-input.tsx
1522
- var import_react8 = require("react");
1523
- var import_js2 = require("@customerhero/js");
1621
+ var import_react9 = require("react");
1622
+ var import_js3 = require("@customerhero/js");
1524
1623
  var import_jsx_runtime8 = require("react/jsx-runtime");
1525
1624
  var MAX_ATTACHMENTS = 3;
1526
1625
  var ALLOWED_MIME_TYPES = [
@@ -1538,47 +1637,50 @@ function ChatInput() {
1538
1637
  sendMessage,
1539
1638
  uploadAttachment,
1540
1639
  isLoading,
1640
+ readOnly,
1541
1641
  config,
1542
1642
  t,
1543
1643
  consumePendingPrefill,
1544
1644
  pendingPrefill
1545
1645
  } = useChat();
1646
+ const theme = useEffectiveTheme();
1647
+ const isDark = theme.scheme === "dark";
1546
1648
  const reduced = useReducedMotion();
1547
- const [value, setValue] = (0, import_react8.useState)("");
1548
- const [attachments, setAttachments] = (0, import_react8.useState)([]);
1549
- const [captureSupported, setCaptureSupported] = (0, import_react8.useState)(false);
1550
- const [menuOpen, setMenuOpen] = (0, import_react8.useState)(false);
1551
- const [dragActive, setDragActive] = (0, import_react8.useState)(false);
1552
- const [transientError, setTransientError] = (0, import_react8.useState)(null);
1553
- const fileInputRef = (0, import_react8.useRef)(null);
1554
- const textInputRef = (0, import_react8.useRef)(null);
1555
- const menuRef = (0, import_react8.useRef)(null);
1556
- const menuButtonRef = (0, import_react8.useRef)(null);
1557
- const dragCounterRef = (0, import_react8.useRef)(0);
1558
- (0, import_react8.useEffect)(() => {
1559
- 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)());
1560
1662
  }, []);
1561
- (0, import_react8.useEffect)(() => {
1663
+ (0, import_react9.useEffect)(() => {
1562
1664
  const id = requestAnimationFrame(() => textInputRef.current?.focus());
1563
1665
  return () => cancelAnimationFrame(id);
1564
1666
  }, []);
1565
- (0, import_react8.useLayoutEffect)(() => {
1667
+ (0, import_react9.useLayoutEffect)(() => {
1566
1668
  const el = textInputRef.current;
1567
1669
  if (!el) return;
1568
1670
  el.style.height = "auto";
1569
1671
  el.style.height = `${el.scrollHeight}px`;
1570
1672
  }, [value]);
1571
- (0, import_react8.useEffect)(() => {
1673
+ (0, import_react9.useEffect)(() => {
1572
1674
  if (pendingPrefill === null) return;
1573
1675
  const text = consumePendingPrefill();
1574
1676
  if (text !== null) setValue(text);
1575
1677
  }, [pendingPrefill, consumePendingPrefill]);
1576
- (0, import_react8.useEffect)(() => {
1678
+ (0, import_react9.useEffect)(() => {
1577
1679
  return () => {
1578
1680
  for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
1579
1681
  };
1580
1682
  }, []);
1581
- (0, import_react8.useEffect)(() => {
1683
+ (0, import_react9.useEffect)(() => {
1582
1684
  if (!menuOpen) return;
1583
1685
  const onClick = (e) => {
1584
1686
  const target = e.target;
@@ -1597,7 +1699,7 @@ function ChatInput() {
1597
1699
  document.removeEventListener("keydown", onKey);
1598
1700
  };
1599
1701
  }, [menuOpen]);
1600
- (0, import_react8.useEffect)(() => {
1702
+ (0, import_react9.useEffect)(() => {
1601
1703
  if (!transientError) return;
1602
1704
  const id = window.setTimeout(() => setTransientError(null), 4e3);
1603
1705
  return () => window.clearTimeout(id);
@@ -1653,10 +1755,10 @@ function ChatInput() {
1653
1755
  const handleCapture = async () => {
1654
1756
  setMenuOpen(false);
1655
1757
  try {
1656
- const blob = await (0, import_js2.captureScreenshot)();
1758
+ const blob = await (0, import_js3.captureScreenshot)();
1657
1759
  await startUpload(blob);
1658
1760
  } catch (e) {
1659
- if (e instanceof import_js2.ScreenshotCancelled) return;
1761
+ if (e instanceof import_js3.ScreenshotCancelled) return;
1660
1762
  }
1661
1763
  };
1662
1764
  const handlePickFile = () => {
@@ -1738,7 +1840,7 @@ function ChatInput() {
1738
1840
  const containerStyle = {
1739
1841
  position: "relative",
1740
1842
  padding: "12px 16px",
1741
- borderTop: "1px solid #eee",
1843
+ borderTop: `1px solid ${theme.divider}`,
1742
1844
  display: "flex",
1743
1845
  flexDirection: "column",
1744
1846
  gap: 8
@@ -1753,14 +1855,14 @@ function ChatInput() {
1753
1855
  const TEXTAREA_MAX_HEIGHT = 140;
1754
1856
  const inputStyle = {
1755
1857
  flex: 1,
1756
- border: "1px solid #e0e0e0",
1858
+ border: `1px solid ${theme.divider}`,
1757
1859
  borderRadius: 18,
1758
1860
  padding: "10px 16px",
1759
1861
  fontSize: 14,
1760
1862
  lineHeight: 1.4,
1761
1863
  outline: "none",
1762
- background: "#fafafa",
1763
- color: config.textColor,
1864
+ background: isDark ? "rgba(255,255,255,0.06)" : "#fafafa",
1865
+ color: theme.text,
1764
1866
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
1765
1867
  resize: "none",
1766
1868
  overflowY: "auto",
@@ -1771,7 +1873,7 @@ function ChatInput() {
1771
1873
  width: 36,
1772
1874
  height: 36,
1773
1875
  borderRadius: "50%",
1774
- background: config.primaryColor,
1876
+ background: theme.primary,
1775
1877
  border: "none",
1776
1878
  color: "white",
1777
1879
  cursor: isLoading ? "not-allowed" : "pointer",
@@ -1801,10 +1903,10 @@ function ChatInput() {
1801
1903
  position: "absolute",
1802
1904
  bottom: "calc(100% + 4px)",
1803
1905
  left: 0,
1804
- background: "white",
1805
- border: "1px solid #e0e0e0",
1906
+ background: theme.background,
1907
+ border: `1px solid ${theme.divider}`,
1806
1908
  borderRadius: 8,
1807
- 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)",
1808
1910
  padding: 4,
1809
1911
  minWidth: 180,
1810
1912
  zIndex: 10,
@@ -1821,7 +1923,7 @@ function ChatInput() {
1821
1923
  borderRadius: 4,
1822
1924
  cursor: "pointer",
1823
1925
  fontSize: 14,
1824
- color: "#333",
1926
+ color: theme.text,
1825
1927
  textAlign: "left",
1826
1928
  whiteSpace: "nowrap",
1827
1929
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
@@ -1829,13 +1931,13 @@ function ChatInput() {
1829
1931
  const dropOverlayStyle = {
1830
1932
  position: "absolute",
1831
1933
  inset: 0,
1832
- background: "rgba(255,255,255,0.92)",
1833
- 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}`,
1834
1936
  borderRadius: 4,
1835
1937
  display: "flex",
1836
1938
  alignItems: "center",
1837
1939
  justifyContent: "center",
1838
- color: config.primaryColor,
1940
+ color: theme.primary,
1839
1941
  fontSize: 14,
1840
1942
  fontWeight: 500,
1841
1943
  pointerEvents: "none",
@@ -1850,7 +1952,7 @@ function ChatInput() {
1850
1952
  padding: "4px 10px",
1851
1953
  fontSize: 12
1852
1954
  };
1853
- const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
1955
+ const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading || readOnly;
1854
1956
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1855
1957
  "div",
1856
1958
  {
@@ -1879,7 +1981,7 @@ function ChatInput() {
1879
1981
  }
1880
1982
  ),
1881
1983
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
1882
- /* @__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: [
1883
1985
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1884
1986
  "button",
1885
1987
  {
@@ -1904,7 +2006,7 @@ function ChatInput() {
1904
2006
  onClick: handlePickFile,
1905
2007
  style: menuItemStyle,
1906
2008
  onMouseEnter: (e) => {
1907
- e.currentTarget.style.background = "#f5f5f5";
2009
+ e.currentTarget.style.background = theme.bubbleBackground;
1908
2010
  },
1909
2011
  onMouseLeave: (e) => {
1910
2012
  e.currentTarget.style.background = "transparent";
@@ -1923,7 +2025,7 @@ function ChatInput() {
1923
2025
  onClick: handleCapture,
1924
2026
  style: menuItemStyle,
1925
2027
  onMouseEnter: (e) => {
1926
- e.currentTarget.style.background = "#f5f5f5";
2028
+ e.currentTarget.style.background = theme.bubbleBackground;
1927
2029
  },
1928
2030
  onMouseLeave: (e) => {
1929
2031
  e.currentTarget.style.background = "transparent";
@@ -1960,14 +2062,14 @@ function ChatInput() {
1960
2062
  onPaste: handlePaste,
1961
2063
  placeholder: config.placeholderText,
1962
2064
  style: inputStyle,
1963
- disabled: isLoading
2065
+ disabled: isLoading || readOnly
1964
2066
  }
1965
2067
  ),
1966
2068
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1967
2069
  "button",
1968
2070
  {
1969
2071
  onClick: handleSend,
1970
- disabled: isLoading || !value.trim(),
2072
+ disabled: isLoading || readOnly || !value.trim(),
1971
2073
  style: sendButtonStyle,
1972
2074
  "aria-label": t("send_message"),
1973
2075
  onMouseEnter: (e) => {
@@ -2226,8 +2328,231 @@ function Spinner2() {
2226
2328
  ] });
2227
2329
  }
2228
2330
 
2229
- // src/components/chat-window.tsx
2331
+ // src/components/incident-banner.tsx
2332
+ var import_react10 = require("react");
2230
2333
  var import_jsx_runtime9 = require("react/jsx-runtime");
2334
+ var PALETTE = {
2335
+ info: {
2336
+ bg: "#EFF6FF",
2337
+ fg: "#1E3A8A",
2338
+ fgMuted: "#3B5BA9",
2339
+ accent: "#2563EB",
2340
+ iconBg: "#DBEAFE",
2341
+ iconFg: "#2563EB"
2342
+ },
2343
+ warning: {
2344
+ bg: "#FFFBEB",
2345
+ fg: "#78350F",
2346
+ fgMuted: "#92541A",
2347
+ accent: "#B45309",
2348
+ iconBg: "#FEF3C7",
2349
+ iconFg: "#B45309"
2350
+ },
2351
+ outage: {
2352
+ bg: "#FEF2F2",
2353
+ fg: "#991B1B",
2354
+ fgMuted: "#B23A3A",
2355
+ accent: "#DC2626",
2356
+ iconBg: "#FEE2E2",
2357
+ iconFg: "#DC2626"
2358
+ }
2359
+ };
2360
+ function SeverityIcon({
2361
+ severity,
2362
+ color
2363
+ }) {
2364
+ const props = {
2365
+ width: 14,
2366
+ height: 14,
2367
+ viewBox: "0 0 24 24",
2368
+ fill: "none",
2369
+ stroke: color,
2370
+ strokeWidth: 2.25,
2371
+ strokeLinecap: "round",
2372
+ strokeLinejoin: "round",
2373
+ "aria-hidden": true
2374
+ };
2375
+ if (severity === "outage") {
2376
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
2377
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2378
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
2380
+ ] });
2381
+ }
2382
+ if (severity === "warning") {
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
2386
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
2387
+ ] });
2388
+ }
2389
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...props, children: [
2390
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2391
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
2392
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
2393
+ ] });
2394
+ }
2395
+ function IncidentBanner() {
2396
+ const { incidentBanner, incidentBannerDismissed, dismissIncidentBanner, t } = useChat();
2397
+ const [linkHover, setLinkHover] = (0, import_react10.useState)(false);
2398
+ const [closeHover, setCloseHover] = (0, import_react10.useState)(false);
2399
+ if (!incidentBanner || incidentBannerDismissed) return null;
2400
+ const palette = PALETTE[incidentBanner.severity];
2401
+ const wrap = {
2402
+ background: palette.bg,
2403
+ color: palette.fg,
2404
+ padding: "12px 14px 12px 12px",
2405
+ display: "flex",
2406
+ gap: 10,
2407
+ alignItems: "flex-start",
2408
+ fontSize: 13,
2409
+ lineHeight: 1.45,
2410
+ boxShadow: "inset 0 -1px 0 rgba(0,0,0,0.04)"
2411
+ };
2412
+ const iconBadge = {
2413
+ width: 22,
2414
+ height: 22,
2415
+ borderRadius: "50%",
2416
+ background: palette.iconBg,
2417
+ display: "flex",
2418
+ alignItems: "center",
2419
+ justifyContent: "center",
2420
+ flexShrink: 0,
2421
+ marginTop: 1
2422
+ };
2423
+ const titleStyle = {
2424
+ margin: 0,
2425
+ fontSize: 13,
2426
+ fontWeight: 600,
2427
+ letterSpacing: "-0.005em"
2428
+ };
2429
+ const bodyStyle = {
2430
+ margin: "3px 0 0",
2431
+ fontSize: 12.5,
2432
+ color: palette.fgMuted
2433
+ };
2434
+ const footerRowStyle = {
2435
+ display: "flex",
2436
+ flexWrap: "wrap",
2437
+ alignItems: "center",
2438
+ gap: 10,
2439
+ marginTop: 8
2440
+ };
2441
+ const etaStyle = {
2442
+ display: "inline-block",
2443
+ padding: "2px 8px",
2444
+ borderRadius: 4,
2445
+ fontSize: 11,
2446
+ fontWeight: 500,
2447
+ color: palette.accent,
2448
+ background: palette.iconBg,
2449
+ lineHeight: 1.4
2450
+ };
2451
+ const linkStyle = {
2452
+ display: "inline-flex",
2453
+ alignItems: "center",
2454
+ gap: 4,
2455
+ color: palette.accent,
2456
+ textDecoration: linkHover ? "underline" : "none",
2457
+ textUnderlineOffset: 3,
2458
+ fontSize: 12.5,
2459
+ fontWeight: 600
2460
+ };
2461
+ const closeButtonStyle = {
2462
+ background: closeHover ? "rgba(0,0,0,0.06)" : "transparent",
2463
+ border: "none",
2464
+ color: palette.fgMuted,
2465
+ cursor: "pointer",
2466
+ padding: 4,
2467
+ borderRadius: 6,
2468
+ flexShrink: 0,
2469
+ lineHeight: 0,
2470
+ marginTop: -2,
2471
+ marginRight: -2,
2472
+ transition: "background-color 0.12s ease"
2473
+ };
2474
+ const role = incidentBanner.severity === "outage" ? "alert" : "status";
2475
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { role, style: wrap, children: [
2476
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: iconBadge, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2477
+ SeverityIcon,
2478
+ {
2479
+ severity: incidentBanner.severity,
2480
+ color: palette.iconFg
2481
+ }
2482
+ ) }),
2483
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2484
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: titleStyle, children: incidentBanner.title }),
2485
+ incidentBanner.body ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: bodyStyle, children: incidentBanner.body }) : null,
2486
+ incidentBanner.eta || incidentBanner.link ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: footerRowStyle, children: [
2487
+ incidentBanner.eta ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: etaStyle, children: incidentBanner.eta }) : null,
2488
+ incidentBanner.link ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2489
+ "a",
2490
+ {
2491
+ href: incidentBanner.link.url,
2492
+ target: "_blank",
2493
+ rel: "noopener noreferrer",
2494
+ style: linkStyle,
2495
+ onMouseEnter: () => setLinkHover(true),
2496
+ onMouseLeave: () => setLinkHover(false),
2497
+ children: [
2498
+ incidentBanner.link.label ?? t("incident_default_link_label"),
2499
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2500
+ "svg",
2501
+ {
2502
+ width: "12",
2503
+ height: "12",
2504
+ viewBox: "0 0 24 24",
2505
+ fill: "none",
2506
+ stroke: "currentColor",
2507
+ strokeWidth: "2.5",
2508
+ strokeLinecap: "round",
2509
+ strokeLinejoin: "round",
2510
+ "aria-hidden": "true",
2511
+ children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("polyline", { points: "12 5 19 12 12 19" })
2514
+ ]
2515
+ }
2516
+ )
2517
+ ]
2518
+ }
2519
+ ) : null
2520
+ ] }) : null
2521
+ ] }),
2522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2523
+ "button",
2524
+ {
2525
+ type: "button",
2526
+ onClick: dismissIncidentBanner,
2527
+ "aria-label": t("incident_dismiss"),
2528
+ style: closeButtonStyle,
2529
+ onMouseEnter: () => setCloseHover(true),
2530
+ onMouseLeave: () => setCloseHover(false),
2531
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2532
+ "svg",
2533
+ {
2534
+ width: "14",
2535
+ height: "14",
2536
+ viewBox: "0 0 24 24",
2537
+ fill: "none",
2538
+ stroke: "currentColor",
2539
+ strokeWidth: "2",
2540
+ strokeLinecap: "round",
2541
+ strokeLinejoin: "round",
2542
+ "aria-hidden": "true",
2543
+ children: [
2544
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2545
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2546
+ ]
2547
+ }
2548
+ )
2549
+ }
2550
+ )
2551
+ ] });
2552
+ }
2553
+
2554
+ // src/components/chat-window.tsx
2555
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2231
2556
  function ConfigError({ message, title }) {
2232
2557
  const errorStyle = {
2233
2558
  flex: 1,
@@ -2239,8 +2564,8 @@ function ConfigError({ message, title }) {
2239
2564
  textAlign: "center",
2240
2565
  gap: 8
2241
2566
  };
2242
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: errorStyle, children: [
2243
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2567
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: errorStyle, children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2244
2569
  "svg",
2245
2570
  {
2246
2571
  width: "32",
@@ -2252,14 +2577,14 @@ function ConfigError({ message, title }) {
2252
2577
  strokeLinecap: "round",
2253
2578
  strokeLinejoin: "round",
2254
2579
  children: [
2255
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2256
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
2257
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
2580
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
2581
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
2582
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
2258
2583
  ]
2259
2584
  }
2260
2585
  ),
2261
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
2262
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
2586
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
2587
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
2263
2588
  ] });
2264
2589
  }
2265
2590
  function fieldKey(field) {
@@ -2286,9 +2611,9 @@ function validateField(field, value) {
2286
2611
  }
2287
2612
  function PreChatFormView() {
2288
2613
  const { preChatForm, submitPreChatForm, cancelPreChatForm, config, t } = useChat();
2289
- const [values, setValues] = (0, import_react9.useState)({});
2290
- const [errors, setErrors] = (0, import_react9.useState)({});
2291
- const [submitting, setSubmitting] = (0, import_react9.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);
2292
2617
  if (!preChatForm) return null;
2293
2618
  function setValue(key, value) {
2294
2619
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -2384,15 +2709,15 @@ function PreChatFormView() {
2384
2709
  fontSize: 14,
2385
2710
  cursor: "pointer"
2386
2711
  };
2387
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2712
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2388
2713
  "form",
2389
2714
  {
2390
2715
  style: containerStyle,
2391
2716
  onSubmit: handleSubmit,
2392
2717
  "data-customerhero-prechat-form": true,
2393
2718
  children: [
2394
- preChatForm.title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { style: { fontSize: 16, fontWeight: 600, margin: 0 }, children: preChatForm.title }),
2395
- preChatForm.description && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 13, margin: 0, opacity: 0.8 }, children: preChatForm.description }),
2719
+ preChatForm.title && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { style: { fontSize: 16, fontWeight: 600, margin: 0 }, children: preChatForm.title }),
2720
+ preChatForm.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontSize: 13, margin: 0, opacity: 0.8 }, children: preChatForm.description }),
2396
2721
  preChatForm.fields.map((field) => {
2397
2722
  const k = fieldKey(field);
2398
2723
  const v = values[k];
@@ -2400,12 +2725,12 @@ function PreChatFormView() {
2400
2725
  const required = "required" in field ? !!field.required : false;
2401
2726
  const label = fieldLabel(field);
2402
2727
  if (field.kind === "textarea") {
2403
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
2404
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2728
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
2729
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2405
2730
  label,
2406
2731
  required && " *"
2407
2732
  ] }),
2408
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2733
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2409
2734
  "textarea",
2410
2735
  {
2411
2736
  style: { ...inputStyle, minHeight: 80, resize: "vertical" },
@@ -2414,32 +2739,32 @@ function PreChatFormView() {
2414
2739
  onChange: (e) => setValue(k, e.target.value)
2415
2740
  }
2416
2741
  ),
2417
- err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
2742
+ err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
2418
2743
  ] }, k);
2419
2744
  }
2420
2745
  if (field.kind === "select") {
2421
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
2422
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2746
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
2747
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2423
2748
  label,
2424
2749
  required && " *"
2425
2750
  ] }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2751
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2427
2752
  "select",
2428
2753
  {
2429
2754
  style: inputStyle,
2430
2755
  value: v ?? "",
2431
2756
  onChange: (e) => setValue(k, e.target.value),
2432
2757
  children: [
2433
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: "\u2014" }),
2434
- field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
2758
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "", children: "\u2014" }),
2759
+ field.options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
2435
2760
  ]
2436
2761
  }
2437
2762
  ),
2438
- err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
2763
+ err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
2439
2764
  ] }, k);
2440
2765
  }
2441
2766
  if (field.kind === "consent") {
2442
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2767
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2443
2768
  "label",
2444
2769
  {
2445
2770
  style: {
@@ -2449,7 +2774,7 @@ function PreChatFormView() {
2449
2774
  gap: 8
2450
2775
  },
2451
2776
  children: [
2452
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2777
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2453
2778
  "input",
2454
2779
  {
2455
2780
  type: "checkbox",
@@ -2458,11 +2783,11 @@ function PreChatFormView() {
2458
2783
  style: { marginTop: 3 }
2459
2784
  }
2460
2785
  ),
2461
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { fontSize: 13, fontWeight: 400 }, children: [
2786
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: { fontSize: 13, fontWeight: 400 }, children: [
2462
2787
  field.label,
2463
- field.url && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2788
+ field.url && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2464
2789
  " ",
2465
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2790
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2466
2791
  "a",
2467
2792
  {
2468
2793
  href: field.url,
@@ -2474,7 +2799,7 @@ function PreChatFormView() {
2474
2799
  )
2475
2800
  ] })
2476
2801
  ] }),
2477
- err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
2802
+ err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
2478
2803
  ]
2479
2804
  },
2480
2805
  k
@@ -2482,12 +2807,12 @@ function PreChatFormView() {
2482
2807
  }
2483
2808
  const inputType = field.kind === "email" ? "email" : field.kind === "phone" ? "tel" : "text";
2484
2809
  const maxLength = field.kind === "text" ? field.maxLength : void 0;
2485
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: labelStyle, children: [
2486
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
2810
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: labelStyle, children: [
2811
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
2487
2812
  label,
2488
2813
  required && " *"
2489
2814
  ] }),
2490
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2815
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2491
2816
  "input",
2492
2817
  {
2493
2818
  type: inputType,
@@ -2497,11 +2822,11 @@ function PreChatFormView() {
2497
2822
  onChange: (e) => setValue(k, e.target.value)
2498
2823
  }
2499
2824
  ),
2500
- err && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: errorStyle, children: err })
2825
+ err && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: errorStyle, children: err })
2501
2826
  ] }, k);
2502
2827
  }),
2503
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: buttonRowStyle, children: [
2504
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2828
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: buttonRowStyle, children: [
2829
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2505
2830
  "button",
2506
2831
  {
2507
2832
  type: "button",
@@ -2511,18 +2836,19 @@ function PreChatFormView() {
2511
2836
  children: t("action_cancel")
2512
2837
  }
2513
2838
  ),
2514
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "submit", style: submitStyle, disabled: submitting, children: preChatForm.submitLabel })
2839
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: submitStyle, disabled: submitting, children: preChatForm.submitLabel })
2515
2840
  ] })
2516
2841
  ]
2517
2842
  }
2518
2843
  );
2519
2844
  }
2520
- function ChatWindow() {
2845
+ function ChatWindow({ embedded } = {}) {
2521
2846
  const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
2522
2847
  const reduced = useReducedMotion();
2523
- const [visible, setVisible] = (0, import_react9.useState)(false);
2524
- const [shouldRender, setShouldRender] = (0, import_react9.useState)(false);
2525
- (0, import_react9.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)(() => {
2526
2852
  if (isOpen) {
2527
2853
  setShouldRender(true);
2528
2854
  requestAnimationFrame(() => {
@@ -2540,48 +2866,59 @@ function ChatWindow() {
2540
2866
  }, [isOpen, reduced]);
2541
2867
  if (!shouldRender) return null;
2542
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;
2543
2873
  const style = {
2544
- position: "fixed",
2545
- bottom: 90,
2546
- [effectivePosition === "bottom-left" ? "left" : "right"]: 20,
2547
- width: 380,
2548
- maxWidth: "calc(100vw - 40px)",
2549
- height: 520,
2550
- maxHeight: "calc(100vh - 120px)",
2551
- 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,
2552
2882
  overflow: "hidden",
2553
2883
  display: "flex",
2554
2884
  flexDirection: "column",
2555
- boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
2556
- zIndex: 99999,
2557
- 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,
2558
2893
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2559
2894
  opacity: visible ? 1 : 0,
2560
2895
  transform: visible ? "translateY(0) scale(1)" : "translateY(16px) scale(0.97)",
2561
2896
  transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease"
2562
2897
  };
2898
+ const isDark = theme.scheme === "dark";
2563
2899
  const poweredStyle = {
2564
2900
  textAlign: "center",
2565
2901
  padding: 6,
2566
2902
  fontSize: 10,
2567
- color: "#aaa"
2903
+ color: isDark ? "rgba(255,255,255,0.45)" : "#aaa"
2568
2904
  };
2569
2905
  const linkStyle = {
2570
- color: "#888",
2906
+ color: isDark ? "rgba(255,255,255,0.6)" : "#888",
2571
2907
  textDecoration: "underline",
2572
2908
  textUnderlineOffset: 2
2573
2909
  };
2574
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
2575
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatHeader, {}),
2576
- configError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : preChatFormVisible ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PreChatFormView, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2577
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatMessages, {}),
2578
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatSuggestions, {}),
2579
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatInput, {})
2910
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
2911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatHeader, {}),
2912
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IncidentBanner, {}),
2913
+ configError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : preChatFormVisible ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreChatFormView, {}) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2914
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatMessages, {}),
2915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatSuggestions, {}),
2916
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatInput, {})
2580
2917
  ] }),
2581
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: poweredStyle, children: [
2918
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: poweredStyle, children: [
2582
2919
  t("powered_by"),
2583
2920
  " ",
2584
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2921
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2585
2922
  "a",
2586
2923
  {
2587
2924
  href: "https://customerhero.app",
@@ -2596,12 +2933,15 @@ function ChatWindow() {
2596
2933
  }
2597
2934
 
2598
2935
  // src/components/chat-widget.tsx
2599
- var import_jsx_runtime10 = require("react/jsx-runtime");
2600
- function ChatWidgetInner({ identity }) {
2936
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2937
+ function ChatWidgetInner({
2938
+ identity,
2939
+ embedded
2940
+ }) {
2601
2941
  const client = useCustomerHeroClient();
2602
2942
  const { configLoaded, configError } = useChat();
2603
- const prevIdentityRef = (0, import_react10.useRef)(void 0);
2604
- (0, import_react10.useEffect)(() => {
2943
+ const prevIdentityRef = (0, import_react12.useRef)(void 0);
2944
+ (0, import_react12.useEffect)(() => {
2605
2945
  const key = identity ? JSON.stringify(identity) : void 0;
2606
2946
  if (key !== prevIdentityRef.current) {
2607
2947
  prevIdentityRef.current = key;
@@ -2611,13 +2951,13 @@ function ChatWidgetInner({ identity }) {
2611
2951
  }
2612
2952
  }, [identity, client]);
2613
2953
  if (!configLoaded || configError) return null;
2614
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2615
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatBubble, {}),
2616
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWindow, {})
2954
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2955
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubble, { embedded }),
2956
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWindow, { embedded })
2617
2957
  ] });
2618
2958
  }
2619
- function ChatWidget({ identity, ...config }) {
2620
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime10.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 }) });
2621
2961
  }
2622
2962
  // Annotate the CommonJS export names for ESM import in node:
2623
2963
  0 && (module.exports = {