@buoy-gg/core 1.7.7 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/lib/commonjs/floatingMenu/AppHost.js +3 -3
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +25 -13
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +746 -0
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.js +16 -4
  5. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +153 -0
  6. package/lib/commonjs/floatingMenu/FloatingMenu.js +8 -8
  7. package/lib/commonjs/floatingMenu/MinimizedToolsContext.js +2 -2
  8. package/lib/commonjs/floatingMenu/autoDiscoverPresets.js +30 -0
  9. package/lib/commonjs/floatingMenu/defaultConfig.js +14 -7
  10. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +12 -6
  11. package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +593 -0
  12. package/lib/commonjs/floatingMenu/floatingTools.js +218 -38
  13. package/lib/commonjs/floatingMenu/floatingTools.web.js +357 -0
  14. package/lib/commonjs/index.js +2 -2
  15. package/lib/commonjs/index.web.js +131 -0
  16. package/lib/commonjs/utils/autoDiscoverPresets.web.js +181 -0
  17. package/lib/module/floatingMenu/AppHost.js +4 -4
  18. package/lib/module/floatingMenu/DevToolsSettingsModal.js +28 -16
  19. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +756 -0
  20. package/lib/module/floatingMenu/FloatingDevTools.js +17 -5
  21. package/lib/module/floatingMenu/FloatingDevTools.web.js +149 -0
  22. package/lib/module/floatingMenu/FloatingMenu.js +9 -9
  23. package/lib/module/floatingMenu/MinimizedToolsContext.js +3 -3
  24. package/lib/module/floatingMenu/autoDiscoverPresets.js +30 -0
  25. package/lib/module/floatingMenu/defaultConfig.js +14 -7
  26. package/lib/module/floatingMenu/dial/DialDevTools.js +13 -7
  27. package/lib/module/floatingMenu/dial/DialDevTools.web.js +590 -0
  28. package/lib/module/floatingMenu/floatingTools.js +219 -39
  29. package/lib/module/floatingMenu/floatingTools.web.js +357 -0
  30. package/lib/module/index.js +2 -2
  31. package/lib/module/index.web.js +24 -0
  32. package/lib/module/utils/autoDiscoverPresets.web.js +174 -0
  33. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +23 -0
  35. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts +1 -1
  37. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts +48 -0
  39. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +8 -7
  43. package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts +26 -0
  46. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts +1 -1
  48. package/lib/typescript/commonjs/floatingMenu/floatingTools.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts +27 -0
  50. package/lib/typescript/commonjs/floatingMenu/floatingTools.web.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/index.web.d.ts +20 -0
  52. package/lib/typescript/commonjs/index.web.d.ts.map +1 -0
  53. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts +58 -0
  54. package/lib/typescript/commonjs/utils/autoDiscoverPresets.web.d.ts.map +1 -0
  55. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  56. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +23 -0
  57. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -0
  58. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts +1 -1
  59. package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
  60. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts +48 -0
  61. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -0
  62. package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
  63. package/lib/typescript/module/floatingMenu/autoDiscoverPresets.d.ts.map +1 -1
  64. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +8 -7
  65. package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
  66. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  67. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts +26 -0
  68. package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -0
  69. package/lib/typescript/module/floatingMenu/floatingTools.d.ts +1 -1
  70. package/lib/typescript/module/floatingMenu/floatingTools.d.ts.map +1 -1
  71. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts +27 -0
  72. package/lib/typescript/module/floatingMenu/floatingTools.web.d.ts.map +1 -0
  73. package/lib/typescript/module/index.web.d.ts +20 -0
  74. package/lib/typescript/module/index.web.d.ts.map +1 -0
  75. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts +58 -0
  76. package/lib/typescript/module/utils/autoDiscoverPresets.web.d.ts.map +1 -0
  77. package/package.json +5 -4
