@droppii-org/chat-sdk 0.0.1

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.
Files changed (100) hide show
  1. package/dist/components/AutoScrollAnchor.d.ts +2 -0
  2. package/dist/components/AutoScrollAnchor.d.ts.map +1 -0
  3. package/dist/components/AutoScrollAnchor.jsx +11 -0
  4. package/dist/components/ChatBubble.d.ts +2 -0
  5. package/dist/components/ChatBubble.d.ts.map +1 -0
  6. package/dist/components/ChatBubble.jsx +32 -0
  7. package/dist/components/ChatHeader.d.ts +8 -0
  8. package/dist/components/ChatHeader.d.ts.map +1 -0
  9. package/dist/components/ChatHeader.jsx +72 -0
  10. package/dist/components/ChatInput.d.ts +4 -0
  11. package/dist/components/ChatInput.d.ts.map +1 -0
  12. package/dist/components/ChatInput.jsx +444 -0
  13. package/dist/components/ChatInputDemo.d.ts +2 -0
  14. package/dist/components/ChatInputDemo.d.ts.map +1 -0
  15. package/dist/components/ChatInputDemo.jsx +53 -0
  16. package/dist/components/ChatInputWithCustomIcon.d.ts +17 -0
  17. package/dist/components/ChatInputWithCustomIcon.d.ts.map +1 -0
  18. package/dist/components/ChatInputWithCustomIcon.jsx +167 -0
  19. package/dist/components/ChatLayout.d.ts +6 -0
  20. package/dist/components/ChatLayout.d.ts.map +1 -0
  21. package/dist/components/ChatLayout.jsx +122 -0
  22. package/dist/components/ConversationItem.d.ts +9 -0
  23. package/dist/components/ConversationItem.d.ts.map +1 -0
  24. package/dist/components/ConversationItem.jsx +51 -0
  25. package/dist/components/ConversationList.d.ts +8 -0
  26. package/dist/components/ConversationList.d.ts.map +1 -0
  27. package/dist/components/ConversationList.jsx +22 -0
  28. package/dist/components/DateDivider.d.ts +7 -0
  29. package/dist/components/DateDivider.d.ts.map +1 -0
  30. package/dist/components/DateDivider.jsx +28 -0
  31. package/dist/components/EmojiPicker.d.ts +4 -0
  32. package/dist/components/EmojiPicker.d.ts.map +1 -0
  33. package/dist/components/EmojiPicker.jsx +229 -0
  34. package/dist/components/ImageLightbox.d.ts +8 -0
  35. package/dist/components/ImageLightbox.d.ts.map +1 -0
  36. package/dist/components/ImageLightbox.jsx +16 -0
  37. package/dist/components/ImagePreviewModal.d.ts +12 -0
  38. package/dist/components/ImagePreviewModal.d.ts.map +1 -0
  39. package/dist/components/ImagePreviewModal.jsx +84 -0
  40. package/dist/components/MessageItem.d.ts +3 -0
  41. package/dist/components/MessageItem.d.ts.map +1 -0
  42. package/dist/components/MessageItem.jsx +99 -0
  43. package/dist/components/MessageItemDemo.d.ts +2 -0
  44. package/dist/components/MessageItemDemo.d.ts.map +1 -0
  45. package/dist/components/MessageItemDemo.jsx +179 -0
  46. package/dist/components/MessageList.d.ts +15 -0
  47. package/dist/components/MessageList.d.ts.map +1 -0
  48. package/dist/components/MessageList.jsx +306 -0
  49. package/dist/components/MessageListDemo.d.ts +2 -0
  50. package/dist/components/MessageListDemo.d.ts.map +1 -0
  51. package/dist/components/MessageListDemo.jsx +183 -0
  52. package/dist/components/StickerPicker.d.ts +4 -0
  53. package/dist/components/StickerPicker.d.ts.map +1 -0
  54. package/dist/components/StickerPicker.jsx +106 -0
  55. package/dist/components/SwipeIndicator.d.ts +9 -0
  56. package/dist/components/SwipeIndicator.d.ts.map +1 -0
  57. package/dist/components/SwipeIndicator.jsx +28 -0
  58. package/dist/components/TextFormattingToolbar.d.ts +4 -0
  59. package/dist/components/TextFormattingToolbar.d.ts.map +1 -0
  60. package/dist/components/TextFormattingToolbar.jsx +52 -0
  61. package/dist/components/TypingIndicator.d.ts +6 -0
  62. package/dist/components/TypingIndicator.d.ts.map +1 -0
  63. package/dist/components/TypingIndicator.jsx +27 -0
  64. package/dist/components/VoiceWaveIcon.d.ts +7 -0
  65. package/dist/components/VoiceWaveIcon.d.ts.map +1 -0
  66. package/dist/components/VoiceWaveIcon.jsx +11 -0
  67. package/dist/context/ChatContext.d.ts +72 -0
  68. package/dist/context/ChatContext.d.ts.map +1 -0
  69. package/dist/context/ChatContext.jsx +346 -0
  70. package/dist/hooks/useChat.d.ts +5 -0
  71. package/dist/hooks/useChat.d.ts.map +1 -0
  72. package/dist/hooks/useChat.js +73 -0
  73. package/dist/hooks/useConversationList.d.ts +5 -0
  74. package/dist/hooks/useConversationList.d.ts.map +1 -0
  75. package/dist/hooks/useConversationList.js +9 -0
  76. package/dist/hooks/useMessages.d.ts +5 -0
  77. package/dist/hooks/useMessages.d.ts.map +1 -0
  78. package/dist/hooks/useMessages.js +192 -0
  79. package/dist/hooks/useSocket.d.ts +7 -0
  80. package/dist/hooks/useSocket.d.ts.map +1 -0
  81. package/dist/hooks/useSocket.js +120 -0
  82. package/dist/hooks/useSwipeGesture.d.ts +11 -0
  83. package/dist/hooks/useSwipeGesture.d.ts.map +1 -0
  84. package/dist/hooks/useSwipeGesture.js +54 -0
  85. package/dist/hooks/useTextSelection.d.ts +13 -0
  86. package/dist/hooks/useTextSelection.d.ts.map +1 -0
  87. package/dist/hooks/useTextSelection.js +132 -0
  88. package/dist/hooks/useTyping.d.ts +7 -0
  89. package/dist/hooks/useTyping.d.ts.map +1 -0
  90. package/dist/hooks/useTyping.js +64 -0
  91. package/dist/index.d.ts +28 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +29 -0
  94. package/dist/types/chat.d.ts +39 -0
  95. package/dist/types/chat.d.ts.map +1 -0
  96. package/dist/types/chat.js +1 -0
  97. package/dist/types/index.d.ts +86 -0
  98. package/dist/types/index.d.ts.map +1 -0
  99. package/dist/types/index.js +1 -0
  100. package/package.json +60 -0
