@poolse/react-native 2.0.13 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,7 +30,12 @@ function _interopNamespace(e) {
30
30
  var ImagePicker__namespace = /*#__PURE__*/_interopNamespace(ImagePicker);
31
31
  var DocumentPicker__namespace = /*#__PURE__*/_interopNamespace(DocumentPicker);
32
32
 
33
- // src/PoolseInbox.tsx
33
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
34
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
35
+ }) : x)(function(x) {
36
+ if (typeof require !== "undefined") return require.apply(this, arguments);
37
+ throw Error('Dynamic require of "' + x + '" is not supported');
38
+ });
34
39
 
35
40
  // src/theme/default.ts
36
41
  var lightColors = {
@@ -1330,6 +1335,51 @@ var styles6 = reactNative.StyleSheet.create({
1330
1335
  fontWeight: "500"
1331
1336
  }
1332
1337
  });
1338
+ function DaySeparator({ label }) {
1339
+ const theme = usePoolseTheme();
1340
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles7.wrap, children: /* @__PURE__ */ jsxRuntime.jsx(
1341
+ reactNative.View,
1342
+ {
1343
+ style: [
1344
+ styles7.chip,
1345
+ {
1346
+ backgroundColor: theme.colors.surface2,
1347
+ borderColor: theme.colors.border
1348
+ }
1349
+ ],
1350
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1351
+ reactNative.Text,
1352
+ {
1353
+ style: [
1354
+ styles7.label,
1355
+ {
1356
+ color: theme.colors.ink2,
1357
+ fontFamily: theme.type.fontBody
1358
+ }
1359
+ ],
1360
+ children: label
1361
+ }
1362
+ )
1363
+ }
1364
+ ) });
1365
+ }
1366
+ var styles7 = reactNative.StyleSheet.create({
1367
+ wrap: {
1368
+ alignItems: "center",
1369
+ marginVertical: 8
1370
+ },
1371
+ chip: {
1372
+ paddingHorizontal: 12,
1373
+ paddingVertical: 4,
1374
+ borderRadius: 999,
1375
+ borderWidth: reactNative.StyleSheet.hairlineWidth
1376
+ },
1377
+ label: {
1378
+ fontSize: 11,
1379
+ fontWeight: "600",
1380
+ letterSpacing: 0.3
1381
+ }
1382
+ });
1333
1383
  function UploadQueueStrip() {
1334
1384
  const theme = usePoolseTheme();
1335
1385
  const upload = useSharedUpload();
@@ -1340,8 +1390,8 @@ function UploadQueueStrip() {
1340
1390
  {
1341
1391
  horizontal: true,
1342
1392
  showsHorizontalScrollIndicator: false,
1343
- contentContainerStyle: styles7.scroll,
1344
- style: [styles7.scrollStyle, { marginBottom: 8 }],
1393
+ contentContainerStyle: styles8.scroll,
1394
+ style: [styles8.scrollStyle, { marginBottom: 8 }],
1345
1395
  children: upload.queue.map((item) => {
1346
1396
  const isError = item.status === "error";
1347
1397
  const isUploading = item.status === "uploading" || item.status === "pending";
@@ -1352,7 +1402,7 @@ function UploadQueueStrip() {
1352
1402
  if (item.status === "ready" || isError) upload.remove(item.localId);
1353
1403
  else upload.cancel(item.localId);
1354
1404
  };
1355
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.tileWrap, children: [
1405
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.tileWrap, children: [
1356
1406
  isImage && previewUri ? /* @__PURE__ */ jsxRuntime.jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsxRuntime.jsx(
1357
1407
  FileTile,
1358
1408
  {
@@ -1367,13 +1417,13 @@ function UploadQueueStrip() {
1367
1417
  {
1368
1418
  onPress: handleClose,
1369
1419
  hitSlop: 8,
1370
- style: [styles7.closeBtn, { backgroundColor: theme.colors.ink }],
1420
+ style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
1371
1421
  accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
1372
1422
  children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
1373
1423
  }
1374
1424
  ) : null,
1375
- isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles7.progressOverlay, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles7.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
1376
- isError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles7.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.errorText, { color: "#fff" }], children: "Failed" }) }) : null
1425
+ isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
1426
+ isError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.errorText, { color: "#fff" }], children: "Failed" }) }) : null
1377
1427
  ] }, item.localId);
1378
1428
  })
1379
1429
  }
