@channlize/react 0.1.1 → 0.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.js CHANGED
@@ -24,6 +24,8 @@ __export(index_exports, {
24
24
  ChannlizeProvider: () => ChannlizeProvider,
25
25
  ContactForm: () => ContactForm,
26
26
  SupportChat: () => SupportChat,
27
+ defaultContactFormLabels: () => defaultContactFormLabels,
28
+ defaultSupportChatLabels: () => defaultSupportChatLabels,
27
29
  useChannlize: () => useChannlize,
28
30
  useChannlizeAnalytics: () => useChannlizeAnalytics,
29
31
  useChatSession: () => useChatSession,
@@ -225,6 +227,7 @@ function useChatSession(options = {}) {
225
227
  console.log("\u{1F4E6} Resuming session from storage:", existingSessionId);
226
228
  const existingSession = await client.getChatSession(existingSessionId);
227
229
  const existingMessages = await client.getChatMessages(existingSessionId);
230
+ sessionRef.current = existingSession;
228
231
  setSession(existingSession);
229
232
  const normalizedMessages2 = existingMessages.map(
230
233
  (msg) => ({
@@ -244,6 +247,7 @@ function useChatSession(options = {}) {
244
247
  }
245
248
  console.log("\u{1F195} Creating new chat session");
246
249
  const response = await client.createChatSession(options.metadata);
250
+ sessionRef.current = response.session;
247
251
  setSession(response.session);
248
252
  saveSessionToStorage(response.session.id);
249
253
  const normalizedMessages = (response.session.messages || []).map(
@@ -314,6 +318,7 @@ function useChatSession(options = {}) {
314
318
  eventSourceRef.current = null;
315
319
  }
316
320
  clearSessionFromStorage();
321
+ sessionRef.current = null;
317
322
  setSession(null);
318
323
  setMessages([]);
319
324
  setIsConnected(false);
@@ -324,6 +329,15 @@ function useChatSession(options = {}) {
324
329
  options.onError?.(error2);
325
330
  }
326
331
  }, [client, options.onError, clearSessionFromStorage]);
332
+ const reconnect = (0, import_react3.useCallback)(() => {
333
+ const current = sessionRef.current;
334
+ if (!current) return;
335
+ if (eventSourceRef.current && eventSourceRef.current.readyState !== EventSource.CLOSED) {
336
+ return;
337
+ }
338
+ setError(null);
339
+ connectToSSE(`/v1/chat/sessions/${current.id}/events`);
340
+ }, [connectToSSE]);
327
341
  (0, import_react3.useEffect)(() => {
328
342
  if (options.autoConnect && !session && !isConnecting && !isInitializedRef.current) {
329
343
  isInitializedRef.current = true;
@@ -346,7 +360,8 @@ function useChatSession(options = {}) {
346
360
  error,
347
361
  sendMessage,
348
362
  startSession,
349
- endSession
363
+ endSession,
364
+ reconnect
350
365
  };
351
366
  }
352
367
 
@@ -363,189 +378,1287 @@ function useChannlizeAnalytics() {
363
378
 
364
379
  // src/components/Form.tsx
365
380
  var import_react5 = require("react");
381
+
382
+ // src/components/chat/icons.tsx
366
383
  var import_jsx_runtime2 = require("react/jsx-runtime");
384
+ function Icon({ size = 20, children, ...props }) {
385
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
386
+ "svg",
387
+ {
388
+ width: size,
389
+ height: size,
390
+ viewBox: "0 0 24 24",
391
+ fill: "none",
392
+ stroke: "currentColor",
393
+ strokeWidth: 2,
394
+ strokeLinecap: "round",
395
+ strokeLinejoin: "round",
396
+ "aria-hidden": "true",
397
+ focusable: "false",
398
+ ...props,
399
+ children
400
+ }
401
+ );
402
+ }
403
+ var ChatIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 11.5a8.4 8.4 0 0 1-9 8.4 9.3 9.3 0 0 1-3.7-.8L3 21l1.9-4.9A8 8 0 0 1 3 11.5 8.6 8.6 0 0 1 12 3a8.6 8.6 0 0 1 9 8.5Z" }) });
404
+ var CloseIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
407
+ ] });
408
+ var ChevronDownIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 9 6 6 6-6" }) });
409
+ var MoreIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
410
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1", fill: "currentColor" }),
411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1", fill: "currentColor" }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1", fill: "currentColor" })
413
+ ] });
414
+ var SendIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
415
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 19V5" }),
416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m5 12 7-7 7 7" })
417
+ ] });
418
+ var PaperclipIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21.4 11.1-9.2 9.2a6 6 0 0 1-8.5-8.5l9.2-9.2a4 4 0 0 1 5.7 5.7l-9.2 9.2a2 2 0 0 1-2.8-2.8l8.5-8.5" }) });
419
+ var FileIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z" }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M14 3v5h5" })
422
+ ] });
423
+ var AlertIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "9" }),
425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 8v4" }),
426
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 16h.01" })
427
+ ] });
428
+ var ExitIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...p, children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
430
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m16 17 5-5-5-5" }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 12H9" })
432
+ ] });
433
+ var CheckIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
434
+
435
+ // src/components/chat/utils.ts
436
+ var GROUP_GAP_MS = 5 * 60 * 1e3;
437
+ function isValidDate(date) {
438
+ return date instanceof Date && !isNaN(date.getTime());
439
+ }
440
+ function dayKey(date) {
441
+ return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
442
+ }
443
+ function groupMessages(messages) {
444
+ const sections = [];
445
+ let section;
446
+ let group;
447
+ let lastTime = 0;
448
+ for (const msg of messages) {
449
+ const time = isValidDate(msg.timestamp) ? msg.timestamp : /* @__PURE__ */ new Date();
450
+ const key = dayKey(time);
451
+ if (!section || section.key !== key) {
452
+ section = { key, date: time, groups: [] };
453
+ sections.push(section);
454
+ group = void 0;
455
+ }
456
+ const sameAuthor = group && group.author === msg.author && (msg.author === "user" || group.authorName === msg.authorName);
457
+ const closeInTime = time.getTime() - lastTime < GROUP_GAP_MS;
458
+ if (!group || !sameAuthor || !closeInTime) {
459
+ group = {
460
+ key: msg.key,
461
+ author: msg.author,
462
+ authorName: msg.authorName,
463
+ avatarUrl: msg.avatarUrl,
464
+ messages: []
465
+ };
466
+ section.groups.push(group);
467
+ }
468
+ group.messages.push(msg);
469
+ lastTime = time.getTime();
470
+ }
471
+ return sections;
472
+ }
473
+ function formatTime(date, locale) {
474
+ if (!isValidDate(date)) return "";
475
+ try {
476
+ return new Intl.DateTimeFormat(locale, {
477
+ hour: "numeric",
478
+ minute: "2-digit"
479
+ }).format(date);
480
+ } catch {
481
+ return "";
482
+ }
483
+ }
484
+ function formatFullDate(date, locale) {
485
+ if (!isValidDate(date)) return "";
486
+ try {
487
+ return new Intl.DateTimeFormat(locale, {
488
+ dateStyle: "medium",
489
+ timeStyle: "short"
490
+ }).format(date);
491
+ } catch {
492
+ return "";
493
+ }
494
+ }
495
+ function formatDayLabel(date, labels, locale, now = /* @__PURE__ */ new Date()) {
496
+ if (!isValidDate(date)) return "";
497
+ const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
498
+ const days = Math.round((startOfDay(now) - startOfDay(date)) / 864e5);
499
+ if (days === 0) return labels.today;
500
+ if (days === 1) return labels.yesterday;
501
+ try {
502
+ if (days > 1 && days < 7) {
503
+ return new Intl.DateTimeFormat(locale, { weekday: "long" }).format(date);
504
+ }
505
+ return new Intl.DateTimeFormat(locale, {
506
+ month: "short",
507
+ day: "numeric",
508
+ ...date.getFullYear() !== now.getFullYear() && { year: "numeric" }
509
+ }).format(date);
510
+ } catch {
511
+ return date.toDateString();
512
+ }
513
+ }
514
+ function formatFileSize(bytes) {
515
+ if (typeof bytes !== "number" || !isFinite(bytes) || bytes < 0) return "";
516
+ if (bytes < 1024) return `${bytes} B`;
517
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
518
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
519
+ }
520
+ function safeUrl(url, baseUrl) {
521
+ if (!url || typeof url !== "string") return void 0;
522
+ try {
523
+ const base = baseUrl || (typeof window !== "undefined" ? window.location.href : void 0);
524
+ const parsed = new URL(url, base);
525
+ if (["http:", "https:", "blob:"].includes(parsed.protocol)) {
526
+ return parsed.href;
527
+ }
528
+ } catch {
529
+ }
530
+ return void 0;
531
+ }
532
+ function toDisplayAttachments(attachments, baseUrl) {
533
+ if (!Array.isArray(attachments)) return [];
534
+ return attachments.map((a, i) => ({
535
+ key: a.id || `${a.name}-${i}`,
536
+ name: a.name || "Attachment",
537
+ size: a.size,
538
+ type: a.type,
539
+ url: safeUrl(a.url, baseUrl)
540
+ }));
541
+ }
542
+ function isImage(attachment) {
543
+ if (attachment.type) return attachment.type.startsWith("image/");
544
+ return /\.(png|jpe?g|gif|webp|avif)$/i.test(attachment.name);
545
+ }
546
+ var URL_PATTERN = /\b(?:https?:\/\/|www\.)[^\s<>"']+/gi;
547
+ var TRAILING_PUNCTUATION = /[.,;:!?'"\]}]+$/;
548
+ function linkify(text) {
549
+ const parts = [];
550
+ if (!text) return parts;
551
+ let last = 0;
552
+ for (const match of text.matchAll(URL_PATTERN)) {
553
+ const start = match.index ?? 0;
554
+ let raw = match[0];
555
+ for (; ; ) {
556
+ const trimmed = raw.replace(TRAILING_PUNCTUATION, "");
557
+ const opens = (trimmed.match(/\(/g) || []).length;
558
+ const closes = (trimmed.match(/\)/g) || []).length;
559
+ const next = trimmed.endsWith(")") && closes > opens ? trimmed.slice(0, -1) : trimmed;
560
+ if (next === raw) break;
561
+ raw = next;
562
+ }
563
+ const href = safeUrl(
564
+ raw.toLowerCase().startsWith("www.") ? `https://${raw}` : raw
565
+ );
566
+ if (!href || !/^https?:/.test(href)) continue;
567
+ if (start > last) {
568
+ parts.push({ type: "text", value: text.slice(last, start) });
569
+ }
570
+ parts.push({ type: "link", value: raw, href });
571
+ last = start + raw.length;
572
+ }
573
+ if (last < text.length) {
574
+ parts.push({ type: "text", value: text.slice(last) });
575
+ }
576
+ return parts;
577
+ }
578
+ function initials(name) {
579
+ if (!name) return "?";
580
+ const words = name.trim().split(/\s+/).filter(Boolean);
581
+ if (words.length === 0) return "?";
582
+ const first = words[0]?.[0] ?? "";
583
+ const second = words.length > 1 ? words[words.length - 1]?.[0] ?? "" : "";
584
+ return (first + second).toUpperCase();
585
+ }
586
+ function parseHexColor(color) {
587
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());
588
+ let hex = m?.[1];
589
+ if (!hex) return null;
590
+ if (hex.length === 3) {
591
+ hex = hex.split("").map((c) => c + c).join("");
592
+ }
593
+ const n = parseInt(hex, 16);
594
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
595
+ }
596
+ function relativeLuminance([r, g, b]) {
597
+ const channel = (v) => {
598
+ const s = v / 255;
599
+ return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
600
+ };
601
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
602
+ }
603
+ function readableTextColor(background) {
604
+ const rgb = parseHexColor(background);
605
+ if (!rgb) return "#ffffff";
606
+ const lum = relativeLuminance(rgb);
607
+ const onWhite = 1.05 / (lum + 0.05);
608
+ const onDark = (lum + 0.05) / (relativeLuminance([17, 24, 39]) + 0.05);
609
+ return onWhite >= 4.5 || onWhite >= onDark ? "#ffffff" : "#111827";
610
+ }
611
+
612
+ // src/components/form/styles.ts
613
+ var FORM_CSS = `
614
+ .channlize-contact{
615
+ --chz-primary:#2563eb;
616
+ --chz-on-primary:#ffffff;
617
+ --chz-bg:#ffffff;
618
+ --chz-surface:#f6f7f9;
619
+ --chz-field:#ffffff;
620
+ --chz-text:#111827;
621
+ --chz-muted:#5d6572;
622
+ --chz-border:#d5d9e0;
623
+ --chz-border-soft:#e3e6eb;
624
+ --chz-danger:#b42318;
625
+ --chz-danger-bg:#fdecec;
626
+ --chz-success:#067647;
627
+ --chz-success-bg:#e7f6ec;
628
+ --chz-radius:10px;
629
+ display:block;
630
+ box-sizing:border-box;
631
+ width:100%;
632
+ max-width:100%;
633
+ font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
634
+ font-size:14px;
635
+ line-height:1.5;
636
+ font-weight:400;
637
+ letter-spacing:normal;
638
+ text-transform:none;
639
+ text-align:left;
640
+ color:var(--chz-text);
641
+ -webkit-font-smoothing:antialiased;
642
+ -moz-osx-font-smoothing:grayscale;
643
+ }
644
+ .channlize-contact[data-theme="dark"]{
645
+ --chz-bg:#16181d;
646
+ --chz-surface:#1f2228;
647
+ --chz-field:#1b1e24;
648
+ --chz-text:#f2f4f7;
649
+ --chz-muted:#a7aebb;
650
+ --chz-border:#3a404a;
651
+ --chz-border-soft:#2f343d;
652
+ --chz-danger:#ff9b9b;
653
+ --chz-danger-bg:#3b1e22;
654
+ --chz-success:#6ce9a6;
655
+ --chz-success-bg:#12301f;
656
+ }
657
+ @media (prefers-color-scheme: dark){
658
+ .channlize-contact[data-theme="auto"]{
659
+ --chz-bg:#16181d;
660
+ --chz-surface:#1f2228;
661
+ --chz-field:#1b1e24;
662
+ --chz-text:#f2f4f7;
663
+ --chz-muted:#a7aebb;
664
+ --chz-border:#3a404a;
665
+ --chz-border-soft:#2f343d;
666
+ --chz-danger:#ff9b9b;
667
+ --chz-danger-bg:#3b1e22;
668
+ --chz-success:#6ce9a6;
669
+ --chz-success-bg:#12301f;
670
+ }
671
+ }
672
+ .channlize-contact[data-variant="card"]{background:var(--chz-bg);border:1px solid var(--chz-border-soft);border-radius:16px;padding:24px;}
673
+ @media (max-width: 480px){.channlize-contact[data-variant="card"]{padding:18px;}}
674
+ .channlize-contact *,.channlize-contact *::before,.channlize-contact *::after{box-sizing:border-box;letter-spacing:inherit;}
675
+ .channlize-contact :where(h2,p,form,fieldset,label){margin:0;padding:0;border:0;font:inherit;color:inherit;background:none;}
676
+ .channlize-contact :where(button,input,textarea,select){font:inherit;color:inherit;letter-spacing:inherit;text-transform:none;margin:0;}
677
+ .channlize-contact :where(button){appearance:none;-webkit-appearance:none;background:none;border:0;padding:0;cursor:pointer;line-height:inherit;text-decoration:none;}
678
+ .channlize-contact :where(button:disabled){cursor:default;}
679
+ .channlize-contact :where(svg){display:block;flex-shrink:0;}
680
+ .channlize-contact :where(:focus-visible){outline:2px solid var(--chz-primary);outline-offset:2px;}
681
+
682
+ .channlize-contact .channlize-form{display:flex;flex-direction:column;gap:18px;}
683
+ .channlize-contact .channlize-form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:18px;}
684
+ .channlize-contact .channlize-field{display:flex;flex-direction:column;gap:6px;min-width:0;}
685
+ .channlize-contact .channlize-label{display:flex;align-items:baseline;gap:6px;font-size:13.5px;font-weight:600;line-height:1.3;color:var(--chz-text);}
686
+ .channlize-contact .channlize-optional{font-weight:400;color:var(--chz-muted);font-size:12.5px;}
687
+ .channlize-contact .channlize-form-input,.channlize-contact .channlize-form-select,.channlize-contact .channlize-form-textarea{display:block;width:100%;min-width:0;padding:10px 12px;border-radius:var(--chz-radius);border:1px solid var(--chz-border);background:var(--chz-field);color:var(--chz-text);font-size:15px;line-height:1.4;box-shadow:none;outline:none;transition:border-color .15s ease,box-shadow .15s ease;}
688
+ .channlize-contact .channlize-form-input,.channlize-contact .channlize-form-select{height:44px;}
689
+ .channlize-contact .channlize-form-textarea{min-height:132px;resize:vertical;}
690
+ .channlize-contact .channlize-form-input::placeholder,.channlize-contact .channlize-form-textarea::placeholder{color:var(--chz-muted);opacity:1;}
691
+ .channlize-contact .channlize-form-input:hover,.channlize-contact .channlize-form-select:hover,.channlize-contact .channlize-form-textarea:hover{border-color:color-mix(in srgb, var(--chz-text) 30%, var(--chz-border));}
692
+ .channlize-contact .channlize-form-input:focus,.channlize-contact .channlize-form-select:focus,.channlize-contact .channlize-form-textarea:focus{border-color:var(--chz-primary);box-shadow:0 0 0 3px rgba(37,99,235,.18);box-shadow:0 0 0 3px color-mix(in srgb, var(--chz-primary) 22%, transparent);outline:none;}
693
+ .channlize-contact .channlize-form-select{appearance:none;-webkit-appearance:none;padding-right:38px;background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 18px) 19px,calc(100% - 13px) 19px;background-size:5px 5px,5px 5px;background-repeat:no-repeat;cursor:pointer;}
694
+ .channlize-contact .channlize-form-select[data-empty="true"]{color:var(--chz-muted);}
695
+ .channlize-contact .channlize-form-select option{color:var(--chz-text);background:var(--chz-field);}
696
+ .channlize-contact .channlize-field[data-invalid="true"] .channlize-form-input,.channlize-contact .channlize-field[data-invalid="true"] .channlize-form-select,.channlize-contact .channlize-field[data-invalid="true"] .channlize-form-textarea{border-color:var(--chz-danger);}
697
+ .channlize-contact .channlize-field[data-invalid="true"] .channlize-form-input:focus,.channlize-contact .channlize-field[data-invalid="true"] .channlize-form-select:focus,.channlize-contact .channlize-field[data-invalid="true"] .channlize-form-textarea:focus{box-shadow:0 0 0 3px color-mix(in srgb, var(--chz-danger) 22%, transparent);}
698
+ .channlize-contact .channlize-field-error{display:flex;align-items:flex-start;gap:6px;font-size:13px;line-height:1.35;color:var(--chz-danger);}
699
+ .channlize-contact .channlize-field-error svg{margin-top:1px;}
700
+ .channlize-contact .channlize-field-meta{display:flex;justify-content:space-between;gap:12px;}
701
+ .channlize-contact .channlize-counter{margin-left:auto;font-size:12px;color:var(--chz-muted);font-variant-numeric:tabular-nums;white-space:nowrap;}
702
+ .channlize-contact .channlize-counter[data-over="true"]{color:var(--chz-danger);}
703
+
704
+ .channlize-contact .channlize-error{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:var(--chz-radius);background:var(--chz-danger-bg);color:var(--chz-danger);font-size:14px;line-height:1.4;}
705
+ .channlize-contact .channlize-error svg{margin-top:1px;}
706
+
707
+ .channlize-contact .channlize-form-footer{display:flex;flex-wrap:wrap;align-items:center;gap:12px 16px;}
708
+ .channlize-contact .channlize-submit{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 20px;border-radius:var(--chz-radius);background:var(--chz-primary);color:var(--chz-on-primary);font-size:15px;font-weight:600;line-height:1.2;transition:filter .15s ease,transform .15s ease,opacity .15s ease;}
709
+ .channlize-contact .channlize-submit:hover:not(:disabled){filter:brightness(1.08);}
710
+ .channlize-contact .channlize-submit:active:not(:disabled){transform:translateY(1px);}
711
+ .channlize-contact .channlize-submit:disabled{opacity:.75;}
712
+ .channlize-contact .channlize-submit:focus-visible{outline:2px solid var(--chz-primary);outline-offset:3px;}
713
+ @media (max-width: 480px){.channlize-contact .channlize-submit{width:100%;}}
714
+ .channlize-contact .channlize-footnote{font-size:12.5px;color:var(--chz-muted);}
715
+ .channlize-contact .channlize-form-spinner{width:14px;height:14px;flex-shrink:0;border-radius:999px;border:2px solid currentColor;border-right-color:transparent;animation:channlize-spin .8s linear infinite;}
716
+
717
+ .channlize-contact .channlize-honeypot{position:absolute !important;left:-10000px !important;top:auto !important;width:1px !important;height:1px !important;overflow:hidden !important;opacity:0 !important;pointer-events:none !important;}
718
+ .channlize-contact .channlize-visually-hidden{position:absolute !important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
719
+
720
+ .channlize-contact .channlize-form-success{display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:24px 8px;animation:channlize-fade-in .25s ease;}
721
+ .channlize-contact .channlize-success-icon{display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;margin-bottom:6px;border-radius:999px;background:var(--chz-success-bg);color:var(--chz-success);}
722
+ .channlize-contact .channlize-success-title{font-size:20px;font-weight:650;line-height:1.3;color:var(--chz-text);outline:none;}
723
+ .channlize-contact .channlize-success-text{max-width:380px;font-size:15px;color:var(--chz-muted);}
724
+ .channlize-contact .channlize-secondary{margin-top:12px;display:inline-flex;align-items:center;justify-content:center;min-height:40px;padding:8px 16px;border-radius:var(--chz-radius);border:1px solid var(--chz-border);background:var(--chz-surface);color:var(--chz-text);font-size:14px;font-weight:600;transition:background-color .15s ease;}
725
+ .channlize-contact .channlize-secondary:hover{background:var(--chz-border-soft);}
726
+
727
+ @media (max-width: 640px), (pointer: coarse){
728
+ .channlize-contact .channlize-form-input,.channlize-contact .channlize-form-select,.channlize-contact .channlize-form-textarea{font-size:16px;}
729
+ }
730
+ @media (prefers-reduced-motion: reduce){
731
+ .channlize-contact,.channlize-contact *{transition-duration:.01ms !important;animation-duration:.01ms !important;animation-iteration-count:1 !important;}
732
+ }
733
+ @keyframes channlize-spin{to{transform:rotate(360deg);}}
734
+ @keyframes channlize-fade-in{from{opacity:0;}to{opacity:1;}}
735
+ `;
736
+
737
+ // src/components/Form.tsx
738
+ var import_jsx_runtime3 = (
739
+ // Static, trusted CSS; dangerouslySetInnerHTML keeps `>` intact in SSR.
740
+ require("react/jsx-runtime")
741
+ );
742
+ var defaultContactFormLabels = {
743
+ name: "Name",
744
+ email: "Email",
745
+ company: "Company",
746
+ subject: "Subject",
747
+ topic: "Topic",
748
+ message: "Message",
749
+ namePlaceholder: "Jane Doe",
750
+ emailPlaceholder: "you@company.com",
751
+ companyPlaceholder: "Acme Inc.",
752
+ subjectPlaceholder: "What is this about?",
753
+ topicPlaceholder: "Choose a topic",
754
+ messagePlaceholder: "How can we help?",
755
+ optional: "(optional)",
756
+ submit: "Send message",
757
+ submitting: "Sending\u2026",
758
+ successTitle: "Message sent",
759
+ successMessage: "Thanks for reaching out. We\u2019ll get back to you by email.",
760
+ sendAnother: "Send another message",
761
+ errorRequired: "Please fill in this field.",
762
+ errorSelect: "Please choose an option.",
763
+ errorEmail: "Enter a valid email address, like name@example.com.",
764
+ errorTooShort: "Please write at least {min} characters.",
765
+ errorTooLong: "Please keep this under {max} characters.",
766
+ errorSummary: "Please fix the highlighted fields.",
767
+ errorGeneric: "We couldn\u2019t send your message. Please try again.",
768
+ errorRateLimit: "Too many messages in a short time. Please wait a minute and try again.",
769
+ footnote: ""
770
+ };
771
+ var DEFAULT_PRIMARY = "#2563eb";
772
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
773
+ var EMPTY_VALUES = {
774
+ name: "",
775
+ email: "",
776
+ company: "",
777
+ subject: "",
778
+ topic: "",
779
+ message: ""
780
+ };
781
+ function normalizeTopics(topics) {
782
+ return (topics ?? []).map(
783
+ (t) => typeof t === "string" ? { value: t, label: t } : { value: t.value, label: t.label ?? t.value }
784
+ );
785
+ }
786
+ function matchTopic(topics, wanted) {
787
+ const w = wanted?.trim().toLowerCase();
788
+ if (!w) return "";
789
+ const hit = topics.find(
790
+ (t) => t.value.toLowerCase() === w || t.label.toLowerCase() === w
791
+ );
792
+ return hit ? hit.value : "";
793
+ }
367
794
  function Form({
368
795
  customFields = [],
369
796
  onSuccess,
370
797
  onError,
371
798
  className = "",
372
799
  styles = {},
373
- submitText = "Send Message",
374
- successMessage = "Thank you! Your message has been sent."
800
+ submitText,
801
+ successMessage,
802
+ theme = "light",
803
+ primaryColor,
804
+ primaryTextColor,
805
+ variant = "card",
806
+ unstyled = false,
807
+ fields = {},
808
+ topics,
809
+ defaultValues,
810
+ labels: labelOverrides,
811
+ minMessageLength = 10,
812
+ maxMessageLength = 5e3,
813
+ honeypot = true,
814
+ showSuccessState = true,
815
+ idPrefix = "channlize"
375
816
  }) {
376
- const [formData, setFormData] = (0, import_react5.useState)({
377
- name: "",
378
- email: "",
379
- subject: "",
380
- message: "",
381
- customFields: {}
817
+ const labels = {
818
+ ...defaultContactFormLabels,
819
+ ...labelOverrides,
820
+ ...submitText !== void 0 && { submit: submitText },
821
+ ...successMessage !== void 0 && { successMessage }
822
+ };
823
+ const topicOptions = (0, import_react5.useMemo)(() => normalizeTopics(topics), [topics]);
824
+ const modes = {
825
+ name: fields.name ?? "required",
826
+ company: fields.company ?? "hidden",
827
+ subject: fields.subject ?? "optional",
828
+ topic: topicOptions.length === 0 ? "hidden" : fields.topic ?? "optional"
829
+ };
830
+ const initialValues = () => ({
831
+ ...EMPTY_VALUES,
832
+ ...defaultValues,
833
+ topic: matchTopic(topicOptions, defaultValues?.topic)
382
834
  });
835
+ const [values, setValues] = (0, import_react5.useState)(initialValues);
836
+ const [custom, setCustom] = (0, import_react5.useState)({});
837
+ const [touched, setTouched] = (0, import_react5.useState)({});
838
+ const [showAllErrors, setShowAllErrors] = (0, import_react5.useState)(false);
839
+ const [trap, setTrap] = (0, import_react5.useState)("");
840
+ const [botSuccess, setBotSuccess] = (0, import_react5.useState)(false);
841
+ const [announcement, setAnnouncement] = (0, import_react5.useState)("");
842
+ const formRef = (0, import_react5.useRef)(null);
843
+ const successRef = (0, import_react5.useRef)(null);
844
+ const wantedTopic = defaultValues?.topic;
845
+ (0, import_react5.useEffect)(() => {
846
+ const matched = matchTopic(topicOptions, wantedTopic);
847
+ if (matched) setValues((v) => v.topic ? v : { ...v, topic: matched });
848
+ }, [wantedTopic, topicOptions]);
383
849
  const { submit, isSubmitting, isSuccess, error, reset } = useContactForm({
384
- onSuccess: () => {
385
- setFormData({
386
- name: "",
387
- email: "",
388
- subject: "",
389
- message: "",
390
- customFields: {}
391
- });
392
- onSuccess?.();
850
+ onSuccess: (response) => {
851
+ setValues((v) => ({
852
+ ...EMPTY_VALUES,
853
+ name: v.name,
854
+ email: v.email,
855
+ company: v.company
856
+ }));
857
+ setCustom({});
858
+ setTouched({});
859
+ setShowAllErrors(false);
860
+ if (!showSuccessState) setAnnouncement(labels.successTitle);
861
+ onSuccess?.(response);
393
862
  },
394
863
  onError
395
864
  });
865
+ const succeeded = (isSuccess || botSuccess) && showSuccessState;
866
+ (0, import_react5.useEffect)(() => {
867
+ if (succeeded) successRef.current?.focus();
868
+ }, [succeeded]);
869
+ const id = (name) => `${idPrefix}-${name}`;
870
+ const validate = (v, c) => {
871
+ const errors2 = {};
872
+ const need = (name, mode) => {
873
+ if (mode === "required" && !v[name].trim()) {
874
+ errors2[name] = name === "topic" ? labels.errorSelect : labels.errorRequired;
875
+ }
876
+ };
877
+ need("name", modes.name);
878
+ need("company", modes.company);
879
+ need("subject", modes.subject);
880
+ need("topic", modes.topic);
881
+ if (!v.email.trim()) errors2.email = labels.errorRequired;
882
+ else if (!EMAIL_PATTERN.test(v.email.trim()))
883
+ errors2.email = labels.errorEmail;
884
+ const message = v.message.trim();
885
+ if (!message) errors2.message = labels.errorRequired;
886
+ else if (message.length < minMessageLength) {
887
+ errors2.message = labels.errorTooShort.replace(
888
+ "{min}",
889
+ String(minMessageLength)
890
+ );
891
+ } else if (message.length > maxMessageLength) {
892
+ errors2.message = labels.errorTooLong.replace(
893
+ "{max}",
894
+ String(maxMessageLength)
895
+ );
896
+ }
897
+ for (const field of customFields) {
898
+ const value = (c[field.name] ?? "").trim();
899
+ if (field.required && !value) {
900
+ errors2[`custom:${field.name}`] = field.type === "select" ? labels.errorSelect : labels.errorRequired;
901
+ } else if (field.type === "email" && value && !EMAIL_PATTERN.test(value)) {
902
+ errors2[`custom:${field.name}`] = labels.errorEmail;
903
+ }
904
+ }
905
+ return errors2;
906
+ };
907
+ const errors = validate(values, custom);
908
+ const visibleError = (key) => showAllErrors || touched[key] ? errors[key] : void 0;
909
+ const handleBlur = (key) => (e) => {
910
+ const value = e.target.value;
911
+ if (value || touched[key] || showAllErrors)
912
+ setTouched((t) => ({ ...t, [key]: true }));
913
+ };
396
914
  const handleSubmit = async (e) => {
397
915
  e.preventDefault();
398
- if (!formData.name || !formData.email || !formData.message) {
916
+ if (isSubmitting) return;
917
+ if (Object.values(errors).some(Boolean)) {
918
+ setShowAllErrors(true);
919
+ setAnnouncement("");
920
+ setTimeout(() => setAnnouncement(labels.errorSummary), 0);
921
+ const order = [
922
+ "name",
923
+ "email",
924
+ "company",
925
+ "topic",
926
+ "subject",
927
+ ...customFields.map((f) => `custom:${f.name}`),
928
+ "message"
929
+ ];
930
+ const firstKey = order.find((key) => errors[key]);
931
+ if (firstKey) {
932
+ const target = formRef.current?.querySelector(
933
+ `[id="${id(firstKey.replace(/^custom:/, ""))}"]`
934
+ );
935
+ target?.focus();
936
+ }
937
+ return;
938
+ }
939
+ if (honeypot && trap) {
940
+ setBotSuccess(true);
399
941
  return;
400
942
  }
401
- await submit(formData);
943
+ const topic = topicOptions.find((t) => t.value === values.topic);
944
+ const customFieldValues = {};
945
+ for (const field of customFields) {
946
+ const v = (custom[field.name] ?? "").trim();
947
+ if (v) customFieldValues[field.name] = v;
948
+ }
949
+ if (modes.topic !== "hidden" && topic)
950
+ customFieldValues.topic = topic.label;
951
+ if (modes.company !== "hidden" && values.company.trim()) {
952
+ customFieldValues.company = values.company.trim();
953
+ }
954
+ const subject = modes.subject !== "hidden" && values.subject.trim() || topic?.label || void 0;
955
+ const data = {
956
+ name: modes.name === "hidden" ? "" : values.name.trim(),
957
+ email: values.email.trim(),
958
+ subject,
959
+ message: values.message.trim(),
960
+ ...Object.keys(customFieldValues).length > 0 && {
961
+ customFields: customFieldValues
962
+ }
963
+ };
964
+ await submit(data);
402
965
  };
403
- const handleInputChange = (field, value) => {
404
- setFormData((prev) => ({
405
- ...prev,
406
- [field]: value
407
- }));
966
+ const handleReset = () => {
967
+ reset();
968
+ setBotSuccess(false);
969
+ setTrap("");
970
+ setAnnouncement("");
408
971
  };
409
- const handleCustomFieldChange = (fieldName, value) => {
410
- setFormData((prev) => ({
411
- ...prev,
412
- customFields: {
413
- ...prev.customFields,
414
- [fieldName]: value
415
- }
416
- }));
972
+ const accent = primaryColor || DEFAULT_PRIMARY;
973
+ const rootStyle = {
974
+ ...primaryColor && { "--chz-primary": primaryColor },
975
+ "--chz-on-primary": primaryTextColor || readableTextColor(accent)
976
+ };
977
+ const rootProps = {
978
+ className: `channlize-contact ${className}`.trim(),
979
+ "data-theme": theme === "system" ? "auto" : theme,
980
+ "data-variant": variant,
981
+ style: rootStyle
982
+ };
983
+ const stylesheet = unstyled ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
984
+ "style",
985
+ {
986
+ "data-channlize-form": "",
987
+ dangerouslySetInnerHTML: { __html: FORM_CSS }
988
+ }
989
+ );
990
+ if (succeeded) {
991
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ...rootProps, children: [
992
+ stylesheet,
993
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
994
+ "div",
995
+ {
996
+ className: "channlize-form-success",
997
+ style: styles.success,
998
+ role: "status",
999
+ children: [
1000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "channlize-success-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { size: 26 }) }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1002
+ "h2",
1003
+ {
1004
+ ref: successRef,
1005
+ tabIndex: -1,
1006
+ className: "channlize-success-title",
1007
+ children: labels.successTitle
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "channlize-success-text", children: labels.successMessage }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1012
+ "button",
1013
+ {
1014
+ type: "button",
1015
+ className: "channlize-secondary",
1016
+ onClick: handleReset,
1017
+ children: labels.sendAnother
1018
+ }
1019
+ )
1020
+ ]
1021
+ }
1022
+ )
1023
+ ] });
1024
+ }
1025
+ const label = (name, text, required) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { htmlFor: id(name), className: "channlize-label", style: styles.label, children: [
1026
+ text,
1027
+ !required && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "channlize-optional", children: labels.optional })
1028
+ ] });
1029
+ const fieldError = (key, errorId) => {
1030
+ const message = visibleError(key);
1031
+ if (!message) return null;
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { id: errorId, className: "channlize-field-error", children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { size: 14 }),
1034
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: message })
1035
+ ] });
1036
+ };
1037
+ const a11y = (key, name, required, extraDescribedBy) => {
1038
+ const invalid = Boolean(visibleError(key));
1039
+ const describedBy = [invalid ? id(`${name}-error`) : null, extraDescribedBy].filter(Boolean).join(" ");
1040
+ return {
1041
+ id: id(name),
1042
+ "aria-invalid": invalid || void 0,
1043
+ "aria-required": required || void 0,
1044
+ "aria-describedby": describedBy || void 0
1045
+ };
417
1046
  };
