Skip to content

Commit

Permalink
[core] Use hidden prop for keepMounted elements (#815)
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks authored Nov 21, 2024
1 parent 1025665 commit f804f4b
Show file tree
Hide file tree
Showing 24 changed files with 182 additions and 119 deletions.
1 change: 1 addition & 0 deletions docs/src/app/experiments/anchor-positioning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default function AnchorPositioning() {
sticky,
arrowPadding,
trackAnchor,
mounted: true,
});

const handleInitialScroll = React.useCallback((node: HTMLDivElement | null) => {
Expand Down
18 changes: 7 additions & 11 deletions docs/src/app/experiments/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const TooltipPopup = styled(Tooltip.Popup)`
&[data-type='css-animation'] {
&[data-open] {
visibility: visible;
animation: ${scaleIn} 0.2s forwards;
}
Expand All @@ -51,21 +50,17 @@ export const TooltipPopup = styled(Tooltip.Popup)`
}
&[data-type='css-animation-keep-mounted'] {
visibility: hidden;
&[data-open] {
visibility: visible;
animation: ${scaleIn} 0.2s forwards;
}
&[data-exiting] {
visibility: visible;
animation: ${scaleOut} 0.2s forwards;
}
}
&[data-type='css-transition'] {
transition-property: opacity, transform, visibility;
transition-property: opacity, transform;
transition-duration: 0.2s;
opacity: 0;
transform: scale(0);
Expand All @@ -82,16 +77,17 @@ export const TooltipPopup = styled(Tooltip.Popup)`
}
&[data-type='css-transition-keep-mounted'] {
transition-property: opacity, transform, visibility;
transition-property: opacity, transform;
transition-duration: 0.2s;
opacity: 0;
transform: scale(0.8);
visibility: hidden;
&[data-open] {
opacity: 1;
transform: scale(1);
visibility: visible;
}
&[data-entering] {
opacity: 0;
transform: scale(0.8);
}
&[data-exiting] {
Expand Down
3 changes: 0 additions & 3 deletions packages/mui-base/src/Checkbox/Root/useCheckboxRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useEnhancedEffect } from '../../utils/useEnhancedEffect';
import { useFieldRootContext } from '../../Field/Root/FieldRootContext';
import { useFieldControlValidation } from '../../Field/Control/useFieldControlValidation';
import { useField } from '../../Field/useField';
import { getInertValue } from '../../utils/getInertValue';

export function useCheckboxRoot(params: UseCheckboxRoot.Parameters): UseCheckboxRoot.ReturnValue {
const {
Expand Down Expand Up @@ -126,8 +125,6 @@ export function useCheckboxRoot(params: UseCheckboxRoot.Parameters): UseCheckbox
tabIndex: -1,
type: 'checkbox',
'aria-hidden': true,
// @ts-ignore
inert: getInertValue(true),
onChange(event) {
// Workaround for https://github.com/facebook/react/issues/9023
if (event.nativeEvent.defaultPrevented) {
Expand Down
5 changes: 3 additions & 2 deletions packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ export function useDialogBackdrop(

const getRootProps = React.useCallback(
(externalProps: React.ComponentPropsWithRef<any>) =>
mergeReactProps(externalProps, {
mergeReactProps<'div'>(externalProps, {
role: 'presentation',
ref: handleRef,
hidden: !mounted,
}),
[handleRef],
[handleRef, mounted],
);

return {
Expand Down
3 changes: 2 additions & 1 deletion packages/mui-base/src/Dialog/Popup/useDialogPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function useDialogPopup(parameters: useDialogPopup.Parameters): useDialog
}, [id, setPopupElementId]);

const getRootProps = (externalProps: React.HTMLAttributes<any>) =>
mergeReactProps(externalProps, {
mergeReactProps<'div'>(externalProps, {
'aria-labelledby': titleElementId ?? undefined,
'aria-describedby': descriptionElementId ?? undefined,
'aria-hidden': !open || undefined,
Expand All @@ -81,6 +81,7 @@ export function useDialogPopup(parameters: useDialogPopup.Parameters): useDialog
...getPopupProps(),
id,
ref: handleRef,
hidden: !mounted,
});

return {
Expand Down
16 changes: 13 additions & 3 deletions packages/mui-base/src/Dialog/Root/useDialogRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { type InteractionType } from '../../utils/useEnhancedClickHandler';
import { type GenericHTMLProps } from '../../utils/types';
import { useOpenInteractionType } from '../../utils/useOpenInteractionType';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useLatestRef } from '../../utils/useLatestRef';

export function useDialogRoot(parameters: useDialogRoot.Parameters): useDialogRoot.ReturnValue {
const {
Expand All @@ -38,8 +39,6 @@ export function useDialogRoot(parameters: useDialogRoot.Parameters): useDialogRo

const popupRef = React.useRef<HTMLElement>(null);

const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);
const runOnceAnimationsFinish = useAnimationsFinished(popupRef);
const [titleElementId, setTitleElementId] = React.useState<string | undefined>(undefined);
const [descriptionElementId, setDescriptionElementId] = React.useState<string | undefined>(
undefined,
Expand All @@ -48,12 +47,23 @@ export function useDialogRoot(parameters: useDialogRoot.Parameters): useDialogRo
const [popupElement, setPopupElement] = React.useState<HTMLElement | null>(null);
const [popupElementId, setPopupElementId] = React.useState<string | undefined>(undefined);

const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);

const runOnceAnimationsFinish = useAnimationsFinished(popupRef);

const openRef = useLatestRef(open);

const setOpen = useEventCallback((nextOpen: boolean, event?: Event) => {
onOpenChange?.(nextOpen, event);
setOpenUnwrapped(nextOpen);

if (!keepMounted && !nextOpen) {
if (animated) {
runOnceAnimationsFinish(() => setMounted(false));
runOnceAnimationsFinish(() => {
if (!openRef.current) {
setMounted(false);
}
});
} else {
setMounted(false);
}
Expand Down
19 changes: 10 additions & 9 deletions packages/mui-base/src/Menu/Positioner/useMenuPositioner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import type {
import { mergeReactProps } from '../../utils/mergeReactProps';
import { Boundary, useAnchorPositioning } from '../../utils/useAnchorPositioning';
import type { GenericHTMLProps } from '../../utils/types';
import { getInertValue } from '../../utils/getInertValue';
import { useMenuRootContext } from '../Root/MenuRootContext';

export function useMenuPositioner(
params: useMenuPositioner.Parameters,
): useMenuPositioner.ReturnValue {
const { open = false, keepMounted } = params;
const { keepMounted, mounted } = params;

const { open } = useMenuRootContext();

const {
positionerStyles,
Expand All @@ -36,16 +38,16 @@ export function useMenuPositioner(
hiddenStyles.pointerEvents = 'none';
}

return mergeReactProps(externalProps, {
return mergeReactProps<'div'>(externalProps, {
role: 'presentation',
hidden: !mounted,
style: {
...positionerStyles,
...hiddenStyles,
},
'aria-hidden': !open || undefined,
inert: getInertValue(!open),
});
},
[positionerStyles, open, keepMounted, hidden],
[keepMounted, open, hidden, positionerStyles, mounted],
);

return React.useMemo(
Expand Down Expand Up @@ -151,10 +153,9 @@ export namespace useMenuPositioner {

export interface Parameters extends SharedParameters {
/**
* If `true`, the Menu is mounted.
* @default true
* Whether the Menu is mounted.
*/
mounted?: boolean;
mounted: boolean;
/**
* The Menu root context.
*/
Expand Down
10 changes: 9 additions & 1 deletion packages/mui-base/src/Menu/Root/useMenuRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useEventCallback } from '../../utils/useEventCallback';
import { useAnimationsFinished } from '../../utils/useAnimationsFinished';
import { useControlled } from '../../utils/useControlled';
import { TYPEAHEAD_RESET_MS } from '../../utils/constants';
import { useLatestRef } from '../../utils/useLatestRef';

const EMPTY_ARRAY: never[] = [];

Expand Down Expand Up @@ -55,12 +56,19 @@ export function useMenuRoot(parameters: useMenuRoot.Parameters): useMenuRoot.Ret
const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, animated);

const runOnceAnimationsFinish = useAnimationsFinished(popupRef);

const openRef = useLatestRef(open);

const setOpen = useEventCallback((nextOpen: boolean, event?: Event) => {
onOpenChange?.(nextOpen, event);
setOpenUnwrapped(nextOpen);
if (!nextOpen) {
if (animated) {
runOnceAnimationsFinish(() => setMounted(false));
runOnceAnimationsFinish(() => {
if (!openRef.current) {
setMounted(false);
}
});
} else {
setMounted(false);
}
Expand Down
27 changes: 17 additions & 10 deletions packages/mui-base/src/Popover/Backdrop/usePopoverBackdrop.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import * as React from 'react';
import { mergeReactProps } from '../../utils/mergeReactProps';
import type { GenericHTMLProps } from '../../utils/types';
import { usePopoverRootContext } from '../Root/PopoverRootContext';

export function usePopoverBackdrop(): usePopoverBackdrop.ReturnValue {
const getBackdropProps = React.useCallback((externalProps = {}) => {
return mergeReactProps<'div'>(externalProps, {
role: 'presentation',
style: {
overflow: 'auto',
position: 'fixed',
inset: 0,
},
});
}, []);
const { mounted } = usePopoverRootContext();

const getBackdropProps = React.useCallback(
(externalProps = {}) => {
return mergeReactProps<'div'>(externalProps, {
role: 'presentation',
hidden: !mounted,
style: {
overflow: 'auto',
position: 'fixed',
inset: 0,
},
});
},
[mounted],
);

return React.useMemo(
() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ describe('<Popover.Positioner />', () => {
}));

describe('prop: keepMounted', () => {
it('has inert attribute when closed', async () => {
it('has hidden attribute when closed', async () => {
await render(
<Popover.Root animated={false}>
<Popover.Positioner keepMounted data-testid="positioner" />
</Popover.Root>,
);

expect(screen.getByTestId('positioner')).to.have.attribute('inert');
expect(screen.getByTestId('positioner')).to.have.attribute('hidden');
});

it('does not have inert attribute when open', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const PopoverPositioner = React.forwardRef(function PopoverPositioner(
anchor,
floatingRootContext,
positionMethod,
mounted,
open,
keepMounted,
side,
Expand Down
15 changes: 10 additions & 5 deletions packages/mui-base/src/Popover/Positioner/usePopoverPositioner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import type {
import { mergeReactProps } from '../../utils/mergeReactProps';
import { Boundary, useAnchorPositioning } from '../../utils/useAnchorPositioning';
import type { GenericHTMLProps } from '../../utils/types';
import { getInertValue } from '../../utils/getInertValue';
import { InteractionType } from '../../utils/useEnhancedClickHandler';
import { usePopoverRootContext } from '../Root/PopoverRootContext';

export function usePopoverPositioner(
params: usePopoverPositioner.Parameters,
): usePopoverPositioner.ReturnValue {
const { open = false, keepMounted = false, initialFocus, openMethod, popupRef } = params;
const { keepMounted, initialFocus, openMethod, popupRef, mounted } = params;

const { open } = usePopoverRootContext();

const {
positionerStyles,
Expand Down Expand Up @@ -64,15 +66,14 @@ export function usePopoverPositioner(

return mergeReactProps<'div'>(externalProps, {
role: 'presentation',
// @ts-ignore
inert: getInertValue(!open),
hidden: !mounted,
style: {
...positionerStyles,
...hiddenStyles,
},
});
},
[positionerStyles, open, keepMounted, hidden],
[keepMounted, open, hidden, mounted, positionerStyles],
);

return React.useMemo(
Expand Down Expand Up @@ -185,6 +186,10 @@ export namespace usePopoverPositioner {
}

export interface Parameters extends SharedParameters {
/**
* Whether the popover is mounted.
*/
mounted: boolean;
/**
* Whether the popover is open.
* @default false
Expand Down
9 changes: 8 additions & 1 deletion packages/mui-base/src/Popover/Root/usePopoverRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { TransitionStatus } from '../../utils/useTransitionStatus';
import { type InteractionType } from '../../utils/useEnhancedClickHandler';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useOpenInteractionType } from '../../utils/useOpenInteractionType';
import { useLatestRef } from '../../utils/useLatestRef';
import {
translateOpenChangeReason,
type OpenChangeReason,
Expand Down Expand Up @@ -62,13 +63,19 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo

const runOnceAnimationsFinish = useAnimationsFinished(popupRef);

const openRef = useLatestRef(open);

const setOpen = useEventCallback(
(nextOpen: boolean, event?: Event, reason?: OpenChangeReason) => {
onOpenChange(nextOpen, event, reason);
setOpenUnwrapped(nextOpen);
if (!keepMounted && !nextOpen) {
if (animated) {
runOnceAnimationsFinish(() => setMounted(false));
runOnceAnimationsFinish(() => {
if (!openRef.current) {
setMounted(false);
}
});
} else {
setMounted(false);
}
Expand Down
Loading

0 comments on commit f804f4b

Please sign in to comment.