Documentation Index Fetch the complete documentation index at: https://mintlify.com/aidenybai/react-grab/llms.txt
Use this file to discover all available pages before exploring further.
Overview
React Grab’s plugin system allows you to extend functionality with custom context menu actions, toolbar items, lifecycle hooks, and theme overrides. Plugins provide a powerful way to integrate React Grab into your development workflow.
Plugin Interface
A plugin is an object that implements the Plugin interface:
Unique identifier for the plugin
Partial theme overrides to customize the visual appearance
Override default options like activationMode, keyHoldDuration, maxContextLines, etc.
Array of context menu actions and/or toolbar menu items
Lifecycle callbacks for events like activation, element selection, copying, etc.
setup
(api: ReactGrabAPI, hooks: ActionContextHooks) => PluginConfig | void
Setup function called when plugin is registered. Receives the full ReactGrabAPI and can return additional configuration or a cleanup function. The React Grab API instance with methods like activate(), deactivate(), copyElement(), getSource(), etc.
Action context hooks for transforming content and handling file operations
Registering a Plugin
Simple Registration
Register a plugin directly on the global API:
window . __REACT_GRAB__ . registerPlugin ({
name: "my-plugin" ,
hooks: {
onElementSelect : ( element ) => {
console . log ( "Selected:" , element . tagName );
},
},
});
React Component Registration
Register inside a useEffect to ensure React Grab is loaded:
import { useEffect } from "react" ;
export function MyPlugin () {
useEffect (() => {
const api = window . __REACT_GRAB__ ;
if ( ! api ) return ;
api . registerPlugin ({
name: "my-plugin" ,
actions: [
{
id: "my-action" ,
label: "My Action" ,
shortcut: "M" ,
onAction : ( context ) => {
console . log ( "Action on:" , context . element );
context . hideContextMenu ();
},
},
],
});
return () => api . unregisterPlugin ( "my-plugin" );
}, []);
return null ;
}
Built-in Plugin Examples
The comment plugin adds context menu and toolbar actions for entering prompt mode:
import type { Plugin } from "react-grab" ;
export const commentPlugin : Plugin = {
name: "comment" ,
setup : ( api ) => ({
actions: [
{
id: "comment" ,
label: "Comment" ,
shortcut: "Enter" ,
onAction : ( context ) => {
context . enterPromptMode ?.();
},
},
{
id: "comment-toolbar" ,
label: "Comment" ,
shortcut: "Enter" ,
target: "toolbar" ,
onAction : () => {
api . comment ();
},
},
],
}),
};
Open Plugin
The open plugin allows opening source files in your editor:
import type { Plugin } from "react-grab" ;
import { openFile } from "./utils/open-file" ;
export const openPlugin : Plugin = {
name: "open" ,
actions: [
{
id: "open" ,
label: "Open" ,
shortcut: "O" ,
enabled : ( context ) => Boolean ( context . filePath ),
onAction : ( context ) => {
if ( ! context . filePath ) return ;
const wasHandled = context . hooks . onOpenFile (
context . filePath ,
context . lineNumber ,
);
if ( ! wasHandled ) {
openFile (
context . filePath ,
context . lineNumber ,
context . hooks . transformOpenFileUrl ,
);
}
context . hideContextMenu ();
context . cleanup ();
},
},
],
};
Copy HTML Plugin
A more complex plugin that uses both hooks and actions:
import type { Plugin } from "react-grab" ;
import { appendStackContext } from "./utils/append-stack-context" ;
import { copyContent } from "./utils/copy-content" ;
export const copyHtmlPlugin : Plugin = {
name: "copy-html" ,
setup : ( api , hooks ) => {
let isPendingSelection = false ;
return {
hooks: {
onElementSelect : ( element ) => {
if ( ! isPendingSelection ) return ;
isPendingSelection = false ;
void Promise . all ([
hooks . transformHtmlContent ( element . outerHTML , [ element ]),
api . getStackContext ( element ),
])
. then (([ transformedHtml , stackContext ]) => {
if ( ! transformedHtml ) return ;
copyContent ( appendStackContext ( transformedHtml , stackContext ));
})
. catch (() => {});
return true ;
},
onDeactivate : () => {
isPendingSelection = false ;
},
cancelPendingToolbarActions : () => {
isPendingSelection = false ;
},
},
actions: [
{
id: "copy-html" ,
label: "Copy HTML" ,
onAction : async ( context ) => {
await context . performWithFeedback ( async () => {
const combinedHtml = context . elements
. map (( element ) => element . outerHTML )
. join ( " \n\n " );
const transformedHtml = await context . hooks . transformHtmlContent (
combinedHtml ,
context . elements ,
);
if ( ! transformedHtml ) return false ;
const stackContext = await api . getStackContext ( context . element );
return copyContent (
appendStackContext ( transformedHtml , stackContext ),
{
componentName: context . componentName ,
tagName: context . tagName ,
},
);
});
},
},
{
id: "copy-html-toolbar" ,
label: "Copy HTML" ,
target: "toolbar" ,
onAction : () => {
isPendingSelection = true ;
api . activate ();
},
},
],
};
},
};
Plugin Hooks
Plugins can listen to various lifecycle events:
Called when React Grab is activated
Called when React Grab is deactivated
onElementHover
(element: Element) => void
Called when hovering over an element
onElementSelect
(element: Element) => boolean | void | Promise<boolean>
Called when an element is selected. Return true to prevent default selection behavior.
onBeforeCopy
(elements: Element[]) => void | Promise<void>
Called before copying elements to clipboard
transformCopyContent
(content: string, elements: Element[]) => string | Promise<string>
Transform the content before it’s copied to clipboard
onAfterCopy
(elements: Element[], success: boolean) => void
Called after copy operation completes
onCopySuccess
(elements: Element[], content: string) => void
Called when copy succeeds
transformHtmlContent
(html: string, elements: Element[]) => string | Promise<string>
Transform HTML content before copying
transformAgentContext
(context: AgentContext, elements: Element[]) => AgentContext | Promise<AgentContext>
Transform the context passed to AI agents
onOpenFile
(filePath: string, lineNumber?: number) => boolean | void
Handle file opening. Return true to prevent default behavior.
transformOpenFileUrl
(url: string, filePath: string, lineNumber?: number) => string
Transform the URL used to open files in editor
PluginConfig
The setup function can return a PluginConfig object:
Cleanup function called when the plugin is unregistered
Complete Example
Here’s a complete plugin that adds a custom analytics tracker:
import type { Plugin } from "react-grab" ;
const analyticsPlugin : Plugin = {
name: "analytics" ,
setup : ( api ) => {
const trackEvent = ( event : string , data : Record < string , any >) => {
console . log ( "Analytics:" , event , data );
// Send to your analytics service
};
return {
hooks: {
onActivate : () => {
trackEvent ( "grab_activated" , {});
},
onElementSelect : ( element ) => {
trackEvent ( "element_selected" , {
tagName: element . tagName ,
});
},
onCopySuccess : ( elements , content ) => {
trackEvent ( "copy_success" , {
elementCount: elements . length ,
contentLength: content . length ,
});
},
},
actions: [
{
id: "track-element" ,
label: "Track Element" ,
shortcut: "T" ,
onAction : ( context ) => {
trackEvent ( "custom_track" , {
tagName: context . tagName ,
componentName: context . componentName ,
filePath: context . filePath ,
});
context . hideContextMenu ();
},
},
],
cleanup : () => {
trackEvent ( "plugin_unregistered" , {});
},
};
},
};
// Register the plugin
window . __REACT_GRAB__ . registerPlugin ( analyticsPlugin );
Best Practices
Unique Names : Always use unique plugin names to avoid conflicts
Cleanup : Return a cleanup function from setup() to clean up resources
Type Safety : Use TypeScript interfaces for better development experience
Error Handling : Handle errors gracefully in async operations
Performance : Avoid heavy operations in frequently called hooks like onElementHover
Unregister : Always unregister plugins when components unmount in React
See Also