@poolse/react-native 2.0.13 → 2.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.js CHANGED
@@ -1,13 +1,20 @@
1
- import { useUser, useMe, useConversations, useAttachmentUpload, useConversation, useMembers, usePresence, useMessages, useTyping, useReactions, useThread, useAttachmentUrl, usePoolse } from '@poolse/react';
2
- import { createContext, forwardRef, useRef, useState, useImperativeHandle, useEffect, useMemo, useCallback, useContext } from 'react';
3
- import { StyleSheet, View, Pressable, TextInput, Text, useWindowDimensions, Animated, Easing, PanResponder, Image, ScrollView, ActivityIndicator, FlatList, KeyboardAvoidingView, Platform, Modal, Dimensions, AppState } from 'react-native';
1
+ import { useUser, useMe, useConversations, useAttachmentUpload, useConversation, useMembers, usePresence, useMessages, useTyping, useRealtimeStatus, computeGroupPosition, formatDayLabel, useReactions, sameDay, useThread, useAttachmentUrl, usePoolse } from '@poolse/react';
2
+ import { createContext, forwardRef, useRef, useState, useEffect, useImperativeHandle, useMemo, useCallback, useContext, createElement } from 'react';
3
+ import { StyleSheet, Platform, Keyboard, View, Pressable, TextInput, Text, useWindowDimensions, Animated, Easing, PanResponder, Image, ScrollView, ActivityIndicator, FlatList, AppState, KeyboardAvoidingView, Modal, Dimensions, Linking } from 'react-native';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { SvgXml, Svg, Path, Circle, G } from 'react-native-svg';
6
6
  import * as ImagePicker from 'expo-image-picker';
7
7
  import * as DocumentPicker from 'expo-document-picker';
8
8
  import { manipulateAsync, SaveFormat } from 'expo-image-manipulator';
9
+ import * as Clipboard from 'expo-clipboard';
10
+ import Markdown from 'react-native-markdown-display';
9
11
 
10
- // src/PoolseInbox.tsx
12
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
13
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
14
+ }) : x)(function(x) {
15
+ if (typeof require !== "undefined") return require.apply(this, arguments);
16
+ throw Error('Dynamic require of "' + x + '" is not supported');
17
+ });
11
18
 
12
19
  // src/theme/default.ts
13
20
  var lightColors = {
@@ -288,6 +295,10 @@ var ICONS = {
288
295
  vb: "0 0 24 24",
289
296
  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"/>'
290
297
  },
298
+ camera: {
299
+ vb: "0 0 24 24",
300
+ d: '<path d="M3 8.5a2 2 0 0 1 2-2h2.4l1.4-2h6.4l1.4 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9Z"/><circle cx="12" cy="13" r="3.5"/>'
301
+ },
291
302
  "check-double": {
292
303
  vb: "0 0 24 24",
293
304
  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"/>'
@@ -1107,7 +1118,12 @@ function useUploadPreview() {
1107
1118
  getPreview: ctx?.getPreview ?? (() => null)
1108
1119
  };
1109
1120
  }
1110
- function AttachmentPicker({ visible, onClose, onPickInputs }) {
1121
+ function AttachmentPicker({
1122
+ visible,
1123
+ onClose,
1124
+ onPickInputs,
1125
+ camera = true
1126
+ }) {
1111
1127
  const theme = usePoolseTheme();
1112
1128
  const upload = useSharedUpload();
1113
1129
  const { setPreview } = useUploadPreview();
@@ -1141,8 +1157,12 @@ function AttachmentPicker({ visible, onClose, onPickInputs }) {
1141
1157
  theme.shadows.lg
1142
1158
  ],
1143
1159
  children: [
1160
+ camera ? /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("camera"), children: [
1161
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "camera", size: 20, color: theme.colors.ink2 }),
1162
+ /* @__PURE__ */ jsx(Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Camera" })
1163
+ ] }) : null,
1144
1164
  /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("image"), children: [
1145
- /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
1165
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "image", size: 20, color: theme.colors.ink2 }),
1146
1166
  /* @__PURE__ */ jsx(Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Photo or video" })
1147
1167
  ] }),
