Skip to content

Commit

Permalink
feat: add new option and polyfill with attribute
Browse files Browse the repository at this point in the history
Add possibility to pass a initialize option with the selector to be used for the polyfill. The
polyfill now support attributes and not only classes.

BREAKING CHANGE: attributes support

fix #11
  • Loading branch information
matteobad committed May 1, 2019
1 parent 8831003 commit 0d8bfeb
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 50 deletions.
116 changes: 66 additions & 50 deletions src/focus-within.js
@@ -1,65 +1,81 @@
import { supportsFocusWithin, validClassName } from './utils'

var focusWithinClass, loaded
import supportsFocusWithin from './utils/supportsFocusWithin'
import addAttribute from './utils/addAttribute'
import removeAttribute from './utils/removeAttribute'

/**
* Update focus-within class on focus and blur events
* @param {FocusEvent} e
* Load polyfill and return loading state boolean
*
* @param {String} selector
* @returns {Boolean}
* @throws {TypeError}
*/
function update (e) {
var element, running

var action = function () {
element = document.activeElement
running = false
function polyfill (selector) {
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.')`)
}

Array.prototype.slice
.call(document.getElementsByClassName(focusWithinClass))
.forEach(function (el) { el.classList.remove(focusWithinClass) })
// check if selector is class or attribute
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.')`)
}

if (e.type === 'focus' && element && element !== document.body) {
for (var el = element; el && el.nodeType === 1; el = el.parentNode) { el.classList.add(focusWithinClass) }
// check if valid selector
try {
document.querySelector(selector)
} catch (error) {
throw new TypeError(`Failed to execute '${this.name}' on 'FocusWithin': parameter 1 ('selector') is not a valid selector.')`)
}
}

if (!running) {
window.requestAnimationFrame(action)
running = true
}
}
var attributeName, attributeValue, isClass, loaded

/**
* Load polyfill
* @param {String} className
* @returns {void}
*/
export function loadPolyfill (className) {
focusWithinClass = className || 'focus-within'
if (!validClassName(focusWithinClass)) {
console.warn('focus-within-polyfill: cannot load. ' + focusWithinClass + ' is not a valid class name')
return
}
selector = selector || '[focus-within]'
isClass = selector.indexOf('.') === 0
attributeName = !isClass ? selector.replace(/[[\]']+/g, '') : 'class'
attributeValue = !isClass ? attributeName : selector.replace('.', '')

/**
* - Remove all applied attributes and
* - Add new attributes based on activeElement
*
* @param {FocusEvent} e
*/
var handler = function (e) {
var element, running

if (!loaded && !supportsFocusWithin()) {
document.addEventListener('focus', update, true)
document.addEventListener('blur', update, true)
loaded = true
console.info('focus-within-polyfill: loaded.')
var _action = function () {
element = document.activeElement
running = false

Array.prototype.slice
.call(document.querySelectorAll(selector))
.forEach(function (el) { removeAttribute(el, attributeName, attributeValue) })

if (e.type === 'focus' && element && element !== document.body) {
for (var el = element; el && el.nodeType === 1; el = el.parentNode) {
addAttribute(el, attributeName, attributeValue)
}
}
}

if (!running) {
window.requestAnimationFrame(_action)
running = true
}
}
}

/**
* Unload polyfill
* @returns {void}
*/
export function unloadPolyfill () {
if (!loaded) {
console.warn('focus-within-polyfill: cannot unload. Polyfill was never loaded.')
return
// kick off polyfill
loaded = !supportsFocusWithin()
if (loaded) {
document.addEventListener('focus', handler, true)
document.addEventListener('blur', handler, true)
}

document.removeEventListener('focus', update, true)
document.removeEventListener('blur', update, true)
loaded = false
console.info('focus-within-polyfill: unloaded.')
return loaded
}

export default {
polyfill: polyfill
}
18 changes: 18 additions & 0 deletions src/utils/addAttribute.js
@@ -0,0 +1,18 @@
/**
* Add user defined attribute to element retaining any previously applied attributes.
* Attribute can be 'class'.
*
* @param {Element} element
* @param {String} name
* @param {String} value
*/
function addAttribute (element, name, value) {
var appliedAttributes = element.getAttribute(name) || ''

if (appliedAttributes.indexOf(value) === -1) {
appliedAttributes = (appliedAttributes + ' ' + value).trim()
element.setAttribute(name, appliedAttributes)
}
}

export default addAttribute
22 changes: 22 additions & 0 deletions src/utils/removeAttribute.js
@@ -0,0 +1,22 @@
/**
* Remove the given attribute value and if no other values are present
* remove the attribute from the Element.
* Attribute can be 'class'.
*
* @param {Element} element
* @param {String} name
* @param {String} value
*/
function removeAttribute (element, name, value) {
var appliedAttributes = element.getAttribute(name) || ''

if (appliedAttributes.indexOf(value) !== -1) {
appliedAttributes = appliedAttributes.replace(value, '').trim()

appliedAttributes !== ''
? element.setAttribute(name, appliedAttributes)
: element.removeAttribute(name)
}
}

export default removeAttribute

0 comments on commit 0d8bfeb

Please sign in to comment.