@@ -1386,7 +1436,7 @@ function ImageTile({ uri, uploading, error }) {
1386
1436
  {
1387
1437
  source: { uri },
1388
1438
  style: [
1389
- styles7.imageTile,
1439
+ styles8.imageTile,
1390
1440
  {
1391
1441
  backgroundColor: theme.colors.surface2,
1392
1442
  borderColor: error ? theme.colors.error : "transparent",
@@ -1408,24 +1458,24 @@ function FileTile({
1408
1458
  reactNative.View,
1409
1459
  {
1410
1460
  style: [
1411
- styles7.fileTile,
1461
+ styles8.fileTile,
1412
1462
  {
1413
1463
  backgroundColor: theme.colors.surface2,
1414
1464
  borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
1415
1465
  }
1416
1466
  ],
1417
1467
  children: [
1418
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles7.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
1419
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles7.fileInfo, children: [
1468
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
1469
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.fileInfo, children: [
1420
1470
  /* @__PURE__ */ jsxRuntime.jsx(
1421
1471
  reactNative.Text,
1422
1472
  {
1423
- style: [styles7.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1473
+ style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1424
1474
  numberOfLines: 1,
1425
1475
  children: filename
1426
1476
  }
1427
1477
  ),
1428
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1478
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1429
1479
  ] })
1430
1480
  ]
1431
1481
  }
@@ -1439,7 +1489,7 @@ function formatBytes(n) {
1439
1489
  return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
1440
1490
  }
1441
1491
  var TILE = 64;
1442
- var styles7 = reactNative.StyleSheet.create({
1492
+ var styles8 = reactNative.StyleSheet.create({
1443
1493
  scrollStyle: {
1444
1494
  overflow: "visible"
1445
1495
  },
@@ -1539,6 +1589,22 @@ var styles7 = reactNative.StyleSheet.create({
1539
1589
  fontWeight: "700"
1540
1590
  }
1541
1591
  });
1592
+ var FALLBACK = {
1593
+ top: reactNative.Platform.OS === "ios" ? 50 : 24,
1594
+ bottom: reactNative.Platform.OS === "ios" ? 34 : 0,
1595
+ left: 0,
1596
+ right: 0
1597
+ };
1598
+ var realHook = null;
1599
+ try {
1600
+ const mod = __require("react-native-safe-area-context");
1601
+ if (mod && typeof mod.useSafeAreaInsets === "function") {
1602
+ realHook = mod.useSafeAreaInsets;
1603
+ }
1604
+ } catch {
1605
+ }
1606
+ var useFallback = () => FALLBACK;
1607
+ var useSafeInsets = realHook ?? useFallback;
1542
1608
  var MessageComposer = react.forwardRef(
1543
1609
  function MessageComposer2({
1544
1610
  onSend,
@@ -1550,13 +1616,32 @@ var MessageComposer = react.forwardRef(
1550
1616
  labelFor,
1551
1617
  attachments = true,
1552
1618
  onAttachPress,
1553
- keyboardOffset: _keyboardOffset = 0
1619
+ keyboardOffset: _keyboardOffset = 0,
1620
+ editingMessage = null,
1621
+ onCancelEdit,
1622
+ onSaveEdit
1554
1623
  }, ref) {
1555
1624
  const theme = usePoolseTheme();
1556
1625
  const inputRef = react.useRef(null);
1557
1626
  const [value, setValue] = react.useState("");
1558
1627
  const [sending, setSending] = react.useState(false);
1628
+ const [keyboardOpen, setKeyboardOpen] = react.useState(false);
1559
1629
  const upload = useSharedUpload();
1630
+ const insets = useSafeInsets();
1631
+ react.useEffect(() => {
1632
+ const show = reactNative.Keyboard.addListener(
1633
+ reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
1634
+ () => setKeyboardOpen(true)
1635
+ );
1636
+ const hide = reactNative.Keyboard.addListener(
1637
+ reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
1638
+ () => setKeyboardOpen(false)
1639
+ );
1640
+ return () => {
1641
+ show.remove();
1642
+ hide.remove();
1643
+ };
1644
+ }, []);
1560
1645
  react.useImperativeHandle(
1561
1646
  ref,
1562
1647
  () => ({
@@ -1575,36 +1660,65 @@ var MessageComposer = react.forwardRef(
1575
1660
  const isUploading = upload.queue.some(
1576
1661
  (item) => item.status === "uploading" || item.status === "pending"
1577
1662
  );
1578
- const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 && readyAttachmentIds.length === 0;
1663
+ const previousDraftRef = react.useRef(null);
1664
+ const editingIdRef = react.useRef(null);
1665
+ react.useEffect(() => {
1666
+ const id = editingMessage?.id ?? null;
1667
+ if (id === editingIdRef.current) return;
1668
+ if (id) {
1669
+ previousDraftRef.current = value;
1670
+ setValue(editingMessage?.body ?? "");
1671
+ inputRef.current?.focus();
1672
+ } else {
1673
+ setValue(previousDraftRef.current ?? "");
1674
+ previousDraftRef.current = null;
1675
+ }
1676
+ editingIdRef.current = id;
1677
+ }, [editingMessage?.id, editingMessage?.body]);
1678
+ const isEditing = !!editingMessage;
1679
+ const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
1680
+ const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
1579
1681
  const handleSend = async () => {
1580
1682
  if (submitDisabled) return;
1581
1683
  const body = value.trim();
1582
- const replyId = replyingTo?.id;
1583
- const attachmentIds = readyAttachmentIds;
1584
- setValue("");
1585
- upload.reset();
1586
1684
  setSending(true);
1587
1685
  try {
1588
- await onSend(body, {
1589
- ...replyId ? { quoted_message_id: replyId } : {},
1590
- ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1591
- });
1686
+ if (isEditing && onSaveEdit) {
1687
+ await onSaveEdit(body);
1688
+ } else {
1689
+ const replyId = replyingTo?.id;
1690
+ const attachmentIds = readyAttachmentIds;
1691
+ setValue("");
1692
+ upload.reset();
1693
+ await onSend(body, {
1694
+ ...replyId ? { quoted_message_id: replyId } : {},
1695
+ ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1696
+ });
1697
+ }
1592
1698
  } finally {
1593
1699
  setSending(false);
1594
1700
  }
1595
1701
  };
1702
+ const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
1596
1703
  return /* @__PURE__ */ jsxRuntime.jsxs(
1597
1704
  reactNative.View,
1598
1705
  {
1599
1706
  style: [
1600
- styles8.wrap,
1707
+ styles9.wrap,
1601
1708
  {
1602
1709
  backgroundColor: theme.colors.surface,
1603
- borderTopColor: theme.colors.border
1710
+ borderTopColor: theme.colors.border,
1711
+ paddingBottom
1604
1712
  }
1605
1713
  ],
1606
1714
  children: [
1607
- replyingTo ? /* @__PURE__ */ jsxRuntime.jsx(
1715
+ isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
1716
+ EditChip,
1717
+ {
1718
+ message: editingMessage,
1719
+ ...onCancelEdit ? { onCancel: onCancelEdit } : {}
1720
+ }
1721
+ ) : replyingTo ? /* @__PURE__ */ jsxRuntime.jsx(
1608
1722
  ReplyChip,
1609
1723
  {
1610
1724
  message: replyingTo,
@@ -1612,14 +1726,14 @@ var MessageComposer = react.forwardRef(
1612
1726
  ...onCancelReply ? { onCancel: onCancelReply } : {}
1613
1727
  }
1614
1728
  ) : null,
1615
- attachments ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
1616
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.row, children: [
1729
+ attachments && !isEditing ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
1730
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles9.row, children: [
1617
1731
  attachments && onAttachPress ? /* @__PURE__ */ jsxRuntime.jsx(
1618
1732
  reactNative.Pressable,
1619
1733
  {
1620
1734
  onPress: onAttachPress,
1621
1735
  hitSlop: 8,
1622
- style: styles8.iconBtn,
1736
+ style: styles9.iconBtn,
1623
1737
  accessibilityLabel: "Attach file",
1624
1738
  children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
1625
1739
  }
@@ -1638,7 +1752,7 @@ var MessageComposer = react.forwardRef(
1638
1752
  multiline: true,
1639
1753
  editable: !disabled,
1640
1754
  style: [
1641
- styles8.input,
1755
+ styles9.input,
1642
1756
  {
1643
1757
  color: theme.colors.ink,
1644
1758
  backgroundColor: theme.colors.surface2,
@@ -1655,7 +1769,7 @@ var MessageComposer = react.forwardRef(
1655
1769
  onPress: handleSend,
1656
1770
  disabled: submitDisabled,
1657
1771
  style: [
1658
- styles8.sendBtn,
1772
+ styles9.sendBtn,
1659
1773
  {
1660
1774
  backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
1661
1775
  opacity: submitDisabled ? 0.6 : 1
@@ -1671,6 +1785,28 @@ var MessageComposer = react.forwardRef(
1671
1785
  );
1672
1786
  }
1673
1787
  );
1788
+ function EditChip({ message, onCancel }) {
1789
+ const theme = usePoolseTheme();
1790
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1791
+ reactNative.View,
1792
+ {
1793
+ style: [
1794
+ styles9.replyChip,
1795
+ {
1796
+ backgroundColor: theme.colors.surface2,
1797
+ borderLeftColor: theme.colors.brand
1798
+ }
1799
+ ],
1800
+ children: [
1801
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
1802
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
1803
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1804
+ ] }),
1805
+ onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1806
+ ]
1807
+ }
1808
+ );
1809
+ }
1674
1810
  function ReplyChip({
1675
1811
  message,
1676
1812
  labelFor,
@@ -1683,7 +1819,7 @@ function ReplyChip({
1683
1819
  reactNative.View,
1684
1820
  {
1685
1821
  style: [
1686
- styles8.replyChip,
1822
+ styles9.replyChip,
1687
1823
  {
1688
1824
  backgroundColor: theme.colors.surface2,
1689
1825
  borderLeftColor: theme.colors.brand
@@ -1691,27 +1827,26 @@ function ReplyChip({
1691
1827
  ],
1692
1828
  children: [
1693
1829
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
1694
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles8.replyName, { color: theme.colors.brand }], children: [
1830
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
1695
1831
  "Replying to ",
1696
1832
  senderName
1697
1833
  ] }),
1698
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1834
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1699
1835
  ] }),
1700
- onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles8.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1836
+ onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1701
1837
  ]
1702
1838
  }
1703
1839
  );
1704
1840
  }
1705
- var styles8 = reactNative.StyleSheet.create({
1841
+ var styles9 = reactNative.StyleSheet.create({
1706
1842
  wrap: {
1707
1843
  borderTopWidth: reactNative.StyleSheet.hairlineWidth,
1708
1844
  paddingTop: 8,
1709
- paddingBottom: 10,
1710
1845
  paddingHorizontal: 12
1711
- // Note: no safe-area inset here on purpose. Wrap your chat
1712
- // surface in <SafeAreaView> (or <SafeAreaView edges={['bottom']}>)
1713
- // at the app root so the home-indicator clearance is handled
1714
- // once. Doing it here too produces double padding.
1846
+ // paddingBottom is applied inline — it follows the keyboard state
1847
+ // (10 px while open, max(10, safeBottomInset) while closed) so the
1848
+ // send/attach buttons stay clear of the home indicator without
1849
+ // double-padding above the keyboard.
1715
1850
  },
1716
1851
  row: {
1717
1852
  flexDirection: "row",
@@ -1800,11 +1935,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1800
1935
  transparent: true,
1801
1936
  animationType: "slide",
1802
1937
  onRequestClose: () => setPickerOpen(false),
1803
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles9.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
1938
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
1804
1939
  reactNative.View,
1805
1940
  {
1806
1941
  style: [
1807
- styles9.sheet,
1942
+ styles10.sheet,
1808
1943
  {
1809
1944
  backgroundColor: theme.colors.surface,
1810
1945
  borderTopLeftRadius: theme.radii.lg,
@@ -1815,7 +1950,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1815
1950
  /* @__PURE__ */ jsxRuntime.jsx(
1816
1951
  reactNative.Text,
1817
1952
  {
1818
- style: [styles9.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1953
+ style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1819
1954
  children: "Mention a member"
1820
1955
  }
1821
1956
  ),
@@ -1846,12 +1981,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1846
1981
  }
1847
1982
  function MentionRow({ member, onPress }) {
1848
1983
  const theme = usePoolseTheme();
1849
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: styles9.row, children: [
1984
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: styles10.row, children: [
1850
1985
  /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
1851
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
1986
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
1852
1987
  ] });
1853
1988
  }
1854
- var styles9 = reactNative.StyleSheet.create({
1989
+ var styles10 = reactNative.StyleSheet.create({
1855
1990
  backdrop: {
1856
1991
  flex: 1,
1857
1992
  backgroundColor: "rgba(0,0,0,0.35)",
@@ -1878,138 +2013,134 @@ var styles9 = reactNative.StyleSheet.create({
1878
2013
  fontSize: 14
1879
2014
  }
1880
2015
  });
2016
+ var AT_BOTTOM_THRESHOLD = 32;
1881
2017
  function MessageList({
1882
2018
  messages,
2019
+ items,
1883
2020
  loading = false,
1884
2021
  hasMore = false,
1885
2022
  onLoadMore,
1886
2023
  renderItem,
1887
2024
  ListHeader = null,
1888
2025
  ListFooter = null,
1889
- emptyState
2026
+ emptyState,
2027
+ meId = null
1890
2028
  }) {
1891
2029
  const theme = usePoolseTheme();
1892
- const data = [...messages].reverse();
2030
+ const flatListRef = react.useRef(null);
2031
+ const [atBottom, setAtBottom] = react.useState(true);
2032
+ const [newCount, setNewCount] = react.useState(0);
2033
+ const lastTailIdRef = react.useRef(null);
2034
+ const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
2035
+ const data = [...sourceItems].reverse();
2036
+ react.useEffect(() => {
2037
+ const tail = messages[messages.length - 1];
2038
+ if (!tail) return;
2039
+ const tailId = tail.id;
2040
+ if (tailId === lastTailIdRef.current) return;
2041
+ const isFirstSet = lastTailIdRef.current === null;
2042
+ lastTailIdRef.current = tailId;
2043
+ if (atBottom || isFirstSet) {
2044
+ setNewCount(0);
2045
+ return;
2046
+ }
2047
+ if (meId !== null && tail.sender_id === meId) return;
2048
+ setNewCount((n) => n + 1);
2049
+ }, [messages, atBottom, meId]);
2050
+ const handleScroll = react.useCallback((e) => {
2051
+ const y = e.nativeEvent.contentOffset.y;
2052
+ const next = y <= AT_BOTTOM_THRESHOLD;
2053
+ setAtBottom((prev) => prev === next ? prev : next);
2054
+ if (next) setNewCount(0);
2055
+ }, []);
2056
+ const scrollToBottom = react.useCallback(() => {
2057
+ flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
2058
+ setNewCount(0);
2059
+ }, []);
1893
2060
  const handleRender = react.useCallback(
1894
- ({ item, index }) => renderItem(item, messages.length - 1 - index),
1895
- [renderItem, messages.length]
2061
+ ({ item, index }) => {
2062
+ if (item.kind === "separator") return item.node;
2063
+ const sourceIndex = data.length - 1 - index;
2064
+ return renderItem(item.msg, sourceIndex);
2065
+ },
2066
+ [renderItem, data.length]
1896
2067
  );
1897
2068
  if (loading && messages.length === 0) {
1898
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles10.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
2069
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
1899
2070
  }
1900
2071
  if (messages.length === 0) {
1901
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles10.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 });
2072
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.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 });
1902
2073
  }
1903
- return /* @__PURE__ */ jsxRuntime.jsx(
1904
- reactNative.FlatList,
1905
- {
1906
- inverted: true,
1907
- data,
1908
- keyExtractor: (item) => item.id,
1909
- renderItem: handleRender,
1910
- onEndReached: hasMore ? onLoadMore : void 0,
1911
- onEndReachedThreshold: 0.3,
1912
- ListHeaderComponent: ListHeader,
1913
- ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
1914
- hasMore && loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
1915
- ListFooter
1916
- ] }),
1917
- style: { backgroundColor: theme.colors.paper },
1918
- contentContainerStyle: { paddingVertical: 8 }
1919
- }
1920
- );
2074
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.root, children: [
2075
+ /* @__PURE__ */ jsxRuntime.jsx(
2076
+ reactNative.FlatList,
2077
+ {
2078
+ ref: flatListRef,
2079
+ inverted: true,
2080
+ data,
2081
+ keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
2082
+ renderItem: handleRender,
2083
+ onEndReached: hasMore ? onLoadMore : void 0,
2084
+ onEndReachedThreshold: 0.3,
2085
+ onScroll: handleScroll,
2086
+ scrollEventThrottle: 32,
2087
+ ListHeaderComponent: ListHeader,
2088
+ ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
2089
+ hasMore && loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
2090
+ ListFooter
2091
+ ] }),
2092
+ style: { backgroundColor: theme.colors.paper },
2093
+ contentContainerStyle: { paddingVertical: 8 }
2094
+ }
2095
+ ),
2096
+ !atBottom && newCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
2097
+ reactNative.Pressable,
2098
+ {
2099
+ onPress: scrollToBottom,
2100
+ style: [
2101
+ styles11.jumpButton,
2102
+ {
2103
+ backgroundColor: theme.colors.brand
2104
+ },
2105
+ theme.shadows.sm
2106
+ ],
2107
+ accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
2108
+ children: [
2109
+ /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
2110
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
2111
+ newCount,
2112
+ " new ",
2113
+ newCount === 1 ? "message" : "messages"
2114
+ ] })
2115
+ ]
2116
+ }
2117
+ ) : null
2118
+ ] });
1921
2119
  }
1922
- var styles10 = reactNative.StyleSheet.create({
2120
+ var styles11 = reactNative.StyleSheet.create({
2121
+ root: {
2122
+ flex: 1
2123
+ },
1923
2124
  placeholder: {
1924
2125
  flex: 1,
1925
2126
  alignItems: "center",
1926
2127
  justifyContent: "center",
1927
2128
  padding: 24
1928
- }
1929
- });
1930
- function EditableMessageBubble({
1931
- message,
1932
- currentUserId,
1933
- onCancel,
1934
- onSave
1935
- }) {
1936
- const theme = usePoolseTheme();
1937
- const isSelf = currentUserId !== null && message.sender_id === currentUserId;
1938
- const [value, setValue] = react.useState(message.body ?? "");
1939
- const [saving, setSaving] = react.useState(false);
1940
- const submit = async () => {
1941
- if (saving || !value.trim()) return;
1942
- setSaving(true);
1943
- try {
1944
- await onSave(value.trim());
1945
- } finally {
1946
- setSaving(false);
1947
- }
1948
- };
1949
- const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
1950
- const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
1951
- return /* @__PURE__ */ jsxRuntime.jsx(
1952
- reactNative.View,
1953
- {
1954
- style: [
1955
- styles11.wrap,
1956
- {
1957
- alignSelf: isSelf ? "flex-end" : "flex-start",
1958
- maxWidth: "80%"
1959
- }
1960
- ],
1961
- children: /* @__PURE__ */ jsxRuntime.jsxs(
1962
- reactNative.View,
1963
- {
1964
- style: [
1965
- styles11.bubble,
1966
- {
1967
- backgroundColor: bg,
1968
- borderRadius: theme.radii.bubble,
1969
- paddingHorizontal: theme.spacing.md,
1970
- paddingVertical: theme.spacing.sm
1971
- },
1972
- theme.shadows.sm
1973
- ],
1974
- children: [
1975
- /* @__PURE__ */ jsxRuntime.jsx(
1976
- reactNative.TextInput,
1977
- {
1978
- value,
1979
- onChangeText: setValue,
1980
- multiline: true,
1981
- autoFocus: true,
1982
- style: {
1983
- color: ink,
1984
- fontFamily: theme.type.fontBody,
1985
- fontSize: theme.type.bodySize,
1986
- minHeight: 24
1987
- }
1988
- }
1989
- ),
1990
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.actions, children: [
1991
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
1992
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
1993
- ] })
1994
- ]
1995
- }
1996
- )
1997
- }
1998
- );
1999
- }
2000
- var styles11 = reactNative.StyleSheet.create({
2001
- wrap: {
2002
- marginBottom: 4
2003
2129
  },
2004
- bubble: {},
2005
- actions: {
2130
+ jumpButton: {
2131
+ position: "absolute",
2132
+ alignSelf: "center",
2133
+ bottom: 12,
2006
2134
  flexDirection: "row",
2007
- justifyContent: "flex-end",
2008
- gap: 12,
2009
- marginTop: 6
2135
+ alignItems: "center",
2136
+ gap: 6,
2137
+ paddingHorizontal: 14,
2138
+ paddingVertical: 8,
2139
+ borderRadius: 999
2010
2140
  },
2011
- btn: {
2012
- fontSize: 13
2141
+ jumpText: {
2142
+ fontSize: 13,
2143
+ fontWeight: "600"
2013
2144
  }
2014
2145
  });
2015
2146
  var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
@@ -2472,6 +2603,77 @@ var styles14 = reactNative.StyleSheet.create({
2472
2603
  borderRadius: 20
2473
2604
  }
2474
2605
  });
2606
+ var MarkdownModule;
2607
+ var warned = false;
2608
+ function loadMarkdown() {
2609
+ if (MarkdownModule === void 0) {
2610
+ try {
2611
+ MarkdownModule = __require("react-native-markdown-display");
2612
+ } catch {
2613
+ MarkdownModule = null;
2614
+ }
2615
+ }
2616
+ const fn = MarkdownModule?.default;
2617
+ if (!fn) {
2618
+ if (!warned) {
2619
+ warned = true;
2620
+ console.warn(
2621
+ "[@poolse/react-native] Markdown rendering requires `react-native-markdown-display`. Install it to enable bold/italic/code/links in message bubbles. Falling back to plain text."
2622
+ );
2623
+ }
2624
+ return null;
2625
+ }
2626
+ return fn;
2627
+ }
2628
+ function renderMarkdown(body, args) {
2629
+ const Markdown = loadMarkdown();
2630
+ if (!Markdown) return null;
2631
+ const baseText = {
2632
+ color: args.textColor,
2633
+ fontFamily: args.fontFamily,
2634
+ fontSize: args.fontSize,
2635
+ lineHeight: args.lineHeight
2636
+ };
2637
+ const style = {
2638
+ body: baseText,
2639
+ paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
2640
+ text: baseText,
2641
+ strong: { ...baseText, fontWeight: "700" },
2642
+ em: { ...baseText, fontStyle: "italic" },
2643
+ s: { ...baseText, textDecorationLine: "line-through" },
2644
+ link: { color: args.linkColor, textDecorationLine: "underline" },
2645
+ code_inline: {
2646
+ ...baseText,
2647
+ backgroundColor: "rgba(127,127,127,0.18)",
2648
+ borderRadius: 4,
2649
+ paddingHorizontal: 4
2650
+ },
2651
+ code_block: {
2652
+ ...baseText,
2653
+ backgroundColor: "rgba(127,127,127,0.18)",
2654
+ padding: 8,
2655
+ borderRadius: 6
2656
+ },
2657
+ fence: {
2658
+ ...baseText,
2659
+ backgroundColor: "rgba(127,127,127,0.18)",
2660
+ padding: 8,
2661
+ borderRadius: 6
2662
+ },
2663
+ bullet_list: { marginTop: 2, marginBottom: 2 },
2664
+ ordered_list: { marginTop: 2, marginBottom: 2 },
2665
+ list_item: baseText,
2666
+ blockquote: {
2667
+ ...baseText,
2668
+ borderLeftWidth: 3,
2669
+ borderLeftColor: args.linkColor,
2670
+ paddingLeft: 8,
2671
+ marginVertical: 4,
2672
+ opacity: 0.85
2673
+ }
2674
+ };
2675
+ return react.createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
2676
+ }
2475
2677
  function MessageBubble({
2476
2678
  message,
2477
2679
  currentUserId,
@@ -2482,7 +2684,8 @@ function MessageBubble({
2482
2684
  maxBodyLength = 0,
2483
2685
  showSenderName = false,
2484
2686
  actionsTrigger,
2485
- showAttachments = true
2687
+ showAttachments = true,
2688
+ markdown = true
2486
2689
  }) {
2487
2690
  const theme = usePoolseTheme();
2488
2691
  const { width: screenWidth } = reactNative.useWindowDimensions();
@@ -2567,22 +2770,53 @@ function MessageBubble({
2567
2770
  ) : null,
2568
2771
  renderAttachments && images.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
2569
2772
  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, maxWidth: contentMaxWidth }, att.id)) }) : null,
2570
- body.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2571
- reactNative.Text,
2572
- {
2573
- style: [
2574
- styles15.body,
2773
+ body.length > 0 ? (() => {
2774
+ if (isDeleted) {
2775
+ return /* @__PURE__ */ jsxRuntime.jsx(
2776
+ reactNative.Text,
2575
2777
  {
2576
- color: isDeleted ? theme.colors.ink3 : textColor,
2577
- fontFamily: theme.type.fontBody,
2578
- fontSize: theme.type.bodySize,
2579
- lineHeight: theme.type.lineHeight,
2580
- fontStyle: isDeleted ? "italic" : "normal"
2778
+ style: [
2779
+ styles15.body,
2780
+ {
2781
+ color: theme.colors.ink3,
2782
+ fontFamily: theme.type.fontBody,
2783
+ fontSize: theme.type.bodySize,
2784
+ lineHeight: theme.type.lineHeight,
2785
+ fontStyle: "italic"
2786
+ }
2787
+ ],
2788
+ children: "Message deleted"
2581
2789
  }
2582
- ],
2583
- children: isDeleted ? "Message deleted" : truncated
2790
+ );
2584
2791
  }
2585
- ) : null,
2792
+ const md = markdown ? renderMarkdown(truncated, {
2793
+ textColor,
2794
+ linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
2795
+ fontFamily: theme.type.fontBody,
2796
+ fontSize: theme.type.bodySize,
2797
+ lineHeight: theme.type.lineHeight,
2798
+ onLinkPress: (url) => {
2799
+ void reactNative.Linking.openURL(url).catch(() => void 0);
2800
+ return false;
2801
+ }
2802
+ }) : null;
2803
+ if (md) return md;
2804
+ return /* @__PURE__ */ jsxRuntime.jsx(
2805
+ reactNative.Text,
2806
+ {
2807
+ style: [
2808
+ styles15.body,
2809
+ {
2810
+ color: textColor,
2811
+ fontFamily: theme.type.fontBody,
2812
+ fontSize: theme.type.bodySize,
2813
+ lineHeight: theme.type.lineHeight
2814
+ }
2815
+ ],
2816
+ children: truncated
2817
+ }
2818
+ );
2819
+ })() : null,
2586
2820
  maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
2587
2821
  reactNative.Text,
2588
2822
  {
@@ -2675,9 +2909,9 @@ function formatTime(iso) {
2675
2909
  try {
2676
2910
  const d = new Date(iso);
2677
2911
  const now = /* @__PURE__ */ new Date();
2678
- const sameDay = isSameDay(d, now);
2912
+ const sameDay2 = isSameDay(d, now);
2679
2913
  const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
2680
- if (sameDay) return time;
2914
+ if (sameDay2) return time;
2681
2915
  const yesterday = new Date(now);
2682
2916
  yesterday.setDate(now.getDate() - 1);
2683
2917
  if (isSameDay(d, yesterday)) return `Yesterday ${time}`;
@@ -2785,12 +3019,26 @@ function ReactionStrip({
2785
3019
  styles16.chip,
2786
3020
  {
2787
3021
  backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
2788
- borderColor: meReacted ? theme.colors.brand : theme.colors.border
3022
+ borderColor: meReacted ? theme.colors.brand : theme.colors.border,
3023
+ borderWidth: meReacted ? 1.5 : 1,
3024
+ transform: meReacted ? [{ scale: 1.04 }] : void 0
2789
3025
  }
2790
3026
  ],
2791
3027
  children: [
2792
3028
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.emoji, children: emoji }),
2793
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles16.count, { color: theme.colors.ink2 }], children: userArr.length })
3029
+ /* @__PURE__ */ jsxRuntime.jsx(
3030
+ reactNative.Text,
3031
+ {
3032
+ style: [
3033
+ styles16.count,
3034
+ {
3035
+ color: meReacted ? theme.colors.brand : theme.colors.ink2,
3036
+ fontWeight: meReacted ? "700" : "600"
3037
+ }
3038
+ ],
3039
+ children: userArr.length
3040
+ }
3041
+ )
2794
3042
  ]
2795
3043
  },
2796
3044
  emoji
@@ -2843,7 +3091,6 @@ var styles16 = reactNative.StyleSheet.create({
2843
3091
  paddingHorizontal: 9,
2844
3092
  paddingVertical: 4,
2845
3093
  borderRadius: 14,
2846
- borderWidth: 1,
2847
3094
  gap: 5,
2848
3095
  minHeight: 26,
2849
3096
  // Subtle elevation so chips lift off the surface
@@ -2859,7 +3106,6 @@ var styles16 = reactNative.StyleSheet.create({
2859
3106
  },
2860
3107
  count: {
2861
3108
  fontSize: 12,
2862
- fontWeight: "600",
2863
3109
  fontVariant: ["tabular-nums"]
2864
3110
  },
2865
3111
  backdrop: {
@@ -2897,11 +3143,10 @@ function MessageRow({
2897
3143
  actions = true,
2898
3144
  threads = true,
2899
3145
  quotations = true,
3146
+ markdown = true,
2900
3147
  readState,
2901
- editing = false,
3148
+ isEditing = false,
2902
3149
  onStartEdit,
2903
- onCancelEdit,
2904
- onSaveEdit,
2905
3150
  onDelete,
2906
3151
  onOpenThread,
2907
3152
  onQuote,
@@ -2922,7 +3167,7 @@ function MessageRow({
2922
3167
  ...msg.reactions ? { initialReactions: msg.reactions } : {}
2923
3168
  });
2924
3169
  const translateX = react.useRef(new reactNative.Animated.Value(0)).current;
2925
- const swipeEnabled = quotations && !!onQuote && !editing;
3170
+ const swipeEnabled = quotations && !!onQuote && !isEditing;
2926
3171
  const panResponder = react.useMemo(
2927
3172
  () => reactNative.PanResponder.create({
2928
3173
  // Tap / long-press still go to the inner Pressable.
@@ -2997,137 +3242,149 @@ function MessageRow({
2997
3242
  const rowSender = react$1.useUser(showAvatarSlot ? msg.sender_external_id : null);
2998
3243
  const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
2999
3244
  const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
3000
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles17.row, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: [
3001
- showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
3002
- Avatar,
3003
- {
3004
- src: avatarUrl ?? null,
3005
- name: senderLabel,
3006
- seed: msg.sender_external_id ?? null,
3007
- size: "sm"
3008
- }
3009
- ) : null }) : null,
3010
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.bubbleCol, children: [
3011
- swipeEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
3012
- reactNative.Animated.View,
3013
- {
3014
- pointerEvents: "none",
3015
- style: [
3016
- styles17.replyHint,
3017
- isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
3018
- {
3019
- opacity: replyIconOpacity,
3020
- transform: [{ scale: replyIconScale }],
3021
- backgroundColor: theme.colors.surface2
3022
- }
3023
- ],
3024
- children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
3025
- }
3026
- ) : null,
3027
- /* @__PURE__ */ jsxRuntime.jsx(
3028
- reactNative.Animated.View,
3029
- {
3030
- ...swipeEnabled ? panResponder.panHandlers : {},
3031
- style: { transform: [{ translateX }] },
3032
- children: /* @__PURE__ */ jsxRuntime.jsx(
3033
- reactNative.Pressable,
3245
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3246
+ reactNative.View,
3247
+ {
3248
+ style: [
3249
+ styles17.row,
3250
+ { alignSelf: isSelf ? "flex-end" : "flex-start" },
3251
+ // Lift this row above the dim backdrop the parent renders
3252
+ // during edit mode. zIndex covers iOS / web; elevation does
3253
+ // the equivalent on Android.
3254
+ isEditing ? styles17.rowEditing : null
3255
+ ],
3256
+ children: [
3257
+ showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
3258
+ Avatar,
3259
+ {
3260
+ src: avatarUrl ?? null,
3261
+ name: senderLabel,
3262
+ seed: msg.sender_external_id ?? null,
3263
+ size: "sm"
3264
+ }
3265
+ ) : null }) : null,
3266
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.bubbleCol, children: [
3267
+ swipeEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
3268
+ reactNative.Animated.View,
3034
3269
  {
3035
- onLongPress: () => {
3036
- if (!actions) return;
3037
- setActionsOpen(true);
3038
- },
3039
- accessibilityRole: "button",
3040
- accessibilityLabel: "Message actions",
3041
- children: editing ? /* @__PURE__ */ jsxRuntime.jsx(
3042
- EditableMessageBubble,
3270
+ pointerEvents: "none",
3271
+ style: [
3272
+ styles17.replyHint,
3273
+ isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
3043
3274
  {
3044
- message: msg,
3045
- currentUserId: meId,
3046
- onCancel: onCancelEdit ?? (() => void 0),
3047
- onSave: onSaveEdit ?? (() => void 0)
3275
+ opacity: replyIconOpacity,
3276
+ transform: [{ scale: replyIconScale }],
3277
+ backgroundColor: theme.colors.surface2
3048
3278
  }
3049
- ) : /* @__PURE__ */ jsxRuntime.jsx(
3050
- MessageBubble,
3279
+ ],
3280
+ children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
3281
+ }
3282
+ ) : null,
3283
+ /* @__PURE__ */ jsxRuntime.jsx(
3284
+ reactNative.Animated.View,
3285
+ {
3286
+ ...swipeEnabled ? panResponder.panHandlers : {},
3287
+ style: { transform: [{ translateX }] },
3288
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3289
+ reactNative.Pressable,
3051
3290
  {
3052
- message: msg,
3053
- currentUserId: meId,
3054
- ...readState ? { readState } : {},
3055
- ...labelFor ? { labelFor } : {},
3056
- ...onQuotedClick ? { onQuotedClick } : {},
3057
- groupPosition,
3058
- maxBodyLength,
3059
- showSenderName,
3060
- showAttachments: attachments,
3061
- actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
3062
- PoolseIcon,
3291
+ onLongPress: () => {
3292
+ if (!actions) return;
3293
+ setActionsOpen(true);
3294
+ },
3295
+ accessibilityRole: "button",
3296
+ accessibilityLabel: "Message actions",
3297
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3298
+ MessageBubble,
3063
3299
  {
3064
- name: "chevron-down",
3065
- size: 14,
3066
- color: isSelf ? theme.colors.onBrand : theme.colors.ink3
3300
+ message: msg,
3301
+ currentUserId: meId,
3302
+ ...readState ? { readState } : {},
3303
+ ...labelFor ? { labelFor } : {},
3304
+ ...onQuotedClick ? { onQuotedClick } : {},
3305
+ groupPosition,
3306
+ maxBodyLength,
3307
+ showSenderName,
3308
+ showAttachments: attachments,
3309
+ markdown,
3310
+ actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
3311
+ PoolseIcon,
3312
+ {
3313
+ name: "chevron-down",
3314
+ size: 14,
3315
+ color: isSelf ? theme.colors.onBrand : theme.colors.ink3
3316
+ }
3317
+ ) }) : null
3067
3318
  }
3068
- ) }) : null
3319
+ )
3069
3320
  }
3070
3321
  )
3071
3322
  }
3072
- )
3073
- }
3074
- ),
3075
- threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
3076
- "\u{1F4AC} ",
3077
- msg.reply_count,
3078
- " ",
3079
- msg.reply_count === 1 ? "reply" : "replies"
3080
- ] }) }) : null,
3081
- reactions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsxRuntime.jsx(
3082
- ReactionStrip,
3083
- {
3084
- messageId: msg.id,
3085
- conversationId: msg.conversation_id,
3086
- meId,
3087
- ...msg.reactions ? { initialReactions: msg.reactions } : {}
3088
- }
3089
- ) }) : null
3090
- ] }),
3091
- actions && actionsOpen ? /* @__PURE__ */ jsxRuntime.jsx(
3092
- MessageActions,
3093
- {
3094
- visible: actionsOpen,
3095
- onClose: () => setActionsOpen(false),
3096
- isSelf,
3097
- ...reactions ? {
3098
- // Inline quick-react row at the top of the sheet —
3099
- // one tap = react + close, no second modal needed.
3100
- onPickReaction: (emoji) => {
3101
- void reactionsApi.addReaction(emoji);
3102
- }
3103
- } : {},
3104
- ...onStartEdit ? {
3105
- onEdit: () => {
3106
- setActionsOpen(false);
3107
- onStartEdit();
3108
- }
3109
- } : {},
3110
- ...onDelete ? {
3111
- onDelete: () => {
3112
- setActionsOpen(false);
3113
- onDelete();
3114
- }
3115
- } : {},
3116
- ...onOpenThread ? {
3117
- onReplyInThread: () => {
3118
- setActionsOpen(false);
3119
- onOpenThread();
3120
- }
3121
- } : {},
3122
- ...quotations && onQuote ? {
3123
- onQuote: () => {
3124
- setActionsOpen(false);
3125
- onQuote();
3323
+ ),
3324
+ threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
3325
+ "\u{1F4AC} ",
3326
+ msg.reply_count,
3327
+ " ",
3328
+ msg.reply_count === 1 ? "reply" : "replies"
3329
+ ] }) }) : null,
3330
+ reactions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsxRuntime.jsx(
3331
+ ReactionStrip,
3332
+ {
3333
+ messageId: msg.id,
3334
+ conversationId: msg.conversation_id,
3335
+ meId,
3336
+ ...msg.reactions ? { initialReactions: msg.reactions } : {}
3337
+ }
3338
+ ) }) : null
3339
+ ] }),
3340
+ actions && actionsOpen ? /* @__PURE__ */ jsxRuntime.jsx(
3341
+ MessageActions,
3342
+ {
3343
+ visible: actionsOpen,
3344
+ onClose: () => setActionsOpen(false),
3345
+ isSelf,
3346
+ ...reactions ? {
3347
+ // Inline quick-react row at the top of the sheet —
3348
+ // one tap = react + close, no second modal needed.
3349
+ onPickReaction: (emoji) => {
3350
+ void reactionsApi.addReaction(emoji);
3351
+ }
3352
+ } : {},
3353
+ ...msg.body && !msg.deleted_at ? {
3354
+ onCopy: () => {
3355
+ setActionsOpen(false);
3356
+ void copyToClipboard(msg.body ?? "");
3357
+ }
3358
+ } : {},
3359
+ ...onStartEdit ? {
3360
+ onEdit: () => {
3361
+ setActionsOpen(false);
3362
+ onStartEdit();
3363
+ }
3364
+ } : {},
3365
+ ...onDelete ? {
3366
+ onDelete: () => {
3367
+ setActionsOpen(false);
3368
+ onDelete();
3369
+ }
3370
+ } : {},
3371
+ ...onOpenThread ? {
3372
+ onReplyInThread: () => {
3373
+ setActionsOpen(false);
3374
+ onOpenThread();
3375
+ }
3376
+ } : {},
3377
+ ...quotations && onQuote ? {
3378
+ onQuote: () => {
3379
+ setActionsOpen(false);
3380
+ onQuote();
3381
+ }
3382
+ } : {}
3126
3383
  }
3127
- } : {}
3128
- }
3129
- ) : null
3130
- ] });
3384
+ ) : null
3385
+ ]
3386
+ }
3387
+ );
3131
3388
  }
