@buoy-gg/shared-ui 2.1.10 → 2.1.12

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 (55) hide show
  1. package/lib/commonjs/dataViewer/DataViewer.js +1 -3
  2. package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +3 -4
  3. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  4. package/lib/commonjs/icons/ImageOverlayIcon.js +142 -0
  5. package/lib/commonjs/icons/index.js +8 -0
  6. package/lib/commonjs/ui/components/EventHistoryViewer/EventHistoryViewer.js +2 -1
  7. package/lib/commonjs/ui/components/WindowControls.js +307 -46
  8. package/lib/commonjs/ui/components/index.js +6 -0
  9. package/lib/commonjs/utils/time/TickContext.js +43 -0
  10. package/lib/commonjs/utils/time/index.js +21 -1
  11. package/lib/commonjs/utils/time/useRelativeTime.js +36 -0
  12. package/lib/module/dataViewer/DataViewer.js +1 -3
  13. package/lib/module/dataViewer/VirtualizedDataExplorer.js +3 -4
  14. package/lib/module/hooks/safe-area-impl.js +1 -1
  15. package/lib/module/icons/ImageOverlayIcon.js +137 -0
  16. package/lib/module/icons/index.js +1 -0
  17. package/lib/module/ui/components/EventHistoryViewer/EventHistoryViewer.js +3 -2
  18. package/lib/module/ui/components/WindowControls.js +308 -48
  19. package/lib/module/ui/components/index.js +1 -1
  20. package/lib/module/utils/time/TickContext.js +38 -0
  21. package/lib/module/utils/time/index.js +3 -1
  22. package/lib/module/utils/time/useRelativeTime.js +33 -0
  23. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  25. package/lib/typescript/commonjs/icons/ImageOverlayIcon.d.ts +14 -0
  26. package/lib/typescript/commonjs/icons/ImageOverlayIcon.d.ts.map +1 -0
  27. package/lib/typescript/commonjs/icons/index.d.ts +1 -0
  28. package/lib/typescript/commonjs/icons/index.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts +8 -3
  30. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/ui/components/index.d.ts +1 -1
  32. package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/utils/time/TickContext.d.ts +21 -0
  34. package/lib/typescript/commonjs/utils/time/TickContext.d.ts.map +1 -0
  35. package/lib/typescript/commonjs/utils/time/index.d.ts +2 -0
  36. package/lib/typescript/commonjs/utils/time/index.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/utils/time/useRelativeTime.d.ts +11 -0
  38. package/lib/typescript/commonjs/utils/time/useRelativeTime.d.ts.map +1 -0
  39. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  40. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  41. package/lib/typescript/module/icons/ImageOverlayIcon.d.ts +14 -0
  42. package/lib/typescript/module/icons/ImageOverlayIcon.d.ts.map +1 -0
  43. package/lib/typescript/module/icons/index.d.ts +1 -0
  44. package/lib/typescript/module/icons/index.d.ts.map +1 -1
  45. package/lib/typescript/module/ui/components/WindowControls.d.ts +8 -3
  46. package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
  47. package/lib/typescript/module/ui/components/index.d.ts +1 -1
  48. package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
  49. package/lib/typescript/module/utils/time/TickContext.d.ts +21 -0
  50. package/lib/typescript/module/utils/time/TickContext.d.ts.map +1 -0
  51. package/lib/typescript/module/utils/time/index.d.ts +2 -0
  52. package/lib/typescript/module/utils/time/index.d.ts.map +1 -1
  53. package/lib/typescript/module/utils/time/useRelativeTime.d.ts +11 -0
  54. package/lib/typescript/module/utils/time/useRelativeTime.d.ts.map +1 -0
  55. package/package.json +4 -4
