Skip to content

Commit 1a5d530

Browse files
committed
Dont consider portals target children for layout related methods
1 parent 9161f5f commit 1a5d530

File tree

3 files changed

+132
-9
lines changed

3 files changed

+132
-9
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,6 +2329,7 @@ FragmentInstance.prototype.addEventListener = function (
23292329
listeners.push({type, listener, optionsOrUseCapture});
23302330
traverseFragmentInstance(
23312331
this._fragmentFiber,
2332+
false,
23322333
addEventListenerToChild,
23332334
type,
23342335
listener,
@@ -2360,6 +2361,7 @@ FragmentInstance.prototype.removeEventListener = function (
23602361
if (typeof listeners !== 'undefined' && listeners.length > 0) {
23612362
traverseFragmentInstance(
23622363
this._fragmentFiber,
2364+
false,
23632365
removeEventListenerFromChild,
23642366
type,
23652367
listener,
@@ -2392,6 +2394,7 @@ FragmentInstance.prototype.focus = function (
23922394
): void {
23932395
traverseFragmentInstance(
23942396
this._fragmentFiber,
2397+
false,
23952398
setFocusIfFocusable,
23962399
focusOptions,
23972400
);
@@ -2402,7 +2405,12 @@ FragmentInstance.prototype.focusLast = function (
24022405
focusOptions?: FocusOptions,
24032406
): void {
24042407
const children: Array<Instance> = [];
2405-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2408+
traverseFragmentInstance(
2409+
this._fragmentFiber,
2410+
false,
2411+
collectChildren,
2412+
children,
2413+
);
24062414
for (let i = children.length - 1; i >= 0; i--) {
24072415
const child = children[i];
24082416
if (setFocusIfFocusable(child, focusOptions)) {
@@ -2423,6 +2431,7 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
24232431
// does not contain document.activeElement
24242432
traverseFragmentInstance(
24252433
this._fragmentFiber,
2434+
false,
24262435
blurActiveElementWithinFragment,
24272436
);
24282437
};
@@ -2445,7 +2454,7 @@ FragmentInstance.prototype.observeUsing = function (
24452454
this._observers = new Set();
24462455
}
24472456
this._observers.add(observer);
2448-
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
2457+
traverseFragmentInstance(this._fragmentFiber, false, observeChild, observer);
24492458
};
24502459
function observeChild(
24512460
child: Instance,
@@ -2468,7 +2477,12 @@ FragmentInstance.prototype.unobserveUsing = function (
24682477
}
24692478
} else {
24702479
this._observers.delete(observer);
2471-
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
2480+
traverseFragmentInstance(
2481+
this._fragmentFiber,
2482+
false,
2483+
unobserveChild,
2484+
observer,
2485+
);
24722486
}
24732487
};
24742488
function unobserveChild(
@@ -2483,7 +2497,12 @@ FragmentInstance.prototype.getClientRects = function (
24832497
this: FragmentInstanceType,
24842498
): Array<DOMRect> {
24852499
const rects: Array<DOMRect> = [];
2486-
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
2500+
traverseFragmentInstance(
2501+
this._fragmentFiber,
2502+
true,
2503+
collectClientRects,
2504+
rects,
2505+
);
24872506
return rects;
24882507
};
24892508
function collectClientRects(child: Instance, rects: Array<DOMRect>): boolean {
@@ -2516,7 +2535,12 @@ FragmentInstance.prototype.compareDocumentPosition = function (
25162535
}
25172536

25182537
const children: Array<Instance> = [];
2519-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2538+
traverseFragmentInstance(
2539+
this._fragmentFiber,
2540+
true,
2541+
collectChildren,
2542+
children,
2543+
);
25202544

25212545
if (children.length === 0) {
25222546
// If the fragment has no children, we can use the parent and

packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,42 @@ describe('FragmentRefs', () => {
611611
expect(logs).toEqual([]);
612612
});
613613

614+
// @gate enableFragmentRefs
615+
it('applies event listeners to portaled children', async () => {
616+
const fragmentRef = React.createRef();
617+
const childARef = React.createRef();
618+
const childBRef = React.createRef();
619+
const root = ReactDOMClient.createRoot(container);
620+
621+
function Test() {
622+
return (
623+
<Fragment ref={fragmentRef}>
624+
<div id="child-a" ref={childARef} />
625+
{ReactDOMClient.createPortal(
626+
<div id="child-b" ref={childBRef} />,
627+
document.body,
628+
)}
629+
</Fragment>
630+
);
631+
}
632+
633+
await act(() => {
634+
root.render(<Test />);
635+
});
636+
637+
const logs = [];
638+
fragmentRef.current.addEventListener('click', e => {
639+
logs.push(e.target.id);
640+
});
641+
642+
childARef.current.click();
643+
expect(logs).toEqual(['child-a']);
644+
645+
logs.length = 0;
646+
childBRef.current.click();
647+
expect(logs).toEqual(['child-b']);
648+
});
649+
614650
describe('with activity', () => {
615651
// @gate enableFragmentRefs && enableActivity
616652
it('does not apply event listeners to hidden trees', async () => {
@@ -1269,7 +1305,7 @@ describe('FragmentRefs', () => {
12691305
<div ref={containerRef}>
12701306
{mount && (
12711307
<Fragment ref={fragmentRef}>
1272-
<div></div>
1308+
<div />
12731309
</Fragment>
12741310
)}
12751311
</div>
@@ -1308,5 +1344,57 @@ describe('FragmentRefs', () => {
13081344
},
13091345
);
13101346
});
1347+
1348+
// @gate enableFragmentRefs
1349+
it('handles portaled elements', async () => {
1350+
const fragmentRef = React.createRef();
1351+
const portaledSiblingRef = React.createRef();
1352+
const portaledChildRef = React.createRef();
1353+
1354+
function Test() {
1355+
return (
1356+
<div>
1357+
{ReactDOMClient.createPortal(
1358+
<div ref={portaledSiblingRef} />,
1359+
document.body,
1360+
)}
1361+
<Fragment ref={fragmentRef}>
1362+
{ReactDOMClient.createPortal(
1363+
<div ref={portaledChildRef} />,
1364+
document.body,
1365+
)}
1366+
<div />
1367+
</Fragment>
1368+
</div>
1369+
);
1370+
}
1371+
1372+
const root = ReactDOMClient.createRoot(container);
1373+
await act(() => root.render(<Test />));
1374+
1375+
expectPosition(
1376+
fragmentRef.current.compareDocumentPosition(portaledSiblingRef.current),
1377+
{
1378+
preceding: false,
1379+
following: true,
1380+
contains: false,
1381+
containedBy: false,
1382+
disconnected: false,
1383+
implementationSpecific: false,
1384+
},
1385+
);
1386+
1387+
expectPosition(
1388+
fragmentRef.current.compareDocumentPosition(portaledChildRef.current),
1389+
{
1390+
preceding: false,
1391+
following: true,
1392+
contains: false,
1393+
containedBy: false,
1394+
disconnected: false,
1395+
implementationSpecific: false,
1396+
},
1397+
);
1398+
});
13111399
});
13121400
});

packages/react-reconciler/src/ReactFiberTreeReflection.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,16 +321,25 @@ export function doesFiberContain(
321321

322322
export function traverseFragmentInstance<A, B, C>(
323323
fragmentFiber: Fiber,
324+
skipPortals: boolean,
324325
fn: (Instance, A, B, C) => boolean,
325326
a: A,
326327
b: B,
327328
c: C,
328329
): void {
329-
traverseFragmentInstanceChildren(fragmentFiber.child, fn, a, b, c);
330+
traverseFragmentInstanceChildren(
331+
fragmentFiber.child,
332+
skipPortals,
333+
fn,
334+
a,
335+
b,
336+
c,
337+
);
330338
}
331339

332340
function traverseFragmentInstanceChildren<A, B, C>(
333341
child: Fiber | null,
342+
skipPortals: boolean,
334343
fn: (Instance, A, B, C) => boolean,
335344
a: A,
336345
b: B,
@@ -346,8 +355,10 @@ function traverseFragmentInstanceChildren<A, B, C>(
346355
child.memoizedState !== null
347356
) {
348357
// Skip hidden subtrees
358+
} else if (skipPortals && child.tag === HostPortal) {
359+
// Skip portals
349360
} else {
350-
traverseFragmentInstanceChildren(child.child, fn, a, b, c);
361+
traverseFragmentInstanceChildren(child.child, skipPortals, fn, a, b, c);
351362
}
352363
child = child.sibling;
353364
}
@@ -370,7 +381,7 @@ export function getFragmentParentHostInstance(fiber: Fiber): null | Instance {
370381

371382
export function getNextSiblingHostInstance(fiber: Fiber): null | Instance {
372383
let nextSibling = null;
373-
traverseFragmentInstanceChildren(fiber.sibling, instance => {
384+
traverseFragmentInstanceChildren(fiber.sibling, true, instance => {
374385
nextSibling = instance;
375386
return true;
376387
});

0 commit comments

Comments
 (0)