rune-grab 0.1.0

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/dist/react.cjs ADDED
@@ -0,0 +1,1796 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/core/screenshot.ts
24
+ async function getStream() {
25
+ if (activeStream) {
26
+ const tracks = activeStream.getVideoTracks();
27
+ if (tracks.length > 0 && tracks[0].readyState === "live") {
28
+ return activeStream;
29
+ }
30
+ activeStream = null;
31
+ }
32
+ try {
33
+ activeStream = await navigator.mediaDevices.getDisplayMedia({
34
+ video: {
35
+ preferCurrentTab: true
36
+ },
37
+ audio: false
38
+ });
39
+ activeStream.getVideoTracks()[0].addEventListener("ended", () => {
40
+ activeStream = null;
41
+ });
42
+ return activeStream;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ async function captureFromStream(stream, rect, format, quality) {
48
+ const video = document.createElement("video");
49
+ video.srcObject = stream;
50
+ video.muted = true;
51
+ video.playsInline = true;
52
+ await new Promise((resolve, reject) => {
53
+ video.onloadedmetadata = () => {
54
+ video.play().then(resolve).catch(reject);
55
+ };
56
+ video.onerror = reject;
57
+ });
58
+ await new Promise((r) => requestAnimationFrame(r));
59
+ await new Promise((r) => requestAnimationFrame(r));
60
+ const track = stream.getVideoTracks()[0];
61
+ const settings = track.getSettings();
62
+ const videoW = settings.width || video.videoWidth;
63
+ const videoH = settings.height || video.videoHeight;
64
+ const scaleX = videoW / window.innerWidth;
65
+ const scaleY = videoH / window.innerHeight;
66
+ const sx = rect.left * scaleX;
67
+ const sy = rect.top * scaleY;
68
+ const sw = rect.width * scaleX;
69
+ const sh = rect.height * scaleY;
70
+ const dpr = window.devicePixelRatio || 1;
71
+ const canvas = document.createElement("canvas");
72
+ canvas.width = rect.width * dpr;
73
+ canvas.height = rect.height * dpr;
74
+ const ctx = canvas.getContext("2d");
75
+ ctx.drawImage(video, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height);
76
+ video.pause();
77
+ video.srcObject = null;
78
+ return canvas.toDataURL(format, quality);
79
+ }
80
+ async function captureRect(rect, format = "image/png", quality = 0.92) {
81
+ if (rect.width === 0 || rect.height === 0) return null;
82
+ try {
83
+ const domRect = new DOMRect(rect.x, rect.y, rect.width, rect.height);
84
+ if (customProvider) {
85
+ return await customProvider(domRect);
86
+ }
87
+ const stream = await getStream();
88
+ if (stream) {
89
+ return await captureFromStream(stream, domRect, format, quality);
90
+ }
91
+ console.warn("[rune-grab] No screenshot method available. Use setCaptureProvider() for Electron apps.");
92
+ return null;
93
+ } catch (err) {
94
+ console.warn("[rune-grab] Screenshot capture failed:", err);
95
+ return null;
96
+ }
97
+ }
98
+ function releaseStream() {
99
+ if (activeStream) {
100
+ activeStream.getTracks().forEach((t) => t.stop());
101
+ activeStream = null;
102
+ }
103
+ }
104
+ var customProvider, activeStream;
105
+ var init_screenshot = __esm({
106
+ "src/core/screenshot.ts"() {
107
+ "use strict";
108
+ customProvider = null;
109
+ activeStream = null;
110
+ }
111
+ });
112
+
113
+ // src/targets/clipboard.ts
114
+ async function copyToClipboard(result) {
115
+ try {
116
+ const items = {};
117
+ items["text/plain"] = new Blob([result.text], { type: "text/plain" });
118
+ if (result.image) {
119
+ const response = await fetch(result.image);
120
+ const imageBlob = await response.blob();
121
+ items["image/png"] = imageBlob;
122
+ }
123
+ await navigator.clipboard.write([
124
+ new ClipboardItem(items)
125
+ ]);
126
+ return true;
127
+ } catch {
128
+ try {
129
+ const textarea = document.createElement("textarea");
130
+ textarea.value = result.text;
131
+ textarea.style.position = "fixed";
132
+ textarea.style.opacity = "0";
133
+ document.body.appendChild(textarea);
134
+ textarea.select();
135
+ document.execCommand("copy");
136
+ document.body.removeChild(textarea);
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+ }
143
+ var init_clipboard = __esm({
144
+ "src/targets/clipboard.ts"() {
145
+ "use strict";
146
+ }
147
+ });
148
+
149
+ // src/targets/app.ts
150
+ async function isHelperRunning() {
151
+ try {
152
+ const ac = new AbortController();
153
+ const timer = setTimeout(() => ac.abort(), 500);
154
+ const res = await fetch(`http://127.0.0.1:${HELPER_PORT}/health`, {
155
+ signal: ac.signal
156
+ });
157
+ clearTimeout(timer);
158
+ return res.ok;
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ async function sendToHelper(target, result) {
164
+ try {
165
+ const res = await fetch(`http://127.0.0.1:${HELPER_PORT}/paste`, {
166
+ method: "POST",
167
+ headers: { "Content-Type": "application/json" },
168
+ body: JSON.stringify({
169
+ target,
170
+ text: result.text,
171
+ image: result.image ?? null,
172
+ label: result.label
173
+ })
174
+ });
175
+ return res.ok;
176
+ } catch {
177
+ return false;
178
+ }
179
+ }
180
+ async function sendToApp(target, result) {
181
+ await copyToClipboard(result);
182
+ if (target === "clipboard") {
183
+ return { success: true, method: "clipboard" };
184
+ }
185
+ const helperUp = await isHelperRunning();
186
+ if (helperUp) {
187
+ const ok = await sendToHelper(target, result);
188
+ if (ok) return { success: true, method: "auto" };
189
+ }
190
+ return { success: true, method: "clipboard" };
191
+ }
192
+ var HELPER_PORT;
193
+ var init_app = __esm({
194
+ "src/targets/app.ts"() {
195
+ "use strict";
196
+ init_clipboard();
197
+ HELPER_PORT = 19274;
198
+ }
199
+ });
200
+
201
+ // src/core/state.ts
202
+ var isMac, DEFAULTS, s;
203
+ var init_state = __esm({
204
+ "src/core/state.ts"() {
205
+ "use strict";
206
+ isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
207
+ DEFAULTS = {
208
+ shortcut: isMac ? "Meta+Shift+G" : "Ctrl+Shift+G",
209
+ target: "clipboard",
210
+ accentColor: "#f5f5f5",
211
+ maxStackDepth: 3,
212
+ onGrab: () => {
213
+ },
214
+ onToggle: () => {
215
+ },
216
+ showTargetPicker: true,
217
+ skipComponents: []
218
+ };
219
+ s = {
220
+ initialized: false,
221
+ active: false,
222
+ locked: false,
223
+ promptMode: false,
224
+ menuExpanded: false,
225
+ targetsExpanded: false,
226
+ grabType: "screenshot",
227
+ autoPasteEnabled: false,
228
+ helperAvailable: false,
229
+ helperChecking: false,
230
+ dockedEdge: null,
231
+ selectedElement: null,
232
+ mmRAF: 0,
233
+ config: { ...DEFAULTS },
234
+ customSkipSet: void 0,
235
+ miniMenu: null,
236
+ overlay: null,
237
+ label: null,
238
+ toolbar: null,
239
+ cursorStyle: null,
240
+ selectionBox: null,
241
+ ssDrawing: false,
242
+ ssStartX: 0,
243
+ ssStartY: 0,
244
+ lastMouseX: 0,
245
+ lastMouseY: 0,
246
+ dragging: false,
247
+ dragPending: false,
248
+ dragStartX: 0,
249
+ dragStartY: 0,
250
+ hasDragged: false,
251
+ dragW: 0,
252
+ dragH: 0,
253
+ dragBaseX: 0,
254
+ dragBaseY: 0,
255
+ dragPointerId: -1,
256
+ dragLastX: 0,
257
+ dragLastY: 0
258
+ };
259
+ }
260
+ });
261
+
262
+ // src/core/constants.ts
263
+ function isLibraryFile(filePath) {
264
+ return filePath.includes("node_modules");
265
+ }
266
+ function isSourceFile(filePath) {
267
+ if (!filePath) return false;
268
+ if (isLibraryFile(filePath)) return false;
269
+ if (filePath.startsWith("webpack-internal:///node_modules/")) return false;
270
+ return true;
271
+ }
272
+ var SKIP_PREFIXES, STYLE_PROPS, STYLE_DEFAULTS, KEEP_ATTRS, Z_OVERLAY, Z_TOOLBAR, OWN_IDS, DRAG_THRESHOLD_PX, HELPER_PORT2, HEALTH_CHECK_TIMEOUT_MS, FLASH_VISIBLE_MS, FLASH_REMOVE_MS, LS_KEY;
273
+ var init_constants = __esm({
274
+ "src/core/constants.ts"() {
275
+ "use strict";
276
+ SKIP_PREFIXES = ["_", "$", "Styled(", "motion(", "chakra(", "withRouter(", "connect(", "styled.", "styled(", "tw.", "rc-", "__"];
277
+ STYLE_PROPS = [
278
+ "display",
279
+ "position",
280
+ "overflow",
281
+ "width",
282
+ "height",
283
+ "minWidth",
284
+ "minHeight",
285
+ "maxWidth",
286
+ "maxHeight",
287
+ "flexDirection",
288
+ "flexWrap",
289
+ "justifyContent",
290
+ "alignItems",
291
+ "alignSelf",
292
+ "gap",
293
+ "gridTemplateColumns",
294
+ "gridTemplateRows",
295
+ "padding",
296
+ "margin",
297
+ "fontSize",
298
+ "fontWeight",
299
+ "fontFamily",
300
+ "lineHeight",
301
+ "letterSpacing",
302
+ "textAlign",
303
+ "textTransform",
304
+ "color",
305
+ "backgroundColor",
306
+ "backgroundImage",
307
+ "borderRadius",
308
+ "boxShadow",
309
+ "borderWidth",
310
+ "borderStyle",
311
+ "borderColor",
312
+ "opacity",
313
+ "transform",
314
+ "filter",
315
+ "backdropFilter",
316
+ "objectFit"
317
+ ];
318
+ STYLE_DEFAULTS = /* @__PURE__ */ new Set([
319
+ "block",
320
+ "inline",
321
+ "none",
322
+ "normal",
323
+ "auto",
324
+ "0px",
325
+ "transparent",
326
+ "rgba(0, 0, 0, 0)",
327
+ "start",
328
+ "visible",
329
+ "static",
330
+ "row",
331
+ "stretch",
332
+ "0px 0px 0px 0px",
333
+ "baseline"
334
+ ]);
335
+ KEEP_ATTRS = ["type", "href", "src", "alt", "role", "aria-label", "placeholder", "name", "id", "data-testid"];
336
+ Z_OVERLAY = 2147483640;
337
+ Z_TOOLBAR = 2147483641;
338
+ OWN_IDS = ["__rune-grab-overlay__", "__rune-grab-label__", "__rune-grab-toolbar__", "__rune-grab-menu__", "__rune-grab-selection__"];
339
+ DRAG_THRESHOLD_PX = 4;
340
+ HELPER_PORT2 = 19274;
341
+ HEALTH_CHECK_TIMEOUT_MS = 800;
342
+ FLASH_VISIBLE_MS = 1e3;
343
+ FLASH_REMOVE_MS = 1200;
344
+ LS_KEY = "__rune-grab-autopaste__";
345
+ }
346
+ });
347
+
348
+ // src/ui/styles.ts
349
+ var FONT, MONO, M_BG, M_FG, M_FG_DIM, M_BORDER, M_HOVER, M_ACTIVE_BG, M_ACTIVE_FG, ICON_SS, ICON_REF, ICON_X, ICON_CHEVRON_RIGHT, ICON_CHEVRON_LEFT, ICON_SEND, ICON_GRIP, TARGET_LABELS, TARGETS_CLIPBOARD, TARGETS_APPS;
350
+ var init_styles = __esm({
351
+ "src/ui/styles.ts"() {
352
+ "use strict";
353
+ FONT = `-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif`;
354
+ MONO = `ui-monospace,SFMono-Regular,Menlo,monospace`;
355
+ M_BG = "#f5f5f5";
356
+ M_FG = "#171717";
357
+ M_FG_DIM = "#737373";
358
+ M_BORDER = "#e5e5e5";
359
+ M_HOVER = "#ebebeb";
360
+ M_ACTIVE_BG = "#171717";
361
+ M_ACTIVE_FG = "#f5f5f5";
362
+ ICON_SS = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`;
363
+ ICON_REF = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`;
364
+ ICON_X = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
365
+ ICON_CHEVRON_RIGHT = `<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`;
366
+ ICON_CHEVRON_LEFT = `<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`;
367
+ ICON_SEND = '<svg width="10" height="10" viewBox="0 0 448 512" fill="currentColor"><path d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264.5 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L70.2 289.1c-9.3 9.8-24.8 10-34.3.4z"/></svg>';
368
+ ICON_GRIP = `<svg width="6" height="10" viewBox="0 0 6 10" fill="currentColor"><circle cx="1" cy="1" r="1"/><circle cx="5" cy="1" r="1"/><circle cx="1" cy="5" r="1"/><circle cx="5" cy="5" r="1"/><circle cx="1" cy="9" r="1"/><circle cx="5" cy="9" r="1"/></svg>`;
369
+ TARGET_LABELS = {
370
+ clipboard: "Clipboard",
371
+ claude: "Claude",
372
+ cursor: "Cursor",
373
+ codex: "Codex",
374
+ "claude-code": "CLI"
375
+ };
376
+ TARGETS_CLIPBOARD = ["clipboard"];
377
+ TARGETS_APPS = ["claude", "cursor", "codex", "claude-code"];
378
+ }
379
+ });
380
+
381
+ // src/ui/menu.ts
382
+ function setMenuCallbacks(activate2, deactivate2) {
383
+ _activate = activate2;
384
+ _deactivate = deactivate2;
385
+ }
386
+ function loadAutoPastePref() {
387
+ try {
388
+ return localStorage.getItem(LS_KEY) === "1";
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
393
+ function saveAutoPastePref(v) {
394
+ try {
395
+ localStorage.setItem(LS_KEY, v ? "1" : "0");
396
+ } catch {
397
+ }
398
+ }
399
+ async function checkHelper() {
400
+ try {
401
+ const ac = new AbortController();
402
+ const timer = setTimeout(() => ac.abort(), HEALTH_CHECK_TIMEOUT_MS);
403
+ const res = await fetch(`http://127.0.0.1:${HELPER_PORT2}/health`, {
404
+ signal: ac.signal
405
+ });
406
+ clearTimeout(timer);
407
+ return res.ok;
408
+ } catch {
409
+ return false;
410
+ }
411
+ }
412
+ async function onAutoPasteToggle(enabled) {
413
+ s.autoPasteEnabled = enabled;
414
+ saveAutoPastePref(enabled);
415
+ if (enabled) {
416
+ s.helperChecking = true;
417
+ renderMiniMenu();
418
+ s.helperAvailable = await checkHelper();
419
+ s.helperChecking = false;
420
+ if (!s.helperAvailable) {
421
+ s.config.target = "clipboard";
422
+ }
423
+ } else {
424
+ s.helperAvailable = false;
425
+ s.config.target = "clipboard";
426
+ }
427
+ renderMiniMenu();
428
+ }
429
+ function buildToggle(on, onChange) {
430
+ const wrap = document.createElement("div");
431
+ wrap.style.cssText = `display:inline-flex;align-items:center;flex-shrink:0;`;
432
+ const track = document.createElement("div");
433
+ track.style.cssText = `
434
+ width:28px;height:16px;border-radius:8px;cursor:pointer;
435
+ transition:background 0.15s;position:relative;
436
+ background:${on ? M_ACTIVE_BG : "#d4d4d4"};
437
+ `;
438
+ const thumb = document.createElement("div");
439
+ thumb.style.cssText = `
440
+ width:12px;height:12px;border-radius:50%;
441
+ background:${on ? M_ACTIVE_FG : "#fff"};
442
+ position:absolute;top:2px;transition:left 0.15s;
443
+ left:${on ? "14px" : "2px"};
444
+ box-shadow:0 1px 2px rgba(0,0,0,0.15);
445
+ `;
446
+ track.appendChild(thumb);
447
+ track.addEventListener("click", (e) => {
448
+ e.stopPropagation();
449
+ onChange(!on);
450
+ });
451
+ wrap.appendChild(track);
452
+ return wrap;
453
+ }
454
+ function grabIconBtn(icon, isActive2, onClick) {
455
+ const btn = document.createElement("button");
456
+ btn.innerHTML = icon;
457
+ btn.style.cssText = `
458
+ width:28px;height:28px;border:none;border-radius:6px;cursor:pointer;
459
+ display:flex;align-items:center;justify-content:center;padding:0;
460
+ transition:all 0.15s;
461
+ background:${isActive2 ? M_ACTIVE_BG : "transparent"};
462
+ color:${isActive2 ? M_ACTIVE_FG : M_FG_DIM};
463
+ `;
464
+ btn.addEventListener("mouseenter", () => {
465
+ if (!isActive2) {
466
+ btn.style.background = M_HOVER;
467
+ btn.style.color = M_FG;
468
+ }
469
+ });
470
+ btn.addEventListener("mouseleave", () => {
471
+ if (!isActive2) {
472
+ btn.style.background = "transparent";
473
+ btn.style.color = M_FG_DIM;
474
+ }
475
+ });
476
+ btn.addEventListener("click", (e) => {
477
+ e.stopPropagation();
478
+ e.preventDefault();
479
+ onClick();
480
+ });
481
+ return btn;
482
+ }
483
+ function sep() {
484
+ const d = document.createElement("div");
485
+ d.style.cssText = `width:1px;height:16px;background:${M_BORDER};margin:0 2px;flex-shrink:0;`;
486
+ return d;
487
+ }
488
+ function onGripPointerDown(e) {
489
+ if (e.button !== 0 || !s.menuExpanded || !s.miniMenu) return;
490
+ e.preventDefault();
491
+ const grip = e.currentTarget;
492
+ grip.setPointerCapture(e.pointerId);
493
+ s.dragPointerId = e.pointerId;
494
+ s.dragStartX = e.clientX;
495
+ s.dragStartY = e.clientY;
496
+ s.dragPending = true;
497
+ s.dragging = false;
498
+ const rect = s.miniMenu.getBoundingClientRect();
499
+ s.dragW = rect.width;
500
+ s.dragH = rect.height;
501
+ if (!s.hasDragged) {
502
+ s.miniMenu.style.top = rect.top + "px";
503
+ s.miniMenu.style.left = rect.left + "px";
504
+ s.miniMenu.style.bottom = "auto";
505
+ s.miniMenu.style.right = "auto";
506
+ s.hasDragged = true;
507
+ }
508
+ s.dragBaseX = parseFloat(s.miniMenu.style.left) || rect.left;
509
+ s.dragBaseY = parseFloat(s.miniMenu.style.top) || rect.top;
510
+ s.dragLastX = s.dragBaseX;
511
+ s.dragLastY = s.dragBaseY;
512
+ }
513
+ function onGripPointerMove(e) {
514
+ if (e.pointerId !== s.dragPointerId || !s.miniMenu) return;
515
+ if (!s.dragPending && !s.dragging) return;
516
+ const dx = e.clientX - s.dragStartX;
517
+ const dy = e.clientY - s.dragStartY;
518
+ if (s.dragPending && !s.dragging) {
519
+ if (Math.abs(dx) < DRAG_THRESHOLD_PX && Math.abs(dy) < DRAG_THRESHOLD_PX) return;
520
+ s.dragging = true;
521
+ s.dragPending = false;
522
+ s.miniMenu.style.transition = "none";
523
+ s.miniMenu.style.willChange = "left, top";
524
+ document.body.style.cursor = "grabbing";
525
+ document.body.style.userSelect = "none";
526
+ }
527
+ if (s.dragging) {
528
+ const vw = document.documentElement.clientWidth;
529
+ const vh = document.documentElement.clientHeight;
530
+ s.dragLastX = Math.max(0, Math.min(s.dragBaseX + dx, vw - s.dragW));
531
+ s.dragLastY = Math.max(0, Math.min(s.dragBaseY + dy, vh - s.dragH));
532
+ s.miniMenu.style.left = s.dragLastX + "px";
533
+ s.miniMenu.style.top = s.dragLastY + "px";
534
+ }
535
+ }
536
+ function onGripPointerUp(e) {
537
+ if (e.pointerId !== s.dragPointerId || !s.miniMenu) return;
538
+ s.dragPointerId = -1;
539
+ document.body.style.cursor = "";
540
+ document.body.style.userSelect = "";
541
+ if (s.dragging) {
542
+ s.dragging = false;
543
+ s.miniMenu.style.willChange = "";
544
+ s.miniMenu.style.transition = "left 0.15s ease, top 0.15s ease";
545
+ s.miniMenu.style.pointerEvents = "none";
546
+ requestAnimationFrame(() => {
547
+ if (s.miniMenu) s.miniMenu.style.pointerEvents = "";
548
+ });
549
+ }
550
+ s.dragPending = false;
551
+ }
552
+ function snapToNearestEdge() {
553
+ if (!s.miniMenu) return;
554
+ const rect = s.miniMenu.getBoundingClientRect();
555
+ const cx = rect.left + rect.width / 2;
556
+ const cy = rect.top + rect.height / 2;
557
+ const vw = document.documentElement.clientWidth;
558
+ const vh = document.documentElement.clientHeight;
559
+ const distTop = cy;
560
+ const distBottom = vh - cy;
561
+ const distLeft = cx;
562
+ const distRight = vw - cx;
563
+ const minDist = Math.min(distTop, distBottom, distLeft, distRight);
564
+ if (minDist === distTop) s.dockedEdge = "top";
565
+ else if (minDist === distBottom) s.dockedEdge = "bottom";
566
+ else if (minDist === distLeft) s.dockedEdge = "left";
567
+ else s.dockedEdge = "right";
568
+ s.miniMenu.style.transition = "none";
569
+ renderMiniMenu();
570
+ requestAnimationFrame(() => {
571
+ if (!s.miniMenu) return;
572
+ const tabRect = s.miniMenu.getBoundingClientRect();
573
+ const tw = tabRect.width;
574
+ const th = tabRect.height;
575
+ let targetX = parseFloat(s.miniMenu.style.left) || 0;
576
+ let targetY = parseFloat(s.miniMenu.style.top) || 0;
577
+ if (s.dockedEdge === "top") targetY = 0;
578
+ else if (s.dockedEdge === "bottom") targetY = vh - th;
579
+ else if (s.dockedEdge === "left") targetX = 0;
580
+ else if (s.dockedEdge === "right") targetX = vw - tw;
581
+ targetX = Math.max(0, Math.min(targetX, vw - tw));
582
+ targetY = Math.max(0, Math.min(targetY, vh - th));
583
+ s.miniMenu.style.transition = "left 0.25s ease, top 0.25s ease";
584
+ s.miniMenu.style.left = targetX + "px";
585
+ s.miniMenu.style.top = targetY + "px";
586
+ setTimeout(() => {
587
+ if (s.miniMenu) s.miniMenu.style.transition = "left 0.15s ease, top 0.15s ease";
588
+ }, 270);
589
+ });
590
+ }
591
+ function clampToViewport() {
592
+ if (!s.miniMenu || !s.hasDragged) return;
593
+ const rect = s.miniMenu.getBoundingClientRect();
594
+ let x = rect.left;
595
+ let y = rect.top;
596
+ let clamped = false;
597
+ const vw = document.documentElement.clientWidth;
598
+ const vh = document.documentElement.clientHeight;
599
+ if (x + rect.width > vw) {
600
+ x = vw - rect.width;
601
+ clamped = true;
602
+ }
603
+ if (x < 0) {
604
+ x = 0;
605
+ clamped = true;
606
+ }
607
+ if (y + rect.height > vh) {
608
+ y = vh - rect.height;
609
+ clamped = true;
610
+ }
611
+ if (y < 0) {
612
+ y = 0;
613
+ clamped = true;
614
+ }
615
+ if (clamped) {
616
+ s.miniMenu.style.transition = "left 0.2s ease, top 0.2s ease";
617
+ s.miniMenu.style.left = x + "px";
618
+ s.miniMenu.style.top = y + "px";
619
+ setTimeout(() => {
620
+ if (s.miniMenu) s.miniMenu.style.transition = "left 0.15s ease, top 0.15s ease";
621
+ }, 220);
622
+ }
623
+ }
624
+ function createMiniMenu() {
625
+ s.miniMenu = document.createElement("div");
626
+ s.miniMenu.id = "__rune-grab-menu__";
627
+ s.dockedEdge = "bottom";
628
+ s.menuExpanded = false;
629
+ s.miniMenu.style.cssText = `
630
+ position:fixed;z-index:${Z_TOOLBAR + 2};
631
+ background:transparent;border:none;
632
+ border-radius:9px;font-family:${FONT};
633
+ box-shadow:none;
634
+ transition:left 0.15s ease, top 0.15s ease;
635
+ user-select:none;
636
+ bottom:0px;right:16px;
637
+ `;
638
+ document.addEventListener("mousemove", (e) => {
639
+ s.lastMouseX = e.clientX;
640
+ s.lastMouseY = e.clientY;
641
+ }, { passive: true });
642
+ document.body.appendChild(s.miniMenu);
643
+ renderMiniMenu();
644
+ }
645
+ function renderMiniMenu() {
646
+ if (!s.miniMenu) return;
647
+ const frag = document.createDocumentFragment();
648
+ if (!s.menuExpanded) {
649
+ s.miniMenu.style.padding = "0";
650
+ s.miniMenu.style.border = "none";
651
+ s.miniMenu.style.boxShadow = "none";
652
+ s.miniMenu.style.background = "transparent";
653
+ let chevronSvg;
654
+ let tabW;
655
+ let tabH;
656
+ let borderRadius;
657
+ const edge = s.dockedEdge || "bottom";
658
+ if (edge === "bottom") {
659
+ chevronSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="${M_FG}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>`;
660
+ tabW = 40;
661
+ tabH = 28;
662
+ borderRadius = "10px 10px 0 0";
663
+ } else if (edge === "top") {
664
+ chevronSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="${M_FG}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
665
+ tabW = 40;
666
+ tabH = 28;
667
+ borderRadius = "0 0 10px 10px";
668
+ } else if (edge === "left") {
669
+ chevronSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="${M_FG}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`;
670
+ tabW = 28;
671
+ tabH = 40;
672
+ borderRadius = "0 10px 10px 0";
673
+ } else {
674
+ chevronSvg = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="${M_FG}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`;
675
+ tabW = 28;
676
+ tabH = 40;
677
+ borderRadius = "10px 0 0 10px";
678
+ }
679
+ const tab = document.createElement("button");
680
+ tab.title = "Rune Grab";
681
+ tab.innerHTML = chevronSvg;
682
+ tab.style.cssText = `
683
+ width:${tabW}px;height:${tabH}px;border:none;cursor:pointer;
684
+ display:flex;align-items:center;justify-content:center;
685
+ background:${M_BG};color:${M_FG};
686
+ border-radius:${borderRadius};
687
+ box-shadow:0 2px 8px rgba(0,0,0,0.08),0 0 0 1px rgba(0,0,0,0.04);
688
+ transition:background 0.12s;padding:0;
689
+ `;
690
+ tab.addEventListener("mouseenter", () => {
691
+ tab.style.background = M_HOVER;
692
+ });
693
+ tab.addEventListener("mouseleave", () => {
694
+ tab.style.background = M_BG;
695
+ });
696
+ tab.addEventListener("click", (e) => {
697
+ e.stopPropagation();
698
+ if (!s.miniMenu) return;
699
+ const rect = s.miniMenu.getBoundingClientRect();
700
+ s.miniMenu.style.bottom = "auto";
701
+ s.miniMenu.style.right = "auto";
702
+ s.miniMenu.style.top = rect.top + "px";
703
+ s.miniMenu.style.left = rect.left + "px";
704
+ s.hasDragged = true;
705
+ s.menuExpanded = true;
706
+ s.dockedEdge = null;
707
+ s.miniMenu.style.background = M_BG;
708
+ s.miniMenu.style.border = `1px solid ${M_BORDER}`;
709
+ s.miniMenu.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08),0 0 0 1px rgba(0,0,0,0.04)";
710
+ s.miniMenu.style.borderRadius = "9px";
711
+ s.miniMenu.style.transition = "none";
712
+ renderMiniMenu();
713
+ s.miniMenu.offsetWidth;
714
+ clampToViewport();
715
+ });
716
+ frag.appendChild(tab);
717
+ s.miniMenu.textContent = "";
718
+ s.miniMenu.appendChild(frag);
719
+ return;
720
+ }
721
+ s.miniMenu.style.padding = "3px";
722
+ const bar = document.createElement("div");
723
+ bar.style.cssText = "display:flex;align-items:center;gap:2px;";
724
+ const grip = document.createElement("div");
725
+ grip.style.cssText = `
726
+ width:12px;height:28px;cursor:grab;display:flex;align-items:center;justify-content:center;
727
+ flex-shrink:0;border-radius:4px;color:${M_FG_DIM};transition:color 0.12s;
728
+ `;
729
+ grip.innerHTML = ICON_GRIP;
730
+ grip.addEventListener("mouseenter", () => {
731
+ grip.style.color = M_FG;
732
+ });
733
+ grip.addEventListener("mouseleave", () => {
734
+ if (!s.dragging) grip.style.color = M_FG_DIM;
735
+ });
736
+ grip.addEventListener("pointerdown", onGripPointerDown);
737
+ grip.addEventListener("pointermove", onGripPointerMove);
738
+ grip.addEventListener("pointerup", onGripPointerUp);
739
+ grip.style.touchAction = "none";
740
+ bar.appendChild(grip);
741
+ const ssActive = s.active && s.grabType === "screenshot";
742
+ bar.appendChild(grabIconBtn(ICON_SS, ssActive, () => {
743
+ if (ssActive) {
744
+ _deactivate();
745
+ } else {
746
+ if (s.active) _deactivate();
747
+ s.grabType = "screenshot";
748
+ _activate();
749
+ }
750
+ renderMiniMenu();
751
+ }));
752
+ const refActive = s.active && s.grabType === "reference";
753
+ bar.appendChild(grabIconBtn(ICON_REF, refActive, () => {
754
+ if (refActive) {
755
+ _deactivate();
756
+ } else {
757
+ if (s.active) _deactivate();
758
+ s.grabType = "reference";
759
+ _activate();
760
+ }
761
+ renderMiniMenu();
762
+ }));
763
+ bar.appendChild(sep());
764
+ const visibleTargets = s.autoPasteEnabled && s.helperAvailable ? [...TARGETS_CLIPBOARD, ...TARGETS_APPS] : TARGETS_CLIPBOARD;
765
+ const toggleBtn = document.createElement("button");
766
+ toggleBtn.innerHTML = s.targetsExpanded ? ICON_CHEVRON_RIGHT : ICON_CHEVRON_LEFT;
767
+ toggleBtn.title = s.targetsExpanded ? "Collapse targets" : "Send to\u2026";
768
+ toggleBtn.style.cssText = `
769
+ width:24px;height:28px;border:none;background:transparent;cursor:pointer;
770
+ display:flex;align-items:center;justify-content:center;border-radius:6px;
771
+ color:${M_FG_DIM};transition:all 0.12s;padding:0;flex-shrink:0;
772
+ `;
773
+ toggleBtn.addEventListener("mouseenter", () => {
774
+ toggleBtn.style.background = M_HOVER;
775
+ toggleBtn.style.color = M_FG;
776
+ });
777
+ toggleBtn.addEventListener("mouseleave", () => {
778
+ toggleBtn.style.background = "transparent";
779
+ toggleBtn.style.color = M_FG_DIM;
780
+ });
781
+ toggleBtn.addEventListener("click", (e) => {
782
+ e.stopPropagation();
783
+ s.targetsExpanded = !s.targetsExpanded;
784
+ if (targetsWrap) {
785
+ if (s.targetsExpanded) {
786
+ targetsWrap.style.width = "auto";
787
+ const naturalW = targetsWrap.scrollWidth;
788
+ targetsWrap.style.width = "0px";
789
+ targetsWrap.offsetWidth;
790
+ targetsWrap.style.width = naturalW + "px";
791
+ targetsWrap.style.opacity = "1";
792
+ setTimeout(() => {
793
+ if (targetsWrap) targetsWrap.style.width = "auto";
794
+ clampToViewport();
795
+ }, 200);
796
+ } else {
797
+ const currentW = targetsWrap.scrollWidth;
798
+ targetsWrap.style.width = currentW + "px";
799
+ targetsWrap.offsetWidth;
800
+ targetsWrap.style.width = "0px";
801
+ targetsWrap.style.opacity = "0";
802
+ }
803
+ }
804
+ toggleBtn.innerHTML = s.targetsExpanded ? ICON_CHEVRON_RIGHT : ICON_CHEVRON_LEFT;
805
+ toggleBtn.title = s.targetsExpanded ? "Collapse targets" : "Send to\u2026";
806
+ });
807
+ bar.appendChild(toggleBtn);
808
+ const targetsWrap = document.createElement("div");
809
+ targetsWrap.style.cssText = `
810
+ display:flex;align-items:center;gap:2px;
811
+ overflow:hidden;white-space:nowrap;
812
+ transition:width 0.2s ease, opacity 0.15s ease;
813
+ ${s.targetsExpanded ? "opacity:1;" : "width:0px;opacity:0;"}
814
+ `;
815
+ for (const t of visibleTargets) {
816
+ const isCurrent = t === s.config.target;
817
+ const btn = document.createElement("button");
818
+ btn.textContent = TARGET_LABELS[t];
819
+ btn.style.cssText = `
820
+ height:28px;padding:0 8px;border:none;border-radius:6px;cursor:pointer;
821
+ font-size:11px;font-weight:${isCurrent ? "600" : "500"};font-family:${FONT};
822
+ transition:all 0.12s;flex-shrink:0;
823
+ background:${isCurrent ? M_ACTIVE_BG : "transparent"};
824
+ color:${isCurrent ? M_ACTIVE_FG : M_FG_DIM};
825
+ `;
826
+ btn.addEventListener("mouseenter", () => {
827
+ if (!isCurrent) btn.style.background = M_HOVER;
828
+ });
829
+ btn.addEventListener("mouseleave", () => {
830
+ if (!isCurrent) btn.style.background = "transparent";
831
+ });
832
+ btn.addEventListener("click", (e) => {
833
+ e.stopPropagation();
834
+ s.config.target = t;
835
+ renderMiniMenu();
836
+ });
837
+ targetsWrap.appendChild(btn);
838
+ }
839
+ bar.appendChild(targetsWrap);
840
+ if (s.targetsExpanded) bar.appendChild(sep());
841
+ bar.appendChild(buildToggle(s.autoPasteEnabled, onAutoPasteToggle));
842
+ if (s.autoPasteEnabled && !s.helperAvailable && !s.helperChecking) {
843
+ bar.appendChild(sep());
844
+ const hint = document.createElement("div");
845
+ hint.style.cssText = `
846
+ font-size:8px;font-family:${MONO};color:${M_FG_DIM};
847
+ padding:0 6px;white-space:nowrap;flex-shrink:0;line-height:1.3;
848
+ `;
849
+ hint.innerHTML = `To enable auto pasting: add <br><span style="color:${M_FG};font-weight:600;">rune-grab serve</span> to your dev script.`;
850
+ bar.appendChild(hint);
851
+ }
852
+ if (s.helperChecking) {
853
+ bar.appendChild(sep());
854
+ const checking = document.createElement("span");
855
+ checking.textContent = "Connecting\u2026";
856
+ checking.style.cssText = `font-size:10px;font-family:${FONT};color:${M_FG_DIM};padding:0 4px;flex-shrink:0;`;
857
+ bar.appendChild(checking);
858
+ }
859
+ const closeBtn = document.createElement("button");
860
+ closeBtn.innerHTML = ICON_X;
861
+ closeBtn.style.cssText = `
862
+ width:28px;height:28px;border:none;background:transparent;cursor:pointer;
863
+ display:flex;align-items:center;justify-content:center;border-radius:6px;
864
+ color:${M_FG_DIM};transition:all 0.12s;padding:0;
865
+ `;
866
+ closeBtn.addEventListener("mouseenter", () => {
867
+ closeBtn.style.background = M_HOVER;
868
+ closeBtn.style.color = M_FG;
869
+ });
870
+ closeBtn.addEventListener("mouseleave", () => {
871
+ closeBtn.style.background = "transparent";
872
+ closeBtn.style.color = M_FG_DIM;
873
+ });
874
+ closeBtn.addEventListener("click", (e) => {
875
+ e.stopPropagation();
876
+ s.menuExpanded = false;
877
+ if (!s.hasDragged && s.miniMenu) {
878
+ const r = s.miniMenu.getBoundingClientRect();
879
+ s.miniMenu.style.top = r.top + "px";
880
+ s.miniMenu.style.left = r.left + "px";
881
+ s.miniMenu.style.bottom = "auto";
882
+ s.miniMenu.style.right = "auto";
883
+ s.hasDragged = true;
884
+ }
885
+ snapToNearestEdge();
886
+ });
887
+ bar.appendChild(closeBtn);
888
+ frag.appendChild(bar);
889
+ s.miniMenu.textContent = "";
890
+ s.miniMenu.appendChild(frag);
891
+ }
892
+ var _activate, _deactivate;
893
+ var init_menu = __esm({
894
+ "src/ui/menu.ts"() {
895
+ "use strict";
896
+ init_constants();
897
+ init_state();
898
+ init_styles();
899
+ }
900
+ });
901
+
902
+ // src/core/frameworks.ts
903
+ function isUsefulName(name, filePath, customSkip) {
904
+ if (!name || name.length <= 1) return false;
905
+ if (customSkip?.has(name)) return false;
906
+ if (name[0] !== name[0].toUpperCase()) return false;
907
+ for (const pfx of SKIP_PREFIXES) {
908
+ if (name.startsWith(pfx)) return false;
909
+ }
910
+ if (/^[A-Z][a-zA-Z]+\d+$/.test(name)) return false;
911
+ if (filePath && !isSourceFile(filePath)) return false;
912
+ return true;
913
+ }
914
+ function getReactFiber(el) {
915
+ const keys = Object.keys(el);
916
+ for (const key of keys) {
917
+ if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
918
+ return el[key];
919
+ }
920
+ }
921
+ return null;
922
+ }
923
+ function getReactComponentInfo(el, customSkip) {
924
+ const fiber = getReactFiber(el);
925
+ if (!fiber) return null;
926
+ let cur = fiber;
927
+ while (cur) {
928
+ if (cur.type && typeof cur.type === "function") {
929
+ const name = cur.type.displayName || cur.type.name;
930
+ const src = cur._debugSource;
931
+ const filePath = src?.fileName ?? null;
932
+ if (name && isUsefulName(name, filePath, customSkip)) {
933
+ return {
934
+ name,
935
+ filePath,
936
+ line: src?.lineNumber ?? null,
937
+ column: src?.columnNumber ?? null
938
+ };
939
+ }
940
+ }
941
+ if (cur.type?.$$typeof) {
942
+ const inner = cur.type.render || cur.type.type;
943
+ if (inner && typeof inner === "function") {
944
+ const name = inner.displayName || inner.name;
945
+ const src = cur._debugSource;
946
+ const filePath = src?.fileName ?? null;
947
+ if (name && isUsefulName(name, filePath, customSkip)) {
948
+ return {
949
+ name,
950
+ filePath,
951
+ line: src?.lineNumber ?? null,
952
+ column: src?.columnNumber ?? null
953
+ };
954
+ }
955
+ }
956
+ }
957
+ cur = cur.return;
958
+ }
959
+ return null;
960
+ }
961
+ function getReactComponentStack(el, maxDepth = 6, customSkip) {
962
+ const fiber = getReactFiber(el);
963
+ if (!fiber) return [];
964
+ const stack = [];
965
+ const seen = /* @__PURE__ */ new Set();
966
+ let cur = fiber;
967
+ while (cur && stack.length < maxDepth) {
968
+ let name = null;
969
+ let src = null;
970
+ if (cur.type && typeof cur.type === "function") {
971
+ name = cur.type.displayName || cur.type.name;
972
+ src = cur._debugSource;
973
+ } else if (cur.type?.$$typeof) {
974
+ const inner = cur.type.render || cur.type.type;
975
+ if (inner && typeof inner === "function") {
976
+ name = inner.displayName || inner.name;
977
+ src = cur._debugSource;
978
+ }
979
+ }
980
+ let filePath = src?.fileName ?? null;
981
+ if (name && isUsefulName(name, filePath, customSkip) && !seen.has(name)) {
982
+ seen.add(name);
983
+ const line = src?.lineNumber ?? null;
984
+ if (filePath) {
985
+ filePath = filePath.replace(/^(webpack-internal:\/\/\/|\/app\/)/, "").replace(/^\.\//, "");
986
+ }
987
+ stack.push({ name, filePath, line });
988
+ }
989
+ cur = cur.return;
990
+ }
991
+ return stack;
992
+ }
993
+ function getVueComponentInfo(el) {
994
+ const vueInstance = el.__vueParentComponent;
995
+ if (vueInstance) {
996
+ const name = vueInstance.type?.name || vueInstance.type?.__name;
997
+ if (name) {
998
+ const file = vueInstance.type?.__file ?? null;
999
+ return { name, filePath: file, line: null, column: null };
1000
+ }
1001
+ }
1002
+ const vue2 = el.__vue__;
1003
+ if (vue2) {
1004
+ const name = vue2.$options?.name || vue2.$options?._componentTag;
1005
+ if (name) {
1006
+ const file = vue2.$options?.__file ?? null;
1007
+ return { name, filePath: file, line: null, column: null };
1008
+ }
1009
+ }
1010
+ return null;
1011
+ }
1012
+ function getSvelteComponentInfo(el) {
1013
+ const meta = el.__svelte_meta;
1014
+ if (meta?.loc) {
1015
+ const file = meta.loc.file;
1016
+ const name = file ? file.split("/").pop()?.replace(/\.svelte$/, "") : null;
1017
+ if (name) {
1018
+ return { name, filePath: file, line: meta.loc.line ?? null, column: meta.loc.column ?? null };
1019
+ }
1020
+ }
1021
+ const keys = Object.keys(el);
1022
+ for (const key of keys) {
1023
+ if (key.startsWith("__svelte")) {
1024
+ return { name: "SvelteComponent", filePath: null, line: null, column: null };
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+ function detectComponent(el, customSkip) {
1030
+ return getReactComponentInfo(el, customSkip) || getVueComponentInfo(el) || getSvelteComponentInfo(el) || null;
1031
+ }
1032
+ function detectComponentStack(el, maxDepth = 6, customSkip) {
1033
+ const reactStack = getReactComponentStack(el, maxDepth, customSkip);
1034
+ if (reactStack.length > 0) return reactStack;
1035
+ const info = getVueComponentInfo(el) || getSvelteComponentInfo(el);
1036
+ if (info) {
1037
+ return [{ name: info.name, filePath: info.filePath, line: info.line }];
1038
+ }
1039
+ return [];
1040
+ }
1041
+ var init_frameworks = __esm({
1042
+ "src/core/frameworks.ts"() {
1043
+ "use strict";
1044
+ init_constants();
1045
+ }
1046
+ });
1047
+
1048
+ // src/core/extract.ts
1049
+ function isLocalhost() {
1050
+ const h = location.hostname;
1051
+ return h === "localhost" || h === "127.0.0.1" || h === "0.0.0.0" || h.endsWith(".local");
1052
+ }
1053
+ function getVisibleText(el) {
1054
+ let text = "";
1055
+ function walk(node) {
1056
+ if (node.nodeType === 3) {
1057
+ const t = (node.textContent || "").trim();
1058
+ if (t) text += (text ? " " : "") + t;
1059
+ } else if (node.nodeType === 1) {
1060
+ for (let i = 0; i < node.childNodes.length; i++) walk(node.childNodes[i]);
1061
+ }
1062
+ }
1063
+ walk(el);
1064
+ if (!text) {
1065
+ text = el.getAttribute("value") || el.getAttribute("alt") || el.getAttribute("aria-label") || el.placeholder || "";
1066
+ }
1067
+ if (text.length > 80) text = text.slice(0, 80) + "...";
1068
+ return text;
1069
+ }
1070
+ function getTagDisplay(el) {
1071
+ let tag = el.tagName.toLowerCase();
1072
+ if (el.id) tag += "#" + el.id;
1073
+ const cls = Array.from(el.classList).filter((c) => !c.startsWith("__rune-grab")).slice(0, 3);
1074
+ if (cls.length) tag += "." + cls.join(".");
1075
+ return tag;
1076
+ }
1077
+ function getKeyStyles(el) {
1078
+ const cs = window.getComputedStyle(el);
1079
+ const out = [];
1080
+ for (const prop of STYLE_PROPS) {
1081
+ const cssProp = prop.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
1082
+ const v = cs.getPropertyValue(cssProp);
1083
+ if (!v || STYLE_DEFAULTS.has(v)) continue;
1084
+ if (prop === "fontSize" && v === "16px") continue;
1085
+ if (prop === "fontWeight" && (v === "400" || v === "normal")) continue;
1086
+ if (prop === "color" && v === "rgb(0, 0, 0)") continue;
1087
+ if (prop === "lineHeight" && v === "normal") continue;
1088
+ if (prop === "letterSpacing" && v === "normal") continue;
1089
+ if (prop === "textAlign" && v === "start") continue;
1090
+ if (prop === "opacity" && v === "1") continue;
1091
+ if (prop === "borderWidth" && v === "0px") continue;
1092
+ if ((prop === "padding" || prop === "margin") && (v === "0px" || /^0px(\s+0px)*$/.test(v))) continue;
1093
+ if (prop === "fontFamily") {
1094
+ const first = v.split(",")[0].trim().replace(/^["']+|["']+$/g, "");
1095
+ if (!first || ["system-ui", "sans-serif", "serif", "monospace", "-apple-system"].includes(first)) continue;
1096
+ out.push("fontFamily: " + first);
1097
+ continue;
1098
+ }
1099
+ out.push(prop + ": " + v);
1100
+ }
1101
+ return out.join("; ");
1102
+ }
1103
+ function cleanHTML(el) {
1104
+ const tag = el.tagName.toLowerCase();
1105
+ const attrs = [];
1106
+ for (const attr of KEEP_ATTRS) {
1107
+ const v = el.getAttribute(attr);
1108
+ if (v) attrs.push(`${attr}="${v}"`);
1109
+ }
1110
+ const cls = Array.from(el.classList).filter((c) => !/^(css-|chakra-|__rune)/.test(c));
1111
+ if (cls.length) attrs.push(`class="${cls.join(" ")}"`);
1112
+ const open = `<${tag}${attrs.length ? " " + attrs.join(" ") : ""}>`;
1113
+ const close = `</${tag}>`;
1114
+ let text = "";
1115
+ el.childNodes.forEach((n) => {
1116
+ if (n.nodeType === 3) text += (n.textContent || "").trim();
1117
+ });
1118
+ const childCount = el.children.length;
1119
+ let inner = text;
1120
+ if (childCount > 0) {
1121
+ inner = (text ? text + " " : "") + `[${childCount} child element${childCount > 1 ? "s" : ""}]`;
1122
+ }
1123
+ if (inner.length > 200) inner = inner.slice(0, 200) + "...";
1124
+ return open + inner + close;
1125
+ }
1126
+ function extractElementMeta(el, customSkip) {
1127
+ const local = isLocalhost();
1128
+ const component = detectComponent(el, customSkip);
1129
+ const tag = getTagDisplay(el);
1130
+ const visibleText = getVisibleText(el);
1131
+ const html = cleanHTML(el);
1132
+ const meta = {
1133
+ tag,
1134
+ visibleText,
1135
+ html,
1136
+ attrs: {
1137
+ role: el.getAttribute("role") || void 0,
1138
+ ariaLabel: el.getAttribute("aria-label") || void 0,
1139
+ alt: el.getAttribute("alt") || void 0,
1140
+ placeholder: el.placeholder || void 0,
1141
+ type: el.getAttribute("type") || void 0,
1142
+ href: el.getAttribute("href") || void 0,
1143
+ id: el.id || void 0,
1144
+ testId: el.getAttribute("data-testid") || void 0
1145
+ },
1146
+ component: component || void 0,
1147
+ isLocal: local
1148
+ };
1149
+ if (local) {
1150
+ meta.componentStack = detectComponentStack(el, 3, customSkip);
1151
+ } else {
1152
+ meta.styles = getKeyStyles(el);
1153
+ }
1154
+ return meta;
1155
+ }
1156
+ function buildLabel(meta) {
1157
+ const textPart = meta.visibleText ? `"${meta.visibleText.slice(0, 30)}"` : "";
1158
+ const tagPart = meta.tag.split(".")[0].split("#")[0];
1159
+ let elType = tagPart;
1160
+ if (tagPart === "button" || meta.attrs.role === "button") elType = "button";
1161
+ else if (tagPart === "a") elType = "link";
1162
+ else if (tagPart === "input") elType = `${meta.attrs.type || "text"} input`;
1163
+ else if (tagPart === "img") elType = "image";
1164
+ else if (tagPart === "textarea") elType = "textarea";
1165
+ let label = textPart ? `${textPart} ${elType}` : elType;
1166
+ if (meta.component?.filePath) {
1167
+ const parts = meta.component.filePath.split("/");
1168
+ const file = parts[parts.length - 1];
1169
+ label += ` in ${file}`;
1170
+ if (meta.component.line) label += `:${meta.component.line}`;
1171
+ } else if (meta.component?.name) {
1172
+ label += ` in <${meta.component.name}>`;
1173
+ }
1174
+ if (label.length > 80) label = label.slice(0, 77) + "...";
1175
+ return label;
1176
+ }
1177
+ function buildContextText(meta, prompt) {
1178
+ const parts = [];
1179
+ if (prompt) parts.push(prompt);
1180
+ if (meta.isLocal && meta.componentStack?.length) {
1181
+ for (const frame of meta.componentStack) {
1182
+ let line = frame.name;
1183
+ if (frame.filePath) {
1184
+ line += ` at ${frame.filePath}`;
1185
+ if (frame.line) line += `:${frame.line}`;
1186
+ }
1187
+ parts.push(line);
1188
+ }
1189
+ }
1190
+ parts.push(`Element: ${meta.html}`);
1191
+ if (meta.visibleText) parts.push(`Text: "${meta.visibleText}"`);
1192
+ const attrs = Object.entries(meta.attrs).filter(([, v]) => v).map(([k, v]) => `${k}="${v}"`).join(" ");
1193
+ if (attrs) parts.push(`Attributes: ${attrs}`);
1194
+ if (meta.styles) parts.push(`Styles: ${meta.styles}`);
1195
+ return parts.join("\n");
1196
+ }
1197
+ var init_extract = __esm({
1198
+ "src/core/extract.ts"() {
1199
+ "use strict";
1200
+ init_constants();
1201
+ init_frameworks();
1202
+ }
1203
+ });
1204
+
1205
+ // src/ui/overlay.ts
1206
+ function isOwnElement(el) {
1207
+ if (!el) return false;
1208
+ let cur = el;
1209
+ while (cur) {
1210
+ if (cur.id && OWN_IDS.includes(cur.id)) return true;
1211
+ cur = cur.parentElement;
1212
+ }
1213
+ return false;
1214
+ }
1215
+ function getToolbarBase() {
1216
+ return `position:fixed;z-index:${Z_TOOLBAR};background:${M_BG};border:1px solid ${M_BORDER};border-radius:9px;box-shadow:0 2px 8px rgba(0,0,0,0.08),0 0 0 1px rgba(0,0,0,0.04);display:none;flex-direction:row;align-items:center;top:0;left:0;font-family:${FONT};`;
1217
+ }
1218
+ function createGrabOverlay() {
1219
+ if (s.overlay) return;
1220
+ s.overlay = document.createElement("div");
1221
+ s.overlay.id = "__rune-grab-overlay__";
1222
+ s.overlay.style.cssText = `position:fixed;pointer-events:none;z-index:${Z_OVERLAY};border:1px dashed ${M_BG};background:transparent;border-radius:3px;transition:all 0.08s ease-out;display:none;top:0;left:0;width:0;height:0;`;
1223
+ s.label = document.createElement("div");
1224
+ s.label.id = "__rune-grab-label__";
1225
+ s.label.style.cssText = `position:fixed;pointer-events:none;z-index:${Z_TOOLBAR};background:${M_BG};border:1px solid ${M_BORDER};color:${M_FG};font-size:10px;font-family:${MONO};padding:2px 6px;border-radius:5px;white-space:nowrap;display:none;top:0;left:0;line-height:1.4;font-weight:500;letter-spacing:0.01em;box-shadow:0 2px 8px rgba(0,0,0,0.08);`;
1226
+ s.toolbar = document.createElement("div");
1227
+ s.toolbar.id = "__rune-grab-toolbar__";
1228
+ s.toolbar.style.cssText = getToolbarBase() + "padding:4px;gap:2px;";
1229
+ s.cursorStyle = document.createElement("style");
1230
+ s.cursorStyle.id = "__rune-grab-cursor__";
1231
+ s.cursorStyle.textContent = ".__rune-grab-active__ *{cursor:crosshair !important;}.__rune-grab-active__{cursor:crosshair !important;}";
1232
+ document.head.appendChild(s.cursorStyle);
1233
+ document.body.appendChild(s.overlay);
1234
+ document.body.appendChild(s.label);
1235
+ document.body.appendChild(s.toolbar);
1236
+ }
1237
+ function removeGrabOverlay() {
1238
+ s.overlay?.remove();
1239
+ s.label?.remove();
1240
+ s.toolbar?.remove();
1241
+ s.cursorStyle?.remove();
1242
+ s.overlay = null;
1243
+ s.label = null;
1244
+ s.toolbar = null;
1245
+ s.cursorStyle = null;
1246
+ }
1247
+ function createSelectionBox() {
1248
+ if (s.selectionBox) return;
1249
+ s.selectionBox = document.createElement("div");
1250
+ s.selectionBox.id = "__rune-grab-selection__";
1251
+ s.selectionBox.style.cssText = `
1252
+ position:fixed;z-index:${Z_OVERLAY};
1253
+ border:1px dashed ${M_BG};
1254
+ background:transparent;
1255
+ border-radius:2px;display:none;
1256
+ pointer-events:none;
1257
+ top:0;left:0;width:0;height:0;
1258
+ `;
1259
+ document.body.appendChild(s.selectionBox);
1260
+ }
1261
+ function removeSelectionBox() {
1262
+ s.selectionBox?.remove();
1263
+ s.selectionBox = null;
1264
+ }
1265
+ function doHighlight(el) {
1266
+ if (!s.overlay || !s.label) return;
1267
+ const r = el.getBoundingClientRect();
1268
+ s.overlay.style.top = r.top + "px";
1269
+ s.overlay.style.left = r.left + "px";
1270
+ s.overlay.style.width = r.width + "px";
1271
+ s.overlay.style.height = r.height + "px";
1272
+ s.overlay.style.display = "block";
1273
+ const meta = extractElementMeta(el, s.customSkipSet);
1274
+ let displayName;
1275
+ if (isLocalhost() && meta.component) {
1276
+ displayName = meta.component.name;
1277
+ if (meta.component.filePath) {
1278
+ const parts = meta.component.filePath.split("/");
1279
+ displayName += ` \xB7 ${parts[parts.length - 1]}`;
1280
+ if (meta.component.line) displayName += `:${meta.component.line}`;
1281
+ }
1282
+ } else {
1283
+ displayName = meta.component ? `<${meta.component.name}>` : `<${meta.tag}>`;
1284
+ }
1285
+ s.label.textContent = displayName;
1286
+ s.label.style.display = "block";
1287
+ let lt = r.top - 22;
1288
+ if (lt < 4) lt = r.bottom + 4;
1289
+ s.label.style.top = lt + "px";
1290
+ s.label.style.left = r.left + "px";
1291
+ }
1292
+ function hideHighlight() {
1293
+ if (s.overlay) s.overlay.style.display = "none";
1294
+ if (s.label) s.label.style.display = "none";
1295
+ }
1296
+ function hideToolbar() {
1297
+ if (s.toolbar) s.toolbar.style.display = "none";
1298
+ }
1299
+ function flash(msg) {
1300
+ if (!document.body) return;
1301
+ const f = document.createElement("div");
1302
+ f.textContent = msg;
1303
+ let x = s.lastMouseX + 12;
1304
+ let y = s.lastMouseY - 30;
1305
+ if (x + 150 > window.innerWidth) x = s.lastMouseX - 150;
1306
+ if (y < 4) y = s.lastMouseY + 16;
1307
+ f.style.cssText = `position:fixed;z-index:${Z_TOOLBAR + 3};top:${y}px;left:${x}px;background:${M_BG};color:${M_FG};font-size:11px;font-family:${FONT};padding:5px 12px;border-radius:7px;font-weight:500;pointer-events:none;opacity:1;transition:opacity 0.3s ease-out;box-shadow:0 2px 8px rgba(0,0,0,0.08),0 0 0 1px rgba(0,0,0,0.04);border:1px solid ${M_BORDER};`;
1308
+ document.body.appendChild(f);
1309
+ setTimeout(() => f.style.opacity = "0", FLASH_VISIBLE_MS);
1310
+ setTimeout(() => f.remove(), FLASH_REMOVE_MS);
1311
+ }
1312
+ function showPromptInput(onSubmit) {
1313
+ if (!s.toolbar) return;
1314
+ s.promptMode = true;
1315
+ const prevTop = s.toolbar.style.top;
1316
+ const prevLeft = s.toolbar.style.left;
1317
+ s.toolbar.innerHTML = "";
1318
+ s.toolbar.style.cssText = getToolbarBase() + "padding:3px 4px 3px 10px;gap:6px;";
1319
+ s.toolbar.style.top = prevTop;
1320
+ s.toolbar.style.left = prevLeft;
1321
+ const input = document.createElement("input");
1322
+ input.type = "text";
1323
+ input.placeholder = "Comment (Enter \u21B5 send, Esc cancel)";
1324
+ input.style.cssText = `background:transparent;border:none;outline:none;color:${M_FG};font-size:12px;font-family:${FONT};width:230px;padding:2px 0;caret-color:${M_FG};`;
1325
+ const sendBtn = document.createElement("button");
1326
+ sendBtn.innerHTML = ICON_SEND;
1327
+ sendBtn.style.cssText = `width:22px;height:22px;border:1px solid ${M_BORDER};background:${M_ACTIVE_BG};color:${M_ACTIVE_FG};border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all 0.15s;`;
1328
+ function submit() {
1329
+ let prompt = input.value.trim();
1330
+ if (prompt && !prompt.endsWith(":")) prompt += ":";
1331
+ s.promptMode = false;
1332
+ onSubmit(prompt);
1333
+ }
1334
+ function cancel() {
1335
+ s.promptMode = false;
1336
+ s.locked = false;
1337
+ hideToolbar();
1338
+ hideHighlight();
1339
+ s.selectedElement = null;
1340
+ }
1341
+ sendBtn.addEventListener("click", (e) => {
1342
+ e.stopPropagation();
1343
+ e.preventDefault();
1344
+ submit();
1345
+ });
1346
+ input.addEventListener("keydown", (e) => {
1347
+ e.stopPropagation();
1348
+ if (e.key === "Enter") {
1349
+ e.preventDefault();
1350
+ submit();
1351
+ }
1352
+ if (e.key === "Escape") {
1353
+ e.preventDefault();
1354
+ cancel();
1355
+ }
1356
+ });
1357
+ input.addEventListener("click", (e) => e.stopPropagation());
1358
+ s.toolbar.appendChild(input);
1359
+ s.toolbar.appendChild(sendBtn);
1360
+ const tw = 270;
1361
+ const curLeft = parseFloat(s.toolbar.style.left);
1362
+ if (curLeft + tw > window.innerWidth - 8) {
1363
+ s.toolbar.style.left = Math.max(8, window.innerWidth - tw - 8) + "px";
1364
+ }
1365
+ s.toolbar.style.display = "flex";
1366
+ setTimeout(() => input.focus(), 0);
1367
+ }
1368
+ var init_overlay = __esm({
1369
+ "src/ui/overlay.ts"() {
1370
+ "use strict";
1371
+ init_constants();
1372
+ init_extract();
1373
+ init_extract();
1374
+ init_state();
1375
+ init_styles();
1376
+ }
1377
+ });
1378
+
1379
+ // src/handlers/keyboard.ts
1380
+ function parseShortcut(shortcut) {
1381
+ const parts = shortcut.split("+");
1382
+ return {
1383
+ key: parts[parts.length - 1].toLowerCase(),
1384
+ meta: parts.some((p) => /^(meta|cmd)$/i.test(p)),
1385
+ ctrl: parts.some((p) => /^(ctrl|control)$/i.test(p)),
1386
+ shift: parts.some((p) => /^shift$/i.test(p)),
1387
+ alt: parts.some((p) => /^(alt|option)$/i.test(p))
1388
+ };
1389
+ }
1390
+ function setKeyboardCallbacks(toggle2, renderMiniMenu2) {
1391
+ _toggle = toggle2;
1392
+ _renderMiniMenu = renderMiniMenu2;
1393
+ }
1394
+ function onShortcut(e) {
1395
+ const sc = parseShortcut(s.config.shortcut);
1396
+ if (e.key.toLowerCase() === sc.key && e.metaKey === sc.meta && e.ctrlKey === sc.ctrl && e.shiftKey === sc.shift && e.altKey === sc.alt) {
1397
+ e.preventDefault();
1398
+ _toggle();
1399
+ _renderMiniMenu();
1400
+ }
1401
+ }
1402
+ var _toggle, _renderMiniMenu;
1403
+ var init_keyboard = __esm({
1404
+ "src/handlers/keyboard.ts"() {
1405
+ "use strict";
1406
+ init_state();
1407
+ }
1408
+ });
1409
+
1410
+ // src/handlers/inspect.ts
1411
+ function setReferenceCallbacks(dispatch, deactivate2, renderMiniMenu2) {
1412
+ _dispatch = dispatch;
1413
+ _deactivate2 = deactivate2;
1414
+ _renderMiniMenu2 = renderMiniMenu2;
1415
+ }
1416
+ async function executeReferenceGrab(el, prompt) {
1417
+ const meta = extractElementMeta(el, s.customSkipSet);
1418
+ const labelText = buildLabel(meta);
1419
+ const contextText = buildContextText(meta, prompt);
1420
+ const result = {
1421
+ type: "reference",
1422
+ label: labelText,
1423
+ text: contextText,
1424
+ prompt,
1425
+ meta
1426
+ };
1427
+ await _dispatch(result);
1428
+ flash(`Sent to ${TARGET_LABELS[s.config.target]}`);
1429
+ s.locked = false;
1430
+ hideToolbar();
1431
+ hideHighlight();
1432
+ s.selectedElement = null;
1433
+ }
1434
+ function showPromptForGrab(el) {
1435
+ if (!s.toolbar) return;
1436
+ const r = el.getBoundingClientRect();
1437
+ const tw = 270;
1438
+ const th = 34;
1439
+ let top = r.bottom + 6;
1440
+ let left = r.left + r.width / 2 - tw / 2;
1441
+ if (top + th > window.innerHeight - 8) top = r.top - th - 6;
1442
+ if (left < 8) left = 8;
1443
+ if (left + tw > window.innerWidth - 8) left = window.innerWidth - tw - 8;
1444
+ s.toolbar.style.top = top + "px";
1445
+ s.toolbar.style.left = left + "px";
1446
+ showPromptInput(async (prompt) => {
1447
+ await executeReferenceGrab(el, prompt);
1448
+ });
1449
+ }
1450
+ function onRefMouseMove(e) {
1451
+ if (s.locked || s.mmRAF) return;
1452
+ s.mmRAF = requestAnimationFrame(() => {
1453
+ s.mmRAF = 0;
1454
+ const el = document.elementFromPoint(e.clientX, e.clientY);
1455
+ if (!el || isOwnElement(el) || el === document.body || el === document.documentElement) {
1456
+ hideHighlight();
1457
+ s.selectedElement = null;
1458
+ return;
1459
+ }
1460
+ s.selectedElement = el;
1461
+ doHighlight(el);
1462
+ });
1463
+ }
1464
+ function onRefClick(e) {
1465
+ if (isOwnElement(e.target)) return;
1466
+ if (s.promptMode) return;
1467
+ e.preventDefault();
1468
+ e.stopPropagation();
1469
+ e.stopImmediatePropagation();
1470
+ if (s.locked) {
1471
+ s.locked = false;
1472
+ hideToolbar();
1473
+ return;
1474
+ }
1475
+ if (s.selectedElement) {
1476
+ s.locked = true;
1477
+ doHighlight(s.selectedElement);
1478
+ showPromptForGrab(s.selectedElement);
1479
+ }
1480
+ }
1481
+ function onRefKeyDown(e) {
1482
+ if (s.promptMode) return;
1483
+ if (e.key === "Escape") {
1484
+ if (s.locked) {
1485
+ s.locked = false;
1486
+ hideToolbar();
1487
+ s.selectedElement = null;
1488
+ hideHighlight();
1489
+ } else {
1490
+ _deactivate2();
1491
+ _renderMiniMenu2();
1492
+ }
1493
+ }
1494
+ }
1495
+ function onRefScroll() {
1496
+ if (s.promptMode) return;
1497
+ if (s.locked) {
1498
+ s.locked = false;
1499
+ hideToolbar();
1500
+ s.selectedElement = null;
1501
+ hideHighlight();
1502
+ }
1503
+ }
1504
+ var _dispatch, _deactivate2, _renderMiniMenu2;
1505
+ var init_inspect = __esm({
1506
+ "src/handlers/inspect.ts"() {
1507
+ "use strict";
1508
+ init_extract();
1509
+ init_state();
1510
+ init_overlay();
1511
+ init_styles();
1512
+ }
1513
+ });
1514
+
1515
+ // src/handlers/capture.ts
1516
+ function setScreenshotCallbacks(dispatch, deactivate2, renderMiniMenu2) {
1517
+ _dispatch2 = dispatch;
1518
+ _deactivate3 = deactivate2;
1519
+ _renderMiniMenu3 = renderMiniMenu2;
1520
+ }
1521
+ function onSSMouseDown(e) {
1522
+ if (e.button !== 0) return;
1523
+ if (isOwnElement(e.target)) return;
1524
+ e.preventDefault();
1525
+ e.stopPropagation();
1526
+ s.ssDrawing = true;
1527
+ s.ssStartX = e.clientX;
1528
+ s.ssStartY = e.clientY;
1529
+ if (s.selectionBox) {
1530
+ s.selectionBox.style.left = s.ssStartX + "px";
1531
+ s.selectionBox.style.top = s.ssStartY + "px";
1532
+ s.selectionBox.style.width = "0px";
1533
+ s.selectionBox.style.height = "0px";
1534
+ s.selectionBox.style.display = "block";
1535
+ }
1536
+ }
1537
+ function onSSMouseMove(e) {
1538
+ if (!s.ssDrawing || !s.selectionBox) return;
1539
+ e.preventDefault();
1540
+ const x = Math.min(e.clientX, s.ssStartX);
1541
+ const y = Math.min(e.clientY, s.ssStartY);
1542
+ const w = Math.abs(e.clientX - s.ssStartX);
1543
+ const h = Math.abs(e.clientY - s.ssStartY);
1544
+ s.selectionBox.style.left = x + "px";
1545
+ s.selectionBox.style.top = y + "px";
1546
+ s.selectionBox.style.width = w + "px";
1547
+ s.selectionBox.style.height = h + "px";
1548
+ }
1549
+ async function onSSMouseUp(e) {
1550
+ if (!s.ssDrawing) return;
1551
+ s.ssDrawing = false;
1552
+ e.preventDefault();
1553
+ e.stopPropagation();
1554
+ const x = Math.min(e.clientX, s.ssStartX);
1555
+ const y = Math.min(e.clientY, s.ssStartY);
1556
+ const w = Math.abs(e.clientX - s.ssStartX);
1557
+ const h = Math.abs(e.clientY - s.ssStartY);
1558
+ const selBox = s.selectionBox;
1559
+ if (selBox) selBox.remove();
1560
+ s.selectionBox = null;
1561
+ if (s.miniMenu) s.miniMenu.style.display = "none";
1562
+ if (s.cursorStyle) s.cursorStyle.disabled = true;
1563
+ await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
1564
+ if (w >= 10 && h >= 10) {
1565
+ const image = await captureRect({ x, y, width: w, height: h });
1566
+ if (s.miniMenu) s.miniMenu.style.display = "";
1567
+ if (s.cursorStyle) s.cursorStyle.disabled = false;
1568
+ createSelectionBox();
1569
+ if (image) {
1570
+ const result = {
1571
+ type: "screenshot",
1572
+ label: `Region ${w}\xD7${h}`,
1573
+ text: `Screenshot region: ${w}\xD7${h} at (${Math.round(x)}, ${Math.round(y)})`,
1574
+ prompt: "",
1575
+ image,
1576
+ meta: { tag: "region", visibleText: "", html: "", attrs: {}, isLocal: isLocalhost() }
1577
+ };
1578
+ await _dispatch2(result);
1579
+ flash("Screenshot captured");
1580
+ }
1581
+ } else {
1582
+ if (s.miniMenu) s.miniMenu.style.display = "";
1583
+ if (s.cursorStyle) s.cursorStyle.disabled = false;
1584
+ createSelectionBox();
1585
+ }
1586
+ }
1587
+ function onSSKeyDown(e) {
1588
+ if (e.key === "Escape") {
1589
+ if (s.ssDrawing) {
1590
+ s.ssDrawing = false;
1591
+ if (s.selectionBox) s.selectionBox.style.display = "none";
1592
+ } else {
1593
+ _deactivate3();
1594
+ _renderMiniMenu3();
1595
+ }
1596
+ }
1597
+ }
1598
+ var _dispatch2, _deactivate3, _renderMiniMenu3;
1599
+ var init_capture = __esm({
1600
+ "src/handlers/capture.ts"() {
1601
+ "use strict";
1602
+ init_screenshot();
1603
+ init_extract();
1604
+ init_state();
1605
+ init_overlay();
1606
+ }
1607
+ });
1608
+
1609
+ // src/core/engine.ts
1610
+ var engine_exports = {};
1611
+ __export(engine_exports, {
1612
+ activate: () => activate,
1613
+ deactivate: () => deactivate,
1614
+ getTarget: () => getTarget,
1615
+ init: () => init,
1616
+ isActive: () => isActive,
1617
+ setTarget: () => setTarget,
1618
+ toggle: () => toggle
1619
+ });
1620
+ async function dispatchResult(result) {
1621
+ s.config.onGrab(result);
1622
+ window.dispatchEvent(new CustomEvent("rune:grab", { detail: result }));
1623
+ if (s.config.target === "clipboard") {
1624
+ await copyToClipboard(result);
1625
+ } else {
1626
+ await sendToApp(s.config.target, result);
1627
+ }
1628
+ }
1629
+ function activate() {
1630
+ if (s.active) return;
1631
+ if (!document.body || !document.head) return;
1632
+ s.active = true;
1633
+ document.documentElement.classList.add("__rune-grab-active__");
1634
+ if (s.grabType === "screenshot") {
1635
+ createGrabOverlay();
1636
+ createSelectionBox();
1637
+ document.addEventListener("mousedown", onSSMouseDown, true);
1638
+ document.addEventListener("mousemove", onSSMouseMove, true);
1639
+ document.addEventListener("mouseup", onSSMouseUp, true);
1640
+ document.addEventListener("keydown", onSSKeyDown, true);
1641
+ } else {
1642
+ createGrabOverlay();
1643
+ document.addEventListener("mousemove", onRefMouseMove, true);
1644
+ document.addEventListener("click", onRefClick, true);
1645
+ document.addEventListener("keydown", onRefKeyDown, true);
1646
+ document.addEventListener("scroll", onRefScroll, true);
1647
+ }
1648
+ s.config.onToggle(true);
1649
+ }
1650
+ function deactivate() {
1651
+ if (!s.active) return;
1652
+ s.active = false;
1653
+ s.locked = false;
1654
+ s.promptMode = false;
1655
+ s.ssDrawing = false;
1656
+ s.selectedElement = null;
1657
+ if (s.mmRAF) {
1658
+ cancelAnimationFrame(s.mmRAF);
1659
+ s.mmRAF = 0;
1660
+ }
1661
+ document.removeEventListener("mousedown", onSSMouseDown, true);
1662
+ document.removeEventListener("mousemove", onSSMouseMove, true);
1663
+ document.removeEventListener("mouseup", onSSMouseUp, true);
1664
+ document.removeEventListener("keydown", onSSKeyDown, true);
1665
+ document.removeEventListener("mousemove", onRefMouseMove, true);
1666
+ document.removeEventListener("click", onRefClick, true);
1667
+ document.removeEventListener("keydown", onRefKeyDown, true);
1668
+ document.removeEventListener("scroll", onRefScroll, true);
1669
+ removeGrabOverlay();
1670
+ removeSelectionBox();
1671
+ document.documentElement.classList.remove("__rune-grab-active__");
1672
+ s.config.onToggle(false);
1673
+ }
1674
+ function toggle() {
1675
+ if (s.active) deactivate();
1676
+ else activate();
1677
+ }
1678
+ function isActive() {
1679
+ return s.active;
1680
+ }
1681
+ function setTarget(target) {
1682
+ s.config.target = target;
1683
+ if (s.menuExpanded) renderMiniMenu();
1684
+ }
1685
+ function getTarget() {
1686
+ return s.config.target;
1687
+ }
1688
+ function init(userConfig = {}) {
1689
+ if (s.initialized) return () => {
1690
+ };
1691
+ s.config = { ...DEFAULTS, ...userConfig };
1692
+ s.initialized = true;
1693
+ if (userConfig.skipComponents?.length) {
1694
+ s.customSkipSet = new Set(userConfig.skipComponents);
1695
+ }
1696
+ setMenuCallbacks(activate, deactivate);
1697
+ setKeyboardCallbacks(toggle, renderMiniMenu);
1698
+ setReferenceCallbacks(dispatchResult, deactivate, renderMiniMenu);
1699
+ setScreenshotCallbacks(dispatchResult, deactivate, renderMiniMenu);
1700
+ const savedAutoPaste = loadAutoPastePref();
1701
+ if (savedAutoPaste) {
1702
+ s.autoPasteEnabled = true;
1703
+ checkHelper().then((ok) => {
1704
+ s.helperAvailable = ok;
1705
+ if (!ok) s.config.target = "clipboard";
1706
+ if (s.menuExpanded) renderMiniMenu();
1707
+ });
1708
+ }
1709
+ const setup = () => {
1710
+ createMiniMenu();
1711
+ document.addEventListener("keydown", onShortcut, true);
1712
+ };
1713
+ if (document.readyState === "loading") {
1714
+ document.addEventListener("DOMContentLoaded", setup, { once: true });
1715
+ } else {
1716
+ setup();
1717
+ }
1718
+ return () => {
1719
+ deactivate();
1720
+ releaseStream();
1721
+ s.miniMenu?.remove();
1722
+ document.removeEventListener("keydown", onShortcut, true);
1723
+ s.initialized = false;
1724
+ s.hasDragged = false;
1725
+ };
1726
+ }
1727
+ var init_engine = __esm({
1728
+ "src/core/engine.ts"() {
1729
+ "use strict";
1730
+ init_screenshot();
1731
+ init_app();
1732
+ init_clipboard();
1733
+ init_state();
1734
+ init_menu();
1735
+ init_overlay();
1736
+ init_keyboard();
1737
+ init_inspect();
1738
+ init_capture();
1739
+ }
1740
+ });
1741
+
1742
+ // src/hooks/react.ts
1743
+ var react_exports = {};
1744
+ __export(react_exports, {
1745
+ useRuneGrab: () => useRuneGrab
1746
+ });
1747
+ module.exports = __toCommonJS(react_exports);
1748
+ var import_react = require("react");
1749
+ function useRuneGrab(config = {}) {
1750
+ const [active, setActive] = (0, import_react.useState)(false);
1751
+ const [target, setTargetState] = (0, import_react.useState)(config.target ?? "clipboard");
1752
+ const [lastGrab, setLastGrab] = (0, import_react.useState)(null);
1753
+ const cleanupRef = (0, import_react.useRef)(null);
1754
+ const engineRef = (0, import_react.useRef)(null);
1755
+ (0, import_react.useEffect)(() => {
1756
+ let mounted = true;
1757
+ Promise.resolve().then(() => (init_engine(), engine_exports)).then((engine) => {
1758
+ if (!mounted) return;
1759
+ engineRef.current = engine;
1760
+ cleanupRef.current = engine.init({
1761
+ ...config,
1762
+ onGrab: (result) => {
1763
+ setLastGrab(result);
1764
+ config.onGrab?.(result);
1765
+ },
1766
+ onToggle: (isActive2) => {
1767
+ setActive(isActive2);
1768
+ config.onToggle?.(isActive2);
1769
+ }
1770
+ });
1771
+ });
1772
+ return () => {
1773
+ mounted = false;
1774
+ cleanupRef.current?.();
1775
+ };
1776
+ }, []);
1777
+ const toggle2 = (0, import_react.useCallback)(() => {
1778
+ engineRef.current?.toggle();
1779
+ }, []);
1780
+ const activate2 = (0, import_react.useCallback)(() => {
1781
+ engineRef.current?.activate();
1782
+ }, []);
1783
+ const deactivate2 = (0, import_react.useCallback)(() => {
1784
+ engineRef.current?.deactivate();
1785
+ }, []);
1786
+ const setTarget2 = (0, import_react.useCallback)((t) => {
1787
+ setTargetState(t);
1788
+ engineRef.current?.setTarget(t);
1789
+ }, []);
1790
+ return { active, toggle: toggle2, activate: activate2, deactivate: deactivate2, setTarget: setTarget2, target, lastGrab };
1791
+ }
1792
+ // Annotate the CommonJS export names for ESM import in node:
1793
+ 0 && (module.exports = {
1794
+ useRuneGrab
1795
+ });
1796
+ //# sourceMappingURL=react.cjs.map