@parlr/react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +918 -0
  3. package/lib/commonjs/components/AttachmentPicker.js +292 -0
  4. package/lib/commonjs/components/AttachmentPicker.js.map +1 -0
  5. package/lib/commonjs/components/AttachmentPreview.js +200 -0
  6. package/lib/commonjs/components/AttachmentPreview.js.map +1 -0
  7. package/lib/commonjs/components/ChatBubble.js +391 -0
  8. package/lib/commonjs/components/ChatBubble.js.map +1 -0
  9. package/lib/commonjs/components/EmptyState.js +115 -0
  10. package/lib/commonjs/components/EmptyState.js.map +1 -0
  11. package/lib/commonjs/components/ParlrChat.js +745 -0
  12. package/lib/commonjs/components/ParlrChat.js.map +1 -0
  13. package/lib/commonjs/components/ParlrConversationList.js +509 -0
  14. package/lib/commonjs/components/ParlrConversationList.js.map +1 -0
  15. package/lib/commonjs/components/PreChatForm.js +263 -0
  16. package/lib/commonjs/components/PreChatForm.js.map +1 -0
  17. package/lib/commonjs/components/RichMessage.js +284 -0
  18. package/lib/commonjs/components/RichMessage.js.map +1 -0
  19. package/lib/commonjs/components/SatisfactionSurvey.js +292 -0
  20. package/lib/commonjs/components/SatisfactionSurvey.js.map +1 -0
  21. package/lib/commonjs/components/TypingIndicator.js +86 -0
  22. package/lib/commonjs/components/TypingIndicator.js.map +1 -0
  23. package/lib/commonjs/core/api.js +310 -0
  24. package/lib/commonjs/core/api.js.map +1 -0
  25. package/lib/commonjs/core/config.js +40 -0
  26. package/lib/commonjs/core/config.js.map +1 -0
  27. package/lib/commonjs/core/errors.js +73 -0
  28. package/lib/commonjs/core/errors.js.map +1 -0
  29. package/lib/commonjs/core/offlineQueue.js +89 -0
  30. package/lib/commonjs/core/offlineQueue.js.map +1 -0
  31. package/lib/commonjs/core/pushNotifications.js +21 -0
  32. package/lib/commonjs/core/pushNotifications.js.map +1 -0
  33. package/lib/commonjs/core/session.js +130 -0
  34. package/lib/commonjs/core/session.js.map +1 -0
  35. package/lib/commonjs/core/theme.js +110 -0
  36. package/lib/commonjs/core/theme.js.map +1 -0
  37. package/lib/commonjs/core/types.js +6 -0
  38. package/lib/commonjs/core/types.js.map +1 -0
  39. package/lib/commonjs/core/websocket.js +245 -0
  40. package/lib/commonjs/core/websocket.js.map +1 -0
  41. package/lib/commonjs/hooks/useChat.js +462 -0
  42. package/lib/commonjs/hooks/useChat.js.map +1 -0
  43. package/lib/commonjs/hooks/useParlr.js +44 -0
  44. package/lib/commonjs/hooks/useParlr.js.map +1 -0
  45. package/lib/commonjs/index.js +185 -0
  46. package/lib/commonjs/index.js.map +1 -0
  47. package/lib/commonjs/package.json +1 -0
  48. package/lib/commonjs/provider/ParlrContext.js +38 -0
  49. package/lib/commonjs/provider/ParlrContext.js.map +1 -0
  50. package/lib/commonjs/provider/ParlrProvider.js +256 -0
  51. package/lib/commonjs/provider/ParlrProvider.js.map +1 -0
  52. package/lib/module/components/AttachmentPicker.js +287 -0
  53. package/lib/module/components/AttachmentPicker.js.map +1 -0
  54. package/lib/module/components/AttachmentPreview.js +195 -0
  55. package/lib/module/components/AttachmentPreview.js.map +1 -0
  56. package/lib/module/components/ChatBubble.js +386 -0
  57. package/lib/module/components/ChatBubble.js.map +1 -0
  58. package/lib/module/components/EmptyState.js +110 -0
  59. package/lib/module/components/EmptyState.js.map +1 -0
  60. package/lib/module/components/ParlrChat.js +740 -0
  61. package/lib/module/components/ParlrChat.js.map +1 -0
  62. package/lib/module/components/ParlrConversationList.js +504 -0
  63. package/lib/module/components/ParlrConversationList.js.map +1 -0
  64. package/lib/module/components/PreChatForm.js +258 -0
  65. package/lib/module/components/PreChatForm.js.map +1 -0
  66. package/lib/module/components/RichMessage.js +280 -0
  67. package/lib/module/components/RichMessage.js.map +1 -0
  68. package/lib/module/components/SatisfactionSurvey.js +287 -0
  69. package/lib/module/components/SatisfactionSurvey.js.map +1 -0
  70. package/lib/module/components/TypingIndicator.js +81 -0
  71. package/lib/module/components/TypingIndicator.js.map +1 -0
  72. package/lib/module/core/api.js +305 -0
  73. package/lib/module/core/api.js.map +1 -0
  74. package/lib/module/core/config.js +36 -0
  75. package/lib/module/core/config.js.map +1 -0
  76. package/lib/module/core/errors.js +64 -0
  77. package/lib/module/core/errors.js.map +1 -0
  78. package/lib/module/core/offlineQueue.js +82 -0
  79. package/lib/module/core/offlineQueue.js.map +1 -0
  80. package/lib/module/core/pushNotifications.js +16 -0
  81. package/lib/module/core/pushNotifications.js.map +1 -0
  82. package/lib/module/core/session.js +122 -0
  83. package/lib/module/core/session.js.map +1 -0
  84. package/lib/module/core/theme.js +105 -0
  85. package/lib/module/core/theme.js.map +1 -0
  86. package/lib/module/core/types.js +4 -0
  87. package/lib/module/core/types.js.map +1 -0
  88. package/lib/module/core/websocket.js +241 -0
  89. package/lib/module/core/websocket.js.map +1 -0
  90. package/lib/module/hooks/useChat.js +458 -0
  91. package/lib/module/hooks/useChat.js.map +1 -0
  92. package/lib/module/hooks/useParlr.js +40 -0
  93. package/lib/module/hooks/useParlr.js.map +1 -0
  94. package/lib/module/index.js +58 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/module/package.json +1 -0
  97. package/lib/module/provider/ParlrContext.js +35 -0
  98. package/lib/module/provider/ParlrContext.js.map +1 -0
  99. package/lib/module/provider/ParlrProvider.js +251 -0
  100. package/lib/module/provider/ParlrProvider.js.map +1 -0
  101. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts +23 -0
  102. package/lib/typescript/commonjs/components/AttachmentPicker.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts +16 -0
  104. package/lib/typescript/commonjs/components/AttachmentPreview.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/components/ChatBubble.d.ts +14 -0
  106. package/lib/typescript/commonjs/components/ChatBubble.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/components/EmptyState.d.ts +10 -0
  108. package/lib/typescript/commonjs/components/EmptyState.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/components/ParlrChat.d.ts +34 -0
  110. package/lib/typescript/commonjs/components/ParlrChat.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts +17 -0
  112. package/lib/typescript/commonjs/components/ParlrConversationList.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/components/PreChatForm.d.ts +20 -0
  114. package/lib/typescript/commonjs/components/PreChatForm.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/components/RichMessage.d.ts +41 -0
  116. package/lib/typescript/commonjs/components/RichMessage.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts +17 -0
  118. package/lib/typescript/commonjs/components/SatisfactionSurvey.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/components/TypingIndicator.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/TypingIndicator.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/core/api.d.ts +37 -0
  122. package/lib/typescript/commonjs/core/api.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/core/config.d.ts +9 -0
  124. package/lib/typescript/commonjs/core/config.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/core/errors.d.ts +35 -0
  126. package/lib/typescript/commonjs/core/errors.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/core/offlineQueue.d.ts +16 -0
  128. package/lib/typescript/commonjs/core/offlineQueue.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/core/pushNotifications.d.ts +6 -0
  130. package/lib/typescript/commonjs/core/pushNotifications.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/core/session.d.ts +15 -0
  132. package/lib/typescript/commonjs/core/session.d.ts.map +1 -0
  133. package/lib/typescript/commonjs/core/theme.d.ts +43 -0
  134. package/lib/typescript/commonjs/core/theme.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/core/types.d.ts +185 -0
  136. package/lib/typescript/commonjs/core/types.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/core/websocket.d.ts +17 -0
  138. package/lib/typescript/commonjs/core/websocket.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/hooks/useChat.d.ts +35 -0
  140. package/lib/typescript/commonjs/hooks/useChat.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/hooks/useParlr.d.ts +11 -0
  142. package/lib/typescript/commonjs/hooks/useParlr.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/index.d.ts +30 -0
  144. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/package.json +1 -0
  146. package/lib/typescript/commonjs/provider/ParlrContext.d.ts +13 -0
  147. package/lib/typescript/commonjs/provider/ParlrContext.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts +5 -0
  149. package/lib/typescript/commonjs/provider/ParlrProvider.d.ts.map +1 -0
  150. package/lib/typescript/module/components/AttachmentPicker.d.ts +23 -0
  151. package/lib/typescript/module/components/AttachmentPicker.d.ts.map +1 -0
  152. package/lib/typescript/module/components/AttachmentPreview.d.ts +16 -0
  153. package/lib/typescript/module/components/AttachmentPreview.d.ts.map +1 -0
  154. package/lib/typescript/module/components/ChatBubble.d.ts +14 -0
  155. package/lib/typescript/module/components/ChatBubble.d.ts.map +1 -0
  156. package/lib/typescript/module/components/EmptyState.d.ts +10 -0
  157. package/lib/typescript/module/components/EmptyState.d.ts.map +1 -0
  158. package/lib/typescript/module/components/ParlrChat.d.ts +34 -0
  159. package/lib/typescript/module/components/ParlrChat.d.ts.map +1 -0
  160. package/lib/typescript/module/components/ParlrConversationList.d.ts +17 -0
  161. package/lib/typescript/module/components/ParlrConversationList.d.ts.map +1 -0
  162. package/lib/typescript/module/components/PreChatForm.d.ts +20 -0
  163. package/lib/typescript/module/components/PreChatForm.d.ts.map +1 -0
  164. package/lib/typescript/module/components/RichMessage.d.ts +41 -0
  165. package/lib/typescript/module/components/RichMessage.d.ts.map +1 -0
  166. package/lib/typescript/module/components/SatisfactionSurvey.d.ts +17 -0
  167. package/lib/typescript/module/components/SatisfactionSurvey.d.ts.map +1 -0
  168. package/lib/typescript/module/components/TypingIndicator.d.ts +7 -0
  169. package/lib/typescript/module/components/TypingIndicator.d.ts.map +1 -0
  170. package/lib/typescript/module/core/api.d.ts +37 -0
  171. package/lib/typescript/module/core/api.d.ts.map +1 -0
  172. package/lib/typescript/module/core/config.d.ts +9 -0
  173. package/lib/typescript/module/core/config.d.ts.map +1 -0
  174. package/lib/typescript/module/core/errors.d.ts +35 -0
  175. package/lib/typescript/module/core/errors.d.ts.map +1 -0
  176. package/lib/typescript/module/core/offlineQueue.d.ts +16 -0
  177. package/lib/typescript/module/core/offlineQueue.d.ts.map +1 -0
  178. package/lib/typescript/module/core/pushNotifications.d.ts +6 -0
  179. package/lib/typescript/module/core/pushNotifications.d.ts.map +1 -0
  180. package/lib/typescript/module/core/session.d.ts +15 -0
  181. package/lib/typescript/module/core/session.d.ts.map +1 -0
  182. package/lib/typescript/module/core/theme.d.ts +43 -0
  183. package/lib/typescript/module/core/theme.d.ts.map +1 -0
  184. package/lib/typescript/module/core/types.d.ts +185 -0
  185. package/lib/typescript/module/core/types.d.ts.map +1 -0
  186. package/lib/typescript/module/core/websocket.d.ts +17 -0
  187. package/lib/typescript/module/core/websocket.d.ts.map +1 -0
  188. package/lib/typescript/module/hooks/useChat.d.ts +35 -0
  189. package/lib/typescript/module/hooks/useChat.d.ts.map +1 -0
  190. package/lib/typescript/module/hooks/useParlr.d.ts +11 -0
  191. package/lib/typescript/module/hooks/useParlr.d.ts.map +1 -0
  192. package/lib/typescript/module/index.d.ts +30 -0
  193. package/lib/typescript/module/index.d.ts.map +1 -0
  194. package/lib/typescript/module/package.json +1 -0
  195. package/lib/typescript/module/provider/ParlrContext.d.ts +13 -0
  196. package/lib/typescript/module/provider/ParlrContext.d.ts.map +1 -0
  197. package/lib/typescript/module/provider/ParlrProvider.d.ts +5 -0
  198. package/lib/typescript/module/provider/ParlrProvider.d.ts.map +1 -0
  199. package/package.json +120 -0
  200. package/src/components/AttachmentPicker.tsx +310 -0
  201. package/src/components/AttachmentPreview.tsx +209 -0
  202. package/src/components/ChatBubble.tsx +424 -0
  203. package/src/components/EmptyState.tsx +118 -0
  204. package/src/components/ParlrChat.tsx +863 -0
  205. package/src/components/ParlrConversationList.tsx +559 -0
  206. package/src/components/PreChatForm.tsx +313 -0
  207. package/src/components/RichMessage.tsx +353 -0
  208. package/src/components/SatisfactionSurvey.tsx +333 -0
  209. package/src/components/TypingIndicator.tsx +89 -0
  210. package/src/core/api.ts +406 -0
  211. package/src/core/config.ts +39 -0
  212. package/src/core/errors.ts +68 -0
  213. package/src/core/offlineQueue.ts +94 -0
  214. package/src/core/pushNotifications.ts +22 -0
  215. package/src/core/session.ts +156 -0
  216. package/src/core/theme.ts +133 -0
  217. package/src/core/types.ts +237 -0
  218. package/src/core/websocket.ts +270 -0
  219. package/src/hooks/useChat.ts +534 -0
  220. package/src/hooks/useParlr.ts +43 -0
  221. package/src/index.ts +98 -0
  222. package/src/provider/ParlrContext.ts +40 -0
  223. package/src/provider/ParlrProvider.tsx +338 -0
