@buoy-gg/image-overlay 2.1.11

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