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

[core] feat(Overlay): add shouldReturnFocusOnClose prop #4904

Merged
merged 1 commit into from
Sep 14, 2021
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
30 changes: 1 addition & 29 deletions packages/core/src/components/drawer/drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,6 @@ export interface IDrawerProps extends OverlayableProps, IBackdropProps, Props {
*/
position?: Position;

/**
* Whether the application should return focus to the last active element in the
* document after this drawer closes.
*
* @default true
*/
shouldReturnFocusOnClose?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this not a break?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more like a QOL improvement for anyone that relies on keyboard navigation, since the focus would previously return to body. If you were manually setting focus in onClose, that still runs at the end of this change so your custom focus behavior will still work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not a break, DrawerProps extends from OverlayableProps!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, i missed that! 👍


/**
* CSS size of the drawer. This sets `width` if `vertical={false}` (default)
* and `height` otherwise.
Expand Down Expand Up @@ -123,7 +115,6 @@ export class Drawer extends AbstractPureComponent2<DrawerProps> {
public static defaultProps: DrawerProps = {
canOutsideClickClose: true,
isOpen: false,
shouldReturnFocusOnClose: true,
style: {},
vertical: false,
};
Expand All @@ -137,8 +128,6 @@ export class Drawer extends AbstractPureComponent2<DrawerProps> {
/** @deprecated use DrawerSize.LARGE */
public static readonly SIZE_LARGE = DrawerSize.LARGE;

private lastActiveElementBeforeOpened: Element | null | undefined;

public render() {
// eslint-disable-next-line deprecation/deprecation
const { size, style, position, vertical } = this.props;
Expand All @@ -161,12 +150,7 @@ export class Drawer extends AbstractPureComponent2<DrawerProps> {
[(realPosition ? isPositionHorizontal(realPosition) : vertical) ? "height" : "width"]: size,
};
return (
<Overlay
{...this.props}
className={Classes.OVERLAY_CONTAINER}
onOpening={this.handleOpening}
onClosed={this.handleClosed}
>
<Overlay {...this.props} className={Classes.OVERLAY_CONTAINER}>
<div className={classes} style={styleProp}>
{this.maybeRenderHeader()}
{this.props.children}
Expand Down Expand Up @@ -226,16 +210,4 @@ export class Drawer extends AbstractPureComponent2<DrawerProps> {
</div>
);
}

private handleOpening = (node: HTMLElement) => {
this.lastActiveElementBeforeOpened = document.activeElement;
this.props.onOpening?.(node);
};

private handleClosed = (node: HTMLElement) => {
if (this.props.shouldReturnFocusOnClose && this.lastActiveElementBeforeOpened instanceof HTMLElement) {
this.lastActiveElementBeforeOpened.focus();
}
this.props.onClosed?.(node);
};
}
24 changes: 22 additions & 2 deletions packages/core/src/components/overlay/overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export interface IOverlayableProps extends IOverlayLifecycleProps {
*/
lazy?: boolean;

/**
* Whether the application should return focus to the last active element in the
* document after this overlay closes.
*
* @default true
*/
shouldReturnFocusOnClose?: boolean;

/**
* Indicates how long (in milliseconds) the overlay's enter/leave transition takes.
* This is used by React `CSSTransition` to know when a transition completes and must match
Expand Down Expand Up @@ -206,6 +214,7 @@ export class Overlay extends AbstractPureComponent2<OverlayProps, IOverlayState>
hasBackdrop: true,
isOpen: false,
lazy: true,
shouldReturnFocusOnClose: true,
transitionDuration: 300,
transitionName: Classes.OVERLAY,
usePortal: true,
Expand All @@ -222,6 +231,8 @@ export class Overlay extends AbstractPureComponent2<OverlayProps, IOverlayState>

private static getLastOpened = () => Overlay.openStack[Overlay.openStack.length - 1];

private lastActiveElementBeforeOpened: Element | null | undefined;

public state: IOverlayState = {
hasEverOpened: this.props.isOpen,
};
Expand Down Expand Up @@ -351,7 +362,7 @@ export class Overlay extends AbstractPureComponent2<OverlayProps, IOverlayState>
) : (
<span className={Classes.OVERLAY_CONTENT}>{child}</span>
);
const { onOpening, onOpened, onClosing, onClosed, transitionDuration, transitionName } = this.props;
const { onOpening, onOpened, onClosing, transitionDuration, transitionName } = this.props;

// a breaking change in react-transition-group types requires us to be explicit about the type overload here,
// using a technique similar to Select.ofType() in @blueprintjs/select
Expand All @@ -365,7 +376,7 @@ export class Overlay extends AbstractPureComponent2<OverlayProps, IOverlayState>
onEntering={onOpening}
onEntered={onOpened}
onExiting={onClosing}
onExited={onClosed}
onExited={this.handleTransitionExited}
timeout={transitionDuration}
addEndListener={this.handleTransitionAddEnd}
>
Expand Down Expand Up @@ -448,8 +459,17 @@ export class Overlay extends AbstractPureComponent2<OverlayProps, IOverlayState>
// add a class to the body to prevent scrolling of content below the overlay
document.body.classList.add(Classes.OVERLAY_OPEN);
}

this.lastActiveElementBeforeOpened = document.activeElement;
}

private handleTransitionExited = (node: HTMLElement) => {
if (this.props.shouldReturnFocusOnClose && this.lastActiveElementBeforeOpened instanceof HTMLElement) {
this.lastActiveElementBeforeOpened.focus();
}
this.props.onClosed?.(node);
};

private handleBackdropMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const { backdropProps, canOutsideClickClose, enforceFocus, onClose } = this.props;
if (canOutsideClickClose) {
Expand Down