function guarded(node) {
const form = node && node.closest && node.closest('form');
return form && form.hasAttribute('data-form-guard') ? form : null;
}
function changed(doc) {
return doc.querySelector('form[data-form-guard][data-form-dirty]');
}
function saving(doc, target) {
const own = guarded(target);
if (own) return own;
const forms = doc.querySelectorAll('form[data-form-guard]');
return forms.length === 1 ? forms[0] : null;
}
function isSave(e) {
return (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.key === 's' || e.key === 'S');
}
function leaves(e) {
if (e.defaultPrevented || e.button !== 0) return null;
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return null;
const link = e.target && e.target.closest && e.target.closest('a[href]');
if (!link || link.hasAttribute('download')) return null;
const target = link.getAttribute('target');
if (target && target !== '_self') return null;
const href = link.getAttribute('href');
return href && !href.startsWith('#') ? link : null;
}
function reveal(control) {
let section = control.closest && control.closest('details[data-section]');
while (section) {
section.open = true;
section = section.parentElement && section.parentElement.closest('details[data-section]');
}
}
function install(doc, win, ask = (question) => win.confirm(question)) {
const mark = (e) => {
const form = guarded(e.target);
if (form) form.setAttribute('data-form-dirty', '');
};
doc.addEventListener('input', mark);
doc.addEventListener('change', mark);
doc.addEventListener('submit', (e) => {
const form = guarded(e.target);
if (form) form.removeAttribute('data-form-dirty');
});
doc.addEventListener('keydown', (e) => {
if (!isSave(e)) return;
const form = saving(doc, e.target);
if (!form) return;
e.preventDefault();
form.requestSubmit();
});
doc.addEventListener(
'click',
(e) => {
const form = leaves(e) && changed(doc);
if (!form) return;
if (ask(form.getAttribute('data-form-guard'))) {
form.removeAttribute('data-form-dirty');
} else {
e.preventDefault();
e.stopPropagation();
}
},
true,
);
win.addEventListener('beforeunload', (e) => {
if (!changed(doc)) return;
e.preventDefault();
e.returnValue = '';
});
doc.addEventListener('invalid', (e) => reveal(e.target), true);
}
if (typeof document !== 'undefined') {
install(document, window);
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { install, reveal };
}