@buoy-gg/core 7.0.41 → 7.0.43
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 +14 -95
- package/bin/buoy.js +19 -340
- package/lib/commonjs/Buoy.js +1 -107
- package/lib/commonjs/browserRegister.js +1 -48
- package/lib/commonjs/browserRegister.server.js +1 -10
- package/lib/commonjs/earlyHookScript.js +1 -0
- package/lib/commonjs/earlyNetwork.js +1 -0
- package/lib/commonjs/floatingMenu/AppHost.js +1 -460
- package/lib/commonjs/floatingMenu/AppHost.web.js +1 -376
- package/lib/commonjs/floatingMenu/AppHostLogic.js +1 -50
- package/lib/commonjs/floatingMenu/AppIcon.js +1 -91
- package/lib/commonjs/floatingMenu/AppIcon.web.js +1 -53
- package/lib/commonjs/floatingMenu/BrowserToolRuntime.js +1 -150
- package/lib/commonjs/floatingMenu/DefaultConfigContext.js +1 -45
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +1 -18
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.shared.js +1 -1114
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +1 -48
- package/lib/commonjs/floatingMenu/DevToolsVisibilityContext.js +1 -49
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +1 -559
- package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +1 -415
- package/lib/commonjs/floatingMenu/FloatingMenu.js +1 -371
- package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +1 -274
- package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +1 -288
- package/lib/commonjs/floatingMenu/MinimizedToolsStack.web.js +1 -377
- package/lib/commonjs/floatingMenu/ToggleStateManager.js +1 -36
- package/lib/commonjs/floatingMenu/ToolHost.js +1 -281
- package/lib/commonjs/floatingMenu/ToolOpenerBridge.js +1 -41
- package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +1 -471
- package/lib/commonjs/floatingMenu/autoExternalSync.js +1 -443
- package/lib/commonjs/floatingMenu/defaultConfig.js +1 -167
- package/lib/commonjs/floatingMenu/deviceIdentity.js +1 -173
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +1 -904
- package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +1 -895
- package/lib/commonjs/floatingMenu/dial/DialHyperspace.js +1 -177
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +1 -304
- package/lib/commonjs/floatingMenu/dial/DialPagination.js +1 -165
- package/lib/commonjs/floatingMenu/dial/DialSnow.js +1 -92
- package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +1 -246
- package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.web.js +2 -147
- package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +1 -93
- package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +1 -42
- package/lib/commonjs/floatingMenu/dial/onboardingConstants.web.js +1 -42
- package/lib/commonjs/floatingMenu/externalSyncGate.js +1 -49
- package/lib/commonjs/floatingMenu/floatingTools.js +1 -42
- package/lib/commonjs/floatingMenu/floatingTools.web.js +1 -30
- package/lib/commonjs/floatingMenu/optionalRequire.js +1 -37
- package/lib/commonjs/floatingMenu/settingsBus.js +1 -12
- package/lib/commonjs/floatingMenu/sync/appSyncAdapter.js +1 -115
- package/lib/commonjs/floatingMenu/types.js +1 -5
- package/lib/commonjs/floatingMenu/useDesktopSyncStatus.js +1 -28
- package/lib/commonjs/floatingMenu/useDesktopSyncStatus.web.js +1 -14
- package/lib/commonjs/index.js +1 -291
- package/lib/commonjs/index.web.js +1 -241
- package/lib/commonjs/reactDevtoolsTypes.js +1 -1
- package/lib/commonjs/releaseOptIn.js +1 -0
- package/lib/commonjs/telemetry/installPing.js +1 -131
- package/lib/commonjs/toolkit.js +1 -584
- package/lib/commonjs/toolkitComponents.js +1 -241
- package/lib/commonjs/utils/autoDiscoverPresets.web.js +1 -46
- package/lib/commonjs/utils/createWebTool.js +1 -20
- package/lib/commonjs/utils/createWebTools.js +1 -30
- package/lib/commonjs/version.js +1 -18
- package/lib/module/Buoy.js +1 -102
- package/lib/module/browserRegister.js +1 -44
- package/lib/module/browserRegister.server.js +1 -6
- package/lib/module/earlyHookScript.js +1 -0
- package/lib/module/earlyNetwork.js +1 -0
- package/lib/module/floatingMenu/AppHost.js +1 -452
- package/lib/module/floatingMenu/AppHost.web.js +1 -368
- package/lib/module/floatingMenu/AppHostLogic.js +1 -45
- package/lib/module/floatingMenu/AppIcon.js +1 -84
- package/lib/module/floatingMenu/AppIcon.web.js +1 -47
- package/lib/module/floatingMenu/BrowserToolRuntime.js +1 -146
- package/lib/module/floatingMenu/DefaultConfigContext.js +1 -39
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +1 -3
- package/lib/module/floatingMenu/DevToolsSettingsModal.shared.js +1 -1110
- package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +1 -44
- package/lib/module/floatingMenu/DevToolsVisibilityContext.js +1 -44
- package/lib/module/floatingMenu/FloatingDevTools.js +1 -556
- package/lib/module/floatingMenu/FloatingDevTools.web.js +1 -412
- package/lib/module/floatingMenu/FloatingMenu.js +1 -366
- package/lib/module/floatingMenu/MinimizedToolsContext.js +1 -266
- package/lib/module/floatingMenu/MinimizedToolsStack.js +1 -285
- package/lib/module/floatingMenu/MinimizedToolsStack.web.js +1 -374
- package/lib/module/floatingMenu/ToggleStateManager.js +1 -32
- package/lib/module/floatingMenu/ToolHost.js +1 -273
- package/lib/module/floatingMenu/ToolOpenerBridge.js +1 -37
- package/lib/module/floatingMenu/autoDiscoverPresets.js +1 -467
- package/lib/module/floatingMenu/autoExternalSync.js +1 -436
- package/lib/module/floatingMenu/defaultConfig.js +1 -158
- package/lib/module/floatingMenu/deviceIdentity.js +1 -163
- package/lib/module/floatingMenu/dial/DialDevTools.js +1 -900
- package/lib/module/floatingMenu/dial/DialDevTools.web.js +1 -891
- package/lib/module/floatingMenu/dial/DialHyperspace.js +1 -173
- package/lib/module/floatingMenu/dial/DialIcon.js +1 -301
- package/lib/module/floatingMenu/dial/DialPagination.js +1 -160
- package/lib/module/floatingMenu/dial/DialSnow.js +1 -90
- package/lib/module/floatingMenu/dial/OnboardingTooltip.js +1 -240
- package/lib/module/floatingMenu/dial/OnboardingTooltip.web.js +2 -142
- package/lib/module/floatingMenu/dial/dialUsageStore.js +1 -85
- package/lib/module/floatingMenu/dial/onboardingConstants.js +1 -3
- package/lib/module/floatingMenu/dial/onboardingConstants.web.js +1 -3
- package/lib/module/floatingMenu/externalSyncGate.js +1 -44
- package/lib/module/floatingMenu/floatingTools.js +1 -27
- package/lib/module/floatingMenu/floatingTools.web.js +1 -3
- package/lib/module/floatingMenu/optionalRequire.js +1 -33
- package/lib/module/floatingMenu/settingsBus.js +1 -3
- package/lib/module/floatingMenu/sync/appSyncAdapter.js +1 -112
- package/lib/module/floatingMenu/types.js +1 -3
- package/lib/module/floatingMenu/useDesktopSyncStatus.js +1 -25
- package/lib/module/floatingMenu/useDesktopSyncStatus.web.js +1 -10
- package/lib/module/index.js +1 -41
- package/lib/module/index.web.js +1 -36
- package/lib/module/reactDevtoolsTypes.js +1 -1
- package/lib/module/releaseOptIn.js +1 -0
- package/lib/module/telemetry/installPing.js +1 -127
- package/lib/module/toolkit.js +1 -62
- package/lib/module/toolkitComponents.js +1 -230
- package/lib/module/utils/autoDiscoverPresets.web.js +1 -38
- package/lib/module/utils/createWebTool.js +1 -17
- package/lib/module/utils/createWebTools.js +1 -25
- package/lib/module/version.js +1 -14
- package/lib/typescript/commonjs/browserRegister.d.ts +9 -1
- package/lib/typescript/commonjs/browserRegister.server.d.ts +1 -0
- package/lib/typescript/commonjs/earlyHookScript.d.ts +12 -0
- package/lib/typescript/commonjs/earlyNetwork.d.ts +52 -0
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +1 -0
- package/lib/typescript/commonjs/releaseOptIn.d.ts +4 -0
- package/lib/typescript/commonjs/version.d.ts +1 -1
- package/lib/typescript/module/browserRegister.d.ts +9 -1
- package/lib/typescript/module/browserRegister.server.d.ts +1 -0
- package/lib/typescript/module/earlyHookScript.d.ts +12 -0
- package/lib/typescript/module/earlyNetwork.d.ts +52 -0
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/module/index.web.d.ts +1 -0
- package/lib/typescript/module/releaseOptIn.d.ts +4 -0
- package/lib/typescript/module/version.d.ts +1 -1
- package/lib/web/chunks/chunk-5SGL6QL4.mjs +1 -0
- package/lib/web/chunks/chunk-FROELTJH.mjs +1 -0
- package/lib/web/chunks/chunk-QZDVCMCT.mjs +1 -2
- package/lib/web/chunks/chunk-R7RGRTY5.mjs +1 -2
- package/lib/web/index.mjs +2 -3
- package/lib/web/register-server.mjs +1 -2
- package/lib/web/register.mjs +1 -2
- package/lib/web/toolkit.mjs +1 -2
- package/package.json +6 -9
- package/lib/typescript/commonjs/Buoy.d.ts.map +0 -1
- package/lib/typescript/commonjs/browserRegister.d.ts.map +0 -1
- package/lib/typescript/commonjs/browserRegister.server.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/AppIcon.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/BrowserToolRuntime.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/ToolOpenerBridge.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/deviceIdentity.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/externalSyncGate.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/sync/appSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.d.ts.map +0 -1
- package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/index.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/reactDevtoolsTypes.d.ts.map +0 -1
- package/lib/typescript/commonjs/telemetry/installPing.d.ts.map +0 -1
- package/lib/typescript/commonjs/toolkit.d.ts.map +0 -1
- package/lib/typescript/commonjs/toolkitComponents.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/createWebTool.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/createWebTools.d.ts.map +0 -1
- package/lib/typescript/commonjs/version.d.ts.map +0 -1
- package/lib/typescript/module/Buoy.d.ts.map +0 -1
- package/lib/typescript/module/browserRegister.d.ts.map +0 -1
- package/lib/typescript/module/browserRegister.server.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/AppHost.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/AppIcon.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/BrowserToolRuntime.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/ToolHost.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/ToolOpenerBridge.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/deviceIdentity.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/externalSyncGate.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/optionalRequire.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/sync/appSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/types.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.d.ts.map +0 -1
- package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.web.d.ts.map +0 -1
- package/lib/typescript/module/index.d.ts.map +0 -1
- package/lib/typescript/module/index.web.d.ts.map +0 -1
- package/lib/typescript/module/reactDevtoolsTypes.d.ts.map +0 -1
- package/lib/typescript/module/telemetry/installPing.d.ts.map +0 -1
- package/lib/typescript/module/toolkit.d.ts.map +0 -1
- package/lib/typescript/module/toolkitComponents.d.ts.map +0 -1
- package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +0 -1
- package/lib/typescript/module/utils/createWebTool.d.ts.map +0 -1
- package/lib/typescript/module/utils/createWebTools.d.ts.map +0 -1
- package/lib/typescript/module/version.d.ts.map +0 -1
- package/lib/web/chunks/chunk-5NZOQR6Q.mjs +0 -2
- package/lib/web/chunks/chunk-CGDSLLZF.mjs +0 -2
- package/lib/web/chunks/chunk-DCJDUSNR.mjs +0 -2
- package/lib/web/chunks/chunk-IDSHFS6G.mjs +0 -2
- package/lib/web/chunks/chunk-JOAMJGCS.mjs +0 -2
- package/lib/web/chunks/chunk-KYKZ2N52.mjs +0 -2
- package/lib/web/chunks/chunk-NGM7JGGS.mjs +0 -2
- package/lib/web/chunks/chunk-Z3XWDDIO.mjs +0 -2
- package/lib/web/chunks/chunk-ZTGQ6TTY.mjs +0 -2
|
@@ -1,891 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* DialDevTools - Web renderer for the radial dial menu.
|
|
5
|
-
*
|
|
6
|
-
* Uses shared layout calculations, styling, and animation config from @buoy-gg/floating-tools-core.
|
|
7
|
-
* Platform-specific: CSS animations, mouse events, web JSX.
|
|
8
|
-
*
|
|
9
|
-
* Visuals mirror the native DialDevTools.tsx exactly — values that never made
|
|
10
|
-
* it into the shared dial.ts are inlined here with provenance comments.
|
|
11
|
-
*
|
|
12
|
-
* @platform React DOM Web (Vite, CRA, Next.js, Electron)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
16
|
-
import { getDialLayout, getAllIconPositions, getGridLineRotations, getIconStaggerInputRange, MAX_DIAL_SLOTS, DIAL_ICON_SIZE, dialAnimationConfig, getDialCSSKeyframesString, getDialCSSAnimationStyles, dialColors, dialStyles, withAlpha, floatingToolsColors } from '@buoy-gg/floating-tools-react';
|
|
17
|
-
import { persistentStorage, monoFont, ToolBackground, useToolBackground } from '@buoy-gg/shared-ui';
|
|
18
|
-
import { DialHyperspace } from "./DialHyperspace.js";
|
|
19
|
-
import { OnboardingTooltip } from "./OnboardingTooltip.web.js";
|
|
20
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
21
|
-
const DIAL_BACKGROUND_FOCUS = {
|
|
22
|
-
x: 0.5,
|
|
23
|
-
y: 0.5
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// native DialDevTools.tsx SETTINGS_TOOLTIP_KEY
|
|
27
|
-
const SETTINGS_TOOLTIP_KEY = '@react_buoy_settings_tooltip_shown';
|
|
28
|
-
|
|
29
|
-
// Inject CSS keyframes once on module load
|
|
30
|
-
let keyframesInjected = false;
|
|
31
|
-
function injectKeyframes() {
|
|
32
|
-
if (keyframesInjected || typeof document === 'undefined') return;
|
|
33
|
-
const style = document.createElement('style');
|
|
34
|
-
style.textContent = getDialCSSKeyframesString();
|
|
35
|
-
document.head.appendChild(style);
|
|
36
|
-
keyframesInjected = true;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Easing functions
|
|
40
|
-
const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
|
|
41
|
-
const easeInCubic = t => t * t * t;
|
|
42
|
-
|
|
43
|
-
// =============================
|
|
44
|
-
// Types
|
|
45
|
-
// =============================
|
|
46
|
-
|
|
47
|
-
// =============================
|
|
48
|
-
// Dial Icon Component
|
|
49
|
-
// =============================
|
|
50
|
-
|
|
51
|
-
function DialIconItem({
|
|
52
|
-
icon,
|
|
53
|
-
index,
|
|
54
|
-
totalIcons,
|
|
55
|
-
position,
|
|
56
|
-
progress,
|
|
57
|
-
onPress
|
|
58
|
-
}) {
|
|
59
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
60
|
-
const [isPressed, setIsPressed] = useState(false);
|
|
61
|
-
const isEmpty = !icon.icon && icon.id.startsWith('empty-');
|
|
62
|
-
const isUnavailable = !isEmpty && icon.unavailable === true;
|
|
63
|
-
const interactive = !isEmpty && !isUnavailable;
|
|
64
|
-
const {
|
|
65
|
-
icons: iconConfig
|
|
66
|
-
} = dialAnimationConfig;
|
|
67
|
-
|
|
68
|
-
// Get stagger input range for this icon
|
|
69
|
-
const staggerInputRange = getIconStaggerInputRange(index, totalIcons);
|
|
70
|
-
|
|
71
|
-
// Calculate this icon's individual progress based on the global progress
|
|
72
|
-
let iconProgress = 0;
|
|
73
|
-
if (progress <= staggerInputRange[1]) {
|
|
74
|
-
iconProgress = 0;
|
|
75
|
-
} else if (progress <= staggerInputRange[2]) {
|
|
76
|
-
iconProgress = (progress - staggerInputRange[1]) / (staggerInputRange[2] - staggerInputRange[1]);
|
|
77
|
-
} else {
|
|
78
|
-
iconProgress = 1;
|
|
79
|
-
}
|
|
80
|
-
iconProgress = Math.max(0, Math.min(1, iconProgress));
|
|
81
|
-
const iconSize = DIAL_ICON_SIZE;
|
|
82
|
-
const pressedScale = iconConfig.pressIn.scale;
|
|
83
|
-
const hoveredScale = 1.1; // web-only hover affordance (no native counterpart)
|
|
84
|
-
|
|
85
|
-
const scale = iconProgress * (isPressed ? pressedScale : isHovered ? hoveredScale : 1);
|
|
86
|
-
|
|
87
|
-
// Opacity interpolation — native DialIcon.tsx: [0, 0.3, 1] -> [0, 0.3, 1]
|
|
88
|
-
let opacity = 0;
|
|
89
|
-
if (iconProgress <= 0) {
|
|
90
|
-
opacity = 0;
|
|
91
|
-
} else if (iconProgress <= 0.3) {
|
|
92
|
-
opacity = iconProgress;
|
|
93
|
-
} else {
|
|
94
|
-
opacity = 0.3 + (iconProgress - 0.3) * (0.7 / 0.7);
|
|
95
|
-
}
|
|
96
|
-
const x = position.x * iconProgress;
|
|
97
|
-
const y = position.y * iconProgress;
|
|
98
|
-
return /*#__PURE__*/_jsx("button", {
|
|
99
|
-
role: "menuitem",
|
|
100
|
-
"aria-label": isEmpty ? 'Empty slot' : icon.name,
|
|
101
|
-
"aria-disabled": !interactive,
|
|
102
|
-
tabIndex: interactive ? 0 : -1,
|
|
103
|
-
onClick: interactive ? onPress : undefined,
|
|
104
|
-
onMouseEnter: () => interactive && setIsHovered(true),
|
|
105
|
-
onMouseLeave: () => {
|
|
106
|
-
setIsHovered(false);
|
|
107
|
-
setIsPressed(false);
|
|
108
|
-
},
|
|
109
|
-
onMouseDown: () => interactive && setIsPressed(true),
|
|
110
|
-
onMouseUp: () => setIsPressed(false),
|
|
111
|
-
style: {
|
|
112
|
-
position: 'absolute',
|
|
113
|
-
left: '50%',
|
|
114
|
-
top: '50%',
|
|
115
|
-
width: iconSize,
|
|
116
|
-
height: iconSize,
|
|
117
|
-
marginLeft: -iconSize / 2,
|
|
118
|
-
marginTop: -iconSize / 2,
|
|
119
|
-
display: 'flex',
|
|
120
|
-
flexDirection: 'column',
|
|
121
|
-
alignItems: 'center',
|
|
122
|
-
justifyContent: 'center',
|
|
123
|
-
cursor: interactive ? 'pointer' : 'default',
|
|
124
|
-
transform: `translate(${x}px, ${y}px) scale(${scale})`,
|
|
125
|
-
opacity,
|
|
126
|
-
transition: isHovered || isPressed ? 'transform 100ms ease' : 'none',
|
|
127
|
-
background: 'none',
|
|
128
|
-
border: 'none',
|
|
129
|
-
padding: 0
|
|
130
|
-
},
|
|
131
|
-
children: isEmpty ? /*#__PURE__*/_jsx("div", {
|
|
132
|
-
style: {
|
|
133
|
-
width: dialStyles.emptySlot.dotSize,
|
|
134
|
-
height: dialStyles.emptySlot.dotSize,
|
|
135
|
-
borderRadius: dialStyles.emptySlot.dotSize / 2,
|
|
136
|
-
backgroundColor: dialColors.emptyDotBackground,
|
|
137
|
-
border: `${dialStyles.emptySlot.borderWidth}px solid ${dialColors.emptyDotBorder}`
|
|
138
|
-
}
|
|
139
|
-
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
140
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
141
|
-
style: {
|
|
142
|
-
position: 'absolute',
|
|
143
|
-
width: `${dialStyles.icon.gradientBgSizePercent}%`,
|
|
144
|
-
height: `${dialStyles.icon.gradientBgSizePercent}%`,
|
|
145
|
-
borderRadius: dialStyles.icon.gradientBgBorderRadius,
|
|
146
|
-
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
147
|
-
opacity: dialStyles.icon.gradientBgOpacity
|
|
148
|
-
}
|
|
149
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
150
|
-
style: {
|
|
151
|
-
position: 'absolute',
|
|
152
|
-
width: `${dialStyles.icon.innerGlowSizePercent}%`,
|
|
153
|
-
height: `${dialStyles.icon.innerGlowSizePercent}%`,
|
|
154
|
-
borderRadius: dialStyles.icon.innerGlowBorderRadius,
|
|
155
|
-
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
156
|
-
opacity: dialStyles.icon.innerGlowOpacity
|
|
157
|
-
}
|
|
158
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
159
|
-
style: {
|
|
160
|
-
marginBottom: dialStyles.icon.wrapperMarginBottom,
|
|
161
|
-
display: 'flex',
|
|
162
|
-
alignItems: 'center',
|
|
163
|
-
justifyContent: 'center',
|
|
164
|
-
fontSize: 32,
|
|
165
|
-
// native DialIcon.tsx: icon rendered at size 32
|
|
166
|
-
filter: isHovered ? 'brightness(1.2)' : 'none'
|
|
167
|
-
},
|
|
168
|
-
children: icon.icon
|
|
169
|
-
}), /*#__PURE__*/_jsx("span", {
|
|
170
|
-
style: {
|
|
171
|
-
fontSize:
|
|
172
|
-
// native: adjustsFontSizeToFit minimumFontScale 0.7 — long
|
|
173
|
-
// names shrink instead of wrapping/overflowing the slot.
|
|
174
|
-
icon.name.length > 8 ? Math.max(dialStyles.icon.label.fontSize * 0.7, dialStyles.icon.label.fontSize * 8 / icon.name.length) : dialStyles.icon.label.fontSize,
|
|
175
|
-
fontWeight: dialStyles.icon.label.fontWeight,
|
|
176
|
-
fontFamily: dialStyles.icon.label.fontFamily,
|
|
177
|
-
color: dialColors.iconLabel,
|
|
178
|
-
letterSpacing: dialStyles.icon.label.letterSpacing,
|
|
179
|
-
marginTop: dialStyles.icon.label.marginTop,
|
|
180
|
-
textTransform: 'uppercase',
|
|
181
|
-
whiteSpace: 'nowrap'
|
|
182
|
-
},
|
|
183
|
-
children: icon.name
|
|
184
|
-
}), isUnavailable &&
|
|
185
|
-
/*#__PURE__*/
|
|
186
|
-
// native DialIcon.tsx unavailableCaption: label size - 1, muted
|
|
187
|
-
_jsx("span", {
|
|
188
|
-
style: {
|
|
189
|
-
fontSize: dialStyles.icon.label.fontSize - 1,
|
|
190
|
-
fontWeight: dialStyles.icon.label.fontWeight,
|
|
191
|
-
fontFamily: dialStyles.icon.label.fontFamily,
|
|
192
|
-
letterSpacing: dialStyles.icon.label.letterSpacing,
|
|
193
|
-
color: floatingToolsColors.muted,
|
|
194
|
-
textTransform: 'uppercase',
|
|
195
|
-
whiteSpace: 'nowrap'
|
|
196
|
-
},
|
|
197
|
-
children: "Unavailable"
|
|
198
|
-
})]
|
|
199
|
-
})
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// =============================
|
|
204
|
-
// Pagination (mirrors native DialPagination.tsx)
|
|
205
|
-
// =============================
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Chevron glyph matching the native pager's ChevronLeft/ChevronRight from
|
|
209
|
-
* @buoy-gg/shared-ui (lucide two-line chevron, size 18, strokeWidth 2).
|
|
210
|
-
* Inlined as SVG because shared-ui is React Native-only.
|
|
211
|
-
*/
|
|
212
|
-
function PagerChevron({
|
|
213
|
-
side,
|
|
214
|
-
color
|
|
215
|
-
}) {
|
|
216
|
-
// native lucide-icons.tsx ChevronLeft: (15,18)->(9,12)->(15,6); ChevronRight mirrored
|
|
217
|
-
const lines = side === 'prev' ? [[15, 18, 9, 12], [9, 12, 15, 6]] : [[9, 18, 15, 12], [15, 12, 9, 6]];
|
|
218
|
-
return /*#__PURE__*/_jsx("svg", {
|
|
219
|
-
width: 18,
|
|
220
|
-
height: 18,
|
|
221
|
-
viewBox: "0 0 24 24",
|
|
222
|
-
"aria-hidden": "true",
|
|
223
|
-
focusable: "false",
|
|
224
|
-
children: lines.map(([x1, y1, x2, y2], i) => /*#__PURE__*/_jsx("line", {
|
|
225
|
-
x1: x1,
|
|
226
|
-
y1: y1,
|
|
227
|
-
x2: x2,
|
|
228
|
-
y2: y2,
|
|
229
|
-
stroke: color,
|
|
230
|
-
strokeWidth: 2
|
|
231
|
-
}, i))
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
function PagerButton({
|
|
235
|
-
side,
|
|
236
|
-
disabled,
|
|
237
|
-
onClick
|
|
238
|
-
}) {
|
|
239
|
-
const [isPressed, setIsPressed] = useState(false);
|
|
240
|
-
|
|
241
|
-
// native DialPagination.tsx: accent = disabled ? emptyDotBorder : dialColors.accent
|
|
242
|
-
const accent = disabled ? dialColors.emptyDotBorder : dialColors.accent;
|
|
243
|
-
return /*#__PURE__*/_jsxs("button", {
|
|
244
|
-
type: "button",
|
|
245
|
-
"aria-label": `${side === 'prev' ? 'Previous' : 'Next'} dial page`,
|
|
246
|
-
disabled: disabled,
|
|
247
|
-
onClick: onClick,
|
|
248
|
-
onMouseDown: () => !disabled && setIsPressed(true),
|
|
249
|
-
onMouseUp: () => setIsPressed(false),
|
|
250
|
-
onMouseLeave: () => setIsPressed(false),
|
|
251
|
-
style: {
|
|
252
|
-
display: 'flex',
|
|
253
|
-
alignItems: 'center',
|
|
254
|
-
gap: 4,
|
|
255
|
-
// native DialPagination.tsx: gap 4
|
|
256
|
-
padding: '10px 16px',
|
|
257
|
-
// native: paddingVertical 10, paddingHorizontal 16
|
|
258
|
-
borderRadius: 10,
|
|
259
|
-
border: `1px solid ${dialColors.dialBorder}`,
|
|
260
|
-
backgroundColor: dialColors.dialBackground,
|
|
261
|
-
// native: buttonText night text, buttonTextDisabled iconLabelMuted
|
|
262
|
-
color: disabled ? dialColors.iconLabelMuted : dialColors.centerText,
|
|
263
|
-
fontSize: 12,
|
|
264
|
-
fontWeight: 900,
|
|
265
|
-
fontFamily: monoFont,
|
|
266
|
-
letterSpacing: 1.5,
|
|
267
|
-
textTransform: 'uppercase',
|
|
268
|
-
cursor: disabled ? 'default' : 'pointer',
|
|
269
|
-
opacity: disabled ? 0.4 : 1,
|
|
270
|
-
// native: buttonDisabled opacity 0.4
|
|
271
|
-
// native: shadowColor dialShadow, shadowOpacity 0.4, shadowRadius 8
|
|
272
|
-
boxShadow: disabled ? 'none' : `0 0 8px ${withAlpha(dialColors.dialShadow, 0.4)}`,
|
|
273
|
-
// native: press-in springs to scale 0.92
|
|
274
|
-
transform: isPressed ? 'scale(0.92)' : 'scale(1)',
|
|
275
|
-
transition: 'transform 100ms ease'
|
|
276
|
-
},
|
|
277
|
-
children: [side === 'prev' && /*#__PURE__*/_jsx(PagerChevron, {
|
|
278
|
-
side: "prev",
|
|
279
|
-
color: accent
|
|
280
|
-
}), side === 'prev' ? 'PREV' : 'NEXT', side === 'next' && /*#__PURE__*/_jsx(PagerChevron, {
|
|
281
|
-
side: "next",
|
|
282
|
-
color: accent
|
|
283
|
-
})]
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// =============================
|
|
288
|
-
// Main DialMenu Component
|
|
289
|
-
// =============================
|
|
290
|
-
|
|
291
|
-
export function DialMenu({
|
|
292
|
-
icons,
|
|
293
|
-
onClose,
|
|
294
|
-
centerLabel = 'MENU',
|
|
295
|
-
onCenterPress,
|
|
296
|
-
settingsOpen = false,
|
|
297
|
-
hintsDisabled = false
|
|
298
|
-
}) {
|
|
299
|
-
const {
|
|
300
|
-
id: backgroundId
|
|
301
|
-
} = useToolBackground();
|
|
302
|
-
|
|
303
|
-
// Animation values
|
|
304
|
-
const [backdropOpacity, setBackdropOpacity] = useState(0);
|
|
305
|
-
const [dialScale, setDialScale] = useState(0);
|
|
306
|
-
const [dialRotation, setDialRotation] = useState(0);
|
|
307
|
-
const [centerButtonScale, setCenterButtonScale] = useState(0);
|
|
308
|
-
const [iconProgress, setIconProgress] = useState(0);
|
|
309
|
-
const [centerPulseScale, setCenterPulseScale] = useState(1);
|
|
310
|
-
|
|
311
|
-
// UI state
|
|
312
|
-
const [isHoveringCenter, setIsHoveringCenter] = useState(false);
|
|
313
|
-
const [isExiting, setIsExiting] = useState(false);
|
|
314
|
-
const [entranceComplete, setEntranceComplete] = useState(false);
|
|
315
|
-
|
|
316
|
-
// Refs
|
|
317
|
-
const dialogRef = useRef(null);
|
|
318
|
-
const centerRef = useRef(null);
|
|
319
|
-
const animationRef = useRef(null);
|
|
320
|
-
const isClosingRef = useRef(false);
|
|
321
|
-
|
|
322
|
-
// Config
|
|
323
|
-
const {
|
|
324
|
-
entrance,
|
|
325
|
-
exit,
|
|
326
|
-
interaction
|
|
327
|
-
} = dialAnimationConfig;
|
|
328
|
-
const cssAnimations = getDialCSSAnimationStyles();
|
|
329
|
-
|
|
330
|
-
// Layout
|
|
331
|
-
const [viewport, setViewport] = useState(() => ({
|
|
332
|
-
width: window.innerWidth,
|
|
333
|
-
height: window.innerHeight
|
|
334
|
-
}));
|
|
335
|
-
useEffect(() => {
|
|
336
|
-
const resize = () => setViewport({
|
|
337
|
-
width: window.innerWidth,
|
|
338
|
-
height: window.innerHeight
|
|
339
|
-
});
|
|
340
|
-
window.addEventListener('resize', resize);
|
|
341
|
-
return () => window.removeEventListener('resize', resize);
|
|
342
|
-
}, []);
|
|
343
|
-
const layout = useMemo(() => getDialLayout({
|
|
344
|
-
screenWidth: viewport.width,
|
|
345
|
-
maxCircleSize: Math.max(120, Math.min(320, viewport.height - 96))
|
|
346
|
-
}), [viewport]);
|
|
347
|
-
const gridRotations = useMemo(() => getGridLineRotations(), []);
|
|
348
|
-
const positions = useMemo(() => getAllIconPositions(MAX_DIAL_SLOTS, layout.iconRadius), [layout.iconRadius]);
|
|
349
|
-
|
|
350
|
-
// Pagination: tools are split across pages of MAX_DIAL_SLOTS.
|
|
351
|
-
const [currentPage, setCurrentPage] = useState(0);
|
|
352
|
-
const pageCount = Math.max(1, Math.ceil(icons.length / MAX_DIAL_SLOTS));
|
|
353
|
-
const safePage = Math.min(currentPage, pageCount - 1);
|
|
354
|
-
const paddedIcons = useMemo(() => {
|
|
355
|
-
const start = safePage * MAX_DIAL_SLOTS;
|
|
356
|
-
const result = [...icons.slice(start, start + MAX_DIAL_SLOTS)];
|
|
357
|
-
while (result.length < MAX_DIAL_SLOTS) {
|
|
358
|
-
result.push({
|
|
359
|
-
id: `empty-${result.length}`,
|
|
360
|
-
name: '',
|
|
361
|
-
icon: null,
|
|
362
|
-
onPress: () => {}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return result;
|
|
366
|
-
}, [icons, safePage]);
|
|
367
|
-
|
|
368
|
-
// Inject keyframes
|
|
369
|
-
useEffect(() => {
|
|
370
|
-
injectKeyframes();
|
|
371
|
-
}, []);
|
|
372
|
-
|
|
373
|
-
// ENTRANCE Animation - runs once on mount
|
|
374
|
-
useEffect(() => {
|
|
375
|
-
// Capture config values at mount time
|
|
376
|
-
const backdropDuration = entrance.backdrop.duration;
|
|
377
|
-
const rotationDuration = entrance.dial.rotation.duration;
|
|
378
|
-
const centerButtonDelay = entrance.centerButton.delay;
|
|
379
|
-
const iconsDelay = entrance.icons.delay;
|
|
380
|
-
const iconsDuration = entrance.icons.duration;
|
|
381
|
-
const startTime = performance.now();
|
|
382
|
-
const animate = currentTime => {
|
|
383
|
-
const elapsed = currentTime - startTime;
|
|
384
|
-
|
|
385
|
-
// Backdrop (0-400ms)
|
|
386
|
-
setBackdropOpacity(Math.min(1, elapsed / backdropDuration));
|
|
387
|
-
|
|
388
|
-
// Dial scale with spring overshoot (~400ms)
|
|
389
|
-
const dialProgress = Math.min(1, elapsed / 400);
|
|
390
|
-
let springScale = easeOutCubic(dialProgress) * 1.1;
|
|
391
|
-
if (dialProgress > 0.7) {
|
|
392
|
-
springScale -= (dialProgress - 0.7) * 0.33;
|
|
393
|
-
}
|
|
394
|
-
if (dialProgress >= 1) springScale = 1;
|
|
395
|
-
setDialScale(Math.max(0, Math.min(1.05, springScale)));
|
|
396
|
-
|
|
397
|
-
// Dial rotation (0-800ms)
|
|
398
|
-
const rotProgress = Math.min(1, elapsed / rotationDuration);
|
|
399
|
-
setDialRotation(easeOutCubic(rotProgress) * 360);
|
|
400
|
-
|
|
401
|
-
// Center button (300ms delay)
|
|
402
|
-
if (elapsed >= centerButtonDelay) {
|
|
403
|
-
const centerProgress = Math.min(1, (elapsed - centerButtonDelay) / 300);
|
|
404
|
-
let centerSpring = easeOutCubic(centerProgress) * 1.15;
|
|
405
|
-
if (centerProgress > 0.6) {
|
|
406
|
-
centerSpring -= (centerProgress - 0.6) * 0.375;
|
|
407
|
-
}
|
|
408
|
-
if (centerProgress >= 1) centerSpring = 1;
|
|
409
|
-
setCenterButtonScale(Math.max(0, Math.min(1.05, centerSpring)));
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Icons (500ms delay, 600ms duration)
|
|
413
|
-
if (elapsed >= iconsDelay) {
|
|
414
|
-
const iconsProgress = Math.min(1, (elapsed - iconsDelay) / iconsDuration);
|
|
415
|
-
setIconProgress(easeOutCubic(iconsProgress));
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Continue or finish
|
|
419
|
-
const totalTime = iconsDelay + iconsDuration;
|
|
420
|
-
if (elapsed < totalTime) {
|
|
421
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
422
|
-
} else {
|
|
423
|
-
// Finalize
|
|
424
|
-
setBackdropOpacity(1);
|
|
425
|
-
setDialScale(1);
|
|
426
|
-
setDialRotation(360);
|
|
427
|
-
setCenterButtonScale(1);
|
|
428
|
-
setIconProgress(1);
|
|
429
|
-
setEntranceComplete(true);
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
433
|
-
return () => {
|
|
434
|
-
if (animationRef.current) cancelAnimationFrame(animationRef.current);
|
|
435
|
-
};
|
|
436
|
-
}, []);
|
|
437
|
-
|
|
438
|
-
// EXIT Animation
|
|
439
|
-
const handleClose = useCallback(() => {
|
|
440
|
-
if (isClosingRef.current) return;
|
|
441
|
-
isClosingRef.current = true;
|
|
442
|
-
setIsExiting(true);
|
|
443
|
-
if (animationRef.current) {
|
|
444
|
-
cancelAnimationFrame(animationRef.current);
|
|
445
|
-
}
|
|
446
|
-
const startTime = performance.now();
|
|
447
|
-
const iconsDuration = exit.icons.duration;
|
|
448
|
-
const centerDuration = exit.centerButton.duration;
|
|
449
|
-
const dialDuration = exit.dialScale.duration;
|
|
450
|
-
const step2Duration = Math.max(centerDuration, dialDuration);
|
|
451
|
-
const backdropDuration = exit.backdrop.duration;
|
|
452
|
-
const animate = currentTime => {
|
|
453
|
-
const elapsed = currentTime - startTime;
|
|
454
|
-
|
|
455
|
-
// Step 1: Icons collapse (0 - 300ms)
|
|
456
|
-
if (elapsed < iconsDuration) {
|
|
457
|
-
const progress = easeInCubic(elapsed / iconsDuration);
|
|
458
|
-
setIconProgress(1 - progress);
|
|
459
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Step 2: Dial + Center scale down in parallel — native runs the center
|
|
464
|
-
// button (200ms) and dial (250ms) with their own durations.
|
|
465
|
-
const step2Start = iconsDuration;
|
|
466
|
-
if (elapsed < step2Start + step2Duration) {
|
|
467
|
-
setIconProgress(0);
|
|
468
|
-
const centerProgress = Math.min(1, (elapsed - step2Start) / centerDuration);
|
|
469
|
-
const dialProgress = Math.min(1, (elapsed - step2Start) / dialDuration);
|
|
470
|
-
setCenterButtonScale(1 - easeInCubic(centerProgress));
|
|
471
|
-
setDialScale(1 - easeInCubic(dialProgress));
|
|
472
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Step 3: Backdrop fade (550 - 750ms)
|
|
477
|
-
const step3Start = step2Start + step2Duration;
|
|
478
|
-
if (elapsed < step3Start + backdropDuration) {
|
|
479
|
-
setDialScale(0);
|
|
480
|
-
setCenterButtonScale(0);
|
|
481
|
-
const progress = (elapsed - step3Start) / backdropDuration;
|
|
482
|
-
setBackdropOpacity(1 - progress);
|
|
483
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Done
|
|
488
|
-
setBackdropOpacity(0);
|
|
489
|
-
onClose();
|
|
490
|
-
};
|
|
491
|
-
animationRef.current = requestAnimationFrame(animate);
|
|
492
|
-
}, [exit, onClose]);
|
|
493
|
-
useEffect(() => {
|
|
494
|
-
const opener = document.activeElement;
|
|
495
|
-
const dialog = dialogRef.current;
|
|
496
|
-
return () => {
|
|
497
|
-
if (opener?.isConnected && (document.activeElement === document.body || dialog?.contains(document.activeElement))) {
|
|
498
|
-
opener.focus();
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
}, []);
|
|
502
|
-
useEffect(() => {
|
|
503
|
-
if (settingsOpen) return;
|
|
504
|
-
centerRef.current?.focus();
|
|
505
|
-
const handleKeyDown = event => {
|
|
506
|
-
const dialog = dialogRef.current;
|
|
507
|
-
if (!dialog) return;
|
|
508
|
-
if (event.key === 'Escape') {
|
|
509
|
-
event.preventDefault();
|
|
510
|
-
handleClose();
|
|
511
|
-
} else if (event.key === 'Tab') {
|
|
512
|
-
const controls = Array.from(dialog.querySelectorAll('button:not(:disabled):not([aria-disabled="true"]), [tabindex="0"]')).filter(element => element.getClientRects().length > 0);
|
|
513
|
-
const first = controls[0];
|
|
514
|
-
const last = controls[controls.length - 1];
|
|
515
|
-
if (!dialog.contains(document.activeElement) || (event.shiftKey ? document.activeElement === first : document.activeElement === last)) {
|
|
516
|
-
event.preventDefault();
|
|
517
|
-
(event.shiftKey ? last : first)?.focus();
|
|
518
|
-
}
|
|
519
|
-
} else if (['ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
|
|
520
|
-
const items = Array.from(dialog.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])'));
|
|
521
|
-
const current = items.indexOf(document.activeElement);
|
|
522
|
-
if (current < 0 || !items.length) return;
|
|
523
|
-
event.preventDefault();
|
|
524
|
-
const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : (current + (event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
|
525
|
-
items[next].focus();
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
529
|
-
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
530
|
-
}, [handleClose, settingsOpen]);
|
|
531
|
-
|
|
532
|
-
// Settings onboarding tooltip — native DialDevTools.tsx:174-196: shown
|
|
533
|
-
// 1200ms after the dial mounts (entrance animations settle first) if never
|
|
534
|
-
// dismissed; suppressed while settings is open; dismissed forever when
|
|
535
|
-
// settings opens.
|
|
536
|
-
const [showSettingsTooltip, setShowSettingsTooltip] = useState(false);
|
|
537
|
-
useEffect(() => {
|
|
538
|
-
if (hintsDisabled) return;
|
|
539
|
-
let cancelled = false;
|
|
540
|
-
const timer = setTimeout(async () => {
|
|
541
|
-
const seen = await persistentStorage.getItem(SETTINGS_TOOLTIP_KEY);
|
|
542
|
-
if (!cancelled && !seen) setShowSettingsTooltip(true);
|
|
543
|
-
}, 1200);
|
|
544
|
-
return () => {
|
|
545
|
-
cancelled = true;
|
|
546
|
-
clearTimeout(timer);
|
|
547
|
-
};
|
|
548
|
-
}, [hintsDisabled]);
|
|
549
|
-
const dismissSettingsTooltip = useCallback(() => {
|
|
550
|
-
setShowSettingsTooltip(false);
|
|
551
|
-
void persistentStorage.setItem(SETTINGS_TOOLTIP_KEY, 'true');
|
|
552
|
-
}, []);
|
|
553
|
-
useEffect(() => {
|
|
554
|
-
if (settingsOpen && showSettingsTooltip) {
|
|
555
|
-
dismissSettingsTooltip();
|
|
556
|
-
}
|
|
557
|
-
}, [settingsOpen, showSettingsTooltip, dismissSettingsTooltip]);
|
|
558
|
-
|
|
559
|
-
// Icon press handler
|
|
560
|
-
const handleIconPress = useCallback(icon => {
|
|
561
|
-
if (icon.id.startsWith('empty-') || isClosingRef.current) return;
|
|
562
|
-
|
|
563
|
-
// Pulse animation
|
|
564
|
-
const [pulseIn, pulseOut] = interaction.iconSelect.pulse;
|
|
565
|
-
const startTime = performance.now();
|
|
566
|
-
const pulseDuration = 150;
|
|
567
|
-
const animatePulse = currentTime => {
|
|
568
|
-
const elapsed = currentTime - startTime;
|
|
569
|
-
if (elapsed < pulseDuration) {
|
|
570
|
-
const progress = easeOutCubic(elapsed / pulseDuration);
|
|
571
|
-
setCenterPulseScale(1 - (1 - pulseIn.scale) * progress);
|
|
572
|
-
requestAnimationFrame(animatePulse);
|
|
573
|
-
} else if (elapsed < pulseDuration * 2) {
|
|
574
|
-
const progress = easeOutCubic((elapsed - pulseDuration) / pulseDuration);
|
|
575
|
-
setCenterPulseScale(pulseIn.scale + (pulseOut.scale - pulseIn.scale) * progress);
|
|
576
|
-
requestAnimationFrame(animatePulse);
|
|
577
|
-
} else {
|
|
578
|
-
setCenterPulseScale(1);
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
requestAnimationFrame(animatePulse);
|
|
582
|
-
|
|
583
|
-
// Trigger action after delay
|
|
584
|
-
setTimeout(() => {
|
|
585
|
-
icon.onPress();
|
|
586
|
-
handleClose();
|
|
587
|
-
}, interaction.iconSelect.actionDelay);
|
|
588
|
-
}, [interaction.iconSelect, handleClose]);
|
|
589
|
-
|
|
590
|
-
// Page navigation - icons are keyed by slot index, so this only swaps
|
|
591
|
-
// their content in place (no remount, no re-animation) for an instant page
|
|
592
|
-
// change.
|
|
593
|
-
const handlePageChange = useCallback(next => {
|
|
594
|
-
if (isClosingRef.current) return;
|
|
595
|
-
const clamped = Math.max(0, Math.min(next, pageCount - 1));
|
|
596
|
-
if (clamped !== safePage) setCurrentPage(clamped);
|
|
597
|
-
}, [pageCount, safePage]);
|
|
598
|
-
|
|
599
|
-
// Computed values
|
|
600
|
-
const buttonContainerSize = layout.buttonSize * dialStyles.centerButton.containerRatio;
|
|
601
|
-
const buttonBorderSize = layout.buttonSize * dialStyles.centerButton.borderRatio;
|
|
602
|
-
// Grid spokes stop this far from the centre: the hub's outer disc plus a breath.
|
|
603
|
-
const hubClearance = layout.buttonSize * dialStyles.centerButton.containerRatio / 2 + 6;
|
|
604
|
-
const isAnimating = entranceComplete && !isExiting;
|
|
605
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
606
|
-
ref: dialogRef,
|
|
607
|
-
role: "dialog",
|
|
608
|
-
"aria-label": "Dial Menu",
|
|
609
|
-
"aria-modal": "true",
|
|
610
|
-
style: {
|
|
611
|
-
position: 'fixed',
|
|
612
|
-
inset: 0,
|
|
613
|
-
zIndex: 10000
|
|
614
|
-
},
|
|
615
|
-
children: [/*#__PURE__*/_jsx("button", {
|
|
616
|
-
type: "button",
|
|
617
|
-
"aria-label": "Close dial menu",
|
|
618
|
-
tabIndex: 0,
|
|
619
|
-
onClick: handleClose,
|
|
620
|
-
style: {
|
|
621
|
-
position: 'absolute',
|
|
622
|
-
inset: 0,
|
|
623
|
-
border: 0,
|
|
624
|
-
padding: 0,
|
|
625
|
-
backgroundColor: dialColors.dialBackdrop,
|
|
626
|
-
opacity: backdropOpacity
|
|
627
|
-
}
|
|
628
|
-
}), /*#__PURE__*/_jsxs("div", {
|
|
629
|
-
style: {
|
|
630
|
-
position: 'absolute',
|
|
631
|
-
left: '50%',
|
|
632
|
-
marginLeft: -layout.circleSize / 2,
|
|
633
|
-
bottom: 80,
|
|
634
|
-
// native DialDevTools.tsx: bottom: 80
|
|
635
|
-
width: layout.circleSize,
|
|
636
|
-
height: layout.circleSize,
|
|
637
|
-
borderRadius: '50%',
|
|
638
|
-
backgroundColor: dialColors.dialBackground,
|
|
639
|
-
border: `${dialStyles.circle.borderWidth}px solid ${dialColors.dialBorder}`,
|
|
640
|
-
// native: shadowColor dialShadow at shadowOpacity 0.5, shadowRadius 20
|
|
641
|
-
boxShadow: `0 0 ${dialStyles.circle.shadowRadius}px ${withAlpha(dialColors.dialShadow, dialStyles.circle.shadowOpacity)}`,
|
|
642
|
-
transform: `scale(${dialScale}) rotate(${dialRotation}deg)`,
|
|
643
|
-
opacity: dialScale > 0.01 ? 1 : 0,
|
|
644
|
-
overflow: 'hidden'
|
|
645
|
-
},
|
|
646
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
647
|
-
style: {
|
|
648
|
-
position: 'absolute',
|
|
649
|
-
inset: 0,
|
|
650
|
-
borderRadius: '50%',
|
|
651
|
-
backgroundColor: dialColors.dialGradient1,
|
|
652
|
-
opacity: dialStyles.gradientLayers[0].opacity
|
|
653
|
-
}
|
|
654
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
655
|
-
style: {
|
|
656
|
-
position: 'absolute',
|
|
657
|
-
inset: 0,
|
|
658
|
-
top: '30%',
|
|
659
|
-
left: '30%',
|
|
660
|
-
borderRadius: '50%',
|
|
661
|
-
backgroundColor: dialColors.dialGradient2,
|
|
662
|
-
opacity: dialStyles.gradientLayers[1].opacity
|
|
663
|
-
}
|
|
664
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
665
|
-
style: {
|
|
666
|
-
position: 'absolute',
|
|
667
|
-
inset: 0,
|
|
668
|
-
top: '50%',
|
|
669
|
-
left: '50%',
|
|
670
|
-
borderRadius: '50%',
|
|
671
|
-
backgroundColor: dialColors.dialGradient3,
|
|
672
|
-
opacity: dialStyles.gradientLayers[2].opacity
|
|
673
|
-
}
|
|
674
|
-
}), gridRotations.map(deg => /*#__PURE__*/_jsx("div", {
|
|
675
|
-
style: {
|
|
676
|
-
position: 'absolute',
|
|
677
|
-
top: '50%',
|
|
678
|
-
left: 0,
|
|
679
|
-
right: 0,
|
|
680
|
-
height: dialStyles.grid.lineHeight,
|
|
681
|
-
transform: `rotate(${deg}deg)`,
|
|
682
|
-
transformOrigin: 'center'
|
|
683
|
-
},
|
|
684
|
-
children: ['left', 'right'].map(side => /*#__PURE__*/_jsx("div", {
|
|
685
|
-
style: {
|
|
686
|
-
position: 'absolute',
|
|
687
|
-
top: 0,
|
|
688
|
-
[side]: 0,
|
|
689
|
-
width: `calc(50% - ${hubClearance}px)`,
|
|
690
|
-
height: dialStyles.grid.lineHeight,
|
|
691
|
-
backgroundColor: dialColors.dialGridLine
|
|
692
|
-
}
|
|
693
|
-
}, side))
|
|
694
|
-
}, deg)), /*#__PURE__*/_jsx("div", {
|
|
695
|
-
"data-testid": "dial-background",
|
|
696
|
-
"data-background": backgroundId,
|
|
697
|
-
style: {
|
|
698
|
-
position: 'absolute',
|
|
699
|
-
inset: 0,
|
|
700
|
-
pointerEvents: 'none'
|
|
701
|
-
},
|
|
702
|
-
children: backgroundId === 'hyperspace' ? /*#__PURE__*/_jsx(DialHyperspace, {
|
|
703
|
-
size: layout.circleSize
|
|
704
|
-
}) : /*#__PURE__*/_jsx(ToolBackground, {
|
|
705
|
-
focus: DIAL_BACKGROUND_FOCUS
|
|
706
|
-
})
|
|
707
|
-
}), paddedIcons.map((icon, index) => /*#__PURE__*/_jsx(DialIconItem, {
|
|
708
|
-
icon: icon,
|
|
709
|
-
index: index,
|
|
710
|
-
totalIcons: paddedIcons.length,
|
|
711
|
-
position: positions[index],
|
|
712
|
-
progress: iconProgress,
|
|
713
|
-
onPress: () => handleIconPress(icon)
|
|
714
|
-
}, index)), /*#__PURE__*/_jsxs("div", {
|
|
715
|
-
style: {
|
|
716
|
-
position: 'absolute',
|
|
717
|
-
left: '50%',
|
|
718
|
-
top: '50%',
|
|
719
|
-
width: buttonContainerSize,
|
|
720
|
-
height: buttonContainerSize,
|
|
721
|
-
marginLeft: -buttonContainerSize / 2,
|
|
722
|
-
marginTop: -buttonContainerSize / 2,
|
|
723
|
-
display: 'flex',
|
|
724
|
-
alignItems: 'center',
|
|
725
|
-
justifyContent: 'center',
|
|
726
|
-
// Glass hub — see the native twin: a barely-there shade on the outer
|
|
727
|
-
// disc, a soft puck inside, no rings, so the tool background runs
|
|
728
|
-
// through the middle of the dial.
|
|
729
|
-
backgroundColor: dialColors.hubHalo,
|
|
730
|
-
borderRadius: layout.buttonSize,
|
|
731
|
-
overflow: 'hidden',
|
|
732
|
-
transform: `scale(${centerButtonScale * centerPulseScale})`,
|
|
733
|
-
opacity: centerButtonScale > 0.01 ? 1 : 0
|
|
734
|
-
},
|
|
735
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
736
|
-
style: {
|
|
737
|
-
position: 'absolute',
|
|
738
|
-
inset: 0,
|
|
739
|
-
backgroundColor: dialColors.dialGradient1,
|
|
740
|
-
opacity: 0.5,
|
|
741
|
-
borderRadius: layout.buttonSize
|
|
742
|
-
}
|
|
743
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
744
|
-
style: {
|
|
745
|
-
position: 'absolute',
|
|
746
|
-
inset: 0,
|
|
747
|
-
top: '20%',
|
|
748
|
-
left: '20%',
|
|
749
|
-
backgroundColor: dialColors.dialGradient2,
|
|
750
|
-
opacity: 0.3,
|
|
751
|
-
borderRadius: layout.buttonSize
|
|
752
|
-
}
|
|
753
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
754
|
-
style: {
|
|
755
|
-
position: 'absolute',
|
|
756
|
-
inset: 0,
|
|
757
|
-
top: '40%',
|
|
758
|
-
left: '40%',
|
|
759
|
-
backgroundColor: dialColors.dialGradient3,
|
|
760
|
-
opacity: 0.2,
|
|
761
|
-
borderRadius: layout.buttonSize
|
|
762
|
-
}
|
|
763
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
764
|
-
style: {
|
|
765
|
-
width: buttonBorderSize,
|
|
766
|
-
height: buttonBorderSize,
|
|
767
|
-
borderRadius: layout.buttonSize * dialStyles.centerButton.borderRadiusRatio,
|
|
768
|
-
backgroundColor: dialColors.hubFill,
|
|
769
|
-
display: 'flex',
|
|
770
|
-
alignItems: 'center',
|
|
771
|
-
justifyContent: 'center'
|
|
772
|
-
},
|
|
773
|
-
children: /*#__PURE__*/_jsx("button", {
|
|
774
|
-
ref: centerRef,
|
|
775
|
-
type: "button",
|
|
776
|
-
"aria-label": settingsOpen ? 'Close settings' : 'Open settings',
|
|
777
|
-
disabled: !onCenterPress,
|
|
778
|
-
onClick: onCenterPress,
|
|
779
|
-
onMouseEnter: () => setIsHoveringCenter(true),
|
|
780
|
-
onMouseLeave: () => setIsHoveringCenter(false),
|
|
781
|
-
style: {
|
|
782
|
-
border: 0,
|
|
783
|
-
padding: 0,
|
|
784
|
-
background: 'transparent',
|
|
785
|
-
width: layout.buttonSize,
|
|
786
|
-
height: layout.buttonSize,
|
|
787
|
-
borderRadius: layout.buttonSize / 2,
|
|
788
|
-
display: 'flex',
|
|
789
|
-
flexDirection: 'column',
|
|
790
|
-
alignItems: 'center',
|
|
791
|
-
justifyContent: 'center',
|
|
792
|
-
cursor: onCenterPress ? 'pointer' : 'default',
|
|
793
|
-
transform: isHoveringCenter ? `scale(${interaction.centerHover.scale})` : 'scale(1)',
|
|
794
|
-
transition: `transform ${interaction.centerHover.duration}ms ease`,
|
|
795
|
-
animation: isAnimating && !isHoveringCenter ? cssAnimations.pulse : 'none'
|
|
796
|
-
},
|
|
797
|
-
children: settingsOpen ?
|
|
798
|
-
/*#__PURE__*/
|
|
799
|
-
// native DialDevTools.tsx:716-724 — stacked CLOSE / SETTINGS
|
|
800
|
-
_jsxs("span", {
|
|
801
|
-
style: {
|
|
802
|
-
display: 'flex',
|
|
803
|
-
flexDirection: 'column',
|
|
804
|
-
alignItems: 'center',
|
|
805
|
-
fontSize: dialStyles.centerButton.text.fontSize - 2,
|
|
806
|
-
fontWeight: dialStyles.centerButton.text.fontWeight,
|
|
807
|
-
fontFamily: dialStyles.centerButton.text.fontFamily,
|
|
808
|
-
color: dialColors.centerText,
|
|
809
|
-
letterSpacing: dialStyles.centerButton.text.letterSpacing,
|
|
810
|
-
textAlign: 'center',
|
|
811
|
-
textShadow: `0 0 4px ${dialColors.centerTextGlow}`,
|
|
812
|
-
textTransform: 'uppercase',
|
|
813
|
-
lineHeight: 1.2
|
|
814
|
-
},
|
|
815
|
-
children: [/*#__PURE__*/_jsx("span", {
|
|
816
|
-
style: {
|
|
817
|
-
marginBottom: -2
|
|
818
|
-
},
|
|
819
|
-
children: "CLOSE"
|
|
820
|
-
}), /*#__PURE__*/_jsx("span", {
|
|
821
|
-
style: {
|
|
822
|
-
marginTop: -2
|
|
823
|
-
},
|
|
824
|
-
children: "SETTINGS"
|
|
825
|
-
})]
|
|
826
|
-
}) : /*#__PURE__*/_jsx("span", {
|
|
827
|
-
style: {
|
|
828
|
-
fontSize: dialStyles.centerButton.text.fontSize,
|
|
829
|
-
fontWeight: dialStyles.centerButton.text.fontWeight,
|
|
830
|
-
fontFamily: dialStyles.centerButton.text.fontFamily,
|
|
831
|
-
color: dialColors.centerText,
|
|
832
|
-
// native DialDevTools.tsx centerText
|
|
833
|
-
letterSpacing: dialStyles.centerButton.text.letterSpacing,
|
|
834
|
-
textAlign: 'center',
|
|
835
|
-
textShadow: `0 0 4px ${dialColors.centerTextGlow}`,
|
|
836
|
-
textTransform: 'uppercase'
|
|
837
|
-
},
|
|
838
|
-
children: centerLabel
|
|
839
|
-
})
|
|
840
|
-
})
|
|
841
|
-
})]
|
|
842
|
-
})]
|
|
843
|
-
}), pageCount > 1 && /*#__PURE__*/_jsxs("div", {
|
|
844
|
-
style: {
|
|
845
|
-
position: 'absolute',
|
|
846
|
-
left: '50%',
|
|
847
|
-
marginLeft: -layout.circleSize / 2,
|
|
848
|
-
top: 'calc(100% - 64px)',
|
|
849
|
-
// native: screenHeight - 80 + 16
|
|
850
|
-
width: layout.circleSize,
|
|
851
|
-
display: 'flex',
|
|
852
|
-
alignItems: 'center',
|
|
853
|
-
justifyContent: 'space-between',
|
|
854
|
-
opacity: Math.min(1, dialScale),
|
|
855
|
-
transform: `scale(${Math.min(1, dialScale)})`
|
|
856
|
-
},
|
|
857
|
-
children: [/*#__PURE__*/_jsx(PagerButton, {
|
|
858
|
-
side: "prev",
|
|
859
|
-
disabled: safePage <= 0,
|
|
860
|
-
onClick: () => handlePageChange(safePage - 1)
|
|
861
|
-
}), /*#__PURE__*/_jsxs("span", {
|
|
862
|
-
style: {
|
|
863
|
-
padding: '6px 12px',
|
|
864
|
-
// native indicator: paddingVertical 6, paddingHorizontal 12
|
|
865
|
-
fontSize: 13,
|
|
866
|
-
fontWeight: 900,
|
|
867
|
-
fontFamily: monoFont,
|
|
868
|
-
letterSpacing: 2,
|
|
869
|
-
color: dialColors.centerText,
|
|
870
|
-
textShadow: `0 0 6px ${dialColors.centerTextGlow}`
|
|
871
|
-
},
|
|
872
|
-
children: [String(safePage + 1).padStart(2, '0'), /*#__PURE__*/_jsxs("span", {
|
|
873
|
-
style: {
|
|
874
|
-
color: dialColors.iconLabelMuted,
|
|
875
|
-
textShadow: 'none'
|
|
876
|
-
},
|
|
877
|
-
children: [' / ', String(pageCount).padStart(2, '0')]
|
|
878
|
-
})]
|
|
879
|
-
}), /*#__PURE__*/_jsx(PagerButton, {
|
|
880
|
-
side: "next",
|
|
881
|
-
disabled: safePage >= pageCount - 1,
|
|
882
|
-
onClick: () => handlePageChange(safePage + 1)
|
|
883
|
-
})]
|
|
884
|
-
}), showSettingsTooltip && !settingsOpen && /*#__PURE__*/_jsx(OnboardingTooltip, {
|
|
885
|
-
title: "Welcome to Buoy!",
|
|
886
|
-
message: "Tap the center button to configure and add tools to your dial menu.",
|
|
887
|
-
onDismiss: dismissSettingsTooltip,
|
|
888
|
-
showArrow: false
|
|
889
|
-
})]
|
|
890
|
-
});
|
|
891
|
-
}
|
|
1
|
+
"use strict";import{useState as u,useEffect as P,useMemo as Z,useRef as J,useCallback as Q}from"react";import{getDialLayout as Pe,getAllIconPositions as $e,getGridLineRotations as Le,getIconStaggerInputRange as Fe,MAX_DIAL_SLOTS as U,DIAL_ICON_SIZE as We,dialAnimationConfig as ge,getDialCSSKeyframesString as Ge,getDialCSSAnimationStyles as He,dialColors as o,dialStyles as e,withAlpha as he,floatingToolsColors as _e}from"@buoy-gg/floating-tools-react";import{persistentStorage as ye,monoFont as be,ToolBackground as je,useToolBackground as Oe}from"@buoy-gg/shared-ui";import{DialHyperspace as qe}from"./DialHyperspace.js";import{OnboardingTooltip as Ue}from"./OnboardingTooltip.web.js";import{jsx as n,Fragment as Ke,jsxs as B}from"react/jsx-runtime";const Ne={x:.5,y:.5};const Se="@react_buoy_settings_tooltip_shown";let xe=false;function Ve(){if(xe||typeof document==="undefined")return;const r=document.createElement("style");r.textContent=Ge();document.head.appendChild(r);xe=true}const G=r=>1-Math.pow(1-r,3);const le=r=>r*r*r;function Xe({icon:r,index:p,totalIcons:R,position:y,progress:l,onPress:x}){const[M,$]=u(false);const[D,I]=u(false);const w=!r.icon&&r.id.startsWith("empty-");const K=!w&&r.unavailable===true;const C=!w&&!K;const{icons:N}=ge;const b=Fe(p,R);let m=0;if(l<=b[1]){m=0}else if(l<=b[2]){m=(l-b[1])/(b[2]-b[1])}else{m=1}m=Math.max(0,Math.min(1,m));const v=We;const ee=N.pressIn.scale;const H=1.1;const V=m*(D?ee:M?H:1);let A=0;if(m<=0){A=0}else if(m<=.3){A=m}else{A=.3+(m-.3)*(.7/.7)}const te=y.x*m;const ne=y.y*m;return n("button",{role:"menuitem","aria-label":w?"Empty slot":r.name,"aria-disabled":!C,tabIndex:C?0:-1,onClick:C?x:void 0,onMouseEnter:()=>C&&$(true),onMouseLeave:()=>{$(false);I(false)},onMouseDown:()=>C&&I(true),onMouseUp:()=>I(false),style:{position:"absolute",left:"50%",top:"50%",width:v,height:v,marginLeft:-v/2,marginTop:-v/2,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",cursor:C?"pointer":"default",transform:`translate(${te}px, ${ne}px) scale(${V})`,opacity:A,transition:M||D?"transform 100ms ease":"none",background:"none",border:"none",padding:0},children:w?n("div",{style:{width:e.emptySlot.dotSize,height:e.emptySlot.dotSize,borderRadius:e.emptySlot.dotSize/2,backgroundColor:o.emptyDotBackground,border:`${e.emptySlot.borderWidth}px solid ${o.emptyDotBorder}`}}):B(Ke,{children:[n("div",{style:{position:"absolute",width:`${e.icon.gradientBgSizePercent}%`,height:`${e.icon.gradientBgSizePercent}%`,borderRadius:e.icon.gradientBgBorderRadius,backgroundColor:"rgba(0, 0, 0, 0.2)",opacity:e.icon.gradientBgOpacity}}),n("div",{style:{position:"absolute",width:`${e.icon.innerGlowSizePercent}%`,height:`${e.icon.innerGlowSizePercent}%`,borderRadius:e.icon.innerGlowBorderRadius,backgroundColor:"rgba(255, 255, 255, 0.02)",opacity:e.icon.innerGlowOpacity}}),n("div",{style:{marginBottom:e.icon.wrapperMarginBottom,display:"flex",alignItems:"center",justifyContent:"center",fontSize:32,filter:M?"brightness(1.2)":"none"},children:r.icon}),n("span",{style:{fontSize:r.name.length>8?Math.max(e.icon.label.fontSize*.7,e.icon.label.fontSize*8/r.name.length):e.icon.label.fontSize,fontWeight:e.icon.label.fontWeight,fontFamily:e.icon.label.fontFamily,color:o.iconLabel,letterSpacing:e.icon.label.letterSpacing,marginTop:e.icon.label.marginTop,textTransform:"uppercase",whiteSpace:"nowrap"},children:r.name}),K&&n("span",{style:{fontSize:e.icon.label.fontSize-1,fontWeight:e.icon.label.fontWeight,fontFamily:e.icon.label.fontFamily,letterSpacing:e.icon.label.letterSpacing,color:_e.muted,textTransform:"uppercase",whiteSpace:"nowrap"},children:"Unavailable"})]})})}function we({side:r,color:p}){const R=r==="prev"?[[15,18,9,12],[9,12,15,6]]:[[9,18,15,12],[15,12,9,6]];return n("svg",{width:18,height:18,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:R.map(([y,l,x,M],$)=>n("line",{x1:y,y1:l,x2:x,y2:M,stroke:p,strokeWidth:2},$))})}function Ce({side:r,disabled:p,onClick:R}){const[y,l]=u(false);const x=p?o.emptyDotBorder:o.accent;return B("button",{type:"button","aria-label":`${r==="prev"?"Previous":"Next"} dial page`,disabled:p,onClick:R,onMouseDown:()=>!p&&l(true),onMouseUp:()=>l(false),onMouseLeave:()=>l(false),style:{display:"flex",alignItems:"center",gap:4,padding:"10px 16px",borderRadius:10,border:`1px solid ${o.dialBorder}`,backgroundColor:o.dialBackground,color:p?o.iconLabelMuted:o.centerText,fontSize:12,fontWeight:900,fontFamily:be,letterSpacing:1.5,textTransform:"uppercase",cursor:p?"default":"pointer",opacity:p?.4:1,boxShadow:p?"none":`0 0 8px ${he(o.dialShadow,.4)}`,transform:y?"scale(0.92)":"scale(1)",transition:"transform 100ms ease"},children:[r==="prev"&&n(we,{side:"prev",color:x}),r==="prev"?"PREV":"NEXT",r==="next"&&n(we,{side:"next",color:x})]})}export function DialMenu({icons:r,onClose:p,centerLabel:R="MENU",onCenterPress:y,settingsOpen:l=false,hintsDisabled:x=false}){const{id:M}=Oe();const[$,D]=u(0);const[I,w]=u(0);const[K,C]=u(0);const[N,b]=u(0);const[m,v]=u(0);const[ee,H]=u(1);const[V,A]=u(false);const[te,ne]=u(false);const[ve,ke]=u(false);const oe=J(null);const de=J(null);const S=J(null);const X=J(false);const{entrance:_,exit:j,interaction:O}=ge;const ze=He();const[ie,Be]=u(()=>({width:window.innerWidth,height:window.innerHeight}));P(()=>{const i=()=>Be({width:window.innerWidth,height:window.innerHeight});window.addEventListener("resize",i);return()=>window.removeEventListener("resize",i)},[]);const s=Z(()=>Pe({screenWidth:ie.width,maxCircleSize:Math.max(120,Math.min(320,ie.height-96))}),[ie]);const Me=Z(()=>Le(),[]);const Ie=Z(()=>$e(U,s.iconRadius),[s.iconRadius]);const[Te,Re]=u(0);const L=Math.max(1,Math.ceil(r.length/U));const k=Math.min(Te,L-1);const ue=Z(()=>{const i=k*U;const t=[...r.slice(i,i+U)];while(t.length<U){t.push({id:`empty-${t.length}`,name:"",icon:null,onPress:()=>{}})}return t},[r,k]);P(()=>{Ve()},[]);P(()=>{const i=_.backdrop.duration;const t=_.dial.rotation.duration;const f=_.centerButton.delay;const c=_.icons.delay;const d=_.icons.duration;const g=performance.now();const h=T=>{const a=T-g;D(Math.min(1,a/i));const z=Math.min(1,a/400);let F=G(z)*1.1;if(z>.7){F-=(z-.7)*.33}if(z>=1)F=1;w(Math.max(0,Math.min(1.05,F)));const E=Math.min(1,a/t);C(G(E)*360);if(a>=f){const W=Math.min(1,(a-f)/300);let ce=G(W)*1.15;if(W>.6){ce-=(W-.6)*.375}if(W>=1)ce=1;b(Math.max(0,Math.min(1.05,ce)))}if(a>=c){const W=Math.min(1,(a-c)/d);v(G(W))}const se=c+d;if(a<se){S.current=requestAnimationFrame(h)}else{D(1);w(1);C(360);b(1);v(1);ke(true)}};S.current=requestAnimationFrame(h);return()=>{if(S.current)cancelAnimationFrame(S.current)}},[]);const q=Q(()=>{if(X.current)return;X.current=true;ne(true);if(S.current){cancelAnimationFrame(S.current)}const i=performance.now();const t=j.icons.duration;const f=j.centerButton.duration;const c=j.dialScale.duration;const d=Math.max(f,c);const g=j.backdrop.duration;const h=T=>{const a=T-i;if(a<t){const E=le(a/t);v(1-E);S.current=requestAnimationFrame(h);return}const z=t;if(a<z+d){v(0);const E=Math.min(1,(a-z)/f);const se=Math.min(1,(a-z)/c);b(1-le(E));w(1-le(se));S.current=requestAnimationFrame(h);return}const F=z+d;if(a<F+g){w(0);b(0);const E=(a-F)/g;D(1-E);S.current=requestAnimationFrame(h);return}D(0);p()};S.current=requestAnimationFrame(h)},[j,p]);P(()=>{const i=document.activeElement;const t=oe.current;return()=>{if(i?.isConnected&&(document.activeElement===document.body||t?.contains(document.activeElement))){i.focus()}}},[]);P(()=>{if(l)return;de.current?.focus();const i=t=>{const f=oe.current;if(!f)return;if(t.key==="Escape"){t.preventDefault();q()}else if(t.key==="Tab"){const c=Array.from(f.querySelectorAll('button:not(:disabled):not([aria-disabled="true"]), [tabindex="0"]')).filter(h=>h.getClientRects().length>0);const d=c[0];const g=c[c.length-1];if(!f.contains(document.activeElement)||(t.shiftKey?document.activeElement===d:document.activeElement===g)){t.preventDefault();(t.shiftKey?g:d)?.focus()}}else if(["ArrowRight","ArrowDown","ArrowLeft","ArrowUp","Home","End"].includes(t.key)){const c=Array.from(f.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])'));const d=c.indexOf(document.activeElement);if(d<0||!c.length)return;t.preventDefault();const g=t.key==="Home"?0:t.key==="End"?c.length-1:(d+(t.key==="ArrowRight"||t.key==="ArrowDown"?1:-1)+c.length)%c.length;c[g].focus()}};document.addEventListener("keydown",i);return()=>document.removeEventListener("keydown",i)},[q,l]);const[re,pe]=u(false);P(()=>{if(x)return;let i=false;const t=setTimeout(async()=>{const f=await ye.getItem(Se);if(!i&&!f)pe(true)},1200);return()=>{i=true;clearTimeout(t)}},[x]);const ae=Q(()=>{pe(false);void ye.setItem(Se,"true")},[]);P(()=>{if(l&&re){ae()}},[l,re,ae]);const De=Q(i=>{if(i.id.startsWith("empty-")||X.current)return;const[t,f]=O.iconSelect.pulse;const c=performance.now();const d=150;const g=h=>{const T=h-c;if(T<d){const a=G(T/d);H(1-(1-t.scale)*a);requestAnimationFrame(g)}else if(T<d*2){const a=G((T-d)/d);H(t.scale+(f.scale-t.scale)*a);requestAnimationFrame(g)}else{H(1)}};requestAnimationFrame(g);setTimeout(()=>{i.onPress();q()},O.iconSelect.actionDelay)},[O.iconSelect,q]);const me=Q(i=>{if(X.current)return;const t=Math.max(0,Math.min(i,L-1));if(t!==k)Re(t)},[L,k]);const Y=s.buttonSize*e.centerButton.containerRatio;const fe=s.buttonSize*e.centerButton.borderRatio;const Ae=s.buttonSize*e.centerButton.containerRatio/2+6;const Ee=ve&&!te;return B("div",{ref:oe,role:"dialog","aria-label":"Dial Menu","aria-modal":"true",style:{position:"fixed",inset:0,zIndex:1e4},children:[n("button",{type:"button","aria-label":"Close dial menu",tabIndex:0,onClick:q,style:{position:"absolute",inset:0,border:0,padding:0,backgroundColor:o.dialBackdrop,opacity:$}}),B("div",{style:{position:"absolute",left:"50%",marginLeft:-s.circleSize/2,bottom:80,width:s.circleSize,height:s.circleSize,borderRadius:"50%",backgroundColor:o.dialBackground,border:`${e.circle.borderWidth}px solid ${o.dialBorder}`,boxShadow:`0 0 ${e.circle.shadowRadius}px ${he(o.dialShadow,e.circle.shadowOpacity)}`,transform:`scale(${I}) rotate(${K}deg)`,opacity:I>.01?1:0,overflow:"hidden"},children:[n("div",{style:{position:"absolute",inset:0,borderRadius:"50%",backgroundColor:o.dialGradient1,opacity:e.gradientLayers[0].opacity}}),n("div",{style:{position:"absolute",inset:0,top:"30%",left:"30%",borderRadius:"50%",backgroundColor:o.dialGradient2,opacity:e.gradientLayers[1].opacity}}),n("div",{style:{position:"absolute",inset:0,top:"50%",left:"50%",borderRadius:"50%",backgroundColor:o.dialGradient3,opacity:e.gradientLayers[2].opacity}}),Me.map(i=>n("div",{style:{position:"absolute",top:"50%",left:0,right:0,height:e.grid.lineHeight,transform:`rotate(${i}deg)`,transformOrigin:"center"},children:["left","right"].map(t=>n("div",{style:{position:"absolute",top:0,[t]:0,width:`calc(50% - ${Ae}px)`,height:e.grid.lineHeight,backgroundColor:o.dialGridLine}},t))},i)),n("div",{"data-testid":"dial-background","data-background":M,style:{position:"absolute",inset:0,pointerEvents:"none"},children:M==="hyperspace"?n(qe,{size:s.circleSize}):n(je,{focus:Ne})}),ue.map((i,t)=>n(Xe,{icon:i,index:t,totalIcons:ue.length,position:Ie[t],progress:m,onPress:()=>De(i)},t)),B("div",{style:{position:"absolute",left:"50%",top:"50%",width:Y,height:Y,marginLeft:-Y/2,marginTop:-Y/2,display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:o.hubHalo,borderRadius:s.buttonSize,overflow:"hidden",transform:`scale(${N*ee})`,opacity:N>.01?1:0},children:[n("div",{style:{position:"absolute",inset:0,backgroundColor:o.dialGradient1,opacity:.5,borderRadius:s.buttonSize}}),n("div",{style:{position:"absolute",inset:0,top:"20%",left:"20%",backgroundColor:o.dialGradient2,opacity:.3,borderRadius:s.buttonSize}}),n("div",{style:{position:"absolute",inset:0,top:"40%",left:"40%",backgroundColor:o.dialGradient3,opacity:.2,borderRadius:s.buttonSize}}),n("div",{style:{width:fe,height:fe,borderRadius:s.buttonSize*e.centerButton.borderRadiusRatio,backgroundColor:o.hubFill,display:"flex",alignItems:"center",justifyContent:"center"},children:n("button",{ref:de,type:"button","aria-label":l?"Close settings":"Open settings",disabled:!y,onClick:y,onMouseEnter:()=>A(true),onMouseLeave:()=>A(false),style:{border:0,padding:0,background:"transparent",width:s.buttonSize,height:s.buttonSize,borderRadius:s.buttonSize/2,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",cursor:y?"pointer":"default",transform:V?`scale(${O.centerHover.scale})`:"scale(1)",transition:`transform ${O.centerHover.duration}ms ease`,animation:Ee&&!V?ze.pulse:"none"},children:l?B("span",{style:{display:"flex",flexDirection:"column",alignItems:"center",fontSize:e.centerButton.text.fontSize-2,fontWeight:e.centerButton.text.fontWeight,fontFamily:e.centerButton.text.fontFamily,color:o.centerText,letterSpacing:e.centerButton.text.letterSpacing,textAlign:"center",textShadow:`0 0 4px ${o.centerTextGlow}`,textTransform:"uppercase",lineHeight:1.2},children:[n("span",{style:{marginBottom:-2},children:"CLOSE"}),n("span",{style:{marginTop:-2},children:"SETTINGS"})]}):n("span",{style:{fontSize:e.centerButton.text.fontSize,fontWeight:e.centerButton.text.fontWeight,fontFamily:e.centerButton.text.fontFamily,color:o.centerText,letterSpacing:e.centerButton.text.letterSpacing,textAlign:"center",textShadow:`0 0 4px ${o.centerTextGlow}`,textTransform:"uppercase"},children:R})})})]})]}),L>1&&B("div",{style:{position:"absolute",left:"50%",marginLeft:-s.circleSize/2,top:"calc(100% - 64px)",width:s.circleSize,display:"flex",alignItems:"center",justifyContent:"space-between",opacity:Math.min(1,I),transform:`scale(${Math.min(1,I)})`},children:[n(Ce,{side:"prev",disabled:k<=0,onClick:()=>me(k-1)}),B("span",{style:{padding:"6px 12px",fontSize:13,fontWeight:900,fontFamily:be,letterSpacing:2,color:o.centerText,textShadow:`0 0 6px ${o.centerTextGlow}`},children:[String(k+1).padStart(2,"0"),B("span",{style:{color:o.iconLabelMuted,textShadow:"none"},children:[" / ",String(L).padStart(2,"0")]})]}),n(Ce,{side:"next",disabled:k>=L-1,onClick:()=>me(k+1)})]}),re&&!l&&n(Ue,{title:"Welcome to Buoy!",message:"Tap the center button to configure and add tools to your dial menu.",onDismiss:ae,showArrow:false})]})}
|