@buoy-gg/image-overlay 7.0.40 → 7.0.41

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 (23) hide show
  1. package/README.md +4 -0
  2. package/lib/commonjs/imageOverlay/components/ImageOverlayModal.js +9 -4
  3. package/lib/commonjs/imageOverlay/components/ImageOverlayStandalone.js +87 -80
  4. package/lib/commonjs/imageOverlay/utils/ImageOverlayController.js +34 -1
  5. package/lib/commonjs/imageOverlay/utils/fiberScanner.js +7 -5
  6. package/lib/commonjs/imageOverlay/utils/imageClipboard.js +22 -0
  7. package/lib/commonjs/imageOverlay/utils/imageClipboard.web.js +44 -0
  8. package/lib/module/imageOverlay/components/ImageOverlayModal.js +9 -4
  9. package/lib/module/imageOverlay/components/ImageOverlayStandalone.js +87 -80
  10. package/lib/module/imageOverlay/utils/ImageOverlayController.js +34 -1
  11. package/lib/module/imageOverlay/utils/fiberScanner.js +7 -5
  12. package/lib/module/imageOverlay/utils/imageClipboard.js +17 -0
  13. package/lib/module/imageOverlay/utils/imageClipboard.web.js +39 -0
  14. package/lib/typescript/imageOverlay/components/ImageOverlayModal.d.ts.map +1 -1
  15. package/lib/typescript/imageOverlay/components/ImageOverlayStandalone.d.ts.map +1 -1
  16. package/lib/typescript/imageOverlay/utils/ImageOverlayController.d.ts.map +1 -1
  17. package/lib/typescript/imageOverlay/utils/fiberScanner.d.ts.map +1 -1
  18. package/lib/typescript/imageOverlay/utils/imageClipboard.d.ts +3 -0
  19. package/lib/typescript/imageOverlay/utils/imageClipboard.d.ts.map +1 -0
  20. package/lib/typescript/imageOverlay/utils/imageClipboard.web.d.ts +9 -0
  21. package/lib/typescript/imageOverlay/utils/imageClipboard.web.d.ts.map +1 -0
  22. package/lib/web/index.mjs +5 -0
  23. package/package.json +32 -10
package/README.md CHANGED
@@ -85,3 +85,7 @@ Use a Free or Pro Buoy account. History limits and paid features vary by tool; s
85
85
  📚 [Full docs](https://buoy.gg/buoy/latest/docs/tools/image-overlay) · [All Buoy tools](https://github.com/Buoy-gg/buoy)
86
86
 
87
87
  Proprietary software. © Buoy LLC. [Terms](https://buoy.gg/terms)
88
+
89
+ ## Web support (unreleased)
90
+
91
+ Tag browser targets with data-testid="image-target:Name" and import the browser registration before React DOM. The shared controls support target and free placement. The browser build is available in this checkout and has not been published yet. See the [web setup guide](https://buoy.gg/buoy/latest/docs/web-preview) for registration, dependencies, and browser boundaries.
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ImageOverlayModal = ImageOverlayModal;
7
7
  exports.ImageOverlayToolView = ImageOverlayToolView;
8
+ var _imageClipboard = require("../utils/imageClipboard");
8
9
  var _react = require("react");
9
10
  var _reactNative = require("react-native");
10
11
  var _sharedUi = require("@buoy-gg/shared-ui");
@@ -80,7 +81,7 @@ function ImageOverlayModal({
80
81
  // Try expo-clipboard (supports images)
81
82
  let expoClipboard = null;
82
83
  try {
83
- expoClipboard = require("expo-clipboard");
84
+ expoClipboard = (0, _imageClipboard.loadImageClipboard)();
84
85
  } catch {/* not installed */}
85
86
 
86
87
  // Only fall back to @react-native-clipboard/clipboard when expo-clipboard
@@ -89,7 +90,7 @@ function ImageOverlayModal({
89
90
  let rnClipboard = null;
90
91
  if (!expoClipboard) {
91
92
  try {
92
- const mod = require("@react-native-clipboard/clipboard");
93
+ const mod = (0, _imageClipboard.loadTextClipboard)();
93
94
  rnClipboard = mod.default || mod;
94
95
  } catch {/* not installed */}
95
96
  }
@@ -323,7 +324,9 @@ function ImageOverlayModal({
323
324
  placeholder: "or enter URL...",
324
325
  placeholderTextColor: _sharedUi.night.color.placeholder,
325
326
  autoCapitalize: "none",
326
- autoCorrect: false
327
+ autoCorrect: false,
328
+ returnKeyType: "go",
329
+ onSubmitEditing: handleLoadImage
327
330
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
328
331
  style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
329
332
  onPress: handleLoadImage,
@@ -444,7 +447,9 @@ function ImageOverlayModal({
444
447
  placeholder: "or enter URL...",
445
448
  placeholderTextColor: _sharedUi.night.color.placeholder,
446
449
  autoCapitalize: "none",
447
- autoCorrect: false
450
+ autoCorrect: false,
451
+ returnKeyType: "go",
452
+ onSubmitEditing: handleLoadImage
448
453
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
449
454
  style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
450
455
  onPress: handleLoadImage,
@@ -38,6 +38,7 @@ function OverlayImage({
38
38
  transform
39
39
  } : {};
40
40
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
41
+ testID: "buoy-internal-image-overlay",
41
42
  source: {
42
43
  uri
43
44
  },
@@ -191,90 +192,96 @@ function FreePlacement({
191
192
  ..._sharedUi.absoluteFill,
192
193
  opacity
193
194
  };
194
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
195
- style: {
196
- position: "absolute",
197
- left: x,
198
- top: y,
199
- width,
200
- height
201
- },
202
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
203
- style: _reactNative.StyleSheet.absoluteFill,
204
- ...(locked ? {} : dragResponder.panHandlers),
205
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(OverlayImage, {
206
- uri: imageUri,
207
- style: imageStyle,
208
- invertX: invertX,
209
- invertY: invertY
210
- })
211
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
212
- pointerEvents: "none",
213
- style: [styles.freeBorder, isInteracting && styles.freeBorderActive, locked && styles.freeBorderLocked]
214
- }), !locked && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
195
+ return (
196
+ /*#__PURE__*/
197
+ // Locked means "let me use the app under it": the box must not swallow
198
+ // touches, or a locked full-screen mockup blocks every tap on the screen.
199
+ (0, _jsxRuntime.jsxs)(_reactNative.View, {
200
+ style: {
201
+ position: "absolute",
202
+ left: x,
203
+ top: y,
204
+ width,
205
+ height
206
+ },
207
+ pointerEvents: locked ? "none" : "auto",
215
208
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
216
- ...resizeHandlers.topLeft.panHandlers,
217
- style: [styles.cornerWrapper, {
218
- top: -20,
219
- left: -20
220
- }],
221
- hitSlop: {
222
- top: 10,
223
- left: 10,
224
- right: 10,
225
- bottom: 10
226
- },
227
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
228
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
229
- })
230
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
231
- ...resizeHandlers.topRight.panHandlers,
232
- style: [styles.cornerWrapper, {
233
- top: -20,
234
- right: -20
235
- }],
236
- hitSlop: {
237
- top: 10,
238
- left: 10,
239
- right: 10,
240
- bottom: 10
241
- },
242
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
243
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
244
- })
245
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
246
- ...resizeHandlers.bottomLeft.panHandlers,
247
- style: [styles.cornerWrapper, {
248
- bottom: -20,
249
- left: -20
250
- }],
251
- hitSlop: {
252
- top: 10,
253
- left: 10,
254
- right: 10,
255
- bottom: 10
256
- },
257
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
258
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
209
+ style: _reactNative.StyleSheet.absoluteFill,
210
+ ...(locked ? {} : dragResponder.panHandlers),
211
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(OverlayImage, {
212
+ uri: imageUri,
213
+ style: imageStyle,
214
+ invertX: invertX,
215
+ invertY: invertY
259
216
  })
260
217
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
261
- ...resizeHandlers.bottomRight.panHandlers,
262
- style: [styles.cornerWrapper, {
263
- bottom: -20,
264
- right: -20
265
- }],
266
- hitSlop: {
267
- top: 10,
268
- left: 10,
269
- right: 10,
270
- bottom: 10
271
- },
272
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
273
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
274
- })
218
+ pointerEvents: "none",
219
+ style: [styles.freeBorder, isInteracting && styles.freeBorderActive, locked && styles.freeBorderLocked]
220
+ }), !locked && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
221
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
222
+ ...resizeHandlers.topLeft.panHandlers,
223
+ style: [styles.cornerWrapper, {
224
+ top: -20,
225
+ left: -20
226
+ }],
227
+ hitSlop: {
228
+ top: 10,
229
+ left: 10,
230
+ right: 10,
231
+ bottom: 10
232
+ },
233
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
234
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
235
+ })
236
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
237
+ ...resizeHandlers.topRight.panHandlers,
238
+ style: [styles.cornerWrapper, {
239
+ top: -20,
240
+ right: -20
241
+ }],
242
+ hitSlop: {
243
+ top: 10,
244
+ left: 10,
245
+ right: 10,
246
+ bottom: 10
247
+ },
248
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
249
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
250
+ })
251
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
252
+ ...resizeHandlers.bottomLeft.panHandlers,
253
+ style: [styles.cornerWrapper, {
254
+ bottom: -20,
255
+ left: -20
256
+ }],
257
+ hitSlop: {
258
+ top: 10,
259
+ left: 10,
260
+ right: 10,
261
+ bottom: 10
262
+ },
263
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
264
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
265
+ })
266
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
267
+ ...resizeHandlers.bottomRight.panHandlers,
268
+ style: [styles.cornerWrapper, {
269
+ bottom: -20,
270
+ right: -20
271
+ }],
272
+ hitSlop: {
273
+ top: 10,
274
+ left: 10,
275
+ right: 10,
276
+ bottom: 10
277
+ },
278
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
279
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
280
+ })
281
+ })]
275
282
  })]
276
- })]
277
- });
283
+ })
284
+ );
278
285
  }
279
286
 
280
287
  // ─── Main Standalone Overlay ──────────────────────────────────────────────────
@@ -107,7 +107,34 @@ function startTraceTracking() {
107
107
  traceUnsubscribe = hook.sub("traceUpdates", handleTraceForAutoTrack);
108
108
  }
109
109
  }
