hermes-chat-react 0.1.2 → 0.1.3

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/react.js CHANGED
@@ -1,6 +1,117 @@
1
1
  import {
2
2
  HermesClient
3
- } from "./chunk-OMLFDWYU.js";
3
+ } from "./chunk-LJYB6LS7.js";
4
+
5
+ // src/react/context/ChatContext.tsx
6
+ import { createContext, useContext } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var ChatContext = createContext(
9
+ void 0
10
+ );
11
+ var ChatProvider = ({
12
+ children,
13
+ value
14
+ }) => /* @__PURE__ */ jsx(ChatContext.Provider, { value, children });
15
+ var useChatContext = (componentName) => {
16
+ const contextValue = useContext(ChatContext);
17
+ if (!contextValue) {
18
+ console.warn(
19
+ `useChatContext was called outside of ChatProvider. Make sure this hook is called within a child of the <Chat> component.${componentName ? ` Errored in: ${componentName}` : ""}`
20
+ );
21
+ return {};
22
+ }
23
+ return contextValue;
24
+ };
25
+
26
+ // src/react/context/RoomStateContext.tsx
27
+ import { createContext as createContext2, useContext as useContext2 } from "react";
28
+ import { jsx as jsx2 } from "react/jsx-runtime";
29
+ var RoomStateContext = createContext2(void 0);
30
+ var RoomStateProvider = ({
31
+ children,
32
+ value
33
+ }) => /* @__PURE__ */ jsx2(RoomStateContext.Provider, { value, children });
34
+ var useRoomStateContext = (componentName) => {
35
+ const contextValue = useContext2(RoomStateContext);
36
+ if (!contextValue) {
37
+ console.warn(
38
+ `useRoomStateContext was called outside of RoomStateProvider. Make sure this hook is called within a child of the <Room> component.${componentName ? ` Errored in: ${componentName}` : ""}`
39
+ );
40
+ return {};
41
+ }
42
+ return contextValue;
43
+ };
44
+
45
+ // src/react/context/RoomActionContext.tsx
46
+ import { createContext as createContext3, useContext as useContext3 } from "react";
47
+ import { jsx as jsx3 } from "react/jsx-runtime";
48
+ var RoomActionContext = createContext3(void 0);
49
+ var RoomActionProvider = ({
50
+ children,
51
+ value
52
+ }) => /* @__PURE__ */ jsx3(RoomActionContext.Provider, { value, children });
53
+ var useRoomActionContext = (componentName) => {
54
+ const contextValue = useContext3(RoomActionContext);
55
+ if (!contextValue) {
56
+ console.warn(
57
+ `useRoomActionContext was called outside of RoomActionProvider. Make sure this hook is called within a child of the <Room> component.${componentName ? ` Errored in: ${componentName}` : ""}`
58
+ );
59
+ return {};
60
+ }
61
+ return contextValue;
62
+ };
63
+
64
+ // src/react/context/MessageContext.tsx
65
+ import { createContext as createContext4, useContext as useContext4 } from "react";
66
+ import { jsx as jsx4 } from "react/jsx-runtime";
67
+ var MessageContext = createContext4(
68
+ void 0
69
+ );
70
+ var MessageProvider = ({
71
+ children,
72
+ value
73
+ }) => /* @__PURE__ */ jsx4(MessageContext.Provider, { value, children });
74
+ var useMessageContext = (componentName) => {
75
+ const contextValue = useContext4(MessageContext);
76
+ if (!contextValue) {
77
+ console.warn(
78
+ `useMessageContext was called outside of MessageProvider.${componentName ? ` Errored in: ${componentName}` : ""}`
79
+ );
80
+ return {};
81
+ }
82
+ return contextValue;
83
+ };
84
+
85
+ // src/react/context/ComponentContext.tsx
86
+ import { createContext as createContext5, useContext as useContext5 } from "react";
87
+ import { jsx as jsx5 } from "react/jsx-runtime";
88
+ var ComponentContext = createContext5({});
89
+ var ComponentProvider = ({
90
+ children,
91
+ value
92
+ }) => /* @__PURE__ */ jsx5(ComponentContext.Provider, { value, children });
93
+ var useComponentContext = (_componentName) => useContext5(ComponentContext);
94
+
95
+ // src/react/context/TypingContext.tsx
96
+ import { createContext as createContext6, useContext as useContext6 } from "react";
97
+ import { jsx as jsx6 } from "react/jsx-runtime";
98
+ var TypingContext = createContext6(
99
+ void 0
100
+ );
101
+ var TypingProvider = ({
102
+ children,
103
+ value
104
+ }) => /* @__PURE__ */ jsx6(TypingContext.Provider, { value, children });
105
+ var useTypingContext = (componentName) => {
106
+ const contextValue = useContext6(TypingContext);
107
+ if (!contextValue) {
108
+ console.warn(
109
+ `useTypingContext was called outside of TypingProvider.${componentName ? ` Errored in: ${componentName}` : ""}`
110
+ );
111
+ return {};
112
+ }
113
+ return contextValue;
114
+ };
4
115
 
5
116
  // src/react/hooks/useMessages.ts
6
117
  import { useState, useEffect, useCallback, useRef } from "react";
@@ -544,152 +655,617 @@ var useUpload = (client) => {
544
655
  return { upload, sendFile, validate, uploading, error, lastUpload };
545
656
  };
546
657
 
