react-grab 0.1.47-dev.e56fcc1 → 0.1.48-dev.06cda9a

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.
Files changed (42) hide show
  1. package/README.md +18 -59
  2. package/dist/{copy-content-5FzVj_Qo.d.cts → copy-content-CNRKFr6F.d.ts} +100 -42
  3. package/dist/{copy-content-BJATOqZF.d.ts → copy-content-CtYUWe5l.d.cts} +100 -42
  4. package/dist/core/index.cjs +1 -1
  5. package/dist/core/index.d.cts +2 -2
  6. package/dist/core/index.d.ts +2 -2
  7. package/dist/core/index.js +1 -1
  8. package/dist/core-BRRAx8TD.js +14 -0
  9. package/dist/core-C_KU3ZuP.cjs +14 -0
  10. package/dist/errors-BEgowzRA.d.ts +29 -0
  11. package/dist/errors-CTlIFDQs.d.cts +29 -0
  12. package/dist/execute-context-menu-action-BD4Nv4s0.js +9 -0
  13. package/dist/execute-context-menu-action-CcaVL0mJ.cjs +9 -0
  14. package/dist/freeze-updates-CCSp2kR9.js +52 -0
  15. package/dist/freeze-updates-CeUaPRYb.cjs +52 -0
  16. package/dist/{index-jMjUhmbS.d.cts → index-BgTe-uWs.d.cts} +1 -1
  17. package/dist/{index-C_bngsx-.d.ts → index-CdWMNcJF.d.ts} +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.cts +10 -7
  20. package/dist/index.d.ts +10 -7
  21. package/dist/index.global.js +21 -19
  22. package/dist/index.js +1 -1
  23. package/dist/open-file-BhNjSVzK.js +29 -0
  24. package/dist/open-file-NhyWAHag.cjs +20 -0
  25. package/dist/primitives.cjs +2 -2
  26. package/dist/primitives.d.cts +41 -2
  27. package/dist/primitives.d.ts +41 -2
  28. package/dist/primitives.js +2 -2
  29. package/dist/renderer-9otVqSB-.cjs +9 -0
  30. package/dist/renderer-B8kwrqGU.js +9 -0
  31. package/dist/styles.css +1 -1
  32. package/package.json +13 -3
  33. package/dist/action-shortcuts-BqUYwepS.js +0 -9
  34. package/dist/action-shortcuts-Zws2aUUQ.cjs +0 -9
  35. package/dist/copy-content-BdNGJB1g.js +0 -10
  36. package/dist/copy-content-CE1c2qMK.cjs +0 -10
  37. package/dist/core-BMr2NQZo.cjs +0 -14
  38. package/dist/core-GnLSAWJ2.js +0 -14
  39. package/dist/freeze-updates-B9uIB2_2.js +0 -51
  40. package/dist/freeze-updates-BgilkYa3.cjs +0 -51
  41. package/dist/renderer-Bjgsgc2_.cjs +0 -9
  42. package/dist/renderer-bvmApDqj.js +0 -9
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Copy any UI element for your agent.
7
7
 