418
- if (isSuccess) {
419
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1047
+ const setValue = (name) => (e) => setValues((v) => ({ ...v, [name]: e.target.value }));
1048
+ const textField = (name, mode, inputProps) => {
1049
+ if (mode === "hidden") return null;
1050
+ const required = mode === "required";
1051
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
420
1052
  "div",
421
1053
  {
422
- className: `channlize-form-success ${className}`,
423
- style: styles.success,
1054
+ className: "channlize-field",
1055
+ "data-invalid": Boolean(visibleError(name)),
1056
+ style: styles.field,
424
1057
  children: [
425
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: successMessage }),
426
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: reset, style: styles.button, children: "Send Another Message" })
1058
+ label(name, labels[name], required),
1059
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1060
+ "input",
1061
+ {
1062
+ ...a11y(name, name, required),
1063
+ className: "channlize-form-input",
1064
+ name,
1065
+ type: inputProps.type,
1066
+ autoComplete: inputProps.autoComplete,
1067
+ inputMode: inputProps.inputMode,
1068
+ placeholder: labels[`${name}Placeholder`],
1069
+ value: values[name],
1070
+ onChange: setValue(name),
1071
+ onBlur: handleBlur(name),
1072
+ maxLength: name === "subject" ? 200 : 254,
1073
+ style: styles.input
1074
+ }
1075
+ ),
1076
+ fieldError(name, id(`${name}-error`))
427
1077
  ]
