Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@eli/micromodal

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@eli/micromodal - npm Package Compare versions

Comparing version 0.4.6 to 0.4.7

323

dist/micromodal.min.js

@@ -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;
}));

5

package.json
{
"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",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc