react-grab 0.0.21 → 0.0.24

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.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { render, createComponent, mergeProps, memo, template, effect, style, insert, setStyleProperty, use } from 'solid-js/web';
2
+ import { createRoot, createSignal, createMemo, createEffect, onCleanup, Show, For } from 'solid-js';
1
3
  import { instrument, _fiberRoots, getFiberFromHostInstance } from 'bippy';
2
4
  import { getOwnerStack, getSourcesFromStack } from 'bippy/dist/source';
3
5
 
@@ -10,6 +12,7 @@ import { getOwnerStack, getSourcesFromStack } from 'bippy/dist/source';
10
12
  * LICENSE file in the root directory of this source tree.
11
13
  */
12
14
 
15
+
13
16
  // src/utils/is-keyboard-event-triggered-by-input.ts
14
17
  var FORM_TAGS_AND_ROLES = [
15
18
  "input",
@@ -90,354 +93,448 @@ var mountRoot = () => {
90
93
  var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
91
94
  return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
92
95
  };
93
-
94
- // src/overlay.ts
95
- var templateCache = /* @__PURE__ */ new Map();
96
- var html = (strings, ...values) => {
97
- const key = strings.join("");
98
- let template = templateCache.get(key);
99
- if (!template) {
100
- template = document.createElement("template");
101
- template.innerHTML = strings.reduce((acc, str, i) => acc + str + (values[i] ?? ""), "");
102
- templateCache.set(key, template);
103
- }
104
- return template.content.firstElementChild.cloneNode(true);
105
- };
96
+ var _tmpl$ = /* @__PURE__ */ template(`<div>`);
97
+ var _tmpl$2 = /* @__PURE__ */ template(`<span style="display:inline-block;width:8px;height:8px;border:1.5px solid rgb(210, 57, 192);border-top-color:transparent;border-radius:50%;margin-right:4px;vertical-align:middle">`);
98
+ var _tmpl$3 = /* @__PURE__ */ template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
99
+ var _tmpl$4 = /* @__PURE__ */ template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
100
+ var _tmpl$5 = /* @__PURE__ */ template(`<div style="position:fixed;padding:2px 6px;background-color:#fde7f7;color:#b21c8e;border:1px solid #f7c5ec;border-radius:4px;font-size:11px;font-weight:500;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;pointer-events:none;transition:opacity 0.2s ease-in-out;display:flex;align-items:center;max-width:calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)));overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><span>`);
101
+ var _tmpl$6 = /* @__PURE__ */ template(`<div style="position:fixed;z-index:2147483647;pointer-events:none;transition:opacity 0.1s ease-in-out"><div style="width:32px;height:2px;background-color:rgba(178, 28, 142, 0.2);border-radius:1px;overflow:hidden;position:relative"><div style="height:100%;background-color:#b21c8e;border-radius:1px;transition:width 0.05s cubic-bezier(0.165, 0.84, 0.44, 1)">`);
106
102
  var VIEWPORT_MARGIN_PX = 8;
107
103
  var LABEL_OFFSET_PX = 6;
108
104
  var INDICATOR_CLAMP_PADDING_PX = 4;
109
105
  var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
110
- var INDICATOR_FADE_MS = 200;
106
+ var SELECTION_LERP_FACTOR = 0.95;
107
+ var MARQUEE_LERP_FACTOR = 0.9;
111
108
  var lerp = (start, end, factor) => {
112
109
  return start + (end - start) * factor;
113
110
  };
114
- var SELECTION_LERP_FACTOR = 0.95;
115
- var createSelectionElement = ({
116
- borderRadius,
117
- height,
118
- transform,
119
- width,
120
- x,
121
- y
122
- }) => {
123
- const overlay = html`
124
- <div style="
125
- position: fixed;
126
- top: ${y}px;
127
- left: ${x}px;
128
- width: ${width}px;
129
- height: ${height}px;
130
- border-radius: ${borderRadius};
131
- transform: ${transform};
132
- pointer-events: auto;
133
- border: 1px solid rgb(210, 57, 192);
134
- background-color: rgba(210, 57, 192, 0.2);
135
- z-index: 2147483646;
136
- box-sizing: border-box;
137
- display: none;
138
- "></div>
139
- `;
140
- return overlay;
141
- };
142
- var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
143
- const currentTop = parseFloat(element.style.top) || 0;
144
- const currentLeft = parseFloat(element.style.left) || 0;
145
- const currentWidth = parseFloat(element.style.width) || 0;
146
- const currentHeight = parseFloat(element.style.height) || 0;
147
- const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
148
- const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
149
- const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
150
- const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
151
- if (element.style.top !== topValue) {
152
- element.style.top = topValue;
153
- }
154
- if (element.style.left !== leftValue) {
155
- element.style.left = leftValue;
156
- }
157
- if (element.style.width !== widthValue) {
158
- element.style.width = widthValue;
159
- }
160
- if (element.style.height !== heightValue) {
161
- element.style.height = heightValue;
162
- }
163
- if (element.style.borderRadius !== borderRadius) {
164
- element.style.borderRadius = borderRadius;
165
- }
166
- if (element.style.transform !== transform) {
167
- element.style.transform = transform;
168
- }
169
- };
170
- var createSelectionOverlay = (root, onSelectionClick) => {
171
- const element = createSelectionElement({
172
- borderRadius: "0px",
173
- height: 0,
174
- transform: "none",
175
- width: 0,
176
- x: -1e3,
177
- y: -1e3
178
- });
179
- root.appendChild(element);
180
- let visible = false;
111
+ var Overlay = (props) => {
112
+ const [currentX, setCurrentX] = createSignal(props.bounds.x);
113
+ const [currentY, setCurrentY] = createSignal(props.bounds.y);
114
+ const [currentWidth, setCurrentWidth] = createSignal(props.bounds.width);
115
+ const [currentHeight, setCurrentHeight] = createSignal(props.bounds.height);
116
+ const [opacity, setOpacity] = createSignal(1);
181
117
  let hasBeenShown = false;
182
- element.addEventListener(
183
- "mousedown",
184
- (event) => {
185
- event.preventDefault();
186
- event.stopPropagation();
187
- event.stopImmediatePropagation();
188
- if (onSelectionClick) {
189
- onSelectionClick();
190
- }
191
- },
192
- true
193
- );
194
- return {
195
- element,
196
- hide: () => {
197
- visible = false;
198
- hasBeenShown = false;
199
- element.style.display = "none";
200
- },
201
- isVisible: () => visible,
202
- show: () => {
203
- visible = true;
204
- element.style.display = "block";
205
- },
206
- update: (selection) => {
207
- if (!hasBeenShown) {
208
- element.style.top = `${selection.y}px`;
209
- element.style.left = `${selection.x}px`;
210
- element.style.width = `${selection.width}px`;
211
- element.style.height = `${selection.height}px`;
212
- element.style.borderRadius = selection.borderRadius;
213
- element.style.transform = selection.transform;
214
- hasBeenShown = true;
118
+ let animationFrameId = null;
119
+ let targetBounds = props.bounds;
120
+ createEffect(() => {
121
+ targetBounds = props.bounds;
122
+ const factor = props.lerpFactor ?? SELECTION_LERP_FACTOR;
123
+ if (!hasBeenShown) {
124
+ setCurrentX(targetBounds.x);
125
+ setCurrentY(targetBounds.y);
126
+ setCurrentWidth(targetBounds.width);
127
+ setCurrentHeight(targetBounds.height);
128
+ hasBeenShown = true;
129
+ return;
130
+ }
131
+ const EPSILON = 0.5;
132
+ const animate = () => {
133
+ const newX = lerp(currentX(), targetBounds.x, factor);
134
+ const newY = lerp(currentY(), targetBounds.y, factor);
135
+ const newWidth = lerp(currentWidth(), targetBounds.width, factor);
136
+ const newHeight = lerp(currentHeight(), targetBounds.height, factor);
137
+ setCurrentX(newX);
138
+ setCurrentY(newY);
139
+ setCurrentWidth(newWidth);
140
+ setCurrentHeight(newHeight);
141
+ const hasConverged = Math.abs(newX - targetBounds.x) < EPSILON && Math.abs(newY - targetBounds.y) < EPSILON && Math.abs(newWidth - targetBounds.width) < EPSILON && Math.abs(newHeight - targetBounds.height) < EPSILON;
142
+ if (!hasConverged) {
143
+ animationFrameId = requestAnimationFrame(animate);
215
144
  } else {
216
- updateSelectionElement(element, selection);
145
+ animationFrameId = null;
146
+ }
147
+ };
148
+ if (animationFrameId !== null) {
149
+ cancelAnimationFrame(animationFrameId);
150
+ }
151
+ animationFrameId = requestAnimationFrame(animate);
152
+ onCleanup(() => {
153
+ if (animationFrameId !== null) {
154
+ cancelAnimationFrame(animationFrameId);
155
+ animationFrameId = null;
217
156
  }
157
+ });
158
+ });
159
+ createEffect(() => {
160
+ if (props.variant === "grabbed") {
161
+ requestAnimationFrame(() => {
162
+ setOpacity(0);
163
+ });
218
164
  }
165
+ });
166
+ const baseStyle = {
167
+ position: "fixed",
168
+ "box-sizing": "border-box",
169
+ "pointer-events": props.variant === "marquee" ? "none" : "auto",
170
+ "z-index": "2147483646"
219
171
  };
220
- };
221
- var createGrabbedOverlay = (root, selection) => {
222
- const element = document.createElement("div");
223
- element.style.position = "fixed";
224
- element.style.top = `${selection.y}px`;
225
- element.style.left = `${selection.x}px`;
226
- element.style.width = `${selection.width}px`;
227
- element.style.height = `${selection.height}px`;
228
- element.style.borderRadius = selection.borderRadius;
229
- element.style.transform = selection.transform;
230
- element.style.pointerEvents = "none";
231
- element.style.border = "1px solid rgb(210, 57, 192)";
232
- element.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
233
- element.style.zIndex = "2147483646";
234
- element.style.boxSizing = "border-box";
235
- element.style.transition = "opacity 0.3s ease-out";
236
- element.style.opacity = "1";
237
- root.appendChild(element);
238
- requestAnimationFrame(() => {
239
- element.style.opacity = "0";
172
+ const variantStyle = () => {
173
+ if (props.variant === "marquee") {
174
+ return {
175
+ border: "1px dashed rgb(210, 57, 192)",
176
+ "background-color": "rgba(210, 57, 192, 0.1)",
177
+ "will-change": "transform, width, height",
178
+ contain: "layout paint size"
179
+ };
180
+ }
181
+ return {
182
+ border: "1px solid rgb(210, 57, 192)",
183
+ "background-color": "rgba(210, 57, 192, 0.2)",
184
+ transition: props.variant === "grabbed" ? "opacity 0.3s ease-out" : void 0
185
+ };
186
+ };
187
+ return createComponent(Show, {
188
+ get when() {
189
+ return props.visible !== false;
190
+ },
191
+ get children() {
192
+ var _el$ = _tmpl$();
193
+ effect((_$p) => style(_el$, {
194
+ ...baseStyle,
195
+ ...variantStyle(),
196
+ top: `${currentY()}px`,
197
+ left: `${currentX()}px`,
198
+ width: `${currentWidth()}px`,
199
+ height: `${currentHeight()}px`,
200
+ "border-radius": props.bounds.borderRadius,
201
+ transform: props.bounds.transform,
202
+ opacity: opacity()
203
+ }, _$p));
204
+ return _el$;
205
+ }
240
206
  });
241
- setTimeout(() => {
242
- element.remove();
243
- }, 300);
244
207
  };
245
- var createSpinner = () => {
246
- const spinner = html`
247
- <span style="
248
- display: inline-block;
249
- width: 8px;
250
- height: 8px;
251
- border: 1.5px solid rgb(210, 57, 192);
252
- border-top-color: transparent;
253
- border-radius: 50%;
254
- margin-right: 4px;
255
- vertical-align: middle;
256
- "></span>
257
- `;
258
- spinner.animate(
259
- [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
260
- {
261
- duration: 600,
262
- easing: "linear",
263
- iterations: Infinity
208
+ var Spinner = (props) => {
209
+ let ref;
210
+ createEffect(() => {
211
+ if (ref) {
212
+ ref.animate([{
213
+ transform: "rotate(0deg)"
214
+ }, {
215
+ transform: "rotate(360deg)"
216
+ }], {
217
+ duration: 600,
218
+ easing: "linear",
219
+ iterations: Infinity
220
+ });
264
221
  }
265
- );
266
- return spinner;
267
- };
268
- var activeIndicator = null;
269
- var createIndicator = () => {
270
- const indicator = html`
271
- <div style="
272
- position: fixed;
273
- top: calc(8px + env(safe-area-inset-top));
274
- padding: 2px 6px;
275
- background-color: #fde7f7;
276
- color: #b21c8e;
277
- border: 1px solid #f7c5ec;
278
- border-radius: 4px;
279
- font-size: 11px;
280
- font-weight: 500;
281
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
282
- z-index: 2147483647;
283
- pointer-events: none;
284
- opacity: 0;
285
- transition: opacity 0.2s ease-in-out;
286
- display: flex;
287
- align-items: center;
288
- max-width: calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)));
289
- overflow: hidden;
290
- text-overflow: ellipsis;
291
- white-space: nowrap;
292
- "></div>
293
- `;
294
- return indicator;
222
+ });
223
+ return (() => {
224
+ var _el$2 = _tmpl$2();
225
+ var _ref$ = ref;
226
+ typeof _ref$ === "function" ? use(_ref$, _el$2) : ref = _el$2;
227
+ effect((_$p) => style(_el$2, {
228
+ ...props.style
229
+ }, _$p));
230
+ return _el$2;
231
+ })();
295
232
  };
296
- var showLabel = (root, selectionLeftPx, selectionTopPx, tagName) => {
297
- let indicator = activeIndicator;
298
- let isNewIndicator = false;
299
- if (!indicator) {
300
- indicator = createIndicator();
301
- root.appendChild(indicator);
302
- activeIndicator = indicator;
303
- isNewIndicator = true;
304
- isProcessing = false;
305
- }
306
- if (!isProcessing) {
307
- const labelText = indicator.querySelector("span");
308
- if (labelText) {
309
- const tagNameMonospace = document.createElement("span");
310
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
311
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
312
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
313
- labelText.replaceChildren(tagNameMonospace);
233
+ var Label = (props) => {
234
+ const [opacity, setOpacity] = createSignal(0);
235
+ let ref;
236
+ createEffect(() => {
237
+ if (props.visible !== false) {
238
+ requestAnimationFrame(() => {
239
+ setOpacity(1);
240
+ });
314
241
  } else {
315
- const newLabelText = document.createElement("span");
316
- const tagNameMonospace = document.createElement("span");
317
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
318
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
319
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
320
- newLabelText.appendChild(tagNameMonospace);
321
- indicator.appendChild(newLabelText);
242
+ setOpacity(0);
322
243
  }
323
- }
324
- const indicatorRect = indicator.getBoundingClientRect();
325
- const viewportWidthPx = window.innerWidth;
326
- const viewportHeightPx = window.innerHeight;
327
- let indicatorLeftPx = Math.round(selectionLeftPx);
328
- let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
329
- const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
330
- const minLeft = VIEWPORT_MARGIN_PX;
331
- const minTop = VIEWPORT_MARGIN_PX;
332
- const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
333
- const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
334
- const willClampLeft = indicatorLeftPx < minLeft;
335
- const willClampTop = indicatorTopPx < minTop;
336
- const isClamped = willClampLeft || willClampTop;
337
- indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
338
- indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
339
- if (isClamped) {
340
- indicatorLeftPx += CLAMPED_PADDING;
341
- indicatorTopPx += CLAMPED_PADDING;
342
- }
343
- indicator.style.left = `${indicatorLeftPx}px`;
344
- indicator.style.top = `${indicatorTopPx}px`;
345
- indicator.style.right = "auto";
346
- if (isNewIndicator) {
347
- requestAnimationFrame(() => {
348
- indicator.style.opacity = "1";
349
- });
350
- } else if (indicator.style.opacity !== "1") {
351
- indicator.style.opacity = "1";
352
- }
353
- };
354
- var isProcessing = false;
355
- var activeGrabbedIndicators = /* @__PURE__ */ new Set();
356
- var updateLabelToProcessing = (root, selectionLeftPx, selectionTopPx) => {
357
- const indicator = createIndicator();
358
- indicator.style.zIndex = "2147483648";
359
- root.appendChild(indicator);
360
- activeGrabbedIndicators.add(indicator);
361
- const positionIndicator = () => {
362
- if (selectionLeftPx === void 0 || selectionTopPx === void 0) return;
363
- const indicatorRect = indicator.getBoundingClientRect();
244
+ });
245
+ createEffect(() => {
246
+ if (props.variant === "success") {
247
+ const fadeOutTimer = setTimeout(() => {
248
+ setOpacity(0);
249
+ }, INDICATOR_SUCCESS_VISIBLE_MS);
250
+ onCleanup(() => clearTimeout(fadeOutTimer));
251
+ }
252
+ });
253
+ const indicatorRect = () => ref?.getBoundingClientRect();
254
+ const computedPosition = () => {
255
+ const rect = indicatorRect();
256
+ if (!rect) return {
257
+ left: props.x,
258
+ top: props.y
259
+ };
364
260
  const viewportWidthPx = window.innerWidth;
365
261
  const viewportHeightPx = window.innerHeight;
366
- let indicatorLeftPx = Math.round(selectionLeftPx);
367
- let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
368
- const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
262
+ let indicatorLeftPx = Math.round(props.x);
263
+ let indicatorTopPx = Math.round(props.y) - rect.height - LABEL_OFFSET_PX;
369
264
  const minLeft = VIEWPORT_MARGIN_PX;
370
265
  const minTop = VIEWPORT_MARGIN_PX;
371
- const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
372
- const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
266
+ const maxLeft = viewportWidthPx - rect.width - VIEWPORT_MARGIN_PX;
267
+ const maxTop = viewportHeightPx - rect.height - VIEWPORT_MARGIN_PX;
373
268
  const willClampLeft = indicatorLeftPx < minLeft;
374
269
  const willClampTop = indicatorTopPx < minTop;
375
270
  const isClamped = willClampLeft || willClampTop;
376
271
  indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
377
272
  indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
378
273
  if (isClamped) {
379
- indicatorLeftPx += CLAMPED_PADDING;
380
- indicatorTopPx += CLAMPED_PADDING;
274
+ indicatorLeftPx += INDICATOR_CLAMP_PADDING_PX;
275
+ indicatorTopPx += INDICATOR_CLAMP_PADDING_PX;
381
276
  }
382
- indicator.style.left = `${indicatorLeftPx}px`;
383
- indicator.style.top = `${indicatorTopPx}px`;
384
- indicator.style.right = "auto";
277
+ return {
278
+ left: indicatorLeftPx,
279
+ top: indicatorTopPx
280
+ };
385
281
  };
386
- const loadingSpinner = createSpinner();
387
- const labelText = document.createElement("span");
388
- labelText.textContent = "Grabbing\u2026";
389
- indicator.appendChild(loadingSpinner);
390
- indicator.appendChild(labelText);
391
- positionIndicator();
392
- requestAnimationFrame(() => {
393
- indicator.style.opacity = "1";
282
+ return createComponent(Show, {
283
+ get when() {
284
+ return props.visible !== false;
285
+ },
286
+ get children() {
287
+ var _el$3 = _tmpl$5(), _el$5 = _el$3.firstChild;
288
+ var _ref$2 = ref;
289
+ typeof _ref$2 === "function" ? use(_ref$2, _el$3) : ref = _el$3;
290
+ insert(_el$3, createComponent(Show, {
291
+ get when() {
292
+ return props.variant === "processing";
293
+ },
294
+ get children() {
295
+ return createComponent(Spinner, {});
296
+ }
297
+ }), _el$5);
298
+ insert(_el$3, createComponent(Show, {
299
+ get when() {
300
+ return props.variant === "success";
301
+ },
302
+ get children() {
303
+ return _tmpl$3();
304
+ }
305
+ }), _el$5);
306
+ insert(_el$5, createComponent(Show, {
307
+ get when() {
308
+ return props.variant === "success";
309
+ },
310
+ children: "Grabbed "
311
+ }), null);
312
+ insert(_el$5, createComponent(Show, {
313
+ get when() {
314
+ return props.variant === "processing";
315
+ },
316
+ children: "Grabbing\u2026"
317
+ }), null);
318
+ insert(_el$5, createComponent(Show, {
319
+ get when() {
320
+ return props.variant === "hover";
321
+ },
322
+ get children() {
323
+ var _el$6 = _tmpl$4();
324
+ insert(_el$6, () => props.text);
325
+ return _el$6;
326
+ }
327
+ }), null);
328
+ insert(_el$5, createComponent(Show, {
329
+ get when() {
330
+ return props.variant !== "hover";
331
+ },
332
+ get children() {
333
+ var _el$7 = _tmpl$4();
334
+ insert(_el$7, () => props.text);
335
+ return _el$7;
336
+ }
337
+ }), null);
338
+ effect((_p$) => {
339
+ var _v$ = `${computedPosition().top}px`, _v$2 = `${computedPosition().left}px`, _v$3 = props.zIndex?.toString() ?? "2147483647", _v$4 = opacity();
340
+ _v$ !== _p$.e && setStyleProperty(_el$3, "top", _p$.e = _v$);
341
+ _v$2 !== _p$.t && setStyleProperty(_el$3, "left", _p$.t = _v$2);
342
+ _v$3 !== _p$.a && setStyleProperty(_el$3, "z-index", _p$.a = _v$3);
343
+ _v$4 !== _p$.o && setStyleProperty(_el$3, "opacity", _p$.o = _v$4);
344
+ return _p$;
345
+ }, {
346
+ e: void 0,
347
+ t: void 0,
348
+ a: void 0,
349
+ o: void 0
350
+ });
351
+ return _el$3;
352
+ }
394
353
  });
395
- return (tagName) => {
396
- indicator.textContent = "";
397
- const checkmarkIcon = document.createElement("span");
398
- checkmarkIcon.textContent = "\u2713";
399
- checkmarkIcon.style.display = "inline-block";
400
- checkmarkIcon.style.marginRight = "4px";
401
- checkmarkIcon.style.fontWeight = "600";
402
- const newLabelText = document.createElement("span");
403
- const tagNameMonospace = document.createElement("span");
404
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
405
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
406
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
407
- newLabelText.appendChild(document.createTextNode("Grabbed "));
408
- newLabelText.appendChild(tagNameMonospace);
409
- indicator.appendChild(checkmarkIcon);
410
- indicator.appendChild(newLabelText);
411
- requestAnimationFrame(() => {
412
- positionIndicator();
413
- });
414
- setTimeout(() => {
415
- indicator.style.opacity = "0";
416
- setTimeout(() => {
417
- indicator.remove();
418
- activeGrabbedIndicators.delete(indicator);
419
- }, INDICATOR_FADE_MS);
420
- }, INDICATOR_SUCCESS_VISIBLE_MS);
421
- };
422
354
  };
423
- var hideLabel = () => {
424
- if (activeIndicator) {
425
- activeIndicator.remove();
426
- activeIndicator = null;
427
- }
428
- isProcessing = false;
355
+ var ProgressIndicator = (props) => {
356
+ const [opacity, setOpacity] = createSignal(0);
357
+ let ref;
358
+ createEffect(() => {
359
+ if (props.visible !== false) {
360
+ requestAnimationFrame(() => {
361
+ setOpacity(1);
362
+ });
363
+ } else {
364
+ setOpacity(0);
365
+ }
366
+ });
367
+ const computedPosition = () => {
368
+ const rect = ref?.getBoundingClientRect();
369
+ if (!rect) return {
370
+ left: props.mouseX,
371
+ top: props.mouseY
372
+ };
373
+ const viewportWidth = window.innerWidth;
374
+ const viewportHeight = window.innerHeight;
375
+ const CURSOR_OFFSET = 14;
376
+ const VIEWPORT_MARGIN = 8;
377
+ let indicatorLeft = props.mouseX - rect.width / 2;
378
+ let indicatorTop = props.mouseY + CURSOR_OFFSET;
379
+ if (indicatorTop + rect.height + VIEWPORT_MARGIN > viewportHeight) {
380
+ indicatorTop = props.mouseY - rect.height - CURSOR_OFFSET;
381
+ }
382
+ indicatorTop = Math.max(VIEWPORT_MARGIN, Math.min(indicatorTop, viewportHeight - rect.height - VIEWPORT_MARGIN));
383
+ indicatorLeft = Math.max(VIEWPORT_MARGIN, Math.min(indicatorLeft, viewportWidth - rect.width - VIEWPORT_MARGIN));
384
+ return {
385
+ left: indicatorLeft,
386
+ top: indicatorTop
387
+ };
388
+ };
389
+ return createComponent(Show, {
390
+ get when() {
391
+ return props.visible !== false;
392
+ },
393
+ get children() {
394
+ var _el$8 = _tmpl$6(), _el$9 = _el$8.firstChild, _el$0 = _el$9.firstChild;
395
+ var _ref$3 = ref;
396
+ typeof _ref$3 === "function" ? use(_ref$3, _el$8) : ref = _el$8;
397
+ effect((_p$) => {
398
+ var _v$5 = `${computedPosition().top}px`, _v$6 = `${computedPosition().left}px`, _v$7 = opacity(), _v$8 = `${Math.min(100, Math.max(0, props.progress * 100))}%`;
399
+ _v$5 !== _p$.e && setStyleProperty(_el$8, "top", _p$.e = _v$5);
400
+ _v$6 !== _p$.t && setStyleProperty(_el$8, "left", _p$.t = _v$6);
401
+ _v$7 !== _p$.a && setStyleProperty(_el$8, "opacity", _p$.a = _v$7);
402
+ _v$8 !== _p$.o && setStyleProperty(_el$0, "width", _p$.o = _v$8);
403
+ return _p$;
404
+ }, {
405
+ e: void 0,
406
+ t: void 0,
407
+ a: void 0,
408
+ o: void 0
409
+ });
410
+ return _el$8;
411
+ }
412
+ });
429
413
  };
430
- var cleanupGrabbedIndicators = () => {
431
- for (const indicator of activeGrabbedIndicators) {
432
- indicator.remove();
433
- }
434
- activeGrabbedIndicators.clear();
414
+ var ReactGrabOverlay = (props) => {
415
+ return [createComponent(Show, {
416
+ get when() {
417
+ return memo(() => !!props.selectionVisible)() && props.selectionBounds;
418
+ },
419
+ get children() {
420
+ return createComponent(Overlay, {
421
+ variant: "selection",
422
+ get bounds() {
423
+ return props.selectionBounds;
424
+ },
425
+ get visible() {
426
+ return props.selectionVisible;
427
+ },
428
+ lerpFactor: SELECTION_LERP_FACTOR
429
+ });
430
+ }
431
+ }), createComponent(Show, {
432
+ get when() {
433
+ return memo(() => !!props.marqueeVisible)() && props.marqueeBounds;
434
+ },
435
+ get children() {
436
+ return createComponent(Overlay, {
437
+ variant: "marquee",
438
+ get bounds() {
439
+ return props.marqueeBounds;
440
+ },
441
+ get visible() {
442
+ return props.marqueeVisible;
443
+ },
444
+ lerpFactor: MARQUEE_LERP_FACTOR
445
+ });
446
+ }
447
+ }), createComponent(For, {
448
+ get each() {
449
+ return props.grabbedOverlays ?? [];
450
+ },
451
+ children: (overlay) => createComponent(Overlay, {
452
+ variant: "grabbed",
453
+ get bounds() {
454
+ return overlay.bounds;
455
+ },
456
+ visible: true
457
+ })
458
+ }), createComponent(For, {
459
+ get each() {
460
+ return props.successLabels ?? [];
461
+ },
462
+ children: (label) => createComponent(Label, {
463
+ variant: "success",
464
+ get text() {
465
+ return label.text;
466
+ },
467
+ get x() {
468
+ return label.x;
469
+ },
470
+ get y() {
471
+ return label.y;
472
+ },
473
+ visible: true,
474
+ zIndex: 2147483648
475
+ })
476
+ }), createComponent(Show, {
477
+ get when() {
478
+ return memo(() => !!(props.labelVisible && props.labelVariant && props.labelText && props.labelX !== void 0))() && props.labelY !== void 0;
479
+ },
480
+ get children() {
481
+ return createComponent(Label, {
482
+ get variant() {
483
+ return props.labelVariant;
484
+ },
485
+ get text() {
486
+ return props.labelText;
487
+ },
488
+ get x() {
489
+ return props.labelX;
490
+ },
491
+ get y() {
492
+ return props.labelY;
493
+ },
494
+ get visible() {
495
+ return props.labelVisible;
496
+ },
497
+ get zIndex() {
498
+ return props.labelZIndex;
499
+ }
500
+ });
501
+ }
502
+ }), createComponent(Show, {
503
+ get when() {
504
+ return memo(() => !!(props.progressVisible && props.progress !== void 0 && props.mouseX !== void 0))() && props.mouseY !== void 0;
505
+ },
506
+ get children() {
507
+ return createComponent(ProgressIndicator, {
508
+ get progress() {
509
+ return props.progress;
510
+ },
511
+ get mouseX() {
512
+ return props.mouseX;
513
+ },
514
+ get mouseY() {
515
+ return props.mouseY;
516
+ },
517
+ get visible() {
518
+ return props.progressVisible;
519
+ }
520
+ });
521
+ }
522
+ })];
435
523
  };
436
524
  instrument({
437
525
  onCommitFiberRoot(_, fiberRoot) {
438
526
  _fiberRoots.add(fiberRoot);
439
527
  }
440
528
  });
529
+ var isValidSource = (source) => {
530
+ const fileName = source.fileName;
531
+ if (fileName.includes("node_modules")) return false;
532
+ if (fileName.includes("/dist/")) return false;
533
+ if (fileName.includes("/.next/")) return false;
534
+ if (fileName.includes("/build/")) return false;
535
+ if (fileName.includes("webpack-internal:")) return false;
536
+ return true;
537
+ };
441
538
  var getSourceTrace = async (element) => {
442
539
  const fiber = getFiberFromHostInstance(element);
443
540
  if (!fiber) return null;
@@ -447,7 +544,7 @@ var getSourceTrace = async (element) => {
447
544
  Number.MAX_SAFE_INTEGER
448
545
  );
449
546
  if (!sources) return null;
450
- return sources;
547
+ return sources.filter(isValidSource);
451
548
  };
452
549
  var getHTMLSnippet = (element) => {
453
550
  const semanticTags = /* @__PURE__ */ new Set([
@@ -648,7 +745,19 @@ var getHTMLSnippet = (element) => {
648
745
  };
649
746
 
650
747
  // src/utils/copy-content.ts
748
+ var waitForFocus = () => {
749
+ if (document.hasFocus()) return Promise.resolve();
750
+ return new Promise((resolve) => {
751
+ const onFocus = () => {
752
+ window.removeEventListener("focus", onFocus);
753
+ resolve();
754
+ };
755
+ window.addEventListener("focus", onFocus);
756
+ window.focus();
757
+ });
758
+ };
651
759
  var copyContent = async (content) => {
760
+ await waitForFocus();
652
761
  try {
653
762
  if (Array.isArray(content)) {
654
763
  if (!navigator?.clipboard?.write) {
@@ -710,218 +819,457 @@ var copyContentFallback = (content) => {
710
819
  }
711
820
  };
712
821
 
713
- // src/core.ts
822
+ // src/core.tsx
714
823
  var init = (rawOptions) => {
715
824
  const options = {
716
825
  enabled: true,
717
- keyHoldDuration: 500,
826
+ keyHoldDuration: 300,
718
827
  ...rawOptions
719
828
  };
720
829
  if (options.enabled === false) {
721
830
  return;
722
831
  }
723
- let holdTimer = null;
724
- let isHoldingKeys = false;
725
- let overlayRoot = null;
726
- let selectionOverlay = null;
727
- let renderFrameId = null;
728
- let isActive = false;
729
- let isCopying = false;
730
- let hoveredElement = null;
731
- let lastGrabbedElement = null;
732
- let mouseX = -1e3;
733
- let mouseY = -1e3;
734
- const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
735
- const getElementAtPosition = (x, y) => {
736
- const elementsAtPoint = document.elementsFromPoint(x, y);
737
- for (const candidateElement of elementsAtPoint) {
738
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
739
- continue;
832
+ return createRoot((dispose) => {
833
+ const [isHoldingKeys, setIsHoldingKeys] = createSignal(false);
834
+ const [mouseX, setMouseX] = createSignal(-1e3);
835
+ const [mouseY, setMouseY] = createSignal(-1e3);
836
+ const [isDragging, setIsDragging] = createSignal(false);
837
+ const [dragStartX, setDragStartX] = createSignal(-1e3);
838
+ const [dragStartY, setDragStartY] = createSignal(-1e3);
839
+ const [isCopying, setIsCopying] = createSignal(false);
840
+ const [lastGrabbedElement, setLastGrabbedElement] = createSignal(null);
841
+ const [progressStartTime, setProgressStartTime] = createSignal(null);
842
+ const [progressTick, setProgressTick] = createSignal(0);
843
+ const [grabbedOverlays, setGrabbedOverlays] = createSignal([]);
844
+ const [successLabels, setSuccessLabels] = createSignal([]);
845
+ const [isActivated, setIsActivated] = createSignal(false);
846
+ const [showProgressIndicator, setShowProgressIndicator] = createSignal(false);
847
+ let holdTimerId = null;
848
+ let progressAnimationId = null;
849
+ let progressDelayTimerId = null;
850
+ const isOverlayActive = createMemo(() => isActivated() && !isCopying());
851
+ const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
852
+ const getElementAtPosition = (x, y) => {
853
+ const elementsAtPoint = document.elementsFromPoint(x, y);
854
+ for (const candidateElement of elementsAtPoint) {
855
+ if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
856
+ continue;
857
+ }
858
+ const computedStyle = window.getComputedStyle(candidateElement);
859
+ if (!isElementVisible(candidateElement, computedStyle)) {
860
+ continue;
861
+ }
862
+ if (computedStyle.pointerEvents === "none") {
863
+ continue;
864
+ }
865
+ return candidateElement;
740
866
  }
741
- const computedStyle = window.getComputedStyle(candidateElement);
742
- if (!isElementVisible(candidateElement, computedStyle)) {
743
- continue;
867
+ return null;
868
+ };
869
+ const getElementsInMarquee = (marqueeRect) => {
870
+ const elements = [];
871
+ const allElements = Array.from(document.querySelectorAll("*"));
872
+ const marqueeLeft = marqueeRect.x;
873
+ const marqueeTop = marqueeRect.y;
874
+ const marqueeRight = marqueeRect.x + marqueeRect.width;
875
+ const marqueeBottom = marqueeRect.y + marqueeRect.height;
876
+ for (const candidateElement of allElements) {
877
+ if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
878
+ continue;
879
+ }
880
+ const computedStyle = window.getComputedStyle(candidateElement);
881
+ if (!isElementVisible(candidateElement, computedStyle)) {
882
+ continue;
883
+ }
884
+ if (computedStyle.pointerEvents === "none") {
885
+ continue;
886
+ }
887
+ const rect = candidateElement.getBoundingClientRect();
888
+ const elementLeft = rect.left;
889
+ const elementTop = rect.top;
890
+ const elementRight = rect.left + rect.width;
891
+ const elementBottom = rect.top + rect.height;
892
+ const intersectionLeft = Math.max(marqueeLeft, elementLeft);
893
+ const intersectionTop = Math.max(marqueeTop, elementTop);
894
+ const intersectionRight = Math.min(marqueeRight, elementRight);
895
+ const intersectionBottom = Math.min(marqueeBottom, elementBottom);
896
+ const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
897
+ const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
898
+ const intersectionArea = intersectionWidth * intersectionHeight;
899
+ const elementArea = Math.max(0, rect.width * rect.height);
900
+ const COVERAGE_THRESHOLD = 0.75;
901
+ const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= COVERAGE_THRESHOLD;
902
+ if (hasMajorityCoverage) {
903
+ elements.push(candidateElement);
904
+ }
744
905
  }
745
- return candidateElement;
746
- }
747
- return null;
748
- };
749
- const wrapInReferencedElement = (content) => `
906
+ return elements;
907
+ };
908
+ const getElementsInMarqueeLoose = (marqueeRect) => {
909
+ const elements = [];
910
+ const allElements = Array.from(document.querySelectorAll("*"));
911
+ const marqueeLeft = marqueeRect.x;
912
+ const marqueeTop = marqueeRect.y;
913
+ const marqueeRight = marqueeRect.x + marqueeRect.width;
914
+ const marqueeBottom = marqueeRect.y + marqueeRect.height;
915
+ for (const candidateElement of allElements) {
916
+ if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
917
+ continue;
918
+ }
919
+ const tag = (candidateElement.tagName || "").toUpperCase();
920
+ if (tag === "HTML" || tag === "BODY") continue;
921
+ const computedStyle = window.getComputedStyle(candidateElement);
922
+ if (!isElementVisible(candidateElement, computedStyle)) {
923
+ continue;
924
+ }
925
+ if (computedStyle.pointerEvents === "none") {
926
+ continue;
927
+ }
928
+ const rect = candidateElement.getBoundingClientRect();
929
+ const elementLeft = rect.left;
930
+ const elementTop = rect.top;
931
+ const elementRight = rect.left + rect.width;
932
+ const elementBottom = rect.top + rect.height;
933
+ const intersects = elementLeft < marqueeRight && elementRight > marqueeLeft && elementTop < marqueeBottom && elementBottom > marqueeTop;
934
+ if (intersects) {
935
+ elements.push(candidateElement);
936
+ }
937
+ }
938
+ return elements;
939
+ };
940
+ const wrapInReferencedElement = (content) => `
750
941
 
751
942
  <referenced_element>
752
943
  ${content}
753
944
  </referenced_element>`;
754
- const handleCopy = async (targetElement) => {
755
- const tagName = (targetElement.tagName || "").toLowerCase();
756
- const elementBounds = targetElement.getBoundingClientRect();
757
- const showSuccessIndicator = updateLabelToProcessing(
758
- overlayRoot,
759
- elementBounds.left,
760
- elementBounds.top
761
- );
762
- try {
763
- const elementHtml = getHTMLSnippet(targetElement);
764
- await copyContent(wrapInReferencedElement(elementHtml));
765
- const componentStackTrace = await getSourceTrace(targetElement);
766
- if (componentStackTrace?.length) {
767
- const formattedStackTrace = componentStackTrace.map(
768
- (source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`
769
- ).join("\n");
770
- await copyContent(
771
- wrapInReferencedElement(
772
- `${elementHtml}
945
+ const wrapInReferencedElements = (content) => `
946
+
947
+ <referenced_elements>
948
+ ${content}
949
+ </referenced_elements>`;
950
+ const addGrabbedOverlay = (bounds) => {
951
+ const id = `grabbed-${Date.now()}-${Math.random()}`;
952
+ setGrabbedOverlays((prev) => [...prev, {
953
+ id,
954
+ bounds
955
+ }]);
956
+ setTimeout(() => {
957
+ setGrabbedOverlays((prev) => prev.filter((overlay) => overlay.id !== id));
958
+ }, 300);
959
+ };
960
+ const addSuccessLabel = (text, x, y) => {
961
+ const id = `success-${Date.now()}-${Math.random()}`;
962
+ setSuccessLabels((prev) => [...prev, {
963
+ id,
964
+ text,
965
+ x,
966
+ y
967
+ }]);
968
+ setTimeout(() => {
969
+ setSuccessLabels((prev) => prev.filter((label) => label.id !== id));
970
+ }, 1700);
971
+ };
972
+ const handleCopy = async (targetElement2) => {
973
+ const elementBounds = targetElement2.getBoundingClientRect();
974
+ const tagName = (targetElement2.tagName || "").toLowerCase();
975
+ addGrabbedOverlay({
976
+ borderRadius: window.getComputedStyle(targetElement2).borderRadius || "0px",
977
+ height: elementBounds.height,
978
+ transform: window.getComputedStyle(targetElement2).transform || "none",
979
+ width: elementBounds.width,
980
+ x: elementBounds.left,
981
+ y: elementBounds.top
982
+ });
983
+ try {
984
+ const elementHtml = getHTMLSnippet(targetElement2);
985
+ await copyContent(wrapInReferencedElement(elementHtml));
986
+ const componentStackTrace = await getSourceTrace(targetElement2);
987
+ if (componentStackTrace?.length) {
988
+ const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
989
+ await copyContent(wrapInReferencedElement(`${elementHtml}
773
990
 
774
991
  Component owner stack:
775
- ${formattedStackTrace}`
776
- )
777
- );
992
+ ${formattedStackTrace}`));
993
+ }
994
+ } catch {
778
995
  }
779
- showSuccessIndicator(tagName);
780
- } catch {
781
- showSuccessIndicator(tagName);
782
- }
783
- };
784
- const hideOverlayAndLabel = () => {
785
- selectionOverlay?.hide();
786
- if (!isCopying) hideLabel();
787
- };
788
- const handleRender = () => {
789
- if (!isActive) {
790
- hideOverlayAndLabel();
791
- hoveredElement = null;
792
- lastGrabbedElement = null;
793
- return;
794
- }
795
- if (isCopying) return;
796
- const targetElement = getElementAtPosition(mouseX, mouseY);
797
- if (!targetElement) {
798
- hideOverlayAndLabel();
799
- hoveredElement = null;
800
- return;
801
- }
802
- if (lastGrabbedElement) {
803
- if (targetElement !== lastGrabbedElement) {
804
- lastGrabbedElement = null;
805
- } else {
806
- hideOverlayAndLabel();
807
- hoveredElement = targetElement;
996
+ addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
997
+ };
998
+ const handleMultipleCopy = async (targetElements) => {
999
+ if (targetElements.length === 0) return;
1000
+ let minX = Infinity;
1001
+ let minY = Infinity;
1002
+ for (const element of targetElements) {
1003
+ const elementBounds = element.getBoundingClientRect();
1004
+ minX = Math.min(minX, elementBounds.left);
1005
+ minY = Math.min(minY, elementBounds.top);
1006
+ addGrabbedOverlay({
1007
+ borderRadius: window.getComputedStyle(element).borderRadius || "0px",
1008
+ height: elementBounds.height,
1009
+ transform: window.getComputedStyle(element).transform || "none",
1010
+ width: elementBounds.width,
1011
+ x: elementBounds.left,
1012
+ y: elementBounds.top
1013
+ });
1014
+ }
1015
+ try {
1016
+ const elementSnippets = [];
1017
+ for (const element of targetElements) {
1018
+ const elementHtml = getHTMLSnippet(element);
1019
+ const componentStackTrace = await getSourceTrace(element);
1020
+ if (componentStackTrace?.length) {
1021
+ const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
1022
+ elementSnippets.push(`${elementHtml}
1023
+
1024
+ Component owner stack:
1025
+ ${formattedStackTrace}`);
1026
+ } else {
1027
+ elementSnippets.push(elementHtml);
1028
+ }
1029
+ }
1030
+ const combinedContent = elementSnippets.join("\n\n---\n\n");
1031
+ await copyContent(wrapInReferencedElements(combinedContent));
1032
+ } catch {
1033
+ }
1034
+ addSuccessLabel(`${targetElements.length} elements`, minX, minY);
1035
+ };
1036
+ const targetElement = createMemo(() => {
1037
+ if (!isOverlayActive() || isDragging()) return null;
1038
+ return getElementAtPosition(mouseX(), mouseY());
1039
+ });
1040
+ const selectionBounds = createMemo(() => {
1041
+ const element = targetElement() ?? lastGrabbedElement();
1042
+ if (!element) return void 0;
1043
+ const elementBounds = element.getBoundingClientRect();
1044
+ const computedStyle = window.getComputedStyle(element);
1045
+ return {
1046
+ borderRadius: computedStyle.borderRadius || "0px",
1047
+ height: elementBounds.height,
1048
+ transform: computedStyle.transform || "none",
1049
+ width: elementBounds.width,
1050
+ x: elementBounds.left,
1051
+ y: elementBounds.top
1052
+ };
1053
+ });
1054
+ const marqueeBounds = createMemo(() => {
1055
+ if (!isDragging()) return void 0;
1056
+ const marqueeX = Math.min(dragStartX(), mouseX());
1057
+ const marqueeY = Math.min(dragStartY(), mouseY());
1058
+ const marqueeWidth = Math.abs(mouseX() - dragStartX());
1059
+ const marqueeHeight = Math.abs(mouseY() - dragStartY());
1060
+ return {
1061
+ borderRadius: "0px",
1062
+ height: marqueeHeight,
1063
+ transform: "none",
1064
+ width: marqueeWidth,
1065
+ x: marqueeX,
1066
+ y: marqueeY
1067
+ };
1068
+ });
1069
+ const labelText = createMemo(() => {
1070
+ const element = targetElement();
1071
+ if (!element) return "";
1072
+ const tagName = (element.tagName || "").toLowerCase();
1073
+ return tagName ? `<${tagName}>` : "<element>";
1074
+ });
1075
+ const labelPosition = createMemo(() => {
1076
+ const element = targetElement() ?? lastGrabbedElement();
1077
+ if (element) {
1078
+ const rect = element.getBoundingClientRect();
1079
+ return {
1080
+ x: rect.left,
1081
+ y: rect.top
1082
+ };
1083
+ }
1084
+ return {
1085
+ x: mouseX(),
1086
+ y: mouseY()
1087
+ };
1088
+ });
1089
+ createEffect(() => {
1090
+ const current = targetElement();
1091
+ const last = lastGrabbedElement();
1092
+ if (last && current && last !== current) {
1093
+ setLastGrabbedElement(null);
1094
+ }
1095
+ });
1096
+ const progress = createMemo(() => {
1097
+ const startTime = progressStartTime();
1098
+ progressTick();
1099
+ if (startTime === null) return 0;
1100
+ const elapsed = Date.now() - startTime;
1101
+ return Math.min(elapsed / options.keyHoldDuration, 1);
1102
+ });
1103
+ const startProgressAnimation = () => {
1104
+ setProgressStartTime(Date.now());
1105
+ setShowProgressIndicator(false);
1106
+ progressDelayTimerId = window.setTimeout(() => {
1107
+ setShowProgressIndicator(true);
1108
+ progressDelayTimerId = null;
1109
+ }, 150);
1110
+ const animateProgress = () => {
1111
+ if (progressStartTime() === null) return;
1112
+ setProgressTick((t) => t + 1);
1113
+ const currentProgress = progress();
1114
+ if (currentProgress < 1) {
1115
+ progressAnimationId = requestAnimationFrame(animateProgress);
1116
+ }
1117
+ };
1118
+ animateProgress();
1119
+ };
1120
+ const stopProgressAnimation = () => {
1121
+ if (progressAnimationId !== null) {
1122
+ cancelAnimationFrame(progressAnimationId);
1123
+ progressAnimationId = null;
1124
+ }
1125
+ if (progressDelayTimerId !== null) {
1126
+ window.clearTimeout(progressDelayTimerId);
1127
+ progressDelayTimerId = null;
1128
+ }
1129
+ setProgressStartTime(null);
1130
+ setShowProgressIndicator(false);
1131
+ };
1132
+ const activateOverlay = () => {
1133
+ stopProgressAnimation();
1134
+ setIsActivated(true);
1135
+ };
1136
+ const abortController = new AbortController();
1137
+ const signal = abortController.signal;
1138
+ window.addEventListener("keydown", (event) => {
1139
+ if (event.key === "Escape" && isHoldingKeys()) {
1140
+ setIsHoldingKeys(false);
1141
+ setIsActivated(false);
1142
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1143
+ stopProgressAnimation();
808
1144
  return;
809
1145
  }
810
- }
811
- hoveredElement = targetElement;
812
- const elementBounds = targetElement.getBoundingClientRect();
813
- const computedStyle = window.getComputedStyle(targetElement);
814
- selectionOverlay?.update({
815
- borderRadius: computedStyle.borderRadius || "0px",
816
- height: elementBounds.height,
817
- transform: computedStyle.transform || "none",
818
- width: elementBounds.width,
819
- x: elementBounds.left,
820
- y: elementBounds.top
1146
+ if (isKeyboardEventTriggeredByInput(event)) return;
1147
+ if (isTargetKeyCombination(event) && !isHoldingKeys()) {
1148
+ setIsHoldingKeys(true);
1149
+ startProgressAnimation();
1150
+ holdTimerId = window.setTimeout(() => {
1151
+ activateOverlay();
1152
+ options.onActivate?.();
1153
+ }, options.keyHoldDuration);
1154
+ }
1155
+ }, {
1156
+ signal
821
1157
  });
822
- if (!selectionOverlay?.isVisible()) selectionOverlay?.show();
823
- showLabel(
824
- overlayRoot,
825
- elementBounds.left,
826
- elementBounds.top,
827
- (targetElement.tagName || "").toLowerCase()
828
- );
829
- };
830
- const scheduleRender = () => {
831
- if (renderFrameId !== null) return;
832
- renderFrameId = requestAnimationFrame(() => {
833
- renderFrameId = null;
834
- handleRender();
1158
+ window.addEventListener("keyup", (event) => {
1159
+ if (isHoldingKeys() && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
1160
+ setIsHoldingKeys(false);
1161
+ setIsActivated(false);
1162
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1163
+ stopProgressAnimation();
1164
+ }
1165
+ }, {
1166
+ signal
835
1167
  });
836
- };
837
- const continuousRender = () => {
838
- scheduleRender();
839
- requestAnimationFrame(continuousRender);
840
- };
841
- const handleMouseMove = (event) => {
842
- mouseX = event.clientX;
843
- mouseY = event.clientY;
844
- scheduleRender();
845
- };
846
- const handleVisibilityChange = () => {
847
- if (document.hidden) {
848
- cleanupGrabbedIndicators();
849
- hideLabel();
850
- }
851
- };
852
- const handleSelectionClick = () => {
853
- if (!hoveredElement || isCopying) return;
854
- isCopying = true;
855
- lastGrabbedElement = hoveredElement;
856
- const targetElement = hoveredElement;
857
- const computedStyle = window.getComputedStyle(targetElement);
858
- const elementBounds = targetElement.getBoundingClientRect();
859
- createGrabbedOverlay(overlayRoot, {
860
- borderRadius: computedStyle.borderRadius || "0px",
861
- height: elementBounds.height,
862
- transform: computedStyle.transform || "none",
863
- width: elementBounds.width,
864
- x: elementBounds.left,
865
- y: elementBounds.top
1168
+ window.addEventListener("mousemove", (event) => {
1169
+ setMouseX(event.clientX);
1170
+ setMouseY(event.clientY);
1171
+ }, {
1172
+ signal
866
1173
  });
867
- void handleCopy(targetElement).finally(() => {
868
- isCopying = false;
869
- isActive = isHoldingKeys;
1174
+ window.addEventListener("mousedown", (event) => {
1175
+ if (!isOverlayActive() || isCopying()) return;
1176
+ setIsDragging(true);
1177
+ setDragStartX(event.clientX);
1178
+ setDragStartY(event.clientY);
1179
+ }, {
1180
+ signal
870
1181
  });
871
- };
872
- const activateOverlay = () => {
873
- if (!overlayRoot) {
874
- overlayRoot = mountRoot();
875
- selectionOverlay = createSelectionOverlay(
876
- overlayRoot,
877
- handleSelectionClick
878
- );
879
- continuousRender();
880
- }
881
- isActive = true;
882
- handleRender();
883
- };
884
- const handleKeyDown = (event) => {
885
- if (event.key === "Escape" && isHoldingKeys) {
886
- isHoldingKeys = false;
887
- if (holdTimer) window.clearTimeout(holdTimer);
888
- isActive = false;
889
- return;
890
- }
891
- if (isKeyboardEventTriggeredByInput(event)) return;
892
- if (isTargetKeyCombination(event) && !isHoldingKeys) {
893
- isHoldingKeys = true;
894
- holdTimer = window.setTimeout(() => {
895
- activateOverlay();
896
- options.onActivate?.();
897
- }, options.keyHoldDuration);
898
- }
899
- };
900
- const handleKeyUp = (event) => {
901
- if (isHoldingKeys && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
902
- isHoldingKeys = false;
903
- if (holdTimer) window.clearTimeout(holdTimer);
904
- isActive = false;
905
- }
906
- };
907
- window.addEventListener("keydown", handleKeyDown);
908
- window.addEventListener("keyup", handleKeyUp);
909
- window.addEventListener("mousemove", handleMouseMove);
910
- window.addEventListener("scroll", scheduleRender, true);
911
- window.addEventListener("resize", scheduleRender);
912
- document.addEventListener("visibilitychange", handleVisibilityChange);
913
- return () => {
914
- window.removeEventListener("keydown", handleKeyDown);
915
- window.removeEventListener("keyup", handleKeyUp);
916
- window.removeEventListener("mousemove", handleMouseMove);
917
- window.removeEventListener("scroll", scheduleRender, true);
918
- window.removeEventListener("resize", scheduleRender);
919
- document.removeEventListener("visibilitychange", handleVisibilityChange);
920
- if (holdTimer) window.clearTimeout(holdTimer);
921
- if (renderFrameId) cancelAnimationFrame(renderFrameId);
922
- cleanupGrabbedIndicators();
923
- hideLabel();
924
- };
1182
+ window.addEventListener("mouseup", (event) => {
1183
+ if (!isDragging()) return;
1184
+ const dragDistanceX = Math.abs(event.clientX - dragStartX());
1185
+ const dragDistanceY = Math.abs(event.clientY - dragStartY());
1186
+ const DRAG_THRESHOLD = 5;
1187
+ const wasDrag = dragDistanceX > DRAG_THRESHOLD || dragDistanceY > DRAG_THRESHOLD;
1188
+ setIsDragging(false);
1189
+ if (wasDrag) {
1190
+ const marqueeX = Math.min(dragStartX(), event.clientX);
1191
+ const marqueeY = Math.min(dragStartY(), event.clientY);
1192
+ const marqueeWidth = Math.abs(event.clientX - dragStartX());
1193
+ const marqueeHeight = Math.abs(event.clientY - dragStartY());
1194
+ const elements = getElementsInMarquee({
1195
+ x: marqueeX,
1196
+ y: marqueeY,
1197
+ width: marqueeWidth,
1198
+ height: marqueeHeight
1199
+ });
1200
+ if (elements.length > 0) {
1201
+ setIsCopying(true);
1202
+ void handleMultipleCopy(elements).finally(() => {
1203
+ setIsCopying(false);
1204
+ });
1205
+ } else {
1206
+ const fallbackElements = getElementsInMarqueeLoose({
1207
+ x: marqueeX,
1208
+ y: marqueeY,
1209
+ width: marqueeWidth,
1210
+ height: marqueeHeight
1211
+ });
1212
+ if (fallbackElements.length > 0) {
1213
+ setIsCopying(true);
1214
+ void handleMultipleCopy(fallbackElements).finally(() => {
1215
+ setIsCopying(false);
1216
+ });
1217
+ }
1218
+ }
1219
+ } else {
1220
+ const element = getElementAtPosition(event.clientX, event.clientY);
1221
+ if (!element) return;
1222
+ setIsCopying(true);
1223
+ setLastGrabbedElement(element);
1224
+ void handleCopy(element).finally(() => {
1225
+ setIsCopying(false);
1226
+ });
1227
+ }
1228
+ }, {
1229
+ signal
1230
+ });
1231
+ window.addEventListener("scroll", () => {
1232
+ }, {
1233
+ signal,
1234
+ capture: true
1235
+ });
1236
+ window.addEventListener("resize", () => {
1237
+ }, {
1238
+ signal
1239
+ });
1240
+ document.addEventListener("visibilitychange", () => {
1241
+ if (document.hidden) {
1242
+ setGrabbedOverlays([]);
1243
+ }
1244
+ }, {
1245
+ signal
1246
+ });
1247
+ onCleanup(() => {
1248
+ abortController.abort();
1249
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1250
+ stopProgressAnimation();
1251
+ });
1252
+ const overlayRoot = mountRoot();
1253
+ const overlayProps = createMemo(() => ({
1254
+ selectionVisible: isOverlayActive() && !isDragging() && !!selectionBounds(),
1255
+ selectionBounds: selectionBounds(),
1256
+ marqueeVisible: isOverlayActive() && isDragging(),
1257
+ marqueeBounds: marqueeBounds(),
1258
+ grabbedOverlays: grabbedOverlays(),
1259
+ successLabels: successLabels(),
1260
+ labelVariant: isCopying() ? "processing" : "hover",
1261
+ labelText: labelText(),
1262
+ labelX: labelPosition().x,
1263
+ labelY: labelPosition().y,
1264
+ labelVisible: isOverlayActive() && !isDragging() && !!targetElement() || isCopying(),
1265
+ progressVisible: isHoldingKeys() && showProgressIndicator(),
1266
+ progress: progress(),
1267
+ mouseX: mouseX(),
1268
+ mouseY: mouseY()
1269
+ }));
1270
+ render(() => createComponent(ReactGrabOverlay, mergeProps(overlayProps)), overlayRoot);
1271
+ return dispose;
1272
+ });
925
1273
  };
926
1274
 
927
1275
  // src/index.ts