428
1078
  }
429
1079
  );
430
- }
431
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
432
- "form",
1080
+ };
1081
+ const topicField = modes.topic === "hidden" ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1082
+ "div",
433
1083
  {
434
- className: `channlize-form ${className}`,
435
- style: styles.form,
436
- onSubmit: handleSubmit,
1084
+ className: "channlize-field",
1085
+ "data-invalid": Boolean(visibleError("topic")),
1086
+ style: styles.field,
437
1087
  children: [
438
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "channlize-field", style: styles.field, children: [
439
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "channlize-name", style: styles.label, children: "Name *" }),
440
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
441
- "input",
442
- {
443
- id: "channlize-name",
1088
+ label("topic", labels.topic, modes.topic === "required"),
1089
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1090
+ "select",
1091
+ {
1092
+ ...a11y("topic", "topic", modes.topic === "required"),
1093
+ className: "channlize-form-select",
1094
+ name: "topic",
1095
+ value: values.topic,
1096
+ "data-empty": !values.topic,
1097
+ onChange: (e) => {
1098
+ setValue("topic")(e);
1099
+ setTouched((t) => ({ ...t, topic: true }));
1100
+ },
1101
+ onBlur: handleBlur("topic"),
1102
+ style: styles.input,
1103
+ children: [
1104
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: labels.topicPlaceholder }),
1105
+ topicOptions.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: t.value, children: t.label }, t.value))
1106
+ ]
1107
+ }
1108
+ ),
1109
+ fieldError("topic", id("topic-error"))
1110
+ ]
1111
+ }
1112
+ );
1113
+ const messageLength = values.message.trim().length;
1114
+ const showCounter = messageLength > maxMessageLength * 0.8;
1115
+ const counterId = id("message-count");
1116
+ const submitError = error ? error.statusCode === 429 || error.code === "RATE_LIMIT_ERROR" ? labels.errorRateLimit : labels.errorGeneric : null;
1117
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ...rootProps, children: [
1118
+ stylesheet,
1119
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1120
+ "form",
1121
+ {
1122
+ ref: formRef,
1123
+ className: "channlize-form",
1124
+ style: styles.form,
1125
+ onSubmit: handleSubmit,
1126
+ noValidate: true,
1127
+ "aria-busy": isSubmitting || void 0,
1128
+ children: [
1129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "channlize-form-row", children: [
1130
+ textField("name", modes.name, {
444
1131
  type: "text",
445
- value: formData.name || "",
446
- onChange: (e) => handleInputChange("name", e.target.value),
447
- required: true,
448
- style: styles.input
449
- }
450
- )
451
- ] }),
452
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "channlize-field", style: styles.field, children: [
453
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "channlize-email", style: styles.label, children: "Email *" }),
454
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
455
- "input",
456
- {
457
- id: "channlize-email",
1132
+ autoComplete: "name"
1133
+ }),
1134
+ textField("email", "required", {
458
1135
  type: "email",
459
- value: formData.email || "",
460
- onChange: (e) => handleInputChange("email", e.target.value),
461
- required: true,
462
- style: styles.input
463
- }
464
- )
465
- ] }),
466
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "channlize-field", style: styles.field, children: [
467
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "channlize-subject", style: styles.label, children: "Subject" }),
468
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
469
- "input",
470
- {
471
- id: "channlize-subject",
1136
+ autoComplete: "email",
1137
+ inputMode: "email"
1138
+ })
1139
+ ] }),
1140
+ (modes.company !== "hidden" || modes.topic !== "hidden") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "channlize-form-row", children: [
1141
+ textField("company", modes.company, {
472
1142
  type: "text",
473
- value: formData.subject || "",
474
- onChange: (e) => handleInputChange("subject", e.target.value),
475
- style: styles.input
476
- }
477
- )
478
- ] }),
479
- customFields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "channlize-field", style: styles.field, children: [
480
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { htmlFor: `channlize-${field.name}`, style: styles.label, children: [
481
- field.label,
482
- " ",
483
- field.required && "*"
1143
+ autoComplete: "organization"
1144
+ }),
1145
+ topicField
484
1146
  ] }),
485
- field.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
486
- "select",
1147
+ textField("subject", modes.subject, {
1148
+ type: "text",
1149
+ autoComplete: "off"
1150
+ }),
1151
+ customFields.map((field) => {
1152
+ const key = `custom:${field.name}`;
1153
+ const required = Boolean(field.required);
1154
+ const common = {
1155
+ ...a11y(key, field.name, required),
1156
+ name: field.name,
1157
+ value: custom[field.name] ?? "",
1158
+ onChange: (e) => {
1159
+ const value = e.target.value;
1160
+ setCustom((c) => ({ ...c, [field.name]: value }));
1161
+ },
1162
+ onBlur: handleBlur(key)
1163
+ };
1164
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1165
+ "div",
1166
+ {
1167
+ className: "channlize-field",
1168
+ "data-invalid": Boolean(visibleError(key)),
1169
+ style: styles.field,
1170
+ children: [
1171
+ label(field.name, field.label, required),
1172
+ field.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1173
+ "select",
1174
+ {
1175
+ ...common,
1176
+ className: "channlize-form-select",
1177
+ "data-empty": !common.value,
1178
+ style: styles.input,
1179
+ children: [
1180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: field.placeholder ?? labels.topicPlaceholder }),
1181
+ field.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: option, children: option }, option))
1182
+ ]
1183
+ }
1184
+ ) : field.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1185
+ "textarea",
1186
+ {
1187
+ ...common,
1188
+ className: "channlize-form-textarea",
1189
+ placeholder: field.placeholder,
1190
+ rows: 3,
1191
+ style: styles.textarea
1192
+ }
1193
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1194
+ "input",
1195
+ {
1196
+ ...common,
1197
+ className: "channlize-form-input",
1198
+ type: field.type,
1199
+ placeholder: field.placeholder,
1200
+ style: styles.input
1201
+ }
1202
+ ),
1203
+ fieldError(key, id(`${field.name}-error`))
1204
+ ]
1205
+ },
1206
+ field.name
1207
+ );
1208
+ }),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1210
+ "div",
487
1211
  {
488
- id: `channlize-${field.name}`,
489
- value: formData.customFields?.[field.name] || "",
490
- onChange: (e) => handleCustomFieldChange(field.name, e.target.value),
491
- required: field.required,
492
- style: styles.input,
1212
+ className: "channlize-field",
1213
+ "data-invalid": Boolean(visibleError("message")),
1214
+ style: styles.field,
493
1215
  children: [
494
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: "Select..." }),
495
- field.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: option, children: option }, option))
1216
+ label("message", labels.message, true),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1218
+ "textarea",
1219
+ {
1220
+ ...a11y(
1221
+ "message",
1222
+ "message",
1223
+ true,
1224
+ showCounter ? counterId : void 0
1225
+ ),
1226
+ className: "channlize-form-textarea",
1227
+ name: "message",
1228
+ rows: 5,
1229
+ placeholder: labels.messagePlaceholder,
1230
+ value: values.message,
1231
+ onChange: setValue("message"),
1232
+ onBlur: handleBlur("message"),
1233
+ style: styles.textarea
1234
+ }
1235
+ ),
1236
+ (visibleError("message") || showCounter) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "channlize-field-meta", children: [
1237
+ fieldError("message", id("message-error")),
1238
+ showCounter && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1239
+ "span",
1240
+ {
1241
+ id: counterId,
1242
+ className: "channlize-counter",
1243
+ "data-over": messageLength > maxMessageLength,
1244
+ children: [
1245
+ messageLength,
1246
+ "/",
1247
+ maxMessageLength
1248
+ ]
1249
+ }
1250
+ )
1251
+ ] })
496
1252
  ]
497
1253
  }
498
- ) : field.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
499
- "textarea",
500
- {
501
- id: `channlize-${field.name}`,
502
- value: formData.customFields?.[field.name] || "",
503
- onChange: (e) => handleCustomFieldChange(field.name, e.target.value),
504
- required: field.required,
505
- style: styles.textarea
506
- }
507
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
508
- "input",
509
- {
510
- id: `channlize-${field.name}`,
511
- type: field.type,
512
- value: formData.customFields?.[field.name] || "",
513
- onChange: (e) => handleCustomFieldChange(field.name, e.target.value),
514
- required: field.required,
515
- style: styles.input
516
- }
517
- )
518
- ] }, field.name)),
519
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "channlize-field", style: styles.field, children: [
520
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "channlize-message", style: styles.label, children: "Message *" }),
521
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
522
- "textarea",
1254
+ ),
1255
+ honeypot && // Bots fill every input; people never see this one. No `id`, so
1256
+ // host-page form helpers keyed on ids skip it too.
1257
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "channlize-honeypot", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { children: [
1258
+ "Leave this field empty",
1259
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1260
+ "input",
1261
+ {
1262
+ type: "text",
1263
+ name: "website",
1264
+ tabIndex: -1,
1265
+ autoComplete: "off",
1266
+ value: trap,
1267
+ onChange: (e) => setTrap(e.target.value)
1268
+ }
1269
+ )
1270
+ ] }) }),
1271
+ submitError && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "channlize-error", role: "alert", style: styles.error, children: [
1272
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { size: 18 }),
1273
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: submitError })
1274
+ ] }),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "channlize-form-footer", children: [
1276
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1277
+ "button",
1278
+ {
1279
+ type: "submit",
1280
+ className: "channlize-submit",
1281
+ disabled: isSubmitting,
1282
+ style: styles.button,
1283
+ children: [
1284
+ isSubmitting && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "channlize-form-spinner", "aria-hidden": "true" }),
1285
+ isSubmitting ? labels.submitting : labels.submit
1286
+ ]
1287
+ }
1288
+ ),
1289
+ labels.footnote && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "channlize-footnote", children: labels.footnote })
1290
+ ] }),
1291
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1292
+ "p",
523
1293
  {
524
- id: "channlize-message",
525
- value: formData.message || "",
526
- onChange: (e) => handleInputChange("message", e.target.value),
527
- required: true,
528
- rows: 4,
529
- style: styles.textarea
1294
+ className: "channlize-visually-hidden",
1295
+ role: "status",
1296
+ "aria-live": "polite",
1297
+ children: announcement
530
1298
  }
531
1299
  )
532
- ] }),
533
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "channlize-error", style: styles.error, children: error.message }),
534
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", disabled: isSubmitting, style: styles.button, children: isSubmitting ? "Sending..." : submitText })
535
- ]
536
- }
537
- );
1300
+ ]
1301
+ }
1302
+ )
1303
+ ] });
538
1304
  }
