feat(react-grab): Alt+click selects all instances of the nearest component#454
feat(react-grab): Alt+click selects all instances of the nearest component#454aidenybai wants to merge 2 commits into
Conversation
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
| // Holding Alt selects every rendered instance of the clicked element's | ||
| // nearest component at once, not just the element under the pointer. | ||
| if (isAltHeld && !store.pendingCommentMode && !isPendingContextMenuSelect()) { | ||
| const instanceElements = findComponentInstanceElements(selectedElement); |
There was a problem hiding this comment.
Alt select ignores pointer target
Medium Severity
Alt component-instance selection calls findComponentInstanceElements with selectedElement, which can come from detectedElement or a prior frozen selection when nothing grabbable is under the pointer. Shift multi-select only uses elementAtPointer, so an Alt+click on empty chrome can bulk-select every instance of a hovered or stale target instead of doing nothing.
Reviewed by Cursor Bugbot for commit 49fc5e6. Configure here.
There was a problem hiding this comment.
1 issue found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/core/index.tsx">
<violation number="1" location="packages/react-grab/src/core/index.tsx:1931">
P2: The Alt-select path passes `selectedElement` to `findComponentInstanceElements`, but `selectedElement` can fall back to the previously frozen element or `detectedElement` when nothing grabbable is directly under the pointer. This means Alt+clicking on empty chrome (or outside any grabbable target) can unintentionally bulk-select all instances of a stale/hovered component. Consider guarding with `elementAtPointer` (similar to the Shift multi-select path) so that Alt+click on empty space is a no-op.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // Holding Alt selects every rendered instance of the clicked element's | ||
| // nearest component at once, not just the element under the pointer. | ||
| if (isAltHeld && !store.pendingCommentMode && !isPendingContextMenuSelect()) { | ||
| const instanceElements = findComponentInstanceElements(selectedElement); |
There was a problem hiding this comment.
P2: The Alt-select path passes selectedElement to findComponentInstanceElements, but selectedElement can fall back to the previously frozen element or detectedElement when nothing grabbable is directly under the pointer. This means Alt+clicking on empty chrome (or outside any grabbable target) can unintentionally bulk-select all instances of a stale/hovered component. Consider guarding with elementAtPointer (similar to the Shift multi-select path) so that Alt+click on empty space is a no-op.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/core/index.tsx, line 1931:
<comment>The Alt-select path passes `selectedElement` to `findComponentInstanceElements`, but `selectedElement` can fall back to the previously frozen element or `detectedElement` when nothing grabbable is directly under the pointer. This means Alt+clicking on empty chrome (or outside any grabbable target) can unintentionally bulk-select all instances of a stale/hovered component. Consider guarding with `elementAtPointer` (similar to the Shift multi-select path) so that Alt+click on empty space is a no-op.</comment>
<file context>
@@ -1891,6 +1925,17 @@ export const init = (rawOptions?: Options): ReactGrabAPI => {
+ // Holding Alt selects every rendered instance of the clicked element's
+ // nearest component at once, not just the element under the pointer.
+ if (isAltHeld && !store.pendingCommentMode && !isPendingContextMenuSelect()) {
+ const instanceElements = findComponentInstanceElements(selectedElement);
+ if (instanceElements.length > 1) {
+ keyboardSelectedElement = null;
</file context>
…yle/edit mode Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 14ec394. Configure here.
| selectionBounds: createElementBounds(element), | ||
| properties, | ||
| preview: createPreviewStyles(element), | ||
| preview: createPreviewStyles(previewTargets), |
There was a problem hiding this comment.
Style submit ignores multi-instance selection
Medium Severity
After Alt multi-select, openEditMode passes every frozen instance into createPreviewStyles, but submit still calls performCopyWithLabel with only currentState.element. Style tweaks preview on all instances, yet the post-submit copy flow describes a single element.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 14ec394. Configure here.
| selectionBounds: createElementBounds(element), | ||
| properties, | ||
| preview: createPreviewStyles(element), | ||
| preview: createPreviewStyles(previewTargets), |
There was a problem hiding this comment.
Edit open collapses multi frozen selection
Medium Severity
When several instances are frozen via Alt, openEditMode supplies all of them as preview targets, but editMode.trigger still calls setFrozenElement, replacing frozenElements with a single entry. Overlays and labels show one selection while style preview updates every instance.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 14ec394. Configure here.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/core/index.tsx">
<violation number="1" location="packages/react-grab/src/core/index.tsx:1485">
P2: Style preview applies to all Alt-selected instances via `previewElements`, but `element` (single) is passed as the primary target to `editMode.trigger`. When the user submits from the edit panel, the copy/submit flow will reference only this single element rather than all previewed instances, creating a visual-vs-action mismatch.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| store.frozenElements.length > 1 && store.frozenElements.includes(element) | ||
| ? [...store.frozenElements] | ||
| : [element]; | ||
| return editMode.trigger(element, position, overrides, previewElements); |
There was a problem hiding this comment.
P2: Style preview applies to all Alt-selected instances via previewElements, but element (single) is passed as the primary target to editMode.trigger. When the user submits from the edit panel, the copy/submit flow will reference only this single element rather than all previewed instances, creating a visual-vs-action mismatch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/core/index.tsx, line 1485:
<comment>Style preview applies to all Alt-selected instances via `previewElements`, but `element` (single) is passed as the primary target to `editMode.trigger`. When the user submits from the edit panel, the copy/submit flow will reference only this single element rather than all previewed instances, creating a visual-vs-action mismatch.</comment>
<file context>
@@ -1453,7 +1475,15 @@ export const init = (rawOptions?: Options): ReactGrabAPI => {
+ store.frozenElements.length > 1 && store.frozenElements.includes(element)
+ ? [...store.frozenElements]
+ : [element];
+ return editMode.trigger(element, position, overrides, previewElements);
+ };
</file context>


Summary
Holding
Opt/Altwhile selecting targets every rendered instance of the clicked element's nearest component at once. This works for grabbing/copying and for the Style (edit) panel, so a style tweak previews across all instances simultaneously.Behaviors
For example, Alt-interacting with one
TodoItemrow targets allTodoItemrows. If only a single instance exists, it falls back to a normal single-element selection.How it works (bippy)
utils/find-component-instances.tsresolves the nearest meaningfully-named component fiber from the clicked DOM node, then traverses each React root with bippy'straverseFiberto find fibers sharing that component type, mapping each back to its nearest host (DOM) node viagetNearestHostFiber. UsesgetFiberFromHostInstance,getDisplayName,isCompositeFiber,getType,traverseFiber,getNearestHostFiber, and_fiberRoots(with a fallback to the target's own root). The clicked instance is always returned first; capped byMAX_COMPONENT_INSTANCE_SELECTION.Key changes
utils/find-component-instances.ts(new) — the bippy-based resolver.utils/preview-styles.ts—createPreviewStylesnow accepts one element or many, applying/restoring inline styles across all targets.core/edit-mode.ts—triggeraccepts optionalpreviewElementsso the Style preview can span multiple elements.core/index.tsx:event.altKeythroughhandlePointerUp→handleSingleClick;selectComponentInstancesfor the copy flow.contextmenuhandler expands to all instances and aligns the primary element with the frozen set.openEditModepasses all frozen instances as preview targets.isAltKeyHeldsignal (tracked via pointer/keydown/keyup/blur) drives analtPreviewElements/altPreviewBoundsmemo that feedsselectionBoundsMultiplefor the live hover highlight.constants.ts—MAX_COMPONENT_INSTANCE_SELECTION.Testing
pnpm build,pnpm typecheck,pnpm lint,pnpm formatpass.e2e/alt-select.spec.ts:TodoIteminstances (and for a deeply-nested child).Summary by cubic
Opt/Alt+click now selects all rendered instances of the nearest named React component in
react-grab, with a live Alt-hover preview of the full set; edits in Style/Edit mode apply across all instances. Falls back to a normal click when only one instance exists. Implements Linear f111.bippyto collect same-type instances across roots; clicked instance first, capped byMAX_COMPONENT_INSTANCE_SELECTION(200).createPreviewStylespreviews and restores styles across them.Written for commit 14ec394. Summary will update on new commits.