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