539
1305
 
540
1306
  // src/components/ContactForm.tsx
541
- var import_jsx_runtime3 = require("react/jsx-runtime");
1307
+ var import_jsx_runtime4 = require("react/jsx-runtime");
542
1308
  function ContactForm(props) {
543
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Form, { ...props });
1309
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Form, { ...props });
544
1310
  }
545
1311
 
546
1312
  // src/components/SupportChat.tsx
1313
+ var import_react7 = require("react");
1314
+
1315
+ // src/components/chat/internal.tsx
547
1316
  var import_react6 = require("react");
548
- var import_jsx_runtime4 = require("react/jsx-runtime");
1317
+
1318
+ // src/components/chat/styles.ts
1319
+ var WIDGET_CSS = `
1320
+ .channlize-widget{
1321
+ --chz-primary:#2563eb;
1322
+ --chz-on-primary:#ffffff;
1323
+ --chz-bg:#ffffff;
1324
+ --chz-surface:#f6f7f9;
1325
+ --chz-bubble:#eff1f4;
1326
+ --chz-text:#111827;
1327
+ --chz-muted:#5d6572;
1328
+ --chz-border:#e3e6eb;
1329
+ --chz-danger:#b91c1c;
1330
+ --chz-danger-bg:#fdecec;
1331
+ --chz-warn-bg:#fff6e0;
1332
+ --chz-warn-text:#7a4b00;
1333
+ --chz-online:#22c55e;
1334
+ --chz-away:#f59e0b;
1335
+ --chz-offline:#9ca3af;
1336
+ --chz-shadow:0 24px 48px -12px rgba(15,23,42,.28),0 0 0 1px rgba(15,23,42,.06);
1337
+ --chz-radius:18px;
1338
+ font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
1339
+ font-size:14px;
1340
+ line-height:1.45;
1341
+ font-weight:400;
1342
+ font-style:normal;
1343
+ letter-spacing:normal;
1344
+ text-transform:none;
1345
+ text-align:left;
1346
+ color:var(--chz-text);
1347
+ -webkit-font-smoothing:antialiased;
1348
+ -moz-osx-font-smoothing:grayscale;
1349
+ -webkit-tap-highlight-color:transparent;
1350
+ }
1351
+ .channlize-widget[data-theme="dark"]{
1352
+ --chz-bg:#16181d;
1353
+ --chz-surface:#1f2228;
1354
+ --chz-bubble:#2a2e36;
1355
+ --chz-text:#f2f4f7;
1356
+ --chz-muted:#a7aebb;
1357
+ --chz-border:#2f343d;
1358
+ --chz-danger:#ff9b9b;
1359
+ --chz-danger-bg:#3b1e22;
1360
+ --chz-warn-bg:#352a10;
1361
+ --chz-warn-text:#f6c768;
1362
+ --chz-offline:#6b7280;
1363
+ --chz-shadow:0 24px 48px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.06);
1364
+ }
1365
+ @media (prefers-color-scheme: dark){
1366
+ .channlize-widget[data-theme="auto"]{
1367
+ --chz-bg:#16181d;
1368
+ --chz-surface:#1f2228;
1369
+ --chz-bubble:#2a2e36;
1370
+ --chz-text:#f2f4f7;
1371
+ --chz-muted:#a7aebb;
1372
+ --chz-border:#2f343d;
1373
+ --chz-danger:#ff9b9b;
1374
+ --chz-danger-bg:#3b1e22;
1375
+ --chz-warn-bg:#352a10;
1376
+ --chz-warn-text:#f6c768;
1377
+ --chz-offline:#6b7280;
1378
+ --chz-shadow:0 24px 48px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.06);
1379
+ }
1380
+ }
1381
+ .channlize-widget *,.channlize-widget *::before,.channlize-widget *::after{box-sizing:border-box;letter-spacing:inherit;word-spacing:inherit;text-transform:inherit;text-indent:0;}
1382
+ .channlize-widget :where(h1,h2,h3,h4,p,ul,ol,li,figure,form,fieldset){margin:0;padding:0;border:0;font:inherit;color:inherit;background:none;}
1383
+ .channlize-widget :where(button,textarea,input){font:inherit;color:inherit;letter-spacing:inherit;text-transform:none;text-shadow:none;margin:0;}
1384
+ .channlize-widget :where(button){appearance:none;-webkit-appearance:none;background:none;border:0;border-radius:0;padding:0;box-shadow:none;cursor:pointer;width:auto;height:auto;min-width:0;min-height:0;line-height:inherit;text-align:center;text-decoration:none;}
1385
+ .channlize-widget :where(button:disabled){cursor:default;}
1386
+ .channlize-widget :where(img,svg){display:block;max-width:100%;border:0;}
1387
+ .channlize-widget :where(a){color:inherit;}
1388
+ .channlize-widget :where(:focus-visible){outline:2px solid var(--chz-primary);outline-offset:2px;}
1389
+
1390
+ /* ---------- Floating widget (ChannlizeChat) ---------- */
1391
+ .channlize-floating{position:static;}
1392
+ .channlize-floating .channlize-launcher-wrap,.channlize-floating .channlize-panel{position:fixed;z-index:var(--chz-z,2147483000);}
1393
+ .channlize-floating[data-position$="right"] .channlize-launcher-wrap,.channlize-floating[data-position$="right"] .channlize-panel{right:calc(var(--chz-x) + env(safe-area-inset-right,0px));}
1394
+ .channlize-floating[data-position$="left"] .channlize-launcher-wrap,.channlize-floating[data-position$="left"] .channlize-panel{left:calc(var(--chz-x) + env(safe-area-inset-left,0px));}
1395
+ .channlize-floating[data-position^="bottom"] .channlize-launcher-wrap,.channlize-floating[data-position^="bottom"] .channlize-panel{bottom:calc(var(--chz-y) + env(safe-area-inset-bottom,0px));}
1396
+ .channlize-floating[data-position^="top"] .channlize-launcher-wrap,.channlize-floating[data-position^="top"] .channlize-panel{top:calc(var(--chz-y) + env(safe-area-inset-top,0px));}
1397
+
1398
+ .channlize-launcher{position:relative;display:flex;align-items:center;justify-content:center;width:var(--chz-launcher-size,56px);height:var(--chz-launcher-size,56px);border-radius:999px;background:var(--chz-launcher-bg,var(--chz-primary));color:var(--chz-launcher-fg,var(--chz-on-primary));box-shadow:0 8px 24px -6px rgba(15,23,42,.35),0 2px 6px rgba(15,23,42,.12);transition:transform .2s cubic-bezier(.2,.8,.2,1),opacity .2s ease,box-shadow .2s ease,background-color .2s ease,visibility 0s;}
1399
+ .channlize-launcher:hover{transform:translateY(-2px) scale(1.04);background:var(--chz-launcher-hover,var(--chz-launcher-bg,var(--chz-primary)));box-shadow:0 12px 28px -6px rgba(15,23,42,.4),0 2px 6px rgba(15,23,42,.12);}
1400
+ .channlize-launcher:active{transform:scale(.96);}
1401
+ .channlize-launcher:focus-visible{outline:3px solid var(--chz-launcher-bg,var(--chz-primary));outline-offset:3px;}
1402
+ .channlize-launcher-custom{display:block;cursor:pointer;}
1403
+ .channlize-launcher-wrap{transition:opacity .2s ease,transform .2s cubic-bezier(.2,.8,.2,1),visibility 0s;}
1404
+ .channlize-floating[data-open="true"] .channlize-launcher-wrap{opacity:0;transform:scale(.6);visibility:hidden;pointer-events:none;transition:opacity .15s ease,transform .15s ease,visibility 0s linear .15s;}
1405
+ .channlize-badge{position:absolute;top:-4px;right:-4px;min-width:20px;height:20px;padding:0 6px;border-radius:999px;background:#d92d20;color:#ffffff;font-size:11px;font-weight:700;line-height:20px;text-align:center;box-shadow:0 0 0 2px var(--chz-bg);animation:channlize-pop .3s cubic-bezier(.2,.8,.2,1);}
1406
+
1407
+ .channlize-panel{display:flex;opacity:0;visibility:hidden;transform:translateY(16px) scale(.97);transform-origin:bottom right;transition:opacity .18s ease,transform .24s cubic-bezier(.2,.8,.2,1),visibility 0s linear .24s;}
1408
+ .channlize-floating[data-position="bottom-left"] .channlize-panel{transform-origin:bottom left;}
1409
+ .channlize-floating[data-position="top-right"] .channlize-panel{transform-origin:top right;transform:translateY(-16px) scale(.97);}
1410
+ .channlize-floating[data-position="top-left"] .channlize-panel{transform-origin:top left;transform:translateY(-16px) scale(.97);}
1411
+ .channlize-floating[data-open="true"] .channlize-panel{opacity:1;visibility:visible;transform:none;transition:opacity .18s ease,transform .24s cubic-bezier(.2,.8,.2,1),visibility 0s;}
1412
+ .channlize-panel .channlize-chat-container{width:var(--chz-panel-width,380px);height:640px;max-height:calc(100vh - var(--chz-y) * 2);max-height:calc(100dvh - var(--chz-y) * 2);min-height:0;border:0;box-shadow:var(--chz-shadow);}
1413
+
1414
+ .channlize-widget[data-animate="false"] *,.channlize-widget[data-animate="false"]{transition:none !important;animation:none !important;}
1415
+ @media (prefers-reduced-motion: reduce){
1416
+ .channlize-widget,.channlize-widget *{transition-duration:.01ms !important;animation-duration:.01ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important;}
1417
+ .channlize-panel{transform:none !important;}
1418
+ }
1419
+
1420
+ @media (max-width: 640px){
1421
+ .channlize-floating[data-hide-mobile="true"] .channlize-launcher-wrap{display:none;}
1422
+ .channlize-floating .channlize-panel{inset:0 !important;width:100%;height:100%;transform-origin:bottom center !important;}
1423
+ .channlize-panel .channlize-chat-container{width:100% !important;height:100% !important;max-height:none !important;border-radius:0 !important;box-shadow:none !important;}
1424
+ .channlize-panel .channlize-chat-header{padding-top:max(14px, env(safe-area-inset-top,0px));}
1425
+ .channlize-panel .channlize-chat-input-container{padding-bottom:max(12px, env(safe-area-inset-bottom,0px));}
1426
+ }
1427
+
1428
+ /* ---------- Chat container ---------- */
1429
+ .channlize-chat-container{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:420px;overflow:hidden;background:var(--chz-bg);color:var(--chz-text);border:1px solid var(--chz-border);border-radius:16px;isolation:isolate;}
1430
+
1431
+ /* Header */
1432
+ .channlize-chat-header{display:flex;align-items:center;gap:12px;padding:14px 12px 14px 16px;background:var(--chz-primary);color:var(--chz-on-primary);flex-shrink:0;}
1433
+ .channlize-header-text{flex:1;min-width:0;}
1434
+ .channlize-chat-title{font-size:15px;font-weight:650;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
1435
+ .channlize-chat-status{display:flex;align-items:center;gap:6px;margin-top:2px;font-size:12.5px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
1436
+ .channlize-chat-status>span:last-child{overflow:hidden;text-overflow:ellipsis;}
1437
+ .channlize-status-dot{flex-shrink:0;width:8px;height:8px;border-radius:999px;background:var(--chz-online);box-shadow:0 0 0 2px color-mix(in srgb, var(--chz-on-primary) 35%, transparent);}
1438
+ .channlize-chat-status[data-status="connecting"] .channlize-status-dot{background:var(--chz-away);animation:channlize-pulse 1.4s ease-in-out infinite;}
1439
+ .channlize-chat-status[data-status="offline"] .channlize-status-dot{background:var(--chz-offline);}
1440
+ .channlize-header-actions{display:flex;align-items:center;gap:2px;flex-shrink:0;}
1441
+ .channlize-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:10px;color:inherit;transition:background-color .15s ease;}
1442
+ .channlize-icon-btn:hover{background:rgba(127,127,127,.16);background:color-mix(in srgb, currentColor 14%, transparent);}
1443
+ .channlize-chat-header .channlize-icon-btn:focus-visible{outline-color:var(--chz-on-primary);outline-offset:0;}
1444
+
1445
+ /* Avatars */
1446
+ .channlize-avatar{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;border-radius:999px;overflow:hidden;background:var(--chz-primary);color:var(--chz-on-primary);font-size:11px;font-weight:650;line-height:1;user-select:none;}
1447
+ .channlize-avatar img{width:100%;height:100%;object-fit:cover;}
1448
+ .channlize-header-avatar{width:40px;height:40px;font-size:15px;background:rgba(255,255,255,.18);background:color-mix(in srgb, var(--chz-on-primary) 18%, transparent);color:inherit;box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--chz-on-primary) 20%, transparent);}
1449
+
1450
+ /* Overflow menu */
1451
+ .channlize-menu-wrap{position:relative;}
1452
+ .channlize-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:3;min-width:200px;padding:6px;border-radius:12px;background:var(--chz-bg);color:var(--chz-text);border:1px solid var(--chz-border);box-shadow:0 12px 32px -8px rgba(15,23,42,.3);animation:channlize-fade-in .14s ease;}
1453
+ .channlize-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border-radius:8px;text-align:left;font-size:14px;color:var(--chz-text);}
1454
+ .channlize-menu-item:hover,.channlize-menu-item:focus-visible{background:var(--chz-surface);outline:none;}
1455
+ .channlize-menu-item svg{color:var(--chz-muted);}
1456
+
1457
+ /* Banner */
1458
+ .channlize-banner{display:flex;align-items:center;gap:8px;padding:8px 14px;font-size:13px;line-height:1.35;background:var(--chz-warn-bg);color:var(--chz-warn-text);border-bottom:1px solid var(--chz-border);flex-shrink:0;animation:channlize-fade-in .2s ease;}
1459
+ .channlize-banner[data-tone="error"]{background:var(--chz-danger-bg);color:var(--chz-danger);}
1460
+ .channlize-banner-text{flex:1;min-width:0;}
1461
+ .channlize-banner-action{font-weight:600;text-decoration:underline;text-underline-offset:2px;border-radius:4px;padding:2px 4px;}
1462
+ .channlize-spinner{width:12px;height:12px;flex-shrink:0;border-radius:999px;border:2px solid currentColor;border-right-color:transparent;animation:channlize-spin .8s linear infinite;}
1463
+
1464
+ /* Messages */
1465
+ .channlize-messages{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px 14px 8px;background:var(--chz-bg);}
1466
+ .channlize-day{display:flex;align-items:center;gap:10px;margin:8px 0 12px;font-size:12px;font-weight:550;color:var(--chz-muted);}
1467
+ .channlize-day::before,.channlize-day::after{content:"";flex:1;height:1px;background:var(--chz-border);}
1468
+ .channlize-group{display:flex;align-items:flex-start;gap:8px;margin-bottom:14px;}
1469
+ .channlize-group[data-author="user"]{justify-content:flex-end;}
1470
+ .channlize-group-body{display:flex;flex-direction:column;align-items:flex-start;gap:3px;min-width:0;max-width:82%;}
1471
+ .channlize-group[data-author="user"] .channlize-group-body{align-items:flex-end;}
1472
+ .channlize-group-avatar{margin-top:20px;}
1473
+ .channlize-group-name{font-size:12px;font-weight:600;color:var(--chz-muted);padding:0 4px 1px;}
1474
+ .channlize-message{position:relative;max-width:100%;padding:9px 13px;border-radius:var(--chz-radius);font-size:14px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;}
1475
+ .channlize-message-agent{background:var(--chz-bubble);color:var(--chz-text);}
1476
+ .channlize-message-user{background:var(--chz-primary);color:var(--chz-on-primary);}
1477
+ .channlize-message-agent[data-pos="first"]{border-bottom-left-radius:6px;}
1478
+ .channlize-message-agent[data-pos="middle"]{border-top-left-radius:6px;border-bottom-left-radius:6px;}
1479
+ .channlize-message-agent[data-pos="last"]{border-top-left-radius:6px;}
1480
+ .channlize-message-user[data-pos="first"]{border-bottom-right-radius:6px;}
1481
+ .channlize-message-user[data-pos="middle"]{border-top-right-radius:6px;border-bottom-right-radius:6px;}
1482
+ .channlize-message-user[data-pos="last"]{border-top-right-radius:6px;}
1483
+ .channlize-message[data-status="sending"]{opacity:.7;}
1484
+ .channlize-message[data-status="failed"]{opacity:.85;box-shadow:0 0 0 2px var(--chz-danger);}
1485
+ .channlize-message-content a{color:inherit;text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px;}
1486
+ .channlize-message-content a:hover{text-decoration-thickness:2px;}
1487
+ .channlize-message-content:empty{display:none;}
1488
+ .channlize-message-timestamp{font-size:11.5px;color:var(--chz-muted);padding:1px 4px 0;}
1489
+ .channlize-message-meta{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--chz-muted);padding:1px 4px 0;}
1490
+ .channlize-message-meta[data-status="failed"]{color:var(--chz-danger);}
1491
+ .channlize-link-btn{font-weight:600;text-decoration:underline;text-underline-offset:2px;border-radius:4px;padding:0 2px;color:inherit;}
1492
+
1493
+ /* Attachments */
1494
+ .channlize-attachments{display:flex;flex-direction:column;gap:6px;margin-top:6px;white-space:normal;}
1495
+ .channlize-message-content:empty+.channlize-attachments{margin-top:0;}
1496
+ .channlize-attachment-image{display:block;border-radius:12px;overflow:hidden;max-width:240px;background:rgba(127,127,127,.12);}
1497
+ .channlize-attachment-image img{width:100%;height:auto;max-height:220px;object-fit:cover;}
1498
+ .channlize-attachment-file{display:flex;align-items:center;gap:10px;min-width:0;max-width:260px;padding:8px 10px;border-radius:12px;background:rgba(127,127,127,.14);background:color-mix(in srgb, currentColor 10%, transparent);color:inherit;text-decoration:none;}
1499
+ a.channlize-attachment-file:hover{background:color-mix(in srgb, currentColor 16%, transparent);}
1500
+ .channlize-attachment-name{display:block;font-size:13px;font-weight:550;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
1501
+ .channlize-attachment-size{display:block;font-size:11.5px;opacity:.8;}
1502
+ .channlize-attachment-text{min-width:0;}
1503
+
1504
+ /* Welcome / empty state */
1505
+ .channlize-welcome{display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;padding:28px 12px 12px;animation:channlize-fade-in .3s ease;}
1506
+ .channlize-welcome .channlize-avatar{width:52px;height:52px;font-size:18px;margin-bottom:8px;}
1507
+ .channlize-welcome-title{font-size:18px;font-weight:650;line-height:1.3;color:var(--chz-text);}
1508
+ .channlize-welcome-text{font-size:14px;color:var(--chz-muted);max-width:280px;}
1509
+ .channlize-notice{margin:4px 0 0;font-size:12.5px;color:var(--chz-muted);}
1510
+ .channlize-suggestions{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;margin-top:16px;}
1511
+ .channlize-chip{padding:8px 14px;border-radius:999px;font-size:13.5px;font-weight:550;line-height:1.3;color:var(--chz-text);background:var(--chz-bg);border:1px solid var(--chz-border);transition:border-color .15s ease,background-color .15s ease;text-align:left;}
1512
+ .channlize-chip:hover:not(:disabled){border-color:var(--chz-primary);background:var(--chz-surface);}
1513
+ .channlize-chip:disabled{opacity:.55;}
1514
+
1515
+ /* Composer */
1516
+ .channlize-chat-input-container{flex-shrink:0;padding:10px 12px 12px;background:var(--chz-bg);border-top:1px solid var(--chz-border);}
1517
+ .channlize-composer{display:flex;align-items:flex-end;gap:6px;padding:6px 6px 6px 12px;border-radius:22px;background:var(--chz-surface);border:1px solid var(--chz-border);transition:border-color .15s ease,box-shadow .15s ease;}
1518
+ .channlize-composer:focus-within{border-color:var(--chz-primary);box-shadow:0 0 0 3px rgba(37,99,235,.15);box-shadow:0 0 0 3px color-mix(in srgb, var(--chz-primary) 18%, transparent);}
1519
+ .channlize-composer[data-has-attach="true"]{padding-left:4px;}
1520
+ .channlize-input{flex:1;min-width:0;display:block;resize:none;border:0;outline:none;background:transparent;color:var(--chz-text);padding:7px 0;max-height:140px;font-size:15px;line-height:1.4;overflow-y:auto;box-shadow:none;border-radius:0;}
1521
+ .channlize-input:focus,.channlize-input:focus-visible{outline:none;}
1522
+ .channlize-input::placeholder{color:var(--chz-muted);opacity:1;}
1523
+ .channlize-input:disabled{opacity:.7;}
1524
+ .channlize-send{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:34px;height:34px;border-radius:999px;background:var(--chz-primary);color:var(--chz-on-primary);transition:opacity .15s ease,transform .15s ease,background-color .15s ease;}
1525
+ .channlize-send:hover:not(:disabled){transform:scale(1.05);}
1526
+ .channlize-send:disabled{background:var(--chz-border);color:var(--chz-muted);}
1527
+ .channlize-composer .channlize-icon-btn{width:34px;height:34px;border-radius:999px;color:var(--chz-muted);}
1528
+ .channlize-composer .channlize-icon-btn:hover{color:var(--chz-text);}
1529
+ .channlize-file-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
1530
+ .channlize-pending-files{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px;}
1531
+ .channlize-pending-file{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:4px 4px 4px 10px;border-radius:999px;background:var(--chz-surface);border:1px solid var(--chz-border);font-size:12.5px;}
1532
+ .channlize-pending-file span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px;}
1533
+ .channlize-pending-file button{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:999px;color:var(--chz-muted);}
1534
+ .channlize-pending-file button:hover{background:var(--chz-border);color:var(--chz-text);}
1535
+ .channlize-composer-footer{display:flex;justify-content:flex-end;padding:4px 6px 0;font-size:11.5px;color:var(--chz-muted);}
1536
+ .channlize-composer-footer[data-over="true"]{color:var(--chz-danger);}
1537
+ @media (max-width: 640px), (pointer: coarse){
1538
+ .channlize-input{font-size:16px;}
1539
+ }
1540
+
1541
+ /* End-chat confirmation sheet */
1542
+ .channlize-sheet-backdrop{position:absolute;inset:0;z-index:5;display:flex;align-items:flex-end;background:rgba(15,23,42,.35);animation:channlize-fade-in .15s ease;}
1543
+ .channlize-sheet{width:100%;padding:20px 18px max(18px, env(safe-area-inset-bottom,0px));background:var(--chz-bg);color:var(--chz-text);border-radius:18px 18px 0 0;box-shadow:0 -8px 30px rgba(15,23,42,.18);animation:channlize-slide-up .22s cubic-bezier(.2,.8,.2,1);}
1544
+ .channlize-sheet-title{font-size:16px;font-weight:650;}
1545
+ .channlize-sheet-text{margin-top:4px;font-size:14px;color:var(--chz-muted);}
1546
+ .channlize-sheet-actions{display:flex;gap:8px;margin-top:16px;}
1547
+ .channlize-btn{flex:1;padding:10px 14px;border-radius:12px;font-size:14px;font-weight:600;transition:background-color .15s ease,filter .15s ease;}
1548
+ .channlize-btn-secondary{background:var(--chz-surface);color:var(--chz-text);border:1px solid var(--chz-border);}
1549
+ .channlize-btn-secondary:hover{background:var(--chz-border);}
1550
+ .channlize-btn-danger{background:#b42318;color:#ffffff;}
1551
+ .channlize-btn-danger:hover{filter:brightness(.92);}
1552
+ .channlize-btn-primary{background:var(--chz-primary);color:var(--chz-on-primary);}
1553
+
1554
+ .channlize-visually-hidden{position:absolute !important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
1555
+
1556
+ @keyframes channlize-spin{to{transform:rotate(360deg);}}
1557
+ @keyframes channlize-pulse{0%,100%{opacity:1;}50%{opacity:.4;}}
1558
+ @keyframes channlize-fade-in{from{opacity:0;}to{opacity:1;}}
1559
+ @keyframes channlize-slide-up{from{transform:translateY(24px);opacity:0;}to{transform:none;opacity:1;}}
1560
+ @keyframes channlize-pop{from{transform:scale(.4);}to{transform:scale(1);}}
1561
+ `;
1562
+
1563
+ // src/components/chat/internal.tsx
1564
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1565
+ var StylesContext = (0, import_react6.createContext)(false);
1566
+ function WidgetStylesProvider({ children }) {
1567
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StylesContext.Provider, { value: true, children: [
1568
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WidgetStyles, {}),
1569
+ children
1570
+ ] });
1571
+ }
1572
+ function WidgetStyles({ ifMissing = false }) {
1573
+ const provided = (0, import_react6.useContext)(StylesContext);
1574
+ if (ifMissing && provided) return null;
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1576
+ "style",
1577
+ {
1578
+ "data-channlize-widget": "",
1579
+ dangerouslySetInnerHTML: { __html: WIDGET_CSS }
1580
+ }
1581
+ );
1582
+ }
1583
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react6.useLayoutEffect : import_react6.useEffect;
1584
+ function useDelayedFlag(value, delayMs) {
1585
+ const [elapsed, setElapsed] = (0, import_react6.useState)(false);
1586
+ (0, import_react6.useEffect)(() => {
1587
+ if (!value) return;
1588
+ const timer = setTimeout(() => setElapsed(true), delayMs);
1589
+ return () => {
1590
+ clearTimeout(timer);
1591
+ setElapsed(false);
1592
+ };
1593
+ }, [value, delayMs]);
1594
+ return value && elapsed;
1595
+ }
1596
+ function Avatar({
1597
+ name,
1598
+ url,
1599
+ node,
1600
+ className = ""
1601
+ }) {
1602
+ const [failedUrl, setFailedUrl] = (0, import_react6.useState)(null);
1603
+ const src = safeUrl(url);
1604
+ if (node) {
1605
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `channlize-avatar ${className}`, "aria-hidden": "true", children: node });
1606
+ }
1607
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `channlize-avatar ${className}`, "aria-hidden": "true", children: src && failedUrl !== src ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1608
+ "img",
1609
+ {
1610
+ src,
1611
+ alt: "",
1612
+ referrerPolicy: "no-referrer",
1613
+ onError: () => setFailedUrl(src)
1614
+ }
1615
+ ) : initials(name) });
1616
+ }
1617
+
1618
+ // src/components/SupportChat.tsx
1619
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1620
+ var defaultSupportChatLabels = {
1621
+ online: "Online",
1622
+ connecting: "Connecting\u2026",
1623
+ reconnecting: "Reconnecting\u2026",
1624
+ offline: "Offline",
1625
+ connectionLost: "Connection lost. New replies may be delayed.",
1626
+ startFailed: "We couldn\u2019t start the chat.",
1627
+ retry: "Retry",
1628
+ today: "Today",
1629
+ yesterday: "Yesterday",
1630
+ you: "You",
1631
+ sending: "Sending\u2026",
1632
+ failed: "Not sent",
1633
+ remove: "Remove",
1634
+ moreOptions: "More options",
1635
+ minimize: "Minimize chat",
1636
+ endChat: "End conversation",
1637
+ endChatTitle: "End this conversation?",
1638
+ endChatDescription: "The chat history will be cleared from this device. You can start a new conversation at any time.",
1639
+ endChatConfirm: "End conversation",
1640
+ cancel: "Cancel",
1641
+ attach: "Attach files",
1642
+ removeAttachment: "Remove attachment",
1643
+ messages: "Messages",
1644
+ conversationEnded: "Conversation ended. Send a message to start a new one.",
1645
+ charactersLeft: "characters"
1646
+ };
1647
+ var DEFAULT_PRIMARY2 = "#2563eb";
1648
+ var MAX_FILES = 10;
1649
+ var STATUS_DELAY_MS = 3e3;
1650
+ var BANNER_DELAY_MS = 5e3;
1651
+ var localIdCounter = 0;
1652
+ function createPendingMessage(content, files) {
1653
+ localIdCounter += 1;
1654
+ return {
1655
+ localId: `local-${Date.now()}-${localIdCounter}`,
1656
+ content,
1657
+ files,
1658
+ timestamp: /* @__PURE__ */ new Date(),
1659
+ status: "sending"
1660
+ };
1661
+ }
549
1662
  function SupportChat({
550
1663
  autoConnect = true,
551
1664
  metadata,
@@ -556,15 +1669,52 @@ function SupportChat({
556
1669
  storageKey,
557
1670
  className = "",
558
1671
  styles = {},
559
- placeholder = "Type your message...",
560
- title = "Support Chat",
561
- sendButtonText = "Send",
1672
+ placeholder = "Write a message\u2026",
1673
+ title = "Support",
1674
+ sendButtonText = "Send message",
562
1675
  showTimestamps = true,
563
- allowAttachments = false
1676
+ allowAttachments = false,
1677
+ avatarUrl,
1678
+ avatar,
1679
+ statusText = "Replies in a few minutes",
1680
+ greeting = "Hi there!",
1681
+ welcomeMessage = "Ask us anything. We\u2019re here to help.",
1682
+ suggestions,
1683
+ theme = "auto",
1684
+ primaryColor,
1685
+ primaryTextColor,
1686
+ locale,
1687
+ maxLength = 5e3,
1688
+ allowEndChat = true,
1689
+ onMinimize,
1690
+ labels: labelOverrides
564
1691
  }) {
565
- const [messageInput, setMessageInput] = (0, import_react6.useState)("");
566
- const [attachments, setAttachments] = (0, import_react6.useState)([]);
567
- const messagesEndRef = (0, import_react6.useRef)(null);
1692
+ const labels = (0, import_react7.useMemo)(
1693
+ () => ({ ...defaultSupportChatLabels, ...labelOverrides }),
1694
+ [labelOverrides]
1695
+ );
1696
+ const { config } = useChannlize();
1697
+ const baseUrl = config.baseUrl || "https://api.channlize.com";
1698
+ const [text, setText] = (0, import_react7.useState)("");
1699
+ const [files, setFiles] = (0, import_react7.useState)([]);
1700
+ const [pending, setPending] = (0, import_react7.useState)([]);
1701
+ const [sendError, setSendError] = (0, import_react7.useState)(null);
1702
+ const [menuOpen, setMenuOpen] = (0, import_react7.useState)(false);
1703
+ const [confirmOpen, setConfirmOpen] = (0, import_react7.useState)(false);
1704
+ const [ended, setEnded] = (0, import_react7.useState)(false);
1705
+ const rootId = (0, import_react7.useId)();
1706
+ const titleId = `${rootId}-title`;
1707
+ const menuId = `${rootId}-menu`;
1708
+ const confirmTitleId = `${rootId}-confirm-title`;
1709
+ const confirmTextId = `${rootId}-confirm-text`;
1710
+ const listRef = (0, import_react7.useRef)(null);
1711
+ const inputRef = (0, import_react7.useRef)(null);
1712
+ const fileInputRef = (0, import_react7.useRef)(null);
1713
+ const menuWrapRef = (0, import_react7.useRef)(null);
1714
+ const moreButtonRef = (0, import_react7.useRef)(null);
1715
+ const cancelRef = (0, import_react7.useRef)(null);
1716
+ const stickToBottomRef = (0, import_react7.useRef)(true);
1717
+ const startingRef = (0, import_react7.useRef)(null);
568
1718
  const {
569
1719
  session,
570
1720
  messages,
@@ -573,7 +1723,8 @@ function SupportChat({
573
1723
  error,
574
1724
  sendMessage,
575
1725
  startSession,
576
- endSession
1726
+ endSession,
1727
+ reconnect
577
1728
  } = useChatSession({
578
1729
  autoConnect,
579
1730
  metadata,
@@ -583,238 +1734,724 @@ function SupportChat({
583
1734
  persistSession,
584
1735
  storageKey
585
1736
  });
586
- (0, import_react6.useEffect)(() => {
587
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
588
- }, [messages]);
589
- const handleSubmit = async (e) => {
590
- e.preventDefault();
591
- if (!messageInput.trim() && attachments.length === 0) return;
592
- try {
593
- await sendMessage(
594
- messageInput,
595
- attachments.length > 0 ? attachments : void 0
1737
+ const sseFailed = error?.code === "SSE_ERROR";
1738
+ const ownSendError = !!error && error === sendError;
1739
+ const startFailed = !session && !!error && !isConnecting && !ownSendError;
1740
+ const streamDown = !!session && !isConnected && !sseFailed;
1741
+ const showConnecting = useDelayedFlag(
1742
+ isConnecting || streamDown,
1743
+ STATUS_DELAY_MS
1744
+ );
1745
+ const showReconnectBanner = useDelayedFlag(
1746
+ streamDown && !isConnecting,
1747
+ BANNER_DELAY_MS
1748
+ );
1749
+ const status = sseFailed || startFailed ? "offline" : showConnecting ? "connecting" : "online";
1750
+ const statusLabel = status === "offline" ? labels.offline : status === "connecting" ? session ? labels.reconnecting : labels.connecting : statusText ? `${labels.online} \xB7 ${statusText}` : labels.online;
1751
+ const displayMessages = (0, import_react7.useMemo)(() => {
1752
+ const fromServer = messages.map(
1753
+ (m, i) => ({
1754
+ key: m.id || `m-${i}`,
1755
+ author: m.author === "user" ? "user" : "agent",
1756
+ content: typeof m.content === "string" ? m.content : "",
1757
+ timestamp: isValidDate(m.timestamp) ? m.timestamp : new Date(m.timestamp),
1758
+ authorName: m.author === "user" ? void 0 : m.authorData?.name || title,
1759
+ avatarUrl: m.author === "user" ? void 0 : m.authorData?.avatarUrl,
1760
+ attachments: toDisplayAttachments(m.attachments, baseUrl),
1761
+ status: "sent",
1762
+ message: m
1763
+ })
1764
+ );
1765
+ const local = pending.map((p) => ({
1766
+ key: p.localId,
1767
+ localId: p.localId,
1768
+ author: "user",
1769
+ content: p.content,
1770
+ timestamp: p.timestamp,
1771
+ attachments: p.files.map((f, i) => ({
1772
+ key: `${p.localId}-${i}`,
1773
+ name: f.name,
1774
+ size: f.size,
1775
+ type: f.type
1776
+ })),
1777
+ status: p.status
1778
+ }));
1779
+ return [...fromServer, ...local];
1780
+ }, [messages, pending, title, baseUrl]);
1781
+ const sections = (0, import_react7.useMemo)(
1782
+ () => groupMessages(displayMessages),
1783
+ [displayMessages]
1784
+ );
1785
+ const lastMessage = displayMessages[displayMessages.length - 1];
1786
+ useIsomorphicLayoutEffect(() => {
1787
+ const el = listRef.current;
1788
+ if (!el) return;
1789
+ if (stickToBottomRef.current || lastMessage?.localId) {
1790
+ el.scrollTop = el.scrollHeight;
1791
+ }
1792
+ }, [lastMessage?.key, lastMessage?.status, displayMessages.length]);
1793
+ const handleScroll = () => {
1794
+ const el = listRef.current;
1795
+ if (!el) return;
1796
+ stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
1797
+ };
1798
+ const handleMediaLoad = () => {
1799
+ const el = listRef.current;
1800
+ if (el && stickToBottomRef.current) el.scrollTop = el.scrollHeight;
1801
+ };
1802
+ useIsomorphicLayoutEffect(() => {
1803
+ const el = inputRef.current;
1804
+ if (!el) return;
1805
+ el.style.height = "auto";
1806
+ el.style.height = `${Math.min(el.scrollHeight, 140)}px`;
1807
+ }, [text]);
1808
+ const deliver = (0, import_react7.useCallback)(
1809
+ async (item) => {
1810
+ setPending(
1811
+ (prev) => prev.map(
1812
+ (p) => p.localId === item.localId ? { ...p, status: "sending" } : p
1813
+ )
596
1814
  );
597
- setMessageInput("");
598
- setAttachments([]);
599
- } catch (err) {
600
- console.error("Failed to send message:", err);
1815
+ try {
1816
+ if (!session) {
1817
+ if (!startingRef.current) {
1818
+ startingRef.current = startSession().finally(() => {
1819
+ startingRef.current = null;
1820
+ });
1821
+ }
1822
+ await startingRef.current;
1823
+ }
1824
+ await sendMessage(
1825
+ item.content,
1826
+ item.files.length > 0 ? item.files : void 0
1827
+ );
1828
+ setPending((prev) => prev.filter((p) => p.localId !== item.localId));
1829
+ } catch (err) {
1830
+ setSendError(err);
1831
+ setPending(
1832
+ (prev) => prev.map(
1833
+ (p) => p.localId === item.localId ? { ...p, status: "failed" } : p
1834
+ )
1835
+ );
1836
+ }
1837
+ },
1838
+ [session, startSession, sendMessage]
1839
+ );
1840
+ const send = (raw, attached) => {
1841
+ const content = raw.trim() || attached.map((f) => f.name).join(", ");
1842
+ if (!content) return;
1843
+ const item = createPendingMessage(content, attached);
1844
+ stickToBottomRef.current = true;
1845
+ setEnded(false);
1846
+ setPending((prev) => [...prev, item]);
1847
+ void deliver(item);
1848
+ };
1849
+ const canSend = (!!text.trim() || files.length > 0) && !isConnecting;
1850
+ const handleSubmit = (e) => {
1851
+ e?.preventDefault();
1852
+ if (!canSend) return;
1853
+ send(text, files);
1854
+ setText("");
1855
+ setFiles([]);
1856
+ inputRef.current?.focus();
1857
+ };
1858
+ const handleKeyDown = (e) => {
1859
+ if (e.key !== "Enter" || e.shiftKey || e.altKey) return;
1860
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
1861
+ if (typeof window !== "undefined" && window.matchMedia?.("(hover: none) and (pointer: coarse)").matches) {
1862
+ return;
601
1863
  }
1864
+ e.preventDefault();
1865
+ handleSubmit();
602
1866
  };
603
1867
  const handleFileChange = (e) => {
604
- if (e.target.files) {
605
- setAttachments(Array.from(e.target.files));
1868
+ const selected = e.target.files ? Array.from(e.target.files) : [];
1869
+ if (selected.length > 0) {
1870
+ setFiles((prev) => [...prev, ...selected].slice(0, MAX_FILES));
606
1871
  }
1872
+ e.target.value = "";
607
1873
  };
608
- const formatTimestamp = (date) => {
609
- if (!date || isNaN(date.getTime())) {
610
- return "";
611
- }
612
- try {
613
- return new Intl.DateTimeFormat("en-US", {
614
- hour: "numeric",
615
- minute: "2-digit",
616
- hour12: true
617
- }).format(date);
618
- } catch (error2) {
619
- console.warn("Failed to format timestamp:", error2);
620
- return "";
1874
+ const retry = (localId) => {
1875
+ const item = pending.find((p) => p.localId === localId);
1876
+ if (item) void deliver(item);
1877
+ };
1878
+ const removePending = (localId) => {
1879
+ setPending((prev) => prev.filter((p) => p.localId !== localId));
1880
+ };
1881
+ (0, import_react7.useEffect)(() => {
1882
+ if (!menuOpen) return;
1883
+ const onPointerDown = (e) => {
1884
+ if (!menuWrapRef.current?.contains(e.target)) {
1885
+ setMenuOpen(false);
1886
+ }
1887
+ };
1888
+ document.addEventListener("pointerdown", onPointerDown);
1889
+ return () => document.removeEventListener("pointerdown", onPointerDown);
1890
+ }, [menuOpen]);
1891
+ (0, import_react7.useEffect)(() => {
1892
+ if (confirmOpen) cancelRef.current?.focus();
1893
+ }, [confirmOpen]);
1894
+ const closeConfirm = () => {
1895
+ setConfirmOpen(false);
1896
+ moreButtonRef.current?.focus();
1897
+ };
1898
+ const confirmEnd = async () => {
1899
+ setConfirmOpen(false);
1900
+ await endSession();
1901
+ setPending([]);
1902
+ setEnded(true);
1903
+ inputRef.current?.focus();
1904
+ };
1905
+ const handleRootKeyDown = (e) => {
1906
+ if (e.key !== "Escape") return;
1907
+ if (confirmOpen) {
1908
+ e.stopPropagation();
1909
+ closeConfirm();
1910
+ } else if (menuOpen) {
1911
+ e.stopPropagation();
1912
+ setMenuOpen(false);
1913
+ moreButtonRef.current?.focus();
1914
+ } else if (onMinimize) {
1915
+ e.stopPropagation();
1916
+ onMinimize();
621
1917
  }
622
1918
  };
623
- if (!session && !isConnecting) {
624
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1919
+ let banner = null;
1920
+ if (sseFailed || startFailed) {
1921
+ banner = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
625
1922
  "div",
626
1923
  {
627
- className: `channlize-chat-container ${className}`,
628
- style: styles.container,
1924
+ className: "channlize-banner channlize-error",
1925
+ "data-tone": "error",
1926
+ role: "alert",
1927
+ style: styles.error,
629
1928
  children: [
630
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "channlize-chat-header", style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: title }) }),
631
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "channlize-chat-start", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: startSession, style: styles.button, children: "Start Chat" }) }),
632
- error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "channlize-error", style: styles.error, children: error.message })
1929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, { size: 16 }),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-banner-text", children: sseFailed ? labels.connectionLost : labels.startFailed }),
1931
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1932
+ "button",
1933
+ {
1934
+ type: "button",
1935
+ className: "channlize-banner-action",
1936
+ onClick: () => sseFailed ? reconnect() : void startSession(),
1937
+ children: labels.retry
1938
+ }
1939
+ )
1940
+ ]
1941
+ }
1942
+ );
1943
+ } else if (error && !ownSendError) {
1944
+ banner = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1945
+ "div",
1946
+ {
1947
+ className: "channlize-banner channlize-error",
1948
+ "data-tone": "error",
1949
+ role: "alert",
1950
+ style: styles.error,
1951
+ children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, { size: 16 }),
1953
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-banner-text", children: error.message })
633
1954
  ]
634
1955
  }