110
+
111
+ /**
112
+ * Remeasure once per frame while Auto Track is on.
113
+ *
114
+ * traceUpdates alone missed the common case: a native scroll moves the target
115
+ * without any React render, so the overlay stayed where the row used to be.
116
+ * One measure in flight at a time, so a slow bridge never queues frames.
117
+ */
118
+ let frameHandle = null;
119
+ let frameMeasuring = false;
120
+ function startFrameTracking() {
121
+ if (frameHandle !== null || typeof requestAnimationFrame !== "function") return;
122
+ const tick = () => {
123
+ frameHandle = requestAnimationFrame(tick);
124
+ if (frameMeasuring || !autoTrackEnabled || !targetInstance) return;
125
+ frameMeasuring = true;
126
+ remeasureTarget().finally(() => {
127
+ frameMeasuring = false;
128
+ });
129
+ };
130
+ frameHandle = requestAnimationFrame(tick);
131
+ }
132
+ function stopFrameTracking() {
133
+ if (frameHandle !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frameHandle);
134
+ frameHandle = null;
135
+ }
110
136
  function stopTraceTracking() {
137
+ stopFrameTracking();
111
138
  if (traceUnsubscribe) {
112
139
  traceUnsubscribe();
113
140
  traceUnsubscribe = null;
@@ -122,7 +149,10 @@ function handleTraceForAutoTrack(_nodes) {
122
149
 
123
150
  // Don't filter by nativeTag — any render could have caused a layout
124
151
  // shift (e.g. sibling resize, scroll). Just remeasure cheaply.
125
- (0, _componentMeasurement.measureInstance)(targetInstance).then(rect => {
152
+ void remeasureTarget();
153
+ }
154
+ function remeasureTarget() {
155
+ return (0, _componentMeasurement.measureInstance)(targetInstance).then(rect => {
126
156
  if (rect && state.targetRect) {
127
157
  // Only notify if position actually changed
128
158
  const prev = state.targetRect;
@@ -134,6 +164,8 @@ function handleTraceForAutoTrack(_nodes) {
134
164
  notify();
135
165
  }
136
166
  }
167
+ }).catch(() => {
168
+ // The target unmounted mid-measure; the next frame tries again.
137
169
  });
138
170
  }
139
171
  function getImageSize(uri) {
@@ -237,6 +269,7 @@ const ImageOverlayController = exports.ImageOverlayController = {
237
269
  autoTrackEnabled = enabled;
238
270
  if (enabled) {
239
271
  startTraceTracking();
272
+ startFrameTracking();
240
273
  } else {
241
274
  stopTraceTracking();
242
275
  }
@@ -20,12 +20,14 @@ const OffscreenComponent = 22;
20
20
  function getFiberRoots() {
21
21
  const hook = (0, _devtoolsHook.getDevToolsHook)();
22
22
  if (!hook?.getFiberRoots) return [];
23
- try {
24
- const rootsSet = hook.getFiberRoots(1);
25
- return rootsSet ? Array.from(rootsSet) : [];
26
- } catch {
27
- return [];
23
+ const roots = new Set();
24
+ const ids = hook.renderers?.keys ? Array.from(hook.renderers.keys()) : [1];
25
+ for (const id of ids) {
26
+ try {
27
+ hook.getFiberRoots(id)?.forEach(root => roots.add(root));
28
+ } catch {/* Other renderers can still provide roots. */}
28
29
  }
30
+ return [...roots];
29
31
  }
30
32
  function isHiddenOffscreen(fiber) {
31
33
  return fiber.tag === OffscreenComponent && fiber.memoizedState !== null;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadImageClipboard = loadImageClipboard;
7
+ exports.loadTextClipboard = loadTextClipboard;
8
+ function loadImageClipboard() {
9
+ try {
10
+ return require('expo-clipboard');
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function loadTextClipboard() {
16
+ try {
17
+ const mod = require('@react-native-clipboard/clipboard');
18
+ return mod.default ?? mod;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.loadImageClipboard = loadImageClipboard;
7
+ exports.loadTextClipboard = loadTextClipboard;
8
+ let image;
9
+ function loadImageClipboard() {
10
+ if (typeof navigator === 'undefined' || !navigator.clipboard) return null;
11
+ return {
12
+ async hasImageAsync() {
13
+ image = undefined;
14
+ if (!navigator.clipboard.read) return false;
15
+ const items = await navigator.clipboard.read();
16
+ for (const item of items) {
17
+ const type = item.types.find(value => value.startsWith('image/'));
18
+ if (type) {
19
+ image = await item.getType(type);
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ },
25
+ async getImageAsync() {
26
+ if (!image) return null;
27
+ const blob = image;
28
+ image = undefined;
29
+ const data = await new Promise((resolve, reject) => {
30
+ const reader = new FileReader();
31
+ reader.onload = () => resolve(String(reader.result));
32
+ reader.onerror = reject;
33
+ reader.readAsDataURL(blob);
34
+ });
35
+ return {
36
+ data
37
+ };
38
+ },
39
+ getStringAsync: () => navigator.clipboard.readText()
40
+ };
41
+ }
42
+ function loadTextClipboard() {
43
+ return null;
44
+ }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { loadImageClipboard, loadTextClipboard } from "../utils/imageClipboard";
3
4
  import { useState, useEffect, useCallback } from "react";
4
5
  import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from "react-native";
5
6
  import { JsModal, ModalHeader, macOSColors, night, Trash2, SquareDashed, Image, PowerToggleButton, windowDragProps, ToggleRow, monoFont } from "@buoy-gg/shared-ui";
@@ -75,7 +76,7 @@ export function ImageOverlayModal({
75
76
  // Try expo-clipboard (supports images)
76
77
  let expoClipboard = null;
77
78
  try {
78
- expoClipboard = require("expo-clipboard");
79
+ expoClipboard = loadImageClipboard();
79
80
  } catch {/* not installed */}
80
81
 
81
82
  // Only fall back to @react-native-clipboard/clipboard when expo-clipboard
@@ -84,7 +85,7 @@ export function ImageOverlayModal({
84
85
  let rnClipboard = null;
85
86
  if (!expoClipboard) {
86
87
  try {
87
- const mod = require("@react-native-clipboard/clipboard");
88
+ const mod = loadTextClipboard();
88
89
  rnClipboard = mod.default || mod;
89
90
  } catch {/* not installed */}
90
91
  }
@@ -318,7 +319,9 @@ export function ImageOverlayModal({
318
319
  placeholder: "or enter URL...",
319
320
  placeholderTextColor: night.color.placeholder,
320
321
  autoCapitalize: "none",
321
- autoCorrect: false
322
+ autoCorrect: false,
323
+ returnKeyType: "go",
324
+ onSubmitEditing: handleLoadImage
322
325
  }), /*#__PURE__*/_jsx(TouchableOpacity, {
323
326
  style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
324
327
  onPress: handleLoadImage,
@@ -439,7 +442,9 @@ export function ImageOverlayModal({
439
442
  placeholder: "or enter URL...",
440
443
  placeholderTextColor: night.color.placeholder,
441
444
  autoCapitalize: "none",
442
- autoCorrect: false
445
+ autoCorrect: false,
446
+ returnKeyType: "go",
447
+ onSubmitEditing: handleLoadImage
443
448
  }), /*#__PURE__*/_jsx(TouchableOpacity, {
444
449
  style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
445
450
  onPress: handleLoadImage,
@@ -34,6 +34,7 @@ function OverlayImage({
34
34
  transform
35
35
  } : {};
36
36
  return /*#__PURE__*/_jsx(RNImage, {
37
+ testID: "buoy-internal-image-overlay",
37
38
  source: {
38
39
  uri
39
40
  },
@@ -187,90 +188,96 @@ function FreePlacement({
187
188
  ...absoluteFill,
188
189
  opacity
189
190
  };
190
- return /*#__PURE__*/_jsxs(View, {
191
- style: {
192
- position: "absolute",
193
- left: x,
194
- top: y,
195
- width,
196
- height
197
- },
198
- children: [/*#__PURE__*/_jsx(View, {
199
- style: StyleSheet.absoluteFill,
200
- ...(locked ? {} : dragResponder.panHandlers),
201
- children: /*#__PURE__*/_jsx(OverlayImage, {
202
- uri: imageUri,
203
- style: imageStyle,
204
- invertX: invertX,
205
- invertY: invertY
206
- })
207
- }), /*#__PURE__*/_jsx(View, {
208
- pointerEvents: "none",
209
- style: [styles.freeBorder, isInteracting && styles.freeBorderActive, locked && styles.freeBorderLocked]
210
- }), !locked && /*#__PURE__*/_jsxs(_Fragment, {
191
+ return (
192
+ /*#__PURE__*/
193
+ // Locked means "let me use the app under it": the box must not swallow
194
+ // touches, or a locked full-screen mockup blocks every tap on the screen.
195
+ _jsxs(View, {
196
+ style: {
197
+ position: "absolute",
198
+ left: x,
199
+ top: y,
200
+ width,
201
+ height
202
+ },
203
+ pointerEvents: locked ? "none" : "auto",
211
204
  children: [/*#__PURE__*/_jsx(View, {
212
- ...resizeHandlers.topLeft.panHandlers,
213
- style: [styles.cornerWrapper, {
214
- top: -20,
215
- left: -20
216
- }],
217
- hitSlop: {
218
- top: 10,
219
- left: 10,
220
- right: 10,
221
- bottom: 10
222
- },
223
- children: /*#__PURE__*/_jsx(View, {
224
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
225
- })
226
- }), /*#__PURE__*/_jsx(View, {
227
- ...resizeHandlers.topRight.panHandlers,
228
- style: [styles.cornerWrapper, {
229
- top: -20,
230
- right: -20
231
- }],
232
- hitSlop: {
233
- top: 10,
234
- left: 10,
235
- right: 10,
236
- bottom: 10
237
- },
238
- children: /*#__PURE__*/_jsx(View, {
239
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
240
- })
241
- }), /*#__PURE__*/_jsx(View, {
242
- ...resizeHandlers.bottomLeft.panHandlers,
243
- style: [styles.cornerWrapper, {
244
- bottom: -20,
245
- left: -20
246
- }],
247
- hitSlop: {
248
- top: 10,
249
- left: 10,
250
- right: 10,
251
- bottom: 10
252
- },
253
- children: /*#__PURE__*/_jsx(View, {
254
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
205
+ style: StyleSheet.absoluteFill,
206
+ ...(locked ? {} : dragResponder.panHandlers),
207
+ children: /*#__PURE__*/_jsx(OverlayImage, {
208
+ uri: imageUri,
209
+ style: imageStyle,
210
+ invertX: invertX,
211
+ invertY: invertY
255
212
  })
256
213
  }), /*#__PURE__*/_jsx(View, {
257
- ...resizeHandlers.bottomRight.panHandlers,
258
- style: [styles.cornerWrapper, {
259
- bottom: -20,
260
- right: -20
261
- }],
262
- hitSlop: {
263
- top: 10,
264
- left: 10,
265
- right: 10,
266
- bottom: 10
267
- },
268
- children: /*#__PURE__*/_jsx(View, {
269
- style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
270
- })
214
+ pointerEvents: "none",
215
+ style: [styles.freeBorder, isInteracting && styles.freeBorderActive, locked && styles.freeBorderLocked]
216
+ }), !locked && /*#__PURE__*/_jsxs(_Fragment, {
217
+ children: [/*#__PURE__*/_jsx(View, {
218
+ ...resizeHandlers.topLeft.panHandlers,
219
+ style: [styles.cornerWrapper, {
220
+ top: -20,
221
+ left: -20
222
+ }],
223
+ hitSlop: {
224
+ top: 10,
225
+ left: 10,
226
+ right: 10,
227
+ bottom: 10
228
+ },
229
+ children: /*#__PURE__*/_jsx(View, {
230
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
231
+ })
232
+ }), /*#__PURE__*/_jsx(View, {
233
+ ...resizeHandlers.topRight.panHandlers,
234
+ style: [styles.cornerWrapper, {
235
+ top: -20,
236
+ right: -20
237
+ }],
238
+ hitSlop: {
239
+ top: 10,
240
+ left: 10,
241
+ right: 10,
242
+ bottom: 10
243
+ },
244
+ children: /*#__PURE__*/_jsx(View, {
245
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
246
+ })
247
+ }), /*#__PURE__*/_jsx(View, {
248
+ ...resizeHandlers.bottomLeft.panHandlers,
249
+ style: [styles.cornerWrapper, {
250
+ bottom: -20,
251
+ left: -20
252
+ }],
253
+ hitSlop: {
254
+ top: 10,
255
+ left: 10,
256
+ right: 10,
257
+ bottom: 10
258
+ },
259
+ children: /*#__PURE__*/_jsx(View, {
260
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
261
+ })
262
+ }), /*#__PURE__*/_jsx(View, {
263
+ ...resizeHandlers.bottomRight.panHandlers,
264
+ style: [styles.cornerWrapper, {
265
+ bottom: -20,
266
+ right: -20
267
+ }],
268
+ hitSlop: {
269
+ top: 10,
270
+ left: 10,
271
+ right: 10,
272
+ bottom: 10
273
+ },
274
+ children: /*#__PURE__*/_jsx(View, {
275
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
276
+ })
277
+ })]
271
278
  })]
272
- })]
273
- });
279
+ })
280
+ );
274
281
  }
275
282
 
276
283
  // ─── Main Standalone Overlay ──────────────────────────────────────────────────
@@ -103,7 +103,34 @@ function startTraceTracking() {
103
103
  traceUnsubscribe = hook.sub("traceUpdates", handleTraceForAutoTrack);
104
104
  }
105
105
  }
106
+
107
+ /**
108
+ * Remeasure once per frame while Auto Track is on.
109
+ *
110
+ * traceUpdates alone missed the common case: a native scroll moves the target
111
+ * without any React render, so the overlay stayed where the row used to be.
112
+ * One measure in flight at a time, so a slow bridge never queues frames.
113
+ */
114
+ let frameHandle = null;
115
+ let frameMeasuring = false;
116
+ function startFrameTracking() {
117
+ if (frameHandle !== null || typeof requestAnimationFrame !== "function") return;
118
+ const tick = () => {
119
+ frameHandle = requestAnimationFrame(tick);
120
+ if (frameMeasuring || !autoTrackEnabled || !targetInstance) return;
121
+ frameMeasuring = true;
122
+ remeasureTarget().finally(() => {
123
+ frameMeasuring = false;
124
+ });
125
+ };
126
+ frameHandle = requestAnimationFrame(tick);
127
+ }
128
+ function stopFrameTracking() {
129
+ if (frameHandle !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frameHandle);
130
+ frameHandle = null;
131
+ }
106
132
  function stopTraceTracking() {
133
+ stopFrameTracking();
107
134
  if (traceUnsubscribe) {
108
135
  traceUnsubscribe();
109
136
  traceUnsubscribe = null;
@@ -118,7 +145,10 @@ function handleTraceForAutoTrack(_nodes) {
118
145
 
119
146
  // Don't filter by nativeTag — any render could have caused a layout
120
147
  // shift (e.g. sibling resize, scroll). Just remeasure cheaply.
121
- measureInstance(targetInstance).then(rect => {
148
+ void remeasureTarget();
149
+ }
150
+ function remeasureTarget() {
151
+ return measureInstance(targetInstance).then(rect => {
122
152
  if (rect && state.targetRect) {
123
153
  // Only notify if position actually changed
124
154
  const prev = state.targetRect;
@@ -130,6 +160,8 @@ function handleTraceForAutoTrack(_nodes) {
130
160
  notify();
131
161
  }
132
162
  }
163
+ }).catch(() => {
164
+ // The target unmounted mid-measure; the next frame tries again.
133
165
  });
134
166
  }
135
167
  function getImageSize(uri) {
@@ -233,6 +265,7 @@ export const ImageOverlayController = {
233
265
  autoTrackEnabled = enabled;
234
266
  if (enabled) {
235
267
  startTraceTracking();
268
+ startFrameTracking();
236
269
  } else {
237
270
  stopTraceTracking();
238
271
  }
@@ -16,12 +16,14 @@ const OffscreenComponent = 22;
16
16
  function getFiberRoots() {
17
17
  const hook = getDevToolsHook();
18
18
  if (!hook?.getFiberRoots) return [];
19
- try {
20
- const rootsSet = hook.getFiberRoots(1);
21
- return rootsSet ? Array.from(rootsSet) : [];
22
- } catch {
23
- return [];
19
+ const roots = new Set();
20
+ const ids = hook.renderers?.keys ? Array.from(hook.renderers.keys()) : [1];
21
+ for (const id of ids) {
22
+ try {
23
+ hook.getFiberRoots(id)?.forEach(root => roots.add(root));
24
+ } catch {/* Other renderers can still provide roots. */}
24
25
  }
26
+ return [...roots];
25
27
  }
26
28
  function isHiddenOffscreen(fiber) {
27
29
  return fiber.tag === OffscreenComponent && fiber.memoizedState !== null;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ export function loadImageClipboard() {
4
+ try {
5
+ return require('expo-clipboard');
6
+ } catch {
7
+ return null;
8
+ }
9
+ }
10
+ export function loadTextClipboard() {
11
+ try {
12
+ const mod = require('@react-native-clipboard/clipboard');
13
+ return mod.default ?? mod;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ let image;
4
+ export function loadImageClipboard() {
5
+ if (typeof navigator === 'undefined' || !navigator.clipboard) return null;
6
+ return {
7
+ async hasImageAsync() {
8
+ image = undefined;
9
+ if (!navigator.clipboard.read) return false;
10
+ const items = await navigator.clipboard.read();
11
+ for (const item of items) {
12
+ const type = item.types.find(value => value.startsWith('image/'));
13
+ if (type) {
14
+ image = await item.getType(type);
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ },
20
+ async getImageAsync() {
21
+ if (!image) return null;
22
+ const blob = image;
23
+ image = undefined;
24
+ const data = await new Promise((resolve, reject) => {
25
+ const reader = new FileReader();
26
+ reader.onload = () => resolve(String(reader.result));
27
+ reader.onerror = reject;
28
+ reader.readAsDataURL(blob);
29
+ });
30
+ return {
31
+ data
32
+ };
33
+ },
34
+ getStringAsync: () => navigator.clipboard.readText()
35
+ };
36
+ }
37
+ export function loadTextClipboard() {
38
+ return null;
39
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ImageOverlayModal.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/components/ImageOverlayModal.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,sBAAsB,EAAkC,MAAM,UAAU,CAAC;AAMvF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAKjE,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,iBAAiB,EACjB,sBAAsB,EACtB,MAAc,EACd,WAAW,GACZ,EAAE,sBAAsB,+BAoiBxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,GAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAG3E"}
1
+ {"version":3,"file":"ImageOverlayModal.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/components/ImageOverlayModal.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,sBAAsB,EAAkC,MAAM,UAAU,CAAC;AAMvF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAKjE,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,iBAAiB,EACjB,sBAAsB,EACtB,MAAc,EACd,WAAW,GACZ,EAAE,sBAAsB,+BAwiBxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,GAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAG3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"ImageOverlayStandalone.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/components/ImageOverlayStandalone.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAwNjF,wBAAgB,sBAAsB,IAAI,KAAK,CAAC,YAAY,GAAG,IAAI,CAuFlE"}
1
+ {"version":3,"file":"ImageOverlayStandalone.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/components/ImageOverlayStandalone.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AA2NjF,wBAAgB,sBAAsB,IAAI,KAAK,CAAC,YAAY,GAAG,IAAI,CAuFlE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ImageOverlayController.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/ImageOverlayController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,UAAU,CAAC;AAK3D,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAqI9C,eAAO,MAAM,sBAAsB;wBACb,QAAQ,GAAG,MAAM,IAAI;gBAM7B,YAAY;wBAME,GAAG,SAAS,MAAM,UAAU,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;qBAmBlD,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;sBAmB3B,MAAM;oBAKR,MAAM;iBAKT,MAAM,KAAK,MAAM;;;sBAeZ,OAAO;0BAKH,OAAO;sBASX,OAAO;yBAIJ,OAAO;wBAKR,OAAO;;;;;iBA+CR,OAAO,CAAC,IAAI,CAAC;uBAYP,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;uBAkC5B,MAAM,KAAK,MAAM;6BAKX,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK,MAAM;;CAgBtE,CAAC"}
1
+ {"version":3,"file":"ImageOverlayController.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/ImageOverlayController.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,UAAU,CAAC;AAK3D,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAsK9C,eAAO,MAAM,sBAAsB;wBACb,QAAQ,GAAG,MAAM,IAAI;gBAM7B,YAAY;wBAME,GAAG,SAAS,MAAM,UAAU,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;qBAmBlD,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;sBAmB3B,MAAM;oBAKR,MAAM;iBAKT,MAAM,KAAK,MAAM;;;sBAeZ,OAAO;0BAKH,OAAO;sBAUX,OAAO;yBAIJ,OAAO;wBAKR,OAAO;;;;;iBA+CR,OAAO,CAAC,IAAI,CAAC;uBAYP,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;uBAkC5B,MAAM,KAAK,MAAM;6BAKX,MAAM,UAAU,MAAM,KAAK,MAAM,KAAK,MAAM;;CAgBtE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fiberScanner.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/fiberScanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAgFjD;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,EAAE,CAmCxD"}
1
+ {"version":3,"file":"fiberScanner.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/fiberScanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAkFjD;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,EAAE,CAmCxD"}
@@ -0,0 +1,3 @@
1
+ export declare function loadImageClipboard(): any;
2
+ export declare function loadTextClipboard(): any;
3
+ //# sourceMappingURL=imageClipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageClipboard.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/imageClipboard.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,IAAI,GAAG,CAAqE;AAC9G,wBAAgB,iBAAiB,IAAI,GAAG,CAAwH"}
@@ -0,0 +1,9 @@
1
+ export declare function loadImageClipboard(): {
2
+ hasImageAsync(): Promise<boolean>;
3
+ getImageAsync(): Promise<{
4
+ data: string;
5
+ } | null>;
6
+ getStringAsync: () => Promise<string>;
7
+ } | null;
8
+ export declare function loadTextClipboard(): null;
9
+ //# sourceMappingURL=imageClipboard.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageClipboard.web.d.ts","sourceRoot":"","sources":["../../../../src/imageOverlay/utils/imageClipboard.web.ts"],"names":[],"mappings":"AACA,wBAAgB,kBAAkB;;;;;;SAuBjC;AACD,wBAAgB,iBAAiB,IAAI,IAAI,CAAiB"}
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ var _e=Object.defineProperty;var at=Object.getOwnPropertyDescriptor;var it=Object.getOwnPropertyNames;var lt=Object.prototype.hasOwnProperty;var st=(e,o)=>{for(var n in o)_e(e,n,{get:o[n],enumerable:!0})},Fe=(e,o,n,a)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of it(o))!lt.call(e,r)&&r!==n&&_e(e,r,{get:()=>o[r],enumerable:!(a=at(o,r))||a.enumerable});return e},M=(e,o,n)=>(Fe(e,o,"default"),n&&Fe(n,o,"default"));typeof window<"u"&&typeof globalThis.global>"u"&&(globalThis.global=globalThis);import{ImageOverlayIcon as Po,createToolFactory as Bo,defineToolPreset as Wo}from"@buoy-gg/shared-ui/web";var fe;function Xe(){return typeof navigator>"u"||!navigator.clipboard?null:{async hasImageAsync(){if(fe=void 0,!navigator.clipboard.read)return!1;let e=await navigator.clipboard.read();for(let o of e){let n=o.types.find(a=>a.startsWith("image/"));if(n)return fe=await o.getType(n),!0}return!1},async getImageAsync(){if(!fe)return null;let e=fe;return fe=void 0,{data:await new Promise((n,a)=>{let r=new FileReader;r.onload=()=>n(String(r.result)),r.onerror=a,r.readAsDataURL(e)})}},getStringAsync:()=>navigator.clipboard.readText()}}function Ye(){return null}import{useState as Te,useEffect as De,useCallback as ue}from"react";var R={};st(R,{AccessibilityInfo:()=>Tt,ActivityIndicator:()=>ht,Alert:()=>le,Animated:()=>wt,AppRegistry:()=>St,AppState:()=>Rt,Appearance:()=>vt,BackHandler:()=>Ct,Button:()=>yt,CheckBox:()=>Dt,Clipboard:()=>It,DeviceEventEmitter:()=>Qt,Dimensions:()=>se,Easing:()=>Ot,FlatList:()=>Et,I18nManager:()=>Mt,Image:()=>pe,ImageBackground:()=>Ft,InteractionManager:()=>Pt,Keyboard:()=>Vt,KeyboardAvoidingView:()=>_t,LayoutAnimation:()=>Bt,Linking:()=>Wt,LogBox:()=>Zt,Modal:()=>pt,NativeEventEmitter:()=>Ht,NativeModules:()=>xt,PanResponder:()=>ce,Picker:()=>Xt,PixelRatio:()=>kt,Platform:()=>At,Pressable:()=>ft,ProgressBar:()=>Yt,RefreshControl:()=>Nt,SafeAreaView:()=>Ut,ScrollView:()=>Pe,SectionList:()=>$t,Share:()=>Lt,StatusBar:()=>jt,StyleSheet:()=>J,Switch:()=>mt,Text:()=>g,TextInput:()=>we,Touchable:()=>Gt,TouchableHighlight:()=>ut,TouchableNativeFeedback:()=>qt,TouchableOpacity:()=>O,TouchableWithoutFeedback:()=>gt,UIManager:()=>We,Vibration:()=>zt,View:()=>l,VirtualizedList:()=>Kt,YellowBox:()=>Jt,findNodeHandle:()=>Be,unstable_createElement:()=>bt,useColorScheme:()=>eo,useLocaleContext:()=>to,useWindowDimensions:()=>oo});M(R,Go);import*as Go from"react-native-web";import{unstable_createElement as bt,NativeModules as xt,AccessibilityInfo as Tt,Animated as wt,Appearance as vt,AppRegistry as St,AppState as Rt,BackHandler as Ct,Clipboard as It,Dimensions as se,Easing as Ot,I18nManager as Mt,Keyboard as Vt,InteractionManager as Pt,LayoutAnimation as Bt,Linking as Wt,NativeEventEmitter as Ht,PanResponder as ce,PixelRatio as kt,Platform as At,Share as Lt,StyleSheet as J,UIManager as We,Vibration as zt,CheckBox as Dt,FlatList as Et,Image as pe,ImageBackground as Ft,KeyboardAvoidingView as _t,Picker as Xt,ProgressBar as Yt,RefreshControl as Nt,SafeAreaView as Ut,SectionList as $t,StatusBar as jt,Touchable as Gt,TouchableNativeFeedback as qt,VirtualizedList as Kt,YellowBox as Jt,LogBox as Zt,DeviceEventEmitter as Qt,useColorScheme as eo,useLocaleContext as to,useWindowDimensions as oo}from"react-native-web";import*as I from"react-native-web";import{createElement as ct,forwardRef as dt}from"react";function j(e){let o=dt((a,r)=>{let{accessibilityState:y,accessibilityValue:T,...h}=a;for(let b of["busy","checked","disabled","expanded","selected"])y?.[b]!==void 0&&h[`aria-${b}`]===void 0&&(h[`aria-${b}`]=y[b]);for(let[b,c]of Object.entries({min:"valuemin",max:"valuemax",now:"valuenow",text:"valuetext"}))T?.[b]!==void 0&&h[`aria-${c}`]===void 0&&(h[`aria-${c}`]=T[b]);return ct(e,{...h,ref:r})}),n=e.State;return n&&Object.assign(o,{State:n}),o.displayName=e.displayName??e.name??"NativeWebView",o}var l=j(I.View),g=j(I.Text),we=j(I.TextInput),O=j(I.TouchableOpacity),ut=j(I.TouchableHighlight),gt=j(I.TouchableWithoutFeedback),ft=j(I.Pressable),Pe=j(I.ScrollView),pt=j(I.Modal),mt=j(I.Switch),ht=j(I.ActivityIndicator),yt=j(I.Button),Be=I.findNodeHandle,le={alert(e,o,n,a){if(typeof document>"u")return;let r=document.createElement("dialog");r.dataset.buoyRoot="",r.setAttribute("aria-label",e),r.style.cssText="position:fixed;inset:0;margin:auto;width:min(440px,90vw);padding:24px;border:1px solid #475569;border-radius:14px;background:#17212e;color:#f1f5f9;font:14px system-ui;box-shadow:0 20px 80px #0008;z-index:2147483647";let y=document.createElement("h2");if(y.textContent=e,y.style.cssText="font-size:18px;margin:0 0 12px",r.appendChild(y),o){let c=document.createElement("p");c.textContent=o,c.style.cssText="line-height:1.5;white-space:pre-wrap;margin:0 0 20px",r.appendChild(c)}let T=document.createElement("div");T.style.cssText="display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px";let h=n?.length?n:[{text:"OK"}],b=()=>{r.close?.(),r.remove()};for(let c of h){let v=document.createElement("button");v.type="button",v.textContent=c.text??"OK",v.style.cssText=`padding:9px 14px;border:1px solid #64748b;border-radius:7px;color:white;background:${c.style==="destructive"?"#a52b38":"#283a4d"};cursor:pointer;font:inherit`,v.onclick=()=>{b(),c.onPress?.()},T.appendChild(v)}r.addEventListener("cancel",c=>{c.preventDefault(),a?.cancelable!==!1&&(b(),a?.onDismiss?.())}),r.appendChild(T),document.body.appendChild(r),r.showModal?r.showModal():r.setAttribute("open","")}};import{JsModal as vo,ModalHeader as Oe,macOSColors as Je,night as m,Trash2 as So,SquareDashed as Ro,Image as Co,PowerToggleButton as Io,windowDragProps as Oo,ToggleRow as Ee,monoFont as Mo}from"@buoy-gg/shared-ui/web";function ve(e){return new Promise(o=>{try{let n=[];e&&n.push(e),e?.canonical?.publicInstance&&n.push(e.canonical.publicInstance),e?.canonical&&e.canonical!==e&&n.push(e.canonical);for(let a of n)if(typeof a.getBoundingClientRect=="function")try{let r=a.getBoundingClientRect();if(r&&typeof r.x=="number"&&typeof r.y=="number"&&typeof r.width=="number"&&typeof r.height=="number"&&r.width>0&&r.height>0){o({x:r.x,y:r.y,width:r.width,height:r.height});return}}catch{}for(let a of n)if(typeof a.measure=="function"){let r=setTimeout(()=>o(null),200);a.measure((y,T,h,b,c,v)=>{clearTimeout(r),c!=null&&v!=null&&h!=null&&b!=null&&h>0&&b>0?o({x:c,y:v,width:h,height:b}):o(null)});return}try{let a=Be(e);if(a!=null){let r=setTimeout(()=>o(null),200);We.measure(a,(y,T,h,b,c,v)=>{clearTimeout(r),c!=null&&v!=null&&h!=null&&b!=null&&h>0&&b>0?o({x:c,y:v,width:h,height:b}):o(null)});return}}catch{}o(null)}catch{o(null)}})}var He=null;function no(e){He=e??null}function Se(){return He||(typeof globalThis>"u"?null:globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__??null)}var Ne="image-target:",ro=5,ao=22;function io(){let e=Se();if(!e?.getFiberRoots)return[];let o=new Set,n=e.renderers?.keys?Array.from(e.renderers.keys()):[1];for(let a of n)try{e.getFiberRoots(a)?.forEach(r=>o.add(r))}catch{}return[...o]}function lo(e){return e.tag===ao&&e.memoizedState!==null}function so(e){return e.memoizedProps?.activityState===0}function me(e,o,n=0,a=new Set){if(!(!e||a.has(e)||n>500)){if(a.add(e),lo(e)){e.sibling&&me(e.sibling,o,n,a);return}if(so(e)){e.sibling&&me(e.sibling,o,n,a);return}o(e,n),e.child&&me(e.child,o,n+1,a),e.sibling&&me(e.sibling,o,n,a)}}function co(e){let o=new Set(["View","RCTView","Text","RCTText","Image","RCTImageView","ScrollView","RCTScrollView","TextInput","RCTTextInput","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","Pressable","FlatList","SectionList","SafeAreaView"]),n=e._debugOwner||e.return,a=0;for(;n&&a<30;){a++;let r=n.type;if(r){let y=typeof r=="string"?r:r.displayName||r.name;if(y&&!o.has(y))return y}n=n.return}return null}function ne(){let e=io();if(e.length===0)return[];let o=[];for(let n of e)me(n.current,a=>{if(a.tag!==ro)return;let r=a.memoizedProps?.testID??a.pendingProps?.testID??a.memoizedProps?.["data-testid"]??a.pendingProps?.["data-testid"];if(!r||!r.startsWith(Ne))return;let y=r.slice(Ne.length);y&&o.push({label:y,testID:r,fiber:a,instance:a.stateNode,componentName:co(a)})});return o}var Ge={mode:null,enabled:!1,imageUri:null,imageWidth:null,imageHeight:null,targetTag:null,targetLabel:null,targetRect:null,showOutline:!0,opacity:.5,scale:1,offsetX:0,offsetY:0,invertX:!1,invertY:!1,locked:!1,freeX:0,freeY:0,freeWidth:200,freeHeight:200},i={...Ge},Ae=new Set,Z=null,Re=null,he=!1,ye=null,uo=null;function V(){for(let e of Ae)e(i)}function go(e){return e?typeof e._nativeTag=="number"?e._nativeTag:typeof e.canonical?._nativeTag=="number"?e.canonical._nativeTag:typeof e.__nativeTag=="number"?e.__nativeTag:null:null}async function Ue(e,o){let n=await ve(e);if(n)return{rect:n,freshInstance:e};let r=ne().find(y=>y.label===o);return r&&(n=await ve(r.instance),n)?{rect:n,freshInstance:r.instance}:{rect:null,freshInstance:e}}function fo(){if(ye)return;let e=Se();if(e){if(e.rendererInterfaces)for(let[,o]of e.rendererInterfaces)typeof o.setTraceUpdatesEnabled=="function"&&o.setTraceUpdatesEnabled(!0);typeof e.sub=="function"&&(ye=e.sub("traceUpdates",ho))}}var de=null,ke=!1;function po(){if(de!==null||typeof requestAnimationFrame!="function")return;let e=()=>{de=requestAnimationFrame(e),!(ke||!he||!Z)&&(ke=!0,qe().finally(()=>{ke=!1}))};de=requestAnimationFrame(e)}function mo(){de!==null&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(de),de=null}function $e(){mo(),ye&&(ye(),ye=null)}function ho(e){!he||!Z||qe()}function qe(){return ve(Z).then(e=>{if(e&&i.targetRect){let o=i.targetRect;(Math.abs(e.x-o.x)>.5||Math.abs(e.y-o.y)>.5||Math.abs(e.width-o.width)>.5||Math.abs(e.height-o.height)>.5)&&(i={...i,targetRect:e},V())}}).catch(()=>{})}function je(e){return new Promise(o=>{pe.getSize(e,(n,a)=>o({width:n,height:a}),()=>o(null))})}var p={subscribe(e){return Ae.add(e),e(i),()=>Ae.delete(e)},getState(){return i},async setTarget(e,o,n){Z=e,Re=o;let a=n?.stateNode??e,{rect:r,freshInstance:y}=await Ue(a,o);Z=y;let T=go(Z);i={...i,mode:"component",targetTag:T,targetLabel:o,targetRect:r},V()},async setImageUri(e){let o=await je(e),n=1;o&&i.targetRect&&o.width>0&&(n=i.targetRect.width/o.width),i={...i,imageUri:e,imageWidth:o?.width??null,imageHeight:o?.height??null,scale:n,showOutline:!1},V()},setOpacity(e){i={...i,opacity:Math.max(0,Math.min(1,e))},V()},setScale(e){i={...i,scale:Math.max(.01,Math.min(5,e))},V()},setOffset(e,o){i={...i,offsetX:e,offsetY:o},V()},toggleInvertX(){i={...i,invertX:!i.invertX},V()},toggleInvertY(){i={...i,invertY:!i.invertY},V()},setLocked(e){i={...i,locked:e},V()},setAutoTrack(e){he=e,e?(fo(),po()):$e()},isAutoTracking(){return he},setShowOutline(e){i={...i,showOutline:e},V()},setEnabled(e){i={...i,enabled:e},V()},toggle(){i={...i,enabled:!i.enabled},V()},fitWidth(){i.imageWidth&&i.targetRect&&i.imageWidth>0&&(i={...i,scale:i.targetRect.width/i.imageWidth},V())},fitHeight(){i.imageHeight&&i.targetRect&&i.imageHeight>0&&(i={...i,scale:i.targetRect.height/i.imageHeight},V())},resetSettings(){if(i.mode==="free"){let e=se.get("window"),o=i.imageWidth??200,n=i.imageHeight??200;i={...i,opacity:.5,freeX:(e.width-o)/2,freeY:(e.height-n)/2,freeWidth:o,freeHeight:n}}else{let e=1;i.imageWidth&&i.targetRect&&i.imageWidth>0&&(e=i.targetRect.width/i.imageWidth),i={...i,opacity:.5,scale:e,offsetX:0,offsetY:0}}V()},async remeasure(){if(!Re)return;let{rect:e,freshInstance:o}=await Ue(Z,Re);Z=o,e&&(i={...i,targetRect:e},V())},async startFreeMode(e){let o=await je(e),n=se.get("window"),a=o?.width??200,r=o?.height??200,y=a,T=r,h=n.width*.8,b=n.height*.6;if(y>h||T>b){let c=Math.min(h/y,b/T);y=Math.round(y*c),T=Math.round(T*c)}i={...i,mode:"free",enabled:!0,imageUri:e,imageWidth:o?.width??null,imageHeight:o?.height??null,opacity:.5,freeX:Math.round((n.width-y)/2),freeY:Math.round((n.height-T)/2),freeWidth:Math.round(y),freeHeight:Math.round(T)},V()},setFreePosition(e,o){i={...i,freeX:e,freeY:o},V()},setFreeDimensions(e,o,n,a){i={...i,freeWidth:e,freeHeight:o,freeX:n,freeY:a},V()},reset(){$e(),he=!1,uo=null,Z=null,Re=null,i={...Ge},V()}};import{ChevronRight as yo,night as q,Hash as bo,ToolEmptyState as xo,monoFont as Le}from"@buoy-gg/shared-ui/web";import{jsx as K,jsxs as be}from"react/jsx-runtime";function To({target:e,onSelect:o}){return be(O,{style:X.card,onPress:o,activeOpacity:.7,children:[K(l,{style:X.colorIndicator}),be(l,{style:X.content,children:[K(l,{style:X.topRow,children:K(g,{style:X.label,numberOfLines:1,children:e.label})}),be(l,{style:X.bottomRow,children:[be(l,{style:X.badgeRow,children:[be(l,{style:X.testIdBadge,children:[K(bo,{size:8,color:q.color.accent}),K(g,{style:X.testIdBadgeText,children:"testID"})]}),K(g,{style:X.testIdValue,numberOfLines:1,children:e.testID})]}),e.componentName&&K(g,{style:X.componentName,numberOfLines:1,children:e.componentName})]})]}),K(yo,{size:16,color:q.color.textTertiary})]})}function Ke({targets:e,onSelect:o}){return e.length===0?K(xo,{testID:"buoy-image-overlay-empty",otto:"scanning",title:"No targets found",message:`Tag components with
3
+ testID="image-target:YourLabel"`}):K(Pe,{style:X.list,contentContainerStyle:X.listContent,children:e.map(n=>K(To,{target:n,onSelect:()=>o(n)},n.testID))})}var X=J.create({list:{flex:1},listContent:{paddingTop:8,paddingBottom:20},card:{flexDirection:"row",alignItems:"center",paddingVertical:10,paddingHorizontal:12,marginHorizontal:12,marginBottom:6,backgroundColor:q.color.surface,borderRadius:8,borderWidth:1,borderColor:q.color.border},colorIndicator:{width:4,height:36,borderRadius:2,marginRight:10,backgroundColor:q.color.accent},content:{flex:1,marginRight:8},topRow:{flexDirection:"row",alignItems:"center",marginBottom:4},label:{fontSize:13,fontWeight:"600",color:q.color.text,flex:1},bottomRow:{gap:2},badgeRow:{flexDirection:"row",alignItems:"center",gap:4},testIdBadge:{flexDirection:"row",alignItems:"center",paddingVertical:2,paddingHorizontal:5,borderRadius:4,backgroundColor:q.color.accentSoft,gap:3},testIdBadgeText:{fontSize:9,fontWeight:"700",color:q.color.accent},testIdValue:{fontSize:11,color:q.color.text,fontFamily:Le,flex:1},componentName:{fontSize:10,color:"#a855f7",fontFamily:Le,marginTop:1},emptyState:{alignItems:"center",paddingVertical:40,paddingHorizontal:32},emptyTitle:{color:q.color.text,fontSize:14,fontWeight:"600",marginBottom:6},emptyText:{color:q.color.textTertiary,fontSize:12,textAlign:"center",lineHeight:18,fontFamily:Le}});import{useRef as oe,useCallback as Ce}from"react";import{night as Q,monoFont as wo}from"@buoy-gg/shared-ui/web";import{jsx as F,jsxs as Ie}from"react/jsx-runtime";function xe({label:e,value:o,min:n,max:a,step:r,displayValue:y,onChange:T}){let h=oe(0),b=oe(0),c=Ce(S=>Math.max(n,Math.min(a,S)),[n,a]),v=a>n?(o-n)/(a-n):0,L=oe(T),N=oe(c),u=oe(n),W=oe(a);L.current=T,N.current=c,u.current=n,W.current=a;let H=Ce(S=>{let E=S-b.current,k=Math.max(0,Math.min(1,E/h.current)),A=u.current+k*(W.current-u.current);L.current(N.current(A))},[]),D=oe(ce.create({onStartShouldSetPanResponder:()=>!0,onMoveShouldSetPanResponder:()=>!0,onPanResponderTerminationRequest:()=>!1,onPanResponderGrant:S=>{H(S.nativeEvent.pageX)},onPanResponderMove:S=>{H(S.nativeEvent.pageX)}})).current,U=Ce(S=>{h.current=S.nativeEvent.layout.width},[]),ee=oe(null),_=Ce(()=>{ee.current?.measure?.((S,E,k,A,x)=>{b.current=x})},[]);return Ie(l,{style:Y.controlRow,children:[F(g,{style:Y.label,children:e}),Ie(l,{style:Y.controlRight,children:[F(O,{style:Y.stepperButton,onPress:()=>T(c(o-r)),activeOpacity:.6,children:F(g,{style:Y.stepperButtonText,children:"-"})}),F(l,{style:Y.sliderHitArea,...D.panHandlers,children:Ie(l,{ref:ee,style:Y.sliderTrack,onLayout:S=>{U(S),_()},pointerEvents:"none",children:[F(l,{style:[Y.sliderFill,{width:`${v*100}%`}]}),F(l,{style:[Y.sliderThumb,{left:`${v*100}%`}]})]})}),F(O,{style:Y.stepperButton,onPress:()=>T(c(o+r)),activeOpacity:.6,children:F(g,{style:Y.stepperButtonText,children:"+"})}),F(g,{style:Y.valueText,children:y})]})]})}function ze({opacity:e,scale:o,offsetX:n,offsetY:a,onOpacityChange:r,onScaleChange:y,onOffsetChange:T,opacityOnly:h}){return h?F(xe,{label:"Opacity",value:e,min:0,max:1,step:.05,displayValue:`${Math.round(e*100)}%`,onChange:r}):Ie(l,{style:Y.container,children:[F(xe,{label:"Opacity",value:e,min:0,max:1,step:.05,displayValue:`${Math.round(e*100)}%`,onChange:r}),F(xe,{label:"Scale",value:o,min:.05,max:3,step:.05,displayValue:`${Math.round(o*100)}%`,onChange:y}),F(xe,{label:"Offset X",value:n,min:-200,max:200,step:2,displayValue:`${Math.round(n)}px`,onChange:b=>T(Math.round(b),a)}),F(xe,{label:"Offset Y",value:a,min:-200,max:200,step:2,displayValue:`${Math.round(a)}px`,onChange:b=>T(n,Math.round(b))})]})}var Y=J.create({container:{gap:8},controlRow:{paddingHorizontal:12,gap:4},label:{fontSize:11,fontWeight:"600",color:Q.color.textTertiary,textTransform:"uppercase",letterSpacing:.5},controlRight:{flexDirection:"row",alignItems:"center",gap:6},stepperButton:{width:28,height:28,borderRadius:6,backgroundColor:Q.color.surfaceElevated,borderWidth:1,borderColor:Q.color.border,justifyContent:"center",alignItems:"center"},stepperButtonText:{fontSize:16,fontWeight:"700",color:Q.color.text,lineHeight:18},sliderHitArea:{flex:1,paddingVertical:20,paddingHorizontal:40,marginVertical:-20,marginHorizontal:-40},sliderTrack:{height:28,justifyContent:"center",borderRadius:6,backgroundColor:Q.color.bg,borderWidth:1,borderColor:Q.color.border,overflow:"hidden"},sliderFill:{position:"absolute",left:0,top:0,bottom:0,backgroundColor:Q.color.accentFill,borderRadius:5},sliderThumb:{position:"absolute",width:4,top:4,bottom:4,marginLeft:-2,borderRadius:2,backgroundColor:Q.color.accent},valueText:{fontSize:11,fontWeight:"600",color:Q.color.text,width:45,textAlign:"right",fontFamily:wo}});import{Fragment as Ze,jsx as s,jsxs as f}from"react/jsx-runtime";var re="";function Me({visible:e,onClose:o,onBack:n,onMinimize:a,enableSharedModalDimensions:r=!1,initialModalState:y,minimizeTargetPosition:T,inline:h=!1,initialView:b}){let[c,v]=Te(()=>{if(b)return b;let d=p.getState();return d.mode==="free"?"free":d.targetRect?"controls":"idle"}),[L,N]=Te(()=>b==="targets"?ne():[]),[u,W]=Te(()=>p.getState()),[H,D]=Te(re),[U,ee]=Te(()=>p.isAutoTracking());De(()=>{re=H},[H]),De(()=>p.subscribe(W),[]),De(()=>(p.setAutoTrack(U),()=>{}),[U]);let _=ue(()=>{let d=ne();N(d),v("targets")},[]),S=ue(async d=>{await p.setTarget(d.instance,d.label,d.fiber),re.trim()&&(await p.setImageUri(re.trim()),p.setEnabled(!0)),v("controls")},[]),E=ue(async()=>{let d=re.trim();d&&(c==="free"?await p.startFreeMode(d):(await p.setImageUri(d),p.setEnabled(!0)))},[c]),k=ue(async()=>{let d=null;try{d=Xe()}catch{}let w=null;if(!d)try{let C=Ye();w=C.default||C}catch{}if(!d&&!w){le.alert("Clipboard not available","Install expo-clipboard or @react-native-clipboard/clipboard to use paste.");return}try{if(d&&typeof d.hasImageAsync=="function"&&await d.hasImageAsync()){let $=await d.getImageAsync({format:"png"});if($?.data){c==="free"?await p.startFreeMode($.data):(await p.setImageUri($.data),p.setEnabled(!0));return}}let C=null;if(d&&typeof d.getStringAsync=="function"?C=await d.getStringAsync():w&&typeof w.getString=="function"&&(C=await w.getString()),C&&(C.startsWith("http")||C.startsWith("data:"))){D(C),re=C;return}d?le.alert("No image detected","Try saving your design image, opening it in Photos or Preview, and copying it from there."):le.alert("Image paste requires expo-clipboard","Your project uses @react-native-clipboard/clipboard which only supports text. Install expo-clipboard to paste images directly, or paste an image URL instead.")}catch{le.alert("Paste failed","Something went wrong reading the clipboard. Try copying the image again.")}},[c]),A=ue(()=>{p.reset(),re="",v("idle"),D(""),N([])},[]),x=ue(()=>{if(c==="controls"){let d=ne();N(d),v("targets")}else(c==="targets"||c==="free")&&p.reset(),v("idle")},[c]),z=(()=>{switch(c){case"targets":return`Targets (${L.length})`;case"controls":return u.targetLabel||"Component Match";case"free":return"Free Placement";default:return"Image Overlay"}})(),ae={showToggleButton:!1,customContent:f(Oe,{children:[s(Oe.Navigation,{onBack:c!=="idle"?x:n}),s(Oe.Content,{title:z}),f(Oe.Actions,{children:[c==="targets"&&s(O,{style:t.headerTextButton,onPress:_,activeOpacity:.7,children:s(g,{style:t.headerTextButtonLabel,children:"Scan"})}),(c==="controls"||c==="free")&&u.imageUri&&s(Io,{isEnabled:u.enabled,onToggle:()=>p.toggle(),accessibilityLabel:"Toggle image overlay"}),c!=="idle"&&s(O,{style:t.headerActionButton,onPress:A,activeOpacity:.7,children:s(So,{size:14,color:m.color.textSecondary})})]})]})},ie=f(l,{style:t.container,children:[c==="idle"&&f(l,{style:t.homeContainer,children:[s(g,{style:t.homeSubtitle,children:"Choose a mode"}),f(l,{style:t.modeGrid,children:[f(O,{style:t.modePanel,onPress:_,activeOpacity:.7,children:[s(l,{style:[t.modeIconCircle,{backgroundColor:m.color.accentSoft}],children:s(Ro,{size:24,color:m.color.accent})}),f(g,{style:t.modePanelTitle,children:["Component",`
4
+ `,"Match"]}),s(g,{style:t.modePanelDesc,children:"Overlay on a tagged component"})]}),f(O,{style:t.modePanel,onPress:()=>v("free"),activeOpacity:.7,children:[s(l,{style:[t.modeIconCircle,{backgroundColor:m.color.accentSoft}],children:s(Co,{size:24,color:m.color.accent})}),f(g,{style:t.modePanelTitle,children:["Free",`
5
+ `,"Placement"]}),s(g,{style:t.modePanelDesc,children:"Drag and resize anywhere"})]})]})]}),c==="targets"&&s(Ke,{targets:L,onSelect:S}),c==="controls"&&f(l,{style:t.controlsContainer,children:[u.targetRect&&f(l,{style:t.infoCard,children:[f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Component"}),s(g,{style:t.infoValue,children:u.targetLabel})]}),f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Size"}),f(g,{style:t.infoValueMono,children:[Math.round(u.targetRect.width)," x ",Math.round(u.targetRect.height)]})]}),f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Position"}),f(g,{style:t.infoValueMono,children:[Math.round(u.targetRect.x),", ",Math.round(u.targetRect.y)]})]}),u.imageWidth!=null&&u.imageHeight!=null&&f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Image"}),f(g,{style:t.infoValueMono,children:[u.imageWidth," x ",u.imageHeight]})]})]}),f(l,{style:t.inputSection,children:[s(g,{style:t.sectionLabel,children:"Image"}),s(O,{style:t.pasteButton,onPress:k,activeOpacity:.7,accessibilityLabel:"Paste from clipboard",accessibilityRole:"button",children:s(g,{style:t.pasteButtonText,children:"Paste from Clipboard"})}),f(l,{style:t.urlRow,children:[s(we,{style:t.urlInput,value:H,onChangeText:D,placeholder:"or enter URL...",placeholderTextColor:m.color.placeholder,autoCapitalize:"none",autoCorrect:!1,returnKeyType:"go",onSubmitEditing:E}),s(O,{style:[t.loadButton,!H.trim()&&t.loadButtonDisabled],onPress:E,disabled:!H.trim(),activeOpacity:.7,children:s(g,{style:t.loadButtonText,children:"Load"})})]})]}),f(l,{style:t.toggleSection,children:[s(Ee,{label:"Show Outline",value:u.showOutline,onValueChange:d=>p.setShowOutline(d)}),s(Ee,{label:"Auto Track",value:U,onValueChange:ee}),u.imageUri&&f(Ze,{children:[f(l,{style:t.blendRow,children:[s(g,{style:t.toggleLabel,children:"Opacity"}),s(l,{style:t.blendButtons,children:[0,25,50,75,100].map(d=>s(O,{style:[t.blendButton,Math.round(u.opacity*100)===d&&t.blendButtonActive],onPress:()=>p.setOpacity(d/100),activeOpacity:.7,children:f(g,{style:[t.blendButtonText,Math.round(u.opacity*100)===d&&t.blendButtonTextActive],children:[d,"%"]})},d))})]}),f(l,{style:t.blendRow,children:[s(g,{style:t.toggleLabel,children:"Flip"}),f(l,{style:t.blendButtons,children:[s(O,{style:[t.blendButton,u.invertX&&t.blendButtonActive],onPress:()=>p.toggleInvertX(),activeOpacity:.7,children:s(g,{style:[t.blendButtonText,u.invertX&&t.blendButtonTextActive],children:"Horizontal"})}),s(O,{style:[t.blendButton,u.invertY&&t.blendButtonActive],onPress:()=>p.toggleInvertY(),activeOpacity:.7,children:s(g,{style:[t.blendButtonText,u.invertY&&t.blendButtonTextActive],children:"Vertical"})})]})]})]})]}),u.imageUri&&s(ze,{opacity:u.opacity,scale:u.scale,offsetX:u.offsetX,offsetY:u.offsetY,onOpacityChange:d=>p.setOpacity(d),onScaleChange:d=>p.setScale(d),onOffsetChange:(d,w)=>p.setOffset(d,w)}),f(l,{style:t.actions,children:[s(O,{style:t.actionButton,onPress:()=>p.resetSettings(),activeOpacity:.7,children:s(g,{style:t.actionButtonText,children:"Reset Settings"})}),s(O,{style:t.actionButton,onPress:()=>p.remeasure(),activeOpacity:.7,children:s(g,{style:t.actionButtonText,children:"Remeasure"})})]})]}),c==="free"&&f(l,{style:t.controlsContainer,children:[f(l,{style:t.inputSection,children:[s(g,{style:t.sectionLabel,children:"Image"}),s(O,{style:t.pasteButton,onPress:k,activeOpacity:.7,accessibilityLabel:"Paste from clipboard",accessibilityRole:"button",children:s(g,{style:t.pasteButtonText,children:"Paste from Clipboard"})}),f(l,{style:t.urlRow,children:[s(we,{style:t.urlInput,value:H,onChangeText:D,placeholder:"or enter URL...",placeholderTextColor:m.color.placeholder,autoCapitalize:"none",autoCorrect:!1,returnKeyType:"go",onSubmitEditing:E}),s(O,{style:[t.loadButton,!H.trim()&&t.loadButtonDisabled],onPress:E,disabled:!H.trim(),activeOpacity:.7,children:s(g,{style:t.loadButtonText,children:"Load"})})]})]}),u.imageUri&&u.mode==="free"&&f(Ze,{children:[f(l,{style:t.infoCard,children:[f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Position"}),f(g,{style:t.infoValueMono,children:[Math.round(u.freeX),", ",Math.round(u.freeY)]})]}),f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Size"}),f(g,{style:t.infoValueMono,children:[Math.round(u.freeWidth)," x ",Math.round(u.freeHeight)]})]}),u.imageWidth!=null&&u.imageHeight!=null&&f(l,{style:t.infoRow,children:[s(g,{style:t.infoLabel,children:"Original"}),f(g,{style:t.infoValueMono,children:[u.imageWidth," x ",u.imageHeight]})]})]}),s(l,{style:t.inputSection,children:s(ze,{opacity:u.opacity,scale:1,offsetX:0,offsetY:0,onOpacityChange:d=>p.setOpacity(d),onScaleChange:()=>{},onOffsetChange:()=>{},opacityOnly:!0})}),f(l,{style:t.toggleSection,children:[s(Ee,{label:"Lock Position",tone:"warning",value:u.locked,onValueChange:d=>p.setLocked(d)}),f(l,{style:t.blendRow,children:[s(g,{style:t.toggleLabel,children:"Opacity"}),s(l,{style:t.blendButtons,children:[0,25,50,75,100].map(d=>s(O,{style:[t.blendButton,Math.round(u.opacity*100)===d&&t.blendButtonActive],onPress:()=>p.setOpacity(d/100),activeOpacity:.7,children:f(g,{style:[t.blendButtonText,Math.round(u.opacity*100)===d&&t.blendButtonTextActive],children:[d,"%"]})},d))})]}),f(l,{style:t.blendRow,children:[s(g,{style:t.toggleLabel,children:"Flip"}),f(l,{style:t.blendButtons,children:[s(O,{style:[t.blendButton,u.invertX&&t.blendButtonActive],onPress:()=>p.toggleInvertX(),activeOpacity:.7,children:s(g,{style:[t.blendButtonText,u.invertX&&t.blendButtonTextActive],children:"Horizontal"})}),s(O,{style:[t.blendButton,u.invertY&&t.blendButtonActive],onPress:()=>p.toggleInvertY(),activeOpacity:.7,children:s(g,{style:[t.blendButtonText,u.invertY&&t.blendButtonTextActive],children:"Vertical"})})]})]})]}),s(l,{style:t.actions,children:s(O,{style:t.actionButton,onPress:()=>p.resetSettings(),activeOpacity:.7,children:s(g,{style:t.actionButtonText,children:"Reset Position"})})})]})]})]});return h?f(l,{style:t.inlineRoot,children:[s(l,{style:t.inlineHeader,...Oo,children:ae.customContent}),ie]}):s(vo,{visible:e,onClose:o,onMinimize:a,minimizeTargetPosition:T,initialModalState:y,header:ae,initialMode:"bottomSheet",persistenceKey:"buoy-image-overlay-modal",enablePersistence:!0,children:ie})}function Vo(e={}){return s(Me,{...e,inline:!0,visible:!0,onClose:()=>{}})}var t=J.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:Je.background.base},inlineHeader:{borderBottomWidth:1,borderBottomColor:Je.border.default,backgroundColor:"transparent"},container:{flex:1,backgroundColor:"transparent"},headerTextButton:{height:32,paddingHorizontal:12,borderRadius:8,backgroundColor:m.color.buttonSurface,borderWidth:1,borderColor:m.color.border,alignItems:"center",justifyContent:"center"},headerTextButtonLabel:{fontSize:12,fontWeight:"600",color:m.color.accent},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:m.color.buttonSurface,borderWidth:1,borderColor:m.color.border,alignItems:"center",justifyContent:"center"},homeContainer:{flex:1,paddingHorizontal:12,paddingTop:16,paddingBottom:20,gap:12},homeSubtitle:{fontSize:12,fontWeight:"600",color:m.color.textTertiary,textTransform:"uppercase",letterSpacing:.5,textAlign:"center"},modeGrid:{flexDirection:"row",gap:10},modePanel:{flex:1,alignItems:"center",paddingVertical:20,paddingHorizontal:12,backgroundColor:m.color.surface,borderRadius:12,borderWidth:1,borderColor:m.color.border,gap:8},modeIconCircle:{width:48,height:48,borderRadius:24,alignItems:"center",justifyContent:"center",marginBottom:4},modePanelTitle:{fontSize:13,fontWeight:"700",color:m.color.text,textAlign:"center",lineHeight:17},modePanelDesc:{fontSize:11,color:m.color.textTertiary,textAlign:"center",lineHeight:15},controlsContainer:{flex:1,paddingTop:8,paddingBottom:24,gap:10},infoCard:{marginHorizontal:12,backgroundColor:m.color.surface,borderRadius:8,borderWidth:1,borderColor:m.color.border,padding:10,gap:4},infoRow:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},infoLabel:{fontSize:11,fontWeight:"500",color:m.color.textTertiary,textTransform:"uppercase",letterSpacing:.3},infoValue:{fontSize:12,fontWeight:"600",color:m.color.text},infoValueMono:{fontSize:12,fontWeight:"600",color:m.color.text,fontFamily:Mo},inputSection:{paddingHorizontal:12,gap:6},sectionLabel:{fontSize:11,fontWeight:"600",color:m.color.textTertiary,textTransform:"uppercase",letterSpacing:.5},pasteButton:{paddingVertical:10,borderRadius:8,backgroundColor:m.color.accentSoft,borderWidth:1,borderColor:m.color.accentBorderStrong,alignItems:"center"},pasteButtonText:{fontSize:13,fontWeight:"600",color:m.color.accent},urlRow:{flexDirection:"row",gap:8},urlInput:{flex:1,height:36,borderRadius:10,backgroundColor:m.color.surfaceElevated,borderWidth:1,borderColor:m.color.border,paddingHorizontal:12,fontSize:13,color:m.color.text},loadButton:{height:36,paddingHorizontal:14,borderRadius:8,backgroundColor:m.color.accent,justifyContent:"center",alignItems:"center"},loadButtonDisabled:{opacity:.4},loadButtonText:{fontSize:13,fontWeight:"600",color:m.color.onAccent},toggleSection:{paddingHorizontal:12,gap:6},toggleRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:m.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:m.color.border,paddingHorizontal:12,paddingVertical:8},toggleLabel:{fontSize:13,fontWeight:"500",color:m.color.textSecondary},blendRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:m.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:m.color.border,paddingHorizontal:12,paddingVertical:6},blendButtons:{flexDirection:"row",gap:4},blendButton:{paddingHorizontal:10,paddingVertical:5,borderRadius:6,backgroundColor:"transparent"},blendButtonActive:{backgroundColor:m.color.accentSoft},blendButtonText:{fontSize:12,fontWeight:"600",color:m.color.textTertiary},blendButtonTextActive:{color:m.color.accent},actions:{flexDirection:"row",paddingHorizontal:12,gap:8,marginTop:4},actionButton:{flex:1,flexDirection:"row",alignItems:"center",justifyContent:"center",gap:6,paddingVertical:8,paddingHorizontal:12,borderRadius:8,backgroundColor:m.color.surfaceElevated,borderWidth:1,borderColor:m.color.border},actionButtonText:{fontSize:12,fontWeight:"500",color:m.color.textSecondary}});import{jsx as Ao}from"react/jsx-runtime";var Ho="#A855F7",Qe=Wo({id:"image-overlay",name:"IMG",description:"Design mockup overlay",slot:"both",icon:({size:e})=>Ao(Po,{size:e,color:Ho}),component:Me,props:{enableSharedModalDimensions:!1}}),ko=Bo(Qe,e=>({enableSharedModalDimensions:e.enableSharedModalDimensions??!1}));import{useState as ot,useEffect as Lo,useRef as et,useMemo as tt,useCallback as zo}from"react";import{absoluteFill as Do,monoFont as Eo}from"@buoy-gg/shared-ui/web";import{Fragment as rt,jsx as B,jsxs as Ve}from"react/jsx-runtime";function nt({uri:e,style:o,invertX:n,invertY:a}){let r=[];n&&r.push({scaleX:-1}),a&&r.push({scaleY:-1});let y=r.length>0?{transform:r}:{};return B(pe,{testID:"buoy-internal-image-overlay",source:{uri:e},style:[o,y],pointerEvents:"none"})}var ge=30;function Fo({imageUri:e,x:o,y:n,width:a,height:r,opacity:y,aspectRatio:T,invertX:h,invertY:b,locked:c}){let[v,L]=ot(!1),N=et({x:0,y:0}),u=et({x:0,y:0,width:0,height:0}),W=se.get("window"),H=tt(()=>ce.create({onStartShouldSetPanResponder:()=>!c,onMoveShouldSetPanResponder:(_,S)=>!c&&(Math.abs(S.dx)>2||Math.abs(S.dy)>2),onPanResponderTerminationRequest:()=>!1,onPanResponderGrant:()=>{let _=p.getState();N.current={x:_.freeX,y:_.freeY},L(!0)},onPanResponderMove:(_,S)=>{let E=p.getState(),k=Math.max(0,Math.min(N.current.x+S.dx,W.width-E.freeWidth)),A=Math.max(0,Math.min(N.current.y+S.dy,W.height-E.freeHeight));p.setFreePosition(k,A)},onPanResponderRelease:()=>L(!1),onPanResponderTerminate:()=>L(!1)}),[W.width,W.height,c]),D=zo(_=>ce.create({onStartShouldSetPanResponder:()=>!c,onMoveShouldSetPanResponder:()=>!c,onPanResponderTerminationRequest:()=>!1,onPanResponderGrant:()=>{let S=p.getState();u.current={x:S.freeX,y:S.freeY,width:S.freeWidth,height:S.freeHeight},L(!0)},onPanResponderMove:(S,E)=>{let{dx:k,dy:A}=E;if(Math.abs(k)<.5&&Math.abs(A)<.5)return;let x=u.current,z=T>0?T:x.width/x.height,ae=Math.abs(k),ie=Math.abs(A),d=ae>=ie,w,C,te=x.x,$=x.y;switch(_){case"bottomRight":{w=d?x.width+k:(x.height+A)*z,w=Math.max(ge,Math.min(w,W.width-x.x)),C=w/z;break}case"bottomLeft":{w=d?x.width-k:(x.height+A)*z,w=Math.max(ge,w),C=w/z,te=x.x+x.width-w,te=Math.max(0,te),w=Math.min(w,x.x+x.width),C=w/z;break}case"topRight":{w=d?x.width+k:(x.height-A)*z,w=Math.max(ge,Math.min(w,W.width-x.x)),C=w/z,$=x.y+x.height-C,$=Math.max(0,$),C=Math.min(C,x.y+x.height),w=C*z;break}case"topLeft":{w=d?x.width-k:(x.height-A)*z,w=Math.max(ge,w),C=w/z,te=x.x+x.width-w,$=x.y+x.height-C,te=Math.max(0,te),$=Math.max(0,$),w=Math.min(w,x.x+x.width),C=w/z,$=x.y+x.height-C;break}}w=Math.max(ge,Math.round(w)),C=Math.max(ge,Math.round(C)),p.setFreeDimensions(w,C,Math.round(te),Math.round($))},onPanResponderRelease:()=>L(!1),onPanResponderTerminate:()=>L(!1)}),[W.width,W.height,T,c]),U=tt(()=>({topLeft:D("topLeft"),topRight:D("topRight"),bottomLeft:D("bottomLeft"),bottomRight:D("bottomRight")}),[D]),ee={...Do,opacity:y};return Ve(l,{style:{position:"absolute",left:o,top:n,width:a,height:r},pointerEvents:c?"none":"auto",children:[B(l,{style:J.absoluteFill,...c?{}:H.panHandlers,children:B(nt,{uri:e,style:ee,invertX:h,invertY:b})}),B(l,{pointerEvents:"none",style:[P.freeBorder,v&&P.freeBorderActive,c&&P.freeBorderLocked]}),!c&&Ve(rt,{children:[B(l,{...U.topLeft.panHandlers,style:[P.cornerWrapper,{top:-20,left:-20}],hitSlop:{top:10,left:10,right:10,bottom:10},children:B(l,{style:[P.cornerHandle,v&&P.cornerHandleActive]})}),B(l,{...U.topRight.panHandlers,style:[P.cornerWrapper,{top:-20,right:-20}],hitSlop:{top:10,left:10,right:10,bottom:10},children:B(l,{style:[P.cornerHandle,v&&P.cornerHandleActive]})}),B(l,{...U.bottomLeft.panHandlers,style:[P.cornerWrapper,{bottom:-20,left:-20}],hitSlop:{top:10,left:10,right:10,bottom:10},children:B(l,{style:[P.cornerHandle,v&&P.cornerHandleActive]})}),B(l,{...U.bottomRight.panHandlers,style:[P.cornerWrapper,{bottom:-20,right:-20}],hitSlop:{top:10,left:10,right:10,bottom:10},children:B(l,{style:[P.cornerHandle,v&&P.cornerHandleActive]})})]})]})}function _o(){let[e,o]=ot(()=>p.getState());Lo(()=>p.subscribe(o),[]);let{mode:n,enabled:a,imageUri:r,imageWidth:y,imageHeight:T,targetRect:h,targetLabel:b,showOutline:c,opacity:v,scale:L,offsetX:N,offsetY:u,invertX:W,invertY:H,locked:D,freeX:U,freeY:ee,freeWidth:_,freeHeight:S}=e,E=y&&T&&T>0?y/T:0;if(n==="free"&&a&&r)return B(l,{style:P.overlay,pointerEvents:"box-none",nativeID:"image-overlay-standalone",children:B(Fo,{imageUri:r,x:U,y:ee,width:_,height:S,opacity:v,aspectRatio:E,invertX:W,invertY:H,locked:D})});if(!h)return null;let k=a&&r;return Ve(l,{style:P.overlay,pointerEvents:"box-none",nativeID:"image-overlay-standalone",children:[c&&Ve(rt,{children:[B(l,{pointerEvents:"none",nativeID:"__image_overlay_highlight__",style:[P.highlight,{left:h.x-3,top:h.y-3,width:h.width+6,height:h.height+6}]}),b&&B(l,{pointerEvents:"none",style:[P.label,{left:h.x,top:h.y-20}],children:B(g,{style:P.labelText,children:b})})]}),k&&(()=>{let A=(y??h.width)*L,x=(T??h.height)*L,z=h.x+h.width/2,ae=h.y+h.height/2,ie={position:"absolute",left:z-A/2+N,top:ae-x/2+u,width:A,height:x,opacity:v};return B(nt,{uri:r,style:ie,invertX:W,invertY:H})})()]})}var P=J.create({overlay:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:999999,elevation:999999,borderWidth:0},highlight:{position:"absolute",borderWidth:2,borderStyle:"dashed",borderColor:"#20C997",backgroundColor:"transparent"},label:{position:"absolute",backgroundColor:"#20C997",paddingHorizontal:6,paddingVertical:2,borderRadius:3},labelText:{color:"#fff",fontSize:10,fontWeight:"700",fontFamily:Eo},freeBorder:{position:"absolute",top:-6,left:-6,right:-6,bottom:-6,borderWidth:1,borderColor:"transparent",borderRadius:4},freeBorderActive:{borderColor:"#20C997",borderStyle:"dashed"},freeBorderLocked:{borderColor:"#f59e0b",borderStyle:"solid"},cornerWrapper:{position:"absolute",width:24,height:24,zIndex:1e3,alignItems:"center",justifyContent:"center"},cornerHandle:{width:12,height:12,borderRadius:6,backgroundColor:"#20C997",borderWidth:2,borderColor:"#fff"},cornerHandleActive:{backgroundColor:"#10b981",shadowColor:"#10b981",shadowOffset:{width:0,height:0},shadowOpacity:.8,shadowRadius:6}});export{p as ImageOverlayController,Me as ImageOverlayModal,_o as ImageOverlayStandalone,Vo as ImageOverlayToolView,ko as createImageOverlayTool,Qe as imageOverlayToolPreset,ne as scanForImageTargets,no as setDevToolsHookOverride};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/image-overlay",
3
- "version": "7.0.40",
3
+ "version": "7.0.41",
4
4
  "description": "Overlay a design mockup on a running React Native app and adjust opacity to compare spacing and alignment.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -10,27 +10,37 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "react-native": "./lib/module/index.js",
13
+ "browser": {
14
+ "types": "./lib/typescript/index.d.ts",
15
+ "default": "./lib/web/index.mjs"
16
+ },
13
17
  "import": {
14
- "default": "./lib/module/index.js",
15
- "types": "./lib/typescript/index.d.ts"
18
+ "types": "./lib/typescript/index.d.ts",
19
+ "default": "./lib/module/index.js"
16
20
  },
17
21
  "require": {
18
- "default": "./lib/commonjs/index.js",
19
- "types": "./lib/typescript/index.d.ts"
22
+ "types": "./lib/typescript/index.d.ts",
23
+ "default": "./lib/commonjs/index.js"
20
24
  }
21
25
  },
22
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./web": {
28
+ "types": "./lib/typescript/index.d.ts",
29
+ "default": "./lib/web/index.mjs"
30
+ }
23
31
  },
24
32
  "files": [
25
33
  "lib"
26
34
  ],
27
35
  "sideEffects": false,
28
36
  "dependencies": {
29
- "@buoy-gg/shared-ui": "^7.0.40"
37
+ "@buoy-gg/shared-ui": "^7.0.41"
30
38
  },
31
39
  "peerDependencies": {
32
40
  "react": "*",
33
- "react-native": "*"
41
+ "react-native": "*",
42
+ "react-native-web": "^0.21.0",
43
+ "react-dom": ">=18"
34
44
  },
35
45
  "devDependencies": {
36
46
  "@types/react": "^19.1.0",
@@ -85,10 +95,22 @@
85
95
  "pixel-perfect",
86
96
  "ui-review"
87
97
  ],
98
+ "peerDependenciesMeta": {
99
+ "react-native": {
100
+ "optional": true
101
+ },
102
+ "react-native-web": {
103
+ "optional": true
104
+ },
105
+ "react-dom": {
106
+ "optional": true
107
+ }
108
+ },
88
109
  "scripts": {
89
- "build": "bob build",
110
+ "build": "bob build && pnpm run build:web",
90
111
  "typecheck": "tsc --noEmit",
91
112
  "clean": "rimraf lib",
92
- "test": "pnpm run typecheck"
113
+ "test": "pnpm run typecheck",
114
+ "build:web": "node ../../scripts/build-web.mjs image-overlay"
93
115
  }
94
116
  }