This commit is contained in:
1
node_modules/focus-group/.coveralls.yml
generated
vendored
Normal file
1
node_modules/focus-group/.coveralls.yml
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
repo_token: oRNCAar3CmRYd80FDRnrjO4NLJaZrMqHc
|
||||
8
node_modules/focus-group/.eslintrc
generated
vendored
Normal file
8
node_modules/focus-group/.eslintrc
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
||||
4
node_modules/focus-group/.npmignore
generated
vendored
Normal file
4
node_modules/focus-group/.npmignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
coverage
|
||||
demo/demo-bundle.js
|
||||
*.log
|
||||
3
node_modules/focus-group/.travis.yml
generated
vendored
Normal file
3
node_modules/focus-group/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- stable
|
||||
20
node_modules/focus-group/CHANGELOG.md
generated
vendored
Normal file
20
node_modules/focus-group/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.1
|
||||
- Fix bug when user-provided options are in a frozen object.
|
||||
|
||||
## 0.3.0
|
||||
- Removed `forwardArrows` and `backArrows` options in favor of `keybindings` option that accepts `next`, `prev`, `first`, or `last`.
|
||||
|
||||
## 0.2.3
|
||||
- Fix bug where providing a `0` index to `addMember()` failed.
|
||||
|
||||
## 0.2.2
|
||||
- Fix bug where `setMembers()` wasn't calling `clearMembers()` first.
|
||||
|
||||
## 0.2.1
|
||||
- Fix bug when there's `member.node` but no `member.text`.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Initial release.
|
||||
50
node_modules/focus-group/CODE_OF_CONDUCT.md
generated
vendored
Normal file
50
node_modules/focus-group/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating
|
||||
documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free
|
||||
experience for everyone, regardless of level of experience, gender, gender
|
||||
identity and expression, sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to
|
||||
fairly and consistently applying these principles to every aspect of managing
|
||||
this project. Project maintainers who do not follow or enforce the Code of
|
||||
Conduct may be permanently removed from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting a project maintainer at david.dave.clark@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. Maintainers are
|
||||
obligated to maintain confidentiality with regard to the reporter of an
|
||||
incident.
|
||||
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 1.3.0, available at
|
||||
[http://contributor-covenant.org/version/1/3/0/][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/3/0/
|
||||
21
node_modules/focus-group/LICENSE
generated
vendored
Normal file
21
node_modules/focus-group/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 David Clark
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
214
node_modules/focus-group/README.md
generated
vendored
Normal file
214
node_modules/focus-group/README.md
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
# focus-group
|
||||
|
||||
[](https://travis-ci.org/davidtheclark/focus-group)
|
||||
[](https://coveralls.io/github/davidtheclark/focus-group?branch=master)
|
||||
|
||||
Create a group of nodes with special focus-related powers.
|
||||
|
||||
Specifically, you can do the following with your focus group:
|
||||
|
||||
- Use different keybindings to move focus through the nodes
|
||||
- Type (with letters) to jump focus to a specific node based on its text
|
||||
|
||||
Essentially, it mimics some of the essential keyboard interactions of a native `<select>`.
|
||||
|
||||
These kinds of powers are useful for:
|
||||
|
||||
- Accessible menus, like [react-aria-menubutton](//github.com/davidtheclark/react-aria-menubutton)
|
||||
- Any other widgets whose keyboard UX will improve by enabling arrow-key navigation and letter-key jumping
|
||||
|
||||
## Concepts
|
||||
|
||||
A focus-group is composed of members.
|
||||
|
||||
The order of the members matters, because focus moves forwards and backwards through the group, in order.
|
||||
|
||||
Each member consists of a DOM node and some text associated with that node.
|
||||
The member's text will be used for letter-key jumping (a.k.a. string searching).
|
||||
Each member's text can be established in a few ways:
|
||||
|
||||
- It can be manually specified when adding the member to the group, via `setMembers()` or `addMember()` (see below).
|
||||
- If the member's node has a `data-focus-group-text` attribute, that value will serve as the member's text.
|
||||
- If neither of the above is provided, the member's text will be the `textContent` of its node.
|
||||
|
||||
## Keyboard Interactions
|
||||
|
||||
When focus is inside the focus-group, the following things should happen:
|
||||
|
||||
- If you press one of your `next` `keybindings` (the down arrow by default), focus moves
|
||||
from the currently focused member to the next member in the group (or wraps back
|
||||
to the front, according to the `wrap` option).
|
||||
- If you press one of your `prev` `keybindings` (the up arrow by default), focus moves
|
||||
from the currently focused member to the previous member in the group (or wraps around
|
||||
to the back, according to the `wrap` option).
|
||||
- If you press a letter key, string searching begins (see below!).
|
||||
|
||||
### String searching
|
||||
|
||||
If the option `stringSearch` is `true` and focus is within the group, the following things happen:
|
||||
|
||||
- When you start typing, focus moves to the first member whose registered text begins with
|
||||
whatever you've been typing.
|
||||
- As long as each keystroke occurs within `stringSearchDelay`,
|
||||
the search string will extend (e.g. `f` -> `fa` -> `far` -> `farm`) and focus will move
|
||||
accordingly.
|
||||
- If no text matches the search string, focus will not move.
|
||||
- After you have not typed any letters for `stringSearchDelay`, the search
|
||||
string resets and you can start over (e.g. you type `fa` then wait and type `go` to match `gorge`).
|
||||
|
||||
This all mimics the native `<select>` behavior.
|
||||
|
||||
Note that like the native `<select>`, typing only matches the *beginning of words*. So you can't focus `David Clark` by typing `Clark`.
|
||||
|
||||
## API
|
||||
|
||||
### var focusGroup = createFocusGroup([options])
|
||||
|
||||
This is the function you get when you `require()` or `import` the module.
|
||||
|
||||
```js
|
||||
var createFocusGroup = require('focus-group');
|
||||
var myMegaMenuFocusGroup = createFocusGroup();
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
**members** { Array }: Designate initial members of the group. Can be any of the following:
|
||||
|
||||
- An array of DOM nodes (or a NodeList, like what's returned by `querySelectorAll()`)
|
||||
- An array of member objects, each object with the following properties: `node` (the DOM node),
|
||||
and (optionally) `text` (the text that should be associated with that node for letter-navigation)
|
||||
|
||||
You can omit this option and add members later with `addMember()` or `setMembers()`. Default: `[]`.
|
||||
|
||||
**keybindings** { Object of `'next'`, `'prev'`, `'first'`, or `'last'` }:
|
||||
Specify which key events should move the focus *forward*, *back*, to the *first* member, or to the *last* member through the group. Provide objects (or arrays of objects) that describe the requirements of a [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) that should trigger that keybinding. Undesignated modifier keys are false by default. So when using something like `{ keyCode: 38 }`, that keybinding will be ignored if key 38 is combined with meta, ctrl, or alt.
|
||||
|
||||
Default:
|
||||
``` js
|
||||
{
|
||||
next: { keyCode: 40 }, // ArrowDown
|
||||
prev: { keyCode: 38 }, // ArrowUp
|
||||
}
|
||||
```
|
||||
|
||||
Use arrays of objects for multiple key bindings:
|
||||
``` js
|
||||
{
|
||||
next: [{ key: 'ArrowDown' }, { key: 'ArrowRight' }],
|
||||
prev: [{ key: 'ArrowUp' }, { key: 'ArrowLeft' }],
|
||||
}
|
||||
```
|
||||
|
||||
Even add modifiers or any valid event properties:
|
||||
``` js
|
||||
{
|
||||
first: { keyCode: 74, metaKey: true },
|
||||
last: { keyCode: 75, metaKey: true },
|
||||
}
|
||||
```
|
||||
|
||||
**wrap** { Boolean }:
|
||||
If `true`, when the arrow keys are moving focus they will wrap around the group. That is, when focus is on the last item and you move focus forward, the first item will focus; and when focus is on the first item and you move focus back, the last item will focus.
|
||||
|
||||
**stringSearch** { Boolean }:
|
||||
If `true`, string searching is enabled (see below).
|
||||
Default: `false`.
|
||||
|
||||
**stringSearchDelay** { Number }:
|
||||
The number of milliseconds that should elapse between the user's last letter entry (with the keyboard)
|
||||
and a refresh of the string search (see below).
|
||||
Default: `800`.
|
||||
|
||||
### focusGroup.activate()
|
||||
|
||||
Start this group listening to keyboard events and responding accordingly.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.deactivate()
|
||||
|
||||
Stop this group listening to keyboard events.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.addMember(member[, index])
|
||||
|
||||
Add a member to the group.
|
||||
|
||||
`member` can be any of the following:
|
||||
|
||||
- A DOM node
|
||||
- An object with the following properties:
|
||||
- `node` (the node itself)
|
||||
- (optionally) `text`: Text that should be associated with that node for letter-navigation. If none is provided, focus-group will check for a `data-focus-group-text` attribute or fallback to the node's `textContent.`
|
||||
|
||||
If `index` is provided, the member will be added at that index.
|
||||
Otherwise, it will be added to the end of the group.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.removeMember(member)
|
||||
|
||||
Remove a member from the group.
|
||||
|
||||
`member` can be any of the following:
|
||||
|
||||
- A DOM node
|
||||
- An index for the member that should be removed.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.clearMembers()
|
||||
|
||||
Empty the focus group of members.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.setMembers(members)
|
||||
|
||||
Set the focus group's members (clearing any that already exist).
|
||||
|
||||
`members` can be any of the following:
|
||||
|
||||
- An array of DOM nodes (or a NodeList, like what's returned by `querySelectorAll()`)
|
||||
- An array of member objects, each object with the following properties:
|
||||
- `node` (the node itself)
|
||||
- (optionally) `text`: Text that should be associated with that node for letter-navigation. If none is provided, focus-group will check for a `data-focus-group-text` attribute or fallback to the node's `textContent.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.getMembers()
|
||||
|
||||
Returns the focus group's current array of members.
|
||||
|
||||
Each item in the array is an object with `node` and `text` properties.
|
||||
|
||||
### focusGroup.focusNodeAtIndex(index)
|
||||
|
||||
Focuses the node at a particular index in the focus group's member array.
|
||||
|
||||
If no member exists at that index, does nothing.
|
||||
|
||||
Returns the focus group instance.
|
||||
|
||||
### focusGroup.moveFocusForward()
|
||||
|
||||
Moves the focus forward one member, if focus is already within the group.
|
||||
|
||||
If focus is not within the group, does nothing.
|
||||
|
||||
Returns the index of the newly focused member.
|
||||
|
||||
### focusGroup.moveFocusBack()
|
||||
|
||||
Moves the focus back one member, if focus is already within the group.
|
||||
|
||||
If focus is not within the group, does nothing.
|
||||
|
||||
Returns the index of the newly focused member.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
123
node_modules/focus-group/demo/index.html
generated
vendored
Normal file
123
node_modules/focus-group/demo/index.html
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>focus-group demo</title>
|
||||
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<meta name="description" content="A demo of focus-group">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>focus-group demo</h1>
|
||||
|
||||
<p>
|
||||
<span style="font-size:2em;vertical-align:middle;">☜</span>
|
||||
<a href="https://github.com/davidtheclark/focus-group" style="vertical-align:middle;">Return to the repository</a>
|
||||
</p>
|
||||
|
||||
<h2>The states</h2>
|
||||
|
||||
In this example, <code>stringSearch</code> is on, so you can search by typing once focus is within the group.
|
||||
|
||||
The default letter keys apply: down moves forward through the group, and up moves back.
|
||||
|
||||
All the list items have a -1 tabindex, so do not receive focus just by tabbing through. And if you hit tab while moving through, focus will leave the group.
|
||||
|
||||
<ul>
|
||||
<li class="state" tabindex="-1">Alabama</li>
|
||||
<li class="state" tabindex="-1">Alaska</li>
|
||||
<li class="state" tabindex="-1">Arizona</li>
|
||||
<li class="state" tabindex="-1">Arkansas</li>
|
||||
<li class="state" tabindex="-1">California</li>
|
||||
<li class="state" tabindex="-1">Colorado</li>
|
||||
<li class="state" tabindex="-1">Connecticut</li>
|
||||
<li class="state" tabindex="-1">Delaware</li>
|
||||
<li class="state" tabindex="-1">Florida</li>
|
||||
<li class="state" tabindex="-1">Georgia</li>
|
||||
<li class="state" tabindex="-1">Hawaii</li>
|
||||
<li class="state" tabindex="-1">Idaho</li>
|
||||
<li class="state" tabindex="-1">Illinois</li>
|
||||
<li class="state" tabindex="-1">Indiana</li>
|
||||
<li class="state" tabindex="-1">Iowa</li>
|
||||
<li class="state" tabindex="-1">Kansas</li>
|
||||
<li class="state" tabindex="-1">Kentucky</li>
|
||||
<li class="state" tabindex="-1">Louisiana</li>
|
||||
<li class="state" tabindex="-1">Maine</li>
|
||||
<li class="state" tabindex="-1">Maryland</li>
|
||||
<li class="state" tabindex="-1">Massachusetts</li>
|
||||
<li class="state" tabindex="-1">Michigan</li>
|
||||
<li class="state" tabindex="-1">Minnesota</li>
|
||||
<li class="state" tabindex="-1">Mississippi</li>
|
||||
<li class="state" tabindex="-1">Missouri</li>
|
||||
<li class="state" tabindex="-1">Montana</li>
|
||||
<li class="state" tabindex="-1">Nebraska</li>
|
||||
<li class="state" tabindex="-1">Nevada</li>
|
||||
<li class="state" tabindex="-1">New Hampshire</li>
|
||||
<li class="state" tabindex="-1">New Jersey</li>
|
||||
<li class="state" tabindex="-1">New Mexico</li>
|
||||
<li class="state" tabindex="-1">New York</li>
|
||||
<li class="state" tabindex="-1">North Carolina</li>
|
||||
<li class="state" tabindex="-1">North Dakota</li>
|
||||
<li class="state" tabindex="-1">Ohio</li>
|
||||
<li class="state" tabindex="-1">Oklahoma</li>
|
||||
<li class="state" tabindex="-1">Oregon</li>
|
||||
<li class="state" tabindex="-1">Pennsylvania</li>
|
||||
<li class="state" tabindex="-1">Rhode Island</li>
|
||||
<li class="state" tabindex="-1">South Carolina</li>
|
||||
<li class="state" tabindex="-1">South Dakota</li>
|
||||
<li class="state" tabindex="-1">Tennessee</li>
|
||||
<li class="state" tabindex="-1">Texas</li>
|
||||
<li class="state" tabindex="-1">Utah</li>
|
||||
<li class="state" tabindex="-1">Vermont</li>
|
||||
<li class="state" tabindex="-1">Virginia</li>
|
||||
<li class="state" tabindex="-1">Washington</li>
|
||||
<li class="state" tabindex="-1">West Virginia</li>
|
||||
<li class="state" tabindex="-1">Wisconsin</li>
|
||||
<li class="state" tabindex="-1">Wyoming</li>
|
||||
</ul>
|
||||
|
||||
<h2>Women's rights activists</h2>
|
||||
|
||||
In this example, the focus-group's members are all links, so they do receive focus when tabbing through. But all the arrows are active in making navigation through the list easier. So is <code>searchString</code>.
|
||||
|
||||
Additionally,
|
||||
<ul>
|
||||
<li>
|
||||
<code>wrap</code> is on, so when navigating with the arrows focus will wrap around from bottom to top and top to bottom.
|
||||
</li>
|
||||
<li>
|
||||
Left and right arrows also navigate.
|
||||
</li>
|
||||
<li>
|
||||
Cmd+J goes to first and Cmd+K goes to last.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
<li><a href="#" class="wra">Addams, Jane</a></li>
|
||||
<li><a href="#" class="wra">Anthony, Susan</a></li>
|
||||
<li><a href="#" class="wra">Blackwell, Alice Stone</a></li>
|
||||
<li><a href="#" class="wra">Blackwell, Antoinette Brown</a></li>
|
||||
<li><a href="#" class="wra">Blackwell, Henry Browne</a></li>
|
||||
<li><a href="#" class="wra">Bloomer, Amelia</a></li>
|
||||
<li><a href="#" class="wra">Helen Gurley Brown</a></li>
|
||||
<li><a href="#" class="wra">Burns, Lucy</a></li>
|
||||
<li><a href="#" class="wra">Ceballos, Jacqueline</a></li>
|
||||
<li><a href="#" class="wra">Catt, Carrie Chapman</a></li>
|
||||
<li><a href="#" class="wra">Channing, William Henry</a></li>
|
||||
<li><a href="#" class="wra">Downer, Carol</a></li>
|
||||
<li><a href="#" class="wra">Freeman, Elisabeth</a></li>
|
||||
<li><a href="#" class="wra">Friedan, Betty</a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<span style="font-size:2em;vertical-align:middle;">☜</span>
|
||||
<a href="https://github.com/davidtheclark/focus-group" style="vertical-align:middle;">Return to the repository</a>
|
||||
</p>
|
||||
|
||||
<script src="demo-bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
node_modules/focus-group/demo/index.js
generated
vendored
Normal file
20
node_modules/focus-group/demo/index.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
var createFocusGroup = require('..');
|
||||
|
||||
var stateNodes = [].slice.call(document.querySelectorAll('.state'));
|
||||
createFocusGroup({
|
||||
members: stateNodes,
|
||||
stringSearch: true,
|
||||
}).activate();
|
||||
|
||||
var wraNodes = document.querySelectorAll('.wra');
|
||||
createFocusGroup({
|
||||
members: wraNodes,
|
||||
stringSearch: true,
|
||||
keybindings: {
|
||||
next: [{ keyCode: 40 }, { keyCode: 39 }],
|
||||
prev: [{ keyCode: 38 }, { keyCode: 37 }],
|
||||
first: { keyCode: 74, metaKey: true },
|
||||
last: { keyCode: 75, metaKey: true }
|
||||
},
|
||||
wrap: true,
|
||||
}).activate();
|
||||
19
node_modules/focus-group/demo/style.css
generated
vendored
Normal file
19
node_modules/focus-group/demo/style.css
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
body {
|
||||
color: #333;
|
||||
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 200px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
a:focus,
|
||||
button:focus {
|
||||
outline: 5px solid lightblue;
|
||||
}
|
||||
275
node_modules/focus-group/index.js
generated
vendored
Normal file
275
node_modules/focus-group/index.js
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
function FocusGroup(options) {
|
||||
options = options || {};
|
||||
var userKeybindings = options.keybindings || {};
|
||||
this._settings = {
|
||||
keybindings: {
|
||||
next: (userKeybindings.next) || { keyCode: 40 },
|
||||
prev: (userKeybindings.prev) || { keyCode: 38 },
|
||||
first: userKeybindings.first,
|
||||
last: userKeybindings.last,
|
||||
},
|
||||
wrap: options.wrap,
|
||||
stringSearch: options.stringSearch,
|
||||
stringSearchDelay: 800
|
||||
};
|
||||
|
||||
// Construct a keybinding lookup that will be more useful later
|
||||
this._keybindingsLookup = [];
|
||||
var action;
|
||||
var eventMatchers
|
||||
for (action in this._settings.keybindings) {
|
||||
eventMatchers = this._settings.keybindings[action];
|
||||
if (!eventMatchers) continue;
|
||||
[].concat(eventMatchers).forEach(function(eventMatcher) {
|
||||
eventMatcher.metaKey = eventMatcher.metaKey || false;
|
||||
eventMatcher.ctrlKey = eventMatcher.ctrlKey || false;
|
||||
eventMatcher.altKey = eventMatcher.altKey || false;
|
||||
eventMatcher.shiftKey = eventMatcher.shiftKey || false;
|
||||
this._keybindingsLookup.push({
|
||||
action: action,
|
||||
eventMatcher: eventMatcher
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this._searchString = '';
|
||||
this._members = [];
|
||||
if (options.members) this.setMembers(options.members);
|
||||
this._boundHandleKeydownEvent = this._handleKeydownEvent.bind(this);
|
||||
}
|
||||
|
||||
FocusGroup.prototype.activate = function() {
|
||||
// Use capture in case other libraries might grab it first -- i.e. React
|
||||
document.addEventListener('keydown', this._boundHandleKeydownEvent, true);
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.deactivate = function() {
|
||||
document.removeEventListener('keydown', this._boundHandleKeydownEvent, true);
|
||||
this._clearSearchStringRefreshTimer();
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype._handleKeydownEvent = function(event) {
|
||||
// Only respond to keyboard events when
|
||||
// focus is already within the focus-group
|
||||
var activeElementIndex = this._getActiveElementIndex();
|
||||
if (activeElementIndex === -1) return;
|
||||
|
||||
// See if the event matches any registered keybinds
|
||||
var eventBound = false;
|
||||
this._keybindingsLookup.forEach(function(keybinding) {
|
||||
if (!matchesEvent(keybinding.eventMatcher, event)) return;
|
||||
eventBound = true;
|
||||
event.preventDefault();
|
||||
switch (keybinding.action) {
|
||||
case 'next':
|
||||
this.moveFocusForward();
|
||||
break;
|
||||
case 'prev':
|
||||
this.moveFocusBack();
|
||||
break;
|
||||
case 'first':
|
||||
this.moveFocusToFirst();
|
||||
break;
|
||||
case 'last':
|
||||
this.moveFocusToLast();
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
if (!eventBound) {
|
||||
this._handleUnboundKey(event);
|
||||
}
|
||||
};
|
||||
|
||||
FocusGroup.prototype.moveFocusForward = function() {
|
||||
var activeElementIndex = this._getActiveElementIndex();
|
||||
var targetIndex;
|
||||
if (activeElementIndex < this._members.length - 1) {
|
||||
targetIndex = activeElementIndex + 1;
|
||||
} else if (this._settings.wrap) {
|
||||
targetIndex = 0;
|
||||
} else {
|
||||
targetIndex = activeElementIndex;
|
||||
}
|
||||
this.focusNodeAtIndex(targetIndex);
|
||||
return targetIndex;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.moveFocusBack = function() {
|
||||
var activeElementIndex = this._getActiveElementIndex();
|
||||
var targetIndex;
|
||||
if (activeElementIndex > 0) {
|
||||
targetIndex = activeElementIndex - 1;
|
||||
} else if (this._settings.wrap) {
|
||||
targetIndex = this._members.length - 1;
|
||||
} else {
|
||||
targetIndex = activeElementIndex;
|
||||
}
|
||||
this.focusNodeAtIndex(targetIndex);
|
||||
return targetIndex;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.moveFocusToFirst = function() {
|
||||
this.focusNodeAtIndex(0);
|
||||
};
|
||||
|
||||
FocusGroup.prototype.moveFocusToLast = function() {
|
||||
this.focusNodeAtIndex(this._members.length - 1);
|
||||
};
|
||||
|
||||
FocusGroup.prototype._handleUnboundKey = function(event) {
|
||||
if (!this._settings.stringSearch) return;
|
||||
|
||||
// While a string search is underway, ignore spaces
|
||||
// and prevent the default space-key behavior
|
||||
if (this._searchString !== '' && (event.key === ' ' || event.keyCode === 32)) {
|
||||
event.preventDefault();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Only respond to letter keys
|
||||
if (!isLetterKeyCode(event.keyCode)) return -1;
|
||||
|
||||
// If the letter key is part of a key combo,
|
||||
// let it do whatever it was going to do
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) return -1;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
this._addToSearchString(String.fromCharCode(event.keyCode));
|
||||
this._runStringSearch();
|
||||
};
|
||||
|
||||
FocusGroup.prototype._clearSearchString = function() {
|
||||
this._searchString = '';
|
||||
};
|
||||
|
||||
FocusGroup.prototype._addToSearchString = function(letter) {
|
||||
// Always store the lowercase version of the letter
|
||||
this._searchString += letter.toLowerCase();
|
||||
};
|
||||
|
||||
FocusGroup.prototype._startSearchStringRefreshTimer = function() {
|
||||
var self = this;
|
||||
this._clearSearchStringRefreshTimer();
|
||||
this._stringSearchTimer = setTimeout(function() {
|
||||
self._clearSearchString();
|
||||
}, this._settings.stringSearchDelay);
|
||||
};
|
||||
|
||||
FocusGroup.prototype._clearSearchStringRefreshTimer = function() {
|
||||
clearTimeout(this._stringSearchTimer);
|
||||
};
|
||||
|
||||
FocusGroup.prototype._runStringSearch = function() {
|
||||
this._startSearchStringRefreshTimer();
|
||||
this.moveFocusByString(this._searchString);
|
||||
};
|
||||
|
||||
FocusGroup.prototype.moveFocusByString = function(str) {
|
||||
var member;
|
||||
for (var i = 0, l = this._members.length; i < l; i++) {
|
||||
member = this._members[i];
|
||||
if (!member.text) continue;
|
||||
|
||||
if (member.text.indexOf(str) === 0) {
|
||||
return focusNode(member.node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FocusGroup.prototype._findIndexOfNode = function(searchNode) {
|
||||
for (var i = 0, l = this._members.length; i < l; i++) {
|
||||
if (this._members[i].node === searchNode) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
FocusGroup.prototype._getActiveElementIndex = function() {
|
||||
return this._findIndexOfNode(document.activeElement);
|
||||
};
|
||||
|
||||
FocusGroup.prototype.focusNodeAtIndex = function(index) {
|
||||
var member = this._members[index];
|
||||
if (member) focusNode(member.node);
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.addMember = function(memberData, index) {
|
||||
var node = memberData.node || memberData;
|
||||
var nodeText = memberData.text || node.getAttribute('data-focus-group-text') || node.textContent || '';
|
||||
|
||||
this._checkNode(node);
|
||||
|
||||
var cleanedNodeText = nodeText.replace(/[\W_]/g, '').toLowerCase();
|
||||
var member = {
|
||||
node: node,
|
||||
text: cleanedNodeText,
|
||||
};
|
||||
|
||||
if (index !== null && index !== undefined) {
|
||||
this._members.splice(index, 0, member);
|
||||
} else {
|
||||
this._members.push(member);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.removeMember = function(member) {
|
||||
var removalIndex = (typeof member === 'number')
|
||||
? member
|
||||
: this._findIndexOfNode(member);
|
||||
if (removalIndex === -1) return;
|
||||
this._members.splice(removalIndex, 1);
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.clearMembers = function() {
|
||||
this._members = [];
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.setMembers = function(nextMembers) {
|
||||
this.clearMembers();
|
||||
for (var i = 0, l = nextMembers.length; i < l; i++) {
|
||||
this.addMember(nextMembers[i]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
FocusGroup.prototype.getMembers = function() {
|
||||
return this._members;
|
||||
};
|
||||
|
||||
FocusGroup.prototype._checkNode = function(node) {
|
||||
if (!node.nodeType || node.nodeType !== window.Node.ELEMENT_NODE) {
|
||||
throw new Error('focus-group: only DOM nodes allowed');
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
function matchesEvent(matcher, event) {
|
||||
for (var key in matcher) {
|
||||
if (event[key] !== undefined && matcher[key] !== event[key]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isLetterKeyCode(keyCode) {
|
||||
return keyCode >= 65 && keyCode <= 90;
|
||||
}
|
||||
|
||||
function focusNode(node) {
|
||||
if (!node || !node.focus) return;
|
||||
node.focus();
|
||||
if (node.tagName.toLowerCase() === 'input') node.select();
|
||||
}
|
||||
|
||||
module.exports = function createFocusGroup(options) {
|
||||
return new FocusGroup(options);
|
||||
};
|
||||
32
node_modules/focus-group/karma.conf.js
generated
vendored
Normal file
32
node_modules/focus-group/karma.conf.js
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
var browserifyIstanbul = require('browserify-istanbul');
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
basePath: '.',
|
||||
browsers: ['Chrome'],
|
||||
frameworks: [
|
||||
'browserify',
|
||||
'mocha',
|
||||
],
|
||||
files: [
|
||||
'test/**/*.js',
|
||||
],
|
||||
preprocessors: {
|
||||
'test/**/*.js': ['browserify'],
|
||||
},
|
||||
reporters: ['progress', 'coverage'],
|
||||
browserify: {
|
||||
debug: true,
|
||||
transform: [browserifyIstanbul({
|
||||
ignore: ['test/**'],
|
||||
})],
|
||||
},
|
||||
coverageReporter: {
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'lcov', subdir: '.' },
|
||||
],
|
||||
},
|
||||
autoWatch: true,
|
||||
});
|
||||
};
|
||||
51
node_modules/focus-group/package.json
generated
vendored
Normal file
51
node_modules/focus-group/package.json
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "focus-group",
|
||||
"version": "0.3.1",
|
||||
"description": "Create a group of nodes with special focusing powers",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"demo-bundle": "browserify demo/index.js -o demo/demo-bundle.js",
|
||||
"demo-watch": "watchify demo/index.js -d -v -o demo/demo-bundle.js",
|
||||
"demo-dev": "npm run demo-watch & http-server demo",
|
||||
"test-dev": "karma start",
|
||||
"test": "karma start --single-run --browsers PhantomJS --reporters progress,coverage,coveralls"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/davidtheclark/focus-group.git"
|
||||
},
|
||||
"keywords": [
|
||||
"focus",
|
||||
"accessibility",
|
||||
"a11y",
|
||||
"arrows",
|
||||
"keyboard",
|
||||
"navigation",
|
||||
"aria"
|
||||
],
|
||||
"author": "David Clark",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/davidtheclark/focus-group/issues"
|
||||
},
|
||||
"homepage": "https://github.com/davidtheclark/focus-group#readme",
|
||||
"devDependencies": {
|
||||
"browserify": "13.0.0",
|
||||
"browserify-istanbul": "0.2.1",
|
||||
"d3-queue": "2.0.2",
|
||||
"eslint": "^3.0.1",
|
||||
"http-server": "0.8.5",
|
||||
"karma": "0.13.19",
|
||||
"karma-browserify": "5.0.1",
|
||||
"karma-chrome-launcher": "0.2.2",
|
||||
"karma-coverage": "0.5.3",
|
||||
"karma-coveralls": "1.1.2",
|
||||
"karma-mocha": "0.2.1",
|
||||
"karma-phantomjs-launcher": "1.0.0",
|
||||
"mocha": "2.4.5",
|
||||
"phantomjs-prebuilt": "2.1.3",
|
||||
"power-assert": "1.2.0",
|
||||
"simulant": "0.2.0",
|
||||
"watchify": "3.7.0"
|
||||
}
|
||||
}
|
||||
534
node_modules/focus-group/test/integration.js
generated
vendored
Normal file
534
node_modules/focus-group/test/integration.js
generated
vendored
Normal file
@@ -0,0 +1,534 @@
|
||||
var assert = require('power-assert');
|
||||
var simulant = require('simulant');
|
||||
var queue = require('d3-queue').queue;
|
||||
var createFocusGroup = require('..');
|
||||
|
||||
var arrowUpEvent = { keyCode: 38 };
|
||||
var arrowDownEvent = { keyCode: 40 };
|
||||
var arrowLeftEvent = { keyCode: 37 };
|
||||
var arrowRightEvent = { keyCode: 39 };
|
||||
|
||||
var nodeOne = document.createElement('button');
|
||||
nodeOne.innerHTML = 'one';
|
||||
document.body.appendChild(nodeOne);
|
||||
var nodeTwo = document.createElement('button');
|
||||
nodeTwo.innerHTML = 'two';
|
||||
document.body.appendChild(nodeTwo);
|
||||
var nodeThree = document.createElement('button');
|
||||
nodeThree.innerHTML = 'three';
|
||||
document.body.appendChild(nodeThree);
|
||||
// nodeFour has no text content
|
||||
var nodeFour = document.createElement('button');
|
||||
document.body.appendChild(nodeFour);
|
||||
|
||||
describe('default settings', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree],
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('with focus outside the group, arrow and letter keys do not affect focus', function() {
|
||||
nodeFour.focus(); // nodeFour is outside group
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeFour);
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeFour);
|
||||
simulateKeydown(arrowLeftEvent);
|
||||
assertActiveElement(nodeFour);
|
||||
simulateKeydown(arrowRightEvent);
|
||||
assertActiveElement(nodeFour);
|
||||
simulateKeydown({ keyCode: 70 });
|
||||
assertActiveElement(nodeFour);
|
||||
simulateKeydown({ keyCode: 88 });
|
||||
assertActiveElement(nodeFour);
|
||||
});
|
||||
|
||||
describe('with focus inside the group', function() {
|
||||
it('down arrow moves focus to next node and sticks on last', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
|
||||
it('up arrow moves focus to previous node and sticks on first', function() {
|
||||
nodeThree.focus();
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
|
||||
it('left and right arrows and letters do not move focus', function() {
|
||||
nodeTwo.focus();
|
||||
simulateKeydown(arrowLeftEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
simulateKeydown(arrowRightEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
simulateKeydown({ keyCode: 70 });
|
||||
assertActiveElement(nodeTwo);
|
||||
simulateKeydown({ keyCode: 88 });
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('all arrows designated', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree],
|
||||
keybindings: {
|
||||
next: [arrowDownEvent, arrowRightEvent],
|
||||
prev: [arrowUpEvent, arrowLeftEvent],
|
||||
},
|
||||
wrap: true
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('up and left arrows move focus forward', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
simulateKeydown(arrowLeftEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
|
||||
it('down and right arrows move focus back', function() {
|
||||
nodeThree.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown(arrowRightEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('modifier keys', function() {
|
||||
beforeEach(function () {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree],
|
||||
keybindings: {
|
||||
first: { keyCode: 36, ctrlKey: true },
|
||||
last: { keyCode: 35, ctrlKey: true },
|
||||
},
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('move focus to first node when holding control and hitting home key', function() {
|
||||
nodeThree.focus();
|
||||
simulateKeydown({ keyCode: 36, ctrlKey: true });
|
||||
assertActiveElement(nodeOne);
|
||||
})
|
||||
|
||||
it('move focus to last node when holding control and hitting end key', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown({ keyCode: 35, ctrlKey: true });
|
||||
assertActiveElement(nodeThree);
|
||||
})
|
||||
});
|
||||
|
||||
describe('all keybinding properties used', function() {
|
||||
beforeEach(function () {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree],
|
||||
keybindings: {
|
||||
next: arrowDownEvent,
|
||||
prev: arrowUpEvent,
|
||||
first: { keyCode: 36 },
|
||||
last: { keyCode: 35 },
|
||||
},
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('move focus forward when hitting down arrow key', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
})
|
||||
|
||||
it('move focus backwards when hitting up arrow key', function() {
|
||||
nodeTwo.focus();
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
})
|
||||
|
||||
it('move focus to first node when hitting home key', function() {
|
||||
nodeThree.focus();
|
||||
simulateKeydown({ keyCode: 36 });
|
||||
assertActiveElement(nodeOne);
|
||||
})
|
||||
|
||||
it('move focus to last node when hitting end key', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown({ keyCode: 35 });
|
||||
assertActiveElement(nodeThree);
|
||||
})
|
||||
});
|
||||
|
||||
describe('arrows with modifier keys should not move focus by default', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree]
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('down arrow with meta key should not move forward', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown({ key: 'ArrowDown', metaKey: true });
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
|
||||
it('up arrow with shift key should not move back', function() {
|
||||
nodeTwo.focus();
|
||||
simulateKeydown({ key: 'ArrowUp', shiftKey: true });
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrap: true', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree],
|
||||
wrap: true,
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('down arrow wraps forward', function() {
|
||||
nodeTwo.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
|
||||
it('up arrow wraps back', function() {
|
||||
nodeTwo.focus();
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown(arrowUpEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringSearch: true', function(done) {
|
||||
before(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree, nodeFour],
|
||||
stringSearch: true,
|
||||
}).activate();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
var q = queue(1);
|
||||
q.defer(function(next) {
|
||||
nodeThree.focus();
|
||||
it('moves on first keystroke', function() {
|
||||
simulateKeydown({ keyCode: 79 }); // "o"
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
next();
|
||||
});
|
||||
q.defer(function(next) {
|
||||
setTimeout(function() {
|
||||
it('300ms continues old search', function() {
|
||||
simulateKeydown({ keyCode: 84 }); // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
next();
|
||||
}, 300);
|
||||
});
|
||||
q.defer(function(next) {
|
||||
setTimeout(function() {
|
||||
it('805ms starts new search', function() {
|
||||
simulateKeydown({ keyCode: 84 }); // "t"
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
next();
|
||||
}, 805);
|
||||
});
|
||||
q.defer(function(next) {
|
||||
setTimeout(function() {
|
||||
it('another key after 10ms extends search', function() {
|
||||
simulateKeydown({ keyCode: 72 }); // "h"
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
next();
|
||||
}, 10);
|
||||
});
|
||||
q.defer(function(next) {
|
||||
setTimeout(function() {
|
||||
it('another key that produces an irrelevant search does not move focus', function() {
|
||||
simulateKeydown({ keyCode: 79 }); // "o"
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
next();
|
||||
}, 10);
|
||||
});
|
||||
q.defer(function(next) {
|
||||
setTimeout(function() {
|
||||
it('after another 805ms the search has reset', function() {
|
||||
simulateKeydown({ keyCode: 84 }); // "t"
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
next();
|
||||
}, 805);
|
||||
});
|
||||
q.awaitAll(function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
describe('stringSearch: true but no search happens', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({
|
||||
members: [nodeOne, nodeTwo, nodeThree, nodeFour],
|
||||
stringSearch: true,
|
||||
}).activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('non-letters do nothing', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown({ keyCode: 54 }); // "5"
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown({ keyCode: 188 }); // ","
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
|
||||
it('letters keys do nothing when ctrl, meta, or alt are also pressed', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown({ keyCode: 84, ctrlKey: true }) // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown({ keyCode: 84, altKey: true }) // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown({ keyCode: 84, metaKey: true }) // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deactivate()', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({ members: [nodeOne, nodeTwo, nodeThree] }).activate()
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('does not respond after deactivation', function() {
|
||||
this.focusGroup.deactivate()
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown({ keyCode: 84 }); // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamically adding and removing nodes', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup().activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('does nothing without nodes', function() {
|
||||
assert.deepEqual(this.focusGroup.getMembers(), []);
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeOne);
|
||||
simulateKeydown({ keyCode: 84 }); // "t"
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
|
||||
it('works after adding nodes one at a time with addMember()', function() {
|
||||
assert.deepEqual(this.focusGroup.getMembers(), []);
|
||||
this.focusGroup.addMember(nodeOne);
|
||||
this.focusGroup.addMember(nodeTwo);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeOne, text: 'one' },
|
||||
{ node: nodeTwo, text: 'two' },
|
||||
]);
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeTwo);
|
||||
});
|
||||
|
||||
it('works after adding all nodels with setMembers()', function() {
|
||||
assert.deepEqual(this.focusGroup.getMembers(), []);
|
||||
this.focusGroup.setMembers([nodeThree, nodeFour]);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeThree, text: 'three' },
|
||||
{ node: nodeFour, text: '' },
|
||||
]);
|
||||
nodeThree.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeFour);
|
||||
});
|
||||
|
||||
it('adds a member at an index with addMember(node, index)', function() {
|
||||
this.focusGroup.setMembers([nodeThree, nodeFour]);
|
||||
this.focusGroup.addMember(nodeOne, 0);
|
||||
this.focusGroup.addMember(nodeTwo, 1);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeOne, text: 'one' },
|
||||
{ node: nodeTwo, text: 'two' },
|
||||
{ node: nodeThree, text: 'three' },
|
||||
{ node: nodeFour, text: "" },
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes a member with removeMember(node)', function() {
|
||||
this.focusGroup.setMembers([nodeThree, nodeFour]);
|
||||
this.focusGroup.removeMember(nodeThree);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeFour, text: "" },
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes a member with removeMember(index)', function() {
|
||||
this.focusGroup.setMembers([nodeThree, nodeFour]);
|
||||
this.focusGroup.removeMember(1);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeThree, text: 'three' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does nothing when you remove a member that does not exist', function() {
|
||||
this.focusGroup.setMembers([nodeThree]);
|
||||
this.focusGroup.removeMember(nodeOne);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeThree, text: "three" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('works while adding and removing nodes at whim', function() {
|
||||
before(function() {
|
||||
this.focusGroup = createFocusGroup().activate()
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('starts with an empty group', function() {
|
||||
assert.deepEqual(this.focusGroup.getMembers(), []);
|
||||
});
|
||||
|
||||
it('adds members with setMembers()', function() {
|
||||
this.focusGroup.setMembers([nodeOne, nodeTwo, nodeThree, nodeFour]);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeOne, text: 'one' },
|
||||
{ node: nodeTwo, text: 'two' },
|
||||
{ node: nodeThree, text: 'three' },
|
||||
{ node: nodeFour, text: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes a member with removeMember(node)', function() {
|
||||
this.focusGroup.removeMember(nodeTwo);
|
||||
assert.deepEqual(this.focusGroup.getMembers(), [
|
||||
{ node: nodeOne, text: 'one' },
|
||||
{ node: nodeThree, text: 'three' },
|
||||
{ node: nodeFour, text: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('still registers proper focus movement', function() {
|
||||
nodeOne.focus();
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
|
||||
it('clears members with clearMembers()', function() {
|
||||
this.focusGroup.clearMembers();
|
||||
assert.deepEqual(this.focusGroup.getMembers(), []);
|
||||
});
|
||||
|
||||
it('still registers proper focus movement', function() {
|
||||
simulateKeydown(arrowDownEvent);
|
||||
assertActiveElement(nodeThree);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when an object without a focus() method is added to the group', function() {
|
||||
it('throws an error', function() {
|
||||
assert.throws(function() {
|
||||
createFocusGroup({ members: [nodeOne, nodeTwo, { foo: 'bar' }] }).activate()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('focusNodeAtIndex', function() {
|
||||
beforeEach(function() {
|
||||
this.focusGroup = createFocusGroup({ members: [nodeOne, nodeTwo, nodeThree, nodeFour] })
|
||||
.activate();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.focusGroup.deactivate();
|
||||
});
|
||||
|
||||
it('works', function() {
|
||||
nodeOne.focus();
|
||||
this.focusGroup.focusNodeAtIndex(2);
|
||||
assertActiveElement(nodeThree);
|
||||
this.focusGroup.focusNodeAtIndex(0);
|
||||
assertActiveElement(nodeOne);
|
||||
this.focusGroup.focusNodeAtIndex(3);
|
||||
assertActiveElement(nodeFour);
|
||||
});
|
||||
|
||||
it('quietly fails to focus when non-existant index is passed', function() {
|
||||
nodeOne.focus();
|
||||
this.focusGroup.focusNodeAtIndex(6);
|
||||
assertActiveElement(nodeOne);
|
||||
});
|
||||
});
|
||||
|
||||
function assertActiveElement(node, message) {
|
||||
assert.equal(document.activeElement, node, message);
|
||||
}
|
||||
|
||||
function simulateKeydown(mockEvent) {
|
||||
simulant.fire(document, 'keydown', mockEvent);
|
||||
}
|
||||
Reference in New Issue
Block a user