@buoy-gg/image-overlay 7.0.41 → 7.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +12 -76
  2. package/lib/commonjs/imageOverlay/components/ImageOverlayModal.js +1 -880
  3. package/lib/commonjs/imageOverlay/components/ImageOverlayStandalone.js +1 -463
  4. package/lib/commonjs/imageOverlay/components/OverlayControls.js +1 -253
  5. package/lib/commonjs/imageOverlay/components/TargetList.js +1 -181
  6. package/lib/commonjs/imageOverlay/types/index.js +1 -1
  7. package/lib/commonjs/imageOverlay/utils/ImageOverlayController.js +1 -426
  8. package/lib/commonjs/imageOverlay/utils/componentMeasurement.js +1 -106
  9. package/lib/commonjs/imageOverlay/utils/devtoolsHook.js +1 -36
  10. package/lib/commonjs/imageOverlay/utils/fiberScanner.js +1 -104
  11. package/lib/commonjs/imageOverlay/utils/imageClipboard.js +1 -22
  12. package/lib/commonjs/imageOverlay/utils/imageClipboard.web.js +1 -44
  13. package/lib/commonjs/index.js +1 -59
  14. package/lib/commonjs/preset.js +1 -42
  15. package/lib/module/imageOverlay/components/ImageOverlayModal.js +1 -875
  16. package/lib/module/imageOverlay/components/ImageOverlayStandalone.js +1 -459
  17. package/lib/module/imageOverlay/components/OverlayControls.js +1 -249
  18. package/lib/module/imageOverlay/components/TargetList.js +1 -177
  19. package/lib/module/imageOverlay/types/index.js +1 -1
  20. package/lib/module/imageOverlay/utils/ImageOverlayController.js +1 -422
  21. package/lib/module/imageOverlay/utils/componentMeasurement.js +1 -102
  22. package/lib/module/imageOverlay/utils/devtoolsHook.js +1 -31
  23. package/lib/module/imageOverlay/utils/fiberScanner.js +1 -100
  24. package/lib/module/imageOverlay/utils/imageClipboard.js +1 -17
  25. package/lib/module/imageOverlay/utils/imageClipboard.web.js +1 -39
  26. package/lib/module/index.js +1 -35
  27. package/lib/module/preset.js +1 -38
  28. package/lib/web/index.mjs +4 -5
  29. package/package.json +3 -6
  30. package/lib/typescript/imageOverlay/components/ImageOverlayModal.d.ts.map +0 -1
  31. package/lib/typescript/imageOverlay/components/ImageOverlayStandalone.d.ts.map +0 -1
  32. package/lib/typescript/imageOverlay/components/OverlayControls.d.ts.map +0 -1
  33. package/lib/typescript/imageOverlay/components/TargetList.d.ts.map +0 -1
  34. package/lib/typescript/imageOverlay/types/index.d.ts.map +0 -1
  35. package/lib/typescript/imageOverlay/utils/ImageOverlayController.d.ts.map +0 -1
  36. package/lib/typescript/imageOverlay/utils/componentMeasurement.d.ts.map +0 -1
  37. package/lib/typescript/imageOverlay/utils/devtoolsHook.d.ts.map +0 -1
  38. package/lib/typescript/imageOverlay/utils/fiberScanner.d.ts.map +0 -1
  39. package/lib/typescript/imageOverlay/utils/imageClipboard.d.ts.map +0 -1
  40. package/lib/typescript/imageOverlay/utils/imageClipboard.web.d.ts.map +0 -1
  41. package/lib/typescript/index.d.ts.map +0 -1
  42. package/lib/typescript/preset.d.ts.map +0 -1
