Skip to content

Commit

Permalink
refactor(supports): return callbacks instead of values for lazy evalu…
Browse files Browse the repository at this point in the history
…ation - #60
  • Loading branch information
rodneyrehm committed Dec 12, 2015
1 parent 7a0c9a2 commit af16bc4
Show file tree
Hide file tree
Showing 42 changed files with 380 additions and 195 deletions.
65 changes: 28 additions & 37 deletions src/is/focus-relevant.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

// determine if an element can be focused by script regardless
// determine if an element supports.can be focused by script regardless
// of the element actually being focusable at the time of execution
// i.e. <input disabled> is conisdered focus-relevant, but not focusable

Expand All @@ -15,23 +15,14 @@ import {
isUserModifyWritable,
} from './is.util';

import canFocusAudioWithoutControls from '../supports/focus-audio-without-controls';
import canFocusChildrenOfFocusableFlexbox from '../supports/focus-children-of-focusable-flexbox';
import canFocusFieldset from '../supports/focus-fieldset';
import canFocusImgIsmap from '../supports/focus-img-ismap';
import canFocusImgUsemapTabindex from '../supports/focus-img-usemap-tabindex';
import canFocusLabelTabindex from '../supports/focus-label-tabindex';
import canFocusObjectSvg from '../supports/focus-object-svg';
import canFocusObjectSwf from '../supports/focus-object-swf';
import canFocusScrollBody from '../supports/focus-scroll-body';
import canFocusScrollContainer from '../supports/focus-scroll-container';
import canFocusScrollContainerWithoutOverflow from '../supports/focus-scroll-container-without-overflow';
import canFocusSummary from '../supports/focus-summary';
import canFocusSvgMethod from '../supports/svg-focus-method';
import canFocusTable from '../supports/focus-table';
import canFocusVideoWithoutControls from '../supports/focus-video-without-controls';
import _supports from './focus-relevant.supports';
let supports;

