react-grab 0.0.11 → 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.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,37 @@
1
- declare const init: () => () => void;
1
+ interface StoreApi<T> {
2
+ getInitialState(): T;
3
+ getState(): T;
4
+ setState(partialState: ((prevState: T) => T) | Partial<T>): T;
5
+ subscribe(listener: Listener<T>): () => void;
6
+ subscribe<U>(listener: Listener<U>, selector: (state: T) => unknown): () => void;
7
+ }
8
+ type Listener<T> = (state: T, prevState: T | undefined) => (() => unknown) | void;
2
9
 
3
- export { init };
10
+ type Hotkey = KeyboardEvent["key"];
11
+
12
+ interface Options {
13
+ enabled?: boolean;
14
+ /**
15
+ * hotkey to trigger the overlay
16
+ *
17
+ * default: "Meta"
18
+ */
19
+ hotkey?: Hotkey | Hotkey[];
20
+ /**
21
+ * time required (ms) to hold the key to trigger the overlay
22
+ *
23
+ * default: 1000
24
+ */
25
+ keyHoldDuration?: number;
26
+ }
27
+ interface LibStore {
28
+ keyPressTimestamps: Map<Hotkey, number>;
29
+ mouseX: number;
30
+ mouseY: number;
31
+ overlayMode: "copying" | "hidden" | "visible";
32
+ pressedKeys: Set<Hotkey>;
33
+ }
34
+ declare const libStore: StoreApi<LibStore>;
35
+ declare const init: (options?: Options) => (() => void) | undefined;
36
+
37
+ export { type Options, init, libStore };
@@ -10,11 +10,217 @@ 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
+
13
217
  // src/overlay.ts
218
+ var VIEWPORT_MARGIN_PX = 8;
219
+ var LABEL_OFFSET_PX = 6;
14
220
  var lerp = (start, end, factor) => {
15
221
  return start + (end - start) * factor;
16
222
  };