635
1956
  );
1957
+ } else if (showReconnectBanner) {
1958
+ banner = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-banner", role: "status", children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-spinner", "aria-hidden": "true" }),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-banner-text", children: labels.reconnecting })
1961
+ ] });
636
1962
  }
637
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1963
+ const accent = primaryColor || DEFAULT_PRIMARY2;
1964
+ const themeVars = {
1965
+ ...primaryColor && { "--chz-primary": primaryColor },
1966
+ "--chz-on-primary": primaryTextColor || readableTextColor(accent)
1967
+ };
1968
+ const headerAvatar = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1969
+ Avatar,
1970
+ {
1971
+ name: title,
1972
+ url: avatarUrl,
1973
+ node: avatar,
1974
+ className: "channlize-header-avatar"
1975
+ }
1976
+ );
1977
+ const counterVisible = text.length > maxLength * 0.8;
1978
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
638
1979
  "div",
639
1980
  {
640
- className: `channlize-chat-container ${className}`,
641
- style: styles.container,
1981
+ className: `channlize-widget channlize-chat-container ${className}`,
1982
+ "data-theme": theme,
1983
+ style: { ...themeVars, ...styles.container },
1984
+ onKeyDown: handleRootKeyDown,
642
1985
  children: [
643
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "channlize-chat-header", style: styles.header, children: [
644
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: title }),
645
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
646
- "div",
647
- {
648
- className: "channlize-chat-status",
649
- style: { display: "flex", alignItems: "center", gap: "0.5rem" },
650
- children: [
651
- isConnecting && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
652
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
653
- "span",
654
- {
655
- style: {
656
- display: "inline-block",
657
- width: "8px",
658
- height: "8px",
659
- borderRadius: "50%",
660
- backgroundColor: "#f59e0b"
661
- }
662
- }
663
- ),
664
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Connecting..." })
665
- ] }),
666
- isConnected && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
667
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
668
- "span",
669
- {
670
- style: {
671
- display: "inline-block",
672
- width: "8px",
673
- height: "8px",
674
- borderRadius: "50%",
675
- backgroundColor: "#10b981"
676
- }
677
- }
678
- ),
679
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Connected" })
680
- ] }),
681
- !isConnected && !isConnecting && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
682
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
683
- "span",
1986
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WidgetStyles, { ifMissing: true }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-chat-header", style: styles.header, children: [
1988
+ headerAvatar,
1989
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-header-text", children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "channlize-chat-title", id: titleId, children: title }),
1991
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1992
+ "p",
1993
+ {
1994
+ className: "channlize-chat-status",
1995
+ "data-status": status,
1996
+ title: statusLabel,
1997
+ children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-status-dot", "aria-hidden": "true" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: statusLabel })
2000
+ ]
2001
+ }
2002
+ )
2003
+ ] }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-header-actions", children: [
2005
+ allowEndChat && session && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-menu-wrap", ref: menuWrapRef, children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2007
+ "button",
2008
+ {
2009
+ ref: moreButtonRef,
2010
+ type: "button",
2011
+ className: "channlize-icon-btn",
2012
+ "aria-label": labels.moreOptions,
2013
+ title: labels.moreOptions,
2014
+ "aria-haspopup": "menu",
2015
+ "aria-expanded": menuOpen,
2016
+ "aria-controls": menuOpen ? menuId : void 0,
2017
+ onClick: () => setMenuOpen((o) => !o),
2018
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MoreIcon, { size: 20 })
2019
+ }
2020
+ ),
2021
+ menuOpen && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2022
+ "div",
2023
+ {
2024
+ className: "channlize-menu",
2025
+ role: "menu",
2026
+ id: menuId,
2027
+ "aria-label": labels.moreOptions,
2028
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2029
+ "button",
684
2030
  {
685
- style: {
686
- display: "inline-block",
687
- width: "8px",
688
- height: "8px",
689
- borderRadius: "50%",
690
- backgroundColor: "#ef4444"
691
- }
2031
+ type: "button",
2032
+ role: "menuitem",
2033
+ className: "channlize-menu-item",
2034
+ autoFocus: true,
2035
+ onClick: () => {
2036
+ setMenuOpen(false);
2037
+ setConfirmOpen(true);
2038
+ },
2039
+ children: [
2040
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExitIcon, { size: 16 }),
2041
+ labels.endChat
2042
+ ]
692
2043
  }
