Skip to content

Commit f53dc02

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

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
@@ -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: 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)