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