693
- ),
694
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Disconnected" })
695
- ] })
696
- ]
697
- }
698
- ),
699
- session && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
700
- "button",
701
- {
702
- onClick: endSession,
703
- style: { ...styles.button, fontSize: "0.875rem" },
704
- type: "button",
705
- children: "End Chat"
706
- }
707
- )
2044
+ )
2045
+ }
2046
+ )
2047
+ ] }),
2048
+ onMinimize && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2049
+ "button",
2050
+ {
2051
+ type: "button",
2052
+ className: "channlize-icon-btn",
2053
+ "aria-label": labels.minimize,
2054
+ title: labels.minimize,
2055
+ onClick: onMinimize,
2056
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronDownIcon, { size: 22 })
2057
+ }
2058
+ )
2059
+ ] })
708
2060
  ] }),
709
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "channlize-messages", style: styles.messagesContainer, children: [
710
- messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", padding: "2rem", color: "#666" }, children: "No messages yet. Start the conversation!" }),
711
- messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
712
- "div",
713
- {
714
- className: `channlize-message ${message.author === "user" ? "channlize-message-user" : "channlize-message-agent"}`,
715
- style: {
716
- ...styles.message,
717
- ...message.author === "user" ? styles.userMessage : styles.agentMessage
718
- },
719
- children: [
720
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "channlize-message-content", children: message.content }),
721
- showTimestamps && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2061
+ banner,
2062
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2063
+ "div",
2064
+ {
2065
+ ref: listRef,
2066
+ className: "channlize-messages channlize-chat-messages",
2067
+ style: styles.messagesContainer,
2068
+ role: "log",
2069
+ "aria-live": "polite",
2070
+ "aria-relevant": "additions",
2071
+ "aria-label": labels.messages,
2072
+ tabIndex: 0,
2073
+ onScroll: handleScroll,
2074
+ onLoadCapture: handleMediaLoad,
2075
+ children: displayMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-welcome", children: [
2076
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Avatar, { name: title, url: avatarUrl, node: avatar }),
2077
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "channlize-welcome-title", children: greeting }),
2078
+ welcomeMessage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "channlize-welcome-text", children: welcomeMessage }),
2079
+ ended && !session && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "channlize-notice", role: "status", children: labels.conversationEnded }),
2080
+ suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-suggestions", children: suggestions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2081
+ "button",
2082
+ {
2083
+ type: "button",
2084
+ className: "channlize-chip",
2085
+ disabled: isConnecting,
2086
+ onClick: () => send(s, []),
2087
+ children: s
2088
+ },
2089
+ s
2090
+ )) })
2091
+ ] }) : sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-day-section", children: [
2092
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-day", role: "separator", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: formatDayLabel(section.date, labels, locale) }) }),
2093
+ section.groups.map((group) => {
2094
+ const last = group.messages[group.messages.length - 1];
2095
+ const isUser = group.author === "user";
2096
+ const speaker = isUser ? labels.you : group.authorName;
2097
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
722
2098
  "div",
723
2099
  {
724
- className: "channlize-message-timestamp",
725
- style: styles.timestamp,
726
- children: formatTimestamp(message.timestamp)
727
- }
728
- )
729
- ]
730
- },
731
- message.id || index
732
- )),
733
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: messagesEndRef })
734
- ] }),
735
- error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "channlize-error", style: styles.error, children: error.message }),
736
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2100
+ className: "channlize-group",
2101
+ "data-author": group.author,
2102
+ children: [
2103
+ !isUser && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2104
+ Avatar,
2105
+ {
2106
+ name: group.authorName,
2107
+ url: group.avatarUrl || (group.authorName === title ? avatarUrl : void 0),
2108
+ node: group.authorName === title ? avatar : void 0,
2109
+ className: "channlize-group-avatar"
2110
+ }
2111
+ ),
2112
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-group-body", children: [
2113
+ !isUser && group.authorName && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2114
+ "div",
2115
+ {
2116
+ className: "channlize-group-name",
2117
+ "aria-hidden": "true",
2118
+ children: group.authorName
2119
+ }
2120
+ ),
2121
+ group.messages.map((msg, i) => {
2122
+ const n = group.messages.length;
2123
+ const pos = n === 1 ? "single" : i === 0 ? "first" : i === n - 1 ? "last" : "middle";
2124
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2125
+ MessageBubble,
2126
+ {
2127
+ msg,
2128
+ pos,
2129
+ speaker,
2130
+ locale,
2131
+ labels,
2132
+ styles,
2133
+ onRetry: retry,
2134
+ onRemove: removePending
2135
+ },
2136
+ msg.key
2137
+ );
2138
+ }),
2139
+ showTimestamps && last && last.status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2140
+ "div",
2141
+ {
2142
+ className: "channlize-message-timestamp",
2143
+ style: styles.timestamp,
2144
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2145
+ "time",
2146
+ {
2147
+ dateTime: isValidDate(last.timestamp) ? last.timestamp.toISOString() : void 0,
2148
+ children: formatTime(last.timestamp, locale)
2149
+ }
2150
+ )
2151
+ }
2152
+ )
2153
+ ] })
2154
+ ]
2155
+ },
2156
+ group.key
2157
+ );
2158
+ })
2159
+ ] }, section.key))
2160
+ }
2161
+ ),
2162
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
737
2163
  "form",
