@buoy-gg/floating-tools-core 2.1.9 → 2.1.11

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 (110) hide show
  1. package/lib/commonjs/FloatingToolsStore.js +501 -1
  2. package/lib/commonjs/colors.js +54 -1
  3. package/lib/commonjs/constants.js +31 -1
  4. package/lib/commonjs/devToolsState.js +325 -1
  5. package/lib/commonjs/dial.js +617 -1
  6. package/lib/commonjs/easing.js +69 -1
  7. package/lib/commonjs/icons/benchmark-icon.js +24 -1
  8. package/lib/commonjs/icons/env-icon.js +24 -1
  9. package/lib/commonjs/icons/events-icon.js +24 -1
  10. package/lib/commonjs/icons/highlight-icon.js +24 -1
  11. package/lib/commonjs/icons/icon-data.js +2268 -1
  12. package/lib/commonjs/icons/icon-factories.js +173 -1
  13. package/lib/commonjs/icons/icon-primitives.js +559 -1
  14. package/lib/commonjs/icons/icon-primitives.native.js +779 -1
  15. package/lib/commonjs/icons/icon-renderer.js +260 -1
  16. package/lib/commonjs/icons/network-icon.js +24 -1
  17. package/lib/commonjs/icons/query-icon.js +24 -1
  18. package/lib/commonjs/icons/redux-icon.js +85 -1
  19. package/lib/commonjs/icons/renders-icon.js +33 -1
  20. package/lib/commonjs/icons/routes-icon.js +49 -1
  21. package/lib/commonjs/icons/sentry-icon.js +24 -1
  22. package/lib/commonjs/icons/storage-icon.js +24 -1
  23. package/lib/commonjs/icons/wifi-icon.js +24 -1
  24. package/lib/commonjs/index.js +723 -1
  25. package/lib/commonjs/settings.js +599 -1
  26. package/lib/commonjs/utils.js +72 -1
  27. package/lib/module/FloatingToolsStore.js +496 -1
  28. package/lib/module/colors.js +49 -1
  29. package/lib/module/constants.js +27 -1
  30. package/lib/module/devToolsState.js +318 -1
  31. package/lib/module/dial.js +603 -1
  32. package/lib/module/easing.js +62 -1
  33. package/lib/module/icons/benchmark-icon.js +15 -1
  34. package/lib/module/icons/env-icon.js +15 -1
  35. package/lib/module/icons/events-icon.js +15 -1
  36. package/lib/module/icons/highlight-icon.js +15 -1
  37. package/lib/module/icons/icon-data.js +2264 -1
  38. package/lib/module/icons/icon-factories.js +163 -1
  39. package/lib/module/icons/icon-primitives.js +547 -1
  40. package/lib/module/icons/icon-primitives.native.js +767 -1
  41. package/lib/module/icons/icon-renderer.js +255 -1
  42. package/lib/module/icons/network-icon.js +15 -1
  43. package/lib/module/icons/query-icon.js +15 -1
  44. package/lib/module/icons/redux-icon.js +81 -1
  45. package/lib/module/icons/renders-icon.js +17 -1
  46. package/lib/module/icons/routes-icon.js +41 -1
  47. package/lib/module/icons/sentry-icon.js +15 -1
  48. package/lib/module/icons/storage-icon.js +15 -1
  49. package/lib/module/icons/wifi-icon.js +15 -1
  50. package/lib/module/index.js +103 -1
  51. package/lib/module/settings.js +587 -1
  52. package/lib/module/utils.js +66 -1
  53. package/lib/typescript/commonjs/FloatingToolsStore.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/colors.d.ts.map +1 -0
  55. package/lib/typescript/commonjs/constants.d.ts.map +1 -0
  56. package/lib/typescript/commonjs/devToolsState.d.ts.map +1 -0
  57. package/lib/typescript/commonjs/dial.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/easing.d.ts.map +1 -0
  59. package/lib/typescript/commonjs/icons/benchmark-icon.d.ts.map +1 -0
  60. package/lib/typescript/commonjs/icons/env-icon.d.ts.map +1 -0
  61. package/lib/typescript/commonjs/icons/events-icon.d.ts.map +1 -0
  62. package/lib/typescript/commonjs/icons/highlight-icon.d.ts.map +1 -0
  63. package/lib/typescript/commonjs/icons/icon-data.d.ts.map +1 -0
  64. package/lib/typescript/commonjs/icons/icon-factories.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/icons/icon-primitives.d.ts.map +1 -0
  66. package/lib/typescript/commonjs/icons/icon-primitives.native.d.ts.map +1 -0
  67. package/lib/typescript/commonjs/icons/icon-renderer.d.ts.map +1 -0
  68. package/lib/typescript/commonjs/icons/network-icon.d.ts.map +1 -0
  69. package/lib/typescript/commonjs/icons/query-icon.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/icons/redux-icon.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/icons/renders-icon.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/icons/routes-icon.d.ts.map +1 -0
  73. package/lib/typescript/commonjs/icons/sentry-icon.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/icons/storage-icon.d.ts.map +1 -0
  75. package/lib/typescript/commonjs/icons/wifi-icon.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  77. package/lib/typescript/commonjs/settings.d.ts +5 -0
  78. package/lib/typescript/commonjs/settings.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/types.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/utils.d.ts.map +1 -0
  81. package/lib/typescript/module/FloatingToolsStore.d.ts.map +1 -0
  82. package/lib/typescript/module/colors.d.ts.map +1 -0
  83. package/lib/typescript/module/constants.d.ts.map +1 -0
  84. package/lib/typescript/module/devToolsState.d.ts.map +1 -0
  85. package/lib/typescript/module/dial.d.ts.map +1 -0
  86. package/lib/typescript/module/easing.d.ts.map +1 -0
  87. package/lib/typescript/module/icons/benchmark-icon.d.ts.map +1 -0
  88. package/lib/typescript/module/icons/env-icon.d.ts.map +1 -0
  89. package/lib/typescript/module/icons/events-icon.d.ts.map +1 -0
  90. package/lib/typescript/module/icons/highlight-icon.d.ts.map +1 -0
  91. package/lib/typescript/module/icons/icon-data.d.ts.map +1 -0
  92. package/lib/typescript/module/icons/icon-factories.d.ts.map +1 -0
  93. package/lib/typescript/module/icons/icon-primitives.d.ts.map +1 -0
  94. package/lib/typescript/module/icons/icon-primitives.native.d.ts.map +1 -0
  95. package/lib/typescript/module/icons/icon-renderer.d.ts.map +1 -0
  96. package/lib/typescript/module/icons/network-icon.d.ts.map +1 -0
  97. package/lib/typescript/module/icons/query-icon.d.ts.map +1 -0
  98. package/lib/typescript/module/icons/redux-icon.d.ts.map +1 -0
  99. package/lib/typescript/module/icons/renders-icon.d.ts.map +1 -0
  100. package/lib/typescript/module/icons/routes-icon.d.ts.map +1 -0
  101. package/lib/typescript/module/icons/sentry-icon.d.ts.map +1 -0
  102. package/lib/typescript/module/icons/storage-icon.d.ts.map +1 -0
  103. package/lib/typescript/module/icons/wifi-icon.d.ts.map +1 -0
  104. package/lib/typescript/module/index.d.ts.map +1 -0
  105. package/lib/typescript/module/settings.d.ts +5 -0
  106. package/lib/typescript/module/settings.d.ts.map +1 -0
  107. package/lib/typescript/module/types.d.ts.map +1 -0
  108. package/lib/typescript/module/utils.d.ts.map +1 -0
  109. package/package.json +8 -8
  110. package/LICENSE +0 -58
