@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,455 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImageOverlayStandalone = ImageOverlayStandalone;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _ImageOverlayController = require("../utils/ImageOverlayController");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
+ /**
13
+ * Standalone overlay that renders:
14
+ * - Component mode: highlight border + image overlay on a tagged component
15
+ * - Free mode: draggable, resizable image anywhere on screen (aspect-ratio locked)
16
+ *
17
+ * Supports Normal, Difference, and Multiply blend modes.
18
+ * Supports image inversion (flip X/Y) and position locking.
19
+ * Auto-mounted by FloatingDevTools alongside other standalone overlays.
20
+ */
21
+
22
+ // ─── Overlay Image (handles flip transforms) ─────────────────────────────────
23
+ function OverlayImage({
24
+ uri,
25
+ style,
26
+ invertX,
27
+ invertY
28
+ }) {
29
+ const transform = [];
30
+ if (invertX) transform.push({
31
+ scaleX: -1
32
+ });
33
+ if (invertY) transform.push({
34
+ scaleY: -1
35
+ });
36
+ const flipStyle = transform.length > 0 ? {
37
+ transform
38
+ } : {};
39
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
40
+ source: {
41
+ uri
42
+ },
43
+ style: [style, flipStyle]
44
+ // @ts-ignore
45
+ ,
46
+ pointerEvents: "none"
47
+ });
48
+ }
49
+
50
+ // ─── Free Placement ───────────────────────────────────────────────────────────
51
+
52
+ const FREE_MIN_SIZE = 30;
53
+ function FreePlacement({
54
+ imageUri,
55
+ x,
56
+ y,
57
+ width,
58
+ height,
59
+ opacity,
60
+ aspectRatio,
61
+ invertX,
62
+ invertY,
63
+ locked
64
+ }) {
65
+ const [isInteracting, setIsInteracting] = (0, _react.useState)(false);
66
+ const startPosRef = (0, _react.useRef)({
67
+ x: 0,
68
+ y: 0
69
+ });
70
+ const startDimsRef = (0, _react.useRef)({
71
+ x: 0,
72
+ y: 0,
73
+ width: 0,
74
+ height: 0
75
+ });
76
+ const screen = _reactNative.Dimensions.get("window");
77
+
78
+ // ── Drag ──
79
+ const dragResponder = (0, _react.useMemo)(() => _reactNative.PanResponder.create({
80
+ onStartShouldSetPanResponder: () => !locked,
81
+ onMoveShouldSetPanResponder: (_, g) => !locked && (Math.abs(g.dx) > 2 || Math.abs(g.dy) > 2),
82
+ onPanResponderTerminationRequest: () => false,
83
+ onPanResponderGrant: () => {
84
+ const s = _ImageOverlayController.ImageOverlayController.getState();
85
+ startPosRef.current = {
86
+ x: s.freeX,
87
+ y: s.freeY
88
+ };
89
+ setIsInteracting(true);
90
+ },
91
+ onPanResponderMove: (_, g) => {
92
+ const s = _ImageOverlayController.ImageOverlayController.getState();
93
+ const newX = Math.max(0, Math.min(startPosRef.current.x + g.dx, screen.width - s.freeWidth));
94
+ const newY = Math.max(0, Math.min(startPosRef.current.y + g.dy, screen.height - s.freeHeight));
95
+ _ImageOverlayController.ImageOverlayController.setFreePosition(newX, newY);
96
+ },
97
+ onPanResponderRelease: () => setIsInteracting(false),
98
+ onPanResponderTerminate: () => setIsInteracting(false)
99
+ }), [screen.width, screen.height, locked]);
100
+
101
+ // ── Aspect-ratio-locked corner resize ──
102
+ const createResizeHandler = (0, _react.useCallback)(corner => _reactNative.PanResponder.create({
103
+ onStartShouldSetPanResponder: () => !locked,
104
+ onMoveShouldSetPanResponder: () => !locked,
105
+ onPanResponderTerminationRequest: () => false,
106
+ onPanResponderGrant: () => {
107
+ const s = _ImageOverlayController.ImageOverlayController.getState();
108
+ startDimsRef.current = {
109
+ x: s.freeX,
110
+ y: s.freeY,
111
+ width: s.freeWidth,
112
+ height: s.freeHeight
113
+ };
114
+ setIsInteracting(true);
115
+ },
116
+ onPanResponderMove: (_, g) => {
117
+ const {
118
+ dx,
119
+ dy
120
+ } = g;
121
+ if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return;
122
+ const start = startDimsRef.current;
123
+ const ratio = aspectRatio > 0 ? aspectRatio : start.width / start.height;
124
+ const absDx = Math.abs(dx);
125
+ const absDy = Math.abs(dy);
126
+ const useDx = absDx >= absDy;
127
+ let newWidth;
128
+ let newHeight;
129
+ let newX = start.x;
130
+ let newY = start.y;
131
+ switch (corner) {
132
+ case "bottomRight":
133
+ {
134
+ newWidth = useDx ? start.width + dx : (start.height + dy) * ratio;
135
+ newWidth = Math.max(FREE_MIN_SIZE, Math.min(newWidth, screen.width - start.x));
136
+ newHeight = newWidth / ratio;
137
+ break;
138
+ }
139
+ case "bottomLeft":
140
+ {
141
+ newWidth = useDx ? start.width - dx : (start.height + dy) * ratio;
142
+ newWidth = Math.max(FREE_MIN_SIZE, newWidth);
143
+ newHeight = newWidth / ratio;
144
+ newX = start.x + start.width - newWidth;
145
+ newX = Math.max(0, newX);
146
+ newWidth = Math.min(newWidth, start.x + start.width);
147
+ newHeight = newWidth / ratio;
148
+ break;
149
+ }
150
+ case "topRight":
151
+ {
152
+ newWidth = useDx ? start.width + dx : (start.height - dy) * ratio;
153
+ newWidth = Math.max(FREE_MIN_SIZE, Math.min(newWidth, screen.width - start.x));
154
+ newHeight = newWidth / ratio;
155
+ newY = start.y + start.height - newHeight;
156
+ newY = Math.max(0, newY);
157
+ newHeight = Math.min(newHeight, start.y + start.height);
158
+ newWidth = newHeight * ratio;
159
+ break;
160
+ }
161
+ case "topLeft":
162
+ {
163
+ newWidth = useDx ? start.width - dx : (start.height - dy) * ratio;
164
+ newWidth = Math.max(FREE_MIN_SIZE, newWidth);
165
+ newHeight = newWidth / ratio;
166
+ newX = start.x + start.width - newWidth;
167
+ newY = start.y + start.height - newHeight;
168
+ newX = Math.max(0, newX);
169
+ newY = Math.max(0, newY);
170
+ newWidth = Math.min(newWidth, start.x + start.width);
171
+ newHeight = newWidth / ratio;
172
+ newY = start.y + start.height - newHeight;
173
+ break;
174
+ }
175
+ }
176
+ newWidth = Math.max(FREE_MIN_SIZE, Math.round(newWidth));
177
+ newHeight = Math.max(FREE_MIN_SIZE, Math.round(newHeight));
178
+ _ImageOverlayController.ImageOverlayController.setFreeDimensions(newWidth, newHeight, Math.round(newX), Math.round(newY));
179
+ },
180
+ onPanResponderRelease: () => setIsInteracting(false),
181
+ onPanResponderTerminate: () => setIsInteracting(false)
182
+ }), [screen.width, screen.height, aspectRatio, locked]);
183
+ const resizeHandlers = (0, _react.useMemo)(() => ({
184
+ topLeft: createResizeHandler("topLeft"),
185
+ topRight: createResizeHandler("topRight"),
186
+ bottomLeft: createResizeHandler("bottomLeft"),
187
+ bottomRight: createResizeHandler("bottomRight")
188
+ }), [createResizeHandler]);
189
+ const imageStyle = {
190
+ ..._reactNative.StyleSheet.absoluteFillObject,
191
+ opacity
192
+ };
193
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
194
+ style: {
195
+ position: "absolute",
196
+ left: x,
197
+ top: y,
198
+ width,
199
+ height
200
+ },
201
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
202
+ style: _reactNative.StyleSheet.absoluteFill,
203
+ ...(locked ? {} : dragResponder.panHandlers),
204
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(OverlayImage, {
205
+ uri: imageUri,
206
+ style: imageStyle,
207
+ invertX: invertX,
208
+ invertY: invertY
209
+ })
210
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
211
+ pointerEvents: "none",
212
+ style: [styles.freeBorder, isInteracting && styles.freeBorderActive, locked && styles.freeBorderLocked]
213
+ }), !locked && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
214
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
215
+ ...resizeHandlers.topLeft.panHandlers,
216
+ style: [styles.cornerWrapper, {
217
+ top: -20,
218
+ left: -20
219
+ }],
220
+ hitSlop: {
221
+ top: 10,
222
+ left: 10,
223
+ right: 10,
224
+ bottom: 10
225
+ },
226
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
227
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
228
+ })
229
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
230
+ ...resizeHandlers.topRight.panHandlers,
231
+ style: [styles.cornerWrapper, {
232
+ top: -20,
233
+ right: -20
234
+ }],
235
+ hitSlop: {
236
+ top: 10,
237
+ left: 10,
238
+ right: 10,
239
+ bottom: 10
240
+ },
241
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
242
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
243
+ })
244
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
245
+ ...resizeHandlers.bottomLeft.panHandlers,
246
+ style: [styles.cornerWrapper, {
247
+ bottom: -20,
248
+ left: -20
249
+ }],
250
+ hitSlop: {
251
+ top: 10,
252
+ left: 10,
253
+ right: 10,
254
+ bottom: 10
255
+ },
256
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
257
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
258
+ })
259
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
260
+ ...resizeHandlers.bottomRight.panHandlers,
261
+ style: [styles.cornerWrapper, {
262
+ bottom: -20,
263
+ right: -20
264
+ }],
265
+ hitSlop: {
266
+ top: 10,
267
+ left: 10,
268
+ right: 10,
269
+ bottom: 10
270
+ },
271
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
272
+ style: [styles.cornerHandle, isInteracting && styles.cornerHandleActive]
273
+ })
274
+ })]
275
+ })]
276
+ });
277
+ }
278
+
279
+ // ─── Main Standalone Overlay ──────────────────────────────────────────────────
280
+
281
+ function ImageOverlayStandalone() {
282
+ const [state, setState] = (0, _react.useState)(() => _ImageOverlayController.ImageOverlayController.getState());
283
+ (0, _react.useEffect)(() => {
284
+ return _ImageOverlayController.ImageOverlayController.subscribe(setState);
285
+ }, []);
286
+ const {
287
+ mode,
288
+ enabled,
289
+ imageUri,
290
+ imageWidth,
291
+ imageHeight,
292
+ targetRect,
293
+ targetLabel,
294
+ showOutline,
295
+ opacity,
296
+ scale,
297
+ offsetX,
298
+ offsetY,
299
+ invertX,
300
+ invertY,
301
+ locked,
302
+ freeX,
303
+ freeY,
304
+ freeWidth,
305
+ freeHeight
306
+ } = state;
307
+ const aspectRatio = imageWidth && imageHeight && imageHeight > 0 ? imageWidth / imageHeight : 0;
308
+
309
+ // ─── Free Placement Mode ───
310
+ if (mode === "free" && enabled && imageUri) {
311
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
312
+ style: styles.overlay,
313
+ pointerEvents: "box-none",
314
+ nativeID: "image-overlay-standalone",
315
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(FreePlacement, {
316
+ imageUri: imageUri,
317
+ x: freeX,
318
+ y: freeY,
319
+ width: freeWidth,
320
+ height: freeHeight,
321
+ opacity: opacity,
322
+ aspectRatio: aspectRatio,
323
+ invertX: invertX,
324
+ invertY: invertY,
325
+ locked: locked
326
+ })
327
+ });
328
+ }
329
+
330
+ // ─── Component Match Mode ───
331
+ if (!targetRect) return null;
332
+ const showImage = enabled && imageUri;
333
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
334
+ style: styles.overlay,
335
+ pointerEvents: "box-none",
336
+ nativeID: "image-overlay-standalone",
337
+ children: [showOutline && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
338
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
339
+ pointerEvents: "none",
340
+ nativeID: "__image_overlay_highlight__",
341
+ style: [styles.highlight, {
342
+ left: targetRect.x - 3,
343
+ top: targetRect.y - 3,
344
+ width: targetRect.width + 6,
345
+ height: targetRect.height + 6
346
+ }]
347
+ }), targetLabel && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
348
+ pointerEvents: "none",
349
+ style: [styles.label, {
350
+ left: targetRect.x,
351
+ top: targetRect.y - 20
352
+ }],
353
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
354
+ style: styles.labelText,
355
+ children: targetLabel
356
+ })
357
+ })]
358
+ }), showImage && (() => {
359
+ const w = (imageWidth ?? targetRect.width) * scale;
360
+ const h = (imageHeight ?? targetRect.height) * scale;
361
+ const centerX = targetRect.x + targetRect.width / 2;
362
+ const centerY = targetRect.y + targetRect.height / 2;
363
+ const imgStyle = {
364
+ position: "absolute",
365
+ left: centerX - w / 2 + offsetX,
366
+ top: centerY - h / 2 + offsetY,
367
+ width: w,
368
+ height: h,
369
+ opacity
370
+ };
371
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(OverlayImage, {
372
+ uri: imageUri,
373
+ style: imgStyle,
374
+ invertX: invertX,
375
+ invertY: invertY
376
+ });
377
+ })()]
378
+ });
379
+ }
380
+ const styles = _reactNative.StyleSheet.create({
381
+ overlay: {
382
+ position: "absolute",
383
+ top: 0,
384
+ left: 0,
385
+ right: 0,
386
+ bottom: 0,
387
+ zIndex: 999999,
388
+ elevation: 999999,
389
+ borderWidth: 0
390
+ },
391
+ highlight: {
392
+ position: "absolute",
393
+ borderWidth: 2,
394
+ borderStyle: "dashed",
395
+ borderColor: "#20C997",
396
+ backgroundColor: "transparent"
397
+ },
398
+ label: {
399
+ position: "absolute",
400
+ backgroundColor: "#20C997",
401
+ paddingHorizontal: 6,
402
+ paddingVertical: 2,
403
+ borderRadius: 3
404
+ },
405
+ labelText: {
406
+ color: "#fff",
407
+ fontSize: 10,
408
+ fontWeight: "700",
409
+ fontFamily: "monospace"
410
+ },
411
+ freeBorder: {
412
+ position: "absolute",
413
+ top: -6,
414
+ left: -6,
415
+ right: -6,
416
+ bottom: -6,
417
+ borderWidth: 1,
418
+ borderColor: "transparent",
419
+ borderRadius: 4
420
+ },
421
+ freeBorderActive: {
422
+ borderColor: "#20C997",
423
+ borderStyle: "dashed"
424
+ },
425
+ freeBorderLocked: {
426
+ borderColor: "#f59e0b",
427
+ borderStyle: "solid"
428
+ },
429
+ cornerWrapper: {
430
+ position: "absolute",
431
+ width: 24,
432
+ height: 24,
433
+ zIndex: 1000,
434
+ alignItems: "center",
435
+ justifyContent: "center"
436
+ },
437
+ cornerHandle: {
438
+ width: 12,
439
+ height: 12,
440
+ borderRadius: 6,
441
+ backgroundColor: "#20C997",
442
+ borderWidth: 2,
443
+ borderColor: "#fff"
444
+ },
445
+ cornerHandleActive: {
446
+ backgroundColor: "#10b981",
447
+ shadowColor: "#10b981",
448
+ shadowOffset: {
449
+ width: 0,
450
+ height: 0
451
+ },
452
+ shadowOpacity: 0.8,
453
+ shadowRadius: 6
454
+ }
455
+ });
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.OverlayControls = OverlayControls;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * Controls for adjusting the overlay: opacity, scale, offset.
13
+ * Each control has a slider track plus -/+ stepper buttons.
14
+ */
15
+
16
+ function SliderStepper({
17
+ label,
18
+ value,
19
+ min,
20
+ max,
21
+ step,
22
+ displayValue,
23
+ onChange
24
+ }) {
25
+ const trackWidth = (0, _react.useRef)(0);
26
+ const trackX = (0, _react.useRef)(0);
27
+ const clamp = (0, _react.useCallback)(v => Math.max(min, Math.min(max, v)), [min, max]);
28
+ const fraction = max > min ? (value - min) / (max - min) : 0;
29
+
30
+ // Keep current values in refs so PanResponder always uses latest props
31
+ const onChangeRef = (0, _react.useRef)(onChange);
32
+ const clampRef = (0, _react.useRef)(clamp);
33
+ const minRef = (0, _react.useRef)(min);
34
+ const maxRef = (0, _react.useRef)(max);
35
+ onChangeRef.current = onChange;
36
+ clampRef.current = clamp;
37
+ minRef.current = min;
38
+ maxRef.current = max;
39
+ const handleSliderEvent = (0, _react.useCallback)(pageX => {
40
+ const x = pageX - trackX.current;
41
+ const frac = Math.max(0, Math.min(1, x / trackWidth.current));
42
+ const val = minRef.current + frac * (maxRef.current - minRef.current);
43
+ onChangeRef.current(clampRef.current(val));
44
+ }, []);
45
+ const panResponder = (0, _react.useRef)(_reactNative.PanResponder.create({
46
+ onStartShouldSetPanResponder: () => true,
47
+ onMoveShouldSetPanResponder: () => true,
48
+ onPanResponderTerminationRequest: () => false,
49
+ onPanResponderGrant: evt => {
50
+ handleSliderEvent(evt.nativeEvent.pageX);
51
+ },
52
+ onPanResponderMove: evt => {
53
+ handleSliderEvent(evt.nativeEvent.pageX);
54
+ }
55
+ })).current;
56
+ const onTrackLayout = (0, _react.useCallback)(e => {
57
+ trackWidth.current = e.nativeEvent.layout.width;
58
+ // We need pageX, measure the track view
59
+ }, []);
60
+ const trackRef = (0, _react.useRef)(null);
61
+ const measureTrack = (0, _react.useCallback)(() => {
62
+ trackRef.current?.measure?.((_x, _y, _w, _h, pageX) => {
63
+ trackX.current = pageX;
64
+ });
65
+ }, []);
66
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
67
+ style: styles.controlRow,
68
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
69
+ style: styles.label,
70
+ children: label
71
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
72
+ style: styles.controlRight,
73
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
74
+ style: styles.stepperButton,
75
+ onPress: () => onChange(clamp(value - step)),
76
+ activeOpacity: 0.6,
77
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
78
+ style: styles.stepperButtonText,
79
+ children: "-"
80
+ })
81
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
82
+ style: styles.sliderHitArea,
83
+ ...panResponder.panHandlers,
84
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
85
+ ref: trackRef,
86
+ style: styles.sliderTrack,
87
+ onLayout: e => {
88
+ onTrackLayout(e);
89
+ measureTrack();
90
+ },
91
+ pointerEvents: "none",
92
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
93
+ style: [styles.sliderFill, {
94
+ width: `${fraction * 100}%`
95
+ }]
96
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
97
+ style: [styles.sliderThumb, {
98
+ left: `${fraction * 100}%`
99
+ }]
100
+ })]
101
+ })
102
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
103
+ style: styles.stepperButton,
104
+ onPress: () => onChange(clamp(value + step)),
105
+ activeOpacity: 0.6,
106
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
107
+ style: styles.stepperButtonText,
108
+ children: "+"
109
+ })
110
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
111
+ style: styles.valueText,
112
+ children: displayValue
113
+ })]
114
+ })]
115
+ });
116
+ }
117
+ function OverlayControls({
118
+ opacity,
119
+ scale,
120
+ offsetX,
121
+ offsetY,
122
+ onOpacityChange,
123
+ onScaleChange,
124
+ onOffsetChange,
125
+ opacityOnly
126
+ }) {
127
+ if (opacityOnly) {
128
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SliderStepper, {
129
+ label: "Opacity",
130
+ value: opacity,
131
+ min: 0,
132
+ max: 1,
133
+ step: 0.05,
134
+ displayValue: `${Math.round(opacity * 100)}%`,
135
+ onChange: onOpacityChange
136
+ });
137
+ }
138
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
139
+ style: styles.container,
140
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SliderStepper, {
141
+ label: "Opacity",
142
+ value: opacity,
143
+ min: 0,
144
+ max: 1,
145
+ step: 0.05,
146
+ displayValue: `${Math.round(opacity * 100)}%`,
147
+ onChange: onOpacityChange
148
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SliderStepper, {
149
+ label: "Scale",
150
+ value: scale,
151
+ min: 0.05,
152
+ max: 3,
153
+ step: 0.05,
154
+ displayValue: `${Math.round(scale * 100)}%`,
155
+ onChange: onScaleChange
156
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SliderStepper, {
157
+ label: "Offset X",
158
+ value: offsetX,
159
+ min: -200,
160
+ max: 200,
161
+ step: 2,
162
+ displayValue: `${Math.round(offsetX)}px`,
163
+ onChange: v => onOffsetChange(Math.round(v), offsetY)
164
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SliderStepper, {
165
+ label: "Offset Y",
166
+ value: offsetY,
167
+ min: -200,
168
+ max: 200,
169
+ step: 2,
170
+ displayValue: `${Math.round(offsetY)}px`,
171
+ onChange: v => onOffsetChange(offsetX, Math.round(v))
172
+ })]
173
+ });
174
+ }
175
+ const THUMB_SIZE = 18;
176
+ const styles = _reactNative.StyleSheet.create({
177
+ container: {
178
+ gap: 8
179
+ },
180
+ controlRow: {
181
+ paddingHorizontal: 12,
182
+ gap: 4
183
+ },
184
+ label: {
185
+ fontSize: 11,
186
+ fontWeight: "600",
187
+ color: _sharedUi.macOSColors.text.muted,
188
+ textTransform: "uppercase",
189
+ letterSpacing: 0.5
190
+ },
191
+ controlRight: {
192
+ flexDirection: "row",
193
+ alignItems: "center",
194
+ gap: 6
195
+ },
196
+ stepperButton: {
197
+ width: 28,
198
+ height: 28,
199
+ borderRadius: 6,
200
+ backgroundColor: _sharedUi.macOSColors.background.hover,
201
+ borderWidth: 1,
202
+ borderColor: _sharedUi.macOSColors.border.default,
203
+ justifyContent: "center",
204
+ alignItems: "center"
205
+ },
206
+ stepperButtonText: {
207
+ fontSize: 16,
208
+ fontWeight: "700",
209
+ color: _sharedUi.macOSColors.text.primary,
210
+ lineHeight: 18
211
+ },
212
+ sliderHitArea: {
213
+ flex: 1,
214
+ paddingVertical: 20,
215
+ paddingHorizontal: 40,
216
+ marginVertical: -20,
217
+ marginHorizontal: -40
218
+ },
219
+ sliderTrack: {
220
+ height: 28,
221
+ justifyContent: "center",
222
+ borderRadius: 6,
223
+ backgroundColor: _sharedUi.macOSColors.background.hover,
224
+ borderWidth: 1,
225
+ borderColor: _sharedUi.macOSColors.border.default,
226
+ overflow: "hidden"
227
+ },
228
+ sliderFill: {
229
+ position: "absolute",
230
+ left: 0,
231
+ top: 0,
232
+ bottom: 0,
233
+ backgroundColor: _sharedUi.buoyColors.primary + "30",
234
+ borderRadius: 5
235
+ },
236
+ sliderThumb: {
237
+ position: "absolute",
238
+ width: 4,
239
+ top: 4,
240
+ bottom: 4,
241
+ marginLeft: -2,
242
+ borderRadius: 2,
243
+ backgroundColor: _sharedUi.buoyColors.primary
244
+ },
245
+ valueText: {
246
+ fontSize: 11,
247
+ fontWeight: "600",
248
+ color: _sharedUi.macOSColors.text.primary,
249
+ width: 45,
250
+ textAlign: "right",
251
+ fontFamily: "monospace"
252
+ }
253
+ });