@customerhero/js 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.js CHANGED
@@ -7,7 +7,29 @@ var DEFAULTS = {
7
7
  position: "bottom-right",
8
8
  placeholderText: "Type your message...",
9
9
  welcomeMessage: "Hi! How can I help you today?",
10
- title: "CustomerHero"
10
+ title: "CustomerHero",
11
+ // Appearance pack (B1–B6) defaults.
12
+ colorScheme: "light",
13
+ // Dark fallbacks used when the effective scheme is dark and no per-chatbot
14
+ // dark color is configured. Operators can — and should — override these.
15
+ primaryColorDark: "#A78BFA",
16
+ backgroundColorDark: "#0F172A",
17
+ textColorDark: "#E5E7EB",
18
+ size: "default",
19
+ cornerStyle: "rounded",
20
+ offsetBottom: 20,
21
+ offsetSide: 20,
22
+ zIndex: 99999
23
+ };
24
+ var SIZE_PRESETS = {
25
+ compact: { bubble: 48, width: 320, height: 480, fontSize: 13 },
26
+ default: { bubble: 56, width: 380, height: 520, fontSize: 14 },
27
+ large: { bubble: 64, width: 440, height: 600, fontSize: 15 }
28
+ };
29
+ var CORNER_RADIUS = {
30
+ soft: 8,
31
+ rounded: 16,
32
+ square: 0
11
33
  };
12
34
 
13
35
  // src/i18n/locales/en.ts
@@ -38,7 +60,10 @@ var en = {
38
60
  drop_files_here: "Drop files here",
39
61
  incident_dismiss: "Dismiss",
40
62
  incident_default_link_label: "Learn more",
41
- attachment_unsupported_type: "Unsupported file type"
63
+ attachment_unsupported_type: "Unsupported file type",
64
+ attachment_open: "Open attachment",
65
+ attachment_image_alt: "Image attachment",
66
+ attachment_location: "Shared location"
42
67
  };
43
68
 
44
69
  // src/i18n/locales/es.ts
@@ -69,7 +94,10 @@ var es = {
69
94
  drop_files_here: "Suelta los archivos aqu\xED",
70
95
  incident_dismiss: "Descartar",
71
96
  incident_default_link_label: "M\xE1s informaci\xF3n",
72
- attachment_unsupported_type: "Tipo de archivo no admitido"
97
+ attachment_unsupported_type: "Tipo de archivo no admitido",
98
+ attachment_open: "Abrir adjunto",
99
+ attachment_image_alt: "Imagen adjunta",
100
+ attachment_location: "Ubicaci\xF3n compartida"
73
101
  };
74
102
 
75
103
  // src/i18n/locales/pt-BR.ts
@@ -100,7 +128,10 @@ var ptBR = {
100
128
  drop_files_here: "Solte os arquivos aqui",
101
129
  incident_dismiss: "Dispensar",
102
130
  incident_default_link_label: "Saiba mais",
103
- attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado"
131
+ attachment_unsupported_type: "Tipo de arquivo n\xE3o suportado",
132
+ attachment_open: "Abrir anexo",
133
+ attachment_image_alt: "Imagem anexada",
134
+ attachment_location: "Localiza\xE7\xE3o compartilhada"
104
135
  };
105
136
 
106
137
  // src/i18n/locales/pt-PT.ts
@@ -131,7 +162,10 @@ var ptPT = {
131
162
  drop_files_here: "Largue os ficheiros aqui",
132
163
  incident_dismiss: "Dispensar",
133
164
  incident_default_link_label: "Saiba mais",
134
- attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado"
165
+ attachment_unsupported_type: "Tipo de ficheiro n\xE3o suportado",
166
+ attachment_open: "Abrir anexo",
167
+ attachment_image_alt: "Imagem anexa",
168
+ attachment_location: "Localiza\xE7\xE3o partilhada"
135
169
  };
136
170
 
137
171
  // src/i18n/locales/fr.ts
