Skip to content

Commit febad9d

Browse files
aidenybaiclaude
andauthored
Refactor: Extract modules from core/index.tsx, eliminate silent error swallowing (#250)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 92509de commit febad9d

File tree

13 files changed

+148
-446
lines changed

13 files changed

+148
-446
lines changed

README.md

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -193,122 +193,6 @@ actions: [
193193

194194
See [`packages/react-grab/src/types.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) for the full `Plugin`, `PluginHooks`, and `PluginConfig` interfaces.
195195

196-
## Primitives
197-
198-
Use primitives to build your own element selector from scratch. Unlike plugins, primitives are standalone utility functions that don't depend on React Grab being initialized.
199-
200-
If you're using primitives to build a custom UI and don't want the default React Grab overlay, disable auto-initialization before importing `react-grab`:
201-
202-
```html
203-
<script>
204-
window.__REACT_GRAB_DISABLED__ = true;
205-
</script>
206-
```
207-
208-
Here's a simple example of how to build your own element selector with hover highlight and one-click inspection:
209-
210-
```bash
211-
npm install react-grab@latest
212-
```
213-
214-
```tsx
215-
import { useState } from "react";
216-
import {
217-
getElementContext,
218-
freeze,
219-
unfreeze,
220-
openFile,
221-
type ReactGrabElementContext,
222-
} from "react-grab/primitives";
223-
224-
const useElementSelector = (
225-
onSelect: (context: ReactGrabElementContext) => void,
226-
) => {
227-
const [isActive, setIsActive] = useState(false);
228-
229-
const startSelecting = () => {
230-
setIsActive(true);
231-
232-
const highlightOverlay = document.createElement("div");
233-
Object.assign(highlightOverlay.style, {
234-
position: "fixed",
235-
pointerEvents: "none",
236-
zIndex: "999999",
237-
border: "2px solid #3b82f6",
238-
transition: "all 75ms ease-out",
239-
display: "none",
240-
});
241-
document.body.appendChild(highlightOverlay);
242-
243-
const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
244-
highlightOverlay.style.display = "none";
245-
const target = document.elementFromPoint(clientX, clientY);
246-
if (!target) return;
247-
const { top, left, width, height } = target.getBoundingClientRect();
248-
Object.assign(highlightOverlay.style, {
249-
top: `${top}px`,
250-
left: `${left}px`,
251-
width: `${width}px`,
252-
height: `${height}px`,
253-
display: "block",
254-
});
255-
};
256-
257-
const handleClick = async ({ clientX, clientY }: MouseEvent) => {
258-
highlightOverlay.style.display = "none";
259-
const target = document.elementFromPoint(clientX, clientY);
260-
teardown();
261-
if (!target) return;
262-
freeze();
263-
onSelect(await getElementContext(target));
264-
unfreeze();
265-
};
266-
267-
const teardown = () => {
268-
document.removeEventListener("mousemove", handleMouseMove);
269-
document.removeEventListener("click", handleClick, true);
270-
highlightOverlay.remove();
271-
setIsActive(false);
272-
};
273-
274-
document.addEventListener("mousemove", handleMouseMove);
275-
document.addEventListener("click", handleClick, true);
276-
};
277-
278-
return { isActive, startSelecting };
279-
};
280-
281-
const ElementSelector = () => {
282-
const [context, setContext] = useState<ReactGrabElementContext | null>(null);
283-
const selector = useElementSelector(setContext);
284-
285-
return (
286-
<div>
287-
<button onClick={selector.startSelecting} disabled={selector.isActive}>
288-
{selector.isActive ? "Selecting…" : "Select Element"}
289-
</button>
290-
{context && (
291-
<div>
292-
<p>Component: {context.componentName}</p>
293-
<p>Selector: {context.selector}</p>
294-
<pre>{context.stackString}</pre>
295-
<button
296-
onClick={() => {
297-
const frame = context.stack[0];
298-
if (frame?.fileName) openFile(frame.fileName, frame.lineNumber);
299-
}}
300-
>
301-
Open in Editor
302-
</button>
303-
</div>
304-
)}
305-
</div>
306-
);
307-
};
308-
```
309-
310-
See [`packages/react-grab/src/primitives.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/primitives.ts) for the full `ReactGrabElementContext`, `getElementContext`, `freeze`, `unfreeze`, and `openFile` primitives.
311-
312196
## Resources & Contributing Back
313197

314198
Want to try it out? Check out [our demo](https://react-grab.com).

packages/grab/README.md

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -193,122 +193,6 @@ actions: [
193193

194194
See [`packages/react-grab/src/types.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) for the full `Plugin`, `PluginHooks`, and `PluginConfig` interfaces.
195195

196-
## Primitives
197-
198-
Use primitives to build your own element selector from scratch. Unlike plugins, primitives are standalone utility functions that don't depend on React Grab being initialized.
199-
200-
If you're using primitives to build a custom UI and don't want the default React Grab overlay, disable auto-initialization before importing `react-grab`:
201-
202-
```html
203-
<script>
204-
window.__REACT_GRAB_DISABLED__ = true;
205-
</script>
206-
```
207-
208-
Here's a simple example of how to build your own element selector with hover highlight and one-click inspection:
209-
210-
```bash
211-
npm install grab@latest
212-
```
213-
214-
```tsx
215-
import { useState } from "react";
216-
import {
217-
getElementContext,
218-
freeze,
219-
unfreeze,
220-
openFile,
221-
type ReactGrabElementContext,
222-
} from "grab/primitives";
223-
224-
const useElementSelector = (
225-
onSelect: (context: ReactGrabElementContext) => void,
226-
) => {
227-
const [isActive, setIsActive] = useState(false);
228-
229-
const startSelecting = () => {
230-
setIsActive(true);
231-
232-
const highlightOverlay = document.createElement("div");
233-
Object.assign(highlightOverlay.style, {
234-
position: "fixed",
235-
pointerEvents: "none",
236-
zIndex: "999999",
237-
border: "2px solid #3b82f6",
238-
transition: "all 75ms ease-out",
239-
display: "none",
240-
});
241-
document.body.appendChild(highlightOverlay);
242-
243-
const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
244-
highlightOverlay.style.display = "none";
245-
const target = document.elementFromPoint(clientX, clientY);
246-
if (!target) return;
247-
const { top, left, width, height } = target.getBoundingClientRect();
248-
Object.assign(highlightOverlay.style, {
249-
top: `${top}px`,
250-
left: `${left}px`,
251-
width: `${width}px`,
252-
height: `${height}px`,
253-
display: "block",
254-
});
255-
};
256-
257-
const handleClick = async ({ clientX, clientY }: MouseEvent) => {
258-
highlightOverlay.style.display = "none";
259-
const target = document.elementFromPoint(clientX, clientY);
260-
teardown();
261-
if (!target) return;
262-
freeze();
263-
onSelect(await getElementContext(target));
264-
unfreeze();
265-
};
266-
267-
const teardown = () => {
268-
document.removeEventListener("mousemove", handleMouseMove);
269-
document.removeEventListener("click", handleClick, true);
270-
highlightOverlay.remove();
271-
setIsActive(false);
272-
};
273-
274-
document.addEventListener("mousemove", handleMouseMove);
275-
document.addEventListener("click", handleClick, true);
276-
};
277-
278-
return { isActive, startSelecting };
279-
};
280-
281-
const ElementSelector = () => {
282-
const [context, setContext] = useState<ReactGrabElementContext | null>(null);
283-
const selector = useElementSelector(setContext);
284-
285-
return (
286-
<div>
287-
<button onClick={selector.startSelecting} disabled={selector.isActive}>
288-
{selector.isActive ? "Selecting…" : "Select Element"}
289-
</button>
290-
{context && (
291-
<div>
292-
<p>Component: {context.componentName}</p>
293-
<p>Selector: {context.selector}</p>
294-
<pre>{context.stackString}</pre>
295-
<button
296-
onClick={() => {
297-
const frame = context.stack[0];
298-
if (frame?.fileName) openFile(frame.fileName, frame.lineNumber);
299-
}}
300-
>
301-
Open in Editor
302-
</button>
303-
</div>
304-
)}
305-
</div>
306-
);
307-
};
308-
```
309-
310-
See [`packages/react-grab/src/primitives.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/primitives.ts) for the full `ReactGrabElementContext`, `getElementContext`, `freeze`, `unfreeze`, and `openFile` primitives.
311-
312196
## Resources & Contributing Back
313197

314198
Want to try it out? Check out [our demo](https://react-grab.com).

packages/react-grab/README.md

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -193,122 +193,6 @@ actions: [
193193

194194
See [`packages/react-grab/src/types.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) for the full `Plugin`, `PluginHooks`, and `PluginConfig` interfaces.
195195

196-
## Primitives
197-
198-
Use primitives to build your own element selector from scratch. Unlike plugins, primitives are standalone utility functions that don't depend on React Grab being initialized.
199-
200-
If you're using primitives to build a custom UI and don't want the default React Grab overlay, disable auto-initialization before importing `react-grab`:
201-
202-
```html
203-
<script>
204-
window.__REACT_GRAB_DISABLED__ = true;
205-
</script>
206-
```
207-
208-
Here's a simple example of how to build your own element selector with hover highlight and one-click inspection:
209-
210-
```bash
211-
npm install react-grab@latest
212-
```
213-
214-
```tsx
215-
import { useState } from "react";
216-
import {
217-
getElementContext,
218-
freeze,
219-
unfreeze,
220-
openFile,
221-
type ReactGrabElementContext,
222-
} from "react-grab/primitives";
223-
224-
const useElementSelector = (
225-
onSelect: (context: ReactGrabElementContext) => void,
226-
) => {
227-
const [isActive, setIsActive] = useState(false);
228-
229-
const startSelecting = () => {
230-
setIsActive(true);
231-
232-
const highlightOverlay = document.createElement("div");
233-
Object.assign(highlightOverlay.style, {
234-
position: "fixed",
235-
pointerEvents: "none",
236-
zIndex: "999999",
237-
border: "2px solid #3b82f6",
238-
transition: "all 75ms ease-out",
239-
display: "none",
240-
});
241-
document.body.appendChild(highlightOverlay);
242-
243-
const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
244-
highlightOverlay.style.display = "none";
245-
const target = document.elementFromPoint(clientX, clientY);
246-
if (!target) return;
247-
const { top, left, width, height } = target.getBoundingClientRect();
248-
Object.assign(highlightOverlay.style, {
249-
top: `${top}px`,
250-
left: `${left}px`,
251-
width: `${width}px`,
252-
height: `${height}px`,
253-
display: "block",
254-
});
255-
};
256-
257-
const handleClick = async ({ clientX, clientY }: MouseEvent) => {
258-
highlightOverlay.style.display = "none";
259-
const target = document.elementFromPoint(clientX, clientY);
260-
teardown();
261-
if (!target) return;
262-
freeze();
263-
onSelect(await getElementContext(target));
264-
unfreeze();
265-
};
266-
267-
const teardown = () => {
268-
document.removeEventListener("mousemove", handleMouseMove);
269-
document.removeEventListener("click", handleClick, true);
270-
highlightOverlay.remove();
271-
setIsActive(false);
272-
};
273-
274-
document.addEventListener("mousemove", handleMouseMove);
275-
document.addEventListener("click", handleClick, true);
276-
};
277-
278-
return { isActive, startSelecting };
279-
};
280-
281-
const ElementSelector = () => {
282-
const [context, setContext] = useState<ReactGrabElementContext | null>(null);
283-
const selector = useElementSelector(setContext);
284-
285-
return (
286-
<div>
287-
<button onClick={selector.startSelecting} disabled={selector.isActive}>
288-
{selector.isActive ? "Selecting…" : "Select Element"}
289-
</button>
290-
{context && (
291-
<div>
292-
<p>Component: {context.componentName}</p>
293-
<p>Selector: {context.selector}</p>
294-
<pre>{context.stackString}</pre>
295-
<button
296-
onClick={() => {
297-
const frame = context.stack[0];
298-
if (frame?.fileName) openFile(frame.fileName, frame.lineNumber);
299-
}}
300-
>
301-
Open in Editor
302-
</button>
303-
</div>
304-
)}
305-
</div>
306-
);
307-
};
308-
```
309-
310-
See [`packages/react-grab/src/primitives.ts`](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/primitives.ts) for the full `ReactGrabElementContext`, `getElementContext`, `freeze`, `unfreeze`, and `openFile` primitives.
311-
312196
## Resources & Contributing Back
313197

314198
Want to try it out? Check out [our demo](https://react-grab.com).

packages/react-grab/e2e/edge-cases.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,10 @@ test.describe("Edge Cases", () => {
324324
test.describe("Viewport Edge Cases", () => {
325325
test("should handle elements outside viewport", async ({ reactGrab }) => {
326326
await reactGrab.activate();
327-
await reactGrab.scrollPage(1000);
328-
await reactGrab.page.waitForTimeout(500);
327+
328+
const footer = reactGrab.page.locator("[data-testid='footer']");
329+
await footer.scrollIntoViewIfNeeded();
330+
await reactGrab.page.waitForTimeout(200);
329331

330332
await reactGrab.hoverElement("[data-testid='footer']");
331333
await reactGrab.waitForSelectionBox();

0 commit comments

Comments
 (0)