Skip to content

Commit

Permalink
fix: correct focus order for non-autofocusable modals, fixes #340
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Dec 15, 2024
1 parent 2ef544d commit 5b06e8d
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 28 deletions.
14 changes: 9 additions & 5 deletions src/Trap.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,13 @@ const activateTrap = () => {
const workingNode = observed || (lastPortaledElement && lastPortaledElement.portaledElement);

// check if lastActiveFocus is still reachable
if (focusOnBody() && lastActiveFocus) {
if (focusOnBody() && lastActiveFocus && lastActiveFocus !== document.body) {
if (
// it was removed
!document.body.contains(lastActiveFocus)
// or not focusable (this is expensive operation)!
|| isNotFocusable(lastActiveFocus)
) {
lastActiveFocus = null;

const newTarget = tryRestoreFocus();
if (newTarget) {
newTarget.focus();
Expand Down Expand Up @@ -159,6 +157,10 @@ const activateTrap = () => {
|| focusIsPortaledPair(activeElement, workingNode)
)
) {
// in case there no yet selected element(first activation),
// but there is some active element
// and autofocus is off
// - we blur currently active element and move focus to the body
if (document && !lastActiveFocus && activeElement && !autoFocus) {
// Check if blur() exists, which is missing on certain elements on IE
if (activeElement.blur) {
Expand All @@ -170,9 +172,11 @@ const activateTrap = () => {
lastPortaledElement = {};
}
}
focusWasOutsideWindow = false;
lastActiveFocus = document && document.activeElement;
tryRestoreFocus = captureFocusRestore(lastActiveFocus);
if (lastActiveFocus !== document.body) {
tryRestoreFocus = captureFocusRestore(lastActiveFocus);
}
focusWasOutsideWindow = false;
}
}

Expand Down
103 changes: 82 additions & 21 deletions stories/Exotic.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,41 @@ import * as React from 'react';
import FocusLock from '../src/index';

export class Video extends React.Component {
state = {
disabled: true,
}
state = {
disabled: true,
}

toggle = () => this.setState({ disabled: !this.state.disabled });
toggle = () => this.setState({ disabled: !this.state.disabled });

render() {
const { disabled } = this.state;
return (
<div>
<button onClick={this.toggle}>!ACTIVATE THE TRAP!</button>
<FocusLock disabled={disabled} _whiteList={(node) => { console.log(node); return false; }}>
<button onClick={this.toggle}>deactivate</button>
<video controls width="250">
<source src="https://interactive-examples.mdn.mozilla.net/media/examples/flower.webm" type="video/webm" />
<source src="https://interactive-examples.mdn.mozilla.net/media/examples/flower.mp4" type="video/mp4" />
Sorry, your browser doesn't support embedded videos.
</video>
<button onClick={this.toggle}>deactivate</button>
</FocusLock>
</div>
);
}
render() {
const { disabled } = this.state;
return (
<div>
<button onClick={this.toggle}>!ACTIVATE THE TRAP!</button>
<FocusLock
disabled={disabled}
_whiteList={(node) => {
console.log(node);
return false;
}}
>
<button onClick={this.toggle}>deactivate</button>
<video controls width="250">
<source
src="https://interactive-examples.mdn.mozilla.net/media/examples/flower.webm"
type="video/webm"
/>
<source
src="https://interactive-examples.mdn.mozilla.net/media/examples/flower.mp4"
type="video/mp4"
/>
Sorry, your browser doesn't support embedded videos.
</video>
<button onClick={this.toggle}>deactivate</button>
</FocusLock>
</div>
);
}
}

export const FormOverride = () => (
Expand All @@ -36,3 +48,52 @@ export const FormOverride = () => (
</form>
</FocusLock>
);


const ModalWithoutAutoFocus = () => (
<div>
<dialog open style={{ border: '1px solid black' }}>
<FocusLock autoFocus={false}>
<div>
<h4>Title</h4>
<div>
<button>Button A</button>
</div>
<div>
<button>Button B</button>
</div>
<div>
<button>Button C</button>
</div>
</div>
</FocusLock>
</dialog>
</div>
);
export const NonAutofocusModal = () => {
const [isOpen, togglerIsOpen] = React.useState(false);

return (
<div className="App">
<div>
<div>
<button onClick={() => togglerIsOpen(true)}>Open modal</button>
</div>
<div>
<button>Other Button</button>
</div>
<div>
<button>Other Button</button>
</div>
<div>
<button>Other Button</button>
</div>
<div>
<button>Other Button</button>
</div>
</div>
<div>{isOpen && <ModalWithoutAutoFocus />}</div>
<button>Other Button</button>
</div>
);
};
5 changes: 3 additions & 2 deletions stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { MUISelect, MUISelectWhite } from './MUI';
import Fight from './FocusFighting';
import { StyledComponent, StyledSection } from './Custom';
import { DisabledForm, DisabledFormWithTabIndex } from './Disabled';
import { FormOverride, Video } from './Exotic';
import { FormOverride, Video, NonAutofocusModal } from './Exotic';
import { TabbableParent } from './TabbableParent';
import { ControlTrapExample, GroupRowingFocusExample, RowingFocusExample } from './control';

Expand Down Expand Up @@ -83,7 +83,8 @@ storiesOf('Exotic', module)
.add('iframe - Sandbox', () => <Frame><SandboxedIFrame /></Frame>)
.add('sidecar', () => <Frame><SideCar /></Frame>)
.add('tabbable parent', () => <Frame><TabbableParent /></Frame>)
.add('form override', () => <Frame><FormOverride /></Frame>);
.add('form override', () => <Frame><FormOverride /></Frame>)
.add('non autofocusable', () => <Frame><NonAutofocusModal /></Frame>);

storiesOf('FocusScope', module)
.add('keyboard navigation', () => <Frame><ControlTrapExample /></Frame>)
Expand Down

0 comments on commit 5b06e8d

Please sign in to comment.