@sincpro/mobile-ui 3.2.0 → 3.3.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.
@@ -0,0 +1,15 @@
1
+ import type { DocumentRenderer } from "./document-renderer.js";
2
+ import type { DocumentSource } from "../utils/document.js";
3
+ export interface DocumentPreviewProps {
4
+ source: DocumentSource;
5
+ title?: string;
6
+ height?: number;
7
+ fit?: "top" | "contain";
8
+ onPress?: () => void;
9
+ renderer?: DocumentRenderer;
10
+ className?: string;
11
+ testID?: string;
12
+ }
13
+ declare function DocumentPreview({ source, title, height, fit, onPress, renderer, className, testID, }: DocumentPreviewProps): import("react").JSX.Element;
14
+ export default DocumentPreview;
15
+ export { DocumentPreview };
@@ -0,0 +1,349 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import ExpoImage from "./Display.ExpoImage.js";
3
+ import Icon from "./Display.Icon.js";
4
+ import { documentHeading, documentMeta, DocumentStatus, useDocumentPages } from "./document-pages.js";
5
+ import Skeleton from "../Feedback/Feedback.Skeleton.js";
6
+ import Pressable from "../primitives/Pressable.js";
7
+ import { useTheme } from "../theme/index.js";
8
+ import { cn } from "../theme/tw.js";
9
+ import { Typography } from "../Typography/index.js";
10
+ import { useWindowDimensions, View } from "react-native";
11
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
12
+ const MAX_THUMBNAIL_WIDTH = 1080;
13
+ function Notice(t0) {
14
+ const $ = _c(8);
15
+ const {
16
+ icon,
17
+ label,
18
+ color
19
+ } = t0;
20
+ let t1;
21
+ if ($[0] !== color || $[1] !== icon) {
22
+ t1 = _jsx(Icon, {
23
+ color: color,
24
+ name: icon,
25
+ size: 28
26
+ });
27
+ $[0] = color;
28
+ $[1] = icon;
29
+ $[2] = t1;
30
+ } else {
31
+ t1 = $[2];
32
+ }
33
+ let t2;
34
+ if ($[3] !== label) {
35
+ t2 = _jsx(Typography.Text, {
36
+ align: "center",
37
+ className: "text-text-secondary",
38
+ variant: "bodySmall",
39
+ children: label
40
+ });
41
+ $[3] = label;
42
+ $[4] = t2;
43
+ } else {
44
+ t2 = $[4];
45
+ }
46
+ let t3;
47
+ if ($[5] !== t1 || $[6] !== t2) {
48
+ t3 = _jsxs(View, {
49
+ className: "flex-1 items-center justify-center gap-2 px-4",
50
+ children: [t1, t2]
51
+ });
52
+ $[5] = t1;
53
+ $[6] = t2;
54
+ $[7] = t3;
55
+ } else {
56
+ t3 = $[7];
57
+ }
58
+ return t3;
59
+ }
60
+ function DocumentPreview(t0) {
61
+ const $ = _c(68);
62
+ const {
63
+ source,
64
+ title,
65
+ height: t1,
66
+ fit: t2,
67
+ onPress,
68
+ renderer,
69
+ className,
70
+ testID
71
+ } = t0;
72
+ const height = t1 === undefined ? 180 : t1;
73
+ const fit = t2 === undefined ? "top" : t2;
74
+ const theme = useTheme();
75
+ const {
76
+ width,
77
+ scale
78
+ } = useWindowDimensions();
79
+ let t3;
80
+ if ($[0] !== scale || $[1] !== width) {
81
+ t3 = Math.round(width * scale);
82
+ $[0] = scale;
83
+ $[1] = width;
84
+ $[2] = t3;
85
+ } else {
86
+ t3 = $[2];
87
+ }
88
+ const document = useDocumentPages(source, Math.min(MAX_THUMBNAIL_WIDTH, t3), true, renderer);
89
+ const page = document.pages[0];
90
+ const failed = document.status === DocumentStatus.ERROR;
91
+ let t4;
92
+ if ($[3] !== document || $[4] !== title) {
93
+ t4 = title ?? documentHeading(document);
94
+ $[3] = document;
95
+ $[4] = title;
96
+ $[5] = t4;
97
+ } else {
98
+ t4 = $[5];
99
+ }
100
+ const heading = t4;
101
+ let t5;
102
+ if ($[6] !== document.isImage || $[7] !== document.label || $[8] !== document.pageCount) {
103
+ t5 = documentMeta(document.label, document.isImage, document.pageCount);
104
+ $[6] = document.isImage;
105
+ $[7] = document.label;
106
+ $[8] = document.pageCount;
107
+ $[9] = t5;
108
+ } else {
109
+ t5 = $[9];
110
+ }
111
+ const meta = t5;
112
+ const handlePress = failed ? document.retry : onPress;
113
+ let t6;
114
+ if ($[10] !== height || $[11] !== theme.bg.muted) {
115
+ t6 = {
116
+ height,
117
+ backgroundColor: theme.bg.muted
118
+ };
119
+ $[10] = height;
120
+ $[11] = theme.bg.muted;
121
+ $[12] = t6;
122
+ } else {
123
+ t6 = $[12];
124
+ }
125
+ let t7;
126
+ if ($[13] !== document.attempt || $[14] !== document.reportPageError || $[15] !== document.status || $[16] !== fit || $[17] !== page) {
127
+ t7 = document.status === DocumentStatus.READY && page ? _jsx(ExpoImage, {
128
+ contentFit: fit === "top" ? "cover" : "contain",
129
+ contentPosition: "top",
130
+ onError: document.reportPageError,
131
+ source: {
132
+ uri: page.uri
133
+ },
134
+ style: {
135
+ flex: 1,
136
+ width: "100%",
137
+ borderTopLeftRadius: 6,
138
+ borderTopRightRadius: 6
139
+ }
140
+ }, document.attempt) : null;
141
+ $[13] = document.attempt;
142
+ $[14] = document.reportPageError;
143
+ $[15] = document.status;
144
+ $[16] = fit;
145
+ $[17] = page;
146
+ $[18] = t7;
147
+ } else {
148
+ t7 = $[18];
149
+ }
150
+ let t8;
151
+ if ($[19] !== document.status) {
152
+ t8 = document.status === DocumentStatus.LOADING ? _jsx(Skeleton, {
153
+ height: "100%",
154
+ radius: 6,
155
+ width: "100%"
156
+ }) : null;
157
+ $[19] = document.status;
158
+ $[20] = t8;
159
+ } else {
160
+ t8 = $[20];
161
+ }
162
+ let t9;
163
+ if ($[21] !== failed || $[22] !== theme.danger) {
164
+ t9 = failed ? _jsx(Notice, {
165
+ color: theme.danger,
166
+ icon: "error",
167
+ label: "No se pudo generar la vista previa. Toca para reintentar."
168
+ }) : null;
169
+ $[21] = failed;
170
+ $[22] = theme.danger;
171
+ $[23] = t9;
172
+ } else {
173
+ t9 = $[23];
174
+ }
175
+ let t10;
176
+ if ($[24] !== document.status || $[25] !== theme.icon.tertiary) {
177
+ t10 = document.status === DocumentStatus.UNSUPPORTED ? _jsx(Notice, {
178
+ color: theme.icon.tertiary,
179
+ icon: "document",
180
+ label: "Vista previa no disponible"
181
+ }) : null;
182
+ $[24] = document.status;
183
+ $[25] = theme.icon.tertiary;
184
+ $[26] = t10;
185
+ } else {
186
+ t10 = $[26];
187
+ }
188
+ let t11;
189
+ if ($[27] !== t10 || $[28] !== t6 || $[29] !== t7 || $[30] !== t8 || $[31] !== t9) {
190
+ t11 = _jsxs(View, {
191
+ className: "px-3 pt-3",
192
+ style: t6,
193
+ children: [t7, t8, t9, t10]
194
+ });
195
+ $[27] = t10;
196
+ $[28] = t6;
197
+ $[29] = t7;
198
+ $[30] = t8;
199
+ $[31] = t9;
200
+ $[32] = t11;
201
+ } else {
202
+ t11 = $[32];
203
+ }
204
+ const t12 = document.isImage ? "image" : "document";
205
+ let t13;
206
+ if ($[33] !== t12 || $[34] !== theme.icon.secondary) {
207
+ t13 = _jsx(Icon, {
208
+ color: theme.icon.secondary,
209
+ name: t12,
210
+ size: 20
211
+ });
212
+ $[33] = t12;
213
+ $[34] = theme.icon.secondary;
214
+ $[35] = t13;
215
+ } else {
216
+ t13 = $[35];
217
+ }
218
+ let t14;
219
+ if ($[36] !== heading) {
220
+ t14 = _jsx(Typography.Text, {
221
+ className: "text-text-primary",
222
+ numberOfLines: 1,
223
+ semibold: true,
224
+ children: heading
225
+ });
226
+ $[36] = heading;
227
+ $[37] = t14;
228
+ } else {
229
+ t14 = $[37];
230
+ }
231
+ let t15;
232
+ if ($[38] !== meta) {
233
+ t15 = _jsx(Typography.Text, {
234
+ className: "text-text-secondary",
235
+ variant: "caption",
236
+ children: meta
237
+ });
238
+ $[38] = meta;
239
+ $[39] = t15;
240
+ } else {
241
+ t15 = $[39];
242
+ }
243
+ let t16;
244
+ if ($[40] !== t14 || $[41] !== t15) {
245
+ t16 = _jsxs(View, {
246
+ className: "flex-1",
247
+ children: [t14, t15]
248
+ });
249
+ $[40] = t14;
250
+ $[41] = t15;
251
+ $[42] = t16;
252
+ } else {
253
+ t16 = $[42];
254
+ }
255
+ let t17;
256
+ if ($[43] !== failed || $[44] !== onPress || $[45] !== theme.icon.tertiary) {
257
+ t17 = onPress && !failed ? _jsx(Icon, {
258
+ color: theme.icon.tertiary,
259
+ name: "next",
260
+ size: 18
261
+ }) : null;
262
+ $[43] = failed;
263
+ $[44] = onPress;
264
+ $[45] = theme.icon.tertiary;
265
+ $[46] = t17;
266
+ } else {
267
+ t17 = $[46];
268
+ }
269
+ let t18;
270
+ if ($[47] !== t13 || $[48] !== t16 || $[49] !== t17) {
271
+ t18 = _jsxs(View, {
272
+ className: "flex-row items-center gap-3 px-3 py-2.5",
273
+ children: [t13, t16, t17]
274
+ });
275
+ $[47] = t13;
276
+ $[48] = t16;
277
+ $[49] = t17;
278
+ $[50] = t18;
279
+ } else {
280
+ t18 = $[50];
281
+ }
282
+ let t19;
283
+ if ($[51] !== t11 || $[52] !== t18) {
284
+ t19 = _jsxs(_Fragment, {
285
+ children: [t11, t18]
286
+ });
287
+ $[51] = t11;
288
+ $[52] = t18;
289
+ $[53] = t19;
290
+ } else {
291
+ t19 = $[53];
292
+ }
293
+ const body = t19;
294
+ let t20;
295
+ if ($[54] !== className) {
296
+ t20 = cn("overflow-hidden rounded-xl border border-border-light bg-bg-card", className);
297
+ $[54] = className;
298
+ $[55] = t20;
299
+ } else {
300
+ t20 = $[55];
301
+ }
302
+ const surface = t20;
303
+ const label = `Vista previa de ${heading}, ${meta}`;
304
+ if (!handlePress) {
305
+ let t21;
306
+ if ($[56] !== body || $[57] !== label || $[58] !== surface || $[59] !== testID) {
307
+ t21 = _jsx(View, {
308
+ accessibilityLabel: label,
309
+ accessibilityRole: "image",
310
+ accessible: true,
311
+ className: surface,
312
+ testID: testID,
313
+ children: body
314
+ });
315
+ $[56] = body;
316
+ $[57] = label;
317
+ $[58] = surface;
318
+ $[59] = testID;
319
+ $[60] = t21;
320
+ } else {
321
+ t21 = $[60];
322
+ }
323
+ return t21;
324
+ }
325
+ const t21 = failed ? "Reintenta generar la vista previa" : "Abre el documento";
326
+ let t22;
327
+ if ($[61] !== body || $[62] !== handlePress || $[63] !== label || $[64] !== surface || $[65] !== t21 || $[66] !== testID) {
328
+ t22 = _jsx(Pressable, {
329
+ accessibilityHint: t21,
330
+ accessibilityLabel: label,
331
+ className: surface,
332
+ onPress: handlePress,
333
+ testID: testID,
334
+ children: body
335
+ });
336
+ $[61] = body;
337
+ $[62] = handlePress;
338
+ $[63] = label;
339
+ $[64] = surface;
340
+ $[65] = t21;
341
+ $[66] = testID;
342
+ $[67] = t22;
343
+ } else {
344
+ t22 = $[67];
345
+ }
346
+ return t22;
347
+ }
348
+ export default DocumentPreview;
349
+ export { DocumentPreview };
@@ -0,0 +1,15 @@
1
+ import type { DocumentRenderer } from "./document-renderer.js";
2
+ import type { DocumentSource } from "../utils/document.js";
3
+ import { type ReactNode } from "react";
4
+ export interface DocumentViewerProps {
5
+ visible: boolean;
6
+ source: DocumentSource | null;
7
+ title?: string;
8
+ onClose: () => void;
9
+ actions?: ReactNode;
10
+ renderer?: DocumentRenderer;
11
+ testID?: string;
12
+ }
13
+ declare function DocumentViewer({ visible, source, title, onClose, actions, renderer, testID, }: DocumentViewerProps): import("react").JSX.Element;
14
+ export default DocumentViewer;
15
+ export { DocumentViewer };
@@ -0,0 +1,465 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import ExpoImage from "./Display.ExpoImage.js";
3
+ import { documentHeading, documentMeta, DocumentStatus, useDocumentPages } from "./document-pages.js";
4
+ import State from "../Feedback/Feedback.State.js";
5
+ import { AppBar } from "../Navigation/Navigation.AppBar.js";
6
+ import { useTheme, useThemeVars } from "../theme/index.js";
7
+ import { Typography } from "../Typography/index.js";
8
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
9
+ import { useState } from "react";
10
+ import { Modal, ScrollView, useWindowDimensions, View } from "react-native";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
13
+ const MAX_RENDER_WIDTH = 2048;
14
+ const ZOOM_SCALE = 2;
15
+ const GUTTER = 12;
16
+ const A4_RATIO = 1 / Math.SQRT2;
17
+ function ViewerPage(t0) {
18
+ const $ = _c(26);
19
+ const {
20
+ page,
21
+ width,
22
+ number,
23
+ pageCount,
24
+ label,
25
+ onError
26
+ } = t0;
27
+ const theme = useTheme();
28
+ const [loadedRatio, setLoadedRatio] = useState(null);
29
+ const ratio = page.width && page.height ? page.width / page.height : loadedRatio ?? A4_RATIO;
30
+ let t1;
31
+ if ($[0] !== width) {
32
+ t1 = {
33
+ width,
34
+ gap: 6
35
+ };
36
+ $[0] = width;
37
+ $[1] = t1;
38
+ } else {
39
+ t1 = $[1];
40
+ }
41
+ let t2;
42
+ if ($[2] !== ratio || $[3] !== theme.border.light || $[4] !== theme.shadow.sm || $[5] !== width) {
43
+ t2 = {
44
+ width,
45
+ aspectRatio: ratio,
46
+ borderColor: theme.border.light,
47
+ borderWidth: 1,
48
+ ...theme.shadow.sm
49
+ };
50
+ $[2] = ratio;
51
+ $[3] = theme.border.light;
52
+ $[4] = theme.shadow.sm;
53
+ $[5] = width;
54
+ $[6] = t2;
55
+ } else {
56
+ t2 = $[6];
57
+ }
58
+ const t3 = pageCount > 1 ? `${label}, página ${number} de ${pageCount}` : label;
59
+ let t4;
60
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
61
+ t4 = event => {
62
+ if (event.source.height > 0) {
63
+ setLoadedRatio(event.source.width / event.source.height);
64
+ }
65
+ };
66
+ $[7] = t4;
67
+ } else {
68
+ t4 = $[7];
69
+ }
70
+ let t5;
71
+ if ($[8] !== page.uri) {
72
+ t5 = {
73
+ uri: page.uri
74
+ };
75
+ $[8] = page.uri;
76
+ $[9] = t5;
77
+ } else {
78
+ t5 = $[9];
79
+ }
80
+ let t6;
81
+ if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
82
+ t6 = {
83
+ width: "100%",
84
+ height: "100%"
85
+ };
86
+ $[10] = t6;
87
+ } else {
88
+ t6 = $[10];
89
+ }
90
+ let t7;
91
+ if ($[11] !== onError || $[12] !== t3 || $[13] !== t5) {
92
+ t7 = _jsx(ExpoImage, {
93
+ accessibilityLabel: t3,
94
+ accessible: true,
95
+ contentFit: "contain",
96
+ onError: onError,
97
+ onLoad: t4,
98
+ source: t5,
99
+ style: t6
100
+ });
101
+ $[11] = onError;
102
+ $[12] = t3;
103
+ $[13] = t5;
104
+ $[14] = t7;
105
+ } else {
106
+ t7 = $[14];
107
+ }
108
+ let t8;
109
+ if ($[15] !== t2 || $[16] !== t7) {
110
+ t8 = _jsx(View, {
111
+ style: t2,
112
+ children: t7
113
+ });
114
+ $[15] = t2;
115
+ $[16] = t7;
116
+ $[17] = t8;
117
+ } else {
118
+ t8 = $[17];
119
+ }
120
+ let t9;
121
+ if ($[18] !== number || $[19] !== pageCount || $[20] !== theme.text) {
122
+ t9 = pageCount > 1 ? _jsxs(Typography.Text, {
123
+ align: "center",
124
+ style: {
125
+ color: theme.text.secondary
126
+ },
127
+ variant: "caption",
128
+ children: ["P\xE1gina ", number, " de ", pageCount]
129
+ }) : null;
130
+ $[18] = number;
131
+ $[19] = pageCount;
132
+ $[20] = theme.text;
133
+ $[21] = t9;
134
+ } else {
135
+ t9 = $[21];
136
+ }
137
+ let t10;
138
+ if ($[22] !== t1 || $[23] !== t8 || $[24] !== t9) {
139
+ t10 = _jsxs(View, {
140
+ style: t1,
141
+ children: [t8, t9]
142
+ });
143
+ $[22] = t1;
144
+ $[23] = t8;
145
+ $[24] = t9;
146
+ $[25] = t10;
147
+ } else {
148
+ t10 = $[25];
149
+ }
150
+ return t10;
151
+ }
152
+ function ViewerContent(t0) {
153
+ const $ = _c(55);
154
+ const {
155
+ source,
156
+ title,
157
+ onClose,
158
+ actions,
159
+ renderer
160
+ } = t0;
161
+ const theme = useTheme();
162
+ const insets = useSafeAreaInsets();
163
+ const {
164
+ width,
165
+ scale
166
+ } = useWindowDimensions();
167
+ const [zoomed, setZoomed] = useState(false);
168
+ let t1;
169
+ if ($[0] !== scale || $[1] !== width) {
170
+ t1 = Math.round(width * scale * ZOOM_SCALE);
171
+ $[0] = scale;
172
+ $[1] = width;
173
+ $[2] = t1;
174
+ } else {
175
+ t1 = $[2];
176
+ }
177
+ const document = useDocumentPages(source, Math.min(MAX_RENDER_WIDTH, t1), false, renderer);
178
+ const ready = document.status === DocumentStatus.READY;
179
+ let t2;
180
+ if ($[3] !== document || $[4] !== title) {
181
+ t2 = title ?? documentHeading(document);
182
+ $[3] = document;
183
+ $[4] = title;
184
+ $[5] = t2;
185
+ } else {
186
+ t2 = $[5];
187
+ }
188
+ const heading = t2;
189
+ const fitWidth = width - insets.left - insets.right - GUTTER * 2;
190
+ const pageWidth = zoomed ? fitWidth * ZOOM_SCALE : fitWidth;
191
+ let t3;
192
+ if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
193
+ t3 = {
194
+ gap: GUTTER * 2
195
+ };
196
+ $[6] = t3;
197
+ } else {
198
+ t3 = $[6];
199
+ }
200
+ let t4;
201
+ if ($[7] !== document.attempt || $[8] !== document.pageCount || $[9] !== document.pages || $[10] !== document.reportPageError || $[11] !== heading || $[12] !== pageWidth) {
202
+ let t5;
203
+ if ($[14] !== document.attempt || $[15] !== document.pageCount || $[16] !== document.reportPageError || $[17] !== heading || $[18] !== pageWidth) {
204
+ t5 = (page, index) => _jsx(ViewerPage, {
205
+ label: heading,
206
+ number: index + 1,
207
+ onError: document.reportPageError,
208
+ page: page,
209
+ pageCount: document.pageCount,
210
+ width: pageWidth
211
+ }, `${document.attempt}:${index}`);
212
+ $[14] = document.attempt;
213
+ $[15] = document.pageCount;
214
+ $[16] = document.reportPageError;
215
+ $[17] = heading;
216
+ $[18] = pageWidth;
217
+ $[19] = t5;
218
+ } else {
219
+ t5 = $[19];
220
+ }
221
+ t4 = document.pages.map(t5);
222
+ $[7] = document.attempt;
223
+ $[8] = document.pageCount;
224
+ $[9] = document.pages;
225
+ $[10] = document.reportPageError;
226
+ $[11] = heading;
227
+ $[12] = pageWidth;
228
+ $[13] = t4;
229
+ } else {
230
+ t4 = $[13];
231
+ }
232
+ let t5;
233
+ if ($[20] !== t4) {
234
+ t5 = _jsx(View, {
235
+ style: t3,
236
+ children: t4
237
+ });
238
+ $[20] = t4;
239
+ $[21] = t5;
240
+ } else {
241
+ t5 = $[21];
242
+ }
243
+ const pages = t5;
244
+ let t6;
245
+ if ($[22] !== theme.bg.muted) {
246
+ t6 = {
247
+ flex: 1,
248
+ backgroundColor: theme.bg.muted
249
+ };
250
+ $[22] = theme.bg.muted;
251
+ $[23] = t6;
252
+ } else {
253
+ t6 = $[23];
254
+ }
255
+ let t7;
256
+ if ($[24] !== ready || $[25] !== zoomed) {
257
+ t7 = ready ? _jsx(AppBar.Action, {
258
+ accessibilityLabel: zoomed ? "Ajustar al ancho" : "Ampliar",
259
+ label: zoomed ? "Ajustar" : "Ampliar",
260
+ onPress: () => setZoomed(_temp)
261
+ }) : null;
262
+ $[24] = ready;
263
+ $[25] = zoomed;
264
+ $[26] = t7;
265
+ } else {
266
+ t7 = $[26];
267
+ }
268
+ let t8;
269
+ if ($[27] !== actions || $[28] !== t7) {
270
+ t8 = _jsxs(_Fragment, {
271
+ children: [t7, actions]
272
+ });
273
+ $[27] = actions;
274
+ $[28] = t7;
275
+ $[29] = t8;
276
+ } else {
277
+ t8 = $[29];
278
+ }
279
+ let t9;
280
+ if ($[30] !== onClose) {
281
+ t9 = _jsx(AppBar.Action, {
282
+ accessibilityLabel: "Cerrar",
283
+ icon: "close",
284
+ onPress: onClose
285
+ });
286
+ $[30] = onClose;
287
+ $[31] = t9;
288
+ } else {
289
+ t9 = $[31];
290
+ }
291
+ let t10;
292
+ if ($[32] !== document.isImage || $[33] !== document.label || $[34] !== document.pageCount) {
293
+ t10 = documentMeta(document.label, document.isImage, document.pageCount);
294
+ $[32] = document.isImage;
295
+ $[33] = document.label;
296
+ $[34] = document.pageCount;
297
+ $[35] = t10;
298
+ } else {
299
+ t10 = $[35];
300
+ }
301
+ let t11;
302
+ if ($[36] !== heading || $[37] !== t10 || $[38] !== t8 || $[39] !== t9) {
303
+ t11 = _jsx(AppBar, {
304
+ actions: t8,
305
+ leading: t9,
306
+ subtitle: t10,
307
+ title: heading
308
+ });
309
+ $[36] = heading;
310
+ $[37] = t10;
311
+ $[38] = t8;
312
+ $[39] = t9;
313
+ $[40] = t11;
314
+ } else {
315
+ t11 = $[40];
316
+ }
317
+ let t12;
318
+ if ($[41] !== document.label || $[42] !== document.retry || $[43] !== document.status || $[44] !== insets.bottom || $[45] !== insets.left || $[46] !== insets.right || $[47] !== pages || $[48] !== ready || $[49] !== zoomed) {
319
+ t12 = ready ? _jsx(ScrollView, {
320
+ contentContainerStyle: {
321
+ paddingTop: GUTTER,
322
+ paddingLeft: insets.left + GUTTER,
323
+ paddingRight: insets.right + GUTTER,
324
+ paddingBottom: insets.bottom + GUTTER * 2
325
+ },
326
+ children: zoomed ? _jsx(ScrollView, {
327
+ horizontal: true,
328
+ children: pages
329
+ }) : pages
330
+ }) : _jsxs(View, {
331
+ style: {
332
+ flex: 1,
333
+ paddingBottom: insets.bottom
334
+ },
335
+ children: [document.status === DocumentStatus.LOADING ? _jsx(State, {
336
+ kind: "loading",
337
+ message: "Generando vista previa\u2026"
338
+ }) : null, document.status === DocumentStatus.ERROR ? _jsx(State, {
339
+ kind: "error",
340
+ message: "No se pudo generar la vista previa del documento.",
341
+ onRetry: document.retry
342
+ }) : null, document.status === DocumentStatus.UNSUPPORTED ? _jsx(State, {
343
+ description: `Esta app no tiene un visor configurado para ${document.label}.`,
344
+ icon: "document",
345
+ kind: "empty",
346
+ title: "Vista previa no disponible"
347
+ }) : null]
348
+ });
349
+ $[41] = document.label;
350
+ $[42] = document.retry;
351
+ $[43] = document.status;
352
+ $[44] = insets.bottom;
353
+ $[45] = insets.left;
354
+ $[46] = insets.right;
355
+ $[47] = pages;
356
+ $[48] = ready;
357
+ $[49] = zoomed;
358
+ $[50] = t12;
359
+ } else {
360
+ t12 = $[50];
361
+ }
362
+ let t13;
363
+ if ($[51] !== t11 || $[52] !== t12 || $[53] !== t6) {
364
+ t13 = _jsxs(View, {
365
+ style: t6,
366
+ children: [t11, t12]
367
+ });
368
+ $[51] = t11;
369
+ $[52] = t12;
370
+ $[53] = t6;
371
+ $[54] = t13;
372
+ } else {
373
+ t13 = $[54];
374
+ }
375
+ return t13;
376
+ }
377
+ function _temp(previous) {
378
+ return !previous;
379
+ }
380
+ function DocumentViewer(t0) {
381
+ const $ = _c(18);
382
+ const {
383
+ visible,
384
+ source,
385
+ title,
386
+ onClose,
387
+ actions,
388
+ renderer,
389
+ testID
390
+ } = t0;
391
+ const reducedMotion = useReducedMotion();
392
+ const themeVars = useThemeVars();
393
+ const t1 = reducedMotion ? "none" : "slide";
394
+ const t2 = visible && source !== null;
395
+ let t3;
396
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
397
+ t3 = {
398
+ flex: 1
399
+ };
400
+ $[0] = t3;
401
+ } else {
402
+ t3 = $[0];
403
+ }
404
+ let t4;
405
+ if ($[1] !== themeVars) {
406
+ t4 = [t3, themeVars];
407
+ $[1] = themeVars;
408
+ $[2] = t4;
409
+ } else {
410
+ t4 = $[2];
411
+ }
412
+ let t5;
413
+ if ($[3] !== actions || $[4] !== onClose || $[5] !== renderer || $[6] !== source || $[7] !== title) {
414
+ t5 = source ? _jsx(ViewerContent, {
415
+ actions: actions,
416
+ onClose: onClose,
417
+ renderer: renderer,
418
+ source: source,
419
+ title: title
420
+ }) : null;
421
+ $[3] = actions;
422
+ $[4] = onClose;
423
+ $[5] = renderer;
424
+ $[6] = source;
425
+ $[7] = title;
426
+ $[8] = t5;
427
+ } else {
428
+ t5 = $[8];
429
+ }
430
+ let t6;
431
+ if ($[9] !== t4 || $[10] !== t5 || $[11] !== testID) {
432
+ t6 = _jsx(View, {
433
+ style: t4,
434
+ testID: testID,
435
+ children: t5
436
+ });
437
+ $[9] = t4;
438
+ $[10] = t5;
439
+ $[11] = testID;
440
+ $[12] = t6;
441
+ } else {
442
+ t6 = $[12];
443
+ }
444
+ let t7;
445
+ if ($[13] !== onClose || $[14] !== t1 || $[15] !== t2 || $[16] !== t6) {
446
+ t7 = _jsx(Modal, {
447
+ animationType: t1,
448
+ navigationBarTranslucent: true,
449
+ onRequestClose: onClose,
450
+ statusBarTranslucent: true,
451
+ visible: t2,
452
+ children: t6
453
+ });
454
+ $[13] = onClose;
455
+ $[14] = t1;
456
+ $[15] = t2;
457
+ $[16] = t6;
458
+ $[17] = t7;
459
+ } else {
460
+ t7 = $[17];
461
+ }
462
+ return t7;
463
+ }
464
+ export default DocumentViewer;
465
+ export { DocumentViewer };
@@ -0,0 +1,21 @@
1
+ import { type DocumentRenderer } from "./document-renderer.js";
2
+ import { type DocumentPage, type DocumentSource } from "../utils/document.js";
3
+ export declare enum DocumentStatus {
4
+ LOADING = "loading",
5
+ READY = "ready",
6
+ UNSUPPORTED = "unsupported",
7
+ ERROR = "error"
8
+ }
9
+ export interface DocumentPagesState {
10
+ label: string;
11
+ isImage: boolean;
12
+ status: DocumentStatus;
13
+ pageCount: number;
14
+ pages: DocumentPage[];
15
+ attempt: number;
16
+ retry: () => void;
17
+ reportPageError: () => void;
18
+ }
19
+ export declare function documentMeta(label: string, isImage: boolean, pageCount: number): string;
20
+ export declare function documentHeading(state: DocumentPagesState): string;
21
+ export declare function useDocumentPages(source: DocumentSource, maxWidth: number, firstPageOnly: boolean, override?: DocumentRenderer): DocumentPagesState;
@@ -0,0 +1,282 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { documentRenderer } from "./document-renderer.js";
3
+ import { documentImageUri, documentMimeType, isImageMimeType, mimeTypeLabel } from "../utils/document.js";
4
+ import { useEffect, useState } from "react";
5
+ export let DocumentStatus = /*#__PURE__*/function (DocumentStatus) {
6
+ DocumentStatus["LOADING"] = "loading";
7
+ DocumentStatus["READY"] = "ready";
8
+ DocumentStatus["UNSUPPORTED"] = "unsupported";
9
+ DocumentStatus["ERROR"] = "error";
10
+ return DocumentStatus;
11
+ }({});
12
+ export function documentMeta(label, isImage, pageCount) {
13
+ if (isImage || pageCount === 0) return label;
14
+ return `${label} · ${pageCount} ${pageCount === 1 ? "página" : "páginas"}`;
15
+ }
16
+ export function documentHeading(state) {
17
+ return state.isImage ? state.label : `Documento ${state.label}`;
18
+ }
19
+ export function useDocumentPages(source, maxWidth, firstPageOnly, override) {
20
+ const $ = _c(45);
21
+ let isImage;
22
+ let renderer;
23
+ let t0;
24
+ if ($[0] !== override || $[1] !== source) {
25
+ const detected = documentMimeType(source);
26
+ renderer = override ?? documentRenderer(detected);
27
+ isImage = !renderer && isImageMimeType(detected);
28
+ t0 = renderer?.label ?? mimeTypeLabel(detected);
29
+ $[0] = override;
30
+ $[1] = source;
31
+ $[2] = isImage;
32
+ $[3] = renderer;
33
+ $[4] = t0;
34
+ } else {
35
+ isImage = $[2];
36
+ renderer = $[3];
37
+ t0 = $[4];
38
+ }
39
+ const label = t0;
40
+ const isUri = "uri" in source;
41
+ const value = isUri ? source.uri : source.base64;
42
+ const {
43
+ mimeType
44
+ } = source;
45
+ const [attempt, setAttempt] = useState(0);
46
+ const [rendered, setRendered] = useState(null);
47
+ const [pageFailure, setPageFailure] = useState(null);
48
+ let t1;
49
+ let t2;
50
+ if ($[5] !== attempt || $[6] !== firstPageOnly || $[7] !== isUri || $[8] !== maxWidth || $[9] !== mimeType || $[10] !== renderer || $[11] !== value) {
51
+ t1 = () => {
52
+ if (!renderer) {
53
+ return;
54
+ }
55
+ const controller = new AbortController();
56
+ const target = isUri ? {
57
+ uri: value,
58
+ mimeType
59
+ } : {
60
+ base64: value,
61
+ mimeType
62
+ };
63
+ const request = {
64
+ renderer,
65
+ value,
66
+ isUri,
67
+ mimeType,
68
+ maxWidth,
69
+ firstPageOnly,
70
+ attempt
71
+ };
72
+ renderer.render(target, {
73
+ maxWidth,
74
+ firstPageOnly,
75
+ signal: controller.signal
76
+ }).then(result => {
77
+ if (!controller.signal.aborted) {
78
+ setRendered({
79
+ ...request,
80
+ failed: false,
81
+ ...result
82
+ });
83
+ }
84
+ }, error => {
85
+ if (controller.signal.aborted) {
86
+ return;
87
+ }
88
+ console.error(`[Document] ${renderer.label} preview failed`, error);
89
+ setRendered({
90
+ ...request,
91
+ failed: true,
92
+ pageCount: 0,
93
+ pages: []
94
+ });
95
+ });
96
+ return () => controller.abort();
97
+ };
98
+ t2 = [renderer, isUri, value, mimeType, maxWidth, firstPageOnly, attempt];
99
+ $[5] = attempt;
100
+ $[6] = firstPageOnly;
101
+ $[7] = isUri;
102
+ $[8] = maxWidth;
103
+ $[9] = mimeType;
104
+ $[10] = renderer;
105
+ $[11] = value;
106
+ $[12] = t1;
107
+ $[13] = t2;
108
+ } else {
109
+ t1 = $[12];
110
+ t2 = $[13];
111
+ }
112
+ useEffect(t1, t2);
113
+ let t3;
114
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
115
+ t3 = () => setAttempt(_temp);
116
+ $[14] = t3;
117
+ } else {
118
+ t3 = $[14];
119
+ }
120
+ let t4;
121
+ if ($[15] !== attempt || $[16] !== value) {
122
+ t4 = () => setPageFailure({
123
+ value,
124
+ attempt
125
+ });
126
+ $[15] = attempt;
127
+ $[16] = value;
128
+ $[17] = t4;
129
+ } else {
130
+ t4 = $[17];
131
+ }
132
+ let t5;
133
+ if ($[18] !== attempt || $[19] !== isImage || $[20] !== label || $[21] !== t4) {
134
+ t5 = {
135
+ label,
136
+ isImage,
137
+ attempt,
138
+ retry: t3,
139
+ reportPageError: t4
140
+ };
141
+ $[18] = attempt;
142
+ $[19] = isImage;
143
+ $[20] = label;
144
+ $[21] = t4;
145
+ $[22] = t5;
146
+ } else {
147
+ t5 = $[22];
148
+ }
149
+ const base = t5;
150
+ let t6;
151
+ if ($[23] === Symbol.for("react.memo_cache_sentinel")) {
152
+ t6 = [];
153
+ $[23] = t6;
154
+ } else {
155
+ t6 = $[23];
156
+ }
157
+ let t7;
158
+ if ($[24] !== base) {
159
+ t7 = {
160
+ ...base,
161
+ pageCount: 0,
162
+ pages: t6
163
+ };
164
+ $[24] = base;
165
+ $[25] = t7;
166
+ } else {
167
+ t7 = $[25];
168
+ }
169
+ const empty = t7;
170
+ const pageFailed = pageFailure?.value === value && pageFailure.attempt === attempt;
171
+ if (pageFailed) {
172
+ let t8;
173
+ if ($[26] !== empty) {
174
+ t8 = {
175
+ ...empty,
176
+ status: DocumentStatus.ERROR
177
+ };
178
+ $[26] = empty;
179
+ $[27] = t8;
180
+ } else {
181
+ t8 = $[27];
182
+ }
183
+ return t8;
184
+ }
185
+ if (isImage) {
186
+ let t8;
187
+ if ($[28] !== source) {
188
+ t8 = documentImageUri(source);
189
+ $[28] = source;
190
+ $[29] = t8;
191
+ } else {
192
+ t8 = $[29];
193
+ }
194
+ let t9;
195
+ if ($[30] !== t8) {
196
+ t9 = [{
197
+ uri: t8
198
+ }];
199
+ $[30] = t8;
200
+ $[31] = t9;
201
+ } else {
202
+ t9 = $[31];
203
+ }
204
+ let t10;
205
+ if ($[32] !== base || $[33] !== t9) {
206
+ t10 = {
207
+ ...base,
208
+ status: DocumentStatus.READY,
209
+ pageCount: 1,
210
+ pages: t9
211
+ };
212
+ $[32] = base;
213
+ $[33] = t9;
214
+ $[34] = t10;
215
+ } else {
216
+ t10 = $[34];
217
+ }
218
+ return t10;
219
+ }
220
+ if (!renderer) {
221
+ let t8;
222
+ if ($[35] !== empty) {
223
+ t8 = {
224
+ ...empty,
225
+ status: DocumentStatus.UNSUPPORTED
226
+ };
227
+ $[35] = empty;
228
+ $[36] = t8;
229
+ } else {
230
+ t8 = $[36];
231
+ }
232
+ return t8;
233
+ }
234
+ const current = rendered?.renderer === renderer && rendered.value === value && rendered.isUri === isUri && rendered.mimeType === mimeType && rendered.maxWidth === maxWidth && rendered.firstPageOnly === firstPageOnly && rendered.attempt === attempt ? rendered : null;
235
+ if (!current) {
236
+ let t8;
237
+ if ($[37] !== empty) {
238
+ t8 = {
239
+ ...empty,
240
+ status: DocumentStatus.LOADING
241
+ };
242
+ $[37] = empty;
243
+ $[38] = t8;
244
+ } else {
245
+ t8 = $[38];
246
+ }
247
+ return t8;
248
+ }
249
+ if (current.failed) {
250
+ let t8;
251
+ if ($[39] !== empty) {
252
+ t8 = {
253
+ ...empty,
254
+ status: DocumentStatus.ERROR
255
+ };
256
+ $[39] = empty;
257
+ $[40] = t8;
258
+ } else {
259
+ t8 = $[40];
260
+ }
261
+ return t8;
262
+ }
263
+ let t8;
264
+ if ($[41] !== base || $[42] !== current.pageCount || $[43] !== current.pages) {
265
+ t8 = {
266
+ ...base,
267
+ status: DocumentStatus.READY,
268
+ pageCount: current.pageCount,
269
+ pages: current.pages
270
+ };
271
+ $[41] = base;
272
+ $[42] = current.pageCount;
273
+ $[43] = current.pages;
274
+ $[44] = t8;
275
+ } else {
276
+ t8 = $[44];
277
+ }
278
+ return t8;
279
+ }
280
+ function _temp(previous) {
281
+ return previous + 1;
282
+ }
@@ -0,0 +1,30 @@
1
+ import type { DocumentPage, DocumentSource } from "../utils/document.js";
2
+ /**
3
+ * The design system shows pages; turning a document into pages is the app's job. Register
4
+ * one renderer per MIME type at startup (`"image/*"` matches every image):
5
+ *
6
+ * registerDocumentRenderers({
7
+ * "application/pdf": { label: "PDF", render: renderPdfPages },
8
+ * "text/html": { label: "HTML", render: renderHtmlPages },
9
+ * });
10
+ *
11
+ * A component can override it with its `renderer` prop; keep that object stable (module
12
+ * scope), since a new one starts a new render. Images without a renderer load natively;
13
+ * any other type without one shows that the preview is unavailable.
14
+ */
15
+ export interface DocumentRenderRequest {
16
+ maxWidth: number;
17
+ firstPageOnly: boolean;
18
+ signal: AbortSignal;
19
+ }
20
+ export interface DocumentRenderResult {
21
+ pageCount: number;
22
+ pages: DocumentPage[];
23
+ }
24
+ export interface DocumentRenderer {
25
+ label: string;
26
+ render: (source: DocumentSource, request: DocumentRenderRequest) => Promise<DocumentRenderResult>;
27
+ }
28
+ export declare function registerDocumentRenderers(entries: Record<string, DocumentRenderer | null>): void;
29
+ export declare function resetDocumentRenderers(): void;
30
+ export declare function documentRenderer(mimeType: string | undefined): DocumentRenderer | undefined;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The design system shows pages; turning a document into pages is the app's job. Register
3
+ * one renderer per MIME type at startup (`"image/*"` matches every image):
4
+ *
5
+ * registerDocumentRenderers({
6
+ * "application/pdf": { label: "PDF", render: renderPdfPages },
7
+ * "text/html": { label: "HTML", render: renderHtmlPages },
8
+ * });
9
+ *
10
+ * A component can override it with its `renderer` prop; keep that object stable (module
11
+ * scope), since a new one starts a new render. Images without a renderer load natively;
12
+ * any other type without one shows that the preview is unavailable.
13
+ */
14
+
15
+ const renderers = new Map();
16
+ export function registerDocumentRenderers(entries) {
17
+ for (const [mimeType, renderer] of Object.entries(entries)) {
18
+ if (renderer) renderers.set(mimeType.toLowerCase(), renderer);else renderers.delete(mimeType.toLowerCase());
19
+ }
20
+ }
21
+ export function resetDocumentRenderers() {
22
+ renderers.clear();
23
+ }
24
+ export function documentRenderer(mimeType) {
25
+ if (!mimeType) return undefined;
26
+ const type = mimeType.toLowerCase();
27
+ return renderers.get(type) ?? renderers.get(`${type.split("/")[0]}/*`);
28
+ }
@@ -8,6 +8,8 @@ import CountRecords from "./Display.CountRecords.js";
8
8
  import DataTable from "./Display.DataTable.js";
