@customerhero/react 2.2.0 → 2.4.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;
@@ -1239,6 +1326,201 @@ function StreamingCursor({ reduced }) {
1239
1326
  )
1240
1327
  ] });
1241
1328
  }
1329
+ function formatBytes(n) {
1330
+ if (n == null || n <= 0) return null;
1331
+ if (n < 1024) return `${n} B`;
1332
+ if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
1333
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
1334
+ }
1335
+ function AttachmentList({
1336
+ attachments,
1337
+ isUser,
1338
+ primaryColor,
1339
+ t
1340
+ }) {
1341
+ const containerStyle = {
1342
+ display: "flex",
1343
+ flexDirection: "column",
1344
+ gap: 6,
1345
+ marginTop: 6,
1346
+ alignItems: isUser ? "flex-end" : "flex-start"
1347
+ };
1348
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: containerStyle, children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1349
+ AttachmentTile,
1350
+ {
1351
+ attachment: a,
1352
+ isUser,
1353
+ primaryColor,
1354
+ t
1355
+ },
1356
+ a.id
1357
+ )) });
1358
+ }
1359
+ function AttachmentTile({
1360
+ attachment,
1361
+ isUser,
1362
+ primaryColor,
1363
+ t
1364
+ }) {
1365
+ const tileBg = isUser ? `${primaryColor}11` : "#f3f4f6";
1366
+ const tileBorder = isUser ? `${primaryColor}33` : "#e5e7eb";
1367
+ if (attachment.kind === "image" && attachment.url) {
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1369
+ "a",
1370
+ {
1371
+ href: attachment.url,
1372
+ target: "_blank",
1373
+ rel: "noopener noreferrer",
1374
+ style: { display: "inline-block", maxWidth: 220, lineHeight: 0 },
1375
+ "aria-label": attachment.filename ?? t("attachment_image_alt"),
1376
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1377
+ "img",
1378
+ {
1379
+ src: attachment.url,
1380
+ alt: attachment.filename ?? t("attachment_image_alt"),
1381
+ style: {
1382
+ display: "block",
1383
+ maxWidth: 220,
1384
+ maxHeight: 220,
1385
+ width: "auto",
1386
+ height: "auto",
1387
+ borderRadius: 12,
1388
+ border: `1px solid ${tileBorder}`,
1389
+ objectFit: "cover"
1390
+ }
1391
+ }
1392
+ )
1393
+ }
1394
+ );
1395
+ }
1396
+ if (attachment.kind === "location" && attachment.location) {
1397
+ const { latitude, longitude, label } = attachment.location;
1398
+ const href = `https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
1399
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1400
+ "a",
1401
+ {
1402
+ href,
1403
+ target: "_blank",
1404
+ rel: "noopener noreferrer",
1405
+ style: tileLinkStyle(tileBg, tileBorder),
1406
+ children: [
1407
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PinIcon, {}),
1408
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1409
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: label || t("attachment_location") }),
1410
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: tileSubStyle(), children: [
1411
+ latitude.toFixed(4),
1412
+ ", ",
1413
+ longitude.toFixed(4)
1414
+ ] })
1415
+ ] })
1416
+ ]
1417
+ }
1418
+ );
1419
+ }
1420
+ if (!attachment.url) return null;
1421
+ const sub = [attachment.mimeType, formatBytes(attachment.sizeBytes)].filter(Boolean).join(" \xB7 ");
1422
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1423
+ "a",
1424
+ {
1425
+ href: attachment.url,
1426
+ target: "_blank",
1427
+ rel: "noopener noreferrer",
1428
+ title: t("attachment_open"),
1429
+ style: tileLinkStyle(tileBg, tileBorder),
1430
+ children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileIcon, { kind: attachment.kind }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileTitleStyle(), children: attachment.filename ?? t("attachment_open") }),
1434
+ sub && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: tileSubStyle(), children: sub })
1435
+ ] })
1436
+ ]
1437
+ }
1438
+ );
1439
+ }
1440
+ function tileLinkStyle(bg, border) {
1441
+ return {
1442
+ display: "flex",
1443
+ alignItems: "center",
1444
+ gap: 10,
1445
+ padding: "8px 12px",
1446
+ background: bg,
1447
+ border: `1px solid ${border}`,
1448
+ borderRadius: 12,
1449
+ textDecoration: "none",
1450
+ color: "inherit",
1451
+ maxWidth: 260
1452
+ };
1453
+ }
1454
+ function tileTitleStyle() {
1455
+ return {
1456
+ fontSize: 13,
1457
+ fontWeight: 500,
1458
+ color: "#1f2937",
1459
+ overflow: "hidden",
1460
+ textOverflow: "ellipsis",
1461
+ whiteSpace: "nowrap",
1462
+ maxWidth: 200
1463
+ };
1464
+ }
1465
+ function tileSubStyle() {
1466
+ return {
1467
+ fontSize: 11,
1468
+ color: "#6b7280",
1469
+ overflow: "hidden",
1470
+ textOverflow: "ellipsis",
1471
+ whiteSpace: "nowrap",
1472
+ maxWidth: 200
1473
+ };
1474
+ }
1475
+ function FileIcon({ kind }) {
1476
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1477
+ "svg",
1478
+ {
1479
+ width: "20",
1480
+ height: "20",
1481
+ viewBox: "0 0 24 24",
1482
+ fill: "none",
1483
+ stroke: "#4b5563",
1484
+ strokeWidth: "2",
1485
+ strokeLinecap: "round",
1486
+ strokeLinejoin: "round",
1487
+ "aria-hidden": "true",
1488
+ style: { flexShrink: 0 },
1489
+ children: kind === "audio" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1490
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 18V5l12-2v13" }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "6", cy: "18", r: "3" }),
1492
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "18", cy: "16", r: "3" })
1493
+ ] }) : kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1494
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }),
1495
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m22 8-6 4 6 4z" })
1496
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "14 2 14 8 20 8" })
1499
+ ] })
1500
+ }
1501
+ );
1502
+ }
1503
+ function PinIcon() {
1504
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1505
+ "svg",
1506
+ {
1507
+ width: "20",
1508
+ height: "20",
1509
+ viewBox: "0 0 24 24",
1510
+ fill: "none",
1511
+ stroke: "#4b5563",
1512
+ strokeWidth: "2",
1513
+ strokeLinecap: "round",
1514
+ strokeLinejoin: "round",
1515
+ "aria-hidden": "true",
1516
+ style: { flexShrink: 0 },
1517
+ children: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 0 1 16 0Z" }),
1519
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "10", r: "3" })
1520
+ ]
1521
+ }
1522
+ );
1523
+ }
1242
1524
  function Message({
1243
1525
  message,
1244
1526
  config,
@@ -1264,14 +1546,16 @@ function Message({
1264
1546
  color: "white",
1265
1547
  borderBottomRightRadius: 4
1266
1548
  } : {
1267
- background: "#f0f0f0",
1549
+ background: config.bubbleBackground,
1268
1550
  color: config.textColor,
1269
1551
  borderBottomLeftRadius: 4
1270
1552
  }
1271
1553
  };
1272
1554
  const linkColor = isUser ? "#ffffff" : config.primaryColor;
1555
+ const hasContent = message.content.trim().length > 0;
1556
+ const hasAttachments = !!message.attachments?.length;
1273
1557
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1558
+ hasContent && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1275
1559
  "div",
1276
1560
  {
1277
1561
  style: bubbleStyle,
@@ -1285,6 +1569,15 @@ function Message({
1285
1569
  ] })
1286
1570
  }
1287
1571
  ),
1572
+ hasAttachments && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1573
+ AttachmentList,
1574
+ {
1575
+ attachments: message.attachments,
1576
+ isUser,
1577
+ primaryColor: config.primaryColor,
1578
+ t
1579
+ }
1580
+ ),
1288
1581
  isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
1289
1582
  !isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1290
1583
  BlockRenderer,
@@ -1360,7 +1653,6 @@ function ChatMessages() {
1360
1653
  messages,
1361
1654
  isLoading,
1362
1655
  error,
1363
- config,
1364
1656
  conversationId,
1365
1657
  rateMessage,
1366
1658
  sendMessage,
@@ -1368,13 +1660,19 @@ function ChatMessages() {
1368
1660
  cancelAction,
1369
1661
  t
1370
1662
  } = useChat();
1663
+ const theme = useEffectiveTheme();
1664
+ const themedConfig = {
1665
+ primaryColor: theme.primary,
1666
+ textColor: theme.text,
1667
+ bubbleBackground: theme.bubbleBackground
1668
+ };
1371
1669
  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);
1670
+ const containerRef = (0, import_react8.useRef)(null);
1671
+ const messagesEndRef = (0, import_react8.useRef)(null);
1672
+ const isFirstRender = (0, import_react8.useRef)(true);
1673
+ const prevMessageCount = (0, import_react8.useRef)(0);
1674
+ const stickRef = (0, import_react8.useRef)(true);
1675
+ const suppressScrollRef = (0, import_react8.useRef)(false);
1378
1676
  const autoScrollTo = (top) => {
1379
1677
  const el = containerRef.current;
1380
1678
  if (!el) return;
@@ -1391,7 +1689,7 @@ function ChatMessages() {
1391
1689
  const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
1392
1690
  stickRef.current = distFromBottom < 60;
1393
1691
  };
1394
- (0, import_react7.useEffect)(() => {
1692
+ (0, import_react8.useEffect)(() => {
1395
1693
  const el = containerRef.current;
1396
1694
  if (!el) return;
1397
1695
  const lastMsg2 = messages[messages.length - 1];
@@ -1416,7 +1714,7 @@ function ChatMessages() {
1416
1714
  isFirstRender.current = false;
1417
1715
  }, [messages, isLoading, reduced]);
1418
1716
  const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
1419
- (0, import_react7.useEffect)(() => {
1717
+ (0, import_react8.useEffect)(() => {
1420
1718
  prevMessageCount.current = messages.length;
1421
1719
  }, [messages.length]);
1422
1720
  let lastBotIndex = -1;
@@ -1441,7 +1739,7 @@ function ChatMessages() {
1441
1739
  Message,
1442
1740
  {
1443
1741
  message: msg,
1444
- config,
1742
+ config: themedConfig,
1445
1743
  onRate: rateMessage,
1446
1744
  onSend: sendMessage,
1447
1745
  onApproveAction: approveAction,
@@ -1480,10 +1778,13 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
1480
1778
  function ChatSuggestions() {
1481
1779
  const { messages, isLoading, config, sendMessage } = useChat();
1482
1780
  const reduced = useReducedMotion();
1781
+ const theme = useEffectiveTheme();
1483
1782
  const hasUserMessage = messages.some((m) => m.role === "user");
1484
1783
  if (config.suggestedMessages.length === 0 || hasUserMessage || isLoading) {
1485
1784
  return null;
1486
1785
  }
1786
+ const isDark = theme.scheme === "dark";
1787
+ const idleBorder = isDark ? "rgba(255,255,255,0.18)" : "#e0e0e0";
1487
1788
  const containerStyle = {
1488
1789
  padding: "8px 16px",
1489
1790
  display: "flex",
@@ -1493,11 +1794,11 @@ function ChatSuggestions() {
1493
1794
  };
1494
1795
  const chipStyle = {
1495
1796
  background: "none",
1496
- border: "1px solid #e0e0e0",
1797
+ border: `1px solid ${idleBorder}`,
1497
1798
  borderRadius: 20,
1498
1799
  padding: "7px 14px",
1499
1800
  fontSize: 13,
1500
- color: "#333",
1801
+ color: theme.text,
1501
1802
  cursor: "pointer",
1502
1803
  textAlign: "left",
1503
1804
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
@@ -1509,11 +1810,11 @@ function ChatSuggestions() {
1509
1810
  style: chipStyle,
1510
1811
  onClick: () => sendMessage(text),
1511
1812
  onMouseEnter: (e) => {
1512
- e.currentTarget.style.borderColor = config.primaryColor;
1513
- e.currentTarget.style.background = `${config.primaryColor}08`;
1813
+ e.currentTarget.style.borderColor = theme.primary;
1814
+ e.currentTarget.style.background = `${theme.primary}14`;
1514
1815
  },
1515
1816
  onMouseLeave: (e) => {
1516
- e.currentTarget.style.borderColor = "#e0e0e0";
1817
+ e.currentTarget.style.borderColor = idleBorder;
1517
1818
  e.currentTarget.style.background = "none";
1518
1819
  },
1519
1820
  children: text
@@ -1523,8 +1824,8 @@ function ChatSuggestions() {
1523
1824
  }
1524
1825
 
1525
1826
  // src/components/chat-input.tsx
1526
- var import_react8 = require("react");
1527
- var import_js2 = require("@customerhero/js");
1827
+ var import_react9 = require("react");
1828
+ var import_js3 = require("@customerhero/js");
1528
1829
  var import_jsx_runtime8 = require("react/jsx-runtime");
1529
1830
  var MAX_ATTACHMENTS = 3;
1530
1831
  var ALLOWED_MIME_TYPES = [
@@ -1542,47 +1843,50 @@ function ChatInput() {
1542
1843
  sendMessage,
1543
1844
  uploadAttachment,
1544
1845
  isLoading,
1846
+ readOnly,
1545
1847
  config,
1546
1848
  t,
1547
1849
  consumePendingPrefill,
1548
1850
  pendingPrefill
1549
1851
  } = useChat();
1852
+ const theme = useEffectiveTheme();
1853
+ const isDark = theme.scheme === "dark";
1550
1854
  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)());
1855
+ const [value, setValue] = (0, import_react9.useState)("");
1856
+ const [attachments, setAttachments] = (0, import_react9.useState)([]);
1857
+ const [captureSupported, setCaptureSupported] = (0, import_react9.useState)(false);
1858
+ const [menuOpen, setMenuOpen] = (0, import_react9.useState)(false);
1859
+ const [dragActive, setDragActive] = (0, import_react9.useState)(false);
1860
+ const [transientError, setTransientError] = (0, import_react9.useState)(null);
1861
+ const fileInputRef = (0, import_react9.useRef)(null);
1862
+ const textInputRef = (0, import_react9.useRef)(null);
1863
+ const menuRef = (0, import_react9.useRef)(null);
1864
+ const menuButtonRef = (0, import_react9.useRef)(null);
1865
+ const dragCounterRef = (0, import_react9.useRef)(0);
1866
+ (0, import_react9.useEffect)(() => {
1867
+ setCaptureSupported((0, import_js3.canCaptureScreenshot)());
1564
1868
  }, []);
1565
- (0, import_react8.useEffect)(() => {
1869
+ (0, import_react9.useEffect)(() => {
1566
1870
  const id = requestAnimationFrame(() => textInputRef.current?.focus());
1567
1871
  return () => cancelAnimationFrame(id);
1568
1872
  }, []);
1569
- (0, import_react8.useLayoutEffect)(() => {
1873
+ (0, import_react9.useLayoutEffect)(() => {
1570
1874
  const el = textInputRef.current;
1571
1875
  if (!el) return;
1572
1876
  el.style.height = "auto";
1573
1877
  el.style.height = `${el.scrollHeight}px`;
1574
1878
  }, [value]);
1575
- (0, import_react8.useEffect)(() => {
1879
+ (0, import_react9.useEffect)(() => {
1576
1880
  if (pendingPrefill === null) return;
1577
1881
  const text = consumePendingPrefill();
1578
1882
  if (text !== null) setValue(text);
1579
1883
  }, [pendingPrefill, consumePendingPrefill]);
1580
- (0, import_react8.useEffect)(() => {
1884
+ (0, import_react9.useEffect)(() => {
1581
1885
  return () => {
1582
1886
  for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
1583
1887
  };
1584
1888
  }, []);
1585
- (0, import_react8.useEffect)(() => {
1889
+ (0, import_react9.useEffect)(() => {
1586
1890
  if (!menuOpen) return;
1587
1891
  const onClick = (e) => {
1588
1892
  const target = e.target;
@@ -1601,7 +1905,7 @@ function ChatInput() {
1601
1905
  document.removeEventListener("keydown", onKey);
1602
1906
  };
1603
1907
  }, [menuOpen]);
1604
- (0, import_react8.useEffect)(() => {
1908
+ (0, import_react9.useEffect)(() => {
1605
1909
  if (!transientError) return;
1606
1910
  const id = window.setTimeout(() => setTransientError(null), 4e3);
1607
1911
  return () => window.clearTimeout(id);
@@ -1657,10 +1961,10 @@ function ChatInput() {
1657
1961
  const handleCapture = async () => {
1658
1962
  setMenuOpen(false);
1659
1963
  try {
1660
- const blob = await (0, import_js2.captureScreenshot)();
1964
+ const blob = await (0, import_js3.captureScreenshot)();
1661
1965
  await startUpload(blob);
1662
1966
  } catch (e) {
1663
- if (e instanceof import_js2.ScreenshotCancelled) return;
1967
+ if (e instanceof import_js3.ScreenshotCancelled) return;
1664
1968
  }
1665
1969
  };
1666
1970
  const handlePickFile = () => {
@@ -1742,7 +2046,7 @@ function ChatInput() {
1742
2046
  const containerStyle = {
1743
2047
  position: "relative",
1744
2048
  padding: "12px 16px",
1745
- borderTop: "1px solid #eee",
2049
+ borderTop: `1px solid ${theme.divider}`,
1746
2050
  display: "flex",
1747
2051
  flexDirection: "column",
1748
2052
  gap: 8
@@ -1757,14 +2061,14 @@ function ChatInput() {
1757
2061
  const TEXTAREA_MAX_HEIGHT = 140;
1758
2062
  const inputStyle = {
1759
2063
  flex: 1,
1760
- border: "1px solid #e0e0e0",
2064
+ border: `1px solid ${theme.divider}`,
1761
2065
  borderRadius: 18,
1762
2066
  padding: "10px 16px",
1763
2067
  fontSize: 14,
1764
2068
  lineHeight: 1.4,
1765
2069
  outline: "none",
1766
- background: "#fafafa",
1767
- color: config.textColor,
2070
+ background: isDark ? "rgba(255,255,255,0.06)" : "#fafafa",
2071
+ color: theme.text,
1768
2072
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
1769
2073
  resize: "none",
1770
2074
  overflowY: "auto",
@@ -1775,7 +2079,7 @@ function ChatInput() {
1775
2079
  width: 36,
1776
2080
  height: 36,
1777
2081
  borderRadius: "50%",
1778
- background: config.primaryColor,
2082
+ background: theme.primary,
1779
2083
  border: "none",
1780
2084
  color: "white",
1781
2085
  cursor: isLoading ? "not-allowed" : "pointer",
@@ -1805,10 +2109,10 @@ function ChatInput() {
1805
2109
  position: "absolute",
1806
2110
  bottom: "calc(100% + 4px)",
1807
2111
  left: 0,
1808
- background: "white",
1809
- border: "1px solid #e0e0e0",
2112
+ background: theme.background,
2113
+ border: `1px solid ${theme.divider}`,
1810
2114
  borderRadius: 8,
1811
- boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
2115
+ boxShadow: isDark ? "0 4px 16px rgba(0,0,0,0.5)" : "0 4px 16px rgba(0,0,0,0.12)",
1812
2116
  padding: 4,
1813
2117
  minWidth: 180,
1814
2118
  zIndex: 10,
@@ -1825,7 +2129,7 @@ function ChatInput() {
1825
2129
  borderRadius: 4,
1826
2130
  cursor: "pointer",
1827
2131
  fontSize: 14,
1828
- color: "#333",
2132
+ color: theme.text,
1829
2133
  textAlign: "left",
1830
2134
  whiteSpace: "nowrap",
1831
2135
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
@@ -1833,13 +2137,13 @@ function ChatInput() {
1833
2137
  const dropOverlayStyle = {
1834
2138
  position: "absolute",
1835
2139
  inset: 0,
1836
- background: "rgba(255,255,255,0.92)",
1837
- border: `2px dashed ${config.primaryColor}`,
2140
+ background: isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.92)",
2141
+ border: `2px dashed ${theme.primary}`,
1838
2142
  borderRadius: 4,
1839
2143
  display: "flex",
1840
2144
  alignItems: "center",
1841
2145
  justifyContent: "center",
1842
- color: config.primaryColor,
2146
+ color: theme.primary,
1843
2147
  fontSize: 14,
1844
2148
  fontWeight: 500,
1845
2149
  pointerEvents: "none",
@@ -1854,7 +2158,7 @@ function ChatInput() {
1854
2158
  padding: "4px 10px",
1855
2159
  fontSize: 12
1856
2160
  };
1857
- const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
2161
+ const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading || readOnly;
1858
2162
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1859
2163
  "div",
1860
2164
  {
@@ -1883,7 +2187,7 @@ function ChatInput() {
1883
2187
  }
1884
2188
  ),
1885
2189
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
1886
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { position: "relative" }, children: [
2190
+ config.allowAttachments !== false && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { position: "relative" }, children: [
1887
2191
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1888
2192
  "button",
1889
2193
  {
@@ -1908,7 +2212,7 @@ function ChatInput() {
1908
2212
  onClick: handlePickFile,
1909
2213
  style: menuItemStyle,
1910
2214
  onMouseEnter: (e) => {
1911
- e.currentTarget.style.background = "#f5f5f5";
2215
+ e.currentTarget.style.background = theme.bubbleBackground;
1912
2216
  },
1913
2217
  onMouseLeave: (e) => {
1914
2218
  e.currentTarget.style.background = "transparent";
@@ -1927,7 +2231,7 @@ function ChatInput() {
1927
2231
  onClick: handleCapture,
1928
2232
  style: menuItemStyle,
1929
2233
  onMouseEnter: (e) => {
1930
- e.currentTarget.style.background = "#f5f5f5";
2234
+ e.currentTarget.style.background = theme.bubbleBackground;
1931
2235
  },
1932
2236
  onMouseLeave: (e) => {
1933
2237
  e.currentTarget.style.background = "transparent";
@@ -1964,14 +2268,14 @@ function ChatInput() {
1964
2268
  onPaste: handlePaste,
1965
2269
  placeholder: config.placeholderText,
1966
2270
  style: inputStyle,
1967
- disabled: isLoading
2271
+ disabled: isLoading || readOnly
1968
2272
  }
1969
2273
  ),
1970
2274
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1971
2275
  "button",
1972
2276
  {
1973
2277
  onClick: handleSend,
1974
- disabled: isLoading || !value.trim(),
2278
+ disabled: isLoading || readOnly || !value.trim(),
1975
2279
  style: sendButtonStyle,
1976
2280
  "aria-label": t("send_message"),
1977
2281
  onMouseEnter: (e) => {
@@ -2079,7 +2383,7 @@ function Thumbnail({
2079
2383
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
2080
2384
  };
2081
2385
  const displayName = filename ?? (isImage ? "" : "Document");
2082
- const sizeLabel = formatBytes(blob.size);
2386
+ const sizeLabel = formatBytes2(blob.size);
2083
2387
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
2084
2388
  isImage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2085
2389
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocIcon, {}),
@@ -2183,7 +2487,7 @@ function DocIcon() {
2183
2487
  }
2184
2488
  );
2185
2489
  }
2186
- function formatBytes(bytes) {
2490
+ function formatBytes2(bytes) {
2187
2491
  if (!Number.isFinite(bytes) || bytes <= 0) return "";
2188
2492
  if (bytes < 1024) return `${bytes} B`;
2189
2493
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
@@ -2231,7 +2535,7 @@ function Spinner2() {
2231
2535
  }
2232
2536
 
2233
2537
  // src/components/incident-banner.tsx
2234
- var import_react9 = require("react");
2538
+ var import_react10 = require("react");
2235
2539
  var import_jsx_runtime9 = require("react/jsx-runtime");
2236
2540
  var PALETTE = {
2237
2541
  info: {
@@ -2296,8 +2600,8 @@ function SeverityIcon({
2296
2600
  }
2297
2601
  function IncidentBanner() {
2298
2602
  const { incidentBanner, incidentBannerDismissed, dismissIncidentBanner, t } = useChat();
2299
- const [linkHover, setLinkHover] = (0, import_react9.useState)(false);
2300
- const [closeHover, setCloseHover] = (0, import_react9.useState)(false);
2603
+ const [linkHover, setLinkHover] = (0, import_react10.useState)(false);
2604
+ const [closeHover, setCloseHover] = (0, import_react10.useState)(false);
2301
2605
  if (!incidentBanner || incidentBannerDismissed) return null;
2302
2606
  const palette = PALETTE[incidentBanner.severity];
2303
2607
  const wrap = {
@@ -2513,9 +2817,9 @@ function validateField(field, value) {
2513
2817
  }
2514
2818
  function PreChatFormView() {
2515
2819
  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);
2820
+ const [values, setValues] = (0, import_react11.useState)({});
2821
+ const [errors, setErrors] = (0, import_react11.useState)({});
2822
+ const [submitting, setSubmitting] = (0, import_react11.useState)(false);
2519
2823
  if (!preChatForm) return null;
2520
2824
  function setValue(key, value) {
2521
2825
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -2744,12 +3048,13 @@ function PreChatFormView() {
2744
3048
  }
2745
3049
  );
2746
3050
  }
2747
- function ChatWindow() {
3051
+ function ChatWindow({ embedded } = {}) {
2748
3052
  const { isOpen, config, configError, t, isRtl, preChatFormVisible } = useChat();
2749
3053
  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)(() => {
3054
+ const theme = useEffectiveTheme();
3055
+ const [visible, setVisible] = (0, import_react11.useState)(false);
3056
+ const [shouldRender, setShouldRender] = (0, import_react11.useState)(false);
3057
+ (0, import_react11.useEffect)(() => {
2753
3058
  if (isOpen) {
2754
3059
  setShouldRender(true);
2755
3060
  requestAnimationFrame(() => {
@@ -2767,34 +3072,44 @@ function ChatWindow() {
2767
3072
  }, [isOpen, reduced]);
2768
3073
  if (!shouldRender) return null;
2769
3074
  const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
3075
+ const colors = theme;
3076
+ const preset = theme.size;
3077
+ const radius = theme.radius;
3078
+ const panelBottom = config.offset.bottom + preset.bubble + 14;
2770
3079
  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,
3080
+ position: embedded ? "absolute" : "fixed",
3081
+ bottom: panelBottom,
3082
+ [effectivePosition === "bottom-left" ? "left" : "right"]: config.offset.side,
3083
+ width: preset.width,
3084
+ maxWidth: embedded ? "calc(100% - 16px)" : "calc(100vw - 40px)",
3085
+ height: preset.height,
3086
+ maxHeight: embedded ? `calc(100% - ${panelBottom + 16}px)` : `calc(100vh - ${panelBottom + 30}px)`,
3087
+ borderRadius: radius,
2779
3088
  overflow: "hidden",
2780
3089
  display: "flex",
2781
3090
  flexDirection: "column",
2782
- boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
2783
- zIndex: 99999,
2784
- background: config.backgroundColor,
3091
+ // In dark mode the default 0.15 opacity black shadow is invisible
3092
+ // against a dark page; switch to a stronger shadow plus a subtle 1px
3093
+ // light outline so the panel still reads as a separated surface.
3094
+ 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)",
3095
+ zIndex: config.zIndex,
3096
+ background: colors.background,
3097
+ color: colors.text,
3098
+ fontSize: preset.fontSize,
2785
3099
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
2786
3100
  opacity: visible ? 1 : 0,
2787
3101
  transform: visible ? "translateY(0) scale(1)" : "translateY(16px) scale(0.97)",
2788
3102
  transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease"
2789
3103
  };
3104
+ const isDark = theme.scheme === "dark";
2790
3105
  const poweredStyle = {
2791
3106
  textAlign: "center",
2792
3107
  padding: 6,
2793
3108
  fontSize: 10,
2794
- color: "#aaa"
3109
+ color: isDark ? "rgba(255,255,255,0.45)" : "#aaa"
2795
3110
  };
2796
3111
  const linkStyle = {
2797
- color: "#888",
3112
+ color: isDark ? "rgba(255,255,255,0.6)" : "#888",
2798
3113
  textDecoration: "underline",
2799
3114
  textUnderlineOffset: 2
2800
3115
  };
@@ -2825,11 +3140,14 @@ function ChatWindow() {
2825
3140
 
2826
3141
  // src/components/chat-widget.tsx
2827
3142
  var import_jsx_runtime11 = require("react/jsx-runtime");
2828
- function ChatWidgetInner({ identity }) {
3143
+ function ChatWidgetInner({
3144
+ identity,
3145
+ embedded
3146
+ }) {
2829
3147
  const client = useCustomerHeroClient();
2830
3148
  const { configLoaded, configError } = useChat();
2831
- const prevIdentityRef = (0, import_react11.useRef)(void 0);
2832
- (0, import_react11.useEffect)(() => {
3149
+ const prevIdentityRef = (0, import_react12.useRef)(void 0);
3150
+ (0, import_react12.useEffect)(() => {
2833
3151
  const key = identity ? JSON.stringify(identity) : void 0;
2834
3152
  if (key !== prevIdentityRef.current) {
2835
3153
  prevIdentityRef.current = key;
@@ -2840,12 +3158,12 @@ function ChatWidgetInner({ identity }) {
2840
3158
  }, [identity, client]);
2841
3159
  if (!configLoaded || configError) return null;
2842
3160
  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, {})
3161
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatBubble, { embedded }),
3162
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWindow, { embedded })
2845
3163
  ] });
2846
3164
  }
2847
- function ChatWidget({ identity, ...config }) {
2848
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWidgetInner, { identity }) });
3165
+ function ChatWidget({ identity, embedded, ...config }) {
3166
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ChatWidgetInner, { identity, embedded }) });
2849
3167
  }
2850
3168
  // Annotate the CommonJS export names for ESM import in node:
2851
3169
  0 && (module.exports = {