@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.
Files changed (270) hide show
  1. package/README.md +14 -95
  2. package/bin/buoy.js +19 -340
  3. package/lib/commonjs/Buoy.js +1 -107
  4. package/lib/commonjs/browserRegister.js +1 -48
  5. package/lib/commonjs/browserRegister.server.js +1 -10
  6. package/lib/commonjs/earlyHookScript.js +1 -0
  7. package/lib/commonjs/earlyNetwork.js +1 -0
  8. package/lib/commonjs/floatingMenu/AppHost.js +1 -460
  9. package/lib/commonjs/floatingMenu/AppHost.web.js +1 -376
  10. package/lib/commonjs/floatingMenu/AppHostLogic.js +1 -50
  11. package/lib/commonjs/floatingMenu/AppIcon.js +1 -91
  12. package/lib/commonjs/floatingMenu/AppIcon.web.js +1 -53
  13. package/lib/commonjs/floatingMenu/BrowserToolRuntime.js +1 -150
  14. package/lib/commonjs/floatingMenu/DefaultConfigContext.js +1 -45
  15. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +1 -18
  16. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.shared.js +1 -1114
  17. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +1 -48
  18. package/lib/commonjs/floatingMenu/DevToolsVisibilityContext.js +1 -49
  19. package/lib/commonjs/floatingMenu/FloatingDevTools.js +1 -559
  20. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +1 -415
  21. package/lib/commonjs/floatingMenu/FloatingMenu.js +1 -371
  22. package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +1 -274
  23. package/lib/commonjs/floatingMenu/MinimizedToolsStack.js +1 -288
  24. package/lib/commonjs/floatingMenu/MinimizedToolsStack.web.js +1 -377
  25. package/lib/commonjs/floatingMenu/ToggleStateManager.js +1 -36
  26. package/lib/commonjs/floatingMenu/ToolHost.js +1 -281
  27. package/lib/commonjs/floatingMenu/ToolOpenerBridge.js +1 -41
  28. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +1 -471
  29. package/lib/commonjs/floatingMenu/autoExternalSync.js +1 -443
  30. package/lib/commonjs/floatingMenu/defaultConfig.js +1 -167
  31. package/lib/commonjs/floatingMenu/deviceIdentity.js +1 -173
  32. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +1 -904
  33. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +1 -895
  34. package/lib/commonjs/floatingMenu/dial/DialHyperspace.js +1 -177
  35. package/lib/commonjs/floatingMenu/dial/DialIcon.js +1 -304
  36. package/lib/commonjs/floatingMenu/dial/DialPagination.js +1 -165
  37. package/lib/commonjs/floatingMenu/dial/DialSnow.js +1 -92
  38. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.js +1 -246
  39. package/lib/commonjs/floatingMenu/dial/OnboardingTooltip.web.js +2 -147
  40. package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +1 -93
  41. package/lib/commonjs/floatingMenu/dial/onboardingConstants.js +1 -42
  42. package/lib/commonjs/floatingMenu/dial/onboardingConstants.web.js +1 -42
  43. package/lib/commonjs/floatingMenu/externalSyncGate.js +1 -49
  44. package/lib/commonjs/floatingMenu/floatingTools.js +1 -42
  45. package/lib/commonjs/floatingMenu/floatingTools.web.js +1 -30
  46. package/lib/commonjs/floatingMenu/optionalRequire.js +1 -37
  47. package/lib/commonjs/floatingMenu/settingsBus.js +1 -12
  48. package/lib/commonjs/floatingMenu/sync/appSyncAdapter.js +1 -115
  49. package/lib/commonjs/floatingMenu/types.js +1 -5
  50. package/lib/commonjs/floatingMenu/useDesktopSyncStatus.js +1 -28
  51. package/lib/commonjs/floatingMenu/useDesktopSyncStatus.web.js +1 -14
  52. package/lib/commonjs/index.js +1 -291
  53. package/lib/commonjs/index.web.js +1 -241
  54. package/lib/commonjs/reactDevtoolsTypes.js +1 -1
  55. package/lib/commonjs/releaseOptIn.js +1 -0
  56. package/lib/commonjs/telemetry/installPing.js +1 -131
  57. package/lib/commonjs/toolkit.js +1 -584
  58. package/lib/commonjs/toolkitComponents.js +1 -241
  59. package/lib/commonjs/utils/autoDiscoverPresets.web.js +1 -46
  60. package/lib/commonjs/utils/createWebTool.js +1 -20
  61. package/lib/commonjs/utils/createWebTools.js +1 -30
  62. package/lib/commonjs/version.js +1 -18
  63. package/lib/module/Buoy.js +1 -102
  64. package/lib/module/browserRegister.js +1 -44
  65. package/lib/module/browserRegister.server.js +1 -6
  66. package/lib/module/earlyHookScript.js +1 -0
  67. package/lib/module/earlyNetwork.js +1 -0
  68. package/lib/module/floatingMenu/AppHost.js +1 -452
  69. package/lib/module/floatingMenu/AppHost.web.js +1 -368
  70. package/lib/module/floatingMenu/AppHostLogic.js +1 -45
  71. package/lib/module/floatingMenu/AppIcon.js +1 -84
  72. package/lib/module/floatingMenu/AppIcon.web.js +1 -47
  73. package/lib/module/floatingMenu/BrowserToolRuntime.js +1 -146
  74. package/lib/module/floatingMenu/DefaultConfigContext.js +1 -39
  75. package/lib/module/floatingMenu/DevToolsSettingsModal.js +1 -3
  76. package/lib/module/floatingMenu/DevToolsSettingsModal.shared.js +1 -1110
  77. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +1 -44
  78. package/lib/module/floatingMenu/DevToolsVisibilityContext.js +1 -44
  79. package/lib/module/floatingMenu/FloatingDevTools.js +1 -556
  80. package/lib/module/floatingMenu/FloatingDevTools.web.js +1 -412
  81. package/lib/module/floatingMenu/FloatingMenu.js +1 -366
  82. package/lib/module/floatingMenu/MinimizedToolsContext.js +1 -266
  83. package/lib/module/floatingMenu/MinimizedToolsStack.js +1 -285
  84. package/lib/module/floatingMenu/MinimizedToolsStack.web.js +1 -374
  85. package/lib/module/floatingMenu/ToggleStateManager.js +1 -32
  86. package/lib/module/floatingMenu/ToolHost.js +1 -273
  87. package/lib/module/floatingMenu/ToolOpenerBridge.js +1 -37
  88. package/lib/module/floatingMenu/autoDiscoverPresets.js +1 -467
  89. package/lib/module/floatingMenu/autoExternalSync.js +1 -436
  90. package/lib/module/floatingMenu/defaultConfig.js +1 -158
  91. package/lib/module/floatingMenu/deviceIdentity.js +1 -163
  92. package/lib/module/floatingMenu/dial/DialDevTools.js +1 -900
  93. package/lib/module/floatingMenu/dial/DialDevTools.web.js +1 -891
  94. package/lib/module/floatingMenu/dial/DialHyperspace.js +1 -173
  95. package/lib/module/floatingMenu/dial/DialIcon.js +1 -301
  96. package/lib/module/floatingMenu/dial/DialPagination.js +1 -160
  97. package/lib/module/floatingMenu/dial/DialSnow.js +1 -90
  98. package/lib/module/floatingMenu/dial/OnboardingTooltip.js +1 -240
  99. package/lib/module/floatingMenu/dial/OnboardingTooltip.web.js +2 -142
  100. package/lib/module/floatingMenu/dial/dialUsageStore.js +1 -85
  101. package/lib/module/floatingMenu/dial/onboardingConstants.js +1 -3
  102. package/lib/module/floatingMenu/dial/onboardingConstants.web.js +1 -3
  103. package/lib/module/floatingMenu/externalSyncGate.js +1 -44
  104. package/lib/module/floatingMenu/floatingTools.js +1 -27
  105. package/lib/module/floatingMenu/floatingTools.web.js +1 -3
  106. package/lib/module/floatingMenu/optionalRequire.js +1 -33
  107. package/lib/module/floatingMenu/settingsBus.js +1 -3
  108. package/lib/module/floatingMenu/sync/appSyncAdapter.js +1 -112
  109. package/lib/module/floatingMenu/types.js +1 -3
  110. package/lib/module/floatingMenu/useDesktopSyncStatus.js +1 -25
  111. package/lib/module/floatingMenu/useDesktopSyncStatus.web.js +1 -10
  112. package/lib/module/index.js +1 -41
  113. package/lib/module/index.web.js +1 -36
  114. package/lib/module/reactDevtoolsTypes.js +1 -1
  115. package/lib/module/releaseOptIn.js +1 -0
  116. package/lib/module/telemetry/installPing.js +1 -127
  117. package/lib/module/toolkit.js +1 -62
  118. package/lib/module/toolkitComponents.js +1 -230
  119. package/lib/module/utils/autoDiscoverPresets.web.js +1 -38
  120. package/lib/module/utils/createWebTool.js +1 -17
  121. package/lib/module/utils/createWebTools.js +1 -25
  122. package/lib/module/version.js +1 -14
  123. package/lib/typescript/commonjs/browserRegister.d.ts +9 -1
  124. package/lib/typescript/commonjs/browserRegister.server.d.ts +1 -0
  125. package/lib/typescript/commonjs/earlyHookScript.d.ts +12 -0
  126. package/lib/typescript/commonjs/earlyNetwork.d.ts +52 -0
  127. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
  128. package/lib/typescript/commonjs/index.web.d.ts +1 -0
  129. package/lib/typescript/commonjs/releaseOptIn.d.ts +4 -0
  130. package/lib/typescript/commonjs/version.d.ts +1 -1
  131. package/lib/typescript/module/browserRegister.d.ts +9 -1
  132. package/lib/typescript/module/browserRegister.server.d.ts +1 -0
  133. package/lib/typescript/module/earlyHookScript.d.ts +12 -0
  134. package/lib/typescript/module/earlyNetwork.d.ts +52 -0
  135. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
  136. package/lib/typescript/module/index.web.d.ts +1 -0
  137. package/lib/typescript/module/releaseOptIn.d.ts +4 -0
  138. package/lib/typescript/module/version.d.ts +1 -1
  139. package/lib/web/chunks/chunk-5SGL6QL4.mjs +1 -0
  140. package/lib/web/chunks/chunk-FROELTJH.mjs +1 -0
  141. package/lib/web/chunks/chunk-QZDVCMCT.mjs +1 -2
  142. package/lib/web/chunks/chunk-R7RGRTY5.mjs +1 -2
  143. package/lib/web/index.mjs +2 -3
  144. package/lib/web/register-server.mjs +1 -2
  145. package/lib/web/register.mjs +1 -2
  146. package/lib/web/toolkit.mjs +1 -2
  147. package/package.json +6 -9
  148. package/lib/typescript/commonjs/Buoy.d.ts.map +0 -1
  149. package/lib/typescript/commonjs/browserRegister.d.ts.map +0 -1
  150. package/lib/typescript/commonjs/browserRegister.server.d.ts.map +0 -1
  151. package/lib/typescript/commonjs/floatingMenu/AppHost.d.ts.map +0 -1
  152. package/lib/typescript/commonjs/floatingMenu/AppHost.web.d.ts.map +0 -1
  153. package/lib/typescript/commonjs/floatingMenu/AppHostLogic.d.ts.map +0 -1
  154. package/lib/typescript/commonjs/floatingMenu/AppIcon.d.ts.map +0 -1
  155. package/lib/typescript/commonjs/floatingMenu/AppIcon.web.d.ts.map +0 -1
  156. package/lib/typescript/commonjs/floatingMenu/BrowserToolRuntime.d.ts.map +0 -1
  157. package/lib/typescript/commonjs/floatingMenu/DefaultConfigContext.d.ts.map +0 -1
  158. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +0 -1
  159. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +0 -1
  160. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +0 -1
  161. package/lib/typescript/commonjs/floatingMenu/DevToolsVisibilityContext.d.ts.map +0 -1
  162. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +0 -1
  163. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +0 -1
  164. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +0 -1
  165. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsContext.d.ts.map +0 -1
  166. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.d.ts.map +0 -1
  167. package/lib/typescript/commonjs/floatingMenu/MinimizedToolsStack.web.d.ts.map +0 -1
  168. package/lib/typescript/commonjs/floatingMenu/ToggleStateManager.d.ts.map +0 -1
  169. package/lib/typescript/commonjs/floatingMenu/ToolHost.d.ts.map +0 -1
  170. package/lib/typescript/commonjs/floatingMenu/ToolOpenerBridge.d.ts.map +0 -1
  171. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +0 -1
  172. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +0 -1
  173. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +0 -1
  174. package/lib/typescript/commonjs/floatingMenu/deviceIdentity.d.ts.map +0 -1
  175. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +0 -1
  176. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +0 -1
  177. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts.map +0 -1
  178. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +0 -1
  179. package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts.map +0 -1
  180. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts.map +0 -1
  181. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.d.ts.map +0 -1
  182. package/lib/typescript/commonjs/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +0 -1
  183. package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +0 -1
  184. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.d.ts.map +0 -1
  185. package/lib/typescript/commonjs/floatingMenu/dial/onboardingConstants.web.d.ts.map +0 -1
  186. package/lib/typescript/commonjs/floatingMenu/externalSyncGate.d.ts.map +0 -1
  187. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +0 -1
  188. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +0 -1
  189. package/lib/typescript/commonjs/floatingMenu/optionalRequire.d.ts.map +0 -1
  190. package/lib/typescript/commonjs/floatingMenu/settingsBus.d.ts.map +0 -1
  191. package/lib/typescript/commonjs/floatingMenu/sync/appSyncAdapter.d.ts.map +0 -1
  192. package/lib/typescript/commonjs/floatingMenu/types.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.d.ts.map +0 -1
  194. package/lib/typescript/commonjs/floatingMenu/useDesktopSyncStatus.web.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/index.d.ts.map +0 -1
  196. package/lib/typescript/commonjs/index.web.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/reactDevtoolsTypes.d.ts.map +0 -1
  198. package/lib/typescript/commonjs/telemetry/installPing.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/toolkit.d.ts.map +0 -1
  200. package/lib/typescript/commonjs/toolkitComponents.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +0 -1
  202. package/lib/typescript/commonjs/utils/createWebTool.d.ts.map +0 -1
  203. package/lib/typescript/commonjs/utils/createWebTools.d.ts.map +0 -1
  204. package/lib/typescript/commonjs/version.d.ts.map +0 -1
  205. package/lib/typescript/module/Buoy.d.ts.map +0 -1
  206. package/lib/typescript/module/browserRegister.d.ts.map +0 -1
  207. package/lib/typescript/module/browserRegister.server.d.ts.map +0 -1
  208. package/lib/typescript/module/floatingMenu/AppHost.d.ts.map +0 -1
  209. package/lib/typescript/module/floatingMenu/AppHost.web.d.ts.map +0 -1
  210. package/lib/typescript/module/floatingMenu/AppHostLogic.d.ts.map +0 -1
  211. package/lib/typescript/module/floatingMenu/AppIcon.d.ts.map +0 -1
  212. package/lib/typescript/module/floatingMenu/AppIcon.web.d.ts.map +0 -1
  213. package/lib/typescript/module/floatingMenu/BrowserToolRuntime.d.ts.map +0 -1
  214. package/lib/typescript/module/floatingMenu/DefaultConfigContext.d.ts.map +0 -1
  215. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +0 -1
  216. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.shared.d.ts.map +0 -1
  217. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +0 -1
  218. package/lib/typescript/module/floatingMenu/DevToolsVisibilityContext.d.ts.map +0 -1
  219. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +0 -1
  220. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +0 -1
  221. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +0 -1
  222. package/lib/typescript/module/floatingMenu/MinimizedToolsContext.d.ts.map +0 -1
  223. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.d.ts.map +0 -1
  224. package/lib/typescript/module/floatingMenu/MinimizedToolsStack.web.d.ts.map +0 -1
  225. package/lib/typescript/module/floatingMenu/ToggleStateManager.d.ts.map +0 -1
  226. package/lib/typescript/module/floatingMenu/ToolHost.d.ts.map +0 -1
  227. package/lib/typescript/module/floatingMenu/ToolOpenerBridge.d.ts.map +0 -1
  228. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +0 -1
  229. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +0 -1
  230. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +0 -1
  231. package/lib/typescript/module/floatingMenu/deviceIdentity.d.ts.map +0 -1
  232. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +0 -1
  233. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +0 -1
  234. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts.map +0 -1
  235. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +0 -1
  236. package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts.map +0 -1
  237. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts.map +0 -1
  238. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.d.ts.map +0 -1
  239. package/lib/typescript/module/floatingMenu/dial/OnboardingTooltip.web.d.ts.map +0 -1
  240. package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +0 -1
  241. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.d.ts.map +0 -1
  242. package/lib/typescript/module/floatingMenu/dial/onboardingConstants.web.d.ts.map +0 -1
  243. package/lib/typescript/module/floatingMenu/externalSyncGate.d.ts.map +0 -1
  244. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +0 -1
  245. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +0 -1
  246. package/lib/typescript/module/floatingMenu/optionalRequire.d.ts.map +0 -1
  247. package/lib/typescript/module/floatingMenu/settingsBus.d.ts.map +0 -1
  248. package/lib/typescript/module/floatingMenu/sync/appSyncAdapter.d.ts.map +0 -1
  249. package/lib/typescript/module/floatingMenu/types.d.ts.map +0 -1
  250. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.d.ts.map +0 -1
  251. package/lib/typescript/module/floatingMenu/useDesktopSyncStatus.web.d.ts.map +0 -1
  252. package/lib/typescript/module/index.d.ts.map +0 -1
  253. package/lib/typescript/module/index.web.d.ts.map +0 -1
  254. package/lib/typescript/module/reactDevtoolsTypes.d.ts.map +0 -1
  255. package/lib/typescript/module/telemetry/installPing.d.ts.map +0 -1
  256. package/lib/typescript/module/toolkit.d.ts.map +0 -1
  257. package/lib/typescript/module/toolkitComponents.d.ts.map +0 -1
  258. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +0 -1
  259. package/lib/typescript/module/utils/createWebTool.d.ts.map +0 -1
  260. package/lib/typescript/module/utils/createWebTools.d.ts.map +0 -1
  261. package/lib/typescript/module/version.d.ts.map +0 -1
  262. package/lib/web/chunks/chunk-5NZOQR6Q.mjs +0 -2
  263. package/lib/web/chunks/chunk-CGDSLLZF.mjs +0 -2
  264. package/lib/web/chunks/chunk-DCJDUSNR.mjs +0 -2
  265. package/lib/web/chunks/chunk-IDSHFS6G.mjs +0 -2
  266. package/lib/web/chunks/chunk-JOAMJGCS.mjs +0 -2
  267. package/lib/web/chunks/chunk-KYKZ2N52.mjs +0 -2
  268. package/lib/web/chunks/chunk-NGM7JGGS.mjs +0 -2
  269. package/lib/web/chunks/chunk-Z3XWDDIO.mjs +0 -2
  270. 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})]})}