8
- React Grab points agents to the actual source behind each selection. Agents are [**2× faster**](https://benchmark.react-grab.com/) and more accurate when using React Grab.
8
+ React Grab points agents to the actual source behind each selection. Agents are [**2× faster**](https://react-grab.com/benchmarks) and more accurate when using React Grab.
9
9
 
10
10
  [**Website →**](https://react-grab.com)
11
11
 
@@ -114,72 +114,31 @@ if (process.env.NODE_ENV === "development") {
114
114
  }
115
115
  ```
116
116
 
117
- ## Plugins
117
+ ## Build your own React Grab
118
118
 
119
- Use plugins to extend React Grab's built-in UI with context menu actions, toolbar menu items, lifecycle hooks, and theme overrides.
119
+ Build a custom interface with the selection engine from `react-grab/primitives`. Use its APIs for hit testing, source context, page freezing, clipboard access, and editor navigation.
120
120
 
121
- Register a plugin using the `registerPlugin` export:
121
+ ### Customize hit testing
122
122
 
123
- ```js
124
- import { registerPlugin } from "react-grab";
123
+ Scope hit testing to a container or replace the default element filter with your own rules.
125
124
 
126
- registerPlugin({
127
- name: "my-plugin",
128
- hooks: {
129
- onElementSelect: (element) => {
130
- console.log("Selected:", element.tagName);
131
- },
132
- },
133
- });
134
- ```
135
-
136
- If writing in React, register inside a `useEffect`:
125
+ ```typescript
126
+ import { getElementAtPoint, isElementGrabbable } from "react-grab/primitives";
137
127
 
138
- ```jsx
139
- import { registerPlugin, unregisterPlugin } from "react-grab";
140
-
141
- useEffect(() => {
142
- registerPlugin({
143
- name: "my-plugin",
144
- actions: [
145
- {
146
- id: "my-action",
147
- label: "My Action",
148
- shortcut: "M",
149
- onAction: (context) => {
150
- console.log("Action on:", context.element);
151
- context.hideContextMenu();
152
- },
153
- },
154
- ],
128
+ export const getPickerTarget = (
129
+ event: PointerEvent,
130
+ appElement: Element,
131
+ toolbarElement: Element,
132
+ ): Element | null =>
133
+ getElementAtPoint(event.clientX, event.clientY, {
134
+ container: appElement,
135
+ filter: (candidate) =>
136
+ isElementGrabbable(candidate) &&
137
+ !toolbarElement.contains(candidate),
155
138
  });
156
-
157
- return () => unregisterPlugin("my-plugin");
158
- }, []);
159
- ```
160
-
161
- Actions use a `target` field to control where they appear. Omit `target` (or set `"context-menu"`) for the right-click menu, or set `"toolbar"` for the toolbar dropdown:
162
-
163
- ```js
164
- actions: [
165
- {
166
- id: "inspect",
167
- label: "Inspect",
168
- shortcut: "I",
169
- onAction: (ctx) => console.dir(ctx.element),
170
- },
171
- {
172
- id: "toggle-freeze",
173
- label: "Freeze",
174
- // Only show in the toolbar
175
- target: "toolbar",
176
- isActive: () => isFrozen,
177
- onAction: () => toggleFreeze(),
178
- },
179
- ];
180
139
  ```
181
140
 
182
- 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.
141
+ Add `data-react-grab-ignore` to your picker interface so hit testing skips its subtree.
183
142
 
184
143
  ## Resources & Contributing Back
185
144
 
@@ -6,6 +6,8 @@
6
6
  * This source code is licensed under the MIT license found in the
7
7
  * LICENSE file in the root directory of this source tree.
8
8
  */
9
+ import { ReactNode } from "react";
10
+
9
11
  //#region ../../node_modules/.pnpm/solid-js@1.9.12/node_modules/solid-js/types/reactive/observable.d.ts
10
12
  declare global {
11
13
  interface SymbolConstructor {
@@ -23,6 +25,17 @@ interface Position {
23
25
  x: number;
24
26
  y: number;
25
27
  }
28
+ interface ElementAtPointOptions {
29
+ container?: Element;
30
+ filter?: (element: Element) => boolean;
31
+ }
32
+ interface ElementBounds {
33
+ x: number;
34
+ y: number;
35
+ width: number;
36
+ height: number;
37
+ borderRadius: string;
38
+ }
26
39
  type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? T[P] extends ((...args: unknown[]) => unknown) ? T[P] : DeepPartial<T[P]> : T[P] };
27
40
  interface Theme {
28
41
  /**
@@ -138,11 +151,13 @@ interface AgentContext<T = unknown> {
138
151
  sessionId?: string;
139
152
  }
140
153
  type ActivationMode = "toggle" | "hold";
141
- interface ActionContextHooks {
142
- transformHtmlContent: (html: string, elements: Element[]) => Promise<string>;
154
+ interface OpenFileActionHooks {
143
155
  onOpenFile: (filePath: string, lineNumber?: number) => boolean | void;
144
156
  transformOpenFileUrl: (url: string, filePath: string, lineNumber?: number) => string;
145
157
  }
158
+ interface ActionContextHooks extends OpenFileActionHooks {
159
+ transformHtmlContent: (html: string, elements: Element[]) => Promise<string>;
160
+ }
146
161
  interface ActionContext {
147
162
  element: Element;
148
163
  elements: Element[];
@@ -228,14 +243,12 @@ interface PreviewStyles {
228
243
  interface EditPanelState {
229
244
  element: Element;
230
245
  position: Position;
231
- selectionBounds: OverlayBounds;
232
246
  properties: EditableProperty[];
233
247
  preview: PreviewStyles;
234
248
  filePath?: string;
235
249
  lineNumber?: number;
236
250
  componentName?: string;
237
251
  tagName?: string;
238
- htmlPreview?: string;
239
252
  initialSearchQuery?: string;
240
253
  hasSessionEdits?: boolean;
241
254
  designTokens?: DesignTokenResolver;
@@ -249,34 +262,35 @@ interface ContextMenuAction {
249
262
  enabled?: boolean | ((context: ActionContext) => boolean);
250
263
  onAction: (context: ContextMenuActionContext) => void | Promise<void>;
251
264
  }
252
- interface ArrowNavigationItem {
265
+ interface HierarchyItem {
253
266
  tagName: string;
254
267
  componentName?: string;
268
+ depth: number;
269
+ isLast: boolean;
255
270
  }
256
- interface ArrowNavigationState {
257
- items: ArrowNavigationItem[];
271
+ interface HierarchyState {
272
+ items: HierarchyItem[];
258
273
  activeIndex: number;
259
- isVisible: boolean;
260
274
  }
261
275
  interface PluginHooks {
262
- onActivate?: () => void;
263
- onDeactivate?: () => void;
264
- onElementHover?: (element: Element) => void;
276
+ onActivate?: () => void | Promise<void>;
277
+ onDeactivate?: () => void | Promise<void>;
278
+ onElementHover?: (element: Element) => void | Promise<void>;
265
279
  onElementSelect?: (element: Element) => boolean | void | Promise<boolean>;
266
- onDragStart?: (startX: number, startY: number) => void;
267
- onDragEnd?: (elements: Element[], bounds: DragRect) => void;
280
+ onDragStart?: (startX: number, startY: number) => void | Promise<void>;
281
+ onDragEnd?: (elements: Element[], bounds: DragRect) => void | Promise<void>;
268
282
  onBeforeCopy?: (elements: Element[]) => void | Promise<void>;
269
283
  transformCopyContent?: (content: string, elements: Element[]) => string | Promise<string>;
270
- onAfterCopy?: (elements: Element[], success: boolean) => void;
271
- onCopySuccess?: (elements: Element[], content: string) => void;
272
- onCopyError?: (error: Error) => void;
273
- onStateChange?: (state: ReactGrabState) => void;
274
- onPromptModeChange?: (isPromptMode: boolean, context: PromptModeContext) => void;
275
- onSelectionBox?: (visible: boolean, bounds: OverlayBounds | null, element: Element | null) => void;
276
- onDragBox?: (visible: boolean, bounds: OverlayBounds | null) => void;
277
- onGrabbedBox?: (bounds: OverlayBounds, element: Element) => void;
278
- onElementLabel?: (visible: boolean, variant: ElementLabelVariant, context: ElementLabelContext) => void;
279
- onContextMenu?: (element: Element, position: Position) => void;
284
+ onAfterCopy?: (elements: Element[], success: boolean) => void | Promise<void>;
285
+ onCopySuccess?: (elements: Element[], content: string) => void | Promise<void>;
286
+ onCopyError?: (error: Error) => void | Promise<void>;
287
+ onStateChange?: (state: ReactGrabState) => void | Promise<void>;
288
+ onPromptModeChange?: (isPromptMode: boolean, context: PromptModeContext) => void | Promise<void>;
289
+ onSelectionBox?: (visible: boolean, bounds: OverlayBounds | null, element: Element | null) => void | Promise<void>;
290
+ onDragBox?: (visible: boolean, bounds: OverlayBounds | null) => void | Promise<void>;
291
+ onGrabbedBox?: (bounds: OverlayBounds, element: Element) => void | Promise<void>;
292
+ onElementLabel?: (visible: boolean, variant: ElementLabelVariant, context: ElementLabelContext) => void | Promise<void>;
293
+ onContextMenu?: (element: Element, position: Position) => void | Promise<void>;
280
294
  onOpenFile?: (filePath: string, lineNumber?: number) => boolean | void;
281
295
  transformHtmlContent?: (html: string, elements: Element[]) => string | Promise<string>;
282
296
  transformAgentContext?: (context: AgentContext, elements: Element[]) => AgentContext | Promise<AgentContext>;
@@ -288,7 +302,7 @@ interface PluginConfig {
288
302
  options?: SettableOptions;
289
303
  actions?: ContextMenuAction[];
290
304
  hooks?: PluginHooks;
291
- cleanup?: () => void;
305
+ cleanup?: () => undefined;
292
306
  }
293
307
  interface Plugin {
294
308
  name: string;
@@ -300,6 +314,12 @@ interface Plugin {
300
314
  }
301
315
  interface Options {
302
316
  enabled?: boolean;
317
+ /**
318
+ * Confine React Grab to a single container element instead of the whole page.
319
+ * Hit-testing, the toolbar viewport, and scroll re-anchoring are scoped to it.
320
+ * Used by the demo build to scope the showcase to its card.
321
+ */
322
+ container?: HTMLElement;
303
323
  activationMode?: ActivationMode;
304
324
  keyHoldDuration?: number;
305
325
  allowActivationInsideInput?: boolean;
@@ -331,12 +351,31 @@ interface Options {
331
351
  interface SettableOptions extends Options {
332
352
  enabled?: never;
333
353
  telemetry?: never;
354
+ container?: never;
334
355
  }
335
356
  interface SourceInfo {
336
357
  filePath: string;
337
358
  lineNumber: number | null;
338
359
  componentName: string | null;
339
360
  }
361
+ interface SelectedElementPayload {
362
+ tagName: string;
363
+ id?: string;
364
+ className?: string;
365
+ textContent?: string;
366
+ componentName?: string;
367
+ filePath?: string;
368
+ lineNumber?: number;
369
+ columnNumber?: number;
370
+ }
371
+ interface ElementSelectedEventDetail {
372
+ elements: SelectedElementPayload[];
373
+ }
374
+ declare global {
375
+ interface WindowEventMap {
376
+ "react-grab:element-selected": CustomEvent<ElementSelectedEventDetail>;
377
+ }
378
+ }
340
379
  interface ToolbarState {
341
380
  edge: "top" | "bottom" | "left" | "right";
342
381
  ratio: number;
@@ -360,6 +399,7 @@ interface ReactGrabAPI {
360
399
  getToolbarState: () => ToolbarState | null;
361
400
  setToolbarState: (state: Partial<ToolbarState>) => void;
362
401
  onToolbarStateChange: (callback: (state: ToolbarState) => void) => () => void;
402
+ reset: () => void;
363
403
  dispose: () => void;
364
404
  copyElement: (elements: Element | Element[]) => Promise<boolean>;
365
405
  getSource: (element: Element) => Promise<SourceInfo | null>;
@@ -405,13 +445,19 @@ interface FrozenLabelEntry {
405
445
  bounds: OverlayBounds;
406
446
  mouseX?: number;
407
447
  }
448
+ interface FrozenLabelEntryAccessor {
449
+ read: () => FrozenLabelEntry | null;
450
+ }
451
+ interface SelectionLabelInstanceAccessor {
452
+ read: () => SelectionLabelInstance | null;
453
+ }
408
454
  interface ReactGrabRendererProps {
409
455
  selectionVisible?: boolean;
410
456
  selectionBounds?: OverlayBounds;
411
457
  selectionBoundsMultiple?: OverlayBounds[];
412
458
  selectionShouldSnap?: boolean;
413
459
  selectionElementsCount?: number;
414
- frozenLabelEntries?: FrozenLabelEntry[];
460
+ frozenLabelEntryAccessors?: FrozenLabelEntryAccessor[];
415
461
  pendingShiftPreviewEntry?: FrozenLabelEntry;
416
462
  selectionFilePath?: string;
417
463
  selectionLineNumber?: number;
@@ -419,9 +465,10 @@ interface ReactGrabRendererProps {
419
465
  selectionComponentName?: string;
420
466
  selectionLabelVisible?: boolean;
421
467
  selectionLabelStatus?: SelectionLabelStatus;
422
- selectionArrowNavigationState?: ArrowNavigationState;
423
- onArrowNavigationSelect?: (index: number) => void;
468
+ hierarchyState?: HierarchyState;
469
+ hierarchyMenuPosition?: DropdownAnchor | null;
424
470
  labelInstances?: SelectionLabelInstance[];
471
+ labelInstanceAccessors?: SelectionLabelInstanceAccessor[];
425
472
  dragVisible?: boolean;
426
473
  dragBounds?: OverlayBounds;
427
474
  grabbedBoxes?: Array<{
@@ -434,12 +481,15 @@ interface ReactGrabRendererProps {
434
481
  inputValue?: string;
435
482
  isPromptMode?: boolean;
436
483
  onShowContextMenuInstance?: (instanceId: string) => void;
484
+ onRetryInstance?: (instanceId: string) => void;
485
+ onAcknowledgeErrorInstance?: (instanceId: string) => void;
437
486
  onLabelInstanceHoverChange?: (instanceId: string, isHovered: boolean) => void;
438
487
  onInputChange?: (value: string) => void;
439
488
  onInputSubmit?: () => void;
440
489
  onToggleExpand?: () => void;
441
490
  selectionLabelShakeCount?: number;
442
491
  onConfirmDismiss?: () => void;
492
+ onOpenSelectionFile?: () => void;
443
493
  discardPrompt?: SelectionDiscardPrompt;
444
494
  toolbarVisible?: boolean;
445
495
  isActive?: boolean;
@@ -521,7 +571,7 @@ interface ReactGrabEntry {
521
571
  frames?: ReactGrabStackFrame[];
522
572
  }
523
573
  //#endregion
524
- //#region ../../node_modules/.pnpm/bippy@0.5.42_react@19.2.6/node_modules/bippy/dist/core.d.ts
574
+ //#region ../../node_modules/.pnpm/bippy@0.6.0_react@19.2.6/node_modules/bippy/dist/unsubscribe.d.ts
525
575
  type Flags = number;
526
576
  type Lanes = number;
527
577
  type TypeOfMode = number;
@@ -611,6 +661,14 @@ interface Effect {
611
661
  interface Family {
612
662
  current: unknown;
613
663
  }
664
+ /**
665
+ * React 19 flight metadata for a server component owner (ReactComponentInfo).
666
+ * Unlike client owners it has no `tag`; the owner chain continues via `owner`.
667
+ */
668
+ interface RendererRefreshUpdate {
669
+ staleFamilies: Set<Family>;
670
+ updatedFamilies: Set<Family>;
671
+ }
614
672
  /**
615
673
  * Represents a react-internal Fiber node.
616
674
  */
@@ -661,6 +719,7 @@ interface ReactDevToolsGlobalHook {
661
719
  onCommitFiberRoot: (rendererID: number, root: FiberRoot, priority: number | void) => void;
662
720
  onCommitFiberUnmount: (rendererID: number, fiber: Fiber) => void;
663
721
  onPostCommitFiberRoot: (rendererID: number, root: FiberRoot) => void;
722
+ onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: ReactNode) => void;
664
723
  renderers: Map<number, ReactRenderer>;
665
724
  supportsFiber: boolean;
666
725
  supportsFlight: boolean;
@@ -679,10 +738,7 @@ interface ReactRenderer {
679
738
  overridePropsRenamePath?: (fiber: Fiber, oldPath: Array<number | string>, newPath: Array<number | string>) => void;
680
739
  reconcilerVersion: string;
681
740
  rendererPackageName: string;
682
- scheduleRefresh?: (root: FiberRoot, update: {
683
- staleFamilies: Set<Family>;
684
- updatedFamilies: Set<Family>;
685
- }) => void;
741
+ scheduleRefresh?: (root: FiberRoot, update: RendererRefreshUpdate) => void;
686
742
  scheduleRoot?: (root: FiberRoot, element: React.ReactNode) => void;
687
743
  scheduleUpdate?: (fiber: Fiber) => void;
688
744
  setErrorHandler?: (newShouldErrorImpl: (fiber: Fiber) => boolean) => void;
@@ -693,28 +749,30 @@ interface ReactRenderer {
693
749
  declare global {
694
750
  var __REACT_DEVTOOLS_GLOBAL_HOOK__: ReactDevToolsGlobalHook | undefined;
695
751
  } //#endregion
696
- //#region src/rdt-hook.d.ts
697
- /**
698
- * Returns `true` if bippy's instrumentation is active.
699
- */
700
- declare const isInstrumentationActive: () => boolean;
701
- /**
702
- * Returns the latest fiber (since it may be double-buffered).
703
- */
752
+ //#region src/unsubscribe.d.ts
704
753
  //#endregion
705
- //#region ../../node_modules/.pnpm/bippy@0.5.42_react@19.2.6/node_modules/bippy/dist/source.d.ts
754
+ //#region ../../node_modules/.pnpm/bippy@0.6.0_react@19.2.6/node_modules/bippy/dist/source.d.ts
706
755
  //#region src/source/parse-stack.d.ts
707
756
  interface StackFrame {
708
757
  args?: unknown[];
709
758
  columnNumber?: number;
710
759
  lineNumber?: number;
760
+ enclosingLineNumber?: number;
761
+ enclosingColumnNumber?: number;
711
762
  fileName?: string;
712
763
  functionName?: string;
713
764
  source?: string;
714
765
  isServer?: boolean;
715
766
  isSymbolicated?: boolean;
767
+ isIgnoreListed?: boolean;
716
768
  }
717
769
  //#endregion
770
+ //#region ../../node_modules/.pnpm/bippy@0.6.0_react@19.2.6/node_modules/bippy/dist/core.d.ts
771
+ /**
772
+ * Returns `true` if bippy's instrumentation is active.
773
+ */
774
+ declare const isInstrumentationActive: () => boolean;
775
+ //#endregion
718
776
  //#region src/utils/copy-content.d.ts
719
777
  interface CopyContentOptions {
720
778
  componentName?: string;
@@ -724,4 +782,4 @@ interface CopyContentOptions {
724
782
  }
725
783
  declare const copyContent: (content: string, options?: CopyContentOptions) => boolean;
726
784
  //#endregion
727
- export { ToolbarState as A, ReactGrabAPI as C, SettableOptions as D, Rect as E, SourceInfo as O, PromptModeContext as S, ReactGrabState as T, OverlayBounds as _, ActionContext as a, PluginHooks as b, AgentContext as c, DeepPartial as d, DragRect as f, Options as g, GrabbedBox as h, isInstrumentationActive as i, Theme as k, ContextMenuAction as l, ElementLabelVariant as m, StackFrame as n, ActionContextHooks as o, ElementLabelContext as p, Fiber as r, ActivationMode as s, copyContent as t, ContextMenuActionContext as u, Plugin as v, ReactGrabRendererProps as w, Position as x, PluginConfig as y };
785
+ export { Rect as A, PluginConfig as C, ReactGrabAPI as D, PromptModeContext as E, ToolbarState as F, SettableOptions as M, SourceInfo as N, ReactGrabRendererProps as O, Theme as P, Plugin as S, Position as T, ElementSelectedEventDetail as _, ActionContext as a, Options as b, AgentContext as c, DeepPartial as d, DragRect as f, ElementLabelVariant as g, ElementLabelContext as h, Fiber as i, SelectedElementPayload as j, ReactGrabState as k, ContextMenuAction as l, ElementBounds as m, isInstrumentationActive as n, ActionContextHooks as o, ElementAtPointOptions as p, StackFrame as r, ActivationMode as s, copyContent as t, ContextMenuActionContext as u, GrabbedBox as v, PluginHooks as w, OverlayBounds as x, OpenFileActionHooks as y };