738
2164
  {
739
2165
  className: "channlize-chat-input-container",
740
2166
  style: styles.inputContainer,
741
2167
  onSubmit: handleSubmit,
742
2168
  children: [
743
- allowAttachments && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
744
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
745
- "input",
746
- {
747
- type: "file",
748
- multiple: true,
749
- onChange: handleFileChange,
750
- style: { fontSize: "0.875rem" }
751
- }
752
- ),
753
- attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: "0.75rem", marginTop: "0.25rem" }, children: [
754
- attachments.length,
755
- " file(s) selected"
756
- ] })
757
- ] }),
758
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
759
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
760
- "input",
761
- {
762
- type: "text",
763
- value: messageInput,
764
- onChange: (e) => setMessageInput(e.target.value),
765
- placeholder,
766
- disabled: !session || isConnecting,
767
- style: { ...styles.input, flex: 1 }
768
- }
769
- ),
770
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2169
+ files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-pending-files", children: files.map((f, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "channlize-pending-file", children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { title: f.name, children: f.name }),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
771
2172
  "button",
772
2173
  {
773
- type: "submit",
774
- disabled: !session || isConnecting || !messageInput.trim() && attachments.length === 0,
775
- style: styles.button,
776
- children: sendButtonText
2174
+ type: "button",
2175
+ "aria-label": `${labels.removeAttachment}: ${f.name}`,
2176
+ onClick: () => setFiles((prev) => prev.filter((_, j) => j !== i)),
2177
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { size: 14 })
777
2178
  }
778
2179
  )
779
- ] })
2180
+ ] }, `${f.name}-${i}`)) }),
2181
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2182
+ "div",
2183
+ {
2184
+ className: "channlize-composer",
2185
+ "data-has-attach": allowAttachments ? "true" : void 0,
2186
+ children: [
2187
+ allowAttachments && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2188
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2189
+ "button",
2190
+ {
2191
+ type: "button",
2192
+ className: "channlize-icon-btn",
2193
+ "aria-label": labels.attach,
2194
+ title: labels.attach,
2195
+ onClick: () => fileInputRef.current?.click(),
2196
+ disabled: files.length >= MAX_FILES,
2197
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PaperclipIcon, { size: 18 })
2198
+ }
2199
+ ),
2200
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2201
+ "input",
2202
+ {
2203
+ ref: fileInputRef,
2204
+ type: "file",
2205
+ multiple: true,
2206
+ className: "channlize-file-input",
2207
+ tabIndex: -1,
2208
+ "aria-hidden": "true",
2209
+ onChange: handleFileChange
2210
+ }
2211
+ )
2212
+ ] }),
2213
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2214
+ "textarea",
2215
+ {
2216
+ ref: inputRef,
2217
+ className: "channlize-input",
2218
+ rows: 1,
2219
+ value: text,
2220
+ maxLength,
2221
+ onChange: (e) => setText(e.target.value),
2222
+ onKeyDown: handleKeyDown,
2223
+ placeholder,
2224
+ "aria-label": placeholder,
2225
+ enterKeyHint: "send",
2226
+ style: styles.input
2227
+ }
2228
+ ),
2229
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2230
+ "button",
2231
+ {
2232
+ type: "submit",
2233
+ className: "channlize-send",
2234
+ disabled: !canSend,
2235
+ "aria-label": sendButtonText,
2236
+ title: sendButtonText,
2237
+ style: styles.button,
2238
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SendIcon, { size: 18 })
2239
+ }
2240
+ )
2241
+ ]
2242
+ }
2243
+ ),
2244
+ counterVisible && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2245
+ "div",
2246
+ {
2247
+ className: "channlize-composer-footer",
2248
+ "data-over": text.length >= maxLength ? "true" : void 0,
2249
+ "aria-live": "polite",
2250
+ children: [
2251
+ text.length,
2252
+ "/",
2253
+ maxLength,
2254
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "channlize-visually-hidden", children: [
2255
+ " ",
2256
+ labels.charactersLeft
2257
+ ] })
2258
+ ]
2259
+ }
2260
+ )
780
2261
  ]
781
2262
  }
2263
+ ),
2264
+ confirmOpen && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2265
+ "div",
2266
+ {
2267
+ className: "channlize-sheet-backdrop",
2268
+ onClick: (e) => {
2269
+ if (e.target === e.currentTarget) closeConfirm();
2270
+ },
2271
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2272
+ "div",
2273
+ {
2274
+ className: "channlize-sheet",
2275
+ role: "alertdialog",
2276
+ "aria-modal": "true",
2277
+ "aria-labelledby": confirmTitleId,
2278
+ "aria-describedby": confirmTextId,
2279
+ children: [
2280
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "channlize-sheet-title", id: confirmTitleId, children: labels.endChatTitle }),
2281
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "channlize-sheet-text", id: confirmTextId, children: labels.endChatDescription }),
2282
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "channlize-sheet-actions", children: [
2283
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2284
+ "button",
2285
+ {
2286
+ ref: cancelRef,
2287
+ type: "button",
2288
+ className: "channlize-btn channlize-btn-secondary",
2289
+ onClick: closeConfirm,
2290
+ children: labels.cancel
2291
+ }
2292
+ ),
2293
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2294
+ "button",
2295
+ {
2296
+ type: "button",
2297
+ className: "channlize-btn channlize-btn-danger",
2298
+ onClick: () => void confirmEnd(),
2299
+ children: labels.endChatConfirm
2300
+ }
2301
+ )
2302
+ ] })
2303
+ ]
2304
+ }
2305
+ )
2306
+ }
782
2307
  )
783
2308
  ]
784
2309
  }
785
2310
  );
786
2311
  }
2312
+ function MessageBubble({
2313
+ msg,
2314
+ pos,
2315
+ speaker,
2316
+ locale,
2317
+ labels,
2318
+ styles,
2319
+ onRetry,
2320
+ onRemove
2321
+ }) {
2322
+ const isUser = msg.author === "user";
2323
+ const parts = (0, import_react7.useMemo)(() => linkify(msg.content), [msg.content]);
2324
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2326
+ "div",
2327
+ {
2328
+ className: `channlize-message ${isUser ? "channlize-message-user" : "channlize-message-agent"}`,
2329
+ "data-pos": pos,
2330
+ "data-status": msg.status,
2331
+ title: formatFullDate(msg.timestamp, locale) || void 0,
2332
+ style: {
2333
+ ...styles.message,
2334
+ ...isUser ? styles.userMessage : styles.agentMessage
2335
+ },
2336
+ children: [
2337
+ speaker && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "channlize-visually-hidden", children: [
2338
+ speaker,
2339
+ ": "
2340
+ ] }),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-message-content", children: parts.map(
2342
+ (part, i) => part.type === "link" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2343
+ "a",
2344
+ {
2345
+ href: part.href,
2346
+ target: "_blank",
2347
+ rel: "noopener noreferrer nofollow",
2348
+ children: part.value
2349
+ },
2350
+ i
2351
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: part.value }, i)
2352
+ ) }),
2353
+ msg.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-attachments", children: msg.attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AttachmentView, { attachment: a }, a.key)) })
2354
+ ]
2355
+ }
2356
+ ),
2357
+ msg.status === "sending" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-message-meta", "data-status": "sending", children: labels.sending }),
2358
+ msg.status === "failed" && msg.localId && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2359
+ "div",
2360
+ {
2361
+ className: "channlize-message-meta",
2362
+ "data-status": "failed",
2363
+ role: "alert",
2364
+ children: [
2365
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, { size: 14 }),
2366
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: labels.failed }),
2367
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
2368
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2369
+ "button",
2370
+ {
2371
+ type: "button",
2372
+ className: "channlize-link-btn",
2373
+ onClick: () => onRetry(msg.localId),
2374
+ children: labels.retry
2375
+ }
2376
+ ),
2377
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
2378
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2379
+ "button",
2380
+ {
2381
+ type: "button",
2382
+ className: "channlize-link-btn",
2383
+ onClick: () => onRemove(msg.localId),
2384
+ children: labels.remove
2385
+ }
2386
+ )
2387
+ ]
2388
+ }
2389
+ )
2390
+ ] });
2391
+ }
2392
+ function AttachmentView({ attachment }) {
2393
+ const size = formatFileSize(attachment.size);
2394
+ if (attachment.url && isImage(attachment)) {
2395
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2396
+ "a",
2397
+ {
2398
+ className: "channlize-attachment-image",
2399
+ href: attachment.url,
2400
+ target: "_blank",
2401
+ rel: "noopener noreferrer",
2402
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2403
+ "img",
2404
+ {
2405
+ src: attachment.url,
2406
+ alt: attachment.name,
2407
+ loading: "lazy",
2408
+ referrerPolicy: "no-referrer"
2409
+ }
2410
+ )
2411
+ }
2412
+ );
2413
+ }
2414
+ const body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2415
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileIcon, { size: 18 }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "channlize-attachment-text", children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-attachment-name", children: attachment.name }),
2418
+ size && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "channlize-attachment-size", children: size })
2419
+ ] })
2420
+ ] });
2421
+ return attachment.url ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2422
+ "a",
2423
+ {
2424
+ className: "channlize-attachment-file",
2425
+ href: attachment.url,
2426
+ target: "_blank",
2427
+ rel: "noopener noreferrer",
2428
+ download: attachment.name,
2429
+ children: body
2430
+ }
2431
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "channlize-attachment-file", children: body });
2432
+ }
787
2433
 
788
2434
  // src/components/ChannlizeChat.tsx
789
- var import_react7 = require("react");
790
- var import_jsx_runtime5 = require("react/jsx-runtime");
2435
+ var import_react8 = require("react");
2436
+ var import_react_dom = require("react-dom");
2437
+ var import_jsx_runtime7 = require("react/jsx-runtime");
791
2438
  var emptySubscribe = () => () => {
792
2439
  };
