@repzo/widget-react-native 1.0.0 → 1.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 (126) hide show
  1. package/README.md +167 -74
  2. package/lib/commonjs/api/client.js.map +1 -1
  3. package/lib/commonjs/attachments/pickers.js +493 -0
  4. package/lib/commonjs/attachments/pickers.js.map +1 -0
  5. package/lib/commonjs/i18n.js +19 -0
  6. package/lib/commonjs/i18n.js.map +1 -1
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/locales.js +16 -0
  9. package/lib/commonjs/locales.js.map +1 -1
  10. package/lib/commonjs/state/widget-store.js +147 -13
  11. package/lib/commonjs/state/widget-store.js.map +1 -1
  12. package/lib/commonjs/types.js +14 -0
  13. package/lib/commonjs/types.js.map +1 -1
  14. package/lib/commonjs/ui/WidgetProvider.js +53 -8
  15. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  16. package/lib/commonjs/ui/components/AttachmentChips.js +135 -0
  17. package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -0
  18. package/lib/commonjs/ui/components/AttachmentMenu.js +125 -0
  19. package/lib/commonjs/ui/components/AttachmentMenu.js.map +1 -0
  20. package/lib/commonjs/ui/components/ImageViewer.js +181 -0
  21. package/lib/commonjs/ui/components/ImageViewer.js.map +1 -0
  22. package/lib/commonjs/ui/components/InAppNotification.js +210 -0
  23. package/lib/commonjs/ui/components/InAppNotification.js.map +1 -0
  24. package/lib/commonjs/ui/screens/chat/ChatView.js +198 -18
  25. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  26. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +19 -0
  27. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  28. package/lib/module/api/client.js.map +1 -1
  29. package/lib/module/attachments/pickers.js +479 -0
  30. package/lib/module/attachments/pickers.js.map +1 -0
  31. package/lib/module/i18n.js +19 -0
  32. package/lib/module/i18n.js.map +1 -1
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/locales.js +16 -0
  35. package/lib/module/locales.js.map +1 -1
  36. package/lib/module/state/widget-store.js +147 -13
  37. package/lib/module/state/widget-store.js.map +1 -1
  38. package/lib/module/types.js +14 -0
  39. package/lib/module/types.js.map +1 -1
  40. package/lib/module/ui/WidgetProvider.js +53 -8
  41. package/lib/module/ui/WidgetProvider.js.map +1 -1
  42. package/lib/module/ui/components/AttachmentChips.js +131 -0
  43. package/lib/module/ui/components/AttachmentChips.js.map +1 -0
  44. package/lib/module/ui/components/AttachmentMenu.js +120 -0
  45. package/lib/module/ui/components/AttachmentMenu.js.map +1 -0
  46. package/lib/module/ui/components/ImageViewer.js +177 -0
  47. package/lib/module/ui/components/ImageViewer.js.map +1 -0
  48. package/lib/module/ui/components/InAppNotification.js +208 -0
  49. package/lib/module/ui/components/InAppNotification.js.map +1 -0
  50. package/lib/module/ui/screens/chat/ChatView.js +201 -21
  51. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  52. package/lib/module/ui/screens/chat/ChatView.styles.js +19 -0
  53. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  54. package/lib/typescript/commonjs/api/client.d.ts +8 -2
  55. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/attachments/pickers.d.ts +81 -0
  57. package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/i18n.d.ts +16 -0
  59. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/index.d.ts +1 -1
  61. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/state/widget-store.d.ts +23 -2
  64. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/types.d.ts +74 -0
  66. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +16 -0
  69. package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts +28 -0
  71. package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +23 -0
  73. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts +28 -0
  75. package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +3 -2
  77. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +18 -0
  79. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +28 -2
  81. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  82. package/lib/typescript/module/api/client.d.ts +8 -2
  83. package/lib/typescript/module/api/client.d.ts.map +1 -1
  84. package/lib/typescript/module/attachments/pickers.d.ts +81 -0
  85. package/lib/typescript/module/attachments/pickers.d.ts.map +1 -0
  86. package/lib/typescript/module/i18n.d.ts +16 -0
  87. package/lib/typescript/module/i18n.d.ts.map +1 -1
  88. package/lib/typescript/module/index.d.ts +1 -1
  89. package/lib/typescript/module/index.d.ts.map +1 -1
  90. package/lib/typescript/module/locales.d.ts.map +1 -1
  91. package/lib/typescript/module/state/widget-store.d.ts +23 -2
  92. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  93. package/lib/typescript/module/types.d.ts +74 -0
  94. package/lib/typescript/module/types.d.ts.map +1 -1
  95. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  96. package/lib/typescript/module/ui/components/AttachmentChips.d.ts +16 -0
  97. package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -0
  98. package/lib/typescript/module/ui/components/AttachmentMenu.d.ts +28 -0
  99. package/lib/typescript/module/ui/components/AttachmentMenu.d.ts.map +1 -0
  100. package/lib/typescript/module/ui/components/ImageViewer.d.ts +23 -0
  101. package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -0
  102. package/lib/typescript/module/ui/components/InAppNotification.d.ts +28 -0
  103. package/lib/typescript/module/ui/components/InAppNotification.d.ts.map +1 -0
  104. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +3 -2
  105. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  106. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +18 -0
  107. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  108. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +28 -2
  109. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  110. package/metro.js +17 -32
  111. package/package.json +18 -1
  112. package/src/api/client.ts +2 -2
  113. package/src/attachments/pickers.ts +515 -0
  114. package/src/i18n.ts +20 -0
  115. package/src/index.tsx +4 -0
  116. package/src/locales.ts +16 -0
  117. package/src/state/widget-store.ts +152 -16
  118. package/src/types.ts +79 -0
  119. package/src/ui/WidgetProvider.tsx +65 -6
  120. package/src/ui/components/AttachmentChips.tsx +135 -0
  121. package/src/ui/components/AttachmentMenu.tsx +132 -0
  122. package/src/ui/components/ImageViewer.tsx +178 -0
  123. package/src/ui/components/InAppNotification.tsx +225 -0
  124. package/src/ui/screens/chat/ChatView.styles.ts +19 -0
  125. package/src/ui/screens/chat/ChatView.tsx +235 -16
  126. package/src/ui/screens/chat/ChatView.types.ts +38 -2
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useState","ActivityIndicator","Image","Modal","Platform","Pressable","ScrollView","StyleSheet","Text","View","useWidgetInsets","fontSize","palette","spacing","CloseIcon","jsx","_jsx","jsxs","_jsxs","ImageViewer","image","labels","onClose","onOpenExternally","insets","status","setStatus","url","state","visible","transparent","animationType","statusBarTranslucent","onRequestClose","children","style","styles","backdrop","accessibilityViewIsModal","fill","contentContainerStyle","center","maximumZoomScale","minimumZoomScale","centerContent","showsHorizontalScrollIndicator","showsVerticalScrollIndicator","bouncesZoom","source","uri","resizeMode","accessibilityLabel","name","accessibilityIgnoresInvertColors","onLoad","onError","absoluteFill","color","white","failed","failedText","loadFailed","onPress","accessibilityRole","pressed","externalButton","externalLabel","openExternally","header","paddingTop","top","sm","pointerEvents","title","numberOfLines","close","hitSlop","closeButton","size","create","flex","backgroundColor","flexGrow","alignItems","justifyContent","width","height","position","left","right","flexDirection","gap","md","paddingHorizontal","lg","paddingBottom","minWidth","body","fontWeight","borderRadius","opacity","xxl","textAlign","paddingVertical","borderWidth","hairlineWidth","borderColor","meta","OS"],"sourceRoot":"../../../../src","sources":["ui/components/ImageViewer.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * The card that floats over the host app when a team reply arrives while the
5
+ * messenger is closed — Intercom's in-app notification. It never times out:
6
+ * tapping it opens the conversation, swiping it left or right dismisses it
7
+ * (the thread stays unread). The store takes it down on its own once the
8
+ * messenger opens or the thread is read.
9
+ *
10
+ * Pure JS gestures (PanResponder), so hosts need no gesture library.
11
+ */
12
+
13
+ import { useEffect, useMemo, useRef } from 'react';
14
+ import { AccessibilityInfo, Animated, PanResponder, Pressable, StyleSheet, Text, View, useWindowDimensions } from 'react-native';
15
+ import { formatLabel } from "../../i18n.js";
16
+ import { useReduceMotion } from "../hooks/use-reduce-motion.js";
17
+ import { fontSize, palette, radius, shadows, spacing, withAlpha } from "../styles/theme.js";
18
+ import { START_ALIGN } from "../utils/rtl.js";
19
+ import { Avatar } from "./Avatar.js";
20
+
21
+ /** Share of the card's width a drag must travel to count as a dismiss. */
22
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
+ const DISMISS_DISTANCE_RATIO = 0.35;
24
+ /** A quick flick dismisses even when it travels less than that. */
25
+ const DISMISS_VELOCITY = 0.6;
26
+ export function InAppNotification({
27
+ notification,
28
+ color,
29
+ bottomInset,
30
+ labels,
31
+ onOpen,
32
+ onDismiss
33
+ }) {
34
+ const {
35
+ width
36
+ } = useWindowDimensions();
37
+ const reduceMotion = useReduceMotion();
38
+ // Read as "hold still" until the setting is known (see useReduceMotion).
39
+ const animate = reduceMotion === false;
40
+ const entry = useRef(new Animated.Value(animate ? 0 : 1)).current;
41
+ const dragX = useRef(new Animated.Value(0)).current;
42
+ const preview = notification.preview || (notification.hasAttachments ? labels.attachment : '');
43
+ const sender = notification.senderName ? formatLabel(labels.replyFrom, {
44
+ name: notification.senderName
45
+ }) : labels.newReply;
46
+ const accessibilityLabel = [preview, sender].filter(Boolean).join(', ');
47
+
48
+ // Mounted once per reply (the provider keys it by message id), so this runs
49
+ // for every new card: slide it in and tell screen-reader users it arrived.
50
+ useEffect(() => {
51
+ if (animate) {
52
+ Animated.spring(entry, {
53
+ toValue: 1,
54
+ useNativeDriver: true,
55
+ speed: 16,
56
+ bounciness: 4
57
+ }).start();
58
+ }
59
+ AccessibilityInfo.announceForAccessibility(accessibilityLabel);
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps
61
+ }, []);
62
+ const panResponder = useMemo(() => {
63
+ const settle = () => Animated.spring(dragX, {
64
+ toValue: 0,
65
+ useNativeDriver: true,
66
+ bounciness: 6
67
+ }).start();
68
+ return PanResponder.create({
69
+ // Capture so a horizontal drag wins over the Pressable underneath; taps
70
+ // and vertical movement still reach it.
71
+ onMoveShouldSetPanResponderCapture: (_, gesture) => Math.abs(gesture.dx) > 8 && Math.abs(gesture.dx) > Math.abs(gesture.dy) * 1.5,
72
+ onPanResponderMove: (_, gesture) => dragX.setValue(gesture.dx),
73
+ onPanResponderTerminationRequest: () => false,
74
+ onPanResponderRelease: (_, gesture) => {
75
+ const farEnough = Math.abs(gesture.dx) > width * DISMISS_DISTANCE_RATIO;
76
+ const fastEnough = Math.abs(gesture.vx) > DISMISS_VELOCITY;
77
+ if (!farEnough && !fastEnough) {
78
+ settle();
79
+ return;
80
+ }
81
+ const direction = (gesture.dx || gesture.vx) < 0 ? -1 : 1;
82
+ Animated.timing(dragX, {
83
+ toValue: direction * width,
84
+ duration: animate ? 180 : 0,
85
+ useNativeDriver: true
86
+ }).start(() => onDismiss());
87
+ },
88
+ onPanResponderTerminate: settle
89
+ });
90
+ }, [animate, dragX, onDismiss, width]);
91
+ const handleAccessibilityAction = event => {
92
+ if (event.nativeEvent.actionName === 'dismiss') onDismiss();
93
+ if (event.nativeEvent.actionName === 'activate') onOpen(notification.conversationId);
94
+ };
95
+ const opacity = Animated.multiply(entry, dragX.interpolate({
96
+ inputRange: [-width, 0, width],
97
+ outputRange: [0, 1, 0],
98
+ extrapolate: 'clamp'
99
+ }));
100
+ const translateY = entry.interpolate({
101
+ inputRange: [0, 1],
102
+ outputRange: [spacing.lg, 0]
103
+ });
104
+ return /*#__PURE__*/_jsx(View, {
105
+ pointerEvents: "box-none",
106
+ style: [styles.wrapper, {
107
+ bottom: bottomInset + spacing.md
108
+ }],
109
+ children: /*#__PURE__*/_jsx(Animated.View, {
110
+ ...panResponder.panHandlers,
111
+ style: [styles.card, {
112
+ opacity,
113
+ transform: [{
114
+ translateX: dragX
115
+ }, {
116
+ translateY
117
+ }]
118
+ }],
119
+ children: /*#__PURE__*/_jsxs(Pressable, {
120
+ onPress: () => onOpen(notification.conversationId),
121
+ accessibilityRole: "button",
122
+ accessibilityLabel: accessibilityLabel,
123
+ accessibilityHint: labels.openHint,
124
+ accessibilityActions: [{
125
+ name: 'activate'
126
+ }, {
127
+ name: 'dismiss',
128
+ label: labels.dismiss
129
+ }],
130
+ onAccessibilityAction: handleAccessibilityAction,
131
+ style: ({
132
+ pressed
133
+ }) => [styles.row, pressed && styles.pressed],
134
+ children: [/*#__PURE__*/_jsx(Avatar, {
135
+ avatar: notification.senderAvatar,
136
+ name: notification.senderName,
137
+ color: color,
138
+ size: 40,
139
+ style: styles.avatar
140
+ }), /*#__PURE__*/_jsxs(View, {
141
+ style: styles.text,
142
+ children: [preview ? /*#__PURE__*/_jsx(Text, {
143
+ numberOfLines: 2,
144
+ style: styles.preview,
145
+ children: preview
146
+ }) : null, /*#__PURE__*/_jsx(Text, {
147
+ numberOfLines: 1,
148
+ style: styles.sender,
149
+ children: sender
150
+ })]
151
+ })]
152
+ })
153
+ })
154
+ });
155
+ }
156
+ const styles = StyleSheet.create({
157
+ wrapper: {
158
+ position: 'absolute',
159
+ left: 0,
160
+ right: 0,
161
+ paddingHorizontal: spacing.lg,
162
+ alignItems: 'center',
163
+ // Above the host's navigator even when the provider is mounted before it.
164
+ zIndex: 1000,
165
+ elevation: 1000
166
+ },
167
+ card: {
168
+ width: '100%',
169
+ maxWidth: 480,
170
+ borderRadius: radius.xl,
171
+ backgroundColor: palette.text,
172
+ ...shadows.floating
173
+ },
174
+ row: {
175
+ flexDirection: 'row',
176
+ alignItems: 'center',
177
+ gap: spacing.md,
178
+ minHeight: 64,
179
+ paddingVertical: spacing.md,
180
+ paddingHorizontal: spacing.lg,
181
+ borderRadius: radius.xl
182
+ },
183
+ pressed: {
184
+ opacity: 0.85
185
+ },
186
+ avatar: {
187
+ borderWidth: 1,
188
+ borderColor: withAlpha(palette.white, 0.1)
189
+ },
190
+ text: {
191
+ flex: 1,
192
+ minWidth: 0,
193
+ gap: 2
194
+ },
195
+ preview: {
196
+ color: palette.white,
197
+ fontSize: fontSize.body,
198
+ fontWeight: '600',
199
+ lineHeight: 20,
200
+ textAlign: START_ALIGN
201
+ },
202
+ sender: {
203
+ color: withAlpha(palette.white, 0.64),
204
+ fontSize: fontSize.meta,
205
+ textAlign: START_ALIGN
206
+ }
207
+ });
208
+ //# sourceMappingURL=InAppNotification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useEffect","useMemo","useRef","AccessibilityInfo","Animated","PanResponder","Pressable","StyleSheet","Text","View","useWindowDimensions","formatLabel","useReduceMotion","fontSize","palette","radius","shadows","spacing","withAlpha","START_ALIGN","Avatar","jsx","_jsx","jsxs","_jsxs","DISMISS_DISTANCE_RATIO","DISMISS_VELOCITY","InAppNotification","notification","color","bottomInset","labels","onOpen","onDismiss","width","reduceMotion","animate","entry","Value","current","dragX","preview","hasAttachments","attachment","sender","senderName","replyFrom","name","newReply","accessibilityLabel","filter","Boolean","join","spring","toValue","useNativeDriver","speed","bounciness","start","announceForAccessibility","panResponder","settle","create","onMoveShouldSetPanResponderCapture","_","gesture","Math","abs","dx","dy","onPanResponderMove","setValue","onPanResponderTerminationRequest","onPanResponderRelease","farEnough","fastEnough","vx","direction","timing","duration","onPanResponderTerminate","handleAccessibilityAction","event","nativeEvent","actionName","conversationId","opacity","multiply","interpolate","inputRange","outputRange","extrapolate","translateY","lg","pointerEvents","style","styles","wrapper","bottom","md","children","panHandlers","card","transform","translateX","onPress","accessibilityRole","accessibilityHint","openHint","accessibilityActions","label","dismiss","onAccessibilityAction","pressed","row","avatar","senderAvatar","size","text","numberOfLines","position","left","right","paddingHorizontal","alignItems","zIndex","elevation","maxWidth","borderRadius","xl","backgroundColor","floating","flexDirection","gap","minHeight","paddingVertical","borderWidth","borderColor","white","flex","minWidth","body","fontWeight","lineHeight","textAlign","meta"],"sourceRoot":"../../../../src","sources":["ui/components/InAppNotification.tsx"],"mappings":"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","ignoreList":[]}
@@ -6,24 +6,29 @@
6
6
  *
