{"version":3,"file":"components-modal.js","mappings":"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","sources":["webpack://silverstripe-base/./node_modules/a11y-dialog/dist/a11y-dialog.esm.js","webpack://silverstripe-base/./themes/app/src/scripts/components/modal.js"],"sourcesContent":["const not = {\n inert: ':not([inert]):not([inert] *)',\n negTabIndex: ':not([tabindex^=\"-\"])',\n disabled: ':not(:disabled)',\n};\n\nvar focusableSelectors = [\n `a[href]${not.inert}${not.negTabIndex}`,\n `area[href]${not.inert}${not.negTabIndex}`,\n `input:not([type=\"hidden\"]):not([type=\"radio\"])${not.inert}${not.negTabIndex}${not.disabled}`,\n `input[type=\"radio\"]${not.inert}${not.negTabIndex}${not.disabled}`,\n `select${not.inert}${not.negTabIndex}${not.disabled}`,\n `textarea${not.inert}${not.negTabIndex}${not.disabled}`,\n `button${not.inert}${not.negTabIndex}${not.disabled}`,\n `details${not.inert} > summary:first-of-type${not.negTabIndex}`,\n // Discard until Firefox supports `:has()`\n // See: https://github.com/KittyGiraudel/focusable-selectors/issues/12\n // `details:not(:has(> summary))${not.inert}${not.negTabIndex}`,\n `iframe${not.inert}${not.negTabIndex}`,\n `audio[controls]${not.inert}${not.negTabIndex}`,\n `video[controls]${not.inert}${not.negTabIndex}`,\n `[contenteditable]${not.inert}${not.negTabIndex}`,\n `[tabindex]${not.inert}${not.negTabIndex}`,\n];\n\n/**\n * Set the focus to the first element with `autofocus` with the element or the\n * element itself.\n */\nfunction moveFocusToDialog(el) {\n const focused = (el.querySelector('[autofocus]') || el);\n focused.focus();\n}\n/**\n * Get the first and last focusable elements in a given tree.\n */\nfunction getFocusableEdges(el) {\n // Check for a focusable element within the subtree of `el`.\n const first = findFocusableElement(el, true);\n // Only if we find the first element do we need to look for the last one. If\n // there’s no last element, we set `last` as a reference to `first` so that\n // the returned array is always of length 2.\n const last = first ? findFocusableElement(el, false) || first : null;\n return [first, last];\n}\n/**\n * Find the first focusable element inside the given node if `forward` is truthy\n * or the last focusable element otherwise.\n */\nfunction findFocusableElement(node, forward) {\n // If we’re walking forward, check if this node is focusable, and return it\n // immediately if it is.\n if (forward && isFocusable(node))\n return node;\n // We should only search the subtree of this node if it can have focusable\n // children.\n if (canHaveFocusableChildren(node)) {\n // Start walking the DOM tree, looking for focusable elements.\n // Case 1: If this node has a shadow root, search it recursively.\n if (node.shadowRoot) {\n // Descend into this subtree.\n let next = getNextChildEl(node.shadowRoot, forward);\n // Traverse siblings, searching the subtree of each one\n // for focusable elements.\n while (next) {\n const focusableEl = findFocusableElement(next, forward);\n if (focusableEl)\n return focusableEl;\n next = getNextSiblingEl(next, forward);\n }\n }\n // Case 2: If this node is a slot for a Custom Element, search its assigned\n // nodes recursively.\n else if (node.localName === 'slot') {\n const assignedElements = node.assignedElements({\n flatten: true,\n });\n if (!forward)\n assignedElements.reverse();\n for (const assignedElement of assignedElements) {\n const focusableEl = findFocusableElement(assignedElement, forward);\n if (focusableEl)\n return focusableEl;\n }\n }\n // Case 3: this is a regular Light DOM node. Search its subtree.\n else {\n // Descend into this subtree.\n let next = getNextChildEl(node, forward);\n // Traverse siblings, searching the subtree of each one\n // for focusable elements.\n while (next) {\n const focusableEl = findFocusableElement(next, forward);\n if (focusableEl)\n return focusableEl;\n next = getNextSiblingEl(next, forward);\n }\n }\n }\n // If we’re walking backward, we want to check the node’s entire subtree\n // before checking the node itself. If this node is focusable, return it.\n if (!forward && isFocusable(node))\n return node;\n return null;\n}\nfunction getNextChildEl(node, forward) {\n return forward ? node.firstElementChild : node.lastElementChild;\n}\nfunction getNextSiblingEl(el, forward) {\n return forward ? el.nextElementSibling : el.previousElementSibling;\n}\n/**\n * Determine if an element is hidden from the user.\n */\nconst isHidden = (el) => {\n // Browsers hide all non-<summary> descendants of closed <details> elements\n // from user interaction, but those non-<summary> elements may still match our\n // focusable-selectors and may still have dimensions, so we need a special\n // case to ignore them.\n if (el.matches('details:not([open]) *') &&\n !el.matches('details>summary:first-of-type'))\n return true;\n // If this element has no painted dimensions, it's hidden.\n return !(el.offsetWidth || el.offsetHeight || el.getClientRects().length);\n};\n/**\n * Determine if an element is focusable and has user-visible painted dimensions.\n */\nconst isFocusable = (el) => {\n // A shadow host that delegates focus will never directly receive focus,\n // even with `tabindex=0`. Consider our <fancy-button> custom element, which\n // delegates focus to its shadow button:\n //\n // <fancy-button tabindex=\"0\">\n // #shadow-root\n // <button><slot></slot></button>\n // </fancy-button>\n //\n // The browser acts as as if there is only one focusable element – the shadow\n // button. Our library should behave the same way.\n if (el.shadowRoot?.delegatesFocus)\n return false;\n return el.matches(focusableSelectors.join(',')) && !isHidden(el);\n};\n/**\n * Determine if an element can have focusable children. Useful for bailing out\n * early when walking the DOM tree.\n * @example\n * This div is inert, so none of its children can be focused, even though they\n * meet our criteria for what is focusable. Once we check the div, we can skip\n * the rest of the subtree.\n * ```html\n * <div inert>\n * <button>Button</button>\n * <a href=\"#\">Link</a>\n * </div>\n * ```\n */\nfunction canHaveFocusableChildren(el) {\n // The browser will never send focus into a Shadow DOM if the host element\n // has a negative tabindex. This applies to both slotted Light DOM Shadow DOM\n // children\n if (el.shadowRoot && el.getAttribute('tabindex') === '-1')\n return false;\n // Elemments matching this selector are either hidden entirely from the user,\n // or are visible but unavailable for interaction. Their descentants can never\n // receive focus.\n return !el.matches(':disabled,[hidden],[inert]');\n}\n/**\n * Get the active element, accounting for Shadow DOM subtrees.\n * @author Cory LaViska\n * @see: https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/\n */\nfunction getActiveElement(root = document) {\n const activeEl = root.activeElement;\n if (!activeEl)\n return null;\n // If there’s a shadow root, recursively find the active element within it.\n // If the recursive call returns null, return the active element\n // of the top-level Document.\n if (activeEl.shadowRoot)\n return getActiveElement(activeEl.shadowRoot) || document.activeElement;\n // If not, we can just return the active element\n return activeEl;\n}\n/**\n * Trap the focus inside the given element\n */\nfunction trapTabKey(el, event) {\n const [firstFocusableChild, lastFocusableChild] = getFocusableEdges(el);\n // If there are no focusable children in the dialog, prevent the user from\n // tabbing out of it\n if (!firstFocusableChild)\n return event.preventDefault();\n const activeElement = getActiveElement();\n // If the SHIFT key is pressed while tabbing (moving backwards) and the\n // currently focused item is the first one, move the focus to the last\n // focusable item from the dialog element\n if (event.shiftKey && activeElement === firstFocusableChild) {\n // @ts-ignore: we know that `lastFocusableChild` is not null here\n lastFocusableChild.focus();\n event.preventDefault();\n }\n // If the SHIFT key is not pressed (moving forwards) and the currently focused\n // item is the last one, move the focus to the first focusable item from the\n // dialog element\n else if (!event.shiftKey && activeElement === lastFocusableChild) {\n firstFocusableChild.focus();\n event.preventDefault();\n }\n}\n\nclass A11yDialog {\n $el;\n id;\n previouslyFocused;\n shown;\n constructor(element) {\n this.$el = element;\n this.id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;\n this.previouslyFocused = null;\n this.shown = false;\n this.maintainFocus = this.maintainFocus.bind(this);\n this.bindKeypress = this.bindKeypress.bind(this);\n this.handleTriggerClicks = this.handleTriggerClicks.bind(this);\n this.show = this.show.bind(this);\n this.hide = this.hide.bind(this);\n this.$el.setAttribute('aria-hidden', 'true');\n this.$el.setAttribute('aria-modal', 'true');\n this.$el.setAttribute('tabindex', '-1');\n if (!this.$el.hasAttribute('role')) {\n this.$el.setAttribute('role', 'dialog');\n }\n document.addEventListener('click', this.handleTriggerClicks, true);\n }\n /**\n * Destroy the current instance (after making sure the dialog has been hidden)\n * and remove all associated listeners from dialog openers and closers\n */\n destroy() {\n // Hide the dialog to avoid destroying an open instance\n this.hide();\n // Remove the click event delegates for our openers and closers\n document.removeEventListener('click', this.handleTriggerClicks, true);\n // Clone and replace the dialog element to prevent memory leaks caused by\n // event listeners that the author might not have cleaned up.\n this.$el.replaceWith(this.$el.cloneNode(true));\n // Dispatch a `destroy` event\n this.fire('destroy');\n return this;\n }\n /**\n * Show the dialog element, trap the current focus within it, listen for some\n * specific key presses and fire all registered callbacks for `show` event\n */\n show(event) {\n // If the dialog is already open, abort\n if (this.shown)\n return this;\n // Keep a reference to the currently focused element to be able to restore\n // it later\n this.shown = true;\n this.$el.removeAttribute('aria-hidden');\n this.previouslyFocused = getActiveElement();\n // Due to a long lasting bug in Safari, clicking an interactive element\n // (like a <button>) does *not* move the focus to that element, which means\n // `document.activeElement` is whatever element is currently focused (like\n // an <input>), or the <body> element otherwise. We can work around that\n // problem by checking whether the focused element is the <body>, and if it,\n // store the click event target.\n // See: https://bugs.webkit.org/show_bug.cgi?id=22261\n if (this.previouslyFocused?.tagName === 'BODY' && event?.target) {\n this.previouslyFocused = event.target;\n }\n // Set the focus to the dialog element\n // See: https://github.com/KittyGiraudel/a11y-dialog/pull/583\n if (event?.type === 'focus') {\n this.maintainFocus(event);\n }\n else {\n moveFocusToDialog(this.$el);\n }\n // Bind a focus event listener to the body element to make sure the focus\n // stays trapped inside the dialog while open, and start listening for some\n // specific key presses (TAB and ESC)\n document.body.addEventListener('focus', this.maintainFocus, true);\n this.$el.addEventListener('keydown', this.bindKeypress, true);\n // Dispatch a `show` event\n this.fire('show', event);\n return this;\n }\n /**\n * Hide the dialog element, restore the focus to the previously active\n * element, stop listening for some specific key presses and fire all\n * registered callbacks for `hide` event\n */\n hide(event) {\n // If the dialog is already closed, abort\n if (!this.shown)\n return this;\n this.shown = false;\n this.$el.setAttribute('aria-hidden', 'true');\n this.previouslyFocused?.focus?.();\n // Remove the focus event listener to the body element and stop listening\n // for specific key presses\n document.body.removeEventListener('focus', this.maintainFocus, true);\n this.$el.removeEventListener('keydown', this.bindKeypress, true);\n // Dispatch a `hide` event\n this.fire('hide', event);\n return this;\n }\n /**\n * Register a new callback for the given event type\n */\n on(type, handler, options) {\n this.$el.addEventListener(type, handler, options);\n return this;\n }\n /**\n * Unregister an existing callback for the given event type\n */\n off(type, handler, options) {\n this.$el.removeEventListener(type, handler, options);\n return this;\n }\n /**\n * Dispatch a custom event from the DOM element associated with this dialog.\n * This allows authors to listen for and respond to the events in their own\n * code\n */\n fire(type, event) {\n this.$el.dispatchEvent(new CustomEvent(type, {\n detail: event,\n cancelable: true,\n }));\n }\n /**\n * Add a delegated event listener for when elememts that open or close the\n * dialog are clicked, and call `show` or `hide`, respectively\n */\n handleTriggerClicks(event) {\n const target = event.target;\n // We use `.closest(..)` and not `.matches(..)` here so that clicking\n // an element nested within a dialog opener does cause the dialog to open\n if (target.closest(`[data-a11y-dialog-show=\"${this.id}\"]`)) {\n this.show(event);\n }\n if (target.closest(`[data-a11y-dialog-hide=\"${this.id}\"]`) ||\n (target.closest('[data-a11y-dialog-hide]') &&\n target.closest('[aria-modal=\"true\"]') === this.$el)) {\n this.hide(event);\n }\n }\n /**\n * Private event handler used when listening to some specific key presses\n * (namely ESC and TAB)\n */\n bindKeypress(event) {\n // This is an escape hatch in case there are nested open dialogs, so that\n // only the top most dialog gets interacted with\n if (document.activeElement?.closest('[aria-modal=\"true\"]') !== this.$el) {\n return;\n }\n let hasOpenPopover = false;\n try {\n hasOpenPopover = !!this.$el.querySelector('[popover]:not([popover=\"manual\"]):popover-open');\n }\n catch {\n // Run that DOM query in a try/catch because not all browsers support the\n // `:popover-open` selector, which would cause the whole expression to\n // fail\n // See: https://caniuse.com/mdn-css_selectors_popover-open\n // See: https://github.com/KittyGiraudel/a11y-dialog/pull/578#discussion_r1343215149\n }\n // If the dialog is shown and the ESC key is pressed, prevent any further\n // effects from the ESC key and hide the dialog, unless:\n // - its role is `alertdialog`, which means it should be modal\n // - or it contains an open popover, in which case ESC should close it\n if (event.key === 'Escape' &&\n this.$el.getAttribute('role') !== 'alertdialog' &&\n !hasOpenPopover) {\n event.preventDefault();\n this.hide(event);\n }\n // If the dialog is shown and the TAB key is pressed, make sure the focus\n // stays trapped within the dialog element\n if (event.key === 'Tab') {\n trapTabKey(this.$el, event);\n }\n }\n /**\n * If the dialog is shown and the focus is not within a dialog element (either\n * this one or another one in case of nested dialogs) or attribute, move it\n * back to the dialog container\n * See: https://github.com/KittyGiraudel/a11y-dialog/issues/177\n */\n maintainFocus(event) {\n const target = event.target;\n if (!target.closest('[aria-modal=\"true\"], [data-a11y-dialog-ignore-focus-trap]')) {\n moveFocusToDialog(this.$el);\n }\n }\n}\n\nfunction instantiateDialogs() {\n for (const el of document.querySelectorAll('[data-a11y-dialog]')) {\n new A11yDialog(el);\n }\n}\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', instantiateDialogs);\n }\n else {\n instantiateDialogs();\n }\n}\n\nexport { A11yDialog as default };\n","import \"@styles/components/modal.css\";\nimport ajax from \"@common/ajax\";\nimport { on, off, fire } from \"delegated-events\";\nimport A11yDialog from \"a11y-dialog\";\n\nconst generateTemplate = (id, useDefaultClose = true, useCustomClose = false, title = false) => {\n const role = useDefaultClose ? \"dialog\" : \"alertdialog\";\n const labeledBy = title ? 'aria-labelledby=\"modal-title\"' : \"\";\n\n const html = `\n <div id=\"${id}\" aria-hidden=\"true\" class=\"modal-wrapper\" ${labeledBy} role=\"${role}\">\n <div class=\"modal-overlay\" ${useDefaultClose && \"data-a11y-dialog-hide\"}></div>\n\n ${\n useCustomClose === false\n ? '<button type=\"button\" class=\"modal-close\" data-a11y-dialog-hide aria-label=\"Close dialog\"> <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"> <path fill=\"currentColor\" d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" /> </svg> </button>'\n : \"\"\n }\n\n <div class=\"modal-container\">\n <svg class=\"site-loader site-loader--centered site-loader--light\" viewBox=\"0 0 50 50\" aria-hidden=\"true\">\n <circle class=\"path\" cx=\"25\" cy=\"25\" r=\"20\" fill=\"none\" stroke-width=\"5\"></circle>\n </svg>\n </div>\n </div>\n `;\n\n return html;\n};\n\nconst populateModal = async (opts = {}, modalEl) => {\n const type = opts.type;\n const title = opts.title ?? \"\";\n const url = opts.url;\n\n let modalContent = \"\";\n\n if (type === \"video\") {\n modalContent = `\n <div class=\"mx-auto w-full max-w-5xl modal-content\" role=\"document\">\n <h1 id=\"modal-title\" class=\"sr-only\">Watch ${title}</h1>\n <div class=\"relative aspect-video modal-body\">\n <iframe title=\"Watch ${title}\" style=\"border: none\" src=\"${url}?autoplay=1&rel=0&enablejsapi=1\" allowfullscreen class=\"absolute inset-0 w-full h-full\"></iframe>\n </div>\n </div>\n `;\n\n modalEl.querySelector(\".modal-container\").innerHTML = modalContent;\n }\n\n if (type === \"content\") {\n modalContent = await ajax.get(url).text();\n\n modalEl.querySelector(\".modal-container\").innerHTML = modalContent;\n }\n\n if (type === \"subscribe\") {\n modalContent = await ajax.get(url).text();\n\n modalEl.querySelector(\".modal-container\").innerHTML = modalContent;\n }\n\n fire(document.documentElement, \"modal:populate\", { modalEl });\n};\n\nconst handleModal = (e) => {\n e.preventDefault();\n\n const triggerEl = e.target.closest(\"[data-modal]\");\n\n openModal({\n type: triggerEl.dataset.modal,\n title: triggerEl.dataset.modalTitle,\n url: triggerEl.href,\n updateUrl: triggerEl.dataset.modalUpdateUrl ?? false,\n useDefaultClose: triggerEl.dataset.modalDisableDefaultClose === undefined,\n useCustomClose: triggerEl.dataset.modalUseCustomClose !== undefined,\n });\n\n return false;\n};\n\nconst handleModalReplace = async (e) => {\n e.preventDefault();\n\n const triggerEl = e.target.closest(\"[data-modal-replace]\");\n const url = triggerEl.href;\n\n const currentModal = document.querySelector(\".modal-wrapper\");\n\n const modalContent = await ajax.get(url).text();\n\n currentModal.querySelector(\".modal-container\").innerHTML = modalContent;\n};\n\nconst openModal = (opts = {}) => {\n const type = opts.type;\n const title = opts.title ?? false;\n let useCustomClose = opts.useCustomClose ?? false;\n let useDefaultClose = opts.useDefaultClose ?? true;\n\n if (type === \"video\") {\n useDefaultClose = false;\n }\n\n if (type === \"subscribe\") {\n useDefaultClose = false;\n useCustomClose = true;\n }\n\n const id = `modal-${Math.floor(Math.random() * Date.now())}`;\n const template = generateTemplate(id, useDefaultClose, useCustomClose, title);\n const node = document.createRange().createContextualFragment(template);\n\n document.body.append(node);\n\n const modalEl = document.getElementById(id);\n const modal = new A11yDialog(modalEl);\n\n const hideModal = () => {\n modal.hide();\n };\n\n if (useCustomClose) {\n on(\"click\", \".modal-custom-close\", hideModal);\n }\n\n const currentUrl = opts.defaultUrl ?? window.location.href;\n\n modal.on(\"show\", () => {\n document.documentElement.classList.add(\"has-modal-active\");\n\n if (opts.updateUrl !== false) {\n window.history.replaceState({}, \"\", opts.updateUrl);\n }\n });\n\n modal.on(\"hide\", () => {\n document.documentElement.classList.remove(\"has-modal-active\");\n\n modal.destroy();\n\n if (opts.updateUrl !== false) {\n window.history.replaceState({}, \"\", currentUrl);\n }\n\n if (useCustomClose) {\n off(\"click\", \".modal-custom-close\", hideModal);\n }\n });\n\n modal.on(\"destroy\", (e) => {\n document.querySelector(\".modal-wrapper\").remove();\n e.target.remove();\n });\n\n modal.show();\n\n populateModal(\n {\n type: type,\n url: opts.url ?? false,\n updateUrl: opts.updateUrl,\n defaultUrl: opts.defaultUrl,\n },\n modalEl,\n );\n};\n\nexport default {\n init: () => {\n const clickArgs = [\"click\", \"[data-modal]\", handleModal];\n const replaceClickArgs = [\"click\", \"[data-modal-replace]\", handleModalReplace];\n\n off(...clickArgs);\n on(...clickArgs);\n\n off(...replaceClickArgs);\n on(...replaceClickArgs);\n\n // If we have video modals, load the YouTube API\n if (document.querySelectorAll('[data-modal=\"video\"]').length) {\n const tag = document.createElement(\"script\");\n tag.src = \"https://www.youtube.com/iframe_api\";\n\n const firstScriptTag = document.getElementsByTagName(\"script\")[0];\n\n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n }\n },\n openModal,\n};\n"],"names":["not","focusableSelectors","moveFocusToDialog","el","querySelector","focus","findFocusableElement","node","forward","isFocusable","shadowRoot","getAttribute","matches","next","getNextChildEl","focusableEl","getNextSiblingEl","localName","assignedElements","flatten","reverse","assignedElement","firstElementChild","lastElementChild","nextElementSibling","previousElementSibling","delegatesFocus","join","offsetWidth","offsetHeight","getClientRects","length","isHidden","getActiveElement","root","document","activeEl","activeElement","A11yDialog","$el","id","previouslyFocused","shown","constructor","element","this","maintainFocus","bind","bindKeypress","handleTriggerClicks","show","hide","setAttribute","hasAttribute","addEventListener","destroy","removeEventListener","replaceWith","cloneNode","fire","event","removeAttribute","tagName","target","type","body","on","handler","options","off","dispatchEvent","CustomEvent","detail","cancelable","closest","hasOpenPopover","key","preventDefault","firstFocusableChild","lastFocusableChild","first","getFocusableEdges","shiftKey","trapTabKey","instantiateDialogs","querySelectorAll","readyState","handleModal","e","triggerEl","openModal","dataset","modal","title","modalTitle","url","href","updateUrl","modalUpdateUrl","useDefaultClose","undefined","modalDisableDefaultClose","useCustomClose","modalUseCustomClose","handleModalReplace","async","currentModal","modalContent","ajax","get","text","innerHTML","opts","Math","floor","random","Date","now","template","generateTemplate","createRange","createContextualFragment","append","modalEl","getElementById","hideModal","currentUrl","defaultUrl","window","location","documentElement","classList","add","history","replaceState","remove","populateModal","init","clickArgs","replaceClickArgs","tag","createElement","src","firstScriptTag","getElementsByTagName","parentNode","insertBefore"],"sourceRoot":""}