wicg-focus-ring
Advanced tools
Comparing version 1.0.1 to 2.0.0
@@ -174,74 +174,23 @@ (function (global, factory) { | ||
/** | ||
* Determine if a DOM element matches a CSS selector | ||
* | ||
* @param {Element} elem | ||
* @param {String} selector | ||
* @return {Boolean} | ||
* @api public | ||
* https://github.com/WICG/focus-ring | ||
*/ | ||
function matches(elem, selector) { | ||
// Vendor-specific implementations of `Element.prototype.matches()`. | ||
var proto = window.Element.prototype; | ||
var nativeMatches = proto.matches || | ||
proto.mozMatchesSelector || | ||
proto.msMatchesSelector || | ||
proto.oMatchesSelector || | ||
proto.webkitMatchesSelector; | ||
if (!elem || elem.nodeType !== 1) { | ||
return false; | ||
} | ||
var parentElem = elem.parentNode; | ||
// use native 'matches' | ||
if (nativeMatches) { | ||
return nativeMatches.call(elem, selector); | ||
} | ||
// native support for `matches` is missing and a fallback is required | ||
var nodes = parentElem.querySelectorAll(selector); | ||
var len = nodes.length; | ||
for (var i = 0; i < len; i++) { | ||
if (nodes[i] === elem) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Expose `matches` | ||
*/ | ||
var index$1 = matches; | ||
/* https://github.com/WICG/focus-ring */ | ||
document.addEventListener('DOMContentLoaded', function() { | ||
function init() { | ||
var hadKeyboardEvent = false; | ||
var keyboardThrottleTimeoutID = 0; | ||
var elWithFocusRing; | ||
// These elements should always have a focus ring drawn, because they are | ||
// associated with switching to a keyboard modality. | ||
var keyboardModalityWhitelist = [ | ||
'input:not([type])', | ||
'input[type=text]', | ||
'input[type=search]', | ||
'input[type=url]', | ||
'input[type=tel]', | ||
'input[type=email]', | ||
'input[type=password]', | ||
'input[type=number]', | ||
'input[type=date]', | ||
'input[type=month]', | ||
'input[type=week]', | ||
'input[type=time]', | ||
'input[type=datetime]', | ||
'input[type=datetime-local]', | ||
'textarea', | ||
'[role=textbox]', | ||
].join(','); | ||
var inputTypesWhitelist = { | ||
'text': true, | ||
'search': true, | ||
'url': true, | ||
'tel': true, | ||
'email': true, | ||
'password': true, | ||
'number': true, | ||
'date': true, | ||
'month': true, | ||
'week': true, | ||
'time': true, | ||
'datetime': true, | ||
'datetime-local': true, | ||
}; | ||
@@ -256,3 +205,15 @@ /** | ||
function focusTriggersKeyboardModality(el) { | ||
return index$1(el, keyboardModalityWhitelist) && index$1(el, ':not([readonly])'); | ||
var type = el.type; | ||
var tagName = el.tagName; | ||
if (tagName == 'INPUT' && inputTypesWhitelist[type] && !el.readonly) | ||
return true; | ||
if (tagName == 'TEXTAREA' && !el.readonly) | ||
return true; | ||
if (el.contentEditable == 'true') | ||
return true; | ||
return false; | ||
} | ||
@@ -285,19 +246,14 @@ | ||
/** | ||
* On `keydown`, set `hadKeyboardEvent`, to be removed 100ms later if there | ||
* are no further keyboard events. The 100ms throttle handles cases where | ||
* focus is redirected programmatically after a keyboard event, such as | ||
* opening a menu or dialog. | ||
* On `keydown`, set `hadKeyboardEvent`, add `focus-ring` class if the | ||
* key was Tab. | ||
* @param {Event} e | ||
*/ | ||
function onKeyDown() { | ||
function onKeyDown(e) { | ||
if (e.altKey || e.ctrlKey || e.metaKey) | ||
return; | ||
if (e.keyCode != 9) | ||
return; | ||
hadKeyboardEvent = true; | ||
// `activeElement` defaults to document.body if nothing focused, | ||
// so check the active element is actually focused. | ||
if (index$1(document.activeElement, ':focus')) | ||
addFocusRingClass(document.activeElement); | ||
if (keyboardThrottleTimeoutID !== 0) | ||
clearTimeout(keyboardThrottleTimeoutID); | ||
keyboardThrottleTimeoutID = setTimeout(function() { | ||
hadKeyboardEvent = false; | ||
keyboardThrottleTimeoutID = 0; | ||
}, 100); | ||
} | ||
@@ -307,10 +263,15 @@ | ||
* On `focus`, add the `focus-ring` class to the target if: | ||
* - a keyboard event happened in the past 100ms, or | ||
* - the focus event target triggers "keyboard modality" and should always | ||
* have a focus ring drawn. | ||
* - the target received focus as a result of keyboard navigation | ||
* - the event target is an element that will likely require interaction | ||
* via the keyboard (e.g. a text box) | ||
* @param {Event} e | ||
*/ | ||
function onFocus(e) { | ||
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) | ||
if (e.target == document) | ||
return; | ||
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) { | ||
addFocusRingClass(e.target); | ||
hadKeyboardEvent = false; | ||
} | ||
} | ||
@@ -323,10 +284,69 @@ | ||
function onBlur(e) { | ||
if (e.target == document) | ||
return; | ||
removeFocusRingClass(e.target); | ||
} | ||
document.body.addEventListener('keydown', onKeyDown, true); | ||
document.body.addEventListener('focus', onFocus, true); | ||
document.body.addEventListener('blur', onBlur, true); | ||
}); | ||
/** | ||
* When the window regains focus, restore the focus-ring class to the element | ||
* to which it was previously applied. | ||
*/ | ||
function onWindowFocus() { | ||
if (document.activeElement == elWithFocusRing) | ||
addFocusRingClass(elWithFocusRing); | ||
elWithFocusRing = null; | ||
} | ||
/** | ||
* When switching windows, keep track of the focused element if it has a | ||
* focus-ring class. | ||
*/ | ||
function onWindowBlur() { | ||
if (document.activeElement.classList.contains('focus-ring')) { | ||
// Keep a reference to the element to which the focus-ring class is applied | ||
// so the focus-ring class can be restored to it if the window regains | ||
// focus after being blurred. | ||
elWithFocusRing = document.activeElement; | ||
} | ||
} | ||
document.addEventListener('keydown', onKeyDown, true); | ||
document.addEventListener('focus', onFocus, true); | ||
document.addEventListener('blur', onBlur, true); | ||
window.addEventListener('focus', onWindowFocus, true); | ||
window.addEventListener('blur', onWindowBlur, true); | ||
document.body.classList.add('js-focus-ring'); | ||
} | ||
/** | ||
* Subscription when the DOM is ready | ||
* @param {Function} callback | ||
*/ | ||
function onDOMReady(callback) { | ||
if (document.readyState === 'complete') { | ||
callback(); | ||
} else { | ||
var loaded = false; | ||
/** | ||
* Callback wrapper for check loaded state | ||
*/ | ||
function load() { | ||
if (!loaded) { | ||
loaded = true; | ||
callback(); | ||
} | ||
} | ||
document.addEventListener('DOMContentLoaded', load, false); | ||
window.addEventListener('load', load, false); | ||
} | ||
} | ||
onDOMReady(init); | ||
}))); |
{ | ||
"name": "wicg-focus-ring", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "Polyfill for :focus-ring pseudo-selector", | ||
@@ -32,5 +32,4 @@ "scripts": { | ||
"dependencies": { | ||
"dom-classlist": "^1.0.1", | ||
"dom-matches": "^2.0.0" | ||
"dom-classlist": "^1.0.1" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
[![Build Status](https://travis-ci.org/WICG/focus-ring.svg?branch=gh-pages)](https://travis-ci.org/WICG/focus-ring) | ||
[![Build Status](https://travis-ci.org/WICG/focus-ring.svg?branch=master)](https://travis-ci.org/WICG/focus-ring) | ||
@@ -19,7 +19,8 @@ Based on the proposed CSS | ||
selectively disable the default focus style | ||
by selecting for the case when `.focus-ring` is _not_ applied: | ||
by selecting for the case when the polyfill is loaded | ||
and `.focus-ring` is _not_ applied to the element: | ||
```html | ||
:focus:not(.focus-ring) { | ||
outline: none; | ||
.js-focus-ring :focus:not(.focus-ring) { | ||
outline-width: 0; | ||
} | ||
@@ -67,3 +68,3 @@ ``` | ||
:focus { | ||
outline: none; | ||
outline-width: 0; | ||
} | ||
@@ -128,4 +129,5 @@ | ||
normatively. An example heuristic is to update modality on each style recalc: | ||
if the most recent user interaction was via the keyboard; and less than 100ms | ||
has elapsed since the last input event; then the modality is keyboard. Otherwise, | ||
if the most recent user interaction was via the keyboard; | ||
and the key pressed was either `Tab` or `Shift + Tab`; | ||
then the modality is keyboard. Otherwise, | ||
the modality is not keyboard. | ||
@@ -140,5 +142,8 @@ | ||
in order for developers to be able to try it out, understand it and provide feedback. | ||
It simply sets a `.focus-ring` class on the active element | ||
It sets a `.js-focus-ring` class on the body element | ||
to provide a way to disable focus styles only when the polyfill is loaded. | ||
It also sets a `.focus-ring` class on the active element | ||
if the script determines that the keyboard is being used. | ||
This attribute is removed on any `blur` event. | ||
This allows authors to write rules | ||
@@ -153,3 +158,4 @@ which show a focus style only when it would be relevant to the user. | ||
- a `focus` event immediately following a `keydown` event | ||
- a `focus` event immediately following a `keydown` event where the key pressed was either `Tab` | ||
or `Shift + Tab`. | ||
- focus moves into an element which requires keyboard interaction, | ||
@@ -156,0 +162,0 @@ such as a text field |
import classList from 'dom-classlist'; | ||
import matches from 'dom-matches'; | ||
/* https://github.com/WICG/focus-ring */ | ||
document.addEventListener('DOMContentLoaded', function() { | ||
/** | ||
* https://github.com/WICG/focus-ring | ||
*/ | ||
function init() { | ||
var hadKeyboardEvent = false; | ||
var keyboardThrottleTimeoutID = 0; | ||
var elWithFocusRing; | ||
// These elements should always have a focus ring drawn, because they are | ||
// associated with switching to a keyboard modality. | ||
var keyboardModalityWhitelist = [ | ||
'input:not([type])', | ||
'input[type=text]', | ||
'input[type=search]', | ||
'input[type=url]', | ||
'input[type=tel]', | ||
'input[type=email]', | ||
'input[type=password]', | ||
'input[type=number]', | ||
'input[type=date]', | ||
'input[type=month]', | ||
'input[type=week]', | ||
'input[type=time]', | ||
'input[type=datetime]', | ||
'input[type=datetime-local]', | ||
'textarea', | ||
'[role=textbox]', | ||
].join(','); | ||
var inputTypesWhitelist = { | ||
'text': true, | ||
'search': true, | ||
'url': true, | ||
'tel': true, | ||
'email': true, | ||
'password': true, | ||
'number': true, | ||
'date': true, | ||
'month': true, | ||
'week': true, | ||
'time': true, | ||
'datetime': true, | ||
'datetime-local': true, | ||
}; | ||
@@ -38,3 +34,15 @@ /** | ||
function focusTriggersKeyboardModality(el) { | ||
return matches(el, keyboardModalityWhitelist) && matches(el, ':not([readonly])'); | ||
var type = el.type; | ||
var tagName = el.tagName; | ||
if (tagName == 'INPUT' && inputTypesWhitelist[type] && !el.readonly) | ||
return true; | ||
if (tagName == 'TEXTAREA' && !el.readonly) | ||
return true; | ||
if (el.contentEditable == 'true') | ||
return true; | ||
return false; | ||
} | ||
@@ -67,19 +75,14 @@ | ||
/** | ||
* On `keydown`, set `hadKeyboardEvent`, to be removed 100ms later if there | ||
* are no further keyboard events. The 100ms throttle handles cases where | ||
* focus is redirected programmatically after a keyboard event, such as | ||
* opening a menu or dialog. | ||
* On `keydown`, set `hadKeyboardEvent`, add `focus-ring` class if the | ||
* key was Tab. | ||
* @param {Event} e | ||
*/ | ||
function onKeyDown() { | ||
function onKeyDown(e) { | ||
if (e.altKey || e.ctrlKey || e.metaKey) | ||
return; | ||
if (e.keyCode != 9) | ||
return; | ||
hadKeyboardEvent = true; | ||
// `activeElement` defaults to document.body if nothing focused, | ||
// so check the active element is actually focused. | ||
if (matches(document.activeElement, ':focus')) | ||
addFocusRingClass(document.activeElement); | ||
if (keyboardThrottleTimeoutID !== 0) | ||
clearTimeout(keyboardThrottleTimeoutID); | ||
keyboardThrottleTimeoutID = setTimeout(function() { | ||
hadKeyboardEvent = false; | ||
keyboardThrottleTimeoutID = 0; | ||
}, 100); | ||
} | ||
@@ -89,10 +92,15 @@ | ||
* On `focus`, add the `focus-ring` class to the target if: | ||
* - a keyboard event happened in the past 100ms, or | ||
* - the focus event target triggers "keyboard modality" and should always | ||
* have a focus ring drawn. | ||
* - the target received focus as a result of keyboard navigation | ||
* - the event target is an element that will likely require interaction | ||
* via the keyboard (e.g. a text box) | ||
* @param {Event} e | ||
*/ | ||
function onFocus(e) { | ||
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) | ||
if (e.target == document) | ||
return; | ||
if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) { | ||
addFocusRingClass(e.target); | ||
hadKeyboardEvent = false; | ||
} | ||
} | ||
@@ -105,8 +113,67 @@ | ||
function onBlur(e) { | ||
if (e.target == document) | ||
return; | ||
removeFocusRingClass(e.target); | ||
} | ||
document.body.addEventListener('keydown', onKeyDown, true); | ||
document.body.addEventListener('focus', onFocus, true); | ||
document.body.addEventListener('blur', onBlur, true); | ||
}); | ||
/** | ||
* When the window regains focus, restore the focus-ring class to the element | ||
* to which it was previously applied. | ||
*/ | ||
function onWindowFocus() { | ||
if (document.activeElement == elWithFocusRing) | ||
addFocusRingClass(elWithFocusRing); | ||
elWithFocusRing = null; | ||
} | ||
/** | ||
* When switching windows, keep track of the focused element if it has a | ||
* focus-ring class. | ||
*/ | ||
function onWindowBlur() { | ||
if (document.activeElement.classList.contains('focus-ring')) { | ||
// Keep a reference to the element to which the focus-ring class is applied | ||
// so the focus-ring class can be restored to it if the window regains | ||
// focus after being blurred. | ||
elWithFocusRing = document.activeElement; | ||
} | ||
} | ||
document.addEventListener('keydown', onKeyDown, true); | ||
document.addEventListener('focus', onFocus, true); | ||
document.addEventListener('blur', onBlur, true); | ||
window.addEventListener('focus', onWindowFocus, true); | ||
window.addEventListener('blur', onWindowBlur, true); | ||
document.body.classList.add('js-focus-ring'); | ||
} | ||
/** | ||
* Subscription when the DOM is ready | ||
* @param {Function} callback | ||
*/ | ||
function onDOMReady(callback) { | ||
if (document.readyState === 'complete') { | ||
callback(); | ||
} else { | ||
var loaded = false; | ||
/** | ||
* Callback wrapper for check loaded state | ||
*/ | ||
function load() { | ||
if (!loaded) { | ||
loaded = true; | ||
callback(); | ||
} | ||
} | ||
document.addEventListener('DOMContentLoaded', load, false); | ||
window.addEventListener('load', load, false); | ||
} | ||
} | ||
onDOMReady(init); |
Sorry, the diff of this file is not supported yet
85910
1
464
161
- Removeddom-matches@^2.0.0
- Removeddom-matches@2.0.0(transitive)