Много разных изменений
This commit is contained in:
103
frontend/admin/js/dialog.js
Normal file
103
frontend/admin/js/dialog.js
Normal file
@@ -0,0 +1,103 @@
|
||||
let activeDialog = null;
|
||||
|
||||
function ensureDialogRoot() {
|
||||
let root = document.getElementById('project-dialog-root');
|
||||
if (!root) {
|
||||
root = document.createElement('div');
|
||||
root.id = 'project-dialog-root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
export function showDialog({ title, message, confirmLabel = 'Подтвердить', cancelLabel = 'Отмена', danger = false, input = null }) {
|
||||
activeDialog?.close(false);
|
||||
const root = ensureDialogRoot();
|
||||
const previousFocus = document.activeElement;
|
||||
root.innerHTML = `
|
||||
<div class="project-dialog-backdrop">
|
||||
<section class="project-dialog" role="dialog" aria-modal="true" aria-labelledby="project-dialog-title" aria-describedby="project-dialog-message">
|
||||
<header>
|
||||
<h2 id="project-dialog-title">${escapeText(title || 'Подтвердите действие')}</h2>
|
||||
<button type="button" class="btn btn-icon-md btn-ghost" data-dialog-cancel aria-label="Закрыть">×</button>
|
||||
</header>
|
||||
<p id="project-dialog-message">${escapeText(message || '')}</p>
|
||||
${input ? `<label class="project-dialog-field"><span>${escapeText(input.label || 'Комментарий')}</span><textarea data-dialog-input rows="4" maxlength="${Number(input.maxlength || 2000)}" placeholder="${escapeText(input.placeholder || '')}">${escapeText(input.value || '')}</textarea></label>` : ''}
|
||||
<div class="project-dialog-error" data-dialog-error role="alert"></div>
|
||||
<footer>
|
||||
${cancelLabel ? `<button type="button" class="btn btn-md btn-secondary" data-dialog-cancel>${escapeText(cancelLabel)}</button>` : ''}
|
||||
<button type="button" class="btn btn-md ${danger ? 'btn-danger' : 'btn-primary'}" data-dialog-confirm>${escapeText(confirmLabel)}</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
const backdrop = root.firstElementChild;
|
||||
const dialog = backdrop.querySelector('.project-dialog');
|
||||
const inputElement = dialog.querySelector('[data-dialog-input]');
|
||||
|
||||
return new Promise(resolve => {
|
||||
const close = result => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
root.innerHTML = '';
|
||||
activeDialog = null;
|
||||
previousFocus?.focus?.();
|
||||
resolve(result);
|
||||
};
|
||||
activeDialog = { close };
|
||||
const cancel = () => close(input ? null : false);
|
||||
const confirm = () => {
|
||||
if (input?.required && !inputElement?.value.trim()) {
|
||||
dialog.querySelector('[data-dialog-error]').textContent = input.requiredMessage || 'Заполните поле';
|
||||
inputElement?.focus();
|
||||
return;
|
||||
}
|
||||
close(input ? inputElement.value.trim() : true);
|
||||
};
|
||||
const handleKeydown = event => {
|
||||
if (event.key === 'Escape') cancel();
|
||||
if (event.key === 'Tab') trapFocus(event, dialog);
|
||||
};
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
dialog.querySelectorAll('[data-dialog-cancel]').forEach(button => button.addEventListener('click', cancel));
|
||||
dialog.querySelector('[data-dialog-confirm]').addEventListener('click', confirm);
|
||||
backdrop.addEventListener('click', event => {
|
||||
if (event.target === backdrop) cancel();
|
||||
});
|
||||
window.setTimeout(() => (inputElement || dialog.querySelector('[data-dialog-confirm]')).focus(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
export function confirmAction(options) {
|
||||
return showDialog(options);
|
||||
}
|
||||
|
||||
export function promptAction(options) {
|
||||
return showDialog({ ...options, input: options.input || { label: 'Комментарий' } });
|
||||
}
|
||||
|
||||
export function alertAction(options) {
|
||||
return showDialog({ ...options, cancelLabel: null, confirmLabel: options.confirmLabel || 'Понятно' });
|
||||
}
|
||||
|
||||
function trapFocus(event, root) {
|
||||
const focusable = [...root.querySelectorAll('button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])')];
|
||||
if (!focusable.length) return;
|
||||
const first = focusable[0];
|
||||
const last = focusable.at(-1);
|
||||
if (event.shiftKey && document.activeElement === first) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function escapeText(value) {
|
||||
return String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
Reference in New Issue
Block a user