@poolse/react-native 2.1.0 → 2.1.2

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.d.cts CHANGED
@@ -578,7 +578,7 @@ interface AttachmentPickerProps {
578
578
  * Override to plug in custom storage / transcoding. Default reads
579
579
  * from `expo-image-picker` / `expo-document-picker`.
580
580
  */
581
- onPickInputs?: (kind: 'image' | 'document') => Promise<{
581
+ onPickInputs?: (kind: 'image' | 'document' | 'camera') => Promise<{
582
582
  body: {
583
583
  uri: string;
584
584
  name: string;
@@ -588,6 +588,14 @@ interface AttachmentPickerProps {
588
588
  byteSize: number;
589
589
  filename?: string;
590
590
  }[]>;
591
+ /**
592
+ * Show the Camera item alongside Photo and File. Defaults true.
593
+ * Disable if your app explicitly doesn't want camera access (some
594
+ * App Store reviewers require an explicit usage description even
595
+ * for the picker entry). Add `NSCameraUsageDescription` to your
596
+ * iOS Info.plist when leaving this on.
597
+ */
598
+ camera?: boolean;
591
599
  }
592
600
  /**
593
601
  * Picker sheet wrapping `expo-image-picker` and `expo-document-picker`.
@@ -596,7 +604,7 @@ interface AttachmentPickerProps {
596
604
  * declared as optional peers because consumers who pass
597
605
  * `attachments={false}` to ConversationView never render this file.
598
606
  */
599
- declare function AttachmentPicker({ visible, onClose, onPickInputs }: AttachmentPickerProps): react_jsx_runtime.JSX.Element;
607
+ declare function AttachmentPicker({ visible, onClose, onPickInputs, camera, }: AttachmentPickerProps): react_jsx_runtime.JSX.Element;
600
608
 
601
609
  interface ImageMosaicProps {
602
610
  images: Attachment[];
@@ -633,6 +641,10 @@ declare const ICONS: {
633
641
  readonly vb: "0 0 24 24";
634
642
  readonly d: "<path d=\"M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z\"/><path d=\"M9.6 19.8a2.5 2.5 0 0 0 4.8 0\"/>";
635
643
  };
644
+ readonly camera: {
645
+ readonly vb: "0 0 24 24";
646
+ readonly d: "<path d=\"M3 8.5a2 2 0 0 1 2-2h2.4l1.4-2h6.4l1.4 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9Z\"/><circle cx=\"12\" cy=\"13\" r=\"3.5\"/>";
647
+ };
636
648
  readonly 'check-double': {
637
649
  readonly vb: "0 0 24 24";
638
650
  readonly d: "<path d=\"M2.5 12.8 6.9 17 15.4 7.6\"/><path d=\"M11.4 16.4 12.3 17.2 21 7.6\"/>";
package/dist/index.d.ts CHANGED
@@ -578,7 +578,7 @@ interface AttachmentPickerProps {
578
578
  * Override to plug in custom storage / transcoding. Default reads
579
579
  * from `expo-image-picker` / `expo-document-picker`.
580
580
  */
581
- onPickInputs?: (kind: 'image' | 'document') => Promise<{
581
+ onPickInputs?: (kind: 'image' | 'document' | 'camera') => Promise<{
582
582
  body: {
583
583
  uri: string;
584
584
  name: string;
@@ -588,6 +588,14 @@ interface AttachmentPickerProps {
588
588
  byteSize: number;
589
589
  filename?: string;
590
590
  }[]>;
591
+ /**
592
+ * Show the Camera item alongside Photo and File. Defaults true.
593
+ * Disable if your app explicitly doesn't want camera access (some
594
+ * App Store reviewers require an explicit usage description even
595
+ * for the picker entry). Add `NSCameraUsageDescription` to your
596
+ * iOS Info.plist when leaving this on.
597
+ */
598
+ camera?: boolean;
591
599
  }
592
600
  /**
593
601
  * Picker sheet wrapping `expo-image-picker` and `expo-document-picker`.
@@ -596,7 +604,7 @@ interface AttachmentPickerProps {
596
604
  * declared as optional peers because consumers who pass
597
605
  * `attachments={false}` to ConversationView never render this file.
598
606
  */
599
- declare function AttachmentPicker({ visible, onClose, onPickInputs }: AttachmentPickerProps): react_jsx_runtime.JSX.Element;
607
+ declare function AttachmentPicker({ visible, onClose, onPickInputs, camera, }: AttachmentPickerProps): react_jsx_runtime.JSX.Element;
600
608
 
601
609
  interface ImageMosaicProps {
602
610
  images: Attachment[];
@@ -633,6 +641,10 @@ declare const ICONS: {
633
641
  readonly vb: "0 0 24 24";
634
642
  readonly d: "<path d=\"M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z\"/><path d=\"M9.6 19.8a2.5 2.5 0 0 0 4.8 0\"/>";
635
643
  };
644
+ readonly camera: {
645
+ readonly vb: "0 0 24 24";
646
+ readonly d: "<path d=\"M3 8.5a2 2 0 0 1 2-2h2.4l1.4-2h6.4l1.4 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9Z\"/><circle cx=\"12\" cy=\"13\" r=\"3.5\"/>";
647
+ };
636
648
  readonly 'check-double': {
637
649
  readonly vb: "0 0 24 24";
638
650
  readonly d: "<path d=\"M2.5 12.8 6.9 17 15.4 7.6\"/><path d=\"M11.4 16.4 12.3 17.2 21 7.6\"/>";
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { useUser, useMe, useConversations, useAttachmentUpload, useConversation, useMembers, usePresence, useMessages, useTyping, useRealtimeStatus, computeGroupPosition, formatDayLabel, useReactions, sameDay, useThread, useAttachmentUrl, usePoolse } from '@poolse/react';
2
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';
3
+ import { StyleSheet, Platform, Keyboard, View, Pressable, TextInput, Text, useWindowDimensions, Animated, Easing, PanResponder, Image, ScrollView, ActivityIndicator, FlatList, AppState, KeyboardAvoidingView, Modal, Dimensions, Linking } from 'react-native';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { SvgXml, Svg, Path, Circle, G } from 'react-native-svg';
6
6
  import * as ImagePicker from 'expo-image-picker';
7
7
  import * as DocumentPicker from 'expo-document-picker';
8
8
  import { manipulateAsync, SaveFormat } from 'expo-image-manipulator';
9
+ import * as Clipboard from 'expo-clipboard';
10
+ import Markdown, { MarkdownIt } from 'react-native-markdown-display';
9
11
 
10
12
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
13
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -293,6 +295,10 @@ var ICONS = {
293
295
  vb: "0 0 24 24",
294
296
  d: '<path d="M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z"/><path d="M9.6 19.8a2.5 2.5 0 0 0 4.8 0"/>'
295
297
  },
298
+ camera: {
299
+ vb: "0 0 24 24",
300
+ d: '<path d="M3 8.5a2 2 0 0 1 2-2h2.4l1.4-2h6.4l1.4 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9Z"/><circle cx="12" cy="13" r="3.5"/>'
301
+ },
296
302
  "check-double": {
297
303
  vb: "0 0 24 24",
298
304
  d: '<path d="M2.5 12.8 6.9 17 15.4 7.6"/><path d="M11.4 16.4 12.3 17.2 21 7.6"/>'
@@ -1112,7 +1118,12 @@ function useUploadPreview() {
1112
1118
  getPreview: ctx?.getPreview ?? (() => null)
1113
1119
  };
1114
1120
  }
1115
- function AttachmentPicker({ visible, onClose, onPickInputs }) {
1121
+ function AttachmentPicker({
1122
+ visible,
1123
+ onClose,
1124
+ onPickInputs,
1125
+ camera = true
1126
+ }) {
1116
1127
  const theme = usePoolseTheme();
1117
1128
  const upload = useSharedUpload();
1118
1129
  const { setPreview } = useUploadPreview();
@@ -1146,8 +1157,12 @@ function AttachmentPicker({ visible, onClose, onPickInputs }) {
1146
1157
  theme.shadows.lg
1147
1158
  ],
1148
1159
  children: [
1160
+ camera ? /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("camera"), children: [
1161
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "camera", size: 20, color: theme.colors.ink2 }),
1162
+ /* @__PURE__ */ jsx(Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Camera" })
1163
+ ] }) : null,
1149
1164
  /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("image"), children: [
1150
- /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 20, color: theme.colors.ink2 }),
1165
+ /* @__PURE__ */ jsx(PoolseIcon, { name: "image", size: 20, color: theme.colors.ink2 }),
1151
1166
  /* @__PURE__ */ jsx(Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Photo or video" })
1152
1167
  ] }),
1153
1168
  /* @__PURE__ */ jsxs(Pressable, { style: styles6.item, onPress: () => pickFromKind("document"), children: [
@@ -1206,7 +1221,52 @@ async function compressImage({
1206
1221
  filename: `${base}.webp`
1207
1222
  };
1208
1223
  }
1224
+ async function assetsToInputs(assets) {
1225
+ return Promise.all(
1226
+ assets.map(async (asset) => {
1227
+ try {
1228
+ const compressed = await compressImage({
1229
+ uri: asset.uri,
1230
+ width: asset.width,
1231
+ height: asset.height,
1232
+ filename: asset.fileName ?? `image-${Date.now()}`
1233
+ });
1234
+ return {
1235
+ body: compressed.blob,
1236
+ contentType: compressed.contentType,
1237
+ byteSize: compressed.blob.size,
1238
+ filename: compressed.filename,
1239
+ _previewUri: asset.uri
1240
+ };
1241
+ } catch (err) {
1242
+ console.warn("[poolse/picker] image compression failed, sending original:", err);
1243
+ const blob = await uriToBlob(asset.uri);
1244
+ const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
1245
+ return {
1246
+ body: blob,
1247
+ contentType,
1248
+ byteSize: asset.fileSize ?? blob.size,
1249
+ filename: asset.fileName ?? `image-${Date.now()}.jpg`,
1250
+ _previewUri: asset.uri
1251
+ };
1252
+ }
1253
+ })
1254
+ );
1255
+ }
1209
1256
  async function defaultAdapter(kind) {
1257
+ if (kind === "camera") {
1258
+ const perm = await ImagePicker.requestCameraPermissionsAsync();
1259
+ if (!perm.granted) {
1260
+ console.warn("[poolse/picker] camera permission denied");
1261
+ return [];
1262
+ }
1263
+ const res2 = await ImagePicker.launchCameraAsync({
1264
+ mediaTypes: "images",
1265
+ quality: 1
1266
+ });
1267
+ if (res2.canceled) return [];
1268
+ return assetsToInputs(res2.assets);
1269
+ }
1210
1270
  if (kind === "image") {
1211
1271
  const res2 = await ImagePicker.launchImageLibraryAsync({
1212
1272
  mediaTypes: "images",
@@ -1216,36 +1276,7 @@ async function defaultAdapter(kind) {
1216
1276
  quality: 1
1217
1277
  });
1218
1278
  if (res2.canceled) return [];
1219
- return Promise.all(
1220
- res2.assets.map(async (asset) => {
1221
- try {
1222
- const compressed = await compressImage({
1223
- uri: asset.uri,
1224
- width: asset.width,
1225
- height: asset.height,
1226
- filename: asset.fileName ?? `image-${Date.now()}`
1227
- });
1228
- return {
1229
- body: compressed.blob,
1230
- contentType: compressed.contentType,
1231
- byteSize: compressed.blob.size,
1232
- filename: compressed.filename,
1233
- _previewUri: asset.uri
1234
- };
1235
- } catch (err) {
1236
- console.warn("[poolse/picker] image compression failed, sending original:", err);
1237
- const blob = await uriToBlob(asset.uri);
1238
- const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
1239
- return {
1240
- body: blob,
1241
- contentType,
1242
- byteSize: asset.fileSize ?? blob.size,
1243
- filename: asset.fileName ?? `image-${Date.now()}.jpg`,
1244
- _previewUri: asset.uri
1245
- };
1246
- }
1247
- })
1248
- );
1279
+ return assetsToInputs(res2.assets);
1249
1280
  }
1250
1281
  const res = await DocumentPicker.getDocumentAsync({
1251
1282
  copyToCacheDirectory: true,
@@ -2580,31 +2611,8 @@ var styles14 = StyleSheet.create({
2580
2611
  borderRadius: 20
2581
2612
  }
2582
2613
  });
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
- }
2614
+ var markdownit = MarkdownIt({ typographer: false, linkify: true });
2605
2615
  function renderMarkdown(body, args) {
2606
- const Markdown = loadMarkdown();
2607
- if (!Markdown) return null;
2608
2616
  const baseText = {
2609
2617
  color: args.textColor,
2610
2618
  fontFamily: args.fontFamily,
@@ -2649,7 +2657,7 @@ function renderMarkdown(body, args) {
2649
2657
  opacity: 0.85
2650
2658
  }
2651
2659
  };
2652
- return createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
2660
+ return createElement(Markdown, { style, markdownit, onLinkPress: args.onLinkPress }, body);
2653
2661
  }
2654
2662
  function MessageBubble({
2655
2663
  message,
@@ -2747,53 +2755,46 @@ function MessageBubble({
2747
2755
  ) : null,
2748
2756
  renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
2749
2757
  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,
2750
- body.length > 0 ? (() => {
2751
- if (isDeleted) {
2752
- return /* @__PURE__ */ jsx(
2753
- Text,
2758
+ body.length > 0 ? isDeleted ? /* @__PURE__ */ jsx(
2759
+ Text,
2760
+ {
2761
+ style: [
2762
+ styles15.body,
2754
2763
  {
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"
2764
+ color: theme.colors.ink3,
2765
+ fontFamily: theme.type.fontBody,
2766
+ fontSize: theme.type.bodySize,
2767
+ lineHeight: theme.type.lineHeight,
2768
+ fontStyle: "italic"
2766
2769
  }
2767
- );
2770
+ ],
2771
+ children: "Message deleted"
2768
2772
  }
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,
2773
+ ) : markdown ? renderMarkdown(truncated, {
2774
+ textColor,
2775
+ linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
2776
+ fontFamily: theme.type.fontBody,
2777
+ fontSize: theme.type.bodySize,
2778
+ lineHeight: theme.type.lineHeight,
2779
+ onLinkPress: (url) => {
2780
+ void Linking.openURL(url).catch(() => void 0);
2781
+ return false;
2782
+ }
2783
+ }) : /* @__PURE__ */ jsx(
2784
+ Text,
2785
+ {
2786
+ style: [
2787
+ styles15.body,
2788
+ {
2789
+ color: textColor,
2790
+ fontFamily: theme.type.fontBody,
2791
+ fontSize: theme.type.bodySize,
2792
+ lineHeight: theme.type.lineHeight
2793
+ }
2794
+ ],
2795
+ children: truncated
2796
+ }
2797
+ ) : null,
2797
2798
  maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
2798
2799
  Text,
2799
2800
  {
@@ -3330,7 +3331,7 @@ function MessageRow({
3330
3331
  ...msg.body && !msg.deleted_at ? {
3331
3332
  onCopy: () => {
3332
3333
  setActionsOpen(false);
3333
- void copyToClipboard(msg.body ?? "");
3334
+ void Clipboard.setStringAsync(msg.body ?? "");
3334
3335
  }
3335
3336
  } : {},
3336
3337
  ...onStartEdit ? {
@@ -3422,27 +3423,6 @@ var styles17 = StyleSheet.create({
3422
3423
  marginTop: 2
3423
3424
  }
3424
3425
  });
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
- }
3446
3426
  var SHEET_HEIGHT_FRACTION = 0.88;
3447
3427
  function ThreadView({
3448
3428
  visible,