@@ -162,7 +196,10 @@ var fr = {
162
196
  drop_files_here: "D\xE9posez les fichiers ici",
163
197
  incident_dismiss: "Ignorer",
164
198
  incident_default_link_label: "En savoir plus",
165
- attachment_unsupported_type: "Type de fichier non pris en charge"
199
+ attachment_unsupported_type: "Type de fichier non pris en charge",
200
+ attachment_open: "Ouvrir la pi\xE8ce jointe",
201
+ attachment_image_alt: "Pi\xE8ce jointe (image)",
202
+ attachment_location: "Position partag\xE9e"
166
203
  };
167
204
 
168
205
  // src/i18n/locales/de.ts
@@ -193,7 +230,10 @@ var de = {
193
230
  drop_files_here: "Dateien hier ablegen",
194
231
  incident_dismiss: "Schlie\xDFen",
195
232
  incident_default_link_label: "Mehr erfahren",
196
- attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt"
233
+ attachment_unsupported_type: "Dateityp nicht unterst\xFCtzt",
234
+ attachment_open: "Anhang \xF6ffnen",
235
+ attachment_image_alt: "Bildanhang",
236
+ attachment_location: "Geteilter Standort"
197
237
  };
198
238
 
199
239
  // src/i18n/locales/it.ts
@@ -224,7 +264,10 @@ var it = {
224
264
  drop_files_here: "Trascina qui i file",
225
265
  incident_dismiss: "Chiudi",
226
266
  incident_default_link_label: "Scopri di pi\xF9",
227
- attachment_unsupported_type: "Tipo di file non supportato"
267
+ attachment_unsupported_type: "Tipo di file non supportato",
268
+ attachment_open: "Apri allegato",
269
+ attachment_image_alt: "Allegato immagine",
270
+ attachment_location: "Posizione condivisa"
228
271
  };
229
272
 
230
273
  // src/i18n/locales/nl.ts
@@ -255,7 +298,10 @@ var nl = {
255
298
  drop_files_here: "Sleep bestanden hier",
256
299
  incident_dismiss: "Sluiten",
257
300
  incident_default_link_label: "Meer informatie",
258
- attachment_unsupported_type: "Bestandstype niet ondersteund"
301
+ attachment_unsupported_type: "Bestandstype niet ondersteund",
302
+ attachment_open: "Bijlage openen",
303
+ attachment_image_alt: "Afbeeldingsbijlage",
304
+ attachment_location: "Gedeelde locatie"
259
305
  };
260
306
 
261
307
  // src/i18n/locales/pl.ts
@@ -286,7 +332,10 @@ var pl = {
286
332
  drop_files_here: "Upu\u015B\u0107 pliki tutaj",
287
333
  incident_dismiss: "Zamknij",
288
334
  incident_default_link_label: "Dowiedz si\u0119 wi\u0119cej",
289
- attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku"
335
+ attachment_unsupported_type: "Nieobs\u0142ugiwany typ pliku",
336
+ attachment_open: "Otw\xF3rz za\u0142\u0105cznik",
337
+ attachment_image_alt: "Za\u0142\u0105cznik obrazu",
338
+ attachment_location: "Udost\u0119pniona lokalizacja"
290
339
  };
291
340
 
292
341
  // src/i18n/locales/tr.ts
@@ -317,7 +366,10 @@ var tr = {
317
366
  drop_files_here: "Dosyalar\u0131 buraya b\u0131rak",
318
367
  incident_dismiss: "Kapat",
319
368
  incident_default_link_label: "Daha fazla bilgi",
320
- attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC"
369
+ attachment_unsupported_type: "Desteklenmeyen dosya t\xFCr\xFC",
370
+ attachment_open: "Eki a\xE7",
371
+ attachment_image_alt: "Resim eki",
372
+ attachment_location: "Payla\u015F\u0131lan konum"
321
373
  };
322
374
 
323
375
  // src/i18n/locales/ar.ts
@@ -348,7 +400,10 @@ var ar = {
348
400
  drop_files_here: "\u0623\u0641\u0644\u062A \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0647\u0646\u0627",
349
401
  incident_dismiss: "\u0625\u063A\u0644\u0627\u0642",
350
402
  incident_default_link_label: "\u0627\u0639\u0631\u0641 \u0627\u0644\u0645\u0632\u064A\u062F",
351
- attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645"
403
+ attachment_unsupported_type: "\u0646\u0648\u0639 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u062F\u0639\u0648\u0645",
404
+ attachment_open: "\u0641\u062A\u062D \u0627\u0644\u0645\u0631\u0641\u0642",
405
+ attachment_image_alt: "\u0635\u0648\u0631\u0629 \u0645\u0631\u0641\u0642\u0629",
406
+ attachment_location: "\u0645\u0648\u0642\u0639 \u0645\u0634\u062A\u0631\u0643"
352
407
  };
