200 lines
6.5 KiB
JavaScript
200 lines
6.5 KiB
JavaScript
(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;
|
|
})();
|