@@ -0,0 +1,740 @@
1
+ "use strict";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Parlr React Native SDK - ParlrChat (main component)
5
+ // ---------------------------------------------------------------------------
6
+ //
7
+ // A self-contained, production-quality chat screen. Drop it onto a screen
8
+ // and everything just works: session creation, WebSocket connection,
9
+ // conversation management, message sending, typing indicators.
10
+ //
11
+ // Features: attachments, conversation close/reopen, pre-chat form, CSAT.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
15
+ import { ActivityIndicator, FlatList, Keyboard, Modal, Platform, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
16
+ import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
17
+ import { useChat } from "../hooks/useChat.js";
18
+ import { ParlrContext } from "../provider/ParlrContext.js";
19
+ import { ChatBubble } from "./ChatBubble.js";
20
+ import { EmptyState } from "./EmptyState.js";
21
+ import { TypingIndicator } from "./TypingIndicator.js";
22
+ import { PreChatForm } from "./PreChatForm.js";
23
+ import { SatisfactionSurvey } from "./SatisfactionSurvey.js";
24
+ import { AttachmentPicker } from "./AttachmentPicker.js";
25
+ import { AttachmentPreview } from "./AttachmentPreview.js";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Props
29
+ // ---------------------------------------------------------------------------
30
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
31
+ // ---------------------------------------------------------------------------
32
+ // i18n defaults
33
+ // ---------------------------------------------------------------------------
34
+
35
+ function getI18n(locale) {
36
+ if (locale?.startsWith('en')) {
37
+ return {
38
+ headerTitle: 'Support',
39
+ placeholder: 'Write a message...',
40
+ online: 'Online',
41
+ offline: 'Offline',
42
+ closeConversation: 'Close conversation',
43
+ reopenConversation: 'Reopen conversation',
44
+ conversationClosed: 'This conversation has been closed.'
45
+ };
46
+ }
47
+ return {
48
+ headerTitle: 'Support',
49
+ placeholder: '\u00c9crivez un message...',
50
+ online: 'En ligne',
51
+ offline: 'Hors ligne',
52
+ closeConversation: 'Fermer la conversation',
53
+ reopenConversation: 'Rouvrir la conversation',
54
+ conversationClosed: 'Cette conversation a \u00e9t\u00e9 ferm\u00e9e.'
55
+ };
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Animated send button
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function SendButton({
63
+ onPress,
64
+ disabled,
65
+ accentColor,
66
+ theme
67
+ }) {
68
+ const scale = useSharedValue(1);
69
+ const animatedStyle = useAnimatedStyle(() => ({
70
+ transform: [{
71
+ scale: scale.value
72
+ }]
73
+ }));
74
+ const handlePressIn = () => {
75
+ scale.value = withSpring(0.85, {
76
+ damping: 15,
77
+ stiffness: 300
78
+ });
79
+ };
80
+ const handlePressOut = () => {
81
+ scale.value = withSpring(1, {
82
+ damping: 15,
83
+ stiffness: 300
84
+ });
85
+ };
86
+ return /*#__PURE__*/_jsx(Pressable, {
87
+ onPress: onPress,
88
+ onPressIn: handlePressIn,
89
+ onPressOut: handlePressOut,
90
+ disabled: disabled,
91
+ hitSlop: 8,
92
+ accessibilityRole: "button",
93
+ accessibilityLabel: "Send message",
94
+ accessibilityState: {
95
+ disabled
96
+ },
97
+ children: /*#__PURE__*/_jsx(Animated.View, {
98
+ style: [styles.sendButton, {
99
+ backgroundColor: disabled ? theme.colors.border : accentColor
100
+ }, animatedStyle],
101
+ children: /*#__PURE__*/_jsx(Text, {
102
+ style: [styles.sendIcon, {
103
+ color: theme.colors.primaryText
104
+ }],
105
+ children: '\u2191'
106
+ })
107
+ })
108
+ });
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Header menu (3-dot)
113
+ // ---------------------------------------------------------------------------
114
+
115
+ function HeaderMenu({
116
+ visible,
117
+ onDismiss,
118
+ onClose,
119
+ onReopen,
120
+ isClosed,
121
+ theme,
122
+ locale
123
+ }) {
124
+ const i18n = getI18n(locale);
125
+ if (!visible) return null;
126
+ return /*#__PURE__*/_jsx(Modal, {
127
+ transparent: true,
128
+ animationType: "fade",
129
+ visible: visible,
130
+ onRequestClose: onDismiss,
131
+ children: /*#__PURE__*/_jsx(Pressable, {
132
+ style: styles.menuBackdrop,
133
+ onPress: onDismiss,
134
+ children: /*#__PURE__*/_jsx(View, {
135
+ style: [styles.menuContainer, {
136
+ backgroundColor: theme.colors.surface
137
+ }],
138
+ children: /*#__PURE__*/_jsx(Pressable, {
139
+ onPress: isClosed ? onReopen : onClose,
140
+ style: styles.menuItem,
141
+ accessibilityRole: "button",
142
+ children: /*#__PURE__*/_jsx(Text, {
143
+ style: [styles.menuItemText, {
144
+ color: theme.colors.text
145
+ }],
146
+ children: isClosed ? i18n.reopenConversation : i18n.closeConversation
147
+ })
148
+ })
149
+ })
150
+ })
151
+ });
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Main component
156
+ // ---------------------------------------------------------------------------
157
+
158
+ export function ParlrChat({
159
+ user,
160
+ conversationId,
161
+ onBack,
162
+ headerTitle,
163
+ placeholder,
164
+ emptyStateTitle,
165
+ emptyStateDescription,
166
+ accentColor: accentColorProp,
167
+ showHeader = true,
168
+ showPreChatForm = false,
169
+ preChatFields = ['name', 'email'],
170
+ showSatisfactionSurvey = true,
171
+ onConversationClosed,
172
+ safeAreaBottom = 0
173
+ }) {
174
+ const {
175
+ config,
176
+ isReady,
177
+ isConnected,
178
+ session,
179
+ identify,
180
+ api,
181
+ theme
182
+ } = useContext(ParlrContext);
183
+ const accentColor = accentColorProp ?? theme.colors.primary;
184
+ const i18n = getI18n(config.locale);
185
+
186
+ // Consider "online" if session is active (REST API works), not just WebSocket.
187
+ const effectiveConnected = isConnected || isReady && !!session;
188
+ const chatState = useChat(conversationId);
189
+ const messages = chatState.messages ?? [];
190
+ const {
191
+ isLoading,
192
+ agentTyping,
193
+ sendMessage,
194
+ notifyTyping,
195
+ loadMore,
196
+ hasMore,
197
+ conversation,
198
+ closeConversation,
199
+ reopenConversation
200
+ } = chatState;
201
+ const [inputText, setInputText] = useState('');
202
+ const [menuVisible, setMenuVisible] = useState(false);
203
+ const [showAttachmentPicker, setShowAttachmentPicker] = useState(false);
204
+ const [pickedFile, setPickedFile] = useState(null);
205
+ const [isIdentified, setIsIdentified] = useState(!showPreChatForm || !!user);
206
+ const [showCSAT, setShowCSAT] = useState(false);
207
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
208
+ const flatListRef = useRef(null);
209
+ const identifiedRef = useRef(false);
210
+ const isNearBottomRef = useRef(true);
211
+
212
+ // --- Keyboard tracking (more reliable than KeyboardAvoidingView) -----------
213
+ useEffect(() => {
214
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
215
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
216
+ const showSub = Keyboard.addListener(showEvent, e => {
217
+ setKeyboardHeight(e.endCoordinates.height);
218
+ });
219
+ const hideSub = Keyboard.addListener(hideEvent, () => {
220
+ setKeyboardHeight(0);
221
+ });
222
+ return () => {
223
+ showSub.remove();
224
+ hideSub.remove();
225
+ };
226
+ }, []);
227
+ const isClosed = conversation?.status === 'closed';
228
+ const handleScroll = useCallback(event => {
229
+ const {
230
+ contentOffset,
231
+ layoutMeasurement,
232
+ contentSize
233
+ } = event.nativeEvent;
234
+ const distanceFromBottom = contentSize.height - layoutMeasurement.height - contentOffset.y;
235
+ isNearBottomRef.current = distanceFromBottom < 150;
236
+ }, []);
237
+
238
+ // --- Identify user on mount ------------------------------------------------
239
+ useEffect(() => {
240
+ if (user && isReady && !identifiedRef.current) {
241
+ identifiedRef.current = true;
242
+ setIsIdentified(true);
243
+ identify(user).catch(err => {
244
+ console.warn('[@parlr/react-native] identify failed:', err);
245
+ });
246
+ }
247
+ }, [user, isReady, identify]);
248
+
249
+ // --- Show CSAT when conversation becomes closed ---
250
+ useEffect(() => {
251
+ if (isClosed && showSatisfactionSurvey) {
252
+ setShowCSAT(true);
253
+ }
254
+ }, [isClosed, showSatisfactionSurvey]);
255
+
256
+ // --- Handlers --------------------------------------------------------------
257
+
258
+ const handleSend = useCallback(() => {
259
+ const text = inputText.trim();
260
+ if (!text) return;
261
+ setInputText('');
262
+ notifyTyping(false);
263
+ sendMessage(text);
264
+ }, [inputText, sendMessage, notifyTyping]);
265
+ const handleTextChange = useCallback(text => {
266
+ setInputText(text);
267
+ notifyTyping(text.length > 0);
268
+ }, [notifyTyping]);
269
+ const handleEndReached = useCallback(() => {
270
+ if (hasMore && !isLoading) {
271
+ loadMore();
272
+ }
273
+ }, [hasMore, isLoading, loadMore]);
274
+ const handleCloseConversation = useCallback(async () => {
275
+ setMenuVisible(false);
276
+ if (closeConversation) {
277
+ await closeConversation();
278
+ onConversationClosed?.();
279
+ }
280
+ }, [closeConversation, onConversationClosed]);
281
+ const handleReopenConversation = useCallback(async () => {
282
+ setMenuVisible(false);
283
+ if (reopenConversation) {
284
+ await reopenConversation();
285
+ setShowCSAT(false);
286
+ }
287
+ }, [reopenConversation]);
288
+ const handlePreChatSubmit = useCallback(async userData => {
289
+ await identify(userData);
290
+ setIsIdentified(true);
291
+ }, [identify]);
292
+ const handleCSATSubmit = useCallback(async (score, comment) => {
293
+ if (api && conversation) {
294
+ await api.submitRating(conversation.id, score, comment);
295
+ }
296
+ setShowCSAT(false);
297
+ }, [api, conversation]);
298
+ const handleCSATDismiss = useCallback(() => {
299
+ setShowCSAT(false);
300
+ }, []);
301
+ const handleFilePicked = useCallback(file => {
302
+ setPickedFile(file);
303
+ setShowAttachmentPicker(false);
304
+ }, []);
305
+ const handleSendAttachment = useCallback(async () => {
306
+ if (!pickedFile || !api) return;
307
+
308
+ // 1. Create conversation if needed (same logic as sendMessage).
309
+ let convId = conversation?.id ?? chatState.conversation?.id;
310
+ if (!convId) {
311
+ try {
312
+ const conv = await api.createConversation();
313
+ convId = conv.id;
314
+ } catch (err) {
315
+ console.warn('[@parlr/react-native] Failed to create conversation for attachment:', err);
316
+ setPickedFile(null);
317
+ return;
318
+ }
319
+ }
320
+
321
+ // 2. Build FormData and upload.
322
+ const formData = new FormData();
323
+ formData.append('file', {
324
+ uri: pickedFile.uri,
325
+ name: pickedFile.name,
326
+ type: pickedFile.type
327
+ });
328
+ try {
329
+ await api.uploadAttachment(convId, formData);
330
+ } catch (err) {
331
+ console.warn('[@parlr/react-native] attachment upload failed:', err);
332
+ }
333
+ setPickedFile(null);
334
+ }, [pickedFile, api, conversation, chatState.conversation]);
335
+
336
+ // --- Render helpers --------------------------------------------------------
337
+
338
+ const renderMessage = useCallback(({
339
+ item,
340
+ index
341
+ }) => /*#__PURE__*/_jsx(ChatBubble, {
342
+ message: item,
343
+ accentColor: accentColor,
344
+ animated: index >= messages.length - 1
345
+ }), [accentColor, messages.length]);
346
+ const keyExtractor = useCallback(item => item.clientId ?? item.id, []);
347
+
348
+ // --- Loading state ---------------------------------------------------------
349
+
350
+ if (!isReady) {
351
+ return /*#__PURE__*/_jsx(View, {
352
+ style: [styles.loadingContainer, {
353
+ backgroundColor: theme.colors.background
354
+ }],
355
+ children: /*#__PURE__*/_jsx(ActivityIndicator, {
356
+ size: "large",
357
+ color: accentColor
358
+ })
359
+ });
360
+ }
361
+
362
+ // --- Pre-chat form ---------------------------------------------------------
363
+
364
+ if (!isIdentified) {
365
+ return /*#__PURE__*/_jsxs(View, {
366
+ style: [styles.root, {
367
+ backgroundColor: theme.colors.background
368
+ }],
369
+ children: [showHeader && /*#__PURE__*/_jsxs(View, {
370
+ style: [styles.header, {
371
+ backgroundColor: theme.colors.surface,
372
+ borderBottomColor: theme.colors.border
373
+ }],
374
+ accessibilityRole: "header",
375
+ children: [onBack && /*#__PURE__*/_jsx(Pressable, {
376
+ onPress: onBack,
377
+ style: styles.backButton,
378
+ hitSlop: 12,
379
+ accessibilityRole: "button",
380
+ accessibilityLabel: "Go back",
381
+ children: /*#__PURE__*/_jsx(Text, {
382
+ style: [styles.backArrow, {
383
+ color: theme.colors.text
384
+ }],
385
+ children: '\u2190'
386
+ })
387
+ }), /*#__PURE__*/_jsx(View, {
388
+ style: styles.headerCenter,
389
+ children: /*#__PURE__*/_jsx(Text, {
390
+ style: [styles.headerTitle, {
391
+ color: theme.colors.text
392
+ }],
393
+ numberOfLines: 1,
394
+ children: headerTitle ?? i18n.headerTitle
395
+ })
396
+ }), onBack && /*#__PURE__*/_jsx(View, {
397
+ style: styles.backButton
398
+ })]
399
+ }), /*#__PURE__*/_jsx(PreChatForm, {
400
+ onSubmit: handlePreChatSubmit,
401
+ fields: preChatFields,
402
+ accentColor: accentColor,
403
+ locale: config.locale
404
+ })]
405
+ });
406
+ }
407
+
408
+ // --- Render ----------------------------------------------------------------
409
+
410
+ const hasMessages = messages.length > 0;
411
+ return /*#__PURE__*/_jsxs(View, {
412
+ style: [styles.root, {
413
+ backgroundColor: theme.colors.background,
414
+ paddingBottom: keyboardHeight > 0 ? keyboardHeight : 0
415
+ }],
416
+ children: [showHeader && /*#__PURE__*/_jsxs(View, {
417
+ style: [styles.header, {
418
+ backgroundColor: theme.colors.surface,
419
+ borderBottomColor: theme.colors.border
420
+ }],
421
+ accessibilityRole: "header",
422
+ children: [onBack && /*#__PURE__*/_jsx(Pressable, {
423
+ onPress: onBack,
424
+ style: styles.backButton,
425
+ hitSlop: 12,
426
+ accessibilityRole: "button",
427
+ accessibilityLabel: "Go back",
428
+ children: /*#__PURE__*/_jsx(Text, {
429
+ style: [styles.backArrow, {
430
+ color: theme.colors.text
431
+ }],
432
+ children: '\u2190'
433
+ })
434
+ }), /*#__PURE__*/_jsxs(View, {
435
+ style: styles.headerCenter,
436
+ children: [/*#__PURE__*/_jsx(Text, {
437
+ style: [styles.headerTitle, {
438
+ color: theme.colors.text
439
+ }],
440
+ numberOfLines: 1,
441
+ children: headerTitle ?? i18n.headerTitle
442
+ }), /*#__PURE__*/_jsxs(View, {
443
+ style: styles.statusRow,
444
+ children: [/*#__PURE__*/_jsx(View, {
445
+ style: [styles.statusDot, {
446
+ backgroundColor: effectiveConnected ? theme.colors.success : theme.colors.textSecondary
447
+ }]
448
+ }), /*#__PURE__*/_jsx(Text, {
449
+ style: [styles.statusText, {
450
+ color: theme.colors.textSecondary
451
+ }],
452
+ children: effectiveConnected ? i18n.online : i18n.offline
453
+ })]
454
+ })]
455
+ }), conversationId && /*#__PURE__*/_jsx(Pressable, {
456
+ onPress: () => setMenuVisible(true),
457
+ style: styles.menuButton,
458
+ hitSlop: 12,
459
+ accessibilityRole: "button",
460
+ accessibilityLabel: "Menu",
461
+ children: /*#__PURE__*/_jsx(Text, {
462
+ style: [styles.menuDots, {
463
+ color: theme.colors.text
464
+ }],
465
+ children: '\u22ee'
466
+ })
467
+ }), !conversationId && onBack && /*#__PURE__*/_jsx(View, {
468
+ style: styles.backButton
469
+ })]
470
+ }), /*#__PURE__*/_jsx(HeaderMenu, {
471
+ visible: menuVisible,
472
+ onDismiss: () => setMenuVisible(false),
473
+ onClose: handleCloseConversation,
474
+ onReopen: handleReopenConversation,
475
+ isClosed: isClosed,
476
+ theme: theme,
477
+ locale: config.locale
478
+ }), hasMessages ? /*#__PURE__*/_jsx(FlatList, {
479
+ ref: flatListRef,
480
+ data: messages,
481
+ renderItem: renderMessage,
482
+ keyExtractor: keyExtractor,
483
+ inverted: false,
484
+ contentContainerStyle: styles.messageList,
485
+ showsVerticalScrollIndicator: false,
486
+ onEndReached: handleEndReached,
487
+ onEndReachedThreshold: 0.3,
488
+ onScroll: handleScroll,
489
+ scrollEventThrottle: 100,
490
+ removeClippedSubviews: true,
491
+ maxToRenderPerBatch: 15,
492
+ windowSize: 10,
493
+ ListHeaderComponent: isLoading ? /*#__PURE__*/_jsx(ActivityIndicator, {
494
+ size: "small",
495
+ color: accentColor,
496
+ style: styles.loadingMore
497
+ }) : null,
498
+ ListFooterComponent: /*#__PURE__*/_jsxs(_Fragment, {
499
+ children: [agentTyping && /*#__PURE__*/_jsx(TypingIndicator, {
500
+ accentColor: accentColor
501
+ }), showCSAT && /*#__PURE__*/_jsx(SatisfactionSurvey, {
502
+ onSubmit: handleCSATSubmit,
503
+ onDismiss: handleCSATDismiss,
504
+ accentColor: accentColor,
505
+ locale: config.locale
506
+ })]
507
+ }),
508
+ onContentSizeChange: () => {
509
+ if (isNearBottomRef.current) {
510
+ flatListRef.current?.scrollToEnd({
511
+ animated: true
512
+ });
513
+ }
514
+ }
515
+ }) : /*#__PURE__*/_jsx(EmptyState, {
516
+ title: emptyStateTitle,
517
+ description: emptyStateDescription,
518
+ accentColor: accentColor,
519
+ locale: config.locale
520
+ }), isClosed && !showCSAT && /*#__PURE__*/_jsx(View, {
521
+ style: [styles.closedBanner, {
522
+ backgroundColor: theme.colors.surface
523
+ }],
524
+ children: /*#__PURE__*/_jsx(Text, {
525
+ style: [styles.closedText, {
526
+ color: theme.colors.textSecondary
527
+ }],
528
+ children: i18n.conversationClosed
529
+ })
530
+ }), pickedFile && /*#__PURE__*/_jsx(AttachmentPreview, {
531
+ file: pickedFile,
532
+ onRemove: () => setPickedFile(null),
533
+ onSend: handleSendAttachment,
534
+ accentColor: accentColor,
535
+ locale: config.locale
536
+ }), showAttachmentPicker && /*#__PURE__*/_jsx(AttachmentPicker, {
537
+ onFilePicked: handleFilePicked,
538
+ onDismiss: () => setShowAttachmentPicker(false),
539
+ accentColor: accentColor,
540
+ locale: config.locale
541
+ }), !isClosed && !pickedFile && !showAttachmentPicker && /*#__PURE__*/_jsxs(View, {
542
+ style: [styles.inputBar, {
543
+ backgroundColor: theme.colors.surface,
544
+ borderTopColor: theme.colors.border,
545
+ paddingBottom: keyboardHeight > 0 ? 10 : 10 + safeAreaBottom
546
+ }],
547
+ children: [/*#__PURE__*/_jsx(Pressable, {
548
+ onPress: () => setShowAttachmentPicker(true),
549
+ style: [styles.attachButton, {
550
+ backgroundColor: theme.colors.surface
551
+ }],
552
+ hitSlop: 8,
553
+ accessibilityRole: "button",
554
+ accessibilityLabel: "Add attachment",
555
+ children: /*#__PURE__*/_jsx(Text, {
556
+ style: [styles.attachIcon, {
557
+ color: theme.colors.textSecondary
558
+ }],
559
+ children: "+"
560
+ })
561
+ }), /*#__PURE__*/_jsx(TextInput, {
562
+ style: [styles.textInput, {
563
+ backgroundColor: theme.colors.surface,
564
+ borderColor: theme.colors.border,
565
+ color: theme.colors.text
566
+ }],
567
+ value: inputText,
568
+ onChangeText: handleTextChange,
569
+ placeholder: placeholder ?? i18n.placeholder,
570
+ placeholderTextColor: theme.colors.textSecondary,
571
+ multiline: true,
572
+ maxLength: 4000,
573
+ returnKeyType: "default",
574
+ blurOnSubmit: false,
575
+ accessibilityLabel: "Message input",
576
+ accessibilityHint: "Type a message to send"
577
+ }), /*#__PURE__*/_jsx(SendButton, {
578
+ onPress: handleSend,
579
+ disabled: inputText.trim().length === 0,
580
+ accentColor: accentColor,
581
+ theme: theme
582
+ })]
583
+ })]
584
+ });
585
+ }
586
+
587
+ // ---------------------------------------------------------------------------
588
+ // Styles
589
+ // ---------------------------------------------------------------------------
590
+
591
+ const styles = StyleSheet.create({
592
+ root: {
593
+ flex: 1
594
+ },
595
+ loadingContainer: {
596
+ flex: 1,
597
+ justifyContent: 'center',
598
+ alignItems: 'center'
599
+ },
600
+ // Header
601
+ header: {
602
+ flexDirection: 'row',
603
+ alignItems: 'center',
604
+ paddingHorizontal: 16,
605
+ paddingTop: 12,
606
+ paddingBottom: 12,
607
+ borderBottomWidth: StyleSheet.hairlineWidth
608
+ },
609
+ backButton: {
610
+ width: 40,
611
+ height: 40,
612
+ justifyContent: 'center',
613
+ alignItems: 'center'
614
+ },
615
+ backArrow: {
616
+ fontSize: 22,
617
+ fontWeight: '500'
618
+ },
619
+ headerCenter: {
620
+ flex: 1,
621
+ alignItems: 'center'
622
+ },
623
+ headerTitle: {
624
+ fontSize: 17,
625
+ fontWeight: '600'
626
+ },
627
+ statusRow: {
628
+ flexDirection: 'row',
629
+ alignItems: 'center',
630
+ marginTop: 2
631
+ },
632
+ statusDot: {
633
+ width: 7,
634
+ height: 7,
635
+ borderRadius: 3.5,
636
+ marginRight: 5
637
+ },
638
+ statusText: {
639
+ fontSize: 12
640
+ },
641
+ menuButton: {
642
+ width: 40,
643
+ height: 40,
644
+ justifyContent: 'center',
645
+ alignItems: 'center'
646
+ },
647
+ menuDots: {
648
+ fontSize: 20,
649
+ fontWeight: '700'
650
+ },
651
+ // Header menu
652
+ menuBackdrop: {
653
+ flex: 1,
654
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
655
+ justifyContent: 'flex-start',
656
+ alignItems: 'flex-end',
657
+ paddingTop: Platform.OS === 'ios' ? 110 : 70,
658
+ paddingRight: 16
659
+ },
660
+ menuContainer: {
661
+ borderRadius: 10,
662
+ paddingVertical: 4,
663
+ minWidth: 200,
664
+ shadowColor: '#000',
665
+ shadowOffset: {
666
+ width: 0,
667
+ height: 4
668
+ },
669
+ shadowOpacity: 0.15,
670
+ shadowRadius: 12,
671
+ elevation: 8
672
+ },
673
+ menuItem: {
674
+ paddingHorizontal: 16,
675
+ paddingVertical: 12
676
+ },
677
+ menuItemText: {
678
+ fontSize: 15
679
+ },
680
+ // Message list
681
+ messageList: {
682
+ paddingVertical: 12
683
+ },
684
+ loadingMore: {
685
+ marginVertical: 8
686
+ },
687
+ // Closed banner
688
+ closedBanner: {
689
+ paddingVertical: 12,
690
+ paddingHorizontal: 16,
691
+ alignItems: 'center'
692
+ },
693
+ closedText: {
694
+ fontSize: 13
695
+ },
696
+ // Input bar
697
+ inputBar: {
698
+ flexDirection: 'row',
699
+ alignItems: 'flex-end',
700
+ paddingHorizontal: 12,
701
+ paddingVertical: 10,
702
+ borderTopWidth: StyleSheet.hairlineWidth,
703
+ gap: 8
704
+ },
705
+ attachButton: {
706
+ width: 38,
707
+ height: 38,
708
+ borderRadius: 19,
709
+ justifyContent: 'center',
710
+ alignItems: 'center',
711
+ marginBottom: 1
712
+ },
713
+ attachIcon: {
714
+ fontSize: 22,
715
+ fontWeight: '300'
716
+ },
717
+ textInput: {
718
+ flex: 1,
719
+ fontSize: 15,
720
+ lineHeight: 20,
721
+ maxHeight: 100,
722
+ paddingHorizontal: 16,
723
+ paddingVertical: 10,
724
+ borderRadius: 22,
725
+ borderWidth: 1
726
+ },
727
+ sendButton: {
728
+ width: 38,
729
+ height: 38,
730
+ borderRadius: 19,
731
+ justifyContent: 'center',
732
+ alignItems: 'center',
733
+ marginBottom: 1
734
+ },
735
+ sendIcon: {
736
+ fontSize: 18,
737
+ fontWeight: '700'
738
+ }
739
+ });
740
+ //# sourceMappingURL=ParlrChat.js.map