@buoy-gg/core 2.1.15 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +4 -34
- package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +3 -25
- package/lib/commonjs/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/commonjs/floatingMenu/FloatingMenu.js +6 -6
- package/lib/commonjs/floatingMenu/defaultConfig.js +1 -1
- package/lib/commonjs/floatingMenu/dial/DialDevTools.js +206 -224
- package/lib/commonjs/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/commonjs/floatingMenu/dial/DialIcon.js +77 -71
- package/lib/commonjs/floatingMenu/dial/DialPagination.js +170 -0
- package/lib/commonjs/floatingMenu/dial/dialUsageStore.js +97 -0
- package/lib/module/floatingMenu/DevToolsSettingsModal.js +5 -35
- package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +4 -28
- package/lib/module/floatingMenu/FloatingDevTools.js +14 -1
- package/lib/module/floatingMenu/FloatingDevTools.web.js +19 -9
- package/lib/module/floatingMenu/FloatingMenu.js +7 -7
- package/lib/module/floatingMenu/defaultConfig.js +1 -1
- package/lib/module/floatingMenu/dial/DialDevTools.js +209 -226
- package/lib/module/floatingMenu/dial/DialDevTools.web.js +82 -7
- package/lib/module/floatingMenu/dial/DialIcon.js +81 -74
- package/lib/module/floatingMenu/dial/DialPagination.js +165 -0
- package/lib/module/floatingMenu/dial/dialUsageStore.js +89 -0
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/commonjs/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/commonjs/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/commonjs/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/FloatingMenu.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts +1 -1
- package/lib/typescript/module/floatingMenu/defaultConfig.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts +0 -2
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialDevTools.web.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts +7 -2
- package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts +22 -0
- package/lib/typescript/module/floatingMenu/dial/DialPagination.d.ts.map +1 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts +34 -0
- package/lib/typescript/module/floatingMenu/dial/dialUsageStore.d.ts.map +1 -0
- package/package.json +5 -5
|
@@ -214,8 +214,14 @@ function DialMenu({
|
|
|
214
214
|
}), []);
|
|
215
215
|
const gridRotations = (0, _react.useMemo)(() => (0, _floatingToolsReact.getGridLineRotations)(), []);
|
|
216
216
|
const positions = (0, _react.useMemo)(() => (0, _floatingToolsReact.getAllIconPositions)(_floatingToolsReact.MAX_DIAL_SLOTS, layout.iconRadius), [layout.iconRadius]);
|
|
217
|
+
|
|
218
|
+
// Pagination: tools are split across pages of MAX_DIAL_SLOTS.
|
|
219
|
+
const [currentPage, setCurrentPage] = (0, _react.useState)(0);
|
|
220
|
+
const pageCount = Math.max(1, Math.ceil(icons.length / _floatingToolsReact.MAX_DIAL_SLOTS));
|
|
221
|
+
const safePage = Math.min(currentPage, pageCount - 1);
|
|
217
222
|
const paddedIcons = (0, _react.useMemo)(() => {
|
|
218
|
-
const
|
|
223
|
+
const start = safePage * _floatingToolsReact.MAX_DIAL_SLOTS;
|
|
224
|
+
const result = [...icons.slice(start, start + _floatingToolsReact.MAX_DIAL_SLOTS)];
|
|
219
225
|
while (result.length < _floatingToolsReact.MAX_DIAL_SLOTS) {
|
|
220
226
|
result.push({
|
|
221
227
|
id: `empty-${result.length}`,
|
|
@@ -225,7 +231,7 @@ function DialMenu({
|
|
|
225
231
|
});
|
|
226
232
|
}
|
|
227
233
|
return result;
|
|
228
|
-
}, [icons]);
|
|
234
|
+
}, [icons, safePage]);
|
|
229
235
|
|
|
230
236
|
// Inject keyframes
|
|
231
237
|
(0, _react.useEffect)(() => {
|
|
@@ -402,10 +408,37 @@ function DialMenu({
|
|
|
402
408
|
}, interaction.iconSelect.actionDelay);
|
|
403
409
|
}, [interaction.iconSelect, handleClose]);
|
|
404
410
|
|
|
411
|
+
// Page navigation - icons are keyed by slot index, so this only swaps
|
|
412
|
+
// their content in place (no remount, no re-animation) for an instant page
|
|
413
|
+
// change.
|
|
414
|
+
const handlePageChange = (0, _react.useCallback)(next => {
|
|
415
|
+
if (isClosingRef.current) return;
|
|
416
|
+
const clamped = Math.max(0, Math.min(next, pageCount - 1));
|
|
417
|
+
if (clamped !== safePage) setCurrentPage(clamped);
|
|
418
|
+
}, [pageCount, safePage]);
|
|
419
|
+
|
|
405
420
|
// Computed values
|
|
406
421
|
const buttonContainerSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.containerRatio;
|
|
407
422
|
const buttonBorderSize = layout.buttonSize * _floatingToolsReact.dialStyles.centerButton.borderRatio;
|
|
408
423
|
const isAnimating = entranceComplete && !isExiting;
|
|
424
|
+
const pagerButtonStyle = disabled => ({
|
|
425
|
+
display: 'flex',
|
|
426
|
+
alignItems: 'center',
|
|
427
|
+
gap: 4,
|
|
428
|
+
padding: '8px 16px',
|
|
429
|
+
borderRadius: 10,
|
|
430
|
+
border: `1px solid ${_floatingToolsReact.dialColors.dialBorder}`,
|
|
431
|
+
backgroundColor: _floatingToolsReact.dialColors.dialBackground,
|
|
432
|
+
color: disabled ? _floatingToolsReact.dialColors.emptyDotBorder : _floatingToolsReact.dialColors.dialShadow,
|
|
433
|
+
fontSize: 12,
|
|
434
|
+
fontWeight: 900,
|
|
435
|
+
fontFamily: 'monospace',
|
|
436
|
+
letterSpacing: 1.5,
|
|
437
|
+
textTransform: 'uppercase',
|
|
438
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
439
|
+
opacity: disabled ? 0.4 : 1,
|
|
440
|
+
boxShadow: disabled ? 'none' : `0 0 8px ${_floatingToolsReact.dialColors.dialShadow}66`
|
|
441
|
+
});
|
|
409
442
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
410
443
|
role: "dialog",
|
|
411
444
|
"aria-label": "Dial Menu",
|
|
@@ -430,11 +463,15 @@ function DialMenu({
|
|
|
430
463
|
backgroundColor: _floatingToolsReact.dialColors.dialBackdrop,
|
|
431
464
|
opacity: backdropOpacity
|
|
432
465
|
}
|
|
433
|
-
}), /*#__PURE__*/(0, _jsxRuntime.
|
|
466
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
434
467
|
style: {
|
|
435
|
-
animation: isAnimating ? cssAnimations.floating : 'none'
|
|
468
|
+
animation: isAnimating ? cssAnimations.floating : 'none',
|
|
469
|
+
display: 'flex',
|
|
470
|
+
flexDirection: 'column',
|
|
471
|
+
alignItems: 'center',
|
|
472
|
+
gap: 16
|
|
436
473
|
},
|
|
437
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
474
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
438
475
|
style: {
|
|
439
476
|
animation: triggerGlitch > 0 && isAnimating ? cssAnimations.glitch : 'none'
|
|
440
477
|
},
|
|
@@ -497,7 +534,7 @@ function DialMenu({
|
|
|
497
534
|
position: positions[index],
|
|
498
535
|
progress: iconProgress,
|
|
499
536
|
onPress: () => handleIconPress(icon)
|
|
500
|
-
},
|
|
537
|
+
}, index)), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
501
538
|
style: {
|
|
502
539
|
position: 'absolute',
|
|
503
540
|
left: '50%',
|
|
@@ -587,7 +624,45 @@ function DialMenu({
|
|
|
587
624
|
})]
|
|
588
625
|
})]
|
|
589
626
|
})
|
|
590
|
-
}, triggerGlitch)
|
|
627
|
+
}, triggerGlitch), pageCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
628
|
+
style: {
|
|
629
|
+
display: 'flex',
|
|
630
|
+
alignItems: 'center',
|
|
631
|
+
gap: 12,
|
|
632
|
+
opacity: Math.min(1, dialScale)
|
|
633
|
+
},
|
|
634
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
635
|
+
type: "button",
|
|
636
|
+
"aria-label": "Previous dial page",
|
|
637
|
+
disabled: safePage <= 0,
|
|
638
|
+
onClick: () => handlePageChange(safePage - 1),
|
|
639
|
+
style: pagerButtonStyle(safePage <= 0),
|
|
640
|
+
children: "\u2039 PREV"
|
|
641
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
642
|
+
style: {
|
|
643
|
+
fontSize: 13,
|
|
644
|
+
fontWeight: 900,
|
|
645
|
+
fontFamily: 'monospace',
|
|
646
|
+
letterSpacing: 2,
|
|
647
|
+
color: '#FFFFFF',
|
|
648
|
+
textShadow: `0 0 6px ${_floatingToolsReact.dialColors.dialShadow}`
|
|
649
|
+
},
|
|
650
|
+
children: [String(safePage + 1).padStart(2, '0'), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
|
|
651
|
+
style: {
|
|
652
|
+
color: _floatingToolsReact.dialColors.iconLabel,
|
|
653
|
+
textShadow: 'none'
|
|
654
|
+
},
|
|
655
|
+
children: [' / ', String(pageCount).padStart(2, '0')]
|
|
656
|
+
})]
|
|
657
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
658
|
+
type: "button",
|
|
659
|
+
"aria-label": "Next dial page",
|
|
660
|
+
disabled: safePage >= pageCount - 1,
|
|
661
|
+
onClick: () => handlePageChange(safePage + 1),
|
|
662
|
+
style: pagerButtonStyle(safePage >= pageCount - 1),
|
|
663
|
+
children: "NEXT \u203A"
|
|
664
|
+
})]
|
|
665
|
+
})]
|
|
591
666
|
})]
|
|
592
667
|
});
|
|
593
668
|
}
|
|
@@ -8,36 +8,25 @@ var _react = require("react");
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
10
10
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Use shared layout calculation
|
|
16
|
-
const layout = (0, _floatingToolsCore.getDialLayout)({
|
|
17
|
-
screenWidth: SCREEN_WIDTH
|
|
18
|
-
});
|
|
19
|
-
const VIEW_SIZE = layout.iconSize;
|
|
20
|
-
const CIRCLE_SIZE = layout.circleSize;
|
|
21
|
-
const CIRCLE_RADIUS = layout.circleRadius;
|
|
11
|
+
// The circle radius depends on the live window width and is computed inside
|
|
12
|
+
// the component (must match DialDevTools' circle, which does the same).
|
|
13
|
+
const VIEW_SIZE = _floatingToolsCore.DIAL_ICON_SIZE;
|
|
22
14
|
const DialIcon = ({
|
|
23
15
|
index,
|
|
24
16
|
icon,
|
|
25
17
|
iconsProgress,
|
|
26
18
|
onPress,
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
totalIcons,
|
|
20
|
+
active
|
|
29
21
|
}) => {
|
|
30
|
-
// Use shared position calculation from core
|
|
31
|
-
const iconPosition = (0, _floatingToolsCore.getIconPosition)(index, totalIcons, layout.iconRadius, _floatingToolsCore.DIAL_START_ANGLE);
|
|
32
|
-
const {
|
|
33
|
-
x: finalX,
|
|
34
|
-
y: finalY,
|
|
35
|
-
angle
|
|
36
|
-
} = iconPosition;
|
|
37
|
-
const radius = layout.iconRadius;
|
|
38
|
-
|
|
39
22
|
// Animation values - using interpolation for better performance
|
|
40
23
|
const scale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
24
|
+
const {
|
|
25
|
+
width: screenWidth
|
|
26
|
+
} = (0, _reactNative.useWindowDimensions)();
|
|
27
|
+
const layout = (0, _react.useMemo)(() => (0, _floatingToolsCore.getDialLayout)({
|
|
28
|
+
screenWidth
|
|
29
|
+
}), [screenWidth]);
|
|
41
30
|
|
|
42
31
|
// Hover animation on press in/out - using shared config
|
|
43
32
|
// Fallback values in case dialAnimationConfig hasn't loaded yet
|
|
@@ -74,78 +63,92 @@ const DialIcon = ({
|
|
|
74
63
|
}).start();
|
|
75
64
|
};
|
|
76
65
|
|
|
77
|
-
//
|
|
78
|
-
|
|
66
|
+
// Position + spiral-entrance interpolations depend only on the (fixed) slot
|
|
67
|
+
// index, so compute them once. This keeps re-renders — which happen on every
|
|
68
|
+
// page change as `active` toggles — cheap.
|
|
69
|
+
const motion = (0, _react.useMemo)(() => {
|
|
70
|
+
const iconPosition = (0, _floatingToolsCore.getIconPosition)(index, totalIcons, layout.iconRadius, _floatingToolsCore.DIAL_START_ANGLE);
|
|
71
|
+
const {
|
|
72
|
+
x: finalX,
|
|
73
|
+
y: finalY,
|
|
74
|
+
angle
|
|
75
|
+
} = iconPosition;
|
|
76
|
+
const radius = layout.iconRadius;
|
|
77
|
+
const staggerInputRange = (0, _floatingToolsCore.getIconStaggerInputRange)(index, totalIcons);
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
// Use interpolation for smooth animation that works both directions
|
|
80
|
+
const staggeredProgress = iconsProgress.interpolate({
|
|
81
|
+
inputRange: staggerInputRange,
|
|
82
|
+
outputRange: [0, 0, 1, 1],
|
|
83
|
+
extrapolate: "clamp"
|
|
84
|
+
});
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
// Spiral animation with interpolation
|
|
87
|
+
const spiralRotation = staggeredProgress.interpolate({
|
|
88
|
+
inputRange: [0, 1],
|
|
89
|
+
outputRange: [Math.PI * 2, 0] // Spiral from 2π to 0
|
|
90
|
+
});
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
// Distance from center
|
|
93
|
+
const distance = staggeredProgress.interpolate({
|
|
94
|
+
inputRange: [0, 1],
|
|
95
|
+
outputRange: [0, radius]
|
|
96
|
+
});
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
// Calculate X and Y positions using Animated operations
|
|
99
|
+
const translateX = _reactNative.Animated.add(_reactNative.Animated.multiply(distance, spiralRotation.interpolate({
|
|
100
|
+
inputRange: [0, Math.PI * 2],
|
|
101
|
+
outputRange: [Math.cos(angle), Math.cos(angle + Math.PI * 2)]
|
|
102
|
+
})), staggeredProgress.interpolate({
|
|
103
|
+
inputRange: [0, 1],
|
|
104
|
+
outputRange: [0, finalX - radius * Math.cos(angle + Math.PI * 2)]
|
|
105
|
+
}));
|
|
106
|
+
const translateY = _reactNative.Animated.add(_reactNative.Animated.multiply(distance, spiralRotation.interpolate({
|
|
107
|
+
inputRange: [0, Math.PI * 2],
|
|
108
|
+
outputRange: [Math.sin(angle), Math.sin(angle + Math.PI * 2)]
|
|
109
|
+
})), staggeredProgress.interpolate({
|
|
110
|
+
inputRange: [0, 1],
|
|
111
|
+
outputRange: [0, finalY - radius * Math.sin(angle + Math.PI * 2)]
|
|
112
|
+
}));
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
// Opacity animation
|
|
115
|
+
const itemOpacity = staggeredProgress.interpolate({
|
|
116
|
+
inputRange: [0, 0.3, 1],
|
|
117
|
+
outputRange: [0, 0.3, 1]
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
translateX,
|
|
121
|
+
translateY,
|
|
122
|
+
itemOpacity,
|
|
123
|
+
progressScale: staggeredProgress
|
|
124
|
+
};
|
|
125
|
+
}, [index, totalIcons, iconsProgress, layout]);
|
|
123
126
|
|
|
124
127
|
// Main animated style for position and appearance
|
|
125
128
|
const animatedStyle = {
|
|
126
129
|
position: "absolute",
|
|
127
|
-
left:
|
|
130
|
+
left: layout.circleRadius - VIEW_SIZE / 2,
|
|
128
131
|
// Center position
|
|
129
|
-
top:
|
|
132
|
+
top: layout.circleRadius - VIEW_SIZE / 2,
|
|
130
133
|
// Center position
|
|
131
|
-
opacity: itemOpacity,
|
|
134
|
+
opacity: motion.itemOpacity,
|
|
132
135
|
transform: [{
|
|
133
|
-
translateX
|
|
136
|
+
translateX: motion.translateX
|
|
134
137
|
},
|
|
135
138
|
// Apply translation from center
|
|
136
139
|
{
|
|
137
|
-
translateY
|
|
140
|
+
translateY: motion.translateY
|
|
138
141
|
},
|
|
139
142
|
// Apply translation from center
|
|
140
143
|
{
|
|
141
|
-
scale: _reactNative.Animated.multiply(scale, progressScale)
|
|
144
|
+
scale: _reactNative.Animated.multiply(scale, motion.progressScale)
|
|
142
145
|
}]
|
|
143
146
|
};
|
|
144
147
|
|
|
145
148
|
// Check if this is an empty spot (no icon and no iconComponent)
|
|
146
149
|
const isEmpty = icon.icon === null && !icon.iconComponent;
|
|
147
150
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
148
|
-
style: [styles.view, animatedStyle],
|
|
151
|
+
style: [styles.view, animatedStyle, !active && styles.hidden],
|
|
149
152
|
children: isEmpty ?
|
|
150
153
|
/*#__PURE__*/
|
|
151
154
|
// Empty spot - just show a subtle circle
|
|
@@ -155,7 +158,7 @@ const DialIcon = ({
|
|
|
155
158
|
style: styles.emptyDot
|
|
156
159
|
})
|
|
157
160
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
158
|
-
onPress: () => onPress(
|
|
161
|
+
onPress: () => onPress(icon),
|
|
159
162
|
onPressIn: handlePressIn,
|
|
160
163
|
onPressOut: handlePressOut,
|
|
161
164
|
style: styles.pressable,
|
|
@@ -194,6 +197,9 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
194
197
|
justifyContent: "center",
|
|
195
198
|
alignItems: "center"
|
|
196
199
|
},
|
|
200
|
+
hidden: {
|
|
201
|
+
display: "none"
|
|
202
|
+
},
|
|
197
203
|
pressable: {
|
|
198
204
|
width: "100%",
|
|
199
205
|
height: "100%",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.DialPagination = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
11
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
+
/** Pad a 1-based page number to two digits, e.g. 3 -> "03". */
|
|
13
|
+
const pad = n => String(n).padStart(2, "0");
|
|
14
|
+
/**
|
|
15
|
+
* A single pager button. Press feedback is driven by a native-driver
|
|
16
|
+
* `Animated` value via `onPressIn`/`onPressOut` — so the scale reacts
|
|
17
|
+
* instantly on the UI thread, with no JS re-render in the press path.
|
|
18
|
+
*/
|
|
19
|
+
const PagerButton = ({
|
|
20
|
+
side,
|
|
21
|
+
label,
|
|
22
|
+
disabled,
|
|
23
|
+
onPress
|
|
24
|
+
}) => {
|
|
25
|
+
const scale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
26
|
+
const springTo = toValue => {
|
|
27
|
+
_reactNative.Animated.spring(scale, {
|
|
28
|
+
toValue,
|
|
29
|
+
damping: 15,
|
|
30
|
+
stiffness: 400,
|
|
31
|
+
useNativeDriver: true
|
|
32
|
+
}).start();
|
|
33
|
+
};
|
|
34
|
+
const accent = disabled ? _floatingToolsCore.dialColors.emptyDotBorder : _sharedUi.buoyColors.primary;
|
|
35
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
36
|
+
style: {
|
|
37
|
+
transform: [{
|
|
38
|
+
scale
|
|
39
|
+
}]
|
|
40
|
+
},
|
|
41
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
|
|
42
|
+
accessibilityRole: "button",
|
|
43
|
+
accessibilityLabel: `${side === "prev" ? "Previous" : "Next"} dial page`,
|
|
44
|
+
accessibilityState: {
|
|
45
|
+
disabled
|
|
46
|
+
},
|
|
47
|
+
disabled: disabled,
|
|
48
|
+
onPress: onPress,
|
|
49
|
+
onPressIn: () => !disabled && springTo(0.92),
|
|
50
|
+
onPressOut: () => springTo(1),
|
|
51
|
+
style: [styles.button, disabled && styles.buttonDisabled],
|
|
52
|
+
children: [side === "prev" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronLeft, {
|
|
53
|
+
size: 18,
|
|
54
|
+
color: accent
|
|
55
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
56
|
+
style: [styles.buttonText, disabled && styles.buttonTextDisabled],
|
|
57
|
+
children: label
|
|
58
|
+
}), side === "next" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
|
|
59
|
+
size: 18,
|
|
60
|
+
color: accent
|
|
61
|
+
})]
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Prev/Next pager shown below the dial when there are more dial tools than
|
|
68
|
+
* fit on a single page. Tools are ranked by usage, so paging walks from the
|
|
69
|
+
* most-used tools toward the least-used.
|
|
70
|
+
*/
|
|
71
|
+
const DialPagination = ({
|
|
72
|
+
page,
|
|
73
|
+
pageCount,
|
|
74
|
+
onPrev,
|
|
75
|
+
onNext,
|
|
76
|
+
animatedStyle
|
|
77
|
+
}) => {
|
|
78
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
|
|
79
|
+
style: [styles.container, animatedStyle],
|
|
80
|
+
pointerEvents: "box-none",
|
|
81
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(PagerButton, {
|
|
82
|
+
side: "prev",
|
|
83
|
+
label: "PREV",
|
|
84
|
+
disabled: page <= 0,
|
|
85
|
+
onPress: onPrev
|
|
86
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
87
|
+
style: styles.indicator,
|
|
88
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
89
|
+
style: styles.indicatorText,
|
|
90
|
+
children: [pad(page + 1), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
91
|
+
style: styles.indicatorTextDim,
|
|
92
|
+
children: [" / ", pad(pageCount)]
|
|
93
|
+
})]
|
|
94
|
+
})
|
|
95
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(PagerButton, {
|
|
96
|
+
side: "next",
|
|
97
|
+
label: "NEXT",
|
|
98
|
+
disabled: page >= pageCount - 1,
|
|
99
|
+
onPress: onNext
|
|
100
|
+
})]
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
exports.DialPagination = DialPagination;
|
|
104
|
+
const styles = _reactNative.StyleSheet.create({
|
|
105
|
+
container: {
|
|
106
|
+
flexDirection: "row",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
justifyContent: "space-between"
|
|
109
|
+
},
|
|
110
|
+
button: {
|
|
111
|
+
flexDirection: "row",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
gap: 4,
|
|
114
|
+
paddingHorizontal: 16,
|
|
115
|
+
paddingVertical: 10,
|
|
116
|
+
borderRadius: 10,
|
|
117
|
+
borderWidth: 1,
|
|
118
|
+
borderColor: _floatingToolsCore.dialColors.dialBorder,
|
|
119
|
+
backgroundColor: _floatingToolsCore.dialColors.dialBackground,
|
|
120
|
+
shadowColor: _floatingToolsCore.dialColors.dialShadow,
|
|
121
|
+
shadowOffset: {
|
|
122
|
+
width: 0,
|
|
123
|
+
height: 0
|
|
124
|
+
},
|
|
125
|
+
shadowOpacity: 0.4,
|
|
126
|
+
shadowRadius: 8,
|
|
127
|
+
elevation: 6
|
|
128
|
+
},
|
|
129
|
+
buttonDisabled: {
|
|
130
|
+
opacity: 0.4
|
|
131
|
+
},
|
|
132
|
+
buttonText: {
|
|
133
|
+
color: _sharedUi.buoyColors.primary,
|
|
134
|
+
fontSize: 12,
|
|
135
|
+
fontWeight: "900",
|
|
136
|
+
fontFamily: "monospace",
|
|
137
|
+
letterSpacing: 1.5,
|
|
138
|
+
textShadowColor: _sharedUi.buoyColors.primary,
|
|
139
|
+
textShadowOffset: {
|
|
140
|
+
width: 0,
|
|
141
|
+
height: 0
|
|
142
|
+
},
|
|
143
|
+
textShadowRadius: 4
|
|
144
|
+
},
|
|
145
|
+
buttonTextDisabled: {
|
|
146
|
+
color: _floatingToolsCore.dialColors.emptyDotBorder,
|
|
147
|
+
textShadowRadius: 0
|
|
148
|
+
},
|
|
149
|
+
indicator: {
|
|
150
|
+
paddingHorizontal: 12,
|
|
151
|
+
paddingVertical: 6
|
|
152
|
+
},
|
|
153
|
+
indicatorText: {
|
|
154
|
+
color: "#FFFFFF",
|
|
155
|
+
fontSize: 13,
|
|
156
|
+
fontWeight: "900",
|
|
157
|
+
fontFamily: "monospace",
|
|
158
|
+
letterSpacing: 2,
|
|
159
|
+
textShadowColor: _floatingToolsCore.dialColors.dialShadow,
|
|
160
|
+
textShadowOffset: {
|
|
161
|
+
width: 0,
|
|
162
|
+
height: 0
|
|
163
|
+
},
|
|
164
|
+
textShadowRadius: 6
|
|
165
|
+
},
|
|
166
|
+
indicatorTextDim: {
|
|
167
|
+
color: _floatingToolsCore.dialColors.iconLabel,
|
|
168
|
+
textShadowRadius: 0
|
|
169
|
+
}
|
|
170
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getRankedToolIds = getRankedToolIds;
|
|
7
|
+
exports.isDialUsageLoaded = isDialUsageLoaded;
|
|
8
|
+
exports.loadDialUsage = loadDialUsage;
|
|
9
|
+
exports.recordToolUsage = recordToolUsage;
|
|
10
|
+
exports.resetDialUsage = resetDialUsage;
|
|
11
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
12
|
+
var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
|
|
13
|
+
/**
|
|
14
|
+
* Dial Usage Store - persists and ranks dial tool usage.
|
|
15
|
+
*
|
|
16
|
+
* Wraps the pure scoring logic from `@buoy-gg/floating-tools-core` with a
|
|
17
|
+
* persisted, in-memory cache. The dial menu uses this to order tools by how
|
|
18
|
+
* recently/frequently they are used, so the most-used tools land on page 1.
|
|
19
|
+
*
|
|
20
|
+
* The cache is loaded eagerly on import so `getRankedToolIds` can run
|
|
21
|
+
* synchronously by the time the dial opens.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const STORAGE_KEY = _sharedUi.devToolsStorageKeys.dial.usage();
|
|
25
|
+
let cache = {};
|
|
26
|
+
let loaded = false;
|
|
27
|
+
let loadPromise = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load persisted usage data into the in-memory cache. Safe to call multiple
|
|
31
|
+
* times — the underlying read happens only once.
|
|
32
|
+
*/
|
|
33
|
+
function loadDialUsage() {
|
|
34
|
+
if (loadPromise) return loadPromise;
|
|
35
|
+
loadPromise = (async () => {
|
|
36
|
+
try {
|
|
37
|
+
const raw = await _sharedUi.persistentStorage.getItem(STORAGE_KEY);
|
|
38
|
+
if (raw) {
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (parsed && typeof parsed === "object") {
|
|
41
|
+
cache = parsed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Ignore — start with an empty usage map.
|
|
46
|
+
} finally {
|
|
47
|
+
loaded = true;
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
return loadPromise;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Kick off the load as soon as this module is imported.
|
|
54
|
+
void loadDialUsage();
|
|
55
|
+
|
|
56
|
+
/** Whether the usage cache has finished loading from storage. */
|
|
57
|
+
function isDialUsageLoaded() {
|
|
58
|
+
return loaded;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Rank tool ids by recency-weighted usage, highest first. Synchronous —
|
|
63
|
+
* operates against the in-memory cache. Never-used tools keep their original
|
|
64
|
+
* order as a tie-breaker.
|
|
65
|
+
*
|
|
66
|
+
* @param orderedIds - Tool ids in their default/registration order
|
|
67
|
+
*/
|
|
68
|
+
function getRankedToolIds(orderedIds) {
|
|
69
|
+
return (0, _floatingToolsCore.rankToolIds)(orderedIds, cache, Date.now());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Record a single press of a tool and persist the updated usage map.
|
|
74
|
+
*
|
|
75
|
+
* @param id - Tool id that was pressed
|
|
76
|
+
*/
|
|
77
|
+
async function recordToolUsage(id) {
|
|
78
|
+
if (!id) return;
|
|
79
|
+
if (!loaded) await loadDialUsage();
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
cache = (0, _floatingToolsCore.pruneUsage)((0, _floatingToolsCore.recordUsage)(cache, id, now), now);
|
|
82
|
+
try {
|
|
83
|
+
await _sharedUi.persistentStorage.setItem(STORAGE_KEY, JSON.stringify(cache));
|
|
84
|
+
} catch {
|
|
85
|
+
// Ignore persistence failure — the in-memory cache is still updated.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Clear all usage data (in-memory and persisted). */
|
|
90
|
+
async function resetDialUsage() {
|
|
91
|
+
cache = {};
|
|
92
|
+
try {
|
|
93
|
+
await _sharedUi.persistentStorage.removeItem(STORAGE_KEY);
|
|
94
|
+
} catch {
|
|
95
|
+
// Ignore — the in-memory cache is already cleared.
|
|
96
|
+
}
|
|
97
|
+
}
|