793
- function subscribeDarkMode(onStoreChange) {
794
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
795
- mediaQuery.addEventListener("change", onStoreChange);
796
- return () => mediaQuery.removeEventListener("change", onStoreChange);
797
- }
798
- function getDarkModeSnapshot() {
799
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
800
- }
801
2440
  function ChannlizeChat({
802
2441
  config,
803
2442
  position = "bottom-right",
804
- offset = { x: 24, y: 24 },
2443
+ offset,
805
2444
  buttonElement,
806
2445
  buttonIcon,
807
2446
  buttonSize = 56,
808
- buttonColors = {
809
- background: "#3b82f6",
810
- hover: "#2563eb",
811
- text: "#ffffff"
812
- },
2447
+ buttonColors,
813
2448
  chatProps = {},
2449
+ primaryColor,
2450
+ theme,
814
2451
  defaultOpen = false,
815
2452
  showBadge = false,
816
2453
  unreadCount = 0,
817
- zIndex = 1e3,
2454
+ zIndex = 2147483e3,
818
2455
  showOnMobile = true,
819
2456
  onOpen,
820
2457
  onClose,
@@ -822,294 +2459,157 @@ function ChannlizeChat({
822
2459
  buttonClassName = "",
823
2460
  chatClassName = ""
824
2461
  }) {
825
- const [isOpen, setIsOpen] = (0, import_react7.useState)(defaultOpen);
826
- const isMounted = (0, import_react7.useSyncExternalStore)(
2462
+ const [isOpen, setIsOpen] = (0, import_react8.useState)(defaultOpen);
2463
+ const [autoUnread, setAutoUnread] = (0, import_react8.useState)(0);
2464
+ const launcherRef = (0, import_react8.useRef)(null);
2465
+ const panelRef = (0, import_react8.useRef)(null);
2466
+ const isOpenRef = (0, import_react8.useRef)(isOpen);
2467
+ const userOnMessageRef = (0, import_react8.useRef)(chatProps.onMessage);
2468
+ const isMounted = (0, import_react8.useSyncExternalStore)(
827
2469
  emptySubscribe,
828
2470
  () => true,
829
2471
  () => false
830
2472
  );
831
- const isDarkMode = (0, import_react7.useSyncExternalStore)(
832
- subscribeDarkMode,
833
- getDarkModeSnapshot,
834
- () => false
835
- );
2473
+ (0, import_react8.useEffect)(() => {
2474
+ isOpenRef.current = isOpen;
2475
+ userOnMessageRef.current = chatProps.onMessage;
2476
+ });
2477
+ const handleMessage = (0, import_react8.useCallback)((message) => {
2478
+ if (!isOpenRef.current && message.author === "agent") {
2479
+ setAutoUnread((n) => n + 1);
2480
+ }
2481
+ userOnMessageRef.current?.(message);
2482
+ }, []);
2483
+ const open = (0, import_react8.useCallback)(() => {
2484
+ setIsOpen(true);
2485
+ setAutoUnread(0);
2486
+ onOpen?.();
2487
+ }, [onOpen]);
2488
+ const close = (0, import_react8.useCallback)(() => {
2489
+ const hadFocus = !!panelRef.current?.contains(document.activeElement);
2490
+ setIsOpen(false);
2491
+ onClose?.();
2492
+ if (hadFocus) {
2493
+ requestAnimationFrame(() => launcherRef.current?.focus());
2494
+ }
2495
+ }, [onClose]);
2496
+ (0, import_react8.useEffect)(() => {
2497
+ const panel = panelRef.current;
2498
+ if (!panel) return;
2499
+ panel.inert = !isOpen;
2500
+ if (!isOpen) return;
2501
+ const frame = requestAnimationFrame(() => {
2502
+ const coarse = window.matchMedia?.("(pointer: coarse)").matches;
2503
+ const input = panel.querySelector("textarea");
2504
+ if (input && !coarse) input.focus();
2505
+ else panel.focus();
2506
+ });
2507
+ return () => cancelAnimationFrame(frame);
2508
+ }, [isOpen, isMounted]);
836
2509
  if (!isMounted) {
837
2510
  return null;
838
2511
  }
839
- const handleToggle = () => {
840
- const newState = !isOpen;
841
- setIsOpen(newState);
842
- if (newState) {
843
- onOpen?.();
844
- } else {
845
- onClose?.();
2512
+ const accent = primaryColor || chatProps.primaryColor;
2513
+ const launcherBg = buttonColors?.background || accent;
2514
+ const badgeCount = showBadge ? unreadCount : autoUnread;
2515
+ const title = chatProps.title || "Support";
2516
+ const rootStyle = {
2517
+ "--chz-x": `${offset?.x ?? 24}px`,
2518
+ "--chz-y": `${offset?.y ?? 24}px`,
2519
+ "--chz-z": String(zIndex),
2520
+ "--chz-launcher-size": `${buttonSize}px`,
2521
+ ...accent && {
2522
+ "--chz-primary": accent,
2523
+ "--chz-on-primary": chatProps.primaryTextColor || readableTextColor(accent)
2524
+ },
2525
+ ...launcherBg && { "--chz-launcher-bg": launcherBg },
2526
+ ...buttonColors?.hover && { "--chz-launcher-hover": buttonColors.hover },
2527
+ ...(buttonColors?.text || launcherBg) && {
2528
+ "--chz-launcher-fg": buttonColors?.text || readableTextColor(launcherBg)
846
2529
  }
847
2530
  };
848
- const positionStyles = {
849
- position: "fixed",
850
- zIndex,
851
- ...position.includes("bottom") ? { bottom: `${offset.y}px` } : { top: `${offset.y}px` },
852
- ...position.includes("right") ? { right: `${offset.x}px` } : { left: `${offset.x}px` }
853
- };
854
- const buttonStyle = {
855
- width: `${buttonSize}px`,
856
- height: `${buttonSize}px`,
857
- borderRadius: "50%",
858
- backgroundColor: buttonColors.background,
859
- color: buttonColors.text,
860
- border: "none",
861
- cursor: "pointer",
862
- display: "flex",
863
- alignItems: "center",
864
- justifyContent: "center",
865
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
866
- transition: enableAnimations ? "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)" : "none",
867
- transform: enableAnimations && isOpen ? "scale(0.9)" : "scale(1)"
868
- };
869
- const chatContainerStyle = {
870
- ...positionStyles,
871
- opacity: isOpen ? 1 : 0,
872
- visibility: isOpen ? "visible" : "hidden",
873
- transform: enableAnimations ? isOpen ? "scale(1) translateY(0)" : "scale(0.95) translateY(10px)" : "none",
874
- transition: enableAnimations ? "opacity 0.2s, transform 0.2s, visibility 0.2s" : "none"
2531
+ const launcherLabel = badgeCount > 0 ? `Open chat, ${badgeCount} unread ${badgeCount === 1 ? "message" : "messages"}` : "Open chat";
2532
+ const badge = badgeCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "channlize-badge", "aria-hidden": "true", children: badgeCount > 99 ? "99+" : badgeCount }) : null;
2533
+ const onCustomLauncherKey = (e) => {
2534
+ if (e.key === "Enter" || e.key === " ") {
2535
+ e.preventDefault();
2536
+ open();
2537
+ }
875
2538
  };
876
- const defaultIcon = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
877
- "svg",
2539
+ const widget = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChannlizeProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WidgetStylesProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2540
+ "div",
878
2541
  {
879
- width: buttonSize * 0.5,
880
- height: buttonSize * 0.5,
881
- viewBox: "0 0 24 24",
882
- fill: "none",
883
- stroke: "currentColor",
884
- strokeWidth: "2",
885
- strokeLinecap: "round",
886
- strokeLinejoin: "round",
887
- children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
888
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
889
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
890
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
891
- /* @__PURE__ */ (0, import_jsx_runtime5.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" }),
892
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M8 10h.01" }),
893
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 10h.01" }),
894
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M16 10h.01" })
895
- ] })
896
- }
897
- );
898
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ChannlizeProvider, { config, children: [
899
- !isOpen && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
900
- "div",
901
- {
902
- style: {
903
- ...positionStyles,
904
- display: !showOnMobile && typeof window !== "undefined" && window.innerWidth < 768 ? "none" : "block"
905
- },
906
- className: buttonClassName,
907
- children: buttonElement ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { onClick: handleToggle, style: { cursor: "pointer" }, children: buttonElement }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
908
- "button",
2542
+ className: "channlize-widget channlize-floating",
2543
+ "data-theme": theme || chatProps.theme || "auto",
2544
+ "data-position": position,
2545
+ "data-open": isOpen ? "true" : "false",
2546
+ "data-animate": enableAnimations ? "true" : "false",
2547
+ "data-hide-mobile": showOnMobile ? void 0 : "true",
2548
+ style: rootStyle,
2549
+ children: [
2550
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `channlize-launcher-wrap ${buttonClassName}`, children: buttonElement ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2551
+ "div",
909
2552
  {
910
- onClick: handleToggle,
911
- style: buttonStyle,
912
- onMouseEnter: (e) => {
913
- if (enableAnimations) {
914
- e.currentTarget.style.backgroundColor = buttonColors.hover || buttonColors.background || "#2563eb";
915
- e.currentTarget.style.transform = "scale(1.05)";
916
- }
2553
+ ref: (el) => {
2554
+ launcherRef.current = el;
917
2555
  },
918
- onMouseLeave: (e) => {
919
- if (enableAnimations) {
920
- e.currentTarget.style.backgroundColor = buttonColors.background || "#3b82f6";
921
- e.currentTarget.style.transform = "scale(1)";
922
- }
2556
+ className: "channlize-launcher-custom",
2557
+ role: "button",
2558
+ tabIndex: isOpen ? -1 : 0,
2559
+ "aria-label": launcherLabel,
2560
+ "aria-expanded": isOpen,
2561
+ onClick: open,
2562
+ onKeyDown: onCustomLauncherKey,
2563
+ children: buttonElement
2564
+ }
2565
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2566
+ "button",
2567
+ {
2568
+ ref: (el) => {
2569
+ launcherRef.current = el;
923
2570
  },
924
- "aria-label": isOpen ? "Close chat" : "Open chat",
2571
+ type: "button",
2572
+ className: "channlize-launcher",
2573
+ onClick: open,
2574
+ "aria-label": launcherLabel,
2575
+ "aria-expanded": isOpen,
2576
+ tabIndex: isOpen ? -1 : 0,
925
2577
  children: [
926
- buttonIcon || defaultIcon,
927
- showBadge && unreadCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
928
- "span",
929
- {
930
- style: {
931
- position: "absolute",
932
- top: "-4px",
933
- right: "-4px",
934
- backgroundColor: "#ef4444",
935
- color: "white",
936
- borderRadius: "10px",
937
- padding: "2px 6px",
938
- fontSize: "12px",
939
- fontWeight: "600",
940
- minWidth: "20px",
941
- textAlign: "center",
942
- boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
943
- },
944
- children: unreadCount > 99 ? "99+" : unreadCount
945
- }
946
- )
2578
+ buttonIcon || /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChatIcon, { size: Math.round(buttonSize * 0.46) }),
2579
+ badge
947
2580
  ]
948
2581
  }
949
- )
950
- }
951
- ),
952
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: chatContainerStyle, className: chatClassName, children: [
953
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
954
- SupportChat,
955
- {
956
- autoConnect: isOpen,
957
- persistSession: true,
958
- title: "Support Chat",
959
- placeholder: "Type your message...",
960
- showTimestamps: true,
961
- ...chatProps,
962
- styles: {
963
- container: {
964
- width: "384px",
965
- height: "600px",
966
- maxHeight: "calc(100vh - 120px)",
967
- borderRadius: "16px",
968
- overflow: "hidden",
969
- boxShadow: isDarkMode ? "0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.5)" : "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
970
- backgroundColor: isDarkMode ? "#1f2937" : "#ffffff",
971
- display: "flex",
972
- flexDirection: "column",
973
- position: "relative",
974
- ...chatProps.styles?.container
975
- },
976
- header: {
977
- background: "linear-gradient(135deg, #3b82f6 0%, #9333ea 100%)",
978
- padding: "20px",
979
- borderRadius: "16px 16px 0 0",
980
- color: "white",
981
- display: "flex",
982
- alignItems: "center",
983
- justifyContent: "space-between",
984
- ...chatProps.styles?.header
985
- },
986
- messagesContainer: {
987
- flex: 1,
988
- overflowY: "auto",
989
- padding: "20px",
990
- backgroundColor: isDarkMode ? "#111827" : "#f9fafb",
991
- ...chatProps.styles?.messagesContainer
992
- },
993
- inputContainer: {
994
- padding: "16px",
995
- backgroundColor: isDarkMode ? "#1f2937" : "white",
996
- borderTop: `1px solid ${isDarkMode ? "#374151" : "#e5e7eb"}`,
997
- ...chatProps.styles?.inputContainer
998
- },
999
- input: {
1000
- ...chatProps.styles?.input,
1001
- borderRadius: "12px",
1002
- padding: "12px 16px",
1003
- fontSize: "14px",
1004
- border: `1px solid ${isDarkMode ? "#374151" : "#d1d5db"}`,
1005
- outline: "none",
1006
- backgroundColor: isDarkMode ? "#374151" : "#ffffff",
1007
- color: isDarkMode ? "#f9fafb" : "#111827",
1008
- fontWeight: "400"
1009
- },
1010
- button: {
1011
- backgroundColor: "#3b82f6",
1012
- color: "white",
1013
- borderRadius: "12px",
1014
- padding: "12px 24px",
1015
- fontSize: "14px",
1016
- fontWeight: "500",
1017
- border: "none",
1018
- cursor: "pointer",
1019
- transition: "background-color 0.2s",
1020
- ...chatProps.styles?.button
1021
- },
1022
- userMessage: {
1023
- backgroundColor: "#3b82f6",
1024
- color: "white",
1025
- padding: "10px 14px",
1026
- borderRadius: "12px 12px 4px 12px",
1027
- marginBottom: "8px",
1028
- marginLeft: "auto",
1029
- maxWidth: "80%",
1030
- width: "fit-content",
1031
- ...chatProps.styles?.userMessage
1032
- },
1033
- agentMessage: {
1034
- backgroundColor: isDarkMode ? "#374151" : "white",
1035
- color: isDarkMode ? "#f9fafb" : "#1f2937",
1036
- padding: "10px 14px",
1037
- borderRadius: "12px 12px 12px 4px",
1038
- marginBottom: "8px",
1039
- marginRight: "auto",
1040
- maxWidth: "80%",
1041
- width: "fit-content",
1042
- boxShadow: isDarkMode ? "0 1px 2px 0 rgb(0 0 0 / 0.3)" : "0 1px 2px 0 rgb(0 0 0 / 0.05)",
1043
- ...chatProps.styles?.agentMessage
1044
- },
1045
- timestamp: {
1046
- fontSize: "11px",
1047
- opacity: 0.7,
1048
- marginTop: "4px",
1049
- color: isDarkMode ? "#d1d5db" : "inherit",
1050
- ...chatProps.styles?.timestamp
1051
- },
1052
- error: {
1053
- backgroundColor: isDarkMode ? "#7f1d1d" : "#fee2e2",
1054
- color: isDarkMode ? "#fca5a5" : "#991b1b",
1055
- padding: "12px",
1056
- margin: "8px 16px",
1057
- borderRadius: "8px",
1058
- fontSize: "14px",
1059
- ...chatProps.styles?.error
1060
- }
2582
+ ) }),
2583
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2584
+ "div",
2585
+ {
2586
+ ref: panelRef,
2587
+ className: `channlize-panel ${chatClassName}`,
2588
+ role: "dialog",
2589
+ "aria-modal": "false",
2590
+ "aria-label": title,
2591
+ "aria-hidden": !isOpen,
2592
+ tabIndex: -1,
2593
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2594
+ SupportChat,
2595
+ {
2596
+ persistSession: true,
2597
+ title,
2598
+ showTimestamps: true,
2599
+ ...chatProps,
2600
+ theme: theme || chatProps.theme,
2601
+ primaryColor: accent,
2602
+ autoConnect: isOpen,
2603
+ onMessage: handleMessage,
2604
+ onMinimize: close
2605
+ }
2606
+ )
1061
2607
  }
1062
- }
1063
- ),
1064
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1065
- "button",
1066
- {
1067
- onClick: handleToggle,
1068
- style: {
1069
- position: "absolute",
1070
- top: "16px",
1071
- right: "16px",
1072
- width: "32px",
1073
- height: "32px",
1074
- borderRadius: "50%",
1075
- backgroundColor: "rgba(255, 255, 255, 0.2)",
1076
- border: "none",
1077
- cursor: "pointer",
1078
- display: "flex",
1079
- alignItems: "center",
1080
- justifyContent: "center",
1081
- color: "white",
1082
- transition: "background-color 0.2s",
1083
- zIndex: 10
1084
- },
1085
- onMouseEnter: (e) => {
1086
- e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
1087
- },
1088
- onMouseLeave: (e) => {
1089
- e.currentTarget.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
1090
- },
1091
- "aria-label": "Close chat",
1092
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1093
- "svg",
1094
- {
1095
- width: "16",
1096
- height: "16",
1097
- viewBox: "0 0 24 24",
1098
- fill: "none",
1099
- stroke: "currentColor",
1100
- strokeWidth: "2",
1101
- strokeLinecap: "round",
1102
- strokeLinejoin: "round",
1103
- children: [
1104
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1105
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1106
- ]
1107
- }
1108
- )
1109
- }
1110
- )
1111
- ] })
1112
- ] });
2608
+ )
2609
+ ]
2610
+ }
2611
+ ) }) });
2612
+ return typeof document !== "undefined" ? (0, import_react_dom.createPortal)(widget, document.body) : widget;
1113
2613
  }
1114
2614
  // Annotate the CommonJS export names for ESM import in node:
1115
2615
  0 && (module.exports = {
@@ -1117,6 +2617,8 @@ function ChannlizeChat({
1117
2617
  ChannlizeProvider,
1118
2618
  ContactForm,
1119
2619
  SupportChat,
2620
+ defaultContactFormLabels,
2621
+ defaultSupportChatLabels,
1120
2622
  useChannlize,
1121
2623
  useChannlizeAnalytics,
1122
2624
  useChatSession,