@poolse/react-native 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2499 @@
1
+ 'use strict';
2
+
3
+ var react$1 = require('@poolse/react');
4
+ var react = require('react');
5
+ var reactNative = require('react-native');
6
+ var reactNativeSvg = require('react-native-svg');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ // src/ConversationView.tsx
10
+
11
+ // src/theme/default.ts
12
+ var lightColors = {
13
+ brand: "#ff5436",
14
+ brandStrong: "#d63415",
15
+ brandSoft: "#ffe9e2",
16
+ onBrand: "#fbf7f4",
17
+ paper: "#fbf7f4",
18
+ surface: "#ffffff",
19
+ surface2: "#f4eee9",
20
+ ink: "#1c1a19",
21
+ ink2: "#6b6560",
22
+ ink3: "#9a938c",
23
+ border: "#eae3dc",
24
+ presence: "#16c172",
25
+ success: "#16a34a",
26
+ warning: "#e08c16",
27
+ error: "#e5484d",
28
+ info: "#3b82f6",
29
+ selfBubble: "#ff5436",
30
+ selfBubbleText: "#fbf7f4",
31
+ otherBubble: "#ffffff",
32
+ otherBubbleText: "#1c1a19",
33
+ unreadPill: "#ff5436",
34
+ unreadPillText: "#fbf7f4"
35
+ };
36
+ var darkColors = {
37
+ brand: "#ff6a4d",
38
+ brandStrong: "#ff8366",
39
+ brandSoft: "#2c1712",
40
+ onBrand: "#fbf7f4",
41
+ paper: "#0e0d10",
42
+ surface: "#16141a",
43
+ surface2: "#201d25",
44
+ ink: "#f6f2ee",
45
+ ink2: "#a8a29b",
46
+ ink3: "#766f68",
47
+ border: "#2a2630",
48
+ presence: "#22d37a",
49
+ success: "#34d399",
50
+ warning: "#fbbf24",
51
+ error: "#f87171",
52
+ info: "#60a5fa",
53
+ selfBubble: "#ff6a4d",
54
+ selfBubbleText: "#fbf7f4",
55
+ otherBubble: "#16141a",
56
+ otherBubbleText: "#f6f2ee",
57
+ unreadPill: "#ff6a4d",
58
+ unreadPillText: "#fbf7f4"
59
+ };
60
+ var type = {
61
+ // RN can't use Bricolage/Hanken/JetBrains without expo-font setup,
62
+ // so we default to the platform sans-serif. Devs override via the
63
+ // theme prop if they ship custom fonts.
64
+ fontDisplay: "System",
65
+ fontBody: "System",
66
+ fontMono: "Menlo",
67
+ bodySize: 15,
68
+ lineHeight: 22,
69
+ weightRegular: "400",
70
+ weightMedium: "500",
71
+ weightSemibold: "600",
72
+ weightBold: "700"
73
+ };
74
+ var radii = {
75
+ sm: 8,
76
+ md: 12,
77
+ lg: 16,
78
+ xl: 24,
79
+ pill: 999,
80
+ bubble: 16
81
+ };
82
+ var spacing = {
83
+ xs: 4,
84
+ sm: 8,
85
+ md: 12,
86
+ lg: 16,
87
+ xl: 24
88
+ };
89
+ var lightShadows = {
90
+ sm: {
91
+ shadowColor: "#1c1a19",
92
+ shadowOpacity: 0.08,
93
+ shadowOffset: { width: 0, height: 1 },
94
+ shadowRadius: 2,
95
+ elevation: 1
96
+ },
97
+ md: {
98
+ shadowColor: "#1c1a19",
99
+ shadowOpacity: 0.18,
100
+ shadowOffset: { width: 0, height: 10 },
101
+ shadowRadius: 30,
102
+ elevation: 6
103
+ },
104
+ lg: {
105
+ shadowColor: "#1c1a19",
106
+ shadowOpacity: 0.28,
107
+ shadowOffset: { width: 0, height: 24 },
108
+ shadowRadius: 60,
109
+ elevation: 12
110
+ }
111
+ };
112
+ var darkShadows = {
113
+ sm: {
114
+ shadowColor: "#000000",
115
+ shadowOpacity: 0.4,
116
+ shadowOffset: { width: 0, height: 1 },
117
+ shadowRadius: 2,
118
+ elevation: 1
119
+ },
120
+ md: {
121
+ shadowColor: "#000000",
122
+ shadowOpacity: 0.55,
123
+ shadowOffset: { width: 0, height: 10 },
124
+ shadowRadius: 30,
125
+ elevation: 6
126
+ },
127
+ lg: {
128
+ shadowColor: "#000000",
129
+ shadowOpacity: 0.7,
130
+ shadowOffset: { width: 0, height: 24 },
131
+ shadowRadius: 60,
132
+ elevation: 12
133
+ }
134
+ };
135
+ var defaultLightTheme = {
136
+ mode: "light",
137
+ colors: lightColors,
138
+ type,
139
+ radii,
140
+ spacing,
141
+ shadows: lightShadows
142
+ };
143
+ var defaultDarkTheme = {
144
+ mode: "dark",
145
+ colors: darkColors,
146
+ type,
147
+ radii,
148
+ spacing,
149
+ shadows: darkShadows
150
+ };
151
+ var ThemeContext = react.createContext(null);
152
+ function PoolseTheme({ mode, theme, children }) {
153
+ const resolved = react.useMemo(() => {
154
+ const base = (mode ?? theme?.mode) === "dark" ? defaultDarkTheme : defaultLightTheme;
155
+ if (!theme) return base;
156
+ return {
157
+ mode: theme.mode ?? base.mode,
158
+ colors: { ...base.colors, ...theme.colors },
159
+ type: { ...base.type, ...theme.type },
160
+ radii: { ...base.radii, ...theme.radii },
161
+ spacing: { ...base.spacing, ...theme.spacing },
162
+ shadows: {
163
+ sm: { ...base.shadows.sm, ...theme.shadows?.sm },
164
+ md: { ...base.shadows.md, ...theme.shadows?.md },
165
+ lg: { ...base.shadows.lg, ...theme.shadows?.lg }
166
+ }
167
+ };
168
+ }, [mode, theme]);
169
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value: resolved, children });
170
+ }
171
+ function usePoolseTheme() {
172
+ return react.useContext(ThemeContext) ?? defaultLightTheme;
173
+ }
174
+
175
+ // src/primitives/icons-data.ts
176
+ var ICONS = {
177
+ attachment: {
178
+ vb: "0 0 24 24",
179
+ d: '<path d="M20 11.6 11.4 20.2a5 5 0 0 1-7.1-7.1l8.7-8.7a3.3 3.3 0 0 1 4.7 4.7l-8.6 8.6a1.65 1.65 0 0 1-2.36-2.32l7.86-7.86"/>'
180
+ },
181
+ bell: {
182
+ vb: "0 0 24 24",
183
+ d: '<path d="M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z"/><path d="M9.6 19.8a2.5 2.5 0 0 0 4.8 0"/>'
184
+ },
185
+ "check-double": {
186
+ vb: "0 0 24 24",
187
+ d: '<path d="M2.5 12.8 6.9 17 15.4 7.6"/><path d="M11.4 16.4 12.3 17.2 21 7.6"/>'
188
+ },
189
+ check: {
190
+ vb: "0 0 24 24",
191
+ d: '<path d="M4.5 12.6 9.4 17.4 19.5 6.6"/>'
192
+ },
193
+ "chevron-down": {
194
+ vb: "0 0 24 24",
195
+ d: '<path d="M5 9 12 16 19 9"/>'
196
+ },
197
+ "chevron-right": {
198
+ vb: "0 0 24 24",
199
+ d: '<path d="M9 5 16 12 9 19"/>'
200
+ },
201
+ close: {
202
+ vb: "0 0 24 24",
203
+ d: '<path d="M6 6 18 18M18 6 6 18"/>'
204
+ },
205
+ code: {
206
+ vb: "0 0 24 24",
207
+ d: '<path d="M9 7.5 4.5 12 9 16.5"/><path d="M15 7.5 19.5 12 15 16.5"/><path d="M13.2 5 10.8 19"/>'
208
+ },
209
+ compose: {
210
+ vb: "0 0 24 24",
211
+ d: '<path d="M11.5 4H6a2.5 2.5 0 0 0-2.5 2.5V18A2.5 2.5 0 0 0 6 20.5h11.5A2.5 2.5 0 0 0 20 18v-5.5"/><path d="M18.4 2.7a2.05 2.05 0 0 1 2.9 2.9L12 15l-4 1 1-4 9.4-9.3Z"/>'
212
+ },
213
+ copy: {
214
+ vb: "0 0 24 24",
215
+ d: '<rect x="8.4" y="8.4" width="11.2" height="11.2" rx="2.3"/><path d="M5.6 15.6H5A1.6 1.6 0 0 1 3.4 14V5A1.6 1.6 0 0 1 5 3.4h9A1.6 1.6 0 0 1 15.6 5v.6"/>'
216
+ },
217
+ download: {
218
+ vb: "0 0 24 24",
219
+ d: '<path d="M12 3.2v11.6"/><path d="M7.4 10.2 12 14.8 16.6 10.2"/><path d="M4 19.6h16"/>'
220
+ },
221
+ edit: {
222
+ vb: "0 0 24 24",
223
+ d: '<path d="M16.7 3.3a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.7-12.7Z"/>'
224
+ },
225
+ emoji: {
226
+ vb: "0 0 24 24",
227
+ d: '<circle cx="12" cy="12" r="8.6"/><circle cx="9" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="15" cy="10" r="1" fill="currentColor" stroke="none"/><path d="M8.4 14.2a4.6 4.6 0 0 0 7.2 0"/>'
228
+ },
229
+ heart: {
230
+ vb: "0 0 24 24",
231
+ d: '<path d="M12 20.3 4.4 12.9a4.6 4.6 0 1 1 6.5-6.5l1.1 1.1 1.1-1.1a4.6 4.6 0 1 1 6.5 6.5L12 20.3Z"/>'
232
+ },
233
+ image: {
234
+ vb: "0 0 24 24",
235
+ d: '<rect x="3.2" y="4.2" width="17.6" height="15.6" rx="2.6"/><circle cx="8.6" cy="9.4" r="1.7" fill="currentColor" stroke="none"/><path d="M20.8 15.8 16 11 6 19.8"/>'
236
+ },
237
+ info: {
238
+ vb: "0 0 24 24",
239
+ d: '<circle cx="12" cy="12" r="8.6"/><path d="M12 11v5.2"/><circle cx="12" cy="7.7" r="1" fill="currentColor" stroke="none"/>'
240
+ },
241
+ key: {
242
+ vb: "0 0 24 24",
243
+ d: '<circle cx="8" cy="8" r="4.4"/><path d="M11.1 11.1 20 20"/><path d="M16.6 16.6 18.6 14.6M14 14l2-2"/>'
244
+ },
245
+ link: {
246
+ vb: "0 0 24 24",
247
+ d: '<path d="M10.2 13.8a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 0 0-5-5l-1.2 1.2"/><path d="M13.8 10.2a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 0 0 5 5l1.2-1.2"/>'
248
+ },
249
+ lock: {
250
+ vb: "0 0 24 24",
251
+ d: '<rect x="4.6" y="10.4" width="14.8" height="9.8" rx="2.2"/><path d="M8 10.4V7a4 4 0 0 1 8 0v3.4"/>'
252
+ },
253
+ message: {
254
+ vb: "0 0 24 24",
255
+ d: '<path d="M20.5 11.4c0 4.5-3.9 8.1-8.5 8.1a9 9 0 0 1-3.7-.78L3.5 20.5l1.8-4.6A7.9 7.9 0 0 1 3.5 11.4C3.5 6.9 7.4 3.3 12 3.3s8.5 3.6 8.5 8.1Z"/>'
256
+ },
257
+ messages: {
258
+ vb: "0 0 24 24",
259
+ d: '<path d="M14 9.2a4.7 4.7 0 0 1-4.7 4.7H6.8L3.5 16.4v-2.9A4.7 4.7 0 0 1 6.8 4.5h2.5A4.7 4.7 0 0 1 14 9.2Z"/><path d="M9.4 13.6A4.7 4.7 0 0 0 13.6 16h3.6l3.3 2.5v-2.9a4.7 4.7 0 0 0-2.4-4.1"/>'
260
+ },
261
+ mic: {
262
+ vb: "0 0 24 24",
263
+ d: '<rect x="9" y="2.6" width="6" height="11.2" rx="3"/><path d="M5.6 11.2a6.4 6.4 0 0 0 12.8 0"/><path d="M12 17.6v3.4"/><path d="M8.6 21h6.8"/>'
264
+ },
265
+ "more-h": {
266
+ vb: "0 0 24 24",
267
+ d: '<circle cx="5.4" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="18.6" cy="12" r="1.6" fill="currentColor" stroke="none"/>'
268
+ },
269
+ "more-v": {
270
+ vb: "0 0 24 24",
271
+ d: '<circle cx="12" cy="5.4" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="18.6" r="1.6" fill="currentColor" stroke="none"/>'
272
+ },
273
+ pin: {
274
+ vb: "0 0 24 24",
275
+ d: '<path d="M9 3.4h6v1.8l-1 5.6 3 3.2H7l3-3.2-1-5.6V3.4Z"/><path d="M12 17v3.6"/>'
276
+ },
277
+ plus: {
278
+ vb: "0 0 24 24",
279
+ d: '<path d="M12 5v14M5 12h14"/>'
280
+ },
281
+ presence: {
282
+ vb: "0 0 24 24",
283
+ d: '<circle cx="12" cy="12" r="9" fill="currentColor" opacity="0.18" stroke="none"/><circle cx="12" cy="12" r="5" fill="currentColor" stroke="none"/>'
284
+ },
285
+ pulse: {
286
+ vb: "0 0 24 24",
287
+ d: '<path d="M3 13h4.6l2.2-4.6 3.4 9 2.2-4.4H21"/>'
288
+ },
289
+ reply: {
290
+ vb: "0 0 24 24",
291
+ d: '<path d="M9 7 4 12l5 5"/><path d="M4 12h9.5a6.5 6.5 0 0 1 6.5 6.5V20"/>'
292
+ },
293
+ search: {
294
+ vb: "0 0 24 24",
295
+ d: '<circle cx="11" cy="11" r="6.8"/><path d="M20.8 20.8 16 16"/>'
296
+ },
297
+ "send-fill": {
298
+ vb: "0 0 24 24",
299
+ d: '<path fill="currentColor" stroke="none" d="M21.7 2.3a1 1 0 0 1 .25 1L15.3 21.8a1 1 0 0 1-1.86.06L10.6 14.2 3 11.4a1 1 0 0 1 .06-1.9L20.7 2.05a1 1 0 0 1 1 .25Z"/>'
300
+ },
301
+ send: {
302
+ vb: "0 0 24 24",
303
+ d: '<path d="M21.4 2.6 11 13"/><path d="M21.4 2.6 14.8 21.5a.6.6 0 0 1-1.1.05L11 14l-7.5-2.7a.6.6 0 0 1 .05-1.1L21.4 2.6Z"/>'
304
+ },
305
+ settings: {
306
+ vb: "0 0 24 24",
307
+ d: '<circle cx="12" cy="12" r="3.1"/><path d="M12 2.6V5M12 19v2.4M4.2 4.2 5.9 5.9M18.1 18.1l1.7 1.7M2.6 12H5M19 12h2.4M4.2 19.8 5.9 18.1M18.1 5.9l1.7-1.7"/>'
308
+ },
309
+ sliders: {
310
+ vb: "0 0 24 24",
311
+ d: '<path d="M4 7h9M17 7h3"/><circle cx="15" cy="7" r="2.1"/><path d="M4 17h3M11 17h9"/><circle cx="9" cy="17" r="2.1"/>'
312
+ },
313
+ "thumbs-up": {
314
+ vb: "0 0 24 24",
315
+ d: '<path d="M7 10.4V20H4.2V10.4Z"/><path d="M7 10.4 11 3.2a2 2 0 0 1 1.9 2.2L12.4 9H18a2 2 0 0 1 2 2.4l-1.1 6A2 2 0 0 1 16.9 20H7"/>'
316
+ },
317
+ trash: {
318
+ vb: "0 0 24 24",
319
+ d: '<path d="M4 7h16"/><path d="M9.2 7V5.2A1.8 1.8 0 0 1 11 3.4h2a1.8 1.8 0 0 1 1.8 1.8V7"/><path d="M6.2 7 7.2 19A1.9 1.9 0 0 0 9.1 20.7h5.8A1.9 1.9 0 0 0 16.8 19L17.8 7"/><path d="M10.3 11v5M13.7 11v5"/>'
320
+ },
321
+ typing: {
322
+ vb: "0 0 24 24",
323
+ d: '<circle cx="6" cy="12" r="1.7" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.7" fill="currentColor" stroke="none"/><circle cx="18" cy="12" r="1.7" fill="currentColor" stroke="none"/>'
324
+ },
325
+ "user-plus": {
326
+ vb: "0 0 24 24",
327
+ d: '<circle cx="9" cy="8" r="3.8"/><path d="M3 20a6 6 0 0 1 12 0"/><path d="M18 8v6M15 11h6"/>'
328
+ },
329
+ user: {
330
+ vb: "0 0 24 24",
331
+ d: '<circle cx="12" cy="8" r="4"/><path d="M4.6 20a7.4 7.4 0 0 1 14.8 0"/>'
332
+ },
333
+ users: {
334
+ vb: "0 0 24 24",
335
+ d: '<circle cx="9" cy="8" r="3.6"/><path d="M2.6 20a6.4 6.4 0 0 1 12.8 0"/><path d="M15.8 4.7a3.6 3.6 0 0 1 0 6.6"/><path d="M17.6 20a6.4 6.4 0 0 0-3.1-5.5"/>'
336
+ },
337
+ webhook: {
338
+ vb: "0 0 24 24",
339
+ d: '<circle cx="6" cy="6" r="2.6"/><circle cx="18" cy="9" r="2.6"/><circle cx="9.5" cy="18" r="2.6"/><path d="M7.9 7.9 12 12l3.7-1.5"/><path d="M11.6 12.7 9.8 16"/>'
340
+ }
341
+ };
342
+ function PoolseIcon({ name, size = 20, color, label }) {
343
+ const theme = usePoolseTheme();
344
+ const def = ICONS[name];
345
+ if (!def) return null;
346
+ const stroke = color ?? theme.colors.ink;
347
+ const xml = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${def.vb}" fill="none" stroke="${stroke}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${def.d}</svg>`;
348
+ const a11y = label === null ? { accessibilityRole: "none" } : { accessibilityRole: "image", accessibilityLabel: label ?? name };
349
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.SvgXml, { xml, width: size, height: size, ...a11y });
350
+ }
351
+ function AttachmentPicker({ visible, onClose, onPickInputs }) {
352
+ const theme = usePoolseTheme();
353
+ const upload = react$1.useAttachmentUpload();
354
+ const pickFromKind = async (kind) => {
355
+ try {
356
+ const adapter = onPickInputs ?? defaultAdapter;
357
+ const inputs = await adapter(kind);
358
+ for (const input of inputs) {
359
+ void upload.upload(input);
360
+ }
361
+ } catch (err) {
362
+ console.warn("[poolse/picker]", err);
363
+ } finally {
364
+ onClose();
365
+ }
366
+ };
367
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { visible, transparent: true, animationType: "slide", onRequestClose: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles.backdrop, onPress: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(
368
+ reactNative.View,
369
+ {
370
+ style: [
371
+ styles.sheet,
372
+ {
373
+ backgroundColor: theme.colors.surface,
374
+ borderTopLeftRadius: theme.radii.lg,
375
+ borderTopRightRadius: theme.radii.lg
376
+ },
377
+ theme.shadows.lg
378
+ ],
379
+ children: [
380
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: styles.item, onPress: () => pickFromKind("image"), children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
382
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.itemText, { color: theme.colors.ink }], children: "Photo or video" })
383
+ ] }),
384
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: styles.item, onPress: () => pickFromKind("document"), children: [
385
+ /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
386
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.itemText, { color: theme.colors.ink }], children: "File" })
387
+ ] }),
388
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles.cancel, onPress: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.itemText, { color: theme.colors.brand }], children: "Cancel" }) })
389
+ ]
390
+ }
391
+ ) }) });
392
+ }
393
+ async function defaultAdapter(kind) {
394
+ if (kind === "image") {
395
+ const mod2 = await tryImport("expo-image-picker");
396
+ if (!mod2)
397
+ throw new Error("expo-image-picker not installed \u2014 `npx expo install expo-image-picker`");
398
+ const res2 = await mod2.launchImageLibraryAsync({ mediaTypes: "images", quality: 0.85 });
399
+ if (res2.canceled) return [];
400
+ return res2.assets.map(
401
+ (asset) => ({
402
+ body: {
403
+ uri: asset.uri,
404
+ name: asset.fileName ?? "image.jpg",
405
+ type: asset.mimeType ?? "image/jpeg"
406
+ },
407
+ contentType: asset.mimeType ?? "image/jpeg",
408
+ byteSize: asset.fileSize ?? 0,
409
+ filename: asset.fileName ?? "image.jpg"
410
+ })
411
+ );
412
+ }
413
+ const mod = await tryImport("expo-document-picker");
414
+ if (!mod)
415
+ throw new Error("expo-document-picker not installed \u2014 `npx expo install expo-document-picker`");
416
+ const res = await mod.getDocumentAsync({ copyToCacheDirectory: true, multiple: true });
417
+ if (res.canceled) return [];
418
+ return res.assets.map(
419
+ (asset) => ({
420
+ body: {
421
+ uri: asset.uri,
422
+ name: asset.name,
423
+ type: asset.mimeType ?? "application/octet-stream"
424
+ },
425
+ contentType: asset.mimeType ?? "application/octet-stream",
426
+ byteSize: asset.size ?? 0,
427
+ filename: asset.name
428
+ })
429
+ );
430
+ }
431
+ async function tryImport(name) {
432
+ try {
433
+ return await new Function("m", "return import(m)")(name);
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+ var styles = reactNative.StyleSheet.create({
439
+ backdrop: {
440
+ flex: 1,
441
+ backgroundColor: "rgba(0,0,0,0.35)",
442
+ justifyContent: "flex-end"
443
+ },
444
+ sheet: {
445
+ paddingVertical: 8,
446
+ paddingBottom: 24
447
+ },
448
+ item: {
449
+ flexDirection: "row",
450
+ alignItems: "center",
451
+ paddingHorizontal: 20,
452
+ paddingVertical: 14,
453
+ gap: 14
454
+ },
455
+ cancel: {
456
+ paddingHorizontal: 20,
457
+ paddingVertical: 14,
458
+ alignItems: "center",
459
+ marginTop: 4
460
+ },
461
+ itemText: {
462
+ fontSize: 15,
463
+ fontWeight: "500"
464
+ }
465
+ });
466
+
467
+ // src/primitives/userColor.ts
468
+ var PALETTE = [
469
+ "#6366f1",
470
+ // indigo
471
+ "#10b981",
472
+ // emerald
473
+ "#f59e0b",
474
+ // amber
475
+ "#ec4899",
476
+ // rose
477
+ "#0ea5e9",
478
+ // sky
479
+ "#8b5cf6",
480
+ // violet
481
+ "#14b8a6",
482
+ // teal
483
+ "#d946ef"
484
+ // fuchsia
485
+ ];
486
+ function userColor(id) {
487
+ let h = 0;
488
+ for (let i = 0; i < id.length; i++) {
489
+ h = h * 31 + id.charCodeAt(i) | 0;
490
+ }
491
+ return PALETTE[Math.abs(h) % PALETTE.length];
492
+ }
493
+ var SIZES = { sm: 28, md: 40, lg: 56 };
494
+ function Avatar({ src, name, seed, online, size = "md" }) {
495
+ const theme = usePoolseTheme();
496
+ const dim = SIZES[size];
497
+ const initials = computeInitials(name);
498
+ const tint = seed ? userColor(seed) : theme.colors.brand;
499
+ const fontSize = size === "sm" ? 11 : size === "lg" ? 22 : 15;
500
+ return /* @__PURE__ */ jsxRuntime.jsxs(
501
+ reactNative.View,
502
+ {
503
+ style: [
504
+ styles2.root,
505
+ {
506
+ width: dim,
507
+ height: dim,
508
+ borderRadius: dim / 2,
509
+ backgroundColor: tint
510
+ }
511
+ ],
512
+ accessibilityRole: "image",
513
+ accessibilityLabel: name ?? "avatar",
514
+ children: [
515
+ src ? /* @__PURE__ */ jsxRuntime.jsx(
516
+ reactNative.Image,
517
+ {
518
+ source: { uri: src },
519
+ style: { width: dim, height: dim, borderRadius: dim / 2 },
520
+ accessible: false
521
+ }
522
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
523
+ reactNative.Text,
524
+ {
525
+ style: {
526
+ color: theme.colors.onBrand,
527
+ fontSize,
528
+ fontWeight: "600",
529
+ fontFamily: theme.type.fontBody
530
+ },
531
+ children: initials
532
+ }
533
+ ),
534
+ online ? /* @__PURE__ */ jsxRuntime.jsx(
535
+ reactNative.View,
536
+ {
537
+ style: [
538
+ styles2.presence,
539
+ {
540
+ backgroundColor: theme.colors.presence,
541
+ borderColor: theme.colors.surface,
542
+ width: dim * 0.32,
543
+ height: dim * 0.32,
544
+ borderRadius: dim * 0.16
545
+ }
546
+ ]
547
+ }
548
+ ) : null
549
+ ]
550
+ }
551
+ );
552
+ }
553
+ function computeInitials(name) {
554
+ if (!name) return "?";
555
+ const parts = name.trim().split(/\s+/).slice(0, 2);
556
+ return parts.map((p) => p.charAt(0).toUpperCase()).join("").slice(0, 2) || "?";
557
+ }
558
+ var styles2 = reactNative.StyleSheet.create({
559
+ root: {
560
+ alignItems: "center",
561
+ justifyContent: "center",
562
+ overflow: "visible"
563
+ },
564
+ presence: {
565
+ position: "absolute",
566
+ bottom: 0,
567
+ right: 0,
568
+ borderWidth: 2
569
+ }
570
+ });
571
+ function UploadQueueStrip() {
572
+ const theme = usePoolseTheme();
573
+ const upload = react$1.useAttachmentUpload();
574
+ if (upload.queue.length === 0) return null;
575
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.strip, children: upload.queue.map((item) => {
576
+ const isError = item.status === "error";
577
+ const isReady = item.status === "ready";
578
+ return /* @__PURE__ */ jsxRuntime.jsxs(
579
+ reactNative.View,
580
+ {
581
+ style: [
582
+ styles3.chip,
583
+ {
584
+ backgroundColor: theme.colors.surface2,
585
+ borderColor: isError ? theme.colors.error : theme.colors.border,
586
+ borderRadius: theme.radii.sm
587
+ }
588
+ ],
589
+ children: [
590
+ /* @__PURE__ */ jsxRuntime.jsx(
591
+ reactNative.Text,
592
+ {
593
+ style: [styles3.name, { color: isError ? theme.colors.error : theme.colors.ink }],
594
+ numberOfLines: 1,
595
+ children: item.filename
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.status, { color: theme.colors.ink3 }], children: isReady ? "ready" : isError ? "failed" : item.status }),
599
+ /* @__PURE__ */ jsxRuntime.jsx(
600
+ reactNative.Pressable,
601
+ {
602
+ hitSlop: 6,
603
+ onPress: () => isReady || isError ? upload.remove(item.localId) : upload.cancel(item.localId),
604
+ style: styles3.btn,
605
+ accessibilityLabel: "Remove upload",
606
+ children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 12, color: theme.colors.ink2 })
607
+ }
608
+ )
609
+ ]
610
+ },
611
+ item.localId
612
+ );
613
+ }) });
614
+ }
615
+ var styles3 = reactNative.StyleSheet.create({
616
+ strip: {
617
+ flexDirection: "row",
618
+ flexWrap: "wrap",
619
+ gap: 6,
620
+ marginBottom: 6
621
+ },
622
+ chip: {
623
+ flexDirection: "row",
624
+ alignItems: "center",
625
+ paddingHorizontal: 8,
626
+ paddingVertical: 4,
627
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
628
+ gap: 6,
629
+ maxWidth: 200
630
+ },
631
+ name: {
632
+ fontSize: 12,
633
+ flex: 1
634
+ },
635
+ status: {
636
+ fontSize: 10,
637
+ textTransform: "capitalize"
638
+ },
639
+ btn: {
640
+ padding: 2
641
+ }
642
+ });
643
+ var MessageComposer = react.forwardRef(
644
+ function MessageComposer2({
645
+ onSend,
646
+ onTyping,
647
+ placeholder = "Type a message\u2026",
648
+ disabled = false,
649
+ replyingTo,
650
+ onCancelReply,
651
+ labelFor,
652
+ attachments = true,
653
+ onAttachPress
654
+ }, ref) {
655
+ const theme = usePoolseTheme();
656
+ const inputRef = react.useRef(null);
657
+ const [value, setValue] = react.useState("");
658
+ const [sending, setSending] = react.useState(false);
659
+ const upload = react$1.useAttachmentUpload();
660
+ react.useImperativeHandle(
661
+ ref,
662
+ () => ({
663
+ addFiles: (inputs) => {
664
+ if (!attachments) return;
665
+ for (const input of inputs) {
666
+ void upload.upload(input);
667
+ }
668
+ },
669
+ focus: () => inputRef.current?.focus(),
670
+ clear: () => setValue("")
671
+ }),
672
+ [attachments, upload]
673
+ );
674
+ const readyAttachmentIds = upload.queue.filter((item) => item.status === "ready" && item.attachment).map((item) => item.attachment.id);
675
+ const isUploading = upload.queue.some(
676
+ (item) => item.status === "uploading" || item.status === "pending"
677
+ );
678
+ const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 && readyAttachmentIds.length === 0;
679
+ const handleSend = async () => {
680
+ if (submitDisabled) return;
681
+ setSending(true);
682
+ try {
683
+ await onSend(value.trim(), {
684
+ ...replyingTo ? { quoted_message_id: replyingTo.id } : {},
685
+ ...readyAttachmentIds.length > 0 ? { attachment_ids: readyAttachmentIds } : {}
686
+ });
687
+ setValue("");
688
+ upload.reset();
689
+ } finally {
690
+ setSending(false);
691
+ }
692
+ };
693
+ return /* @__PURE__ */ jsxRuntime.jsx(
694
+ reactNative.KeyboardAvoidingView,
695
+ {
696
+ behavior: reactNative.Platform.OS === "ios" ? "padding" : void 0,
697
+ keyboardVerticalOffset: reactNative.Platform.OS === "ios" ? 80 : 0,
698
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
699
+ reactNative.View,
700
+ {
701
+ style: [
702
+ styles4.wrap,
703
+ {
704
+ backgroundColor: theme.colors.surface,
705
+ borderTopColor: theme.colors.border
706
+ }
707
+ ],
708
+ children: [
709
+ replyingTo ? /* @__PURE__ */ jsxRuntime.jsx(
710
+ ReplyChip,
711
+ {
712
+ message: replyingTo,
713
+ ...labelFor ? { labelFor } : {},
714
+ ...onCancelReply ? { onCancel: onCancelReply } : {}
715
+ }
716
+ ) : null,
717
+ attachments ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
718
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.row, children: [
719
+ attachments && onAttachPress ? /* @__PURE__ */ jsxRuntime.jsx(
720
+ reactNative.Pressable,
721
+ {
722
+ onPress: onAttachPress,
723
+ hitSlop: 8,
724
+ style: styles4.iconBtn,
725
+ accessibilityLabel: "Attach file",
726
+ children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
727
+ }
728
+ ) : null,
729
+ /* @__PURE__ */ jsxRuntime.jsx(
730
+ reactNative.TextInput,
731
+ {
732
+ ref: inputRef,
733
+ value,
734
+ onChangeText: (text) => {
735
+ setValue(text);
736
+ onTyping?.();
737
+ },
738
+ placeholder,
739
+ placeholderTextColor: theme.colors.ink3,
740
+ multiline: true,
741
+ editable: !disabled,
742
+ style: [
743
+ styles4.input,
744
+ {
745
+ color: theme.colors.ink,
746
+ backgroundColor: theme.colors.surface2,
747
+ borderRadius: theme.radii.lg,
748
+ fontFamily: theme.type.fontBody,
749
+ fontSize: theme.type.bodySize
750
+ }
751
+ ]
752
+ }
753
+ ),
754
+ /* @__PURE__ */ jsxRuntime.jsx(
755
+ reactNative.Pressable,
756
+ {
757
+ onPress: handleSend,
758
+ disabled: submitDisabled,
759
+ style: [
760
+ styles4.sendBtn,
761
+ {
762
+ backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
763
+ opacity: submitDisabled ? 0.6 : 1
764
+ }
765
+ ],
766
+ accessibilityLabel: "Send message",
767
+ children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "send", size: 18, color: theme.colors.onBrand })
768
+ }
769
+ )
770
+ ] })
771
+ ]
772
+ }
773
+ )
774
+ }
775
+ );
776
+ }
777
+ );
778
+ function ReplyChip({
779
+ message,
780
+ labelFor,
781
+ onCancel
782
+ }) {
783
+ const theme = usePoolseTheme();
784
+ const senderName = message.sender_external_id ? labelFor?.(message.sender_external_id) ?? message.sender_external_id : "Unknown";
785
+ return /* @__PURE__ */ jsxRuntime.jsxs(
786
+ reactNative.View,
787
+ {
788
+ style: [
789
+ styles4.replyChip,
790
+ {
791
+ backgroundColor: theme.colors.surface2,
792
+ borderLeftColor: theme.colors.brand
793
+ }
794
+ ],
795
+ children: [
796
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
797
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles4.replyName, { color: theme.colors.brand }], children: [
798
+ "Replying to ",
799
+ senderName
800
+ ] }),
801
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
802
+ ] }),
803
+ onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles4.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
804
+ ]
805
+ }
806
+ );
807
+ }
808
+ var styles4 = reactNative.StyleSheet.create({
809
+ wrap: {
810
+ borderTopWidth: reactNative.StyleSheet.hairlineWidth,
811
+ paddingHorizontal: 12,
812
+ paddingVertical: 8
813
+ },
814
+ row: {
815
+ flexDirection: "row",
816
+ alignItems: "flex-end",
817
+ gap: 8
818
+ },
819
+ iconBtn: {
820
+ padding: 8,
821
+ justifyContent: "center",
822
+ alignItems: "center"
823
+ },
824
+ input: {
825
+ flex: 1,
826
+ minHeight: 40,
827
+ maxHeight: 120,
828
+ paddingHorizontal: 12,
829
+ paddingTop: 10,
830
+ paddingBottom: 10
831
+ },
832
+ sendBtn: {
833
+ width: 40,
834
+ height: 40,
835
+ borderRadius: 20,
836
+ justifyContent: "center",
837
+ alignItems: "center"
838
+ },
839
+ replyChip: {
840
+ flexDirection: "row",
841
+ alignItems: "center",
842
+ borderLeftWidth: 3,
843
+ paddingLeft: 10,
844
+ paddingRight: 6,
845
+ paddingVertical: 6,
846
+ marginBottom: 8,
847
+ borderRadius: 6
848
+ },
849
+ replyName: {
850
+ fontSize: 11,
851
+ fontWeight: "600",
852
+ marginBottom: 2
853
+ },
854
+ replyBody: {
855
+ fontSize: 12
856
+ },
857
+ replyCancel: {
858
+ padding: 6
859
+ }
860
+ });
861
+ function MentionInput({ conversationId, onSend, ...composerProps }) {
862
+ const theme = usePoolseTheme();
863
+ const { members } = react$1.useMembers(conversationId);
864
+ const [pickerOpen, setPickerOpen] = react.useState(false);
865
+ const [pickerQuery, setPickerQuery] = react.useState("");
866
+ const [pendingMentionUuids, setPendingMentionUuids] = react.useState([]);
867
+ const [text, setText] = react.useState("");
868
+ const filteredMembers = react.useMemo(() => {
869
+ if (!pickerQuery) return members;
870
+ const q = pickerQuery.toLowerCase();
871
+ return members.filter((m) => m.external_id.toLowerCase().includes(q));
872
+ }, [members, pickerQuery]);
873
+ const handleSend = async (body, opts) => {
874
+ await onSend(body, {
875
+ ...opts,
876
+ ...pendingMentionUuids.length > 0 ? { mentions: pendingMentionUuids } : {}
877
+ });
878
+ setPendingMentionUuids([]);
879
+ setText("");
880
+ };
881
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
882
+ /* @__PURE__ */ jsxRuntime.jsx(
883
+ MessageComposer,
884
+ {
885
+ ...composerProps,
886
+ onSend: handleSend,
887
+ onTyping: () => {
888
+ composerProps.onTyping?.();
889
+ const lastChar = text.slice(-1);
890
+ if (lastChar === "@") setPickerOpen(true);
891
+ }
892
+ }
893
+ ),
894
+ /* @__PURE__ */ jsxRuntime.jsx(
895
+ reactNative.Modal,
896
+ {
897
+ visible: pickerOpen,
898
+ transparent: true,
899
+ animationType: "slide",
900
+ onRequestClose: () => setPickerOpen(false),
901
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles5.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
902
+ reactNative.View,
903
+ {
904
+ style: [
905
+ styles5.sheet,
906
+ {
907
+ backgroundColor: theme.colors.surface,
908
+ borderTopLeftRadius: theme.radii.lg,
909
+ borderTopRightRadius: theme.radii.lg
910
+ }
911
+ ],
912
+ children: [
913
+ /* @__PURE__ */ jsxRuntime.jsx(
914
+ reactNative.Text,
915
+ {
916
+ style: [styles5.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
917
+ children: "Mention a member"
918
+ }
919
+ ),
920
+ /* @__PURE__ */ jsxRuntime.jsx(
921
+ reactNative.FlatList,
922
+ {
923
+ data: filteredMembers,
924
+ keyExtractor: (m) => m.user_id,
925
+ renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(
926
+ MentionRow,
927
+ {
928
+ member: item,
929
+ onPress: () => {
930
+ setPendingMentionUuids((prev) => [...prev, item.user_id]);
931
+ setText((prev) => `${prev}${item.external_id} `);
932
+ setPickerOpen(false);
933
+ }
934
+ }
935
+ )
936
+ }
937
+ )
938
+ ]
939
+ }
940
+ ) })
941
+ }
942
+ )
943
+ ] });
944
+ }
945
+ function MentionRow({ member, onPress }) {
946
+ const theme = usePoolseTheme();
947
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: styles5.row, children: [
948
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
949
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
950
+ ] });
951
+ }
952
+ var styles5 = reactNative.StyleSheet.create({
953
+ backdrop: {
954
+ flex: 1,
955
+ backgroundColor: "rgba(0,0,0,0.35)",
956
+ justifyContent: "flex-end"
957
+ },
958
+ sheet: {
959
+ maxHeight: "60%",
960
+ paddingVertical: 12
961
+ },
962
+ title: {
963
+ fontSize: 14,
964
+ fontWeight: "600",
965
+ paddingHorizontal: 16,
966
+ paddingBottom: 8
967
+ },
968
+ row: {
969
+ flexDirection: "row",
970
+ alignItems: "center",
971
+ paddingHorizontal: 16,
972
+ paddingVertical: 10,
973
+ gap: 10
974
+ },
975
+ rowText: {
976
+ fontSize: 14
977
+ }
978
+ });
979
+ function MessageList({
980
+ messages,
981
+ loading = false,
982
+ hasMore = false,
983
+ onLoadMore,
984
+ renderItem,
985
+ ListHeader = null,
986
+ ListFooter = null,
987
+ emptyState
988
+ }) {
989
+ const theme = usePoolseTheme();
990
+ const data = [...messages].reverse();
991
+ const handleRender = react.useCallback(
992
+ ({ item, index }) => renderItem(item, messages.length - 1 - index),
993
+ [renderItem, messages.length]
994
+ );
995
+ if (loading && messages.length === 0) {
996
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
997
+ }
998
+ if (messages.length === 0) {
999
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
1000
+ }
1001
+ return /* @__PURE__ */ jsxRuntime.jsx(
1002
+ reactNative.FlatList,
1003
+ {
1004
+ inverted: true,
1005
+ data,
1006
+ keyExtractor: (item) => item.id,
1007
+ renderItem: handleRender,
1008
+ onEndReached: hasMore ? onLoadMore : void 0,
1009
+ onEndReachedThreshold: 0.3,
1010
+ ListHeaderComponent: ListFooter,
1011
+ ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
1012
+ hasMore && loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
1013
+ ListHeader
1014
+ ] }),
1015
+ style: { backgroundColor: theme.colors.paper },
1016
+ contentContainerStyle: { paddingVertical: 8 }
1017
+ }
1018
+ );
1019
+ }
1020
+ var styles6 = reactNative.StyleSheet.create({
1021
+ placeholder: {
1022
+ flex: 1,
1023
+ alignItems: "center",
1024
+ justifyContent: "center",
1025
+ padding: 24
1026
+ }
1027
+ });
1028
+ function EditableMessageBubble({
1029
+ message,
1030
+ currentUserId,
1031
+ onCancel,
1032
+ onSave
1033
+ }) {
1034
+ const theme = usePoolseTheme();
1035
+ const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1036
+ const [value, setValue] = react.useState(message.body ?? "");
1037
+ const [saving, setSaving] = react.useState(false);
1038
+ const submit = async () => {
1039
+ if (saving || !value.trim()) return;
1040
+ setSaving(true);
1041
+ try {
1042
+ await onSave(value.trim());
1043
+ } finally {
1044
+ setSaving(false);
1045
+ }
1046
+ };
1047
+ const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1048
+ const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1049
+ return /* @__PURE__ */ jsxRuntime.jsx(
1050
+ reactNative.View,
1051
+ {
1052
+ style: [
1053
+ styles7.wrap,
1054
+ {
1055
+ alignSelf: isSelf ? "flex-end" : "flex-start",
1056
+ maxWidth: "80%"
1057
+ }
1058
+ ],
1059
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1060
+ reactNative.View,
1061
+ {
1062
+ style: [
1063
+ styles7.bubble,
1064
+ {
1065
+ backgroundColor: bg,
1066
+ borderRadius: theme.radii.bubble,
1067
+ paddingHorizontal: theme.spacing.md,
1068
+ paddingVertical: theme.spacing.sm
1069
+ },
1070
+ theme.shadows.sm
1071
+ ],
1072
+ children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsx(
1074
+ reactNative.TextInput,
1075
+ {
1076
+ value,
1077
+ onChangeText: setValue,
1078
+ multiline: true,
1079
+ autoFocus: true,
1080
+ style: {
1081
+ color: ink,
1082
+ fontFamily: theme.type.fontBody,
1083
+ fontSize: theme.type.bodySize,
1084
+ minHeight: 24
1085
+ }
1086
+ }
1087
+ ),
1088
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.actions, children: [
1089
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
1090
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
1091
+ ] })
1092
+ ]
1093
+ }
1094
+ )
1095
+ }
1096
+ );
1097
+ }
1098
+ var styles7 = reactNative.StyleSheet.create({
1099
+ wrap: {
1100
+ marginBottom: 4
1101
+ },
1102
+ bubble: {},
1103
+ actions: {
1104
+ flexDirection: "row",
1105
+ justifyContent: "flex-end",
1106
+ gap: 12,
1107
+ marginTop: 6
1108
+ },
1109
+ btn: {
1110
+ fontSize: 13
1111
+ }
1112
+ });
1113
+ function MessageActions({
1114
+ visible,
1115
+ onClose,
1116
+ isSelf,
1117
+ onReply,
1118
+ onReplyInThread,
1119
+ onQuote,
1120
+ onEdit,
1121
+ onDelete,
1122
+ onCopy,
1123
+ onReact
1124
+ }) {
1125
+ const theme = usePoolseTheme();
1126
+ const items = [];
1127
+ if (onReact) items.push({ label: "React", icon: "emoji", onPress: onReact });
1128
+ if (onReply) items.push({ label: "Reply", icon: "reply", onPress: onReply });
1129
+ if (onQuote) items.push({ label: "Quote reply", icon: "reply", onPress: onQuote });
1130
+ if (onReplyInThread)
1131
+ items.push({ label: "Reply in thread", icon: "messages", onPress: onReplyInThread });
1132
+ if (onCopy) items.push({ label: "Copy text", icon: "copy", onPress: onCopy });
1133
+ if (isSelf && onEdit) items.push({ label: "Edit", icon: "edit", onPress: onEdit });
1134
+ if (isSelf && onDelete)
1135
+ items.push({ label: "Delete", icon: "trash", onPress: onDelete, danger: true });
1136
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { visible, transparent: true, animationType: "fade", onRequestClose: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles8.backdrop, onPress: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
1137
+ reactNative.View,
1138
+ {
1139
+ style: [
1140
+ styles8.sheet,
1141
+ {
1142
+ backgroundColor: theme.colors.surface,
1143
+ borderTopLeftRadius: theme.radii.lg,
1144
+ borderTopRightRadius: theme.radii.lg
1145
+ },
1146
+ theme.shadows.lg
1147
+ ],
1148
+ children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress: item.onPress, style: styles8.item, children: [
1149
+ /* @__PURE__ */ jsxRuntime.jsx(
1150
+ PoolseIcon,
1151
+ {
1152
+ name: item.icon,
1153
+ size: 18,
1154
+ color: item.danger ? theme.colors.error : theme.colors.ink2
1155
+ }
1156
+ ),
1157
+ /* @__PURE__ */ jsxRuntime.jsx(
1158
+ reactNative.Text,
1159
+ {
1160
+ style: [
1161
+ styles8.itemText,
1162
+ {
1163
+ color: item.danger ? theme.colors.error : theme.colors.ink,
1164
+ fontFamily: theme.type.fontBody
1165
+ }
1166
+ ],
1167
+ children: item.label
1168
+ }
1169
+ )
1170
+ ] }, item.label))
1171
+ }
1172
+ ) }) });
1173
+ }
1174
+ var styles8 = reactNative.StyleSheet.create({
1175
+ backdrop: {
1176
+ flex: 1,
1177
+ backgroundColor: "rgba(0,0,0,0.35)",
1178
+ justifyContent: "flex-end"
1179
+ },
1180
+ sheet: {
1181
+ paddingVertical: 8,
1182
+ paddingBottom: 24
1183
+ },
1184
+ item: {
1185
+ flexDirection: "row",
1186
+ alignItems: "center",
1187
+ paddingHorizontal: 20,
1188
+ paddingVertical: 14,
1189
+ gap: 14
1190
+ },
1191
+ itemText: {
1192
+ fontSize: 15,
1193
+ fontWeight: "500"
1194
+ }
1195
+ });
1196
+ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
1197
+ const theme = usePoolseTheme();
1198
+ const isImage = (attachment.content_type ?? "").startsWith("image/");
1199
+ const { url } = react$1.useAttachmentUrl(attachment.id);
1200
+ if (isImage) {
1201
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress, disabled: !onPress, children: url ? /* @__PURE__ */ jsxRuntime.jsx(
1202
+ reactNative.Image,
1203
+ {
1204
+ source: { uri: url },
1205
+ style: {
1206
+ width: maxWidth,
1207
+ height: 200,
1208
+ borderRadius: theme.radii.md,
1209
+ backgroundColor: theme.colors.surface2
1210
+ },
1211
+ resizeMode: "cover",
1212
+ accessible: true,
1213
+ accessibilityLabel: attachment.original_filename ?? "attachment image"
1214
+ }
1215
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1216
+ reactNative.View,
1217
+ {
1218
+ style: [
1219
+ styles9.imagePlaceholder,
1220
+ {
1221
+ width: maxWidth,
1222
+ height: 200,
1223
+ borderRadius: theme.radii.md,
1224
+ backgroundColor: theme.colors.surface2
1225
+ }
1226
+ ]
1227
+ }
1228
+ ) });
1229
+ }
1230
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1231
+ reactNative.Pressable,
1232
+ {
1233
+ onPress,
1234
+ disabled: !onPress,
1235
+ style: [
1236
+ styles9.fileCard,
1237
+ {
1238
+ backgroundColor: theme.colors.surface2,
1239
+ borderColor: theme.colors.border,
1240
+ borderRadius: theme.radii.md
1241
+ }
1242
+ ],
1243
+ children: [
1244
+ /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.ink2 }),
1245
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, marginLeft: 8 }, children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsx(
1247
+ reactNative.Text,
1248
+ {
1249
+ style: [styles9.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1250
+ numberOfLines: 1,
1251
+ children: attachment.original_filename ?? "file"
1252
+ }
1253
+ ),
1254
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.fileSize, { color: theme.colors.ink3 }], children: formatBytes(attachment.byte_size) })
1255
+ ] })
1256
+ ]
1257
+ }
1258
+ );
1259
+ }
1260
+ function formatBytes(n) {
1261
+ if (n < 1024) return `${n} B`;
1262
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
1263
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
1264
+ return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
1265
+ }
1266
+ var styles9 = reactNative.StyleSheet.create({
1267
+ imagePlaceholder: {},
1268
+ fileCard: {
1269
+ flexDirection: "row",
1270
+ alignItems: "center",
1271
+ padding: 8,
1272
+ borderWidth: reactNative.StyleSheet.hairlineWidth
1273
+ },
1274
+ fileName: {
1275
+ fontSize: 13,
1276
+ fontWeight: "500"
1277
+ },
1278
+ fileSize: {
1279
+ fontSize: 11
1280
+ }
1281
+ });
1282
+ function MessageBubble({
1283
+ message,
1284
+ currentUserId,
1285
+ readState,
1286
+ labelFor,
1287
+ onQuotedClick,
1288
+ groupPosition = "standalone",
1289
+ maxBodyLength = 0,
1290
+ showSenderName = false,
1291
+ actionsTrigger,
1292
+ showAttachments = true
1293
+ }) {
1294
+ const theme = usePoolseTheme();
1295
+ const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1296
+ const [expanded, setExpanded] = react.useState(false);
1297
+ const sender = react$1.useUser(!isSelf ? message.sender_external_id : null);
1298
+ const senderFallbackName = message.sender_external_id ?? "Unknown";
1299
+ const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
1300
+ const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
1301
+ const tailCorner = groupPosition === "last" || groupPosition === "standalone";
1302
+ const bubbleColor = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1303
+ const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1304
+ const radii2 = {
1305
+ topLeft: theme.radii.bubble,
1306
+ topRight: theme.radii.bubble,
1307
+ bottomLeft: theme.radii.bubble,
1308
+ bottomRight: theme.radii.bubble
1309
+ };
1310
+ if (tailCorner) {
1311
+ if (isSelf) radii2.bottomRight = 4;
1312
+ else radii2.bottomLeft = 4;
1313
+ }
1314
+ const body = message.body ?? "";
1315
+ const truncated = maxBodyLength > 0 && body.length > maxBodyLength && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
1316
+ const time = formatTime(message.inserted_at);
1317
+ const isDeleted = !!message.deleted_at;
1318
+ const isEdited = !!message.edited_at && !isDeleted;
1319
+ const images = (message.attachments ?? []).filter((a) => isImageContentType(a.content_type));
1320
+ const files = (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type));
1321
+ const renderAttachments = showAttachments && (images.length > 0 || files.length > 0);
1322
+ return /* @__PURE__ */ jsxRuntime.jsx(
1323
+ reactNative.View,
1324
+ {
1325
+ style: [
1326
+ styles10.wrap,
1327
+ {
1328
+ alignSelf: isSelf ? "flex-end" : "flex-start",
1329
+ maxWidth: "80%"
1330
+ }
1331
+ ],
1332
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1333
+ reactNative.View,
1334
+ {
1335
+ style: [
1336
+ styles10.bubble,
1337
+ {
1338
+ backgroundColor: bubbleColor,
1339
+ borderColor: isSelf ? "transparent" : theme.colors.border,
1340
+ borderWidth: isSelf ? 0 : reactNative.StyleSheet.hairlineWidth,
1341
+ borderTopLeftRadius: radii2.topLeft,
1342
+ borderTopRightRadius: radii2.topRight,
1343
+ borderBottomLeftRadius: radii2.bottomLeft,
1344
+ borderBottomRightRadius: radii2.bottomRight,
1345
+ paddingHorizontal: theme.spacing.md,
1346
+ paddingVertical: theme.spacing.sm
1347
+ },
1348
+ theme.shadows.sm
1349
+ ],
1350
+ children: [
1351
+ showSenderName && !isSelf && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.senderName, { color: senderTint, fontFamily: theme.type.fontBody }], children: senderLabel }) : null,
1352
+ message.quoted_message ? /* @__PURE__ */ jsxRuntime.jsx(
1353
+ QuotedCard,
1354
+ {
1355
+ quoted: message.quoted_message,
1356
+ isSelf,
1357
+ ...labelFor ? { labelFor } : {},
1358
+ ...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
1359
+ }
1360
+ ) : null,
1361
+ renderAttachments && images.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: images.map((att) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment: att }, att.id)) }) : null,
1362
+ renderAttachments && files.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment: att }, att.id)) }) : null,
1363
+ body.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1364
+ reactNative.Text,
1365
+ {
1366
+ style: [
1367
+ styles10.body,
1368
+ {
1369
+ color: isDeleted ? theme.colors.ink3 : textColor,
1370
+ fontFamily: theme.type.fontBody,
1371
+ fontSize: theme.type.bodySize,
1372
+ lineHeight: theme.type.lineHeight,
1373
+ fontStyle: isDeleted ? "italic" : "normal"
1374
+ }
1375
+ ],
1376
+ children: isDeleted ? "Message deleted" : truncated
1377
+ }
1378
+ ) : null,
1379
+ maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
1380
+ reactNative.Text,
1381
+ {
1382
+ style: [
1383
+ styles10.readMore,
1384
+ { color: isSelf ? theme.colors.onBrand : theme.colors.brand }
1385
+ ],
1386
+ children: expanded ? "Show less" : "Read more"
1387
+ }
1388
+ ) }) : null,
1389
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles10.metaRow, children: [
1390
+ isEdited ? /* @__PURE__ */ jsxRuntime.jsxs(
1391
+ reactNative.Text,
1392
+ {
1393
+ style: [styles10.meta, { color: isSelf ? theme.colors.onBrand : theme.colors.ink3 }],
1394
+ children: [
1395
+ "edited \xB7",
1396
+ " "
1397
+ ]
1398
+ }
1399
+ ) : null,
1400
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.meta, { color: isSelf ? theme.colors.onBrand : theme.colors.ink3 }], children: time }),
1401
+ isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1402
+ PoolseIcon,
1403
+ {
1404
+ name: readState === "read" ? "check-double" : "check",
1405
+ size: 12,
1406
+ color: theme.colors.onBrand
1407
+ }
1408
+ ) }) : null
1409
+ ] }),
1410
+ actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles10.actionsSlot, children: actionsTrigger }) : null
1411
+ ]
1412
+ }
1413
+ )
1414
+ }
1415
+ );
1416
+ }
1417
+ function QuotedCard({
1418
+ quoted,
1419
+ isSelf,
1420
+ labelFor,
1421
+ onPress
1422
+ }) {
1423
+ const theme = usePoolseTheme();
1424
+ const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? quoted.sender_external_id : "Unknown";
1425
+ const bg = isSelf ? "rgba(255,255,255,0.18)" : theme.colors.surface2;
1426
+ const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
1427
+ const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
1428
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1429
+ reactNative.Pressable,
1430
+ {
1431
+ onPress,
1432
+ disabled: !onPress,
1433
+ style: [
1434
+ styles10.quoted,
1435
+ {
1436
+ backgroundColor: bg,
1437
+ borderLeftColor: accent
1438
+ }
1439
+ ],
1440
+ children: [
1441
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.quotedSender, { color: accent }], children: senderName }),
1442
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.body ?? "\u2026" })
1443
+ ]
1444
+ }
1445
+ );
1446
+ }
1447
+ function isImageContentType(ct) {
1448
+ if (!ct) return false;
1449
+ return ct.startsWith("image/");
1450
+ }
1451
+ function formatTime(iso) {
1452
+ try {
1453
+ const d = new Date(iso);
1454
+ return d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
1455
+ } catch {
1456
+ return "";
1457
+ }
1458
+ }
1459
+ var styles10 = reactNative.StyleSheet.create({
1460
+ wrap: {
1461
+ marginBottom: 4
1462
+ },
1463
+ bubble: {
1464
+ position: "relative"
1465
+ },
1466
+ senderName: {
1467
+ fontSize: 12,
1468
+ fontWeight: "600",
1469
+ marginBottom: 2
1470
+ },
1471
+ body: {
1472
+ flexShrink: 1
1473
+ },
1474
+ readMore: {
1475
+ fontSize: 12,
1476
+ fontWeight: "600",
1477
+ marginTop: 4
1478
+ },
1479
+ metaRow: {
1480
+ flexDirection: "row",
1481
+ alignItems: "center",
1482
+ justifyContent: "flex-end",
1483
+ marginTop: 4
1484
+ },
1485
+ meta: {
1486
+ fontSize: 10,
1487
+ opacity: 0.7
1488
+ },
1489
+ actionsSlot: {
1490
+ position: "absolute",
1491
+ top: 4,
1492
+ right: 4
1493
+ },
1494
+ quoted: {
1495
+ borderLeftWidth: 3,
1496
+ paddingLeft: 8,
1497
+ paddingVertical: 4,
1498
+ paddingRight: 6,
1499
+ marginBottom: 6,
1500
+ borderRadius: 4
1501
+ },
1502
+ quotedSender: {
1503
+ fontSize: 12,
1504
+ fontWeight: "600",
1505
+ marginBottom: 2
1506
+ },
1507
+ quotedBody: {
1508
+ fontSize: 12,
1509
+ opacity: 0.85
1510
+ }
1511
+ });
1512
+ function MessageRow({
1513
+ msg,
1514
+ meId,
1515
+ reactions = true,
1516
+ attachments = true,
1517
+ actions = true,
1518
+ threads = true,
1519
+ quotations = true,
1520
+ readState,
1521
+ editing = false,
1522
+ onStartEdit,
1523
+ onCancelEdit,
1524
+ onSaveEdit,
1525
+ onDelete,
1526
+ onOpenThread,
1527
+ onQuote,
1528
+ labelFor,
1529
+ onQuotedClick,
1530
+ groupPosition = "standalone",
1531
+ maxBodyLength = 0,
1532
+ showSenderName = false,
1533
+ showAvatar = false,
1534
+ avatarFor
1535
+ }) {
1536
+ const theme = usePoolseTheme();
1537
+ const isSelf = meId !== null && msg.sender_id === meId;
1538
+ const [actionsOpen, setActionsOpen] = react.useState(false);
1539
+ const showAvatarSlot = showAvatar && !isSelf;
1540
+ const showAvatarRender = showAvatarSlot && (groupPosition === "last" || groupPosition === "standalone");
1541
+ const avatarUrl = avatarFor && msg.sender_external_id ? avatarFor(msg.sender_external_id) : null;
1542
+ const senderLabel = msg.sender_external_id && labelFor ? labelFor(msg.sender_external_id) : msg.sender_external_id ?? null;
1543
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles11.row, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: [
1544
+ showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles11.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
1545
+ Avatar,
1546
+ {
1547
+ src: avatarUrl ?? null,
1548
+ name: senderLabel,
1549
+ seed: msg.sender_external_id ?? null,
1550
+ size: "sm"
1551
+ }
1552
+ ) : null }) : null,
1553
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.bubbleCol, children: [
1554
+ /* @__PURE__ */ jsxRuntime.jsx(
1555
+ reactNative.Pressable,
1556
+ {
1557
+ onLongPress: () => actions && setActionsOpen(true),
1558
+ accessibilityRole: "button",
1559
+ accessibilityLabel: "Message actions",
1560
+ children: editing ? /* @__PURE__ */ jsxRuntime.jsx(
1561
+ EditableMessageBubble,
1562
+ {
1563
+ message: msg,
1564
+ currentUserId: meId,
1565
+ onCancel: onCancelEdit ?? (() => void 0),
1566
+ onSave: onSaveEdit ?? (() => void 0)
1567
+ }
1568
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1569
+ MessageBubble,
1570
+ {
1571
+ message: msg,
1572
+ currentUserId: meId,
1573
+ ...readState ? { readState } : {},
1574
+ ...labelFor ? { labelFor } : {},
1575
+ ...onQuotedClick ? { onQuotedClick } : {},
1576
+ groupPosition,
1577
+ maxBodyLength,
1578
+ showSenderName,
1579
+ showAttachments: attachments,
1580
+ actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
1581
+ PoolseIcon,
1582
+ {
1583
+ name: "chevron-down",
1584
+ size: 14,
1585
+ color: isSelf ? theme.colors.onBrand : theme.colors.ink3
1586
+ }
1587
+ ) }) : null
1588
+ }
1589
+ )
1590
+ }
1591
+ ),
1592
+ threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onOpenThread, style: styles11.threadPill, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles11.threadPillText, { color: theme.colors.brand }], children: [
1593
+ "\u{1F4AC} ",
1594
+ msg.reply_count,
1595
+ " ",
1596
+ msg.reply_count === 1 ? "reply" : "replies"
1597
+ ] }) }) : null,
1598
+ reactions && msg.reactions && Object.keys(msg.reactions).length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles11.reactionsRow, children: Object.entries(msg.reactions).map(([emoji, userIds]) => /* @__PURE__ */ jsxRuntime.jsxs(
1599
+ reactNative.View,
1600
+ {
1601
+ style: [
1602
+ styles11.reactionChip,
1603
+ {
1604
+ backgroundColor: theme.colors.surface2,
1605
+ borderColor: theme.colors.border
1606
+ }
1607
+ ],
1608
+ children: [
1609
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles11.reactionEmoji, children: emoji }),
1610
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.reactionCount, { color: theme.colors.ink2 }], children: Array.isArray(userIds) ? userIds.length : 0 })
1611
+ ]
1612
+ },
1613
+ emoji
1614
+ )) }) : null
1615
+ ] }),
1616
+ actions && actionsOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1617
+ MessageActions,
1618
+ {
1619
+ visible: actionsOpen,
1620
+ onClose: () => setActionsOpen(false),
1621
+ isSelf,
1622
+ ...onStartEdit ? {
1623
+ onEdit: () => {
1624
+ setActionsOpen(false);
1625
+ onStartEdit();
1626
+ }
1627
+ } : {},
1628
+ ...onDelete ? {
1629
+ onDelete: () => {
1630
+ setActionsOpen(false);
1631
+ onDelete();
1632
+ }
1633
+ } : {},
1634
+ ...onOpenThread ? {
1635
+ onReplyInThread: () => {
1636
+ setActionsOpen(false);
1637
+ onOpenThread();
1638
+ }
1639
+ } : {},
1640
+ ...quotations && onQuote ? {
1641
+ onQuote: () => {
1642
+ setActionsOpen(false);
1643
+ onQuote();
1644
+ }
1645
+ } : {}
1646
+ }
1647
+ ) : null
1648
+ ] });
1649
+ }
1650
+ var styles11 = reactNative.StyleSheet.create({
1651
+ row: {
1652
+ flexDirection: "row",
1653
+ alignItems: "flex-end",
1654
+ marginVertical: 2,
1655
+ paddingHorizontal: 8,
1656
+ maxWidth: "95%"
1657
+ },
1658
+ avatarSlot: {
1659
+ width: 36,
1660
+ marginRight: 6
1661
+ },
1662
+ bubbleCol: {
1663
+ flexShrink: 1
1664
+ },
1665
+ threadPill: {
1666
+ alignSelf: "flex-start",
1667
+ marginTop: 2,
1668
+ paddingHorizontal: 8,
1669
+ paddingVertical: 3
1670
+ },
1671
+ threadPillText: {
1672
+ fontSize: 11,
1673
+ fontWeight: "600"
1674
+ },
1675
+ reactionsRow: {
1676
+ flexDirection: "row",
1677
+ flexWrap: "wrap",
1678
+ marginTop: 4,
1679
+ gap: 4
1680
+ },
1681
+ reactionChip: {
1682
+ flexDirection: "row",
1683
+ alignItems: "center",
1684
+ paddingHorizontal: 6,
1685
+ paddingVertical: 2,
1686
+ borderRadius: 12,
1687
+ borderWidth: reactNative.StyleSheet.hairlineWidth
1688
+ },
1689
+ reactionEmoji: {
1690
+ fontSize: 13,
1691
+ marginRight: 3
1692
+ },
1693
+ reactionCount: {
1694
+ fontSize: 11,
1695
+ fontWeight: "600"
1696
+ }
1697
+ });
1698
+ function ThreadView({
1699
+ visible,
1700
+ onClose,
1701
+ conversationId,
1702
+ rootMessage,
1703
+ labelFor
1704
+ }) {
1705
+ const theme = usePoolseTheme();
1706
+ const { me } = react$1.useMe();
1707
+ const meId = me?.id ?? null;
1708
+ const { replies, sendReply, loadMore, hasMore } = react$1.useThread(conversationId, rootMessage.id);
1709
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { visible, animationType: "slide", onRequestClose: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles12.root, { backgroundColor: theme.colors.paper }], children: [
1710
+ /* @__PURE__ */ jsxRuntime.jsxs(
1711
+ reactNative.View,
1712
+ {
1713
+ style: [
1714
+ styles12.header,
1715
+ {
1716
+ borderBottomColor: theme.colors.border,
1717
+ backgroundColor: theme.colors.surface
1718
+ }
1719
+ ],
1720
+ children: [
1721
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onClose, hitSlop: 8, style: styles12.closeBtn, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 18, color: theme.colors.ink }) }),
1722
+ /* @__PURE__ */ jsxRuntime.jsx(
1723
+ reactNative.Text,
1724
+ {
1725
+ style: [styles12.title, { color: theme.colors.ink, fontFamily: theme.type.fontDisplay }],
1726
+ children: "Thread"
1727
+ }
1728
+ )
1729
+ ]
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles12.rootPreview, { backgroundColor: theme.colors.surface2 }], children: /* @__PURE__ */ jsxRuntime.jsx(
1733
+ MessageBubble,
1734
+ {
1735
+ message: rootMessage,
1736
+ currentUserId: meId,
1737
+ ...labelFor ? { labelFor } : {}
1738
+ }
1739
+ ) }),
1740
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1741
+ MessageList,
1742
+ {
1743
+ messages: replies,
1744
+ hasMore,
1745
+ onLoadMore: loadMore,
1746
+ renderItem: (msg) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { paddingHorizontal: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1747
+ MessageBubble,
1748
+ {
1749
+ message: msg,
1750
+ currentUserId: meId,
1751
+ ...labelFor ? { labelFor } : {}
1752
+ }
1753
+ ) }, msg.id)
1754
+ }
1755
+ ) }),
1756
+ /* @__PURE__ */ jsxRuntime.jsx(
1757
+ MessageComposer,
1758
+ {
1759
+ onSend: async (body, opts) => {
1760
+ await sendReply({ body, ...opts });
1761
+ },
1762
+ placeholder: "Reply to thread\u2026"
1763
+ }
1764
+ )
1765
+ ] }) });
1766
+ }
1767
+ var styles12 = reactNative.StyleSheet.create({
1768
+ root: {
1769
+ flex: 1
1770
+ },
1771
+ header: {
1772
+ flexDirection: "row",
1773
+ alignItems: "center",
1774
+ paddingHorizontal: 12,
1775
+ paddingVertical: 12,
1776
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
1777
+ gap: 8
1778
+ },
1779
+ closeBtn: {
1780
+ padding: 6
1781
+ },
1782
+ title: {
1783
+ fontSize: 16,
1784
+ fontWeight: "600"
1785
+ },
1786
+ rootPreview: {
1787
+ paddingVertical: 8,
1788
+ paddingHorizontal: 8
1789
+ }
1790
+ });
1791
+ function UserName({ externalId, fallback, labelFor }) {
1792
+ const theme = usePoolseTheme();
1793
+ const name = useDisplayName(externalId, labelFor) ?? fallback ?? externalId ?? "";
1794
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.ink, fontFamily: theme.type.fontBody }, children: name });
1795
+ }
1796
+ function useDisplayName(externalId, labelFor) {
1797
+ const resolved = react$1.useUser(externalId ?? null);
1798
+ if (!externalId) return "";
1799
+ if (labelFor) {
1800
+ const override = labelFor(externalId);
1801
+ if (override) return override;
1802
+ }
1803
+ return resolved.profile?.displayName ?? externalId;
1804
+ }
1805
+ function TypingIndicator({ typing, labelFor }) {
1806
+ const ids = Array.from(typing);
1807
+ const isTyping = ids.length > 0;
1808
+ if (!isTyping) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 0 } });
1809
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles13.row, children: [
1810
+ /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}),
1811
+ /* @__PURE__ */ jsxRuntime.jsx(TypingLabel, { ids, ...labelFor ? { labelFor } : {} })
1812
+ ] });
1813
+ }
1814
+ function TypingDots() {
1815
+ const theme = usePoolseTheme();
1816
+ const a = react.useRef(new reactNative.Animated.Value(0)).current;
1817
+ const b = react.useRef(new reactNative.Animated.Value(0)).current;
1818
+ const c = react.useRef(new reactNative.Animated.Value(0)).current;
1819
+ react.useEffect(() => {
1820
+ const loop = (val, delay) => reactNative.Animated.loop(
1821
+ reactNative.Animated.sequence([
1822
+ reactNative.Animated.delay(delay),
1823
+ reactNative.Animated.timing(val, {
1824
+ toValue: 1,
1825
+ duration: 400,
1826
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
1827
+ useNativeDriver: true
1828
+ }),
1829
+ reactNative.Animated.timing(val, {
1830
+ toValue: 0,
1831
+ duration: 400,
1832
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
1833
+ useNativeDriver: true
1834
+ })
1835
+ ])
1836
+ );
1837
+ const animations = [loop(a, 0), loop(b, 150), loop(c, 300)];
1838
+ animations.forEach((anim) => anim.start());
1839
+ return () => animations.forEach((anim) => anim.stop());
1840
+ }, [a, b, c]);
1841
+ const dot = (val) => ({
1842
+ width: 6,
1843
+ height: 6,
1844
+ borderRadius: 3,
1845
+ backgroundColor: theme.colors.ink3,
1846
+ marginHorizontal: 2,
1847
+ transform: [
1848
+ {
1849
+ translateY: val.interpolate({ inputRange: [0, 1], outputRange: [0, -3] })
1850
+ }
1851
+ ],
1852
+ opacity: val.interpolate({ inputRange: [0, 1], outputRange: [0.5, 1] })
1853
+ });
1854
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles13.dotsWrap, children: [
1855
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: dot(a) }),
1856
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: dot(b) }),
1857
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: dot(c) })
1858
+ ] });
1859
+ }
1860
+ function TypingLabel({
1861
+ ids,
1862
+ labelFor
1863
+ }) {
1864
+ const theme = usePoolseTheme();
1865
+ const id1 = ids[0] ?? null;
1866
+ const id2 = ids[1] ?? null;
1867
+ const name1 = useDisplayName(id1, labelFor);
1868
+ const name2 = useDisplayName(id2, labelFor);
1869
+ const text = ids.length === 1 ? `${name1} is typing` : ids.length === 2 ? `${name1} and ${name2} are typing` : `${ids.length} people are typing`;
1870
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles13.label, { color: theme.colors.ink2, fontFamily: theme.type.fontBody }], children: text });
1871
+ }
1872
+ var styles13 = reactNative.StyleSheet.create({
1873
+ row: {
1874
+ flexDirection: "row",
1875
+ alignItems: "center",
1876
+ paddingHorizontal: 12,
1877
+ paddingVertical: 6
1878
+ },
1879
+ dotsWrap: {
1880
+ flexDirection: "row",
1881
+ alignItems: "flex-end",
1882
+ marginRight: 8,
1883
+ height: 12
1884
+ },
1885
+ label: {
1886
+ fontSize: 12
1887
+ }
1888
+ });
1889
+ function ConversationView({
1890
+ conversationId,
1891
+ emptyState,
1892
+ renderMessage,
1893
+ labelFor,
1894
+ avatarFor,
1895
+ reactions = true,
1896
+ mentions = true,
1897
+ attachments = true,
1898
+ actions = true,
1899
+ threads = true,
1900
+ quotations = true,
1901
+ readReceipts = true,
1902
+ maxBodyLength = 200,
1903
+ senderLabels = "auto",
1904
+ avatars = "auto",
1905
+ onMarkedRead
1906
+ }) {
1907
+ const theme = usePoolseTheme();
1908
+ const { me } = react$1.useMe();
1909
+ const meId = me?.id ?? null;
1910
+ const {
1911
+ messages,
1912
+ loading,
1913
+ hasMore,
1914
+ loadMore,
1915
+ send,
1916
+ edit,
1917
+ delete: deleteMsg
1918
+ } = react$1.useMessages(conversationId);
1919
+ const { typing, signalTyping } = react$1.useTyping(conversationId);
1920
+ const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
1921
+ const { members } = react$1.useMembers(wantsMembers ? conversationId : "");
1922
+ const isGroupChat = members.length > 2;
1923
+ const showSenderLabels = senderLabels === "always" || senderLabels === "auto" && isGroupChat;
1924
+ const showAvatars = avatars === "always" || avatars === "auto" && isGroupChat;
1925
+ const [replyingTo, setReplyingTo] = react.useState(null);
1926
+ const [editingId, setEditingId] = react.useState(null);
1927
+ const [threadRoot, setThreadRoot] = react.useState(null);
1928
+ const [pickerOpen, setPickerOpen] = react.useState(false);
1929
+ const renderItem = react.useCallback(
1930
+ (msg) => {
1931
+ if (renderMessage) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMessage(msg, meId) });
1932
+ const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId) : void 0;
1933
+ return /* @__PURE__ */ jsxRuntime.jsx(
1934
+ MessageRow,
1935
+ {
1936
+ msg,
1937
+ meId,
1938
+ reactions,
1939
+ attachments,
1940
+ actions,
1941
+ threads,
1942
+ quotations,
1943
+ ...readState ? { readState } : {},
1944
+ editing: editingId === msg.id,
1945
+ onStartEdit: () => setEditingId(msg.id),
1946
+ onCancelEdit: () => setEditingId(null),
1947
+ onSaveEdit: async (body) => {
1948
+ await edit(msg.id, body);
1949
+ setEditingId(null);
1950
+ },
1951
+ onDelete: () => deleteMsg(msg.id),
1952
+ ...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
1953
+ ...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
1954
+ ...labelFor ? { labelFor } : {},
1955
+ ...avatarFor ? { avatarFor } : {},
1956
+ showSenderName: showSenderLabels,
1957
+ showAvatar: showAvatars,
1958
+ maxBodyLength
1959
+ }
1960
+ );
1961
+ },
1962
+ [
1963
+ renderMessage,
1964
+ meId,
1965
+ readReceipts,
1966
+ members,
1967
+ reactions,
1968
+ attachments,
1969
+ actions,
1970
+ threads,
1971
+ quotations,
1972
+ editingId,
1973
+ edit,
1974
+ deleteMsg,
1975
+ labelFor,
1976
+ avatarFor,
1977
+ showSenderLabels,
1978
+ showAvatars,
1979
+ maxBodyLength
1980
+ ]
1981
+ );
1982
+ const composerProps = react.useMemo(
1983
+ () => ({
1984
+ onSend: async (body, opts) => {
1985
+ await send({ body, ...opts });
1986
+ setReplyingTo(null);
1987
+ },
1988
+ onTyping: signalTyping,
1989
+ ...attachments ? { onAttachPress: () => setPickerOpen(true) } : {},
1990
+ ...replyingTo ? { replyingTo } : {},
1991
+ onCancelReply: () => setReplyingTo(null),
1992
+ ...labelFor ? { labelFor } : {},
1993
+ attachments
1994
+ }),
1995
+ [send, signalTyping, attachments, replyingTo, labelFor]
1996
+ );
1997
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles14.root, { backgroundColor: theme.colors.paper }], children: [
1998
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles14.listWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
1999
+ MessageList,
2000
+ {
2001
+ messages,
2002
+ loading,
2003
+ hasMore,
2004
+ onLoadMore: loadMore,
2005
+ renderItem,
2006
+ ListHeader: /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
2007
+ ...emptyState !== void 0 ? { emptyState } : {}
2008
+ }
2009
+ ) }),
2010
+ mentions ? /* @__PURE__ */ jsxRuntime.jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsxRuntime.jsx(MessageComposer, { ...composerProps }),
2011
+ attachments ? /* @__PURE__ */ jsxRuntime.jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
2012
+ threads && threadRoot ? /* @__PURE__ */ jsxRuntime.jsx(
2013
+ ThreadView,
2014
+ {
2015
+ visible: true,
2016
+ onClose: () => setThreadRoot(null),
2017
+ conversationId,
2018
+ rootMessage: threadRoot,
2019
+ ...labelFor ? { labelFor } : {}
2020
+ }
2021
+ ) : null
2022
+ ] });
2023
+ }
2024
+ function readStateForMessage(msg, members, meId) {
2025
+ const otherMembers = members.filter((m) => m.user_id !== meId);
2026
+ if (otherMembers.length === 0) return "sent";
2027
+ const anyRead = otherMembers.some(
2028
+ (m) => m.last_read_message_id !== null && msg.id !== null && m.last_read_message_id >= msg.id
2029
+ );
2030
+ return anyRead ? "read" : "sent";
2031
+ }
2032
+ var styles14 = reactNative.StyleSheet.create({
2033
+ root: {
2034
+ flex: 1
2035
+ },
2036
+ listWrap: {
2037
+ flex: 1
2038
+ }
2039
+ });
2040
+ function ConversationList({
2041
+ selectedId,
2042
+ onSelect,
2043
+ renderItem,
2044
+ emptyState,
2045
+ conversations: controlled,
2046
+ loading: controlledLoading,
2047
+ error: controlledError,
2048
+ unreadCounts
2049
+ }) {
2050
+ const theme = usePoolseTheme();
2051
+ const auto = react$1.useConversations();
2052
+ const isControlled = controlled !== void 0;
2053
+ const conversations = isControlled ? controlled : auto.conversations;
2054
+ const loading = isControlled ? controlledLoading ?? false : auto.loading;
2055
+ const error = isControlled ? controlledError ?? null : auto.error;
2056
+ const counts = unreadCounts ?? (isControlled ? void 0 : auto.unreadCounts);
2057
+ if (loading && conversations.length === 0) {
2058
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
2059
+ }
2060
+ if (error) {
2061
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.error, fontFamily: theme.type.fontBody }, children: "Failed to load conversations." }) });
2062
+ }
2063
+ if (conversations.length === 0) {
2064
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" || emptyState == null ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState ?? "No conversations yet." }) : emptyState });
2065
+ }
2066
+ return /* @__PURE__ */ jsxRuntime.jsx(
2067
+ reactNative.FlatList,
2068
+ {
2069
+ data: conversations,
2070
+ keyExtractor: (c) => c.id,
2071
+ style: { backgroundColor: theme.colors.paper },
2072
+ renderItem: ({ item }) => {
2073
+ const selected = selectedId === item.id;
2074
+ if (renderItem) {
2075
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderItem(item, selected) });
2076
+ }
2077
+ const unread = counts?.[item.id] ?? 0;
2078
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2079
+ reactNative.Pressable,
2080
+ {
2081
+ onPress: () => onSelect?.(item),
2082
+ style: [
2083
+ styles15.row,
2084
+ {
2085
+ backgroundColor: selected ? theme.colors.surface2 : "transparent"
2086
+ }
2087
+ ],
2088
+ children: [
2089
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: null, name: item.name ?? null, seed: item.id, size: "md" }),
2090
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles15.body, children: [
2091
+ /* @__PURE__ */ jsxRuntime.jsx(
2092
+ reactNative.Text,
2093
+ {
2094
+ style: [styles15.name, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
2095
+ numberOfLines: 1,
2096
+ children: item.name ?? "Untitled conversation"
2097
+ }
2098
+ ),
2099
+ item.last_message_at ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.preview, { color: theme.colors.ink3 }], numberOfLines: 1, children: formatRelative(item.last_message_at) }) : null
2100
+ ] }),
2101
+ unread > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.unreadPill, { backgroundColor: theme.colors.unreadPill }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.unreadText, { color: theme.colors.unreadPillText }], children: unread > 99 ? "99+" : String(unread) }) }) : null
2102
+ ]
2103
+ }
2104
+ );
2105
+ }
2106
+ }
2107
+ );
2108
+ }
2109
+ function formatRelative(iso) {
2110
+ try {
2111
+ const d = new Date(iso);
2112
+ const now = Date.now();
2113
+ const diff = now - d.getTime();
2114
+ if (diff < 6e4) return "just now";
2115
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m`;
2116
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h`;
2117
+ return `${Math.floor(diff / 864e5)}d`;
2118
+ } catch {
2119
+ return "";
2120
+ }
2121
+ }
2122
+ var styles15 = reactNative.StyleSheet.create({
2123
+ placeholder: {
2124
+ flex: 1,
2125
+ alignItems: "center",
2126
+ justifyContent: "center",
2127
+ padding: 24
2128
+ },
2129
+ row: {
2130
+ flexDirection: "row",
2131
+ alignItems: "center",
2132
+ paddingHorizontal: 12,
2133
+ paddingVertical: 10,
2134
+ gap: 10
2135
+ },
2136
+ body: {
2137
+ flex: 1,
2138
+ gap: 2
2139
+ },
2140
+ name: {
2141
+ fontSize: 15,
2142
+ fontWeight: "600"
2143
+ },
2144
+ preview: {
2145
+ fontSize: 12
2146
+ },
2147
+ unreadPill: {
2148
+ minWidth: 22,
2149
+ height: 22,
2150
+ borderRadius: 11,
2151
+ paddingHorizontal: 6,
2152
+ alignItems: "center",
2153
+ justifyContent: "center"
2154
+ },
2155
+ unreadText: {
2156
+ fontSize: 11,
2157
+ fontWeight: "700"
2158
+ }
2159
+ });
2160
+ function MemberList({
2161
+ conversationId,
2162
+ labelFor,
2163
+ avatarFor,
2164
+ onlineExternalIds,
2165
+ canRemove,
2166
+ renderItem,
2167
+ emptyState
2168
+ }) {
2169
+ const theme = usePoolseTheme();
2170
+ const { members, loading, error, removeMember } = react$1.useMembers(conversationId);
2171
+ const { online: presenceOnline } = react$1.usePresence(
2172
+ onlineExternalIds === void 0 ? conversationId : ""
2173
+ );
2174
+ const effectiveOnline = onlineExternalIds ?? presenceOnline;
2175
+ if (loading && members.length === 0) {
2176
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
2177
+ }
2178
+ if (error) {
2179
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: [
2180
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.error, fontFamily: theme.type.fontBody }, children: "Failed to load members." }),
2181
+ error.message ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles16.errorDetail, { color: theme.colors.ink3 }], children: error.message }) : null
2182
+ ] });
2183
+ }
2184
+ if (members.length === 0) {
2185
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles16.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" || emptyState == null ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState ?? "No members." }) : emptyState });
2186
+ }
2187
+ return /* @__PURE__ */ jsxRuntime.jsx(
2188
+ reactNative.FlatList,
2189
+ {
2190
+ data: members,
2191
+ keyExtractor: (m) => m.user_id,
2192
+ style: { backgroundColor: theme.colors.paper },
2193
+ renderItem: ({ item }) => {
2194
+ if (renderItem) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderItem(item) });
2195
+ const name = labelFor?.(item.external_id) ?? item.external_id;
2196
+ const avatarUrl = avatarFor?.(item.external_id) ?? null;
2197
+ const isOnline = effectiveOnline.has(item.external_id);
2198
+ const showRemove = canRemove?.(item) ?? false;
2199
+ return /* @__PURE__ */ jsxRuntime.jsx(
2200
+ MemberRow,
2201
+ {
2202
+ name,
2203
+ externalId: item.external_id,
2204
+ avatarUrl,
2205
+ role: item.role,
2206
+ online: isOnline,
2207
+ ...showRemove ? { onRemove: () => removeMember(item.external_id) } : {}
2208
+ }
2209
+ );
2210
+ }
2211
+ }
2212
+ );
2213
+ }
2214
+ function MemberRow({
2215
+ name,
2216
+ externalId,
2217
+ avatarUrl,
2218
+ role,
2219
+ online,
2220
+ onRemove
2221
+ }) {
2222
+ const theme = usePoolseTheme();
2223
+ const displayName = useDisplayName(externalId) || name;
2224
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.row, children: [
2225
+ /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: avatarUrl, name: displayName, seed: externalId, online, size: "md" }),
2226
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles16.body, children: [
2227
+ /* @__PURE__ */ jsxRuntime.jsx(
2228
+ reactNative.Text,
2229
+ {
2230
+ style: [styles16.name, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
2231
+ numberOfLines: 1,
2232
+ children: displayName
2233
+ }
2234
+ ),
2235
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles16.role, { color: theme.colors.ink3 }], children: role })
2236
+ ] }),
2237
+ onRemove ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onRemove, hitSlop: 8, style: styles16.removeBtn, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
2238
+ ] });
2239
+ }
2240
+ var styles16 = reactNative.StyleSheet.create({
2241
+ placeholder: {
2242
+ flex: 1,
2243
+ alignItems: "center",
2244
+ justifyContent: "center",
2245
+ padding: 24
2246
+ },
2247
+ errorDetail: {
2248
+ marginTop: 6,
2249
+ fontSize: 11
2250
+ },
2251
+ row: {
2252
+ flexDirection: "row",
2253
+ alignItems: "center",
2254
+ paddingHorizontal: 12,
2255
+ paddingVertical: 8,
2256
+ gap: 10
2257
+ },
2258
+ body: {
2259
+ flex: 1,
2260
+ gap: 1
2261
+ },
2262
+ name: {
2263
+ fontSize: 14,
2264
+ fontWeight: "500"
2265
+ },
2266
+ role: {
2267
+ fontSize: 11,
2268
+ textTransform: "capitalize"
2269
+ },
2270
+ removeBtn: {
2271
+ padding: 6
2272
+ }
2273
+ });
2274
+ var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
2275
+ function ReactionStrip({
2276
+ messageId,
2277
+ conversationId,
2278
+ meId,
2279
+ initialReactions
2280
+ }) {
2281
+ const theme = usePoolseTheme();
2282
+ const { reactions, addReaction, removeReaction } = react$1.useReactions(messageId, {
2283
+ conversationId,
2284
+ currentUserId: meId,
2285
+ ...initialReactions ? { initialReactions } : {}
2286
+ });
2287
+ const entries = Object.entries(reactions);
2288
+ if (entries.length === 0) return null;
2289
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.strip, children: entries.map(([emoji, userIds]) => {
2290
+ const userArr = Array.isArray(userIds) ? userIds : [];
2291
+ const meReacted = meId !== null && userArr.includes(meId);
2292
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2293
+ reactNative.Pressable,
2294
+ {
2295
+ onPress: () => meReacted ? removeReaction(emoji) : addReaction(emoji),
2296
+ style: [
2297
+ styles17.chip,
2298
+ {
2299
+ backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
2300
+ borderColor: meReacted ? theme.colors.brand : theme.colors.border
2301
+ }
2302
+ ],
2303
+ children: [
2304
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.emoji, children: emoji }),
2305
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles17.count, { color: theme.colors.ink2 }], children: userArr.length })
2306
+ ]
2307
+ },
2308
+ emoji
2309
+ );
2310
+ }) });
2311
+ }
2312
+ function ReactionPicker({
2313
+ visible,
2314
+ onClose,
2315
+ onPick,
2316
+ emojis = DEFAULT_EMOJIS
2317
+ }) {
2318
+ const theme = usePoolseTheme();
2319
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { visible, transparent: true, animationType: "fade", onRequestClose: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles17.backdrop, onPress: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
2320
+ reactNative.View,
2321
+ {
2322
+ style: [
2323
+ styles17.sheet,
2324
+ {
2325
+ backgroundColor: theme.colors.surface,
2326
+ borderRadius: theme.radii.lg
2327
+ },
2328
+ theme.shadows.lg
2329
+ ],
2330
+ children: emojis.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
2331
+ reactNative.Pressable,
2332
+ {
2333
+ style: styles17.emojiBtn,
2334
+ onPress: () => {
2335
+ onPick(emoji);
2336
+ onClose();
2337
+ },
2338
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles17.bigEmoji, children: emoji })
2339
+ },
2340
+ emoji
2341
+ ))
2342
+ }
2343
+ ) }) });
2344
+ }
2345
+ var styles17 = reactNative.StyleSheet.create({
2346
+ strip: {
2347
+ flexDirection: "row",
2348
+ flexWrap: "wrap",
2349
+ gap: 4,
2350
+ marginTop: 4
2351
+ },
2352
+ chip: {
2353
+ flexDirection: "row",
2354
+ alignItems: "center",
2355
+ paddingHorizontal: 8,
2356
+ paddingVertical: 3,
2357
+ borderRadius: 12,
2358
+ borderWidth: reactNative.StyleSheet.hairlineWidth,
2359
+ gap: 4
2360
+ },
2361
+ emoji: {
2362
+ fontSize: 14
2363
+ },
2364
+ count: {
2365
+ fontSize: 11,
2366
+ fontWeight: "600"
2367
+ },
2368
+ backdrop: {
2369
+ flex: 1,
2370
+ backgroundColor: "rgba(0,0,0,0.35)",
2371
+ justifyContent: "center",
2372
+ alignItems: "center"
2373
+ },
2374
+ sheet: {
2375
+ flexDirection: "row",
2376
+ padding: 8,
2377
+ gap: 4
2378
+ },
2379
+ emojiBtn: {
2380
+ paddingHorizontal: 10,
2381
+ paddingVertical: 8
2382
+ },
2383
+ bigEmoji: {
2384
+ fontSize: 28
2385
+ }
2386
+ });
2387
+ var TILE_PATH = "M50 4 C20 4 4 20 4 50 C4 80 20 96 50 96 C80 96 96 80 96 50 C96 20 80 4 50 4 Z";
2388
+ var PULSE_PATH = "M16 55 H34 L40 55 L47 30 L54 73 L60 55 H72";
2389
+ var WORDMARK_P_PATH = "M62 -169V258V528H194L196 378L214 376Q223 432 246.0 469.0Q269 506 305.5 524.0Q342 542 389 542Q459 542 509.5 508.0Q560 474 587.0 411.0Q614 348 614 260Q614 182 590.5 120.0Q567 58 519.5 22.0Q472 -14 399 -14Q350 -14 316.0 3.5Q282 21 258.5 55.0Q235 89 219 140H200Q206 113 211.0 85.0Q216 57 219.5 31.0Q223 5 223 -19V-169ZM340 113Q373 113 396.5 131.0Q420 149 433.0 182.0Q446 215 446 259Q446 306 432.5 339.5Q419 373 394.0 391.5Q369 410 336 410Q305 410 283.5 396.5Q262 383 248.5 361.5Q235 340 229.0 316.0Q223 292 223 271V249Q223 230 228.0 211.0Q233 192 243.0 174.5Q253 157 267.0 143.0Q281 129 299.5 121.0Q318 113 340 113Z";
2390
+ var WORDMARK_O_PATH = "M305 -14Q225 -14 163.5 17.0Q102 48 67.0 110.0Q32 172 32 265Q32 358 67.0 419.5Q102 481 164.0 511.5Q226 542 306 542Q386 542 448.0 511.0Q510 480 545.0 418.5Q580 357 580 264Q580 169 544.0 107.0Q508 45 445.5 15.5Q383 -14 305 -14ZM310 103Q346 103 370.0 119.5Q394 136 406.0 170.0Q418 204 418 254Q418 307 405.0 343.5Q392 380 366.5 399.5Q341 419 301 419Q267 419 242.5 402.5Q218 386 206.0 352.0Q194 318 194 267Q194 185 224.5 144.0Q255 103 310 103Z";
2391
+ var WORDMARK_L_PATH = "M62 0V720H220V0Z";
2392
+ var WORDMARK_S_PATH = "M278 -14Q225 -14 182.0 -4.5Q139 5 107.0 23.0Q75 41 54.5 66.5Q34 92 26 123L146 176Q153 159 170.5 141.5Q188 124 217.5 113.0Q247 102 289 102Q329 102 351.5 113.5Q374 125 374 147Q374 163 361.5 172.5Q349 182 324.0 189.5Q299 197 262 204Q224 212 185.0 222.5Q146 233 112.0 251.5Q78 270 57.5 300.5Q37 331 37 378Q37 427 63.0 463.5Q89 500 140.5 521.0Q192 542 266 542Q332 542 382.0 525.0Q432 508 465.5 476.5Q499 445 512 401L383 355Q378 377 362.5 393.0Q347 409 323.5 417.5Q300 426 268 426Q229 426 207.5 414.0Q186 402 186 382Q186 366 200.5 355.5Q215 345 242.0 338.0Q269 331 306 323Q345 315 383.5 304.5Q422 294 453.5 276.5Q485 259 503.5 230.5Q522 202 522 157Q522 104 494.0 65.5Q466 27 411.5 6.5Q357 -14 278 -14Z";
2393
+ var WORDMARK_E_PATH = "M313 -14Q247 -14 195.0 3.5Q143 21 106.5 55.0Q70 89 51.0 139.0Q32 189 32 254Q32 317 50.5 370.0Q69 423 103.5 461.5Q138 500 188.5 521.0Q239 542 302 542Q366 542 414.5 521.5Q463 501 494.5 461.0Q526 421 541.0 363.5Q556 306 551 232L138 229V313L459 316L402 277Q409 327 396.0 358.5Q383 390 358.0 404.5Q333 419 304 419Q269 419 242.5 400.0Q216 381 201.0 345.0Q186 309 186 257Q186 175 221.5 138.0Q257 101 312 101Q339 101 357.0 108.5Q375 116 386.5 127.5Q398 139 404.5 153.0Q411 167 415 180L555 150Q547 113 529.5 83.0Q512 53 482.5 31.0Q453 9 411.5 -2.5Q370 -14 313 -14Z";
2394
+ var VARIANT_VB = {
2395
+ lockup: { vb: [0, 0, 454, 100], aspect: 4.54 },
2396
+ mark: { vb: [0, 0, 100, 100], aspect: 1 },
2397
+ wordmark: { vb: [0, 0, 350, 100], aspect: 3.5 },
2398
+ mono: { vb: [0, 0, 454, 100], aspect: 4.54 }
2399
+ };
2400
+ function PoolseLogo({ variant = "lockup", size = 28, color, label }) {
2401
+ const theme = usePoolseTheme();
2402
+ const mono = variant === "mono";
2403
+ const monoColor = color ?? theme.colors.ink;
2404
+ const tileFill = mono ? "none" : theme.colors.brand;
2405
+ const tileStroke = mono ? monoColor : "transparent";
2406
+ const tileStrokeWidth = mono ? 5 : 0;
2407
+ const pulseStroke = mono ? monoColor : theme.colors.onBrand;
2408
+ const pulseDotFill = mono ? monoColor : theme.colors.onBrand;
2409
+ const inkFill = mono ? monoColor : theme.colors.ink;
2410
+ const oFill = mono ? monoColor : theme.colors.brand;
2411
+ const { vb, aspect } = VARIANT_VB[variant];
2412
+ const width = size * aspect;
2413
+ const showMark = variant === "lockup" || variant === "mark" || variant === "mono";
2414
+ const showWordmark = variant === "lockup" || variant === "wordmark" || variant === "mono";
2415
+ const wordmarkOffsetX = variant === "wordmark" ? 0 : 126;
2416
+ const a11y = label === null ? {} : { accessibilityLabel: label ?? "poolse" };
2417
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSvg.Svg, { viewBox: vb.join(" "), width, height: size, ...a11y, children: [
2418
+ showMark ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2419
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: TILE_PATH, fill: tileFill, stroke: tileStroke, strokeWidth: tileStrokeWidth }),
2420
+ /* @__PURE__ */ jsxRuntime.jsx(
2421
+ reactNativeSvg.Path,
2422
+ {
2423
+ d: PULSE_PATH,
2424
+ fill: "none",
2425
+ stroke: pulseStroke,
2426
+ strokeWidth: 9,
2427
+ strokeLinecap: "round",
2428
+ strokeLinejoin: "round"
2429
+ }
2430
+ ),
2431
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Circle, { cx: 72, cy: 55, r: 6.2, fill: pulseDotFill })
2432
+ ] }) : null,
2433
+ showWordmark ? /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSvg.G, { transform: `translate(${wordmarkOffsetX},85.0) scale(0.097222 -0.097222)`, children: [
2434
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_P_PATH, fill: inkFill, transform: "translate(0,0)" }),
2435
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_O_PATH, fill: oFill, transform: "translate(658,0)" }),
2436
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_O_PATH, fill: oFill, transform: "translate(1280,0)" }),
2437
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_L_PATH, fill: inkFill, transform: "translate(1902,0)" }),
2438
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_S_PATH, fill: inkFill, transform: "translate(2195,0)" }),
2439
+ /* @__PURE__ */ jsxRuntime.jsx(reactNativeSvg.Path, { d: WORDMARK_E_PATH, fill: inkFill, transform: "translate(2754,0)" })
2440
+ ] }) : null
2441
+ ] });
2442
+ }
2443
+
2444
+ // src/primitives/index.ts
2445
+ var primitives = { PoolseIcon, PoolseLogo, Avatar };
2446
+ function useAppStateLifecycle() {
2447
+ const poolse = react$1.usePoolse();
2448
+ react.useEffect(() => {
2449
+ let prev = reactNative.AppState.currentState;
2450
+ const sub = reactNative.AppState.addEventListener("change", (next) => {
2451
+ const wasActive = prev === "active";
2452
+ const isActive = next === "active";
2453
+ if (wasActive && !isActive) {
2454
+ try {
2455
+ poolse.realtime?.disconnect?.();
2456
+ } catch {
2457
+ }
2458
+ } else if (!wasActive && isActive) {
2459
+ try {
2460
+ poolse.realtime?.connect?.();
2461
+ } catch {
2462
+ }
2463
+ }
2464
+ prev = next;
2465
+ });
2466
+ return () => sub.remove();
2467
+ }, [poolse]);
2468
+ }
2469
+
2470
+ exports.AttachmentPicker = AttachmentPicker;
2471
+ exports.AttachmentPreview = AttachmentPreview;
2472
+ exports.Avatar = Avatar;
2473
+ exports.ConversationList = ConversationList;
2474
+ exports.ConversationView = ConversationView;
2475
+ exports.EditableMessageBubble = EditableMessageBubble;
2476
+ exports.MemberList = MemberList;
2477
+ exports.MentionInput = MentionInput;
2478
+ exports.MessageActions = MessageActions;
2479
+ exports.MessageBubble = MessageBubble;
2480
+ exports.MessageComposer = MessageComposer;
2481
+ exports.MessageList = MessageList;
2482
+ exports.MessageRow = MessageRow;
2483
+ exports.PoolseIcon = PoolseIcon;
2484
+ exports.PoolseLogo = PoolseLogo;
2485
+ exports.PoolseTheme = PoolseTheme;
2486
+ exports.Primitives = primitives;
2487
+ exports.ReactionPicker = ReactionPicker;
2488
+ exports.ReactionStrip = ReactionStrip;
2489
+ exports.ThreadView = ThreadView;
2490
+ exports.TypingIndicator = TypingIndicator;
2491
+ exports.UploadQueueStrip = UploadQueueStrip;
2492
+ exports.UserName = UserName;
2493
+ exports.defaultDarkTheme = defaultDarkTheme;
2494
+ exports.defaultLightTheme = defaultLightTheme;
2495
+ exports.useAppStateLifecycle = useAppStateLifecycle;
2496
+ exports.useDisplayName = useDisplayName;
2497
+ exports.usePoolseTheme = usePoolseTheme;
2498
+ //# sourceMappingURL=index.cjs.map
2499
+ //# sourceMappingURL=index.cjs.map