@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.js CHANGED
@@ -1,13 +1,18 @@
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, Linking, Dimensions } 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
9
 
10
- // src/PoolseInbox.tsx
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
11
16
 
12
17
  // src/theme/default.ts
13
18
  var lightColors = {
@@ -1307,6 +1312,51 @@ var styles6 = StyleSheet.create({
1307
1312
  fontWeight: "500"
1308
1313
  }
1309
1314
  });
1315
+ function DaySeparator({ label }) {
1316
+ const theme = usePoolseTheme();
1317
+ return /* @__PURE__ */ jsx(View, { style: styles7.wrap, children: /* @__PURE__ */ jsx(
1318
+ View,
1319
+ {
1320
+ style: [
1321
+ styles7.chip,
1322
+ {
1323
+ backgroundColor: theme.colors.surface2,
1324
+ borderColor: theme.colors.border
1325
+ }
1326
+ ],
1327
+ children: /* @__PURE__ */ jsx(
1328
+ Text,
1329
+ {
1330
+ style: [
1331
+ styles7.label,
1332
+ {
1333
+ color: theme.colors.ink2,
1334
+ fontFamily: theme.type.fontBody
1335
+ }
1336
+ ],
1337
+ children: label
1338
+ }
1339
+ )
1340
+ }
1341
+ ) });
1342
+ }
1343
+ var styles7 = StyleSheet.create({
1344
+ wrap: {
1345
+ alignItems: "center",
1346
+ marginVertical: 8
1347
+ },
1348
+ chip: {
1349
+ paddingHorizontal: 12,
1350
+ paddingVertical: 4,
1351
+ borderRadius: 999,
1352
+ borderWidth: StyleSheet.hairlineWidth
1353
+ },
1354
+ label: {
1355
+ fontSize: 11,
1356
+ fontWeight: "600",
1357
+ letterSpacing: 0.3
1358
+ }
1359
+ });
1310
1360
  function UploadQueueStrip() {
1311
1361
  const theme = usePoolseTheme();
1312
1362
  const upload = useSharedUpload();
@@ -1317,8 +1367,8 @@ function UploadQueueStrip() {
1317
1367
  {
1318
1368
  horizontal: true,
1319
1369
  showsHorizontalScrollIndicator: false,
1320
- contentContainerStyle: styles7.scroll,
1321
- style: [styles7.scrollStyle, { marginBottom: 8 }],
1370
+ contentContainerStyle: styles8.scroll,
1371
+ style: [styles8.scrollStyle, { marginBottom: 8 }],
1322
1372
  children: upload.queue.map((item) => {
1323
1373
  const isError = item.status === "error";
1324
1374
  const isUploading = item.status === "uploading" || item.status === "pending";
@@ -1329,7 +1379,7 @@ function UploadQueueStrip() {
1329
1379
  if (item.status === "ready" || isError) upload.remove(item.localId);
1330
1380
  else upload.cancel(item.localId);
1331
1381
  };
1332
- return /* @__PURE__ */ jsxs(View, { style: styles7.tileWrap, children: [
1382
+ return /* @__PURE__ */ jsxs(View, { style: styles8.tileWrap, children: [
1333
1383
  isImage && previewUri ? /* @__PURE__ */ jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsx(
1334
1384
  FileTile,
1335
1385
  {
@@ -1344,13 +1394,13 @@ function UploadQueueStrip() {
1344
1394
  {
1345
1395
  onPress: handleClose,
1346
1396
  hitSlop: 8,
1347
- style: [styles7.closeBtn, { backgroundColor: theme.colors.ink }],
1397
+ style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
1348
1398
  accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
1349
1399
  children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
1350
1400
  }
1351
1401
  ) : 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
1402
+ isUploading ? /* @__PURE__ */ jsx(View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsx(View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
1403
+ 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
1404
  ] }, item.localId);
1355
1405
  })
1356
1406
  }
@@ -1363,7 +1413,7 @@ function ImageTile({ uri, uploading, error }) {
1363
1413
  {
1364
1414
  source: { uri },
1365
1415
  style: [
1366
- styles7.imageTile,
1416
+ styles8.imageTile,
1367
1417
  {
1368
1418
  backgroundColor: theme.colors.surface2,
1369
1419
  borderColor: error ? theme.colors.error : "transparent",
@@ -1385,24 +1435,24 @@ function FileTile({
1385
1435
  View,
1386
1436
  {
1387
1437
  style: [
1388
- styles7.fileTile,
1438
+ styles8.fileTile,
1389
1439
  {
1390
1440
  backgroundColor: theme.colors.surface2,
1391
1441
  borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
1392
1442
  }
1393
1443
  ],
1394
1444
  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: [
1445
+ /* @__PURE__ */ jsx(View, { style: [styles8.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
1446
+ /* @__PURE__ */ jsxs(View, { style: styles8.fileInfo, children: [
1397
1447
  /* @__PURE__ */ jsx(
1398
1448
  Text,
1399
1449
  {
1400
- style: [styles7.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1450
+ style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1401
1451
  numberOfLines: 1,
1402
1452
  children: filename
1403
1453
  }
1404
1454
  ),
1405
- /* @__PURE__ */ jsx(Text, { style: [styles7.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1455
+ /* @__PURE__ */ jsx(Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
1406
1456
  ] })
1407
1457
  ]
1408
1458
  }
@@ -1416,7 +1466,7 @@ function formatBytes(n) {
1416
1466
  return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
1417
1467
  }
1418
1468
  var TILE = 64;
1419
- var styles7 = StyleSheet.create({
1469
+ var styles8 = StyleSheet.create({
1420
1470
  scrollStyle: {
1421
1471
  overflow: "visible"
1422
1472
  },
@@ -1516,6 +1566,22 @@ var styles7 = StyleSheet.create({
1516
1566
  fontWeight: "700"
1517
1567
  }
1518
1568
  });
1569
+ var FALLBACK = {
1570
+ top: Platform.OS === "ios" ? 50 : 24,
1571
+ bottom: Platform.OS === "ios" ? 34 : 0,
1572
+ left: 0,
1573
+ right: 0
1574
+ };
1575
+ var realHook = null;
1576
+ try {
1577
+ const mod = __require("react-native-safe-area-context");
1578
+ if (mod && typeof mod.useSafeAreaInsets === "function") {
1579
+ realHook = mod.useSafeAreaInsets;
1580
+ }
1581
+ } catch {
1582
+ }
1583
+ var useFallback = () => FALLBACK;
1584
+ var useSafeInsets = realHook ?? useFallback;
1519
1585
  var MessageComposer = forwardRef(
1520
1586
  function MessageComposer2({
1521
1587
  onSend,
@@ -1527,13 +1593,32 @@ var MessageComposer = forwardRef(
1527
1593
  labelFor,
1528
1594
  attachments = true,
1529
1595
  onAttachPress,
1530
- keyboardOffset: _keyboardOffset = 0
1596
+ keyboardOffset: _keyboardOffset = 0,
1597
+ editingMessage = null,
1598
+ onCancelEdit,
1599
+ onSaveEdit
1531
1600
  }, ref) {
1532
1601
  const theme = usePoolseTheme();
1533
1602
  const inputRef = useRef(null);
1534
1603
  const [value, setValue] = useState("");
1535
1604
  const [sending, setSending] = useState(false);
1605
+ const [keyboardOpen, setKeyboardOpen] = useState(false);
1536
1606
  const upload = useSharedUpload();
1607
+ const insets = useSafeInsets();
1608
+ useEffect(() => {
1609
+ const show = Keyboard.addListener(
1610
+ Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
1611
+ () => setKeyboardOpen(true)
1612
+ );
1613
+ const hide = Keyboard.addListener(
1614
+ Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
1615
+ () => setKeyboardOpen(false)
1616
+ );
1617
+ return () => {
1618
+ show.remove();
1619
+ hide.remove();
1620
+ };
1621
+ }, []);
1537
1622
  useImperativeHandle(
1538
1623
  ref,
1539
1624
  () => ({
@@ -1552,36 +1637,65 @@ var MessageComposer = forwardRef(
1552
1637
  const isUploading = upload.queue.some(
1553
1638
  (item) => item.status === "uploading" || item.status === "pending"
1554
1639
  );
1555
- const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 && readyAttachmentIds.length === 0;
1640
+ const previousDraftRef = useRef(null);
1641
+ const editingIdRef = useRef(null);
1642
+ useEffect(() => {
1643
+ const id = editingMessage?.id ?? null;
1644
+ if (id === editingIdRef.current) return;
1645
+ if (id) {
1646
+ previousDraftRef.current = value;
1647
+ setValue(editingMessage?.body ?? "");
1648
+ inputRef.current?.focus();
1649
+ } else {
1650
+ setValue(previousDraftRef.current ?? "");
1651
+ previousDraftRef.current = null;
1652
+ }
1653
+ editingIdRef.current = id;
1654
+ }, [editingMessage?.id, editingMessage?.body]);
1655
+ const isEditing = !!editingMessage;
1656
+ const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
1657
+ const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
1556
1658
  const handleSend = async () => {
1557
1659
  if (submitDisabled) return;
1558
1660
  const body = value.trim();
1559
- const replyId = replyingTo?.id;
1560
- const attachmentIds = readyAttachmentIds;
1561
- setValue("");
1562
- upload.reset();
1563
1661
  setSending(true);
1564
1662
  try {
1565
- await onSend(body, {
1566
- ...replyId ? { quoted_message_id: replyId } : {},
1567
- ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1568
- });
1663
+ if (isEditing && onSaveEdit) {
1664
+ await onSaveEdit(body);
1665
+ } else {
1666
+ const replyId = replyingTo?.id;
1667
+ const attachmentIds = readyAttachmentIds;
1668
+ setValue("");
1669
+ upload.reset();
1670
+ await onSend(body, {
1671
+ ...replyId ? { quoted_message_id: replyId } : {},
1672
+ ...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
1673
+ });
1674
+ }
1569
1675
  } finally {
1570
1676
  setSending(false);
1571
1677
  }
1572
1678
  };
1679
+ const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
1573
1680
  return /* @__PURE__ */ jsxs(
1574
1681
  View,
1575
1682
  {
1576
1683
  style: [
1577
- styles8.wrap,
1684
+ styles9.wrap,
1578
1685
  {
1579
1686
  backgroundColor: theme.colors.surface,
1580
- borderTopColor: theme.colors.border
1687
+ borderTopColor: theme.colors.border,
1688
+ paddingBottom
1581
1689
  }
1582
1690
  ],
1583
1691
  children: [
1584
- replyingTo ? /* @__PURE__ */ jsx(
1692
+ isEditing ? /* @__PURE__ */ jsx(
1693
+ EditChip,
1694
+ {
1695
+ message: editingMessage,
1696
+ ...onCancelEdit ? { onCancel: onCancelEdit } : {}
1697
+ }
1698
+ ) : replyingTo ? /* @__PURE__ */ jsx(
1585
1699
  ReplyChip,
1586
1700
  {
1587
1701
  message: replyingTo,
@@ -1589,14 +1703,14 @@ var MessageComposer = forwardRef(
1589
1703
  ...onCancelReply ? { onCancel: onCancelReply } : {}
1590
1704
  }
1591
1705
  ) : null,
1592
- attachments ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
1593
- /* @__PURE__ */ jsxs(View, { style: styles8.row, children: [
1706
+ attachments && !isEditing ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
1707
+ /* @__PURE__ */ jsxs(View, { style: styles9.row, children: [
1594
1708
  attachments && onAttachPress ? /* @__PURE__ */ jsx(
1595
1709
  Pressable,
1596
1710
  {
1597
1711
  onPress: onAttachPress,
1598
1712
  hitSlop: 8,
1599
- style: styles8.iconBtn,
1713
+ style: styles9.iconBtn,
1600
1714
  accessibilityLabel: "Attach file",
1601
1715
  children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
1602
1716
  }
@@ -1615,7 +1729,7 @@ var MessageComposer = forwardRef(
1615
1729
  multiline: true,
1616
1730
  editable: !disabled,
1617
1731
  style: [
1618
- styles8.input,
1732
+ styles9.input,
1619
1733
  {
1620
1734
  color: theme.colors.ink,
1621
1735
  backgroundColor: theme.colors.surface2,
@@ -1632,7 +1746,7 @@ var MessageComposer = forwardRef(
1632
1746
  onPress: handleSend,
1633
1747
  disabled: submitDisabled,
1634
1748
  style: [
1635
- styles8.sendBtn,
1749
+ styles9.sendBtn,
1636
1750
  {
1637
1751
  backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
1638
1752
  opacity: submitDisabled ? 0.6 : 1
@@ -1648,6 +1762,28 @@ var MessageComposer = forwardRef(
1648
1762
  );
1649
1763
  }
1650
1764
  );
1765
+ function EditChip({ message, onCancel }) {
1766
+ const theme = usePoolseTheme();
1767
+ return /* @__PURE__ */ jsxs(
1768
+ View,
1769
+ {
1770
+ style: [
1771
+ styles9.replyChip,
1772
+ {
1773
+ backgroundColor: theme.colors.surface2,
1774
+ borderLeftColor: theme.colors.brand
1775
+ }
1776
+ ],
1777
+ children: [
1778
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
1779
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
1780
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1781
+ ] }),
1782
+ onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
1783
+ ]
1784
+ }
1785
+ );
1786
+ }
1651
1787
  function ReplyChip({
1652
1788
  message,
1653
1789
  labelFor,
@@ -1660,7 +1796,7 @@ function ReplyChip({
1660
1796
  View,
1661
1797
  {
1662
1798
  style: [
1663
- styles8.replyChip,
1799
+ styles9.replyChip,
1664
1800
  {
1665
1801
  backgroundColor: theme.colors.surface2,
1666
1802
  borderLeftColor: theme.colors.brand
@@ -1668,27 +1804,26 @@ function ReplyChip({
1668
1804
  ],
1669
1805
  children: [
1670
1806
  /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
1671
- /* @__PURE__ */ jsxs(Text, { style: [styles8.replyName, { color: theme.colors.brand }], children: [
1807
+ /* @__PURE__ */ jsxs(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
1672
1808
  "Replying to ",
1673
1809
  senderName
1674
1810
  ] }),
1675
- /* @__PURE__ */ jsx(Text, { style: [styles8.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1811
+ /* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
1676
1812
  ] }),
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
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
1678
1814
  ]
1679
1815
  }
1680
1816
  );
1681
1817
  }
1682
- var styles8 = StyleSheet.create({
1818
+ var styles9 = StyleSheet.create({
1683
1819
  wrap: {
1684
1820
  borderTopWidth: StyleSheet.hairlineWidth,
1685
1821
  paddingTop: 8,
1686
- paddingBottom: 10,
1687
1822
  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.
1823
+ // paddingBottom is applied inline — it follows the keyboard state
1824
+ // (10 px while open, max(10, safeBottomInset) while closed) so the
1825
+ // send/attach buttons stay clear of the home indicator without
1826
+ // double-padding above the keyboard.
1692
1827
  },
1693
1828
  row: {
1694
1829
  flexDirection: "row",
@@ -1777,11 +1912,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1777
1912
  transparent: true,
1778
1913
  animationType: "slide",
1779
1914
  onRequestClose: () => setPickerOpen(false),
1780
- children: /* @__PURE__ */ jsx(Pressable, { style: styles9.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
1915
+ children: /* @__PURE__ */ jsx(Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
1781
1916
  View,
1782
1917
  {
1783
1918
  style: [
1784
- styles9.sheet,
1919
+ styles10.sheet,
1785
1920
  {
1786
1921
  backgroundColor: theme.colors.surface,
1787
1922
  borderTopLeftRadius: theme.radii.lg,
@@ -1792,7 +1927,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1792
1927
  /* @__PURE__ */ jsx(
1793
1928
  Text,
1794
1929
  {
1795
- style: [styles9.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1930
+ style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
1796
1931
  children: "Mention a member"
1797
1932
  }
1798
1933
  ),
@@ -1823,12 +1958,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
1823
1958
  }
1824
1959
  function MentionRow({ member, onPress }) {
1825
1960
  const theme = usePoolseTheme();
1826
- return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles9.row, children: [
1961
+ return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles10.row, children: [
1827
1962
  /* @__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 })
1963
+ /* @__PURE__ */ jsx(Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
1829
1964
  ] });
1830
1965
  }
1831
- var styles9 = StyleSheet.create({
1966
+ var styles10 = StyleSheet.create({
1832
1967
  backdrop: {
1833
1968
  flex: 1,
1834
1969
  backgroundColor: "rgba(0,0,0,0.35)",
@@ -1855,138 +1990,134 @@ var styles9 = StyleSheet.create({
1855
1990
  fontSize: 14
1856
1991
  }
1857
1992
  });
1993
+ var AT_BOTTOM_THRESHOLD = 32;
1858
1994
  function MessageList({
1859
1995
  messages,
1996
+ items,
1860
1997
  loading = false,
1861
1998
  hasMore = false,
1862
1999
  onLoadMore,
1863
2000
  renderItem,
1864
2001
  ListHeader = null,
1865
2002
  ListFooter = null,
1866
- emptyState
2003
+ emptyState,
2004
+ meId = null
1867
2005
  }) {
1868
2006
  const theme = usePoolseTheme();
1869
- const data = [...messages].reverse();
2007
+ const flatListRef = useRef(null);
2008
+ const [atBottom, setAtBottom] = useState(true);
2009
+ const [newCount, setNewCount] = useState(0);
2010
+ const lastTailIdRef = useRef(null);
2011
+ const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
2012
+ const data = [...sourceItems].reverse();
2013
+ useEffect(() => {
2014
+ const tail = messages[messages.length - 1];
2015
+ if (!tail) return;
2016
+ const tailId = tail.id;
2017
+ if (tailId === lastTailIdRef.current) return;
2018
+ const isFirstSet = lastTailIdRef.current === null;
2019
+ lastTailIdRef.current = tailId;
2020
+ if (atBottom || isFirstSet) {
2021
+ setNewCount(0);
2022
+ return;
2023
+ }
2024
+ if (meId !== null && tail.sender_id === meId) return;
2025
+ setNewCount((n) => n + 1);
2026
+ }, [messages, atBottom, meId]);
2027
+ const handleScroll = useCallback((e) => {
2028
+ const y = e.nativeEvent.contentOffset.y;
2029
+ const next = y <= AT_BOTTOM_THRESHOLD;
2030
+ setAtBottom((prev) => prev === next ? prev : next);
2031
+ if (next) setNewCount(0);
2032
+ }, []);
2033
+ const scrollToBottom = useCallback(() => {
2034
+ flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
2035
+ setNewCount(0);
2036
+ }, []);
1870
2037
  const handleRender = useCallback(
1871
- ({ item, index }) => renderItem(item, messages.length - 1 - index),
1872
- [renderItem, messages.length]
2038
+ ({ item, index }) => {
2039
+ if (item.kind === "separator") return item.node;
2040
+ const sourceIndex = data.length - 1 - index;
2041
+ return renderItem(item.msg, sourceIndex);
2042
+ },
2043
+ [renderItem, data.length]
1873
2044
  );
1874
2045
  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 }) });
2046
+ return /* @__PURE__ */ jsx(View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
1876
2047
  }
1877
2048
  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 });
2049
+ 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
2050
  }
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
- );
2051
+ return /* @__PURE__ */ jsxs(View, { style: styles11.root, children: [
2052
+ /* @__PURE__ */ jsx(
2053
+ FlatList,
2054
+ {
2055
+ ref: flatListRef,
2056
+ inverted: true,
2057
+ data,
2058
+ keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
2059
+ renderItem: handleRender,
2060
+ onEndReached: hasMore ? onLoadMore : void 0,
2061
+ onEndReachedThreshold: 0.3,
2062
+ onScroll: handleScroll,
2063
+ scrollEventThrottle: 32,
2064
+ ListHeaderComponent: ListHeader,
2065
+ ListFooterComponent: /* @__PURE__ */ jsxs(View, { children: [
2066
+ hasMore && loading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
2067
+ ListFooter
2068
+ ] }),
2069
+ style: { backgroundColor: theme.colors.paper },
2070
+ contentContainerStyle: { paddingVertical: 8 }
2071
+ }
2072
+ ),
2073
+ !atBottom && newCount > 0 ? /* @__PURE__ */ jsxs(
2074
+ Pressable,
2075
+ {
2076
+ onPress: scrollToBottom,
2077
+ style: [
2078
+ styles11.jumpButton,
2079
+ {
2080
+ backgroundColor: theme.colors.brand
2081
+ },
2082
+ theme.shadows.sm
2083
+ ],
2084
+ accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
2085
+ children: [
2086
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
2087
+ /* @__PURE__ */ jsxs(Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
2088
+ newCount,
2089
+ " new ",
2090
+ newCount === 1 ? "message" : "messages"
2091
+ ] })
2092
+ ]
2093
+ }
2094
+ ) : null
2095
+ ] });
1898
2096
  }
1899
- var styles10 = StyleSheet.create({
2097
+ var styles11 = StyleSheet.create({
2098
+ root: {
2099
+ flex: 1
2100
+ },
1900
2101
  placeholder: {
1901
2102
  flex: 1,
1902
2103
  alignItems: "center",
1903
2104
  justifyContent: "center",
1904
2105
  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
2106
  },
1981
- bubble: {},
1982
- actions: {
2107
+ jumpButton: {
2108
+ position: "absolute",
2109
+ alignSelf: "center",
2110
+ bottom: 12,
1983
2111
  flexDirection: "row",
1984
- justifyContent: "flex-end",
1985
- gap: 12,
1986
- marginTop: 6
2112
+ alignItems: "center",
2113
+ gap: 6,
2114
+ paddingHorizontal: 14,
2115
+ paddingVertical: 8,
2116
+ borderRadius: 999
1987
2117
  },
1988
- btn: {
1989
- fontSize: 13
2118
+ jumpText: {
2119
+ fontSize: 13,
2120
+ fontWeight: "600"
1990
2121
  }
1991
2122
  });
1992
2123
  var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
@@ -2449,6 +2580,77 @@ var styles14 = StyleSheet.create({
2449
2580
  borderRadius: 20
2450
2581
  }
2451
2582
  });
2583
+ var MarkdownModule;
2584
+ var warned = false;
2585
+ function loadMarkdown() {
2586
+ if (MarkdownModule === void 0) {
2587
+ try {
2588
+ MarkdownModule = __require("react-native-markdown-display");
2589
+ } catch {
2590
+ MarkdownModule = null;
2591
+ }
2592
+ }
2593
+ const fn = MarkdownModule?.default;
2594
+ if (!fn) {
2595
+ if (!warned) {
2596
+ warned = true;
2597
+ console.warn(
2598
+ "[@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."
2599
+ );
2600
+ }
2601
+ return null;
2602
+ }
2603
+ return fn;
2604
+ }
2605
+ function renderMarkdown(body, args) {
2606
+ const Markdown = loadMarkdown();
2607
+ if (!Markdown) return null;
2608
+ const baseText = {
2609
+ color: args.textColor,
2610
+ fontFamily: args.fontFamily,
2611
+ fontSize: args.fontSize,
2612
+ lineHeight: args.lineHeight
2613
+ };
2614
+ const style = {
2615
+ body: baseText,
2616
+ paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
2617
+ text: baseText,
2618
+ strong: { ...baseText, fontWeight: "700" },
2619
+ em: { ...baseText, fontStyle: "italic" },
2620
+ s: { ...baseText, textDecorationLine: "line-through" },
2621
+ link: { color: args.linkColor, textDecorationLine: "underline" },
2622
+ code_inline: {
2623
+ ...baseText,
2624
+ backgroundColor: "rgba(127,127,127,0.18)",
2625
+ borderRadius: 4,
2626
+ paddingHorizontal: 4
2627
+ },
2628
+ code_block: {
2629
+ ...baseText,
2630
+ backgroundColor: "rgba(127,127,127,0.18)",
2631
+ padding: 8,
2632
+ borderRadius: 6
2633
+ },
2634
+ fence: {
2635
+ ...baseText,
2636
+ backgroundColor: "rgba(127,127,127,0.18)",
2637
+ padding: 8,
2638
+ borderRadius: 6
2639
+ },
2640
+ bullet_list: { marginTop: 2, marginBottom: 2 },
2641
+ ordered_list: { marginTop: 2, marginBottom: 2 },
2642
+ list_item: baseText,
2643
+ blockquote: {
2644
+ ...baseText,
2645
+ borderLeftWidth: 3,
2646
+ borderLeftColor: args.linkColor,
2647
+ paddingLeft: 8,
2648
+ marginVertical: 4,
2649
+ opacity: 0.85
2650
+ }
2651
+ };
2652
+ return createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
2653
+ }
2452
2654
  function MessageBubble({
2453
2655
  message,
2454
2656
  currentUserId,
@@ -2459,7 +2661,8 @@ function MessageBubble({
2459
2661
  maxBodyLength = 0,
2460
2662
  showSenderName = false,
2461
2663
  actionsTrigger,
2462
- showAttachments = true
2664
+ showAttachments = true,
2665
+ markdown = true
2463
2666
  }) {
2464
2667
  const theme = usePoolseTheme();
2465
2668
  const { width: screenWidth } = useWindowDimensions();
@@ -2544,22 +2747,53 @@ function MessageBubble({
2544
2747
  ) : null,
2545
2748
  renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
2546
2749
  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(
2548
- Text,
2549
- {
2550
- style: [
2551
- styles15.body,
2750
+ body.length > 0 ? (() => {
2751
+ if (isDeleted) {
2752
+ return /* @__PURE__ */ jsx(
2753
+ Text,
2552
2754
  {
2553
- color: isDeleted ? theme.colors.ink3 : textColor,
2554
- fontFamily: theme.type.fontBody,
2555
- fontSize: theme.type.bodySize,
2556
- lineHeight: theme.type.lineHeight,
2557
- fontStyle: isDeleted ? "italic" : "normal"
2755
+ style: [
2756
+ styles15.body,
2757
+ {
2758
+ color: theme.colors.ink3,
2759
+ fontFamily: theme.type.fontBody,
2760
+ fontSize: theme.type.bodySize,
2761
+ lineHeight: theme.type.lineHeight,
2762
+ fontStyle: "italic"
2763
+ }
2764
+ ],
2765
+ children: "Message deleted"
2558
2766
  }
2559
- ],
2560
- children: isDeleted ? "Message deleted" : truncated
2767
+ );
2561
2768
  }
2562
- ) : null,
2769
+ const md = markdown ? renderMarkdown(truncated, {
2770
+ textColor,
2771
+ linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
2772
+ fontFamily: theme.type.fontBody,
2773
+ fontSize: theme.type.bodySize,
2774
+ lineHeight: theme.type.lineHeight,
2775
+ onLinkPress: (url) => {
2776
+ void Linking.openURL(url).catch(() => void 0);
2777
+ return false;
2778
+ }
2779
+ }) : null;
2780
+ if (md) return md;
2781
+ return /* @__PURE__ */ jsx(
2782
+ Text,
2783
+ {
2784
+ style: [
2785
+ styles15.body,
2786
+ {
2787
+ color: textColor,
2788
+ fontFamily: theme.type.fontBody,
2789
+ fontSize: theme.type.bodySize,
2790
+ lineHeight: theme.type.lineHeight
2791
+ }
2792
+ ],
2793
+ children: truncated
2794
+ }
2795
+ );
2796
+ })() : null,
2563
2797
  maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
2564
2798
  Text,
2565
2799
  {
@@ -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 copyToClipboard(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
@@ -3159,6 +3422,27 @@ var styles17 = StyleSheet.create({
3159
3422
  marginTop: 2
3160
3423
  }
3161
3424
  });
3425
+ var clipboardModule;
3426
+ var clipboardWarned = false;
3427
+ async function copyToClipboard(text) {
3428
+ if (clipboardModule === void 0) {
3429
+ try {
3430
+ clipboardModule = __require("expo-clipboard");
3431
+ } catch {
3432
+ clipboardModule = null;
3433
+ }
3434
+ }
3435
+ if (!clipboardModule?.setStringAsync) {
3436
+ if (!clipboardWarned) {
3437
+ clipboardWarned = true;
3438
+ console.warn(
3439
+ "[@poolse/react-native] Copy action requires `expo-clipboard`. Install it to enable copy-to-clipboard."
3440
+ );
3441
+ }
3442
+ return;
3443
+ }
3444
+ await clipboardModule.setStringAsync(text);
3445
+ }
3162
3446
  var SHEET_HEIGHT_FRACTION = 0.88;
3163
3447
  function ThreadView({
3164
3448
  visible,
@@ -3214,31 +3498,34 @@ function ThreadSheet({
3214
3498
  const [pickerOpen, setPickerOpen] = useState(false);
3215
3499
  const [editingId, setEditingId] = useState(null);
3216
3500
  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]
3501
+ (msg) => {
3502
+ const isSelf = meId !== null && msg.sender_id === meId;
3503
+ const canEdit = isSelf && !msg.deleted_at;
3504
+ return /* @__PURE__ */ jsx(
3505
+ MessageRow,
3506
+ {
3507
+ msg,
3508
+ meId,
3509
+ reactions: true,
3510
+ attachments: true,
3511
+ actions: true,
3512
+ threads: false,
3513
+ quotations: false,
3514
+ isEditing: editingId === msg.id,
3515
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3516
+ onDelete: () => {
3517
+ void deleteReply(msg.id);
3518
+ },
3519
+ ...labelFor ? { labelFor } : {},
3520
+ ...avatarFor ? { avatarFor } : {}
3521
+ }
3522
+ );
3523
+ },
3524
+ [meId, editingId, deleteReply, labelFor, avatarFor]
3525
+ );
3526
+ const editingMessage = useMemo(
3527
+ () => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
3528
+ [editingId, replies]
3242
3529
  );
3243
3530
  const rootRow = useMemo(
3244
3531
  () => /* @__PURE__ */ jsx(
@@ -3324,7 +3611,14 @@ function ThreadSheet({
3324
3611
  },
3325
3612
  placeholder: "Reply to thread\u2026",
3326
3613
  attachments: true,
3327
- onAttachPress: () => setPickerOpen(true)
3614
+ onAttachPress: () => setPickerOpen(true),
3615
+ editingMessage,
3616
+ onCancelEdit: () => setEditingId(null),
3617
+ onSaveEdit: async (body) => {
3618
+ if (!editingId) return;
3619
+ await edit(editingId, body);
3620
+ setEditingId(null);
3621
+ }
3328
3622
  }
3329
3623
  )
3330
3624
  ]
@@ -3488,6 +3782,7 @@ var styles19 = StyleSheet.create({
3488
3782
  fontSize: 12
3489
3783
  }
3490
3784
  });
3785
+ var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
3491
3786
  function ConversationView({
3492
3787
  conversationId,
3493
3788
  emptyState,
@@ -3504,6 +3799,7 @@ function ConversationView({
3504
3799
  threads = true,
3505
3800
  quotations = true,
3506
3801
  readReceipts = true,
3802
+ markdown = true,
3507
3803
  maxBodyLength = 200,
3508
3804
  senderLabels = "auto",
3509
3805
  avatars = "auto",
@@ -3522,9 +3818,25 @@ function ConversationView({
3522
3818
  send,
3523
3819
  edit,
3524
3820
  delete: deleteMsg,
3525
- markReadUpTo
3821
+ markReadUpTo,
3822
+ refetch
3526
3823
  } = useMessages(conversationId);
3527
3824
  const { typing, signalTyping } = useTyping(conversationId);
3825
+ const realtimeStatus = useRealtimeStatus();
3826
+ useEffect(() => {
3827
+ const sub = AppState.addEventListener("change", (next) => {
3828
+ if (next === "active") void refetch();
3829
+ });
3830
+ return () => sub.remove();
3831
+ }, [refetch]);
3832
+ const prevStatusRef = useRef(realtimeStatus);
3833
+ useEffect(() => {
3834
+ const prev = prevStatusRef.current;
3835
+ prevStatusRef.current = realtimeStatus;
3836
+ if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
3837
+ void refetch();
3838
+ }
3839
+ }, [realtimeStatus, refetch]);
3528
3840
  const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
3529
3841
  const { members } = useMembers(wantsMembers ? conversationId : "");
3530
3842
  const isGroupChat = members.length > 2;
@@ -3555,10 +3867,24 @@ function ConversationView({
3555
3867
  });
3556
3868
  onMarkedRead?.(conversationId, latest.id);
3557
3869
  }, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
3870
+ const groupPositionById = useMemo(() => {
3871
+ const map = /* @__PURE__ */ new Map();
3872
+ for (let i = 0; i < messages.length; i++) {
3873
+ const msg = messages[i];
3874
+ if (!msg) continue;
3875
+ const prev = i > 0 ? messages[i - 1] ?? null : null;
3876
+ const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
3877
+ map.set(msg.id, computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
3878
+ }
3879
+ return map;
3880
+ }, [messages]);
3558
3881
  const renderItem = useCallback(
3559
3882
  (msg) => {
3560
3883
  if (renderMessage) return /* @__PURE__ */ jsx(Fragment, { children: renderMessage(msg, meId) });
3561
3884
  const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
3885
+ const isSelf = meId !== null && msg.sender_id === meId;
3886
+ const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
3887
+ const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
3562
3888
  return /* @__PURE__ */ jsx(
3563
3889
  MessageRow,
3564
3890
  {
@@ -3570,13 +3896,8 @@ function ConversationView({
3570
3896
  threads,
3571
3897
  quotations,
3572
3898
  ...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
- },
3899
+ isEditing: editingId === msg.id,
3900
+ ...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
3580
3901
  onDelete: () => deleteMsg(msg.id),
3581
3902
  ...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
3582
3903
  ...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
@@ -3584,7 +3905,9 @@ function ConversationView({
3584
3905
  ...avatarFor ? { avatarFor } : {},
3585
3906
  showSenderName: showSenderLabels,
3586
3907
  showAvatar: showAvatars,
3587
- maxBodyLength
3908
+ maxBodyLength,
3909
+ groupPosition,
3910
+ markdown
3588
3911
  }
3589
3912
  );
3590
3913
  },
@@ -3599,16 +3922,38 @@ function ConversationView({
3599
3922
  threads,
3600
3923
  quotations,
3601
3924
  editingId,
3602
- edit,
3603
3925
  deleteMsg,
3604
3926
  labelFor,
3605
3927
  avatarFor,
3606
3928
  showSenderLabels,
3607
3929
  showAvatars,
3608
3930
  maxBodyLength,
3609
- sequenceByMessageId
3931
+ sequenceByMessageId,
3932
+ groupPositionById,
3933
+ markdown
3610
3934
  ]
3611
3935
  );
3936
+ const editingMessage = useMemo(
3937
+ () => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
3938
+ [editingId, messages]
3939
+ );
3940
+ const listItems = useMemo(() => {
3941
+ const out = [];
3942
+ let prev = null;
3943
+ for (const msg of messages) {
3944
+ if (prev === null || !sameDay(msg.inserted_at, prev.inserted_at)) {
3945
+ const label = formatDayLabel(msg.inserted_at);
3946
+ out.push({
3947
+ kind: "separator",
3948
+ id: `sep-${msg.id}`,
3949
+ node: /* @__PURE__ */ jsx(DaySeparator, { label })
3950
+ });
3951
+ }
3952
+ out.push({ kind: "msg", msg });
3953
+ prev = msg;
3954
+ }
3955
+ return out;
3956
+ }, [messages]);
3612
3957
  const composerProps = useMemo(
3613
3958
  () => ({
3614
3959
  onSend: async (body, opts) => {
@@ -3622,9 +3967,28 @@ function ConversationView({
3622
3967
  onCancelReply: () => setReplyingTo(null),
3623
3968
  ...labelFor ? { labelFor } : {},
3624
3969
  attachments,
3625
- keyboardOffset
3970
+ keyboardOffset,
3971
+ editingMessage,
3972
+ onCancelEdit: () => setEditingId(null),
3973
+ onSaveEdit: async (body) => {
3974
+ if (!editingId) return;
3975
+ await edit(editingId, body);
3976
+ setEditingId(null);
3977
+ }
3626
3978
  }),
3627
- [send, signalTyping, attachments, replyingTo, labelFor, keyboardOffset, onSent, conversationId]
3979
+ [
3980
+ send,
3981
+ signalTyping,
3982
+ attachments,
3983
+ replyingTo,
3984
+ labelFor,
3985
+ keyboardOffset,
3986
+ onSent,
3987
+ conversationId,
3988
+ editingMessage,
3989
+ editingId,
3990
+ edit
3991
+ ]
3628
3992
  );
3629
3993
  const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsx(
3630
3994
  ChatHeader,
@@ -3644,18 +4008,30 @@ function ConversationView({
3644
4008
  keyboardVerticalOffset: keyboardOffset,
3645
4009
  children: [
3646
4010
  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
- ) }),
4011
+ /* @__PURE__ */ jsxs(View, { style: styles20.listWrap, children: [
4012
+ /* @__PURE__ */ jsx(
4013
+ MessageList,
4014
+ {
4015
+ messages,
4016
+ items: listItems,
4017
+ meId,
4018
+ loading,
4019
+ hasMore,
4020
+ onLoadMore: loadMore,
4021
+ renderItem,
4022
+ ListHeader: /* @__PURE__ */ jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
4023
+ ...emptyState !== void 0 ? { emptyState } : {}
4024
+ }
4025
+ ),
4026
+ editingId ? /* @__PURE__ */ jsx(
4027
+ Pressable,
4028
+ {
4029
+ style: styles20.editBackdrop,
4030
+ onPress: () => setEditingId(null),
4031
+ accessibilityLabel: "Cancel editing"
4032
+ }
4033
+ ) : null
4034
+ ] }),
3659
4035
  mentions ? /* @__PURE__ */ jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsx(MessageComposer, { ...composerProps }),
3660
4036
  attachments ? /* @__PURE__ */ jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
3661
4037
  threads && threadRoot ? /* @__PURE__ */ jsx(
@@ -3691,6 +4067,14 @@ var styles20 = StyleSheet.create({
3691
4067
  },
3692
4068
  listWrap: {
3693
4069
  flex: 1
4070
+ },
4071
+ editBackdrop: {
4072
+ ...StyleSheet.absoluteFillObject,
4073
+ backgroundColor: "rgba(0,0,0,0.45)",
4074
+ // Sit above message rows by default but below any row that
4075
+ // explicitly elevates itself (MessageRow bumps zIndex/elevation
4076
+ // when isEditing is true).
4077
+ zIndex: 5
3694
4078
  }
3695
4079
  });
3696
4080
  function UserPickerSheet({
@@ -4549,6 +4933,6 @@ function useAppStateLifecycle() {
4549
4933
  }, [poolse]);
4550
4934
  }
4551
4935
 
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 };
4936
+ 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
4937
  //# sourceMappingURL=index.js.map
4554
4938
  //# sourceMappingURL=index.js.map