@eli/micromodal
Advanced tools
Comparing version 0.4.6 to 0.4.7
@@ -1,1 +0,322 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).MicroModal=t()}(this,function(){"use strict";return(()=>{const e=["a[href]","area[href]",'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',"select:not([disabled]):not([aria-hidden])","textarea:not([disabled]):not([aria-hidden])","button:not([disabled]):not([aria-hidden])","iframe","object","embed","[contenteditable]",'[tabindex]:not([tabindex^="-"])'];class t{constructor({targetModal:e,triggers:t=[],onShow:o=(()=>{}),onClose:i=(()=>{}),openTrigger:n="data-micromodal-trigger",closeTrigger:s="data-micromodal-close",disableScroll:a=!1,disableFocus:l=!1,awaitCloseAnimation:d=!1,awaitOpenAnimation:r=!1,debugMode:c=!1}){this.modal=document.getElementById(e),this.config={debugMode:c,disableScroll:a,openTrigger:n,closeTrigger:s,onShow:o,onClose:i,awaitCloseAnimation:d,awaitOpenAnimation:r,disableFocus:l},t.length>0&&this.registerTriggers(...t),this.onClick=this.onClick.bind(this),this.onKeydown=this.onKeydown.bind(this)}registerTriggers(...e){e.filter(Boolean).forEach(e=>{e.addEventListener("click",e=>this.showModal(e))})}showModal(){if(this.activeElement=document.activeElement,this.modal.setAttribute("aria-hidden","false"),this.modal.classList.add("is-open"),this.scrollBehaviour("disable"),this.addEventListeners(),this.config.awaitOpenAnimation){const e=()=>{this.modal.removeEventListener("animationend",e,!1),this.setFocusToFirstNode()};this.modal.addEventListener("animationend",e,!1)}else this.setFocusToFirstNode();this.config.onShow(this.modal,this.activeElement)}closeModal(){const e=this.modal;this.modal.setAttribute("aria-hidden","true"),this.removeEventListeners(),this.scrollBehaviour("enable"),this.activeElement&&this.activeElement.focus(),this.config.onClose(this.modal),this.config.awaitCloseAnimation?this.modal.addEventListener("animationend",function t(){e.classList.remove("is-open"),e.removeEventListener("animationend",t,!1)},!1):e.classList.remove("is-open")}closeModalById(e){this.modal=document.getElementById(e),this.modal&&this.closeModal()}scrollBehaviour(e){if(!this.config.disableScroll)return;const t=document.querySelector("body");switch(e){case"enable":Object.assign(t.style,{overflow:"",height:""});break;case"disable":Object.assign(t.style,{overflow:"hidden",height:"100vh"})}}addEventListeners(){this.modal.addEventListener("touchstart",this.onClick),this.modal.addEventListener("click",this.onClick),document.addEventListener("keydown",this.onKeydown)}removeEventListeners(){this.modal.removeEventListener("touchstart",this.onClick),this.modal.removeEventListener("click",this.onClick),document.removeEventListener("keydown",this.onKeydown)}onClick(e){e.target.hasAttribute(this.config.closeTrigger)&&(this.closeModal(),e.preventDefault())}onKeydown(e){27===e.keyCode&&this.closeModal(e),9===e.keyCode&&this.maintainFocus(e)}getFocusableNodes(){const t=this.modal.querySelectorAll(e);return Array(...t)}setFocusToFirstNode(){if(this.config.disableFocus)return;const e=this.getFocusableNodes();e.length&&e[0].focus()}maintainFocus(e){const t=this.getFocusableNodes();if(this.modal.contains(document.activeElement)){const o=t.indexOf(document.activeElement);e.shiftKey&&0===o&&(t[t.length-1].focus(),e.preventDefault()),e.shiftKey||o!==t.length-1||(t[0].focus(),e.preventDefault())}else t[0].focus()}}let o=null;const i=e=>{if(!document.getElementById(e))return console.warn(`MicroModal: ❗Seems like you have missed %c'${e}'`,"background-color: #f8f9fa;color: #50596c;font-weight: bold;","ID somewhere in your code. Refer example below to resolve it."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",`<div class="modal" id="${e}"></div>`),!1},n=(e,t)=>((e=>{if(e.length<=0)console.warn("MicroModal: ❗Please specify at least one %c'micromodal-trigger'","background-color: #f8f9fa;color: #50596c;font-weight: bold;","data attribute."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",'<a href="#" data-micromodal-trigger="my-modal"></a>')})(e),!t||(Object.keys(t).forEach(e=>{i(e)}),!0));return{init:e=>{const i=Object.assign({},{openTrigger:"data-micromodal-trigger"},e),s=[...document.querySelectorAll(`[${i.openTrigger}]`)],a=((e,t)=>{const o=[];return e.forEach(e=>{const i=e.attributes[t].value;void 0===o[i]&&(o[i]=[]),o[i].push(e)}),o})(s,i.openTrigger);!0===i.debugMode&&!1===n(s,a)||Object.keys(a).forEach(e=>{let n=a[e];i.targetModal=e,i.triggers=[...n],o=new t(i)})},show:(e,n)=>{const s=n||{};s.targetModal=e,!0===s.debugMode&&!1===i(e)||(o=new t(s)).showModal()},close:e=>{e?o.closeModalById(e):o.closeModal()}}})()}); | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global = global || self, global.MicroModal = factory()); | ||
}(this, function () { 'use strict'; | ||
const MicroModal = (() => { | ||
const FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', 'select:not([disabled]):not([aria-hidden])', 'textarea:not([disabled]):not([aria-hidden])', 'button:not([disabled]):not([aria-hidden])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])']; | ||
class Modal { | ||
constructor({ | ||
targetModal, | ||
triggers = [], | ||
onShow = () => {}, | ||
onClose = () => {}, | ||
openTrigger = 'data-micromodal-trigger', | ||
closeTrigger = 'data-micromodal-close', | ||
disableScroll = false, | ||
disableFocus = false, | ||
awaitCloseAnimation = false, | ||
awaitOpenAnimation = false, | ||
debugMode = false | ||
}) { | ||
// Save a reference of the modal | ||
this.modal = document.getElementById(targetModal); // Save a reference to the passed config | ||
this.config = { | ||
debugMode, | ||
disableScroll, | ||
openTrigger, | ||
closeTrigger, | ||
onShow, | ||
onClose, | ||
awaitCloseAnimation, | ||
awaitOpenAnimation, | ||
disableFocus // Register click events only if pre binding eventListeners | ||
}; | ||
if (triggers.length > 0) this.registerTriggers(...triggers); // pre bind functions for event listeners | ||
this.onClick = this.onClick.bind(this); | ||
this.onKeydown = this.onKeydown.bind(this); | ||
} | ||
/** | ||
* Loops through all openTriggers and binds click event | ||
* @param {array} triggers [Array of node elements] | ||
* @return {void} | ||
*/ | ||
registerTriggers(...triggers) { | ||
triggers.filter(Boolean).forEach(trigger => { | ||
trigger.addEventListener('click', event => this.showModal(event)); | ||
}); | ||
} | ||
showModal() { | ||
this.activeElement = document.activeElement; | ||
this.modal.setAttribute('aria-hidden', 'false'); | ||
this.modal.classList.add('is-open'); | ||
this.scrollBehaviour('disable'); | ||
this.addEventListeners(); | ||
if (this.config.awaitOpenAnimation) { | ||
const handler = () => { | ||
this.modal.removeEventListener('animationend', handler, false); | ||
this.setFocusToFirstNode(); | ||
}; | ||
this.modal.addEventListener('animationend', handler, false); | ||
} else { | ||
this.setFocusToFirstNode(); | ||
} | ||
this.config.onShow(this.modal, this.activeElement); | ||
} | ||
closeModal() { | ||
const modal = this.modal; | ||
this.modal.setAttribute('aria-hidden', 'true'); | ||
this.removeEventListeners(); | ||
this.scrollBehaviour('enable'); | ||
if (this.activeElement) { | ||
this.activeElement.focus(); | ||
} | ||
this.config.onClose(this.modal); | ||
if (this.config.awaitCloseAnimation) { | ||
this.modal.addEventListener('animationend', function handler() { | ||
modal.classList.remove('is-open'); | ||
modal.removeEventListener('animationend', handler, false); | ||
}, false); | ||
} else { | ||
modal.classList.remove('is-open'); | ||
} | ||
} | ||
closeModalById(targetModal) { | ||
this.modal = document.getElementById(targetModal); | ||
if (this.modal) this.closeModal(); | ||
} | ||
scrollBehaviour(toggle) { | ||
if (!this.config.disableScroll) return; | ||
const body = document.querySelector('body'); | ||
switch (toggle) { | ||
case 'enable': | ||
Object.assign(body.style, { | ||
overflow: '', | ||
height: '' | ||
}); | ||
break; | ||
case 'disable': | ||
Object.assign(body.style, { | ||
overflow: 'hidden', | ||
height: '100vh' | ||
}); | ||
break; | ||
default: | ||
} | ||
} | ||
addEventListeners() { | ||
this.modal.addEventListener('touchstart', this.onClick); | ||
this.modal.addEventListener('click', this.onClick); | ||
document.addEventListener('keydown', this.onKeydown); | ||
} | ||
removeEventListeners() { | ||
this.modal.removeEventListener('touchstart', this.onClick); | ||
this.modal.removeEventListener('click', this.onClick); | ||
document.removeEventListener('keydown', this.onKeydown); | ||
} | ||
onClick(event) { | ||
if (event.target.hasAttribute(this.config.closeTrigger)) { | ||
this.closeModal(); | ||
event.preventDefault(); | ||
} | ||
} | ||
onKeydown(event) { | ||
if (event.keyCode === 27) this.closeModal(event); | ||
if (event.keyCode === 9) this.maintainFocus(event); | ||
} | ||
getFocusableNodes() { | ||
const nodes = this.modal.querySelectorAll(FOCUSABLE_ELEMENTS); | ||
return Array(...nodes); | ||
} | ||
setFocusToFirstNode() { | ||
if (this.config.disableFocus) return; | ||
const focusableNodes = this.getFocusableNodes(); | ||
if (focusableNodes.length) focusableNodes[0].focus(); | ||
} | ||
maintainFocus(event) { | ||
const focusableNodes = this.getFocusableNodes(); // if disableFocus is true | ||
if (!this.modal.contains(document.activeElement)) { | ||
focusableNodes[0].focus(); | ||
} else { | ||
const focusedItemIndex = focusableNodes.indexOf(document.activeElement); | ||
if (event.shiftKey && focusedItemIndex === 0) { | ||
focusableNodes[focusableNodes.length - 1].focus(); | ||
event.preventDefault(); | ||
} | ||
if (!event.shiftKey && focusedItemIndex === focusableNodes.length - 1) { | ||
focusableNodes[0].focus(); | ||
event.preventDefault(); | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* Modal prototype ends. | ||
* Here on code is responsible for detecting and | ||
* auto binding event handlers on modal triggers | ||
*/ | ||
// Keep a reference to the opened modal | ||
let activeModal = null; | ||
/** | ||
* Generates an associative array of modals and it's | ||
* respective triggers | ||
* @param {array} triggers An array of all triggers | ||
* @param {string} triggerAttr The data-attribute which triggers the module | ||
* @return {array} | ||
*/ | ||
const generateTriggerMap = (triggers, triggerAttr) => { | ||
const triggerMap = []; | ||
triggers.forEach(trigger => { | ||
const targetModal = trigger.attributes[triggerAttr].value; | ||
if (triggerMap[targetModal] === undefined) triggerMap[targetModal] = []; | ||
triggerMap[targetModal].push(trigger); | ||
}); | ||
return triggerMap; | ||
}; | ||
/** | ||
* Validates whether a modal of the given id exists | ||
* in the DOM | ||
* @param {number} id The id of the modal | ||
* @return {boolean} | ||
*/ | ||
const validateModalPresence = id => { | ||
if (!document.getElementById(id)) { | ||
console.warn(`MicroModal: \u2757Seems like you have missed %c'${id}'`, 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', 'ID somewhere in your code. Refer example below to resolve it.'); | ||
console.warn(`%cExample:`, 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', `<div class="modal" id="${id}"></div>`); | ||
return false; | ||
} | ||
}; | ||
/** | ||
* Validates if there are modal triggers present | ||
* in the DOM | ||
* @param {array} triggers An array of data-triggers | ||
* @return {boolean} | ||
*/ | ||
const validateTriggerPresence = triggers => { | ||
if (triggers.length <= 0) { | ||
console.warn(`MicroModal: \u2757Please specify at least one %c'micromodal-trigger'`, 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', 'data attribute.'); | ||
console.warn(`%cExample:`, 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', `<a href="#" data-micromodal-trigger="my-modal"></a>`); | ||
return false; | ||
} | ||
}; | ||
/** | ||
* Checks if triggers and their corresponding modals | ||
* are present in the DOM | ||
* @param {array} triggers Array of DOM nodes which have data-triggers | ||
* @param {array} triggerMap Associative array of modals and their triggers | ||
* @return {boolean} | ||
*/ | ||
const validateArgs = (triggers, triggerMap) => { | ||
validateTriggerPresence(triggers); | ||
if (!triggerMap) return true; | ||
Object.keys(triggerMap).forEach(id => { | ||
validateModalPresence(id); | ||
}); | ||
return true; | ||
}; | ||
/** | ||
* Binds click handlers to all modal triggers | ||
* @param {object} config [description] | ||
* @return void | ||
*/ | ||
const init = config => { | ||
// Create an config object with default openTrigger | ||
const options = Object.assign({}, { | ||
openTrigger: 'data-micromodal-trigger' | ||
}, config); // Collects all the nodes with the trigger | ||
const triggers = [...document.querySelectorAll(`[${options.openTrigger}]`)]; // Makes a mappings of modals with their trigger nodes | ||
const triggerMap = generateTriggerMap(triggers, options.openTrigger); // Checks if modals and triggers exist in dom | ||
if (options.debugMode === true && validateArgs(triggers, triggerMap) === false) return; // For every target modal creates a new instance | ||
Object.keys(triggerMap).forEach(key => { | ||
let value = triggerMap[key]; | ||
options.targetModal = key; | ||
options.triggers = [...value]; | ||
activeModal = new Modal(options); // eslint-disable-line no-new | ||
}); | ||
}; | ||
/** | ||
* Shows a particular modal | ||
* @param {string} targetModal [The id of the modal to display] | ||
* @param {object} config [The configuration object to pass] | ||
* @return {void} | ||
*/ | ||
const show = (targetModal, config) => { | ||
const options = config || {}; | ||
options.targetModal = targetModal; // Checks if modals and triggers exist in dom | ||
if (options.debugMode === true && validateModalPresence(targetModal) === false) return; // stores reference to active modal | ||
activeModal = new Modal(options); // eslint-disable-line no-new | ||
activeModal.showModal(); | ||
}; | ||
/** | ||
* Closes the active modal | ||
* @param {string} targetModal [The id of the modal to close] | ||
* @return {void} | ||
*/ | ||
const close = targetModal => { | ||
targetModal ? activeModal.closeModalById(targetModal) : activeModal.closeModal(); | ||
}; | ||
return { | ||
init, | ||
show, | ||
close | ||
}; | ||
})(); | ||
return MicroModal; | ||
})); |
{ | ||
"name": "@eli/micromodal", | ||
"version": "0.4.6", | ||
"version": "0.4.7", | ||
"description": "Tiny javascript library for creating accessible modal dialogs", | ||
@@ -9,4 +9,3 @@ "main": "dist/micromodal.js", | ||
"files": [ | ||
"/dist", | ||
"./" | ||
"/dist" | ||
], | ||
@@ -13,0 +12,0 @@ "jsdelivr": "dist/micromodal.min.js", |
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
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
0
0
31994
4
792