3132
3389
  var styles17 = reactNative.StyleSheet.create({
3133
3390
  row: {
@@ -3143,6 +3400,12 @@ var styles17 = reactNative.StyleSheet.create({
3143
3400
  paddingHorizontal: 10,
3144
3401
  maxWidth: "95%"
3145
3402
  },
3403
+ rowEditing: {
3404
+ // Above ConversationView's editBackdrop (zIndex 5). Elevation
3405
+ // does the equivalent stacking on Android.
3406
+ zIndex: 10,
3407
+ elevation: 10
3408
+ },
3146
3409
  avatarSlot: {
3147
3410
  width: 36,
3148
3411
  marginRight: 6
@@ -3182,6 +3445,27 @@ var styles17 = reactNative.StyleSheet.create({
3182
3445
  marginTop: 2
3183
3446
  }
3184
3447
  });
3448
+ var clipboardModule;
3449
+ var clipboardWarned = false;
3450
+ async function copyToClipboard(text) {
3451
+ if (clipboardModule === void 0) {
3452
+ try {
3453
+ clipboardModule = __require("expo-clipboard");
3454
+ } catch {
3455
+ clipboardModule = null;
3456
+ }
3457
+ }
3458
+ if (!clipboardModule?.setStringAsync) {
3459
+ if (!clipboardWarned) {
3460
+ clipboardWarned = true;
3461
+ console.warn(
3462
+ "[@poolse/react-native] Copy action requires `expo-clipboard`. Install it to enable copy-to-clipboard."
3463
+ );
3464
+ }
3465
+ return;
3466
+ }
3467
+ await clipboardModule.setStringAsync(text);
3468
+ }
3185
3469
  var SHEET_HEIGHT_FRACTION = 0.88;
3186
3470
  function ThreadView({
3187
3471
  visible,
@@ -3237,31 +3521,34 @@ function ThreadSheet({
3237
3521
  const [pickerOpen, setPickerOpen] = react.useState(false);
3238
3522
  const [editingId, setEditingId] = react.useState(null);
3239
3523
  const renderReply = react.useCallback(
3240
- (msg) => /* @__PURE__ */ jsxRuntime.jsx(
3241
- MessageRow,
3242
- {
3243
- msg,
3244
- meId,
3245
- reactions: true,
3246
- attachments: true,
3247
- actions: true,
3248
- threads: false,
3249
- quotations: false,
3250
- editing: editingId === msg.id,
3251
- onStartEdit: () => setEditingId(msg.id),
3252
- onCancelEdit: () => setEditingId(null),
3253
- onSaveEdit: async (body) => {
3254
- await edit(msg.id, body);
3255
- setEditingId(null);
3256
- },
3257
- onDelete: () => {
3258
- void deleteReply(msg.id);
3259
- },
3260
- ...labelFor ? { labelFor } : {},
3261
- ...avatarFor ? { avatarFor } : {}
3262
- }
3263
- ),
3264
- [meId, editingId, edit, deleteReply, labelFor, avatarFor]
3524
+ (msg) => {
3525
+ const isSelf = meId !== null && msg.sender_id === meId;
3526
+ const canEdit = isSelf && !msg.deleted_at;
3527
+ return /* @__PURE__ */ jsxRuntime.jsx(
3528
+ MessageRow,
3529
+ {
3530
+ msg,
3531
+ meId,
3532
+ reactions: true,
3533
+ attachments: true,
3534
+ actions: true,
3535
+ threads: false,
3536
+ quotations: false,
3537
+ isEditing: editingId === msg.id,
3538
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3539
+ onDelete: () => {
3540
+ void deleteReply(msg.id);
3541
+ },
3542
+ ...labelFor ? { labelFor } : {},
3543
+ ...avatarFor ? { avatarFor } : {}
3544
+ }
3545
+ );
3546
+ },
3547
+ [meId, editingId, deleteReply, labelFor, avatarFor]
3548
+ );
3549
+ const editingMessage = react.useMemo(
3550
+ () => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
3551
+ [editingId, replies]
3265
3552
  );
3266
3553
  const rootRow = react.useMemo(
3267
3554
  () => /* @__PURE__ */ jsxRuntime.jsx(
@@ -3347,7 +3634,14 @@ function ThreadSheet({
3347
3634
  },
3348
3635
  placeholder: "Reply to thread\u2026",
3349
3636
  attachments: true,
3350
- onAttachPress: () => setPickerOpen(true)
3637
+ onAttachPress: () => setPickerOpen(true),
3638
+ editingMessage,
3639
+ onCancelEdit: () => setEditingId(null),
3640
+ onSaveEdit: async (body) => {
3641
+ if (!editingId) return;
3642
+ await edit(editingId, body);
3643
+ setEditingId(null);
3644
+ }
3351
3645
  }
3352
3646
  )
3353
3647
  ]
@@ -3511,6 +3805,7 @@ var styles19 = reactNative.StyleSheet.create({
3511
3805
  fontSize: 12
3512
3806
  }
3513
3807
  });
3808
+ var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
3514
3809
  function ConversationView({
3515
3810
  conversationId,
3516
3811
  emptyState,
@@ -3527,6 +3822,7 @@ function ConversationView({
3527
3822
  threads = true,
3528
3823
  quotations = true,
3529
3824
  readReceipts = true,
3825
+ markdown = true,
3530
3826
  maxBodyLength = 200,
3531
3827
  senderLabels = "auto",
3532
3828
  avatars = "auto",
@@ -3545,9 +3841,25 @@ function ConversationView({
3545
3841
  send,
3546
3842
  edit,
3547
3843
  delete: deleteMsg,
3548
- markReadUpTo
3844
+ markReadUpTo,
3845
+ refetch
3549
3846
  } = react$1.useMessages(conversationId);
3550
3847
  const { typing, signalTyping } = react$1.useTyping(conversationId);
3848
+ const realtimeStatus = react$1.useRealtimeStatus();
3849
+ react.useEffect(() => {
3850
+ const sub = reactNative.AppState.addEventListener("change", (next) => {
3851
+ if (next === "active") void refetch();
3852
+ });
3853
+ return () => sub.remove();
3854
+ }, [refetch]);
3855
+ const prevStatusRef = react.useRef(realtimeStatus);
3856
+ react.useEffect(() => {
3857
+ const prev = prevStatusRef.current;
3858
+ prevStatusRef.current = realtimeStatus;
3859
+ if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
3860
+ void refetch();
3861
+ }
3862
+ }, [realtimeStatus, refetch]);
3551
3863
  const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
3552
3864
  const { members } = react$1.useMembers(wantsMembers ? conversationId : "");
3553
3865
  const isGroupChat = members.length > 2;
@@ -3578,10 +3890,24 @@ function ConversationView({
3578
3890
  });
3579
3891
  onMarkedRead?.(conversationId, latest.id);
3580
3892
  }, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
3893
+ const groupPositionById = react.useMemo(() => {
3894
+ const map = /* @__PURE__ */ new Map();
3895
+ for (let i = 0; i < messages.length; i++) {
3896
+ const msg = messages[i];
3897
+ if (!msg) continue;
3898
+ const prev = i > 0 ? messages[i - 1] ?? null : null;
3899
+ const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
3900
+ map.set(msg.id, react$1.computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
3901
+ }
3902
+ return map;
3903
+ }, [messages]);
3581
3904
  const renderItem = react.useCallback(
3582
3905
  (msg) => {
3583
3906
  if (renderMessage) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMessage(msg, meId) });
3584
3907
  const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
3908
+ const isSelf = meId !== null && msg.sender_id === meId;
3909
+ const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
3910
+ const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
3585
3911
  return /* @__PURE__ */ jsxRuntime.jsx(
3586
3912
  MessageRow,
3587
3913
  {
@@ -3593,13 +3919,8 @@ function ConversationView({
3593
3919
  threads,
3594
3920
  quotations,
3595
3921
  ...readState ? { readState } : {},
3596
- editing: editingId === msg.id,
3597
- onStartEdit: () => setEditingId(msg.id),
3598
- onCancelEdit: () => setEditingId(null),
3599
- onSaveEdit: async (body) => {
3600
- await edit(msg.id, body);
3601
- setEditingId(null);
3602
- },
3922
+ isEditing: editingId === msg.id,
3923
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3603
3924
  onDelete: () => deleteMsg(msg.id),
3604
3925
  ...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
3605
3926
  ...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
@@ -3607,7 +3928,9 @@ function ConversationView({
3607
3928
  ...avatarFor ? { avatarFor } : {},
3608
3929
  showSenderName: showSenderLabels,
3609
3930
  showAvatar: showAvatars,
3610
- maxBodyLength
3931
+ maxBodyLength,
3932
+ groupPosition,
3933
+ markdown
3611
3934
  }
3612
3935
  );
3613
3936
  },
@@ -3622,16 +3945,38 @@ function ConversationView({
3622
3945
  threads,
3623
3946
  quotations,
3624
3947
  editingId,
3625
- edit,
3626
3948
  deleteMsg,
3627
3949
  labelFor,
3628
3950
  avatarFor,
3629
3951
  showSenderLabels,
3630
3952
  showAvatars,
3631
3953
  maxBodyLength,
3632
- sequenceByMessageId
3954
+ sequenceByMessageId,
3955
+ groupPositionById,
3956
+ markdown
3633
3957
  ]
3634
3958
  );
3959
+ const editingMessage = react.useMemo(
3960
+ () => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
3961
+ [editingId, messages]
3962
+ );
3963
+ const listItems = react.useMemo(() => {
3964
+ const out = [];
3965
+ let prev = null;
3966
+ for (const msg of messages) {
3967
+ if (prev === null || !react$1.sameDay(msg.inserted_at, prev.inserted_at)) {
3968
+ const label = react$1.formatDayLabel(msg.inserted_at);
3969
+ out.push({
3970
+ kind: "separator",
3971
+ id: `sep-${msg.id}`,
3972
+ node: /* @__PURE__ */ jsxRuntime.jsx(DaySeparator, { label })
3973
+ });
3974
+ }
3975
+ out.push({ kind: "msg", msg });
3976
+ prev = msg;
3977
+ }
3978
+ return out;
3979
+ }, [messages]);
3635
3980
  const composerProps = react.useMemo(
3636
3981
  () => ({
3637
3982
  onSend: async (body, opts) => {
@@ -3645,9 +3990,28 @@ function ConversationView({
3645
3990
  onCancelReply: () => setReplyingTo(null),
3646
3991
  ...labelFor ? { labelFor } : {},
3647
3992
  attachments,
3648
- keyboardOffset
3993
+ keyboardOffset,
3994
+ editingMessage,
3995
+ onCancelEdit: () => setEditingId(null),
3996
+ onSaveEdit: async (body) => {
3997
+ if (!editingId) return;
3998
+ await edit(editingId, body);
3999
+ setEditingId(null);
4000
+ }
3649
4001
  }),
3650
- [send, signalTyping, attachments, replyingTo, labelFor, keyboardOffset, onSent, conversationId]
4002
+ [
4003
+ send,
4004
+ signalTyping,
4005
+ attachments,
4006
+ replyingTo,
4007
+ labelFor,
4008
+ keyboardOffset,
4009
+ onSent,
4010
+ conversationId,
4011
+ editingMessage,
4012
+ editingId,
4013
+ edit
4014
+ ]
3651
4015
  );
3652
4016
  const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsxRuntime.jsx(
3653
4017
  ChatHeader,
@@ -3667,18 +4031,30 @@ function ConversationView({
3667
4031
  keyboardVerticalOffset: keyboardOffset,
3668
4032
  children: [
3669
4033
  headerNode,
3670
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles20.listWrap, children: /* @__PURE__ */ jsxRuntime.jsx(
3671
- MessageList,
3672
- {
3673
- messages,
3674
- loading,
3675
- hasMore,
3676
- onLoadMore: loadMore,
3677
- renderItem,
3678
- ListHeader: /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
3679
- ...emptyState !== void 0 ? { emptyState } : {}
3680
- }
3681
- ) }),
4034
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles20.listWrap, children: [
4035
+ /* @__PURE__ */ jsxRuntime.jsx(
4036
+ MessageList,
4037
+ {
4038
+ messages,
4039
+ items: listItems,
4040
+ meId,
4041
+ loading,
4042
+ hasMore,
4043
+ onLoadMore: loadMore,
4044
+ renderItem,
4045
+ ListHeader: /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
4046
+ ...emptyState !== void 0 ? { emptyState } : {}
4047
+ }
4048
+ ),
4049
+ editingId ? /* @__PURE__ */ jsxRuntime.jsx(
4050
+ reactNative.Pressable,
4051
+ {
4052
+ style: styles20.editBackdrop,
4053
+ onPress: () => setEditingId(null),
4054
+ accessibilityLabel: "Cancel editing"
4055
+ }
4056
+ ) : null
4057
+ ] }),
3682
4058
  mentions ? /* @__PURE__ */ jsxRuntime.jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsxRuntime.jsx(MessageComposer, { ...composerProps }),
3683
4059
  attachments ? /* @__PURE__ */ jsxRuntime.jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
3684
4060
  threads && threadRoot ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -3714,6 +4090,14 @@ var styles20 = reactNative.StyleSheet.create({
3714
4090
  },
3715
4091
  listWrap: {
3716
4092
  flex: 1
4093
+ },
4094
+ editBackdrop: {
4095
+ ...reactNative.StyleSheet.absoluteFillObject,
4096
+ backgroundColor: "rgba(0,0,0,0.45)",
4097
+ // Sit above message rows by default but below any row that
4098
+ // explicitly elevates itself (MessageRow bumps zIndex/elevation
4099
+ // when isEditing is true).
4100
+ zIndex: 5
3717
4101
  }
3718
4102
  });
3719
4103
  function UserPickerSheet({
@@ -4578,7 +4962,7 @@ exports.Avatar = Avatar;
4578
4962
  exports.ChatHeader = ChatHeader;
4579
4963
  exports.ConversationList = ConversationList;
4580
4964
  exports.ConversationView = ConversationView;
4581
- exports.EditableMessageBubble = EditableMessageBubble;
4965
+ exports.DaySeparator = DaySeparator;
4582
4966
  exports.GroupDetailsSheet = GroupDetailsSheet;
4583
4967
  exports.ImageMosaic = ImageMosaic;
4584
4968
  exports.MemberList = MemberList;