7
7
  * Mobile differences: older messages load through an explicit "Load earlier
8
8
  * messages" control (a scroll-anchored infinite list fights the keyboard), and
9
- * the composer hides the attach button because v1 has no picker.
9
+ * the attach button only shows when the host app has a picker to open (see
10
+ * `attachments/pickers.ts`).
10
11
  */
11
12
 
12
13
  import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
13
- import { Animated, Easing, Image, Pressable, ScrollView, Text, TextInput, View } from 'react-native';
14
+ import { Animated, Easing, Image, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native';
14
15
  import { formatAgentUiActionMessage, getAgentUiBlocks, getProcedureForm } from "../../../agent-ui-blocks.js";
16
+ import { MAX_ATTACHMENTS, isImageAttachment } from "../../../attachments/pickers.js";
15
17
  import { AgentUiBlocks } from "../../components/AgentUiBlocks.js";
18
+ import { AttachmentChips } from "../../components/AttachmentChips.js";
19
+ import { AttachmentMenu, showNativeAttachmentMenu } from "../../components/AttachmentMenu.js";
16
20
  import { Avatar } from "../../components/Avatar.js";
21
+ import { ImageViewer } from "../../components/ImageViewer.js";
17
22
  import { WidgetErrorBoundary } from "../../components/ErrorBoundary.js";
18
23
  import { GradientBackground } from "../../components/Gradient.js";
19
- import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon } from "../../components/icons.js";
24
+ import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon, PaperclipIcon } from "../../components/icons.js";
20
25
  import { Markdown } from "../../components/Markdown.js";
