react-grab 0.0.9 → 0.0.12

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.
@@ -10,6 +10,440 @@ var ReactGrab = (function (exports) {
10
10
  * LICENSE file in the root directory of this source tree.
11
11
  */
12
12
 
13
+ // src/hotkeys.ts
14
+ var FORM_TAGS_AND_ROLES = [
15
+ "input",
16
+ "textarea",
17
+ "select",
18
+ "searchbox",
19
+ "slider",
20
+ "spinbutton",
21
+ "menuitem",
22
+ "menuitemcheckbox",
23
+ "menuitemradio",
24
+ "option",
25
+ "radio",
26
+ "textbox"
27
+ ];
28
+ var isCustomElement = (element) => {
29
+ return Boolean(element.tagName) && !element.tagName.startsWith("-") && element.tagName.includes("-");
30
+ };
31
+ var isReadonlyArray = (value) => {
32
+ return Array.isArray(value);
33
+ };
34
+ var isHotkeyEnabledOnTagName = (event, enabledOnTags = false) => {
35
+ const { composed, target } = event;
36
+ let targetTagName;
37
+ let targetRole;
38
+ if (target instanceof HTMLElement && isCustomElement(target) && composed) {
39
+ const composedPath = event.composedPath();
40
+ const targetElement = composedPath[0];
41
+ if (targetElement instanceof HTMLElement) {
42
+ targetTagName = targetElement.tagName;
43
+ targetRole = targetElement.role;
44
+ }
45
+ } else if (target instanceof HTMLElement) {
46
+ targetTagName = target.tagName;
47
+ targetRole = target.role;
48
+ }
49
+ if (isReadonlyArray(enabledOnTags)) {
50
+ return Boolean(
51
+ targetTagName && enabledOnTags && enabledOnTags.some(
52
+ (tag) => typeof targetTagName === "string" && tag.toLowerCase() === targetTagName.toLowerCase() || tag === targetRole
53
+ )
54
+ );
55
+ }
56
+ return Boolean(targetTagName && enabledOnTags && enabledOnTags);
57
+ };
58
+ var isKeyboardEventTriggeredByInput = (event) => {
59
+ return isHotkeyEnabledOnTagName(event, FORM_TAGS_AND_ROLES);
60
+ };
61
+ var trackHotkeys = () => {
62
+ const handleKeyDown = (event) => {
63
+ if (isKeyboardEventTriggeredByInput(event)) {
64
+ return;
65
+ }
66
+ if (event.code === undefined) {
67
+ return;
68
+ }
69
+ libStore.setState((state) => {
70
+ const newTimestamps = new Map(state.keyPressTimestamps);
71
+ if (!state.pressedKeys.has(event.key)) {
72
+ newTimestamps.set(event.key, Date.now());
73
+ }
74
+ return {
75
+ ...state,
76
+ keyPressTimestamps: newTimestamps,
77
+ pressedKeys: /* @__PURE__ */ new Set([event.key, ...state.pressedKeys])
78
+ };
79
+ });
80
+ };
81
+ const handleKeyUp = (event) => {
82
+ if (event.code === undefined) {
83
+ return;
84
+ }
85
+ libStore.setState((state) => {
86
+ const newTimestamps = new Map(state.keyPressTimestamps);
87
+ newTimestamps.delete(event.key);
88
+ return {
89
+ ...state,
90
+ keyPressTimestamps: newTimestamps,
91
+ pressedKeys: new Set(
92
+ [...state.pressedKeys].filter((key) => key !== event.key)
93
+ )
94
+ };
95
+ });
96
+ };
97
+ const handleBlur = () => {
98
+ libStore.setState((state) => ({
99
+ ...state,
100
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
101
+ pressedKeys: /* @__PURE__ */ new Set()
102
+ }));
103
+ };
104
+ const handleContextmenu = () => {
105
+ libStore.setState((state) => ({
106
+ ...state,
107
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
108
+ pressedKeys: /* @__PURE__ */ new Set()
109
+ }));
110
+ };
111
+ document.addEventListener("keydown", handleKeyDown);
112
+ document.addEventListener("keyup", handleKeyUp);
113
+ window.addEventListener("blur", handleBlur);
114
+ window.addEventListener("contextmenu", handleContextmenu);
115
+ return () => {
116
+ document.removeEventListener("keydown", handleKeyDown);
117
+ document.removeEventListener("keyup", handleKeyUp);
118
+ window.removeEventListener("blur", handleBlur);
119
+ window.removeEventListener("contextmenu", handleContextmenu);
120
+ };
121
+ };
122
+ var isKeyPressed = (key) => {
123
+ const { pressedKeys } = libStore.getState();
124
+ if (key.length === 1) {
125
+ return pressedKeys.has(key.toLowerCase()) || pressedKeys.has(key.toUpperCase());
126
+ }
127
+ return pressedKeys.has(key);
128
+ };
129
+ var watchKeyHeldFor = (key, duration, onHeld) => {
130
+ let timeoutId = null;
131
+ let unsubscribe = null;
132
+ const cleanup = () => {
133
+ if (timeoutId !== null) {
134
+ clearTimeout(timeoutId);
135
+ timeoutId = null;
136
+ }
137
+ if (unsubscribe !== null) {
138
+ unsubscribe();
139
+ unsubscribe = null;
140
+ }
141
+ };
142
+ const checkSingleKeyPressed = (keyToCheck, pressedKeys) => {
143
+ if (keyToCheck.length === 1) {
144
+ return pressedKeys.has(keyToCheck.toLowerCase()) || pressedKeys.has(keyToCheck.toUpperCase());
145
+ }
146
+ return pressedKeys.has(keyToCheck);
147
+ };
148
+ const checkAllKeysPressed = (pressedKeys) => {
149
+ if (Array.isArray(key)) {
150
+ return key.every((keyFromCombo) => checkSingleKeyPressed(keyFromCombo, pressedKeys));
151
+ }
152
+ return checkSingleKeyPressed(key, pressedKeys);
153
+ };
154
+ const getKeyFromTimestamps = (keyToFind, timestamps) => {
155
+ if (keyToFind.length === 1) {
156
+ return timestamps.get(keyToFind.toLowerCase()) || timestamps.get(keyToFind.toUpperCase());
157
+ }
158
+ return timestamps.get(keyToFind);
159
+ };
160
+ const getEarliestPressTime = (timestamps) => {
161
+ const keysToInspect = Array.isArray(key) ? key : [key];
162
+ let earliest;
163
+ for (const keyFromCombo of keysToInspect) {
164
+ const timestamp = getKeyFromTimestamps(keyFromCombo, timestamps);
165
+ if (timestamp === undefined) {
166
+ return undefined;
167
+ }
168
+ if (earliest === undefined || timestamp < earliest) {
169
+ earliest = timestamp;
170
+ }
171
+ }
172
+ return earliest;
173
+ };
174
+ const scheduleCallback = () => {
175
+ const state = libStore.getState();
176
+ const { keyPressTimestamps, pressedKeys } = state;
177
+ if (!checkAllKeysPressed(pressedKeys)) {
178
+ if (timeoutId !== null) {
179
+ clearTimeout(timeoutId);
180
+ timeoutId = null;
181
+ }
182
+ return;
183
+ }
184
+ const earliestPressTime = getEarliestPressTime(keyPressTimestamps);
185
+ if (earliestPressTime === undefined) {
186
+ if (timeoutId !== null) {
187
+ clearTimeout(timeoutId);
188
+ timeoutId = null;
189
+ }
190
+ return;
191
+ }
192
+ const elapsed = Date.now() - earliestPressTime;
193
+ const remaining = duration - elapsed;
194
+ if (remaining <= 0) {
195
+ onHeld();
196
+ cleanup();
197
+ return;
198
+ }
199
+ if (timeoutId !== null) {
200
+ clearTimeout(timeoutId);
201
+ }
202
+ timeoutId = setTimeout(() => {
203
+ onHeld();
204
+ cleanup();
205
+ }, remaining);
206
+ };
207
+ unsubscribe = libStore.subscribe(
208
+ () => {
209
+ scheduleCallback();
210
+ },
211
+ (state) => state.pressedKeys
212
+ );
213
+ scheduleCallback();
214
+ return cleanup;
215
+ };
216
+
217
+ // src/overlay.ts
218
+ var VIEWPORT_MARGIN_PX = 8;
219
+ var LABEL_OFFSET_PX = 6;
220
+ var lerp = (start, end, factor) => {
221
+ return start + (end - start) * factor;
222
+ };
223
+ var SELECTION_LERP_FACTOR = 0.95;
224
+ var createSelectionElement = ({
225
+ borderRadius,
226
+ height,
227
+ transform,
228
+ width,
229
+ x,
230
+ y
231
+ }) => {
232
+ const overlay = document.createElement("div");
233
+ overlay.style.position = "fixed";
234
+ overlay.style.top = `${y}px`;
235
+ overlay.style.left = `${x}px`;
236
+ overlay.style.width = `${width}px`;
237
+ overlay.style.height = `${height}px`;
238
+ overlay.style.borderRadius = borderRadius;
239
+ overlay.style.transform = transform;
240
+ overlay.style.pointerEvents = "none";
241
+ overlay.style.border = "1px solid rgb(210, 57, 192)";
242
+ overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
243
+ overlay.style.zIndex = "2147483646";
244
+ overlay.style.boxSizing = "border-box";
245
+ overlay.style.display = "none";
246
+ return overlay;
247
+ };
248
+ var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
249
+ const currentTop = parseFloat(element.style.top) || 0;
250
+ const currentLeft = parseFloat(element.style.left) || 0;
251
+ const currentWidth = parseFloat(element.style.width) || 0;
252
+ const currentHeight = parseFloat(element.style.height) || 0;
253
+ const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
254
+ const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
255
+ const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
256
+ const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
257
+ if (element.style.top !== topValue) {
258
+ element.style.top = topValue;
259
+ }
260
+ if (element.style.left !== leftValue) {
261
+ element.style.left = leftValue;
262
+ }
263
+ if (element.style.width !== widthValue) {
264
+ element.style.width = widthValue;
265
+ }
266
+ if (element.style.height !== heightValue) {
267
+ element.style.height = heightValue;
268
+ }
269
+ if (element.style.borderRadius !== borderRadius) {
270
+ element.style.borderRadius = borderRadius;
271
+ }
272
+ if (element.style.transform !== transform) {
273
+ element.style.transform = transform;
274
+ }
275
+ };
276
+ var createSelectionOverlay = (root) => {
277
+ const element = createSelectionElement({
278
+ borderRadius: "0px",
279
+ height: 0,
280
+ transform: "none",
281
+ width: 0,
282
+ x: -1e3,
283
+ y: -1e3
284
+ });
285
+ root.appendChild(element);
286
+ let visible = false;
287
+ return {
288
+ hide: () => {
289
+ visible = false;
290
+ element.style.display = "none";
291
+ element.style.pointerEvents = "none";
292
+ },
293
+ isVisible: () => visible,
294
+ show: () => {
295
+ visible = true;
296
+ element.style.display = "block";
297
+ element.style.pointerEvents = "auto";
298
+ },
299
+ update: (selection) => {
300
+ updateSelectionElement(element, selection);
301
+ }
302
+ };
303
+ };
304
+ var createSpinner = () => {
305
+ const spinner = document.createElement("span");
306
+ spinner.style.display = "inline-block";
307
+ spinner.style.width = "8px";
308
+ spinner.style.height = "8px";
309
+ spinner.style.border = "1.5px solid rgb(210, 57, 192)";
310
+ spinner.style.borderTopColor = "transparent";
311
+ spinner.style.borderRadius = "50%";
312
+ spinner.style.marginRight = "4px";
313
+ spinner.style.verticalAlign = "middle";
314
+ spinner.animate(
315
+ [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
316
+ {
317
+ duration: 600,
318
+ easing: "linear",
319
+ iterations: Infinity
320
+ }
321
+ );
322
+ return spinner;
323
+ };
324
+ var activeIndicator = null;
325
+ var createIndicator = () => {
326
+ const indicator = document.createElement("div");
327
+ indicator.style.position = "fixed";
328
+ indicator.style.top = "calc(8px + env(safe-area-inset-top))";
329
+ indicator.style.padding = "2px 6px";
330
+ indicator.style.backgroundColor = "#fde7f7";
331
+ indicator.style.color = "#b21c8e";
332
+ indicator.style.border = "1px solid #f7c5ec";
333
+ indicator.style.borderRadius = "4px";
334
+ indicator.style.fontSize = "11px";
335
+ indicator.style.fontWeight = "500";
336
+ indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
337
+ indicator.style.zIndex = "2147483647";
338
+ indicator.style.pointerEvents = "none";
339
+ indicator.style.opacity = "0";
340
+ indicator.style.transition = "opacity 0.2s ease-in-out";
341
+ indicator.style.display = "flex";
342
+ indicator.style.alignItems = "center";
343
+ indicator.style.maxWidth = "calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)))";
344
+ indicator.style.overflow = "hidden";
345
+ indicator.style.textOverflow = "ellipsis";
346
+ indicator.style.whiteSpace = "nowrap";
347
+ return indicator;
348
+ };
349
+ var showCopyIndicator = (selectionLeftPx, selectionTopPx) => {
350
+ if (activeIndicator) {
351
+ activeIndicator.remove();
352
+ activeIndicator = null;
353
+ }
354
+ const indicator = createIndicator();
355
+ const loadingSpinner = createSpinner();
356
+ const labelText = document.createElement("span");
357
+ labelText.textContent = "Grabbing\u2026";
358
+ indicator.appendChild(loadingSpinner);
359
+ indicator.appendChild(labelText);
360
+ document.body.appendChild(indicator);
361
+ activeIndicator = indicator;
362
+ const indicatorRect = indicator.getBoundingClientRect();
363
+ const viewportWidthPx = window.innerWidth;
364
+ const viewportHeightPx = window.innerHeight;
365
+ let indicatorLeftPx = Math.round(selectionLeftPx);
366
+ let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
367
+ indicatorLeftPx = Math.max(
368
+ VIEWPORT_MARGIN_PX,
369
+ Math.min(
370
+ indicatorLeftPx,
371
+ viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX
372
+ )
373
+ );
374
+ indicatorTopPx = Math.max(
375
+ VIEWPORT_MARGIN_PX,
376
+ Math.min(
377
+ indicatorTopPx,
378
+ viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX
379
+ )
380
+ );
381
+ indicator.style.left = `${indicatorLeftPx}px`;
382
+ indicator.style.top = `${indicatorTopPx}px`;
383
+ indicator.style.right = "auto";
384
+ requestAnimationFrame(() => {
385
+ indicator.style.opacity = "1";
386
+ });
387
+ return (tagName) => {
388
+ loadingSpinner.remove();
389
+ const checkmarkIcon = document.createElement("span");
390
+ checkmarkIcon.textContent = "\u2713";
391
+ checkmarkIcon.style.display = "inline-block";
392
+ checkmarkIcon.style.marginRight = "4px";
393
+ checkmarkIcon.style.fontWeight = "600";
394
+ indicator.insertBefore(checkmarkIcon, labelText);
395
+ const tagNameMonospace = document.createElement("span");
396
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
397
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
398
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
399
+ labelText.replaceChildren(
400
+ document.createTextNode("Grabbed "),
401
+ tagNameMonospace
402
+ );
403
+ setTimeout(() => {
404
+ indicator.style.opacity = "0";
405
+ setTimeout(() => {
406
+ indicator.remove();
407
+ if (activeIndicator === indicator) {
408
+ activeIndicator = null;
409
+ }
410
+ }, 200);
411
+ }, 1500);
412
+ };
413
+ };
414
+
415
+ // src/utils/copy-text.ts
416
+ var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
417
+ var copyTextToClipboard = async (text) => {
418
+ if (IS_NAVIGATOR_CLIPBOARD_AVAILABLE) {
419
+ try {
420
+ await navigator.clipboard.writeText(text);
421
+ return true;
422
+ } catch {
423
+ }
424
+ }
425
+ const textareaElement = document.createElement("textarea");
426
+ textareaElement.value = text;
427
+ textareaElement.setAttribute("readonly", "");
428
+ textareaElement.style.position = "fixed";
429
+ textareaElement.style.top = "-9999px";
430
+ textareaElement.style.opacity = "0";
431
+ textareaElement.style.pointerEvents = "none";
432
+ const doc = document.body || document.documentElement;
433
+ doc.appendChild(textareaElement);
434
+ textareaElement.select();
435
+ textareaElement.setSelectionRange(0, textareaElement.value.length);
436
+ let didCopyToClipboard = false;
437
+ try {
438
+ didCopyToClipboard = document.execCommand("copy");
439
+ } catch {
440
+ didCopyToClipboard = false;
441
+ } finally {
442
+ doc.removeChild(textareaElement);
443
+ }
444
+ return didCopyToClipboard;
445
+ };
446
+
13
447
  // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
14
448
  var version = "0.3.31";
15
449
  var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
@@ -1983,7 +2417,7 @@ ${error.stack}`;
1983
2417
  return matches;
1984
2418
  };
1985
2419
 
1986
- // src/core.ts
2420
+ // src/utils/data.ts
1987
2421
  var getStack = async (element) => {
1988
2422
  const fiber = getFiberFromHostInstance(element);
1989
2423
  if (!fiber) return null;
@@ -2225,190 +2659,21 @@ ${error.stack}`;
2225
2659
  return lines.join("\n");
2226
2660
  };
2227
2661
 
2228
- // src/overlay.ts
2229
- var createSelection = ({
2230
- borderRadius,
2231
- height,
2232
- transform,
2233
- width,
2234
- x,
2235
- y
2236
- }) => {
2237
- const overlay = document.createElement("div");
2238
- overlay.style.position = "fixed";
2239
- overlay.style.top = `${y}px`;
2240
- overlay.style.left = `${x}px`;
2241
- overlay.style.width = `${width}px`;
2242
- overlay.style.height = `${height}px`;
2243
- overlay.style.borderRadius = borderRadius;
2244
- overlay.style.transform = transform;
2245
- overlay.style.pointerEvents = "none";
2246
- overlay.style.border = "2px solid #007AFF";
2247
- overlay.style.backgroundColor = "rgba(0, 122, 255, 0.1)";
2248
- overlay.style.zIndex = "2147483646";
2249
- overlay.style.boxSizing = "border-box";
2250
- overlay.animate(
2251
- [
2252
- { backgroundColor: "rgba(0, 122, 255, 0.1)" },
2253
- { backgroundColor: "rgba(0, 122, 255, 0.15)" },
2254
- { backgroundColor: "rgba(0, 122, 255, 0.1)" }
2255
- ],
2256
- {
2257
- duration: 2e3,
2258
- easing: "ease-in-out",
2259
- iterations: Infinity
2260
- }
2261
- );
2262
- return overlay;
2263
- };
2264
- var lerp = (start, end, factor) => {
2265
- return start + (end - start) * factor;
2266
- };
2267
- var SELECTION_LERP_FACTOR = 1;
2268
- var updateSelection = (element, { borderRadius, height, transform, width, x, y }) => {
2269
- const currentTop = parseFloat(element.style.top) || 0;
2270
- const currentLeft = parseFloat(element.style.left) || 0;
2271
- const currentWidth = parseFloat(element.style.width) || 0;
2272
- const currentHeight = parseFloat(element.style.height) || 0;
2273
- const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
2274
- const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
2275
- const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
2276
- const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
2277
- if (element.style.top !== topValue) {
2278
- element.style.top = topValue;
2279
- }
2280
- if (element.style.left !== leftValue) {
2281
- element.style.left = leftValue;
2282
- }
2283
- if (element.style.width !== widthValue) {
2284
- element.style.width = widthValue;
2285
- }
2286
- if (element.style.height !== heightValue) {
2287
- element.style.height = heightValue;
2288
- }
2289
- if (element.style.borderRadius !== borderRadius) {
2290
- element.style.borderRadius = borderRadius;
2291
- }
2292
- if (element.style.transform !== transform) {
2293
- element.style.transform = transform;
2294
- }
2295
- };
2296
- var createSpinner = () => {
2297
- const spinner = document.createElement("span");
2298
- spinner.style.display = "inline-block";
2299
- spinner.style.width = "8px";
2300
- spinner.style.height = "8px";
2301
- spinner.style.border = "1.5px solid #1e4ed8";
2302
- spinner.style.borderTopColor = "transparent";
2303
- spinner.style.borderRadius = "50%";
2304
- spinner.style.marginRight = "4px";
2305
- spinner.style.verticalAlign = "middle";
2306
- spinner.animate(
2307
- [
2308
- { transform: "rotate(0deg)" },
2309
- { transform: "rotate(360deg)" }
2310
- ],
2311
- {
2312
- duration: 600,
2313
- easing: "linear",
2314
- iterations: Infinity
2315
- }
2316
- );
2317
- return spinner;
2318
- };
2319
- var createIndicator = (x, y) => {
2320
- const indicator = document.createElement("div");
2321
- indicator.style.position = "fixed";
2322
- indicator.style.left = `${x}px`;
2323
- indicator.style.top = `${y - 4}px`;
2324
- indicator.style.transform = "translateY(-100%)";
2325
- indicator.style.padding = "0px 4px";
2326
- indicator.style.backgroundColor = "#dbeafe";
2327
- indicator.style.color = "#1e4ed8";
2328
- indicator.style.border = "1px solid #c7dbfb";
2329
- indicator.style.borderRadius = "4px";
2330
- indicator.style.fontSize = "11px";
2331
- indicator.style.fontWeight = "500";
2332
- indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
2333
- indicator.style.zIndex = "2147483647";
2334
- indicator.style.pointerEvents = "none";
2335
- indicator.style.opacity = "0";
2336
- indicator.style.transition = "opacity 0.2s ease-in-out";
2337
- indicator.style.display = "flex";
2338
- indicator.style.alignItems = "center";
2339
- return indicator;
2340
- };
2341
- var showCopyIndicator = (x, y) => {
2342
- const indicator = createIndicator(x, y);
2343
- const spinner = createSpinner();
2344
- const text = document.createElement("span");
2345
- text.textContent = "Copying\u2026";
2346
- indicator.appendChild(spinner);
2347
- indicator.appendChild(text);
2348
- document.body.appendChild(indicator);
2349
- requestAnimationFrame(() => {
2350
- indicator.style.opacity = "1";
2351
- });
2352
- return () => {
2353
- spinner.remove();
2354
- text.textContent = "Copied!";
2355
- setTimeout(() => {
2356
- indicator.style.opacity = "0";
2357
- setTimeout(() => {
2358
- document.body.removeChild(indicator);
2359
- }, 200);
2360
- }, 1500);
2361
- };
2362
- };
2363
-
2364
- // src/utils/copy-text.ts
2365
- var copyText = async (text) => {
2366
- if (navigator.clipboard && window.isSecureContext) {
2367
- try {
2368
- await navigator.clipboard.writeText(text);
2369
- return true;
2370
- } catch {
2371
- }
2372
- }
2373
- const textareaEl = document.createElement("textarea");
2374
- textareaEl.value = text;
2375
- textareaEl.setAttribute("readonly", "");
2376
- textareaEl.style.position = "fixed";
2377
- textareaEl.style.top = "-9999px";
2378
- textareaEl.style.opacity = "0";
2379
- textareaEl.style.pointerEvents = "none";
2380
- document.body.appendChild(textareaEl);
2381
- textareaEl.select();
2382
- textareaEl.setSelectionRange(0, textareaEl.value.length);
2383
- let didCopy = false;
2384
- try {
2385
- didCopy = document.execCommand("copy");
2386
- } catch {
2387
- didCopy = false;
2388
- } finally {
2389
- document.body.removeChild(textareaEl);
2390
- }
2391
- return didCopy;
2392
- };
2393
-
2394
2662
  // src/utils/is-element-visible.ts
2395
- var isElementVisible = (element, computedStyle = null) => {
2396
- if (!computedStyle) {
2397
- computedStyle = window.getComputedStyle(element);
2398
- }
2399
- return computedStyle.display !== "none" && computedStyle.visibility !== "hidden";
2663
+ var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
2664
+ return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
2400
2665
  };
2401
2666
 
2402
2667
  // src/utils/mount-root.ts
2403
2668
  var ATTRIBUTE_NAME = "data-react-grab";
2404
2669
  var mountRoot = () => {
2405
- const existingHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2406
- if (existingHost) {
2407
- const existingRoot = existingHost.shadowRoot?.querySelector(
2670
+ const mountedHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2671
+ if (mountedHost) {
2672
+ const mountedRoot = mountedHost.shadowRoot?.querySelector(
2408
2673
  `[${ATTRIBUTE_NAME}]`
2409
2674
  );
2410
- if (existingRoot instanceof HTMLDivElement && existingHost.shadowRoot) {
2411
- return existingRoot;
2675
+ if (mountedRoot instanceof HTMLDivElement && mountedHost.shadowRoot) {
2676
+ return mountedRoot;
2412
2677
  }
2413
2678
  }
2414
2679
  const host = document.createElement("div");
@@ -2419,7 +2684,6 @@ ${error.stack}`;
2419
2684
  host.style.left = "0";
2420
2685
  const shadowRoot = host.attachShadow({ mode: "open" });
2421
2686
  const root = document.createElement("div");
2422
- root.style.transition = "opacity 0.1s ease-out";
2423
2687
  root.setAttribute(ATTRIBUTE_NAME, "true");
2424
2688
  shadowRoot.appendChild(root);
2425
2689
  const doc = document.body ?? document.documentElement;
@@ -2440,6 +2704,77 @@ ${error.stack}`;
2440
2704
  return setTimeout(callback, 0);
2441
2705
  };
2442
2706
 
2707
+ // src/utils/store.ts
2708
+ var createStore = (initializer) => {
2709
+ const subscriberMap = /* @__PURE__ */ new Map();
2710
+ let currentListenerIndex = 0;
2711
+ let currentState;
2712
+ const setState = (maybeStateOrReducer) => {
2713
+ const prevState = currentState;
2714
+ const resolvedState = typeof maybeStateOrReducer === "function" ? maybeStateOrReducer(prevState) : maybeStateOrReducer;
2715
+ const nextState = {
2716
+ ...prevState,
2717
+ ...resolvedState
2718
+ };
2719
+ currentState = nextState;
2720
+ for (const entry of subscriberMap.values()) {
2721
+ if (entry.type === "selected" /* Selected */) {
2722
+ const nextSelectedValue = entry.selector(nextState);
2723
+ const prevSelectedValue = entry.prevSelectedValue;
2724
+ if (!Object.is(prevSelectedValue, nextSelectedValue)) {
2725
+ entry.prevSelectedValue = nextSelectedValue;
2726
+ entry.listener(nextSelectedValue, prevSelectedValue);
2727
+ }
2728
+ } else {
2729
+ entry.listener(nextState, prevState);
2730
+ }
2731
+ }
2732
+ return currentState;
2733
+ };
2734
+ const getState = () => {
2735
+ return currentState;
2736
+ };
2737
+ const initialState = initializer(setState, getState);
2738
+ currentState = initialState;
2739
+ const subscribeWithSelector = (listener, selector) => {
2740
+ const index = String(currentListenerIndex++);
2741
+ const wrappedListener = (value, prevValue) => listener(value, prevValue);
2742
+ const entry = {
2743
+ listener: wrappedListener,
2744
+ prevSelectedValue: selector(currentState),
2745
+ selector,
2746
+ type: "selected" /* Selected */
2747
+ };
2748
+ subscriberMap.set(index, entry);
2749
+ return () => {
2750
+ subscriberMap.delete(index);
2751
+ };
2752
+ };
2753
+ const subscribeToFullState = (listener) => {
2754
+ const index = String(currentListenerIndex++);
2755
+ const entry = {
2756
+ listener,
2757
+ type: "full" /* Full */
2758
+ };
2759
+ subscriberMap.set(index, entry);
2760
+ return () => {
2761
+ subscriberMap.delete(index);
2762
+ };
2763
+ };
2764
+ function subscribe(subscriber, selector) {
2765
+ return selector ? subscribeWithSelector(subscriber, selector) : subscribeToFullState(subscriber);
2766
+ }
2767
+ const store = {
2768
+ getInitialState() {
2769
+ return initialState;
2770
+ },
2771
+ getState,
2772
+ setState,
2773
+ subscribe
2774
+ };
2775
+ return store;
2776
+ };
2777
+
2443
2778
  // src/utils/throttle.ts
2444
2779
  var throttle = (fn, delay) => {
2445
2780
  let timeout = null;
@@ -2462,173 +2797,282 @@ ${error.stack}`;
2462
2797
  };
2463
2798
 
2464
2799
  // src/index.ts
2465
- var TICK_INTERVAL = 50;
2466
2800
  var THROTTLE_DELAY = 16;
2467
- var init = () => {
2801
+ var libStore = createStore(() => ({
2802
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
2803
+ mouseX: -1e3,
2804
+ mouseY: -1e3,
2805
+ overlayMode: "hidden",
2806
+ pressedKeys: /* @__PURE__ */ new Set()
2807
+ }));
2808
+ var init = (options = {}) => {
2809
+ if (options.enabled === false) {
2810
+ return;
2811
+ }
2812
+ const resolvedOptions = {
2813
+ enabled: true,
2814
+ hotkey: "Meta",
2815
+ keyHoldDuration: 500,
2816
+ ...options
2817
+ };
2468
2818
  const root = mountRoot();
2469
- let mouseX = 0;
2470
- let mouseY = 0;
2471
- let isSelectionVisible = false;
2472
- let shiftKeyTimeout = null;
2473
- let currentHoveredElement = null;
2474
- const isFocusedInInput = () => {
2475
- const active = document.activeElement;
2476
- return active?.tagName === "INPUT" || active?.tagName === "TEXTAREA";
2477
- };
2478
- const currentSelection = createSelection({
2479
- borderRadius: "0px",
2480
- height: 0,
2481
- transform: "none",
2482
- width: 0,
2483
- x: -1e3,
2484
- y: -1e3
2485
- });
2486
- currentSelection.style.display = "none";
2487
- root.appendChild(currentSelection);
2488
- const render = () => {
2489
- if (!isSelectionVisible) return;
2490
- const element = document.elementsFromPoint(mouseX, mouseY).filter((element2) => !element2.hasAttribute(ATTRIBUTE_NAME))[0];
2491
- if (!element) {
2492
- currentHoveredElement = null;
2819
+ const selectionOverlay = createSelectionOverlay(root);
2820
+ let hoveredElement = null;
2821
+ let isCopying = false;
2822
+ const checkIsActivationHotkeyPressed = () => {
2823
+ if (Array.isArray(resolvedOptions.hotkey)) {
2824
+ for (const key of resolvedOptions.hotkey) {
2825
+ if (!isKeyPressed(key)) {
2826
+ return false;
2827
+ }
2828
+ }
2829
+ return true;
2830
+ }
2831
+ return isKeyPressed(resolvedOptions.hotkey);
2832
+ };
2833
+ const isCopyHotkeyPressed = () => {
2834
+ return isKeyPressed("Meta") && isKeyPressed("C");
2835
+ };
2836
+ let cleanupActivationHotkeyWatcher = null;
2837
+ const handleKeyStateChange = (pressedKeys) => {
2838
+ const { overlayMode } = libStore.getState();
2839
+ if (pressedKeys.has("Escape") || pressedKeys.has("Esc")) {
2840
+ libStore.setState((state) => {
2841
+ const nextPressedKeys = new Set(state.pressedKeys);
2842
+ nextPressedKeys.delete("Escape");
2843
+ nextPressedKeys.delete("Esc");
2844
+ const nextTimestamps = new Map(state.keyPressTimestamps);
2845
+ nextTimestamps.delete("Escape");
2846
+ nextTimestamps.delete("Esc");
2847
+ const activationKeys = Array.isArray(resolvedOptions.hotkey) ? resolvedOptions.hotkey : [resolvedOptions.hotkey];
2848
+ for (const activationKey of activationKeys) {
2849
+ if (activationKey.length === 1) {
2850
+ nextPressedKeys.delete(activationKey.toLowerCase());
2851
+ nextPressedKeys.delete(activationKey.toUpperCase());
2852
+ nextTimestamps.delete(activationKey.toLowerCase());
2853
+ nextTimestamps.delete(activationKey.toUpperCase());
2854
+ } else {
2855
+ nextPressedKeys.delete(activationKey);
2856
+ nextTimestamps.delete(activationKey);
2857
+ }
2858
+ }
2859
+ return {
2860
+ ...state,
2861
+ keyPressTimestamps: nextTimestamps,
2862
+ overlayMode: "hidden",
2863
+ pressedKeys: nextPressedKeys
2864
+ };
2865
+ });
2866
+ if (cleanupActivationHotkeyWatcher) {
2867
+ cleanupActivationHotkeyWatcher();
2868
+ cleanupActivationHotkeyWatcher = null;
2869
+ }
2493
2870
  return;
2494
2871
  }
2495
- const computedStyle = window.getComputedStyle(element);
2496
- if (!isElementVisible(element, computedStyle)) return;
2497
- const rect = element.getBoundingClientRect();
2498
- currentHoveredElement = element;
2499
- updateSelection(currentSelection, {
2500
- borderRadius: computedStyle.borderRadius,
2501
- height: rect.height,
2502
- transform: computedStyle.transform,
2503
- width: rect.width,
2504
- x: rect.left,
2505
- y: rect.top
2506
- });
2872
+ if (isCopyHotkeyPressed() && overlayMode === "visible") {
2873
+ libStore.setState((state) => ({
2874
+ ...state,
2875
+ overlayMode: "copying"
2876
+ }));
2877
+ return;
2878
+ }
2879
+ const isActivationHotkeyPressed = checkIsActivationHotkeyPressed();
2880
+ if (!isActivationHotkeyPressed) {
2881
+ if (cleanupActivationHotkeyWatcher) {
2882
+ cleanupActivationHotkeyWatcher();
2883
+ cleanupActivationHotkeyWatcher = null;
2884
+ }
2885
+ if (overlayMode !== "hidden") {
2886
+ libStore.setState((state) => ({
2887
+ ...state,
2888
+ overlayMode: "hidden"
2889
+ }));
2890
+ }
2891
+ return;
2892
+ }
2893
+ if (overlayMode === "hidden" && !cleanupActivationHotkeyWatcher) {
2894
+ cleanupActivationHotkeyWatcher = watchKeyHeldFor(
2895
+ resolvedOptions.hotkey,
2896
+ resolvedOptions.keyHoldDuration,
2897
+ () => {
2898
+ libStore.setState((state) => ({
2899
+ ...state,
2900
+ overlayMode: "visible"
2901
+ }));
2902
+ cleanupActivationHotkeyWatcher = null;
2903
+ }
2904
+ );
2905
+ }
2507
2906
  };
2508
- const throttledRender = throttle(() => {
2509
- scheduleRunWhenIdle(() => {
2510
- render();
2511
- });
2907
+ const cleanupKeyStateChangeSubscription = libStore.subscribe(
2908
+ handleKeyStateChange,
2909
+ (state) => state.pressedKeys
2910
+ );
2911
+ const handleMouseMove = throttle((event) => {
2912
+ libStore.setState((state) => ({
2913
+ ...state,
2914
+ mouseX: event.clientX,
2915
+ mouseY: event.clientY
2916
+ }));
2512
2917
  }, THROTTLE_DELAY);
2513
- window.addEventListener("mousemove", (event) => {
2514
- mouseX = event.clientX;
2515
- mouseY = event.clientY;
2516
- throttledRender();
2517
- });
2518
- window.addEventListener("resize", () => {
2519
- throttledRender();
2520
- });
2521
- window.addEventListener("scroll", () => {
2522
- throttledRender();
2523
- });
2524
- let timeout = null;
2525
- const nextTick = () => {
2526
- timeout = window.setTimeout(() => {
2527
- throttledRender();
2528
- nextTick();
2529
- }, TICK_INTERVAL);
2530
- };
2531
- nextTick();
2532
- window.addEventListener("keydown", (event) => {
2533
- if (event.key === "Meta") {
2534
- if (isFocusedInInput()) return;
2535
- if (shiftKeyTimeout !== null) {
2536
- window.clearTimeout(shiftKeyTimeout);
2918
+ const handleMouseDown = (event) => {
2919
+ if (event.button !== 0) {
2920
+ return;
2921
+ }
2922
+ const { overlayMode } = libStore.getState();
2923
+ if (overlayMode === "hidden") {
2924
+ return;
2925
+ }
2926
+ event.preventDefault();
2927
+ event.stopPropagation();
2928
+ event.stopImmediatePropagation();
2929
+ libStore.setState((state) => ({
2930
+ ...state,
2931
+ overlayMode: "copying"
2932
+ }));
2933
+ };
2934
+ window.addEventListener("mousemove", handleMouseMove);
2935
+ window.addEventListener("mousedown", handleMouseDown);
2936
+ const cleanupTrackHotkeys = trackHotkeys();
2937
+ const getElementAtPosition = (x, y) => {
2938
+ const elements = document.elementsFromPoint(x, y);
2939
+ for (const element of elements) {
2940
+ if (element.closest(`[${ATTRIBUTE_NAME}]`)) {
2941
+ continue;
2537
2942
  }
2538
- shiftKeyTimeout = window.setTimeout(() => {
2539
- isSelectionVisible = true;
2540
- currentSelection.style.display = "block";
2541
- currentSelection.style.pointerEvents = "auto";
2542
- throttledRender();
2543
- }, 200);
2943
+ const computedStyle = window.getComputedStyle(element);
2944
+ if (!isElementVisible(element, computedStyle)) {
2945
+ continue;
2946
+ }
2947
+ return element;
2544
2948
  }
2545
- if ((event.key === "c" || event.key === "C") && event.metaKey) {
2546
- if (isSelectionVisible && currentHoveredElement) {
2547
- event.preventDefault();
2548
- event.stopPropagation();
2549
- event.stopImmediatePropagation();
2550
- const element = currentHoveredElement;
2551
- const rect = element.getBoundingClientRect();
2552
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2553
- getStack(element).then((stack) => {
2554
- const htmlSnippet = getHTMLSnippet(element);
2555
- let output = htmlSnippet;
2556
- if (stack) {
2557
- const filteredStack = filterStack(stack);
2558
- const serializedStack = serializeStack(filteredStack);
2559
- output = `${serializedStack}
2949
+ return null;
2950
+ };
2951
+ const handleCopy = async (element) => {
2952
+ const rect = element.getBoundingClientRect();
2953
+ const cleanupCopyIndicator = showCopyIndicator(rect.left, rect.top);
2954
+ try {
2955
+ const stack = await getStack(element);
2956
+ const htmlSnippet = getHTMLSnippet(element);
2957
+ let text = htmlSnippet;
2958
+ if (stack) {
2959
+ const filteredStack = filterStack(stack);
2960
+ const serializedStack = serializeStack(filteredStack);
2961
+ text = `${serializedStack}
2560
2962
 
2561
2963
  ${htmlSnippet}`;
2562
- }
2563
- return copyText(output);
2564
- }).then((success) => {
2565
- if (success) {
2566
- showSuccess();
2567
- }
2568
- }).catch(() => {
2569
- });
2570
2964
  }
2965
+ await copyTextToClipboard(`
2966
+ ${text}`);
2967
+ const tagName = (element.tagName || "").toLowerCase();
2968
+ cleanupCopyIndicator(tagName);
2969
+ } catch {
2970
+ cleanupCopyIndicator();
2571
2971
  }
2572
- });
2573
- window.addEventListener("mousedown", (event) => {
2574
- if (event.button !== 0) return;
2575
- if (isSelectionVisible) {
2576
- event.preventDefault();
2577
- event.stopPropagation();
2578
- event.stopImmediatePropagation();
2579
- if (currentHoveredElement) {
2580
- const element = currentHoveredElement;
2581
- const rect = element.getBoundingClientRect();
2582
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2583
- getStack(element).then((stack) => {
2584
- const htmlSnippet = getHTMLSnippet(element);
2585
- let output = htmlSnippet;
2586
- if (stack) {
2587
- const filteredStack = filterStack(stack);
2588
- const serializedStack = serializeStack(filteredStack);
2589
- output = `## Referenced element:
2590
- ${htmlSnippet}
2591
- Stack trace:
2592
- ${serializedStack}
2593
-
2594
- Path: ${window.location.pathname}`;
2595
- }
2596
- return copyText(output);
2597
- }).then((success) => {
2598
- if (success) {
2599
- showSuccess();
2600
- }
2601
- }).catch(() => {
2972
+ };
2973
+ const handleRender = throttle((state) => {
2974
+ const { mouseX, mouseY, overlayMode } = state;
2975
+ if (overlayMode === "hidden") {
2976
+ if (selectionOverlay.isVisible()) {
2977
+ selectionOverlay.hide();
2978
+ hoveredElement = null;
2979
+ }
2980
+ return;
2981
+ }
2982
+ if (overlayMode === "copying" && hoveredElement) {
2983
+ const computedStyle2 = window.getComputedStyle(hoveredElement);
2984
+ const rect2 = hoveredElement.getBoundingClientRect();
2985
+ selectionOverlay.update({
2986
+ borderRadius: computedStyle2.borderRadius || "0px",
2987
+ height: rect2.height,
2988
+ transform: computedStyle2.transform || "none",
2989
+ width: rect2.width,
2990
+ x: rect2.left,
2991
+ y: rect2.top
2992
+ });
2993
+ if (!selectionOverlay.isVisible()) {
2994
+ selectionOverlay.show();
2995
+ }
2996
+ if (!isCopying) {
2997
+ isCopying = true;
2998
+ void handleCopy(hoveredElement).finally(() => {
2999
+ libStore.setState((state2) => ({
3000
+ ...state2,
3001
+ overlayMode: "hidden"
3002
+ }));
3003
+ isCopying = false;
2602
3004
  });
2603
3005
  }
3006
+ return;
2604
3007
  }
2605
- });
2606
- window.addEventListener("keyup", (event) => {
2607
- if (event.key === "Meta") {
2608
- if (shiftKeyTimeout !== null) {
2609
- window.clearTimeout(shiftKeyTimeout);
2610
- shiftKeyTimeout = null;
3008
+ const element = getElementAtPosition(mouseX, mouseY);
3009
+ if (!element) {
3010
+ if (selectionOverlay.isVisible()) {
3011
+ selectionOverlay.hide();
2611
3012
  }
3013
+ hoveredElement = null;
3014
+ return;
2612
3015
  }
2613
- currentSelection.style.display = "none";
2614
- currentSelection.style.pointerEvents = "none";
2615
- isSelectionVisible = false;
2616
- throttledRender();
3016
+ hoveredElement = element;
3017
+ const rect = element.getBoundingClientRect();
3018
+ const computedStyle = window.getComputedStyle(element);
3019
+ const borderRadius = computedStyle.borderRadius || "0px";
3020
+ const transform = computedStyle.transform || "none";
3021
+ selectionOverlay.update({
3022
+ borderRadius,
3023
+ height: rect.height,
3024
+ transform,
3025
+ width: rect.width,
3026
+ x: rect.left,
3027
+ y: rect.top
3028
+ });
3029
+ if (!selectionOverlay.isVisible()) {
3030
+ selectionOverlay.show();
3031
+ }
3032
+ }, 10);
3033
+ const cleanupRenderSubscription = libStore.subscribe((state) => {
3034
+ scheduleRunWhenIdle(() => {
3035
+ handleRender(state);
3036
+ });
2617
3037
  });
3038
+ let timeout = null;
3039
+ const render = () => {
3040
+ timeout = window.setTimeout(() => {
3041
+ scheduleRunWhenIdle(() => {
3042
+ handleRender(libStore.getState());
3043
+ render();
3044
+ });
3045
+ }, 100);
3046
+ };
3047
+ render();
2618
3048
  return () => {
3049
+ window.removeEventListener("mousemove", handleMouseMove);
3050
+ window.removeEventListener("mousedown", handleMouseDown);
3051
+ cleanupTrackHotkeys();
3052
+ cleanupRenderSubscription();
3053
+ cleanupKeyStateChangeSubscription();
2619
3054
  if (timeout) {
2620
3055
  window.clearTimeout(timeout);
2621
- timeout = null;
2622
3056
  }
2623
- if (shiftKeyTimeout !== null) {
2624
- window.clearTimeout(shiftKeyTimeout);
2625
- shiftKeyTimeout = null;
3057
+ if (cleanupActivationHotkeyWatcher) {
3058
+ cleanupActivationHotkeyWatcher();
2626
3059
  }
2627
- root.remove();
2628
- throttledRender.cancel();
2629
3060
  };
2630
3061
  };
2631
- init();
3062
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
3063
+ const currentScript = document.currentScript;
3064
+ let options = {};
3065
+ if (currentScript) {
3066
+ const maybeOptions = currentScript.getAttribute("data-options");
3067
+ if (maybeOptions) {
3068
+ try {
3069
+ options = JSON.parse(maybeOptions);
3070
+ } catch {
3071
+ }
3072
+ }
3073
+ }
3074
+ init(options);
3075
+ }
2632
3076
  /*! Bundled license information:
2633
3077
 
2634
3078
  bippy/dist/src-R2iEnVC1.js:
@@ -2673,6 +3117,7 @@ Path: ${window.location.pathname}`;
2673
3117
  */
2674
3118
 
2675
3119
  exports.init = init;
3120
+ exports.libStore = libStore;
2676
3121
 
2677
3122
  return exports;
2678
3123