@buoy-gg/shared-ui 2.1.3 → 2.1.4-beta.1
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/lib/commonjs/clipboard/clipboard-impl.js +86 -10
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/index.js +42 -0
- package/lib/commonjs/storage/devToolsStorageKeys.js +6 -1
- package/lib/commonjs/utils/index.js +38 -1
- package/lib/commonjs/utils/safeExpoRouter.js +172 -0
- package/lib/module/clipboard/clipboard-impl.js +85 -10
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/storage/devToolsStorageKeys.js +6 -1
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/safeExpoRouter.js +163 -0
- package/lib/typescript/commonjs/clipboard/clipboard-impl.d.ts +15 -8
- package/lib/typescript/commonjs/clipboard/clipboard-impl.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/index.d.ts +1 -0
- package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/safeExpoRouter.d.ts +17 -0
- package/lib/typescript/commonjs/utils/safeExpoRouter.d.ts.map +1 -0
- package/lib/typescript/module/clipboard/clipboard-impl.d.ts +15 -8
- package/lib/typescript/module/clipboard/clipboard-impl.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/module/utils/index.d.ts +1 -0
- package/lib/typescript/module/utils/index.d.ts.map +1 -1
- package/lib/typescript/module/utils/safeExpoRouter.d.ts +17 -0
- package/lib/typescript/module/utils/safeExpoRouter.d.ts.map +1 -0
- package/package.json +4 -7
- package/scripts/detect-clipboard.js +78 -126
|
@@ -5,22 +5,98 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.isClipboardAvailable = exports.clipboardType = exports.clipboardFunction = void 0;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Detected: none
|
|
10
|
-
* Generated at: 2026-02-03T00:33:15.804Z
|
|
8
|
+
* Runtime clipboard implementation
|
|
11
9
|
*
|
|
12
|
-
*
|
|
10
|
+
* Uses Metro's allowOptionalDependencies with top-level try-catch
|
|
11
|
+
* so Metro marks these requires as optional (skipped if not installed).
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* We grab module references eagerly (for Metro), but detect which one
|
|
14
|
+
* actually works lazily on first use — by trying to call it. This avoids
|
|
15
|
+
* NativeModules checks which don't work with TurboModules/new architecture.
|
|
16
|
+
*
|
|
17
|
+
* Consumers must set `transformer.allowOptionalDependencies = true`
|
|
18
|
+
* in their metro.config.js for this to work.
|
|
19
|
+
*
|
|
20
|
+
* Fallback chain:
|
|
21
|
+
* 1. expo-clipboard
|
|
22
|
+
* 2. @react-native-clipboard/clipboard
|
|
23
|
+
* 3. Graceful failure
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
// Grab module references at load time (top-level try-catch for Metro)
|
|
27
|
+
// Always require both — we decide which actually works at call time
|
|
28
|
+
let _expoClipboard = null;
|
|
29
|
+
try {
|
|
30
|
+
_expoClipboard = require("expo-clipboard");
|
|
31
|
+
} catch {}
|
|
32
|
+
let _rnClipboard = null;
|
|
33
|
+
try {
|
|
34
|
+
const mod = require("@react-native-clipboard/clipboard");
|
|
35
|
+
_rnClipboard = mod.default || mod;
|
|
36
|
+
} catch {}
|
|
37
|
+
|
|
38
|
+
// Lazy detection: resolved on first clipboardFunction() call
|
|
39
|
+
let _detectedType = null;
|
|
40
|
+
let _clipboardFn = null;
|
|
41
|
+
let _detected = false;
|
|
42
|
+
async function detect(text) {
|
|
43
|
+
// 1. Try expo-clipboard by actually calling it
|
|
44
|
+
if (_expoClipboard && typeof _expoClipboard.setStringAsync === "function") {
|
|
45
|
+
try {
|
|
46
|
+
await _expoClipboard.setStringAsync(text);
|
|
47
|
+
_detectedType = "expo";
|
|
48
|
+
_clipboardFn = async t => {
|
|
49
|
+
await _expoClipboard.setStringAsync(t);
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
_detected = true;
|
|
53
|
+
return true;
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Try @react-native-clipboard/clipboard
|
|
58
|
+
if (_rnClipboard && typeof _rnClipboard.setString === "function") {
|
|
59
|
+
try {
|
|
60
|
+
_rnClipboard.setString(text);
|
|
61
|
+
_detectedType = "react-native";
|
|
62
|
+
_clipboardFn = async t => {
|
|
63
|
+
_rnClipboard.setString(t);
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
_detected = true;
|
|
67
|
+
return true;
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
_detected = true;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const clipboardType = () => {
|
|
74
|
+
return _detectedType;
|
|
75
|
+
};
|
|
76
|
+
exports.clipboardType = clipboardType;
|
|
77
|
+
const isClipboardAvailable = () => {
|
|
78
|
+
// Before first use, optimistically return true if we have a module ref
|
|
79
|
+
if (!_detected) return _expoClipboard != null || _rnClipboard != null;
|
|
80
|
+
return _clipboardFn != null;
|
|
81
|
+
};
|
|
21
82
|
exports.isClipboardAvailable = isClipboardAvailable;
|
|
22
83
|
const clipboardFunction = async text => {
|
|
23
|
-
|
|
84
|
+
// If already detected, use the cached function
|
|
85
|
+
if (_detected && _clipboardFn) {
|
|
86
|
+
try {
|
|
87
|
+
return await _clipboardFn(text);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("[buoy] Clipboard copy failed:", error);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// First call: detect which clipboard works by actually trying it
|
|
95
|
+
if (!_detected) {
|
|
96
|
+
const result = await detect(text);
|
|
97
|
+
if (result) return true;
|
|
98
|
+
}
|
|
99
|
+
console.warn("[buoy] No clipboard library available. Install expo-clipboard or @react-native-clipboard/clipboard.");
|
|
24
100
|
return false;
|
|
25
101
|
};
|
|
26
102
|
exports.clipboardFunction = clipboardFunction;
|
|
@@ -7,7 +7,7 @@ exports.useNativeSafeAreaInsets = exports.safeAreaType = exports.hasSafeAreaPack
|
|
|
7
7
|
/**
|
|
8
8
|
* Auto-generated safe area implementation
|
|
9
9
|
* Detected: none
|
|
10
|
-
* Generated at: 2026-02-
|
|
10
|
+
* Generated at: 2026-02-11T19:07:44.917Z
|
|
11
11
|
*
|
|
12
12
|
* DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
|
|
13
13
|
*
|
package/lib/commonjs/index.js
CHANGED
|
@@ -30,6 +30,12 @@ var _exportNames = {
|
|
|
30
30
|
subscriberCountNotifier: true,
|
|
31
31
|
subscribeToSubscriberCountChanges: true,
|
|
32
32
|
notifySubscriberCountChange: true,
|
|
33
|
+
useSafeRouter: true,
|
|
34
|
+
useSafePathname: true,
|
|
35
|
+
useSafeSegments: true,
|
|
36
|
+
useSafeGlobalSearchParams: true,
|
|
37
|
+
getSafeRouter: true,
|
|
38
|
+
isExpoRouterAvailable: true,
|
|
33
39
|
isPlainObjectUtil: true,
|
|
34
40
|
useSafeAreaInsets: true,
|
|
35
41
|
useFilterManager: true,
|
|
@@ -202,6 +208,12 @@ Object.defineProperty(exports, "getSafeAreaInsets", {
|
|
|
202
208
|
return _index3.getSafeAreaInsets;
|
|
203
209
|
}
|
|
204
210
|
});
|
|
211
|
+
Object.defineProperty(exports, "getSafeRouter", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function () {
|
|
214
|
+
return _index3.getSafeRouter;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
205
217
|
Object.defineProperty(exports, "getStorageBackendType", {
|
|
206
218
|
enumerable: true,
|
|
207
219
|
get: function () {
|
|
@@ -244,6 +256,12 @@ Object.defineProperty(exports, "isEmpty", {
|
|
|
244
256
|
return _index3.isEmpty;
|
|
245
257
|
}
|
|
246
258
|
});
|
|
259
|
+
Object.defineProperty(exports, "isExpoRouterAvailable", {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
get: function () {
|
|
262
|
+
return _index3.isExpoRouterAvailable;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
247
265
|
Object.defineProperty(exports, "isJsonSerializable", {
|
|
248
266
|
enumerable: true,
|
|
249
267
|
get: function () {
|
|
@@ -376,6 +394,30 @@ Object.defineProperty(exports, "useSafeAreaInsets", {
|
|
|
376
394
|
return _index1.useSafeAreaInsets;
|
|
377
395
|
}
|
|
378
396
|
});
|
|
397
|
+
Object.defineProperty(exports, "useSafeGlobalSearchParams", {
|
|
398
|
+
enumerable: true,
|
|
399
|
+
get: function () {
|
|
400
|
+
return _index3.useSafeGlobalSearchParams;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
Object.defineProperty(exports, "useSafePathname", {
|
|
404
|
+
enumerable: true,
|
|
405
|
+
get: function () {
|
|
406
|
+
return _index3.useSafePathname;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
Object.defineProperty(exports, "useSafeRouter", {
|
|
410
|
+
enumerable: true,
|
|
411
|
+
get: function () {
|
|
412
|
+
return _index3.useSafeRouter;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
Object.defineProperty(exports, "useSafeSegments", {
|
|
416
|
+
enumerable: true,
|
|
417
|
+
get: function () {
|
|
418
|
+
return _index3.useSafeSegments;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
379
421
|
var _index = require("./ui/index.js");
|
|
380
422
|
Object.keys(_index).forEach(function (key) {
|
|
381
423
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -197,11 +197,16 @@ const LEGACY_DEV_TOOL_PATTERNS = ["@devtools", "@dev_tools_", "@modal_state_",
|
|
|
197
197
|
function isDevToolsStorageKey(key) {
|
|
198
198
|
if (!key) return false;
|
|
199
199
|
|
|
200
|
-
// Check if it starts with our base prefix
|
|
200
|
+
// Check if it starts with our base prefix (@react_buoy)
|
|
201
201
|
if (key.startsWith(devToolsStorageKeys.base)) {
|
|
202
202
|
return true;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// Check for buoy- prefixed keys (modal persistence, license, etc.)
|
|
206
|
+
if (key.startsWith("buoy-")) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
205
210
|
// Check for legacy dev tool keys that need cleanup
|
|
206
211
|
for (const pattern of LEGACY_DEV_TOOL_PATTERNS) {
|
|
207
212
|
if (key.startsWith(pattern)) {
|
|
@@ -51,6 +51,12 @@ Object.defineProperty(exports, "getSafeAreaInsets", {
|
|
|
51
51
|
return _getSafeAreaInsets.getSafeAreaInsets;
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
+
Object.defineProperty(exports, "getSafeRouter", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function () {
|
|
57
|
+
return _safeExpoRouter.getSafeRouter;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
54
60
|
Object.defineProperty(exports, "getStorageBackendType", {
|
|
55
61
|
enumerable: true,
|
|
56
62
|
get: function () {
|
|
@@ -81,6 +87,12 @@ Object.defineProperty(exports, "isEmpty", {
|
|
|
81
87
|
return _typeHelpers.isEmpty;
|
|
82
88
|
}
|
|
83
89
|
});
|
|
90
|
+
Object.defineProperty(exports, "isExpoRouterAvailable", {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: function () {
|
|
93
|
+
return _safeExpoRouter.isExpoRouterAvailable;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
84
96
|
Object.defineProperty(exports, "isJsonSerializable", {
|
|
85
97
|
enumerable: true,
|
|
86
98
|
get: function () {
|
|
@@ -159,6 +171,30 @@ Object.defineProperty(exports, "truncateText", {
|
|
|
159
171
|
return _valueFormatting.truncateText;
|
|
160
172
|
}
|
|
161
173
|
});
|
|
174
|
+
Object.defineProperty(exports, "useSafeGlobalSearchParams", {
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () {
|
|
177
|
+
return _safeExpoRouter.useSafeGlobalSearchParams;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
Object.defineProperty(exports, "useSafePathname", {
|
|
181
|
+
enumerable: true,
|
|
182
|
+
get: function () {
|
|
183
|
+
return _safeExpoRouter.useSafePathname;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
Object.defineProperty(exports, "useSafeRouter", {
|
|
187
|
+
enumerable: true,
|
|
188
|
+
get: function () {
|
|
189
|
+
return _safeExpoRouter.useSafeRouter;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
Object.defineProperty(exports, "useSafeSegments", {
|
|
193
|
+
enumerable: true,
|
|
194
|
+
get: function () {
|
|
195
|
+
return _safeExpoRouter.useSafeSegments;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
162
198
|
var _displayValue = require("./displayValue.js");
|
|
163
199
|
var _getSafeAreaInsets = require("./getSafeAreaInsets.js");
|
|
164
200
|
var _persistentStorage = require("./persistentStorage.js");
|
|
@@ -167,4 +203,5 @@ var _typeHelpers = require("./typeHelpers.js");
|
|
|
167
203
|
var _valueFormatting = require("./valueFormatting.js");
|
|
168
204
|
var _loadOptionalModule = require("./loadOptionalModule.js");
|
|
169
205
|
var _subscribable = require("./subscribable.js");
|
|
170
|
-
var _subscriberCountNotifier = require("./subscriberCountNotifier.js");
|
|
206
|
+
var _subscriberCountNotifier = require("./subscriberCountNotifier.js");
|
|
207
|
+
var _safeExpoRouter = require("./safeExpoRouter.js");
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getSafeRouter = getSafeRouter;
|
|
7
|
+
exports.isExpoRouterAvailable = isExpoRouterAvailable;
|
|
8
|
+
exports.useSafeGlobalSearchParams = useSafeGlobalSearchParams;
|
|
9
|
+
exports.useSafePathname = useSafePathname;
|
|
10
|
+
exports.useSafeRouter = useSafeRouter;
|
|
11
|
+
exports.useSafeSegments = useSafeSegments;
|
|
12
|
+
var _reactNative = require("react-native");
|
|
13
|
+
/**
|
|
14
|
+
* Safe wrapper for expo-router
|
|
15
|
+
*
|
|
16
|
+
* Provides optional imports for expo-router hooks and utilities.
|
|
17
|
+
* Falls back to no-op implementations when expo-router is not installed.
|
|
18
|
+
*
|
|
19
|
+
* On RN CLI (without Expo native modules), expo-router's JS may be bundled but
|
|
20
|
+
* its hooks crash at runtime because native Expo modules are missing.
|
|
21
|
+
* We detect the Expo native runtime before attempting to use expo-router hooks.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
let expoRouter = null;
|
|
25
|
+
let isAvailable = false;
|
|
26
|
+
let checkedAvailability = false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if the Expo native runtime is available.
|
|
30
|
+
* Handles both old SDKs (NativeModules.ExpoLinking) and new SDKs (54+)
|
|
31
|
+
* where native modules moved to the Expo Modules API.
|
|
32
|
+
*/
|
|
33
|
+
function hasExpoNativeRuntime() {
|
|
34
|
+
// Check legacy NativeModules bridge (Expo SDK < 54)
|
|
35
|
+
if (_reactNative.NativeModules.ExpoLinking) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for Expo Modules Core native bridge (Expo SDK 51+)
|
|
40
|
+
// NativeUnimoduleProxy is registered by expo-modules-core on both old and new arch
|
|
41
|
+
if (_reactNative.NativeModules.NativeUnimoduleProxy) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for Expo Go app
|
|
46
|
+
if (_reactNative.NativeModules.ExpoGoModule || _reactNative.NativeModules.ExpoUpdates) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback: try loading expo-modules-core which is required by all Expo native modules
|
|
51
|
+
try {
|
|
52
|
+
require("expo-modules-core");
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function checkExpoRouterAvailability() {
|
|
59
|
+
if (checkedAvailability) return isAvailable;
|
|
60
|
+
try {
|
|
61
|
+
// Verify the Expo native runtime is available.
|
|
62
|
+
// On RN CLI, expo-router JS may be bundled but native modules aren't registered,
|
|
63
|
+
// so require() succeeds but hooks crash at runtime.
|
|
64
|
+
if (!hasExpoNativeRuntime()) {
|
|
65
|
+
checkedAvailability = true;
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
expoRouter = require("expo-router");
|
|
69
|
+
isAvailable = expoRouter != null && typeof expoRouter.usePathname === "function" && typeof expoRouter.useSegments === "function";
|
|
70
|
+
} catch (error) {
|
|
71
|
+
isAvailable = false;
|
|
72
|
+
expoRouter = null;
|
|
73
|
+
}
|
|
74
|
+
checkedAvailability = true;
|
|
75
|
+
return isAvailable;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// No-op implementations when expo-router is not available
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
function noOpUseRouter() {
|
|
83
|
+
return {
|
|
84
|
+
push: () => console.warn("[buoy] expo-router not installed: push() unavailable"),
|
|
85
|
+
replace: () => console.warn("[buoy] expo-router not installed: replace() unavailable"),
|
|
86
|
+
back: () => console.warn("[buoy] expo-router not installed: back() unavailable"),
|
|
87
|
+
canGoBack: () => false,
|
|
88
|
+
setParams: () => console.warn("[buoy] expo-router not installed: setParams() unavailable"),
|
|
89
|
+
navigate: () => console.warn("[buoy] expo-router not installed: navigate() unavailable")
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function noOpUsePathname() {
|
|
93
|
+
return "/";
|
|
94
|
+
}
|
|
95
|
+
function noOpUseSegments() {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
function noOpUseGlobalSearchParams() {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Safe hook exports
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
function useSafeRouter() {
|
|
107
|
+
if (!checkExpoRouterAvailability()) {
|
|
108
|
+
return noOpUseRouter();
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return expoRouter.useRouter();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn("[buoy] Failed to use expo-router.useRouter:", error);
|
|
114
|
+
return noOpUseRouter();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function useSafePathname() {
|
|
118
|
+
if (!checkExpoRouterAvailability()) {
|
|
119
|
+
return noOpUsePathname();
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return expoRouter.usePathname();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn("[buoy] Failed to use expo-router.usePathname:", error);
|
|
125
|
+
return noOpUsePathname();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function useSafeSegments() {
|
|
129
|
+
if (!checkExpoRouterAvailability()) {
|
|
130
|
+
return noOpUseSegments();
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return expoRouter.useSegments();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.warn("[buoy] Failed to use expo-router.useSegments:", error);
|
|
136
|
+
return noOpUseSegments();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function useSafeGlobalSearchParams() {
|
|
140
|
+
if (!checkExpoRouterAvailability()) {
|
|
141
|
+
return noOpUseGlobalSearchParams();
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return expoRouter.useGlobalSearchParams();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.warn("[buoy] Failed to use expo-router.useGlobalSearchParams:", error);
|
|
147
|
+
return noOpUseGlobalSearchParams();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Router instance getter (for imperative navigation)
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
function getSafeRouter() {
|
|
156
|
+
if (!checkExpoRouterAvailability()) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
return expoRouter.router || null;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Availability check
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
function isExpoRouterAvailable() {
|
|
171
|
+
return checkExpoRouterAvailability();
|
|
172
|
+
}
|
|
@@ -1,20 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Detected: none
|
|
6
|
-
* Generated at: 2026-02-03T00:33:15.804Z
|
|
4
|
+
* Runtime clipboard implementation
|
|
7
5
|
*
|
|
8
|
-
*
|
|
6
|
+
* Uses Metro's allowOptionalDependencies with top-level try-catch
|
|
7
|
+
* so Metro marks these requires as optional (skipped if not installed).
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* We grab module references eagerly (for Metro), but detect which one
|
|
10
|
+
* actually works lazily on first use — by trying to call it. This avoids
|
|
11
|
+
* NativeModules checks which don't work with TurboModules/new architecture.
|
|
12
|
+
*
|
|
13
|
+
* Consumers must set `transformer.allowOptionalDependencies = true`
|
|
14
|
+
* in their metro.config.js for this to work.
|
|
15
|
+
*
|
|
16
|
+
* Fallback chain:
|
|
17
|
+
* 1. expo-clipboard
|
|
18
|
+
* 2. @react-native-clipboard/clipboard
|
|
19
|
+
* 3. Graceful failure
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
// Grab module references at load time (top-level try-catch for Metro)
|
|
23
|
+
// Always require both — we decide which actually works at call time
|
|
24
|
+
let _expoClipboard = null;
|
|
25
|
+
try {
|
|
26
|
+
_expoClipboard = require("expo-clipboard");
|
|
27
|
+
} catch {}
|
|
28
|
+
let _rnClipboard = null;
|
|
29
|
+
try {
|
|
30
|
+
const mod = require("@react-native-clipboard/clipboard");
|
|
31
|
+
_rnClipboard = mod.default || mod;
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
34
|
+
// Lazy detection: resolved on first clipboardFunction() call
|
|
35
|
+
let _detectedType = null;
|
|
36
|
+
let _clipboardFn = null;
|
|
37
|
+
let _detected = false;
|
|
38
|
+
async function detect(text) {
|
|
39
|
+
// 1. Try expo-clipboard by actually calling it
|
|
40
|
+
if (_expoClipboard && typeof _expoClipboard.setStringAsync === "function") {
|
|
41
|
+
try {
|
|
42
|
+
await _expoClipboard.setStringAsync(text);
|
|
43
|
+
_detectedType = "expo";
|
|
44
|
+
_clipboardFn = async t => {
|
|
45
|
+
await _expoClipboard.setStringAsync(t);
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
_detected = true;
|
|
49
|
+
return true;
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. Try @react-native-clipboard/clipboard
|
|
54
|
+
if (_rnClipboard && typeof _rnClipboard.setString === "function") {
|
|
55
|
+
try {
|
|
56
|
+
_rnClipboard.setString(text);
|
|
57
|
+
_detectedType = "react-native";
|
|
58
|
+
_clipboardFn = async t => {
|
|
59
|
+
_rnClipboard.setString(t);
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
_detected = true;
|
|
63
|
+
return true;
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
_detected = true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
export const clipboardType = () => {
|
|
70
|
+
return _detectedType;
|
|
71
|
+
};
|
|
72
|
+
export const isClipboardAvailable = () => {
|
|
73
|
+
// Before first use, optimistically return true if we have a module ref
|
|
74
|
+
if (!_detected) return _expoClipboard != null || _rnClipboard != null;
|
|
75
|
+
return _clipboardFn != null;
|
|
76
|
+
};
|
|
17
77
|
export const clipboardFunction = async text => {
|
|
18
|
-
|
|
78
|
+
// If already detected, use the cached function
|
|
79
|
+
if (_detected && _clipboardFn) {
|
|
80
|
+
try {
|
|
81
|
+
return await _clipboardFn(text);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("[buoy] Clipboard copy failed:", error);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// First call: detect which clipboard works by actually trying it
|
|
89
|
+
if (!_detected) {
|
|
90
|
+
const result = await detect(text);
|
|
91
|
+
if (result) return true;
|
|
92
|
+
}
|
|
93
|
+
console.warn("[buoy] No clipboard library available. Install expo-clipboard or @react-native-clipboard/clipboard.");
|
|
19
94
|
return false;
|
|
20
95
|
};
|
package/lib/module/index.js
CHANGED
|
@@ -25,7 +25,9 @@ loadOptionalModule, getCachedOptionalModule,
|
|
|
25
25
|
// Subscribable base class for self-managing listeners
|
|
26
26
|
Subscribable,
|
|
27
27
|
// Subscriber count notifier for cross-package notifications
|
|
28
|
-
subscriberCountNotifier, subscribeToSubscriberCountChanges, notifySubscriberCountChange
|
|
28
|
+
subscriberCountNotifier, subscribeToSubscriberCountChanges, notifySubscriberCountChange,
|
|
29
|
+
// Safe expo-router wrappers (falls back to no-ops on RN CLI)
|
|
30
|
+
useSafeRouter, useSafePathname, useSafeSegments, useSafeGlobalSearchParams, getSafeRouter, isExpoRouterAvailable } from "./utils/index.js";
|
|
29
31
|
|
|
30
32
|
// Also export formatting utils
|
|
31
33
|
export * from "./utils/formatting/index.js";
|
|
@@ -190,11 +190,16 @@ const LEGACY_DEV_TOOL_PATTERNS = ["@devtools", "@dev_tools_", "@modal_state_",
|
|
|
190
190
|
export function isDevToolsStorageKey(key) {
|
|
191
191
|
if (!key) return false;
|
|
192
192
|
|
|
193
|
-
// Check if it starts with our base prefix
|
|
193
|
+
// Check if it starts with our base prefix (@react_buoy)
|
|
194
194
|
if (key.startsWith(devToolsStorageKeys.base)) {
|
|
195
195
|
return true;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// Check for buoy- prefixed keys (modal persistence, license, etc.)
|
|
199
|
+
if (key.startsWith("buoy-")) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
198
203
|
// Check for legacy dev tool keys that need cleanup
|
|
199
204
|
for (const pattern of LEGACY_DEV_TOOL_PATTERNS) {
|
|
200
205
|
if (key.startsWith(pattern)) {
|
|
@@ -8,4 +8,5 @@ export { getValueType, isPrimitive, isJsonSerializable, isValidJson, getConstruc
|
|
|
8
8
|
export { parseValue, formatValue, getTypeColor, truncateText, flattenObject, formatPath } from "./valueFormatting.js";
|
|
9
9
|
export { loadOptionalModule, getCachedOptionalModule } from "./loadOptionalModule.js";
|
|
10
10
|
export { Subscribable } from "./subscribable.js";
|
|
11
|
-
export { subscriberCountNotifier, subscribeToSubscriberCountChanges, notifySubscriberCountChange } from "./subscriberCountNotifier.js";
|
|
11
|
+
export { subscriberCountNotifier, subscribeToSubscriberCountChanges, notifySubscriberCountChange } from "./subscriberCountNotifier.js";
|
|
12
|
+
export { useSafeRouter, useSafePathname, useSafeSegments, useSafeGlobalSearchParams, getSafeRouter, isExpoRouterAvailable } from "./safeExpoRouter.js";
|