@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,389 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Singleton state manager for the image overlay tool.
5
- * Bridges the modal (controls) and standalone overlay (rendering).
6
- * Supports two modes: "component" (match to tagged element) and "free" (drag anywhere).
7
- */
8
-
9
- import { Image as RNImage, Dimensions } from "react-native";
10
- import { measureInstance } from "./componentMeasurement";
11
- import { scanForImageTargets } from "./fiberScanner";
12
- import { getDevToolsHook } from "./devtoolsHook";
13
- const DEFAULT_STATE = {
14
- mode: null,
15
- enabled: false,
16
- imageUri: null,
17
- imageWidth: null,
18
- imageHeight: null,
19
- targetTag: null,
20
- targetLabel: null,
21
- targetRect: null,
22
- showOutline: true,
23
- opacity: 0.5,
24
- scale: 1.0,
25
- offsetX: 0,
26
- offsetY: 0,
27
- invertX: false,
28
- invertY: false,
29
- locked: false,
30
- freeX: 0,
31
- freeY: 0,
32
- freeWidth: 200,
33
- freeHeight: 200
34
- };
35
- let state = {
36
- ...DEFAULT_STATE
37
- };
38
- const listeners = new Set();
39
-
40
- // Hold onto references so we can remeasure
41
- let targetInstance = null;
42
- let targetLabel = null;
43
-
44
- // Auto-tracking via React DevTools traceUpdates
45
- let autoTrackEnabled = false;
46
- let traceUnsubscribe = null;
47
- let trackingNativeTag = null;
48
- function notify() {
49
- for (const listener of listeners) {
50
- listener(state);
51
- }
52
- }
53
- function getNativeTag(instance) {
54
- if (!instance) return null;
55
- if (typeof instance._nativeTag === "number") return instance._nativeTag;
56
- if (typeof instance.canonical?._nativeTag === "number") return instance.canonical._nativeTag;
57
- if (typeof instance.__nativeTag === "number") return instance.__nativeTag;
58
- return null;
59
- }
60
- async function measureWithFallback(instance, label) {
61
- let rect = await measureInstance(instance);
62
- if (rect) return {
63
- rect,
64
- freshInstance: instance
65
- };
66
- const targets = scanForImageTargets();
67
- const fresh = targets.find(t => t.label === label);
68
- if (fresh) {
69
- rect = await measureInstance(fresh.instance);
70
- if (rect) return {
71
- rect,
72
- freshInstance: fresh.instance
73
- };
74
- }
75
- return {
76
- rect: null,
77
- freshInstance: instance
78
- };
79
- }
80
-
81
- /**
82
- * Subscribe to React DevTools traceUpdates to remeasure our target
83
- * component in real-time whenever ANY component re-renders (which
84
- * includes scroll events that reposition our target).
85
- */
86
- function startTraceTracking() {
87
- if (traceUnsubscribe) return; // already tracking
88
-
89
- const hook = getDevToolsHook();
90
- if (!hook) return;
91
-
92
- // Enable trace updates on all renderers (same as highlight-updates)
93
- if (hook.rendererInterfaces) {
94
- for (const [, renderer] of hook.rendererInterfaces) {
95
- if (typeof renderer.setTraceUpdatesEnabled === "function") {
96
- renderer.setTraceUpdatesEnabled(true);
97
- }
98
- }
99
- }
100
-
101
- // Subscribe to traceUpdates — fires whenever components render
102
- if (typeof hook.sub === "function") {
103
- traceUnsubscribe = hook.sub("traceUpdates", handleTraceForAutoTrack);
104
- }
105
- }
106
- function stopTraceTracking() {
107
- if (traceUnsubscribe) {
108
- traceUnsubscribe();
109
- traceUnsubscribe = null;
110
- }
111
- // Note: we don't disable traceUpdates on renderers because
112
- // highlight-updates may also be using them
113
- }
114
-
115
- /** Called on every traceUpdates event — remeasure our target */
116
- function handleTraceForAutoTrack(_nodes) {
117
- if (!autoTrackEnabled || !targetInstance) return;
118
-
119
- // Don't filter by nativeTag — any render could have caused a layout
120
- // shift (e.g. sibling resize, scroll). Just remeasure cheaply.
121
- measureInstance(targetInstance).then(rect => {
122
- if (rect && state.targetRect) {
123
- // Only notify if position actually changed
124
- const prev = state.targetRect;
125
- if (Math.abs(rect.x - prev.x) > 0.5 || Math.abs(rect.y - prev.y) > 0.5 || Math.abs(rect.width - prev.width) > 0.5 || Math.abs(rect.height - prev.height) > 0.5) {
126
- state = {
127
- ...state,
128
- targetRect: rect
129
- };
130
- notify();
131
- }
132
- }
133
- });
134
- }
135
- function getImageSize(uri) {
136
- return new Promise(resolve => {
137
- RNImage.getSize(uri, (width, height) => resolve({
138
- width,
139
- height
140
- }), () => resolve(null));
141
- });
142
- }
143
- export const ImageOverlayController = {
144
- subscribe(listener) {
145
- listeners.add(listener);
146
- listener(state);
147
- return () => listeners.delete(listener);
148
- },
149
- getState() {
150
- return state;
151
- },
152
- // ─── Component Match Mode ───────────────────────────────────────────
153
-
154
- async setTarget(instance, label, fiber) {
155
- targetInstance = instance;
156
- targetLabel = label;
157
- const instanceToMeasure = fiber?.stateNode ?? instance;
158
- const {
159
- rect,
160
- freshInstance
161
- } = await measureWithFallback(instanceToMeasure, label);
162
- targetInstance = freshInstance;
163
- const tag = getNativeTag(targetInstance);
164
- state = {
165
- ...state,
166
- mode: "component",
167
- targetTag: tag,
168
- targetLabel: label,
169
- targetRect: rect
170
- };
171
- notify();
172
- },
173
- async setImageUri(uri) {
174
- const size = await getImageSize(uri);
175
- let autoScale = 1.0;
176
- if (size && state.targetRect && size.width > 0) {
177
- autoScale = state.targetRect.width / size.width;
178
- }
179
- state = {
180
- ...state,
181
- imageUri: uri,
182
- imageWidth: size?.width ?? null,
183
- imageHeight: size?.height ?? null,
184
- scale: autoScale,
185
- showOutline: false
186
- };
187
- notify();
188
- },
189
- setOpacity(value) {
190
- state = {
191
- ...state,
192
- opacity: Math.max(0, Math.min(1, value))
193
- };
194
- notify();
195
- },
196
- setScale(value) {
197
- state = {
198
- ...state,
199
- scale: Math.max(0.01, Math.min(5, value))
200
- };
201
- notify();
202
- },
203
- setOffset(x, y) {
204
- state = {
205
- ...state,
206
- offsetX: x,
207
- offsetY: y
208
- };
209
- notify();
210
- },
211
- toggleInvertX() {
212
- state = {
213
- ...state,
214
- invertX: !state.invertX
215
- };
216
- notify();
217
- },
218
- toggleInvertY() {
219
- state = {
220
- ...state,
221
- invertY: !state.invertY
222
- };
223
- notify();
224
- },
225
- setLocked(locked) {
226
- state = {
227
- ...state,
228
- locked
229
- };
230
- notify();
231
- },
232
- setAutoTrack(enabled) {
233
- autoTrackEnabled = enabled;
234
- if (enabled) {
235
- startTraceTracking();
236
- } else {
237
- stopTraceTracking();
238
- }
239
- },
240
- isAutoTracking() {
241
- return autoTrackEnabled;
242
- },
243
- setShowOutline(show) {
244
- state = {
245
- ...state,
246
- showOutline: show
247
- };
248
- notify();
249
- },
250
- setEnabled(enabled) {
251
- state = {
252
- ...state,
253
- enabled
254
- };
255
- notify();
256
- },
257
- toggle() {
258
- state = {
259
- ...state,
260
- enabled: !state.enabled
261
- };
262
- notify();
263
- },
264
- fitWidth() {
265
- if (state.imageWidth && state.targetRect && state.imageWidth > 0) {
266
- state = {
267
- ...state,
268
- scale: state.targetRect.width / state.imageWidth
269
- };
270
- notify();
271
- }
272
- },
273
- fitHeight() {
274
- if (state.imageHeight && state.targetRect && state.imageHeight > 0) {
275
- state = {
276
- ...state,
277
- scale: state.targetRect.height / state.imageHeight
278
- };
279
- notify();
280
- }
281
- },
282
- resetSettings() {
283
- if (state.mode === "free") {
284
- const screen = Dimensions.get("window");
285
- const w = state.imageWidth ?? 200;
286
- const h = state.imageHeight ?? 200;
287
- state = {
288
- ...state,
289
- opacity: 0.5,
290
- freeX: (screen.width - w) / 2,
291
- freeY: (screen.height - h) / 2,
292
- freeWidth: w,
293
- freeHeight: h
294
- };
295
- } else {
296
- let autoScale = 1.0;
297
- if (state.imageWidth && state.targetRect && state.imageWidth > 0) {
298
- autoScale = state.targetRect.width / state.imageWidth;
299
- }
300
- state = {
301
- ...state,
302
- opacity: 0.5,
303
- scale: autoScale,
304
- offsetX: 0,
305
- offsetY: 0
306
- };
307
- }
308
- notify();
309
- },
310
- async remeasure() {
311
- if (!targetLabel) return;
312
- const {
313
- rect,
314
- freshInstance
315
- } = await measureWithFallback(targetInstance, targetLabel);
316
- targetInstance = freshInstance;
317
- if (rect) {
318
- state = {
319
- ...state,
320
- targetRect: rect
321
- };
322
- notify();
323
- }
324
- },
325
- // ─── Free Placement Mode ───────────────────────────────────────────
326
-
327
- async startFreeMode(uri) {
328
- const size = await getImageSize(uri);
329
- const screen = Dimensions.get("window");
330
- const w = size?.width ?? 200;
331
- const h = size?.height ?? 200;
332
-
333
- // Scale down if image is larger than 80% of screen
334
- let displayW = w;
335
- let displayH = h;
336
- const maxW = screen.width * 0.8;
337
- const maxH = screen.height * 0.6;
338
- if (displayW > maxW || displayH > maxH) {
339
- const ratio = Math.min(maxW / displayW, maxH / displayH);
340
- displayW = Math.round(displayW * ratio);
341
- displayH = Math.round(displayH * ratio);
342
- }
343
- state = {
344
- ...state,
345
- mode: "free",
346
- enabled: true,
347
- imageUri: uri,
348
- imageWidth: size?.width ?? null,
349
- imageHeight: size?.height ?? null,
350
- opacity: 0.5,
351
- freeX: Math.round((screen.width - displayW) / 2),
352
- freeY: Math.round((screen.height - displayH) / 2),
353
- freeWidth: Math.round(displayW),
354
- freeHeight: Math.round(displayH)
355
- };
356
- notify();
357
- },
358
- setFreePosition(x, y) {
359
- state = {
360
- ...state,
361
- freeX: x,
362
- freeY: y
363
- };
364
- notify();
365
- },
366
- setFreeDimensions(width, height, x, y) {
367
- state = {
368
- ...state,
369
- freeWidth: width,
370
- freeHeight: height,
371
- freeX: x,
372
- freeY: y
373
- };
374
- notify();
375
- },
376
- // ─── Shared ─────────────────────────────────────────────────────────
377
-
378
- reset() {
379
- stopTraceTracking();
380
- autoTrackEnabled = false;
381
- trackingNativeTag = null;
382
- targetInstance = null;
383
- targetLabel = null;
384
- state = {
385
- ...DEFAULT_STATE
386
- };
387
- notify();
388
- }
389
- };
1
+ "use strict";import{Image as k,Dimensions as b}from"react-native";import{measureInstance as m}from"./componentMeasurement";import{scanForImageTargets as R}from"./fiberScanner";import{getDevToolsHook as F}from"./devtoolsHook";const v={mode:null,enabled:false,imageUri:null,imageWidth:null,imageHeight:null,targetTag:null,targetLabel:null,targetRect:null,showOutline:true,opacity:.5,scale:1,offsetX:0,offsetY:0,invertX:false,invertY:false,locked:false,freeX:0,freeY:0,freeWidth:200,freeHeight:200};let t={...v};const d=new Set;let o=null;let h=null;let g=false;let u=null;let X=null;function i(){for(const e of d){e(t)}}function Y(e){if(!e)return null;if(typeof e._nativeTag==="number")return e._nativeTag;if(typeof e.canonical?._nativeTag==="number")return e.canonical._nativeTag;if(typeof e.__nativeTag==="number")return e.__nativeTag;return null}async function M(e,a){let n=await m(e);if(n)return{rect:n,freshInstance:e};const s=R();const f=s.find(r=>r.label===a);if(f){n=await m(f.instance);if(n)return{rect:n,freshInstance:f.instance}}return{rect:null,freshInstance:e}}function A(){if(u)return;const e=F();if(!e)return;if(e.rendererInterfaces){for(const[,a]of e.rendererInterfaces){if(typeof a.setTraceUpdatesEnabled==="function"){a.setTraceUpdatesEnabled(true)}}}if(typeof e.sub==="function"){u=e.sub("traceUpdates",U)}}let c=null;let w=false;function S(){if(c!==null||typeof requestAnimationFrame!=="function")return;const e=()=>{c=requestAnimationFrame(e);if(w||!g||!o)return;w=true;I().finally(()=>{w=false})};c=requestAnimationFrame(e)}function _(){if(c!==null&&typeof cancelAnimationFrame==="function")cancelAnimationFrame(c);c=null}function W(){_();if(u){u();u=null}}function U(e){if(!g||!o)return;void I()}function I(){return m(o).then(e=>{if(e&&t.targetRect){const a=t.targetRect;if(Math.abs(e.x-a.x)>.5||Math.abs(e.y-a.y)>.5||Math.abs(e.width-a.width)>.5||Math.abs(e.height-a.height)>.5){t={...t,targetRect:e};i()}}}).catch(()=>{})}function H(e){return new Promise(a=>{k.getSize(e,(n,s)=>a({width:n,height:s}),()=>a(null))})}export const ImageOverlayController={subscribe(e){d.add(e);e(t);return()=>d.delete(e)},getState(){return t},async setTarget(e,a,n){o=e;h=a;const s=n?.stateNode??e;const{rect:f,freshInstance:r}=await M(s,a);o=r;const l=Y(o);t={...t,mode:"component",targetTag:l,targetLabel:a,targetRect:f};i()},async setImageUri(e){const a=await H(e);let n=1;if(a&&t.targetRect&&a.width>0){n=t.targetRect.width/a.width}t={...t,imageUri:e,imageWidth:a?.width??null,imageHeight:a?.height??null,scale:n,showOutline:false};i()},setOpacity(e){t={...t,opacity:Math.max(0,Math.min(1,e))};i()},setScale(e){t={...t,scale:Math.max(.01,Math.min(5,e))};i()},setOffset(e,a){t={...t,offsetX:e,offsetY:a};i()},toggleInvertX(){t={...t,invertX:!t.invertX};i()},toggleInvertY(){t={...t,invertY:!t.invertY};i()},setLocked(e){t={...t,locked:e};i()},setAutoTrack(e){g=e;if(e){A();S()}else{W()}},isAutoTracking(){return g},setShowOutline(e){t={...t,showOutline:e};i()},setEnabled(e){t={...t,enabled:e};i()},toggle(){t={...t,enabled:!t.enabled};i()},fitWidth(){if(t.imageWidth&&t.targetRect&&t.imageWidth>0){t={...t,scale:t.targetRect.width/t.imageWidth};i()}},fitHeight(){if(t.imageHeight&&t.targetRect&&t.imageHeight>0){t={...t,scale:t.targetRect.height/t.imageHeight};i()}},resetSettings(){if(t.mode==="free"){const e=b.get("window");const a=t.imageWidth??200;const n=t.imageHeight??200;t={...t,opacity:.5,freeX:(e.width-a)/2,freeY:(e.height-n)/2,freeWidth:a,freeHeight:n}}else{let e=1;if(t.imageWidth&&t.targetRect&&t.imageWidth>0){e=t.targetRect.width/t.imageWidth}t={...t,opacity:.5,scale:e,offsetX:0,offsetY:0}}i()},async remeasure(){if(!h)return;const{rect:e,freshInstance:a}=await M(o,h);o=a;if(e){t={...t,targetRect:e};i()}},async startFreeMode(e){const a=await H(e);const n=b.get("window");const s=a?.width??200;const f=a?.height??200;let r=s;let l=f;const T=n.width*.8;const p=n.height*.6;if(r>T||l>p){const y=Math.min(T/r,p/l);r=Math.round(r*y);l=Math.round(l*y)}t={...t,mode:"free",enabled:true,imageUri:e,imageWidth:a?.width??null,imageHeight:a?.height??null,opacity:.5,freeX:Math.round((n.width-r)/2),freeY:Math.round((n.height-l)/2),freeWidth:Math.round(r),freeHeight:Math.round(l)};i()},setFreePosition(e,a){t={...t,freeX:e,freeY:a};i()},setFreeDimensions(e,a,n,s){t={...t,freeWidth:e,freeHeight:a,freeX:n,freeY:s};i()},reset(){W();g=false;X=null;o=null;h=null;t={...v};i()}};
@@ -1,102 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Component measurement utilities for image overlay positioning.
5
- * Adapted from debug-borders/utils/componentMeasurement.js
6
- *
7
- * Supports both Fabric (new arch) and Paper (legacy) measurement APIs.
8
- */
9
-
10
- import { findNodeHandle, UIManager } from "react-native";
11
- /**
12
- * Measures a component instance and returns its screen-space bounding rect.
13
- * Tries multiple strategies to handle Fabric, Paper, and edge cases.
14
- */
15
- export function measureInstance(instance) {
16
- return new Promise(resolve => {
17
- try {
18
- // Build a list of candidates to try measuring
19
- const candidates = [];
20
-
21
- // Direct instance (ReactFabricHostComponent has measure/getBoundingClientRect)
22
- if (instance) candidates.push(instance);
23
-
24
- // Fabric: instance.canonical.publicInstance
25
- if (instance?.canonical?.publicInstance) {
26
- candidates.push(instance.canonical.publicInstance);
27
- }
28
- // Fabric fallback: instance.canonical itself
29
- if (instance?.canonical && instance.canonical !== instance) {
30
- candidates.push(instance.canonical);
31
- }
32
-
33
- // Try getBoundingClientRect first (modern Fabric, synchronous)
34
- for (const publicInstance of candidates) {
35
- if (typeof publicInstance.getBoundingClientRect === "function") {
36
- try {
37
- const rect = publicInstance.getBoundingClientRect();
38
- if (rect && typeof rect.x === "number" && typeof rect.y === "number" && typeof rect.width === "number" && typeof rect.height === "number" && rect.width > 0 && rect.height > 0) {
39
- resolve({
40
- x: rect.x,
41
- y: rect.y,
42
- width: rect.width,
43
- height: rect.height
44
- });
45
- return;
46
- }
47
- } catch {
48
- // Try next candidate
49
- }
50
- }
51
- }
52
-
53
- // Try measure() callback on all candidates
54
- for (const publicInstance of candidates) {
55
- if (typeof publicInstance.measure === "function") {
56
- const timeout = setTimeout(() => resolve(null), 200);
57
- publicInstance.measure((_x, _y, width, height, pageX, pageY) => {
58
- clearTimeout(timeout);
59
- if (pageX != null && pageY != null && width != null && height != null && width > 0 && height > 0) {
60
- resolve({
61
- x: pageX,
62
- y: pageY,
63
- width,
64
- height
65
- });
66
- } else {
67
- resolve(null);
68
- }
69
- });
70
- return;
71
- }
72
- }
73
-
74
- // Last resort: try findNodeHandle + UIManager.measure
75
- try {
76
- const nodeHandle = findNodeHandle(instance);
77
- if (nodeHandle != null) {
78
- const timeout = setTimeout(() => resolve(null), 200);
79
- UIManager.measure(nodeHandle, (_x, _y, width, height, pageX, pageY) => {
80
- clearTimeout(timeout);
81
- if (pageX != null && pageY != null && width != null && height != null && width > 0 && height > 0) {
82
- resolve({
83
- x: pageX,
84
- y: pageY,
85
- width,
86
- height
87
- });
88
- } else {
89
- resolve(null);
90
- }
91
- });
92
- return;
93
- }
94
- } catch {
95
- // findNodeHandle not available or failed
96
- }
97
- resolve(null);
98
- } catch {
99
- resolve(null);
100
- }
101
- });
102
- }
1
+ "use strict";import{findNodeHandle as m,UIManager as y}from"react-native";export function measureInstance(u){return new Promise(t=>{try{const e=[];if(u)e.push(u);if(u?.canonical?.publicInstance){e.push(u.canonical.publicInstance)}if(u?.canonical&&u.canonical!==u){e.push(u.canonical)}for(const l of e){if(typeof l.getBoundingClientRect==="function"){try{const n=l.getBoundingClientRect();if(n&&typeof n.x==="number"&&typeof n.y==="number"&&typeof n.width==="number"&&typeof n.height==="number"&&n.width>0&&n.height>0){t({x:n.x,y:n.y,width:n.width,height:n.height});return}}catch{}}}for(const l of e){if(typeof l.measure==="function"){const n=setTimeout(()=>t(null),200);l.measure((r,a,c,o,i,f)=>{clearTimeout(n);if(i!=null&&f!=null&&c!=null&&o!=null&&c>0&&o>0){t({x:i,y:f,width:c,height:o})}else{t(null)}});return}}try{const l=m(u);if(l!=null){const n=setTimeout(()=>t(null),200);y.measure(l,(r,a,c,o,i,f)=>{clearTimeout(n);if(i!=null&&f!=null&&c!=null&&o!=null&&c>0&&o>0){t({x:i,y:f,width:c,height:o})}else{t(null)}});return}}catch{}t(null)}catch{t(null)}})}
@@ -1,31 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Single accessor for the React DevTools global hook.
5
- *
6
- * Both the fiber scanner (target discovery) and the controller (auto-track via
7
- * traceUpdates) reach for `__REACT_DEVTOOLS_GLOBAL_HOOK__`. Routing both through
8
- * here gives embedded hosts — the docs-site live demo, and any future desktop
9
- * port — one place to substitute their own hook, so the tool never instruments
10
- * the host page's React renderer just because someone flipped Auto Track.
11
- *
12
- * On device nothing calls setDevToolsHookOverride, so this stays a plain
13
- * passthrough to the global.
14
- */
15
-
16
- let hookOverride = null;
17
-
18
- /**
19
- * Substitute the hook the tool reads. Pass null to fall back to the global.
20
- * The override only needs the parts the tool actually uses:
21
- * `getFiberRoots(rendererId)`, and optionally `sub("traceUpdates", fn)` plus
22
- * `rendererInterfaces` for auto-track.
23
- */
24
- export function setDevToolsHookOverride(hook) {
25
- hookOverride = hook ?? null;
26
- }
27
- export function getDevToolsHook() {
28
- if (hookOverride) return hookOverride;
29
- if (typeof global === "undefined") return null;
30
- return global.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? null;
31
- }
1
+ "use strict";let e=null;export function setDevToolsHookOverride(o){e=o??null}export function getDevToolsHook(){if(e)return e;if(typeof global==="undefined")return null;return global.__REACT_DEVTOOLS_GLOBAL_HOOK__??null}
@@ -1,98 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Fiber tree scanner for discovering image-overlay targets.
5
- * Finds components tagged with testID="image-target:LabelName".
6
- *
7
- * Core traversal adapted from debug-borders/utils/fiberTreeTraversal.js
8
- */
9
-
10
- import { getDevToolsHook } from "./devtoolsHook";
11
- const IMAGE_TARGET_PREFIX = "image-target:";
12
-
13
- // Fiber tag constants (from React internals)
14
- const HostComponent = 5;
15
- const OffscreenComponent = 22;
16
- function getFiberRoots() {
17
- const hook = getDevToolsHook();
18
- if (!hook?.getFiberRoots) return [];
19
- try {
20
- const rootsSet = hook.getFiberRoots(1);
21
- return rootsSet ? Array.from(rootsSet) : [];
22
- } catch {
23
- return [];
24
- }
25
- }
26
- function isHiddenOffscreen(fiber) {
27
- return fiber.tag === OffscreenComponent && fiber.memoizedState !== null;
28
- }
29
- function isInactiveScreen(fiber) {
30
- return fiber.memoizedProps?.activityState === 0;
31
- }
32
- function traverseFiberTree(fiber, callback, depth = 0, visited = new Set()) {
33
- if (!fiber || visited.has(fiber) || depth > 500) return;
34
- visited.add(fiber);
35
- if (isHiddenOffscreen(fiber)) {
36
- if (fiber.sibling) traverseFiberTree(fiber.sibling, callback, depth, visited);
37
- return;
38
- }
39
- if (isInactiveScreen(fiber)) {
40
- if (fiber.sibling) traverseFiberTree(fiber.sibling, callback, depth, visited);
41
- return;
42
- }
43
- callback(fiber, depth);
44
- if (fiber.child) traverseFiberTree(fiber.child, callback, depth + 1, visited);
45
- if (fiber.sibling) traverseFiberTree(fiber.sibling, callback, depth, visited);
46
- }
47
-
48
- /**
49
- * Walk the fiber tree upward to find the owning user-defined React component name.
50
- */
51
- function getOwningComponentName(fiber) {
52
- const internalNames = new Set(["View", "RCTView", "Text", "RCTText", "Image", "RCTImageView", "ScrollView", "RCTScrollView", "TextInput", "RCTTextInput", "TouchableOpacity", "TouchableHighlight", "TouchableWithoutFeedback", "Pressable", "FlatList", "SectionList", "SafeAreaView"]);
53
- let current = fiber._debugOwner || fiber.return;
54
- let depth = 0;
55
- while (current && depth < 30) {
56
- depth++;
57
- const type = current.type;
58
- if (type) {
59
- const name = typeof type === "string" ? type : type.displayName || type.name;
60
- if (name && !internalNames.has(name)) {
61
- return name;
62
- }
63
- }
64
- current = current.return;
65
- }
66
- return null;
67
- }
68
-
69
- /**
70
- * Scan the React fiber tree for components with testID="image-target:*".
71
- * Returns an array of discovered targets with their fiber references for measurement.
72
- */
73
- export function scanForImageTargets() {
74
- const roots = getFiberRoots();
75
- if (roots.length === 0) return [];
76
- const targets = [];
77
- for (const root of roots) {
78
- traverseFiberTree(root.current, fiber => {
79
- if (fiber.tag !== HostComponent) return;
80
-
81
- // react-native-web rewrites testID to data-testid before it reaches the
82
- // host fiber, so read both — otherwise the scanner finds nothing in any
83
- // RNW-rendered tree (example-web, the docs-site live demo).
84
- const testID = fiber.memoizedProps?.testID ?? fiber.pendingProps?.testID ?? fiber.memoizedProps?.["data-testid"] ?? fiber.pendingProps?.["data-testid"];
85
- if (!testID || !testID.startsWith(IMAGE_TARGET_PREFIX)) return;
86
- const label = testID.slice(IMAGE_TARGET_PREFIX.length);
87
- if (!label) return;
88
- targets.push({
89
- label,
90
- testID,
91
- fiber,
92
- instance: fiber.stateNode,
93
- componentName: getOwningComponentName(fiber)
94
- });
95
- });
96
- }
97
- return targets;
98
- }
1
+ "use strict";import{getDevToolsHook as c}from"./devtoolsHook";const a="image-target:";const u=5;const g=22;function l(){const t=c();if(!t?.getFiberRoots)return[];const o=new Set;const n=t.renderers?.keys?Array.from(t.renderers.keys()):[1];for(const e of n){try{t.getFiberRoots(e)?.forEach(s=>o.add(s))}catch{}}return[...o]}function m(t){return t.tag===g&&t.memoizedState!==null}function d(t){return t.memoizedProps?.activityState===0}function i(t,o,n=0,e=new Set){if(!t||e.has(t)||n>500)return;e.add(t);if(m(t)){if(t.sibling)i(t.sibling,o,n,e);return}if(d(t)){if(t.sibling)i(t.sibling,o,n,e);return}o(t,n);if(t.child)i(t.child,o,n+1,e);if(t.sibling)i(t.sibling,o,n,e)}function p(t){const o=new Set(["View","RCTView","Text","RCTText","Image","RCTImageView","ScrollView","RCTScrollView","TextInput","RCTTextInput","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","Pressable","FlatList","SectionList","SafeAreaView"]);let n=t._debugOwner||t.return;let e=0;while(n&&e<30){e++;const s=n.type;if(s){const r=typeof s==="string"?s:s.displayName||s.name;if(r&&!o.has(r)){return r}}n=n.return}return null}export function scanForImageTargets(){const t=l();if(t.length===0)return[];const o=[];for(const n of t){i(n.current,e=>{if(e.tag!==u)return;const s=e.memoizedProps?.testID??e.pendingProps?.testID??e.memoizedProps?.["data-testid"]??e.pendingProps?.["data-testid"];if(!s||!s.startsWith(a))return;const r=s.slice(a.length);if(!r)return;o.push({label:r,testID:s,fiber:e,instance:e.stateNode,componentName:p(e)})})}return o}
@@ -0,0 +1 @@
1
+ "use strict";export function loadImageClipboard(){try{return require("expo-clipboard")}catch{return null}}export function loadTextClipboard(){try{const r=require("@react-native-clipboard/clipboard");return r.default??r}catch{return null}}
@@ -0,0 +1 @@
1
+ "use strict";let e;export function loadImageClipboard(){if(typeof navigator==="undefined"||!navigator.clipboard)return null;return{async hasImageAsync(){e=void 0;if(!navigator.clipboard.read)return false;const n=await navigator.clipboard.read();for(const t of n){const r=t.types.find(i=>i.startsWith("image/"));if(r){e=await t.getType(r);return true}}return false},async getImageAsync(){if(!e)return null;const n=e;e=void 0;const t=await new Promise((r,i)=>{const a=new FileReader;a.onload=()=>r(String(a.result));a.onerror=i;a.readAsDataURL(n)});return{data:t}},getStringAsync:()=>navigator.clipboard.readText()}}export function loadTextClipboard(){return null}
@@ -1,35 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * @buoy-gg/image-overlay
5
- *
6
- * Image overlay tool for comparing design mockups against your running app.
7
- *
8
- * PUBLIC API - Only these exports are supported for external use.
9
- */
10
-
11
- // =============================================================================
12
- // PRESET (Primary entry point for users)
13
- // =============================================================================
14
- export { imageOverlayToolPreset, createImageOverlayTool } from "./preset";
15
-
16
- // =============================================================================
17
- // COMPONENTS (For custom UI implementations)
18
- // =============================================================================
19
- export { ImageOverlayModal, ImageOverlayToolView } from "./imageOverlay/components/ImageOverlayModal";
20
- export { ImageOverlayStandalone } from "./imageOverlay/components/ImageOverlayStandalone";
21
-
22
- // =============================================================================
23
- // CONTROLLER (For programmatic control)
24
- // =============================================================================
25
- export { ImageOverlayController } from "./imageOverlay/utils/ImageOverlayController";
26
-
27
- // =============================================================================
28
- // UTILITIES (For custom integrations)
29
- // =============================================================================
30
- export { scanForImageTargets } from "./imageOverlay/utils/fiberScanner";
31
- export { setDevToolsHookOverride } from "./imageOverlay/utils/devtoolsHook";
32
-
33
- // =============================================================================
34
- // TYPES (For TypeScript users)
35
- // =============================================================================
1
+ "use strict";export{imageOverlayToolPreset,createImageOverlayTool}from"./preset";export{ImageOverlayModal,ImageOverlayToolView}from"./imageOverlay/components/ImageOverlayModal";export{ImageOverlayStandalone}from"./imageOverlay/components/ImageOverlayStandalone";export{ImageOverlayController}from"./imageOverlay/utils/ImageOverlayController";export{scanForImageTargets}from"./imageOverlay/utils/fiberScanner";export{setDevToolsHookOverride}from"./imageOverlay/utils/devtoolsHook";