Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,63 @@ const ScrollJumpContext = () => {
);
};

const MultiScrollParent = () => {
const { targetRef, containerRef } = usePositioning({});
const scrollContainerRef = React.useRef<HTMLDivElement>(null);

const scroll = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({ top: 100 });
}
};

return (
<>
<span>The popover should stay attached to the trigger</span>
<button id="scroll" onClick={scroll}>
scroll
</button>
<div
ref={scrollContainerRef}
style={{
border: '2px dashed green',
height: 300,
width: 400,
overflow: 'auto',
display: 'grid',
gridTemplateColumns: '350px auto',
gridTemplateRows: '800px',
}}
>
<div style={{ position: 'relative' }}>
<div
style={{
overflow: 'auto',
border: '2px dashed red',
position: 'absolute',
top: 150,
left: 100,
padding: 20,
}}
>
<button id="target" ref={targetRef}>
Trigger
</button>
</div>
</div>
<div
style={{
backgroundImage: 'linear-gradient(to bottom, #ffffff, #b9b9b9, #777777, #3b3b3b, #000000)',
}}
/>
</div>
<div ref={containerRef} style={{ border: '2px solid blue', padding: 20, backgroundColor: 'white' }}>
Popover
</div>
</>
);
};

storiesOf('Positioning', module)
.addDecorator(story => (
<div
Expand Down Expand Up @@ -1161,7 +1218,12 @@ storiesOf('Positioning', module)
'disable CSS transform with position fixed',
() => <PositionAndAlignProps positionFixed useTransform={false} />,
{ includeRtl: true },
);
)
.addStory('Multiple scroll parents', () => (
<StoryWright steps={new Steps().click('#scroll').snapshot('container attached to target').end()}>
<MultiScrollParent />
</StoryWright>
));

storiesOf('Positioning (no decorator)', module)
.addStory('scroll jumps', () => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: Consider all parents as scroll parents",
"packageName": "@fluentui/react-positioning",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { computePosition } from '@floating-ui/dom';
import type { Middleware, Placement, Strategy } from '@floating-ui/dom';
import type { PositionManager, TargetElement } from './types';
import { debounce, writeArrowUpdates, writeContainerUpdates, getScrollParent } from './utils';
import { debounce, writeArrowUpdates, writeContainerUpdates } from './utils';
import { isHTMLElement } from '@fluentui/react-utilities';
import { listScrollParents } from './utils/listScrollParents';

interface PositionManagerOptions {
/**
Expand Down Expand Up @@ -67,9 +68,9 @@ export function createPositionManager(options: PositionManagerOptions): Position
}

if (isFirstUpdate) {
scrollParents.add(getScrollParent(container));
listScrollParents(container).forEach(scrollParent => scrollParents.add(scrollParent));
if (isHTMLElement(target)) {
scrollParents.add(getScrollParent(target));
listScrollParents(target).forEach(scrollParent => scrollParents.add(scrollParent));
}

scrollParents.forEach(scrollParent => {
Expand Down Expand Up @@ -127,6 +128,7 @@ export function createPositionManager(options: PositionManagerOptions): Position
scrollParents.forEach(scrollParent => {
scrollParent.removeEventListener('scroll', updatePosition);
});
scrollParents.clear();
};

if (targetWindow) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { listScrollParents } from './listScrollParents';

describe('listScrollParents', () => {
beforeEach(() => {
document.body.innerHTML = '';
});

const createScrollParent = () => {
const el = document.createElement('div');
el.style.overflow = 'auto';
return el;
};

it('should return all scroll parents include and up to body', () => {
const start = document.createElement('div');
const scrollParent1 = createScrollParent();
const scrollParent2 = createScrollParent();

scrollParent1.appendChild(start);
scrollParent2.appendChild(scrollParent1);
document.body.append(scrollParent2);

const scrollParents = listScrollParents(start);

expect(scrollParents.length).toBe(3);
expect(scrollParents).toContain(scrollParent1);
expect(scrollParents).toContain(scrollParent2);
expect(scrollParents).toContain(document.body);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getScrollParent } from './getScrollParent';

export function listScrollParents(node: HTMLElement): HTMLElement[] {
const scrollParents: HTMLElement[] = [];

let cur: HTMLElement | null = node;
while (cur) {
const scrollParent = getScrollParent(cur);

if (node.ownerDocument.body === scrollParent) {
scrollParents.push(scrollParent);
break;
}

scrollParents.push(scrollParent);
cur = scrollParent;
}

return scrollParents;
}