1148
1168
  /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("document"), children: [
@@ -1201,7 +1221,52 @@ async function compressImage({
1201
1221
  filename: `${base}.webp`
1202
1222
  };
1203
1223
  }
1224
+ async function assetsToInputs(assets) {
1225
+ return Promise.all(
1226
+ assets.map(async (asset) => {
1227
+ try {
1228
+ const compressed = await compressImage({
1229
+ uri: asset.uri,
1230
+ width: asset.width,
1231
+ height: asset.height,
1232
+ filename: asset.fileName ?? `image-${Date.now()}`
1233
+ });
1234
+ return {
1235
+ body: compressed.blob,
1236
+ contentType: compressed.contentType,
1237
+ byteSize: compressed.blob.size,
1238
+ filename: compressed.filename,
1239
+ _previewUri: asset.uri
1240
+ };
1241
+ } catch (err) {
1242
+ console.warn("[poolse/picker] image compression failed, sending original:", err);
1243
+ const blob = await uriToBlob(asset.uri);
1244
+ const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
1245
+ return {
1246
+ body: blob,
1247
+ contentType,
1248
+ byteSize: asset.fileSize ?? blob.size,
1249
+ filename: asset.fileName ?? `image-${Date.now()}.jpg`,
1250
+ _previewUri: asset.uri
1251
+ };
1252
+ }
1253
+ })
1254
+ );
1255
+ }
1204
1256
  async function defaultAdapter(kind) {
1257
+ if (kind === "camera") {
1258
+ const perm = await ImagePicker.requestCameraPermissionsAsync();
1259
+ if (!perm.granted) {
1260
+ console.warn("[poolse/picker] camera permission denied");
1261
+ return [];
1262
+ }
1263
+ const res2 = await ImagePicker.launchCameraAsync({
1264
+ mediaTypes: "images",
1265
+ quality: 1
1266
+ });
1267
+ if (res2.canceled) return [];
1268
+ return assetsToInputs(res2.assets);
1269
+ }
1205
1270
  if (kind === "image") {
1206
1271
  const res2 = await ImagePicker.launchImageLibraryAsync({
1207
1272
  mediaTypes: "images",
@@ -1211,36 +1276,7 @@ async function defaultAdapter(kind) {
1211
1276
  quality: 1
1212
1277
  });
1213
1278
  if (res2.canceled) return [];
1214
- return Promise.all(
1215
- res2.assets.map(async (asset) => {
1216
- try {
1217
- const compressed = await compressImage({
1218
- uri: asset.uri,
1219
- width: asset.width,
1220
- height: asset.height,
1221
- filename: asset.fileName ?? `image-${Date.now()}`
1222
- });
1223
- return {
1224
- body: compressed.blob,
1225
- contentType: compressed.contentType,
1226
- byteSize: compressed.blob.size,
1227
- filename: compressed.filename,
1228
- _previewUri: asset.uri
1229
- };
1230
- } catch (err) {
1231
- console.warn("[poolse/picker] image compression failed, sending original:", err);
1232
- const blob = await uriToBlob(asset.uri);
1233
- const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
1234
- return {
1235
- body: blob,
1236
- contentType,
1237
- byteSize: asset.fileSize ?? blob.size,
1238
- filename: asset.fileName ?? `image-${Date.now()}.jpg`,
1239
- _previewUri: asset.uri
1240
- };
1241
- }
1242
- })
1243
- );
1279
+ return assetsToInputs(res2.assets);
1244
1280
  }
1245
1281
  const res = await DocumentPicker.getDocumentAsync({
1246
1282
  copyToCacheDirectory: true,
@@ -1307,6 +1343,51 @@ var styles6 = StyleSheet.create({
1307
1343
  fontWeight: "500"
1308
1344
  }
1309
1345
  });
1346
+ function DaySeparator({ label }) {
1347
+ const theme = usePoolseTheme();
1348
+ return /* @__PURE__ */ jsx(View, { style: styles7.wrap, children: /* @__PURE__ */ jsx(
1349
+ View,
1350
+ {
1351
+ style: [
1352
+ styles7.chip,
1353
+ {
1354
+ backgroundColor: theme.colors.surface2,
1355
+ borderColor: theme.colors.border
1356
+ }
1357
+ ],
1358
+ children: /* @__PURE__ */ jsx(
1359
+ Text,
1360
+ {
1361
+ style: [
1362
+ styles7.label,
1363
+ {
1364
+ color: theme.colors.ink2,
1365
+ fontFamily: theme.type.fontBody
1366
+ }
1367
+ ],
1368
+ children: label
1369
+ }
1370
+ )
1371
+ }
1372
+ ) });
1373
+ }
1374
+ var styles7 = StyleSheet.create({
1375
+ wrap: {
1376
+ alignItems: "center",
1377
+ marginVertical: 8
1378
+ },
1379
+ chip: {
1380
+ paddingHorizontal: 12,
1381
+ paddingVertical: 4,
1382
+ borderRadius: 999,
1383
+ borderWidth: StyleSheet.hairlineWidth
1384
+ },
1385
+ label: {
1386
+ fontSize: 11,
1387
+ fontWeight: "600",
1388
+ letterSpacing: 0.3
1389
+ }
1390
+ });
1310
1391
  function UploadQueueStrip() {
1311
1392
  const theme = usePoolseTheme();
1312
1393
  const upload = useSharedUpload();
@@ -1317,8 +1398,8 @@ function UploadQueueStrip() {
1317
1398
  {
1318
1399
  horizontal: true,
1319
1400
  showsHorizontalScrollIndicator: false,
1320
- contentContainerStyle: styles7.scroll,
1321
- style: [styles7.scrollStyle, { marginBottom: 8 }],
1401
+ contentContainerStyle: styles8.scroll,
1402
+ style: [styles8.scrollStyle, { marginBottom: 8 }],
1322
1403
  children: upload.queue.map((item) => {
1323
1404
  const isError = item.status === "error";
1324
1405
  const isUploading = item.status === "uploading" || item.status === "pending";
@@ -1329,7 +1410,7 @@ function UploadQueueStrip() {
1329
1410
  if (item.status === "ready" || isError) upload.remove(item.localId);
1330
1411
  else upload.cancel(item.localId);
1331
1412
  };
1332
- return /* @__PURE__ */ jsxs(View, { style: styles7.tileWrap, children: [
1413
+ return /* @__PURE__ */ jsxs(View, { style: styles8.tileWrap, children: [
1333
1414
  isImage && previewUri ? /* @__PURE__ */ jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsx(
1334
1415
  FileTile,
1335
1416
  {
@@ -1344,13 +1425,13 @@ function UploadQueueStrip() {
1344
1425
  {
1345
1426
  onPress: handleClose,
1346
1427
  hitSlop: 8,
1347
- style: [styles7.closeBtn, { backgroundColor: theme.colors.ink }],
1428
+ style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
1348
1429
  accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
1349
1430
  children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
1350
1431
  }
1351
1432
  ) : null,
1352
- isUploading ? /* @__PURE__ */ jsx(View, { style: styles7.progressOverlay, children: /* @__PURE__ */ jsx(View, { style: [styles7.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
1353
- isError ? /* @__PURE__ */ jsx(View, { style: [styles7.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsx(Text, { style: [styles7.errorText, { color: "#fff" }], children: "Failed" }) }) : null
1433
+ isUploading ? /* @__PURE__ */ jsx(View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsx(View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
1434
+ isError ? /* @__PURE__ */ jsx(View, { style: [styles8.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsx(Text, { style: [styles8.errorText, { color: "#fff" }], children: "Failed" }) }) : null
1354
1435
  ] }, item.localId);
1355
1436
  })
1356
1437
  }
@@ -1363,7 +1444,7 @@ function ImageTile({ uri, uploading, error }) {
1363
1444
  {
1364
1445
  source: { uri },
1365
1446
  style: [
1366
- styles7.imageTile,
1447
+ styles8.imageTile,
1367
1448
  {
1368
1449
  backgroundColor: theme.colors.surface2,
1369
1450
  borderColor: error ? theme.colors.error : "transparent",
@@ -1385,24 +1466,24 @@ function FileTile({
1385
1466
  View,
1386
1467
  {
1387
1468
  style: [
1388
- styles7.fileTile,
1469
+ styles8.fileTile,
1389
1470
  {
1390
1471
  backgroundColor: theme.colors.surface2,
1391
1472
  borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
1392
1473
  }
1393
1474
  ],
1394
1475
  children: [
1395
- /* @__PURE__ */ jsx(View, { style: [styles7.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
1396
- /* @__PURE__ */ jsxs(View, { style: styles7.fileInfo, children: [
1476
+ /* @__PURE__ */ jsx(View, { style: [styles8.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
1477
+ /* @__PURE__ */ jsxs(View, { style: styles8.fileInfo, children: [
1397
1478
  /* @__PURE__ */ jsx(
1398
1479
  Text,
1399
1480
  {
1400
- style: [styles7.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1481
+ style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1401
1482
  numberOfLines: 1,
1402
1483
  children: filename
1403
1484
  }
1404
1485
  ),
1405
- /* @__PURE__ */ jsx(Text, { style: [styles7.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1486
+ /* @__PURE__ */ jsx(Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1406
1487
  ] })
1407
1488
  ]
1408
1489
  }
@@ -1416,7 +1497,7 @@ function formatBytes(n) {
1416
1497
  return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
1417
1498
  }
1418
1499
  var TILE = 64;
1419
- var styles7 = StyleSheet.create({
1500
+ var styles8 = StyleSheet.create({
1420
1501
  scrollStyle: {
1421
1502
  overflow: "visible"
1422
1503
  },
@@ -1516,6 +1597,22 @@ var styles7 = StyleSheet.create({
1516
1597
  fontWeight: "700"
1517
1598
  }
1518
1599
  });
1600
+ var FALLBACK = {
1601
+ top: Platform.OS === "ios" ? 50 : 24,
1602
+ bottom: Platform.OS === "ios" ? 34 : 0,
1603
+ left: 0,
1604
+ right: 0
1605
+ };
1606
+ var realHook = null;
1607
+ try {
1608
+ const mod = __require("react-native-safe-area-context");
1609
+ if (mod && typeof mod.useSafeAreaInsets === "function") {
1610
+ realHook = mod.useSafeAreaInsets;
1611
+ }
1612
+ } catch {
1613
+ }
1614
+ var useFallback = () => FALLBACK;
1615
+ var useSafeInsets = realHook ?? useFallback;
1519
1616
  var MessageComposer = forwardRef(
1520
1617
  function MessageComposer2({
1521
1618
  onSend,
@@ -1527,13 +1624,32 @@ var MessageComposer = forwardRef(
1527
1624
  labelFor,
1528
1625
  attachments = true,
1529
1626
  onAttachPress,
1530
- keyboardOffset: _keyboardOffset = 0
1627
+ keyboardOffset: _keyboardOffset = 0,
1628
+ editingMessage = null,
1629
+ onCancelEdit,
1630
+ onSaveEdit
1531
1631
  }, ref) {
1532
1632
  const theme = usePoolseTheme();
1533
1633
  const inputRef = useRef(null);
1534
1634
  const [value, setValue] = useState("");
1535
1635
  const [sending, setSending] = useState(false);
1636
+ const [keyboardOpen, setKeyboardOpen] = useState(false);
1536
1637
  const upload = useSharedUpload();
1638
+ const insets = useSafeInsets();
1639
+ useEffect(() => {
1640
+ const show = Keyboard.addListener(
1641
+ Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
1642
+ () => setKeyboardOpen(true)
1643
+ );
1644
+ const hide = Keyboard.addListener(
1645
+ Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
1646
+ () => setKeyboardOpen(false)
1647
+ );
1648
+ return () => {
1649
+ show.remove();
1650
+ hide.remove();
1651
+ };
1652
+ }, []);
1537
1653
  useImperativeHandle(
1538
1654
  ref,
1539
1655
  () => ({
@@ -1552,36 +1668,65 @@ var MessageComposer = forwardRef(
1552
1668
  const isUploading = upload.queue.some(
1553
1669
  (item) => item.status === "uploading" || item.status === "pending"
1554
1670
  );
1555
- const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 && readyAttachmentIds.length === 0;
1671
+ const previousDraftRef = useRef(null);
1672
+ const editingIdRef = useRef(null);
1673
+ useEffect(() => {
1674
+ const id = editingMessage?.id ?? null;
1675
+ if (id === editingIdRef.current) return;
1676
+ if (id) {
1677
+ previousDraftRef.current = value;
1678
+ setValue(editingMessage?.body ?? "");
1679
+ inputRef.current?.focus();
1680
+ } else {
1681
+ setValue(previousDraftRef.current ?? "");
1682
+ previousDraftRef.current = null;
1683
+ }
1684
+ editingIdRef.current = id;
1685
+ }, [editingMessage?.id, editingMessage?.body]);
1686
+ const isEditing = !!editingMessage;
1687
+ const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
1688
+ const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
1556
1689
  const handleSend = async () => {
1557
1690
  if (submitDisabled) return;
1558
1691
  const body = value.trim();
1559
- const replyId = replyingTo?.id;
1560
- const attachmentIds = readyAttachmentIds;
1561
- setValue("");
1562
- upload.reset();
1563
1692
  setSending(true);
1564
1693
  try {
1565
- await onSend(body, {
1566
- ...replyId ? { quoted_message_id: replyId } : {},
1567
- ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1568
- });
1694
+ if (isEditing && onSaveEdit) {
1695
+ await onSaveEdit(body);
1696
+ } else {
1697
+ const replyId = replyingTo?.id;
1698
+ const attachmentIds = readyAttachmentIds;
1699
+ setValue("");
1700
+ upload.reset();
1701
+ await onSend(body, {
1702
+ ...replyId ? { quoted_message_id: replyId } : {},
1703
+ ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1704
+ });
1705
+ }
1569
1706
  } finally {
1570
1707
  setSending(false);
1571
1708
  }
1572
1709
  };
1710
+ const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
1573
1711
  return /* @__PURE__ */ jsxs(
1574
1712
  View,
1575
1713
  {
1576
1714
  style: [
1577
- styles8.wrap,
1715
+ styles9.wrap,
1578
1716
  {
1579
1717
  backgroundColor: theme.colors.surface,
1580
- borderTopColor: theme.colors.border
1718
+ borderTopColor: theme.colors.border,
1719
+ paddingBottom
1581
1720
  }
1582
1721
  ],
1583
1722
  children: [
1584
- replyingTo ? /* @__PURE__ */ jsx(
1723
+ isEditing ? /* @__PURE__ */ jsx(
1724
+ EditChip,
1725
+ {
1726
+ message: editingMessage,
1727
+ ...onCancelEdit ? { onCancel: onCancelEdit } : {}
1728
+ }
1729
+ ) : replyingTo ? /* @__PURE__ */ jsx(
1585
1730
  ReplyChip,
1586
1731
  {
1587
1732
  message: replyingTo,
@@ -1589,14 +1734,14 @@ var MessageComposer = forwardRef(
1589
1734
  ...onCancelReply ? { onCancel: onCancelReply } : {}
1590
1735
  }
1591
1736
  ) : null,
1592
- attachments ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
1593
- /* @__PURE__ */ jsxs(View, { style: styles8.row, children: [
1737
+ attachments && !isEditing ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
1738
+ /* @__PURE__ */ jsxs(View, { style: styles9.row, children: [
1594
1739
  attachments && onAttachPress ? /* @__PURE__ */ jsx(
1595
1740
  Pressable,
1596
1741
  {
1597
1742
  onPress: onAttachPress,
1598
1743
  hitSlop: 8,
1599
- style: styles8.iconBtn,
1744
+ style: styles9.iconBtn,
1600
1745
  accessibilityLabel: "Attach file",
1601
1746
  children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
1602
1747
  }
@@ -1615,7 +1760,7 @@ var MessageComposer = forwardRef(
1615
1760
  multiline: true,
1616
1761
  editable: !disabled,
1617
1762
  style: [
1618
- styles8.input,
1763
+ styles9.input,
1619
1764
  {
1620
1765
  color: theme.colors.ink,
1621
1766
  backgroundColor: theme.colors.surface2,
@@ -1632,7 +1777,7 @@ var MessageComposer = forwardRef(
1632
1777
  onPress: handleSend,
1633
1778
  disabled: submitDisabled,
1634
1779
  style: [
1635
- styles8.sendBtn,
1780
+ styles9.sendBtn,
1636
1781
  {
1637
1782
  backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
1638
1783
  opacity: submitDisabled ? 0.6 : 1
@@ -1648,6 +1793,28 @@ var MessageComposer = forwardRef(
1648
1793
  );
1649
1794
  }
1650
1795
  );
1796
+ function EditChip({ message, onCancel }) {
1797
+ const theme = usePoolseTheme();
1798
+ return /* @__PURE__ */ jsxs(
1799
+ View,
1800
+ {
1801
+ style: [
1802
+ styles9.replyChip,
1803
+ {
1804
+ backgroundColor: theme.colors.surface2,
1805
+ borderLeftColor: theme.colors.brand
1806
+ }
1807
+ ],
1808
+ children: [
1809
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
1810
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
1811
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1812
+ ] }),
1813
+ onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1814
+ ]
1815
+ }
1816
+ );
1817
+ }
1651
1818
  function ReplyChip({
1652
1819
  message,
1653
1820
  labelFor,
@@ -1660,7 +1827,7 @@ function ReplyChip({
1660
1827
  View,
1661
1828
  {
1662
1829
  style: [
1663
- styles8.replyChip,
1830
+ styles9.replyChip,
1664
1831
  {
1665
1832
  backgroundColor: theme.colors.surface2,
1666
1833
  borderLeftColor: theme.colors.brand
@@ -1668,27 +1835,26 @@ function ReplyChip({
1668
1835
  ],
1669
1836
  children: [
1670
1837
  /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
1671
- /* @__PURE__ */ jsxs(Text, { style: [styles8.replyName, { color: theme.colors.brand }], children: [
1838
+ /* @__PURE__ */ jsxs(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
1672
1839
  "Replying to ",
1673
1840
  senderName
1674
1841
  ] }),
1675
- /* @__PURE__ */ jsx(Text, { style: [styles8.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1842
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1676
1843
  ] }),
1677
- onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles8.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1844
+ onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1678
1845
  ]
1679
1846
  }
1680
1847
  );
1681
1848
  }
1682
- var styles8 = StyleSheet.create({
1849
+ var styles9 = StyleSheet.create({
1683
1850
  wrap: {
1684
1851
  borderTopWidth: StyleSheet.hairlineWidth,
1685
1852
  paddingTop: 8,
1686
- paddingBottom: 10,
1687
1853
  paddingHorizontal: 12
1688
- // Note: no safe-area inset here on purpose. Wrap your chat
1689
- // surface in <SafeAreaView> (or <SafeAreaView edges={['bottom']}>)
1690
- // at the app root so the home-indicator clearance is handled
1691
- // once. Doing it here too produces double padding.
1854
+ // paddingBottom is applied inline — it follows the keyboard state
1855
+ // (10 px while open, max(10, safeBottomInset) while closed) so the
1856
+ // send/attach buttons stay clear of the home indicator without
1857
+ // double-padding above the keyboard.
1692
1858
  },
1693
1859
  row: {
1694
1860
  flexDirection: "row",
@@ -1777,11 +1943,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1777
1943
  transparent: true,
1778
1944
  animationType: "slide",
1779
1945
  onRequestClose: () => setPickerOpen(false),
1780
- children: /* @__PURE__ */ jsx(Pressable, { style: styles9.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
1946
+ children: /* @__PURE__ */ jsx(Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
1781
1947
  View,
1782
1948
  {
1783
1949
  style: [
1784
- styles9.sheet,
1950
+ styles10.sheet,
1785
1951
  {
1786
1952
  backgroundColor: theme.colors.surface,
1787
1953
  borderTopLeftRadius: theme.radii.lg,
@@ -1792,7 +1958,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1792
1958
  /* @__PURE__ */ jsx(
1793
1959
  Text,
1794
1960
  {
1795
- style: [styles9.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1961
+ style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1796
1962
  children: "Mention a member"
1797
1963
  }
1798
1964
  ),
@@ -1823,12 +1989,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1823
1989
  }
1824
1990
  function MentionRow({ member, onPress }) {
1825
1991
  const theme = usePoolseTheme();
1826
- return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles9.row, children: [
1992
+ return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles10.row, children: [
1827
1993
  /* @__PURE__ */ jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
1828
- /* @__PURE__ */ jsx(Text, { style: [styles9.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
1994
+ /* @__PURE__ */ jsx(Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
1829
1995
  ] });
1830
1996
  }
1831
- var styles9 = StyleSheet.create({
1997
+ var styles10 = StyleSheet.create({
1832
1998
  backdrop: {
1833
1999
  flex: 1,
1834
2000
  backgroundColor: "rgba(0,0,0,0.35)",
@@ -1855,138 +2021,134 @@ var styles9 = StyleSheet.create({
1855
2021
  fontSize: 14
1856
2022
  }
1857
2023
  });
2024
+ var AT_BOTTOM_THRESHOLD = 32;
1858
2025
  function MessageList({
1859
2026
  messages,
2027
+ items,
1860
2028
  loading = false,
1861
2029
  hasMore = false,
1862
2030
  onLoadMore,
1863
2031
  renderItem,
1864
2032
  ListHeader = null,
1865
2033
  ListFooter = null,
1866
- emptyState
2034
+ emptyState,
2035
+ meId = null
1867
2036
  }) {
1868
2037
  const theme = usePoolseTheme();
1869
- const data = [...messages].reverse();
2038
+ const flatListRef = useRef(null);
2039
+ const [atBottom, setAtBottom] = useState(true);
2040
+ const [newCount, setNewCount] = useState(0);
2041
+ const lastTailIdRef = useRef(null);
2042
+ const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
2043
+ const data = [...sourceItems].reverse();
2044
+ useEffect(() => {
2045
+ const tail = messages[messages.length - 1];
2046
+ if (!tail) return;
2047
+ const tailId = tail.id;
2048
+ if (tailId === lastTailIdRef.current) return;
2049
+ const isFirstSet = lastTailIdRef.current === null;
2050
+ lastTailIdRef.current = tailId;
2051
+ if (atBottom || isFirstSet) {
2052
+ setNewCount(0);
2053
+ return;
2054
+ }
2055
+ if (meId !== null && tail.sender_id === meId) return;
2056
+ setNewCount((n) => n + 1);
2057
+ }, [messages, atBottom, meId]);
2058
+ const handleScroll = useCallback((e) => {
2059
+ const y = e.nativeEvent.contentOffset.y;
2060
+ const next = y <= AT_BOTTOM_THRESHOLD;
2061
+ setAtBottom((prev) => prev === next ? prev : next);
2062
+ if (next) setNewCount(0);
2063
+ }, []);
2064
+ const scrollToBottom = useCallback(() => {
2065
+ flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
2066
+ setNewCount(0);
2067
+ }, []);
1870
2068
  const handleRender = useCallback(
1871
- ({ item, index }) => renderItem(item, messages.length - 1 - index),
1872
- [renderItem, messages.length]
2069
+ ({ item, index }) => {
2070
+ if (item.kind === "separator") return item.node;
2071
+ const sourceIndex = data.length - 1 - index;
2072
+ return renderItem(item.msg, sourceIndex);
2073
+ },
2074
+ [renderItem, data.length]
1873
2075
  );
1874
2076
  if (loading && messages.length === 0) {
1875
- return /* @__PURE__ */ jsx(View, { style: [styles10.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
2077
+ return /* @__PURE__ */ jsx(View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
1876
2078
  }
1877
2079
  if (messages.length === 0) {
1878
- return /* @__PURE__ */ jsx(View, { style: [styles10.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
2080
+ return /* @__PURE__ */ jsx(View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
1879
2081
  }
1880
- return /* @__PURE__ */ jsx(
1881
- FlatList,
1882
- {
1883
- inverted: true,
1884
- data,
1885
- keyExtractor: (item) => item.id,
1886
- renderItem: handleRender,
1887
- onEndReached: hasMore ? onLoadMore : void 0,
1888
- onEndReachedThreshold: 0.3,
1889
- ListHeaderComponent: ListHeader,
1890
- ListFooterComponent: /* @__PURE__ */ jsxs(View, { children: [
1891
- hasMore && loading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
1892
- ListFooter
1893
- ] }),
1894
- style: { backgroundColor: theme.colors.paper },
1895
- contentContainerStyle: { paddingVertical: 8 }
1896
- }
1897
- );
2082
+ return /* @__PURE__ */ jsxs(View, { style: styles11.root, children: [
2083
+ /* @__PURE__ */ jsx(
2084
+ FlatList,
2085
+ {
2086
+ ref: flatListRef,
2087
+ inverted: true,
2088
+ data,
2089
+ keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
2090
+ renderItem: handleRender,
2091
+ onEndReached: hasMore ? onLoadMore : void 0,
2092
+ onEndReachedThreshold: 0.3,
2093
+ onScroll: handleScroll,
2094
+ scrollEventThrottle: 32,
2095
+ ListHeaderComponent: ListHeader,
2096
+ ListFooterComponent: /* @__PURE__ */ jsxs(View, { children: [
2097
+ hasMore && loading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
2098
+ ListFooter
2099
+ ] }),
2100
+ style: { backgroundColor: theme.colors.paper },
2101
+ contentContainerStyle: { paddingVertical: 8 }
2102
+ }
2103
+ ),
2104
+ !atBottom && newCount > 0 ? /* @__PURE__ */ jsxs(
2105
+ Pressable,
2106
+ {
2107
+ onPress: scrollToBottom,
2108
+ style: [
2109
+ styles11.jumpButton,
2110
+ {
2111
+ backgroundColor: theme.colors.brand
2112
+ },
2113
+ theme.shadows.sm
2114
+ ],
2115
+ accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
2116
+ children: [
2117
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
2118
+ /* @__PURE__ */ jsxs(Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
2119
+ newCount,
2120
+ " new ",
2121
+ newCount === 1 ? "message" : "messages"
2122
+ ] })
2123
+ ]
2124
+ }
2125
+ ) : null
2126
+ ] });
1898
2127
  }
1899
- var styles10 = StyleSheet.create({
2128
+ var styles11 = StyleSheet.create({
2129
+ root: {
2130
+ flex: 1
2131
+ },
1900
2132
  placeholder: {
1901
2133
  flex: 1,
1902
2134
  alignItems: "center",
1903
2135
  justifyContent: "center",
1904
2136
  padding: 24
1905
- }
1906
- });
1907
- function EditableMessageBubble({
1908
- message,
1909
- currentUserId,
1910
- onCancel,
1911
- onSave
1912
- }) {
1913
- const theme = usePoolseTheme();
1914
- const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1915
- const [value, setValue] = useState(message.body ?? "");
1916
- const [saving, setSaving] = useState(false);
1917
- const submit = async () => {
1918
- if (saving || !value.trim()) return;
1919
- setSaving(true);
1920
- try {
1921
- await onSave(value.trim());
1922
- } finally {
1923
- setSaving(false);
1924
- }
1925
- };
1926
- const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1927
- const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1928
- return /* @__PURE__ */ jsx(
1929
- View,
1930
- {
1931
- style: [
1932
- styles11.wrap,
1933
- {
1934
- alignSelf: isSelf ? "flex-end" : "flex-start",
1935
- maxWidth: "80%"
1936
- }
1937
- ],
1938
- children: /* @__PURE__ */ jsxs(
1939
- View,
1940
- {
1941
- style: [
1942
- styles11.bubble,
1943
- {
1944
- backgroundColor: bg,
1945
- borderRadius: theme.radii.bubble,
1946
- paddingHorizontal: theme.spacing.md,
1947
- paddingVertical: theme.spacing.sm
1948
- },
1949
- theme.shadows.sm
1950
- ],
1951
- children: [
1952
- /* @__PURE__ */ jsx(
1953
- TextInput,
1954
- {
1955
- value,
1956
- onChangeText: setValue,
1957
- multiline: true,
1958
- autoFocus: true,
1959
- style: {
1960
- color: ink,
1961
- fontFamily: theme.type.fontBody,
1962
- fontSize: theme.type.bodySize,
1963
- minHeight: 24
1964
- }
1965
- }
1966
- ),
1967
- /* @__PURE__ */ jsxs(View, { style: styles11.actions, children: [
1968
- /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles11.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
1969
- /* @__PURE__ */ jsx(Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles11.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
1970
- ] })
1971
- ]
1972
- }
1973
- )
1974
- }
1975
- );
1976
- }
1977
- var styles11 = StyleSheet.create({
1978
- wrap: {
1979
- marginBottom: 4
1980
2137
  },
1981
- bubble: {},
1982
- actions: {
2138
+ jumpButton: {
2139
+ position: "absolute",
2140
+ alignSelf: "center",
2141
+ bottom: 12,
1983
2142
  flexDirection: "row",
1984
- justifyContent: "flex-end",
1985
- gap: 12,
1986
- marginTop: 6
2143
+ alignItems: "center",
2144
+ gap: 6,
2145
+ paddingHorizontal: 14,
2146
+ paddingVertical: 8,
2147
+ borderRadius: 999
1987
2148
  },
1988
- btn: {
1989
- fontSize: 13
2149
+ jumpText: {
2150
+ fontSize: 13,
2151
+ fontWeight: "600"
1990
2152
  }
1991
2153
  });
1992
2154
  var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
@@ -2449,6 +2611,53 @@ var styles14 = StyleSheet.create({
2449
2611
  borderRadius: 20
2450
2612
  }
2451
2613
  });
2614
+ function renderMarkdown(body, args) {
2615
+ const baseText = {
2616
+ color: args.textColor,
2617
+ fontFamily: args.fontFamily,
2618
+ fontSize: args.fontSize,
2619
+ lineHeight: args.lineHeight
2620
+ };
2621
+ const style = {
2622
+ body: baseText,
2623
+ paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
2624
+ text: baseText,
2625
+ strong: { ...baseText, fontWeight: "700" },
2626
+ em: { ...baseText, fontStyle: "italic" },
2627
+ s: { ...baseText, textDecorationLine: "line-through" },
2628
+ link: { color: args.linkColor, textDecorationLine: "underline" },
2629
+ code_inline: {
2630
+ ...baseText,
2631
+ backgroundColor: "rgba(127,127,127,0.18)",
2632
+ borderRadius: 4,
2633
+ paddingHorizontal: 4
2634
+ },
2635
+ code_block: {
2636
+ ...baseText,
2637
+ backgroundColor: "rgba(127,127,127,0.18)",
2638
+ padding: 8,
2639
+ borderRadius: 6
2640
+ },
2641
+ fence: {
2642
+ ...baseText,
2643
+ backgroundColor: "rgba(127,127,127,0.18)",
2644
+ padding: 8,
2645
+ borderRadius: 6
2646
+ },
2647
+ bullet_list: { marginTop: 2, marginBottom: 2 },
2648
+ ordered_list: { marginTop: 2, marginBottom: 2 },
2649
+ list_item: baseText,
2650
+ blockquote: {
2651
+ ...baseText,
2652
+ borderLeftWidth: 3,
2653
+ borderLeftColor: args.linkColor,
2654
+ paddingLeft: 8,
2655
+ marginVertical: 4,
2656
+ opacity: 0.85
2657
+ }
2658
+ };
2659
+ return createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
2660
+ }
2452
2661
  function MessageBubble({
2453
2662
  message,
2454
2663
  currentUserId,
@@ -2459,7 +2668,8 @@ function MessageBubble({
2459
2668
  maxBodyLength = 0,
2460
2669
  showSenderName = false,
2461
2670
  actionsTrigger,
2462
- showAttachments = true
2671
+ showAttachments = true,
2672
+ markdown = true
2463
2673
  }) {
2464
2674
  const theme = usePoolseTheme();
2465
2675
  const { width: screenWidth } = useWindowDimensions();
@@ -2544,20 +2754,44 @@ function MessageBubble({
2544
2754
  ) : null,
2545
2755
  renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
2546
2756
  renderAttachments && files.length > 0 ? /* @__PURE__ */ jsx(View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment: att, maxWidth: contentMaxWidth }, att.id)) }) : null,
2547
- body.length > 0 ? /* @__PURE__ */ jsx(
2757
+ body.length > 0 ? isDeleted ? /* @__PURE__ */ jsx(
2548
2758
  Text,
2549
2759
  {
2550
2760
  style: [
2551
2761
  styles15.body,
2552
2762
  {
2553
- color: isDeleted ? theme.colors.ink3 : textColor,
2763
+ color: theme.colors.ink3,
2554
2764
  fontFamily: theme.type.fontBody,
2555
2765
  fontSize: theme.type.bodySize,
2556
2766
  lineHeight: theme.type.lineHeight,
2557
- fontStyle: isDeleted ? "italic" : "normal"
2767
+ fontStyle: "italic"
2558
2768
  }
2559
2769
  ],
2560
- children: isDeleted ? "Message deleted" : truncated
2770
+ children: "Message deleted"
2771
+ }
2772
+ ) : markdown ? renderMarkdown(truncated, {
2773
+ textColor,
2774
+ linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
2775
+ fontFamily: theme.type.fontBody,
2776
+ fontSize: theme.type.bodySize,
2777
+ lineHeight: theme.type.lineHeight,
2778
+ onLinkPress: (url) => {
2779
+ void Linking.openURL(url).catch(() => void 0);
2780
+ return false;
2781
+ }
2782
+ }) : /* @__PURE__ */ jsx(
2783
+ Text,
2784
+ {
2785
+ style: [
2786
+ styles15.body,
2787
+ {
2788
+ color: textColor,
2789
+ fontFamily: theme.type.fontBody,
2790
+ fontSize: theme.type.bodySize,
2791
+ lineHeight: theme.type.lineHeight
2792
+ }
2793
+ ],
2794
+ children: truncated
2561
2795
  }
2562
2796
  ) : null,
2563
2797
  maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
@@ -2652,9 +2886,9 @@ function formatTime(iso) {
2652
2886
  try {
2653
2887
  const d = new Date(iso);
2654
2888
  const now = /* @__PURE__ */ new Date();
2655
- const sameDay = isSameDay(d, now);
2889
+ const sameDay2 = isSameDay(d, now);
2656
2890
  const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
2657
- if (sameDay) return time;
2891
+ if (sameDay2) return time;
2658
2892
  const yesterday = new Date(now);
2659
2893
  yesterday.setDate(now.getDate() - 1);
2660
2894
  if (isSameDay(d, yesterday)) return `Yesterday ${time}`;
@@ -2762,12 +2996,26 @@ function ReactionStrip({
2762
2996
  styles16.chip,
2763
2997
  {
2764
2998
  backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
2765
- borderColor: meReacted ? theme.colors.brand : theme.colors.border
2999
+ borderColor: meReacted ? theme.colors.brand : theme.colors.border,
3000
+ borderWidth: meReacted ? 1.5 : 1,
3001
+ transform: meReacted ? [{ scale: 1.04 }] : void 0
2766
3002
  }
2767
3003
  ],
2768
3004
  children: [
2769
3005
  /* @__PURE__ */ jsx(Text, { style: styles16.emoji, children: emoji }),
2770
- /* @__PURE__ */ jsx(Text, { style: [styles16.count, { color: theme.colors.ink2 }], children: userArr.length })
3006
+ /* @__PURE__ */ jsx(
3007
+ Text,
3008
+ {
3009
+ style: [
3010
+ styles16.count,
3011
+ {
3012
+ color: meReacted ? theme.colors.brand : theme.colors.ink2,
3013
+ fontWeight: meReacted ? "700" : "600"
3014
+ }
3015
+ ],
3016
+ children: userArr.length
3017
+ }
3018
+ )
2771
3019
  ]
2772
3020
  },
2773
3021
  emoji
@@ -2820,7 +3068,6 @@ var styles16 = StyleSheet.create({
2820
3068
  paddingHorizontal: 9,
2821
3069
  paddingVertical: 4,
2822
3070
  borderRadius: 14,
2823
- borderWidth: 1,
2824
3071
  gap: 5,
2825
3072
  minHeight: 26,
2826
3073
  // Subtle elevation so chips lift off the surface
@@ -2836,7 +3083,6 @@ var styles16 = StyleSheet.create({
2836
3083
  },
2837
3084
  count: {
2838
3085
  fontSize: 12,
2839
- fontWeight: "600",
2840
3086
  fontVariant: ["tabular-nums"]
2841
3087
  },
2842
3088
  backdrop: {
@@ -2874,11 +3120,10 @@ function MessageRow({
2874
3120
  actions = true,
2875
3121
  threads = true,
2876
3122
  quotations = true,
3123
+ markdown = true,
2877
3124
  readState,
2878
- editing = false,
3125
+ isEditing = false,
2879
3126
  onStartEdit,
2880
- onCancelEdit,
2881
- onSaveEdit,
2882
3127
  onDelete,
2883
3128
  onOpenThread,
2884
3129
  onQuote,
@@ -2899,7 +3144,7 @@ function MessageRow({
2899
3144
  ...msg.reactions ? { initialReactions: msg.reactions } : {}
2900
3145
  });
2901
3146
  const translateX = useRef(new Animated.Value(0)).current;
2902
- const swipeEnabled = quotations && !!onQuote && !editing;
3147
+ const swipeEnabled = quotations && !!onQuote && !isEditing;
2903
3148
  const panResponder = useMemo(
2904
3149
  () => PanResponder.create({
2905
3150
  // Tap / long-press still go to the inner Pressable.
@@ -2974,137 +3219,149 @@ function MessageRow({
2974
3219
  const rowSender = useUser(showAvatarSlot ? msg.sender_external_id : null);
2975
3220
  const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
2976
3221
  const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
2977
- return /* @__PURE__ */ jsxs(View, { style: [styles17.row, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: [
2978
- showAvatarSlot ? /* @__PURE__ */ jsx(View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsx(
2979
- Avatar,
2980
- {
2981
- src: avatarUrl ?? null,
2982
- name: senderLabel,
2983
- seed: msg.sender_external_id ?? null,
2984
- size: "sm"
2985
- }
2986
- ) : null }) : null,
2987
- /* @__PURE__ */ jsxs(View, { style: styles17.bubbleCol, children: [
2988
- swipeEnabled ? /* @__PURE__ */ jsx(
2989
- Animated.View,
2990
- {
2991
- pointerEvents: "none",
2992
- style: [
2993
- styles17.replyHint,
2994
- isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
2995
- {
2996
- opacity: replyIconOpacity,
2997
- transform: [{ scale: replyIconScale }],
2998
- backgroundColor: theme.colors.surface2
2999
- }
3000
- ],
3001
- children: /* @__PURE__ */ jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
3002
- }
3003
- ) : null,
3004
- /* @__PURE__ */ jsx(
3005
- Animated.View,
3006
- {
3007
- ...swipeEnabled ? panResponder.panHandlers : {},
3008
- style: { transform: [{ translateX }] },
3009
- children: /* @__PURE__ */ jsx(
3010
- Pressable,
3222
+ return /* @__PURE__ */ jsxs(
3223
+ View,
3224
+ {
3225
+ style: [
3226
+ styles17.row,
3227
+ { alignSelf: isSelf ? "flex-end" : "flex-start" },
3228
+ // Lift this row above the dim backdrop the parent renders
3229
+ // during edit mode. zIndex covers iOS / web; elevation does
3230
+ // the equivalent on Android.
3231
+ isEditing ? styles17.rowEditing : null
3232
+ ],
3233
+ children: [
3234
+ showAvatarSlot ? /* @__PURE__ */ jsx(View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsx(
3235
+ Avatar,
3236
+ {
3237
+ src: avatarUrl ?? null,
3238
+ name: senderLabel,
3239
+ seed: msg.sender_external_id ?? null,
3240
+ size: "sm"
3241
+ }
3242
+ ) : null }) : null,
3243
+ /* @__PURE__ */ jsxs(View, { style: styles17.bubbleCol, children: [
3244
+ swipeEnabled ? /* @__PURE__ */ jsx(
3245
+ Animated.View,
3011
3246
  {
3012
- onLongPress: () => {
3013
- if (!actions) return;
3014
- setActionsOpen(true);
3015
- },
3016
- accessibilityRole: "button",
3017
- accessibilityLabel: "Message actions",
3018
- children: editing ? /* @__PURE__ */ jsx(
3019
- EditableMessageBubble,
3247
+ pointerEvents: "none",
3248
+ style: [
3249
+ styles17.replyHint,
3250
+ isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
3020
3251
  {
3021
- message: msg,
3022
- currentUserId: meId,
3023
- onCancel: onCancelEdit ?? (() => void 0),
3024
- onSave: onSaveEdit ?? (() => void 0)
3252
+ opacity: replyIconOpacity,
3253
+ transform: [{ scale: replyIconScale }],
3254
+ backgroundColor: theme.colors.surface2
3025
3255
  }
3026
- ) : /* @__PURE__ */ jsx(
3027
- MessageBubble,
3256
+ ],
3257
+ children: /* @__PURE__ */ jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
3258
+ }
3259
+ ) : null,
3260
+ /* @__PURE__ */ jsx(
3261
+ Animated.View,
3262
+ {
3263
+ ...swipeEnabled ? panResponder.panHandlers : {},
3264
+ style: { transform: [{ translateX }] },
3265
+ children: /* @__PURE__ */ jsx(
3266
+ Pressable,
3028
3267
  {
3029
- message: msg,
3030
- currentUserId: meId,
3031
- ...readState ? { readState } : {},
3032
- ...labelFor ? { labelFor } : {},
3033
- ...onQuotedClick ? { onQuotedClick } : {},
3034
- groupPosition,
3035
- maxBodyLength,
3036
- showSenderName,
3037
- showAttachments: attachments,
3038
- actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
3039
- PoolseIcon,
3268
+ onLongPress: () => {
3269
+ if (!actions) return;
3270
+ setActionsOpen(true);
3271
+ },
3272
+ accessibilityRole: "button",
3273
+ accessibilityLabel: "Message actions",
3274
+ children: /* @__PURE__ */ jsx(
3275
+ MessageBubble,
3040
3276
  {
3041
- name: "chevron-down",
3042
- size: 14,
3043
- color: isSelf ? theme.colors.onBrand : theme.colors.ink3
3277
+ message: msg,
3278
+ currentUserId: meId,
3279
+ ...readState ? { readState } : {},
3280
+ ...labelFor ? { labelFor } : {},
3281
+ ...onQuotedClick ? { onQuotedClick } : {},
3282
+ groupPosition,
3283
+ maxBodyLength,
3284
+ showSenderName,
3285
+ showAttachments: attachments,
3286
+ markdown,
3287
+ actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
3288
+ PoolseIcon,
3289
+ {
3290
+ name: "chevron-down",
3291
+ size: 14,
3292
+ color: isSelf ? theme.colors.onBrand : theme.colors.ink3
3293
+ }
3294
+ ) }) : null
3044
3295
  }
3045
- ) }) : null
3296
+ )
3046
3297
  }
3047
3298
  )
3048
3299
  }
3049
- )
3050
- }
3051
- ),
3052
- threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsx(Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxs(Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
3053
- "\u{1F4AC} ",
3054
- msg.reply_count,
3055
- " ",
3056
- msg.reply_count === 1 ? "reply" : "replies"
3057
- ] }) }) : null,
3058
- reactions ? /* @__PURE__ */ jsx(View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsx(
3059
- ReactionStrip,
3060
- {
3061
- messageId: msg.id,
3062
- conversationId: msg.conversation_id,
3063
- meId,
3064
- ...msg.reactions ? { initialReactions: msg.reactions } : {}
3065
- }
3066
- ) }) : null
3067
- ] }),
3068
- actions && actionsOpen ? /* @__PURE__ */ jsx(
3069
- MessageActions,
3070
- {
3071
- visible: actionsOpen,
3072
- onClose: () => setActionsOpen(false),
3073
- isSelf,
3074
- ...reactions ? {
3075
- // Inline quick-react row at the top of the sheet —
3076
- // one tap = react + close, no second modal needed.
3077
- onPickReaction: (emoji) => {
3078
- void reactionsApi.addReaction(emoji);
3079
- }
3080
- } : {},
3081
- ...onStartEdit ? {
3082
- onEdit: () => {
3083
- setActionsOpen(false);
3084
- onStartEdit();
3085
- }
3086
- } : {},
3087
- ...onDelete ? {
3088
- onDelete: () => {
3089
- setActionsOpen(false);
3090
- onDelete();
3091
- }
3092
- } : {},
3093
- ...onOpenThread ? {
3094
- onReplyInThread: () => {
3095
- setActionsOpen(false);
3096
- onOpenThread();
3097
- }
3098
- } : {},
3099
- ...quotations && onQuote ? {
3100
- onQuote: () => {
3101
- setActionsOpen(false);
3102
- onQuote();
3300
+ ),
3301
+ threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsx(Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxs(Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
3302
+ "\u{1F4AC} ",
3303
+ msg.reply_count,
3304
+ " ",
3305
+ msg.reply_count === 1 ? "reply" : "replies"
3306
+ ] }) }) : null,
3307
+ reactions ? /* @__PURE__ */ jsx(View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsx(
3308
+ ReactionStrip,
3309
+ {
3310
+ messageId: msg.id,
3311
+ conversationId: msg.conversation_id,
3312
+ meId,
3313
+ ...msg.reactions ? { initialReactions: msg.reactions } : {}
3314
+ }
3315
+ ) }) : null
3316
+ ] }),
3317
+ actions && actionsOpen ? /* @__PURE__ */ jsx(
3318
+ MessageActions,
3319
+ {
3320
+ visible: actionsOpen,
3321
+ onClose: () => setActionsOpen(false),
3322
+ isSelf,
3323
+ ...reactions ? {
3324
+ // Inline quick-react row at the top of the sheet —
3325
+ // one tap = react + close, no second modal needed.
3326
+ onPickReaction: (emoji) => {
3327
+ void reactionsApi.addReaction(emoji);
3328
+ }
3329
+ } : {},
3330
+ ...msg.body && !msg.deleted_at ? {
3331
+ onCopy: () => {
3332
+ setActionsOpen(false);
3333
+ void Clipboard.setStringAsync(msg.body ?? "");
3334
+ }
3335
+ } : {},
3336
+ ...onStartEdit ? {
3337
+ onEdit: () => {
3338
+ setActionsOpen(false);
3339
+ onStartEdit();
3340
+ }
3341
+ } : {},
3342
+ ...onDelete ? {
3343
+ onDelete: () => {
3344
+ setActionsOpen(false);
3345
+ onDelete();
3346
+ }
3347
+ } : {},
3348
+ ...onOpenThread ? {
3349
+ onReplyInThread: () => {
3350
+ setActionsOpen(false);
3351
+ onOpenThread();
3352
+ }
3353
+ } : {},
3354
+ ...quotations && onQuote ? {
3355
+ onQuote: () => {
3356
+ setActionsOpen(false);
3357
+ onQuote();
3358
+ }
3359
+ } : {}
3103
3360
  }
3104
- } : {}
3105
- }
3106
- ) : null
3107
- ] });
3361
+ ) : null
3362
+ ]
3363
+ }
3364
+ );
3108
3365
  }
3109
3366
  var styles17 = StyleSheet.create({
3110
3367
  row: {
@@ -3120,6 +3377,12 @@ var styles17 = StyleSheet.create({
3120
3377
  paddingHorizontal: 10,
3121
3378
  maxWidth: "95%"
3122
3379
  },
3380
+ rowEditing: {
3381
+ // Above ConversationView's editBackdrop (zIndex 5). Elevation
3382
+ // does the equivalent stacking on Android.
3383
+ zIndex: 10,
3384
+ elevation: 10
3385
+ },
3123
3386
  avatarSlot: {
3124
3387
  width: 36,
3125
3388
  marginRight: 6
@@ -3214,31 +3477,34 @@ function ThreadSheet({
3214
3477
  const [pickerOpen, setPickerOpen] = useState(false);
3215
3478
  const [editingId, setEditingId] = useState(null);
3216
3479
  const renderReply = useCallback(
3217
- (msg) => /* @__PURE__ */ jsx(
3218
- MessageRow,
3219
- {
3220
- msg,
3221
- meId,
3222
- reactions: true,
3223
- attachments: true,
3224
- actions: true,
3225
- threads: false,
3226
- quotations: false,
3227
- editing: editingId === msg.id,
3228
- onStartEdit: () => setEditingId(msg.id),
3229
- onCancelEdit: () => setEditingId(null),
3230
- onSaveEdit: async (body) => {
3231
- await edit(msg.id, body);
3232
- setEditingId(null);
3233
- },
3234
- onDelete: () => {
3235
- void deleteReply(msg.id);
3236
- },
3237
- ...labelFor ? { labelFor } : {},
3238
- ...avatarFor ? { avatarFor } : {}
3239
- }
3240
- ),
3241
- [meId, editingId, edit, deleteReply, labelFor, avatarFor]
3480
+ (msg) => {
3481
+ const isSelf = meId !== null && msg.sender_id === meId;
3482
+ const canEdit = isSelf && !msg.deleted_at;
3483
+ return /* @__PURE__ */ jsx(
3484
+ MessageRow,
3485
+ {
3486
+ msg,
3487
+ meId,
3488
+ reactions: true,
3489
+ attachments: true,
3490
+ actions: true,
3491
+ threads: false,
3492
+ quotations: false,
3493
+ isEditing: editingId === msg.id,
3494
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3495
+ onDelete: () => {
3496
+ void deleteReply(msg.id);
3497
+ },
3498
+ ...labelFor ? { labelFor } : {},
3499
+ ...avatarFor ? { avatarFor } : {}
3500
+ }
3501
+ );
3502
+ },
3503
+ [meId, editingId, deleteReply, labelFor, avatarFor]
3504
+ );
3505
+ const editingMessage = useMemo(
3506
+ () => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
3507
+ [editingId, replies]
3242
3508
  );
3243
3509
  const rootRow = useMemo(
3244
3510
  () => /* @__PURE__ */ jsx(
@@ -3324,7 +3590,14 @@ function ThreadSheet({
3324
3590
  },
3325
3591
  placeholder: "Reply to thread\u2026",
3326
3592
  attachments: true,
3327
- onAttachPress: () => setPickerOpen(true)
3593
+ onAttachPress: () => setPickerOpen(true),
3594
+ editingMessage,
3595
+ onCancelEdit: () => setEditingId(null),
3596
+ onSaveEdit: async (body) => {
3597
+ if (!editingId) return;
3598
+ await edit(editingId, body);
3599
+ setEditingId(null);
3600
+ }
3328
3601
  }
3329
3602
  )
3330
3603
  ]
@@ -3488,6 +3761,7 @@ var styles19 = StyleSheet.create({
3488
3761
  fontSize: 12
3489
3762
  }
3490
3763
  });
3764
+ var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
3491
3765
  function ConversationView({
3492
3766
  conversationId,
3493
3767
  emptyState,
@@ -3504,6 +3778,7 @@ function ConversationView({
3504
3778
  threads = true,
3505
3779
  quotations = true,
3506
3780
  readReceipts = true,
3781
+ markdown = true,
3507
3782
  maxBodyLength = 200,
3508
3783
  senderLabels = "auto",
3509
3784
  avatars = "auto",
@@ -3522,9 +3797,25 @@ function ConversationView({
3522
3797
  send,
3523
3798
  edit,
3524
3799
  delete: deleteMsg,
3525
- markReadUpTo
3800
+ markReadUpTo,
3801
+ refetch
3526
3802
  } = useMessages(conversationId);
3527
3803
  const { typing, signalTyping } = useTyping(conversationId);
3804
+ const realtimeStatus = useRealtimeStatus();
3805
+ useEffect(() => {
3806
+ const sub = AppState.addEventListener("change", (next) => {
3807
+ if (next === "active") void refetch();
3808
+ });
3809
+ return () => sub.remove();
3810
+ }, [refetch]);
3811
+ const prevStatusRef = useRef(realtimeStatus);
3812
+ useEffect(() => {
3813
+ const prev = prevStatusRef.current;
3814
+ prevStatusRef.current = realtimeStatus;
3815
+ if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
3816
+ void refetch();
3817
+ }
3818
+ }, [realtimeStatus, refetch]);
3528
3819
  const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
3529
3820
  const { members } = useMembers(wantsMembers ? conversationId : "");
3530
3821
  const isGroupChat = members.length > 2;
@@ -3555,10 +3846,24 @@ function ConversationView({
3555
3846
  });
3556
3847
  onMarkedRead?.(conversationId, latest.id);
3557
3848
  }, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
3849
+ const groupPositionById = useMemo(() => {
3850
+ const map = /* @__PURE__ */ new Map();
3851
+ for (let i = 0; i < messages.length; i++) {
3852
+ const msg = messages[i];
3853
+ if (!msg) continue;
3854
+ const prev = i > 0 ? messages[i - 1] ?? null : null;
3855
+ const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
3856
+ map.set(msg.id, computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
3857
+ }
3858
+ return map;
3859
+ }, [messages]);
3558
3860
  const renderItem = useCallback(
3559
3861
  (msg) => {
3560
3862
  if (renderMessage) return /* @__PURE__ */ jsx(Fragment, { children: renderMessage(msg, meId) });
3561
3863
  const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
3864
+ const isSelf = meId !== null && msg.sender_id === meId;
3865
+ const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
3866
+ const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
3562
3867
  return /* @__PURE__ */ jsx(
3563
3868
  MessageRow,
3564
3869
  {
@@ -3570,13 +3875,8 @@ function ConversationView({
3570
3875
  threads,
3571
3876
  quotations,
3572
3877
  ...readState ? { readState } : {},
3573
- editing: editingId === msg.id,
3574
- onStartEdit: () => setEditingId(msg.id),
3575
- onCancelEdit: () => setEditingId(null),
3576
- onSaveEdit: async (body) => {
3577
- await edit(msg.id, body);
3578
- setEditingId(null);
3579
- },
3878
+ isEditing: editingId === msg.id,
3879
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3580
3880
  onDelete: () => deleteMsg(msg.id),
3581
3881
  ...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
3582
3882
  ...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
@@ -3584,7 +3884,9 @@ function ConversationView({
3584
3884
  ...avatarFor ? { avatarFor } : {},
3585
3885
  showSenderName: showSenderLabels,
3586
3886
  showAvatar: showAvatars,
3587
- maxBodyLength
3887
+ maxBodyLength,
3888
+ groupPosition,
3889
+ markdown
3588
3890
  }
3589
3891
  );
3590
3892
  },
@@ -3599,16 +3901,38 @@ function ConversationView({
3599
3901
  threads,
3600
3902
  quotations,
3601
3903
  editingId,
3602
- edit,
3603
3904
  deleteMsg,
3604
3905
  labelFor,
3605
3906
  avatarFor,
3606
3907
  showSenderLabels,
3607
3908
  showAvatars,
3608
3909
  maxBodyLength,
3609
- sequenceByMessageId
3910
+ sequenceByMessageId,
3911
+ groupPositionById,
3912
+ markdown
3610
3913
  ]
3611
3914
  );
3915
+ const editingMessage = useMemo(
3916
+ () => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
3917
+ [editingId, messages]
3918
+ );
3919
+ const listItems = useMemo(() => {
3920
+ const out = [];
3921
+ let prev = null;
3922
+ for (const msg of messages) {
3923
+ if (prev === null || !sameDay(msg.inserted_at, prev.inserted_at)) {
3924
+ const label = formatDayLabel(msg.inserted_at);
3925
+ out.push({
3926
+ kind: "separator",
3927
+ id: `sep-${msg.id}`,
3928
+ node: /* @__PURE__ */ jsx(DaySeparator, { label })
3929
+ });
3930
+ }
3931
+ out.push({ kind: "msg", msg });
3932
+ prev = msg;
3933
+ }
3934
+ return out;
3935
+ }, [messages]);
3612
3936
  const composerProps = useMemo(
3613
3937
  () => ({
3614
3938
  onSend: async (body, opts) => {
@@ -3622,9 +3946,28 @@ function ConversationView({
3622
3946
  onCancelReply: () => setReplyingTo(null),
3623
3947
  ...labelFor ? { labelFor } : {},
3624
3948
  attachments,
3625
- keyboardOffset
3949
+ keyboardOffset,
3950
+ editingMessage,
3951
+ onCancelEdit: () => setEditingId(null),
3952
+ onSaveEdit: async (body) => {
3953
+ if (!editingId) return;
3954
+ await edit(editingId, body);
3955
+ setEditingId(null);
3956
+ }
3626
3957
  }),
3627
- [send, signalTyping, attachments, replyingTo, labelFor, keyboardOffset, onSent, conversationId]
3958
+ [
3959
+ send,
3960
+ signalTyping,
3961
+ attachments,
3962
+ replyingTo,
3963
+ labelFor,
3964
+ keyboardOffset,
3965
+ onSent,
3966
+ conversationId,
3967
+ editingMessage,
3968
+ editingId,
3969
+ edit
3970
+ ]
3628
3971
  );
3629
3972
  const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsx(
3630
3973
  ChatHeader,
@@ -3644,18 +3987,30 @@ function ConversationView({
3644
3987
  keyboardVerticalOffset: keyboardOffset,
3645
3988
  children: [
3646
3989
  headerNode,
3647
- /* @__PURE__ */ jsx(View, { style: styles20.listWrap, children: /* @__PURE__ */ jsx(
3648
- MessageList,
3649
- {
3650
- messages,
3651
- loading,
3652
- hasMore,
3653
- onLoadMore: loadMore,
3654
- renderItem,
3655
- ListHeader: /* @__PURE__ */ jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
3656
- ...emptyState !== void 0 ? { emptyState } : {}
3657
- }
3658
- ) }),
3990
+ /* @__PURE__ */ jsxs(View, { style: styles20.listWrap, children: [
3991
+ /* @__PURE__ */ jsx(
3992
+ MessageList,
3993
+ {
3994
+ messages,
3995
+ items: listItems,
3996
+ meId,
3997
+ loading,
3998
+ hasMore,
3999
+ onLoadMore: loadMore,
4000
+ renderItem,
4001
+ ListHeader: /* @__PURE__ */ jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
4002
+ ...emptyState !== void 0 ? { emptyState } : {}
4003
+ }
4004
+ ),
4005
+ editingId ? /* @__PURE__ */ jsx(
4006
+ Pressable,
4007
+ {
4008
+ style: styles20.editBackdrop,
4009
+ onPress: () => setEditingId(null),
4010
+ accessibilityLabel: "Cancel editing"
4011
+ }
4012
+ ) : null
4013
+ ] }),
3659
4014
  mentions ? /* @__PURE__ */ jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsx(MessageComposer, { ...composerProps }),
3660
4015
  attachments ? /* @__PURE__ */ jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
3661
4016
  threads && threadRoot ? /* @__PURE__ */ jsx(
@@ -3691,6 +4046,14 @@ var styles20 = StyleSheet.create({
3691
4046
  },
3692
4047
  listWrap: {
3693
4048
  flex: 1
4049
+ },
4050
+ editBackdrop: {
4051
+ ...StyleSheet.absoluteFillObject,
4052
+ backgroundColor: "rgba(0,0,0,0.45)",
4053
+ // Sit above message rows by default but below any row that
4054
+ // explicitly elevates itself (MessageRow bumps zIndex/elevation
4055
+ // when isEditing is true).
4056
+ zIndex: 5
3694
4057
  }
3695
4058
  });
3696
4059
  function UserPickerSheet({
@@ -4549,6 +4912,6 @@ function useAppStateLifecycle() {
4549
4912
  }, [poolse]);
4550
4913
  }
4551
4914
 
4552
- export { AttachmentPicker, AttachmentPreview, Avatar, ChatHeader, ConversationList, ConversationView, EditableMessageBubble, GroupDetailsSheet, ImageMosaic, MemberList, MentionInput, MessageActions, MessageBubble, MessageComposer, MessageList, MessageRow, PoolseIcon, PoolseInbox, PoolseLogo, PoolseTheme, primitives as Primitives, ReactionPicker, ReactionStrip, ThreadView, TypingIndicator, UploadQueueStrip, UserName, defaultDarkTheme, defaultLightTheme, useAppStateLifecycle, useDisplayName, usePoolseTheme };
4915
+ export { AttachmentPicker, AttachmentPreview, Avatar, ChatHeader, ConversationList, ConversationView, DaySeparator, GroupDetailsSheet, ImageMosaic, MemberList, MentionInput, MessageActions, MessageBubble, MessageComposer, MessageList, MessageRow, PoolseIcon, PoolseInbox, PoolseLogo, PoolseTheme, primitives as Primitives, ReactionPicker, ReactionStrip, ThreadView, TypingIndicator, UploadQueueStrip, UserName, defaultDarkTheme, defaultLightTheme, useAppStateLifecycle, useDisplayName, usePoolseTheme };
4553
4916
  //# sourceMappingURL=index.js.map
4554
4917
  //# sourceMappingURL=index.js.map