Skip to content

Commit

Permalink
[fixed] strict matching for tabbable nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Hancock, Matthew authored and diasbruno committed Feb 7, 2024
1 parent e7c4a63 commit f9bc6a0
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 2 deletions.
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ <h2>an accessible React modal dialog component</h2>
<div id="example" class="padbox">
<a class="btn btn-primary" href="/basic/">Basic</a>
<a class="btn btn-primary" href="/bootstrap/">Bootstrap</a>
<a class="btn btn-primary" href="/wc/">Web Component</a>
</div>
<a target="_top" href="https://github.com/reactjs/react-modal"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://github-camo.global.ssl.fastly.net/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
</body>
Expand Down
39 changes: 39 additions & 0 deletions examples/wc/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.ReactModal__Overlay {
-webkit-perspective: 600;
perspective: 600;
opacity: 0;
overflow-x: hidden;
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.5);
}

.ReactModal__Overlay--after-open {
opacity: 1;
transition: opacity 150ms ease-out;
}

.ReactModal__Content {
-webkit-transform: scale(0.5) rotateX(-30deg);
transform: scale(0.5) rotateX(-30deg);
}

.ReactModal__Content--after-open {
-webkit-transform: scale(1) rotateX(0deg);
transform: scale(1) rotateX(0deg);
transition: all 150ms ease-in;
}

.ReactModal__Overlay--before-close {
opacity: 0;
}

.ReactModal__Content--before-close {
-webkit-transform: scale(0.5) rotateX(30deg);
transform: scale(0.5) rotateX(30deg);
transition: all 150ms ease-in;
}

.ReactModal__Content.modal-dialog {
border: none;
background-color: transparent;
}
88 changes: 88 additions & 0 deletions examples/wc/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Modal from 'react-modal';

import '@webcomponents/custom-elements/src/native-shim';

var appElement = document.getElementById('example');

Modal.setAppElement(appElement);

class App extends Component {
constructor(props) {
super(props);
this.state = { modalIsOpen: false };
}

openModal = () => {
this.setState({modalIsOpen: true});
}

closeModal = () => {
this.setState({modalIsOpen: false});
}

handleModalCloseRequest = () => {
// opportunity to validate something and keep the modal open even if it
// requested to be closed
this.setState({modalIsOpen: false});
}

handleSaveClicked = (e) => {
alert('Save button was clicked');
}

render() {
return (
<div>
<button type="button" className="btn btn-primary" onClick={this.openModal}>Open Modal</button>
<Modal
className="Modal__Bootstrap modal-dialog"
closeTimeoutMS={150}
isOpen={this.state.modalIsOpen}
onRequestClose={this.handleModalCloseRequest}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Modal title</h4>
<div className="close">
<awesome-button></awesome-button>
<button type="button" className="close" onClick={this.handleModalCloseRequest}>
<span aria-hidden="true">&times;</span>
<span className="sr-only">Close</span>
</button>
</div>
</div>
<div className="modal-body">
<h4>Really long content...</h4>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={this.handleModalCloseRequest}>Close</button>
<button type="button" className="btn btn-primary" onClick={this.handleSaveClicked}>Save changes</button>
</div>
</div>
</Modal>
</div>
);
}
}

ReactDOM.render(<App/>, appElement);

class AwesomeButton extends HTMLElement {
constructor() {
super();
}

// this shows with no shadow root
connectedCallback() {
this.innerHTML = `
<button>I'm Awesome!</button>
`;
}
}

customElements.define("awesome-button", AwesomeButton);
19 changes: 19 additions & 0 deletions examples/wc/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!doctype html public "embarassment">
<html>
<head>
<title>Bootstrap-Style Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="/base.css" type="text/css" />
<link rel="stylesheet" href="app.css"/>
</head>
<body>
<header class="branding padbox">
<h1>react-modal</h1>
<h2>an accessible React modal dialog component</h2>
</header>
<div id="example" class="padbox"></div>
<a target="_top" href="https://github.com/reactjs/react-modal"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://github-camo.global.ssl.fastly.net/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
<script type="application/javascript" src="/__build__/wc.js"></script>
</body>
</html>
65 changes: 64 additions & 1 deletion specs/Modal.helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,28 @@ export default () => {
tabbable(elem).should.not.containEql(button);
});

describe("inside Web Components", () => {
it("excludes elements that contain reserved node names", () => {
const button = document.createElement("button");
button.innerHTML = "I am a good button";
elem.appendChild(button);

const badButton = document.createElement("bad-button");
badButton.innerHTML = "I am a bad button";
elem.appendChild(badButton);

tabbable(elem).should.deepEqual([button]);
});

it("includes elements that contain reserved node names with tabindex", () => {
const trickButton = document.createElement("trick-button");
trickButton.innerHTML = "I am a good button";
trickButton.tabIndex = '0';
elem.appendChild(trickButton);

tabbable(elem).should.deepEqual([trickButton]);
});

describe("inside Web Components with shadow dom", () => {
let wc;
let input;
class TestWebComponent extends HTMLElement {
Expand Down Expand Up @@ -169,6 +190,48 @@ export default () => {
tabbable(elem).should.not.containEql(input);
});
});

describe("inside Web Components with no shadow dom", () => {
let wc;
let button;
class ButtonWebComponent extends HTMLElement {
constructor() {
super();
}

connectedCallback() {
this.innerHTML = '<button>Normal button</button>';
this.style.display = "block";
this.style.width = "100px";
this.style.height = "25px";
}
}

const registerButtonComponent = () => {
if (window.customElements.get("button-web-component")) {
return;
}
window.customElements.define("button-web-component", ButtonWebComponent);
};

beforeEach(() => {
registerButtonComponent();
wc = document.createElement("button-web-component");

elem.appendChild(wc);
});

afterEach(() => {
// remove Web Component
elem.removeChild(wc);
});

it("includes only focusable elements", () => {
button = wc.querySelector('button');

tabbable(elem).should.deepEqual([button]);
});
});
});
});
};
3 changes: 2 additions & 1 deletion src/helpers/tabbable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
const DISPLAY_NONE = "none";
const DISPLAY_CONTENTS = "contents";

const tabbableNode = /input|select|textarea|button|object|iframe/;
// match the whole word to prevent fuzzy searching
const tabbableNode = /^(input|select|textarea|button|object|iframe)$/;

function isNotOverflowing(element, style) {
return (
Expand Down

0 comments on commit f9bc6a0

Please sign in to comment.