@@ -131,9 +131,7 @@ const DataViewer = ({
131
131
  };
132
132
  exports.DataViewer = DataViewer;
133
133
  const styles = _reactNative.StyleSheet.create({
134
- container: {
135
- flex: 1
136
- },
134
+ container: {},
137
135
  header: {
138
136
  flexDirection: "row",
139
137
  justifyContent: "space-between",
@@ -60,7 +60,6 @@ const STABLE_STYLES = _reactNative.StyleSheet.create({
60
60
  borderRadius: 8,
61
61
  borderWidth: 1,
62
62
  borderColor: _index.gameUIColors.primary + "14" // border-white/[0.08]
63
- // Remove flex: 1 and minHeight to allow natural sizing
64
63
  },
65
64
  header: {
66
65
  flexDirection: "column",
@@ -888,7 +887,7 @@ const VirtualizedDataExplorer = ({
888
887
 
889
888
  // Simple keyExtractor without useCallback [[memory:4875251]]
890
889
  const keyExtractor = item => item.id;
891
- const hasData = data && (typeof data === "object" || Array.isArray(data)) && (Array.isArray(data) ? data.length > 0 : Object.keys(data).length > 0);
890
+ const hasData = data !== null && data !== undefined && (typeof data === "string" ? data.length > 0 : typeof data === "number" || typeof data === "boolean" ? true : Array.isArray(data) ? data.length > 0 : typeof data === "object" ? Object.keys(data).length > 0 : false);
892
891
 
893
892
  // Raw mode: render data directly without header/container
894
893
  if (rawMode) {
@@ -908,7 +907,7 @@ const VirtualizedDataExplorer = ({
908
907
  }
909
908
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
910
909
  style: {
911
- flex: 1
910
+ minHeight: isProcessing ? 40 : flatData.length * ITEM_HEIGHT
912
911
  },
913
912
  children: isProcessing ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
914
913
  style: {
@@ -1023,7 +1022,7 @@ const VirtualizedDataExplorer = ({
1023
1022
  })
1024
1023
  }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1025
1024
  style: {
1026
- height: Math.min(flatData.length * ITEM_HEIGHT, 400),
1025
+ height: flatData.length * ITEM_HEIGHT,
1027
1026
  position: "relative"
1028
1027
  },
1029
1028
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_IndentGuidesOverlay.IndentGuidesOverlay, {
@@ -7,7 +7,7 @@ exports.useNativeSafeAreaInsets = exports.safeAreaType = exports.hasSafeAreaPack
7
7
  /**
8
8
  * Auto-generated safe area implementation
9
9
  * Detected: none
10
- * Generated at: 2026-03-19T00:08:43.640Z
10
+ * Generated at: 2026-04-21T17:25:18.600Z
11
11
  *
12
12
  * DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
13
13
  *
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImageOverlayIcon = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _jsxRuntime = require("react/jsx-runtime");
9
+ /**
10
+ * ImageOverlayIcon - Icon for the Image Overlay dev tool
11
+ * Shows layered image frames with a mountain/sun motif,
12
+ * representing design mockup overlay functionality.
13
+ */
14
+ const ImageOverlayIcon = ({
15
+ size = 24,
16
+ color = "#A855F7",
17
+ glowColor
18
+ }) => {
19
+ const scale = size / 24;
20
+ const activeColor = color;
21
+ const activeGlow = glowColor || color;
22
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
23
+ style: {
24
+ width: size,
25
+ height: size,
26
+ position: "relative",
27
+ alignItems: "center",
28
+ justifyContent: "center"
29
+ },
30
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
31
+ style: {
32
+ position: "absolute",
33
+ width: 14 * scale,
34
+ height: 14 * scale,
35
+ borderRadius: 2 * scale,
36
+ borderWidth: 1.5 * scale,
37
+ borderColor: activeGlow,
38
+ backgroundColor: "transparent",
39
+ left: 2 * scale,
40
+ top: 2 * scale,
41
+ opacity: 0.25
42
+ }
43
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
44
+ style: {
45
+ position: "absolute",
46
+ width: 15 * scale,
47
+ height: 15 * scale,
48
+ borderRadius: 2.5 * scale,
49
+ backgroundColor: activeGlow,
50
+ left: 7 * scale,
51
+ top: 7 * scale,
52
+ opacity: 0.12
53
+ }
54
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
55
+ style: {
56
+ position: "absolute",
57
+ width: 14 * scale,
58
+ height: 14 * scale,
59
+ borderRadius: 2 * scale,
60
+ borderWidth: 1.5 * scale,
61
+ borderColor: activeColor,
62
+ backgroundColor: "transparent",
63
+ left: 8 * scale,
64
+ top: 8 * scale,
65
+ opacity: 0.9
66
+ }
67
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
68
+ style: {
69
+ position: "absolute",
70
+ width: 3 * scale,
71
+ height: 3 * scale,
72
+ borderRadius: 1.5 * scale,
73
+ backgroundColor: activeColor,
74
+ left: 11 * scale,
75
+ top: 11 * scale,
76
+ opacity: 0.8
77
+ }
78
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
79
+ style: {
80
+ position: "absolute",
81
+ width: 6 * scale,
82
+ height: 1.5 * scale,
83
+ backgroundColor: activeColor,
84
+ left: 9 * scale,
85
+ top: 18.5 * scale,
86
+ opacity: 0.7,
87
+ transform: [{
88
+ rotate: "-35deg"
89
+ }],
90
+ transformOrigin: "right center",
91
+ borderRadius: 0.5 * scale
92
+ }
93
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
94
+ style: {
95
+ position: "absolute",
96
+ width: 6 * scale,
97
+ height: 1.5 * scale,
98
+ backgroundColor: activeColor,
99
+ left: 14.5 * scale,
100
+ top: 18.5 * scale,
101
+ opacity: 0.7,
102
+ transform: [{
103
+ rotate: "35deg"
104
+ }],
105
+ transformOrigin: "left center",
106
+ borderRadius: 0.5 * scale
107
+ }
108
+ }), [{
109
+ x: 2,
110
+ y: 2
111
+ }, {
112
+ x: 16,
113
+ y: 2
114
+ }, {
115
+ x: 2,
116
+ y: 16
117
+ }].map((pos, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
118
+ style: {
119
+ position: "absolute",
120
+ width: 2 * scale,
121
+ height: 2 * scale,
122
+ borderRadius: 1 * scale,
123
+ backgroundColor: activeGlow,
124
+ left: (pos.x - 1) * scale,
125
+ top: (pos.y - 1) * scale,
126
+ opacity: 0.5
127
+ }
128
+ }, `dot-${i}`)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
129
+ style: {
130
+ position: "absolute",
131
+ width: 8 * scale,
132
+ height: 1.5 * scale,
133
+ borderRadius: 0.75 * scale,
134
+ backgroundColor: "#fff",
135
+ left: 11 * scale,
136
+ top: 8.5 * scale,
137
+ opacity: 0.15
138
+ }
139
+ })]
140
+ });
141
+ };
142
+ exports.ImageOverlayIcon = ImageOverlayIcon;
@@ -22,6 +22,7 @@ var _exportNames = {
22
22
  HighlightUpdatesIcon: true,
23
23
  RenderCountIcon: true,
24
24
  BenchmarkIcon: true,
25
+ ImageOverlayIcon: true,
25
26
  IconBackground: true
26
27
  };
27
28
  Object.defineProperty(exports, "BenchmarkIcon", {
@@ -60,6 +61,12 @@ Object.defineProperty(exports, "IconBackground", {
60
61
  return _IconBackground.IconBackground;
61
62
  }
62
63
  });
64
+ Object.defineProperty(exports, "ImageOverlayIcon", {
65
+ enumerable: true,
66
+ get: function () {
67
+ return _ImageOverlayIcon.ImageOverlayIcon;
68
+ }
69
+ });
63
70
  Object.defineProperty(exports, "LaptopIcon", {
64
71
  enumerable: true,
65
72
  get: function () {
@@ -160,6 +167,7 @@ var _RouteMapIcon = require("./RouteMapIcon.js");
160
167
  var _StackPulseIcon = require("./StackPulseIcon.js");
161
168
  var _RenderCountIcon = require("./RenderCountIcon.js");
162
169
  var _BenchmarkIcon = require("./BenchmarkIcon.js");
170
+ var _ImageOverlayIcon = require("./ImageOverlayIcon.js");
163
171
  var _IconBackground = require("./IconBackground.js");
164
172
  var _lucideIcons = require("./lucide-icons.js");
165
173
  Object.keys(_lucideIcons).forEach(function (key) {
@@ -82,8 +82,9 @@ const EventHistoryViewer = exports.EventHistoryViewer = /*#__PURE__*/(0, _react.
82
82
  diffDescription: diffViewDescription,
83
83
  diffIcon: diffViewIcon,
84
84
  diffDisabled: diffDisabled
85
- }), activeView === "current" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
85
+ }), activeView === "current" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
86
86
  style: styles.contentSection,
87
+ showsVerticalScrollIndicator: true,
87
88
  children: renderCurrentView()
88
89
  }), activeView === "diff" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
89
90
  style: styles.contentSection,
@@ -4,9 +4,25 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.WindowControls = WindowControls;
7
+ exports.setExpandableWindowControls = setExpandableWindowControls;
8
+ var _react = require("react");
7
9
  var _reactNative = require("react-native");
8
10
  var _index = require("../../icons/index.js");
9
11
  var _jsxRuntime = require("react/jsx-runtime");
12
+ // ============================================================================
13
+ // Global expandable setting — controlled via setExpandableWindowControls()
14
+ // ============================================================================
15
+
16
+ let _expandableEnabled = true; // Default ON
17
+
18
+ /**
19
+ * Set whether window controls use the expandable iPad-style behavior.
20
+ * Called by the floating-tools settings system when the setting changes.
21
+ */
22
+ function setExpandableWindowControls(enabled) {
23
+ _expandableEnabled = enabled;
24
+ }
25
+
10
26
  // ============================================================================
11
27
  // Types
12
28
  // ============================================================================
@@ -24,19 +40,28 @@ const COLORS = {
24
40
  toggleMode: "#28C840" // Green - expand/fullscreen button
25
41
  };
26
42
 
27
- // macOS-style circular button dimensions
28
- const BUTTON_SIZE = 12; // Small circular buttons
29
- const BUTTON_SPACING = 8; // Spacing between buttons
30
- const ICON_SIZE = 8; // Icon size inside the buttons
43
+ // Collapsed state (original small dots)
44
+ const BUTTON_SIZE = 12;
45
+ const BUTTON_SPACING = 8;
46
+ const ICON_SIZE = 8;
47
+
48
+ // Expanded state (large easy-to-tap buttons)
49
+ const EXPANDED_BUTTON_SIZE = 36;
50
+ const EXPANDED_BUTTON_SPACING = 12;
51
+ const EXPANDED_ICON_SIZE = 16;
52
+ const EXPANDED_PADDING = 8;
53
+
54
+ // Auto-dismiss timeout
55
+ const AUTO_DISMISS_MS = 3000;
31
56
 
32
57
  // ============================================================================
33
58
  // Component
34
59
  // ============================================================================
35
60
 
36
61
  /**
37
- * macOS-style window control buttons with Windows ordering.
38
- * Circular colored buttons inspired by macOS window controls.
39
- * Order (left to right): Minimize (yellow) Expand (green) → Close (red)
62
+ * iPad-style expandable window controls.
63
+ * Default: small macOS-style colored dots. On tap, expands to reveal
64
+ * large, easy-to-press buttons. Auto-dismisses after timeout or tap outside.
40
65
  */
41
66
  function WindowControls({
42
67
  onClose,
@@ -45,44 +70,236 @@ function WindowControls({
45
70
  mode
46
71
  }) {
47
72
  // Show action-based icon: what will happen when clicked
48
- // - If floating, show DockBottom (clicking will dock to bottom)
49
- // - If bottomSheet, show FloatWindow (clicking will make it float)
50
73
  const ToggleModeIcon = mode === "floating" ? _index.DockBottom : _index.FloatWindow;
51
74
  const toggleModeLabel = mode === "floating" ? "Dock to bottom sheet" : "Make floating window";
52
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
53
- style: styles.container,
54
- children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
55
- onPress: onMinimize,
56
- style: [styles.button, styles.minimizeButton],
57
- activeOpacity: 0.8,
58
- accessibilityLabel: "Minimize modal",
59
- accessibilityRole: "button",
60
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
61
- size: ICON_SIZE,
62
- color: "#7A5A00",
63
- strokeWidth: 1.5
64
- })
65
- }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
66
- onPress: onToggleMode,
67
- style: [styles.button, styles.expandButton],
68
- activeOpacity: 0.8,
69
- accessibilityLabel: toggleModeLabel,
75
+
76
+ // When expandable is disabled, render original directly-tappable buttons
77
+ if (!_expandableEnabled) {
78
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
79
+ style: styles.container,
80
+ children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
81
+ onPress: onMinimize,
82
+ style: [styles.dot, styles.minimizeDot],
83
+ activeOpacity: 0.8,
84
+ accessibilityLabel: "Minimize modal",
85
+ accessibilityRole: "button",
86
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
87
+ size: ICON_SIZE,
88
+ color: "#7A5A00",
89
+ strokeWidth: 1.5
90
+ })
91
+ }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
92
+ onPress: onToggleMode,
93
+ style: [styles.dot, styles.expandDot],
94
+ activeOpacity: 0.8,
95
+ accessibilityLabel: toggleModeLabel,
96
+ accessibilityRole: "button",
97
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
98
+ size: ICON_SIZE,
99
+ color: "#004A1A",
100
+ strokeWidth: 1.5
101
+ })
102
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
103
+ onPress: onClose,
104
+ style: [styles.dot, styles.closeDot],
105
+ activeOpacity: 0.8,
106
+ accessibilityLabel: "Close modal",
107
+ accessibilityRole: "button",
108
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
109
+ size: ICON_SIZE,
110
+ color: "#4A0000",
111
+ strokeWidth: 1.5
112
+ })
113
+ })]
114
+ });
115
+ }
116
+
117
+ // On real devices, use iPad-style expandable controls
118
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpandableWindowControls, {
119
+ onClose: onClose,
120
+ onMinimize: onMinimize,
121
+ onToggleMode: onToggleMode,
122
+ mode: mode
123
+ });
124
+ }
125
+ function ExpandableWindowControls({
126
+ onClose,
127
+ onMinimize,
128
+ onToggleMode,
129
+ mode
130
+ }) {
131
+ const [expanded, setExpanded] = (0, _react.useState)(false);
132
+ const [triggerLayout, setTriggerLayout] = (0, _react.useState)(null);
133
+ const expandAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
134
+ const dismissTimeout = (0, _react.useRef)(null);
135
+ const triggerRef = (0, _react.useRef)(null);
136
+ const ToggleModeIcon = mode === "floating" ? _index.DockBottom : _index.FloatWindow;
137
+ const toggleModeLabel = mode === "floating" ? "Dock to bottom sheet" : "Make floating window";
138
+ const clearDismissTimer = (0, _react.useCallback)(() => {
139
+ if (dismissTimeout.current) {
140
+ clearTimeout(dismissTimeout.current);
141
+ dismissTimeout.current = null;
142
+ }
143
+ }, []);
144
+ const collapse = (0, _react.useCallback)(() => {
145
+ clearDismissTimer();
146
+ _reactNative.Animated.spring(expandAnim, {
147
+ toValue: 0,
148
+ tension: 200,
149
+ friction: 20,
150
+ useNativeDriver: true
151
+ }).start(() => {
152
+ setExpanded(false);
153
+ });
154
+ }, [expandAnim, clearDismissTimer]);
155
+ const expand = (0, _react.useCallback)(() => {
156
+ // Measure trigger position on screen before expanding
157
+ triggerRef.current?.measureInWindow((x, y, width, height) => {
158
+ setTriggerLayout({
159
+ x,
160
+ y,
161
+ width,
162
+ height
163
+ });
164
+ setExpanded(true);
165
+ _reactNative.Animated.spring(expandAnim, {
166
+ toValue: 1,
167
+ tension: 180,
168
+ friction: 18,
169
+ useNativeDriver: true
170
+ }).start();
171
+
172
+ // Auto-dismiss
173
+ clearDismissTimer();
174
+ dismissTimeout.current = setTimeout(collapse, AUTO_DISMISS_MS);
175
+ });
176
+ }, [expandAnim, collapse, clearDismissTimer]);
177
+ (0, _react.useEffect)(() => {
178
+ return clearDismissTimer;
179
+ }, [clearDismissTimer]);
180
+ const handleAction = (0, _react.useCallback)(action => {
181
+ collapse();
182
+ action();
183
+ }, [collapse]);
184
+
185
+ // Animation interpolations
186
+ const scale = expandAnim.interpolate({
187
+ inputRange: [0, 1],
188
+ outputRange: [0.3, 1]
189
+ });
190
+ const opacity = expandAnim.interpolate({
191
+ inputRange: [0, 0.5, 1],
192
+ outputRange: [0, 0.8, 1]
193
+ });
194
+
195
+ // Count visible buttons for layout calculation
196
+ const buttonCount = 1 + (onMinimize ? 1 : 0) + (onToggleMode ? 1 : 0);
197
+ const expandedWidth = buttonCount * EXPANDED_BUTTON_SIZE + (buttonCount - 1) * EXPANDED_BUTTON_SPACING + EXPANDED_PADDING * 2;
198
+ const expandedHeight = EXPANDED_BUTTON_SIZE + EXPANDED_PADDING * 2;
199
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
200
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
201
+ ref: triggerRef,
202
+ onPress: expand,
203
+ activeOpacity: 0.7,
204
+ accessibilityLabel: "Open window controls",
70
205
  accessibilityRole: "button",
71
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
72
- size: ICON_SIZE,
73
- color: "#004A1A",
74
- strokeWidth: 1.5
206
+ style: styles.trigger,
207
+ hitSlop: {
208
+ top: 8,
209
+ bottom: 8,
210
+ left: 8,
211
+ right: 8
212
+ },
213
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
214
+ style: styles.container,
215
+ children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
216
+ style: [styles.dot, styles.minimizeDot],
217
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
218
+ size: ICON_SIZE,
219
+ color: "#7A5A00",
220
+ strokeWidth: 1.5
221
+ })
222
+ }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
223
+ style: [styles.dot, styles.expandDot],
224
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
225
+ size: ICON_SIZE,
226
+ color: "#004A1A",
227
+ strokeWidth: 1.5
228
+ })
229
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
230
+ style: [styles.dot, styles.closeDot],
231
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
232
+ size: ICON_SIZE,
233
+ color: "#4A0000",
234
+ strokeWidth: 1.5
235
+ })
236
+ })]
75
237
  })