21
26
  import { useReduceMotion } from "../../hooks/use-reduce-motion.js";
22
27
  import { globalStyles } from "../../styles/global.js";
23
28
  import { contrastingTextColor, mixHex, palette, withAlpha } from "../../styles/theme.js";
24
29
  import { formatClockTime, formatDayLabel, isSameDay } from "../../utils/conversations.js";
25
30
  import { stripHtml } from "../../utils/html.js";
26
- import { openExternalUrl } from "../../utils/safe-url.js";
31
+ import { isHttpUrl, openExternalUrl } from "../../utils/safe-url.js";
27
32
  import { backDirection, isRTL } from "../../utils/rtl.js";
28
33
  import { styles } from "./ChatView.styles.js";
29
34
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
@@ -40,6 +45,8 @@ export function ChatView({
40
45
  prefillText,
41
46
  offlineMessage,
42
47
  labels,
48
+ attachmentSources = [],
49
+ onPickAttachments,
43
50
  onSendMessage,
44
51
  onIdentifyVisitor,
45
52
  onLoadMore,
@@ -49,14 +56,38 @@ export function ChatView({
49
56
  }) {
50
57
  const [input, setInput] = useState(prefillText || '');
51
58
  const [composerFocused, setComposerFocused] = useState(false);
59
+ const [drafts, setDrafts] = useState([]);
60
+ const [menuOpen, setMenuOpen] = useState(false);
61
+ const [viewerImage, setViewerImage] = useState(null);
62
+ const [picking, setPicking] = useState(false);
63
+ // Only attachment sends hold the composer; text sends stay fire-and-forget.
64
+ const [sending, setSending] = useState(false);
65
+ const [notice, setNotice] = useState(null);
52
66
  const reduceMotion = useReduceMotion();
53
67
  const scrollRef = useRef(null);
54
68
  const typingStopTimer = useRef(null);
69
+ const noticeTimer = useRef(null);
70
+ const pickerTimer = useRef(null);
71
+ const mounted = useRef(false);
55
72
  useEffect(() => {
56
73
  if (prefillText) setInput(prefillText);
57
74
  }, [prefillText]);
58
- useEffect(() => () => {
59
- if (typingStopTimer.current) clearTimeout(typingStopTimer.current);
75
+
76
+ // The provider keys this screen by the store's thread generation. Pending
77
+ // picks and sends belong to this mount, even before a draft gets a server ID.
78
+ useEffect(() => {
79
+ mounted.current = true;
80
+ return () => {
81
+ mounted.current = false;
82
+ if (typingStopTimer.current) clearTimeout(typingStopTimer.current);
83
+ if (noticeTimer.current) clearTimeout(noticeTimer.current);
84
+ if (pickerTimer.current) clearTimeout(pickerTimer.current);
85
+ };
86
+ }, []);
87
+ const showNotice = useCallback(message => {
88
+ if (noticeTimer.current) clearTimeout(noticeTimer.current);
89
+ setNotice(message);
90
+ if (message) noticeTimer.current = setTimeout(() => setNotice(null), 5000);
60
91
  }, []);
61
92
  const scrollToBottom = useCallback((animated = true) => {
62
93
  scrollRef.current?.scrollToEnd({
@@ -100,15 +131,101 @@ export function ChatView({
100
131
  viewportHeight.current = height;
101
132
  if (shrank) scrollToBottom(false);
102
133
  }, [scrollToBottom]);
103
- const canSend = input.trim().length > 0;
134
+ const showAttach = attachmentSources.length > 0 && !!onPickAttachments;
135
+ const canSend = !sending && (input.trim().length > 0 || drafts.length > 0);
136
+ const problemLabel = problem => {
137
+ switch (problem) {
138
+ case 'tooMany':
139
+ return labels.attach.tooMany;
140
+ case 'tooLarge':
141
+ return labels.attach.tooLarge;
142
+ case 'tooLargeTogether':
143
+ return labels.attach.tooLargeTogether;
144
+ case 'cameraDenied':
145
+ return labels.attach.cameraDenied;
146
+ case 'failed':
147
+ return labels.attach.failed;
148
+ default:
149
+ return null;
150
+ }
151
+ };
152
+ const handlePick = async source => {
153
+ if (!mounted.current || !onPickAttachments || picking) return;
154
+ setPicking(true);
155
+ try {
156
+ const result = await onPickAttachments(source, drafts);
157
+ if (!mounted.current) return;
158
+ if (result.added.length > 0) {
159
+ setDrafts(current => [...current, ...result.added].slice(0, MAX_ATTACHMENTS));
160
+ }
161
+ showNotice(problemLabel(result.problem));
162
+ } finally {
163
+ if (mounted.current) setPicking(false);
164
+ }
165
+ };
166
+ const menuOptions = attachmentSources.map(source => ({
167
+ key: source,
168
+ label: source === 'photo' ? labels.attach.photoLibrary : source === 'camera' ? labels.attach.takePhoto : labels.attach.file
169
+ }));
170
+ const handleAttachPress = () => {
171
+ if (sending || picking) return;
172
+ if (drafts.length >= MAX_ATTACHMENTS) {
173
+ showNotice(labels.attach.tooMany);
174
+ return;
175
+ }
176
+ const only = attachmentSources.length === 1 ? attachmentSources[0] : undefined;
177
+ if (only) {
178
+ void handlePick(only);
179
+ return;
180
+ }
181
+ const native = showNativeAttachmentMenu(menuOptions, labels.attach.cancel, source => {
182
+ if (!mounted.current) return;
183
+ // Presenting a picker while the action sheet is still animating away
184
+ // fails silently on iOS; let it finish first.
185
+ pickerTimer.current = setTimeout(() => void handlePick(source), 350);
186
+ });
187
+ if (!native) setMenuOpen(true);
188
+ };
189
+ const handleMenuSelect = source => {
190
+ setMenuOpen(false);
191
+ void handlePick(source);
192
+ };
104
193
  const handleSend = () => {
105
194
  const trimmed = input.trim();
106
- if (!trimmed) return;
195
+ if (!canSend) return;
107
196
  setInput('');
108
197
  if (typingStopTimer.current) clearTimeout(typingStopTimer.current);
109
198
  onTyping(false);
110
- onSendMessage(trimmed);
111
199
  scrollToBottom();
200
+ if (drafts.length === 0) {
201
+ // The failed bubble already surfaces a failure to the visitor.
202
+ onSendMessage(trimmed).catch(() => {});
203
+ return;
204
+ }
205
+ const sent = drafts;
206
+ setSending(true);
207
+ showNotice(null);
208
+ onSendMessage(trimmed, sent.map(draft => ({
209
+ name: draft.name,
210
+ url: draft.dataUrl,
211
+ contentType: draft.contentType,
212
+ size: draft.size,
213
+ previewUrl: draft.previewUri
214
+ }))).then(() => {
215
+ if (!mounted.current) return;
216
+ const sentIds = new Set(sent.map(draft => draft.id));
217
+ setDrafts(current => current.filter(draft => !sentIds.has(draft.id)));
218
+ }).catch(error => {
219
+ // The visitor has moved to another thread: nothing to restore there.
220
+ if (!mounted.current) return;
221
+ // Chips stay for a retry; put the text back too unless they've
222
+ // already started typing something else.
223
+ setInput(current => current || trimmed);
224
+ const status = error?.status;
225
+ showNotice(status === 413 ? labels.attach.tooLargeTogether : labels.failedToSend);
226
+ }).finally(() => {
227
+ if (mounted.current) setSending(false);
228
+ });
112
229
  };
113
230
  const handleChangeText = text => {
114
231
  setInput(text);
@@ -125,7 +242,7 @@ export function ChatView({
125
242
  phone: stringPayload(action.payload.phone)
126
243
  });
127
244
  }
128
- onSendMessage(formatAgentUiActionMessage(action), undefined, action);
245
+ onSendMessage(formatAgentUiActionMessage(action), undefined, action).catch(() => {});
129
246
  scrollToBottom();
130
247
  }, [onIdentifyVisitor, onSendMessage, scrollToBottom]);
131
248
  const headerTextColor = contrastingTextColor(color);
@@ -262,9 +379,10 @@ export function ChatView({
262
379
  message: message,
263
380
  color: color,
264
381
  labels: labels,
382
+ onOpenImage: setViewerImage,
265
383
  senderLabel: isAgentMessage && (startsRun || startsDay) ? message.senderName || agentName || labels.team : null,
266
384
  timeLabel: formatClockTime(message.createdAt, locale),
267
- onSubmitProcedureForm: shouldShowProcedureForm(messages, message) ? body => onSendMessage(body) : undefined,
385
+ onSubmitProcedureForm: shouldShowProcedureForm(messages, message) ? body => void onSendMessage(body).catch(() => {}) : undefined,
268
386
  onAgentUiAction: shouldShowAgentUiBlocks(messages, message) ? handleAgentUiAction : undefined
269
387
  })
270
388
  })]
@@ -294,13 +412,44 @@ export function ChatView({
294
412
  style: styles.offlineText,
295
413
  children: offlineMessage
296
414
  })
297
- }) : null, /*#__PURE__*/_jsx(View, {
415
+ }) : null, /*#__PURE__*/_jsxs(View, {
298
416
  style: styles.composerWrapper,
299
- children: /*#__PURE__*/_jsxs(View, {
300
- style: [styles.composer, composerFocused && styles.composerFocused, composerFocused && {
417
+ children: [notice ? /*#__PURE__*/_jsx(Text, {
418
+ style: styles.composerNotice,
419
+ accessibilityRole: "alert",
420
+ accessibilityLiveRegion: "polite",
421
+ children: notice
422
+ }) : null, drafts.length > 0 ? /*#__PURE__*/_jsx(AttachmentChips, {
423
+ attachments: drafts,
424
+ disabled: sending,
425
+ removeLabel: labels.attach.remove,
426
+ onRemove: id => {
427
+ setDrafts(current => current.filter(draft => draft.id !== id));
428
+ showNotice(null);
429
+ }
430
+ }) : null, /*#__PURE__*/_jsxs(View, {
431
+ style: [styles.composer, showAttach && styles.composerWithAttach, composerFocused && styles.composerFocused, composerFocused && {
301
432
  borderColor: withAlpha(color, 0.55)
302
433
  }],
303
- children: [/*#__PURE__*/_jsx(TextInput, {
434
+ children: [showAttach ? /*#__PURE__*/_jsx(Pressable, {
435
+ onPress: handleAttachPress,
436
+ disabled: sending || picking,
437
+ accessibilityRole: "button",
438
+ accessibilityLabel: labels.attach.button,
439
+ accessibilityState: {
440
+ disabled: sending || picking
441
+ }
442
+ // 36pt circle + 4pt on every side = a 44pt target.
443
+ ,
444
+ hitSlop: 4,
445
+ style: ({
446
+ pressed
447
+ }) => [styles.attachButton, (pressed || sending || picking) && globalStyles.pressed],
448
+ children: /*#__PURE__*/_jsx(PaperclipIcon, {
449
+ size: 20,
450
+ color: palette.textMuted
451
+ })
452
+ }) : null, /*#__PURE__*/_jsx(TextInput, {
304
453
  value: input,
305
454
  onChangeText: handleChangeText,
306
455
  onFocus: () => setComposerFocused(true),
@@ -328,7 +477,21 @@ export function ChatView({
328
477
  color: canSend ? contrastingTextColor(color) : palette.textSubtle
329
478
  })
330
479
  })]
331
- })
480
+ })]
481
+ }), showAttach && Platform.OS !== 'ios' ? /*#__PURE__*/_jsx(AttachmentMenu, {
482
+ visible: menuOpen,
483
+ options: menuOptions,
484
+ cancelLabel: labels.attach.cancel,
485
+ onSelect: handleMenuSelect,
486
+ onClose: () => setMenuOpen(false)
487
+ }) : null, /*#__PURE__*/_jsx(ImageViewer, {
488
+ image: viewerImage,
489
+ labels: labels.viewer,
490
+ onClose: () => setViewerImage(null),
491
+ onOpenExternally: url => {
492
+ setViewerImage(null);
493
+ void openExternalUrl(url);
494
+ }
332
495
  })]
333
496
  });
334
497
  }
@@ -339,7 +502,8 @@ function MessageBubble({
339
502
  senderLabel,
340
503
  timeLabel,
341
504
  onSubmitProcedureForm,
342
- onAgentUiAction
505
+ onAgentUiAction,
506
+ onOpenImage
343
507
  }) {
344
508
  if (message.messageType === 'system') {
345
509
  const text = message.bodyPlain || stripHtml(message.body || '') || labels.conversationUpdated;
@@ -379,7 +543,8 @@ function MessageBubble({
379
543
  style: [styles.attachments, hasBody && styles.attachmentsSpaced],
380
544
  children: message.attachments.map(attachment => /*#__PURE__*/_jsx(AttachmentPreview, {
381
545
  attachment: attachment,
382
- isVisitor: isVisitor
546
+ isVisitor: isVisitor,
547
+ onOpenImage: onOpenImage
383
548
  }, attachment.id))
384
549
  }) : null, message.status === 'failed' ? /*#__PURE__*/_jsx(Text, {
385
550
  style: [styles.failedText, {
@@ -407,14 +572,24 @@ function MessageBubble({
407
572
  }
408
573
  function AttachmentPreview({
409
574
  attachment,
410
- isVisitor
575
+ isVisitor,
576
+ onOpenImage
411
577
  }) {
412
578
  // `url` is empty when the server sent a non-http(s) URL (see resolveUrl).
413
- const isImage = attachment.contentType?.startsWith('image/') && !!attachment.url;
414
- const open = () => void openExternalUrl(attachment.url);
579
+ // An unsent attachment shows its local file (or data URL): it renders, but
580
+ // there is nothing to open until the server copy arrives.
581
+ const [imageFailed, setImageFailed] = useState(false);
582
+ const isImage = isImageAttachment(attachment.contentType) && !!attachment.url && !imageFailed;
583
+ const openable = isHttpUrl(attachment.url);
584
+ // Images open in the in-app viewer; other files (PDF, sheets) need the OS.
585
+ const open = !openable ? undefined : isImage ? () => onOpenImage({
586
+ url: attachment.url,
587
+ name: attachment.name
588
+ }) : () => void openExternalUrl(attachment.url);
415
589
  if (isImage) {
416
590
  return /*#__PURE__*/_jsx(Pressable, {
417
591
  onPress: open,
592
+ disabled: !openable,
418
593
  accessibilityRole: "imagebutton",
419
594
  accessibilityLabel: attachment.name,
420
595
  children: /*#__PURE__*/_jsx(Image, {
@@ -424,11 +599,16 @@ function AttachmentPreview({
424
599
  style: styles.attachmentImage,
425
600
  resizeMode: "cover",
426
601
  accessibilityIgnoresInvertColors: true
602
+ // An image that can't load (expired link, deleted file) falls back to
603
+ // the file row below instead of an empty box.
604
+ ,
605
+ onError: () => setImageFailed(true)
427
606
  })
428
607
  });
429
608
  }
430
609
  return /*#__PURE__*/_jsx(Pressable, {
431
610
  onPress: open,
611
+ disabled: !openable,
432
612
  accessibilityRole: "button",
433
613
  style: [styles.attachmentFile, {
434
614
  borderColor: isVisitor ? withAlpha(palette.white, 0.35) : palette.border