@@ -0,0 +1,593 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DialMenu = DialMenu;
7
+ var _react = require("react");
8
+ var _floatingToolsReact = require("@buoy-gg/floating-tools-react");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ /**
11
+ * DialDevTools - Web renderer for the radial dial menu.
12
+ *
13
+ * Uses shared layout calculations, styling, and animation config from @buoy-gg/floating-tools-core.
14
+ * Platform-specific: CSS animations, mouse events, web JSX.
15
+ *
16
+ * Animation behavior matches mobile exactly - see docs/dial-animation-spec.md
17
+ *
18
+ * @platform React DOM Web (Vite, CRA, Next.js, Electron)
19
+ */
20
+
21
+ // Inject CSS keyframes once on module load
22
+ let keyframesInjected = false;
23
+ function injectKeyframes() {
24
+ if (keyframesInjected || typeof document === 'undefined') return;
25
+ const style = document.createElement('style');
26
+ style.textContent = (0, _floatingToolsReact.getDialCSSKeyframesString)();
27
+ document.head.appendChild(style);
28
+ keyframesInjected = true;
29
+ }
30
+
31
+ // Easing functions
32
+ const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
33
+ const easeInCubic = t => t * t * t;
34
+
35
+ // =============================
36
+ // Types
37
+ // =============================
38
+
39
+ // =============================
40
+ // Dial Icon Component
41
+ // =============================
42
+
43
+ function DialIconItem({
44
+ icon,
45
+ index,
46
+ totalIcons,
47
+ position,
48
+ progress,
49
+ onPress
50
+ }) {
51
+ const [isHovered, setIsHovered] = (0, _react.useState)(false);
52
+ const [isPressed, setIsPressed] = (0, _react.useState)(false);
53
+ const isEmpty = !icon.icon && icon.id.startsWith('empty-');
54
+ const {
55
+ icons: iconConfig
56
+ } = _floatingToolsReact.dialAnimationConfig;
57
+
58
+ // Get stagger input range for this icon
59
+ const staggerInputRange = (0, _floatingToolsReact.getIconStaggerInputRange)(index, totalIcons);
60
+
61
+ // Calculate this icon's individual progress based on the global progress
62
+ let iconProgress = 0;
63
+ if (progress <= staggerInputRange[1]) {
64
+ iconProgress = 0;
65
+ } else if (progress <= staggerInputRange[2]) {
66
+ iconProgress = (progress - staggerInputRange[1]) / (staggerInputRange[2] - staggerInputRange[1]);
67
+ } else {
68
+ iconProgress = 1;
69
+ }
70
+ iconProgress = Math.max(0, Math.min(1, iconProgress));
71
+ const iconSize = 60;
72
+ const pressedScale = iconConfig.pressIn.scale;
73
+ const hoveredScale = 1.1;
74
+ const scale = iconProgress * (isPressed ? pressedScale : isHovered ? hoveredScale : 1);
75
+
76
+ // Opacity interpolation
77
+ let opacity = 0;
78
+ if (iconProgress <= 0) {
79
+ opacity = 0;
80
+ } else if (iconProgress <= 0.3) {
81
+ opacity = iconProgress;
82
+ } else {
83
+ opacity = 0.3 + (iconProgress - 0.3) * (0.7 / 0.7);
84
+ }
85
+ const x = position.x * iconProgress;
86
+ const y = position.y * iconProgress;
87
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
88
+ role: "menuitem",
89
+ "aria-label": isEmpty ? 'Empty slot' : icon.name,
90
+ "aria-disabled": isEmpty,
91
+ tabIndex: isEmpty ? -1 : 0,
92
+ onClick: isEmpty ? undefined : onPress,
93
+ onMouseEnter: () => !isEmpty && setIsHovered(true),
94
+ onMouseLeave: () => {
95
+ setIsHovered(false);
96
+ setIsPressed(false);
97
+ },
98
+ onMouseDown: () => !isEmpty && setIsPressed(true),
99
+ onMouseUp: () => setIsPressed(false),
100
+ style: {
101
+ position: 'absolute',
102
+ left: '50%',
103
+ top: '50%',
104
+ width: iconSize,
105
+ height: iconSize,
106
+ marginLeft: -iconSize / 2,
107
+ marginTop: -iconSize / 2,
108
+ display: 'flex',
109
+ flexDirection: 'column',
110
+ alignItems: 'center',
111
+ justifyContent: 'center',
112
+ cursor: isEmpty ? 'default' : 'pointer',
113
+ transform: `translate(${x}px, ${y}px) scale(${scale})`,
114
+ opacity,
115
+ transition: isHovered || isPressed ? 'transform 100ms ease' : 'none',
116
+ background: 'none',
117
+ border: 'none',
118
+ padding: 0
119
+ },
120
+ children: isEmpty ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
121
+ style: {
122
+ width: _floatingToolsReact.dialStyles.emptySlot.dotSize,
123
+ height: _floatingToolsReact.dialStyles.emptySlot.dotSize,
124
+ borderRadius: _floatingToolsReact.dialStyles.emptySlot.dotSize / 2,
125
+ backgroundColor: _floatingToolsReact.dialColors.emptyDotBackground,
126
+ border: `${_floatingToolsReact.dialStyles.emptySlot.borderWidth}px solid ${_floatingToolsReact.dialColors.emptyDotBorder}`
127
+ }
128
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
129
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
130
+ style: {
131
+ position: 'absolute',
132
+ width: `${_floatingToolsReact.dialStyles.icon.gradientBgSizePercent}%`,
133
+ height: `${_floatingToolsReact.dialStyles.icon.gradientBgSizePercent}%`,
134
+ borderRadius: _floatingToolsReact.dialStyles.icon.gradientBgBorderRadius,
135
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
136
+ opacity: _floatingToolsReact.dialStyles.icon.gradientBgOpacity
137
+ }
138
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
139
+ style: {
140
+ position: 'absolute',
141
+ width: `${_floatingToolsReact.dialStyles.icon.innerGlowSizePercent}%`,
142
+ height: `${_floatingToolsReact.dialStyles.icon.innerGlowSizePercent}%`,
143
+ borderRadius: _floatingToolsReact.dialStyles.icon.innerGlowBorderRadius,
144
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
145
+ opacity: _floatingToolsReact.dialStyles.icon.innerGlowOpacity
146
+ }
147
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
148
+ style: {
149
+ marginBottom: _floatingToolsReact.dialStyles.icon.wrapperMarginBottom,
150
+ display: 'flex',
151
+ alignItems: 'center',
152
+ justifyContent: 'center',
153
+ fontSize: 32,
154
+ filter: isHovered ? 'brightness(1.2)' : 'none'
155
+ },
156
+ children: icon.icon
157
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
158
+ style: {
159
+ fontSize: _floatingToolsReact.dialStyles.icon.label.fontSize,
160
+ fontWeight: _floatingToolsReact.dialStyles.icon.label.fontWeight,
161
+ fontFamily: _floatingToolsReact.dialStyles.icon.label.fontFamily,
162
+ color: _floatingToolsReact.dialColors.iconLabel,
163
+ letterSpacing: _floatingToolsReact.dialStyles.icon.label.letterSpacing,
164
+ marginTop: _floatingToolsReact.dialStyles.icon.label.marginTop,
165
+ textTransform: 'uppercase'
166
+ },
167
+ children: icon.name
168
+ })]
169
+ })
170
+ });
171
+ }
172
+
173
+ // =============================
174
+ // Main DialMenu Component
175
+ // =============================
176
+
177
+ function DialMenu({
178
+ icons,
179
+ onClose,
180
+ centerLabel = 'MENU',
181
+ onCenterPress
182
+ }) {
183
+ // Animation values
184
+ const [backdropOpacity, setBackdropOpacity] = (0, _react.useState)(0);
185
+ const [dialScale, setDialScale] = (0, _react.useState)(0);
186
+ const [dialRotation, setDialRotation] = (0, _react.useState)(0);
187
+ const [centerButtonScale, setCenterButtonScale] = (0, _react.useState)(0);
188
+ const [iconProgress, setIconProgress] = (0, _react.useState)(0);
189
+ const [centerPulseScale, setCenterPulseScale] = (0, _react.useState)(1);
190
+
191
+ // UI state
192
+ const [isHoveringCenter, setIsHoveringCenter] = (0, _react.useState)(false);
193
+ const [triggerGlitch, setTriggerGlitch] = (0, _react.useState)(0);
194
+ const [isExiting, setIsExiting] = (0, _react.useState)(false);
195
+ const [entranceComplete, setEntranceComplete] = (0, _react.useState)(false);
196
+
197
+ // Refs
198
+ const animationRef = (0, _react.useRef)(null);
199
+ const glitchIntervalRef = (0, _react.useRef)(null);
200
+ const isClosingRef = (0, _react.useRef)(false);
201
+
202
+ // Config
203
+ const {
204
+ entrance,
205
+ exit,
206
+ continuous,
207
+ interaction
208
+ } = _floatingToolsReact.dialAnimationConfig;
209
+ const cssAnimations = (0, _floatingToolsReact.getDialCSSAnimationStyles)();
210
+
211
+ // Layout
212
+ const layout = (0, _react.useMemo)(() => (0, _floatingToolsReact.getDialLayout)({
213
+ screenWidth: window.innerWidth
214
+ }), []);
215
+ const gridRotations = (0, _react.useMemo)(() => (0, _floatingToolsReact.getGridLineRotations)(), []);
216
+ const positions = (0, _react.useMemo)(() => (0, _floatingToolsReact.getAllIconPositions)(_floatingToolsReact.MAX_DIAL_SLOTS, layout.iconRadius), [layout.iconRadius]);
217
+ const paddedIcons = (0, _react.useMemo)(() => {
218
+ const result = [...icons.slice(0, _floatingToolsReact.MAX_DIAL_SLOTS)];
219
+ while (result.length < _floatingToolsReact.MAX_DIAL_SLOTS) {
220
+ result.push({
221
+ id: `empty-${result.length}`,
222
+ name: '',
223
+ icon: null,
224
+ onPress: () => {}
225
+ });
226
+ }
227
+ return result;
228
+ }, [icons]);
229
+
230
+ // Inject keyframes
231
+ (0, _react.useEffect)(() => {
232
+ injectKeyframes();
233
+ }, []);
234
+
235
+ // ENTRANCE Animation - runs once on mount
236
+ (0, _react.useEffect)(() => {
237
+ // Capture config values at mount time
238
+ const backdropDuration = entrance.backdrop.duration;
239
+ const rotationDuration = entrance.dial.rotation.duration;
240
+ const centerButtonDelay = entrance.centerButton.delay;
241
+ const iconsDelay = entrance.icons.delay;
242
+ const iconsDuration = entrance.icons.duration;
243
+ const glitchInterval = continuous.glitch.interval;
244
+ const startTime = performance.now();
245
+ const animate = currentTime => {
246
+ const elapsed = currentTime - startTime;
247
+
248
+ // Backdrop (0-400ms)
249
+ setBackdropOpacity(Math.min(1, elapsed / backdropDuration));
250
+
251
+ // Dial scale with spring overshoot (~400ms)
252
+ const dialProgress = Math.min(1, elapsed / 400);
253
+ let springScale = easeOutCubic(dialProgress) * 1.1;
254
+ if (dialProgress > 0.7) {
255
+ springScale -= (dialProgress - 0.7) * 0.33;
256
+ }
257
+ if (dialProgress >= 1) springScale = 1;
258
+ setDialScale(Math.max(0, Math.min(1.05, springScale)));
259
+
260
+ // Dial rotation (0-800ms)
261
+ const rotProgress = Math.min(1, elapsed / rotationDuration);
262
+ setDialRotation(easeOutCubic(rotProgress) * 360);
263
+
264
+ // Center button (300ms delay)
265
+ if (elapsed >= centerButtonDelay) {
266
+ const centerProgress = Math.min(1, (elapsed - centerButtonDelay) / 300);
267
+ let centerSpring = easeOutCubic(centerProgress) * 1.15;
268
+ if (centerProgress > 0.6) {
269
+ centerSpring -= (centerProgress - 0.6) * 0.375;
270
+ }
271
+ if (centerProgress >= 1) centerSpring = 1;
272
+ setCenterButtonScale(Math.max(0, Math.min(1.05, centerSpring)));
273
+ }
274
+
275
+ // Icons (500ms delay, 600ms duration)
276
+ if (elapsed >= iconsDelay) {
277
+ const iconsProgress = Math.min(1, (elapsed - iconsDelay) / iconsDuration);
278
+ setIconProgress(easeOutCubic(iconsProgress));
279
+ }
280
+
281
+ // Continue or finish
282
+ const totalTime = iconsDelay + iconsDuration;
283
+ if (elapsed < totalTime) {
284
+ animationRef.current = requestAnimationFrame(animate);
285
+ } else {
286
+ // Finalize
287
+ setBackdropOpacity(1);
288
+ setDialScale(1);
289
+ setDialRotation(360);
290
+ setCenterButtonScale(1);
291
+ setIconProgress(1);
292
+ setEntranceComplete(true);
293
+ }
294
+ };
295
+ animationRef.current = requestAnimationFrame(animate);
296
+
297
+ // Glitch interval
298
+ glitchIntervalRef.current = setInterval(() => {
299
+ setTriggerGlitch(prev => prev + 1);
300
+ }, glitchInterval);
301
+ return () => {
302
+ if (animationRef.current) cancelAnimationFrame(animationRef.current);
303
+ if (glitchIntervalRef.current) clearInterval(glitchIntervalRef.current);
304
+ };
305
+ }, []);
306
+
307
+ // EXIT Animation
308
+ const handleClose = (0, _react.useCallback)(() => {
309
+ if (isClosingRef.current) return;
310
+ isClosingRef.current = true;
311
+ setIsExiting(true);
312
+ if (glitchIntervalRef.current) {
313
+ clearInterval(glitchIntervalRef.current);
314
+ glitchIntervalRef.current = null;
315
+ }
316
+ if (animationRef.current) {
317
+ cancelAnimationFrame(animationRef.current);
318
+ }
319
+ const startTime = performance.now();
320
+ const iconsDuration = exit.icons.duration;
321
+ const dialDuration = Math.max(exit.centerButton.duration, exit.dialScale.duration);
322
+ const backdropDuration = exit.backdrop.duration;
323
+ const animate = currentTime => {
324
+ const elapsed = currentTime - startTime;
325
+
326
+ // Step 1: Icons collapse (0 - 300ms)
327
+ if (elapsed < iconsDuration) {
328
+ const progress = easeInCubic(elapsed / iconsDuration);
329
+ setIconProgress(1 - progress);
330
+ animationRef.current = requestAnimationFrame(animate);
331
+ return;
332
+ }
333
+
334
+ // Step 2: Dial + Center scale down (300 - 550ms)
335
+ const step2Start = iconsDuration;
336
+ if (elapsed < step2Start + dialDuration) {
337
+ setIconProgress(0);
338
+ const progress = easeInCubic((elapsed - step2Start) / dialDuration);
339
+ setDialScale(1 - progress);
340
+ setCenterButtonScale(1 - progress);
341
+ animationRef.current = requestAnimationFrame(animate);
342
+ return;
343
+ }
344
+
345
+ // Step 3: Backdrop fade (550 - 750ms)
346
+ const step3Start = step2Start + dialDuration;
347
+ if (elapsed < step3Start + backdropDuration) {
348
+ setDialScale(0);
349
+ setCenterButtonScale(0);
350
+ const progress = (elapsed - step3Start) / backdropDuration;
351
+ setBackdropOpacity(1 - progress);
352
+ animationRef.current = requestAnimationFrame(animate);
353
+ return;
354
+ }
355
+
356
+ // Done
357
+ setBackdropOpacity(0);
358
+ onClose();
359
+ };
360
+ animationRef.current = requestAnimationFrame(animate);
361
+ }, [exit, onClose]);
362
+
363
+ // Document-level Escape key handler
364
+ (0, _react.useEffect)(() => {
365
+ const handleKeyDown = e => {
366
+ if (e.key === 'Escape') {
367
+ handleClose();
368
+ }
369
+ };
370
+ document.addEventListener('keydown', handleKeyDown);
371
+ return () => document.removeEventListener('keydown', handleKeyDown);
372
+ }, [handleClose]);
373
+
374
+ // Icon press handler
375
+ const handleIconPress = (0, _react.useCallback)(icon => {
376
+ if (icon.id.startsWith('empty-') || isClosingRef.current) return;
377
+
378
+ // Pulse animation
379
+ const [pulseIn, pulseOut] = interaction.iconSelect.pulse;
380
+ const startTime = performance.now();
381
+ const pulseDuration = 150;
382
+ const animatePulse = currentTime => {
383
+ const elapsed = currentTime - startTime;
384
+ if (elapsed < pulseDuration) {
385
+ const progress = easeOutCubic(elapsed / pulseDuration);
386
+ setCenterPulseScale(1 - (1 - pulseIn.scale) * progress);
387
+ requestAnimationFrame(animatePulse);
388
+ } else if (elapsed < pulseDuration * 2) {
389
+ const progress = easeOutCubic((elapsed - pulseDuration) / pulseDuration);
390
+ setCenterPulseScale(pulseIn.scale + (pulseOut.scale - pulseIn.scale) * progress);
391
+ requestAnimationFrame(animatePulse);
392
+ } else {
393
+ setCenterPulseScale(1);
394
+ }
395
+ };
396
+ requestAnimationFrame(animatePulse);
397
+
398
+ // Trigger action after delay
399
+ setTimeout(() => {
400
+ icon.onPress();
401
+ handleClose();
402
+ }, interaction.iconSelect.actionDelay);
403
+ }, [interaction.iconSelect, handleClose]);
404
+
405
+ // Computed values
406
+ const buttonContainerSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.containerRatio;
407
+ const buttonBorderSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.borderRatio;
408
+ const isAnimating = entranceComplete && !isExiting;
409
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
410
+ role: "dialog",
411
+ "aria-label": "Dial Menu",
412
+ "aria-modal": "true",
413
+ style: {
414
+ position: 'fixed',
415
+ inset: 0,
416
+ zIndex: 10000,
417
+ display: 'flex',
418
+ alignItems: 'center',
419
+ justifyContent: 'center'
420
+ },
421
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
422
+ role: "button",
423
+ "aria-label": "Close dial menu",
424
+ tabIndex: 0,
425
+ onClick: handleClose,
426
+ onKeyDown: e => e.key === 'Escape' && handleClose(),
427
+ style: {
428
+ position: 'absolute',
429
+ inset: 0,
430
+ backgroundColor: _floatingToolsReact.dialColors.dialBackdrop,
431
+ opacity: backdropOpacity
432
+ }
433
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
434
+ style: {
435
+ animation: isAnimating ? cssAnimations.floating : 'none'
436
+ },
437
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
438
+ style: {
439
+ animation: triggerGlitch > 0 && isAnimating ? cssAnimations.glitch : 'none'
440
+ },
441
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
442
+ style: {
443
+ position: 'relative',
444
+ width: layout.circleSize,
445
+ height: layout.circleSize,
446
+ borderRadius: '50%',
447
+ backgroundColor: _floatingToolsReact.dialColors.dialBackground,
448
+ border: `${_floatingToolsReact.dialStyles.circle.borderWidth}px solid ${_floatingToolsReact.dialColors.dialBorder}`,
449
+ boxShadow: `0 0 ${_floatingToolsReact.dialStyles.circle.shadowRadius}px ${_floatingToolsReact.dialColors.dialShadow}`,
450
+ transform: `scale(${dialScale}) rotate(${dialRotation}deg)`,
451
+ opacity: dialScale > 0.01 ? 1 : 0,
452
+ overflow: 'hidden'
453
+ },
454
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
455
+ style: {
456
+ position: 'absolute',
457
+ inset: 0,
458
+ borderRadius: '50%',
459
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient1,
460
+ opacity: _floatingToolsReact.dialStyles.gradientLayers[0].opacity
461
+ }
462
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
463
+ style: {
464
+ position: 'absolute',
465
+ inset: 0,
466
+ top: '30%',
467
+ left: '30%',
468
+ borderRadius: '50%',
469
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient2,
470
+ opacity: _floatingToolsReact.dialStyles.gradientLayers[1].opacity
471
+ }
472
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
473
+ style: {
474
+ position: 'absolute',
475
+ inset: 0,
476
+ top: '50%',
477
+ left: '50%',
478
+ borderRadius: '50%',
479
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient3,
480
+ opacity: _floatingToolsReact.dialStyles.gradientLayers[2].opacity
481
+ }
482
+ }), gridRotations.map(deg => /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
483
+ style: {
484
+ position: 'absolute',
485
+ top: '50%',
486
+ left: 0,
487
+ right: 0,
488
+ height: _floatingToolsReact.dialStyles.grid.lineHeight,
489
+ backgroundColor: _floatingToolsReact.dialColors.dialGridLine,
490
+ transform: `rotate(${deg}deg)`,
491
+ transformOrigin: 'center'
492
+ }
493
+ }, deg)), paddedIcons.map((icon, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(DialIconItem, {
494
+ icon: icon,
495
+ index: index,
496
+ totalIcons: paddedIcons.length,
497
+ position: positions[index],
498
+ progress: iconProgress,
499
+ onPress: () => handleIconPress(icon)
500
+ }, icon.id)), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
501
+ style: {
502
+ position: 'absolute',
503
+ left: '50%',
504
+ top: '50%',
505
+ width: buttonContainerSize,
506
+ height: buttonContainerSize,
507
+ marginLeft: -buttonContainerSize / 2,
508
+ marginTop: -buttonContainerSize / 2,
509
+ display: 'flex',
510
+ alignItems: 'center',
511
+ justifyContent: 'center',
512
+ backgroundColor: _floatingToolsReact.dialColors.dialBackground,
513
+ borderRadius: layout.buttonSize,
514
+ overflow: 'hidden',
515
+ transform: `scale(${centerButtonScale * centerPulseScale})`,
516
+ opacity: centerButtonScale > 0.01 ? 1 : 0
517
+ },
518
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
519
+ style: {
520
+ position: 'absolute',
521
+ inset: 0,
522
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient1,
523
+ opacity: 0.5,
524
+ borderRadius: layout.buttonSize
525
+ }
526
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
527
+ style: {
528
+ position: 'absolute',
529
+ inset: 0,
530
+ top: '20%',
531
+ left: '20%',
532
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient2,
533
+ opacity: 0.3,
534
+ borderRadius: layout.buttonSize
535
+ }
536
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
537
+ style: {
538
+ position: 'absolute',
539
+ inset: 0,
540
+ top: '40%',
541
+ left: '40%',
542
+ backgroundColor: _floatingToolsReact.dialColors.dialGradient3,
543
+ opacity: 0.2,
544
+ borderRadius: layout.buttonSize
545
+ }
546
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
547
+ style: {
548
+ width: buttonBorderSize,
549
+ height: buttonBorderSize,
550
+ borderRadius: layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.borderRadiusRatio,
551
+ backgroundColor: _floatingToolsReact.dialColors.dialGridLine,
552
+ border: `${_floatingToolsReact.dialStyles.centerButton.borderWidth}px solid ${_floatingToolsReact.dialColors.dialBorder}`,
553
+ display: 'flex',
554
+ alignItems: 'center',
555
+ justifyContent: 'center'
556
+ },
557
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
558
+ onClick: onCenterPress,
559
+ onMouseEnter: () => setIsHoveringCenter(true),
560
+ onMouseLeave: () => setIsHoveringCenter(false),
561
+ style: {
562
+ width: layout.buttonSize,
563
+ height: layout.buttonSize,
564
+ borderRadius: layout.buttonSize / 2,
565
+ display: 'flex',
566
+ flexDirection: 'column',
567
+ alignItems: 'center',
568
+ justifyContent: 'center',
569
+ cursor: onCenterPress ? 'pointer' : 'default',
570
+ transform: isHoveringCenter ? `scale(${interaction.centerHover.scale})` : 'scale(1)',
571
+ transition: `transform ${interaction.centerHover.duration}ms ease`,
572
+ animation: isAnimating && !isHoveringCenter ? cssAnimations.breathing : 'none'
573
+ },
574
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
575
+ style: {
576
+ fontSize: _floatingToolsReact.dialStyles.centerButton.text.fontSize,
577
+ fontWeight: _floatingToolsReact.dialStyles.centerButton.text.fontWeight,
578
+ fontFamily: _floatingToolsReact.dialStyles.centerButton.text.fontFamily,
579
+ color: _floatingToolsReact.dialColors.centerText,
580
+ letterSpacing: _floatingToolsReact.dialStyles.centerButton.text.letterSpacing,
581
+ textShadow: `0 0 4px ${_floatingToolsReact.dialColors.centerTextGlow}`,
582
+ textTransform: 'uppercase'
583
+ },
584
+ children: centerLabel
585
+ })
586
+ })
587
+ })]
588
+ })]
589
+ })
590
+ }, triggerGlitch)
591
+ })]
592
+ });
593
+ }