76
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
77
- onPress: onClose,
78
- style: [styles.button, styles.closeButton],
79
- activeOpacity: 0.8,
80
- accessibilityLabel: "Close modal",
81
- accessibilityRole: "button",
82
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
83
- size: ICON_SIZE,
84
- color: "#4A0000",
85
- strokeWidth: 1.5
238
+ }), expanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
239
+ transparent: true,
240
+ visible: true,
241
+ statusBarTranslucent: true,
242
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
243
+ onPress: collapse,
244
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
245
+ style: styles.overlay,
246
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
247
+ style: [styles.expandedPanel, {
248
+ transform: [{
249
+ scale
250
+ }],
251
+ opacity,
252
+ // Position the expanded panel anchored to trigger's top-right
253
+ top: triggerLayout ? triggerLayout.y - EXPANDED_PADDING : 0,
254
+ right: triggerLayout ? Math.max(4,
255
+ // screen width minus trigger's right edge
256
+ // We don't need Dimensions here since we use measureInWindow
257
+ // which gives absolute coords. right = screenWidth - (x + width)
258
+ 0) : 0,
259
+ // Position using left instead, anchored to trigger's right edge
260
+ left: triggerLayout ? triggerLayout.x + triggerLayout.width - expandedWidth : undefined
261
+ }],
262
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
263
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
264
+ style: styles.expandedContainer,
265
+ children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
266
+ onPress: () => handleAction(onMinimize),
267
+ style: [styles.expandedButton, styles.minimizeButtonExpanded],
268
+ activeOpacity: 0.7,
269
+ accessibilityLabel: "Minimize modal",
270
+ accessibilityRole: "button",
271
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
272
+ size: EXPANDED_ICON_SIZE,
273
+ color: "#7A5A00",
274
+ strokeWidth: 2
275
+ })
276
+ }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
277
+ onPress: () => handleAction(onToggleMode),
278
+ style: [styles.expandedButton, styles.expandButtonExpanded],
279
+ activeOpacity: 0.7,
280
+ accessibilityLabel: toggleModeLabel,
281
+ accessibilityRole: "button",
282
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
283
+ size: EXPANDED_ICON_SIZE,
284
+ color: "#004A1A",
285
+ strokeWidth: 2
286
+ })
287
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
288
+ onPress: () => handleAction(onClose),
289
+ style: [styles.expandedButton, styles.closeButtonExpanded],
290
+ activeOpacity: 0.7,
291
+ accessibilityLabel: "Close modal",
292
+ accessibilityRole: "button",
293
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
294
+ size: EXPANDED_ICON_SIZE,
295
+ color: "#4A0000",
296
+ strokeWidth: 2
297
+ })
298
+ })]
299
+ })
300
+ })
301
+ })
302
+ })
86
303
  })
