Skip to content

Commit ec67519

Browse files
committed
Dont consider portals target children for layout related methods
1 parent cffff32 commit ec67519

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
@@ -2629,6 +2629,7 @@ FragmentInstance.prototype.addEventListener = function (
26292629
listeners.push({type, listener, optionsOrUseCapture});
26302630
traverseFragmentInstance(
26312631
this._fragmentFiber,
2632+
false,
26322633
addEventListenerToChild,
26332634
type,
26342635
listener,
@@ -2660,6 +2661,7 @@ FragmentInstance.prototype.removeEventListener = function (
26602661
if (typeof listeners !== 'undefined' && listeners.length > 0) {
26612662
traverseFragmentInstance(
26622663
this._fragmentFiber,
2664+
false,
26632665
removeEventListenerFromChild,
26642666
type,
26652667
listener,
@@ -2692,6 +2694,7 @@ FragmentInstance.prototype.focus = function (
26922694
): void {
26932695
traverseFragmentInstance(
26942696
this._fragmentFiber,
2697+
false,
26952698
setFocusIfFocusable,
26962699
focusOptions,
26972700
);
@@ -2702,7 +2705,12 @@ FragmentInstance.prototype.focusLast = function (
27022705
focusOptions?: FocusOptions,
27032706
): void {
27042707
const children: Array<Instance> = [];
2705-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2708+
traverseFragmentInstance(
2709+
this._fragmentFiber,
2710+
false,
2711+
collectChildren,
2712+
children,
2713+
);
27062714
for (let i = children.length - 1; i >= 0; i--) {
27072715
const child = children[i];
27082716
if (setFocusIfFocusable(child, focusOptions)) {
@@ -2723,6 +2731,7 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
27232731
// does not contain document.activeElement
27242732
traverseFragmentInstance(
27252733
this._fragmentFiber,
2734+
false,
27262735
blurActiveElementWithinFragment,
27272736
);
27282737
};
@@ -2745,7 +2754,7 @@ FragmentInstance.prototype.observeUsing = function (
27452754
this._observers = new Set();
27462755
}
27472756
this._observers.add(observer);
2748-
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
2757+
traverseFragmentInstance(this._fragmentFiber, false, observeChild, observer);
27492758
};
27502759
function observeChild(
27512760
child: Instance,
@@ -2768,7 +2777,12 @@ FragmentInstance.prototype.unobserveUsing = function (
27682777
}
27692778
} else {
27702779
this._observers.delete(observer);
2771-
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
2780+
traverseFragmentInstance(
2781+
this._fragmentFiber,
2782+
false,
2783+
unobserveChild,
2784+
observer,
2785+
);
27722786
}
27732787
};
27742788
function unobserveChild(
@@ -2783,7 +2797,12 @@ FragmentInstance.prototype.getClientRects = function (
27832797
this: FragmentInstanceType,
27842798
): Array<DOMRect> {
27852799
const rects: Array<DOMRect> = [];
2786-
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
2800+
traverseFragmentInstance(
2801+
this._fragmentFiber,
2802+
true,
2803+
collectClientRects,
2804+
rects,
2805+
);
27872806
return rects;
27882807
};
27892808
function collectClientRects(child: Instance, rects: Array<DOMRect>): boolean {
@@ -2816,7 +2835,12 @@ FragmentInstance.prototype.compareDocumentPosition = function (
28162835
}
28172836

28182837
const children: Array<Instance> = [];
2819-
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
2838+
traverseFragmentInstance(
2839+
this._fragmentFiber,
2840+
true,
2841+
collectChildren,
2842+
children,
2843+
);
28202844

28212845
if (children.length === 0) {
28222846
// 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
@@ -348,16 +348,25 @@ export function doesFiberContain(
348348
export function traverseFragmentInstance<I, A, B, C>(
349349
fragmentFiber: Fiber,
350350
fn: (I, A, B, C) => boolean,
351+
skipPortals: boolean,
351352
a: A,
352353
b: B,
353354
c: C,
354355
): void {
355-
traverseFragmentInstanceChildren(fragmentFiber.child, fn, a, b, c);
356+
traverseFragmentInstanceChildren(
357+
fragmentFiber.child,
358+
skipPortals,
359+
fn,
360+
a,
361+
b,
362+
c,
363+
);
356364
}
357365

358366
function traverseFragmentInstanceChildren<I, A, B, C>(
359367
child: Fiber | null,
360368
fn: (I, A, B, C) => boolean,
369+
skipPortals: boolean,
361370
a: A,
362371
b: B,
363372
c: C,
@@ -372,8 +381,10 @@ function traverseFragmentInstanceChildren<I, A, B, C>(
372381
child.memoizedState !== null
373382
) {
374383
// Skip hidden subtrees
384+
} else if (skipPortals && child.tag === HostPortal) {
385+
// Skip portals
375386
} else {
376-
traverseFragmentInstanceChildren(child.child, fn, a, b, c);
387+
traverseFragmentInstanceChildren(child.child, skipPortals, fn, a, b, c);
377388
}
378389
child = child.sibling;
379390
}
@@ -396,7 +407,7 @@ export function getFragmentParentHostInstance(fiber: Fiber): null | Instance {
396407

397408
export function getNextSiblingHostInstance(fiber: Fiber): null | Instance {
398409
let nextSibling = null;
399-
traverseFragmentInstanceChildren(fiber.sibling, instance => {
410+
traverseFragmentInstanceChildren(fiber.sibling, true, instance => {
400411
nextSibling = instance;
401412
return true;
402413
});

0 commit comments

Comments
 (0)