17
- var SELECTION_LERP_FACTOR = 0.99;
223
+ var SELECTION_LERP_FACTOR = 0.95;
18
224
  var createSelectionElement = ({
19
225
  borderRadius,
20
226
  height,
@@ -32,23 +238,11 @@ var ReactGrab = (function (exports) {
32
238
  overlay.style.borderRadius = borderRadius;
33
239
  overlay.style.transform = transform;
34
240
  overlay.style.pointerEvents = "none";
35
- overlay.style.border = "2px solid #007AFF";
36
- overlay.style.backgroundColor = "rgba(0, 122, 255, 0.1)";
241
+ overlay.style.border = "1px solid rgb(210, 57, 192)";
242
+ overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
37
243
  overlay.style.zIndex = "2147483646";
38
244
  overlay.style.boxSizing = "border-box";
39
245
  overlay.style.display = "none";
40
- overlay.animate(
41
- [
42
- { backgroundColor: "rgba(0, 122, 255, 0.1)" },
43
- { backgroundColor: "rgba(0, 122, 255, 0.15)" },
44
- { backgroundColor: "rgba(0, 122, 255, 0.1)" }
45
- ],
46
- {
47
- duration: 2e3,
48
- easing: "ease-in-out",
49
- iterations: Infinity
50
- }
51
- );
52
246
  return overlay;
53
247
  };
54
248
  var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
@@ -79,52 +273,46 @@ var ReactGrab = (function (exports) {
79
273
  element.style.transform = transform;
80
274
  }
81
275
  };
82
- var SelectionOverlay = class {
83
- element;
84
- visible = false;
85
- constructor(root) {
86
- this.element = createSelectionElement({
87
- borderRadius: "0px",
88
- height: 0,
89
- transform: "none",
90
- width: 0,
91
- x: -1e3,
92
- y: -1e3
93
- });
94
- root.appendChild(this.element);
95
- }
96
- hide() {
97
- this.visible = false;
98
- this.element.style.display = "none";
99
- this.element.style.pointerEvents = "none";
100
- }
101
- isVisible() {
102
- return this.visible;
103
- }
104
- show() {
105
- this.visible = true;
106
- this.element.style.display = "block";
107
- this.element.style.pointerEvents = "auto";
108
- }
109
- update(selection) {
110
- updateSelectionElement(this.element, selection);
111
- }
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
+ };
112
303
  };
113
304
  var createSpinner = () => {
114
305
  const spinner = document.createElement("span");
115
306
  spinner.style.display = "inline-block";
116
307
  spinner.style.width = "8px";
117
308
  spinner.style.height = "8px";
118
- spinner.style.border = "1.5px solid #1e4ed8";
309
+ spinner.style.border = "1.5px solid rgb(210, 57, 192)";
119
310
  spinner.style.borderTopColor = "transparent";
120
311
  spinner.style.borderRadius = "50%";
121
312
  spinner.style.marginRight = "4px";
122
313
  spinner.style.verticalAlign = "middle";
123
314
  spinner.animate(
124
- [
125
- { transform: "rotate(0deg)" },
126
- { transform: "rotate(360deg)" }
127
- ],
315
+ [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
128
316
  {
129
317
  duration: 600,
130
318
  easing: "linear",
@@ -133,16 +321,15 @@ var ReactGrab = (function (exports) {
133
321
  );
134
322
  return spinner;
135
323
  };
136
- var createIndicator = (x, y) => {
324
+ var activeIndicator = null;
325
+ var createIndicator = () => {
137
326
  const indicator = document.createElement("div");
138
327
  indicator.style.position = "fixed";
139
- indicator.style.left = `${x}px`;
140
- indicator.style.top = `${y - 4}px`;
141
- indicator.style.transform = "translateY(-100%)";
142
- indicator.style.padding = "0px 4px";
143
- indicator.style.backgroundColor = "#dbeafe";
144
- indicator.style.color = "#1e4ed8";
145
- indicator.style.border = "1px solid #c7dbfb";
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";
146
333
  indicator.style.borderRadius = "4px";
147
334
  indicator.style.fontSize = "11px";
148
335
  indicator.style.fontWeight = "500";
@@ -153,59 +340,108 @@ var ReactGrab = (function (exports) {
153
340
  indicator.style.transition = "opacity 0.2s ease-in-out";
154
341
  indicator.style.display = "flex";
155
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";
156
347
  return indicator;
157
348
  };
158
- var showCopyIndicator = (x, y) => {
159
- const indicator = createIndicator(x, y);
160
- const spinner = createSpinner();
161
- const text = document.createElement("span");
162
- text.textContent = "Copying\u2026";
163
- indicator.appendChild(spinner);
164
- indicator.appendChild(text);
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);
165
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";
166
384
  requestAnimationFrame(() => {
167
385
  indicator.style.opacity = "1";
168
386
  });
169
- return () => {
170
- spinner.remove();
171
- text.textContent = "Copied!";
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
+ );
172
403
  setTimeout(() => {
173
404
  indicator.style.opacity = "0";
174
405
  setTimeout(() => {
175
- document.body.removeChild(indicator);
406
+ indicator.remove();
407
+ if (activeIndicator === indicator) {
408
+ activeIndicator = null;
409
+ }
176
410
  }, 200);
177
411
  }, 1500);
178
412
  };
179
413
  };
180
414
 
181
415
  // src/utils/copy-text.ts
182
- var copyText = async (text) => {
183
- if (navigator.clipboard && window.isSecureContext) {
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) {
184
419
  try {
185
420
  await navigator.clipboard.writeText(text);
186
421
  return true;
187
422
  } catch {
188
423
  }
189
424
  }
190
- const textareaEl = document.createElement("textarea");
191
- textareaEl.value = text;
192
- textareaEl.setAttribute("readonly", "");
193
- textareaEl.style.position = "fixed";
194
- textareaEl.style.top = "-9999px";
195
- textareaEl.style.opacity = "0";
196
- textareaEl.style.pointerEvents = "none";
197
- document.body.appendChild(textareaEl);
198
- textareaEl.select();
199
- textareaEl.setSelectionRange(0, textareaEl.value.length);
200
- let didCopy = false;
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;
201
437
  try {
202
- didCopy = document.execCommand("copy");
438
+ didCopyToClipboard = document.execCommand("copy");
203
439
  } catch {
204
- didCopy = false;
440
+ didCopyToClipboard = false;
205
441
  } finally {
206
- document.body.removeChild(textareaEl);
442
+ doc.removeChild(textareaElement);
207
443
  }
208
- return didCopy;
444
+ return didCopyToClipboard;
209
445
  };
210
446
 
211
447
  // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
@@ -2424,23 +2660,20 @@ ${error.stack}`;
2424
2660
  };
2425
2661
 
2426
2662
  // src/utils/is-element-visible.ts
2427
- var isElementVisible = (element, computedStyle = null) => {
2428
- if (!computedStyle) {
2429
- computedStyle = window.getComputedStyle(element);
2430
- }
2431
- 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";
2432
2665
  };
2433
2666
 
2434
2667
  // src/utils/mount-root.ts
2435
2668
  var ATTRIBUTE_NAME = "data-react-grab";
2436
2669
  var mountRoot = () => {
2437
- const existingHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2438
- if (existingHost) {
2439
- const existingRoot = existingHost.shadowRoot?.querySelector(
2670
+ const mountedHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2671
+ if (mountedHost) {
2672
+ const mountedRoot = mountedHost.shadowRoot?.querySelector(
2440
2673
  `[${ATTRIBUTE_NAME}]`
2441
2674
  );
2442
- if (existingRoot instanceof HTMLDivElement && existingHost.shadowRoot) {
2443
- return existingRoot;
2675
+ if (mountedRoot instanceof HTMLDivElement && mountedHost.shadowRoot) {
2676
+ return mountedRoot;
2444
2677
  }
2445
2678
  }
2446
2679
  const host = document.createElement("div");
@@ -2451,7 +2684,6 @@ ${error.stack}`;
2451
2684
  host.style.left = "0";
2452
2685
  const shadowRoot = host.attachShadow({ mode: "open" });
2453
2686
  const root = document.createElement("div");
2454
- root.style.transition = "opacity 0.1s ease-out";
2455
2687
  root.setAttribute(ATTRIBUTE_NAME, "true");
2456
2688
  shadowRoot.appendChild(root);
2457
2689
  const doc = document.body ?? document.documentElement;
@@ -2472,6 +2704,77 @@ ${error.stack}`;
2472
2704
  return setTimeout(callback, 0);
2473
2705
  };
2474
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
+
2475
2778
  // src/utils/throttle.ts
2476
2779
  var throttle = (fn, delay) => {
2477
2780
  let timeout = null;
@@ -2494,167 +2797,282 @@ ${error.stack}`;
2494
2797
  };
2495
2798
 
2496
2799
  // src/index.ts
2497
- var TICK_INTERVAL = 50;
2498
2800
  var THROTTLE_DELAY = 16;
2499
- 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
+ };
2500
2818
  const root = mountRoot();
2501
- let mouseX = 0;
2502
- let mouseY = 0;
2503
- let shiftKeyTimeout = null;
2504
- let currentHoveredElement = null;
2505
- const isFocusedInInput = () => {
2506
- const active = document.activeElement;
2507
- return active?.tagName === "INPUT" || active?.tagName === "TEXTAREA";
2508
- };
2509
- const selectionOverlay = new SelectionOverlay(root);
2510
- const render = () => {
2511
- if (!selectionOverlay.isVisible()) return;
2512
- const element = document.elementsFromPoint(mouseX, mouseY).filter((element2) => !element2.hasAttribute(ATTRIBUTE_NAME))[0];
2513
- if (!element) {
2514
- 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
+ }
2515
2870
  return;
2516
2871
  }
2517
- const computedStyle = window.getComputedStyle(element);
2518
- if (!isElementVisible(element, computedStyle)) return;
2519
- const rect = element.getBoundingClientRect();
2520
- currentHoveredElement = element;
2521
- selectionOverlay.update({
2522
- borderRadius: computedStyle.borderRadius,
2523
- height: rect.height,
2524
- transform: computedStyle.transform,
2525
- width: rect.width,
2526
- x: rect.left,
2527
- y: rect.top
2528
- });
2529
- };
2530
- const throttledRender = throttle(() => {
2531
- scheduleRunWhenIdle(() => {
2532
- render();
2533
- });
2534
- }, THROTTLE_DELAY);
2535
- window.addEventListener("mousemove", (event) => {
2536
- mouseX = event.clientX;
2537
- mouseY = event.clientY;
2538
- throttledRender();
2539
- });
2540
- window.addEventListener("resize", () => {
2541
- throttledRender();
2542
- });
2543
- window.addEventListener("scroll", () => {
2544
- throttledRender();
2545
- });
2546
- let timeout = null;
2547
- const nextTick = () => {
2548
- timeout = window.setTimeout(() => {
2549
- throttledRender();
2550
- nextTick();
2551
- }, TICK_INTERVAL);
2552
- };
2553
- nextTick();
2554
- window.addEventListener("keydown", (event) => {
2555
- if (event.key === "Meta") {
2556
- if (isFocusedInInput()) return;
2557
- if (shiftKeyTimeout !== null) {
2558
- window.clearTimeout(shiftKeyTimeout);
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;
2559
2884
  }
2560
- shiftKeyTimeout = window.setTimeout(() => {
2561
- selectionOverlay.show();
2562
- throttledRender();
2563
- }, 200);
2885
+ if (overlayMode !== "hidden") {
2886
+ libStore.setState((state) => ({
2887
+ ...state,
2888
+ overlayMode: "hidden"
2889
+ }));
2890
+ }
2891
+ return;
2564
2892
  }
2565
- if (event.key === "Escape") {
2566
- if (selectionOverlay.isVisible()) {
2567
- selectionOverlay.hide();
2568
- if (shiftKeyTimeout !== null) {
2569
- window.clearTimeout(shiftKeyTimeout);
2570
- shiftKeyTimeout = null;
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;
2571
2903
  }
2904
+ );
2905
+ }
2906
+ };
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
+ }));
2917
+ }, THROTTLE_DELAY);
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;
2942
+ }
2943
+ const computedStyle = window.getComputedStyle(element);
2944
+ if (!isElementVisible(element, computedStyle)) {
2945
+ continue;
2572
2946
  }
