Comparing version 0.1.0 to 0.2.0
/*! | ||
* ctrly v0.1.0 (2018-08-21) | ||
* ctrly v0.2.0 | ||
* Copyright (c) 2018 Jan Sorgalla | ||
@@ -32,16 +32,9 @@ * License: MIT | ||
function scrollPositionRestorer(element) { | ||
var positions = parents(element).map(function (element) { | ||
return { | ||
element: element, | ||
top: element.scrollTop, | ||
left: element.scrollLeft | ||
}; | ||
var positions = parents(element).map(function (parent) { | ||
return [parent, parent.scrollTop, parent.scrollLeft]; | ||
}); | ||
return function () { | ||
positions.forEach(function (_ref) { | ||
var element = _ref.element, | ||
top = _ref.top, | ||
left = _ref.left; | ||
element.scrollTop = top; | ||
element.scrollLeft = left; | ||
positions.forEach(function (cache) { | ||
cache[0].scrollTop = cache[1]; | ||
cache[0].scrollLeft = cache[2]; | ||
}); | ||
@@ -236,2 +229,60 @@ }; | ||
var selector = ['a[href]', | ||
'area[href]', 'input', 'select', 'textarea', 'button', 'iframe', 'object', 'audio[controls]', 'video[controls]', '[contenteditable]', '[tabindex]'].join(','); | ||
var inputNodeNameRegexp = /^(input|select|textarea|button|object)$/; | ||
function focusableFilter(element) { | ||
var nodeName = element.nodeName.toLowerCase(); | ||
if (nodeName === 'area') { | ||
return isValidArea(element); | ||
} | ||
if (element.disabled) { | ||
return false; | ||
} | ||
if (inputNodeNameRegexp.test(nodeName)) { | ||
var fieldset = closest(element, 'fieldset'); | ||
if (fieldset && fieldset.disabled) { | ||
return false; | ||
} | ||
} | ||
return visible(element); | ||
} | ||
function tabbableFilter(element) { | ||
var index = tabIndex(element); | ||
return focusableFilter(element) && index >= 0; | ||
} | ||
function compare(a, b) { | ||
var aIndex = tabIndex(a, true); | ||
var bIndex = tabIndex(b, true); | ||
if (aIndex === bIndex) { | ||
return a.compareDocumentPosition(b) & 2 ? 1 : -1; | ||
} | ||
return aIndex - bIndex; | ||
} | ||
function isValidArea(element) { | ||
var map = element.parentNode; | ||
var mapName = map.name; | ||
if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') { | ||
return false; | ||
} | ||
var images = find("img[usemap=\"#".concat(mapName, "\"]")); | ||
return images.length > 0 && visible(images[0]); | ||
} | ||
function visible(element) { | ||
var style = getComputedStyle(element); | ||
return ( | ||
style.visibility !== 'hidden' && style.visibility !== 'collapse' && style.display !== 'none' && parents(element).every(function (el) { | ||
return getComputedStyle(el).display !== 'none'; | ||
}) | ||
); | ||
} | ||
function tabIndex(element) { | ||
var positiveOnly = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | ||
var index = parseInt(element.getAttribute('tabindex'), 10); | ||
return isNaN(index) ? 0 : positiveOnly && index < 0 ? 0 : index; | ||
} | ||
function tabbable(element) { | ||
return find(selector, arguments.length > 0 ? element : document).filter(tabbableFilter).sort(compare); | ||
} | ||
var defaultOptions = { | ||
@@ -241,13 +292,10 @@ selector: '[data-ctrly]', | ||
focusTarget: true, | ||
closeOnBlur: true, | ||
closeOnEsc: true, | ||
closeOnOutsideClick: true, | ||
closeOnScroll: false, | ||
constrainFocus: false, | ||
trapFocus: false, | ||
allowMultiple: false, | ||
on: null | ||
}; | ||
var focusableElementsSelector = 'a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),iframe,object,embed,[contenteditable],[tabindex]:not([tabindex^="-"])'; | ||
var passiveEventOptions = { | ||
passive: true | ||
}; | ||
function settings(opts) { | ||
@@ -268,9 +316,13 @@ var extended = {}; | ||
} | ||
function findControls(target) { | ||
return find("[aria-controls=\"".concat(target.id, "\"]")); | ||
} | ||
function findTarget(control) { | ||
var targetId = control.getAttribute('aria-controls'); | ||
return { | ||
targetId: targetId, | ||
target: targetId ? document.getElementById(targetId) : null | ||
}; | ||
return document.getElementById(control.getAttribute('aria-controls') || control.getAttribute('data-ctrly')); | ||
} | ||
function resetControl(control) { | ||
control.removeAttribute('aria-controls'); | ||
control.removeAttribute('aria-expanded'); | ||
} | ||
var idCounter = 0; | ||
function ctrly() { | ||
@@ -281,3 +333,3 @@ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var eventListener = options.on || {}; | ||
var removers = {}; | ||
var instances = {}; | ||
function context(control) { | ||
@@ -300,22 +352,14 @@ if (!options.context) { | ||
} | ||
function close(control) { | ||
var returnFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
var _findTarget = findTarget(control), | ||
targetId = _findTarget.targetId, | ||
target = _findTarget.target; | ||
if (!targetId) { | ||
control.setAttribute('aria-expanded', 'false'); | ||
return false; | ||
} | ||
function reset() { | ||
if (removers[targetId]) { | ||
removers[targetId](); | ||
delete removers[targetId]; | ||
function findParentTarget(control) { | ||
var element = control; | ||
while (element) { | ||
if (element.id && instances[element.id]) { | ||
return element; | ||
} | ||
find("[aria-controls=\"".concat(targetId, "\"]")).forEach(function (c) { | ||
c.setAttribute('aria-expanded', 'false'); | ||
}); | ||
element = element.parentElement; | ||
} | ||
} | ||
function close(target) { | ||
var returnFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
if (!target) { | ||
reset(); | ||
return false; | ||
@@ -329,4 +373,13 @@ } | ||
} | ||
reset(); | ||
var currentActiveElement = activeElement(); | ||
var _ref = instances[target.id] || {}, | ||
lastActiveElement = _ref.lastActiveElement, | ||
destroy = _ref.destroy; | ||
delete instances[target.id]; | ||
if (destroy) { | ||
destroy(); | ||
} | ||
findControls(target).forEach(function (c) { | ||
c.setAttribute('aria-expanded', 'false'); | ||
}); | ||
target.removeAttribute('data-ctrly-opened'); | ||
@@ -336,4 +389,4 @@ target.setAttribute('aria-hidden', 'true'); | ||
target.blur(); | ||
if (returnFocus && target.contains(currentActiveElement)) { | ||
focus(control, { | ||
if (returnFocus && lastActiveElement && target.contains(currentActiveElement)) { | ||
focus(lastActiveElement, { | ||
restoreScrollPosition: true | ||
@@ -345,5 +398,6 @@ }); | ||
} | ||
function closeOthers(control) { | ||
find(controlSelector, context(control)).forEach(function (other) { | ||
if (other !== control) { | ||
function closeOthers(target) { | ||
find(controlSelector, context(target)).forEach(function (control) { | ||
var other = findTarget(control); | ||
if (other && other.id !== target.id) { | ||
close(other, false); | ||
@@ -363,10 +417,28 @@ } | ||
if (options.closeOnOutsideClick || options.closeOnScroll) { | ||
removeFuncs.push(on(target, 'mouseenter', activate, passiveEventOptions)); | ||
removeFuncs.push(on(target, 'mouseleave', deactivate, passiveEventOptions)); | ||
removeFuncs.push(on(target, 'touchstart', activate, passiveEventOptions)); | ||
removeFuncs.push(on(target, 'touchend', deactivate, passiveEventOptions)); | ||
removeFuncs.push(on(target, 'mouseenter', activate, { | ||
passive: true | ||
})); | ||
removeFuncs.push(on(target, 'mouseleave', deactivate, { | ||
passive: true | ||
})); | ||
removeFuncs.push(on(target, 'touchstart', activate, { | ||
passive: true | ||
})); | ||
removeFuncs.push(on(target, 'touchend', deactivate, { | ||
passive: true | ||
})); | ||
} | ||
if (options.closeOnBlur && !options.trapFocus) { | ||
removeFuncs.push(on(target, 'focusout', function (e) { | ||
if (!e.relatedTarget || !target.contains(e.relatedTarget)) { | ||
close(target, false); | ||
} | ||
}, { | ||
capture: true, | ||
passive: true | ||
})); | ||
} | ||
if (options.closeOnEsc) { | ||
removeFuncs.push(on(document, 'keydown', function (e) { | ||
if (keyCode(e) === 27 && close(control)) { | ||
if (keyCode(e) === 27 && close(target)) { | ||
e.preventDefault(); | ||
@@ -379,5 +451,7 @@ } | ||
if (!active && keyCode(e) === 1 && !closest(e.target, controlSelector)) { | ||
close(control); | ||
close(target); | ||
} | ||
}, passiveEventOptions)); | ||
}, { | ||
passive: true | ||
})); | ||
} | ||
@@ -387,7 +461,9 @@ if (options.closeOnScroll) { | ||
if (!active) { | ||
close(control); | ||
close(target); | ||
} | ||
}, passiveEventOptions)); | ||
}, { | ||
passive: true | ||
})); | ||
} | ||
if (options.constrainFocus) { | ||
if (options.trapFocus) { | ||
removeFuncs.push(on(document, 'keydown', function (e) { | ||
@@ -397,4 +473,4 @@ if (keyCode(e) !== 9) { | ||
} | ||
var focusableElements = find(focusableElementsSelector, target); | ||
if (!focusableElements[0]) { | ||
var tabbableElements = tabbable(target); | ||
if (!tabbableElements[0]) { | ||
e.preventDefault(); | ||
@@ -405,4 +481,4 @@ focus(target); | ||
var active = activeElement(); | ||
var firstTabStop = focusableElements[0]; | ||
var lastTabStop = focusableElements[focusableElements.length - 1]; | ||
var firstTabStop = tabbableElements[0]; | ||
var lastTabStop = tabbableElements[tabbableElements.length - 1]; | ||
if (e.shiftKey && active === firstTabStop) { | ||
@@ -426,7 +502,5 @@ e.preventDefault(); | ||
function open(control) { | ||
var _findTarget2 = findTarget(control), | ||
targetId = _findTarget2.targetId, | ||
target = _findTarget2.target; | ||
var target = findTarget(control); | ||
if (!target) { | ||
control.setAttribute('aria-expanded', 'false'); | ||
resetControl(control); | ||
return false; | ||
@@ -440,4 +514,7 @@ } | ||
} | ||
removers[targetId] = addEventListeners(control, target); | ||
find("[aria-controls=\"".concat(targetId, "\"]")).forEach(function (c) { | ||
instances[target.id] = { | ||
lastActiveElement: activeElement(), | ||
destroy: addEventListeners(control, target) | ||
}; | ||
findControls(target).forEach(function (c) { | ||
c.setAttribute('aria-expanded', 'true'); | ||
@@ -458,4 +535,11 @@ }); | ||
} | ||
var target = findTarget(control); | ||
if (!target) { | ||
if (close(findParentTarget(control))) { | ||
e.preventDefault(); | ||
} | ||
return; | ||
} | ||
if (control.getAttribute('aria-expanded') === 'true') { | ||
if (close(control)) { | ||
if (close(target)) { | ||
e.preventDefault(); | ||
@@ -466,9 +550,9 @@ } | ||
if (!options.allowMultiple) { | ||
closeOthers(control); | ||
closeOthers(target); | ||
} | ||
var target = open(control); | ||
open(control); | ||
if (target) { | ||
e.preventDefault(); | ||
if (options.focusTarget) { | ||
focus(find(focusableElementsSelector, target)[0] || target); | ||
focus(tabbable(target)[0] || target); | ||
} | ||
@@ -482,3 +566,19 @@ target.scrollTop = 0; | ||
find(controlSelector).forEach(function (control) { | ||
if (control.getAttribute('aria-expanded') === 'true') { | ||
var target = findTarget(control); | ||
if (!target) { | ||
resetControl(control); | ||
return; | ||
} | ||
control.setAttribute('aria-controls', target.id); | ||
var labelledBy = findControls(target).map(function (control) { | ||
if (!control.id) { | ||
control.setAttribute('id', 'ctrly-control-' + ++idCounter); | ||
} | ||
return control.id; | ||
}); | ||
var newLabelledBy = (target.getAttribute('aria-labelledby') || '').split(' ').concat(labelledBy).filter(function (id, pos, arr) { | ||
return id !== '' && arr.indexOf(id) === pos; | ||
}); | ||
target.setAttribute('aria-labelledby', newLabelledBy.join(' ')); | ||
if (control.getAttribute('aria-expanded') === 'true' || control.hasAttribute('data-ctrly-open')) { | ||
open(control); | ||
@@ -488,7 +588,4 @@ return; | ||
control.setAttribute('aria-expanded', 'false'); | ||
var _findTarget3 = findTarget(control), | ||
target = _findTarget3.target; | ||
if (target) { | ||
target.setAttribute('aria-hidden', 'true'); | ||
} | ||
target.setAttribute('aria-hidden', 'true'); | ||
target.removeAttribute('tabindex'); | ||
}); | ||
@@ -503,7 +600,11 @@ }); | ||
find(controlSelector).forEach(function (control) { | ||
close(control, false); | ||
var target = findTarget(control); | ||
if (target) { | ||
close(target, false); | ||
} | ||
}); | ||
for (var id in removers) { | ||
if (Object.prototype.hasOwnProperty.call(removers, id)) { | ||
removers[id].call(); | ||
for (var id in instances) { | ||
if (Object.prototype.hasOwnProperty.call(instances, id)) { | ||
instances[id].destroy(); | ||
delete instances[id]; | ||
} | ||
@@ -510,0 +611,0 @@ } |
/*! | ||
* ctrly v0.1.0 (2018-08-21) | ||
* ctrly v0.2.0 | ||
* Copyright (c) 2018 Jan Sorgalla | ||
* License: MIT | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.ctrly=t()}(this,function(){"use strict";function h(){try{var e=document.activeElement;return e&&e.nodeName?e:document.body}catch(e){return document.body}}function b(e){var t,n;(1<arguments.length&&void 0!==arguments[1]?arguments[1]:{}).restoreScrollPosition&&(n=function(e){for(var t=[];e&&e.parentNode&&1===e.parentNode.nodeType;)e=e.parentNode,t.push(e);return t}(e).map(function(e){return{element:e,top:e.scrollTop,left:e.scrollLeft}}),t=function(){n.forEach(function(e){var t=e.element,n=e.top,r=e.left;t.scrollTop=n,t.scrollLeft=r})});try{e.focus()}catch(e){}t&&t()}function m(e,t){var n=1<arguments.length?t:document;return n&&"function"==typeof n.querySelectorAll?[].slice.call(n.querySelectorAll(e)):[]}function n(e,t){if(!e)return!1;var n=e.matches||e.webkitMatchesSelector||e.msMatchesSelector;return"function"==typeof n&&n.call(e,t)}function y(e,t){if(!e)return null;if("function"==typeof e.closest)return e.closest(t);do{if(n(e,t))return e;e=e.parentNode}while(e&&1===e.nodeType);return null}var t;function a(){if(t)return t;t={capture:!1,once:!1,passive:!1};var e={get capture(){return!(t.capture=!0)},get once(){return!(t.once=!0)},get passive(){return!(t.passive=!0)}};return window.addEventListener("test",e,e),window.removeEventListener("test",e,e),t}function i(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},t=a(),n=t.once,r=t.passive,o=t.capture;return n||r||o?(n||delete e.once,r||delete e.passive,o||delete e.capture,e):Boolean(e.capture)}function g(t,e,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:{capture:!1};if(!t||"function"!=typeof t.addEventListener)return function(){};var o=n,c=function(){!function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:{capture:!1};e&&"function"==typeof e.removeEventListener&&e.removeEventListener(t,n,i(r))}(t,e,o,r)};return r.once&&!a().once&&(o=function(e){c(),n.call(t,e)}),t.addEventListener(e,o,i(r)),c}var u={selector:"[data-ctrly]",context:null,focusTarget:!0,closeOnEsc:!0,closeOnOutsideClick:!0,closeOnScroll:!1,constrainFocus:!1,allowMultiple:!1,on:null},E='a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),iframe,object,embed,[contenteditable],[tabindex]:not([tabindex^="-"])',A={passive:!0};function w(e){return"which"in e?e.which:e.keyCode}function x(e){var t=e.getAttribute("aria-controls");return{targetId:t,target:t?document.getElementById(t):null}}return function(){var n,r,e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},s=(n={},[u,e].forEach(function(e){for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]=e[t])}),n),d=s.selector,o=s.on||{},f={};function p(e,t){return("function"!=typeof o[t]||!1!==o[t](e))&&!1!==function(e,t){var n,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!e||"function"!=typeof e.dispatchEvent)return!0;r.bubbles=r.bubbles||!1,r.cancelable=r.cancelable||!1,r.composed=r.composed||!1,r.detail=r.detail||null;try{n=new CustomEvent(t,r)}catch(e){(n=document.createEvent("CustomEvent")).initCustomEvent(t,r.bubbles,r.cancelable,r.detail)}return e.dispatchEvent(n)}(e,"ctrly:".concat(t),{bubbles:!0,cancelable:!0})}function v(e){var t=!(1<arguments.length&&void 0!==arguments[1])||arguments[1],n=x(e),r=n.targetId,o=n.target;if(!r)return e.setAttribute("aria-expanded","false"),!1;function c(){f[r]&&(f[r](),delete f[r]),m('[aria-controls="'.concat(r,'"]')).forEach(function(e){e.setAttribute("aria-expanded","false")})}if(!o)return c(),!1;if(!o.hasAttribute("data-ctrly-opened"))return!1;if(!p(o,"close"))return!1;c();var a=h();return o.removeAttribute("data-ctrly-opened"),o.setAttribute("aria-hidden","true"),o.removeAttribute("tabindex"),o.blur(),t&&o.contains(a)&&b(e,{restoreScrollPosition:!0}),p(o,"closed"),o}function c(t){var e;m(d,(e=t,s.context?y(e,s.context):document)).forEach(function(e){e!==t&&v(e,!1)})}function a(e){var t,c,n,r,o,a,i=x(e),u=i.targetId,l=i.target;return l?!l.hasAttribute("data-ctrly-opened")&&!!p(l,"open")&&(f[u]=(t=e,c=l,r=!(n=[]),o=function(){r=!0},a=function(){r=!1},(s.closeOnOutsideClick||s.closeOnScroll)&&(n.push(g(c,"mouseenter",o,A)),n.push(g(c,"mouseleave",a,A)),n.push(g(c,"touchstart",o,A)),n.push(g(c,"touchend",a,A))),s.closeOnEsc&&n.push(g(document,"keydown",function(e){27===w(e)&&v(t)&&e.preventDefault()})),s.closeOnOutsideClick&&n.push(g(document,"click",function(e){r||1!==w(e)||y(e.target,d)||v(t)},A)),s.closeOnScroll&&n.push(g(window,"scroll",function(){r||v(t)},A)),s.constrainFocus&&n.push(g(document,"keydown",function(e){if(9===w(e)){var t=m(E,c);if(!t[0])return e.preventDefault(),void b(c);var n=h(),r=t[0],o=t[t.length-1];if(e.shiftKey&&n===r)return e.preventDefault(),void b(o);e.shiftKey||n!==o||(b(r),e.preventDefault())}})),function(){for(;n.length;)n.shift().call()}),m('[aria-controls="'.concat(u,'"]')).forEach(function(e){e.setAttribute("aria-expanded","true")}),l.setAttribute("data-ctrly-opened",""),l.setAttribute("aria-hidden","false"),l.setAttribute("tabindex","-1"),p(l,"opened"),l):(e.setAttribute("aria-expanded","false"),!1)}function t(){var e,t;r||(r=function(e,t,n,r){var o=4<arguments.length&&void 0!==arguments[4]?arguments[4]:{capture:!1},c=!0===o.once;delete o.once;var a=g(e,t,function(e){var t=y(e.target,n);t&&(c&&a(),r.call(t,e,t))},o);return a}(document,"click",d,function(e,t){if(1===w(e))if("true"!==t.getAttribute("aria-expanded")){s.allowMultiple||c(t);var n=a(t);n&&(e.preventDefault(),s.focusTarget&&b(m(E,n)[0]||n),n.scrollTop=0,n.scrollLeft=0)}else v(t)&&e.preventDefault()})),e=function(){m(d).forEach(function(e){if("true"!==e.getAttribute("aria-expanded")){e.setAttribute("aria-expanded","false");var t=x(e).target;t&&t.setAttribute("aria-hidden","true")}else a(e)})},"complete"!==(t=document.readyState)&&"interactive"!==t?document.addEventListener("DOMContentLoaded",function(){e()},i({capture:!0,once:!0,passive:!0})):setTimeout(e,0)}return t(),{init:t,destroy:function(){for(var e in r&&(r(),r=null),m(d).forEach(function(e){v(e,!1)}),f)Object.prototype.hasOwnProperty.call(f,e)&&f[e].call()}}}}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.ctrly=t()}(this,function(){"use strict";function f(){try{var e=document.activeElement;return e&&e.nodeName?e:document.body}catch(e){return document.body}}function r(e){for(var t=[];e&&e.parentNode&&1===e.parentNode.nodeType;)e=e.parentNode,t.push(e);return t}function p(e){var t,n;(1<arguments.length&&void 0!==arguments[1]?arguments[1]:{}).restoreScrollPosition&&(n=r(e).map(function(e){return[e,e.scrollTop,e.scrollLeft]}),t=function(){n.forEach(function(e){e[0].scrollTop=e[1],e[0].scrollLeft=e[2]})});try{e.focus()}catch(e){}t&&t()}function v(e,t){var n=1<arguments.length?t:document;return n&&"function"==typeof n.querySelectorAll?[].slice.call(n.querySelectorAll(e)):[]}function n(e,t){if(!e)return!1;var n=e.matches||e.webkitMatchesSelector||e.msMatchesSelector;return"function"==typeof n&&n.call(e,t)}function b(e,t){if(!e)return null;if("function"==typeof e.closest)return e.closest(t);do{if(n(e,t))return e;e=e.parentNode}while(e&&1===e.nodeType);return null}var t;function a(){if(t)return t;t={capture:!1,once:!1,passive:!1};var e={get capture(){return!(t.capture=!0)},get once(){return!(t.once=!0)},get passive(){return!(t.passive=!0)}};return window.addEventListener("test",e,e),window.removeEventListener("test",e,e),t}function h(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},t=a(),n=t.once,r=t.passive,o=t.capture;return n||r||o?(n||delete e.once,r||delete e.passive,o||delete e.capture,e):Boolean(e.capture)}function m(t,e,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:{capture:!1};if(!t||"function"!=typeof t.addEventListener)return function(){};var o=n,i=function(){!function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:{capture:!1};e&&"function"==typeof e.removeEventListener&&e.removeEventListener(t,n,h(r))}(t,e,o,r)};return r.once&&!a().once&&(o=function(e){i(),n.call(t,e)}),t.addEventListener(e,o,h(r)),i}var o=["a[href]","area[href]","input","select","textarea","button","iframe","object","audio[controls]","video[controls]","[contenteditable]","[tabindex]"].join(","),i=/^(input|select|textarea|button|object)$/;function c(e){var t=e.nodeName.toLowerCase();if("area"===t)return function(e){var t=e.parentNode,n=t.name;if(!e.href||!n||"map"!==t.nodeName.toLowerCase())return!1;var r=v('img[usemap="#'.concat(n,'"]'));return 0<r.length&&s(r[0])}(e);if(e.disabled)return!1;if(i.test(t)){var n=b(e,"fieldset");if(n&&n.disabled)return!1}return s(e)}function u(e){var t=d(e);return c(e)&&0<=t}function l(e,t){var n=d(e,!0),r=d(t,!0);return n===r?2&e.compareDocumentPosition(t)?1:-1:n-r}function s(e){var t=getComputedStyle(e);return"hidden"!==t.visibility&&"collapse"!==t.visibility&&"none"!==t.display&&r(e).every(function(e){return"none"!==getComputedStyle(e).display})}function d(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=parseInt(e.getAttribute("tabindex"),10);return isNaN(n)?0:t&&n<0?0:n}function y(e){return v(o,0<arguments.length?e:document).filter(u).sort(l)}var g={selector:"[data-ctrly]",context:null,focusTarget:!0,closeOnBlur:!0,closeOnEsc:!0,closeOnOutsideClick:!0,closeOnScroll:!1,trapFocus:!1,allowMultiple:!1,on:null};function A(e){return"which"in e?e.which:e.keyCode}function E(e){return v('[aria-controls="'.concat(e.id,'"]'))}function w(e){return document.getElementById(e.getAttribute("aria-controls")||e.getAttribute("data-ctrly"))}function x(e){e.removeAttribute("aria-controls"),e.removeAttribute("aria-expanded")}var O=0;return function(){var n,r,e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},c=(n={},[g,e].forEach(function(e){for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]=e[t])}),n),u=c.selector,o=c.on||{},l={};function s(e,t){return("function"!=typeof o[t]||!1!==o[t](e))&&!1!==function(e,t){var n,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!e||"function"!=typeof e.dispatchEvent)return!0;r.bubbles=r.bubbles||!1,r.cancelable=r.cancelable||!1,r.composed=r.composed||!1,r.detail=r.detail||null;try{n=new CustomEvent(t,r)}catch(e){(n=document.createEvent("CustomEvent")).initCustomEvent(t,r.bubbles,r.cancelable,r.detail)}return e.dispatchEvent(n)}(e,"ctrly:".concat(t),{bubbles:!0,cancelable:!0})}function d(e){var t=!(1<arguments.length&&void 0!==arguments[1])||arguments[1];if(!e)return!1;if(!e.hasAttribute("data-ctrly-opened"))return!1;if(!s(e,"close"))return!1;var n=f(),r=l[e.id]||{},o=r.lastActiveElement,i=r.destroy;return delete l[e.id],i&&i(),E(e).forEach(function(e){e.setAttribute("aria-expanded","false")}),e.removeAttribute("data-ctrly-opened"),e.setAttribute("aria-hidden","true"),e.removeAttribute("tabindex"),e.blur(),t&&o&&e.contains(n)&&p(o,{restoreScrollPosition:!0}),s(e,"closed"),e}function i(n){var e;v(u,(e=n,c.context?b(e,c.context):document)).forEach(function(e){var t=w(e);t&&t.id!==n.id&&d(t,!1)})}function a(e){var i,t,n,r,o,a=w(e);return a?!a.hasAttribute("data-ctrly-opened")&&!!s(a,"open")&&(l[a.id]={lastActiveElement:f(),destroy:(i=a,t=[],n=!1,r=function(){n=!0},o=function(){n=!1},(c.closeOnOutsideClick||c.closeOnScroll)&&(t.push(m(i,"mouseenter",r,{passive:!0})),t.push(m(i,"mouseleave",o,{passive:!0})),t.push(m(i,"touchstart",r,{passive:!0})),t.push(m(i,"touchend",o,{passive:!0}))),c.closeOnBlur&&!c.trapFocus&&t.push(m(i,"focusout",function(e){e.relatedTarget&&i.contains(e.relatedTarget)||d(i,!1)},{capture:!0,passive:!0})),c.closeOnEsc&&t.push(m(document,"keydown",function(e){27===A(e)&&d(i)&&e.preventDefault()})),c.closeOnOutsideClick&&t.push(m(document,"click",function(e){n||1!==A(e)||b(e.target,u)||d(i)},{passive:!0})),c.closeOnScroll&&t.push(m(window,"scroll",function(){n||d(i)},{passive:!0})),c.trapFocus&&t.push(m(document,"keydown",function(e){if(9===A(e)){var t=y(i);if(!t[0])return e.preventDefault(),void p(i);var n=f(),r=t[0],o=t[t.length-1];if(e.shiftKey&&n===r)return e.preventDefault(),void p(o);e.shiftKey||n!==o||(p(r),e.preventDefault())}})),function(){for(;t.length;)t.shift().call()})},E(a).forEach(function(e){e.setAttribute("aria-expanded","true")}),a.setAttribute("data-ctrly-opened",""),a.setAttribute("aria-hidden","false"),a.setAttribute("tabindex","-1"),s(a,"opened"),a):(x(e),!1)}function t(){var e,t;r||(r=function(e,t,n,r){var o=4<arguments.length&&void 0!==arguments[4]?arguments[4]:{capture:!1},i=!0===o.once;delete o.once;var a=m(e,t,function(e){var t=b(e.target,n);t&&(i&&a(),r.call(t,e,t))},o);return a}(document,"click",u,function(e,t){if(1===A(e)){var n=w(t);n?"true"!==t.getAttribute("aria-expanded")?(c.allowMultiple||i(n),a(t),n&&(e.preventDefault(),c.focusTarget&&p(y(n)[0]||n),n.scrollTop=0,n.scrollLeft=0)):d(n)&&e.preventDefault():d(function(e){for(var t=e;t;){if(t.id&&l[t.id])return t;t=t.parentElement}}(t))&&e.preventDefault()}})),e=function(){v(u).forEach(function(e){var t=w(e);if(t){e.setAttribute("aria-controls",t.id);var n=E(t).map(function(e){return e.id||e.setAttribute("id","ctrly-control-"+ ++O),e.id}),r=(t.getAttribute("aria-labelledby")||"").split(" ").concat(n).filter(function(e,t,n){return""!==e&&n.indexOf(e)===t});t.setAttribute("aria-labelledby",r.join(" ")),"true"===e.getAttribute("aria-expanded")||e.hasAttribute("data-ctrly-open")?a(e):(e.setAttribute("aria-expanded","false"),t.setAttribute("aria-hidden","true"),t.removeAttribute("tabindex"))}else x(e)})},"complete"!==(t=document.readyState)&&"interactive"!==t?document.addEventListener("DOMContentLoaded",function(){e()},h({capture:!0,once:!0,passive:!0})):setTimeout(e,0)}return t(),{init:t,destroy:function(){for(var e in r&&(r(),r=null),v(u).forEach(function(e){var t=w(e);t&&d(t,!1)}),l)Object.prototype.hasOwnProperty.call(l,e)&&(l[e].destroy(),delete l[e])}}}}); |
{ | ||
"name": "ctrly", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Lightweight and dependency-free content toggling with a focus on accessibility.", | ||
@@ -8,3 +8,3 @@ "repository": "git@github.com:jsor/ctrly.git", | ||
"name": "Jan Sorgalla", | ||
"url": "http://sorgalla.com", | ||
"url": "https://sorgalla.com", | ||
"email": "jsorgalla@gmail.com" | ||
@@ -23,3 +23,4 @@ }, | ||
"browser": "dist/ctrly.min.js", | ||
"module": "src/ctrly.mjs", | ||
"module": "dist/ctrly-module.mjs", | ||
"es2015": "src/ctrly-es2015.js", | ||
"files": [ | ||
@@ -30,3 +31,9 @@ "dist", | ||
"browserslist": [ | ||
"last 2 versions" | ||
"defaults", | ||
"IE >= 10", | ||
"Chrome >= 60", | ||
"Edge >= 15", | ||
"Firefox >= 54", | ||
"iOS >= 10.3", | ||
"Safari >= 10.1" | ||
], | ||
@@ -54,3 +61,3 @@ "xo": { | ||
"lint": "xo", | ||
"size": "size-limit --limit 2.5KB dist/ctrly.min.js", | ||
"size": "size-limit --limit 2.7KB dist/ctrly.js", | ||
"test": "npm run lint && karma start --single-run", | ||
@@ -61,7 +68,7 @@ "test:browserstack": "npm run build:test && browserstack-runner", | ||
"devDependencies": { | ||
"@babel/core": "^7.0.0-rc.1", | ||
"@babel/preset-env": "^7.0.0-rc.1", | ||
"browserstack-runner": "^0.8.0", | ||
"@babel/core": "^7.0.0", | ||
"@babel/preset-env": "^7.0.0", | ||
"browserstack-runner": "^0.9.0", | ||
"chai": "^4.1.2", | ||
"domestique": "^1.4.0", | ||
"domestique": "^1.5.0", | ||
"karma": "^3.0.0", | ||
@@ -71,3 +78,3 @@ "karma-chai": "^0.1.0", | ||
"karma-coverage": "^1.1.2", | ||
"karma-coverage-istanbul-reporter": "^2.0.1", | ||
"karma-coverage-istanbul-reporter": "^2.0.3", | ||
"karma-firefox-launcher": "^1.1.0", | ||
@@ -78,14 +85,14 @@ "karma-mocha": "^1.3.0", | ||
"mocha": "^5.2.0", | ||
"rollup": "^0.64.1", | ||
"rollup-plugin-babel": "^4.0.0-beta.8", | ||
"rollup": "^0.65.2", | ||
"rollup-plugin-babel": "^4.0.3", | ||
"rollup-plugin-clean": "^1.0.0", | ||
"rollup-plugin-cleanup": "^3.0.0", | ||
"rollup-plugin-commonjs": "^9.1.5", | ||
"rollup-plugin-commonjs": "^9.1.6", | ||
"rollup-plugin-istanbul": "^2.0.1", | ||
"rollup-plugin-node-resolve": "^3.3.0", | ||
"rollup-plugin-uglify": "^4.0.0", | ||
"rollup-plugin-node-resolve": "^3.4.0", | ||
"rollup-plugin-uglify": "^5.0.2", | ||
"simulant": "^0.2.2", | ||
"size-limit": "^0.19.2", | ||
"xo": "^0.22.0" | ||
"size-limit": "^0.20.0", | ||
"xo": "^0.23.0" | ||
} | ||
} |
111
README.md
@@ -20,3 +20,3 @@ ctrly | ||
Minified and gzipped, the total footprint weights about 2kB. | ||
Minified and gzipped, the total footprint weights about 2.7kB. | ||
@@ -54,10 +54,17 @@ Installation | ||
The control must have a `data-ctrly` attribute and link to it's target through a | ||
`aria-controls` attribute which must contains the id of the target. | ||
The control must have a `data-ctrly` which must contain the ID of the target. | ||
```html | ||
<button data-ctrly aria-controls="my-target">Toggle</button> | ||
<button data-ctrly="my-target">Toggle</button> | ||
<section id="my-target">You clicked the toggle to make me visible</section> | ||
``` | ||
> The [specification](https://www.w3.org/TR/wai-aria-1.1/#aria-controls) also | ||
allows a | ||
[*ID reference list*](https://www.w3.org/TR/wai-aria-1.1/#valuetype_idref_list) | ||
(a list of multiple, space-separated ID references) as value of the | ||
`arial-controls` attribute. | ||
This is **not** supported by ctrly and only a **single** ID reference is | ||
allowed. | ||
To initialize all controls, the `ctrly()` function must be called once. | ||
@@ -69,2 +76,28 @@ | ||
ctrly then adds all required ARIA attributes, the `aria-controls` and | ||
`aria-expanded` attributes to the control and the `aria-hidden` and | ||
`aria-labelledby` to the target. | ||
If the control does not have an `id` attribute, ctrly will add an auto-generated | ||
ID. | ||
The fully generated HTML looks like the following. | ||
```html | ||
<button | ||
data-ctrly="my-target" | ||
id="ctrly-control-1" | ||
aria-controls="my-target" | ||
aria-expanded="false"> | ||
Toggle | ||
</button> | ||
<section | ||
id="my-target" | ||
aria-hidden="false" | ||
aria-labelledby="ctrly-control-1" | ||
> | ||
You clicked the toggle to make me visible | ||
</section> | ||
``` | ||
**Note:** ctrly does not ship with any default CSS which shows and hides the | ||
@@ -81,5 +114,2 @@ target element as it makes no assumptions on how the visibility is controlled. | ||
} | ||
.target-selector[aria-hidden="false"] { | ||
display: block; | ||
} | ||
@@ -90,27 +120,15 @@ /* Toggle via the visibility property */ | ||
} | ||
.target-selector[aria-hidden="false"] { | ||
visibility: visible; | ||
} | ||
``` | ||
It is also possible to toggle the visibility via the | ||
[`hidden` attribute](https://developer.mozilla.org/de/docs/Web/HTML/Globale_Attribute/hidden). | ||
This can be implemented by using event callbacks to remove and add the attribute. | ||
It is also good practice to hide the controls if JavaScript is disabled. | ||
```js | ||
ctrly({ | ||
on: { | ||
open: target => { | ||
target.removeAttribute('hidden'); | ||
}, | ||
close: target => { | ||
target.addAttribute('hidden'); | ||
} | ||
} | ||
}); | ||
This can be done depending on the presence of the `aria-controls` attribute | ||
added by ctrly. | ||
```css | ||
.control-selector:not([aria-controls]) { | ||
display: none; | ||
} | ||
``` | ||
More information about the event callbacks can be found in the | ||
[Events section](#events). | ||
API | ||
@@ -169,6 +187,7 @@ --- | ||
* [focusTarget](#focustarget) | ||
* [closeOnBlur](#closeonblur) | ||
* [closeOnEsc](#closeonesc) | ||
* [closeOnOutsideClick](#closeonoutsideclick) | ||
* [closeOnScroll](#closeonscroll) | ||
* [constrainFocus](#constrainfocus) | ||
* [trapFocus](#trapfocus) | ||
* [allowMultiple](#allowmultiple) | ||
@@ -186,3 +205,3 @@ * [on](#on) | ||
```html | ||
<button class="my-control" aria-controls="my-target">Toggle</button> | ||
<button class="my-control" data-ctrly="my-target">Toggle</button> | ||
``` | ||
@@ -200,3 +219,3 @@ | ||
A selector to group controls together. Can be used in combination with the | ||
A selector to group targets together. Can be used in combination with the | ||
[allowMultiple](#allowmultiple) option to allow or disallow multiple open | ||
@@ -211,6 +230,6 @@ targets inside a context. | ||
<div class="my-context"> | ||
<button data-ctrly aria-controls="my-target">Toggle</button> | ||
<button data-ctrly="my-target">Toggle</button> | ||
</div> | ||
<div class="my-context"> | ||
<button data-ctrly aria-controls="my-target">Toggle</button> | ||
<button data-ctrly="my-target">Toggle</button> | ||
</div> | ||
@@ -241,2 +260,21 @@ ``` | ||
### closeOnBlur | ||
*Default:* `true` | ||
By default, targets are closed when the focus is shifted from an element inside | ||
the target to an element outside the target. Passing `false` as an option | ||
disables this behavior. | ||
> This setting is always `false` if [`trapFocus`](#trapfocus) is set | ||
to `true`. | ||
#### Example | ||
```js | ||
ctrly({ | ||
closeOnBlur: false | ||
}); | ||
``` | ||
### closeOnEsc | ||
@@ -287,8 +325,8 @@ | ||
### constrainFocus | ||
### trapFocus | ||
*Default:* `false` | ||
By default, targets are closed when there is a mouse click outside the target. | ||
Passing `false` as an option disables this behavior. | ||
Passing `true` as an option ensures that <kbd>TAB</kbd> and | ||
<kbd>SHIFT</kbd>+<kbd>TAB</kbd> do not move focus outside the target. | ||
@@ -315,2 +353,5 @@ #### Example | ||
> To allow multiple open targets, [`closeOnBlur`](#closeonblur) must be set to | ||
`false`. | ||
#### Example | ||
@@ -317,0 +358,0 @@ |
205
src/ctrly.js
@@ -9,3 +9,4 @@ import { | ||
on, | ||
ready | ||
ready, | ||
tabbable | ||
} from 'domestique'; | ||
@@ -17,6 +18,7 @@ | ||
focusTarget: true, | ||
closeOnBlur: true, | ||
closeOnEsc: true, | ||
closeOnOutsideClick: true, | ||
closeOnScroll: false, | ||
constrainFocus: false, | ||
trapFocus: false, | ||
allowMultiple: false, | ||
@@ -26,8 +28,2 @@ on: null | ||
const focusableElementsSelector = 'a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),iframe,object,embed,[contenteditable],[tabindex]:not([tabindex^="-"])'; | ||
const passiveEventOptions = { | ||
passive: true | ||
}; | ||
function settings(opts) { | ||
@@ -52,8 +48,19 @@ const extended = {}; | ||
function findControls(target) { | ||
return find(`[aria-controls="${target.id}"]`); | ||
} | ||
function findTarget(control) { | ||
const targetId = control.getAttribute('aria-controls'); | ||
return document.getElementById( | ||
control.getAttribute('aria-controls') || control.getAttribute('data-ctrly') | ||
); | ||
} | ||
return {targetId, target: targetId ? document.getElementById(targetId) : null}; | ||
function resetControl(control) { | ||
control.removeAttribute('aria-controls'); | ||
control.removeAttribute('aria-expanded'); | ||
} | ||
let idCounter = 0; | ||
export default function ctrly(opts = {}) { | ||
@@ -65,3 +72,3 @@ const options = settings(opts); | ||
const removers = {}; | ||
const instances = {}; | ||
@@ -89,23 +96,16 @@ function context(control) { | ||
function close(control, returnFocus = true) { | ||
const {targetId, target} = findTarget(control); | ||
function findParentTarget(control) { | ||
let element = control; | ||
if (!targetId) { | ||
control.setAttribute('aria-expanded', 'false'); | ||
return false; | ||
} | ||
function reset() { | ||
if (removers[targetId]) { | ||
removers[targetId](); | ||
delete removers[targetId]; | ||
while (element) { | ||
if (element.id && instances[element.id]) { | ||
return element; | ||
} | ||
find(`[aria-controls="${targetId}"]`).forEach(c => { | ||
c.setAttribute('aria-expanded', 'false'); | ||
}); | ||
element = element.parentElement; | ||
} | ||
} | ||
function close(target, returnFocus = true) { | ||
if (!target) { | ||
reset(); | ||
return false; | ||
@@ -122,7 +122,16 @@ } | ||
reset(); | ||
// Store reference before we call target.blur() | ||
const currentActiveElement = activeElement(); | ||
const {lastActiveElement, destroy} = instances[target.id] || {}; | ||
delete instances[target.id]; | ||
if (destroy) { | ||
destroy(); | ||
} | ||
findControls(target).forEach(c => { | ||
c.setAttribute('aria-expanded', 'false'); | ||
}); | ||
target.removeAttribute('data-ctrly-opened'); | ||
@@ -136,4 +145,8 @@ | ||
// We return focus only if the current focus is inside this target | ||
if (returnFocus && target.contains(currentActiveElement)) { | ||
focus(control, { | ||
if ( | ||
returnFocus && | ||
lastActiveElement && | ||
target.contains(currentActiveElement) | ||
) { | ||
focus(lastActiveElement, { | ||
restoreScrollPosition: true | ||
@@ -148,5 +161,7 @@ }); | ||
function closeOthers(control) { | ||
find(controlSelector, context(control)).forEach(other => { | ||
if (other !== control) { | ||
function closeOthers(target) { | ||
find(controlSelector, context(target)).forEach(control => { | ||
const other = findTarget(control); | ||
if (other && other.id !== target.id) { | ||
close(other, false); | ||
@@ -172,19 +187,32 @@ } | ||
removeFuncs.push( | ||
on(target, 'mouseenter', activate, passiveEventOptions) | ||
on(target, 'mouseenter', activate, {passive: true}) | ||
); | ||
removeFuncs.push( | ||
on(target, 'mouseleave', deactivate, passiveEventOptions) | ||
on(target, 'mouseleave', deactivate, {passive: true}) | ||
); | ||
removeFuncs.push( | ||
on(target, 'touchstart', activate, passiveEventOptions) | ||
on(target, 'touchstart', activate, {passive: true}) | ||
); | ||
removeFuncs.push( | ||
on(target, 'touchend', deactivate, passiveEventOptions) | ||
on(target, 'touchend', deactivate, {passive: true}) | ||
); | ||
} | ||
if (options.closeOnBlur && !options.trapFocus) { | ||
removeFuncs.push( | ||
on(target, 'focusout', e => { | ||
if ( | ||
!e.relatedTarget || | ||
!target.contains(e.relatedTarget) | ||
) { | ||
close(target, false); | ||
} | ||
}, {capture: true, passive: true}) | ||
); | ||
} | ||
if (options.closeOnEsc) { | ||
removeFuncs.push( | ||
on(document, 'keydown', e => { | ||
if (keyCode(e) === 27 && close(control)) { | ||
if (keyCode(e) === 27 && close(target)) { | ||
e.preventDefault(); | ||
@@ -204,5 +232,5 @@ } | ||
if (!active && keyCode(e) === 1 && !closest(e.target, controlSelector)) { | ||
close(control); | ||
close(target); | ||
} | ||
}, passiveEventOptions) | ||
}, {passive: true}) | ||
); | ||
@@ -215,9 +243,9 @@ } | ||
if (!active) { | ||
close(control); | ||
close(target); | ||
} | ||
}, passiveEventOptions) | ||
}, {passive: true}) | ||
); | ||
} | ||
if (options.constrainFocus) { | ||
if (options.trapFocus) { | ||
removeFuncs.push( | ||
@@ -229,5 +257,5 @@ on(document, 'keydown', e => { | ||
const focusableElements = find(focusableElementsSelector, target); | ||
const tabbableElements = tabbable(target); | ||
if (!focusableElements[0]) { | ||
if (!tabbableElements[0]) { | ||
e.preventDefault(); | ||
@@ -239,4 +267,4 @@ focus(target); | ||
const active = activeElement(); | ||
const firstTabStop = focusableElements[0]; | ||
const lastTabStop = focusableElements[focusableElements.length - 1]; | ||
const firstTabStop = tabbableElements[0]; | ||
const lastTabStop = tabbableElements[tabbableElements.length - 1]; | ||
@@ -265,6 +293,6 @@ if (e.shiftKey && active === firstTabStop) { | ||
function open(control) { | ||
const {targetId, target} = findTarget(control); | ||
const target = findTarget(control); | ||
if (!target) { | ||
control.setAttribute('aria-expanded', 'false'); | ||
resetControl(control); | ||
return false; | ||
@@ -281,5 +309,8 @@ } | ||
removers[targetId] = addEventListeners(control, target); | ||
instances[target.id] = { | ||
lastActiveElement: activeElement(), | ||
destroy: addEventListeners(control, target) | ||
}; | ||
find(`[aria-controls="${targetId}"]`).forEach(c => { | ||
findControls(target).forEach(c => { | ||
c.setAttribute('aria-expanded', 'true'); | ||
@@ -307,4 +338,16 @@ }); | ||
const target = findTarget(control); | ||
if (!target) { | ||
// Allow controls without a value for data-ctrly | ||
// to close a target if it's a child element | ||
if (close(findParentTarget(control))) { | ||
e.preventDefault(); | ||
} | ||
return; | ||
} | ||
if (control.getAttribute('aria-expanded') === 'true') { | ||
if (close(control)) { | ||
if (close(target)) { | ||
e.preventDefault(); | ||
@@ -317,6 +360,6 @@ } | ||
if (!options.allowMultiple) { | ||
closeOthers(control); | ||
closeOthers(target); | ||
} | ||
const target = open(control); | ||
open(control); | ||
@@ -328,3 +371,3 @@ if (target) { | ||
focus( | ||
find(focusableElementsSelector, target)[0] || target | ||
tabbable(target)[0] || target | ||
); | ||
@@ -342,14 +385,39 @@ } | ||
find(controlSelector).forEach(control => { | ||
if (control.getAttribute('aria-expanded') === 'true') { | ||
open(control); | ||
const target = findTarget(control); | ||
if (!target) { | ||
resetControl(control); | ||
return; | ||
} | ||
control.setAttribute('aria-expanded', 'false'); | ||
control.setAttribute('aria-controls', target.id); | ||
const {target} = findTarget(control); | ||
const labelledBy = findControls(target).map(control => { | ||
if (!control.id) { | ||
control.setAttribute('id', 'ctrly-control-' + ++idCounter); | ||
} | ||
if (target) { | ||
target.setAttribute('aria-hidden', 'true'); | ||
return control.id; | ||
}); | ||
const newLabelledBy = (target.getAttribute('aria-labelledby') || '') | ||
.split(' ') | ||
.concat(labelledBy) | ||
.filter((id, pos, arr) => { | ||
return id !== '' && arr.indexOf(id) === pos; | ||
}); | ||
target.setAttribute('aria-labelledby', newLabelledBy.join(' ')); | ||
if ( | ||
control.getAttribute('aria-expanded') === 'true' || | ||
control.hasAttribute('data-ctrly-open') | ||
) { | ||
open(control); | ||
return; | ||
} | ||
control.setAttribute('aria-expanded', 'false'); | ||
target.setAttribute('aria-hidden', 'true'); | ||
target.removeAttribute('tabindex'); | ||
}); | ||
@@ -366,9 +434,14 @@ }); | ||
find(controlSelector).forEach(control => { | ||
close(control, false); | ||
const target = findTarget(control); | ||
if (target) { | ||
close(target, false); | ||
} | ||
}); | ||
// Iterate leftover removers | ||
for (const id in removers) { | ||
if (Object.prototype.hasOwnProperty.call(removers, id)) { | ||
removers[id].call(); | ||
// Iterate leftover instances | ||
for (const id in instances) { | ||
if (Object.prototype.hasOwnProperty.call(instances, id)) { | ||
instances[id].destroy(); | ||
delete instances[id]; | ||
} | ||
@@ -375,0 +448,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
79843
1871
510