@buoy-gg/core 7.0.40 → 7.0.42

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