Skip to content

Commit 0f2bf9e

Browse files
committed
[fixed] corretly walk when using TAB.
closes #511.
1 parent 5cf9326 commit 0f2bf9e

File tree

3 files changed

+103
-71
lines changed

3 files changed

+103
-71
lines changed

Diff for: examples/basic/app.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
33
import Modal from 'react-modal';
44
import SimpleUsage from './simple_usage';
55
import MultipleModals from './multiple_modals';
6+
import Forms from './forms';
67
import ReactRouter from './react-router';
78

89
const appElement = document.getElementById('example');
@@ -11,6 +12,7 @@ Modal.setAppElement('#example');
1112

1213
const examples = [
1314
SimpleUsage,
15+
Forms,
1416
MultipleModals,
1517
ReactRouter
1618
];

Diff for: examples/basic/forms/index.js

+45-63
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,78 @@
11
import React, { Component } from 'react';
22
import Modal from 'react-modal';
3-
import MyModal from './modal';
43

54
const MODAL_A = 'modal_a';
65
const MODAL_B = 'modal_b';
76

87
const DEFAULT_TITLE = 'Default title';
98

10-
class SimpleUsage extends Component {
9+
class Forms extends Component {
1110
constructor(props) {
1211
super(props);
13-
this.state = {
14-
title1: DEFAULT_TITLE,
15-
currentModal: null
16-
};
17-
}
18-
19-
toggleModal = key => event => {
20-
event.preventDefault();
21-
if (this.state.currentModal) {
22-
this.handleModalCloseRequest();
23-
return;
24-
}
25-
26-
this.setState({
27-
...this.state,
28-
currentModal: key,
29-
title1: DEFAULT_TITLE
30-
});
31-
}
32-
33-
handleModalCloseRequest = () => {
34-
// opportunity to validate something and keep the modal open even if it
35-
// requested to be closed
36-
this.setState({
37-
...this.state,
38-
currentModal: null
39-
});
40-
}
4112

42-
handleInputChange = e => {
43-
let text = e.target.value;
44-
if (text == '') {
45-
text = DEFAULT_TITLE;
46-
}
47-
this.setState({ ...this.state, title1: text });
13+
this.state = { isOpen: false };
4814
}
4915

50-
handleOnAfterOpenModal = () => {
51-
// when ready, we can access the available refs.
52-
this.heading && (this.heading.style.color = '#F00');
16+
toggleModal = event => {
17+
console.log(event);
18+
const { isOpen } = this.state;
19+
this.setState({ isOpen: !isOpen });
5320
}
5421

55-
headingRef = h1 => this.heading = h1;
56-
5722
render() {
58-
const { currentModal } = this.state;
23+
const { isOpen } = this.state;
5924

6025
return (
6126
<div>
62-
<button onClick={this.toggleModal(MODAL_A)}>Open Modal A</button>
63-
<button onClick={this.toggleModal(MODAL_B)}>Open Modal B</button>
64-
<MyModal
65-
title={this.state.title1}
66-
isOpen={currentModal == MODAL_A}
67-
onAfterOpen={this.handleOnAfterOpenModal}
68-
onRequestClose={this.handleModalCloseRequest}
69-
askToClose={this.toggleModal(MODAL_A)}
70-
onChangeInput={this.handleInputChange} />
27+
<button className="btn btn-primary" onClick={this.toggleModal}>Open Modal</button>
7128
<Modal
72-
ref="mymodal2"
73-
id="test2"
29+
id="modal_with_forms"
30+
isOpen={isOpen}
31+
closeTimeoutMS={150}
32+
contentLabel="modalB"
33+
shouldCloseOnOverlayClick={true}
34+
onRequestClose={this.toggleModal}
7435
aria={{
7536
labelledby: "heading",
7637
describedby: "fulldescription"
77-
}}
78-
closeTimeoutMS={150}
79-
contentLabel="modalB"
80-
isOpen={currentModal == MODAL_B}
81-
onAfterOpen={this.handleOnAfterOpenModal}
82-
onRequestClose={this.toggleModal(MODAL_B)}>
83-
<h1 id="heading" ref={headingRef}>This is the modal 2!</h1>
38+
}}>
39+
<h1 id="heading">Forms!</h1>
8440
<div id="fulldescription" tabIndex="0" role="document">
8541
<p>This is a description of what it does: nothing :)</p>
86-
</div>p
42+
43+
<form>
44+
<fieldset>
45+
<input type="text" />
46+
<input type="text" />
47+
</fieldset>
48+
<fieldset>
49+
<legend>Radio buttons</legend>
50+
<label>
51+
<input id="radio-a" name="radios" type="radio" /> A
52+
</label>
53+
<label>
54+
<input id="radio-b" name="radios" type="radio" /> B
55+
</label>
56+
</fieldset>
57+
<fieldset>
58+
<legend>Checkbox buttons</legend>
59+
<label>
60+
<input id="checkbox-a" name="checkbox-a" type="checkbox" /> A
61+
</label>
62+
<label>
63+
<input id="checkbox-b" name="checkbox-b" type="checkbox" /> B
64+
</label>
65+
</fieldset>
66+
<input type="text" />
67+
</form>
68+
</div>
8769
</Modal>
8870
</div>
8971
);
9072
}
9173
}
9274