353
408
 
354
409
  // src/i18n/locales/ja.ts
@@ -379,7 +434,10 @@ var ja = {
379
434
  drop_files_here: "\u30D5\u30A1\u30A4\u30EB\u3092\u3053\u3053\u306B\u30C9\u30ED\u30C3\u30D7",
380
435
  incident_dismiss: "\u9589\u3058\u308B",
381
436
  incident_default_link_label: "\u8A73\u7D30\u3092\u898B\u308B",
382
- attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F"
437
+ attachment_unsupported_type: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F",
438
+ attachment_open: "\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F",
439
+ attachment_image_alt: "\u753B\u50CF\u306E\u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB",
440
+ attachment_location: "\u5171\u6709\u3055\u308C\u305F\u4F4D\u7F6E\u60C5\u5831"
383
441
  };
384
442
 
385
443
  // src/i18n/locales/ko.ts
@@ -410,7 +468,10 @@ var ko = {
410
468
  drop_files_here: "\uD30C\uC77C\uC744 \uC5EC\uAE30\uC5D0 \uB193\uAE30",
411
469
  incident_dismiss: "\uB2EB\uAE30",
412
470
  incident_default_link_label: "\uC790\uC138\uD788 \uC54C\uC544\uBCF4\uAE30",
413
- attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD"
471
+ attachment_unsupported_type: "\uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD",
472
+ attachment_open: "\uCCA8\uBD80 \uD30C\uC77C \uC5F4\uAE30",
473
+ attachment_image_alt: "\uC774\uBBF8\uC9C0 \uCCA8\uBD80 \uD30C\uC77C",
474
+ attachment_location: "\uACF5\uC720\uB41C \uC704\uCE58"
414
475
  };
415
476
 
416
477
  // src/i18n/locales/zh-CN.ts
@@ -441,7 +502,10 @@ var zhCN = {
441
502
  drop_files_here: "\u5C06\u6587\u4EF6\u62D6\u653E\u5230\u6B64\u5904",
442
503
  incident_dismiss: "\u5173\u95ED",
443
504
  incident_default_link_label: "\u4E86\u89E3\u66F4\u591A",
444
- attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B"
505
+ attachment_unsupported_type: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B",
506
+ attachment_open: "\u6253\u5F00\u9644\u4EF6",
507
+ attachment_image_alt: "\u56FE\u7247\u9644\u4EF6",
508
+ attachment_location: "\u5171\u4EAB\u7684\u4F4D\u7F6E"
445
509
  };
446
510
 
447
511
  // src/i18n/locales/zh-TW.ts
@@ -472,7 +536,10 @@ var zhTW = {
472
536
  drop_files_here: "\u5C07\u6A94\u6848\u62D6\u653E\u5230\u6B64\u8655",
473
537
  incident_dismiss: "\u95DC\u9589",
474
538
  incident_default_link_label: "\u77AD\u89E3\u66F4\u591A",
475
- attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B"
539
+ attachment_unsupported_type: "\u4E0D\u652F\u63F4\u7684\u6A94\u6848\u985E\u578B",
540
+ attachment_open: "\u958B\u555F\u9644\u4EF6",
541
+ attachment_image_alt: "\u5716\u7247\u9644\u4EF6",
542
+ attachment_location: "\u5171\u4EAB\u7684\u4F4D\u7F6E"
476
543
  };
477
544
 
478
545
  // src/i18n/index.ts
@@ -975,7 +1042,14 @@ function startTriggersRuntime(options) {
975
1042
  }
976
1043
 
977
1044
  // src/client.ts
1045
+ function clampInt(value, min, max, fallback) {
1046
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
1047
+ return Math.max(min, Math.min(max, Math.trunc(value)));
1048
+ }
978
1049
  function resolveConfig(userConfig, fetched) {
1050
+ const launcherUser = userConfig.launcher ?? {};
1051
+ const launcherFetched = fetched?.launcher ?? {};
1052
+ const offsetUser = userConfig.offset ?? {};
979
1053
  return {
980
1054
  chatbotId: userConfig.chatbotId,
981
1055
  apiBase: userConfig.apiBase ?? DEFAULTS.apiBase,
@@ -988,7 +1062,32 @@ function resolveConfig(userConfig, fetched) {
988
1062
  title: userConfig.title ?? fetched?.title ?? DEFAULTS.title,
989
1063
  avatarUrl: userConfig.avatarUrl ?? fetched?.avatarUrl,
990
1064
  suggestedMessages: userConfig.suggestedMessages ?? fetched?.suggestedMessages ?? [],
991
- stringOverrides: fetched?.stringOverrides
1065
+ stringOverrides: fetched?.stringOverrides,
1066
+ // Appearance pack. Color palette + size + corner style + launcher all
1067
+ // come from the server widget_config (with host-side override). The
1068
+ // *runtime* knobs — colorScheme, offset, zIndex — are host-only because
1069
+ // they depend on the page the widget is embedded in, not the chatbot.
1070
+ primaryColorDark: userConfig.primaryColorDark ?? fetched?.primaryColorDark,
1071
+ backgroundColorDark: userConfig.backgroundColorDark ?? fetched?.backgroundColorDark,
1072
+ textColorDark: userConfig.textColorDark ?? fetched?.textColorDark,
1073
+ size: userConfig.size ?? fetched?.size ?? DEFAULTS.size,
1074
+ cornerStyle: userConfig.cornerStyle ?? fetched?.cornerStyle ?? DEFAULTS.cornerStyle,
1075
+ launcher: {
1076
+ iconUrl: launcherUser.iconUrl ?? launcherFetched.iconUrl,
1077
+ label: launcherUser.label ?? launcherFetched.label,
1078
+ showOnlineDot: launcherUser.showOnlineDot ?? launcherFetched.showOnlineDot ?? false
1079
+ },
1080
+ colorScheme: userConfig.colorScheme ?? DEFAULTS.colorScheme,
1081
+ offset: {
1082
+ bottom: clampInt(offsetUser.bottom, 0, 1e3, DEFAULTS.offsetBottom),
1083
+ side: clampInt(offsetUser.side, 0, 1e3, DEFAULTS.offsetSide)
1084
+ },
1085
+ zIndex: clampInt(userConfig.zIndex, 0, 2e9, DEFAULTS.zIndex),
1086
+ // Defaults to true — the widget shows the attach button unless the
1087
+ // chatbot explicitly opts out via widget_config or the host passes
1088
+ // allowAttachments=false. Server still enforces the same flag at the
1089
+ // upload endpoint either way.
1090
+ allowAttachments: userConfig.allowAttachments ?? fetched?.allowAttachments ?? true
992
1091
  };
993
1092
  }
994
1093
  function sanitizeIncidentBanner(input) {
@@ -1078,9 +1177,40 @@ var CustomerHeroChat = class {
1078
1177
  pendingTriggerId: null,
1079
1178
  pendingPrefill: null,
1080
1179
  incidentBanner: null,
1081
- incidentBannerDismissed: false
1180
+ incidentBannerDismissed: false,
1181
+ readOnly: false
1082
1182
  };
1083
1183
  }
1184
+ /**
1185
+ * Mark the config as loaded and put the client into read-only preview
1186
+ * mode without hitting the API. Used by `@customerhero/react/preview` to
1187
+ * render the widget against a host-supplied config (the dashboard preview
1188
+ * pane). Public API consumers should not call this.
1189
+ *
1190
+ * Pass a config to re-resolve and update the rendered colors/size/launcher
1191
+ * in place. Callers should reuse the same client instance across config
1192
+ * changes so the open animation only fires once.
1193
+ *
1194
+ * @internal
1195
+ */
1196
+ __seedForPreview(config, extras) {
1197
+ const resolved = config ? resolveConfig(config) : this.state.config;
1198
+ if (config) this.userConfig = config;
1199
+ const seededMessages = resolved.welcomeMessage ? [{ role: "bot", content: resolved.welcomeMessage }] : [];
1200
+ const sanitizedBanner = extras && "banner" in extras ? sanitizeIncidentBanner(extras.banner ?? null) : this.state.incidentBanner;
1201
+ this.setState({
1202
+ config: resolved,
1203
+ configLoaded: true,
1204
+ configError: null,
1205
+ readOnly: true,
1206
+ isOpen: true,
1207
+ messages: seededMessages,
1208
+ incidentBanner: sanitizedBanner,
1209
+ // Reset the dismissed flag so toggling the banner on in the dashboard
1210
+ // re-renders it after a previous preview-side dismiss.
1211
+ incidentBannerDismissed: false
1212
+ });
1213
+ }
1084
1214
  // ── Proactive engagement state ─────────────────────────────────────
1085
1215
  triggersRuntime = null;
1086
1216
  preChatFormSubmitted = false;
@@ -1189,12 +1319,15 @@ var CustomerHeroChat = class {
1189
1319
  const { chatbotId, apiBase } = this.state.config;
1190
1320
  const { conversationId } = this.state;
1191
1321
  if (!conversationId) return;
1322
+ const readToken = this.storage?.getItem(`ch_conv_token_${chatbotId}`);
1323
+ const messagesUrl = `${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`;
1192
1324
  try {
1193
1325
  const response = await fetch(
1194
- `${apiBase}/api/chat/${chatbotId}/messages/${conversationId}`
1326
+ readToken ? `${messagesUrl}?t=${encodeURIComponent(readToken)}` : messagesUrl
1195
1327
  );
1196
1328
  if (!response.ok) {
1197
1329
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1330
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1198
1331
  this.setState({ conversationId: null });
1199
1332
  return;
1200
1333
  }
@@ -1206,7 +1339,8 @@ var CustomerHeroChat = class {
1206
1339
  content: m.content,
1207
1340
  ...m.sources ? { sources: m.sources } : {},
1208
1341
  ...m.blocks ? { blocks: m.blocks } : {},
1209
- ...m.suggestions ? { suggestions: m.suggestions } : {}
1342
+ ...m.suggestions ? { suggestions: m.suggestions } : {},
1343
+ ...m.attachments?.length ? { attachments: m.attachments } : {}
1210
1344
  }));
1211
1345
  const lastBotIndex = findLastIndex(
1212
1346
  messages,
@@ -1226,6 +1360,7 @@ var CustomerHeroChat = class {
1226
1360
  }
1227
1361
  }
1228
1362
  async sendMessage(message, options) {
1363
+ if (this.state.readOnly) return;
1229
1364
  const trimmed = message.trim();
1230
1365
  const attachmentTokens = options?.attachmentTokens ?? [];
1231
1366
  if (!trimmed || this.state.isLoading) return;
@@ -1309,6 +1444,16 @@ var CustomerHeroChat = class {
1309
1444
  }
1310
1445
  break;
1311
1446
  }
1447
+ case "read-token": {
1448
+ const tok = safeParse(evt.data);
1449
+ if (tok?.readToken) {
1450
+ this.storage?.setItem(
1451
+ `ch_conv_token_${chatbotId}`,
1452
+ tok.readToken
1453
+ );
1454
+ }
1455
+ break;
1456
+ }
1312
1457
  case "token": {
1313
1458
  const tok = safeParse(evt.data);
1314
1459
  const text = tok?.text ?? "";
@@ -1421,6 +1566,9 @@ var CustomerHeroChat = class {
1421
1566
  await this.loadHistory();
1422
1567
  return;
1423
1568
  }
1569
+ const decisionBlock = this.state.messages[targetIndex].blocks?.find(
1570
+ (b) => b.type === "action_confirmation" && b.pendingToolCallId === pendingId
1571
+ );
1424
1572
  const messages = this.state.messages.slice();
1425
1573
  const original = messages[targetIndex];
1426
1574
  messages[targetIndex] = {
@@ -1429,7 +1577,8 @@ var CustomerHeroChat = class {
1429
1577
  };
1430
1578
  this.setState({ messages, error: null });
1431
1579
  const { chatbotId, apiBase } = this.state.config;
1432
- const url = `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
1580
+ const href = decision === "approve" ? decisionBlock?.approveHref : decisionBlock?.cancelHref;
1581
+ const url = href ? `${apiBase}${href}` : `${apiBase}/api/chat/${chatbotId}/tool-calls/${pendingId}/decision`;
1433
1582
  try {
1434
1583
  const response = await fetch(url, {
1435
1584
  method: "POST",
@@ -1461,6 +1610,16 @@ var CustomerHeroChat = class {
1461
1610
  }
1462
1611
  break;
1463
1612
  }
1613
+ case "read-token": {
1614
+ const tok = safeParse(evt.data);
1615
+ if (tok?.readToken) {
1616
+ this.storage?.setItem(
1617
+ `ch_conv_token_${chatbotId}`,
1618
+ tok.readToken
1619
+ );
1620
+ }
1621
+ break;
1622
+ }
1464
1623
  case "token": {
1465
1624
  const tok = safeParse(evt.data);
1466
1625
  const text = tok?.text ?? "";
@@ -1567,6 +1726,7 @@ var CustomerHeroChat = class {
1567
1726
  reset() {
1568
1727
  const { chatbotId, welcomeMessage } = this.state.config;
1569
1728
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1729
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1570
1730
  this.setState({
1571
1731
  messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
1572
1732
  conversationId: null,
@@ -1734,6 +1894,7 @@ var CustomerHeroChat = class {
1734
1894
  };
1735
1895
  const { chatbotId, welcomeMessage } = this.state.config;
1736
1896
  this.storage?.removeItem(`ch_conv_${chatbotId}`);
1897
+ this.storage?.removeItem(`ch_conv_token_${chatbotId}`);
1737
1898
  this.setState({
1738
1899
  messages: welcomeMessage ? [{ role: "bot", content: welcomeMessage }] : [],
1739
1900
  conversationId: null,
@@ -1778,6 +1939,33 @@ function pickExtension(mime) {
1778
1939
  return "jpg";
1779
1940
  }
1780
1941
 
1942
+ // src/theme.ts
1943
+ function resolveScheme(colorScheme, prefersDark) {
1944
+ if (colorScheme === "dark") return "dark";
1945
+ if (colorScheme === "light") return "light";
1946
+ return prefersDark ? "dark" : "light";
1947
+ }
1948
+ function effectiveColors(config, scheme) {
1949
+ if (scheme === "dark") {
1950
+ return {
1951
+ primary: config.primaryColorDark ?? DEFAULTS.primaryColorDark,
1952
+ background: config.backgroundColorDark ?? DEFAULTS.backgroundColorDark,
1953
+ text: config.textColorDark ?? DEFAULTS.textColorDark
1954
+ };
1955
+ }
1956
+ return {
1957
+ primary: config.primaryColor,
1958
+ background: config.backgroundColor,
1959
+ text: config.textColor
1960
+ };
1961
+ }
1962
+ function sizePreset(size) {
1963
+ return SIZE_PRESETS[size];
1964
+ }
1965
+ function panelRadius(cornerStyle) {
1966
+ return CORNER_RADIUS[cornerStyle];
1967
+ }
1968
+
1781
1969
  // src/screenshot.ts
1782
1970
  var ScreenshotCancelled = class extends Error {
1783
1971
  constructor(message = "Screenshot cancelled") {
@@ -1911,8 +2099,10 @@ async function canvasToBlob(canvas, quality) {
1911
2099
  });
1912
2100
  }
1913
2101
  export {
2102
+ CORNER_RADIUS,
1914
2103
  CustomerHeroChat,
1915
2104
  DEFAULTS,
2105
+ SIZE_PRESETS,
1916
2106
  SUPPORTED_LOCALES,
1917
2107
  ScreenshotCancelled,
1918
2108
  ScreenshotUnavailable,
@@ -1920,9 +2110,13 @@ export {
1920
2110
  captureScreenshot,
1921
2111
  createTranslator,
1922
2112
  detectLocale,
2113
+ effectiveColors,
1923
2114
  evaluate,
1924
2115
  isRtlLocale,
2116
+ panelRadius,
1925
2117
  pickFire,
1926
2118
  resolveLocale,
2119
+ resolveScheme,
2120
+ sizePreset,
1927
2121
  startTriggersRuntime
1928
2122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customerhero/js",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "Framework-agnostic JavaScript client for the CustomerHero chat widget.",
6
6
  "keywords": [