Skip to content

Instantly share code, notes, and snippets.

@andy-blum
Last active May 8, 2020 17:40
Show Gist options
  • Save andy-blum/cc1e15e8fd83f34baba58efadee1514e to your computer and use it in GitHub Desktop.
Save andy-blum/cc1e15e8fd83f34baba58efadee1514e to your computer and use it in GitHub Desktop.
Utility Functions
const focusTrap = {
active: false,
trigger: false,
container: false,
selectables: 'a[href],area[href],input:not([disabled]):not([type="hidden"]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),[tabindex="0"]',
items: [],
init: function(container, trigger = false) {
this.active = true;
this.container = container;
this.items = [];
trap = this;
Array.from(container.querySelectorAll(this.selectables)).forEach(function(el){
if (el.getBoundingClientRect().width > 0) {
trap.items.push(el);
}
});
this.items[0].focus();
document.addEventListener('click', this.clickListener);
document.addEventListener('keydown', this.keyListener);
},
clickListener: function(e) {
const trap = focusTrap;
const path = getDomPath(e.target);
if (!path.includes(trap.container)) {
focusTrap.destroy();
}
},
keyListener: function(e) {
//handle tab
if (e.keyCode === 9) {
e.preventDefault();
const i = focusTrap.items.indexOf(e.target);
let j;
if (e.shiftKey) {
if (i == 0) {
console.log(trap.items.length);
j = focusTrap.items.length - 1;
} else {
j = i - 1;
}
} else {
if (i == (focusTrap.items.length - 1)) {
j = 0
} else {
j = i + 1;
}
}
focusTrap.items[j].focus();
}
//handle escape
if (e.keyCode === 27) {
trap.destroy();
}
},
destroy: function() {
if (this.active) {
if (this.trigger) {
this.trigger.focus();
}
this.container.dispatchEvent(new CustomEvent('exitfocustrap', {bubbles:true,cancelable:true}));
this.active = false;
this.trigger = false;
this.container = false;
this.items = [];
document.removeEventListener('click', this.clickListener);
document.removeEventListener('keydown', this.keyListener);
} else {
console.error('Cannot destroy inactive focus trap.')
}
}
};
function trapFocus(container, trigger = false) {
focusTrap.init(container, trigger);
}
function getDomPath(el) {
const path = [];
while (el) {
path.unshift(el);
el = el.parentElement;
}
return path;
}
function getVisibleChildNodes(input) {
if (!input instanceof Node) {
return false;d
} else {
const filteredNodes =
// Create array from child nodes
Array.from(input.childNodes)
// Remove comments
.filter(function(node){
if (node.nodeName != '#comment') {return node;}
})
// Remove nodes that are only whitespace
.filter(function(node){
if (node.nodeName != '#text'){
return node;
} else if (node.wholeText.replace(/\s*/g,"") != "") {
return node;
}
});
return filteredNodes
}
}
Array.from(document.querySelectorAll('a, button')).forEach(function(link){
link.removeEventListener('mousedown', preventFocusOnClick);
});
function preventFocusOnClick(e) {
e.preventDefault();
}
if (!!Promise) {
window.slideUp = function(target, duration=500) {
return new Promise((resolve, reject) => {
if (!(target instanceof HTMLElement)) {
reject('Unable to slide element.', target);
} else {
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.boxSizing = 'border-box';
target.style.height = target.offsetHeight + 'px';
target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
window.setTimeout( () => {
target.style.display = 'none';
target.style.removeProperty('height');
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
resolve();
}, duration);
}
});
}
window.slideDown = function(target, duration=500) {
return new Promise((resolve, reject) => {
if (!(target instanceof HTMLElement)) {
reject('Unable to slide element.', target);
} else {
target.style.removeProperty('display');
let display = window.getComputedStyle(target).display;
if (display === 'none')
display = 'block';
target.style.display = display;
let height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
target.offsetHeight;
target.style.boxSizing = 'border-box';
target.style.transitionProperty = "height, margin, padding";
target.style.transitionDuration = duration + 'ms';
target.style.height = height + 'px';
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
window.setTimeout( () => {
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
resolve();
}, duration);
}
});
}
window.slideToHeight = function(target, height, duration = 500) {
return new Promise((resolve, reject) => {
if (!(target instanceof HTMLElement)) {
reject('Unable to slide element.', target);
} else if (!height) {
reject('slideToHeight() requires a height to slide to.');
} else {
target.style.transitionProperty = 'height';
target.style.transitionDuration = duration + 'ms';
target.style.boxSizing = 'border-box';
target.style.height = target.offsetHeight + 'px';
target.style.overflow = 'hidden';
target.style.height = height;
window.setTimeout( () => {
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
resolve();
}, duration);
}
});
}
window.slideToggle = function(target, duration = 500) {
return new Promise((resolve, reject) => {
if (!(target instanceof HTMLElement)) {
reject('Unable to slide element.', target);
} else {
if (window.getComputedStyle(target).display === 'none') {
slideDown(target, duration);
setTimeout(resolve(), duration);
} else {
slideUp(target, duration);
setTimeout(resolve(), duration);
}
}
});
}
} else {
window.slideUp = function(target, duration = 500){
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.boxSizing = 'border-box';
target.style.height = target.offsetHeight + 'px';
target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
window.setTimeout( () => {
target.style.display = 'none';
target.style.removeProperty('height');
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
}
window.slideDown = function(target, duration = 500){
target.style.removeProperty('display');
let display = window.getComputedStyle(target).display;
if (display === 'none')
display = 'block';
target.style.display = display;
let height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
target.offsetHeight;
target.style.boxSizing = 'border-box';
target.style.transitionProperty = "height, margin, padding";
target.style.transitionDuration = duration + 'ms';
target.style.height = height + 'px';
target.style.removeProperty('padding-top');
target.style.removeProperty('padding-bottom');
target.style.removeProperty('margin-top');
target.style.removeProperty('margin-bottom');
window.setTimeout( () => {
target.style.removeProperty('height');
target.style.removeProperty('overflow');
target.style.removeProperty('transition-duration');
target.style.removeProperty('transition-property');
}, duration);
}
window.slideToggle = function(target, duration = 500){
if (window.getComputedStyle(target).display === 'none') {
slideDown(target, duration);
} else {
slideUp(target, duration);
}
}
}

This gist contains various utility functions.

  • getDomPath()
  • getVisibleChildNodes()
  • preventFocusOnClick()
  • slideUp(), slideDown(), slideToggle()
  • trapFocus(), focusTrap = {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment