@microsoft/power-apps-native-host 0.2.22

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.
Files changed (104) hide show
  1. package/LICENSE +112 -0
  2. package/README.md +59 -0
  3. package/app.plugin.js +4 -0
  4. package/lib/NOTICE.txt +1659 -0
  5. package/lib/commonjs/ErrorBoundary.js +128 -0
  6. package/lib/commonjs/auth/AuthContext.js +281 -0
  7. package/lib/commonjs/bridge/NativePowerAppsBridge.js +279 -0
  8. package/lib/commonjs/bridge/setupNativeHost.js +65 -0
  9. package/lib/commonjs/connections/ConnectionSetupScreen.js +531 -0
  10. package/lib/commonjs/connections/disambiguate.js +88 -0
  11. package/lib/commonjs/connections/types.js +58 -0
  12. package/lib/commonjs/connections/useConnectionSetup.js +236 -0
  13. package/lib/commonjs/controls/FilePicker.js +183 -0
  14. package/lib/commonjs/controls/ImagePicker.js +236 -0
  15. package/lib/commonjs/index.js +246 -0
  16. package/lib/commonjs/logger.js +27 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +606 -0
  19. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +194 -0
  20. package/lib/commonjs/platform/resolveConnections.js +264 -0
  21. package/lib/commonjs/platform/useConnectionRefs.js +270 -0
  22. package/lib/commonjs/provider/PowerAppsHostProvider.js +336 -0
  23. package/lib/commonjs/provider/PowerAppsProvider.js +351 -0
  24. package/lib/commonjs/telemetry/index.js +70 -0
  25. package/lib/commonjs/theme/ThemeProvider.js +80 -0
  26. package/lib/commonjs/theme/tokens.js +50 -0
  27. package/lib/commonjs/utils/dataverseUtils.js +98 -0
  28. package/lib/commonjs/utils/isDevPlayer.js +27 -0
  29. package/lib/module/ErrorBoundary.js +122 -0
  30. package/lib/module/auth/AuthContext.js +276 -0
  31. package/lib/module/bridge/NativePowerAppsBridge.js +273 -0
  32. package/lib/module/bridge/setupNativeHost.js +62 -0
  33. package/lib/module/connections/ConnectionSetupScreen.js +526 -0
  34. package/lib/module/connections/disambiguate.js +83 -0
  35. package/lib/module/connections/types.js +60 -0
  36. package/lib/module/connections/useConnectionSetup.js +233 -0
  37. package/lib/module/controls/FilePicker.js +178 -0
  38. package/lib/module/controls/ImagePicker.js +231 -0
  39. package/lib/module/index.js +41 -0
  40. package/lib/module/logger.js +22 -0
  41. package/lib/module/package.json +1 -0
  42. package/lib/module/platform/PowerPlatformConnectionApi.js +595 -0
  43. package/lib/module/platform/PowerPlatformEnvironmentApi.js +186 -0
  44. package/lib/module/platform/resolveConnections.js +259 -0
  45. package/lib/module/platform/useConnectionRefs.js +267 -0
  46. package/lib/module/provider/PowerAppsHostProvider.js +332 -0
  47. package/lib/module/provider/PowerAppsProvider.js +345 -0
  48. package/lib/module/telemetry/index.js +55 -0
  49. package/lib/module/theme/ThemeProvider.js +63 -0
  50. package/lib/module/theme/tokens.js +46 -0
  51. package/lib/module/utils/dataverseUtils.js +90 -0
  52. package/lib/module/utils/isDevPlayer.js +23 -0
  53. package/lib/typescript/commonjs/ErrorBoundary.d.ts +30 -0
  54. package/lib/typescript/commonjs/auth/AuthContext.d.ts +57 -0
  55. package/lib/typescript/commonjs/bridge/NativePowerAppsBridge.d.ts +112 -0
  56. package/lib/typescript/commonjs/bridge/setupNativeHost.d.ts +39 -0
  57. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +107 -0
  58. package/lib/typescript/commonjs/connections/disambiguate.d.ts +43 -0
  59. package/lib/typescript/commonjs/connections/types.d.ts +91 -0
  60. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +96 -0
  61. package/lib/typescript/commonjs/controls/FilePicker.d.ts +65 -0
  62. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +76 -0
  63. package/lib/typescript/commonjs/index.d.ts +42 -0
  64. package/lib/typescript/commonjs/logger.d.ts +11 -0
  65. package/lib/typescript/commonjs/package.json +1 -0
  66. package/lib/typescript/commonjs/platform/PowerPlatformConnectionApi.d.ts +205 -0
  67. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  68. package/lib/typescript/commonjs/platform/resolveConnections.d.ts +86 -0
  69. package/lib/typescript/commonjs/platform/useConnectionRefs.d.ts +126 -0
  70. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +123 -0
  71. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +148 -0
  72. package/lib/typescript/commonjs/telemetry/index.d.ts +48 -0
  73. package/lib/typescript/commonjs/theme/ThemeProvider.d.ts +30 -0
  74. package/lib/typescript/commonjs/theme/tokens.d.ts +27 -0
  75. package/lib/typescript/commonjs/utils/dataverseUtils.d.ts +39 -0
  76. package/lib/typescript/commonjs/utils/isDevPlayer.d.ts +19 -0
  77. package/lib/typescript/module/ErrorBoundary.d.ts +30 -0
  78. package/lib/typescript/module/auth/AuthContext.d.ts +57 -0
  79. package/lib/typescript/module/bridge/NativePowerAppsBridge.d.ts +112 -0
  80. package/lib/typescript/module/bridge/setupNativeHost.d.ts +39 -0
  81. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +107 -0
  82. package/lib/typescript/module/connections/disambiguate.d.ts +43 -0
  83. package/lib/typescript/module/connections/types.d.ts +91 -0
  84. package/lib/typescript/module/connections/useConnectionSetup.d.ts +96 -0
  85. package/lib/typescript/module/controls/FilePicker.d.ts +65 -0
  86. package/lib/typescript/module/controls/ImagePicker.d.ts +76 -0
  87. package/lib/typescript/module/index.d.ts +42 -0
  88. package/lib/typescript/module/logger.d.ts +11 -0
  89. package/lib/typescript/module/package.json +1 -0
  90. package/lib/typescript/module/platform/PowerPlatformConnectionApi.d.ts +205 -0
  91. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +108 -0
  92. package/lib/typescript/module/platform/resolveConnections.d.ts +86 -0
  93. package/lib/typescript/module/platform/useConnectionRefs.d.ts +126 -0
  94. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +123 -0
  95. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +148 -0
  96. package/lib/typescript/module/telemetry/index.d.ts +48 -0
  97. package/lib/typescript/module/theme/ThemeProvider.d.ts +30 -0
  98. package/lib/typescript/module/theme/tokens.d.ts +27 -0
  99. package/lib/typescript/module/utils/dataverseUtils.d.ts +39 -0
  100. package/lib/typescript/module/utils/isDevPlayer.d.ts +19 -0
  101. package/package.json +119 -0
  102. package/scripts/build-ios-base.js +96 -0
  103. package/scripts/bundle.js +95 -0
  104. package/scripts/generate-connector-schemas.js +75 -0
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ConnectionStatus = void 0;
7
+ /*!
8
+ * power-apps-native-host — connection disambiguation types
9
+ *
10
+ * Defines the native-host-specific types for connection resolution. The web
11
+ * host equivalents live in @microsoft/power-apps-host, but the native host
12
+ * implements a lighter version that does not depend on IConnectionService.
13
+ */
14
+ /**
15
+ * Resolved authorization state of a connection instance.
16
+ *
17
+ * - `Connected` — platform status is Connected AND the ISV appid is in the connection ACL.
18
+ * - `NeedsAclGrant` — platform status is Connected but the ISV appid is missing from the ACL.
19
+ * - `NotConnected` — platform status is anything other than Connected.
20
+ */
21
+ let ConnectionStatus = exports.ConnectionStatus = /*#__PURE__*/function (ConnectionStatus) {
22
+ ConnectionStatus["Connected"] = "Connected";
23
+ ConnectionStatus["NeedsAclGrant"] = "NeedsAclGrant";
24
+ ConnectionStatus["NotConnected"] = "NotConnected";
25
+ return ConnectionStatus;
26
+ }({});
27
+ /** A single connection instance returned by the Power Platform connectivity API. */
28
+ /**
29
+ * Result of attempting to match a single connection reference to an instance.
30
+ *
31
+ * - `resolved` — exactly one Connected connection found; `connectionId` is set.
32
+ * - `missing` — no connections found for this connector in the user's environment.
33
+ * - `ambiguous` — multiple Connected connections found; the app must pick one.
34
+ */
35
+ /**
36
+ * Disambiguation result for a single connection reference (one apiId, possibly
37
+ * mapped to multiple data source names in the final connections map).
38
+ */
39
+ /**
40
+ * Callback invoked when a connection reference has multiple Connected candidate
41
+ * connections. The consuming app should present a native picker UI (e.g. a
42
+ * bottom sheet) and return the selected connection ID.
43
+ *
44
+ * Return `null` to leave the connection unresolved (the SDK will receive an
45
+ * empty `connectionName` and data operations for that connector will fail).
46
+ *
47
+ * @param context.localRefId — local reference key from power.config.json
48
+ * @param context.apiId — connector API identifier
49
+ * @param context.candidates — list of Connected connection instances to choose from
50
+ */
51
+ /**
52
+ * Callback invoked when the user has no connection for a required connector.
53
+ *
54
+ * The consuming app can redirect to the Power Apps maker portal or show an
55
+ * in-app connection setup flow. Return `true` to indicate the user has
56
+ * created a connection and the hook should retry; return `false` to leave the
57
+ * connection unresolved.
58
+ */
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useConnectionSetup = useConnectionSetup;
7
+ var _react = require("react");
8
+ var _PowerPlatformConnectionApi = require("../platform/PowerPlatformConnectionApi.js");
9
+ var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
10
+ var _disambiguate = require("../connections/disambiguate.js");
11
+ var _logger = require("../logger.js");
12
+ var _index = require("../telemetry/index.js");
13
+ /*!
14
+ * power-apps-native-host — useConnectionSetup
15
+ *
16
+ * A hook that drives the "create missing connection" flow for a single connector.
17
+ *
18
+ * Usage pattern (in the ConnectionSetupScreen):
19
+ *
20
+ * const setup = useConnectionSetup({
21
+ * connectorId: 'shared_office365users',
22
+ * environmentId,
23
+ * region,
24
+ * acquireToken,
25
+ * onConnected: (connectionId) => { ... mark resolved, advance to next ... }
26
+ * });
27
+ *
28
+ * // Render setup.consentUrl in a WebView when setup.phase === 'oauth'
29
+ * // Call setup.startCreate() to trigger connection creation
30
+ * // Call setup.checkNow() to re-poll after WebView finishes
31
+ */
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Phase machine
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * The current phase of the connection setup flow for one connector.
39
+ *
40
+ * idle — not yet started.
41
+ * creating — POST in progress (showing a spinner).
42
+ * oauth — connection created; OAuth consent URL is ready (show WebView).
43
+ * confirming — WebView dismissed; polling for Connected status.
44
+ * connected — connection is Connected; ready to proceed.
45
+ * error — unrecoverable error; `errorMessage` is set.
46
+ */
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Hook
50
+ // ---------------------------------------------------------------------------
51
+
52
+ function useConnectionSetup({
53
+ connectorId,
54
+ environmentId,
55
+ region = 'prod',
56
+ acquireToken,
57
+ onConnected,
58
+ isvClientId,
59
+ oauthRedirectUrl,
60
+ polling
61
+ }) {
62
+ const [phase, setPhase] = (0, _react.useState)('idle');
63
+ const [consentUrl, setConsentUrl] = (0, _react.useState)(null);
64
+ const [errorMessage, setErrorMessage] = (0, _react.useState)(null);
65
+
66
+ // Track the connection name across re-renders without triggering re-renders.
67
+ const connectionNameRef = (0, _react.useRef)(null);
68
+ // Prevent concurrent startCreate / checkNow calls.
69
+ const inFlightRef = (0, _react.useRef)(false);
70
+ const handleConnected = (0, _react.useCallback)(async instance => {
71
+ setPhase('connected');
72
+ setConsentUrl(null);
73
+
74
+ // The RP auto-writes the user as Owner when a connection is created.
75
+ // However, APIM also checks that the requesting appid is present in the
76
+ // ACE's AppIds list. For third-party ISV tokens this is never populated
77
+ // automatically — we must explicitly add the ISV's client ID to the
78
+ // connection ACL so that APIM accepts tokens bearing that appid at runtime.
79
+ if (isvClientId) {
80
+ const paToken = await acquireToken([_PowerPlatformConnectionApi.SERVICE_POWERAPPS_SCOPE]);
81
+ const userOid = paToken ? (0, _PowerPlatformConnectionApi.parseJwtOid)(paToken) : null;
82
+ if (!paToken || !userOid) {
83
+ setPhase('error');
84
+ setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
85
+ (0, _logger.debugWarn)('[useConnectionSetup] could not grant connection ACL: missing Power Apps token or user OID');
86
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
87
+ phase: 'setupConnectionAcl',
88
+ reason: !paToken ? 'missingPowerAppsToken' : 'missingUserOid'
89
+ });
90
+ return;
91
+ }
92
+ const aclGranted = await (0, _PowerPlatformConnectionApi.addAadAppToConnectionAcl)(connectorId, instance.id, environmentId, isvClientId, userOid, paToken);
93
+ if (!aclGranted) {
94
+ setPhase('error');
95
+ setErrorMessage('Connection was created, but access could not be granted to this app. Please try again.');
96
+ (0, _index.getAppLogger)().trackErrorEvent(_index.HostOperationNames.PlatformConnectionAclGrant, {
97
+ phase: 'setupConnectionAcl',
98
+ reason: 'aclGrantRejected'
99
+ });
100
+ return;
101
+ }
102
+ }
103
+ onConnected?.(instance);
104
+ }, [onConnected, acquireToken, connectorId, environmentId, isvClientId]);
105
+ const startCreate = (0, _react.useCallback)(async () => {
106
+ if (inFlightRef.current) return;
107
+ inFlightRef.current = true;
108
+ setPhase('creating');
109
+ setErrorMessage(null);
110
+ setConsentUrl(null);
111
+ connectionNameRef.current = null;
112
+ try {
113
+ const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
114
+ if (!token) {
115
+ setPhase('error');
116
+ setErrorMessage('Failed to acquire authentication token');
117
+ return;
118
+ }
119
+ const result = await (0, _PowerPlatformConnectionApi.createConnection)(connectorId, environmentId, region, token, undefined, oauthRedirectUrl);
120
+ if (result.status === 'error') {
121
+ setPhase('error');
122
+ setErrorMessage(result.message);
123
+ return;
124
+ }
125
+ connectionNameRef.current = result.connectionName;
126
+ if (result.status === 'connected') {
127
+ // Connection is already Connected — poll once to get the full instance.
128
+ const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, result.connectionName, environmentId, region, acquireToken, {
129
+ timeoutMs: 10_000,
130
+ ...polling
131
+ });
132
+ if (instance) {
133
+ handleConnected(instance);
134
+ } else {
135
+ // Already created but not Connected yet — treat as oauth phase without a URL.
136
+ setPhase('oauth');
137
+ }
138
+ return;
139
+ }
140
+
141
+ // status === 'pending': show the OAuth URL in a WebView.
142
+ setConsentUrl(result.consentLink);
143
+ setPhase('oauth');
144
+
145
+ // Start background polling immediately — the user may finish OAuth quickly.
146
+ const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, result.connectionName, environmentId, region, acquireToken, polling);
147
+
148
+ // If the phase changed while we were polling (e.g., user dismissed WebView
149
+ // and checkNow already resolved it), don't override.
150
+ if (instance && phase !== 'connected') {
151
+ handleConnected(instance);
152
+ } else if (!instance && phase !== 'connected') {
153
+ setPhase('error');
154
+ setErrorMessage('Connection setup timed out. Please try again.');
155
+ }
156
+ } finally {
157
+ inFlightRef.current = false;
158
+ }
159
+ }, [connectorId, environmentId, region, acquireToken, polling, handleConnected, phase]);
160
+ const checkNow = (0, _react.useCallback)(async () => {
161
+ const name = connectionNameRef.current;
162
+ if (!name || inFlightRef.current) return;
163
+ if (phase !== 'oauth' && phase !== 'confirming') return;
164
+ inFlightRef.current = true;
165
+ setPhase('confirming');
166
+ try {
167
+ const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, name, environmentId, region, acquireToken, {
168
+ intervalMs: 1000,
169
+ timeoutMs: 30_000,
170
+ ...polling
171
+ });
172
+ if (instance) {
173
+ handleConnected(instance);
174
+ } else {
175
+ // Not yet connected — go back to oauth so the user can retry.
176
+ setPhase('oauth');
177
+ setErrorMessage('Connection is not yet active. Complete the sign-in and try again.');
178
+ }
179
+ } finally {
180
+ inFlightRef.current = false;
181
+ }
182
+ }, [phase, environmentId, region, acquireToken, polling, handleConnected]);
183
+ const reset = (0, _react.useCallback)(() => {
184
+ inFlightRef.current = false;
185
+ connectionNameRef.current = null;
186
+ setPhase('idle');
187
+ setConsentUrl(null);
188
+ setErrorMessage(null);
189
+ }, []);
190
+
191
+ /**
192
+ * Call after the in-app browser session closes.
193
+ *
194
+ * If a CommitTokenCode `code` is provided (from the redirect URL `?code=`
195
+ * param), POST it to `confirmConsentCode` first so APIM can activate the
196
+ * connection. Then poll until Connected regardless of whether the confirm
197
+ * call succeeded (the connection may already be Connected server-side).
198
+ */
199
+ const completeConsent = (0, _react.useCallback)(async code => {
200
+ const name = connectionNameRef.current;
201
+ if (!name || inFlightRef.current) return;
202
+ inFlightRef.current = true;
203
+ setPhase('confirming');
204
+ try {
205
+ if (code) {
206
+ const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
207
+ if (token) {
208
+ const connectionId = (0, _disambiguate.extractConnectionId)(name);
209
+ await (0, _PowerPlatformConnectionApi.confirmConsentCode)(connectorId, connectionId, environmentId, region, token, code);
210
+ }
211
+ }
212
+ const instance = await (0, _PowerPlatformConnectionApi.pollConnectionStatus)(connectorId, name, environmentId, region, acquireToken, {
213
+ intervalMs: 1000,
214
+ timeoutMs: 30_000,
215
+ ...polling
216
+ });
217
+ if (instance) {
218
+ handleConnected(instance);
219
+ } else {
220
+ setPhase('oauth');
221
+ setErrorMessage('Connection is not yet active. Complete the sign-in and try again.');
222
+ }
223
+ } finally {
224
+ inFlightRef.current = false;
225
+ }
226
+ }, [connectorId, environmentId, region, acquireToken, polling, handleConnected]);
227
+ return {
228
+ phase,
229
+ consentUrl,
230
+ errorMessage,
231
+ startCreate,
232
+ checkNow,
233
+ completeConsent,
234
+ reset
235
+ };
236
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FilePicker = FilePicker;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var DocumentPicker = _interopRequireWildcard(require("expo-document-picker"));
10
+ var Sharing = _interopRequireWildcard(require("expo-sharing"));
11
+ var _expoFileSystem = require("expo-file-system");
12
+ var _vectorIcons = require("@expo/vector-icons");
13
+ var _ThemeProvider = require("../theme/ThemeProvider.js");
14
+ var _dataverseUtils = require("../utils/dataverseUtils.js");
15
+ var _jsxRuntime = require("react/jsx-runtime");
16
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
17
+ /**
18
+ * A unified file component for view and edit scenarios.
19
+ *
20
+ * - Pass `onDownload` alone for a read-only row that opens the share sheet on tap.
21
+ * - Pass `onChange` alone for a pick-only button (e.g. in an edit form).
22
+ * - Pass both to show the existing file name (tappable to download) alongside a
23
+ * replace button — useful when the edit screen should also allow previewing.
24
+ */
25
+ function FilePicker({
26
+ label,
27
+ fileName,
28
+ onDownload,
29
+ shareTitle,
30
+ onChange,
31
+ acceptedTypes = ['*/*'],
32
+ pickButtonLabel = 'Select File',
33
+ replaceButtonLabel = 'Replace',
34
+ readingLabel = 'Reading file...',
35
+ containerStyle,
36
+ labelStyle,
37
+ fileRowStyle,
38
+ fileNameStyle,
39
+ buttonStyle,
40
+ buttonTextStyle,
41
+ accentColor
42
+ }) {
43
+ const theme = (0, _ThemeProvider.useThemeTokens)();
44
+ const accent = accentColor ?? theme.accentBase;
45
+ const [downloading, setDownloading] = (0, _react.useState)(false);
46
+ const [pickedName, setPickedName] = (0, _react.useState)(null);
47
+ const [reading, setReading] = (0, _react.useState)(false);
48
+ const bg = theme.surface0;
49
+ const labelColor = theme.text2;
50
+ const displayName = pickedName ?? fileName;
51
+ const download = async () => {
52
+ if (!onDownload) return;
53
+ setDownloading(true);
54
+ try {
55
+ const bytes = await onDownload();
56
+ const destFile = new _expoFileSystem.File(_expoFileSystem.Paths.cache, displayName ?? 'download');
57
+ destFile.write(bytes);
58
+ await Sharing.shareAsync(destFile.uri, {
59
+ dialogTitle: shareTitle ?? displayName ?? 'File'
60
+ });
61
+ } catch (e) {
62
+ _reactNative.Alert.alert('Download Failed', e instanceof Error ? e.message : String(e));
63
+ } finally {
64
+ setDownloading(false);
65
+ }
66
+ };
67
+ const pick = async () => {
68
+ if (!onChange) return;
69
+ const result = await DocumentPicker.getDocumentAsync({
70
+ type: acceptedTypes,
71
+ copyToCacheDirectory: true
72
+ });
73
+ if (!result.canceled && result.assets.length > 0) {
74
+ const picked = result.assets[0];
75
+ setPickedName(picked.name);
76
+ setReading(true);
77
+ try {
78
+ const bytes = await (0, _dataverseUtils.readFileAsUint8Array)(picked.uri);
79
+ const mimeType = picked.mimeType ?? 'application/octet-stream';
80
+ onChange({
81
+ uri: picked.uri,
82
+ name: picked.name,
83
+ mimeType,
84
+ file: (0, _dataverseUtils.createDataverseUploadFile)(picked.name, mimeType, bytes),
85
+ bytes
86
+ });
87
+ } finally {
88
+ setReading(false);
89
+ }
90
+ }
91
+ };
92
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
93
+ style: [styles.container, containerStyle],
94
+ children: [label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
95
+ style: [styles.label, {
96
+ color: labelColor
97
+ }, labelStyle],
98
+ children: label
99
+ }) : null, displayName ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
100
+ style: [styles.fileRow, fileRowStyle],
101
+ onPress: onDownload ? download : undefined,
102
+ activeOpacity: onDownload ? 0.7 : 1,
103
+ disabled: downloading,
104
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
105
+ name: "file-text",
106
+ size: 16,
107
+ color: accent
108
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
109
+ style: [styles.fileName, {
110
+ color: accent
111
+ }, fileNameStyle],
112
+ numberOfLines: 1,
113
+ children: displayName
114
+ }), onDownload && (downloading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
115
+ size: "small",
116
+ color: accent
117
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
118
+ name: "download",
119
+ size: 16,
120
+ color: accent
121
+ }))]
122
+ }) : null, onChange ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
123
+ style: [styles.button, {
124
+ borderColor: accent,
125
+ backgroundColor: bg
126
+ }, buttonStyle],
127
+ onPress: pick,
128
+ activeOpacity: 0.7,
129
+ disabled: reading,
130
+ children: [reading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
131
+ size: "small",
132
+ color: accent
133
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Feather, {
134
+ name: displayName ? 'refresh-cw' : 'file-plus',
135
+ size: 16,
136
+ color: accent
137
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
138
+ style: [styles.buttonText, {
139
+ color: accent
140
+ }, buttonTextStyle],
141
+ children: reading ? readingLabel : displayName ? replaceButtonLabel : pickButtonLabel
142
+ })]
143
+ }) : null]
144
+ });
145
+ }
146
+ const styles = _reactNative.StyleSheet.create({
147
+ container: {
148
+ marginBottom: 16
149
+ },
150
+ label: {
151
+ fontSize: 12,
152
+ fontWeight: '600',
153
+ marginBottom: 4
154
+ },
155
+ fileRow: {
156
+ flexDirection: 'row',
157
+ alignItems: 'center',
158
+ gap: 8,
159
+ paddingVertical: 4
160
+ },
161
+ fileName: {
162
+ flex: 1,
163
+ fontSize: 14,
164
+ fontWeight: '600'
165
+ },
166
+ button: {
167
+ flexDirection: 'row',
168
+ alignItems: 'center',
169
+ gap: 8,
170
+ paddingHorizontal: 16,
171
+ paddingVertical: 10,
172
+ borderWidth: 1,
173
+ borderRadius: 8,
174
+ alignSelf: 'flex-start',
175
+ maxWidth: '100%',
176
+ marginTop: 4
177
+ },
178
+ buttonText: {
179
+ fontSize: 14,
180
+ fontWeight: '600',
181
+ flexShrink: 1
182
+ }
183
+ });