547
- // src/react/components/MessageList.tsx
548
- import { useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
549
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
550
- var formatTime = (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
551
- var formatFileSize = (bytes) => {
552
- if (!bytes) return "";
553
- if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
554
- if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
555
- return `${bytes} B`;
556
- };
557
- var REACTION_EMOJIS = [
558
- "\u{1FAE0}",
559
- "\u{1F979}",
560
- "\u{1FAE1}",
561
- "\u{1F90C}",
562
- "\u{1FAF6}",
563
- "\u{1F480}",
564
- "\u{1F525}",
565
- "\u2728",
566
- "\u{1FAE3}",
567
- "\u{1F62E}\u200D\u{1F4A8}",
568
- "\u{1FA84}",
569
- "\u{1F972}",
570
- "\u{1F485}",
571
- "\u{1FAE6}",
572
- "\u{1F92F}",
573
- "\u{1F31A}",
574
- "\u{1F441}\uFE0F",
575
- "\u{1FAC0}",
576
- "\u{1F98B}",
577
- "\u{1FA90}"
578
- ];
579
- var EmojiPicker = ({ onPick, onClose, isOwn }) => {
580
- const ref = useRef4(null);
658
+ // src/react/components/Chat/Chat.tsx
659
+ import { useState as useState7, useEffect as useEffect6, useCallback as useCallback8, useMemo } from "react";
660
+ import { jsx as jsx7 } from "react/jsx-runtime";
661
+ var Chat = ({
662
+ client,
663
+ theme = "light",
664
+ customClasses,
665
+ initialNavOpen = false,
666
+ children
667
+ }) => {
668
+ const [activeRoom, setActiveRoom] = useState7(void 0);
669
+ const [navOpen, setNavOpen] = useState7(initialNavOpen);
670
+ const [currentUser, setCurrentUser] = useState7(
671
+ client.currentUser
672
+ );
581
673
  useEffect6(() => {
582
- const handler = (e) => {
583
- if (ref.current && !ref.current.contains(e.target)) onClose();
674
+ if (client.currentUser) {
675
+ setCurrentUser(client.currentUser);
676
+ }
677
+ const onConnected = () => setCurrentUser(client.currentUser);
678
+ client.on("connected", onConnected);
679
+ return () => {
680
+ client.off("connected", onConnected);
584
681
  };
585
- document.addEventListener("mousedown", handler);
586
- return () => document.removeEventListener("mousedown", handler);
587
- }, [onClose]);
588
- return /* @__PURE__ */ jsx(
682
+ }, [client]);
683
+ const openMobileNav = useCallback8(() => setNavOpen(true), []);
684
+ const closeMobileNav = useCallback8(() => setNavOpen(false), []);
685
+ const handleSetActiveRoom = useCallback8(
686
+ (room) => {
687
+ setActiveRoom(room);
688
+ setNavOpen(false);
689
+ },
690
+ []
691
+ );
692
+ const chatContextValue = useMemo(
693
+ () => ({
694
+ client,
695
+ currentUser,
696
+ theme,
697
+ activeRoom,
698
+ setActiveRoom: handleSetActiveRoom,
699
+ openMobileNav,
700
+ closeMobileNav,
701
+ navOpen,
702
+ customClasses
703
+ }),
704
+ [
705
+ client,
706
+ currentUser,
707
+ theme,
708
+ activeRoom,
709
+ handleSetActiveRoom,
710
+ openMobileNav,
711
+ closeMobileNav,
712
+ navOpen,
713
+ customClasses
714
+ ]
715
+ );
716
+ const containerClass = customClasses?.chat || `hermes-chat hermes-chat--${theme}`;
717
+ return /* @__PURE__ */ jsx7(ChatProvider, { value: chatContextValue, children: /* @__PURE__ */ jsx7("div", { className: containerClass, children }) });
718
+ };
719
+
720
+ // src/react/components/Room/Room.tsx
721
+ import { useState as useState8, useCallback as useCallback9, useMemo as useMemo2, useEffect as useEffect7, useRef as useRef4 } from "react";
722
+ import { jsx as jsx8 } from "react/jsx-runtime";
723
+ var Room = ({
724
+ roomId,
725
+ children,
726
+ // Component overrides forwarded to ComponentContext
727
+ Avatar: Avatar2,
728
+ Message: MessageOverride,
729
+ MessageStatus: MessageStatus2,
730
+ MessageActions: MessageActions2,
731
+ DateSeparator: DateSeparator2,
732
+ EmptyStateIndicator: EmptyStateIndicator2,
733
+ LoadingIndicator: LoadingIndicator2,
734
+ LoadingErrorIndicator: LoadingErrorIndicator2,
735
+ ReactionPicker: ReactionPicker2,
736
+ TypingIndicator: TypingIndicator2,
737
+ MediaMessage: MediaMessage2,
738
+ ThreadHeader: ThreadHeader2,
739
+ Modal: Modal2,
740
+ ChatInput: ChatInput2,
741
+ RoomListItem,
742
+ Search: Search2,
743
+ OnlineBadge: OnlineBadge2
744
+ }) => {
745
+ const { client, customClasses } = useChatContext("Room");
746
+ const [messages, setMessages] = useState8([]);
747
+ const [loading, setLoading] = useState8(true);
748
+ const [loadingMore, setLoadingMore] = useState8(false);
749
+ const [hasMore, setHasMore] = useState8(false);
750
+ const [error, setError] = useState8(null);
751
+ const oldestMessageId = useRef4(void 0);
752
+ const [thread, setThread] = useState8(null);
753
+ const [threadMessages, setThreadMessages] = useState8([]);
754
+ const [threadHasMore, setThreadHasMore] = useState8(false);
755
+ const [threadLoadingMore, setThreadLoadingMore] = useState8(false);
756
+ const [typingUsers, setTypingUsers] = useState8(
757
+ /* @__PURE__ */ new Map()
758
+ );
759
+ const typingTimeouts = useRef4(
760
+ /* @__PURE__ */ new Map()
761
+ );
762
+ const isTypingRef = useRef4(false);
763
+ const stopTypingTimeout = useRef4(null);
764
+ useEffect7(() => {
765
+ if (!roomId || !client?.isConnected) return;
766
+ setMessages([]);
767
+ setHasMore(false);
768
+ setThread(null);
769
+ setThreadMessages([]);
770
+ oldestMessageId.current = void 0;
771
+ setLoading(true);
772
+ setError(null);
773
+ client.getHistory(roomId).then(({ messages: msgs, hasMore: more }) => {
774
+ setMessages(msgs);
775
+ setHasMore(more);
776
+ if (msgs.length > 0) oldestMessageId.current = msgs[0]._id;
777
+ }).catch((err) => setError(err.message)).finally(() => setLoading(false));
778
+ }, [roomId, client?.isConnected]);
779
+ useEffect7(() => {
780
+ if (!roomId || !client) return;
781
+ const onReceive = (msg) => {
782
+ if (msg.roomId !== roomId) return;
783
+ if (msg.threadParentId && thread && msg.threadParentId === thread._id) {
784
+ setThreadMessages(
785
+ (prev) => prev.find((m) => m._id === msg._id) ? prev : [...prev, msg]
786
+ );
787
+ setMessages(
788
+ (prev) => prev.map(
789
+ (m) => m._id === msg.threadParentId ? { ...m, replyCount: (m.replyCount || 0) + 1 } : m
790
+ )
791
+ );
792
+ return;
793
+ }
794
+ setMessages(
795
+ (prev) => prev.find((m) => m._id === msg._id) ? prev : [...prev, msg]
796
+ );
797
+ };
798
+ const onDeleted = ({ messageId }) => {
799
+ setMessages(
800
+ (prev) => prev.map(
801
+ (m) => m._id === messageId ? { ...m, isDeleted: true, text: void 0 } : m
802
+ )
803
+ );
804
+ setThreadMessages(
805
+ (prev) => prev.map(
806
+ (m) => m._id === messageId ? { ...m, isDeleted: true, text: void 0 } : m
807
+ )
808
+ );
809
+ };
810
+ const onEdited = (msg) => {
811
+ setMessages((prev) => prev.map((m) => m._id === msg._id ? msg : m));
812
+ setThreadMessages(
813
+ (prev) => prev.map((m) => m._id === msg._id ? msg : m)
814
+ );
815
+ };
816
+ const onReaction = ({ messageId, reactions }) => {
817
+ setMessages(
818
+ (prev) => prev.map((m) => m._id === messageId ? { ...m, reactions } : m)
819
+ );
820
+ setThreadMessages(
821
+ (prev) => prev.map((m) => m._id === messageId ? { ...m, reactions } : m)
822
+ );
823
+ };
824
+ client.on("message:receive", onReceive);
825
+ client.on("message:deleted", onDeleted);
826
+ client.on("message:edited", onEdited);
827
+ client.on("reaction:updated", onReaction);
828
+ return () => {
829
+ client.off("message:receive", onReceive);
830
+ client.off("message:deleted", onDeleted);
831
+ client.off("message:edited", onEdited);
832
+ client.off("reaction:updated", onReaction);
833
+ };
834
+ }, [roomId, client, thread]);
835
+ useEffect7(() => {
836
+ if (!roomId || !client) return;
837
+ const onStart = (event) => {
838
+ if (event.roomId !== roomId) return;
839
+ if (event.userId === client.currentUser?.userId) return;
840
+ setTypingUsers(
841
+ (prev) => new Map(prev).set(event.userId, event.displayName)
842
+ );
843
+ const existing = typingTimeouts.current.get(event.userId);
844
+ if (existing) clearTimeout(existing);
845
+ const t = setTimeout(() => {
846
+ setTypingUsers((prev) => {
847
+ const next = new Map(prev);
848
+ next.delete(event.userId);
849
+ return next;
850
+ });
851
+ }, 4e3);
852
+ typingTimeouts.current.set(event.userId, t);
853
+ };
854
+ const onStop = (event) => {
855
+ if (event.roomId !== roomId) return;
856
+ setTypingUsers((prev) => {
857
+ const next = new Map(prev);
858
+ next.delete(event.userId);
859
+ return next;
860
+ });
861
+ const existing = typingTimeouts.current.get(event.userId);
862
+ if (existing) clearTimeout(existing);
863
+ };
864
+ client.on("typing:started", onStart);
865
+ client.on("typing:stopped", onStop);
866
+ return () => {
867
+ client.off("typing:started", onStart);
868
+ client.off("typing:stopped", onStop);
869
+ typingTimeouts.current.forEach(clearTimeout);
870
+ typingTimeouts.current.clear();
871
+ setTypingUsers(/* @__PURE__ */ new Map());
872
+ };
873
+ }, [roomId, client]);
874
+ const sendMessage = useCallback9(
875
+ async (input) => {
876
+ if (!roomId) throw new Error("No room selected");
877
+ return client.sendMessage({ ...input, roomId });
878
+ },
879
+ [roomId, client]
880
+ );
881
+ const editMessage = useCallback9(
882
+ async (messageId, text) => {
883
+ if (!roomId) throw new Error("No room selected");
884
+ return client.editMessage(messageId, roomId, text);
885
+ },
886
+ [roomId, client]
887
+ );
888
+ const deleteMessage = useCallback9(
889
+ async (messageId) => {
890
+ if (!roomId) throw new Error("No room selected");
891
+ return client.deleteMessage(messageId, roomId);
892
+ },
893
+ [roomId, client]
894
+ );
895
+ const addReaction = useCallback9(
896
+ async (messageId, emoji) => {
897
+ if (!roomId) throw new Error("No room selected");
898
+ return client.addReaction(messageId, roomId, emoji);
899
+ },
900
+ [roomId, client]
901
+ );
902
+ const loadMore = useCallback9(async () => {
903
+ if (!roomId || loadingMore || !hasMore) return;
904
+ setLoadingMore(true);
905
+ try {
906
+ const { messages: older, hasMore: more } = await client.getHistory(
907
+ roomId,
908
+ oldestMessageId.current
909
+ );
910
+ setMessages((prev) => [...older, ...prev]);
911
+ setHasMore(more);
912
+ if (older.length > 0) oldestMessageId.current = older[0]._id;
913
+ } catch (err) {
914
+ setError(err.message);
915
+ } finally {
916
+ setLoadingMore(false);
917
+ }
918
+ }, [roomId, loadingMore, hasMore, client]);
919
+ const markRead = useCallback9(
920
+ async (lastMessageId) => {
921
+ if (!roomId) return;
922
+ await client.markSeen(roomId, lastMessageId);
923
+ },
924
+ [roomId, client]
925
+ );
926
+ const openThread = useCallback9((message) => {
927
+ setThread(message);
928
+ setThreadMessages([]);
929
+ setThreadHasMore(false);
930
+ }, []);
931
+ const closeThread = useCallback9(() => {
932
+ setThread(null);
933
+ setThreadMessages([]);
934
+ }, []);
935
+ const loadMoreThread = useCallback9(async () => {
936
+ }, []);
937
+ const startTyping = useCallback9(() => {
938
+ if (!roomId) return;
939
+ if (!isTypingRef.current) {
940
+ client.startTyping(roomId);
941
+ isTypingRef.current = true;
942
+ }
943
+ if (stopTypingTimeout.current) clearTimeout(stopTypingTimeout.current);
944
+ stopTypingTimeout.current = setTimeout(() => {
945
+ client.stopTyping(roomId);
946
+ isTypingRef.current = false;
947
+ }, 3e3);
948
+ }, [roomId, client]);
949
+ const stopTyping = useCallback9(() => {
950
+ if (!roomId) return;
951
+ if (stopTypingTimeout.current) clearTimeout(stopTypingTimeout.current);
952
+ if (isTypingRef.current) {
953
+ client.stopTyping(roomId);
954
+ isTypingRef.current = false;
955
+ }
956
+ }, [roomId, client]);
957
+ const typingText = useMemo2(() => {
958
+ const names = Array.from(typingUsers.values());
959
+ if (names.length === 0) return null;
960
+ if (names.length === 1) return `${names[0]} is typing...`;
961
+ if (names.length === 2) return `${names[0]} and ${names[1]} are typing...`;
962
+ return `${names[0]} and ${names.length - 1} others are typing...`;
963
+ }, [typingUsers]);
964
+ const roomStateValue = useMemo2(
965
+ () => ({
966
+ room: { _id: roomId },
967
+ messages,
968
+ loading,
969
+ loadingMore,
970
+ hasMore,
971
+ error,
972
+ members: [],
973
+ thread,
974
+ threadMessages,
975
+ threadHasMore,
976
+ threadLoadingMore,
977
+ pinnedMessages: messages.filter((m) => m.pinnedAt)
978
+ }),
979
+ [messages, loading, loadingMore, hasMore, error, thread, threadMessages, threadHasMore, threadLoadingMore, roomId]
980
+ );
981
+ const roomActionValue = useMemo2(
982
+ () => ({
983
+ sendMessage,
984
+ editMessage,
985
+ deleteMessage,
986
+ addReaction,
987
+ loadMore,
988
+ markRead,
989
+ openThread,
990
+ closeThread,
991
+ loadMoreThread
992
+ }),
993
+ [sendMessage, editMessage, deleteMessage, addReaction, loadMore, markRead, openThread, closeThread, loadMoreThread]
994
+ );
995
+ const typingValue = useMemo2(
996
+ () => ({
997
+ typingUsers,
998
+ typingText,
999
+ isAnyoneTyping: typingUsers.size > 0,
1000
+ startTyping,
1001
+ stopTyping
1002
+ }),
1003
+ [typingUsers, typingText, startTyping, stopTyping]
1004
+ );
1005
+ const componentOverrides = useMemo2(
1006
+ () => ({
1007
+ Avatar: Avatar2,
1008
+ Message: MessageOverride,
1009
+ MessageStatus: MessageStatus2,
1010
+ MessageActions: MessageActions2,
1011
+ DateSeparator: DateSeparator2,
1012
+ EmptyStateIndicator: EmptyStateIndicator2,
1013
+ LoadingIndicator: LoadingIndicator2,
1014
+ LoadingErrorIndicator: LoadingErrorIndicator2,
1015
+ ReactionPicker: ReactionPicker2,
1016
+ TypingIndicator: TypingIndicator2,
1017
+ MediaMessage: MediaMessage2,
1018
+ ThreadHeader: ThreadHeader2,
1019
+ Modal: Modal2,
1020
+ ChatInput: ChatInput2,
1021
+ RoomListItem,
1022
+ Search: Search2,
1023
+ OnlineBadge: OnlineBadge2
1024
+ }),
1025
+ [Avatar2, MessageOverride, MessageStatus2, MessageActions2, DateSeparator2, EmptyStateIndicator2, LoadingIndicator2, LoadingErrorIndicator2, ReactionPicker2, TypingIndicator2, MediaMessage2, ThreadHeader2, Modal2, ChatInput2, RoomListItem, Search2, OnlineBadge2]
1026
+ );
1027
+ const containerClass = customClasses?.room || "hermes-room";
1028
+ return /* @__PURE__ */ jsx8(RoomStateProvider, { value: roomStateValue, children: /* @__PURE__ */ jsx8(RoomActionProvider, { value: roomActionValue, children: /* @__PURE__ */ jsx8(TypingProvider, { value: typingValue, children: /* @__PURE__ */ jsx8(ComponentProvider, { value: componentOverrides, children: /* @__PURE__ */ jsx8("div", { className: containerClass, children }) }) }) }) });
1029
+ };
1030
+
1031
+ // src/react/components/Window/Window.tsx
1032
+ import { jsx as jsx9 } from "react/jsx-runtime";
1033
+ var Window = ({
1034
+ className = "",
1035
+ children
1036
+ }) => {
1037
+ const { customClasses } = useChatContext("Window");
1038
+ const containerClass = customClasses?.window || `hermes-window ${className}`.trim();
1039
+ return /* @__PURE__ */ jsx9(
589
1040
  "div",
590
1041
  {
591
- ref,
1042
+ className: containerClass,
592
1043
  style: {
593
- position: "absolute",
594
- bottom: "calc(100% + 8px)",
595
- [isOwn ? "right" : "left"]: 0,
596
- zIndex: 100,
597
- background: "#1a1a2e",
598
- border: "1px solid rgba(255,255,255,0.1)",
599
- borderRadius: 14,
600
- padding: "8px 10px",
601
- boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
602
- display: "grid",
603
- gridTemplateColumns: "repeat(5, 1fr)",
604
- gap: 4,
605
- animation: "hermes-pop 0.15s ease"
1044
+ display: "flex",
1045
+ flexDirection: "column",
1046
+ height: "100%",
1047
+ flex: 1,
1048
+ minWidth: 0,
1049
+ overflow: "hidden"
606
1050
  },
607
- children: REACTION_EMOJIS.map((emoji) => /* @__PURE__ */ jsx(
608
- "button",
609
- {
610
- onClick: () => {
611
- onPick(emoji);
612
- onClose();
613
- },
614
- style: {
615
- background: "none",
616
- border: "none",
617
- cursor: "pointer",
618
- fontSize: 20,
619
- padding: "4px",
620
- borderRadius: 8,
621
- lineHeight: 1,
622
- transition: "transform 0.1s, background 0.1s"
623
- },
624
- onMouseEnter: (e) => {
625
- e.currentTarget.style.transform = "scale(1.3)";
626
- e.currentTarget.style.background = "rgba(255,255,255,0.08)";
627
- },
628
- onMouseLeave: (e) => {
629
- e.currentTarget.style.transform = "scale(1)";
630
- e.currentTarget.style.background = "none";
631
- },
632
- children: emoji
633
- },
634
- emoji
635
- ))
1051
+ children
636
1052
  }
637
1053
  );
638
1054
  };
639
- var TypingIndicator = ({ typingUsers }) => {
640
- if (!typingUsers.length) return null;
641
- const text = typingUsers.length === 1 ? `${typingUsers[0].displayName} is typing` : typingUsers.length === 2 ? `${typingUsers[0].displayName} and ${typingUsers[1].displayName} are typing` : `${typingUsers[0].displayName} and ${typingUsers.length - 1} others are typing`;
1055
+
1056
+ // src/react/components/MessageList.tsx
1057
+ import React10, { useEffect as useEffect9, useRef as useRef6 } from "react";
1058
+
1059
+ // src/react/components/Message/Message.tsx
1060
+ import { useState as useState9, useRef as useRef5, useEffect as useEffect8 } from "react";
1061
+
1062
+ // src/react/components/Avatar/Avatar.tsx
1063
+ import { jsx as jsx10, jsxs } from "react/jsx-runtime";
1064
+ var getInitials = (name) => {
1065
+ if (!name) return "?";
1066
+ const parts = name.trim().split(/\s+/);
1067
+ if (parts.length >= 2) return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
1068
+ return name.slice(0, 2).toUpperCase();
1069
+ };
1070
+ var Avatar = ({
1071
+ image,
1072
+ name,
1073
+ size = 36,
1074
+ shape = "circle",
1075
+ className = "",
1076
+ online
1077
+ }) => {
1078
+ const borderRadius = shape === "circle" ? "50%" : shape === "rounded" ? "8px" : "0";
642
1079
  return /* @__PURE__ */ jsxs(
643
1080
  "div",
644
1081
  {
1082
+ className: `hermes-avatar ${className}`,
645
1083
  style: {
646
- display: "flex",
647
- alignItems: "center",
648
- gap: 8,
649
- padding: "6px 16px 2px",
650
- minHeight: 28
1084
+ position: "relative",
1085
+ width: size,
1086
+ height: size,
1087
+ flexShrink: 0
651
1088
  },
652
1089
  children: [
653
- /* @__PURE__ */ jsx(
1090
+ image ? /* @__PURE__ */ jsx10(
1091
+ "img",
1092
+ {
1093
+ src: image,
1094
+ alt: name || "avatar",
1095
+ style: {
1096
+ width: size,
1097
+ height: size,
1098
+ borderRadius,
1099
+ objectFit: "cover",
1100
+ display: "block"
1101
+ }
1102
+ }
1103
+ ) : /* @__PURE__ */ jsx10(
654
1104
  "div",
655
1105
  {
656
1106
  style: {
1107
+ width: size,
1108
+ height: size,
1109
+ borderRadius,
1110
+ background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
657
1111
  display: "flex",
658
1112
  alignItems: "center",
659
- gap: 3,
660
- background: "#f0f0f0",
661
- borderRadius: 12,
662
- padding: "6px 10px"
1113
+ justifyContent: "center",
1114
+ color: "#fff",
1115
+ fontWeight: 700,
1116
+ fontSize: size * 0.38,
1117
+ userSelect: "none"
663
1118
  },
664
- children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
665
- "span",
666
- {
667
- style: {
668
- width: 6,
669
- height: 6,
670
- borderRadius: "50%",
671
- background: "#999",
672
- display: "block",
673
- animation: `hermes-bounce 1.2s ease-in-out ${i * 0.18}s infinite`
674
- }
675
- },
676
- i
677
- ))
1119
+ children: getInitials(name)
678
1120
  }
679
1121
  ),
680
- /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "#999" }, children: text })
1122
+ online !== void 0 && /* @__PURE__ */ jsx10(
1123
+ "span",
1124
+ {
1125
+ style: {
1126
+ position: "absolute",
1127
+ bottom: 0,
1128
+ right: 0,
1129
+ width: size * 0.3,
1130
+ height: size * 0.3,
1131
+ borderRadius: "50%",
1132
+ background: online ? "#22c55e" : "#9ca3af",
1133
+ border: "2px solid #fff"
1134
+ }
1135
+ }
1136
+ )
681
1137
  ]
682
1138
  }
683
1139
  );
684
1140
  };
