{"version":3,"file":"index.modern.mjs","sources":["../src/lib/defaults.js","../src/lib/store.js","../src/lib/constants.js","../src/lib/utils.js","../src/lib/dom.js","../src/lib/factory.js","../src/index.js"],"sourcesContent":["/* istanbul ignore file */\n/*\n * Default settings used by a Modal instance if not otherwise overwritten with config\n *\n * @property onClassName, String, \n * @property toggleSelectorAttribute, String\n * @property callback, Function\n * @property startOpen, Boolean\n * @property delay, Number\n */\nexport default {\n    onClassName: 'is--active',\n    toggleSelectorAttribute: 'data-modal-toggle',\n    callback: false,\n    startOpen: false,\n    delay: 0\n};","export const createStore = () => {\r\n    let state = {};\r\n    \r\n    const getState = () => state;\r\n\r\n    const update = (nextState, effects) => {\r\n        state = nextState ?? state;\r\n        if (!effects) return;\r\n        effects.forEach(effect => effect(state));\r\n    };\r\n    \r\n    return { update, getState };\r\n};","/* istanbul ignore file */\r\nexport const ACCEPTED_TRIGGERS = ['button', 'a'];\r\n\r\nexport const FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled]):not([type=hidden])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex=\"-1\"])'];\r\n\r\nexport const EVENTS = {\r\n    OPEN: 'modal.open',\r\n    CLOSE: 'modal.close'\r\n};","/*\r\n * Converts a passed selector which can be of varying types into an array of DOM Objects\r\n *\r\n * @param selector, Can be a string, Array of DOM nodes, a NodeList or a single DOM element.\r\n */\r\nexport const getSelection = selector => {\r\n    if (typeof selector === 'string') return [].slice.call(document.querySelectorAll(selector));\r\n    if (selector instanceof Array) return selector;\r\n    if (Object.prototype.isPrototypeOf.call(NodeList.prototype, selector)) return [].slice.call(selector);\r\n    if (selector instanceof HTMLElement) return [selector];\r\n    return [];\r\n};\r\n\r\n/*\r\n * Dispatch a custom event to the document\r\n *\r\n * @param type, String, name of the event\r\n * @param store, Object, store of the current instance state\r\n */\r\nexport const broadcast = (type, store) => () => {\r\n    const event = new CustomEvent(type, {\r\n        bubbles: true,\r\n        detail: {\r\n            getState: store.getState\r\n        }\r\n    });\r\n    window.document.dispatchEvent(event);\r\n};","import { FOCUSABLE_ELEMENTS, ACCEPTED_TRIGGERS, EVENTS } from './constants';\r\nimport { broadcast } from './utils';\r\n\r\n/*\r\n * @param node, HTMLElement \r\n * @return child HTMLElement with dialog/alertdialog role\r\n */\r\nexport const findDialog = node => (node.querySelector('[role=dialog]') || node.querySelector('[role=alertdialog]')) || console.warn(`No dialog or alertdialog found in modal node`);\r\n\r\n/*\r\n * @param node, HTMLElement, modal node\r\n * @param settings, Object, instance configuration\r\n * @return Array of HTMLElements that open/close the modal node\r\n */\r\nexport const findToggles = (node, settings) => {\r\n    const toggleSelector = node.getAttribute(settings.toggleSelectorAttribute);\r\n    const composeSelector = classSelector => ACCEPTED_TRIGGERS.map(sel => `${sel}.${classSelector}`).join(', ');\r\n\r\n    const toggles = toggleSelector && [].slice.call(document.querySelectorAll(composeSelector(toggleSelector)));\r\n    if (!toggles) return void console.warn(`Modal cannot be initialised, no modal toggle elements found. Does the modal have a ${settings.toggleSelectorAttribute} attribute that identifies toggle buttons or links?`);\r\n    return toggles;\r\n};\r\n\r\n/* \r\n  * @param node, HTMLElement\r\n  * @return Array of focusable child HTMLElements\r\n */\r\nexport const getFocusableChildren = node => [].slice.call(node.querySelectorAll(FOCUSABLE_ELEMENTS.join(',')));\r\n\r\n/* \r\n * Partially applied function that returns function\r\n *\r\n * @param store, Object, store of the current instance state\r\n * @returns Function, handler for keyDown\r\n *\r\n * @param event, Event\r\n */\r\nexport const keyListener = store => event => {\r\n    const state = store.getState();\r\n    const { isOpen } = state;\r\n    if (isOpen && event.keyCode === 27) {\r\n        event.preventDefault();\r\n        store.update({\r\n            ...store.getState(),\r\n            isOpen: !isOpen\r\n        }, [ change(store) ]);\r\n    }\r\n    if (isOpen && event.keyCode === 9) trapTab(state)(event);\r\n};\r\n\r\n/* \r\n * Partially applied function that returns a function\r\n *\r\n * @param state, Object, current instance state\r\n * @returns Function\r\n *\r\n * @param event, Event\r\n */\r\nconst trapTab = state => event => {\r\n    const focusedIndex = state.focusableChildren.indexOf(document.activeElement);\r\n    if (event.shiftKey && focusedIndex === 0) {\r\n        event.preventDefault();\r\n        state.focusableChildren[state.focusableChildren.length - 1].focus();\r\n    } else if (!event.shiftKey && focusedIndex === state.focusableChildren.length - 1) {\r\n        event.preventDefault();\r\n        state.focusableChildren[0].focus();\r\n    }\r\n};\r\n\r\n/* \r\n * @param state, Object, the current instance state\r\n */\r\nconst toggle = state => {\r\n    state.node[state.isOpen ? 'removeAttribute' : 'setAttribute']('hidden', 'hidden');\r\n    const children = [].slice.call(document.querySelectorAll('body > *'));\r\n    children.forEach(child => child !== state.node && child[state.isOpen ? 'setAttribute' : 'removeAttribute']('aria-hidden', 'true'));\r\n    state.node.classList.toggle(state.settings.onClassName);\r\n    document.documentElement.classList.toggle('is--modal');\r\n};\r\n\r\n/* \r\n * @param store, Object, store of the current instance state\r\n */\r\nconst open = store => () => {\r\n    const state = store.getState();\r\n    if (state.dialog.hasAttribute('aria-hidden')) state.dialog.removeAttribute('aria-hidden'); // past implementations encouraged having aria-hidden on dialog when closed\r\n    const ref = document.body.firstElementChild || null;\r\n    if (ref !== state.node) document.body.insertBefore(state.node, ref);\r\n    document.addEventListener('keydown', state.keyListener);\r\n    toggle(state);\r\n    const focusFn = () => state.focusableChildren.length > 0 && state.focusableChildren[0].focus();\r\n    if (state.settings.delay) window.setTimeout(focusFn, state.settings.delay);\r\n    else focusFn();\r\n    broadcast(EVENTS.OPEN, store)();\r\n};\r\n\r\n/* \r\n * @param store, Object, store of the current instance state\r\n */\r\nconst close = store => () => {\r\n    const state = store.getState();\r\n    document.removeEventListener('keydown', state.keyListener);\r\n    toggle(state);\r\n    state.lastFocused.focus();\r\n    broadcast(EVENTS.CLOSE, store)();\r\n};\r\n\r\n\r\n/* \r\n * Partially applied function that returns a function\r\n *\r\n * @param store, Object, store of the current instance state\r\n * @returns Function\r\n *\r\n */\r\nexport const change = store => state => {\r\n    if (state.isOpen) open(store)();\r\n    else close(store)(state);\r\n    typeof state.settings.callback === 'function' &&  state.settings.callback.call(state);\r\n};\r\n\r\n/*\r\n * Partially applied function that returns a function\r\n * Sets aria attributes and adds eventListener on each modal toggle\r\n *\r\n * @param store, Object, store of the current instance state\r\n * @returns Function\r\n * \r\n * @param node, HTMLElement, modal node\r\n * @param dialog, HTMLElement, dialog/alertdialog node\r\n * @param toggles, Array of HTMLElements, trigger elements\r\n * \r\n */\r\nexport const initUI = store => ({ node, dialog, toggles }) => {\r\n    if (!dialog || !toggles) return;\r\n    node.setAttribute('hidden', 'hidden');\r\n    if (\r\n        !dialog.getAttribute('aria-label') &&\r\n        (!dialog.getAttribute('aria-labelledby') || !document.querySelector(`#${dialog.getAttribute('aria-labelledby')}`))\r\n    ) console.warn(`The modal dialog should have an aria-labelledby attribute that matches the id of an element that contains text, or an aria-label attribute.`);\r\n    if (dialog.getAttribute('role') === 'alertdialog' && (!dialog.getAttribute('aria-describedby') || !document.querySelector(`#${dialog.getAttribute('aria-describedby')}`))) console.warn(`The alertdialog should have an aria-describedby attribute that matches the id of an element that contains text`);\r\n    \r\n    toggles.forEach(tgl => {\r\n        tgl.addEventListener('click', e => {\r\n            e.preventDefault();\r\n            lifecycle(store);\r\n        });\r\n    });\r\n};\r\n\r\n/*\r\n * @param store, Object, store of the current instance state\r\n*/\r\nexport const lifecycle = store => store.update({\r\n    ...store.getState(),\r\n    isOpen: !store.getState().isOpen,\r\n    lastFocused: store.getState().isOpen ? store.getState().lastFocused : document.activeElement\r\n}, [ change(store) ]);","import { createStore } from './store';\r\nimport {\r\n    findDialog,\r\n    findToggles,\r\n    initUI,\r\n    getFocusableChildren,\r\n    keyListener,\r\n    lifecycle\r\n} from './dom';\r\n\r\nexport default ({ node, settings }) => {\r\n    const store = createStore();\r\n    \r\n    store.update({\r\n        settings,\r\n        node,\r\n        dialog: findDialog(node),\r\n        toggles: findToggles(node, settings),\r\n        focusableChildren: getFocusableChildren(node),\r\n        keyListener: keyListener(store),\r\n        lastFocused: false,\r\n        isOpen: false\r\n    }, [\r\n        initUI(store),\r\n        () => settings.startOpen && lifecycle(store)\r\n    ]);\r\n\r\n    return {\r\n        getState: store.getState,\r\n        open() {\r\n            if (store.getState().isOpen) return;\r\n            lifecycle(store);\r\n        },\r\n        close() {\r\n            if (!store.getState().isOpen) return;\r\n            lifecycle(store);\r\n        }\r\n    };\r\n};","import defaults from './lib/defaults';\nimport factory from './lib/factory';\nimport { getSelection } from './lib/utils';\n\n/*\n * Returns an array of objects augmenting DOM elements that match a selector\n *\n * @param selector, Can be a string, Array of DOM nodes, a NodeList or a single DOM element.\n * @params options, Object, to be merged with defaults to become the settings propery of each returned object\n * \n * @return Array of modal Objects, one for each DOM node found\n */\nexport default (selector, options) => {\n    let nodes = getSelection(selector);\n\n    if (nodes.length === 0) return console.warn(`Modal not initialised, no elements found for selector '${selector}'`);\n   \n    //return array of Objects, one for each DOM node found\n    //each Object has a prototype consisting of the node (HTMLElement),\n    //and a settings property composed from defaults, data-attributes on the node, and options passed to init\n    return nodes.map(node => Object.create(factory({\n        settings: { ...defaults, ...node.dataset, ...options },\n        node\n    })));\n};"],"names":["defaults","onClassName","toggleSelectorAttribute","callback","startOpen","delay","ACCEPTED_TRIGGERS","FOCUSABLE_ELEMENTS","broadcast","type","store","event","CustomEvent","bubbles","detail","getState","window","document","dispatchEvent","findDialog","node","querySelector","console","warn","findToggles","settings","toggleSelector","getAttribute","toggles","slice","call","querySelectorAll","classSelector","map","sel","join","getFocusableChildren","keyListener","state","isOpen","keyCode","preventDefault","update","_extends","change","trapTab","focusedIndex","focusableChildren","indexOf","activeElement","shiftKey","length","focus","toggle","forEach","child","classList","documentElement","dialog","hasAttribute","removeAttribute","ref","body","firstElementChild","insertBefore","addEventListener","focusFn","setTimeout","open","removeEventListener","lastFocused","close","initUI","setAttribute","tgl","e","lifecycle","index","selector","options","nodes","Array","Object","prototype","isPrototypeOf","NodeList","HTMLElement","getSelection","create","createStore","nextState","effects","effect","factory","dataset"],"mappings":"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"}