@buoy-gg/image-overlay 7.0.41 → 7.0.43

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 (42) hide show
  1. package/README.md +12 -76
  2. package/lib/commonjs/imageOverlay/components/ImageOverlayModal.js +1 -880
  3. package/lib/commonjs/imageOverlay/components/ImageOverlayStandalone.js +1 -463
  4. package/lib/commonjs/imageOverlay/components/OverlayControls.js +1 -253
  5. package/lib/commonjs/imageOverlay/components/TargetList.js +1 -181
  6. package/lib/commonjs/imageOverlay/types/index.js +1 -1
  7. package/lib/commonjs/imageOverlay/utils/ImageOverlayController.js +1 -426
  8. package/lib/commonjs/imageOverlay/utils/componentMeasurement.js +1 -106
  9. package/lib/commonjs/imageOverlay/utils/devtoolsHook.js +1 -36
  10. package/lib/commonjs/imageOverlay/utils/fiberScanner.js +1 -104
  11. package/lib/commonjs/imageOverlay/utils/imageClipboard.js +1 -22
  12. package/lib/commonjs/imageOverlay/utils/imageClipboard.web.js +1 -44
  13. package/lib/commonjs/index.js +1 -59
  14. package/lib/commonjs/preset.js +1 -42
  15. package/lib/module/imageOverlay/components/ImageOverlayModal.js +1 -875
  16. package/lib/module/imageOverlay/components/ImageOverlayStandalone.js +1 -459
  17. package/lib/module/imageOverlay/components/OverlayControls.js +1 -249
  18. package/lib/module/imageOverlay/components/TargetList.js +1 -177
  19. package/lib/module/imageOverlay/types/index.js +1 -1
  20. package/lib/module/imageOverlay/utils/ImageOverlayController.js +1 -422
  21. package/lib/module/imageOverlay/utils/componentMeasurement.js +1 -102
  22. package/lib/module/imageOverlay/utils/devtoolsHook.js +1 -31
  23. package/lib/module/imageOverlay/utils/fiberScanner.js +1 -100
  24. package/lib/module/imageOverlay/utils/imageClipboard.js +1 -17
  25. package/lib/module/imageOverlay/utils/imageClipboard.web.js +1 -39
  26. package/lib/module/index.js +1 -35
  27. package/lib/module/preset.js +1 -38
  28. package/lib/web/index.mjs +4 -5
  29. package/package.json +3 -6
  30. package/lib/typescript/imageOverlay/components/ImageOverlayModal.d.ts.map +0 -1
  31. package/lib/typescript/imageOverlay/components/ImageOverlayStandalone.d.ts.map +0 -1
  32. package/lib/typescript/imageOverlay/components/OverlayControls.d.ts.map +0 -1
  33. package/lib/typescript/imageOverlay/components/TargetList.d.ts.map +0 -1
  34. package/lib/typescript/imageOverlay/types/index.d.ts.map +0 -1
  35. package/lib/typescript/imageOverlay/utils/ImageOverlayController.d.ts.map +0 -1
  36. package/lib/typescript/imageOverlay/utils/componentMeasurement.d.ts.map +0 -1
  37. package/lib/typescript/imageOverlay/utils/devtoolsHook.d.ts.map +0 -1
  38. package/lib/typescript/imageOverlay/utils/fiberScanner.d.ts.map +0 -1
  39. package/lib/typescript/imageOverlay/utils/imageClipboard.d.ts.map +0 -1
  40. package/lib/typescript/imageOverlay/utils/imageClipboard.web.d.ts.map +0 -1
  41. package/lib/typescript/index.d.ts.map +0 -1
  42. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,880 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.ImageOverlayModal = ImageOverlayModal;