2947
+ return element;
2573
2948
  }
2574
- if ((event.key === "c" || event.key === "C") && event.metaKey) {
2575
- if (selectionOverlay.isVisible() && currentHoveredElement) {
2576
- event.preventDefault();
2577
- event.stopPropagation();
2578
- event.stopImmediatePropagation();
2579
- const element = currentHoveredElement;
2580
- const rect = element.getBoundingClientRect();
2581
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2582
- getStack(element).then((stack) => {
2583
- const htmlSnippet = getHTMLSnippet(element);
2584
- let output = htmlSnippet;
2585
- if (stack) {
2586
- const filteredStack = filterStack(stack);
2587
- const serializedStack = serializeStack(filteredStack);
2588
- 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}
2589
2962
 
2590
2963
  ${htmlSnippet}`;
2591
- }
2592
- return copyText(output);
2593
- }).then((success) => {
2594
- if (success) {
2595
- showSuccess();
2596
- }
2597
- }).catch(() => {
2598
- });
2599
2964
  }
2965
+ await copyTextToClipboard(`
2966
+ ${text}`);
2967
+ const tagName = (element.tagName || "").toLowerCase();
2968
+ cleanupCopyIndicator(tagName);
2969
+ } catch {
2970
+ cleanupCopyIndicator();
2600
2971
  }
2601
- });
2602
- window.addEventListener("mousedown", (event) => {
2603
- if (event.button !== 0) return;
2604
- if (selectionOverlay.isVisible()) {
2605
- event.preventDefault();
2606
- event.stopPropagation();
2607
- event.stopImmediatePropagation();
2608
- if (currentHoveredElement) {
2609
- const element = currentHoveredElement;
2610
- const rect = element.getBoundingClientRect();
2611
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2612
- getStack(element).then((stack) => {
2613
- const htmlSnippet = getHTMLSnippet(element);
2614
- let output = htmlSnippet;
2615
- if (stack) {
2616
- const filteredStack = filterStack(stack);
2617
- const serializedStack = serializeStack(filteredStack);
2618
- output = `## Referenced element:
2619
- ${htmlSnippet}
2620
- Stack trace:
2621
- ${serializedStack}
2622
-
2623
- Path: ${window.location.pathname}`;
2624
- }
2625
- return copyText(output);
2626
- }).then((success) => {
2627
- if (success) {
2628
- showSuccess();
2629
- }
2630
- }).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;
2631
3004
  });
2632
3005
  }
3006
+ return;
2633
3007
  }
2634
- });
2635
- window.addEventListener("keyup", (event) => {
2636
- if (event.key === "Meta") {
2637
- if (shiftKeyTimeout !== null) {
2638
- window.clearTimeout(shiftKeyTimeout);
2639
- shiftKeyTimeout = null;
3008
+ const element = getElementAtPosition(mouseX, mouseY);
3009
+ if (!element) {
3010
+ if (selectionOverlay.isVisible()) {
3011
+ selectionOverlay.hide();
2640
3012
  }
2641
- selectionOverlay.hide();
3013
+ hoveredElement = null;
3014
+ return;
3015
+ }
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();
2642
3031
  }
3032
+ }, 10);
3033
+ const cleanupRenderSubscription = libStore.subscribe((state) => {
3034
+ scheduleRunWhenIdle(() => {
3035
+ handleRender(state);
3036
+ });
2643
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();
2644
3048
  return () => {
3049
+ window.removeEventListener("mousemove", handleMouseMove);
3050
+ window.removeEventListener("mousedown", handleMouseDown);
3051
+ cleanupTrackHotkeys();
3052
+ cleanupRenderSubscription();
3053
+ cleanupKeyStateChangeSubscription();
2645
3054
  if (timeout) {
2646
3055
  window.clearTimeout(timeout);
2647
- timeout = null;
2648
3056
  }
2649
- if (shiftKeyTimeout !== null) {
2650
- window.clearTimeout(shiftKeyTimeout);
2651
- shiftKeyTimeout = null;
3057
+ if (cleanupActivationHotkeyWatcher) {
3058
+ cleanupActivationHotkeyWatcher();
2652
3059
  }
2653
- root.remove();
2654
- throttledRender.cancel();
2655
3060
  };
2656
3061
  };
2657
- 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
+ }
2658
3076
  /*! Bundled license information:
2659
3077
 
2660
3078
  bippy/dist/src-R2iEnVC1.js:
@@ -2699,6 +3117,7 @@ Path: ${window.location.pathname}`;
2699
3117
  */
2700
3118
 
2701
3119
  exports.init = init;
3120
+ exports.libStore = libStore;
2702
3121
 
2703
3122
  return exports;
2704
3123
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grab",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/aidenybai/react-grab#readme",