export default function(element) {
if (!supports) {
supports = _supports();
}

if (element === document) {
element = document.documentElement;
}
Expand All @@ -43,15 +34,15 @@ export default function(element) {
const nodeName = element.nodeName.toLowerCase();

if (nodeName === 'input' && element.type === 'hidden') {
// input[type="hidden"] cannot be focused
// input[type="hidden"] supports.cannot be focused
return false;
}

if (nodeName === 'input' || nodeName === 'select' || nodeName === 'button' || nodeName === 'textarea') {
return true;
}

if (nodeName === 'label' && !canFocusLabelTabindex) {
if (nodeName === 'label' && !supports.canFocusLabelTabindex) {
// <label tabindex="0"> is only tabbable in Firefox, not script-focusable
// there's no way to make an element focusable other than by adding a tabindex,
// and focus behavior of the label element seems hard-wired to ignore tabindex
Expand All @@ -74,10 +65,10 @@ export default function(element) {

if (nodeName === 'object') {
const svgType = element.getAttribute('type');
if (!canFocusObjectSvg && svgType === 'image/svg+xml') {
if (!supports.canFocusObjectSvg && svgType === 'image/svg+xml') {
// object[type="image/svg+xml"] is not focusable in Internet Explorer
return false;
} else if (!canFocusObjectSwf && svgType === 'application/x-shockwave-flash') {
} else if (!supports.canFocusObjectSwf && svgType === 'application/x-shockwave-flash') {
// object[type="application/x-shockwave-flash"] is not focusable in Internet Explorer 9
return false;
}
Expand All @@ -101,37 +92,37 @@ export default function(element) {
return true;
}

if (nodeName === 'audio' && (canFocusAudioWithoutControls || element.hasAttribute('controls'))) {
if (nodeName === 'audio' && (supports.canFocusAudioWithoutControls || element.hasAttribute('controls'))) {
return true;
}

if (nodeName === 'video' && (canFocusVideoWithoutControls || element.hasAttribute('controls'))) {
if (nodeName === 'video' && (supports.canFocusVideoWithoutControls || element.hasAttribute('controls'))) {
return true;
}

if (canFocusSummary && nodeName === 'summary') {
if (supports.canFocusSummary && nodeName === 'summary') {
return true;
}

if (nodeName === 'img' && element.hasAttribute('usemap') && validTabindex) {
// Gecko, Trident and Edge do not allow an image with an image map and tabindex to be focused,
// it appears the tabindex is overruled so focus is still forwarded to the <map>
return canFocusImgUsemapTabindex;
return supports.canFocusImgUsemapTabindex;
}

if (canFocusTable && (nodeName === 'table' || nodeName === 'td')) {
// IE10-11 can focus <table> and <td>
if (supports.canFocusTable && (nodeName === 'table' || nodeName === 'td')) {
// IE10-11 supports.can focus <table> and <td>
return true;
}

if (canFocusFieldset && nodeName === 'fieldset') {
// IE10-11 can focus <fieldset>
if (supports.canFocusFieldset && nodeName === 'fieldset') {
// IE10-11 supports.can focus <fieldset>
return true;
}

if (nodeName === 'svg') {
if (!canFocusSvgMethod) {
// Firefox and IE cannot focus SVG elements because SVGElement.prototype.focus is missing
if (!supports.canFocusSvgMethod) {
// Firefox and IE supports.cannot focus SVG elements because SVGElement.prototype.focus is missing
return false;
}
// NOTE: in Chrome this would be something like 'svg, svg *,' as *every* svg element with a focus event listener is focusable
Expand All @@ -142,8 +133,8 @@ export default function(element) {
polyfillElementPrototypeMatches(_window);
if (element.matches('svg a[*|href]')) {
// Namespace problems of [xlink:href] explained in http://stackoverflow.com/a/23047888/515124
// Firefox cannot focus <svg> child elements from script
return canFocusSvgMethod;
// Firefox supports.cannot focus <svg> child elements from script
return supports.canFocusSvgMethod;
}

// http://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
Expand All @@ -156,7 +147,7 @@ export default function(element) {
return true;
}

if (canFocusImgIsmap && nodeName === 'img' && element.hasAttribute('ismap')) {
if (supports.canFocusImgIsmap && nodeName === 'img' && element.hasAttribute('ismap')) {
// IE10-11 considers the <img> in <a href><img ismap> focusable
// https://github.com/medialize/ally.js/issues/20
const hasLinkParent = getParents({context: element}).some(
Expand All @@ -169,8 +160,8 @@ export default function(element) {
}

// https://github.com/medialize/ally.js/issues/21
if (canFocusScrollContainer) {
if (canFocusScrollContainerWithoutOverflow) {
if (supports.canFocusScrollContainer) {
if (supports.canFocusScrollContainerWithoutOverflow) {
// Internet Explorer does will consider the scrollable area focusable
// if the element is a <div> or a <span> and it is in fact scrollable,
// regardless of the CSS overflow property
Expand All @@ -186,14 +177,14 @@ export default function(element) {

const parent = element.parentElement;
if (parent) {
if (canFocusScrollBody && isScrollableContainer(parent, nodeName)) {
if (supports.canFocusScrollBody && isScrollableContainer(parent, nodeName)) {
// scrollable bodies are focusable Internet Explorer
// https://github.com/medialize/ally.js/issues/21
return true;
}

// Children of focusable elements with display:flex are focusable in IE10-11
if (canFocusChildrenOfFocusableFlexbox) {
if (supports.canFocusChildrenOfFocusableFlexbox) {
const parentStyle = window.getComputedStyle(parent, null);
if (parentStyle.display.indexOf('flex') > -1) {
return true;
Expand Down
37 changes: 37 additions & 0 deletions src/is/focus-relevant.supports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

import memorizeResult from '../supports/memorize-result';
import canFocusAudioWithoutControls from '../supports/focus-audio-without-controls';
import canFocusChildrenOfFocusableFlexbox from '../supports/focus-children-of-focusable-flexbox';
import canFocusFieldset from '../supports/focus-fieldset';
import canFocusImgIsmap from '../supports/focus-img-ismap';
import canFocusImgUsemapTabindex from '../supports/focus-img-usemap-tabindex';
import canFocusLabelTabindex from '../supports/focus-label-tabindex';
import canFocusObjectSvg from '../supports/focus-object-svg';
import canFocusObjectSwf from '../supports/focus-object-swf';
import canFocusScrollBody from '../supports/focus-scroll-body';
import canFocusScrollContainer from '../supports/focus-scroll-container';
import canFocusScrollContainerWithoutOverflow from '../supports/focus-scroll-container-without-overflow';
import canFocusSummary from '../supports/focus-summary';
import canFocusSvgMethod from '../supports/svg-focus-method';
import canFocusTable from '../supports/focus-table';
import canFocusVideoWithoutControls from '../supports/focus-video-without-controls';

export default memorizeResult(function() {
return {
canFocusAudioWithoutControls: canFocusAudioWithoutControls(),
canFocusChildrenOfFocusableFlexbox: canFocusChildrenOfFocusableFlexbox(),
canFocusFieldset: canFocusFieldset(),
canFocusImgIsmap: canFocusImgIsmap(),
canFocusImgUsemapTabindex: canFocusImgUsemapTabindex(),
canFocusLabelTabindex: canFocusLabelTabindex(),
canFocusObjectSvg: canFocusObjectSvg(),
canFocusObjectSwf: canFocusObjectSwf(),
canFocusScrollBody: canFocusScrollBody(),
canFocusScrollContainer: canFocusScrollContainer(),
canFocusScrollContainerWithoutOverflow: canFocusScrollContainerWithoutOverflow(),
canFocusSummary: canFocusSummary(),
canFocusSvgMethod: canFocusSvgMethod(),
canFocusTable: canFocusTable(),
canFocusVideoWithoutControls: canFocusVideoWithoutControls(),
};
});
23 changes: 14 additions & 9 deletions src/is/native-disabled-supported.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@

// Determine if an element supports the disabled attribute

import canFocusDisabledFieldset from '../supports/focus-fieldset-disabled';
import _supports from './native-disabled-supported.supports';
let supports;

// http://www.w3.org/TR/html5/disabled-elements.html#concept-element-disabled
let disabledElementsPattern = /^(input|select|textarea|button|fieldset)$/;

// fieldset[tabindex=0][disabled] should not be focusable, but Blink and WebKit disagree
// @specification http://www.w3.org/TR/html5/disabled-elements.html#concept-element-disabled
// @browser-issue Chromium https://crbug.com/453847
// @browser-issue WebKit https://bugs.webkit.org/show_bug.cgi?id=141086
if (canFocusDisabledFieldset) {
disabledElementsPattern = /^(input|select|textarea|button)$/;
}

export default function(element) {
if (!supports) {
supports = _supports();

// fieldset[tabindex=0][disabled] should not be focusable, but Blink and WebKit disagree
// @specification http://www.w3.org/TR/html5/disabled-elements.html#concept-element-disabled
// @browser-issue Chromium https://crbug.com/453847
// @browser-issue WebKit https://bugs.webkit.org/show_bug.cgi?id=141086
if (supports.canFocusDisabledFieldset) {
disabledElementsPattern = /^(input|select|textarea|button)$/;
}
}

if (!element || element.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError('is/native-disabled-supported requires an argument of type Element');
}
Expand Down
9 changes: 9 additions & 0 deletions src/is/native-disabled-supported.supports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import memorizeResult from '../supports/memorize-result';
import canFocusDisabledFieldset from '../supports/focus-fieldset-disabled';

export default memorizeResult(function() {
return {
canFocusDisabledFieldset: canFocusDisabledFieldset(),
};
});
23 changes: 13 additions & 10 deletions src/is/valid-area.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@

import isVisible from './visible';
import getParents from '../get/parents';
import canFocusAreaImgTabindex from '../supports/focus-area-img-tabindex';
import canFocusAreaTabindex from '../supports/focus-area-tabindex';
import canFocusAreaWithoutHref from '../supports/focus-area-without-href';
import canFocusBrokenImageMaps from '../supports/focus-broken-image-map';
import {getImageOfArea} from './is.util';

import _supports from './valid-area.supports';
let supports;

// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-usemap
// https://github.com/jquery/jquery-ui/blob/master/ui/core.js#L88-L107
export default function(element) {
if (!supports) {
supports = _supports();
}

if (!element || element.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError('is/valid-area requires an argument of type Element');
}
Expand All @@ -23,7 +26,7 @@ export default function(element) {
}

const hasTabindex = element.hasAttribute('tabindex');
if (!canFocusAreaTabindex && hasTabindex) {
if (!supports.canFocusAreaTabindex && hasTabindex) {
// Blink and WebKit do not consider <area tabindex="-1" href="#void"> focusable
return false;
}
Expand All @@ -35,15 +38,15 @@ export default function(element) {

// Firefox only allows fully loaded images to reference image maps
// https://stereochro.me/ideas/detecting-broken-images-js
if (!canFocusBrokenImageMaps && (!img.complete || !img.naturalHeight || img.offsetWidth <= 0 || img.offsetHeight <= 0)) {
if (!supports.canFocusBrokenImageMaps && (!img.complete || !img.naturalHeight || img.offsetWidth <= 0 || img.offsetHeight <= 0)) {
return false;
}

// Firefox can focus area elements even if they don't have an href attribute
if (!canFocusAreaWithoutHref && !element.href) {
// Internet explorer can focus area elements without href if either
// Firefox supports.can focus area elements even if they don't have an href attribute
if (!supports.canFocusAreaWithoutHref && !element.href) {
// Internet explorer supports.can focus area elements without href if either
// the area element or the image element has a tabindex attribute
return canFocusAreaTabindex && hasTabindex || canFocusAreaImgTabindex && img.hasAttribute('tabindex');
return supports.canFocusAreaTabindex && hasTabindex || supports.canFocusAreaImgTabindex && img.hasAttribute('tabindex');
}

// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-usemap
Expand Down
15 changes: 15 additions & 0 deletions src/is/valid-area.supports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import memorizeResult from '../supports/memorize-result';
import canFocusAreaImgTabindex from '../supports/focus-area-img-tabindex';
import canFocusAreaTabindex from '../supports/focus-area-tabindex';
import canFocusAreaWithoutHref from '../supports/focus-area-without-href';
import canFocusBrokenImageMaps from '../supports/focus-broken-image-map';

export default memorizeResult(function() {
return {
canFocusAreaImgTabindex: canFocusAreaImgTabindex(),
canFocusAreaTabindex: canFocusAreaTabindex(),
canFocusAreaWithoutHref: canFocusAreaWithoutHref(),
canFocusBrokenImageMaps: canFocusBrokenImageMaps(),
};
});
17 changes: 13 additions & 4 deletions src/is/valid-tabindex.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@

// determine if an element's tabindex attribute has a valid value

import allowsInvalidValue from '../supports/focus-invalid-tabindex';
import allowsTrailingCharacters from '../supports/focus-tabindex-trailing-characters';
import _supports from './valid-tabindex.supports';
let supports;

// http://www.w3.org/TR/html5/infrastructure.html#rules-for-parsing-integers
// NOTE: all browsers agree to allow trailing spaces as well
const validIntegerPattern = allowsTrailingCharacters ? /^\s*(-|\+)?[0-9]+.*$/ : /^\s*(-|\+)?[0-9]+\s*$/;
const validIntegerPatternNoTrailing = /^\s*(-|\+)?[0-9]+\s*$/;
const validIntegerPatternWithTrailing = /^\s*(-|\+)?[0-9]+.*$/;

export default function(element) {
if (!supports) {
supports = _supports();
}

const validIntegerPattern = supports.allowsTrailingCharacters
? validIntegerPatternWithTrailing
: validIntegerPatternNoTrailing;

if (element === document) {
element = document.documentElement;
}
Expand All @@ -27,7 +36,7 @@ export default function(element) {
}

// @browser-issue Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
if (allowsInvalidValue) {
if (supports.allowsInvalidValue) {
return true;
}
// an element matches the tabindex selector even if its value is invalid
Expand Down
11 changes: 11 additions & 0 deletions src/is/valid-tabindex.supports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import memorizeResult from '../supports/memorize-result';
import allowsInvalidValue from '../supports/focus-invalid-tabindex';
import allowsTrailingCharacters from '../supports/focus-tabindex-trailing-characters';

export default memorizeResult(function() {
return {
allowsInvalidValue: allowsInvalidValue(),
allowsTrailingCharacters: allowsTrailingCharacters(),
};
});
3 changes: 2 additions & 1 deletion src/query/focusable.quick.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import selector from '../selector/focusable';
import isFocusable from '../is/focusable';

export default function queryFocusableQuick({context, includeContext} = {}) {
const elements = context.querySelectorAll(selector);
const _selector = selector();
const elements = context.querySelectorAll(_selector);
// the selector potentially matches more than really is focusable
const result = [].filter.call(elements, isFocusable);
// add context if requested and focusable
Expand Down

0 comments on commit af16bc4

Please sign in to comment.