Skip to content

Commit e3f23c9

Browse files
committed
Dont consider portals target children for layout related methods
1 parent 4a14191 commit e3f23c9

File tree

3 files changed

+127
-9
lines changed

3 files changed

+127
-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
@@ -2448,6 +2448,7 @@ FragmentInstance.prototype.addEventListener = function (
24482448
listeners.push({type, listener, optionsOrUseCapture});
24492449
traverseFragmentInstance(
24502450
this._fragmentFiber,
2451+
false,
24512452
addEventListenerToChild,
24522453
type,
24532454
listener,
@@ -2479,6 +2480,7 @@ FragmentInstance.prototype.removeEventListener = function (
24792480
if (typeof listeners !== 'undefined' && listeners.length > 0) {
24802481
traverseFragmentInstance(
24812482
this._fragmentFiber,
2483+
false,
24822484
removeEventListenerFromChild,
24832485
type,
24842486
listener,
@@ -2511,6 +2513,7 @@ FragmentInstance.prototype.focus = function (
25112513
): void {
25122514
traverseFragmentInstance(
25132515
this._fragmentFiber,
2516+
false,
25142517
setFocusIfFocusable,
25152518
focusOptions,
25162519
);
@@ -2521,7 +2524,12 @@ FragmentInstance.prototype.focusLast = function (
25212524
focusOptions?: FocusOptions,
25222525
): void {
25232526
const children: Array<Instance> = [];
2524-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2527+
traverseFragmentInstance(
2528+
this._fragmentFiber,
2529+
false,
2530+
collectChildren,
2531+
children,
2532+
);
25252533
for (let i = children.length - 1; i >= 0; i--) {
25262534
const child = children[i];
25272535
if (setFocusIfFocusable(child, focusOptions)) {
@@ -2542,6 +2550,7 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
25422550
// does not contain document.activeElement
25432551
traverseFragmentInstance(
25442552
this._fragmentFiber,
2553+
false,
25452554
blurActiveElementWithinFragment,
25462555
);
25472556
};
@@ -2564,7 +2573,7 @@ FragmentInstance.prototype.observeUsing = function (
25642573
this._observers = new Set();
25652574
}
25662575
this._observers.add(observer);
2567-
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
2576+
traverseFragmentInstance(this._fragmentFiber, false, observeChild, observer);
25682577
};
25692578
function observeChild(
25702579
child: Instance,
@@ -2587,7 +2596,12 @@ FragmentInstance.prototype.unobserveUsing = function (
25872596
}
25882597
} else {
25892598
this._observers.delete(observer);
2590-
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
2599+
traverseFragmentInstance(
2600+
this._fragmentFiber,
2601+
false,
2602+
unobserveChild,
2603+
observer,
2604+
);
25912605
}
25922606
};
25932607
function unobserveChild(
@@ -2602,7 +2616,12 @@ FragmentInstance.prototype.getClientRects = function (
26022616
this: FragmentInstanceType,
26032617
): Array<DOMRect> {
26042618
const rects: Array<DOMRect> = [];
2605-
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
2619+
traverseFragmentInstance(
2620+
this._fragmentFiber,
2621+
true,
2622+
collectClientRects,
2623+
rects,
2624+
);
26062625
return rects;
26072626
};
26082627
function collectClientRects(child: Instance, rects: Array<DOMRect>): boolean {
@@ -2635,7 +2654,12 @@ FragmentInstance.prototype.compareDocumentPosition = function (
26352654
}
26362655

26372656
const children: Array<Instance> = [];
2638-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2657+
traverseFragmentInstance(
2658+
this._fragmentFiber,
2659+
true,
2660+
collectChildren,
2661+
children,
2662+
);
26392663

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

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

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
let React;
1313
let ReactDOMClient;
14+
let ReactDOM;
15+
let createPortal;
1416
let act;
1517
let container;
1618
let Fragment;
@@ -31,6 +33,8 @@ describe('FragmentRefs', () => {
3133
Fragment = React.Fragment;
3234
Activity = React.unstable_Activity;
3335
ReactDOMClient = require('react-dom/client');
36+
ReactDOM = require('react-dom');
37+
createPortal = ReactDOM.createPortal;
3438
act = require('internal-test-utils').act;
3539
const IntersectionMocks = require('./utils/IntersectionMocks');
3640
mockIntersectionObserver = IntersectionMocks.mockIntersectionObserver;
@@ -611,6 +615,39 @@ describe('FragmentRefs', () => {
611615
expect(logs).toEqual([]);
612616
});
613617

618+
// @gate enableFragmentRefs
619+
it('applies event listeners to portaled children', async () => {
620+
const fragmentRef = React.createRef();
621+
const childARef = React.createRef();
622+
const childBRef = React.createRef();
623+
const root = ReactDOMClient.createRoot(container);
624+
625+
function Test() {
626+
return (
627+
<Fragment ref={fragmentRef}>
628+
<div id="child-a" ref={childARef} />
629+
{createPortal(<div id="child-b" ref={childBRef} />, document.body)}
630+
</Fragment>
631+
);
632+
}
633+
634+
await act(() => {
635+
root.render(<Test />);
636+
});
637+
638+
const logs = [];
639+
fragmentRef.current.addEventListener('click', e => {
640+
logs.push(e.target.id);
641+
});
642+
643+
childARef.current.click();
644+
expect(logs).toEqual(['child-a']);
645+
646+
logs.length = 0;
647+
childBRef.current.click();
648+
expect(logs).toEqual(['child-b']);
649+
});
650+
614651
describe('with activity', () => {
615652
// @gate enableFragmentRefs && enableActivity
616653
it('does not apply event listeners to hidden trees', async () => {
@@ -1269,7 +1306,7 @@ describe('FragmentRefs', () => {
12691306
<div ref={containerRef}>
12701307
{mount && (
12711308
<Fragment ref={fragmentRef}>
1272-
<div></div>
1309+
<div />
12731310
</Fragment>
12741311
)}
12751312
</div>
@@ -1308,5 +1345,51 @@ describe('FragmentRefs', () => {
13081345
},
13091346
);
13101347
});
1348+
1349+
// @gate enableFragmentRefs
1350+
it('handles portaled elements', async () => {
1351+
const fragmentRef = React.createRef();
1352+
const portaledSiblingRef = React.createRef();
1353+
const portaledChildRef = React.createRef();
1354+
1355+
function Test() {
1356+
return (
1357+
<div>
1358+
{createPortal(<div ref={portaledSiblingRef} />, document.body)}
1359+
<Fragment ref={fragmentRef}>
1360+
{createPortal(<div ref={portaledChildRef} />, document.body)}
1361+
<div />
1362+
</Fragment>
1363+
</div>
1364+
);
1365+
}
1366+
1367+
const root = ReactDOMClient.createRoot(container);
1368+
await act(() => root.render(<Test />));
1369+
1370+
expectPosition(
1371+
fragmentRef.current.compareDocumentPosition(portaledSiblingRef.current),
1372+
{
1373+
preceding: false,
1374+
following: true,
1375+
contains: false,
1376+
containedBy: false,
1377+
disconnected: false,
1378+
implementationSpecific: false,
1379+
},
1380+
);
1381+
1382+
expectPosition(
1383+
fragmentRef.current.compareDocumentPosition(portaledChildRef.current),
1384+
{
1385+
preceding: false,
1386+
following: true,
1387+
contains: false,
1388+
containedBy: false,
1389+
disconnected: false,
1390+
implementationSpecific: false,
1391+
},
1392+
);
1393+
});
13111394
});
13121395
});

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)