87
304
  })]
88
305
  });
@@ -93,26 +310,70 @@ function WindowControls({
93
310
  // ============================================================================
94
311
 
95
312
  const styles = _reactNative.StyleSheet.create({
313
+ trigger: {
314
+ padding: 2
315
+ },
96
316
  container: {
97
317
  flexDirection: "row",
98
318
  alignItems: "center",
99
319
  gap: BUTTON_SPACING
100
320
  },
101
- button: {
321
+ dot: {
102
322
  width: BUTTON_SIZE,
103
323
  height: BUTTON_SIZE,
104
324
  borderRadius: BUTTON_SIZE / 2,
105
- // Perfectly circular
106
325
  alignItems: "center",
107
326
  justifyContent: "center"
108
327
  },
109
- closeButton: {
328
+ closeDot: {
329
+ backgroundColor: COLORS.close
330
+ },
331
+ minimizeDot: {
332
+ backgroundColor: COLORS.minimize
333
+ },
334
+ expandDot: {
335
+ backgroundColor: COLORS.toggleMode
336
+ },
337
+ overlay: {
338
+ flex: 1
339
+ },
340
+ expandedPanel: {
341
+ position: "absolute",
342
+ zIndex: 9999
343
+ },
344
+ expandedContainer: {
345
+ flexDirection: "row",
346
+ alignItems: "center",
347
+ gap: EXPANDED_BUTTON_SPACING,
348
+ backgroundColor: "rgba(16, 22, 35, 0.95)",
349
+ borderRadius: (EXPANDED_BUTTON_SIZE + EXPANDED_PADDING * 2) / 2,
350
+ paddingHorizontal: EXPANDED_PADDING,
351
+ paddingVertical: EXPANDED_PADDING,
352
+ borderWidth: 1,
353
+ borderColor: "rgba(0, 184, 230, 0.3)",
354
+ shadowColor: "#000",
355
+ shadowOffset: {
356
+ width: 0,
357
+ height: 4
358
+ },
359
+ shadowOpacity: 0.4,
360
+ shadowRadius: 12,
361
+ elevation: 30
362
+ },
363
+ expandedButton: {
364
+ width: EXPANDED_BUTTON_SIZE,
365
+ height: EXPANDED_BUTTON_SIZE,
366
+ borderRadius: EXPANDED_BUTTON_SIZE / 2,
367
+ alignItems: "center",
368
+ justifyContent: "center"
369
+ },
370
+ closeButtonExpanded: {
110
371
  backgroundColor: COLORS.close
111
372
  },
112
- minimizeButton: {
373
+ minimizeButtonExpanded: {
113
374
  backgroundColor: COLORS.minimize
114
375
  },
115
- expandButton: {
376
+ expandButtonExpanded: {
116
377
  backgroundColor: COLORS.toggleMode
117
378
  }
118
379
  });
@@ -327,6 +327,12 @@ Object.defineProperty(exports, "WindowControls", {
327
327
  return _WindowControls.WindowControls;
328
328
  }
329
329
  });
330
+ Object.defineProperty(exports, "setExpandableWindowControls", {
331
+ enumerable: true,
332
+ get: function () {
333
+ return _WindowControls.setExpandableWindowControls;
334
+ }
335
+ });
330
336
  var _BackButton = require("./BackButton.js");
331
337
  var _ValueTypeBadge = require("./ValueTypeBadge.js");
332
338
  var _StorageTypeBadge = require("./StorageTypeBadge.js");