(function () { var backdrop = null; var modal = null; var titleEl = null; var bodyEl = null; var cancelBtn = null; var confirmBtn = null; var footerEl = null; var prevFocus = null; var currentOpts = null; var onKeydown = null; function build() { backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop'; modal = document.createElement('div'); modal.className = 'modal panel panel-floating'; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); var header = document.createElement('div'); header.className = 'panel-header'; titleEl = document.createElement('span'); header.appendChild(titleEl); bodyEl = document.createElement('div'); bodyEl.className = 'panel-body'; footerEl = document.createElement('div'); footerEl.className = 'panel-footer'; cancelBtn = document.createElement('button'); cancelBtn.type = 'button'; confirmBtn = document.createElement('button'); confirmBtn.type = 'button'; modal.appendChild(header); modal.appendChild(bodyEl); modal.appendChild(footerEl); backdrop.appendChild(modal); backdrop.addEventListener('mousedown', function (e) { if (e.target === backdrop) close(); }); wireDrag(header); cancelBtn.addEventListener('click', close); confirmBtn.addEventListener('click', function () { if (confirmBtn.disabled) return; if (currentOpts && currentOpts.confirm && currentOpts.confirm.onConfirm) { currentOpts.confirm.onConfirm(); } }); } // wireDrag makes the modal draggable by `handle`. Dragging switches the // modal to fixed positioning so flexbox alignment doesn't fight us. function wireDrag(handle) { var dragging = false; var startX, startY, originX, originY; function onDown(e) { if (e.button !== undefined && e.button !== 0) return; var pt = e.touches ? e.touches[0] : e; var rect = modal.getBoundingClientRect(); modal.classList.add('is-dragged'); modal.style.position = 'fixed'; modal.style.left = rect.left + 'px'; modal.style.top = rect.top + 'px'; dragging = true; startX = pt.clientX; startY = pt.clientY; originX = rect.left; originY = rect.top; e.preventDefault(); } function onMove(e) { if (!dragging) return; var pt = e.touches ? e.touches[0] : e; var dx = pt.clientX - startX; var dy = pt.clientY - startY; var w = modal.offsetWidth; var h = modal.offsetHeight; var maxX = window.innerWidth - w; var maxY = window.innerHeight - h; var x = Math.max(0, Math.min(maxX, originX + dx)); var y = Math.max(0, Math.min(maxY, originY + dy)); modal.style.left = x + 'px'; modal.style.top = y + 'px'; } function onUp() { dragging = false; } handle.addEventListener('mousedown', onDown); handle.addEventListener('touchstart', onDown, { passive: false }); document.addEventListener('mousemove', onMove); document.addEventListener('touchmove', onMove, { passive: false }); document.addEventListener('mouseup', onUp); document.addEventListener('touchend', onUp); } function open(opts) { if (backdrop && backdrop.parentNode) close(); if (!backdrop) build(); currentOpts = opts; prevFocus = document.activeElement; modal.classList.remove('is-dragged'); modal.style.position = ''; modal.style.left = ''; modal.style.top = ''; titleEl.textContent = opts.title || ''; bodyEl.textContent = ''; if (opts.body instanceof Node) { bodyEl.appendChild(opts.body); } else if (typeof opts.body === 'string') { bodyEl.textContent = opts.body; } var confirmOpts = opts.confirm || {}; var cancelOpts = opts.cancel || {}; confirmBtn.textContent = confirmOpts.label || 'OK'; confirmBtn.className = 'btn' + (confirmOpts.danger ? ' danger' : ''); confirmBtn.disabled = !!confirmOpts.initiallyDisabled; cancelBtn.textContent = cancelOpts.label || 'CANCEL'; cancelBtn.className = 'btn'; footerEl.textContent = ''; if (opts.swapButtons) { footerEl.appendChild(confirmBtn); footerEl.appendChild(cancelBtn); } else { footerEl.appendChild(cancelBtn); footerEl.appendChild(confirmBtn); } document.body.appendChild(backdrop); setTimeout(function () { if (cancelOpts.autofocus) { cancelBtn.focus(); return; } var firstInput = bodyEl.querySelector('input, textarea, select'); if (firstInput) { firstInput.focus(); if (firstInput.select) firstInput.select(); } else { confirmBtn.focus(); } }, 0); var enterConfirms = confirmOpts.enterConfirms !== false; onKeydown = function (e) { if (e.key === 'Escape') { e.preventDefault(); close(); return; } if (e.key === 'Enter' && enterConfirms) { var tag = (e.target && e.target.tagName) || ''; if (tag === 'TEXTAREA') return; e.preventDefault(); if (!confirmBtn.disabled) confirmBtn.click(); } }; document.addEventListener('keydown', onKeydown); return { close: close, setConfirmDisabled: function (d) { confirmBtn.disabled = !!d; }, confirmButton: confirmBtn }; } function close() { if (!backdrop || !backdrop.parentNode) return; if (onKeydown) { document.removeEventListener('keydown', onKeydown); onKeydown = null; } backdrop.parentNode.removeChild(backdrop); currentOpts = null; var toRestore = prevFocus; prevFocus = null; if (toRestore && toRestore.focus) { try { toRestore.focus(); } catch (e) {} } } window.openModal = open; window.closeModal = close; })();