// Редактор точной настройки палитры. import { PALETTE_PRESETS, PALETTE_ROLES, exportPalette, getPaletteSettings, importPalette, resolvePalette, resolveThemeMode, setPaletteSettings, } from '../services/theme-service.js'; export function openPaletteEditor({ theme = resolveThemeMode(), onClose } = {}) { let editTheme = theme === 'light' ? 'light' : 'dark'; const opener = document.activeElement; const modal = document.createElement('div'); modal.className = 'modal palette-editor'; modal.innerHTML = ` `; const card = modal.querySelector('.palette-editor__card'); const presetsEl = modal.querySelector('.palette-editor__presets'); const rolesEl = modal.querySelector('.palette-editor__roles'); const jsonEl = modal.querySelector('.palette-editor__json'); const errorEl = modal.querySelector('.palette-editor__error'); for (const [id, preset] of Object.entries(PALETTE_PRESETS)) { const option = document.createElement('option'); option.value = id; option.textContent = preset.label; presetsEl.append(option); } const update = (mutate) => { const settings = getPaletteSettings(); const next = { preset: settings.preset, custom: { dark: { ...settings.custom.dark }, light: { ...settings.custom.light } } }; mutate(next); setPaletteSettings(next); render(); }; function render() { const settings = getPaletteSettings(); const colors = resolvePalette(editTheme, settings); const custom = settings.custom[editTheme]; presetsEl.value = settings.preset; modal.querySelectorAll('.palette-editor__preview span').forEach((swatch, index) => { swatch.style.backgroundColor = [colors.background, colors.surface, colors.accent, colors['surface-selected']][index]; }); modal.querySelectorAll('[data-edit-theme]').forEach((btn) => { btn.setAttribute('aria-checked', String(btn.dataset.editTheme === editTheme)); }); rolesEl.replaceChildren(...PALETTE_ROLES.map((role) => { const row = document.createElement('label'); row.className = 'palette-editor__role'; const changed = Boolean(custom[role.id]); row.innerHTML = ` ${role.label}${changed ? ' изменён' : ''} ${colors[role.id]} `; const input = row.querySelector('input'); input.addEventListener('input', () => { document.documentElement.style.setProperty(`--${role.id}`, input.value); row.querySelector('code').textContent = input.value; }); input.addEventListener('change', () => update((next) => { next.custom[editTheme][role.id] = input.value; })); return row; })); jsonEl.value = exportPalette(); errorEl.hidden = true; } const close = () => { document.removeEventListener('keydown', onKeydown); modal.remove(); opener?.focus?.(); onClose?.(); }; const onKeydown = (event) => { if (event.key === 'Escape') close(); }; modal.addEventListener('click', async (event) => { if (event.target === modal) { close(); return; } // data-edit-theme, а не data-theme: data-theme стоит на , и closest() находил бы его при любом клике. const themeBtn = event.target.closest('[data-edit-theme]'); if (themeBtn) { editTheme = themeBtn.dataset.editTheme; render(); return; } if (event.target.closest('.palette-editor__close')) { close(); return; } const action = event.target.closest('[data-action]')?.dataset.action; if (action === 'done') close(); if (action === 'reset-theme') update((next) => { next.custom[editTheme] = {}; }); if (action === 'copy') { try { await navigator.clipboard.writeText(jsonEl.value); } catch { jsonEl.select(); } } if (action === 'import') { try { importPalette(jsonEl.value); render(); } catch { errorEl.textContent = 'Не удалось прочитать палитру: проверьте, что текст скопирован целиком.'; errorEl.hidden = false; } } }); presetsEl.addEventListener('change', () => update((next) => { next.preset = presetsEl.value; next.custom = { dark: {}, light: {} }; })); document.addEventListener('keydown', onKeydown); render(); document.body.append(modal); card.querySelector('.palette-editor__close').focus(); return close; }