@@ -1 +1,496 @@
1
- "use strict";import{VISIBLE_HANDLE_WIDTH,DRAG_THRESHOLD,EDGE_PADDING,SAVE_DEBOUNCE_MS,STORAGE_KEYS}from"./constants.js";export class FloatingToolsStore{isDragging=!1;isHidden=!1;dragStartPos={x:0,y:0};dragStartMouse={x:0,y:0};hasMoved=!1;savedPosition=null;saveTimeout=null;isInitialized=!1;listeners=new Set;constructor(i){this.screenWidth=i.screenWidth,this.screenHeight=i.screenHeight,this.bubbleSize=i.initialBubbleSize??{width:100,height:32},this.minPosition=i.minPosition??{x:EDGE_PADDING,y:EDGE_PADDING},this.visibleHandleWidth=i.visibleHandleWidth??VISIBLE_HANDLE_WIDTH,this.dragThreshold=i.dragThreshold??DRAG_THRESHOLD,this.edgePadding=i.edgePadding??EDGE_PADDING,this.enablePositionPersistence=i.enablePositionPersistence??!0,this.storage=i.storage,this.onPositionChange=i.onPositionChange,this.onHiddenChange=i.onHiddenChange,this.onDraggingChange=i.onDraggingChange,this.position=i.initialPosition??{x:this.screenWidth-this.bubbleSize.width-20,y:Math.max(this.minPosition.y,Math.min(100,this.screenHeight-this.bubbleSize.height-this.edgePadding))},this.stateSnapshot=this.createSnapshot()}subscribe=i=>(this.listeners.add(i),()=>this.listeners.delete(i));getSnapshot=()=>this.stateSnapshot;createSnapshot(){return{position:{...this.position},isDragging:this.isDragging,isHidden:this.isHidden,bubbleSize:{...this.bubbleSize}}}notify(){this.stateSnapshot=this.createSnapshot(),this.listeners.forEach(i=>i())}async initialize(){if(!this.isInitialized&&this.enablePositionPersistence&&this.storage){try{const[i,t]=await Promise.all([this.storage.getItem(STORAGE_KEYS.POSITION_X),this.storage.getItem(STORAGE_KEYS.POSITION_Y)]);if(null!==i&&null!==t){const s=parseFloat(i),e=parseFloat(t);if(!Number.isNaN(s)&&!Number.isNaN(e)){const i={x:s,y:e},t=this.validatePosition(i);(Math.abs(i.x-t.x)>5||Math.abs(i.y-t.y)>5)&&this.savePosition(t.x,t.y),this.position=t,t.x>=this.screenWidth-this.visibleHandleWidth-5&&(this.isHidden=!0),this.notify()}}}catch{}this.isInitialized=!0}else this.isInitialized=!0}getBounds(){return{minX:this.minPosition.x,maxX:this.screenWidth-this.visibleHandleWidth,minY:this.minPosition.y,maxY:this.screenHeight-this.bubbleSize.height-this.edgePadding}}validatePosition(i){const t=this.getBounds();return{x:Math.max(t.minX,Math.min(i.x,t.maxX)),y:Math.max(t.minY,Math.min(i.y,t.maxY))}}setPosition(i){this.position=i,this.onPositionChange?.(i),this.notify()}setBubbleSize(i){this.bubbleSize.width===i.width&&this.bubbleSize.height===i.height||(this.bubbleSize=i,this.notify())}setScreenSize(i,t){this.screenWidth=i,this.screenHeight=t;const s=this.validatePosition(this.position);s.x===this.position.x&&s.y===this.position.y||(this.position=s,this.notify())}setMinPosition(i){this.minPosition=i}async savePosition(i,t){if(this.enablePositionPersistence&&this.storage)try{await Promise.all([this.storage.setItem(STORAGE_KEYS.POSITION_X,i.toString()),this.storage.setItem(STORAGE_KEYS.POSITION_Y,t.toString())])}catch{}}debouncedSavePosition(i,t){this.saveTimeout&&clearTimeout(this.saveTimeout),this.saveTimeout=setTimeout(()=>this.savePosition(i,t),SAVE_DEBOUNCE_MS)}toggleHideShow(){const i=this.position.x>this.screenWidth-this.bubbleSize.width/2;if(this.isHidden||i){let i,t;return this.savedPosition&&this.savedPosition.x<this.screenWidth-this.bubbleSize.width/2?(i=this.savedPosition.x,t=this.savedPosition.y):(i=this.screenWidth-this.bubbleSize.width-20,t=this.position.y),this.isHidden=!1,this.onHiddenChange?.(!1),this.notify(),{targetPosition:{x:i,y:t},isHiding:!1}}{this.savedPosition={...this.position};const i=this.screenWidth-this.visibleHandleWidth;return this.isHidden=!0,this.onHiddenChange?.(!0),this.notify(),{targetPosition:{x:i,y:this.position.y},isHiding:!0}}}forceHide(){if(this.isHidden)return{targetPosition:this.position};this.savedPosition={...this.position};const i=this.screenWidth-this.visibleHandleWidth;return this.isHidden=!0,this.onHiddenChange?.(!0),this.notify(),{targetPosition:{x:i,y:this.position.y}}}forceShow(){if(!this.isHidden)return{targetPosition:this.position};let i,t;return this.savedPosition?(i=this.savedPosition.x,t=this.savedPosition.y):(i=this.screenWidth-this.bubbleSize.width-20,t=this.position.y),this.isHidden=!1,this.onHiddenChange?.(!1),this.notify(),{targetPosition:{x:i,y:t}}}handleDragStart(i){this.hasMoved=!1,this.dragStartPos={...this.position},this.dragStartMouse={x:i.clientX,y:i.clientY}}handleDragMove(i){const t=i.clientX-this.dragStartMouse.x,s=i.clientY-this.dragStartMouse.y,e=Math.abs(t)+Math.abs(s);if(!this.hasMoved&&e>this.dragThreshold&&(this.hasMoved=!0,this.isDragging=!0,this.onDraggingChange?.(!0),this.notify()),this.hasMoved){const i=this.dragStartPos.x+t,e=this.dragStartPos.y+s,h=this.getBounds(),n=Math.max(h.minX,Math.min(i,this.screenWidth-this.visibleHandleWidth+this.bubbleSize.width)),o=Math.max(h.minY,Math.min(e,h.maxY));return this.position={x:n,y:o},this.onPositionChange?.(this.position),this.notify(),{position:this.position,isDragging:!0}}return{position:this.position,isDragging:!1}}handleDragEnd(i){if(!this.hasMoved)return this.isDragging=!1,this.onDraggingChange?.(!1),this.notify(),{position:this.position,shouldHide:!1,wasTap:!0};const t=i.clientX-this.dragStartMouse.x,s=i.clientY-this.dragStartMouse.y,e=this.dragStartPos.x+t,h=this.dragStartPos.y+s,n=e+this.bubbleSize.width/2>this.screenWidth,o=this.getBounds();if(n){const i=this.screenWidth-this.visibleHandleWidth,t=Math.max(o.minY,Math.min(h,o.maxY));return this.isHidden=!0,this.onHiddenChange?.(!0),this.isDragging=!1,this.onDraggingChange?.(!1),this.notify(),{position:{x:i,y:t},shouldHide:!0,wasTap:!1}}{this.isHidden&&e<this.screenWidth-this.visibleHandleWidth-10&&(this.isHidden=!1,this.onHiddenChange?.(!1));const i=Math.max(o.minX,Math.min(e,this.screenWidth-this.bubbleSize.width-this.edgePadding)),t=Math.max(o.minY,Math.min(h,o.maxY));return i<this.screenWidth-this.bubbleSize.width/2&&(this.savedPosition={x:i,y:t}),this.position={x:i,y:t},this.isDragging=!1,this.onDraggingChange?.(!1),this.debouncedSavePosition(i,t),this.notify(),{position:{x:i,y:t},shouldHide:!1,wasTap:!1}}}commitPosition(i){this.position=i,this.savePosition(i.x,i.y),this.notify()}destroy(){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=null),this.listeners.clear()}}
1
+ "use strict";
2
+
3
+ /**
4
+ * FloatingToolsStore - Headless state management for floating tools.
5
+ *
6
+ * This class manages all the business logic for the floating tools bubble:
7
+ * - Position state and validation
8
+ * - Drag detection (tap vs drag)
9
+ * - Hide/show toggle
10
+ * - Auto-hide when dragged past edge
11
+ * - Position persistence
12
+ *
13
+ * Platform-specific code (animation, event handling) is NOT included here.
14
+ * Use this with platform-specific hooks/bindings.
15
+ */
16
+
17
+ import { VISIBLE_HANDLE_WIDTH, DRAG_THRESHOLD, EDGE_PADDING, SAVE_DEBOUNCE_MS, STORAGE_KEYS } from "./constants.js";
18
+ export class FloatingToolsStore {
19
+ // State
20
+
21
+ isDragging = false;
22
+ isHidden = false;
23
+
24
+ // Configuration
25
+
26
+ // Callbacks
27
+
28
+ // Drag tracking
29
+ dragStartPos = {
30
+ x: 0,
31
+ y: 0
32
+ };
33
+ dragStartMouse = {
34
+ x: 0,
35
+ y: 0
36
+ };
37
+ hasMoved = false;
38
+ savedPosition = null;
39
+
40
+ // Persistence
41
+ saveTimeout = null;
42
+ isInitialized = false;
43
+
44
+ // Subscribers (for useSyncExternalStore)
45
+ listeners = new Set();
46
+ constructor(options) {
47
+ this.screenWidth = options.screenWidth;
48
+ this.screenHeight = options.screenHeight;
49
+ this.bubbleSize = options.initialBubbleSize ?? {
50
+ width: 100,
51
+ height: 32
52
+ };
53
+ this.minPosition = options.minPosition ?? {
54
+ x: EDGE_PADDING,
55
+ y: EDGE_PADDING
56
+ };
57
+ this.visibleHandleWidth = options.visibleHandleWidth ?? VISIBLE_HANDLE_WIDTH;
58
+ this.dragThreshold = options.dragThreshold ?? DRAG_THRESHOLD;
59
+ this.edgePadding = options.edgePadding ?? EDGE_PADDING;
60
+ this.enablePositionPersistence = options.enablePositionPersistence ?? true;
61
+ this.storage = options.storage;
62
+ this.onPositionChange = options.onPositionChange;
63
+ this.onHiddenChange = options.onHiddenChange;
64
+ this.onDraggingChange = options.onDraggingChange;
65
+
66
+ // Default position
67
+ this.position = options.initialPosition ?? {
68
+ x: this.screenWidth - this.bubbleSize.width - 20,
69
+ y: Math.max(this.minPosition.y, Math.min(100, this.screenHeight - this.bubbleSize.height - this.edgePadding))
70
+ };
71
+
72
+ // Initialize snapshot
73
+ this.stateSnapshot = this.createSnapshot();
74
+ }
75
+
76
+ // =============================
77
+ // Subscription (for React hooks)
78
+ // =============================
79
+
80
+ subscribe = listener => {
81
+ this.listeners.add(listener);
82
+ return () => this.listeners.delete(listener);
83
+ };
84
+ getSnapshot = () => {
85
+ return this.stateSnapshot;
86
+ };
87
+ createSnapshot() {
88
+ return {
89
+ position: {
90
+ ...this.position
91
+ },
92
+ isDragging: this.isDragging,
93
+ isHidden: this.isHidden,
94
+ bubbleSize: {
95
+ ...this.bubbleSize
96
+ }
97
+ };
98
+ }
99
+ notify() {
100
+ this.stateSnapshot = this.createSnapshot();
101
+ this.listeners.forEach(listener => listener());
102
+ }
103
+
104
+ // =============================
105
+ // Initialization
106
+ // =============================
107
+
108
+ /**
109
+ * Initialize the store by loading persisted position.
110
+ * Call this after setting up storage adapter.
111
+ */
112
+ async initialize() {
113
+ if (this.isInitialized || !this.enablePositionPersistence || !this.storage) {
114
+ this.isInitialized = true;
115
+ return;
116
+ }
117
+ try {
118
+ const [xStr, yStr] = await Promise.all([this.storage.getItem(STORAGE_KEYS.POSITION_X), this.storage.getItem(STORAGE_KEYS.POSITION_Y)]);
119
+ if (xStr !== null && yStr !== null) {
120
+ const x = parseFloat(xStr);
121
+ const y = parseFloat(yStr);
122
+ if (!Number.isNaN(x) && !Number.isNaN(y)) {
123
+ const saved = {
124
+ x,
125
+ y
126
+ };
127
+ const validated = this.validatePosition(saved);
128
+
129
+ // Save corrected position if out of bounds
130
+ if (Math.abs(saved.x - validated.x) > 5 || Math.abs(saved.y - validated.y) > 5) {
131
+ this.savePosition(validated.x, validated.y);
132
+ }
133
+ this.position = validated;
134
+
135
+ // Check if loaded in hidden state
136
+ if (validated.x >= this.screenWidth - this.visibleHandleWidth - 5) {
137
+ this.isHidden = true;
138
+ }
139
+ this.notify();
140
+ }
141
+ }
142
+ } catch {
143
+ // Failed to load - use default position
144
+ }
145
+ this.isInitialized = true;
146
+ }
147
+
148
+ // =============================
149
+ // Position Management
150
+ // =============================
151
+
152
+ /**
153
+ * Get current bounds for position validation.
154
+ */
155
+ getBounds() {
156
+ return {
157
+ minX: this.minPosition.x,
158
+ maxX: this.screenWidth - this.visibleHandleWidth,
159
+ minY: this.minPosition.y,
160
+ maxY: this.screenHeight - this.bubbleSize.height - this.edgePadding
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Validate and clamp a position to screen boundaries.
166
+ */
167
+ validatePosition(pos) {
168
+ const bounds = this.getBounds();
169
+ return {
170
+ x: Math.max(bounds.minX, Math.min(pos.x, bounds.maxX)),
171
+ y: Math.max(bounds.minY, Math.min(pos.y, bounds.maxY))
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Set position directly (for animation callbacks).
177
+ */
178
+ setPosition(pos) {
179
+ this.position = pos;
180
+ this.onPositionChange?.(pos);
181
+ this.notify();
182
+ }
183
+
184
+ /**
185
+ * Update bubble size (call on layout).
186
+ * Only notifies if size actually changed.
187
+ */
188
+ setBubbleSize(size) {
189
+ if (this.bubbleSize.width === size.width && this.bubbleSize.height === size.height) {
190
+ return;
191
+ }
192
+ this.bubbleSize = size;
193
+ this.notify();
194
+ }
195
+
196
+ /**
197
+ * Update screen dimensions (call on resize).
198
+ */
199
+ setScreenSize(width, height) {
200
+ this.screenWidth = width;
201
+ this.screenHeight = height;
202
+ // Re-validate position
203
+ const validated = this.validatePosition(this.position);
204
+ if (validated.x !== this.position.x || validated.y !== this.position.y) {
205
+ this.position = validated;
206
+ this.notify();
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Update min position (e.g., for safe area insets).
212
+ */
213
+ setMinPosition(minPos) {
214
+ this.minPosition = minPos;
215
+ }
216
+
217
+ // =============================
218
+ // Persistence
219
+ // =============================
220
+
221
+ async savePosition(x, y) {
222
+ if (!this.enablePositionPersistence || !this.storage) return;
223
+ try {
224
+ await Promise.all([this.storage.setItem(STORAGE_KEYS.POSITION_X, x.toString()), this.storage.setItem(STORAGE_KEYS.POSITION_Y, y.toString())]);
225
+ } catch {
226
+ // Failed to save - continue without persistence
227
+ }
228
+ }
229
+ debouncedSavePosition(x, y) {
230
+ if (this.saveTimeout) clearTimeout(this.saveTimeout);
231
+ this.saveTimeout = setTimeout(() => this.savePosition(x, y), SAVE_DEBOUNCE_MS);
232
+ }
233
+
234
+ // =============================
235
+ // Hide/Show Toggle
236
+ // =============================
237
+
238
+ /**
239
+ * Toggle between hidden and visible states.
240
+ * Returns the target position for animation.
241
+ */
242
+ toggleHideShow() {
243
+ const isVisuallyOffScreen = this.position.x > this.screenWidth - this.bubbleSize.width / 2;
244
+ if (this.isHidden || isVisuallyOffScreen) {
245
+ // Show the bubble
246
+ let targetX;
247
+ let targetY;
248
+ if (this.savedPosition && this.savedPosition.x < this.screenWidth - this.bubbleSize.width / 2) {
249
+ targetX = this.savedPosition.x;
250
+ targetY = this.savedPosition.y;
251
+ } else {
252
+ targetX = this.screenWidth - this.bubbleSize.width - 20;
253
+ targetY = this.position.y;
254
+ }
255
+ this.isHidden = false;
256
+ this.onHiddenChange?.(false);
257
+ this.notify();
258
+ return {
259
+ targetPosition: {
260
+ x: targetX,
261
+ y: targetY
262
+ },
263
+ isHiding: false
264
+ };
265
+ } else {
266
+ // Hide the bubble
267
+ this.savedPosition = {
268
+ ...this.position
269
+ };
270
+ const hiddenX = this.screenWidth - this.visibleHandleWidth;
271
+ this.isHidden = true;
272
+ this.onHiddenChange?.(true);
273
+ this.notify();
274
+ return {
275
+ targetPosition: {
276
+ x: hiddenX,
277
+ y: this.position.y
278
+ },
279
+ isHiding: true
280
+ };
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Force hide (for pushToSide behavior).
286
+ */
287
+ forceHide() {
288
+ if (this.isHidden) {
289
+ return {
290
+ targetPosition: this.position
291
+ };
292
+ }
293
+ this.savedPosition = {
294
+ ...this.position
295
+ };
296
+ const hiddenX = this.screenWidth - this.visibleHandleWidth;
297
+ this.isHidden = true;
298
+ this.onHiddenChange?.(true);
299
+ this.notify();
300
+ return {
301
+ targetPosition: {
302
+ x: hiddenX,
303
+ y: this.position.y
304
+ }
305
+ };
306
+ }
307
+
308
+ /**
309
+ * Force show (restore from pushToSide).
310
+ */
311
+ forceShow() {
312
+ if (!this.isHidden) {
313
+ return {
314
+ targetPosition: this.position
315
+ };
316
+ }
317
+ let targetX;
318
+ let targetY;
319
+ if (this.savedPosition) {
320
+ targetX = this.savedPosition.x;
321
+ targetY = this.savedPosition.y;
322
+ } else {
323
+ targetX = this.screenWidth - this.bubbleSize.width - 20;
324
+ targetY = this.position.y;
325
+ }
326
+ this.isHidden = false;
327
+ this.onHiddenChange?.(false);
328
+ this.notify();
329
+ return {
330
+ targetPosition: {
331
+ x: targetX,
332
+ y: targetY
333
+ }
334
+ };
335
+ }
336
+
337
+ // =============================
338
+ // Drag Handling
339
+ // =============================
340
+
341
+ /**
342
+ * Start a drag operation.
343
+ */
344
+ handleDragStart(event) {
345
+ this.hasMoved = false;
346
+ this.dragStartPos = {
347
+ ...this.position
348
+ };
349
+ this.dragStartMouse = {
350
+ x: event.clientX,
351
+ y: event.clientY
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Process drag movement.
357
+ * Returns the new position (for animation) and whether drag threshold was exceeded.
358
+ */
359
+ handleDragMove(event) {
360
+ const dx = event.clientX - this.dragStartMouse.x;
361
+ const dy = event.clientY - this.dragStartMouse.y;
362
+ const totalDistance = Math.abs(dx) + Math.abs(dy);
363
+
364
+ // Check if moved enough to be considered a drag
365
+ if (!this.hasMoved && totalDistance > this.dragThreshold) {
366
+ this.hasMoved = true;
367
+ this.isDragging = true;
368
+ this.onDraggingChange?.(true);
369
+ this.notify();
370
+ }
371
+ if (this.hasMoved) {
372
+ const newX = this.dragStartPos.x + dx;
373
+ const newY = this.dragStartPos.y + dy;
374
+
375
+ // Allow overflow to right for hide detection, but clamp Y
376
+ const bounds = this.getBounds();
377
+ const clampedX = Math.max(bounds.minX, Math.min(newX, this.screenWidth - this.visibleHandleWidth + this.bubbleSize.width));
378
+ const clampedY = Math.max(bounds.minY, Math.min(newY, bounds.maxY));
379
+ this.position = {
380
+ x: clampedX,
381
+ y: clampedY
382
+ };
383
+ this.onPositionChange?.(this.position);
384
+ this.notify();
385
+ return {
386
+ position: this.position,
387
+ isDragging: true
388
+ };
389
+ }
390
+ return {
391
+ position: this.position,
392
+ isDragging: false
393
+ };
394
+ }
395
+
396
+ /**
397
+ * End a drag operation.
398
+ * Returns the result including whether to animate to hidden state.
399
+ */
400
+ handleDragEnd(event) {
401
+ const wasTap = !this.hasMoved;
402
+ if (wasTap) {
403
+ this.isDragging = false;
404
+ this.onDraggingChange?.(false);
405
+ this.notify();
406
+ return {
407
+ position: this.position,
408
+ shouldHide: false,
409
+ wasTap: true
410
+ };
411
+ }
412
+
413
+ // Calculate final position
414
+ const dx = event.clientX - this.dragStartMouse.x;
415
+ const dy = event.clientY - this.dragStartMouse.y;
416
+ const currentX = this.dragStartPos.x + dx;
417
+ const currentY = this.dragStartPos.y + dy;
418
+
419
+ // Check if should auto-hide (bubble midpoint past right edge)
420
+ const bubbleMidpoint = currentX + this.bubbleSize.width / 2;
421
+ const shouldHide = bubbleMidpoint > this.screenWidth;
422
+ const bounds = this.getBounds();
423
+ if (shouldHide) {
424
+ const hiddenX = this.screenWidth - this.visibleHandleWidth;
425
+ const clampedY = Math.max(bounds.minY, Math.min(currentY, bounds.maxY));
426
+ this.isHidden = true;
427
+ this.onHiddenChange?.(true);
428
+ this.isDragging = false;
429
+ this.onDraggingChange?.(false);
430
+ this.notify();
431
+ return {
432
+ position: {
433
+ x: hiddenX,
434
+ y: clampedY
435
+ },
436
+ shouldHide: true,
437
+ wasTap: false
438
+ };
439
+ } else {
440
+ // Check if pulling back from hidden
441
+ if (this.isHidden && currentX < this.screenWidth - this.visibleHandleWidth - 10) {
442
+ this.isHidden = false;
443
+ this.onHiddenChange?.(false);
444
+ }
445
+
446
+ // Clamp to visible area
447
+ const clampedX = Math.max(bounds.minX, Math.min(currentX, this.screenWidth - this.bubbleSize.width - this.edgePadding));
448
+ const clampedY = Math.max(bounds.minY, Math.min(currentY, bounds.maxY));
449
+
450
+ // Save position if visible
451
+ if (clampedX < this.screenWidth - this.bubbleSize.width / 2) {
452
+ this.savedPosition = {
453
+ x: clampedX,
454
+ y: clampedY
455
+ };
456
+ }
457
+ this.position = {
458
+ x: clampedX,
459
+ y: clampedY
460
+ };
461
+ this.isDragging = false;
462
+ this.onDraggingChange?.(false);
463
+ this.debouncedSavePosition(clampedX, clampedY);
464
+ this.notify();
465
+ return {
466
+ position: {
467
+ x: clampedX,
468
+ y: clampedY
469
+ },
470
+ shouldHide: false,
471
+ wasTap: false
472
+ };
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Commit position after animation completes.
478
+ */
479
+ commitPosition(pos) {
480
+ this.position = pos;
481
+ this.savePosition(pos.x, pos.y);
482
+ this.notify();
483
+ }
484
+
485
+ // =============================
486
+ // Cleanup
487
+ // =============================
488
+
489
+ destroy() {
490
+ if (this.saveTimeout) {
491
+ clearTimeout(this.saveTimeout);
492
+ this.saveTimeout = null;
493
+ }
494
+ this.listeners.clear();
495
+ }
496
+ }
@@ -1 +1,49 @@
1
- "use strict";export const floatingToolsColors={panel:"#1A1A1A",muted:"#888888",secondary:"#E0E0E0",info:"#20C997",success:"#20C997",optional:"#FFA94D",error:"#EF4444",primary:"#20C997",dragActive:"#20C997"};export function withAlpha(t,o){return"number"==typeof o?`${t}${Math.round(255*o).toString(16).padStart(2,"0")}`:`${t}${o}`}
1
+ "use strict";
2
+
3
+ /**
4
+ * Shared color palette for floating tools.
5
+ * Matches the gameUIColors from @buoy-gg/shared-ui.
6
+ *
7
+ * These are duplicated here to avoid requiring @buoy-gg/shared-ui
8
+ * as a dependency (which has React Native dependencies).
9
+ *
10
+ * TODO: Consider extracting colors to a truly platform-agnostic package
11
+ * that both shared-ui and floating-tools-core can depend on.
12
+ */
13
+
14
+ /**
15
+ * Buoy brand colors - EXACT values from web docs site dark theme
16
+ */
17
+ export const floatingToolsColors = {
18
+ /** Dark panel background */
19
+ panel: '#1A1A1A',
20
+ /** Muted gray for secondary elements */
21
+ muted: '#888888',
22
+ /** Primary text color */
23
+ secondary: '#E0E0E0',
24
+ /** Info/active state color (Buoy teal) */
25
+ info: '#20C997',
26
+ /** Success/admin color (Buoy teal) */
27
+ success: '#20C997',
28
+ /** Warning/optional color (Buoy orange) */
29
+ optional: '#FFA94D',
30
+ /** Error color (Buoy red) */
31
+ error: '#EF4444',
32
+ /** Primary accent color (Buoy teal) */
33
+ primary: '#20C997',
34
+ /** Color used when dragging the floating tools (Buoy teal) */
35
+ dragActive: '#20C997'
36
+ };
37
+ /**
38
+ * Get a color with optional alpha.
39
+ * @param color - The base color (hex)
40
+ * @param alpha - Alpha value as hex string (00-FF) or number (0-1)
41
+ */
42
+ export function withAlpha(color, alpha) {
43
+ if (typeof alpha === 'number') {
44
+ // Convert 0-1 to hex
45
+ const hex = Math.round(alpha * 255).toString(16).padStart(2, '0');
46
+ return `${color}${hex}`;
47
+ }
48
+ return `${color}${alpha}`;
49
+ }
@@ -1 +1,27 @@
1
- "use strict";export const VISIBLE_HANDLE_WIDTH=32;export const DRAG_THRESHOLD=5;export const ANIMATION_DURATION=200;export const EDGE_PADDING=10;export const SAVE_DEBOUNCE_MS=500;export const STORAGE_KEYS={POSITION_X:"@react_buoy_bubble_position_x",POSITION_Y:"@react_buoy_bubble_position_y"};
1
+ "use strict";
2
+
3
+ /**
4
+ * Shared constants for floating tools behavior.
5
+ * These values ensure consistent behavior across platforms.
6
+ */
7
+
8
+ /** Width of the visible grip handle when bubble is hidden (pixels) */
9
+ export const VISIBLE_HANDLE_WIDTH = 32;
10
+
11
+ /** Minimum movement to distinguish drag from tap (pixels) */
12
+ export const DRAG_THRESHOLD = 5;
13
+
14
+ /** Animation duration for hide/show transitions (milliseconds) */
15
+ export const ANIMATION_DURATION = 200;
16
+
17
+ /** Padding from screen edges (pixels) */
18
+ export const EDGE_PADDING = 10;
19
+
20
+ /** Debounce delay for position persistence (milliseconds) */
21
+ export const SAVE_DEBOUNCE_MS = 500;
22
+
23
+ /** Storage keys for position persistence */
24
+ export const STORAGE_KEYS = {
25
+ POSITION_X: '@react_buoy_bubble_position_x',
26
+ POSITION_Y: '@react_buoy_bubble_position_y'
27
+ };