685
- var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, renderAvatar }) => {
686
- const [hovered, setHovered] = useState7(false);
687
- const [pickerOpen, setPickerOpen] = useState7(false);
688
- if (message.isDeleted) {
689
- return /* @__PURE__ */ jsx(
690
- "div",
691
- {
692
- style: {
1141
+
1142
+ // src/react/components/Message/MessageStatus.tsx
1143
+ import { jsx as jsx11 } from "react/jsx-runtime";
1144
+ var MessageStatus = ({
1145
+ status,
1146
+ seenCount = 0,
1147
+ isMyMessage = true,
1148
+ className = ""
1149
+ }) => {
1150
+ if (!isMyMessage) return null;
1151
+ const color = status === "seen" ? "#0084ff" : "rgba(128,128,128,0.6)";
1152
+ const checks = status === "sent" ? "\u2713" : "\u2713\u2713";
1153
+ return /* @__PURE__ */ jsx11(
1154
+ "span",
1155
+ {
1156
+ className: `hermes-message-status ${className}`,
1157
+ title: status === "seen" ? `Seen by ${seenCount} ${seenCount === 1 ? "person" : "people"}` : status === "delivered" ? "Delivered" : "Sent",
1158
+ style: {
1159
+ fontSize: 11,
1160
+ color,
1161
+ marginLeft: 4,
1162
+ userSelect: "none"
1163
+ },
1164
+ children: checks
1165
+ }
1166
+ );
1167
+ };
1168
+
1169
+ // src/react/components/Message/MessageActions.tsx
1170
+ import { jsx as jsx12, jsxs as jsxs2 } from "react/jsx-runtime";
1171
+ var ActionBtn = ({ onClick, title, children }) => /* @__PURE__ */ jsx12(
1172
+ "button",
1173
+ {
1174
+ onClick,
1175
+ title,
1176
+ style: {
1177
+ background: "#fff",
1178
+ border: "1px solid rgba(0,0,0,0.1)",
1179
+ borderRadius: 8,
1180
+ cursor: "pointer",
1181
+ fontSize: 14,
1182
+ padding: "3px 6px",
1183
+ lineHeight: 1,
1184
+ boxShadow: "0 1px 4px rgba(0,0,0,0.1)",
1185
+ transition: "transform 0.1s"
1186
+ },
1187
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.15)",
1188
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
1189
+ children
1190
+ }
1191
+ );
1192
+ var MessageActions = ({
1193
+ isOwn,
1194
+ isText,
1195
+ hasThread = false,
1196
+ replyCount = 0,
1197
+ onReact,
1198
+ onReply,
1199
+ onThread,
1200
+ onEdit,
1201
+ onDelete,
1202
+ onPin,
1203
+ className = ""
1204
+ }) => /* @__PURE__ */ jsxs2(
1205
+ "div",
1206
+ {
1207
+ className: `hermes-message-actions ${className}`,
1208
+ style: {
1209
+ display: "flex",
1210
+ flexDirection: isOwn ? "row-reverse" : "row",
1211
+ gap: 2
1212
+ },
1213
+ children: [
1214
+ onReact && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onReact, title: "React", children: "\u{1F60A}" }),
1215
+ onReply && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onReply, title: "Reply", children: "\u21A9" }),
1216
+ onThread && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onThread, title: `Thread${replyCount > 0 ? ` (${replyCount})` : ""}`, children: "\u{1F9F5}" }),
1217
+ isOwn && isText && onEdit && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onEdit, title: "Edit", children: "\u270F\uFE0F" }),
1218
+ onPin && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onPin, title: "Pin", children: "\u{1F4CC}" }),
1219
+ isOwn && onDelete && /* @__PURE__ */ jsx12(ActionBtn, { onClick: onDelete, title: "Delete", children: "\u{1F5D1}" })
1220
+ ]
1221
+ }
1222
+ );
1223
+
1224
+ // src/react/components/Message/Message.tsx
1225
+ import { jsx as jsx13, jsxs as jsxs3 } from "react/jsx-runtime";
1226
+ var REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F525}", "\u{1F389}", "\u{1F44F}"];
1227
+ var formatTime = (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
1228
+ var formatFileSize = (bytes) => {
1229
+ if (!bytes) return "";
1230
+ if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1231
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1232
+ return `${bytes} B`;
1233
+ };
1234
+ var Message = ({
1235
+ message,
1236
+ isOwn,
1237
+ onEdit,
1238
+ onDelete,
1239
+ onReact,
1240
+ onReply,
1241
+ onOpenThread,
1242
+ onPin,
1243
+ renderAvatar,
1244
+ senderName,
1245
+ senderImage,
1246
+ groupStyle = "single",
1247
+ className = "",
1248
+ showAvatar = true
1249
+ }) => {
1250
+ const [hovered, setHovered] = useState9(false);
1251
+ const [pickerOpen, setPickerOpen] = useState9(false);
1252
+ const pickerRef = useRef5(null);
1253
+ useEffect8(() => {
1254
+ if (!pickerOpen) return;
1255
+ const handler = (e) => {
1256
+ if (pickerRef.current && !pickerRef.current.contains(e.target)) {
1257
+ setPickerOpen(false);
1258
+ }
1259
+ };
1260
+ document.addEventListener("mousedown", handler);
1261
+ return () => document.removeEventListener("mousedown", handler);
1262
+ }, [pickerOpen]);
1263
+ if (message.isDeleted) {
1264
+ return /* @__PURE__ */ jsx13(
1265
+ "div",
1266
+ {
1267
+ className: `hermes-message hermes-message--deleted ${className}`,
1268
+ style: {
693
1269
  opacity: 0.5,
694
1270
  fontStyle: "italic",
695
1271
  padding: "4px 16px",
@@ -699,40 +1275,24 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
699
1275
  }
700
1276
  );
701
1277
  }
702
- return /* @__PURE__ */ jsxs(
1278
+ const showAvatarSlot = showAvatar && !isOwn && (groupStyle === "bottom" || groupStyle === "single");
1279
+ return /* @__PURE__ */ jsxs3(
703
1280
  "div",
704
1281
  {
1282
+ className: `hermes-message ${isOwn ? "hermes-message--own" : "hermes-message--other"} ${className}`,
705
1283
  onMouseEnter: () => setHovered(true),
706
- onMouseLeave: () => {
707
- setHovered(false);
708
- },
1284
+ onMouseLeave: () => setHovered(false),
709
1285
  style: {
710
1286
  display: "flex",
711
1287
  flexDirection: isOwn ? "row-reverse" : "row",
712
1288
  alignItems: "flex-end",
713
1289
  gap: 8,
714
- marginBottom: 4,
1290
+ marginBottom: groupStyle === "bottom" || groupStyle === "single" ? 8 : 2,
715
1291
  position: "relative"
716
1292
  },
717
1293
  children: [
718
- !isOwn && /* @__PURE__ */ jsx("div", { style: { flexShrink: 0 }, children: renderAvatar ? renderAvatar(message.senderId) : /* @__PURE__ */ jsx(
719
- "div",
720
- {
721
- style: {
722
- width: 32,
723
- height: 32,
724
- borderRadius: "50%",
725
- background: "#e0e0e0",
726
- display: "flex",
727
- alignItems: "center",
728
- justifyContent: "center",
729
- fontSize: 12,
730
- fontWeight: 600
731
- },
732
- children: message.senderId.slice(-2).toUpperCase()
733
- }
734
- ) }),
735
- /* @__PURE__ */ jsxs(
1294
+ !isOwn && /* @__PURE__ */ jsx13("div", { style: { flexShrink: 0, width: 32 }, children: showAvatarSlot ? renderAvatar ? renderAvatar(message.senderId) : /* @__PURE__ */ jsx13(Avatar, { image: senderImage, name: senderName || message.senderId, size: 32 }) : null }),
1295
+ /* @__PURE__ */ jsxs3(
736
1296
  "div",
737
1297
  {
738
1298
  style: {
@@ -742,13 +1302,10 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
742
1302
  alignItems: isOwn ? "flex-end" : "flex-start"
743
1303
  },
744
1304
  children: [
745
- (onEdit || onDelete || onReact || onReply) && /* @__PURE__ */ jsxs(
1305
+ (onEdit || onDelete || onReact || onReply || onOpenThread) && /* @__PURE__ */ jsxs3(
746
1306
  "div",
747
1307
  {
748
1308
  style: {
749
- display: "flex",
750
- flexDirection: isOwn ? "row-reverse" : "row",
751
- gap: 2,
752
1309
  marginBottom: 4,
753
1310
  opacity: hovered ? 1 : 0,
754
1311
  pointerEvents: hovered ? "auto" : "none",
@@ -756,55 +1313,86 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
756
1313
  position: "relative"
757
1314
  },
758
1315
  children: [
759
- onReact && /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
760
- /* @__PURE__ */ jsx(
761
- ActionBtn,
762
- {
763
- onClick: () => setPickerOpen((p) => !p),
764
- title: "React",
765
- children: "\u{1FAE0}"
766
- }
767
- ),
768
- pickerOpen && /* @__PURE__ */ jsx(
769
- EmojiPicker,
770
- {
771
- isOwn,
772
- onPick: (emoji) => onReact(message._id, emoji),
773
- onClose: () => setPickerOpen(false)
774
- }
775
- )
776
- ] }),
777
- onReply && /* @__PURE__ */ jsx(ActionBtn, { onClick: () => onReply(message), title: "Reply", children: "\u21A9" }),
778
- isOwn && onEdit && message.type === "text" && /* @__PURE__ */ jsx(
779
- ActionBtn,
1316
+ /* @__PURE__ */ jsx13(
1317
+ MessageActions,
780
1318
  {
781
- onClick: () => {
1319
+ isOwn,
1320
+ isText: message.type === "text",
1321
+ hasThread: !!message.replyCount && message.replyCount > 0,
1322
+ replyCount: message.replyCount,
1323
+ onReact: onReact ? () => setPickerOpen((p) => !p) : void 0,
1324
+ onReply: onReply ? () => onReply(message) : void 0,
1325
+ onThread: onOpenThread ? () => onOpenThread(message) : void 0,
1326
+ onEdit: onEdit ? () => {
782
1327
  const text = window.prompt("Edit message:", message.text);
783
1328
  if (text) onEdit(message._id, text);
784
- },
785
- title: "Edit",
786
- children: "\u270F\uFE0F"
1329
+ } : void 0,
1330
+ onDelete: onDelete ? () => onDelete(message._id) : void 0,
1331
+ onPin: onPin ? () => onPin(message) : void 0
787
1332
  }
788
1333
  ),
789
- isOwn && onDelete && /* @__PURE__ */ jsx(ActionBtn, { onClick: () => onDelete(message._id), title: "Delete", children: "\u{1F5D1}" })
1334
+ pickerOpen && onReact && /* @__PURE__ */ jsx13(
1335
+ "div",
1336
+ {
1337
+ ref: pickerRef,
1338
+ style: {
1339
+ position: "absolute",
1340
+ bottom: "calc(100% + 4px)",
1341
+ [isOwn ? "right" : "left"]: 0,
1342
+ zIndex: 100,
1343
+ background: "#1a1a2e",
1344
+ border: "1px solid rgba(255,255,255,0.1)",
1345
+ borderRadius: 14,
1346
+ padding: "8px 10px",
1347
+ boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
1348
+ display: "flex",
1349
+ gap: 4,
1350
+ animation: "hermes-pop 0.15s ease"
1351
+ },
1352
+ children: REACTION_EMOJIS.map((emoji) => /* @__PURE__ */ jsx13(
1353
+ "button",
1354
+ {
1355
+ onClick: () => {
1356
+ onReact(message._id, emoji);
1357
+ setPickerOpen(false);
1358
+ },
1359
+ style: {
1360
+ background: "none",
1361
+ border: "none",
1362
+ cursor: "pointer",
1363
+ fontSize: 20,
1364
+ padding: "4px",
1365
+ borderRadius: 8,
1366
+ lineHeight: 1,
1367
+ transition: "transform 0.1s"
1368
+ },
1369
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.3)",
1370
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
1371
+ children: emoji
1372
+ },
1373
+ emoji
1374
+ ))
1375
+ }
1376
+ )
790
1377
  ]
791
1378
  }
792
1379
  ),
793
- /* @__PURE__ */ jsxs(
1380
+ !isOwn && (groupStyle === "top" || groupStyle === "single") && senderName && /* @__PURE__ */ jsx13("span", { style: { fontSize: 11, fontWeight: 600, opacity: 0.6, marginBottom: 2, marginLeft: 4 }, children: senderName }),
1381
+ /* @__PURE__ */ jsxs3(
794
1382
  "div",
795
1383
  {
796
1384
  style: {
797
1385
  padding: "8px 12px",
798
- borderRadius: isOwn ? "16px 16px 4px 16px" : "16px 16px 16px 4px",
1386
+ borderRadius: isOwn ? groupStyle === "top" || groupStyle === "single" ? "16px 16px 4px 16px" : "16px 4px 4px 16px" : groupStyle === "top" || groupStyle === "single" ? "16px 16px 16px 4px" : "4px 16px 16px 4px",
799
1387
  background: isOwn ? "#0084ff" : "#f0f0f0",
800
1388
  color: isOwn ? "#fff" : "#000"
801
1389
  },
802
1390
  children: [
803
- message.replyTo && /* @__PURE__ */ jsx(
1391
+ message.replyTo && /* @__PURE__ */ jsx13(
804
1392
  "div",
805
1393
  {
806
1394
  style: {
807
- borderLeft: "3px solid rgba(255,255,255,0.4)",
1395
+ borderLeft: `3px solid ${isOwn ? "rgba(255,255,255,0.4)" : "rgba(0,132,255,0.4)"}`,
808
1396
  paddingLeft: 8,
809
1397
  marginBottom: 6,
810
1398
  fontSize: 12,
@@ -813,27 +1401,24 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
813
1401
  children: "Replying to a message"
814
1402
  }
815
1403
  ),
816
- message.type === "text" && /* @__PURE__ */ jsxs("p", { style: { margin: 0, wordBreak: "break-word" }, children: [
1404
+ message.type === "text" && /* @__PURE__ */ jsxs3("p", { style: { margin: 0, wordBreak: "break-word" }, children: [
817
1405
  message.text,
818
- message.editedAt && /* @__PURE__ */ jsx("span", { style: { fontSize: 10, opacity: 0.6, marginLeft: 6 }, children: "(edited)" })
1406
+ message.editedAt && /* @__PURE__ */ jsx13("span", { style: { fontSize: 10, opacity: 0.6, marginLeft: 6 }, children: "(edited)" })
819
1407
  ] }),
820
- message.type === "link" && /* @__PURE__ */ jsxs("div", { children: [
821
- message.text && /* @__PURE__ */ jsx("p", { style: { margin: "0 0 4px" }, children: message.text }),
822
- /* @__PURE__ */ jsx(
1408
+ message.type === "link" && /* @__PURE__ */ jsxs3("div", { children: [
1409
+ message.text && /* @__PURE__ */ jsx13("p", { style: { margin: "0 0 4px" }, children: message.text }),
1410
+ /* @__PURE__ */ jsx13(
823
1411
  "a",
824
1412
  {
825
1413
  href: message.url,
826
1414
  target: "_blank",
827
1415
  rel: "noopener noreferrer",
828
- style: {
829
- color: isOwn ? "#cce4ff" : "#0084ff",
830
- wordBreak: "break-all"
831
- },
1416
+ style: { color: isOwn ? "#cce4ff" : "#0084ff", wordBreak: "break-all" },
832
1417
  children: message.url
833
1418
  }
834
1419
  )
835
1420
  ] }),
836
- message.type === "image" && /* @__PURE__ */ jsx(
1421
+ message.type === "image" && /* @__PURE__ */ jsx13(
837
1422
  "img",
838
1423
  {
839
1424
  src: message.url,
@@ -841,16 +1426,17 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
841
1426
  style: { maxWidth: "100%", borderRadius: 8, display: "block" }
842
1427
  }
843
1428
  ),
844
- message.type === "video" && /* @__PURE__ */ jsx(
1429
+ message.type === "video" && /* @__PURE__ */ jsx13(
845
1430
  "video",
846
1431
  {
847
1432
  src: message.url,
1433
+ poster: message.thumbnail,
848
1434
  controls: true,
849
1435
  style: { maxWidth: "100%", borderRadius: 8 }
850
1436
  }
851
1437
  ),
852
- message.type === "audio" && /* @__PURE__ */ jsx("audio", { src: message.url, controls: true, style: { width: "100%" } }),
853
- message.type === "document" && /* @__PURE__ */ jsxs(
1438
+ message.type === "audio" && /* @__PURE__ */ jsx13("audio", { src: message.url, controls: true, style: { width: "100%" } }),
1439
+ message.type === "document" && /* @__PURE__ */ jsxs3(
854
1440
  "a",
855
1441
  {
856
1442
  href: message.url,
@@ -864,67 +1450,90 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
864
1450
  textDecoration: "none"
865
1451
  },
866
1452
  children: [
867
- /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u{1F4C4}" }),
868
- /* @__PURE__ */ jsxs("div", { children: [
869
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, fontSize: 13 }, children: message.fileName }),
870
- /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: formatFileSize(message.fileSize) })
1453
+ /* @__PURE__ */ jsx13("span", { style: { fontSize: 24 }, children: "\u{1F4C4}" }),
1454
+ /* @__PURE__ */ jsxs3("div", { children: [
1455
+ /* @__PURE__ */ jsx13("div", { style: { fontWeight: 600, fontSize: 13 }, children: message.fileName }),
1456
+ /* @__PURE__ */ jsx13("div", { style: { fontSize: 11, opacity: 0.7 }, children: formatFileSize(message.fileSize) })
871
1457
  ] })
872
1458
  ]
873
1459
  }
874
1460
  ),
875
- /* @__PURE__ */ jsx(
1461
+ /* @__PURE__ */ jsxs3(
876
1462
  "div",
877
1463
  {
878
1464
  style: {
1465
+ display: "flex",
1466
+ alignItems: "center",
1467
+ justifyContent: "flex-end",
1468
+ gap: 4,
879
1469
  fontSize: 10,
880
1470
  opacity: 0.6,
881
- textAlign: "right",
882
1471
  marginTop: 4
883
1472
  },
884
- children: formatTime(message.createdAt)
1473
+ children: [
1474
+ formatTime(message.createdAt),
1475
+ message.pinnedAt && /* @__PURE__ */ jsx13("span", { title: "Pinned", children: "\u{1F4CC}" }),
1476
+ isOwn && /* @__PURE__ */ jsx13(
1477
+ MessageStatus,
1478
+ {
1479
+ status: message.deliveryStatus,
1480
+ seenCount: message.seenBy?.length || 0,
1481
+ isMyMessage: isOwn
1482
+ }
1483
+ )
1484
+ ]
885
1485
  }
886
1486
  )
887
1487
  ]
888
1488
  }
889
1489
  ),
890
- message.reactions?.filter((r) => r.users.length > 0).length > 0 && /* @__PURE__ */ jsx(
891
- "div",
1490
+ message.replyCount && message.replyCount > 0 && onOpenThread && /* @__PURE__ */ jsxs3(
1491
+ "button",
892
1492
  {
893
- style: { display: "flex", gap: 4, flexWrap: "wrap", marginTop: 4 },
894
- children: message.reactions.filter((r) => r.users.length > 0).map((r) => /* @__PURE__ */ jsxs(
895
- "span",
896
- {
897
- onClick: () => onReact?.(message._id, r.emoji),
898
- style: {
899
- background: "#f0f0f0",
900
- border: "1px solid rgba(0,0,0,0.08)",
901
- borderRadius: 20,
902
- padding: "2px 8px",
903
- fontSize: 13,
904
- cursor: "pointer",
905
- display: "flex",
906
- alignItems: "center",
907
- gap: 4,
908
- transition: "transform 0.1s",
909
- userSelect: "none"
910
- },
911
- onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
912
- onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
913
- children: [
914
- r.emoji,
915
- /* @__PURE__ */ jsx(
916
- "span",
917
- {
918
- style: { fontSize: 11, fontWeight: 600, color: "#555" },
919
- children: r.users.length
920
- }
921
- )
922
- ]
923
- },
924
- r.emoji
925
- ))
1493
+ onClick: () => onOpenThread(message),
1494
+ style: {
1495
+ background: "none",
1496
+ border: "none",
1497
+ color: "#0084ff",
1498
+ cursor: "pointer",
1499
+ fontSize: 12,
1500
+ fontWeight: 600,
1501
+ padding: "2px 4px",
1502
+ marginTop: 2
1503
+ },
1504
+ children: [
1505
+ message.replyCount,
1506
+ " ",
1507
+ message.replyCount === 1 ? "reply" : "replies"
1508
+ ]
926
1509
  }
927
- )
1510
+ ),
1511
+ message.reactions?.filter((r) => r.users.length > 0).length > 0 && /* @__PURE__ */ jsx13("div", { style: { display: "flex", gap: 4, flexWrap: "wrap", marginTop: 4 }, children: message.reactions.filter((r) => r.users.length > 0).map((r) => /* @__PURE__ */ jsxs3(
1512
+ "span",
1513
+ {
1514
+ onClick: () => onReact?.(message._id, r.emoji),
1515
+ style: {
1516
+ background: "#f0f0f0",
1517
+ border: "1px solid rgba(0,0,0,0.08)",
1518
+ borderRadius: 20,
1519
+ padding: "2px 8px",
1520
+ fontSize: 13,
1521
+ cursor: onReact ? "pointer" : "default",
1522
+ display: "flex",
1523
+ alignItems: "center",
1524
+ gap: 4,
1525
+ transition: "transform 0.1s",
1526
+ userSelect: "none"
1527
+ },
1528
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
1529
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
1530
+ children: [
1531
+ r.emoji,
1532
+ /* @__PURE__ */ jsx13("span", { style: { fontSize: 11, fontWeight: 600, color: "#555" }, children: r.users.length })
1533
+ ]
1534
+ },
1535
+ r.emoji
1536
+ )) })
928
1537
  ]
929
1538
  }
930
1539
  )
@@ -932,52 +1541,277 @@ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, rend
932
1541
  }
933
1542
  );
934
1543
  };
935
- var ActionBtn = ({ onClick, title, children }) => /* @__PURE__ */ jsx(
936
- "button",
1544
+
1545
+ // src/react/components/DateSeparator/DateSeparator.tsx
1546
+ import { jsx as jsx14, jsxs as jsxs4 } from "react/jsx-runtime";
1547
+ var defaultFormat = (date) => {
1548
+ const now = /* @__PURE__ */ new Date();
1549
+ const isToday = date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear();
1550
+ const yesterday = new Date(now);
1551
+ yesterday.setDate(yesterday.getDate() - 1);
1552
+ const isYesterday = date.getDate() === yesterday.getDate() && date.getMonth() === yesterday.getMonth() && date.getFullYear() === yesterday.getFullYear();
1553
+ if (isToday) return "Today";
1554
+ if (isYesterday) return "Yesterday";
1555
+ return date.toLocaleDateString(void 0, {
1556
+ weekday: "long",
1557
+ year: "numeric",
1558
+ month: "long",
1559
+ day: "numeric"
1560
+ });
1561
+ };
1562
+ var DateSeparator = ({
1563
+ date,
1564
+ formatDate = defaultFormat,
1565
+ className = ""
1566
+ }) => /* @__PURE__ */ jsxs4(
1567
+ "div",
937
1568
  {
938
- onClick,
939
- title,
1569
+ className: `hermes-date-separator ${className}`,
940
1570
  style: {
941
- background: "#fff",
942
- border: "1px solid rgba(0,0,0,0.1)",
943
- borderRadius: 8,
944
- cursor: "pointer",
945
- fontSize: 14,
946
- padding: "3px 6px",
947
- lineHeight: 1,
948
- boxShadow: "0 1px 4px rgba(0,0,0,0.1)",
949
- transition: "transform 0.1s"
1571
+ display: "flex",
1572
+ alignItems: "center",
1573
+ gap: 12,
1574
+ padding: "16px 0"
950
1575
  },
951
- onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.15)",
952
- onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
953
- children
1576
+ children: [
1577
+ /* @__PURE__ */ jsx14(
1578
+ "div",
1579
+ {
1580
+ style: { flex: 1, height: 1, background: "rgba(128,128,128,0.2)" }
1581
+ }
1582
+ ),
1583
+ /* @__PURE__ */ jsx14(
1584
+ "span",
1585
+ {
1586
+ style: {
1587
+ fontSize: 12,
1588
+ fontWeight: 600,
1589
+ color: "rgba(128,128,128,0.7)",
1590
+ whiteSpace: "nowrap",
1591
+ userSelect: "none"
1592
+ },
1593
+ children: formatDate(date)
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ jsx14(
1597
+ "div",
1598
+ {
1599
+ style: { flex: 1, height: 1, background: "rgba(128,128,128,0.2)" }
1600
+ }
1601
+ )
1602
+ ]
954
1603
  }
955
1604
  );
956
- var MessageList = ({
957
- messages,
958
- currentUser,
959
- loading = false,
960
- loadingMore = false,
961
- hasMore = false,
962
- onLoadMore,
963
- onEdit,
964
- onDelete,
965
- onReact,
966
- onReply,
967
- renderMessage,
968
- renderAvatar,
969
- className = "",
970
- autoScroll = true,
971
- typingUsers = []
1605
+
1606
+ // src/react/components/EmptyStateIndicator/EmptyStateIndicator.tsx
1607
+ import { jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
1608
+ var DEFAULT_TEXTS = {
1609
+ message: "No messages yet. Say hello! \u{1F44B}",
1610
+ room: "No conversations yet.",
1611
+ thread: "No replies yet.",
1612
+ search: "No results found."
1613
+ };
1614
+ var EmptyStateIndicator = ({
1615
+ listType = "message",
1616
+ text,
1617
+ className = ""
1618
+ }) => /* @__PURE__ */ jsxs5(
1619
+ "div",
1620
+ {
1621
+ className: `hermes-empty-state ${className}`,
1622
+ style: {
1623
+ display: "flex",
1624
+ flexDirection: "column",
1625
+ alignItems: "center",
1626
+ justifyContent: "center",
1627
+ gap: 8,
1628
+ padding: 32,
1629
+ textAlign: "center",
1630
+ opacity: 0.5,
1631
+ flex: 1
1632
+ },
1633
+ children: [
1634
+ /* @__PURE__ */ jsx15("span", { style: { fontSize: 36 }, children: listType === "message" ? "\u{1F4AC}" : listType === "room" ? "\u{1F4ED}" : listType === "thread" ? "\u{1F9F5}" : "\u{1F50D}" }),
1635
+ /* @__PURE__ */ jsx15("span", { style: { fontSize: 14 }, children: text || DEFAULT_TEXTS[listType] })
1636
+ ]
1637
+ }
1638
+ );
1639
+
1640
+ // src/react/components/Loading/LoadingIndicator.tsx
1641
+ import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
1642
+ var LoadingIndicator = ({
1643
+ size = 32,
1644
+ color = "#0084ff",
1645
+ text,
1646
+ className = ""
1647
+ }) => /* @__PURE__ */ jsxs6(
1648
+ "div",
1649
+ {
1650
+ className: `hermes-loading ${className}`,
1651
+ style: {
1652
+ display: "flex",
1653
+ flexDirection: "column",
1654
+ alignItems: "center",
1655
+ justifyContent: "center",
1656
+ gap: 8,
1657
+ padding: 16
1658
+ },
1659
+ children: [
1660
+ /* @__PURE__ */ jsx16(
1661
+ "div",
1662
+ {
1663
+ style: {
1664
+ width: size,
1665
+ height: size,
1666
+ border: `3px solid rgba(128,128,128,0.15)`,
1667
+ borderTopColor: color,
1668
+ borderRadius: "50%",
1669
+ animation: "hermes-spin 0.8s linear infinite"
1670
+ }
1671
+ }
1672
+ ),
1673
+ text && /* @__PURE__ */ jsx16("span", { style: { fontSize: 13, opacity: 0.6 }, children: text }),
1674
+ /* @__PURE__ */ jsx16("style", { children: `
1675
+ @keyframes hermes-spin {
1676
+ to { transform: rotate(360deg); }
1677
+ }
1678
+ ` })
1679
+ ]
1680
+ }
1681
+ );
1682
+ var LoadingErrorIndicator = ({
1683
+ error,
1684
+ onRetry,
1685
+ className = ""
972
1686
  }) => {
973
- const bottomRef = useRef4(null);
974
- const containerRef = useRef4(null);
975
- useEffect6(() => {
1687
+ if (!error) return null;
1688
+ const message = typeof error === "string" ? error : error.message;
1689
+ return /* @__PURE__ */ jsxs6(
1690
+ "div",
1691
+ {
1692
+ className: `hermes-loading-error ${className}`,
1693
+ style: {
1694
+ display: "flex",
1695
+ flexDirection: "column",
1696
+ alignItems: "center",
1697
+ justifyContent: "center",
1698
+ gap: 8,
1699
+ padding: 24,
1700
+ textAlign: "center"
1701
+ },
1702
+ children: [
1703
+ /* @__PURE__ */ jsx16("span", { style: { fontSize: 28 }, children: "\u26A0\uFE0F" }),
1704
+ /* @__PURE__ */ jsx16("span", { style: { fontSize: 14, opacity: 0.7 }, children: message }),
1705
+ onRetry && /* @__PURE__ */ jsx16(
1706
+ "button",
1707
+ {
1708
+ onClick: onRetry,
1709
+ style: {
1710
+ marginTop: 4,
1711
+ padding: "6px 16px",
1712
+ border: "1px solid rgba(128,128,128,0.3)",
1713
+ borderRadius: 8,
1714
+ background: "none",
1715
+ cursor: "pointer",
1716
+ fontSize: 13,
1717
+ fontWeight: 600
1718
+ },
1719
+ children: "Retry"
1720
+ }
1721
+ )
1722
+ ]
1723
+ }
1724
+ );
1725
+ };
1726
+
1727
+ // src/react/components/TypingIndicator.tsx
1728
+ import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1729
+ var TypingIndicator = ({
1730
+ typingText,
1731
+ className = ""
1732
+ }) => {
1733
+ if (!typingText) return null;
1734
+ return /* @__PURE__ */ jsxs7(
1735
+ "div",
1736
+ {
1737
+ className: `hermes-typing-indicator ${className}`,
1738
+ style: {
1739
+ display: "flex",
1740
+ alignItems: "center",
1741
+ gap: 6,
1742
+ padding: "4px 16px",
1743
+ minHeight: 24
1744
+ },
1745
+ children: [
1746
+ /* @__PURE__ */ jsx17("div", { style: { display: "flex", gap: 3 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx17(
1747
+ "span",
1748
+ {
1749
+ style: {
1750
+ width: 6,
1751
+ height: 6,
1752
+ borderRadius: "50%",
1753
+ background: "#aaa",
1754
+ display: "block",
1755
+ animation: `hermes-bounce 1.2s ease-in-out ${i * 0.2}s infinite`
1756
+ }
1757
+ },
1758
+ i
1759
+ )) }),
1760
+ /* @__PURE__ */ jsx17("span", { style: { fontSize: 12, opacity: 0.6 }, children: typingText }),
1761
+ /* @__PURE__ */ jsx17("style", { children: `
1762
+ @keyframes hermes-bounce {
1763
+ 0%, 80%, 100% { transform: translateY(0); }
1764
+ 40% { transform: translateY(-4px); }
1765
+ }
1766
+ ` })
1767
+ ]
1768
+ }
1769
+ );
1770
+ };
1771
+
1772
+ // src/react/components/MessageList.tsx
1773
+ import { Fragment, jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
1774
+ var isSameDay = (d1, d2) => {
1775
+ const a = new Date(d1);
1776
+ const b = new Date(d2);
1777
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1778
+ };
1779
+ var MessageList = (props) => {
1780
+ const chatCtx = useChatContext("MessageList");
1781
+ const roomStateCtx = useRoomStateContext("MessageList");
1782
+ const roomActionCtx = useRoomActionContext("MessageList");
1783
+ const typingCtx = useTypingContext("MessageList");
1784
+ const componentCtx = useComponentContext("MessageList");
1785
+ const messages = props.messages ?? roomStateCtx.messages ?? [];
1786
+ const currentUser = props.currentUser ?? chatCtx.currentUser;
1787
+ const loading = props.loading ?? roomStateCtx.loading ?? false;
1788
+ const loadingMore = props.loadingMore ?? roomStateCtx.loadingMore ?? false;
1789
+ const hasMore = props.hasMore ?? roomStateCtx.hasMore ?? false;
1790
+ const onLoadMore = props.onLoadMore ?? roomActionCtx.loadMore;
1791
+ const onEdit = props.onEdit ?? (roomActionCtx.editMessage ? (id, text) => roomActionCtx.editMessage(id, text) : void 0);
1792
+ const onDelete = props.onDelete ?? (roomActionCtx.deleteMessage ? (id) => roomActionCtx.deleteMessage(id) : void 0);
1793
+ const onReact = props.onReact ?? (roomActionCtx.addReaction ? (id, emoji) => roomActionCtx.addReaction(id, emoji) : void 0);
1794
+ const onReply = props.onReply;
1795
+ const onOpenThread = props.onOpenThread ?? (roomActionCtx.openThread ? (msg) => roomActionCtx.openThread(msg) : void 0);
1796
+ const autoScroll = props.autoScroll ?? true;
1797
+ const disableDateSeparator = props.disableDateSeparator ?? false;
1798
+ const className = props.className ?? "";
1799
+ const renderMessage = props.renderMessage;
1800
+ const renderAvatar = props.renderAvatar;
1801
+ const typingText = props.typingText ?? typingCtx.typingText ?? null;
1802
+ const MessageComponent = componentCtx.Message || Message;
1803
+ const DateSepComponent = componentCtx.DateSeparator || DateSeparator;
1804
+ const EmptyComponent = componentCtx.EmptyStateIndicator || EmptyStateIndicator;
1805
+ const LoadingComponent = componentCtx.LoadingIndicator || LoadingIndicator;
1806
+ const TypingComponent = componentCtx.TypingIndicator || TypingIndicator;
1807
+ const bottomRef = useRef6(null);
1808
+ const containerRef = useRef6(null);
1809
+ useEffect9(() => {
976
1810
  if (autoScroll && bottomRef.current) {
977
1811
  bottomRef.current.scrollIntoView({ behavior: "smooth" });
978
1812
  }
979
1813
  }, [messages, autoScroll]);
980
- useEffect6(() => {
1814
+ useEffect9(() => {
981
1815
  const container = containerRef.current;
982
1816
  if (!container || !onLoadMore) return;
983
1817
  const onScroll = () => {
@@ -987,21 +1821,33 @@ var MessageList = ({
987
1821
  return () => container.removeEventListener("scroll", onScroll);
988
1822
  }, [hasMore, loadingMore, onLoadMore]);
989
1823
  if (loading) {
990
- return /* @__PURE__ */ jsx(
1824
+ return /* @__PURE__ */ jsx18(
991
1825
  "div",
992
1826
  {
993
1827
  style: {
994
1828
  display: "flex",
995
1829
  alignItems: "center",
996
1830
  justifyContent: "center",
997
- height: "100%"
1831
+ height: "100%",
1832
+ flex: 1
998
1833
  },
999
- children: "Loading messages..."
1834
+ children: /* @__PURE__ */ jsx18(LoadingComponent, { text: "Loading messages..." })
1000
1835
  }
1001
1836
  );
1002
1837
  }
1003
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1004
- /* @__PURE__ */ jsx("style", { children: `
1838
+ const getGroupStyle = (index) => {
1839
+ const msg = messages[index];
1840
+ const prev = index > 0 ? messages[index - 1] : null;
1841
+ const next = index < messages.length - 1 ? messages[index + 1] : null;
1842
+ const sameSenderPrev = prev && prev.senderId === msg.senderId && !prev.isDeleted && isSameDay(prev.createdAt, msg.createdAt);
1843
+ const sameSenderNext = next && next.senderId === msg.senderId && !next.isDeleted && isSameDay(next.createdAt, msg.createdAt);
1844
+ if (sameSenderPrev && sameSenderNext) return "middle";
1845
+ if (sameSenderPrev) return "bottom";
1846
+ if (sameSenderNext) return "top";
1847
+ return "single";
1848
+ };
1849
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
1850
+ /* @__PURE__ */ jsx18("style", { children: `
1005
1851
  @keyframes hermes-bounce {
1006
1852
  0%, 80%, 100% { transform: translateY(0); }
1007
1853
  40% { transform: translateY(-5px); }
@@ -1011,7 +1857,7 @@ var MessageList = ({
1011
1857
  to { opacity: 1; transform: scale(1); }
1012
1858
  }
1013
1859
  ` }),
1014
- /* @__PURE__ */ jsxs(
1860
+ /* @__PURE__ */ jsxs8(
1015
1861
  "div",
1016
1862
  {
1017
1863
  ref: containerRef,
@@ -1020,11 +1866,11 @@ var MessageList = ({
1020
1866
  overflowY: "auto",
1021
1867
  display: "flex",
1022
1868
  flexDirection: "column",
1023
- height: "100%",
1869
+ flex: 1,
1024
1870
  padding: "16px"
1025
1871
  },
1026
1872
  children: [
1027
- hasMore && /* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginBottom: 12 }, children: loadingMore ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, opacity: 0.5 }, children: "Loading older messages..." }) : /* @__PURE__ */ jsx(
1873
+ hasMore && /* @__PURE__ */ jsx18("div", { style: { textAlign: "center", marginBottom: 12 }, children: loadingMore ? /* @__PURE__ */ jsx18(LoadingComponent, { size: 20, text: "Loading older messages..." }) : /* @__PURE__ */ jsx18(
1028
1874
  "button",
1029
1875
  {
1030
1876
  onClick: onLoadMore,
@@ -1039,35 +1885,32 @@ var MessageList = ({
1039
1885
  children: "Load older messages"
1040
1886
  }
1041
1887
  ) }),
1042
- messages.length === 0 && /* @__PURE__ */ jsx(
1043
- "div",
1044
- {
1045
- style: {
1046
- textAlign: "center",
1047
- opacity: 0.4,
1048
- margin: "auto",
1049
- fontSize: 14
1050
- },
1051
- children: "No messages yet. Say hello! \u{1F44B}"
1052
- }
1053
- ),
1054
- messages.map((message) => {
1055
- const isOwn = message.senderId === currentUser.userId;
1056
- return /* @__PURE__ */ jsx("div", { style: { marginBottom: 8 }, children: renderMessage ? renderMessage(message, isOwn) : /* @__PURE__ */ jsx(
1057
- DefaultMessage,
1058
- {
1059
- message,
1060
- isOwn,
1061
- onEdit,
1062
- onDelete,
1063
- onReact,
1064
- onReply,
1065
- renderAvatar
1066
- }
1067
- ) }, message._id);
1888
+ messages.length === 0 && /* @__PURE__ */ jsx18(EmptyComponent, { listType: "message" }),
1889
+ messages.map((message, index) => {
1890
+ const isOwn = message.senderId === currentUser?.userId;
1891
+ const groupStyle = getGroupStyle(index);
1892
+ const showDateSep = !disableDateSeparator && (index === 0 || !isSameDay(messages[index - 1].createdAt, message.createdAt));
1893
+ return /* @__PURE__ */ jsxs8(React10.Fragment, { children: [
1894
+ showDateSep && /* @__PURE__ */ jsx18(DateSepComponent, { date: new Date(message.createdAt) }),
1895
+ /* @__PURE__ */ jsx18("div", { style: { marginBottom: groupStyle === "bottom" || groupStyle === "single" ? 8 : 2 }, children: renderMessage ? renderMessage(message, isOwn) : /* @__PURE__ */ jsx18(
1896
+ MessageComponent,
1897
+ {
1898
+ message,
1899
+ isOwn,
1900
+ onEdit,
1901
+ onDelete,
1902
+ onReact,
1903
+ onReply,
1904
+ onOpenThread,
1905
+ renderAvatar,
1906
+ groupStyle,
1907
+ showAvatar: true
1908
+ }
1909
+ ) })
1910
+ ] }, message._id);
1068
1911
  }),
1069
- /* @__PURE__ */ jsx(TypingIndicator, { typingUsers }),
1070
- /* @__PURE__ */ jsx("div", { ref: bottomRef })
1912
+ typingText && /* @__PURE__ */ jsx18(TypingComponent, { typingText }),
1913
+ /* @__PURE__ */ jsx18("div", { ref: bottomRef })
1071
1914
  ]
1072
1915
  }
1073
1916
  )
@@ -1075,28 +1918,34 @@ var MessageList = ({
1075
1918
  };
1076
1919
 
1077
1920
  // src/react/components/ChatInput.tsx
1078
- import { useState as useState8, useRef as useRef5, useCallback as useCallback8 } from "react";
1079
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1080
- var ChatInput = ({
1081
- onSendText,
1082
- onSendFile,
1083
- onTypingStart,
1084
- onTypingStop,
1085
- replyingTo,
1086
- onCancelReply,
1087
- disabled = false,
1088
- placeholder = "Type a message...",
1089
- maxLength = 4e3,
1090
- className = "",
1091
- inputClassName = "",
1092
- renderAttachIcon,
1093
- renderSendIcon
1094
- }) => {
1095
- const [text, setText] = useState8("");
1096
- const [sending, setSending] = useState8(false);
1097
- const fileRef = useRef5(null);
1098
- const textareaRef = useRef5(null);
1099
- const resizeTextarea = useCallback8(() => {
1921
+ import { useState as useState10, useRef as useRef7, useCallback as useCallback10 } from "react";
1922
+ import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
1923
+ var ChatInput = (props) => {
1924
+ const roomActionCtx = useRoomActionContext("ChatInput");
1925
+ const typingCtx = useTypingContext("ChatInput");
1926
+ const chatCtx = useChatContext("ChatInput");
1927
+ const onSendText = props.onSendText ?? (roomActionCtx.sendMessage ? async (text2) => {
1928
+ await roomActionCtx.sendMessage({ type: "text", text: text2 });
1929
+ } : void 0);
1930
+ const onSendFile = props.onSendFile;
1931
+ const onTypingStart = props.onTypingStart ?? typingCtx.startTyping;
1932
+ const onTypingStop = props.onTypingStop ?? typingCtx.stopTyping;
1933
+ const {
1934
+ replyingTo,
1935
+ onCancelReply,
1936
+ disabled = false,
1937
+ placeholder = "Type a message...",
1938
+ maxLength = 4e3,
1939
+ className = "",
1940
+ inputClassName = "",
1941
+ renderAttachIcon,
1942
+ renderSendIcon
1943
+ } = props;
1944
+ const [text, setText] = useState10("");
1945
+ const [sending, setSending] = useState10(false);
1946
+ const fileRef = useRef7(null);
1947
+ const textareaRef = useRef7(null);
1948
+ const resizeTextarea = useCallback10(() => {
1100
1949
  const el = textareaRef.current;
1101
1950
  if (!el) return;
1102
1951
  el.style.height = "auto";
@@ -1109,7 +1958,7 @@ var ChatInput = ({
1109
1958
  };
1110
1959
  const handleSend = async () => {
1111
1960
  const trimmed = text.trim();
1112
- if (!trimmed || sending || disabled) return;
1961
+ if (!trimmed || sending || disabled || !onSendText) return;
1113
1962
  setSending(true);
1114
1963
  try {
1115
1964
  await onSendText(trimmed);
@@ -1132,7 +1981,7 @@ var ChatInput = ({
1132
1981
  await onSendFile(file);
1133
1982
  if (fileRef.current) fileRef.current.value = "";
1134
1983
  };
1135
- return /* @__PURE__ */ jsxs2(
1984
+ return /* @__PURE__ */ jsxs9(
1136
1985
  "div",
1137
1986
  {
1138
1987
  className: `hermes-chat-input ${className}`,
@@ -1143,7 +1992,7 @@ var ChatInput = ({
1143
1992
  borderTop: "1px solid #e0e0e0"
1144
1993
  },
1145
1994
  children: [
1146
- replyingTo && /* @__PURE__ */ jsxs2(
1995
+ replyingTo && /* @__PURE__ */ jsxs9(
1147
1996
  "div",
1148
1997
  {
1149
1998
  className: "hermes-chat-input__reply",
@@ -1159,11 +2008,11 @@ var ChatInput = ({
1159
2008
  fontSize: 12
1160
2009
  },
1161
2010
  children: [
1162
- /* @__PURE__ */ jsxs2("div", { style: { overflow: "hidden" }, children: [
1163
- /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600, marginRight: 4 }, children: "Replying to:" }),
1164
- /* @__PURE__ */ jsx2("span", { style: { opacity: 0.7 }, children: replyingTo.type === "text" ? replyingTo.text?.slice(0, 60) : `[${replyingTo.type}]` })
2011
+ /* @__PURE__ */ jsxs9("div", { style: { overflow: "hidden" }, children: [
2012
+ /* @__PURE__ */ jsx19("span", { style: { fontWeight: 600, marginRight: 4 }, children: "Replying to:" }),
2013
+ /* @__PURE__ */ jsx19("span", { style: { opacity: 0.7 }, children: replyingTo.type === "text" ? replyingTo.text?.slice(0, 60) : `[${replyingTo.type}]` })
1165
2014
  ] }),
1166
- /* @__PURE__ */ jsx2(
2015
+ /* @__PURE__ */ jsx19(
1167
2016
  "button",
1168
2017
  {
1169
2018
  onClick: onCancelReply,
@@ -1180,14 +2029,14 @@ var ChatInput = ({
1180
2029
  ]
1181
2030
  }
1182
2031
  ),
1183
- /* @__PURE__ */ jsxs2(
2032
+ /* @__PURE__ */ jsxs9(
1184
2033
  "div",
1185
2034
  {
1186
2035
  className: "hermes-chat-input__row",
1187
2036
  style: { display: "flex", alignItems: "flex-end", gap: 8 },
1188
2037
  children: [
1189
- onSendFile && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1190
- /* @__PURE__ */ jsx2(
2038
+ onSendFile && /* @__PURE__ */ jsxs9(Fragment2, { children: [
2039
+ /* @__PURE__ */ jsx19(
1191
2040
  "button",
1192
2041
  {
1193
2042
  onClick: () => fileRef.current?.click(),
@@ -1201,23 +2050,10 @@ var ChatInput = ({
1201
2050
  flexShrink: 0,
1202
2051
  opacity: disabled ? 0.4 : 1
1203
2052
  },
1204
- children: renderAttachIcon ? renderAttachIcon() : /* @__PURE__ */ jsx2(
1205
- "svg",
1206
- {
1207
- width: "20",
1208
- height: "20",
1209
- viewBox: "0 0 24 24",
1210
- fill: "none",
1211
- stroke: "currentColor",
1212
- strokeWidth: "2",
1213
- strokeLinecap: "round",
1214
- strokeLinejoin: "round",
1215
- children: /* @__PURE__ */ jsx2("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" })
1216
- }
1217
- )
2053
+ children: renderAttachIcon ? renderAttachIcon() : /* @__PURE__ */ jsx19("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx19("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) })
1218
2054
  }
1219
2055
  ),
1220
- /* @__PURE__ */ jsx2(
2056
+ /* @__PURE__ */ jsx19(
1221
2057
  "input",
1222
2058
  {
1223
2059
  ref: fileRef,
@@ -1228,7 +2064,7 @@ var ChatInput = ({
1228
2064
  }
1229
2065
  )
1230
2066
  ] }),
1231
- /* @__PURE__ */ jsx2(
2067
+ /* @__PURE__ */ jsx19(
1232
2068
  "textarea",
1233
2069
  {
1234
2070
  ref: textareaRef,
@@ -1255,7 +2091,7 @@ var ChatInput = ({
1255
2091
  }
1256
2092
  }
1257
2093
  ),
1258
- /* @__PURE__ */ jsx2(
2094
+ /* @__PURE__ */ jsx19(
1259
2095
  "button",
1260
2096
  {
1261
2097
  onClick: handleSend,
@@ -1269,35 +2105,24 @@ var ChatInput = ({
1269
2105
  flexShrink: 0,
1270
2106
  opacity: !text.trim() || sending || disabled ? 0.4 : 1
1271
2107
  },
1272
- children: renderSendIcon ? renderSendIcon() : /* @__PURE__ */ jsx2("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
2108
+ children: renderSendIcon ? renderSendIcon() : /* @__PURE__ */ jsx19("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx19("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
1273
2109
  }
1274
2110
  )
1275
2111
  ]
1276
2112
  }
1277
2113
  ),
1278
- text.length > maxLength * 0.8 && /* @__PURE__ */ jsxs2(
1279
- "div",
1280
- {
1281
- style: {
1282
- fontSize: 10,
1283
- textAlign: "right",
1284
- opacity: 0.5,
1285
- marginTop: 2
1286
- },
1287
- children: [
1288
- text.length,
1289
- "/",
1290
- maxLength
1291
- ]
1292
- }
1293
- )
2114
+ text.length > maxLength * 0.8 && /* @__PURE__ */ jsxs9("div", { style: { fontSize: 10, textAlign: "right", opacity: 0.5, marginTop: 2 }, children: [
2115
+ text.length,
2116
+ "/",
2117
+ maxLength
2118
+ ] })
1294
2119
  ]
1295
2120
  }
1296
2121
  );
1297
2122
  };
1298
2123
 
1299
2124
  // src/react/components/RoomList.tsx
1300
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2125
+ import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1301
2126
  var formatLastActivity = (iso) => {
1302
2127
  const date = new Date(iso);
1303
2128
  const now = /* @__PURE__ */ new Date();
@@ -1328,7 +2153,7 @@ var getLastMessagePreview = (room) => {
1328
2153
  if (msg.type === "link") return `\u{1F517} ${msg.url}`;
1329
2154
  return "";
1330
2155
  };
1331
- var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassName }) => /* @__PURE__ */ jsxs3(
2156
+ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassName }) => /* @__PURE__ */ jsxs10(
1332
2157
  "div",
1333
2158
  {
1334
2159
  className: `hermes-room-item ${isActive ? "hermes-room-item--active" : ""} ${itemClassName ?? ""}`,
@@ -1342,7 +2167,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1342
2167
  borderLeft: isActive ? "3px solid #0084ff" : "3px solid transparent"
1343
2168
  },
1344
2169
  children: [
1345
- /* @__PURE__ */ jsx3("div", { style: { flexShrink: 0 }, children: renderAvatar ? renderAvatar(room) : /* @__PURE__ */ jsx3(
2170
+ /* @__PURE__ */ jsx20("div", { style: { flexShrink: 0 }, children: renderAvatar ? renderAvatar(room) : /* @__PURE__ */ jsx20(
1346
2171
  "div",
1347
2172
  {
1348
2173
  style: {
@@ -1359,8 +2184,8 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1359
2184
  children: room.type === "group" ? "G" : "D"
1360
2185
  }
1361
2186
  ) }),
1362
- /* @__PURE__ */ jsxs3("div", { style: { flex: 1, overflow: "hidden" }, children: [
1363
- /* @__PURE__ */ jsxs3(
2187
+ /* @__PURE__ */ jsxs10("div", { style: { flex: 1, overflow: "hidden" }, children: [
2188
+ /* @__PURE__ */ jsxs10(
1364
2189
  "div",
1365
2190
  {
1366
2191
  style: {
@@ -1369,7 +2194,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1369
2194
  alignItems: "baseline"
1370
2195
  },
1371
2196
  children: [
1372
- /* @__PURE__ */ jsx3(
2197
+ /* @__PURE__ */ jsx20(
1373
2198
  "span",
1374
2199
  {
1375
2200
  style: {
@@ -1382,7 +2207,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1382
2207
  children: getRoomName(room, currentUserId)
1383
2208
  }
1384
2209
  ),
1385
- /* @__PURE__ */ jsx3(
2210
+ /* @__PURE__ */ jsx20(
1386
2211
  "span",
1387
2212
  {
1388
2213
  style: { fontSize: 11, opacity: 0.5, flexShrink: 0, marginLeft: 4 },
@@ -1392,7 +2217,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1392
2217
  ]
1393
2218
  }
1394
2219
  ),
1395
- /* @__PURE__ */ jsxs3(
2220
+ /* @__PURE__ */ jsxs10(
1396
2221
  "div",
1397
2222
  {
1398
2223
  style: {
@@ -1402,7 +2227,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1402
2227
  marginTop: 2
1403
2228
  },
1404
2229
  children: [
1405
- /* @__PURE__ */ jsx3(
2230
+ /* @__PURE__ */ jsx20(
1406
2231
  "span",
1407
2232
  {
1408
2233
  style: {
@@ -1415,7 +2240,7 @@ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassN
1415
2240
  children: getLastMessagePreview(room)
1416
2241
  }
1417
2242
  ),
1418
- room.unreadCount > 0 && /* @__PURE__ */ jsx3(
2243
+ room.unreadCount > 0 && /* @__PURE__ */ jsx20(
1419
2244
  "span",
1420
2245
  {
1421
2246
  style: {
@@ -1452,7 +2277,7 @@ var RoomList = ({
1452
2277
  className = "",
1453
2278
  itemClassName = ""
1454
2279
  }) => {
1455
- return /* @__PURE__ */ jsxs3(
2280
+ return /* @__PURE__ */ jsxs10(
1456
2281
  "div",
1457
2282
  {
1458
2283
  className: `hermes-room-list ${className}`,
@@ -1463,7 +2288,7 @@ var RoomList = ({
1463
2288
  overflowY: "auto"
1464
2289
  },
1465
2290
  children: [
1466
- (onCreateDirect || onCreateGroup) && /* @__PURE__ */ jsxs3(
2291
+ (onCreateDirect || onCreateGroup) && /* @__PURE__ */ jsxs10(
1467
2292
  "div",
1468
2293
  {
1469
2294
  style: {
@@ -1473,7 +2298,7 @@ var RoomList = ({
1473
2298
  borderBottom: "1px solid #e0e0e0"
1474
2299
  },
1475
2300
  children: [
1476
- onCreateDirect && /* @__PURE__ */ jsx3(
2301
+ onCreateDirect && /* @__PURE__ */ jsx20(
1477
2302
  "button",
1478
2303
  {
1479
2304
  onClick: onCreateDirect,
@@ -1491,7 +2316,7 @@ var RoomList = ({
1491
2316
  children: "+ Direct"
1492
2317
  }
1493
2318
  ),
1494
- onCreateGroup && /* @__PURE__ */ jsx3(
2319
+ onCreateGroup && /* @__PURE__ */ jsx20(
1495
2320
  "button",
1496
2321
  {
1497
2322
  onClick: onCreateGroup,
@@ -1511,8 +2336,8 @@ var RoomList = ({
1511
2336
  ]
1512
2337
  }
1513
2338
  ),
1514
- loading && /* @__PURE__ */ jsx3("div", { style: { padding: "12px 16px", opacity: 0.5, fontSize: 13 }, children: "Loading rooms..." }),
1515
- !loading && rooms.length === 0 && /* @__PURE__ */ jsx3(
2339
+ loading && /* @__PURE__ */ jsx20("div", { style: { padding: "12px 16px", opacity: 0.5, fontSize: 13 }, children: "Loading rooms..." }),
2340
+ !loading && rooms.length === 0 && /* @__PURE__ */ jsx20(
1516
2341
  "div",
1517
2342
  {
1518
2343
  style: {
@@ -1526,7 +2351,7 @@ var RoomList = ({
1526
2351
  ),
1527
2352
  !loading && rooms.map((room) => {
1528
2353
  const isActive = room._id === activeRoomId;
1529
- return /* @__PURE__ */ jsx3("div", { onClick: () => onSelectRoom(room), children: renderRoomItem ? renderRoomItem(room, isActive) : /* @__PURE__ */ jsx3(
2354
+ return /* @__PURE__ */ jsx20("div", { onClick: () => onSelectRoom(room), children: renderRoomItem ? renderRoomItem(room, isActive) : /* @__PURE__ */ jsx20(
1530
2355
  DefaultRoomItem,
1531
2356
  {
1532
2357
  room,
@@ -1542,58 +2367,13 @@ var RoomList = ({
1542
2367
  );
1543
2368
  };
1544
2369
 
1545
- // src/react/components/TypingIndicator.tsx
1546
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1547
- var TypingIndicator2 = ({
1548
- typingText,
1549
- className = ""
1550
- }) => {
1551
- if (!typingText) return null;
1552
- return /* @__PURE__ */ jsxs4(
1553
- "div",
1554
- {
1555
- className: `hermes-typing-indicator ${className}`,
1556
- style: {
1557
- display: "flex",
1558
- alignItems: "center",
1559
- gap: 6,
1560
- padding: "4px 16px",
1561
- minHeight: 24
1562
- },
1563
- children: [
1564
- /* @__PURE__ */ jsx4("div", { style: { display: "flex", gap: 3 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx4(
1565
- "span",
1566
- {
1567
- style: {
1568
- width: 6,
1569
- height: 6,
1570
- borderRadius: "50%",
1571
- background: "#aaa",
1572
- display: "block",
1573
- animation: `hermes-bounce 1.2s ease-in-out ${i * 0.2}s infinite`
1574
- }
1575
- },
1576
- i
1577
- )) }),
1578
- /* @__PURE__ */ jsx4("span", { style: { fontSize: 12, opacity: 0.6 }, children: typingText }),
1579
- /* @__PURE__ */ jsx4("style", { children: `
1580
- @keyframes hermes-bounce {
1581
- 0%, 80%, 100% { transform: translateY(0); }
1582
- 40% { transform: translateY(-4px); }
1583
- }
1584
- ` })
1585
- ]
1586
- }
1587
- );
1588
- };
1589
-
1590
2370
  // src/react/components/OnlineBadge.tsx
1591
- import { jsx as jsx5 } from "react/jsx-runtime";
2371
+ import { jsx as jsx21 } from "react/jsx-runtime";
1592
2372
  var OnlineBadge = ({
1593
2373
  isOnline,
1594
2374
  size = 10,
1595
2375
  className = ""
1596
- }) => /* @__PURE__ */ jsx5(
2376
+ }) => /* @__PURE__ */ jsx21(
1597
2377
  "span",
1598
2378
  {
1599
2379
  className: `hermes-online-badge ${isOnline ? "hermes-online-badge--online" : "hermes-online-badge--offline"} ${className}`,
@@ -1611,9 +2391,9 @@ var OnlineBadge = ({
1611
2391
  );
1612
2392
 
1613
2393
  // src/react/components/ReactionPicker.tsx
1614
- import { useState as useState9, useRef as useRef6, useEffect as useEffect7 } from "react";
1615
- import EmojiPicker2, { Theme } from "emoji-picker-react";
1616
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2394
+ import { useState as useState11, useRef as useRef8, useEffect as useEffect10 } from "react";
2395
+ import EmojiPicker, { Theme } from "emoji-picker-react";
2396
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
1617
2397
  var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F525}", "\u{1F389}", "\u{1F44F}"];
1618
2398
  var ReactionPicker = ({
1619
2399
  onSelect,
@@ -1623,8 +2403,8 @@ var ReactionPicker = ({
1623
2403
  className = "",
1624
2404
  align = "left"
1625
2405
  }) => {
1626
- const [showPicker, setShowPicker] = useState9(false);
1627
- const containerRef = useRef6(null);
2406
+ const [showPicker, setShowPicker] = useState11(false);
2407
+ const containerRef = useRef8(null);
1628
2408
  const hasReacted = (emoji) => {
1629
2409
  if (!currentUserId) return false;
1630
2410
  return currentReactions.find((r) => r.emoji === emoji)?.users.includes(currentUserId) ?? false;
@@ -1633,7 +2413,7 @@ var ReactionPicker = ({
1633
2413
  onSelect(emojiData.emoji);
1634
2414
  setShowPicker(false);
1635
2415
  };
1636
- useEffect7(() => {
2416
+ useEffect10(() => {
1637
2417
  const handleOutsideClick = (e) => {
1638
2418
  if (!containerRef.current) return;
1639
2419
  const target = e.target;
@@ -1646,14 +2426,14 @@ var ReactionPicker = ({
1646
2426
  }
1647
2427
  return () => window.removeEventListener("click", handleOutsideClick);
1648
2428
  }, [showPicker]);
1649
- return /* @__PURE__ */ jsxs5(
2429
+ return /* @__PURE__ */ jsxs11(
1650
2430
  "div",
1651
2431
  {
1652
2432
  ref: containerRef,
1653
2433
  style: { position: "relative", display: "inline-block" },
1654
2434
  className,
1655
2435
  children: [
1656
- /* @__PURE__ */ jsxs5(
2436
+ /* @__PURE__ */ jsxs11(
1657
2437
  "div",
1658
2438
  {
1659
2439
  style: {
@@ -1666,7 +2446,7 @@ var ReactionPicker = ({
1666
2446
  border: "1px solid rgba(255,255,255,0.08)"
1667
2447
  },
1668
2448
  children: [
1669
- emojis.map((emoji) => /* @__PURE__ */ jsx6(
2449
+ emojis.map((emoji) => /* @__PURE__ */ jsx22(
1670
2450
  "button",
1671
2451
  {
1672
2452
  onClick: () => onSelect(emoji),
@@ -1686,7 +2466,7 @@ var ReactionPicker = ({
1686
2466
  },
1687
2467
  emoji
1688
2468
  )),
1689
- /* @__PURE__ */ jsx6(
2469
+ /* @__PURE__ */ jsx22(
1690
2470
  "button",
1691
2471
  {
1692
2472
  onClick: (e) => {
@@ -1707,7 +2487,7 @@ var ReactionPicker = ({
1707
2487
  ]
1708
2488
  }
1709
2489
  ),
1710
- showPicker && /* @__PURE__ */ jsx6(
2490
+ showPicker && /* @__PURE__ */ jsx22(
1711
2491
  "div",
1712
2492
  {
1713
2493
  onMouseDown: (e) => e.stopPropagation(),
@@ -1719,8 +2499,8 @@ var ReactionPicker = ({
1719
2499
  zIndex: 50,
1720
2500
  animation: "hermes-pop 0.15s ease"
1721
2501
  },
1722
- children: /* @__PURE__ */ jsx6(
1723
- EmojiPicker2,
2502
+ children: /* @__PURE__ */ jsx22(
2503
+ EmojiPicker,
1724
2504
  {
1725
2505
  theme: Theme.DARK,
1726
2506
  onEmojiClick: handleEmojiClick,
@@ -1732,7 +2512,7 @@ var ReactionPicker = ({
1732
2512
  )
1733
2513
  }
1734
2514
  ),
1735
- /* @__PURE__ */ jsx6("style", { children: `
2515
+ /* @__PURE__ */ jsx22("style", { children: `
1736
2516
  @keyframes hermes-pop {
1737
2517
  from {
1738
2518
  opacity: 0;
@@ -1750,7 +2530,7 @@ var ReactionPicker = ({
1750
2530
  };
1751
2531
 
1752
2532
  // src/react/components/MediaMessage.tsx
1753
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2533
+ import { jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
1754
2534
  var formatFileSize2 = (bytes) => {
1755
2535
  if (!bytes) return "";
1756
2536
  if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
@@ -1763,13 +2543,13 @@ var MediaMessage = ({
1763
2543
  maxWidth = 300
1764
2544
  }) => {
1765
2545
  if (!message.url) return null;
1766
- return /* @__PURE__ */ jsxs6(
2546
+ return /* @__PURE__ */ jsxs12(
1767
2547
  "div",
1768
2548
  {
1769
2549
  className: `hermes-media-message hermes-media-message--${message.type} ${className}`,
1770
2550
  style: { maxWidth },
1771
2551
  children: [
1772
- message.type === "image" && /* @__PURE__ */ jsx7(
2552
+ message.type === "image" && /* @__PURE__ */ jsx23(
1773
2553
  "img",
1774
2554
  {
1775
2555
  src: message.url,
@@ -1783,7 +2563,7 @@ var MediaMessage = ({
1783
2563
  onClick: () => window.open(message.url, "_blank")
1784
2564
  }
1785
2565
  ),
1786
- message.type === "video" && /* @__PURE__ */ jsx7(
2566
+ message.type === "video" && /* @__PURE__ */ jsx23(
1787
2567
  "video",
1788
2568
  {
1789
2569
  src: message.url,
@@ -1792,17 +2572,17 @@ var MediaMessage = ({
1792
2572
  style: { width: "100%", borderRadius: 10 }
1793
2573
  }
1794
2574
  ),
1795
- message.type === "audio" && /* @__PURE__ */ jsxs6(
2575
+ message.type === "audio" && /* @__PURE__ */ jsxs12(
1796
2576
  "div",
1797
2577
  {
1798
2578
  style: { display: "flex", alignItems: "center", gap: 8, padding: 8 },
1799
2579
  children: [
1800
- /* @__PURE__ */ jsx7("span", { style: { fontSize: 20 }, children: "\u{1F3B5}" }),
1801
- /* @__PURE__ */ jsx7("audio", { src: message.url, controls: true, style: { flex: 1, height: 36 } })
2580
+ /* @__PURE__ */ jsx23("span", { style: { fontSize: 20 }, children: "\u{1F3B5}" }),
2581
+ /* @__PURE__ */ jsx23("audio", { src: message.url, controls: true, style: { flex: 1, height: 36 } })
1802
2582
  ]
1803
2583
  }
1804
2584
  ),
1805
- message.type === "document" && /* @__PURE__ */ jsxs6(
2585
+ message.type === "document" && /* @__PURE__ */ jsxs12(
1806
2586
  "a",
1807
2587
  {
1808
2588
  href: message.url,
@@ -1819,9 +2599,9 @@ var MediaMessage = ({
1819
2599
  color: "inherit"
1820
2600
  },
1821
2601
  children: [
1822
- /* @__PURE__ */ jsx7("span", { style: { fontSize: 28, flexShrink: 0 }, children: "\u{1F4C4}" }),
1823
- /* @__PURE__ */ jsxs6("div", { style: { overflow: "hidden" }, children: [
1824
- /* @__PURE__ */ jsx7(
2602
+ /* @__PURE__ */ jsx23("span", { style: { fontSize: 28, flexShrink: 0 }, children: "\u{1F4C4}" }),
2603
+ /* @__PURE__ */ jsxs12("div", { style: { overflow: "hidden" }, children: [
2604
+ /* @__PURE__ */ jsx23(
1825
2605
  "div",
1826
2606
  {
1827
2607
  style: {
@@ -1834,7 +2614,7 @@ var MediaMessage = ({
1834
2614
  children: message.fileName ?? "Document"
1835
2615
  }
1836
2616
  ),
1837
- /* @__PURE__ */ jsxs6("div", { style: { fontSize: 11, opacity: 0.6 }, children: [
2617
+ /* @__PURE__ */ jsxs12("div", { style: { fontSize: 11, opacity: 0.6 }, children: [
1838
2618
  formatFileSize2(message.fileSize),
1839
2619
  " \xB7 Click to download"
1840
2620
  ] })
@@ -1842,7 +2622,7 @@ var MediaMessage = ({
1842
2622
  ]
1843
2623
  }
1844
2624
  ),
1845
- message.type === "link" && /* @__PURE__ */ jsxs6(
2625
+ message.type === "link" && /* @__PURE__ */ jsxs12(
1846
2626
  "a",
1847
2627
  {
1848
2628
  href: message.url,
@@ -1870,21 +2650,435 @@ var MediaMessage = ({
1870
2650
  }
1871
2651
  );
1872
2652
  };
2653
+
2654
+ // src/react/components/Thread/ThreadHeader.tsx
2655
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
2656
+ var ThreadHeader = ({
2657
+ thread,
2658
+ onClose,
2659
+ className = ""
2660
+ }) => /* @__PURE__ */ jsxs13(
2661
+ "div",
2662
+ {
2663
+ className: `hermes-thread-header ${className}`,
2664
+ style: {
2665
+ display: "flex",
2666
+ alignItems: "center",
2667
+ justifyContent: "space-between",
2668
+ padding: "12px 16px",
2669
+ borderBottom: "1px solid rgba(128,128,128,0.15)"
2670
+ },
2671
+ children: [
2672
+ /* @__PURE__ */ jsxs13("div", { style: { flex: 1, overflow: "hidden" }, children: [
2673
+ /* @__PURE__ */ jsx24("div", { style: { fontWeight: 700, fontSize: 15, marginBottom: 2 }, children: "Thread" }),
2674
+ /* @__PURE__ */ jsx24(
2675
+ "div",
2676
+ {
2677
+ style: {
2678
+ fontSize: 12,
2679
+ opacity: 0.6,
2680
+ overflow: "hidden",
2681
+ textOverflow: "ellipsis",
2682
+ whiteSpace: "nowrap"
2683
+ },
2684
+ children: thread.type === "text" ? thread.text?.slice(0, 80) : `[${thread.type}]`
2685
+ }
2686
+ )
2687
+ ] }),
2688
+ /* @__PURE__ */ jsx24(
2689
+ "button",
2690
+ {
2691
+ onClick: onClose,
2692
+ style: {
2693
+ background: "none",
2694
+ border: "none",
2695
+ cursor: "pointer",
2696
+ fontSize: 18,
2697
+ padding: 4,
2698
+ opacity: 0.6,
2699
+ lineHeight: 1
2700
+ },
2701
+ children: "\u2715"
2702
+ }
2703
+ )
2704
+ ]
2705
+ }
2706
+ );
2707
+
2708
+ // src/react/components/Thread/Thread.tsx
2709
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
2710
+ var Thread = ({
2711
+ className = "",
2712
+ autoFocus = true
2713
+ }) => {
2714
+ const { currentUser, customClasses } = useChatContext("Thread");
2715
+ const { thread, threadMessages, threadHasMore, threadLoadingMore } = useRoomStateContext("Thread");
2716
+ const { closeThread, addReaction, deleteMessage, editMessage } = useRoomActionContext("Thread");
2717
+ const {
2718
+ ThreadHeader: CustomThreadHeader,
2719
+ Message: CustomMessage
2720
+ } = useComponentContext("Thread");
2721
+ if (!thread) return null;
2722
+ const ThreadHeaderComponent = CustomThreadHeader || ThreadHeader;
2723
+ const MessageComponent = CustomMessage || Message;
2724
+ const threadClass = customClasses?.thread || `hermes-thread ${className}`.trim();
2725
+ return /* @__PURE__ */ jsxs14(
2726
+ "div",
2727
+ {
2728
+ className: threadClass,
2729
+ style: {
2730
+ display: "flex",
2731
+ flexDirection: "column",
2732
+ height: "100%",
2733
+ borderLeft: "1px solid rgba(128,128,128,0.15)",
2734
+ minWidth: 320,
2735
+ maxWidth: 420
2736
+ },
2737
+ children: [
2738
+ /* @__PURE__ */ jsx25(ThreadHeaderComponent, { thread, onClose: closeThread }),
2739
+ /* @__PURE__ */ jsx25(
2740
+ "div",
2741
+ {
2742
+ style: {
2743
+ padding: "12px 16px",
2744
+ borderBottom: "1px solid rgba(128,128,128,0.1)",
2745
+ background: "rgba(128,128,128,0.03)"
2746
+ },
2747
+ children: /* @__PURE__ */ jsx25(
2748
+ MessageComponent,
2749
+ {
2750
+ message: thread,
2751
+ isOwn: thread.senderId === currentUser?.userId,
2752
+ onReact: (id, emoji) => addReaction(id, emoji),
2753
+ showAvatar: true
2754
+ }
2755
+ )
2756
+ }
2757
+ ),
2758
+ /* @__PURE__ */ jsx25(
2759
+ "div",
2760
+ {
2761
+ style: {
2762
+ flex: 1,
2763
+ overflowY: "auto",
2764
+ padding: 16,
2765
+ display: "flex",
2766
+ flexDirection: "column"
2767
+ },
2768
+ children: threadMessages.length === 0 ? /* @__PURE__ */ jsx25(EmptyStateIndicator, { listType: "thread" }) : threadMessages.map((msg) => /* @__PURE__ */ jsx25("div", { style: { marginBottom: 8 }, children: /* @__PURE__ */ jsx25(
2769
+ MessageComponent,
2770
+ {
2771
+ message: msg,
2772
+ isOwn: msg.senderId === currentUser?.userId,
2773
+ onEdit: (id, text) => editMessage(id, text),
2774
+ onDelete: (id) => deleteMessage(id),
2775
+ onReact: (id, emoji) => addReaction(id, emoji),
2776
+ showAvatar: true
2777
+ }
2778
+ ) }, msg._id))
2779
+ }
2780
+ )
2781
+ ]
2782
+ }
2783
+ );
2784
+ };
2785
+
2786
+ // src/react/components/Modal/Modal.tsx
2787
+ import { useEffect as useEffect11, useCallback as useCallback11 } from "react";
2788
+ import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
2789
+ var Modal = ({
2790
+ open,
2791
+ onClose,
2792
+ className = "",
2793
+ children
2794
+ }) => {
2795
+ const handleKeyDown = useCallback11(
2796
+ (e) => {
2797
+ if (e.key === "Escape") onClose();
2798
+ },
2799
+ [onClose]
2800
+ );
2801
+ useEffect11(() => {
2802
+ if (open) {
2803
+ document.addEventListener("keydown", handleKeyDown);
2804
+ document.body.style.overflow = "hidden";
2805
+ }
2806
+ return () => {
2807
+ document.removeEventListener("keydown", handleKeyDown);
2808
+ document.body.style.overflow = "";
2809
+ };
2810
+ }, [open, handleKeyDown]);
2811
+ if (!open) return null;
2812
+ return /* @__PURE__ */ jsxs15(
2813
+ "div",
2814
+ {
2815
+ className: `hermes-modal-overlay ${className}`,
2816
+ onClick: onClose,
2817
+ style: {
2818
+ position: "fixed",
2819
+ inset: 0,
2820
+ zIndex: 9999,
2821
+ display: "flex",
2822
+ alignItems: "center",
2823
+ justifyContent: "center",
2824
+ background: "rgba(0,0,0,0.6)",
2825
+ backdropFilter: "blur(4px)",
2826
+ animation: "hermes-fade-in 0.15s ease"
2827
+ },
2828
+ children: [
2829
+ /* @__PURE__ */ jsxs15(
2830
+ "div",
2831
+ {
2832
+ className: "hermes-modal-content",
2833
+ onClick: (e) => e.stopPropagation(),
2834
+ style: {
2835
+ maxWidth: "90vw",
2836
+ maxHeight: "90vh",
2837
+ overflow: "auto",
2838
+ borderRadius: 12,
2839
+ background: "#fff",
2840
+ boxShadow: "0 16px 64px rgba(0,0,0,0.3)",
2841
+ animation: "hermes-pop 0.2s ease"
2842
+ },
2843
+ children: [
2844
+ /* @__PURE__ */ jsx26(
2845
+ "button",
2846
+ {
2847
+ onClick: onClose,
2848
+ style: {
2849
+ position: "absolute",
2850
+ top: 16,
2851
+ right: 16,
2852
+ background: "rgba(0,0,0,0.5)",
2853
+ color: "#fff",
2854
+ border: "none",
2855
+ borderRadius: "50%",
2856
+ width: 32,
2857
+ height: 32,
2858
+ cursor: "pointer",
2859
+ fontSize: 16,
2860
+ display: "flex",
2861
+ alignItems: "center",
2862
+ justifyContent: "center",
2863
+ zIndex: 1
2864
+ },
2865
+ children: "\u2715"
2866
+ }
2867
+ ),
2868
+ children
2869
+ ]
2870
+ }
2871
+ ),
2872
+ /* @__PURE__ */ jsx26("style", { children: `
2873
+ @keyframes hermes-fade-in {
2874
+ from { opacity: 0; }
2875
+ to { opacity: 1; }
2876
+ }
2877
+ @keyframes hermes-pop {
2878
+ from { opacity: 0; transform: scale(0.9); }
2879
+ to { opacity: 1; transform: scale(1); }
2880
+ }
2881
+ ` })
2882
+ ]
2883
+ }
2884
+ );
2885
+ };
2886
+
2887
+ // src/react/components/Search/Search.tsx
2888
+ import { useState as useState12, useCallback as useCallback12, useMemo as useMemo3 } from "react";
2889
+ import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
2890
+ var Search = ({
2891
+ messages = [],
2892
+ onSelectResult,
2893
+ placeholder = "Search messages...",
2894
+ className = ""
2895
+ }) => {
2896
+ const [query, setQuery] = useState12("");
2897
+ const [focused, setFocused] = useState12(false);
2898
+ const results = useMemo3(() => {
2899
+ if (!query.trim()) return [];
2900
+ const lower = query.toLowerCase();
2901
+ return messages.filter(
2902
+ (m) => !m.isDeleted && m.type === "text" && m.text?.toLowerCase().includes(lower)
2903
+ ).slice(0, 20);
2904
+ }, [query, messages]);
2905
+ const handleSelect = useCallback12(
2906
+ (msg) => {
2907
+ onSelectResult?.(msg);
2908
+ setQuery("");
2909
+ setFocused(false);
2910
+ },
2911
+ [onSelectResult]
2912
+ );
2913
+ return /* @__PURE__ */ jsxs16(
2914
+ "div",
2915
+ {
2916
+ className: `hermes-search ${className}`,
2917
+ style: { position: "relative" },
2918
+ children: [
2919
+ /* @__PURE__ */ jsxs16(
2920
+ "div",
2921
+ {
2922
+ style: {
2923
+ display: "flex",
2924
+ alignItems: "center",
2925
+ gap: 8,
2926
+ padding: "6px 12px",
2927
+ border: "1px solid rgba(128,128,128,0.2)",
2928
+ borderRadius: 10,
2929
+ background: focused ? "#fff" : "rgba(128,128,128,0.05)",
2930
+ transition: "background 0.15s, border-color 0.15s",
2931
+ borderColor: focused ? "#0084ff" : "rgba(128,128,128,0.2)"
2932
+ },
2933
+ children: [
2934
+ /* @__PURE__ */ jsx27("span", { style: { fontSize: 14, opacity: 0.5 }, children: "\u{1F50D}" }),
2935
+ /* @__PURE__ */ jsx27(
2936
+ "input",
2937
+ {
2938
+ type: "text",
2939
+ value: query,
2940
+ onChange: (e) => setQuery(e.target.value),
2941
+ onFocus: () => setFocused(true),
2942
+ onBlur: () => setTimeout(() => setFocused(false), 200),
2943
+ placeholder,
2944
+ style: {
2945
+ flex: 1,
2946
+ border: "none",
2947
+ outline: "none",
2948
+ fontSize: 13,
2949
+ background: "transparent"
2950
+ }
2951
+ }
2952
+ ),
2953
+ query && /* @__PURE__ */ jsx27(
2954
+ "button",
2955
+ {
2956
+ onClick: () => setQuery(""),
2957
+ style: {
2958
+ background: "none",
2959
+ border: "none",
2960
+ cursor: "pointer",
2961
+ fontSize: 14,
2962
+ opacity: 0.5,
2963
+ lineHeight: 1
2964
+ },
2965
+ children: "\u2715"
2966
+ }
2967
+ )
2968
+ ]
2969
+ }
2970
+ ),
2971
+ focused && query.trim() && /* @__PURE__ */ jsx27(
2972
+ "div",
2973
+ {
2974
+ style: {
2975
+ position: "absolute",
2976
+ top: "calc(100% + 4px)",
2977
+ left: 0,
2978
+ right: 0,
2979
+ zIndex: 50,
2980
+ background: "#fff",
2981
+ border: "1px solid rgba(128,128,128,0.15)",
2982
+ borderRadius: 10,
2983
+ boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
2984
+ maxHeight: 300,
2985
+ overflowY: "auto"
2986
+ },
2987
+ children: results.length === 0 ? /* @__PURE__ */ jsx27(
2988
+ "div",
2989
+ {
2990
+ style: {
2991
+ padding: 16,
2992
+ textAlign: "center",
2993
+ fontSize: 13,
2994
+ opacity: 0.5
2995
+ },
2996
+ children: "No results found"
2997
+ }
2998
+ ) : results.map((msg) => /* @__PURE__ */ jsxs16(
2999
+ "div",
3000
+ {
3001
+ onClick: () => handleSelect(msg),
3002
+ style: {
3003
+ padding: "8px 12px",
3004
+ cursor: "pointer",
3005
+ borderBottom: "1px solid rgba(128,128,128,0.08)",
3006
+ transition: "background 0.1s"
3007
+ },
3008
+ onMouseEnter: (e) => e.currentTarget.style.background = "rgba(0,132,255,0.05)",
3009
+ onMouseLeave: (e) => e.currentTarget.style.background = "transparent",
3010
+ children: [
3011
+ /* @__PURE__ */ jsx27(
3012
+ "div",
3013
+ {
3014
+ style: {
3015
+ fontSize: 13,
3016
+ overflow: "hidden",
3017
+ textOverflow: "ellipsis",
3018
+ whiteSpace: "nowrap"
3019
+ },
3020
+ children: msg.text
3021
+ }
3022
+ ),
3023
+ /* @__PURE__ */ jsx27("div", { style: { fontSize: 11, opacity: 0.5, marginTop: 2 }, children: new Date(msg.createdAt).toLocaleString() })
3024
+ ]
3025
+ },
3026
+ msg._id
3027
+ ))
3028
+ }
3029
+ )
3030
+ ]
3031
+ }
3032
+ );
3033
+ };
1873
3034
  export {
3035
+ Avatar,
3036
+ Chat,
3037
+ ChatContext,
1874
3038
  ChatInput,
3039
+ ChatProvider,
3040
+ ComponentContext,
3041
+ ComponentProvider,
3042
+ DateSeparator,
3043
+ EmptyStateIndicator,
1875
3044
  HermesClient,
3045
+ LoadingErrorIndicator,
3046
+ LoadingIndicator,
1876
3047
  MediaMessage,
3048
+ Message,
3049
+ MessageActions,
3050
+ MessageContext,
1877
3051
  MessageList,
3052
+ MessageProvider,
3053
+ MessageStatus,
3054
+ Modal,
1878
3055
  OnlineBadge,
1879
3056
  ReactionPicker,
3057
+ Room,
3058
+ RoomActionContext,
3059
+ RoomActionProvider,
1880
3060
  RoomList,
1881
- TypingIndicator2 as TypingIndicator,
3061
+ RoomStateContext,
3062
+ RoomStateProvider,
3063
+ Search,
3064
+ Thread,
3065
+ ThreadHeader,
3066
+ TypingContext,
3067
+ TypingIndicator,
3068
+ TypingProvider,
3069
+ Window,
3070
+ useChatContext,
3071
+ useComponentContext,
3072
+ useMessageContext,
1882
3073
  useMessages,
1883
3074
  usePresence,
1884
3075
  useReactions,
1885
3076
  useReadReceipts,
3077
+ useRoomActionContext,
3078
+ useRoomStateContext,
1886
3079
  useRooms,
1887
3080
  useTyping,
3081
+ useTypingContext,
1888
3082
  useUpload
1889
3083
  };
1890
3084
  //# sourceMappingURL=react.js.map