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