@buoy-gg/shared-ui 7.0.36 → 7.0.38
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/README.md +33 -26
- package/floating.d.ts +1 -0
- package/floating.js +2 -0
- package/lib/commonjs/JsModal.js +16 -1
- package/lib/commonjs/JsModal.web.js +16 -1
- package/lib/commonjs/dataViewer/CyberpunkInput.js +6 -1
- package/lib/commonjs/dataViewer/LiveExplorer.js +28 -16
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/icons/ReactQueryIcon.js +9 -161
- package/lib/commonjs/icons/SentryBugIcon.js +9 -171
- package/lib/commonjs/index.js +57 -6
- package/lib/commonjs/license/AccountSetup.js +56 -0
- package/lib/commonjs/license/AccountSetup.web.js +81 -0
- package/lib/commonjs/license/FeatureGate.js +10 -43
- package/lib/commonjs/license/FreeAccountPrompt.js +35 -12
- package/lib/commonjs/license/index.js +0 -6
- package/lib/commonjs/license/licenseModule.js +56 -36
- package/lib/commonjs/license/tierLimits.js +12 -63
- package/lib/commonjs/storage/devToolsStorageKeys.js +8 -1
- package/lib/commonjs/stores/BaseEventStore.js +21 -5
- package/lib/commonjs/stores/createToolStore.js +21 -3
- package/lib/commonjs/stores/floatingToolStore.js +71 -0
- package/lib/commonjs/stores/floatingToolStore.test.js +112 -0
- package/lib/commonjs/sync/toolActionRegistry.js +85 -2
- package/lib/commonjs/ui/backgrounds/README.md +52 -0
- package/lib/commonjs/ui/backgrounds/ToolBackground.js +4 -2
- package/lib/commonjs/ui/backgrounds/index.js +8 -1
- package/lib/commonjs/ui/backgrounds/registry.js +8 -0
- package/lib/commonjs/ui/backgrounds/variants/wormhole/motion.js +48 -0
- package/lib/commonjs/ui/backgrounds/variants/wormhole/textures.js +3 -0
- package/lib/commonjs/ui/backgrounds/variants/wormhole.js +133 -0
- package/lib/commonjs/ui/components/ClipboardHintBanner.js +5 -6
- package/lib/commonjs/ui/components/CompactRow.js +94 -74
- package/lib/commonjs/ui/components/DraggableHeader.js +16 -1
- package/lib/commonjs/ui/components/HeaderSearchButton.js +17 -4
- package/lib/commonjs/ui/floating/FloatingTool.js +1010 -0
- package/lib/commonjs/ui/floating/FloatingTool.web.js +591 -0
- package/lib/commonjs/ui/floating/FloatingToolHost.js +32 -0
- package/lib/commonjs/ui/floating/FloatingToolHost.web.js +28 -0
- package/lib/commonjs/ui/floating/index.js +105 -0
- package/lib/commonjs/ui/floating/index.web.js +105 -0
- package/lib/commonjs/ui/floating/onboardingConstants.js +70 -0
- package/lib/commonjs/ui/floating/onboardingConstants.web.js +34 -0
- package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +3 -1
- package/lib/commonjs/ui/index.js +118 -2
- package/lib/commonjs/ui/night/nightTheme.js +10 -0
- package/lib/commonjs/ui/night/primitives.js +22 -5
- package/lib/commonjs/ui/otto/OttoSceneView.js +69 -0
- package/lib/commonjs/ui/otto/index.js +22 -0
- package/lib/commonjs/ui/otto/ottoEngine.js +10 -7
- package/lib/commonjs/ui/otto/ottoScenes.js +949 -0
- package/lib/commonjs/utils/minimalSuperJSON.js +13 -3
- package/lib/module/JsModal.js +16 -1
- package/lib/module/JsModal.web.js +16 -1
- package/lib/module/dataViewer/CyberpunkInput.js +6 -1
- package/lib/module/dataViewer/LiveExplorer.js +28 -16
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/icons/ReactQueryIcon.js +9 -161
- package/lib/module/icons/SentryBugIcon.js +9 -171
- package/lib/module/index.js +4 -1
- package/lib/module/license/AccountSetup.js +52 -0
- package/lib/module/license/AccountSetup.web.js +77 -0
- package/lib/module/license/FeatureGate.js +10 -43
- package/lib/module/license/FreeAccountPrompt.js +35 -12
- package/lib/module/license/index.js +1 -1
- package/lib/module/license/licenseModule.js +50 -35
- package/lib/module/license/tierLimits.js +13 -63
- package/lib/module/storage/devToolsStorageKeys.js +8 -1
- package/lib/module/stores/BaseEventStore.js +21 -5
- package/lib/module/stores/createToolStore.js +21 -3
- package/lib/module/stores/floatingToolStore.js +61 -0
- package/lib/module/stores/floatingToolStore.test.js +111 -0
- package/lib/module/sync/toolActionRegistry.js +84 -2
- package/lib/module/ui/backgrounds/README.md +52 -0
- package/lib/module/ui/backgrounds/ToolBackground.js +4 -2
- package/lib/module/ui/backgrounds/index.js +2 -1
- package/lib/module/ui/backgrounds/registry.js +8 -0
- package/lib/module/ui/backgrounds/variants/wormhole/motion.js +44 -0
- package/lib/module/ui/backgrounds/variants/wormhole/textures.js +3 -0
- package/lib/module/ui/backgrounds/variants/wormhole.js +129 -0
- package/lib/module/ui/components/ClipboardHintBanner.js +5 -6
- package/lib/module/ui/components/CompactRow.js +94 -74
- package/lib/module/ui/components/DraggableHeader.js +16 -1
- package/lib/module/ui/components/HeaderSearchButton.js +17 -4
- package/lib/module/ui/floating/FloatingTool.js +1003 -0
- package/lib/module/ui/floating/FloatingTool.web.js +590 -0
- package/lib/module/ui/floating/FloatingToolHost.js +29 -0
- package/lib/module/ui/floating/FloatingToolHost.web.js +25 -0
- package/lib/module/ui/floating/index.js +6 -0
- package/lib/module/ui/floating/index.web.js +6 -0
- package/lib/module/ui/floating/onboardingConstants.js +64 -0
- package/lib/module/ui/floating/onboardingConstants.web.js +27 -0
- package/lib/module/ui/gameUI/constants/gameUIColors.js +3 -1
- package/lib/module/ui/index.js +5 -1
- package/lib/module/ui/night/nightTheme.js +10 -0
- package/lib/module/ui/night/primitives.js +22 -5
- package/lib/module/ui/otto/OttoSceneView.js +64 -0
- package/lib/module/ui/otto/index.js +3 -1
- package/lib/module/ui/otto/ottoEngine.js +10 -7
- package/lib/module/ui/otto/ottoScenes.js +942 -0
- package/lib/module/utils/minimalSuperJSON.js +13 -3
- package/lib/typescript/commonjs/JsModal.d.ts +43 -2
- package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/JsModal.web.d.ts +5 -2
- package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/CyberpunkInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/LiveExplorer.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/icons/ReactQueryIcon.d.ts +1 -2
- package/lib/typescript/commonjs/icons/ReactQueryIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/icons/SentryBugIcon.d.ts +1 -2
- package/lib/typescript/commonjs/icons/SentryBugIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/AccountSetup.d.ts +4 -0
- package/lib/typescript/commonjs/license/AccountSetup.d.ts.map +1 -0
- package/lib/typescript/commonjs/license/AccountSetup.web.d.ts +2 -0
- package/lib/typescript/commonjs/license/AccountSetup.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/license/FeatureGate.d.ts +3 -19
- package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts +8 -11
- package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/index.d.ts +1 -1
- package/lib/typescript/commonjs/license/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/licenseModule.d.ts +18 -31
- package/lib/typescript/commonjs/license/licenseModule.d.ts.map +1 -1
- package/lib/typescript/commonjs/license/tierLimits.d.ts +9 -52
- package/lib/typescript/commonjs/license/tierLimits.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +6 -0
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/BaseEventStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/createToolStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/floatingToolStore.d.ts +27 -0
- package/lib/typescript/commonjs/stores/floatingToolStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/stores/floatingToolStore.test.d.ts +2 -0
- package/lib/typescript/commonjs/stores/floatingToolStore.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts +4 -32
- package/lib/typescript/commonjs/sync/toolActionRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts +7 -1
- package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/index.d.ts +2 -1
- package/lib/typescript/commonjs/ui/backgrounds/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/types.d.ts +11 -1
- package/lib/typescript/commonjs/ui/backgrounds/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/motion.d.ts +24 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/motion.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/textures.d.ts +13 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole/textures.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole.d.ts +3 -0
- package/lib/typescript/commonjs/ui/backgrounds/variants/wormhole.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ClipboardHintBanner.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +8 -1
- package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts +4 -1
- package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts +7 -1
- package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/floating/FloatingTool.d.ts +79 -0
- package/lib/typescript/commonjs/ui/floating/FloatingTool.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/FloatingTool.web.d.ts +64 -0
- package/lib/typescript/commonjs/ui/floating/FloatingTool.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/FloatingToolHost.d.ts +3 -0
- package/lib/typescript/commonjs/ui/floating/FloatingToolHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/FloatingToolHost.web.d.ts +3 -0
- package/lib/typescript/commonjs/ui/floating/FloatingToolHost.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/index.d.ts +7 -0
- package/lib/typescript/commonjs/ui/floating/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/index.web.d.ts +7 -0
- package/lib/typescript/commonjs/ui/floating/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/onboardingConstants.d.ts +30 -0
- package/lib/typescript/commonjs/ui/floating/onboardingConstants.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/floating/onboardingConstants.web.d.ts +13 -0
- package/lib/typescript/commonjs/ui/floating/onboardingConstants.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +2 -0
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +6 -0
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/night/nightTheme.d.ts +10 -0
- package/lib/typescript/commonjs/ui/night/nightTheme.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/night/primitives.d.ts +10 -3
- package/lib/typescript/commonjs/ui/night/primitives.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/otto/OttoSceneView.d.ts +30 -0
- package/lib/typescript/commonjs/ui/otto/OttoSceneView.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/otto/index.d.ts +2 -0
- package/lib/typescript/commonjs/ui/otto/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts +1 -1
- package/lib/typescript/commonjs/ui/otto/ottoEngine.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/otto/ottoScenes.d.ts +75 -0
- package/lib/typescript/commonjs/ui/otto/ottoScenes.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/minimalSuperJSON.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.d.ts +43 -2
- package/lib/typescript/module/JsModal.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.web.d.ts +5 -2
- package/lib/typescript/module/JsModal.web.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/CyberpunkInput.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/LiveExplorer.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/icons/ReactQueryIcon.d.ts +1 -2
- package/lib/typescript/module/icons/ReactQueryIcon.d.ts.map +1 -1
- package/lib/typescript/module/icons/SentryBugIcon.d.ts +1 -2
- package/lib/typescript/module/icons/SentryBugIcon.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/license/AccountSetup.d.ts +4 -0
- package/lib/typescript/module/license/AccountSetup.d.ts.map +1 -0
- package/lib/typescript/module/license/AccountSetup.web.d.ts +2 -0
- package/lib/typescript/module/license/AccountSetup.web.d.ts.map +1 -0
- package/lib/typescript/module/license/FeatureGate.d.ts +3 -19
- package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
- package/lib/typescript/module/license/FreeAccountPrompt.d.ts +8 -11
- package/lib/typescript/module/license/FreeAccountPrompt.d.ts.map +1 -1
- package/lib/typescript/module/license/index.d.ts +1 -1
- package/lib/typescript/module/license/index.d.ts.map +1 -1
- package/lib/typescript/module/license/licenseModule.d.ts +18 -31
- package/lib/typescript/module/license/licenseModule.d.ts.map +1 -1
- package/lib/typescript/module/license/tierLimits.d.ts +9 -52
- package/lib/typescript/module/license/tierLimits.d.ts.map +1 -1
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +6 -0
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/module/stores/BaseEventStore.d.ts.map +1 -1
- package/lib/typescript/module/stores/createToolStore.d.ts.map +1 -1
- package/lib/typescript/module/stores/floatingToolStore.d.ts +27 -0
- package/lib/typescript/module/stores/floatingToolStore.d.ts.map +1 -0
- package/lib/typescript/module/stores/floatingToolStore.test.d.ts +2 -0
- package/lib/typescript/module/stores/floatingToolStore.test.d.ts.map +1 -0
- package/lib/typescript/module/sync/toolActionRegistry.d.ts +4 -32
- package/lib/typescript/module/sync/toolActionRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts +7 -1
- package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/index.d.ts +2 -1
- package/lib/typescript/module/ui/backgrounds/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/types.d.ts +11 -1
- package/lib/typescript/module/ui/backgrounds/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/backgrounds/variants/wormhole/motion.d.ts +24 -0
- package/lib/typescript/module/ui/backgrounds/variants/wormhole/motion.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/variants/wormhole/textures.d.ts +13 -0
- package/lib/typescript/module/ui/backgrounds/variants/wormhole/textures.d.ts.map +1 -0
- package/lib/typescript/module/ui/backgrounds/variants/wormhole.d.ts +3 -0
- package/lib/typescript/module/ui/backgrounds/variants/wormhole.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ClipboardHintBanner.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/CompactRow.d.ts +8 -1
- package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/DraggableHeader.d.ts +4 -1
- package/lib/typescript/module/ui/components/DraggableHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts +7 -1
- package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/floating/FloatingTool.d.ts +79 -0
- package/lib/typescript/module/ui/floating/FloatingTool.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/FloatingTool.web.d.ts +64 -0
- package/lib/typescript/module/ui/floating/FloatingTool.web.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/FloatingToolHost.d.ts +3 -0
- package/lib/typescript/module/ui/floating/FloatingToolHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/FloatingToolHost.web.d.ts +3 -0
- package/lib/typescript/module/ui/floating/FloatingToolHost.web.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/index.d.ts +7 -0
- package/lib/typescript/module/ui/floating/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/index.web.d.ts +7 -0
- package/lib/typescript/module/ui/floating/index.web.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/onboardingConstants.d.ts +30 -0
- package/lib/typescript/module/ui/floating/onboardingConstants.d.ts.map +1 -0
- package/lib/typescript/module/ui/floating/onboardingConstants.web.d.ts +13 -0
- package/lib/typescript/module/ui/floating/onboardingConstants.web.d.ts.map +1 -0
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +2 -0
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +6 -0
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/night/nightTheme.d.ts +10 -0
- package/lib/typescript/module/ui/night/nightTheme.d.ts.map +1 -1
- package/lib/typescript/module/ui/night/primitives.d.ts +10 -3
- package/lib/typescript/module/ui/night/primitives.d.ts.map +1 -1
- package/lib/typescript/module/ui/otto/OttoSceneView.d.ts +30 -0
- package/lib/typescript/module/ui/otto/OttoSceneView.d.ts.map +1 -0
- package/lib/typescript/module/ui/otto/index.d.ts +2 -0
- package/lib/typescript/module/ui/otto/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/otto/ottoEngine.d.ts +1 -1
- package/lib/typescript/module/ui/otto/ottoEngine.d.ts.map +1 -1
- package/lib/typescript/module/ui/otto/ottoScenes.d.ts +75 -0
- package/lib/typescript/module/ui/otto/ottoScenes.d.ts.map +1 -0
- package/lib/typescript/module/utils/minimalSuperJSON.d.ts.map +1 -1
- package/package.json +25 -5
- package/scripts/generate-wormhole-textures.cjs +261 -0
- package/scripts/test-wormhole-motion.cjs +59 -0
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Divider = Divider;
|
|
7
|
+
exports.FloatingTool = FloatingTool;
|
|
8
|
+
exports.UserStatus = UserStatus;
|
|
9
|
+
exports.getBubbleAnchorPosition = getBubbleAnchorPosition;
|
|
10
|
+
var _react = require("react");
|
|
11
|
+
var _reactNative = require("react-native");
|
|
12
|
+
var _nightTheme = require("../night/nightTheme.js");
|
|
13
|
+
var _DraggableHeader = require("../components/DraggableHeader.js");
|
|
14
|
+
var _EnvironmentSelectorInline = require("../../env/EnvironmentSelectorInline");
|
|
15
|
+
var _GripVerticalIcon = require("../components/GripVerticalIcon.js");
|
|
16
|
+
var _gameUIColors = require("../gameUI/constants/gameUIColors.js");
|
|
17
|
+
var _index = require("../../utils/index.js");
|
|
18
|
+
var _index2 = require("../../hooks/index.js");
|
|
19
|
+
var _onboardingConstants = require("./onboardingConstants");
|
|
20
|
+
var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
21
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
22
|
+
// Shared utilities from floating-tools-core
|
|
23
|
+
|
|
24
|
+
// Re-export UserRole type for consumers
|
|
25
|
+
|
|
26
|
+
// Using Views to render grip dots; no react-native-svg dependency
|
|
27
|
+
|
|
28
|
+
const STORAGE_KEYS = {
|
|
29
|
+
BUBBLE_POSITION_X: "@react_buoy_bubble_position_x",
|
|
30
|
+
BUBBLE_POSITION_Y: "@react_buoy_bubble_position_y"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Position constants
|
|
34
|
+
const VISIBLE_HANDLE_WIDTH = 32;
|
|
35
|
+
const EDGE_PADDING = 20;
|
|
36
|
+
const LAYOUT_SETTLE_DELAY_MS = 150;
|
|
37
|
+
|
|
38
|
+
/** Helper to get current value from Animated.Value without triggering updates */
|
|
39
|
+
const getAnimatedValue = value => value.__getValue();
|
|
40
|
+
|
|
41
|
+
// =============================
|
|
42
|
+
// Position persistence hook
|
|
43
|
+
// Extracted logic dedicated to state/IO
|
|
44
|
+
// =============================
|
|
45
|
+
/**
|
|
46
|
+
* Custom hook for managing floating tools position persistence
|
|
47
|
+
*
|
|
48
|
+
* Handles loading, saving, and validating the position of the floating tools bubble
|
|
49
|
+
* with automatic boundary checking and storage management.
|
|
50
|
+
*
|
|
51
|
+
* @param props - Configuration for position management
|
|
52
|
+
* @param props.animatedPosition - Animated.ValueXY for position updates
|
|
53
|
+
* @param props.bubbleWidth - Width of the bubble for boundary calculations
|
|
54
|
+
* @param props.bubbleHeight - Height of the bubble for boundary calculations
|
|
55
|
+
* @param props.enabled - Whether position persistence is enabled
|
|
56
|
+
* @param props.visibleHandleWidth - Width of visible handle when bubble is hidden
|
|
57
|
+
* @param props.listenersSuspended - Pause automatic listeners without disabling manual saves
|
|
58
|
+
* @param props.onPositionLoaded - Callback fired once position is loaded and set
|
|
59
|
+
*
|
|
60
|
+
* @returns Object containing position management functions and loading state
|
|
61
|
+
*
|
|
62
|
+
* @performance Uses debounced saving to avoid excessive storage operations
|
|
63
|
+
* @performance Validates positions against screen boundaries and safe areas
|
|
64
|
+
*/
|
|
65
|
+
function useFloatingToolsPosition({
|
|
66
|
+
animatedPosition,
|
|
67
|
+
bubbleWidth = 100,
|
|
68
|
+
bubbleHeight = 32,
|
|
69
|
+
enabled = true,
|
|
70
|
+
visibleHandleWidth = 32,
|
|
71
|
+
listenersSuspended = false,
|
|
72
|
+
defaultPlacement = "top",
|
|
73
|
+
onPositionLoaded,
|
|
74
|
+
storageKey
|
|
75
|
+
}) {
|
|
76
|
+
// Use state instead of ref so we can react to changes and prevent races
|
|
77
|
+
const [isInitialized, setIsInitialized] = (0, _react.useState)(false);
|
|
78
|
+
const saveTimeoutRef = (0, _react.useRef)(undefined);
|
|
79
|
+
// Track if component is mounted to prevent state updates after unmount
|
|
80
|
+
const isMountedRef = (0, _react.useRef)(true);
|
|
81
|
+
|
|
82
|
+
// Cleanup on unmount - cancel any pending saves
|
|
83
|
+
(0, _react.useEffect)(() => {
|
|
84
|
+
isMountedRef.current = true;
|
|
85
|
+
return () => {
|
|
86
|
+
isMountedRef.current = false;
|
|
87
|
+
// Clear any pending debounced saves on unmount to prevent saving stale/invalid positions
|
|
88
|
+
if (saveTimeoutRef.current) {
|
|
89
|
+
clearTimeout(saveTimeoutRef.current);
|
|
90
|
+
saveTimeoutRef.current = undefined;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}, []);
|
|
94
|
+
const savePosition = (0, _react.useCallback)(async (x, y) => {
|
|
95
|
+
if (!enabled) return;
|
|
96
|
+
// Don't save if component unmounted or position is clearly invalid
|
|
97
|
+
if (!isMountedRef.current) return;
|
|
98
|
+
// Guard against saving invalid positions (e.g., during HMR with stale dimensions)
|
|
99
|
+
const {
|
|
100
|
+
width: screenWidth
|
|
101
|
+
} = _reactNative.Dimensions.get("window");
|
|
102
|
+
if (screenWidth <= 0 || x < 0 || y < 0) return;
|
|
103
|
+
try {
|
|
104
|
+
await Promise.all([_index.persistentStorage.setItem(storageKey ? `${storageKey}:x` : STORAGE_KEYS.BUBBLE_POSITION_X, x.toString()), _index.persistentStorage.setItem(storageKey ? `${storageKey}:y` : STORAGE_KEYS.BUBBLE_POSITION_Y, y.toString())]);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Failed to save position - continue without persistence
|
|
107
|
+
}
|
|
108
|
+
}, [enabled, storageKey]);
|
|
109
|
+
const debouncedSavePosition = (0, _react.useCallback)((x, y) => {
|
|
110
|
+
// Don't schedule saves if unmounted
|
|
111
|
+
if (!isMountedRef.current) return;
|
|
112
|
+
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
|
113
|
+
saveTimeoutRef.current = setTimeout(() => {
|
|
114
|
+
// Double-check mount status before saving
|
|
115
|
+
if (isMountedRef.current) {
|
|
116
|
+
savePosition(x, y);
|
|
117
|
+
}
|
|
118
|
+
}, 500);
|
|
119
|
+
}, [savePosition]);
|
|
120
|
+
const loadPosition = (0, _react.useCallback)(async () => {
|
|
121
|
+
if (!enabled) return null;
|
|
122
|
+
try {
|
|
123
|
+
const [xStr, yStr] = await Promise.all([_index.persistentStorage.getItem(storageKey ? `${storageKey}:x` : STORAGE_KEYS.BUBBLE_POSITION_X), _index.persistentStorage.getItem(storageKey ? `${storageKey}:y` : STORAGE_KEYS.BUBBLE_POSITION_Y)]);
|
|
124
|
+
if (xStr !== null && yStr !== null) {
|
|
125
|
+
const x = parseFloat(xStr);
|
|
126
|
+
const y = parseFloat(yStr);
|
|
127
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) {
|
|
128
|
+
return {
|
|
129
|
+
x,
|
|
130
|
+
y
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Failed to load position - use default
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}, [enabled, storageKey]);
|
|
139
|
+
const validatePosition = (0, _react.useCallback)(position => {
|
|
140
|
+
const {
|
|
141
|
+
width: screenWidth,
|
|
142
|
+
height: screenHeight
|
|
143
|
+
} = _reactNative.Dimensions.get("window");
|
|
144
|
+
const safeArea = (0, _index.getSafeAreaInsets)();
|
|
145
|
+
|
|
146
|
+
// Guard against invalid screen dimensions (can happen during HMR)
|
|
147
|
+
// Return the position as-is but mark as invalid so caller knows not to trust it
|
|
148
|
+
if (screenWidth <= 0 || screenHeight <= 0) {
|
|
149
|
+
return {
|
|
150
|
+
...position,
|
|
151
|
+
isValid: false
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Prevent going off left, top, and bottom edges with safe area
|
|
156
|
+
// Allow pushing off-screen to the right so only the grab handle remains visible
|
|
157
|
+
const minX = safeArea.left;
|
|
158
|
+
const maxX = screenWidth - visibleHandleWidth;
|
|
159
|
+
// Add padding below the safe area top to ensure bubble doesn't go behind notch
|
|
160
|
+
const minY = safeArea.top + EDGE_PADDING;
|
|
161
|
+
const maxY = screenHeight - bubbleHeight - safeArea.bottom; // Respect safe area bottom
|
|
162
|
+
const clamped = {
|
|
163
|
+
x: Math.max(minX, Math.min(position.x, maxX)),
|
|
164
|
+
y: Math.max(minY, Math.min(position.y, maxY)),
|
|
165
|
+
isValid: true
|
|
166
|
+
};
|
|
167
|
+
return clamped;
|
|
168
|
+
}, [visibleHandleWidth, bubbleHeight]);
|
|
169
|
+
(0, _react.useEffect)(() => {
|
|
170
|
+
if (!enabled || isInitialized) return;
|
|
171
|
+
let cancelled = false;
|
|
172
|
+
const restore = async () => {
|
|
173
|
+
const saved = await loadPosition();
|
|
174
|
+
|
|
175
|
+
// Check if cancelled or unmounted before updating state
|
|
176
|
+
if (cancelled || !isMountedRef.current) return;
|
|
177
|
+
const {
|
|
178
|
+
width: screenWidth,
|
|
179
|
+
height: screenHeight
|
|
180
|
+
} = _reactNative.Dimensions.get("window");
|
|
181
|
+
const safeArea = (0, _index.getSafeAreaInsets)();
|
|
182
|
+
|
|
183
|
+
// If dimensions are invalid (HMR scenario), wait and retry
|
|
184
|
+
if (screenWidth <= 0 || screenHeight <= 0) {
|
|
185
|
+
// Schedule a retry after a short delay
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
if (isMountedRef.current && !cancelled) {
|
|
188
|
+
restore();
|
|
189
|
+
}
|
|
190
|
+
}, 100);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let finalPosition;
|
|
194
|
+
let wasHidden = false;
|
|
195
|
+
if (saved) {
|
|
196
|
+
const validated = validatePosition(saved);
|
|
197
|
+
|
|
198
|
+
// If validation returned invalid, retry later
|
|
199
|
+
if (!validated.isValid) {
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
if (isMountedRef.current && !cancelled) {
|
|
202
|
+
restore();
|
|
203
|
+
}
|
|
204
|
+
}, 100);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if the saved position is out of bounds
|
|
209
|
+
const wasOutOfBounds = Math.abs(saved.x - validated.x) > 5 || Math.abs(saved.y - validated.y) > 5;
|
|
210
|
+
if (wasOutOfBounds) {
|
|
211
|
+
// Save the corrected position
|
|
212
|
+
await savePosition(validated.x, validated.y);
|
|
213
|
+
}
|
|
214
|
+
finalPosition = {
|
|
215
|
+
x: validated.x,
|
|
216
|
+
y: validated.y
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Check if loaded in hidden state (bubble pushed to right edge)
|
|
220
|
+
wasHidden = validated.x >= screenWidth - visibleHandleWidth - 5;
|
|
221
|
+
} else {
|
|
222
|
+
const defaultY = defaultPlacement === "bottom" ? Math.max(safeArea.top + EDGE_PADDING, screenHeight - bubbleHeight - safeArea.bottom - EDGE_PADDING) : Math.max(safeArea.top + EDGE_PADDING, Math.min(100, screenHeight - bubbleHeight - safeArea.bottom));
|
|
223
|
+
finalPosition = {
|
|
224
|
+
x: screenWidth - bubbleWidth - EDGE_PADDING,
|
|
225
|
+
y: defaultY
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Final mount check before updating
|
|
230
|
+
if (cancelled || !isMountedRef.current) return;
|
|
231
|
+
animatedPosition.setValue(finalPosition);
|
|
232
|
+
setIsInitialized(true);
|
|
233
|
+
onPositionLoaded?.(finalPosition, wasHidden);
|
|
234
|
+
};
|
|
235
|
+
restore();
|
|
236
|
+
return () => {
|
|
237
|
+
cancelled = true;
|
|
238
|
+
};
|
|
239
|
+
}, [enabled, isInitialized, animatedPosition, loadPosition, validatePosition, savePosition, bubbleWidth, bubbleHeight, visibleHandleWidth, onPositionLoaded]);
|
|
240
|
+
|
|
241
|
+
// Listener effect - now properly depends on isInitialized state
|
|
242
|
+
(0, _react.useEffect)(() => {
|
|
243
|
+
// Don't attach listener until position is loaded
|
|
244
|
+
if (!enabled || !isInitialized || listenersSuspended) return;
|
|
245
|
+
const listener = animatedPosition.addListener(value => {
|
|
246
|
+
debouncedSavePosition(value.x, value.y);
|
|
247
|
+
});
|
|
248
|
+
return () => {
|
|
249
|
+
animatedPosition.removeListener(listener);
|
|
250
|
+
// Clear debounce timer when listener is removed
|
|
251
|
+
if (saveTimeoutRef.current) {
|
|
252
|
+
clearTimeout(saveTimeoutRef.current);
|
|
253
|
+
saveTimeoutRef.current = undefined;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}, [enabled, isInitialized, listenersSuspended, animatedPosition, debouncedSavePosition]);
|
|
257
|
+
return {
|
|
258
|
+
savePosition,
|
|
259
|
+
isInitialized
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// =============================
|
|
264
|
+
// UI-only leaf components
|
|
265
|
+
// =============================
|
|
266
|
+
function Divider() {
|
|
267
|
+
const dividerStyle = {
|
|
268
|
+
width: 1,
|
|
269
|
+
height: 12,
|
|
270
|
+
backgroundColor: _gameUIColors.buoyColors.textMuted + "66",
|
|
271
|
+
flexShrink: 0
|
|
272
|
+
};
|
|
273
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
274
|
+
style: dividerStyle
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Use shared getUserStatusConfig from floating-tools-core
|
|
279
|
+
// Wraps the core function to use our buoyColors (mapped to gameUIColors format)
|
|
280
|
+
const gameUIColorsCompat = {
|
|
281
|
+
success: "#20C997",
|
|
282
|
+
// teal
|
|
283
|
+
warning: "#FFA94D",
|
|
284
|
+
error: "#EF4444",
|
|
285
|
+
muted: _gameUIColors.buoyColors.textMuted,
|
|
286
|
+
secondary: _gameUIColors.buoyColors.textSecondary,
|
|
287
|
+
info: "#20C997",
|
|
288
|
+
// teal
|
|
289
|
+
primary: "#20C997",
|
|
290
|
+
optional: _gameUIColors.buoyColors.textSecondary // optional status color
|
|
291
|
+
};
|
|
292
|
+
function getUserStatusConfig(userRole) {
|
|
293
|
+
return (0, _floatingToolsCore.getUserStatusConfig)(userRole, gameUIColorsCompat);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Context to avoid brittle prop threading and keep API composable
|
|
297
|
+
const FloatingToolsContext = /*#__PURE__*/(0, _react.createContext)({
|
|
298
|
+
isDragging: false
|
|
299
|
+
});
|
|
300
|
+
function UserStatus({
|
|
301
|
+
userRole,
|
|
302
|
+
onPress
|
|
303
|
+
}) {
|
|
304
|
+
const {
|
|
305
|
+
isDragging
|
|
306
|
+
} = (0, _react.useContext)(FloatingToolsContext);
|
|
307
|
+
const config = getUserStatusConfig(userRole);
|
|
308
|
+
const containerStyle = {
|
|
309
|
+
flexDirection: "row",
|
|
310
|
+
alignItems: "center",
|
|
311
|
+
paddingVertical: 6,
|
|
312
|
+
paddingHorizontal: 8,
|
|
313
|
+
flexShrink: 0
|
|
314
|
+
};
|
|
315
|
+
const dotStyle = {
|
|
316
|
+
width: 6,
|
|
317
|
+
height: 6,
|
|
318
|
+
borderRadius: 3,
|
|
319
|
+
backgroundColor: config.dotColor,
|
|
320
|
+
marginRight: 4
|
|
321
|
+
};
|
|
322
|
+
const textStyle = {
|
|
323
|
+
fontSize: 10,
|
|
324
|
+
fontWeight: "500",
|
|
325
|
+
color: config.textColor,
|
|
326
|
+
letterSpacing: 0.3
|
|
327
|
+
};
|
|
328
|
+
if (!onPress) {
|
|
329
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
330
|
+
style: containerStyle,
|
|
331
|
+
accessibilityLabel: `User status: ${config.label}`,
|
|
332
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
333
|
+
style: dotStyle
|
|
334
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
335
|
+
style: textStyle,
|
|
336
|
+
children: config.label
|
|
337
|
+
})]
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
341
|
+
accessibilityRole: "button",
|
|
342
|
+
accessibilityLabel: "Open Dev Tools Menu",
|
|
343
|
+
testID: "open-dev-tools-button",
|
|
344
|
+
onPress: onPress,
|
|
345
|
+
hitSlop: {
|
|
346
|
+
top: 10,
|
|
347
|
+
bottom: 10,
|
|
348
|
+
left: 0,
|
|
349
|
+
right: 10
|
|
350
|
+
},
|
|
351
|
+
disabled: isDragging,
|
|
352
|
+
activeOpacity: 0.85,
|
|
353
|
+
style: containerStyle,
|
|
354
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
355
|
+
style: dotStyle
|
|
356
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
357
|
+
style: textStyle,
|
|
358
|
+
children: config.label
|
|
359
|
+
})]
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// =============================
|
|
364
|
+
// Helpers (using shared filterValidChildren from core)
|
|
365
|
+
// =============================
|
|
366
|
+
function interleaveWithDividers(childrenArray) {
|
|
367
|
+
const validChildren = (0, _floatingToolsCore.filterValidChildren)(childrenArray);
|
|
368
|
+
const result = [];
|
|
369
|
+
validChildren.forEach((child, index) => {
|
|
370
|
+
result.push(child);
|
|
371
|
+
if (index < validChildren.length - 1) result.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(Divider, {}, `divider-${index}`));
|
|
372
|
+
});
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// =============================
|
|
377
|
+
// Main Component (presentation only)
|
|
378
|
+
// =============================
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* FloatingTools - A draggable, resizable bubble for development tools
|
|
382
|
+
*
|
|
383
|
+
* This component provides a floating bubble interface that can contain various
|
|
384
|
+
* development tools and controls. It features:
|
|
385
|
+
* - Drag and drop positioning with boundary constraints
|
|
386
|
+
* - Hide/show functionality by dragging to screen edge
|
|
387
|
+
* - Position persistence across app restarts
|
|
388
|
+
* - Safe area aware positioning
|
|
389
|
+
* - Automatic divider insertion between child components
|
|
390
|
+
*
|
|
391
|
+
* @param props - Configuration for the floating tools
|
|
392
|
+
* @param props.enablePositionPersistence - Whether to save/restore position (default: true)
|
|
393
|
+
* @param props.children - Child components to render in the bubble
|
|
394
|
+
*
|
|
395
|
+
* @returns JSX.Element representing the floating tools bubble
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```typescript
|
|
399
|
+
* <FloatingTools enablePositionPersistence={true}>
|
|
400
|
+
* <UserStatus userRole="admin" onPress={handleUserPress} />
|
|
401
|
+
* <ToolButton onPress={openSettings} />
|
|
402
|
+
* </FloatingTools>
|
|
403
|
+
* ```
|
|
404
|
+
*
|
|
405
|
+
* @performance Uses native driver animations for smooth positioning
|
|
406
|
+
* @performance Implements efficient boundary checking and position validation
|
|
407
|
+
* @performance Includes debounced position saving for optimal storage performance
|
|
408
|
+
*/
|
|
409
|
+
function FloatingTool({
|
|
410
|
+
id,
|
|
411
|
+
suspendPointerEvents = false,
|
|
412
|
+
revealToken,
|
|
413
|
+
variant = "bubble",
|
|
414
|
+
dragFromSurface = false,
|
|
415
|
+
accessibilityLabel = "Move dev tools bubble",
|
|
416
|
+
minimizedStack,
|
|
417
|
+
hasMinimizedStack = false,
|
|
418
|
+
accessory,
|
|
419
|
+
handleAdornment,
|
|
420
|
+
enablePositionPersistence = true,
|
|
421
|
+
defaultPlacement = "top",
|
|
422
|
+
pushToSide = false,
|
|
423
|
+
centerOnboarding = false,
|
|
424
|
+
children,
|
|
425
|
+
environment,
|
|
426
|
+
availableEnvironments,
|
|
427
|
+
onEnvironmentSwitch,
|
|
428
|
+
showEnvironmentSelector = false
|
|
429
|
+
}) {
|
|
430
|
+
// Animated position and drag state
|
|
431
|
+
const animatedPosition = (0, _react.useRef)(new _reactNative.Animated.ValueXY()).current;
|
|
432
|
+
const [isDragging, setIsDragging] = (0, _react.useState)(false);
|
|
433
|
+
const [bubbleSize, setBubbleSize] = (0, _react.useState)({
|
|
434
|
+
width: 100,
|
|
435
|
+
height: 32
|
|
436
|
+
});
|
|
437
|
+
const [isHidden, setIsHidden] = (0, _react.useState)(false);
|
|
438
|
+
const [accessoryHeight, setAccessoryHeight] = (0, _react.useState)(0);
|
|
439
|
+
const [accessoryAbove, setAccessoryAbove] = (0, _react.useState)(false);
|
|
440
|
+
|
|
441
|
+
// Store the position before hiding to restore when showing
|
|
442
|
+
const savedPositionRef = (0, _react.useRef)(null);
|
|
443
|
+
|
|
444
|
+
// Track if the hide state was triggered by pushToSide prop vs user toggle
|
|
445
|
+
const isPushedBySideRef = (0, _react.useRef)(false);
|
|
446
|
+
|
|
447
|
+
// Track if user has explicitly chosen to show the menu (overriding pushToSide)
|
|
448
|
+
const userWantsVisibleRef = (0, _react.useRef)(false);
|
|
449
|
+
|
|
450
|
+
// Track previous pushToSide value to detect transitions
|
|
451
|
+
const prevPushToSideRef = (0, _react.useRef)(pushToSide);
|
|
452
|
+
|
|
453
|
+
// Track previous bubble width for auto-adjustment when tools are added/removed
|
|
454
|
+
const prevBubbleWidthRef = (0, _react.useRef)(null);
|
|
455
|
+
|
|
456
|
+
// Track if layout has settled (after initial render cycle completes)
|
|
457
|
+
const [isLayoutSettled, setIsLayoutSettled] = (0, _react.useState)(false);
|
|
458
|
+
const safeAreaInsets = (0, _index2.useSafeAreaInsets)();
|
|
459
|
+
const {
|
|
460
|
+
width: screenWidth,
|
|
461
|
+
height: screenHeight
|
|
462
|
+
} = (0, _reactNative.useWindowDimensions)();
|
|
463
|
+
|
|
464
|
+
// Callback when position is loaded - sets hidden state properly without racing
|
|
465
|
+
const handlePositionLoaded = (0, _react.useCallback)((position, wasHidden) => {
|
|
466
|
+
if (wasHidden) {
|
|
467
|
+
setIsHidden(true);
|
|
468
|
+
}
|
|
469
|
+
}, []);
|
|
470
|
+
|
|
471
|
+
// Position persistence (state/IO extracted to hook)
|
|
472
|
+
const {
|
|
473
|
+
savePosition,
|
|
474
|
+
isInitialized: isPositionInitialized
|
|
475
|
+
} = useFloatingToolsPosition({
|
|
476
|
+
animatedPosition,
|
|
477
|
+
bubbleWidth: bubbleSize.width,
|
|
478
|
+
bubbleHeight: bubbleSize.height,
|
|
479
|
+
enabled: enablePositionPersistence,
|
|
480
|
+
visibleHandleWidth: VISIBLE_HANDLE_WIDTH,
|
|
481
|
+
listenersSuspended: isDragging,
|
|
482
|
+
defaultPlacement,
|
|
483
|
+
onPositionLoaded: handlePositionLoaded,
|
|
484
|
+
storageKey: id ? `@buoy/floating-tool/${id}` : undefined
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Effect to handle pushToSide prop changes
|
|
488
|
+
(0, _react.useEffect)(() => {
|
|
489
|
+
// Don't process pushToSide until position is initialized (when persistence enabled)
|
|
490
|
+
if (enablePositionPersistence && !isPositionInitialized) return;
|
|
491
|
+
|
|
492
|
+
// Reset user override when pushToSide becomes true (dial/modal opens)
|
|
493
|
+
// This allows auto-hide to work after user manually showed the menu
|
|
494
|
+
if (!prevPushToSideRef.current && pushToSide) {
|
|
495
|
+
userWantsVisibleRef.current = false;
|
|
496
|
+
}
|
|
497
|
+
prevPushToSideRef.current = pushToSide;
|
|
498
|
+
|
|
499
|
+
// Only push to side if:
|
|
500
|
+
// 1. pushToSide is true
|
|
501
|
+
// 2. Not currently hidden
|
|
502
|
+
// 3. Not being dragged
|
|
503
|
+
// 4. User hasn't manually restored (userWantsVisibleRef)
|
|
504
|
+
if (pushToSide && !isHidden && !isDragging && !userWantsVisibleRef.current) {
|
|
505
|
+
// Push to side
|
|
506
|
+
const currentX = getAnimatedValue(animatedPosition.x);
|
|
507
|
+
const currentY = getAnimatedValue(animatedPosition.y);
|
|
508
|
+
|
|
509
|
+
// Save current position
|
|
510
|
+
savedPositionRef.current = {
|
|
511
|
+
x: currentX,
|
|
512
|
+
y: currentY
|
|
513
|
+
};
|
|
514
|
+
const hiddenX = screenWidth - VISIBLE_HANDLE_WIDTH;
|
|
515
|
+
isPushedBySideRef.current = true;
|
|
516
|
+
setIsHidden(true);
|
|
517
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
518
|
+
toValue: {
|
|
519
|
+
x: hiddenX,
|
|
520
|
+
y: currentY
|
|
521
|
+
},
|
|
522
|
+
duration: 200,
|
|
523
|
+
useNativeDriver: false
|
|
524
|
+
}).start(() => {
|
|
525
|
+
savePosition(hiddenX, currentY);
|
|
526
|
+
});
|
|
527
|
+
} else if (!pushToSide && isHidden && isPushedBySideRef.current) {
|
|
528
|
+
// Restore from side when pushToSide becomes false
|
|
529
|
+
if (savedPositionRef.current) {
|
|
530
|
+
const {
|
|
531
|
+
x: targetX,
|
|
532
|
+
y: targetY
|
|
533
|
+
} = savedPositionRef.current;
|
|
534
|
+
isPushedBySideRef.current = false;
|
|
535
|
+
userWantsVisibleRef.current = false; // Reset user override when tools close
|
|
536
|
+
setIsHidden(false);
|
|
537
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
538
|
+
toValue: {
|
|
539
|
+
x: targetX,
|
|
540
|
+
y: targetY
|
|
541
|
+
},
|
|
542
|
+
duration: 200,
|
|
543
|
+
useNativeDriver: false
|
|
544
|
+
}).start(() => {
|
|
545
|
+
savePosition(targetX, targetY);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}, [enablePositionPersistence, isPositionInitialized, pushToSide, isHidden, isDragging, screenWidth, animatedPosition, savePosition]);
|
|
550
|
+
|
|
551
|
+
// Hidden state is now set via onPositionLoaded callback - no racing effect needed
|
|
552
|
+
|
|
553
|
+
// Wait for layout to settle before enabling width tracking
|
|
554
|
+
// This prevents false-positive width changes during initial render when
|
|
555
|
+
// the bubble measures multiple times as children render
|
|
556
|
+
(0, _react.useEffect)(() => {
|
|
557
|
+
if (!isPositionInitialized || isLayoutSettled) return;
|
|
558
|
+
const timer = setTimeout(() => {
|
|
559
|
+
// Capture the current width as the baseline AFTER layout has settled
|
|
560
|
+
prevBubbleWidthRef.current = bubbleSize.width;
|
|
561
|
+
setIsLayoutSettled(true);
|
|
562
|
+
}, LAYOUT_SETTLE_DELAY_MS);
|
|
563
|
+
return () => clearTimeout(timer);
|
|
564
|
+
}, [isPositionInitialized, isLayoutSettled, bubbleSize.width]);
|
|
565
|
+
|
|
566
|
+
// Auto-adjust position when bubble width changes (tools added/removed)
|
|
567
|
+
// This keeps the bubble's right edge in the same relative position
|
|
568
|
+
(0, _react.useEffect)(() => {
|
|
569
|
+
// Skip if position not initialized yet
|
|
570
|
+
if (!isPositionInitialized) return;
|
|
571
|
+
|
|
572
|
+
// Skip until layout has settled (prevents false-positive width changes during initial render)
|
|
573
|
+
if (!isLayoutSettled) return;
|
|
574
|
+
|
|
575
|
+
// Skip if prevBubbleWidthRef hasn't been set yet
|
|
576
|
+
if (prevBubbleWidthRef.current === null) return;
|
|
577
|
+
|
|
578
|
+
// Skip if currently dragging - let drag handle position
|
|
579
|
+
if (isDragging) {
|
|
580
|
+
prevBubbleWidthRef.current = bubbleSize.width;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const prevWidth = prevBubbleWidthRef.current;
|
|
584
|
+
const newWidth = bubbleSize.width;
|
|
585
|
+
const widthDelta = newWidth - prevWidth;
|
|
586
|
+
|
|
587
|
+
// Skip if no significant change (avoid jitter)
|
|
588
|
+
if (Math.abs(widthDelta) < 2) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Update ref first
|
|
593
|
+
prevBubbleWidthRef.current = newWidth;
|
|
594
|
+
|
|
595
|
+
// If hidden, only adjust the saved position (not the current animated position)
|
|
596
|
+
if (isHidden) {
|
|
597
|
+
if (savedPositionRef.current) {
|
|
598
|
+
const adjustedX = savedPositionRef.current.x - widthDelta;
|
|
599
|
+
// Clamp to valid bounds
|
|
600
|
+
savedPositionRef.current.x = Math.max(safeAreaInsets.left, Math.min(adjustedX, screenWidth - newWidth - EDGE_PADDING));
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Get current position
|
|
606
|
+
const currentX = getAnimatedValue(animatedPosition.x);
|
|
607
|
+
const currentY = getAnimatedValue(animatedPosition.y);
|
|
608
|
+
|
|
609
|
+
// Calculate new X position
|
|
610
|
+
// If bubble grew (positive delta), move left (decrease X)
|
|
611
|
+
// If bubble shrunk (negative delta), move right (increase X)
|
|
612
|
+
const newX = currentX - widthDelta;
|
|
613
|
+
|
|
614
|
+
// Validate new position is within bounds
|
|
615
|
+
const clampedX = Math.max(safeAreaInsets.left, Math.min(newX, screenWidth - VISIBLE_HANDLE_WIDTH));
|
|
616
|
+
|
|
617
|
+
// Only animate if position actually changed
|
|
618
|
+
if (Math.abs(clampedX - currentX) > 1) {
|
|
619
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
620
|
+
toValue: {
|
|
621
|
+
x: clampedX,
|
|
622
|
+
y: currentY
|
|
623
|
+
},
|
|
624
|
+
duration: 150,
|
|
625
|
+
useNativeDriver: false
|
|
626
|
+
}).start(() => {
|
|
627
|
+
savePosition(clampedX, currentY);
|
|
628
|
+
// Update savedPositionRef so show/hide works correctly
|
|
629
|
+
if (savedPositionRef.current) {
|
|
630
|
+
savedPositionRef.current.x = clampedX;
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}, [bubbleSize.width, isPositionInitialized, isLayoutSettled, isDragging, isHidden, animatedPosition, screenWidth, safeAreaInsets.left, savePosition]);
|
|
635
|
+
|
|
636
|
+
// Default position when persistence disabled or during onboarding
|
|
637
|
+
(0, _react.useEffect)(() => {
|
|
638
|
+
if (!enablePositionPersistence) {
|
|
639
|
+
if (centerOnboarding) {
|
|
640
|
+
// Center the bubble for onboarding - position under tooltip arrow
|
|
641
|
+
// Use shared calculation to ensure perfect alignment across all screens
|
|
642
|
+
const bottomOffset = (0, _onboardingConstants.calculateTargetPosition)();
|
|
643
|
+
const centerX = (screenWidth - bubbleSize.width) / 2;
|
|
644
|
+
const centerY = screenHeight - bubbleSize.height - bottomOffset;
|
|
645
|
+
animatedPosition.setValue({
|
|
646
|
+
x: centerX,
|
|
647
|
+
y: centerY
|
|
648
|
+
});
|
|
649
|
+
} else {
|
|
650
|
+
// Default right-side position
|
|
651
|
+
const defaultY = defaultPlacement === "bottom" ? Math.max(safeAreaInsets.top + EDGE_PADDING, screenHeight - bubbleSize.height - safeAreaInsets.bottom - EDGE_PADDING) : Math.max(safeAreaInsets.top + EDGE_PADDING, Math.min(100, screenHeight - bubbleSize.height - safeAreaInsets.bottom));
|
|
652
|
+
animatedPosition.setValue({
|
|
653
|
+
x: screenWidth - bubbleSize.width - EDGE_PADDING,
|
|
654
|
+
y: defaultY
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}, [enablePositionPersistence, centerOnboarding, animatedPosition, bubbleSize.width, bubbleSize.height, safeAreaInsets.top, safeAreaInsets.bottom, screenWidth, screenHeight]);
|
|
659
|
+
|
|
660
|
+
// Toggle hide/show function
|
|
661
|
+
const toggleHideShow = (0, _react.useCallback)(() => {
|
|
662
|
+
const currentX = getAnimatedValue(animatedPosition.x);
|
|
663
|
+
const currentY = getAnimatedValue(animatedPosition.y);
|
|
664
|
+
|
|
665
|
+
// Check if the menu is visually off-screen (more than half hidden to the right)
|
|
666
|
+
// This handles edge cases where isHidden state might be out of sync with actual position
|
|
667
|
+
const isVisuallyOffScreen = currentX > screenWidth - bubbleSize.width / 2;
|
|
668
|
+
if (isHidden || isVisuallyOffScreen) {
|
|
669
|
+
// Show the bubble - restore to saved position or default visible position
|
|
670
|
+
let targetX;
|
|
671
|
+
let targetY;
|
|
672
|
+
if (savedPositionRef.current && savedPositionRef.current.x < screenWidth - bubbleSize.width / 2) {
|
|
673
|
+
// Restore to the saved position only if it's a valid visible position
|
|
674
|
+
targetX = savedPositionRef.current.x;
|
|
675
|
+
targetY = savedPositionRef.current.y;
|
|
676
|
+
} else {
|
|
677
|
+
// Default visible position if no saved position or saved position is off-screen
|
|
678
|
+
targetX = screenWidth - bubbleSize.width - EDGE_PADDING;
|
|
679
|
+
targetY = currentY;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// User explicitly wants the menu visible (overrides pushToSide)
|
|
683
|
+
isPushedBySideRef.current = false;
|
|
684
|
+
userWantsVisibleRef.current = true;
|
|
685
|
+
setIsHidden(false);
|
|
686
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
687
|
+
toValue: {
|
|
688
|
+
x: targetX,
|
|
689
|
+
y: targetY
|
|
690
|
+
},
|
|
691
|
+
duration: 200,
|
|
692
|
+
useNativeDriver: false
|
|
693
|
+
}).start(() => {
|
|
694
|
+
savePosition(targetX, targetY);
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
// Hide the bubble - save current position before hiding
|
|
698
|
+
savedPositionRef.current = {
|
|
699
|
+
x: currentX,
|
|
700
|
+
y: currentY
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// User explicitly wants the menu hidden
|
|
704
|
+
userWantsVisibleRef.current = false;
|
|
705
|
+
const hiddenX = screenWidth - VISIBLE_HANDLE_WIDTH;
|
|
706
|
+
setIsHidden(true);
|
|
707
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
708
|
+
toValue: {
|
|
709
|
+
x: hiddenX,
|
|
710
|
+
y: currentY
|
|
711
|
+
},
|
|
712
|
+
duration: 200,
|
|
713
|
+
useNativeDriver: false
|
|
714
|
+
}).start(() => {
|
|
715
|
+
savePosition(hiddenX, currentY);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}, [animatedPosition, isHidden, bubbleSize.width, savePosition, screenWidth]);
|
|
719
|
+
const handleDragStart = (0, _react.useCallback)(() => {
|
|
720
|
+
setIsDragging(true);
|
|
721
|
+
}, []);
|
|
722
|
+
const handleDragEnd = (0, _react.useCallback)(finalPosition => {
|
|
723
|
+
let {
|
|
724
|
+
x: currentX,
|
|
725
|
+
y: currentY
|
|
726
|
+
} = finalPosition;
|
|
727
|
+
|
|
728
|
+
// Check if bubble is more than 50% over the right edge
|
|
729
|
+
const bubbleMidpoint = currentX + bubbleSize.width / 2;
|
|
730
|
+
const shouldHide = bubbleMidpoint > screenWidth;
|
|
731
|
+
if (shouldHide) {
|
|
732
|
+
// Animate to hidden position (only grabber visible)
|
|
733
|
+
const hiddenX = screenWidth - VISIBLE_HANDLE_WIDTH;
|
|
734
|
+
setIsHidden(true);
|
|
735
|
+
|
|
736
|
+
// Update savedPositionRef to preserve the Y position when dragging while hidden
|
|
737
|
+
// This ensures that when showing, the bubble restores to the new Y position
|
|
738
|
+
if (savedPositionRef.current) {
|
|
739
|
+
savedPositionRef.current.y = currentY;
|
|
740
|
+
} else {
|
|
741
|
+
// If no saved position exists, create one with default visible X
|
|
742
|
+
savedPositionRef.current = {
|
|
743
|
+
x: screenWidth - bubbleSize.width - EDGE_PADDING,
|
|
744
|
+
y: currentY
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
_reactNative.Animated.timing(animatedPosition, {
|
|
748
|
+
toValue: {
|
|
749
|
+
x: hiddenX,
|
|
750
|
+
y: currentY
|
|
751
|
+
},
|
|
752
|
+
duration: 200,
|
|
753
|
+
useNativeDriver: false
|
|
754
|
+
}).start(() => {
|
|
755
|
+
savePosition(hiddenX, currentY);
|
|
756
|
+
});
|
|
757
|
+
} else {
|
|
758
|
+
// Check if we're in hidden state and user is pulling it back
|
|
759
|
+
if (isHidden && currentX < screenWidth - VISIBLE_HANDLE_WIDTH - 10) {
|
|
760
|
+
setIsHidden(false);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Update saved position if bubble is in visible area (not hidden)
|
|
764
|
+
if (currentX < screenWidth - bubbleSize.width / 2) {
|
|
765
|
+
savedPositionRef.current = {
|
|
766
|
+
x: currentX,
|
|
767
|
+
y: currentY
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
savePosition(currentX, currentY);
|
|
771
|
+
}
|
|
772
|
+
setIsDragging(false);
|
|
773
|
+
}, [animatedPosition, bubbleSize.width, isHidden, savePosition, screenWidth]);
|
|
774
|
+
const lastViewport = (0, _react.useRef)({
|
|
775
|
+
width: screenWidth,
|
|
776
|
+
height: screenHeight
|
|
777
|
+
});
|
|
778
|
+
(0, _react.useEffect)(() => {
|
|
779
|
+
if (enablePositionPersistence && !isPositionInitialized) return;
|
|
780
|
+
if (lastViewport.current.width === screenWidth && lastViewport.current.height === screenHeight) return;
|
|
781
|
+
lastViewport.current = {
|
|
782
|
+
width: screenWidth,
|
|
783
|
+
height: screenHeight
|
|
784
|
+
};
|
|
785
|
+
const clamp = point => ({
|
|
786
|
+
x: Math.max(safeAreaInsets.left, Math.min(point.x, screenWidth - bubbleSize.width - EDGE_PADDING)),
|
|
787
|
+
y: Math.max(safeAreaInsets.top + EDGE_PADDING, Math.min(point.y, screenHeight - bubbleSize.height - safeAreaInsets.bottom))
|
|
788
|
+
});
|
|
789
|
+
const current = clamp({
|
|
790
|
+
x: getAnimatedValue(animatedPosition.x),
|
|
791
|
+
y: getAnimatedValue(animatedPosition.y)
|
|
792
|
+
});
|
|
793
|
+
if (isHidden) current.x = screenWidth - VISIBLE_HANDLE_WIDTH;
|
|
794
|
+
if (savedPositionRef.current) savedPositionRef.current = clamp(savedPositionRef.current);
|
|
795
|
+
animatedPosition.stopAnimation();
|
|
796
|
+
animatedPosition.setValue(current);
|
|
797
|
+
void savePosition(current.x, current.y);
|
|
798
|
+
}, [screenWidth, screenHeight, isPositionInitialized, enablePositionPersistence, bubbleSize, safeAreaInsets, isHidden, animatedPosition, savePosition]);
|
|
799
|
+
const hasAccessory = Boolean(accessory);
|
|
800
|
+
(0, _react.useEffect)(() => {
|
|
801
|
+
if (!hasAccessory) return;
|
|
802
|
+
const update = ({
|
|
803
|
+
y
|
|
804
|
+
}) => setAccessoryAbove(y + bubbleSize.height + accessoryHeight > screenHeight - safeAreaInsets.bottom);
|
|
805
|
+
update({
|
|
806
|
+
y: getAnimatedValue(animatedPosition.y)
|
|
807
|
+
});
|
|
808
|
+
const listener = animatedPosition.addListener(update);
|
|
809
|
+
return () => animatedPosition.removeListener(listener);
|
|
810
|
+
}, [animatedPosition, bubbleSize.height, accessoryHeight, screenHeight, safeAreaInsets.bottom, hasAccessory]);
|
|
811
|
+
|
|
812
|
+
// Stable styles
|
|
813
|
+
const bubbleStyle = (0, _react.useMemo)(() => ({
|
|
814
|
+
position: "absolute",
|
|
815
|
+
zIndex: 1001,
|
|
816
|
+
transform: animatedPosition.getTranslateTransform()
|
|
817
|
+
}), [animatedPosition]);
|
|
818
|
+
const previousReveal = (0, _react.useRef)(undefined);
|
|
819
|
+
(0, _react.useEffect)(() => {
|
|
820
|
+
if (enablePositionPersistence && !isPositionInitialized) return;
|
|
821
|
+
if (previousReveal.current === revealToken) return;
|
|
822
|
+
previousReveal.current = revealToken;
|
|
823
|
+
if (revealToken !== undefined && revealToken > 0 && isHidden) toggleHideShow();
|
|
824
|
+
}, [revealToken, isPositionInitialized, enablePositionPersistence, isHidden, toggleHideShow]);
|
|
825
|
+
const hasMinimizedTools = hasMinimizedStack;
|
|
826
|
+
const containerStyle = {
|
|
827
|
+
flexDirection: "row",
|
|
828
|
+
alignItems: "center",
|
|
829
|
+
backgroundColor: _gameUIColors.buoyColors.card,
|
|
830
|
+
// When minimized tools showing, remove top-left radius so they connect seamlessly
|
|
831
|
+
borderTopLeftRadius: hasMinimizedTools ? 0 : 6,
|
|
832
|
+
borderTopRightRadius: 6,
|
|
833
|
+
borderBottomLeftRadius: 6,
|
|
834
|
+
borderBottomRightRadius: 6,
|
|
835
|
+
// Use individual border widths when minimized tools are showing
|
|
836
|
+
// Always show top border when dragging for visual feedback
|
|
837
|
+
borderLeftWidth: isDragging ? 2 : 1,
|
|
838
|
+
borderRightWidth: isDragging ? 2 : 1,
|
|
839
|
+
borderBottomWidth: isDragging ? 2 : 1,
|
|
840
|
+
borderTopWidth: isDragging ? 2 : hasMinimizedTools ? 0 : 1,
|
|
841
|
+
borderColor: isDragging ? _floatingToolsCore.floatingToolsColors.dragActive : _gameUIColors.buoyColors.textMuted + "66",
|
|
842
|
+
overflow: "visible",
|
|
843
|
+
elevation: 8,
|
|
844
|
+
shadowColor: isDragging ? _floatingToolsCore.floatingToolsColors.dragActive + "99" : "#000",
|
|
845
|
+
shadowOffset: {
|
|
846
|
+
width: 0,
|
|
847
|
+
height: isDragging ? 6 : 4
|
|
848
|
+
},
|
|
849
|
+
shadowOpacity: isDragging ? 0.6 : 0.3,
|
|
850
|
+
shadowRadius: isDragging ? 12 : 8,
|
|
851
|
+
...(variant === "strip" ? {
|
|
852
|
+
borderRadius: _nightTheme.night.radius.card,
|
|
853
|
+
borderTopLeftRadius: _nightTheme.night.radius.card,
|
|
854
|
+
borderTopRightRadius: _nightTheme.night.radius.card,
|
|
855
|
+
borderBottomLeftRadius: _nightTheme.night.radius.card,
|
|
856
|
+
borderBottomRightRadius: _nightTheme.night.radius.card,
|
|
857
|
+
minHeight: 48,
|
|
858
|
+
backgroundColor: _nightTheme.night.color.surface,
|
|
859
|
+
borderColor: isDragging ? _nightTheme.night.color.accentBorderStrong : _nightTheme.night.color.separator,
|
|
860
|
+
shadowOpacity: 0.24,
|
|
861
|
+
shadowRadius: 16
|
|
862
|
+
} : {})
|
|
863
|
+
};
|
|
864
|
+
const dragHandleStyle = {
|
|
865
|
+
paddingHorizontal: 6,
|
|
866
|
+
paddingVertical: 6,
|
|
867
|
+
backgroundColor: _gameUIColors.buoyColors.textMuted + "1A",
|
|
868
|
+
alignItems: "center",
|
|
869
|
+
justifyContent: "center",
|
|
870
|
+
width: 32,
|
|
871
|
+
...(variant === "strip" ? {
|
|
872
|
+
minHeight: 48
|
|
873
|
+
} : {}),
|
|
874
|
+
borderRightWidth: 1,
|
|
875
|
+
borderRightColor: _gameUIColors.buoyColors.textMuted + "66",
|
|
876
|
+
// Remove top-left radius when minimized tools are showing
|
|
877
|
+
borderTopLeftRadius: hasMinimizedTools ? 0 : undefined,
|
|
878
|
+
...(variant === "strip" ? {
|
|
879
|
+
backgroundColor: "transparent",
|
|
880
|
+
borderRightWidth: 0
|
|
881
|
+
} : {})
|
|
882
|
+
};
|
|
883
|
+
const contentStyle = {
|
|
884
|
+
flexDirection: "row",
|
|
885
|
+
alignItems: "center",
|
|
886
|
+
gap: 6,
|
|
887
|
+
paddingRight: 8
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
// Compose actions row with automatic dividers
|
|
891
|
+
const actions = (0, _react.useMemo)(() => variant === "strip" ? _react.Children.toArray(children) : interleaveWithDividers(_react.Children.toArray(children)), [children, variant]);
|
|
892
|
+
|
|
893
|
+
// Width for the minimized tools stack - match the drag handle width
|
|
894
|
+
const minimizedStackWidth = 32;
|
|
895
|
+
|
|
896
|
+
// Don't render until position is initialized (when persistence enabled)
|
|
897
|
+
// This prevents the bubble from appearing at {0,0} before position loads
|
|
898
|
+
if (enablePositionPersistence && !isPositionInitialized) {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
902
|
+
pointerEvents: suspendPointerEvents ? "none" : "box-none",
|
|
903
|
+
testID: id ?? "buoy-floating-tool",
|
|
904
|
+
style: bubbleStyle,
|
|
905
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
906
|
+
style: {
|
|
907
|
+
overflow: "visible"
|
|
908
|
+
},
|
|
909
|
+
children: [minimizedStack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
910
|
+
style: {
|
|
911
|
+
position: "absolute",
|
|
912
|
+
bottom: "100%",
|
|
913
|
+
left: 0,
|
|
914
|
+
width: minimizedStackWidth,
|
|
915
|
+
overflow: "visible",
|
|
916
|
+
zIndex: 1001
|
|
917
|
+
},
|
|
918
|
+
children: minimizedStack
|
|
919
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_DraggableHeader.DraggableHeader, {
|
|
920
|
+
position: animatedPosition,
|
|
921
|
+
enabled: dragFromSurface,
|
|
922
|
+
claimOnMove: false,
|
|
923
|
+
onDragStart: handleDragStart,
|
|
924
|
+
onDragEnd: handleDragEnd,
|
|
925
|
+
containerBounds: {
|
|
926
|
+
width: screenWidth,
|
|
927
|
+
height: screenHeight
|
|
928
|
+
},
|
|
929
|
+
elementSize: bubbleSize,
|
|
930
|
+
minPosition: {
|
|
931
|
+
x: safeAreaInsets.left,
|
|
932
|
+
y: safeAreaInsets.top + EDGE_PADDING
|
|
933
|
+
},
|
|
934
|
+
maxOverflowX: bubbleSize.width,
|
|
935
|
+
hitSlop: {
|
|
936
|
+
top: 10,
|
|
937
|
+
bottom: 10,
|
|
938
|
+
left: 10,
|
|
939
|
+
right: 10
|
|
940
|
+
},
|
|
941
|
+
style: containerStyle,
|
|
942
|
+
onLayout: event => {
|
|
943
|
+
const {
|
|
944
|
+
width,
|
|
945
|
+
height
|
|
946
|
+
} = event.nativeEvent.layout;
|
|
947
|
+
setBubbleSize({
|
|
948
|
+
width,
|
|
949
|
+
height
|
|
950
|
+
});
|
|
951
|
+
},
|
|
952
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_DraggableHeader.DraggableHeader, {
|
|
953
|
+
position: animatedPosition,
|
|
954
|
+
testID: id ? `${id}-grip` : "buoy-floating-grip",
|
|
955
|
+
accessibilityLabel: accessibilityLabel,
|
|
956
|
+
onDragStart: handleDragStart,
|
|
957
|
+
onDragEnd: handleDragEnd,
|
|
958
|
+
onTap: toggleHideShow,
|
|
959
|
+
containerBounds: {
|
|
960
|
+
width: screenWidth,
|
|
961
|
+
height: screenHeight
|
|
962
|
+
},
|
|
963
|
+
elementSize: bubbleSize,
|
|
964
|
+
minPosition: {
|
|
965
|
+
x: safeAreaInsets.left,
|
|
966
|
+
y: safeAreaInsets.top + EDGE_PADDING
|
|
967
|
+
},
|
|
968
|
+
style: dragHandleStyle,
|
|
969
|
+
enabled: true,
|
|
970
|
+
maxOverflowX: bubbleSize.width,
|
|
971
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_GripVerticalIcon.GripVerticalIcon, {
|
|
972
|
+
size: variant === "strip" ? 20 : 12,
|
|
973
|
+
color: _gameUIColors.buoyColors.textSecondary + "CC"
|
|
974
|
+
}), handleAdornment]
|
|
975
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(FloatingToolsContext.Provider, {
|
|
976
|
+
value: {
|
|
977
|
+
isDragging
|
|
978
|
+
},
|
|
979
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
980
|
+
style: contentStyle,
|
|
981
|
+
children: [showEnvironmentSelector && environment && availableEnvironments && onEnvironmentSwitch && /*#__PURE__*/(0, _jsxRuntime.jsx)(_EnvironmentSelectorInline.EnvironmentSelectorInline, {
|
|
982
|
+
currentEnvironment: environment,
|
|
983
|
+
availableEnvironments: availableEnvironments,
|
|
984
|
+
onSelect: onEnvironmentSwitch
|
|
985
|
+
}), actions]
|
|
986
|
+
})
|
|
987
|
+
})]
|
|
988
|
+
}), !isHidden && accessory && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
989
|
+
onLayout: event => setAccessoryHeight(event.nativeEvent.layout.height),
|
|
990
|
+
style: {
|
|
991
|
+
position: "absolute",
|
|
992
|
+
...(accessoryAbove ? {
|
|
993
|
+
bottom: "100%"
|
|
994
|
+
} : {
|
|
995
|
+
top: "100%"
|
|
996
|
+
}),
|
|
997
|
+
left: 0,
|
|
998
|
+
width: "100%",
|
|
999
|
+
zIndex: 1002
|
|
1000
|
+
},
|
|
1001
|
+
children: accessory
|
|
1002
|
+
})]
|
|
1003
|
+
})
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/** Native AppHost gets its anchor from the minimized-tools provider. */
|
|
1008
|
+
function getBubbleAnchorPosition() {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|