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

[Popper] Convert code to typescript #34771

Merged
merged 15 commits into from Dec 20, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/pages/base/api/popper-unstyled.json
Expand Up @@ -50,7 +50,7 @@
"styles": { "classes": [], "globalClasses": {}, "name": null },
"spread": true,
"forwardsRefTo": "HTMLDivElement",
"filename": "/packages/mui-base/src/PopperUnstyled/PopperUnstyled.js",
"filename": "/packages/mui-base/src/PopperUnstyled/PopperUnstyled.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/base/react-popper/\">Unstyled Popper</a></li></ul>",
"cssComponent": false
Expand Down
10 changes: 2 additions & 8 deletions docs/pages/material-ui/api/popper.json
Expand Up @@ -13,10 +13,6 @@
"type": { "name": "shape", "description": "{ root?: func<br>&#124;&nbsp;object }" }
},
"container": { "type": { "name": "union", "description": "HTML element<br>&#124;&nbsp;func" } },
"direction": {
"type": { "name": "enum", "description": "'ltr'<br>&#124;&nbsp;'rtl'" },
"default": "'ltr'"
},
"disablePortal": { "type": { "name": "bool" } },
"keepMounted": { "type": { "name": "bool" } },
"modifiers": {
Expand All @@ -29,15 +25,13 @@
"type": {
"name": "enum",
"description": "'auto-end'<br>&#124;&nbsp;'auto-start'<br>&#124;&nbsp;'auto'<br>&#124;&nbsp;'bottom-end'<br>&#124;&nbsp;'bottom-start'<br>&#124;&nbsp;'bottom'<br>&#124;&nbsp;'left-end'<br>&#124;&nbsp;'left-start'<br>&#124;&nbsp;'left'<br>&#124;&nbsp;'right-end'<br>&#124;&nbsp;'right-start'<br>&#124;&nbsp;'right'<br>&#124;&nbsp;'top-end'<br>&#124;&nbsp;'top-start'<br>&#124;&nbsp;'top'"
},
"default": "'bottom'"
}
},
"popperOptions": {
"type": {
"name": "shape",
"description": "{ modifiers?: array, onFirstUpdate?: func, placement?: 'auto-end'<br>&#124;&nbsp;'auto-start'<br>&#124;&nbsp;'auto'<br>&#124;&nbsp;'bottom-end'<br>&#124;&nbsp;'bottom-start'<br>&#124;&nbsp;'bottom'<br>&#124;&nbsp;'left-end'<br>&#124;&nbsp;'left-start'<br>&#124;&nbsp;'left'<br>&#124;&nbsp;'right-end'<br>&#124;&nbsp;'right-start'<br>&#124;&nbsp;'right'<br>&#124;&nbsp;'top-end'<br>&#124;&nbsp;'top-start'<br>&#124;&nbsp;'top', strategy?: 'absolute'<br>&#124;&nbsp;'fixed' }"
},
"default": "{}"
}
},
"popperRef": { "type": { "name": "custom", "description": "ref" } },
"slotProps": {
Expand Down
3 changes: 1 addition & 2 deletions docs/translations/api-docs/popper/popper.json
Expand Up @@ -16,8 +16,7 @@
"slotProps": "The props used for each slot inside the Popper.",
"slots": "The components used for each slot inside the Popper. Either a string to use a HTML element or a component.",
"sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the <a href=\"/system/getting-started/the-sx-prop/\">`sx` page</a> for more details.",
"transition": "Help supporting a react-transition-group/Transition component.",
"direction": "Direction of the text."
"transition": "Help supporting a react-transition-group/Transition component."
},
"classDescriptions": {}
}
Expand Up @@ -30,7 +30,7 @@ describe('<PopperUnstyled />', () => {
}));