9
9
  import Date from "./Display.Date.js";
10
10
  import Divider from "./Display.Divider.js";
11
+ import DocumentPreview from "./Display.DocumentPreview.js";
12
+ import DocumentViewer from "./Display.DocumentViewer.js";
11
13
  import ExpoImage from "./Display.ExpoImage.js";
12
14
  import GradientSurface from "./Display.GradientSurface.js";
13
15
  import IconBadge from "./Display.IconBadge.js";
@@ -70,6 +72,8 @@ export declare const Display: {
70
72
  Tag: typeof Tag;
71
73
  Image: typeof DisplayImage;
72
74
  ExpoImage: typeof ExpoImage;
75
+ DocumentPreview: typeof DocumentPreview;
76
+ DocumentViewer: typeof DocumentViewer;
73
77
  MediaCard: typeof MediaCard;
74
78
  MediaEmbed: typeof MediaEmbed;
75
79
  Story: typeof Story;
@@ -109,13 +113,17 @@ export { type BadgeProps, BadgeVariants };
109
113
  export type { AmountSummaryRootProps, AmountSummaryRowProps, AmountSummaryTotalProps, } from "./Display.AmountSummary.js";
110
114
  export type { CarouselControlsProps, CarouselProps, } from "./Display.Carousel.js";
111
115
  export type { CircleButtonProps } from "./Display.CircleButton.js";
116
+ export type { DocumentPreviewProps } from "./Display.DocumentPreview.js";
117
+ export type { DocumentViewerProps } from "./Display.DocumentViewer.js";
112
118
  export type { IconProps } from "./Display.Icon.js";
113
119
  export type { ListGroupProps } from "./Display.ListGroup.js";
114
120
  export type { ListItemProps } from "./Display.ListItem.js";
115
121
  export type { MenuCardProps } from "./Display.MenuCard.js";
116
122
  export type { MenuGridItem } from "./Display.MenuGrid.js";
117
123
  export { defineStatuses, resolveStatus, type StatusBadgeProps, type StatusDefinition, type StatusMap, type StatusTone, } from "./Display.StatusBadge.js";
124
+ export { type DocumentRenderer, type DocumentRenderRequest, type DocumentRenderResult, registerDocumentRenderers, resetDocumentRenderers, } from "./document-renderer.js";
118
125
  export { ICON_FONT_FAMILY, ICON_FONTS } from "./icon-font.js";
119
126
  export { type BuiltInIconName, ICON_NAMES, type IconName, } from "./icon-map.js";
120
127
  export { type CustomIconRegistry, type CustomIconRenderer, registerIcons, } from "./icon-registry.js";
128
+ export { documentBase64, documentMimeType, type DocumentPage, type DocumentSource, } from "../utils/document.js";
121
129
  export default Display;
@@ -12,6 +12,8 @@ import CountRecords from "./Display.CountRecords.js";
12
12
  import DataTable from "./Display.DataTable.js";
13
13
  import Date from "./Display.Date.js";
14
14
  import Divider from "./Display.Divider.js";
15
+ import DocumentPreview from "./Display.DocumentPreview.js";
16
+ import DocumentViewer from "./Display.DocumentViewer.js";
15
17
  import ExpoImage from "./Display.ExpoImage.js";
16
18
  import GradientSurface from "./Display.GradientSurface.js";
17
19
  import Icon from "./Display.Icon.js";
@@ -58,6 +60,8 @@ export const Display = {
58
60
  Tag,
59
61
  Image: DisplayImage,
60
62
  ExpoImage,
63
+ DocumentPreview,
64
+ DocumentViewer,
61
65
  MediaCard,
62
66
  MediaEmbed,
63
67
  Story,
@@ -95,7 +99,9 @@ export const Display = {
95
99
  };
96
100
  export { BadgeVariants };
97
101
  export { defineStatuses, resolveStatus } from "./Display.StatusBadge.js";
102
+ export { registerDocumentRenderers, resetDocumentRenderers } from "./document-renderer.js";
98
103
  export { ICON_FONT_FAMILY, ICON_FONTS } from "./icon-font.js";
99
104
  export { ICON_NAMES } from "./icon-map.js";
100
105
  export { registerIcons } from "./icon-registry.js";
106
+ export { documentBase64, documentMimeType } from "../utils/document.js";
101
107
  export default Display;
@@ -0,0 +1,18 @@
1
+ export type DocumentSource = {
2
+ uri: string;
3
+ mimeType?: string;
4
+ } | {
5
+ base64: string;
6
+ mimeType?: string;
7
+ };
8
+ export interface DocumentPage {
9
+ uri: string;
10
+ width?: number;
11
+ height?: number;
12
+ }
13
+ export declare function stripDataUri(value: string): string;
14
+ export declare function documentMimeType(source: DocumentSource): string | undefined;
15
+ export declare function isImageMimeType(mimeType: string | undefined): boolean;
16
+ export declare function mimeTypeLabel(mimeType: string | undefined): string;
17
+ export declare function documentBase64(source: DocumentSource): string | undefined;
18
+ export declare function documentImageUri(source: DocumentSource): string;
@@ -0,0 +1,46 @@
1
+ const DATA_URI_PREFIX = /^data:([^;,]*)[^,]*,/;
2
+ const BASE64_SIGNATURES = [["JVBER", "application/pdf"], ["iVBORw0KGgo", "image/png"], ["/9j/", "image/jpeg"], ["R0lGOD", "image/gif"], ["UklGR", "image/webp"], ["PCFET0NUWVBF", "text/html"], ["PCFkb2N0eXBl", "text/html"], ["PGh0bWw", "text/html"]];
3
+ const FILE_EXTENSIONS = {
4
+ pdf: "application/pdf",
5
+ html: "text/html",
6
+ htm: "text/html"
7
+ };
8
+ const MIME_LABELS = {
9
+ "application/pdf": "PDF",
10
+ "text/html": "HTML"
11
+ };
12
+ function dataUriMimeType(value) {
13
+ return DATA_URI_PREFIX.exec(value)?.[1] || undefined;
14
+ }
15
+ export function stripDataUri(value) {
16
+ const prefix = DATA_URI_PREFIX.exec(value);
17
+ return prefix ? value.slice(prefix[0].length) : value;
18
+ }
19
+ export function documentMimeType(source) {
20
+ if (source.mimeType) return source.mimeType;
21
+ if ("uri" in source) {
22
+ const extension = /\.([a-z0-9]+)($|[?#])/i.exec(source.uri)?.[1]?.toLowerCase();
23
+ return dataUriMimeType(source.uri) ?? (extension ? FILE_EXTENSIONS[extension] : undefined);
24
+ }
25
+ const declared = dataUriMimeType(source.base64);
26
+ if (declared) return declared;
27
+ const payload = source.base64.trimStart();
28
+ return BASE64_SIGNATURES.find(([signature]) => payload.startsWith(signature))?.[1];
29
+ }
30
+ export function isImageMimeType(mimeType) {
31
+ return !mimeType || mimeType.toLowerCase().startsWith("image/");
32
+ }
33
+ export function mimeTypeLabel(mimeType) {
34
+ if (isImageMimeType(mimeType)) return "Imagen";
35
+ const type = mimeType.toLowerCase();
36
+ return MIME_LABELS[type] ?? type.split("/").pop().toUpperCase();
37
+ }
38
+ export function documentBase64(source) {
39
+ if ("base64" in source) return stripDataUri(source.base64).trim();
40
+ return source.uri.startsWith("data:") ? stripDataUri(source.uri) : undefined;
41
+ }
42
+ export function documentImageUri(source) {
43
+ if ("uri" in source) return source.uri;
44
+ if (source.base64.startsWith("data:")) return source.base64;
45
+ return `data:${documentMimeType(source) ?? "image/png"};base64,${source.base64.trim()}`;
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sincpro/mobile-ui",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Sincpro Mobile design system: generic, presentational React Native components (no domain logic)",
5
5
  "main": "expo/AppEntry.js",
6
6
  "types": "./dist/index.d.ts",
@@ -119,8 +119,10 @@
119
119
  "eslint-plugin-react-hooks": "^7.1.1",
120
120
  "eslint-plugin-simple-import-sort": "^12.1.1",
121
121
  "expo": "~58.0.23",
122
+ "expo-asset": "~58.0.20",
122
123
  "expo-checkbox": "~58.0.2",
123
124
  "expo-clipboard": "~58.0.4",
125
+ "expo-file-system": "~58.0.13",
124
126
  "expo-font": "~58.0.11",
125
127
  "expo-image": "~58.0.18",
126
128
  "expo-linear-gradient": "~58.0.3",
@@ -139,6 +141,7 @@
139
141
  "react-native-currency-input": "^1.1.1",
140
142
  "react-native-gesture-handler": "~3.3.0",
141
143
  "react-native-keyboard-controller": "1.22.7",
144
+ "react-native-pdf-thumbnail": "2.0.0",
142
145
  "react-native-reanimated": "4.7.1",
143
146
  "react-native-safe-area-context": "~5.11.1",
144
147
  "react-native-sortables": "^1.10.1",