@buoy-gg/image-overlay 7.0.40 → 7.0.42

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