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

Draft: [PopperUnstyled] Replace Popper.js package with Floating UI package #35914

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions packages/mui-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"dependencies": {
"@babel/runtime": "^7.20.7",
"@emotion/is-prop-valid": "^1.2.0",
"@floating-ui/dom": "^1.1.0",
"@mui/types": "^7.2.3",
"@mui/utils": "^5.11.2",
"@popperjs/core": "^2.11.6",
Expand Down
139 changes: 55 additions & 84 deletions packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ import {
unstable_useEnhancedEffect as useEnhancedEffect,
unstable_useForkRef as useForkRef,
} from '@mui/utils';
import { createPopper, Instance, Modifier, Placement, State, VirtualElement } from '@popperjs/core';
import {
autoUpdate,
computePosition,
flip,
Middleware,
Placement,
ReferenceElement,
shift,
VirtualElement,
} from '@floating-ui/dom';
import PropTypes from 'prop-types';
import composeClasses from '../composeClasses';
import Portal from '../Portal';
Expand Down Expand Up @@ -84,7 +93,7 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(
component,
direction,
disablePortal,
modifiers,
middleware,
open,
ownerState,
placement: initialPlacement,
Expand All @@ -99,12 +108,8 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(
const tooltipRef = React.useRef<HTMLElement | null>(null);
const ownRef = useForkRef(tooltipRef, ref);

const popperRef = React.useRef<Instance | null>(null);
const handlePopperRef = useForkRef(popperRef, popperRefProp);
const handlePopperRefRef = React.useRef(handlePopperRef);
useEnhancedEffect(() => {
handlePopperRefRef.current = handlePopperRef;
}, [handlePopperRef]);
const popperRef = React.useRef<ReferenceElement | null>(null);

React.useImperativeHandle(popperRefProp, () => popperRef.current!, []);

const rtlPlacement = flipPlacement(initialPlacement, direction);
Expand All @@ -117,12 +122,6 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(
HTMLElement | VirtualElement | null | undefined
>(resolveAnchorEl(anchorEl));

React.useEffect(() => {
if (popperRef.current) {
popperRef.current.forceUpdate();
}
});

React.useEffect(() => {
if (anchorEl) {
setResolvedAnchorElement(resolveAnchorEl(anchorEl));
Expand All @@ -134,10 +133,6 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(
return undefined;
}

const handlePopperUpdate = (data: State) => {
setPlacement(data.placement);
};

if (process.env.NODE_ENV !== 'production') {
if (
resolvedAnchorElement &&
Expand All @@ -164,49 +159,27 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(
}
}

let popperModifiers: Partial<Modifier<any, any>>[] = [
{
name: 'preventOverflow',
options: {
altBoundary: disablePortal,
},
},
{
name: 'flip',
options: {
altBoundary: disablePortal,
},
},
{
name: 'onUpdate',
enabled: true,
phase: 'afterWrite',
fn: ({ state }) => {
handlePopperUpdate(state);
},
},
let popperMiddleware: Middleware[] = [
shift({ altBoundary: disablePortal }),
flip({ altBoundary: disablePortal }),
];

if (modifiers != null) {
popperModifiers = popperModifiers.concat(modifiers);
}
if (popperOptions && popperOptions.modifiers != null) {
popperModifiers = popperModifiers.concat(popperOptions.modifiers);
if (middleware != null) {
popperMiddleware = popperMiddleware.concat(middleware);
}

const popper = createPopper(resolvedAnchorElement, tooltipRef.current!, {
placement: rtlPlacement,
...popperOptions,
modifiers: popperModifiers,
const popperUnsubscribe = autoUpdate(resolvedAnchorElement, tooltipRef.current!, async () => {
const state = await computePosition(resolvedAnchorElement, tooltipRef.current!, {
placement: rtlPlacement,
...popperOptions,
middleware: popperMiddleware,
});
setPlacement(state.placement);
});

handlePopperRefRef.current!(popper);

return () => {
popper.destroy();
handlePopperRefRef.current!(null);
popperUnsubscribe();
};
}, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]);
}, [resolvedAnchorElement, disablePortal, middleware, open, popperOptions, rtlPlacement]);

const childProps: PopperUnstyledChildrenProps = { placement: placement! };

Expand Down Expand Up @@ -259,7 +232,7 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(
direction = 'ltr',
disablePortal = false,
keepMounted = false,
modifiers,
middleware,
open,
placement = 'bottom',
popperOptions = defaultPopperOptions,
Expand Down Expand Up @@ -313,7 +286,7 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(
anchorEl={anchorEl}
direction={direction}
disablePortal={disablePortal}
modifiers={modifiers}
middleware={middleware}
ref={ref}
open={transition ? !exited : open}
placement={placement}
Expand Down Expand Up @@ -442,27 +415,11 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
* For this reason, modifiers should be very performant to avoid bottlenecks.
* To learn how to create a modifier, [read the modifiers documentation](https://popper.js.org/docs/v2/modifiers/).
*/
modifiers: PropTypes.arrayOf(
middleware: PropTypes.arrayOf(
PropTypes.shape({
data: PropTypes.object,
effect: PropTypes.func,
enabled: PropTypes.bool,
fn: PropTypes.func,
name: PropTypes.any,
options: PropTypes.object,
phase: PropTypes.oneOf([
'afterMain',
'afterRead',
'afterWrite',
'beforeMain',
'beforeRead',
'beforeWrite',
'main',
'read',
'write',
]),
requires: PropTypes.arrayOf(PropTypes.string),
requiresIfExists: PropTypes.arrayOf(PropTypes.string),
fn: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
options: PropTypes.any,
}),
),
/**
Expand All @@ -474,9 +431,6 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
* @default 'bottom'
*/
placement: PropTypes.oneOf([
'auto-end',
'auto-start',
'auto',
'bottom-end',
'bottom-start',
'bottom',
Expand All @@ -495,12 +449,17 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
* @default {}
*/
popperOptions: PropTypes.shape({
modifiers: PropTypes.array,
onFirstUpdate: PropTypes.func,
middleware: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.oneOf([false]),
PropTypes.shape({
fn: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
options: PropTypes.any,
}),
]),
),
placement: PropTypes.oneOf([
'auto-end',
'auto-start',
'auto',
'bottom-end',
'bottom-start',
'bottom',
Expand All @@ -514,6 +473,18 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
'top-start',
'top',
]),
platform: PropTypes.shape({
convertOffsetParentRelativeRectToViewportRelativeRect: PropTypes.func,
getClientRects: PropTypes.func,
getClippingRect: PropTypes.func.isRequired,
getDimensions: PropTypes.func.isRequired,
getDocumentElement: PropTypes.func,
getElementRects: PropTypes.func.isRequired,
getOffsetParent: PropTypes.func,
getScale: PropTypes.func,
isElement: PropTypes.func,
isRTL: PropTypes.func,
}),
strategy: PropTypes.oneOf(['absolute', 'fixed']),
}),
/**
Expand Down
10 changes: 5 additions & 5 deletions packages/mui-base/src/PopperUnstyled/PopperUnstyled.types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ComputePositionConfig, Middleware, ReferenceElement, VirtualElement } from '@floating-ui/dom';
import { OverrideProps } from '@mui/types';
import { Instance, Options, OptionsGeneric, VirtualElement } from '@popperjs/core';
import * as React from 'react';
import { PortalProps } from '../Portal';
import { SlotComponentProps } from '../utils';

export type PopperPlacementType = Options['placement'];
export type PopperPlacementType = ComputePositionConfig['placement'];

interface PopperUnstyledComponentsPropsOverrides {}

Expand Down Expand Up @@ -65,7 +65,7 @@ export interface PopperUnstyledOwnProps {
* For this reason, modifiers should be very performant to avoid bottlenecks.
* To learn how to create a modifier, [read the modifiers documentation](https://popper.js.org/docs/v2/modifiers/).
*/
modifiers?: Options['modifiers'];
middleware?: Middleware[];
/**
* If `true`, the component is shown.
*/
Expand All @@ -79,11 +79,11 @@ export interface PopperUnstyledOwnProps {
* Options provided to the [`Popper.js`](https://popper.js.org/docs/v2/constructors/#options) instance.
* @default {}
*/
popperOptions?: Partial<OptionsGeneric<any>>;
popperOptions?: Partial<ComputePositionConfig>;
/**
* A ref that points to the used popper instance.
*/
popperRef?: React.Ref<Instance>;
popperRef?: React.Ref<ReferenceElement>;
/**
* The props used for each slot inside the Popper.
* @default {}
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,18 @@
lodash.isundefined "^3.0.1"
lodash.uniq "^4.5.0"

"@floating-ui/core@^1.0.5":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.1.0.tgz#0a1dee4bbce87ff71602625d33f711cafd8afc08"
integrity sha512-zbsLwtnHo84w1Kc8rScAo5GMk1GdecSlrflIbfnEBJwvTSj1SL6kkOYV+nHraMCPEy+RNZZUaZyL8JosDGCtGQ==

"@floating-ui/dom@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.1.0.tgz#29fea1344fdef15b6ba270a733d20b7134fee5c2"
integrity sha512-TSogMPVxbRe77QCj1dt8NmRiJasPvuc+eT5jnJ6YpLqgOD2zXc5UA3S1qwybN+GVCDNdKfpKy1oj8RpzLJvh6A==
dependencies:
"@floating-ui/core" "^1.0.5"

"@fortawesome/[email protected]":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073"
Expand Down