9375
export default {
94-
label: "#1. Working with one modal at a time.",
95-
app: SimpleUsage
76+
label: "#3. Modal with forms fields.",
77+
app: Forms
9678
};

Diff for: src/helpers/scopeTab.js

+56-8
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,65 @@ import findTabbable from "./tabbable";
22

33
export default function scopeTab(node, event) {
44
const tabbable = findTabbable(node);
5+
56
if (!tabbable.length) {
7+
// Do nothing, since there are no elements that can receive focus.
8+
event.preventDefault();
9+
return;
10+
}
11+
12+
const shiftKey = event.shiftKey;
13+
const head = tabbable[0];
14+
const tail = tabbable[tabbable.length - 1];
15+
16+
// proceed with default browser behavior
17+
if (node === document.activeElement) {
18+
return;
19+
}
20+
21+
var target;
22+
if (tail === document.activeElement && !shiftKey) {
23+
target = head;
24+
}
25+
26+
if (head === document.activeElement && shiftKey) {
27+
target = tail;
28+
}
29+
30+
if (target) {
631
event.preventDefault();
32+
target.focus();
733
return;
834
}
9-
const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];
10-
const leavingFinalTabbable =
11-
finalTabbable === document.activeElement ||
12-
// handle immediate shift+tab after opening with mouse
13-
node === document.activeElement;
14-
if (!leavingFinalTabbable) return;
35+
36+
// Safari radio issue.
37+
//
38+
// Safari does not move the focus to the radio button,
39+
// so we need to force it to really walk through all elements.
40+
//
41+
// This is very error prune, since we are trying to guess
42+
// if it is a safari browser from the first occurence between
43+
// chrome or safari.
44+
//
45+
// The chrome user agent contains the first ocurrence
46+
// as the 'chrome/version' and later the 'safari/version'.
47+
const checkSafari = /(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent);
48+
const isSafariDesktop =
49+
checkSafari != null &&
50+
checkSafari[1] != "Chrome" &&
51+
/\biPod\b|\biPad\b/g.exec(navigator.userAgent) == null;
52+
53+
// If we are not in safari desktop, let the browser control
54+
// the focus
55+
if (!isSafariDesktop) return;
56+
57+
var x = tabbable.indexOf(document.activeElement);
58+
59+
if (x > -1) {
60+
x += shiftKey ? -1 : 1;
61+
}
62+
1563
event.preventDefault();
16-
const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];
17-
target.focus();
64+
65+
tabbable[x].focus();
1866
}

0 commit comments

Comments
 (0)