@@ -0,0 +1,5 @@
1
+ export declare function useConversationList(): {
2
+ conversations: import("..").Conversation[];
3
+ isLoading: boolean;
4
+ };
5
+ //# sourceMappingURL=useConversationList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationList.d.ts","sourceRoot":"","sources":["../../src/hooks/useConversationList.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB;;;EAOlC"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ import { useChatContext } from "../context/ChatContext";
3
+ export function useConversationList() {
4
+ const { state } = useChatContext();
5
+ return {
6
+ conversations: state.conversations || [],
7
+ isLoading: false,
8
+ };
9
+ }
@@ -0,0 +1,5 @@
1
+ export declare function useMessages(conversationId: string): {
2
+ messages: import("..").Message[];
3
+ isLoading: boolean;
4
+ };
5
+ //# sourceMappingURL=useMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useMessages.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,cAAc,EAAE,MAAM;;;EA+LjD"}
@@ -0,0 +1,192 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ import { useChatContext } from "../context/ChatContext";
4
+ export function useMessages(conversationId) {
5
+ const { state, dispatch } = useChatContext();
6
+ useEffect(() => {
7
+ var _a;
8
+ // Load messages for conversation if not already loaded
9
+ if (conversationId && !state.messages[conversationId] && ((_a = state.config) === null || _a === void 0 ? void 0 : _a.token)) {
10
+ // For demo, add some mock messages with different types
11
+ const mockMessages = [
12
+ // Older messages (yesterday)
13
+ {
14
+ id: "old-1",
15
+ conversationId,
16
+ senderId: "user-2",
17
+ content: "Hey! How was your weekend?",
18
+ type: "text",
19
+ timestamp: new Date(Date.now() - 86400000 - 3600000), // Yesterday, 1 hour earlier
20
+ status: "read",
21
+ },
22
+ {
23
+ id: "old-2",
24
+ conversationId,
25
+ senderId: state.config.userId,
26
+ content: "It was great! Went hiking with friends 🏔️",
27
+ type: "text",
28
+ timestamp: new Date(Date.now() - 86400000 - 3000000), // Yesterday, 50 minutes earlier
29
+ status: "read",
30
+ },
31
+ {
32
+ id: "old-3",
33
+ conversationId,
34
+ senderId: state.config.userId,
35
+ content: "Here are some photos from the trip!",
36
+ type: "image",
37
+ timestamp: new Date(Date.now() - 86400000 - 2700000), // Yesterday, 45 minutes earlier
38
+ status: "read",
39
+ attachments: [
40
+ {
41
+ id: "att-old-1",
42
+ name: "mountain1.jpg",
43
+ url: "/placeholder.svg?height=300&width=400",
44
+ type: "image/jpeg",
45
+ size: 245760,
46
+ },
47
+ {
48
+ id: "att-old-2",
49
+ name: "mountain2.jpg",
50
+ url: "/placeholder.svg?height=300&width=400",
51
+ type: "image/jpeg",
52
+ size: 198432,
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ id: "old-4",
58
+ conversationId,
59
+ senderId: "user-2",
60
+ content: "Wow, those are amazing! 😍 The view is incredible!",
61
+ type: "text",
62
+ timestamp: new Date(Date.now() - 86400000 - 2400000), // Yesterday, 40 minutes earlier
63
+ status: "read",
64
+ },
65
+ // Today's messages
66
+ {
67
+ id: "1",
68
+ conversationId,
69
+ senderId: "user-2",
70
+ content: "Hey there! How are you doing today? 😊",
71
+ type: "text",
72
+ timestamp: new Date(Date.now() - 3600000), // 1 hour ago
73
+ status: "read",
74
+ },
75
+ {
76
+ id: "2",
77
+ conversationId,
78
+ senderId: state.config.userId,
79
+ content: "I'm doing great! Thanks for asking. How about you?",
80
+ type: "text",
81
+ timestamp: new Date(Date.now() - 3000000), // 50 minutes ago
82
+ status: "read",
83
+ },
84
+ {
85
+ id: "3",
86
+ conversationId,
87
+ senderId: "user-2",
88
+ content: "I'm good too! By the way, check out this cool website: https://example.com",
89
+ type: "text",
90
+ timestamp: new Date(Date.now() - 2700000), // 45 minutes ago
91
+ status: "read",
92
+ },
93
+ {
94
+ id: "4",
95
+ conversationId,
96
+ senderId: state.config.userId,
97
+ content: "Look at this beautiful sunset!",
98
+ type: "image",
99
+ timestamp: new Date(Date.now() - 2400000), // 40 minutes ago
100
+ status: "read",
101
+ attachments: [
102
+ {
103
+ id: "att-1",
104
+ name: "sunset.jpg",
105
+ url: "/placeholder.svg?height=300&width=400",
106
+ type: "image/jpeg",
107
+ size: 245760,
108
+ },
109
+ ],
110
+ },
111
+ {
112
+ id: "5",
113
+ conversationId,
114
+ senderId: "user-2",
115
+ content: "Here's the document you requested",
116
+ type: "file",
117
+ timestamp: new Date(Date.now() - 1800000), // 30 minutes ago
118
+ status: "read",
119
+ attachments: [
120
+ {
121
+ id: "att-2",
122
+ name: "project-proposal.pdf",
123
+ url: "/placeholder.svg?height=200&width=200",
124
+ type: "application/pdf",
125
+ size: 1024000,
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ id: "6",
131
+ conversationId,
132
+ senderId: state.config.userId,
133
+ content: "Photos from my vacation 📸",
134
+ type: "image",
135
+ timestamp: new Date(Date.now() - 1200000), // 20 minutes ago
136
+ status: "read",
137
+ attachments: [
138
+ {
139
+ id: "att-3",
140
+ name: "beach1.jpg",
141
+ url: "/placeholder.svg?height=200&width=300",
142
+ type: "image/jpeg",
143
+ size: 180000,
144
+ },
145
+ {
146
+ id: "att-4",
147
+ name: "beach2.jpg",
148
+ url: "/placeholder.svg?height=200&width=300",
149
+ type: "image/jpeg",
150
+ size: 195000,
151
+ },
152
+ ],
153
+ },
154
+ {
155
+ id: "7",
156
+ conversationId,
157
+ senderId: "user-2",
158
+ content: "Wow, those photos are amazing! 🏖️",
159
+ type: "text",
160
+ timestamp: new Date(Date.now() - 600000), // 10 minutes ago
161
+ status: "read",
162
+ },
163
+ {
164
+ id: "8",
165
+ conversationId,
166
+ senderId: state.config.userId,
167
+ content: "Thanks! The weather was perfect 🌞",
168
+ type: "text",
169
+ timestamp: new Date(Date.now() - 300000), // 5 minutes ago
170
+ status: "read",
171
+ },
172
+ {
173
+ id: "9",
174
+ conversationId,
175
+ senderId: "user-2",
176
+ content: "I'm so jealous! I need a vacation too 😅",
177
+ type: "text",
178
+ timestamp: new Date(Date.now() - 60000), // 1 minute ago
179
+ status: "delivered",
180
+ },
181
+ ];
182
+ dispatch({
183
+ type: "SET_MESSAGES",
184
+ payload: { conversationId, messages: mockMessages },
185
+ });
186
+ }
187
+ }, [conversationId, state.messages, state.config, dispatch]);
188
+ return {
189
+ messages: state.messages[conversationId] || [], // Ensure always returns array
190
+ isLoading: false,
191
+ };
192
+ }
@@ -0,0 +1,7 @@
1
+ export declare function useSocket(): {
2
+ isConnected: boolean;
3
+ sendMessage: (message: any) => boolean;
4
+ disconnect: () => void;
5
+ reconnect: () => void;
6
+ };
7
+ //# sourceMappingURL=useSocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useSocket.ts"],"names":[],"mappings":"AAMA,wBAAgB,SAAS;;2BAgGmB,GAAG;;;EA+B9C"}
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import { useEffect, useRef, useCallback } from "react";
3
+ import { useChatContext } from "../context/ChatContext";
4
+ export function useSocket() {
5
+ const { state, dispatch } = useChatContext();
6
+ const socketRef = useRef(null);
7
+ const reconnectTimeoutRef = useRef();
8
+ const reconnectAttempts = useRef(0);
9
+ const maxReconnectAttempts = 3;
10
+ const connect = useCallback(() => {
11
+ var _a;
12
+ // Skip WebSocket connection in demo mode or if no URL provided
13
+ if (!((_a = state.config) === null || _a === void 0 ? void 0 : _a.wsUrl) || state.config.wsUrl.includes("localhost") || state.config.wsUrl === "demo") {
14
+ console.log("WebSocket disabled for demo mode");
15
+ dispatch({ type: "SET_CONNECTION_STATUS", payload: false });
16
+ return;
17
+ }
18
+ if (reconnectAttempts.current >= maxReconnectAttempts) {
19
+ console.log("Max reconnection attempts reached");
20
+ return;
21
+ }
22
+ try {
23
+ const ws = new WebSocket(`${state.config.wsUrl}?token=${state.config.token}`);
24
+ ws.onopen = () => {
25
+ console.log("WebSocket connected");
26
+ dispatch({ type: "SET_CONNECTION_STATUS", payload: true });
27
+ socketRef.current = ws;
28
+ reconnectAttempts.current = 0;
29
+ };
30
+ ws.onmessage = (event) => {
31
+ try {
32
+ const message = JSON.parse(event.data);
33
+ handleSocketMessage(message);
34
+ }
35
+ catch (error) {
36
+ console.error("Failed to parse socket message:", error);
37
+ }
38
+ };
39
+ ws.onclose = (event) => {
40
+ console.log("WebSocket disconnected", event.code, event.reason);
41
+ dispatch({ type: "SET_CONNECTION_STATUS", payload: false });
42
+ socketRef.current = null;
43
+ // Only attempt to reconnect if it wasn't a manual close
44
+ if (event.code !== 1000 && reconnectAttempts.current < maxReconnectAttempts) {
45
+ reconnectAttempts.current++;
46
+ reconnectTimeoutRef.current = setTimeout(() => {
47
+ console.log(`Reconnection attempt ${reconnectAttempts.current}`);
48
+ connect();
49
+ }, 3000 * reconnectAttempts.current); // Exponential backoff
50
+ }
51
+ };
52
+ ws.onerror = (error) => {
53
+ console.warn("WebSocket connection failed - running in offline mode");
54
+ dispatch({ type: "SET_CONNECTION_STATUS", payload: false });
55
+ // Don't attempt to reconnect on error to avoid spam
56
+ reconnectAttempts.current = maxReconnectAttempts;
57
+ };
58
+ }
59
+ catch (error) {
60
+ console.warn("Failed to create WebSocket connection - running in offline mode");
61
+ dispatch({ type: "SET_CONNECTION_STATUS", payload: false });
62
+ }
63
+ }, [state.config]);
64
+ const handleSocketMessage = useCallback((message) => {
65
+ switch (message.type) {
66
+ case "message":
67
+ dispatch({ type: "ADD_MESSAGE", payload: message.data });
68
+ break;
69
+ case "typing":
70
+ if (message.data.isTyping) {
71
+ dispatch({ type: "SET_TYPING", payload: message.data });
72
+ }
73
+ else {
74
+ dispatch({
75
+ type: "REMOVE_TYPING",
76
+ payload: {
77
+ userId: message.data.userId,
78
+ conversationId: message.data.conversationId,
79
+ },
80
+ });
81
+ }
82
+ break;
83
+ case "read":
84
+ dispatch({ type: "UPDATE_MESSAGE", payload: message.data });
85
+ break;
86
+ case "user_status":
87
+ dispatch({ type: "UPDATE_USER", payload: message.data });
88
+ break;
89
+ }
90
+ }, [dispatch]);
91
+ const sendMessage = useCallback((message) => {
92
+ var _a;
93
+ if (((_a = socketRef.current) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
94
+ socketRef.current.send(JSON.stringify(message));
95
+ return true;
96
+ }
97
+ console.log("WebSocket not connected - message not sent:", message);
98
+ return false;
99
+ }, []);
100
+ const disconnect = useCallback(() => {
101
+ if (reconnectTimeoutRef.current) {
102
+ clearTimeout(reconnectTimeoutRef.current);
103
+ }
104
+ if (socketRef.current) {
105
+ socketRef.current.close(1000, "Manual disconnect");
106
+ socketRef.current = null;
107
+ }
108
+ reconnectAttempts.current = maxReconnectAttempts;
109
+ }, []);
110
+ useEffect(() => {
111
+ connect();
112
+ return disconnect;
113
+ }, [connect, disconnect]);
114
+ return {
115
+ isConnected: state.isConnected,
116
+ sendMessage,
117
+ disconnect,
118
+ reconnect: connect,
119
+ };
120
+ }
@@ -0,0 +1,11 @@
1
+ interface SwipeGestureOptions {
2
+ onSwipeRight?: () => void;
3
+ onSwipeLeft?: () => void;
4
+ threshold?: number;
5
+ restraint?: number;
6
+ allowedTime?: number;
7
+ enabled?: boolean;
8
+ }
9
+ export declare function useSwipeGesture({ onSwipeRight, onSwipeLeft, threshold, restraint, allowedTime, enabled, }: SwipeGestureOptions): import("react").RefObject<HTMLElement>;
10
+ export {};
11
+ //# sourceMappingURL=useSwipeGesture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSwipeGesture.d.ts","sourceRoot":"","sources":["../../src/hooks/useSwipeGesture.ts"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,eAAe,CAAC,EAC9B,YAAY,EACZ,WAAW,EACX,SAAe,EACf,SAAe,EACf,WAAiB,EACjB,OAAc,GACf,EAAE,mBAAmB,0CA+DrB"}
@@ -0,0 +1,54 @@
1
+ "use client";
2
+ import { useEffect, useRef, useCallback } from "react";
3
+ export function useSwipeGesture({ onSwipeRight, onSwipeLeft, threshold = 100, restraint = 100, allowedTime = 300, enabled = true, }) {
4
+ const touchStartX = useRef(0);
5
+ const touchStartY = useRef(0);
6
+ const touchStartTime = useRef(0);
7
+ const elementRef = useRef(null);
8
+ const handleTouchStart = useCallback((e) => {
9
+ if (!enabled)
10
+ return;
11
+ const touch = e.touches[0];
12
+ touchStartX.current = touch.clientX;
13
+ touchStartY.current = touch.clientY;
14
+ touchStartTime.current = Date.now();
15
+ }, [enabled]);
16
+ const handleTouchEnd = useCallback((e) => {
17
+ if (!enabled)
18
+ return;
19
+ const touch = e.changedTouches[0];
20
+ const touchEndX = touch.clientX;
21
+ const touchEndY = touch.clientY;
22
+ const touchEndTime = Date.now();
23
+ const distanceX = touchEndX - touchStartX.current;
24
+ const distanceY = touchEndY - touchStartY.current;
25
+ const elapsedTime = touchEndTime - touchStartTime.current;
26
+ // Check if the swipe meets our criteria
27
+ if (elapsedTime <= allowedTime) {
28
+ // Check if horizontal distance is sufficient and vertical distance is within restraint
29
+ if (Math.abs(distanceX) >= threshold && Math.abs(distanceY) <= restraint) {
30
+ if (distanceX > 0) {
31
+ // Swipe right
32
+ onSwipeRight === null || onSwipeRight === void 0 ? void 0 : onSwipeRight();
33
+ }
34
+ else {
35
+ // Swipe left
36
+ onSwipeLeft === null || onSwipeLeft === void 0 ? void 0 : onSwipeLeft();
37
+ }
38
+ }
39
+ }
40
+ }, [enabled, threshold, restraint, allowedTime, onSwipeRight, onSwipeLeft]);
41
+ useEffect(() => {
42
+ const element = elementRef.current;
43
+ if (!element || !enabled)
44
+ return;
45
+ // Add passive event listeners for better performance
46
+ element.addEventListener("touchstart", handleTouchStart, { passive: true });
47
+ element.addEventListener("touchend", handleTouchEnd, { passive: true });
48
+ return () => {
49
+ element.removeEventListener("touchstart", handleTouchStart);
50
+ element.removeEventListener("touchend", handleTouchEnd);
51
+ };
52
+ }, [handleTouchStart, handleTouchEnd, enabled]);
53
+ return elementRef;
54
+ }
@@ -0,0 +1,13 @@
1
+ export declare function useTextSelection(): {
2
+ textareaRef: import("react").RefObject<HTMLTextAreaElement>;
3
+ getSelection: () => {
4
+ start: number;
5
+ end: number;
6
+ selectedText: string;
7
+ hasSelection: boolean;
8
+ } | null;
9
+ replaceSelection: (newText: string, selectReplaced?: boolean) => void;
10
+ wrapSelection: (prefix: string, suffix?: string) => void;
11
+ applyFormat: (format: string) => void;
12
+ };
13
+ //# sourceMappingURL=useTextSelection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTextSelection.d.ts","sourceRoot":"","sources":["../../src/hooks/useTextSelection.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB;;;;;;;;gCAmBiB,MAAM;4BA4B1C,MAAM,WAAU,MAAM;0BAqBtB,MAAM;EA0ElB"}
@@ -0,0 +1,132 @@
1
+ "use client";
2
+ import { useCallback, useRef } from "react";
3
+ export function useTextSelection() {
4
+ const textareaRef = useRef(null);
5
+ const getSelection = useCallback(() => {
6
+ const textarea = textareaRef.current;
7
+ if (!textarea)
8
+ return null;
9
+ const start = textarea.selectionStart;
10
+ const end = textarea.selectionEnd;
11
+ const selectedText = textarea.value.substring(start, end);
12
+ return {
13
+ start,
14
+ end,
15
+ selectedText,
16
+ hasSelection: start !== end,
17
+ };
18
+ }, []);
19
+ const replaceSelection = useCallback((newText, selectReplaced = false) => {
20
+ const textarea = textareaRef.current;
21
+ if (!textarea)
22
+ return;
23
+ const start = textarea.selectionStart;
24
+ const end = textarea.selectionEnd;
25
+ const value = textarea.value;
26
+ const newValue = value.substring(0, start) + newText + value.substring(end);
27
+ // Update the textarea value
28
+ textarea.value = newValue;
29
+ // Trigger change event
30
+ const event = new Event("input", { bubbles: true });
31
+ textarea.dispatchEvent(event);
32
+ // Set cursor position
33
+ if (selectReplaced) {
34
+ textarea.setSelectionRange(start, start + newText.length);
35
+ }
36
+ else {
37
+ textarea.setSelectionRange(start + newText.length, start + newText.length);
38
+ }
39
+ textarea.focus();
40
+ }, []);
41
+ const wrapSelection = useCallback((prefix, suffix = prefix) => {
42
+ const selection = getSelection();
43
+ if (!selection)
44
+ return;
45
+ const { selectedText, hasSelection } = selection;
46
+ if (hasSelection) {
47
+ // Wrap selected text
48
+ const wrappedText = `${prefix}${selectedText}${suffix}`;
49
+ replaceSelection(wrappedText, true);
50
+ }
51
+ else {
52
+ // Insert formatting markers at cursor
53
+ const placeholder = prefix === suffix ? "text" : "link";
54
+ const wrappedText = `${prefix}${placeholder}${suffix}`;
55
+ replaceSelection(wrappedText, true);
56
+ }
57
+ }, [getSelection, replaceSelection]);
58
+ const applyFormat = useCallback((format) => {
59
+ const selection = getSelection();
60
+ if (!selection)
61
+ return;
62
+ const { selectedText, hasSelection, start } = selection;
63
+ switch (format) {
64
+ case "bold":
65
+ wrapSelection("**");
66
+ break;
67
+ case "italic":
68
+ wrapSelection("*");
69
+ break;
70
+ case "strikethrough":
71
+ wrapSelection("~~");
72
+ break;
73
+ case "code":
74
+ wrapSelection("`");
75
+ break;
76
+ case "quote":
77
+ if (hasSelection) {
78
+ const quotedText = selectedText
79
+ .split("\n")
80
+ .map((line) => `> ${line}`)
81
+ .join("\n");
82
+ replaceSelection(quotedText);
83
+ }
84
+ else {
85
+ replaceSelection("> ");
86
+ }
87
+ break;
88
+ case "link":
89
+ if (hasSelection) {
90
+ const linkText = `[${selectedText}](url)`;
91
+ replaceSelection(linkText, true);
92
+ }
93
+ else {
94
+ replaceSelection("[text](url)", true);
95
+ }
96
+ break;
97
+ case "numbered-list":
98
+ if (hasSelection) {
99
+ const listText = selectedText
100
+ .split("\n")
101
+ .map((line, index) => `${index + 1}. ${line}`)
102
+ .join("\n");
103
+ replaceSelection(listText);
104
+ }
105
+ else {
106
+ replaceSelection("1. ");
107
+ }
108
+ break;
109
+ case "bullet-list":
110
+ if (hasSelection) {
111
+ const listText = selectedText
112
+ .split("\n")
113
+ .map((line) => `• ${line}`)
114
+ .join("\n");
115
+ replaceSelection(listText);
116
+ }
117
+ else {
118
+ replaceSelection("• ");
119
+ }
120
+ break;
121
+ default:
122
+ break;
123
+ }
124
+ }, [getSelection, wrapSelection, replaceSelection]);
125
+ return {
126
+ textareaRef,
127
+ getSelection,
128
+ replaceSelection,
129
+ wrapSelection,
130
+ applyFormat,
131
+ };
132
+ }
@@ -0,0 +1,7 @@
1
+ export declare function useTyping(conversationId: string): {
2
+ startTyping: () => void;
3
+ stopTyping: () => void;
4
+ typingUsers: import("..").User[];
5
+ isTyping: boolean;
6
+ };
7
+ //# sourceMappingURL=useTyping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTyping.d.ts","sourceRoot":"","sources":["../../src/hooks/useTyping.ts"],"names":[],"mappings":"AAMA,wBAAgB,SAAS,CAAC,cAAc,EAAE,MAAM;;;;;EAgE/C"}
@@ -0,0 +1,64 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ import { useChatContext } from "../context/ChatContext";
4
+ import { useSocket } from "./useSocket";
5
+ export function useTyping(conversationId) {
6
+ var _a, _b;
7
+ const { state } = useChatContext();
8
+ const { sendMessage } = useSocket();
9
+ const typingTimeoutRef = useRef();
10
+ const startTyping = useCallback(() => {
11
+ var _a;
12
+ if (!((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId) || !conversationId)
13
+ return;
14
+ sendMessage({
15
+ type: "typing",
16
+ data: {
17
+ userId: state.config.userId,
18
+ conversationId,
19
+ isTyping: true,
20
+ },
21
+ });
22
+ // Clear existing timeout
23
+ if (typingTimeoutRef.current) {
24
+ clearTimeout(typingTimeoutRef.current);
25
+ }
26
+ // Stop typing after 3 seconds of inactivity
27
+ typingTimeoutRef.current = setTimeout(() => {
28
+ stopTyping();
29
+ }, 3000);
30
+ }, [(_a = state.config) === null || _a === void 0 ? void 0 : _a.userId, conversationId, sendMessage]);
31
+ const stopTyping = useCallback(() => {
32
+ var _a;
33
+ if (!((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId) || !conversationId)
34
+ return;
35
+ sendMessage({
36
+ type: "typing",
37
+ data: {
38
+ userId: state.config.userId,
39
+ conversationId,
40
+ isTyping: false,
41
+ },
42
+ });
43
+ if (typingTimeoutRef.current) {
44
+ clearTimeout(typingTimeoutRef.current);
45
+ }
46
+ }, [(_b = state.config) === null || _b === void 0 ? void 0 : _b.userId, conversationId, sendMessage]);
47
+ const typingUsers = state.typingStatuses
48
+ .filter((t) => { var _a; return t.conversationId === conversationId && t.userId !== ((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId) && t.isTyping; })
49
+ .map((t) => state.users[t.userId])
50
+ .filter(Boolean);
51
+ useEffect(() => {
52
+ return () => {
53
+ if (typingTimeoutRef.current) {
54
+ clearTimeout(typingTimeoutRef.current);
55
+ }
56
+ };
57
+ }, []);
58
+ return {
59
+ startTyping,
60
+ stopTyping,
61
+ typingUsers,
62
+ isTyping: typingUsers.length > 0,
63
+ };
64
+ }