@@ -1,875 +1 @@
1
- "use strict";
2
-
3
- import { loadImageClipboard, loadTextClipboard } from "../utils/imageClipboard";
4
- import { useState, useEffect, useCallback } from "react";
5
- import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from "react-native";
6
- import { JsModal, ModalHeader, macOSColors, night, Trash2, SquareDashed, Image, PowerToggleButton, windowDragProps, ToggleRow, monoFont } from "@buoy-gg/shared-ui";
7
- import { ImageOverlayController } from "../utils/ImageOverlayController";
8
- import { scanForImageTargets } from "../utils/fiberScanner";
9
- import { TargetList } from "./TargetList";
10
- import { OverlayControls } from "./OverlayControls";
11
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
12
- // Module-level so it survives remounts from JsModal
13
- let persistedImageUrl = "";
14
- export function ImageOverlayModal({
15
- visible,
16
- onClose,
17
- onBack,
18
- onMinimize,
19
- enableSharedModalDimensions = false,
20
- initialModalState,
21
- minimizeTargetPosition,
22
- inline = false,
23
- initialView
24
- }) {
25
- const [view, setView] = useState(() => {
26
- if (initialView) return initialView;
27
- const s = ImageOverlayController.getState();
28
- if (s.mode === "free") return "free";
29
- if (s.targetRect) return "controls";
30
- return "idle";
31
- });
32
- // Opening straight onto the target list has to arrive scanned — the list is
33
- // otherwise only ever filled by the button that navigates to it.
34
- const [targets, setTargets] = useState(() => initialView === "targets" ? scanForImageTargets() : []);
35
- const [state, setState] = useState(() => ImageOverlayController.getState());
36
- const [imageUrl, setImageUrl] = useState(persistedImageUrl);
37
- const [autoTrack, setAutoTrack] = useState(() => ImageOverlayController.isAutoTracking());
38
- useEffect(() => {
39
- persistedImageUrl = imageUrl;
40
- }, [imageUrl]);
41
- useEffect(() => {
42
- return ImageOverlayController.subscribe(setState);
43
- }, []);
44
-
45
- // Sync auto-track toggle with controller
46
- useEffect(() => {
47
- ImageOverlayController.setAutoTrack(autoTrack);
48
- return () => {
49
- // Don't stop tracking on unmount — let it persist while modal is minimized
50
- };
51
- }, [autoTrack]);
52
- const handleScan = useCallback(() => {
53
- const found = scanForImageTargets();
54
- setTargets(found);
55
- setView("targets");
56
- }, []);
57
- const handleSelectTarget = useCallback(async target => {
58
- await ImageOverlayController.setTarget(target.instance, target.label, target.fiber);
59
- if (persistedImageUrl.trim()) {
60
- await ImageOverlayController.setImageUri(persistedImageUrl.trim());
61
- ImageOverlayController.setEnabled(true);
62
- }
63
- setView("controls");
64
- }, []);
65
- const handleLoadImage = useCallback(async () => {
66
- const url = persistedImageUrl.trim();
67
- if (!url) return;
68
- if (view === "free") {
69
- await ImageOverlayController.startFreeMode(url);
70
- } else {
71
- await ImageOverlayController.setImageUri(url);
72
- ImageOverlayController.setEnabled(true);
73
- }
74
- }, [view]);
75
- const handlePasteImage = useCallback(async () => {
76
- // Try expo-clipboard (supports images)
77
- let expoClipboard = null;
78
- try {
79
- expoClipboard = loadImageClipboard();
80
- } catch {/* not installed */}
81
-
82
- // Only fall back to @react-native-clipboard/clipboard when expo-clipboard
83
- // isn't present. Requiring it runs TurboModuleRegistry.getEnforcing('RNCClipboard'),
84
- // which crashes in Expo Go where that native module isn't in the binary.
85
- let rnClipboard = null;
86
- if (!expoClipboard) {
87
- try {
88
- const mod = loadTextClipboard();
89
- rnClipboard = mod.default || mod;
90
- } catch {/* not installed */}
91
- }
92
-
93
- // No clipboard library at all
94
- if (!expoClipboard && !rnClipboard) {
95
- Alert.alert("Clipboard not available", "Install expo-clipboard or @react-native-clipboard/clipboard to use paste.");
96
- return;
97
- }
98
- try {
99
- // 1. Try image paste (expo-clipboard only)
100
- if (expoClipboard && typeof expoClipboard.hasImageAsync === "function") {
101
- const hasImage = await expoClipboard.hasImageAsync();
102
- if (hasImage) {
103
- const image = await expoClipboard.getImageAsync({
104
- format: "png"
105
- });
106
- if (image?.data) {
107
- if (view === "free") {
108
- await ImageOverlayController.startFreeMode(image.data);
109
- } else {
110
- await ImageOverlayController.setImageUri(image.data);
111
- ImageOverlayController.setEnabled(true);
112
- }
113
- return;
114
- }
115
- }
116
- }
117
-
118
- // 2. Try text paste as URL (either library)
119
- let text = null;
120
- if (expoClipboard && typeof expoClipboard.getStringAsync === "function") {
121
- text = await expoClipboard.getStringAsync();
122
- } else if (rnClipboard && typeof rnClipboard.getString === "function") {
123
- text = await rnClipboard.getString();
124
- }
125
- if (text && (text.startsWith("http") || text.startsWith("data:"))) {
126
- setImageUrl(text);
127
- persistedImageUrl = text;
128
- return;
129
- }
130
-
131
- // 3. Nothing useful found
132
- if (!expoClipboard) {
133
- // RN clipboard can't read images — tell them why
134
- 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.");
135
- } else {
136
- Alert.alert("No image detected", "Try saving your design image, opening it in Photos or Preview, and copying it from there.");
137
- }
138
- } catch {
139
- Alert.alert("Paste failed", "Something went wrong reading the clipboard. Try copying the image again.");
140
- }
141
- }, [view]);
142
- const handleReset = useCallback(() => {
143
- ImageOverlayController.reset();
144
- persistedImageUrl = "";
145
- setView("idle");
146
- setImageUrl("");
147
- setTargets([]);
148
- }, []);
149
- const handleBack = useCallback(() => {
150
- if (view === "controls") {
151
- const found = scanForImageTargets();
152
- setTargets(found);
153
- setView("targets");
154
- } else if (view === "targets" || view === "free") {
155
- ImageOverlayController.reset();
156
- setView("idle");
157
- } else {
158
- setView("idle");
159
- }
160
- }, [view]);
161
- const headerTitle = (() => {
162
- switch (view) {
163
- case "targets":
164
- return `Targets (${targets.length})`;
165
- case "controls":
166
- return state.targetLabel || "Component Match";
167
- case "free":
168
- return "Free Placement";
169
- default:
170
- return "Image Overlay";
171
- }
172
- })();
173
- const headerContent = {
174
- showToggleButton: false,
175
- customContent: /*#__PURE__*/_jsxs(ModalHeader, {
176
- children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
177
- onBack: view !== "idle" ? handleBack : onBack
178
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
179
- title: headerTitle
180
- }), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
181
- children: [view === "targets" && /*#__PURE__*/_jsx(TouchableOpacity, {
182
- style: styles.headerTextButton,
183
- onPress: handleScan,
184
- activeOpacity: 0.7,
185
- children: /*#__PURE__*/_jsx(Text, {
186
- style: styles.headerTextButtonLabel,
187
- children: "Scan"
188
- })
189
- }), (view === "controls" || view === "free") && state.imageUri && /*#__PURE__*/_jsx(PowerToggleButton, {
190
- isEnabled: state.enabled,
191
- onToggle: () => ImageOverlayController.toggle(),
192
- accessibilityLabel: "Toggle image overlay"
193
- }), view !== "idle" && /*#__PURE__*/_jsx(TouchableOpacity, {
194
- style: styles.headerActionButton,
195
- onPress: handleReset,
196
- activeOpacity: 0.7,
197
- children: /*#__PURE__*/_jsx(Trash2, {
198
- size: 14,
199
- color: night.color.textSecondary
200
- })
201
- })]
202
- })]
203
- })
204
- };
205
- const body = /*#__PURE__*/_jsxs(View, {
206
- style: styles.container,
207
- children: [view === "idle" && /*#__PURE__*/_jsxs(View, {
208
- style: styles.homeContainer,
209
- children: [/*#__PURE__*/_jsx(Text, {
210
- style: styles.homeSubtitle,
211
- children: "Choose a mode"
212
- }), /*#__PURE__*/_jsxs(View, {
213
- style: styles.modeGrid,
214
- children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
215
- style: styles.modePanel,
216
- onPress: handleScan,
217
- activeOpacity: 0.7,
218
- children: [/*#__PURE__*/_jsx(View, {
219
- style: [styles.modeIconCircle, {
220
- backgroundColor: night.color.accentSoft
221
- }],
222
- children: /*#__PURE__*/_jsx(SquareDashed, {
223
- size: 24,
224
- color: night.color.accent
225
- })
226
- }), /*#__PURE__*/_jsxs(Text, {
227
- style: styles.modePanelTitle,
228
- children: ["Component", "\n", "Match"]
229
- }), /*#__PURE__*/_jsx(Text, {
230
- style: styles.modePanelDesc,
231
- children: "Overlay on a tagged component"
232
- })]
233
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
234
- style: styles.modePanel,
235
- onPress: () => setView("free"),
236
- activeOpacity: 0.7,
237
- children: [/*#__PURE__*/_jsx(View, {
238
- style: [styles.modeIconCircle, {
239
- backgroundColor: night.color.accentSoft
240
- }],
241
- children: /*#__PURE__*/_jsx(Image, {
242
- size: 24,
243
- color: night.color.accent
244
- })
245
- }), /*#__PURE__*/_jsxs(Text, {
246
- style: styles.modePanelTitle,
247
- children: ["Free", "\n", "Placement"]
248
- }), /*#__PURE__*/_jsx(Text, {
249
- style: styles.modePanelDesc,
250
- children: "Drag and resize anywhere"
251
- })]
252
- })]
253
- })]
254
- }), view === "targets" && /*#__PURE__*/_jsx(TargetList, {
255
- targets: targets,
256
- onSelect: handleSelectTarget
257
- }), view === "controls" && /*#__PURE__*/_jsxs(View, {
258
- style: styles.controlsContainer,
259
- children: [state.targetRect && /*#__PURE__*/_jsxs(View, {
260
- style: styles.infoCard,
261
- children: [/*#__PURE__*/_jsxs(View, {
262
- style: styles.infoRow,
263
- children: [/*#__PURE__*/_jsx(Text, {
264
- style: styles.infoLabel,
265
- children: "Component"
266
- }), /*#__PURE__*/_jsx(Text, {
267
- style: styles.infoValue,
268
- children: state.targetLabel
269
- })]
270
- }), /*#__PURE__*/_jsxs(View, {
271
- style: styles.infoRow,
272
- children: [/*#__PURE__*/_jsx(Text, {
273
- style: styles.infoLabel,
274
- children: "Size"
275
- }), /*#__PURE__*/_jsxs(Text, {
276
- style: styles.infoValueMono,
277
- children: [Math.round(state.targetRect.width), " x ", Math.round(state.targetRect.height)]
278
- })]
279
- }), /*#__PURE__*/_jsxs(View, {
280
- style: styles.infoRow,
281
- children: [/*#__PURE__*/_jsx(Text, {
282
- style: styles.infoLabel,
283
- children: "Position"
284
- }), /*#__PURE__*/_jsxs(Text, {
285
- style: styles.infoValueMono,
286
- children: [Math.round(state.targetRect.x), ", ", Math.round(state.targetRect.y)]
287
- })]
288
- }), state.imageWidth != null && state.imageHeight != null && /*#__PURE__*/_jsxs(View, {
289
- style: styles.infoRow,
290
- children: [/*#__PURE__*/_jsx(Text, {
291
- style: styles.infoLabel,
292
- children: "Image"
293
- }), /*#__PURE__*/_jsxs(Text, {
294
- style: styles.infoValueMono,
295
- children: [state.imageWidth, " x ", state.imageHeight]
296
- })]
297
- })]
298
- }), /*#__PURE__*/_jsxs(View, {
299
- style: styles.inputSection,
300
- children: [/*#__PURE__*/_jsx(Text, {
301
- style: styles.sectionLabel,
302
- children: "Image"
303
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
304
- style: styles.pasteButton,
305
- onPress: handlePasteImage,
306
- activeOpacity: 0.7,
307
- accessibilityLabel: "Paste from clipboard",
308
- accessibilityRole: "button",
309
- children: /*#__PURE__*/_jsx(Text, {
310
- style: styles.pasteButtonText,
311
- children: "Paste from Clipboard"
312
- })
313
- }), /*#__PURE__*/_jsxs(View, {
314
- style: styles.urlRow,
315
- children: [/*#__PURE__*/_jsx(TextInput, {
316
- style: styles.urlInput,
317
- value: imageUrl,
318
- onChangeText: setImageUrl,
319
- placeholder: "or enter URL...",
320
- placeholderTextColor: night.color.placeholder,
321
- autoCapitalize: "none",
322
- autoCorrect: false,
323
- returnKeyType: "go",
324
- onSubmitEditing: handleLoadImage
325
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
326
- style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
327
- onPress: handleLoadImage,
328
- disabled: !imageUrl.trim(),
329
- activeOpacity: 0.7,
330
- children: /*#__PURE__*/_jsx(Text, {
331
- style: styles.loadButtonText,
332
- children: "Load"
333
- })
334
- })]
335
- })]
336
- }), /*#__PURE__*/_jsxs(View, {
337
- style: styles.toggleSection,
338
- children: [/*#__PURE__*/_jsx(ToggleRow, {
339
- label: "Show Outline",
340
- value: state.showOutline,
341
- onValueChange: v => ImageOverlayController.setShowOutline(v)
342
- }), /*#__PURE__*/_jsx(ToggleRow, {
343
- label: "Auto Track",
344
- value: autoTrack,
345
- onValueChange: setAutoTrack
346
- }), state.imageUri && /*#__PURE__*/_jsxs(_Fragment, {
347
- children: [/*#__PURE__*/_jsxs(View, {
348
- style: styles.blendRow,
349
- children: [/*#__PURE__*/_jsx(Text, {
350
- style: styles.toggleLabel,
351
- children: "Opacity"
352
- }), /*#__PURE__*/_jsx(View, {
353
- style: styles.blendButtons,
354
- children: [0, 25, 50, 75, 100].map(pct => /*#__PURE__*/_jsx(TouchableOpacity, {
355
- style: [styles.blendButton, Math.round(state.opacity * 100) === pct && styles.blendButtonActive],
356
- onPress: () => ImageOverlayController.setOpacity(pct / 100),
357
- activeOpacity: 0.7,
358
- children: /*#__PURE__*/_jsxs(Text, {
359
- style: [styles.blendButtonText, Math.round(state.opacity * 100) === pct && styles.blendButtonTextActive],
360
- children: [pct, "%"]
361
- })
362
- }, pct))
363
- })]
364
- }), /*#__PURE__*/_jsxs(View, {
365
- style: styles.blendRow,
366
- children: [/*#__PURE__*/_jsx(Text, {
367
- style: styles.toggleLabel,
368
- children: "Flip"
369
- }), /*#__PURE__*/_jsxs(View, {
370
- style: styles.blendButtons,
371
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
372
- style: [styles.blendButton, state.invertX && styles.blendButtonActive],
373
- onPress: () => ImageOverlayController.toggleInvertX(),
374
- activeOpacity: 0.7,
375
- children: /*#__PURE__*/_jsx(Text, {
376
- style: [styles.blendButtonText, state.invertX && styles.blendButtonTextActive],
377
- children: "Horizontal"
378
- })
379
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
380
- style: [styles.blendButton, state.invertY && styles.blendButtonActive],
381
- onPress: () => ImageOverlayController.toggleInvertY(),
382
- activeOpacity: 0.7,
383
- children: /*#__PURE__*/_jsx(Text, {
384
- style: [styles.blendButtonText, state.invertY && styles.blendButtonTextActive],
385
- children: "Vertical"
386
- })
387
- })]
388
- })]
389
- })]
390
- })]
391
- }), state.imageUri && /*#__PURE__*/_jsx(OverlayControls, {
392
- opacity: state.opacity,
393
- scale: state.scale,
394
- offsetX: state.offsetX,
395
- offsetY: state.offsetY,
396
- onOpacityChange: v => ImageOverlayController.setOpacity(v),
397
- onScaleChange: v => ImageOverlayController.setScale(v),
398
- onOffsetChange: (x, y) => ImageOverlayController.setOffset(x, y)
399
- }), /*#__PURE__*/_jsxs(View, {
400
- style: styles.actions,
401
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
402
- style: styles.actionButton,
403
- onPress: () => ImageOverlayController.resetSettings(),
404
- activeOpacity: 0.7,
405
- children: /*#__PURE__*/_jsx(Text, {
406
- style: styles.actionButtonText,
407
- children: "Reset Settings"
408
- })
409
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
410
- style: styles.actionButton,
411
- onPress: () => ImageOverlayController.remeasure(),
412
- activeOpacity: 0.7,
413
- children: /*#__PURE__*/_jsx(Text, {
414
- style: styles.actionButtonText,
415
- children: "Remeasure"
416
- })
417
- })]
418
- })]
419
- }), view === "free" && /*#__PURE__*/_jsxs(View, {
420
- style: styles.controlsContainer,
421
- children: [/*#__PURE__*/_jsxs(View, {
422
- style: styles.inputSection,
423
- children: [/*#__PURE__*/_jsx(Text, {
424
- style: styles.sectionLabel,
425
- children: "Image"
426
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
427
- style: styles.pasteButton,
428
- onPress: handlePasteImage,
429
- activeOpacity: 0.7,
430
- accessibilityLabel: "Paste from clipboard",
431
- accessibilityRole: "button",
432
- children: /*#__PURE__*/_jsx(Text, {
433
- style: styles.pasteButtonText,
434
- children: "Paste from Clipboard"
435
- })
436
- }), /*#__PURE__*/_jsxs(View, {
437
- style: styles.urlRow,
438
- children: [/*#__PURE__*/_jsx(TextInput, {
439
- style: styles.urlInput,
440
- value: imageUrl,
441
- onChangeText: setImageUrl,
442
- placeholder: "or enter URL...",
443
- placeholderTextColor: night.color.placeholder,
444
- autoCapitalize: "none",
445
- autoCorrect: false,
446
- returnKeyType: "go",
447
- onSubmitEditing: handleLoadImage
448
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
449
- style: [styles.loadButton, !imageUrl.trim() && styles.loadButtonDisabled],
450
- onPress: handleLoadImage,
451
- disabled: !imageUrl.trim(),
452
- activeOpacity: 0.7,
453
- children: /*#__PURE__*/_jsx(Text, {
454
- style: styles.loadButtonText,
455
- children: "Load"
456
- })
457
- })]
458
- })]
459
- }), state.imageUri && state.mode === "free" && /*#__PURE__*/_jsxs(_Fragment, {
460
- children: [/*#__PURE__*/_jsxs(View, {
461
- style: styles.infoCard,
462
- children: [/*#__PURE__*/_jsxs(View, {
463
- style: styles.infoRow,
464
- children: [/*#__PURE__*/_jsx(Text, {
465
- style: styles.infoLabel,
466
- children: "Position"
467
- }), /*#__PURE__*/_jsxs(Text, {
468
- style: styles.infoValueMono,
469
- children: [Math.round(state.freeX), ", ", Math.round(state.freeY)]
470
- })]
471
- }), /*#__PURE__*/_jsxs(View, {
472
- style: styles.infoRow,
473
- children: [/*#__PURE__*/_jsx(Text, {
474
- style: styles.infoLabel,
475
- children: "Size"
476
- }), /*#__PURE__*/_jsxs(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__*/_jsxs(View, {
481
- style: styles.infoRow,
482
- children: [/*#__PURE__*/_jsx(Text, {
483
- style: styles.infoLabel,
484
- children: "Original"
485
- }), /*#__PURE__*/_jsxs(Text, {
486
- style: styles.infoValueMono,
487
- children: [state.imageWidth, " x ", state.imageHeight]
488
- })]
489
- })]
490
- }), /*#__PURE__*/_jsx(View, {
491
- style: styles.inputSection,
492
- children: /*#__PURE__*/_jsx(OverlayControls, {
493
- opacity: state.opacity,
494
- scale: 1,
495
- offsetX: 0,
496
- offsetY: 0,
497
- onOpacityChange: v => ImageOverlayController.setOpacity(v),
498
- onScaleChange: () => {},
499
- onOffsetChange: () => {},
500
- opacityOnly: true
501
- })
502
- }), /*#__PURE__*/_jsxs(View, {
503
- style: styles.toggleSection,
504
- children: [/*#__PURE__*/_jsx(ToggleRow, {
505
- label: "Lock Position",
506
- tone: "warning",
507
- value: state.locked,
508
- onValueChange: v => ImageOverlayController.setLocked(v)
509
- }), /*#__PURE__*/_jsxs(View, {
510
- style: styles.blendRow,
511
- children: [/*#__PURE__*/_jsx(Text, {
512
- style: styles.toggleLabel,
513
- children: "Opacity"
514
- }), /*#__PURE__*/_jsx(View, {
515
- style: styles.blendButtons,
516
- children: [0, 25, 50, 75, 100].map(pct => /*#__PURE__*/_jsx(TouchableOpacity, {
517
- style: [styles.blendButton, Math.round(state.opacity * 100) === pct && styles.blendButtonActive],
518
- onPress: () => ImageOverlayController.setOpacity(pct / 100),
519
- activeOpacity: 0.7,
520
- children: /*#__PURE__*/_jsxs(Text, {
521
- style: [styles.blendButtonText, Math.round(state.opacity * 100) === pct && styles.blendButtonTextActive],
522
- children: [pct, "%"]
523
- })
524
- }, pct))
525
- })]
526
- }), /*#__PURE__*/_jsxs(View, {
527
- style: styles.blendRow,
528
- children: [/*#__PURE__*/_jsx(Text, {
529
- style: styles.toggleLabel,
530
- children: "Flip"
531
- }), /*#__PURE__*/_jsxs(View, {
532
- style: styles.blendButtons,
533
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
534
- style: [styles.blendButton, state.invertX && styles.blendButtonActive],
535
- onPress: () => ImageOverlayController.toggleInvertX(),
536
- activeOpacity: 0.7,
537
- children: /*#__PURE__*/_jsx(Text, {
538
- style: [styles.blendButtonText, state.invertX && styles.blendButtonTextActive],
539
- children: "Horizontal"
540
- })
541
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
542
- style: [styles.blendButton, state.invertY && styles.blendButtonActive],
543
- onPress: () => ImageOverlayController.toggleInvertY(),
544
- activeOpacity: 0.7,
545
- children: /*#__PURE__*/_jsx(Text, {
546
- style: [styles.blendButtonText, state.invertY && styles.blendButtonTextActive],
547
- children: "Vertical"
548
- })
549
- })]
550
- })]
551
- })]
552
- }), /*#__PURE__*/_jsx(View, {
553
- style: styles.actions,
554
- children: /*#__PURE__*/_jsx(TouchableOpacity, {
555
- style: styles.actionButton,
556
- onPress: () => ImageOverlayController.resetSettings(),
557
- activeOpacity: 0.7,
558
- children: /*#__PURE__*/_jsx(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__*/_jsxs(View, {
572
- style: styles.inlineRoot,
573
- children: [/*#__PURE__*/_jsx(View, {
574
- style: styles.inlineHeader,
575
- ...windowDragProps,
576
- children: headerContent.customContent
577
- }), body]
578
- });
579
- }
580
- return /*#__PURE__*/_jsx(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
- export function ImageOverlayToolView(props = {}) {
604
- return /*#__PURE__*/_jsx(ImageOverlayModal, {
605
- ...props,
606
- inline: true,
607
- visible: true,
608
- onClose: () => {}
609
- });
610
- }
611
- const styles = StyleSheet.create({
612
- inlineRoot: {
613
- flex: 1,
614
- minHeight: 0,
615
- backgroundColor: macOSColors.background.base
616
- },
617
- inlineHeader: {
618
- borderBottomWidth: 1,
619
- borderBottomColor: 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: night.color.buttonSurface,
633
- borderWidth: 1,
634
- borderColor: night.color.border,
635
- alignItems: "center",
636
- justifyContent: "center"
637
- },
638
- headerTextButtonLabel: {
639
- fontSize: 12,
640
- fontWeight: "600",
641
- color: night.color.accent
642
- },
643
- headerActionButton: {
644
- width: 32,
645
- height: 32,
646
- borderRadius: 8,
647
- backgroundColor: night.color.buttonSurface,
648
- borderWidth: 1,
649
- borderColor: 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: 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: night.color.surface,
679
- borderRadius: 12,
680
- borderWidth: 1,
681
- borderColor: 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: night.color.text,
696
- textAlign: "center",
697
- lineHeight: 17
698
- },
699
- modePanelDesc: {
700
- fontSize: 11,
701
- color: 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: night.color.surface,
715
- borderRadius: 8,
716
- borderWidth: 1,
717
- borderColor: 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: night.color.textTertiary,
730
- textTransform: "uppercase",
731
- letterSpacing: 0.3
732
- },
733
- infoValue: {
734
- fontSize: 12,
735
- fontWeight: "600",
736
- color: night.color.text
737
- },
738
- infoValueMono: {
739
- fontSize: 12,
740
- fontWeight: "600",
741
- color: night.color.text,
742
- fontFamily: monoFont
743
- },
744
- inputSection: {
745
- paddingHorizontal: 12,
746
- gap: 6
747
- },
748
- sectionLabel: {
749
- fontSize: 11,
750
- fontWeight: "600",
751
- color: night.color.textTertiary,
752
- textTransform: "uppercase",
753
- letterSpacing: 0.5
754
- },
755
- pasteButton: {
756
- paddingVertical: 10,
757
- borderRadius: 8,
758
- backgroundColor: night.color.accentSoft,
759
- borderWidth: 1,
760
- borderColor: night.color.accentBorderStrong,
761
- alignItems: "center"
762
- },
763
- pasteButtonText: {
764
- fontSize: 13,
765
- fontWeight: "600",
766
- color: 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: night.color.surfaceElevated,
777
- borderWidth: 1,
778
- borderColor: night.color.border,
779
- paddingHorizontal: 12,
780
- fontSize: 13,
781
- color: night.color.text
782
- },
783
- loadButton: {
784
- height: 36,
785
- paddingHorizontal: 14,
786
- borderRadius: 8,
787
- backgroundColor: 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: 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: night.color.surfaceElevated,
808
- borderRadius: 8,
809
- borderWidth: 1,
810
- borderColor: night.color.border,
811
- paddingHorizontal: 12,
812
- paddingVertical: 8
813
- },
814
- toggleLabel: {
815
- fontSize: 13,
816
- fontWeight: "500",
817
- color: night.color.textSecondary
818
- },
819
- blendRow: {
820
- flexDirection: "row",
821
- alignItems: "center",
822
- justifyContent: "space-between",
823
- backgroundColor: night.color.surfaceElevated,
824
- borderRadius: 8,
825
- borderWidth: 1,
826
- borderColor: 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: night.color.accentSoft
842
- },
843
- blendButtonText: {
844
- fontSize: 12,
845
- fontWeight: "600",
846
- color: night.color.textTertiary
847
- },
848
- blendButtonTextActive: {
849
- color: 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: night.color.surfaceElevated,
867
- borderWidth: 1,
868
- borderColor: night.color.border
869
- },
870
- actionButtonText: {
871
- fontSize: 12,
872
- fontWeight: "500",
873
- color: night.color.textSecondary
874
- }
875
- });
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}});