Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
"sharp",
"spawn-sync",
"unrs-resolver"
]
],
"patchedDependencies": {
"bippy@0.7.0": "patches/bippy@0.7.0.patch"
}
}
}
2 changes: 1 addition & 1 deletion packages/react-grab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
},
"dependencies": {
"@react-grab/cli": "workspace:*",
"bippy": "^0.6.1"
"bippy": "^0.7.0"
},
"devDependencies": {
"@babel/core": "^7.29.0",
Expand Down
7 changes: 4 additions & 3 deletions packages/react-grab/src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getDisplayName,
getLatestFiber,
isCompositeFiber,
isFiber,
traverseFiber,
type Fiber,
} from "bippy";
Expand Down Expand Up @@ -259,11 +260,11 @@ export interface ResolvedSource extends SourceLocation {
const pickNearestSourceFrame = (frames: StackFrame[]): StackFrame | null => frames[0] ?? null;

const getSourceComponentName = (
fiber: Fiber | undefined,
debugOwner: Fiber["_debugOwner"],
isNextProject: boolean,
): string | null => {
if (!fiber || !isCompositeFiber(fiber)) return null;
return toSourceComponentName(getDisplayName(fiber.type), isNextProject);
if (!isFiber(debugOwner) || !isCompositeFiber(debugOwner)) return null;
return toSourceComponentName(getDisplayName(debugOwner.type), isNextProject);
};

// getSource reads React's own dev-only debug data, so it works without bippy
Expand Down
46 changes: 31 additions & 15 deletions packages/react-grab/src/core/element-anchors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
getFiberFromHostInstance,
getLatestFiber,
getNearestHostFibers,
isHostFiber,
type Fiber,
} from "bippy";
import { getFiberFromHostInstance, getLatestFiber, isHostFiber, type Fiber } from "bippy";
import { indexInParent } from "../utils/index-in-parent.js";
import { isShadowRoot } from "../utils/is-shadow-root.js";
import { isElementNode } from "../utils/is-element-node.js";
Expand Down Expand Up @@ -55,15 +49,37 @@ const resolveLiveAnchor = (anchor: ElementAnchor): Element | null => {
return candidate && candidate.tagName === anchorTagName ? candidate : null;
}

for (const hostFiber of getNearestHostFibers(latestParentFiber)) {
const node = hostFiber.stateNode;
// The tag match keeps recovery from latching onto an unrelated host when the
// original element type is gone. Same-tag siblings under a shared composite
// ancestor can't be told apart and resolve to the first match, which still
// leaves the selection on a valid node instead of dropping it.
if (isElementNode(node) && node.isConnected && node.tagName === anchorTagName) {
return node;
return findNearestHostElementWithTag(latestParentFiber, anchorTagName);
};

// Walks the nearest host fibers below `parentFiber` (stopping at each host
// boundary, like bippy's removed getNearestHostFibers) for a connected element
// with the anchor's tag. The tag match keeps recovery from latching onto an
// unrelated host when the original element type is gone. Same-tag siblings
// under a shared composite ancestor can't be told apart and resolve to the
// first match, which still leaves the selection on a valid node instead of
// dropping it.
const findNearestHostElementWithTag = (
parentFiber: Fiber,
anchorTagName: string,
): Element | null => {
let fiber: Fiber | null = parentFiber.child;
while (fiber) {
if (isHostFiber(fiber)) {
const node = fiber.stateNode;
if (isElementNode(node) && node.isConnected && node.tagName === anchorTagName) {
return node;
}
} else if (fiber.child) {
fiber = fiber.child;
continue;
}
while (fiber !== parentFiber && !fiber.sibling) {
fiber = fiber.return;
if (!fiber) return null;
}
if (fiber === parentFiber) return null;
fiber = fiber.sibling;
}
return null;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/react-grab/src/utils/freeze-updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RecoverableError } from "../errors.js";
import { reportRecoverableError } from "./report-recoverable-error.js";
import { IS_DEMO } from "./runtime-mode.js";

interface FiberRootLike extends FiberRoot {
interface FiberRootLike extends Omit<FiberRoot, "current"> {
current: Fiber | null;
}

Expand Down
47 changes: 47 additions & 0 deletions patches/bippy@0.7.0.patch

Large diffs are not rendered by default.

37 changes: 29 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading