diff --git a/css/felexdev-themes.css b/css/felexdev-themes.css index d78437f..6d34e76 100644 --- a/css/felexdev-themes.css +++ b/css/felexdev-themes.css @@ -52,14 +52,6 @@ --text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); --font-family: 'Arial', sans-serif; } -.flxd-tm-color-scheme-old-terminal { - --bg-color: #222; - --text-color: #9d9; - --accent-color: #40a9ff; - --border-color: #444; - --text-shadow: 0px 0px 10px rgba(255, 255, 0, 0.9); - --font-family: Monospace; -} body { margin: auto; diff --git a/js/felexdev-theme-manager-minified.js b/js/felexdev-theme-manager-minified.js index 59a7837..14ec866 100644 --- a/js/felexdev-theme-manager-minified.js +++ b/js/felexdev-theme-manager-minified.js @@ -1,34 +1,9 @@ /** * @file felexdev-theme-manager.js - * @description Миниицированная версия + * @description Минимальная версия * @version 0.0.1 * @author felex67 (admin@felexdev.ru) * @license Apache2.0 * @warning Системная версия('auto') должна регистрироваться первой!!! */ -class ThemeManager { - /** Cunfigurations */ - static #c = { - btnid:'btn-toggle', // Идентификатор кнопки переключения: `gpfx + btnid == 'flxd-tm-btn-toggle'` - btnti:0, // Индекс табуляции кнопки переключения тем - gpfx:'flxd-tm', // Глобальный префикс: `gpfx + '-' == 'flxd-tm-'` - stid:'color-scheme', // Селектор CSS: `gpfx + stdid == 'flxd-tm-color-scheme'` - svg:'', - /* */ - };static #cl=document.documentElement.classList;static #b=document.createElement('div');static #i=0;static #t=[];static toggle=()=>{this.#s((this.#i+1)%this.#t.length);};static{let m=this;let c=m.#c;c.gpfx=c.gpfx+'-';c.stid=c.gpfx+c.stid;m.#b.id=c.btnid=c.gpfx+c.btnid;m.#b.tabIndex=m.#c.btnti;m.#b.setAttribute('aria-label', 'toggle color scheme');m.#b.addEventListener('click',m.toggle);m.#b.addEventListener('keydown',(e)=>{if(e.key==' '||e.key=='Enter')this.toggle();});};static #at=(n,i=null)=>{this.#t.push({n:n,i:i||this.#c.svg,s:`${this.#c.stid}-${n}`});};static #s(i){if(i==this.#i)return;let m=this;if(i&&m.#i)m.#cl.replace(m.#t[m.#i].s,m.#t[i].s);else if(m.#i)m.#cl.remove(m.#t[m.#i].s);else m.#cl.add(m.#t[i].s);m.#sv(m.#i=i);m.#b.innerHTML=m.#t[i].i;m.#b.setAttribute('aria-current',m.#t[i].n);};static #sv=(()=>{let m=this;let s=(i)=>{console.error(m.#c.gpfx+'#sv: Can`t save theme id, storages are unreachable.');};try{m.#i=parseInt(localStorage.getItem(m.#c.stid))||0;s=(i)=>{try{localStorage.setItem(m.#c.stid,i);}catch(e){console.error(e);}};}catch(e){console.error(m.#c.gpfx+'#sv-IIFE: localStorage is unreachable, trying cookie.');try{let c=document.cookie.match(`${m.#c.stid}\\s*=\\s*(\\d+)`);m.#i=c?parseInt(c[1])||0:0;s=(i)=>{try{document.cookie=`${m.#c.stid}=${i};max-age=${31536000};path=/`;}catch(e){console.error(e);}};}catch(e){console.error(m.#c.gpfx+`#s-IIFE: All storages are unreachable('localStorage' & 'document.cookie')`);}}return s;})();static{let m=this; - /** - * Add new theme - * @typedef {function} add - * @param {string} themeName - Название темы используемое в селекторе CSS. 'auto' -> 'flxd-tm-color-scheme-auto' - * @param {string?} themeIcon - Текстовое векторное изображение PlainSVG, если равно нулю используется иконка по умолчанию: 2-я фаза луны. - */ let add=m.#at; - /** @warning Системная версия('auto') должна регистрироваться первой!!! */ - add('auto',''); - add('light',''); - add('dark',''); - /* Темы добавлять здесь */ - add('old-terminal',''); - /* */ - if(m.#i>=m.#t.length||m.#i<0){console.error(`${m.#c.gpfx}bottom-static: Invalid theme index loaded '${m.#i}'`);m.#i=0;m.#sv(m.#i)}m.#cl.add(m.#t[m.#i].s);m.#b.innerHTML=m.#t[m.#i].i;m.#b.setAttribute('aria-current',m.#t[m.#i].n); - };static set=(name)=>{let i=-1;for(let j=0;j{window.removeEventListener('DOMContentLoaded',this.#_);document.body.appendChild(this.#b);};static{let m=this;m.#s(m.#i);if(!document.body)window.addEventListener('DOMContentLoaded',this.#_);} -}; \ No newline at end of file +class ThemeManager {static #c={bid:'btn-toggle',btid:0,gpfx:'flxd-tm',tpfx:'color-scheme',svg:'',mrqd:window.matchMedia('(prefers-color-scheme: dark)'),rqdh:null};static #cl=document.documentElement.classList;static #b=document.createElement('div');static #i=0;static #t=[];static toggle=()=>{this.#s((this.#i+1)%this.#t.length);};static #bclk=(e)=>{if(e.key==='Enter'||e.key===' ')this.#s((this.#i+1)%2)};static #stch=(e)=>{if(e.matches!=this.#i)this.#s(e.matches+0);};static{let c=this.#c;let b=this.#b;c.gpfx=c.gpfx+'-';c.tpfx=`${c.gpfx}${c.tpfx}-`;b.id=c.bid=c.gpfx+c.bid;b.tabIndex=c.btid;b.innerHTML=c.svg;b.setAttribute('aria-label', 'toggle color scheme');b.addEventListener('click',this.#bclk);b.addEventListener('keydown',this.#bclk);c.rqdh=c.mrqd.addEventListener('change',this.#stch);};static #at=(n)=>{this.#t.push({n:n,s:this.#c.tpfx+n});};static #s(i){if(i==this.#i)return;let m=this;m.#cl.replace(m.#t[m.#i].s,m.#t[i].s);m.#sv(m.#i=i);m.#b.setAttribute('aria-current',m.#t[i].n);};static #sv=(()=>{let s=()=>{console.error(this.#c.gpfx+'#sv: Невозможно сохранить тему, хранилище недоступно.');};try{this.#i=parseInt(localStorage.getItem(this.#c.tpfx))||0;s=()=>{try{localStorage.setItem(this.#c.tpfx,this.#i);}catch(e){console.error(e);}};}catch(e){console.error(this.#c.gpfx+'#sv-IIFE: localStorage не доступен.');}return s;})();static{let i=this.#i;let t=this.#t;this.#at('light');this.#at('dark');if(i>=t.length||i<0){console.error(`${this.#c.gpfx}bottom-static: В хранилище сохранён не валидный индекс '${i}'. Сброс на системную тему`);this.#i=this.#c.mrqd.matches+0;}this.#cl.add(t[i].s);this.#b.setAttribute('aria-current',t[i].n);};static set=(name)=>{let i=-1;for(let j=0;j{window.removeEventListener('DOMContentLoaded',this.#_);document.body.appendChild(this.#b);};static {if(!document.body)window.addEventListener('DOMContentLoaded',this.#_);};}; \ No newline at end of file diff --git a/js/felexdev-theme-manager.js b/js/felexdev-theme-manager.js index 3c27a6b..f645a9f 100644 --- a/js/felexdev-theme-manager.js +++ b/js/felexdev-theme-manager.js @@ -1,6 +1,6 @@ /** * @file felexdev-theme-manager.js - * @description Минифицированная версия + * @description Минимальная версия * @version 0.0.1 * @author felex67 (admin@felexdev.ru) * @license Apache2.0 @@ -11,7 +11,6 @@ class ThemeManager { * @typedef {Object} Theme * * @property {string} name - Внутреннее название темы. - * @property {string} icon - PlainSVG-иконка темы. * @property {string} selector - CSS-селектор темы. Без точки. */ @@ -24,7 +23,6 @@ class ThemeManager { * @property {string} globalPrefix - Глобальный префикс. * @property {string} themePrefix - Префикс CSS-class-селектора темы. * @property {string} svg - PlainSVG-иконка темы. - * @type {{ btnId: string; btnTabIndex: number; globalPrefix: string; themePrefix: string; svg: string; }} */ static #config = { btnId: 'btn-toggle', // Идентификатор кнопки переключения: `gpfx + btnid == 'flxd-tm-btn-toggle'` @@ -32,6 +30,8 @@ class ThemeManager { globalPrefix: 'flxd-tm', // Глобальный префикс: `gpfx + '-' == 'flxd-tm-'` themePrefix: 'color-scheme', // Селектор CSS: `gpfx + stdid == 'flxd-tm-color-scheme'` svg: '', + mediaRequestDark: window.matchMedia('(prefers-color-scheme: dark)'), + requestDarkHandle: null, }; /** @@ -57,7 +57,7 @@ class ThemeManager { * @type {number} */ static #index = 0; - + /** * Массив тем * @@ -65,89 +65,113 @@ class ThemeManager { * @type {[Theme]} */ static #themes = []; + + /** + * Переключает тему + */ static toggle = () => { this.#set((this.#index + 1) % this.#themes.length); }; - static /* Инициализация конфигурации */ { + + /** + * Обработчик события нажатия кнопки переключения темы + */ + static #btnClick = (e) => { + if (e.key === 'Enter' || e.key === ' ') + this.#set((this.#index + 1) % 2) + }; + + + /** + * Обработчик события изменения системной темы + * + * @param {Event} e + */ + static #sysThemeChange = (e) => { + if (e.matches != this.#index) + this.#set(e.matches + 0); + }; + + static { /* Инициализация конфигурации */ const config = this.#config; + const button = this.#button; + /* Настраиваем префиксы */ config.globalPrefix = config.globalPrefix + '-'; config.themePrefix = `${config.globalPrefix}${config.themePrefix}-`; - this.#button.id = config.btnId = config.globalPrefix + config.btnId; - this.#button.tabIndex = config.btnTabIndex; - this.#button.setAttribute('aria-label', 'toggle color scheme'); - this.#button.addEventListener('click', this.toggle); - this.#button.addEventListener('keydown', (e) => { if (e.key == ' ' || e.key == 'Enter') this.toggle();}); + /* Настраиваем кнопку */ + button.id = config.btnId = config.globalPrefix + config.btnId; + button.tabIndex = config.btnTabIndex; + button.innerHTML = config.svg; + /* Настраиваем обработчики */ + button.setAttribute('aria-label', 'toggle color scheme'); + button.addEventListener('click', this.#btnClick); + button.addEventListener('keydown', this.#btnClick); + /* Устанавливаем обработчик смены системной темы */ + config.requestDarkHandle = config.mediaRequestDark.addEventListener('change', this.#sysThemeChange); }; /** * Добавить новую тему - * @param {string} themeName - Название темы используемое в селекторе CSS. 'auto' -> 'flxd-tm-color-scheme-auto' - * @param {string?} themeIcon - Текстовое векторное изображение PlainSVG, если равно нулю используется иконка по умолчанию: 2-я фаза луны. + * @param {string} name - Название темы используемое в селекторе CSS. 'light' -> 'flxd-tm-color-scheme-light' */ - static #addTheme = (name, icon = null) => { + static #addTheme = (name) => { const theme = { name: name, - icon: icon || this.#config.svg, selector: this.#config.themePrefix + name, }; this.#themes.push(theme); }; + /** + * Устанавливаем тему по индексу + * @param {number} index + */ static #set(index) { if (index == this.#index) - return; - if (index && this.#index) - this.#classList.replace(this.#themes[this.#index].selector, this.#themes[index].selector); - else if (this.#index) - this.#classList.remove(this.#themes[this.#index].selector); - else this.#classList.add(this.#themes[index].selector); + return; + this.#classList.replace(this.#themes[this.#index].selector, this.#themes[index].selector); this.#save(this.#index = index); - this.#button.innerHTML = this.#themes[index].icon; this.#button.setAttribute('aria-current', this.#themes[index].name); }; + + /** + * Сохраняет индекс текущей темы + * @type {function() => void} + * @returns void + */ static #save = (() => { - let save = (index) => { console.error(this.#config.globalPrefix + '#save: Невозможно сохранить тему, хранилища не доступны.'); }; + let save = () => { console.error(this.#config.globalPrefix + '#save: Невозможно сохранить тему, хранилище недоступно.'); }; try { this.#index = parseInt(localStorage.getItem(this.#config.themePrefix)) || 0; - save = (index) => { - try { localStorage.setItem(this.#config.themePrefix, index); } + save = () => { + try { localStorage.setItem(this.#config.themePrefix, this.#index); } catch(e) { console.error(e); } }; } catch(e) { - console.error(this.#config.globalPrefix + '#sv-IIFE: localStorage не доступен, пробую cookie.'); - try { - let cookie = document.cookie.match(`${this.#config.themePrefix}\\s*=\\s*(\\d+)`); - this.#index = cookie ? parseInt(c[1]) || 0 : 0; - save = (i) => { - try { - document.cookie = `${this.#config.themePrefix}=${i};max-age=${31536000};path=/`; - } - catch(e) { console.error(e); } - }; - } - catch(e) { console.error(this.#config.globalPrefix + `#s-IIFE: Хранилища не доступны. Настройки темы не сохраняются!`); } + console.error(this.#config.globalPrefix + '#sv-IIFE: localStorage не доступен.'); } return save; })(); - static /* Инициализация тем */ { + static { /* Инициализация тем */ let index = this.#index; let themes = this.#themes; - let addTheme = this.#addTheme; - /** @warning Системная версия('auto') должна регистрироваться первой!!! */ - addTheme('auto',''); - addTheme('light',''); - addTheme('dark',''); - /* Новые темы добавлять здесь */ - addTheme('old-terminal',''); - /* */ + /* Добавляем темы(светлая/темная) */ + this.#addTheme('light'); + this.#addTheme('dark'); + /* Проверяем индекс */ if (index >= themes.length || index < 0) { console.error(`${this.#config.globalPrefix}bottom-static: В хранилище сохранён не валидный индекс '${index}'. Сброс на системную тему`); - this.#index = index = 0; + this.#index = this.#config.mediaRequestDark.matches + 0; } - if(index) this.#classList.add(themes[index].selector); - this.#button.innerHTML = themes[index].icon; + /* Применяем тему */ + this.#classList.add(themes[index].selector); this.#button.setAttribute('aria-current', themes[index].name); }; + /** + * + * @param {string} name Название темы + * @throws Error если `name` нет в списке + */ static set = (name) => { let i = -1; for(let j = 0; j < this.#themes.length; j++) { @@ -161,12 +185,15 @@ class ThemeManager { } this.#set(i); }; + /** + * Инициализация интерфейса. Добавляет кнопку к документу + */ static #_ = () => { window.removeEventListener('DOMContentLoaded',this.#_); document.body.appendChild(this.#button); }; + /** Назначаем обработчик события создания DOM */ static { - let m = this; this.#set(this.#index); if (!document.body)window.addEventListener('DOMContentLoaded', this.#_); }