112 lines
5.1 KiB
JavaScript
112 lines
5.1 KiB
JavaScript
/*
|
|
* Comportements communs du back-office (aucune dépendance).
|
|
* Chaque bloc s'active par un attribut data-* : sans JavaScript, les formulaires fonctionnent quand même.
|
|
*/
|
|
(() => {
|
|
'use strict';
|
|
|
|
// --- Navigation en bandeau (petits écrans) : l'entrée active reste visible ---------------------
|
|
const current = document.querySelector('.nav .nav-link.active');
|
|
if (current && current.parentElement.scrollWidth > current.parentElement.clientWidth) {
|
|
current.parentElement.scrollLeft = current.offsetLeft - 16;
|
|
}
|
|
|
|
// --- Notifications : fermeture manuelle, et automatique après quelques secondes ----------------
|
|
const dismiss = (toast) => {
|
|
toast.classList.add('is-leaving');
|
|
setTimeout(() => toast.remove(), 220);
|
|
};
|
|
document.querySelectorAll('[data-toast]').forEach((toast) => {
|
|
let timer = setTimeout(() => dismiss(toast), 9000);
|
|
toast.addEventListener('mouseenter', () => clearTimeout(timer));
|
|
toast.addEventListener('mouseleave', () => { timer = setTimeout(() => dismiss(toast), 4000); });
|
|
toast.querySelector('[data-dismiss]')?.addEventListener('click', () => { clearTimeout(timer); dismiss(toast); });
|
|
});
|
|
|
|
// --- Confirmation : <form data-confirm="Question ?" data-confirm-label="Supprimer" data-confirm-danger>
|
|
const dialog = document.getElementById('confirm-dialog');
|
|
if (dialog && typeof dialog.showModal === 'function') {
|
|
const message = dialog.querySelector('[data-message]');
|
|
const ok = dialog.querySelector('[data-ok]');
|
|
let pending = null;
|
|
|
|
document.addEventListener('submit', (e) => {
|
|
const form = e.target;
|
|
if (!(form instanceof HTMLFormElement) || !form.dataset.confirm || form.dataset.confirmed) return;
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
pending = form;
|
|
message.textContent = form.dataset.confirm;
|
|
ok.textContent = form.dataset.confirmLabel || 'Confirmer';
|
|
ok.classList.toggle('danger', 'confirmDanger' in form.dataset);
|
|
ok.classList.toggle('solid', 'confirmDanger' in form.dataset);
|
|
dialog.returnValue = '';
|
|
dialog.showModal();
|
|
}, true);
|
|
|
|
dialog.addEventListener('close', () => {
|
|
const form = pending;
|
|
pending = null;
|
|
if (!form || dialog.returnValue !== 'ok') return;
|
|
form.dataset.confirmed = '1';
|
|
form.requestSubmit ? form.requestSubmit() : form.submit();
|
|
});
|
|
}
|
|
|
|
// --- Bouton occupé : <form data-busy="Texte pendant l'envoi…"> ----------------------------------
|
|
document.addEventListener('submit', (e) => {
|
|
const form = e.target;
|
|
if (!(form instanceof HTMLFormElement) || !form.dataset.busy || e.defaultPrevented) return;
|
|
const button = form.querySelector('button[type=submit], button:not([type])');
|
|
if (!button) return;
|
|
button.classList.add('is-busy');
|
|
button.setAttribute('aria-disabled', 'true');
|
|
const label = button.querySelector('[data-label]');
|
|
if (label) label.textContent = form.dataset.busy;
|
|
});
|
|
|
|
// --- Copie dans le presse-papiers : <button data-copy="texte"> ---------------------------------
|
|
document.addEventListener('click', async (e) => {
|
|
const button = e.target.closest('[data-copy]');
|
|
if (!button) return;
|
|
const label = button.querySelector('[data-label]') || button;
|
|
const initial = label.textContent;
|
|
try {
|
|
await navigator.clipboard.writeText(button.dataset.copy);
|
|
label.textContent = 'Copié';
|
|
} catch {
|
|
label.textContent = 'Copie impossible';
|
|
}
|
|
setTimeout(() => { label.textContent = initial; }, 1600);
|
|
});
|
|
|
|
// --- Zone de dépôt : <label class="dropzone" data-dropzone><input type="file"> … ---------------
|
|
document.querySelectorAll('[data-dropzone]').forEach((zone) => {
|
|
const input = zone.querySelector('input[type=file]');
|
|
const name = zone.querySelector('[data-name]');
|
|
const prompt = zone.querySelector('[data-prompt]');
|
|
if (!input) return;
|
|
|
|
const show = () => {
|
|
const file = input.files[0];
|
|
if (name) { name.textContent = file ? file.name : ''; name.hidden = !file; }
|
|
if (prompt) prompt.hidden = Boolean(file);
|
|
};
|
|
input.addEventListener('change', show);
|
|
|
|
['dragenter', 'dragover'].forEach((type) => zone.addEventListener(type, (e) => {
|
|
e.preventDefault();
|
|
zone.classList.add('is-over');
|
|
}));
|
|
['dragleave', 'drop'].forEach((type) => zone.addEventListener(type, () => zone.classList.remove('is-over')));
|
|
zone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
if (input.disabled || !e.dataTransfer.files.length) return;
|
|
const transfer = new DataTransfer();
|
|
transfer.items.add(e.dataTransfer.files[0]);
|
|
input.files = transfer.files;
|
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
});
|
|
});
|
|
})();
|