Skip to content

Commit

Permalink
Merge pull request #131 from medialize/issue/121-element-focus
Browse files Browse the repository at this point in the history
closes #121
  • Loading branch information
rodneyrehm committed Mar 28, 2016
2 parents 1a87674 + 13604a1 commit fb48917
Show file tree
Hide file tree
Showing 26 changed files with 225 additions and 134 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## master

* fixing `supports/focus-in-hiden-iframe` to avoid `document.write()` - [issue #126](https://github.com/medialize/ally.js/issues/126)
* adding [`ally.element.focus`][ally/element/focus] - [issue #121](https://github.com/medialize/ally.js/issues/121)
* removing `svgelement.prototype.focus` as this should be covered more elegantly by [`ally.element.focus`][ally/element/focus]


## 1.1.0 - Reality Strikes Back
Expand Down Expand Up @@ -282,6 +284,7 @@ Version `1.0.0` is a complete rewrite from the the early `0.0.x` releases, there


[ally/element/disabled]: http://allyjs.io/api/element/disabled.html
[ally/element/focus]: http://allyjs.io/api/element/focus.html
[ally/event/active-element]: http://allyjs.io/api/event/active-element.html
[ally/event/shadow-focus]: http://allyjs.io/api/event/shadow-focus.html
[ally/fix/pointer-focus-children]: http://allyjs.io/api/fix/pointer-focus-children.html
Expand Down
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Unlike any other ally modules, these components do not take take [`options.conte
Making up for missing or lacking DOM mutation APIs.

* [`ally.element.disabled`](element/disabled.md) disables all elements, not only form controls
* [`ally.element.focus`](element/focus.md) shifts focus to an element


## Reacting to element state
Expand Down
64 changes: 64 additions & 0 deletions docs/api/element/focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
layout: doc-api.html
tags: argument-list
---

# ally.element.focus

Shifts focus to an element if it does not already have focus.


## Description

The [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) method is available on all [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). Considering the following HTML, applying `focus()` to the `<span>` element does not do anything. `ally.element.focus()` uses [`ally.get.focusTarget`](../get/focus-target.md) to identify the nearest focusable ancestor, in this case the `<a>`, and shifts focus to that.

```html
<a href="#victim">
<span>click me</span>
</a>
```

While `focus()` is available on `HTMLElement`, this is not necessarily true for [`SVGElement`](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement). Only Blink and WebKit expose the `focus()` method on SVG elements, Gecko, Trident and Edge do not. This will likely change once [SVG 2](http://www.w3.org/TR/SVG2/interact.html#Focus) is a thing. Until then `ally.element.focus()` tries to apply `HTMLElement.prototoype.focus` to `SVGElement`s. This only works for Internet Explorer 9 - 11, as Gecko and Edge actively prevent this.


## Usage

```js
var element = document.getElementById('victim');
var result = ally.element.focus(element);
```

### Arguments

| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| element | [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) | *required* | The Element to focus |

### Returns

[`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) that received focus or `null` if focus could not be shifted.

### Throws

[`TypeError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError) if `element` argument is not of type `HTMLElement`.


## Examples


## Changes

* Added in `v#master`.


## Notes


## Related resources


## Contributing

* [module source](https://github.com/medialize/ally.js/blob/master/src/element/focus.js)
* [document source](https://github.com/medialize/ally.js/blob/master/docs/api/element/focus.md)
* [unit test](https://github.com/medialize/ally.js/blob/master/test/unit/element.focus.test.js)
1 change: 0 additions & 1 deletion docs/api/prototype.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ tags: internal
The prototype infrastructure makes functions available in modern browsers available to older browsers lacking the native support.

* `prototype/element.prototoype.matches.js` polyfills [Element.matches](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches)
* `prototype/svgelement.prototype.focus.js` makes [`HTMLElement.focus`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) available at `SVGElement.focus` (which is not formally specified prior to [SVG 2](http://www.w3.org/TR/SVG2/interact.html#Focus))
* `prototype/window.customevent.js` polyfills the [`CustomEvent`](https://developer.mozilla.org/en/docs/Web/API/CustomEvent) constructor (but returns it rather than overwriting `window.CustomEvent` in Internet Explorer)
* `prototype/window.requestanimationframe.js` polyfills the [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) function for older Internet Explorer

Expand Down
1 change: 0 additions & 1 deletion docs/api/supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ The supports infrastructure is a set of tests determining browser behavior and c
| focus-video-without-controls | boolean | true if `<video>` is focusable (while only `<video controls>` should be) |
| focusout-event | boolean | true if `focusout` is dispatched synchronously |
| tabsequence-area-at-img-position | boolean | true if `<area>` are tabbed at the DOM position of `<img usemap="…">` |
| svg-focus-method | boolean | true if `SVGElement.prototype.focus` exists natively |


## Contributing
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"scripts": {
"init": "npm install && npm run build && npm run build:website",
"test": "node test/run-local.js",
"test:server": "intern-runner config=test/browser",
"test:sauce": "intern-runner config=test/sauce",
"test:browserstack": "intern-runner config=test/browserstack",
"clean": "rm -rf dist web reports && mkdirp dist web reports",
Expand Down
2 changes: 2 additions & 0 deletions src/element/_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// exporting modules to be included the UMD bundle

import disabled from './disabled';
import focus from './focus';
export default {
disabled,
focus,
};
54 changes: 54 additions & 0 deletions src/element/focus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

// wrapper for HTMLElement.prototype.focus

import getFocusTarget from '../get/focus-target';
import isActiveElement from '../is/active-element';
import contextToElement from '../util/context-to-element';
import getWindow from '../util/get-window';

export default function(context) {
const element = contextToElement({
label: 'element/focus',
context,
});

const target = getFocusTarget({
context: element,
except: {
// exclude elements affected by the IE flexbox bug
flexbox: true,
// exclude overflowing elements that are not intentionally
// made focusable by adding a tabindex attribute
scrollable: true,
// include elements that don't have a focus() method
onlyTabbable: true,
},
});

if (!target) {
return null;
}

if (isActiveElement(target)) {
return target;
}

if (target.focus) {
target.focus();
return isActiveElement(target) ? target : null;
}

const _window = getWindow(target);

try {
// The element itself does not have a focus method.
// This is true for SVG elements in Firefox and IE,
// as well as MathML elements in every browser.
// IE9 - 11 will let us abuse HTMLElement's focus method,
// Firefox and Edge will throw an error.
_window.HTMLElement.prototype.focus.call(target);
return target;
} catch (e) {
return null;
}
}
2 changes: 0 additions & 2 deletions src/is/focus-relevant.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// i.e. <input disabled> is conisdered focus-relevant, but not focusable

import polyfillElementPrototypeMatches from '../prototype/element.prototype.matches';
import polyfillSVGElementPrototypeFocus from '../prototype/svgelement.prototype.focus';
import getParents from '../get/parents';
import contextToElement from '../util/context-to-element';
import getWindow from '../util/get-window';
Expand Down Expand Up @@ -148,7 +147,6 @@ function isFocusRelevantRules({
return true;
}

polyfillSVGElementPrototypeFocus(_window);
if (supports.canFocusSvgFocusableAttribute && element.ownerSVGElement) {
// Internet Explorer understands the focusable attribute introduced in SVG Tiny 1.2
return Boolean(focusableAttribute && focusableAttribute === 'true');
Expand Down
2 changes: 0 additions & 2 deletions src/is/focus-relevant.supports.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import canFocusSvg from '../supports/focus-svg';
import canFocusSvgFocusableAttribute from '../supports/focus-svg-focusable-attribute';
import canFocusTable from '../supports/focus-table';
import canFocusVideoWithoutControls from '../supports/focus-video-without-controls';
import svgFocusMethod from '../supports/svg-focus-method';

export default memorizeResult(function() {
return {
Expand All @@ -48,6 +47,5 @@ export default memorizeResult(function() {
canFocusSvgFocusableAttribute: canFocusSvgFocusableAttribute(),
canFocusTable: canFocusTable(),
canFocusVideoWithoutControls: canFocusVideoWithoutControls(),
svgFocusMethod: svgFocusMethod(),
};
});
4 changes: 0 additions & 4 deletions src/is/focusable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
// Gecko: `svg a[xlink|href]` is not identified as focusable (because SVGElement.prototype.focus is missing)
// Blink, WebKit: SVGElements that have been made focusable by adding a focus event listener are not identified as focusable

import polyfillSVGElementPrototypeFocus from '../prototype/svgelement.prototype.focus';
import isFocusRelevant from './focus-relevant';
import isValidArea from './valid-area';
import isVisible from './visible';
import isDisabled from './disabled';
import isOnlyTabbable from './only-tabbable';
import contextToElement from '../util/context-to-element';
import getFrameElement from '../util/get-frame-element';
import getWindow from '../util/get-window';
import tabindexValue from '../util/tabindex-value';

import _supports from './focus-relevant.supports';
Expand Down Expand Up @@ -48,8 +46,6 @@ function isOnlyFocusRelevant(element) {
return _tabindex === null;
}

const _window = getWindow(element);
polyfillSVGElementPrototypeFocus(_window);
if (supports.canFocusSvgFocusableAttribute && (element.ownerSVGElement || nodeName === 'svg')) {
// Internet Explorer understands the focusable attribute introduced in SVG Tiny 1.2
const focusableAttribute = element.getAttribute('focusable');
Expand Down
64 changes: 0 additions & 64 deletions src/prototype/svgelement.prototype.focus.js

This file was deleted.

1 change: 0 additions & 1 deletion src/selector/focusable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// there are too many edge cases that they could be covered in
// a simple CSS selector…

import '../prototype/svgelement.prototype.focus';
import selectInShadows from '../util/select-in-shadows';

import _supports from './focusable.supports';
Expand Down
4 changes: 2 additions & 2 deletions src/supports/detect-focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ function detectFocus(options) {
const allowsFocus = options.validate ? options.validate(element) : document.activeElement === focus;
// restore focus to what it was before test and cleanup
// IE10 does not redirect focus to <body> when the activeElement is removed
document.activeElement && document.activeElement.blur();
previousActiveElement && previousActiveElement.focus() || document.body.focus();
document.activeElement && document.activeElement.blur && document.activeElement.blur();
previousActiveElement && previousActiveElement.focus && previousActiveElement.focus() || document.body.focus();
document.body.removeChild(wrapper);
// restore scroll position
window.scrollTop !== previousScrollTop && (window.scrollTop = previousScrollTop);
Expand Down
3 changes: 1 addition & 2 deletions src/supports/focus-svg-focusable-attribute.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import '../prototype/svgelement.prototype.focus';
import detectFocus from './detect-focus';
import memorizeResult from './memorize-result';
import platform from '../util/platform';
Expand All @@ -14,7 +13,7 @@ export default memorizeResult(() => detectFocus({
},
validate: function(element) {
const focus = element.querySelector('text');
if (platform.is.TRIDENT && (!focus.focus || focus.focus._polyfill === 'noop')) {
if (platform.is.TRIDENT) {
// Edge 13 does not allow polyfilling the missing SVGElement.prototype.focus anymore
return true;
}
Expand Down
1 change: 0 additions & 1 deletion src/supports/focus-svg-tabindex-attribute.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import '../prototype/svgelement.prototype.focus';
import detectFocus from './detect-focus';
import memorizeResult from './memorize-result';

Expand Down
2 changes: 1 addition & 1 deletion src/supports/focus-svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default memorizeResult(() => detectFocus({
},
validate: function(element) {
const focus = element.firstChild;
if (platform.is.TRIDENT && (!focus.focus || focus.focus._polyfill === 'noop')) {
if (platform.is.TRIDENT) {
// Edge 13 does not allow polyfilling the missing SVGElement.prototype.focus anymore
return true;
}
Expand Down
10 changes: 0 additions & 10 deletions src/supports/svg-focus-method.js

This file was deleted.

4 changes: 4 additions & 0 deletions test/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ define([
'./intern',
], function(config) {

// make sure to start the proxy, but not run the tests when
// this config is used with the intern-runner CLI
config.proxyOnly = true;

delete config.reporters;

return config;
Expand Down
4 changes: 2 additions & 2 deletions test/helper/fixtures/custom.fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ define([], function() {
document.body.focus();
}

document.activeElement && document.activeElement.blur();
document.activeElement && document.activeElement.blur && document.activeElement.blur();
previous.parentNode.removeChild(previous);
}

Expand All @@ -18,7 +18,7 @@ define([], function() {
// IE10 does not redirect focus to <body> when the activeElement is removed
// blur shadowed activeElements before removal
// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1117535#c5
document.activeElement && document.activeElement.blur();
document.activeElement && document.activeElement.blur && document.activeElement.blur();
fixture.root.parentNode.removeChild(fixture.root);
},
add: function(_html, id) {
Expand Down
2 changes: 1 addition & 1 deletion test/helper/supports.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ define(function(require) {
canFocusSvgTabindexAttribute: require('ally/supports/focus-svg-tabindex-attribute')(),
canFocusoutEvent: require('ally/supports/focusout-event')(),
cssShadowPiercingDeepCombinator: require('ally/supports/css-shadow-piercing-deep-combinator')(),
svgFocusMethod: require('ally/supports/svg-focus-method')(),
tabsequenceSortsAreaAtImagePosition: require('ally/supports/tabsequence-area-at-img-position')(),
svgFocusMethod: Boolean(SVGElement.prototype.focus),
};

});
Loading

0 comments on commit fb48917

Please sign in to comment.