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