7
- exports.ImageOverlayToolView = ImageOverlayToolView;
8
- var _imageClipboard = require("../utils/imageClipboard");
9
- var _react = require("react");
10
- var _reactNative = require("react-native");
11
- var _sharedUi = require("@buoy-gg/shared-ui");
12
- var _ImageOverlayController = require("../utils/ImageOverlayController");
13
- var _fiberScanner = require("../utils/fiberScanner");
14
- var _TargetList = require("./TargetList");
15
- var _OverlayControls = require("./OverlayControls");
16
- var _jsxRuntime = require("react/jsx-runtime");
17
- // Module-level so it survives remounts from JsModal
18
- let persistedImageUrl = "";
19
- function ImageOverlayModal({
20
- visible,
21
- onClose,
22
- onBack,
23
- onMinimize,
24
- enableSharedModalDimensions = false,
25
- initialModalState,
26
- minimizeTargetPosition,
27
- inline = false,
28
- initialView
29
- }) {
30
- const [view, setView] = (0, _react.useState)(() => {
31
- if (initialView) return initialView;
32
- const s = _ImageOverlayController.ImageOverlayController.getState();
33
- if (s.mode === "free") return "free";
34
- if (s.targetRect) return "controls";
35
- return "idle";
36
- });
37
- // Opening straight onto the target list has to arrive scanned — the list is
38
- // otherwise only ever filled by the button that navigates to it.
39
- const [targets, setTargets] = (0, _react.useState)(() => initialView === "targets" ? (0, _fiberScanner.scanForImageTargets)() : []);
40
- const [state, setState] = (0, _react.useState)(() => _ImageOverlayController.ImageOverlayController.getState());
41
- const [imageUrl, setImageUrl] = (0, _react.useState)(persistedImageUrl);
42
- const [autoTrack, setAutoTrack] = (0, _react.useState)(() => _ImageOverlayController.ImageOverlayController.isAutoTracking());
43
- (0, _react.useEffect)(() => {
44
- persistedImageUrl = imageUrl;
45
- }, [imageUrl]);
46
- (0, _react.useEffect)(() => {
47
- return _ImageOverlayController.ImageOverlayController.subscribe(setState);
48
- }, []);
49
-
50
- // Sync auto-track toggle with controller
51
- (0, _react.useEffect)(() => {
52
- _ImageOverlayController.ImageOverlayController.setAutoTrack(autoTrack);
53
- return () => {
54
- // Don't stop tracking on unmount — let it persist while modal is minimized
55
- };
56
- }, [autoTrack]);
57
- const handleScan = (0, _react.useCallback)(() => {
58
- const found = (0, _fiberScanner.scanForImageTargets)();
59
- setTargets(found);
60
- setView("targets");
61
- }, []);
62
- const handleSelectTarget = (0, _react.useCallback)(async target => {
63
- await _ImageOverlayController.ImageOverlayController.setTarget(target.instance, target.label, target.fiber);
64
- if (persistedImageUrl.trim()) {
65
- await _ImageOverlayController.ImageOverlayController.setImageUri(persistedImageUrl.trim());
66
- _ImageOverlayController.ImageOverlayController.setEnabled(true);
67
- }
68
- setView("controls");
69
- }, []);
70
- const handleLoadImage = (0, _react.useCallback)(async () => {
71
- const url = persistedImageUrl.trim();
72
- if (!url) return;
73
- if (view === "free") {
74
- await _ImageOverlayController.ImageOverlayController.startFreeMode(url);
75
- } else {
76
- await _ImageOverlayController.ImageOverlayController.setImageUri(url);
77
- _ImageOverlayController.ImageOverlayController.setEnabled(true);
78
- }
79
- }, [view]);
80
- const handlePasteImage = (0, _react.useCallback)(async () => {
81
- // Try expo-clipboard (supports images)
82
- let expoClipboard = null;
83
- try {
84
- expoClipboard = (0, _imageClipboard.loadImageClipboard)();
85
- } catch {/* not installed */}
86
-
87
- // Only fall back to @react-native-clipboard/clipboard when expo-clipboard
88
- // isn't present. Requiring it runs TurboModuleRegistry.getEnforcing('RNCClipboard'),
89
- // which crashes in Expo Go where that native module isn't in the binary.
90
- let rnClipboard = null;
91
- if (!expoClipboard) {
92
- try {
93
- const mod = (0, _imageClipboard.loadTextClipboard)();
94
- rnClipboard = mod.default || mod;
95
- } catch {/* not installed */}
96
- }
97
-
98
- // No clipboard library at all
99
- if (!expoClipboard && !rnClipboard) {
100
- _reactNative.Alert.alert("Clipboard not available", "Install expo-clipboard or @react-native-clipboard/clipboard to use paste.");
101
- return;
102
- }
103
- try {
104
- // 1. Try image paste (expo-clipboard only)
105
- if (expoClipboard && typeof expoClipboard.hasImageAsync === "function") {
106
- const hasImage = await expoClipboard.hasImageAsync();
107
- if (hasImage) {
108
- const image = await expoClipboard.getImageAsync({
109
- format: "png"
110
- });
111
- if (image?.data) {
112
- if (view === "free") {
113
- await _ImageOverlayController.ImageOverlayController.startFreeMode(image.data);
114
- } else {
115
- await _ImageOverlayController.ImageOverlayController.setImageUri(image.data);
116
- _ImageOverlayController.ImageOverlayController.setEnabled(true);
117
- }
118
- return;
119
- }
120
- }
121
- }
122
-
123
- // 2. Try text paste as URL (either library)
124
- let text = null;
125
- if (expoClipboard && typeof expoClipboard.getStringAsync === "function") {
126
- text = await expoClipboard.getStringAsync();
127
- } else if (rnClipboard && typeof rnClipboard.getString === "function") {
128
- text = await rnClipboard.getString();
129
- }
130
- if (text && (text.startsWith("http") || text.startsWith("data:"))) {
131
- setImageUrl(text);
132
- persistedImageUrl = text;
133
- return;
134
- }
135
-
136
- // 3. Nothing useful found
137
- if (!expoClipboard) {
138
- // RN clipboard can't read images — tell them why
139
- _reactNative.Alert.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.");
140
- } else {
141
- _reactNative.Alert.alert("No image detected", "Try saving your design image, opening it in Photos or Preview, and copying it from there.");
142
- }
143
- } catch {
144
- _reactNative.Alert.alert("Paste failed", "Something went wrong reading the clipboard. Try copying the image again.");
145
- }
146
- }, [view]);
147
- const handleReset = (0, _react.useCallback)(() => {
148
- _ImageOverlayController.ImageOverlayController.reset();
149
- persistedImageUrl = "";
150
- setView("idle");
151
- setImageUrl("");
152
- setTargets([]);
153
- }, []);
154
- const handleBack = (0, _react.useCallback)(() => {
155
- if (view === "controls") {
156
- const found = (0, _fiberScanner.scanForImageTargets)();
157
- setTargets(found);
158
- setView("targets");
159
- } else if (view === "targets" || view === "free") {
160
- _ImageOverlayController.ImageOverlayController.reset();
161
- setView("idle");
162
- } else {
163
- setView("idle");
164
- }
165
- }, [view]);
166
- const headerTitle = (() => {
167
- switch (view) {
168
- case "targets":
169
- return `Targets (${targets.length})`;
170
- case "controls":
171
- return state.targetLabel || "Component Match";
172
- case "free":
173
- return "Free Placement";
174
- default:
175
- return "Image Overlay";
176
- }
177
- })();
178
- const headerContent = {
179
- showToggleButton: false,
180
- customContent: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
181
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
182
- onBack: view !== "idle" ? handleBack : onBack
183
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
184
- title: headerTitle
185
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
186
- children: [view === "targets" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
187
- style: styles.headerTextButton,
188
- onPress: handleScan,
189
- activeOpacity: 0.7,
190
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
191
- style: styles.headerTextButtonLabel,
192
- children: "Scan"
193
- })
194
- }), (view === "controls" || view === "free") && state.imageUri && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
195
- isEnabled: state.enabled,
196
- onToggle: () => _ImageOverlayController.ImageOverlayController.toggle(),
197
- accessibilityLabel: "Toggle image overlay"
198
- }), view !== "idle" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
199
- style: styles.headerActionButton,
200
- onPress: handleReset,
201
- activeOpacity: 0.7,
202
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
203
- size: 14,
204
- color: _sharedUi.night.color.textSecondary
205
- })
206
- })]
207
- })]
208
- })
209
- };
210
- const body = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
211
- style: styles.container,
212
- children: [view === "idle" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
213
- style: styles.homeContainer,
214
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
215
- style: styles.homeSubtitle,
216
- children: "Choose a mode"
217
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
218
- style: styles.modeGrid,
219
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
220
- style: styles.modePanel,
221
- onPress: handleScan,
222
- activeOpacity: 0.7,
223
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
224
- style: [styles.modeIconCircle, {
225
- backgroundColor: _sharedUi.night.color.accentSoft
226
- }],
227
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SquareDashed, {
228
- size: 24,
229
- color: _sharedUi.night.color.accent
230
- })
231
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
232
- style: styles.modePanelTitle,
233
- children: ["Component", "\n", "Match"]
234
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
235
- style: styles.modePanelDesc,
236
- children: "Overlay on a tagged component"
237
- })]
238
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
239
- style: styles.modePanel,
240
- onPress: () => setView("free"),
241
- activeOpacity: 0.7,
242
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
243
- style: [styles.modeIconCircle, {
244
- backgroundColor: _sharedUi.night.color.accentSoft
245
- }],
246
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Image, {
247
- size: 24,
248
- color: _sharedUi.night.color.accent
249
- })
250
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
251
- style: styles.modePanelTitle,
252
- children: ["Free", "\n", "Placement"]
253
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
254
- style: styles.modePanelDesc,
255
- children: "Drag and resize anywhere"
256
- })]
257
- })]
258
- })]
259
- }), view === "targets" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_TargetList.TargetList, {
260
- targets: targets,
261
- onSelect: handleSelectTarget
262
- }), view === "controls" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
263
- style: styles.controlsContainer,
264
- children: [state.targetRect && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
265
- style: styles.infoCard,
266
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
267
- style: styles.infoRow,
268
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
269
- style: styles.infoLabel,
270
- children: "Component"
271
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
272
- style: styles.infoValue,
273
- children: state.targetLabel
274
- })]
275
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
276
- style: styles.infoRow,
277
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
278
- style: styles.infoLabel,
279
- children: "Size"
280
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
281
- style: styles.infoValueMono,
282
- children: [Math.round(state.targetRect.width), " x ", Math.round(state.targetRect.height)]
283
- })]
284
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
285
- style: styles.infoRow,
286
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
287
- style: styles.infoLabel,
288
- children: "Position"
289
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
290
- style: styles.infoValueMono,
291
- children: [Math.round(state.targetRect.x), ", ", Math.round(state.targetRect.y)]
292
- })]
293
- }), state.imageWidth != null && state.imageHeight != null && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
294
- style: styles.infoRow,
295
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
296
- style: styles.infoLabel,
297
- children: "Image"
298
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
299
- style: styles.infoValueMono,
300
- children: [state.imageWidth, " x ", state.imageHeight]
301
- })]
302
- })]
303
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
304
- style: styles.inputSection,
305
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
306
- style: styles.sectionLabel,
307
- children: "Image"
308
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
309
- style: styles.pasteButton,
310
- onPress: handlePasteImage,
311
- activeOpacity: 0.7,
312
- accessibilityLabel: "Paste from clipboard",
313
- accessibilityRole: "button",
314
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
315
- style: styles.pasteButtonText,
316
- children: "Paste from Clipboard"
317
- })
318
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
319
- style: styles.urlRow,
320
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
321
- style: styles.urlInput,
322
- value: imageUrl,
323
- onChangeText: setImageUrl,
324
- placeholder: "or enter URL...",
325
- placeholderTextColor: _sharedUi.night.color.placeholder,
326
- autoCapitalize: "none",
327
- autoCorrect: false,
328
- returnKeyType: "go",
329
- onSubmitEditing: handleLoadImage
330
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
331
- style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
332
- onPress: handleLoadImage,
333
- disabled: !imageUrl.trim(),
334
- activeOpacity: 0.7,
335
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
336
- style: styles.loadButtonText,
337
- children: "Load"
338
- })
339
- })]
340
- })]
341
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
342
- style: styles.toggleSection,
343
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToggleRow, {
344
- label: "Show Outline",
345
- value: state.showOutline,
346
- onValueChange: v => _ImageOverlayController.ImageOverlayController.setShowOutline(v)
347
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToggleRow, {
348
- label: "Auto Track",
349
- value: autoTrack,
350
- onValueChange: setAutoTrack
351
- }), state.imageUri && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
352
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
353
- style: styles.blendRow,
354
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
355
- style: styles.toggleLabel,
356
- children: "Opacity"
357
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
358
- style: styles.blendButtons,
359
- children: [0, 25, 50, 75, 100].map(pct => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
360
- style: [styles.blendButton, Math.round(state.opacity * 100) === pct && styles.blendButtonActive],
361
- onPress: () => _ImageOverlayController.ImageOverlayController.setOpacity(pct / 100),
362
- activeOpacity: 0.7,
363
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
364
- style: [styles.blendButtonText, Math.round(state.opacity * 100) === pct && styles.blendButtonTextActive],
365
- children: [pct, "%"]
366
- })
367
- }, pct))
368
- })]
369
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
370
- style: styles.blendRow,
371
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
372
- style: styles.toggleLabel,
373
- children: "Flip"
374
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
375
- style: styles.blendButtons,
376
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
377
- style: [styles.blendButton, state.invertX && styles.blendButtonActive],
378
- onPress: () => _ImageOverlayController.ImageOverlayController.toggleInvertX(),
379
- activeOpacity: 0.7,
380
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
381
- style: [styles.blendButtonText, state.invertX && styles.blendButtonTextActive],
382
- children: "Horizontal"
383
- })
384
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
385
- style: [styles.blendButton, state.invertY && styles.blendButtonActive],
386
- onPress: () => _ImageOverlayController.ImageOverlayController.toggleInvertY(),
387
- activeOpacity: 0.7,
388
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
389
- style: [styles.blendButtonText, state.invertY && styles.blendButtonTextActive],
390
- children: "Vertical"
391
- })
392
- })]
393
- })]
394
- })]
395
- })]
396
- }), state.imageUri && /*#__PURE__*/(0, _jsxRuntime.jsx)(_OverlayControls.OverlayControls, {
397
- opacity: state.opacity,
398
- scale: state.scale,
399
- offsetX: state.offsetX,
400
- offsetY: state.offsetY,
401
- onOpacityChange: v => _ImageOverlayController.ImageOverlayController.setOpacity(v),
402
- onScaleChange: v => _ImageOverlayController.ImageOverlayController.setScale(v),
403
- onOffsetChange: (x, y) => _ImageOverlayController.ImageOverlayController.setOffset(x, y)
404
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
405
- style: styles.actions,
406
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
407
- style: styles.actionButton,
408
- onPress: () => _ImageOverlayController.ImageOverlayController.resetSettings(),
409
- activeOpacity: 0.7,
410
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
411
- style: styles.actionButtonText,
412
- children: "Reset Settings"
413
- })
414
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
415
- style: styles.actionButton,
416
- onPress: () => _ImageOverlayController.ImageOverlayController.remeasure(),
417
- activeOpacity: 0.7,
418
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
419
- style: styles.actionButtonText,
420
- children: "Remeasure"
421
- })
422
- })]
423
- })]
424
- }), view === "free" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
425
- style: styles.controlsContainer,
426
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
427
- style: styles.inputSection,
428
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
429
- style: styles.sectionLabel,
430
- children: "Image"
431
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
432
- style: styles.pasteButton,
433
- onPress: handlePasteImage,
434
- activeOpacity: 0.7,
435
- accessibilityLabel: "Paste from clipboard",
436
- accessibilityRole: "button",
437
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
438
- style: styles.pasteButtonText,
439
- children: "Paste from Clipboard"
440
- })
441
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
442
- style: styles.urlRow,
443
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
444
- style: styles.urlInput,
445
- value: imageUrl,
446
- onChangeText: setImageUrl,
447
- placeholder: "or enter URL...",
448
- placeholderTextColor: _sharedUi.night.color.placeholder,
449
- autoCapitalize: "none",
450
- autoCorrect: false,
451
- returnKeyType: "go",
452
- onSubmitEditing: handleLoadImage
453
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
454
- style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
455
- onPress: handleLoadImage,
456
- disabled: !imageUrl.trim(),
457
- activeOpacity: 0.7,
458
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
459
- style: styles.loadButtonText,
460
- children: "Load"
461
- })
462
- })]
463
- })]
464
- }), state.imageUri && state.mode === "free" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
465
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
466
- style: styles.infoCard,
467
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
468
- style: styles.infoRow,
469
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
470
- style: styles.infoLabel,
471
- children: "Position"
472
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
473
- style: styles.infoValueMono,
474
- children: [Math.round(state.freeX), ", ", Math.round(state.freeY)]
475
- })]
476
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
477
- style: styles.infoRow,
478
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
479
- style: styles.infoLabel,
480
- children: "Size"
481
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
482
- style: styles.infoValueMono,
483
- children: [Math.round(state.freeWidth), " x ", Math.round(state.freeHeight)]
484
- })]
485
- }), state.imageWidth != null && state.imageHeight != null && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
486
- style: styles.infoRow,
487
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
488
- style: styles.infoLabel,
489
- children: "Original"
490
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
491
- style: styles.infoValueMono,
492
- children: [state.imageWidth, " x ", state.imageHeight]
493
- })]
494
- })]
495
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
496
- style: styles.inputSection,
497
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_OverlayControls.OverlayControls, {
498
- opacity: state.opacity,
499
- scale: 1,
500
- offsetX: 0,
501
- offsetY: 0,
502
- onOpacityChange: v => _ImageOverlayController.ImageOverlayController.setOpacity(v),
503
- onScaleChange: () => {},
504
- onOffsetChange: () => {},
505
- opacityOnly: true
506
- })
507
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
508
- style: styles.toggleSection,
509
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToggleRow, {
510
- label: "Lock Position",
511
- tone: "warning",
512
- value: state.locked,
513
- onValueChange: v => _ImageOverlayController.ImageOverlayController.setLocked(v)
514
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
515
- style: styles.blendRow,
516
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
517
- style: styles.toggleLabel,
518
- children: "Opacity"
519
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
520
- style: styles.blendButtons,
521
- children: [0, 25, 50, 75, 100].map(pct => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
522
- style: [styles.blendButton, Math.round(state.opacity * 100) === pct && styles.blendButtonActive],
523
- onPress: () => _ImageOverlayController.ImageOverlayController.setOpacity(pct / 100),
524
- activeOpacity: 0.7,
525
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
526
- style: [styles.blendButtonText, Math.round(state.opacity * 100) === pct && styles.blendButtonTextActive],
527
- children: [pct, "%"]
528
- })
529
- }, pct))
530
- })]
531
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
532
- style: styles.blendRow,
533
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
534
- style: styles.toggleLabel,
535
- children: "Flip"
536
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
537
- style: styles.blendButtons,
538
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
539
- style: [styles.blendButton, state.invertX && styles.blendButtonActive],
540
- onPress: () => _ImageOverlayController.ImageOverlayController.toggleInvertX(),
541
- activeOpacity: 0.7,
542
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
543
- style: [styles.blendButtonText, state.invertX && styles.blendButtonTextActive],
544
- children: "Horizontal"
545
- })
546
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
547
- style: [styles.blendButton, state.invertY && styles.blendButtonActive],
548
- onPress: () => _ImageOverlayController.ImageOverlayController.toggleInvertY(),
549
- activeOpacity: 0.7,
550
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
551
- style: [styles.blendButtonText, state.invertY && styles.blendButtonTextActive],
552
- children: "Vertical"
553
- })
554
- })]
555
- })]
556
- })]
557
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
558
- style: styles.actions,
559
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
560
- style: styles.actionButton,
561
- onPress: () => _ImageOverlayController.ImageOverlayController.resetSettings(),
562
- activeOpacity: 0.7,
563
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
564
- style: styles.actionButtonText,
565
- children: "Reset Position"
566
- })
567
- })
568
- })]
569
- })]
570
- })]
571
- });
572
-
573
- // The ONLY difference between the phone and an embedded host is which shell
574
- // the header and body go in. Everything above this is shared.
575
- if (inline) {
576
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
577
- style: styles.inlineRoot,
578
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
579
- style: styles.inlineHeader,
580
- ..._sharedUi.windowDragProps,
581
- children: headerContent.customContent
582
- }), body]
583
- });
584
- }
585
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
586
- visible: visible,
587
- onClose: onClose,
588
- onMinimize: onMinimize,
589
- minimizeTargetPosition: minimizeTargetPosition,
590
- initialModalState: initialModalState,
591
- header: headerContent,
592
- initialMode: "bottomSheet",
593
- persistenceKey: "buoy-image-overlay-modal",
594
- enablePersistence: true,
595
- children: body
596
- });
597
- }
598
-
599
- /**
600
- * The same Image Overlay tool as `ImageOverlayModal`, rendered INLINE for hosts
601
- * that place tools in a panel rather than a bottom sheet (the docs-site live
602
- * demo). Exists so there is exactly one implementation of the tool's header,
603
- * mode chooser, target list and controls.
604
- *
605
- * `initialView` seeds which screen it opens on — the view is otherwise internal
606
- * state reachable only by tapping.
607
- */
608
- function ImageOverlayToolView(props = {}) {
609
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageOverlayModal, {
610
- ...props,
611
- inline: true,
612
- visible: true,
613
- onClose: () => {}
614
- });
615
- }
616
- const styles = _reactNative.StyleSheet.create({
617
- inlineRoot: {
618
- flex: 1,
619
- minHeight: 0,
620
- backgroundColor: _sharedUi.macOSColors.background.base
621
- },
622
- inlineHeader: {
623
- borderBottomWidth: 1,
624
- borderBottomColor: _sharedUi.macOSColors.border.default,
625
- // Clear, like Bench — the host's hoisted sky shows through the header.
626
- backgroundColor: "transparent"
627
- },
628
- container: {
629
- flex: 1,
630
- // Transparent so the night sheet's star field shows through.
631
- backgroundColor: "transparent"
632
- },
633
- headerTextButton: {
634
- height: 32,
635
- paddingHorizontal: 12,
636
- borderRadius: 8,
637
- backgroundColor: _sharedUi.night.color.buttonSurface,
638
- borderWidth: 1,
639
- borderColor: _sharedUi.night.color.border,
640
- alignItems: "center",
641
- justifyContent: "center"
642
- },
643
- headerTextButtonLabel: {
644
- fontSize: 12,
645
- fontWeight: "600",
646
- color: _sharedUi.night.color.accent
647
- },
648
- headerActionButton: {
649
- width: 32,
650
- height: 32,
651
- borderRadius: 8,
652
- backgroundColor: _sharedUi.night.color.buttonSurface,
653
- borderWidth: 1,
654
- borderColor: _sharedUi.night.color.border,
655
- alignItems: "center",
656
- justifyContent: "center"
657
- },
658
- // Home — mode selection
659
- homeContainer: {
660
- flex: 1,
661
- paddingHorizontal: 12,
662
- paddingTop: 16,
663
- paddingBottom: 20,
664
- gap: 12
665
- },
666
- homeSubtitle: {
667
- fontSize: 12,
668
- fontWeight: "600",
669
- color: _sharedUi.night.color.textTertiary,
670
- textTransform: "uppercase",
671
- letterSpacing: 0.5,
672
- textAlign: "center"
673
- },
674
- modeGrid: {
675
- flexDirection: "row",
676
- gap: 10
677
- },
678
- modePanel: {
679
- flex: 1,
680
- alignItems: "center",
681
- paddingVertical: 20,
682
- paddingHorizontal: 12,
683
- backgroundColor: _sharedUi.night.color.surface,
684
- borderRadius: 12,
685
- borderWidth: 1,
686
- borderColor: _sharedUi.night.color.border,
687
- gap: 8
688
- },
689
- modeIconCircle: {
690
- width: 48,
691
- height: 48,
692
- borderRadius: 24,
693
- alignItems: "center",
694
- justifyContent: "center",
695
- marginBottom: 4
696
- },
697
- modePanelTitle: {
698
- fontSize: 13,
699
- fontWeight: "700",
700
- color: _sharedUi.night.color.text,
701
- textAlign: "center",
702
- lineHeight: 17
703
- },
704
- modePanelDesc: {
705
- fontSize: 11,
706
- color: _sharedUi.night.color.textTertiary,
707
- textAlign: "center",
708
- lineHeight: 15
709
- },
710
- // Controls
711
- controlsContainer: {
712
- flex: 1,
713
- paddingTop: 8,
714
- paddingBottom: 24,
715
- gap: 10
716
- },
717
- infoCard: {
718
- marginHorizontal: 12,
719
- backgroundColor: _sharedUi.night.color.surface,
720
- borderRadius: 8,
721
- borderWidth: 1,
722
- borderColor: _sharedUi.night.color.border,
723
- padding: 10,
724
- gap: 4
725
- },
726
- infoRow: {
727
- flexDirection: "row",
728
- justifyContent: "space-between",
729
- alignItems: "center"
730
- },
731
- infoLabel: {
732
- fontSize: 11,
733
- fontWeight: "500",
734
- color: _sharedUi.night.color.textTertiary,
735
- textTransform: "uppercase",
736
- letterSpacing: 0.3
737
- },
738
- infoValue: {
739
- fontSize: 12,
740
- fontWeight: "600",
741
- color: _sharedUi.night.color.text
742
- },
743
- infoValueMono: {
744
- fontSize: 12,
745
- fontWeight: "600",
746
- color: _sharedUi.night.color.text,
747
- fontFamily: _sharedUi.monoFont
748
- },
749
- inputSection: {
750
- paddingHorizontal: 12,
751
- gap: 6
752
- },
753
- sectionLabel: {
754
- fontSize: 11,
755
- fontWeight: "600",
756
- color: _sharedUi.night.color.textTertiary,
757
- textTransform: "uppercase",
758
- letterSpacing: 0.5
759
- },
760
- pasteButton: {
761
- paddingVertical: 10,
762
- borderRadius: 8,
763
- backgroundColor: _sharedUi.night.color.accentSoft,
764
- borderWidth: 1,
765
- borderColor: _sharedUi.night.color.accentBorderStrong,
766
- alignItems: "center"
767
- },
768
- pasteButtonText: {
769
- fontSize: 13,
770
- fontWeight: "600",
771
- color: _sharedUi.night.color.accent
772
- },
773
- urlRow: {
774
- flexDirection: "row",
775
- gap: 8
776
- },
777
- urlInput: {
778
- flex: 1,
779
- height: 36,
780
- borderRadius: 10,
781
- backgroundColor: _sharedUi.night.color.surfaceElevated,
782
- borderWidth: 1,
783
- borderColor: _sharedUi.night.color.border,
784
- paddingHorizontal: 12,
785
- fontSize: 13,
786
- color: _sharedUi.night.color.text
787
- },
788
- loadButton: {
789
- height: 36,
790
- paddingHorizontal: 14,
791
- borderRadius: 8,
792
- backgroundColor: _sharedUi.night.color.accent,
793
- justifyContent: "center",
794
- alignItems: "center"
795
- },
796
- loadButtonDisabled: {
797
- opacity: 0.4
798
- },
799
- loadButtonText: {
800
- fontSize: 13,
801
- fontWeight: "600",
802
- color: _sharedUi.night.color.onAccent
803
- },
804
- toggleSection: {
805
- paddingHorizontal: 12,
806
- gap: 6
807
- },
808
- toggleRow: {
809
- flexDirection: "row",
810
- alignItems: "center",
811
- justifyContent: "space-between",
812
- backgroundColor: _sharedUi.night.color.surfaceElevated,
813
- borderRadius: 8,
814
- borderWidth: 1,
815
- borderColor: _sharedUi.night.color.border,
816
- paddingHorizontal: 12,
817
- paddingVertical: 8
818
- },
819
- toggleLabel: {
820
- fontSize: 13,
821
- fontWeight: "500",
822
- color: _sharedUi.night.color.textSecondary
823
- },
824
- blendRow: {
825
- flexDirection: "row",
826
- alignItems: "center",
827
- justifyContent: "space-between",
828
- backgroundColor: _sharedUi.night.color.surfaceElevated,
829
- borderRadius: 8,
830
- borderWidth: 1,
831
- borderColor: _sharedUi.night.color.border,
832
- paddingHorizontal: 12,
833
- paddingVertical: 6
834
- },
835
- blendButtons: {
836
- flexDirection: "row",
837
- gap: 4
838
- },
839
- blendButton: {
840
- paddingHorizontal: 10,
841
- paddingVertical: 5,
842
- borderRadius: 6,
843
- backgroundColor: "transparent"
844
- },
845
- blendButtonActive: {
846
- backgroundColor: _sharedUi.night.color.accentSoft
847
- },
848
- blendButtonText: {
849
- fontSize: 12,
850
- fontWeight: "600",
851
- color: _sharedUi.night.color.textTertiary
852
- },
853
- blendButtonTextActive: {
854
- color: _sharedUi.night.color.accent
855
- },
856
- actions: {
857
- flexDirection: "row",
858
- paddingHorizontal: 12,
859
- gap: 8,
860
- marginTop: 4
861
- },
862
- actionButton: {
863
- flex: 1,
864
- flexDirection: "row",
865
- alignItems: "center",
866
- justifyContent: "center",
867
- gap: 6,
868
- paddingVertical: 8,
869
- paddingHorizontal: 12,
870
- borderRadius: 8,
871
- backgroundColor: _sharedUi.night.color.surfaceElevated,
872
- borderWidth: 1,
873
- borderColor: _sharedUi.night.color.border
874
- },
875
- actionButtonText: {
876
- fontSize: 12,
877
- fontWeight: "500",
878
- color: _sharedUi.night.color.textSecondary
879
- }
880
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ImageOverlayModal=ImageOverlayModal;exports.ImageOverlayToolView=ImageOverlayToolView;var _imageClipboard=require("../utils/imageClipboard");var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _ImageOverlayController=require("../utils/ImageOverlayController");var _fiberScanner=require("../utils/fiberScanner");var _TargetList=require("./TargetList");var _OverlayControls=require("./OverlayControls");var _jsxRuntime=require("react/jsx-runtime");let persistedImageUrl="";function ImageOverlayModal({visible:c,onClose:m,onBack:T,onMinimize:C,enableSharedModalDimensions:z=false,initialModalState:j,minimizeTargetPosition:v,inline:O=false,initialView:d}){const[o,l]=(0,_react.useState)(()=>{if(d)return d;const e=_ImageOverlayController.ImageOverlayController.getState();if(e.mode==="free")return"free";if(e.targetRect)return"controls";return"idle"});const[y,g]=(0,_react.useState)(()=>d==="targets"?(0,_fiberScanner.scanForImageTargets)():[]);const[t,w]=(0,_react.useState)(()=>_ImageOverlayController.ImageOverlayController.getState());const[n,i]=(0,_react.useState)(persistedImageUrl);const[h,I]=(0,_react.useState)(()=>_ImageOverlayController.ImageOverlayController.isAutoTracking());(0,_react.useEffect)(()=>{persistedImageUrl=n},[n]);(0,_react.useEffect)(()=>{return _ImageOverlayController.ImageOverlayController.subscribe(w)},[]);(0,_react.useEffect)(()=>{_ImageOverlayController.ImageOverlayController.setAutoTrack(h);return()=>{}},[h]);const x=(0,_react.useCallback)(()=>{const e=(0,_fiberScanner.scanForImageTargets)();g(e);l("targets")},[]);const S=(0,_react.useCallback)(async e=>{await _ImageOverlayController.ImageOverlayController.setTarget(e.instance,e.label,e.fiber);if(persistedImageUrl.trim()){await _ImageOverlayController.ImageOverlayController.setImageUri(persistedImageUrl.trim());_ImageOverlayController.ImageOverlayController.setEnabled(true)}l("controls")},[]);const s=(0,_react.useCallback)(async()=>{const e=persistedImageUrl.trim();if(!e)return;if(o==="free"){await _ImageOverlayController.ImageOverlayController.startFreeMode(e)}else{await _ImageOverlayController.ImageOverlayController.setImageUri(e);_ImageOverlayController.ImageOverlayController.setEnabled(true)}},[o]);const b=(0,_react.useCallback)(async()=>{let e=null;try{e=(0,_imageClipboard.loadImageClipboard)()}catch{}let a=null;if(!e){try{const r=(0,_imageClipboard.loadTextClipboard)();a=r.default||r}catch{}}if(!e&&!a){_reactNative.Alert.alert("Clipboard not available","Install expo-clipboard or @react-native-clipboard/clipboard to use paste.");return}try{if(e&&typeof e.hasImageAsync==="function"){const P=await e.hasImageAsync();if(P){const u=await e.getImageAsync({format:"png"});if(u?.data){if(o==="free"){await _ImageOverlayController.ImageOverlayController.startFreeMode(u.data)}else{await _ImageOverlayController.ImageOverlayController.setImageUri(u.data);_ImageOverlayController.ImageOverlayController.setEnabled(true)}return}}}let r=null;if(e&&typeof e.getStringAsync==="function"){r=await e.getStringAsync()}else if(a&&typeof a.getString==="function"){r=await a.getString()}if(r&&(r.startsWith("http")||r.startsWith("data:"))){i(r);persistedImageUrl=r;return}if(!e){_reactNative.Alert.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.")}else{_reactNative.Alert.alert("No image detected","Try saving your design image, opening it in Photos or Preview, and copying it from there.")}}catch{_reactNative.Alert.alert("Paste failed","Something went wrong reading the clipboard. Try copying the image again.")}},[o]);const B=(0,_react.useCallback)(()=>{_ImageOverlayController.ImageOverlayController.reset();persistedImageUrl="";l("idle");i("");g([])},[]);const V=(0,_react.useCallback)(()=>{if(o==="controls"){const e=(0,_fiberScanner.scanForImageTargets)();g(e);l("targets")}else if(o==="targets"||o==="free"){_ImageOverlayController.ImageOverlayController.reset();l("idle")}else{l("idle")}},[o]);const R=(()=>{switch(o){case"targets":return`Targets (${y.length})`;case"controls":return t.targetLabel||"Component Match";case"free":return"Free Placement";default:return"Image Overlay"}})();const f={showToggleButton:false,customContent:(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:o!=="idle"?V:T}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:R}),(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions,{children:[o==="targets"&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.headerTextButton,onPress:x,activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.headerTextButtonLabel,children:"Scan"})}),(o==="controls"||o==="free")&&t.imageUri&&(0,_jsxRuntime.jsx)(_sharedUi.PowerToggleButton,{isEnabled:t.enabled,onToggle:()=>_ImageOverlayController.ImageOverlayController.toggle(),accessibilityLabel:"Toggle image overlay"}),o!=="idle"&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.headerActionButton,onPress:B,activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_sharedUi.Trash2,{size:14,color:_sharedUi.night.color.textSecondary})})]})]})};const p=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.container,children:[o==="idle"&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.homeContainer,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.homeSubtitle,children:"Choose a mode"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.modeGrid,children:[(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.modePanel,onPress:x,activeOpacity:.7,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.modeIconCircle,{backgroundColor:_sharedUi.night.color.accentSoft}],children:(0,_jsxRuntime.jsx)(_sharedUi.SquareDashed,{size:24,color:_sharedUi.night.color.accent})}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.modePanelTitle,children:["Component","\n","Match"]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.modePanelDesc,children:"Overlay on a tagged component"})]}),(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.modePanel,onPress:()=>l("free"),activeOpacity:.7,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.modeIconCircle,{backgroundColor:_sharedUi.night.color.accentSoft}],children:(0,_jsxRuntime.jsx)(_sharedUi.Image,{size:24,color:_sharedUi.night.color.accent})}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.modePanelTitle,children:["Free","\n","Placement"]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.modePanelDesc,children:"Drag and resize anywhere"})]})]})]}),o==="targets"&&(0,_jsxRuntime.jsx)(_TargetList.TargetList,{targets:y,onSelect:S}),o==="controls"&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.controlsContainer,children:[t.targetRect&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoCard,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Component"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoValue,children:t.targetLabel})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Size"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[Math.round(t.targetRect.width)," x ",Math.round(t.targetRect.height)]})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Position"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[Math.round(t.targetRect.x),", ",Math.round(t.targetRect.y)]})]}),t.imageWidth!=null&&t.imageHeight!=null&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Image"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[t.imageWidth," x ",t.imageHeight]})]})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inputSection,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sectionLabel,children:"Image"}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.pasteButton,onPress:b,activeOpacity:.7,accessibilityLabel:"Paste from clipboard",accessibilityRole:"button",children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.pasteButtonText,children:"Paste from Clipboard"})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.urlRow,children:[(0,_jsxRuntime.jsx)(_reactNative.TextInput,{style:styles.urlInput,value:n,onChangeText:i,placeholder:"or enter URL...",placeholderTextColor:_sharedUi.night.color.placeholder,autoCapitalize:"none",autoCorrect:false,returnKeyType:"go",onSubmitEditing:s}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.loadButton,!n.trim()&&styles.loadButtonDisabled],onPress:s,disabled:!n.trim(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.loadButtonText,children:"Load"})})]})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.toggleSection,children:[(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{label:"Show Outline",value:t.showOutline,onValueChange:e=>_ImageOverlayController.ImageOverlayController.setShowOutline(e)}),(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{label:"Auto Track",value:h,onValueChange:I}),t.imageUri&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.toggleLabel,children:"Opacity"}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.blendButtons,children:[0,25,50,75,100].map(e=>(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,Math.round(t.opacity*100)===e&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.setOpacity(e/100),activeOpacity:.7,children:(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:[styles.blendButtonText,Math.round(t.opacity*100)===e&&styles.blendButtonTextActive],children:[e,"%"]})},e))})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.toggleLabel,children:"Flip"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendButtons,children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,t.invertX&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.toggleInvertX(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.blendButtonText,t.invertX&&styles.blendButtonTextActive],children:"Horizontal"})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,t.invertY&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.toggleInvertY(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.blendButtonText,t.invertY&&styles.blendButtonTextActive],children:"Vertical"})})]})]})]})]}),t.imageUri&&(0,_jsxRuntime.jsx)(_OverlayControls.OverlayControls,{opacity:t.opacity,scale:t.scale,offsetX:t.offsetX,offsetY:t.offsetY,onOpacityChange:e=>_ImageOverlayController.ImageOverlayController.setOpacity(e),onScaleChange:e=>_ImageOverlayController.ImageOverlayController.setScale(e),onOffsetChange:(e,a)=>_ImageOverlayController.ImageOverlayController.setOffset(e,a)}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.actions,children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.actionButton,onPress:()=>_ImageOverlayController.ImageOverlayController.resetSettings(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.actionButtonText,children:"Reset Settings"})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.actionButton,onPress:()=>_ImageOverlayController.ImageOverlayController.remeasure(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.actionButtonText,children:"Remeasure"})})]})]}),o==="free"&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.controlsContainer,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inputSection,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sectionLabel,children:"Image"}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.pasteButton,onPress:b,activeOpacity:.7,accessibilityLabel:"Paste from clipboard",accessibilityRole:"button",children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.pasteButtonText,children:"Paste from Clipboard"})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.urlRow,children:[(0,_jsxRuntime.jsx)(_reactNative.TextInput,{style:styles.urlInput,value:n,onChangeText:i,placeholder:"or enter URL...",placeholderTextColor:_sharedUi.night.color.placeholder,autoCapitalize:"none",autoCorrect:false,returnKeyType:"go",onSubmitEditing:s}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.loadButton,!n.trim()&&styles.loadButtonDisabled],onPress:s,disabled:!n.trim(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.loadButtonText,children:"Load"})})]})]}),t.imageUri&&t.mode==="free"&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoCard,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Position"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[Math.round(t.freeX),", ",Math.round(t.freeY)]})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Size"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[Math.round(t.freeWidth)," x ",Math.round(t.freeHeight)]})]}),t.imageWidth!=null&&t.imageHeight!=null&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.infoRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.infoLabel,children:"Original"}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.infoValueMono,children:[t.imageWidth," x ",t.imageHeight]})]})]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.inputSection,children:(0,_jsxRuntime.jsx)(_OverlayControls.OverlayControls,{opacity:t.opacity,scale:1,offsetX:0,offsetY:0,onOpacityChange:e=>_ImageOverlayController.ImageOverlayController.setOpacity(e),onScaleChange:()=>{},onOffsetChange:()=>{},opacityOnly:true})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.toggleSection,children:[(0,_jsxRuntime.jsx)(_sharedUi.ToggleRow,{label:"Lock Position",tone:"warning",value:t.locked,onValueChange:e=>_ImageOverlayController.ImageOverlayController.setLocked(e)}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.toggleLabel,children:"Opacity"}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.blendButtons,children:[0,25,50,75,100].map(e=>(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,Math.round(t.opacity*100)===e&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.setOpacity(e/100),activeOpacity:.7,children:(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:[styles.blendButtonText,Math.round(t.opacity*100)===e&&styles.blendButtonTextActive],children:[e,"%"]})},e))})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendRow,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.toggleLabel,children:"Flip"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.blendButtons,children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,t.invertX&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.toggleInvertX(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.blendButtonText,t.invertX&&styles.blendButtonTextActive],children:"Horizontal"})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:[styles.blendButton,t.invertY&&styles.blendButtonActive],onPress:()=>_ImageOverlayController.ImageOverlayController.toggleInvertY(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.blendButtonText,t.invertY&&styles.blendButtonTextActive],children:"Vertical"})})]})]})]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.actions,children:(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{style:styles.actionButton,onPress:()=>_ImageOverlayController.ImageOverlayController.resetSettings(),activeOpacity:.7,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.actionButtonText,children:"Reset Position"})})})]})]})]});if(O){return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inlineRoot,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.inlineHeader,..._sharedUi.windowDragProps,children:f.customContent}),p]})}return(0,_jsxRuntime.jsx)(_sharedUi.JsModal,{visible:c,onClose:m,onMinimize:C,minimizeTargetPosition:v,initialModalState:j,header:f,initialMode:"bottomSheet",persistenceKey:"buoy-image-overlay-modal",enablePersistence:true,children:p})}function ImageOverlayToolView(c={}){return(0,_jsxRuntime.jsx)(ImageOverlayModal,{...c,inline:true,visible:true,onClose:()=>{}})}const styles=_reactNative.StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:_sharedUi.macOSColors.background.base},inlineHeader:{borderBottomWidth:1,borderBottomColor:_sharedUi.macOSColors.border.default,backgroundColor:"transparent"},container:{flex:1,backgroundColor:"transparent"},headerTextButton:{height:32,paddingHorizontal:12,borderRadius:8,backgroundColor:_sharedUi.night.color.buttonSurface,borderWidth:1,borderColor:_sharedUi.night.color.border,alignItems:"center",justifyContent:"center"},headerTextButtonLabel:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.accent},headerActionButton:{width:32,height:32,borderRadius:8,backgroundColor:_sharedUi.night.color.buttonSurface,borderWidth:1,borderColor:_sharedUi.night.color.border,alignItems:"center",justifyContent:"center"},homeContainer:{flex:1,paddingHorizontal:12,paddingTop:16,paddingBottom:20,gap:12},homeSubtitle:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.textTertiary,textTransform:"uppercase",letterSpacing:.5,textAlign:"center"},modeGrid:{flexDirection:"row",gap:10},modePanel:{flex:1,alignItems:"center",paddingVertical:20,paddingHorizontal:12,backgroundColor:_sharedUi.night.color.surface,borderRadius:12,borderWidth:1,borderColor:_sharedUi.night.color.border,gap:8},modeIconCircle:{width:48,height:48,borderRadius:24,alignItems:"center",justifyContent:"center",marginBottom:4},modePanelTitle:{fontSize:13,fontWeight:"700",color:_sharedUi.night.color.text,textAlign:"center",lineHeight:17},modePanelDesc:{fontSize:11,color:_sharedUi.night.color.textTertiary,textAlign:"center",lineHeight:15},controlsContainer:{flex:1,paddingTop:8,paddingBottom:24,gap:10},infoCard:{marginHorizontal:12,backgroundColor:_sharedUi.night.color.surface,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.border,padding:10,gap:4},infoRow:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},infoLabel:{fontSize:11,fontWeight:"500",color:_sharedUi.night.color.textTertiary,textTransform:"uppercase",letterSpacing:.3},infoValue:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.text},infoValueMono:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.text,fontFamily:_sharedUi.monoFont},inputSection:{paddingHorizontal:12,gap:6},sectionLabel:{fontSize:11,fontWeight:"600",color:_sharedUi.night.color.textTertiary,textTransform:"uppercase",letterSpacing:.5},pasteButton:{paddingVertical:10,borderRadius:8,backgroundColor:_sharedUi.night.color.accentSoft,borderWidth:1,borderColor:_sharedUi.night.color.accentBorderStrong,alignItems:"center"},pasteButtonText:{fontSize:13,fontWeight:"600",color:_sharedUi.night.color.accent},urlRow:{flexDirection:"row",gap:8},urlInput:{flex:1,height:36,borderRadius:10,backgroundColor:_sharedUi.night.color.surfaceElevated,borderWidth:1,borderColor:_sharedUi.night.color.border,paddingHorizontal:12,fontSize:13,color:_sharedUi.night.color.text},loadButton:{height:36,paddingHorizontal:14,borderRadius:8,backgroundColor:_sharedUi.night.color.accent,justifyContent:"center",alignItems:"center"},loadButtonDisabled:{opacity:.4},loadButtonText:{fontSize:13,fontWeight:"600",color:_sharedUi.night.color.onAccent},toggleSection:{paddingHorizontal:12,gap:6},toggleRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:_sharedUi.night.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.border,paddingHorizontal:12,paddingVertical:8},toggleLabel:{fontSize:13,fontWeight:"500",color:_sharedUi.night.color.textSecondary},blendRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:_sharedUi.night.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.border,paddingHorizontal:12,paddingVertical:6},blendButtons:{flexDirection:"row",gap:4},blendButton:{paddingHorizontal:10,paddingVertical:5,borderRadius:6,backgroundColor:"transparent"},blendButtonActive:{backgroundColor:_sharedUi.night.color.accentSoft},blendButtonText:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.textTertiary},blendButtonTextActive:{color:_sharedUi.night.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:_sharedUi.night.color.surfaceElevated,borderWidth:1,borderColor:_sharedUi.night.color.border},actionButtonText:{fontSize:12,fontWeight:"500",color:_sharedUi.night.color.textSecondary}});