focus-within-polyfill
Advanced tools
Comparing version 4.0.0 to 4.1.0
@@ -0,1 +1,8 @@ | ||
# [4.1.0](https://github.com/matteobad/focus-within-polyfill/compare/v4.0.0...v4.1.0) (2019-05-02) | ||
### Features | ||
* add support for shadow dom ([01707bf](https://github.com/matteobad/focus-within-polyfill/commit/01707bf)) | ||
# [4.0.0](https://github.com/matteobad/focus-within-polyfill/compare/v3.1.0...v4.0.0) (2019-05-01) | ||
@@ -2,0 +9,0 @@ |
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).focusWithin=t()}(this,function(){"use strict";function e(e,t,o){var n=e.getAttribute(t)||"";-1===n.indexOf(o)&&(n=(n+" "+o).trim(),e.setAttribute(t,n))}return{polyfill:function(t){if(t){if("string"!=typeof t)throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a string.')`);if("."!==t.charAt(0)&&"["!==t.charAt(0)&&"]"!==t.charAt(t.length-1))throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a valid selector.')`);try{document.querySelector(t)}catch(e){throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a valid selector.')`)}}var o,n,r,i;r=0===(t=t||"[focus-within]").indexOf("."),o=r?"class":t.replace(/[[\]']+/g,""),n=r?t.replace(".",""):o;var c=function(r){var i,c;c||(window.requestAnimationFrame(function(){if(i=document.activeElement,c=!1,Array.prototype.slice.call(document.querySelectorAll(t)).forEach(function(e){var t,r,i,c;r=o,i=n,-1!==(c=(t=e).getAttribute(r)||"").indexOf(i)&&(""!==(c=c.replace(i,"").trim())?t.setAttribute(r,c):t.removeAttribute(r))}),"focus"===r.type&&i&&i!==document.body)for(var u=i;u&&1===u.nodeType;u=u.parentNode)e(u,o,n)}),c=!0)};return(i=!function(){try{return document.querySelector(":focus-within"),console.info("focus-within-polyfill: focus-within supported"),!0}catch(e){return console.info("focus-within-polyfill: focus-within not supported"),!1}}())&&(document.addEventListener("focus",c,!0),document.addEventListener("blur",c,!0)),i}}}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).focusWithin=t()}(this,function(){"use strict";return{polyfill:function(e){var t,r,o,n;if(e){if("string"!=typeof e)throw new TypeError('Failed to execute "polyfill" on "focusWithin":parameter 1 ("selector") is not a string.');if("."!==e.charAt(0)&&"["!==e.charAt(0)&&"]"!==e.charAt(e.length-1))throw new TypeError('Failed to execute "polyfill" on "focusWithin":parameter 1 ("selector") is not a valid selector.');try{document.querySelector(e)}catch(e){throw new TypeError('Failed to execute "polyfill" on "focusWithin":parameter 1 ("selector") is not a valid selector.')}}function i(e){for(var t=[];e&&(1===e.nodeType||11===e.nodeType);)11!==e.nodeType?(t.push(e),e=e.parentNode):e=e.host;return t}function c(e){var c;o=function(){for(var e=document.activeElement;e&&e.shadowRoot&&e.shadowRoot.activeElement;)e=e.shadowRoot.activeElement;return e}(),c||(window.requestAnimationFrame(function(){var a,u;c=!1,Array.prototype.slice.call(i(n)).forEach((a=t,u=r,function(e){var t=e.getAttribute(a)||"";if(-1!==t.indexOf(u)){var r=t.replace(u,"").trim();""===r?e.removeAttribute(a):e.setAttribute(a,r)}})),n=o,"focus"===e.type&&o&&Array.prototype.slice.call(i(o)).forEach(function(e,t){return function(r){var o=r.getAttribute(e)||"";if(-1===o.indexOf(t)){var n=(o+" "+t).trim();r.setAttribute(e,n)}}}(t,r))}),c=!0)}e=0!==(e=e||"[focus-within]").indexOf(".")?t=r=e.replace(/[[\]']+/g,""):(t="class",r=e.replace(".",""));try{return document.querySelector(":focus-within"),!0}catch(e){return document.addEventListener("focus",c,!0),document.addEventListener("blur",c,!0),!1}}}}); | ||
//# sourceMappingURL=focus-within-polyfill.js.map |
{ | ||
"name": "focus-within-polyfill", | ||
"version": "4.0.0", | ||
"version": "4.1.0", | ||
"description": "focus-within pseudo selector polyfill", | ||
@@ -21,9 +21,4 @@ "main": "dist/focus-within-polyfill.js", | ||
"homepage": "https://github.com/matteobad/focus-within-polyfill#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/matteobad/focus-within-polyfill" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/matteobad/focus-within-polyfill/issues" | ||
}, | ||
"repository": "https://github.com/matteobad/focus-within-polyfill", | ||
"bugs": "https://github.com/matteobad/focus-within-polyfill/issues", | ||
"keywords": [ | ||
@@ -37,3 +32,5 @@ "polyfill", | ||
"vanilla", | ||
"vanilla-js" | ||
"vanilla-js", | ||
"webcomponents", | ||
"shadowRoot" | ||
], | ||
@@ -67,5 +64,2 @@ "author": "Matteo Badini", | ||
}, | ||
"browserslist": [ | ||
"defaults" | ||
], | ||
"config": { | ||
@@ -72,0 +66,0 @@ "commitizen": { |
@@ -7,12 +7,11 @@ # `:focus-within` Pseudo-Class Polyfill | ||
* [How to use](#hot-to-use) | ||
* [Notes](#notes) | ||
* [How it works](#how-it-works) | ||
* [Features](#features) | ||
* [Browser support](#browser-support) | ||
* [`:focus-within`](#focus-within) | ||
* [Demo](#demo) | ||
This package will add two event listeners, one on the *focus* event and one on the *blur* event to trigger the automatic apply and remove of a custom attribute to indicate wheter the Element should have a `:focus-within` pseudo-class. In order to do so and be compatible with older version of IE and Edge the `setAttribute` method is used to set both attributes and classes. This will prevent error like: *`classList` is undefined for SVG Element*. | ||
The `:focus-within` CSS pseudo-class represents an element that has received focus or contains an element that has received focus. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by `:focus`. (This includes descendants in shadow trees.) | ||
## `:focus-within` | ||
This selector is useful, to take a common example, for highlighting an entire `<form>` container when the user focuses on one of its `<input>` fields. | ||
The `:focus-within` CSS pseudo-class represents an element that has received focus or contains an element that has received focus. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by :focus. | ||
More information on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within). | ||
@@ -58,13 +57,24 @@ | ||
## Notes | ||
## How it works | ||
This polyfill does not support shadow DOM. The goal is to polyfill a feature that is missing in IE and EDGE and since shadow DOM is another feature that [needs polyfilling](https://caniuse.com/#feat=shadowdomv1) in this browser I won't implement such feature, but any PR is well accepted. | ||
This package will add two event listeners, one on the *focus* event and one on the *blur* event to trigger the automatic apply and remove of a custom attribute to indicate wheter the Element should have a `:focus-within` pseudo-class. In order to do so and be compatible with older version of IE and EDGE the `getAttribute`, `setAttribute` and `removeAttribute` methods are used to set both attributes and classes. Then `requestAnimationFrame` is used to apply the modification to the DOM. | ||
This polyfill is compatible with native [Shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom#what) and with the [webcomponents polyfill](https://www.webcomponents.org/polyfills/). This means that even on IE11 and EDGE you will be able to use the `:focus-within` pseudo-class to style elements even outside a shadowRoot. | ||
## Features | ||
* _Custom attribute/class_ value to apply the polyfill | ||
* _Shady CSS/DOM_ support even with the [webcomponents polyfill](https://www.webcomponents.org/polyfills/) | ||
* _Non standard Element_ support, like SVG Element with link inside | ||
## Browser Support | ||
* _Natively supported in Chrome_ | ||
* _Natively supported in Firefox_ | ||
* _Natively supported in Safari_ | ||
* _Natively supported in Opera_ | ||
* IE 10+ | ||
* Edge | ||
| Polyfill | Edge | IE9+ | Chrome | Firefox | Safari | | ||
| --------------- |:----:|:----:|:------:|:-------:|:------:| | ||
| `:focus-within` | ✓ | ✓ | ✓ | ✓ | ✓ | | ||
\* This polyfill maybe work on older versions of the browsers. | ||
## Demo | ||
You can try a working demo [here](https://matteobad.github.io/focus-within-polyfill). |
@@ -1,5 +0,1 @@ | ||
import supportsFocusWithin from './utils/supportsFocusWithin' | ||
import addAttribute from './utils/addAttribute' | ||
import removeAttribute from './utils/removeAttribute' | ||
/** | ||
@@ -13,6 +9,11 @@ * Load polyfill and return loading state boolean | ||
function polyfill (selector) { | ||
var attrName, attrValue, activeElement, lastActiveElement | ||
// Sanity check | ||
if (selector) { | ||
// check if selector is a string | ||
if (typeof selector !== 'string') { | ||
throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a string.')`) | ||
throw new TypeError( | ||
'Failed to execute "polyfill" on "focusWithin":' + | ||
'parameter 1 ("selector") is not a string.') | ||
} | ||
@@ -22,5 +23,6 @@ | ||
if (selector.charAt(0) !== '.' && (selector.charAt(0) !== '[' && selector.charAt(selector.length - 1) !== ']')) { | ||
throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a valid selector.')`) | ||
throw new TypeError( | ||
'Failed to execute "polyfill" on "focusWithin":' + | ||
'parameter 1 ("selector") is not a valid selector.') | ||
} | ||
// check if valid selector | ||
@@ -30,39 +32,109 @@ try { | ||
} catch (error) { | ||
throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a valid selector.')`) | ||
throw new TypeError( | ||
'Failed to execute "polyfill" on "focusWithin":' + | ||
'parameter 1 ("selector") is not a valid selector.') | ||
} | ||
} | ||
var attributeName, attributeValue, isClass, loaded | ||
// Variables initialization | ||
selector = selector || '[focus-within]' | ||
isClass = selector.indexOf('.') === 0 | ||
attributeName = !isClass ? selector.replace(/[[\]']+/g, '') : 'class' | ||
attributeValue = !isClass ? attributeName : selector.replace('.', '') | ||
selector = (selector.indexOf('.') !== 0) | ||
? (attrName = attrValue = selector.replace(/[[\]']+/g, '')) | ||
: (attrName = 'class', attrValue = selector.replace('.', '')) | ||
/** | ||
* - Remove all applied attributes and | ||
* - Add new attributes based on activeElement | ||
* Find and return the current Element with focus. | ||
* This will loop through shadow DOM. | ||
* | ||
* @returns {Element} | ||
*/ | ||
function findActiveElement () { | ||
var activeEl = document.activeElement | ||
while (activeEl && activeEl.shadowRoot && activeEl.shadowRoot.activeElement) { | ||
activeEl = activeEl.shadowRoot.activeElement | ||
} | ||
return activeEl | ||
} | ||
/** | ||
* Create and return the event path from root to element. | ||
* The computed path includes element inside a shadowRoot. | ||
* | ||
* @param {Element} el | ||
* @returns {Array} | ||
*/ | ||
function computeEventPath (el) { | ||
var path = [] | ||
while (el && (el.nodeType === 1 || el.nodeType === 11)) { | ||
if (el.nodeType !== 11) { | ||
path.push(el) | ||
el = el.parentNode | ||
} else { | ||
el = el.host | ||
} | ||
} | ||
return path | ||
} | ||
/** | ||
* Add user defined attribute to element retaining any previously applied attributes. | ||
* Attribute can be the 'class' attribute for compatibility reasons. | ||
* | ||
* @param {String} name | ||
* @param {String} value | ||
*/ | ||
function addAttribute (name, value) { | ||
return function (el) { | ||
var attributes = el.getAttribute(name) || '' | ||
if (attributes.indexOf(value) === -1) { | ||
var newAttributes = (attributes + ' ' + value).trim() | ||
el.setAttribute(name, newAttributes) | ||
} | ||
} | ||
} | ||
/** | ||
* Remove user defined attribute value or entire attribute if last one. | ||
* Attribute can be the 'class' attribute for compatibility reasons. | ||
* | ||
* @param {String} name | ||
* @param {String} value | ||
*/ | ||
function removeAttribute (name, value) { | ||
return function (el) { | ||
var attributes = el.getAttribute(name) || '' | ||
if (attributes.indexOf(value) !== -1) { | ||
var newAttributes = attributes.replace(value, '').trim() | ||
if (newAttributes === '') el.removeAttribute(name) | ||
else el.setAttribute(name, newAttributes) | ||
} | ||
} | ||
} | ||
/** | ||
* Use rAF to remove and apply custom attribute on FocusEvent. | ||
* | ||
* @param {FocusEvent} e | ||
*/ | ||
var handler = function (e) { | ||
var element, running | ||
function handler (e) { | ||
var running | ||
activeElement = findActiveElement() | ||
var _action = function () { | ||
element = document.activeElement | ||
function action () { | ||
running = false | ||
Array.prototype.slice | ||
.call(document.querySelectorAll(selector)) | ||
.forEach(function (el) { removeAttribute(el, attributeName, attributeValue) }) | ||
.call(computeEventPath(lastActiveElement)) | ||
.forEach(removeAttribute(attrName, attrValue)) | ||
if (e.type === 'focus' && element && element !== document.body) { | ||
for (var el = element; el && el.nodeType === 1; el = el.parentNode) { | ||
addAttribute(el, attributeName, attributeValue) | ||
} | ||
} | ||
lastActiveElement = activeElement | ||
if (e.type !== 'focus' || !activeElement) return | ||
Array.prototype.slice | ||
.call(computeEventPath(activeElement)) | ||
.forEach(addAttribute(attrName, attrValue)) | ||
} | ||
if (!running) { | ||
window.requestAnimationFrame(_action) | ||
window.requestAnimationFrame(action) | ||
running = true | ||
@@ -72,10 +144,10 @@ } | ||
// kick off polyfill | ||
loaded = !supportsFocusWithin() | ||
if (loaded) { | ||
try { | ||
document.querySelector(':focus-within') | ||
return true | ||
} catch (error) { | ||
document.addEventListener('focus', handler, true) | ||
document.addEventListener('blur', handler, true) | ||
return false | ||
} | ||
return loaded | ||
} | ||
@@ -82,0 +154,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
32701
173
78
18
1
1