it('should pass ownerState to overridable component', () => {
const CustomComponent = React.forwardRef(({ ownerState }, ref) => (
const CustomComponent = React.forwardRef<HTMLDivElement, any>(({ ownerState }, ref) => (
<div ref={ref} data-testid={ownerState.foo} />
));
render(
Expand Down
@@ -1,4 +1,5 @@
import * as React from 'react';
import { OverridableComponent } from '@mui/types';
import {
chainPropTypes,
HTMLElementType,
Expand All @@ -7,14 +8,24 @@ import {
unstable_useEnhancedEffect as useEnhancedEffect,
unstable_useForkRef as useForkRef,
} from '@mui/utils';
import { createPopper } from '@popperjs/core';
import { createPopper, Instance, Modifier, Placement, State, VirtualElement } from '@popperjs/core';
import PropTypes from 'prop-types';
import composeClasses from '../composeClasses';
import Portal from '../Portal';
import { getPopperUnstyledUtilityClass } from './popperUnstyledClasses';
import { useSlotProps } from '../utils';

function flipPlacement(placement, direction) {
import { useSlotProps, WithOptionalOwnerState } from '../utils';
import {
PopperPlacementType,
PopperTooltipProps,
PopperTooltipTypeMap,
PopperUnstyledChildrenProps,
PopperUnstyledProps,
PopperUnstyledRootSlotProps,
PopperUnstyledTransitionProps,
PopperUnstyledTypeMap,
} from './PopperUnstyled.types';

function flipPlacement(placement?: PopperPlacementType, direction?: 'ltr' | 'rtl') {
if (direction === 'ltr') {
return placement;
}
Expand All @@ -33,10 +44,26 @@ function flipPlacement(placement, direction) {
}
}

function resolveAnchorEl(anchorEl) {
function resolveAnchorEl(
anchorEl:
| VirtualElement
| (() => VirtualElement)
| HTMLElement
| (() => HTMLElement)
| null
| undefined,
): HTMLElement | VirtualElement | null | undefined {
return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
}

function isHTMLElement(element: HTMLElement | VirtualElement): element is HTMLElement {
return (element as HTMLElement).nodeType !== undefined;
}

function isVirtualElement(element: HTMLElement | VirtualElement): element is VirtualElement {
return !isHTMLElement(element);
}

const useUtilityClasses = () => {
const slots = {
root: ['root'],
Expand All @@ -47,8 +74,10 @@ const useUtilityClasses = () => {

const defaultPopperOptions = {};

/* eslint-disable react/prop-types */
const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
const PopperTooltip = React.forwardRef(function PopperTooltip(
props: PopperTooltipProps,
ref: React.ForwardedRef<HTMLElement>,
) {
const {
anchorEl,
children,
Expand All @@ -67,26 +96,26 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
...other
} = props;

const tooltipRef = React.useRef(null);
const tooltipRef = React.useRef<HTMLElement | null>(null);
const ownRef = useForkRef(tooltipRef, ref);

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

const rtlPlacement = flipPlacement(initialPlacement, direction);
/**
* placement initialized from prop but can change during lifetime if modifiers.flip.
* modifiers.flip is essentially a flip for controlled/uncontrolled behavior
*/
const [placement, setPlacement] = React.useState(rtlPlacement);
const [resolvedAnchorElement, setResolvedAnchorElement] = React.useState(
resolveAnchorEl(anchorEl),
);
const [placement, setPlacement] = React.useState<Placement | undefined>(rtlPlacement);
const [resolvedAnchorElement, setResolvedAnchorElement] = React.useState<
HTMLElement | VirtualElement | null | undefined
>(resolveAnchorEl(anchorEl));

React.useEffect(() => {
if (popperRef.current) {
Expand All @@ -105,12 +134,16 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
return undefined;
}

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

if (process.env.NODE_ENV !== 'production') {
if (resolvedAnchorElement && resolvedAnchorElement.nodeType === 1) {
if (
resolvedAnchorElement &&
isHTMLElement(resolvedAnchorElement) &&
resolvedAnchorElement.nodeType === 1
) {
const box = resolvedAnchorElement.getBoundingClientRect();

if (
Expand All @@ -131,7 +164,7 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
}
}

let popperModifiers = [
let popperModifiers: Partial<Modifier<any, any>>[] = [
{
name: 'preventOverflow',
options: {
Expand Down Expand Up @@ -161,29 +194,29 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
popperModifiers = popperModifiers.concat(popperOptions.modifiers);
}

const popper = createPopper(resolvedAnchorElement, tooltipRef.current, {
const popper = createPopper(resolvedAnchorElement, tooltipRef.current!, {
placement: rtlPlacement,
...popperOptions,
modifiers: popperModifiers,
});

handlePopperRefRef.current(popper);
handlePopperRefRef.current!(popper);

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

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

if (TransitionProps !== null) {
childProps.TransitionProps = TransitionProps;
}

const classes = useUtilityClasses();
const Root = component ?? slots.root ?? 'div';
const rootProps = useSlotProps({
const rootProps: WithOptionalOwnerState<PopperUnstyledRootSlotProps> = useSlotProps({
elementType: Root,
externalSlotProps: slotProps.root,
externalForwardedProps: other,
Expand All @@ -202,13 +235,23 @@ const PopperTooltip = React.forwardRef(function PopperTooltip(props, ref) {
return (
<Root {...rootProps}>{typeof children === 'function' ? children(childProps) : children}</Root>
);
});
/* eslint-enable react/prop-types */
}) as OverridableComponent<PopperTooltipTypeMap>;

/**
* Poppers rely on the 3rd party library [Popper.js](https://popper.js.org/docs/v2/) for positioning.
*
* Demos:
*
* - [Unstyled Popper](https://mui.com/base/react-popper/)
*
* API:
*
* - [PopperUnstyled API](https://mui.com/base/api/popper-unstyled/)
*/
const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) {
const PopperUnstyled = React.forwardRef(function PopperUnstyled(
props: PopperUnstyledProps,
ref: React.ForwardedRef<HTMLDivElement>,
) {
const {
anchorEl,
children,
Expand All @@ -223,6 +266,8 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) {
popperRef,
style,
transition = false,
slotProps = {},
slots = {},
...other
} = props;

Expand All @@ -243,8 +288,24 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) {
// If the container prop is provided, use that
// If the anchorEl prop is provided, use its parent body element as the container
// If neither are provided let the Modal take care of choosing the container
const container =
containerProp || (anchorEl ? ownerDocument(resolveAnchorEl(anchorEl)).body : undefined);
let container;
if (containerProp) {
container = containerProp;
} else if (anchorEl) {
const resolvedAnchorEl = resolveAnchorEl(anchorEl);
container =
resolvedAnchorEl && isHTMLElement(resolvedAnchorEl)
? ownerDocument(resolvedAnchorEl).body
: ownerDocument(null).body;
}
const display = !open && keepMounted && (!transition || exited) ? 'none' : undefined;
const transitionProps: PopperUnstyledTransitionProps | undefined = transition
? {
in: open,
onEnter: handleEnter,
onExited: handleExited,
}
: undefined;

return (
<Portal disablePortal={disablePortal} container={container}>
Expand All @@ -258,36 +319,30 @@ const PopperUnstyled = React.forwardRef(function PopperUnstyled(props, ref) {
placement={placement}
popperOptions={popperOptions}
popperRef={popperRef}
slotProps={slotProps}
slots={slots}
{...other}
style={{
// Prevents scroll issue, waiting for Popper.js to add this style once initiated.
position: 'fixed',
// Fix Popper.js display issue
top: 0,
left: 0,
display: !open && keepMounted && (!transition || exited) ? 'none' : null,
display,
...style,
}}
TransitionProps={
transition
? {
in: open,
onEnter: handleEnter,
onExited: handleExited,
}
: null
}
TransitionProps={transitionProps}
>
{children}
</PopperTooltip>
</Portal>
);
});
}) as OverridableComponent<PopperUnstyledTypeMap>;

PopperUnstyled.propTypes /* remove-proptypes */ = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the d.ts file and run "yarn proptypes" |
// | To update them edit TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* An HTML element, [virtualElement](https://popper.js.org/docs/v2/virtual-elements/),
Expand All @@ -301,7 +356,11 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
if (props.open) {
const resolvedAnchorEl = resolveAnchorEl(props.anchorEl);

if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) {
if (
resolvedAnchorEl &&
isHTMLElement(resolvedAnchorEl) &&
resolvedAnchorEl.nodeType === 1
) {
const box = resolvedAnchorEl.getBoundingClientRect();

if (
Expand All @@ -322,7 +381,8 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
} else if (
!resolvedAnchorEl ||
typeof resolvedAnchorEl.getBoundingClientRect !== 'function' ||
(resolvedAnchorEl.contextElement != null &&
(isVirtualElement(resolvedAnchorEl) &&
resolvedAnchorEl.contextElement != null &&
resolvedAnchorEl.contextElement.nodeType !== 1)
) {
return new Error(
Expand Down Expand Up @@ -484,6 +544,6 @@ PopperUnstyled.propTypes /* remove-proptypes */ = {
* @default false
*/
transition: PropTypes.bool,
};
} as any;

export default PopperUnstyled;