Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popover: remove custom frame scroll/resize listeners #54286

Merged
merged 3 commits into from
Sep 12, 2023
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
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- `Popover`: Remove unused `overlay` type from `positionToPlacement` utility function ([#54101](https://github.com/WordPress/gutenberg/pull/54101)).
- `Higher Order` -- `with-focus-outside`: Convert to TypeScript ([#53980](https://github.com/WordPress/gutenberg/pull/53980)).
- `IsolatedEventContainer`: Convert unit test to TypeScript ([#54316](https://github.com/WordPress/gutenberg/pull/54316)).
- `Popover`: Remove `scroll` and `resize` listeners for iframe overflow parents and rely on recently added native Floating UI support ([#54286](https://github.com/WordPress/gutenberg/pull/54286)).

### Experimental

Expand Down
43 changes: 0 additions & 43 deletions packages/components/src/popover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
import { close } from '@wordpress/icons';
import deprecated from '@wordpress/deprecated';
import { Path, SVG } from '@wordpress/primitives';
import { getScrollContainer } from '@wordpress/dom';

/**
* Internal dependencies
Expand All @@ -52,7 +51,6 @@ import {
computePopoverPosition,
positionToPlacement,
placementToMotionAnimationProps,
getReferenceOwnerDocument,
getReferenceElement,
} from './utils';
import type { WordPressComponentProps } from '../ui/context';
Expand Down Expand Up @@ -199,9 +197,6 @@ const UnforwardedPopover = (

const [ fallbackReferenceElement, setFallbackReferenceElement ] =
useState< HTMLSpanElement | null >( null );
const [ referenceOwnerDocument, setReferenceOwnerDocument ] = useState<
Document | undefined
>();

const anchorRefFallback: RefCallback< HTMLSpanElement > = useCallback(
( node ) => {
Expand Down Expand Up @@ -315,15 +310,6 @@ const UnforwardedPopover = (
?.current;

useLayoutEffect( () => {
const resultingReferenceOwnerDoc = getReferenceOwnerDocument( {
anchor,
anchorRef,
anchorRect,
getAnchorRect,
fallbackReferenceElement,
fallbackDocument: document,
} );

const resultingReferenceElement = getReferenceElement( {
anchor,
anchorRef,
Expand All @@ -333,8 +319,6 @@ const UnforwardedPopover = (
} );

refs.setReference( resultingReferenceElement );

setReferenceOwnerDocument( resultingReferenceOwnerDoc );
}, [
anchor,
anchorRef,
Expand All @@ -348,33 +332,6 @@ const UnforwardedPopover = (
refs,
] );

// If the reference element is in a different ownerDocument (e.g. iFrame),
// we need to manually update the floating's position as the reference's owner
// document scrolls.
useLayoutEffect( () => {
if (
! referenceOwnerDocument ||
! referenceOwnerDocument.defaultView
) {
return;
}

const { defaultView } = referenceOwnerDocument;
const { frameElement } = defaultView;

const scrollContainer = frameElement
? getScrollContainer( frameElement )
: null;

defaultView.addEventListener( 'resize', update );
scrollContainer?.addEventListener( 'scroll', update );

return () => {
defaultView.removeEventListener( 'resize', update );
scrollContainer?.removeEventListener( 'scroll', update );
};
}, [ referenceOwnerDocument, update ] );

const mergedFloatingRef = useMergeRefs( [
refs.setFloating,
dialogRef,
Expand Down
85 changes: 17 additions & 68 deletions packages/components/src/popover/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
*/
// eslint-disable-next-line no-restricted-imports
import type { MotionProps } from 'framer-motion';
import type {
Placement,
ReferenceType,
VirtualElement,
} from '@floating-ui/react-dom';
import type { Placement, ReferenceType } from '@floating-ui/react-dom';

/**
* Internal dependencies
Expand Down Expand Up @@ -142,58 +138,17 @@ export const placementToMotionAnimationProps = (
};
};

export const getReferenceOwnerDocument = ( {
anchor,
anchorRef,
anchorRect,
getAnchorRect,
fallbackReferenceElement,
fallbackDocument,
}: Pick<
PopoverProps,
'anchorRef' | 'anchorRect' | 'getAnchorRect' | 'anchor'
> & {
fallbackReferenceElement: Element | null;
fallbackDocument: Document;
} ): Document => {
// In floating-ui's terms:
// - "reference" refers to the popover's anchor element.
// - "floating" refers the floating popover's element.
// A floating element can also be positioned relative to a virtual element,
// instead of a real one. A virtual element is represented by an object
// with the `getBoundingClientRect()` function (like real elements).
// See https://floating-ui.com/docs/virtual-elements for more info.
let resultingReferenceOwnerDoc;
if ( ( anchor as VirtualElement )?.contextElement ) {
resultingReferenceOwnerDoc = ( anchor as VirtualElement ).contextElement
?.ownerDocument;
} else if ( anchor ) {
resultingReferenceOwnerDoc = anchor.ownerDocument;
} else if ( ( anchorRef as PopoverAnchorRefTopBottom | undefined )?.top ) {
resultingReferenceOwnerDoc = ( anchorRef as PopoverAnchorRefTopBottom )
?.top.ownerDocument;
} else if ( ( anchorRef as Range | undefined )?.startContainer ) {
resultingReferenceOwnerDoc = ( anchorRef as Range ).startContainer
.ownerDocument;
} else if (
( anchorRef as PopoverAnchorRefReference | undefined )?.current
) {
resultingReferenceOwnerDoc = (
( anchorRef as PopoverAnchorRefReference ).current as Element
).ownerDocument;
} else if ( anchorRef as Element | undefined ) {
// This one should be deprecated.
resultingReferenceOwnerDoc = ( anchorRef as Element ).ownerDocument;
} else if ( anchorRect && anchorRect?.ownerDocument ) {
resultingReferenceOwnerDoc = anchorRect.ownerDocument;
} else if ( getAnchorRect ) {
resultingReferenceOwnerDoc = getAnchorRect(
fallbackReferenceElement
)?.ownerDocument;
}
function isTopBottom(
anchorRef: PopoverProps[ 'anchorRef' ]
): anchorRef is PopoverAnchorRefTopBottom {
return !! ( anchorRef as PopoverAnchorRefTopBottom )?.top;
}

return resultingReferenceOwnerDoc ?? fallbackDocument;
};
function isRef(
anchorRef: PopoverProps[ 'anchorRef' ]
): anchorRef is PopoverAnchorRefReference {
return !! ( anchorRef as PopoverAnchorRefReference )?.current;
}

export const getReferenceElement = ( {
anchor,
Expand All @@ -211,19 +166,15 @@ export const getReferenceElement = ( {

if ( anchor ) {
referenceElement = anchor;
} else if ( ( anchorRef as PopoverAnchorRefTopBottom | undefined )?.top ) {
} else if ( isTopBottom( anchorRef ) ) {
// Create a virtual element for the ref. The expectation is that
// if anchorRef.top is defined, then anchorRef.bottom is defined too.
// Seems to be used by the block toolbar, when multiple blocks are selected
// (top and bottom blocks are used to calculate the resulting rect).
referenceElement = {
getBoundingClientRect() {
const topRect = (
anchorRef as PopoverAnchorRefTopBottom
).top.getBoundingClientRect();
const bottomRect = (
anchorRef as PopoverAnchorRefTopBottom
).bottom.getBoundingClientRect();
const topRect = anchorRef.top.getBoundingClientRect();
const bottomRect = anchorRef.bottom.getBoundingClientRect();
return new window.DOMRect(
topRect.x,
topRect.y,
Expand All @@ -232,12 +183,10 @@ export const getReferenceElement = ( {
);
},
};
} else if (
( anchorRef as PopoverAnchorRefReference | undefined )?.current
) {
} else if ( isRef( anchorRef ) ) {
// Standard React ref.
referenceElement = ( anchorRef as PopoverAnchorRefReference ).current;
} else if ( anchorRef as Element | undefined ) {
referenceElement = anchorRef.current;
} else if ( anchorRef ) {
// If `anchorRef` holds directly the element's value (no `current` key)
// This is a weird scenario and should be deprecated.
referenceElement = anchorRef as Element;
Expand Down