/* * HSThemeSwitch * @version: 4.2.0 * @author: Preline Labs Ltd. * @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html) * Copyright 2024 Preline Labs Ltd. */ import { IThemeSwitch, IThemeSwitchOptions } from '../theme-switch/interfaces'; import HSBasePlugin from '../base-plugin'; class HSThemeSwitch extends HSBasePlugin implements IThemeSwitch { public theme: string; public type: 'change' | 'click'; private onElementChangeListener: (evt: Event) => void; private onElementClickListener: () => void; private static systemThemeObserver: | ((e: MediaQueryListEvent) => void) | null = null; constructor( el: HTMLElement | HTMLInputElement, options?: IThemeSwitchOptions, ) { super(el, options); const data = el.getAttribute('data-hs-theme-switch'); const dataOptions: IThemeSwitchOptions = data ? JSON.parse(data) : {}; const concatOptions = { ...dataOptions, ...options, }; this.theme = concatOptions?.theme || localStorage.getItem('hs_theme') || 'default'; this.type = concatOptions?.type || 'change'; this.init(); } private elementChange(evt: Event) { const theme = (evt.target as HTMLInputElement).checked ? 'dark' : 'default'; this.setAppearance(theme); this.toggleObserveSystemTheme(); } private elementClick(theme: string) { this.setAppearance(theme); this.toggleObserveSystemTheme(); } private init() { this.createCollection(window.$hsThemeSwitchCollection, this); if (this.theme !== 'default') this.setAppearance(); if (this.type === 'click') this.buildSwitchTypeOfClick(); else this.buildSwitchTypeOfChange(); } private buildSwitchTypeOfChange() { (this.el as HTMLInputElement).checked = this.theme === 'dark'; this.toggleObserveSystemTheme(); this.onElementChangeListener = (evt) => this.elementChange(evt); this.el.addEventListener('change', this.onElementChangeListener); } private buildSwitchTypeOfClick() { const theme = this.el.getAttribute('data-hs-theme-click-value'); this.toggleObserveSystemTheme(); this.onElementClickListener = () => this.elementClick(theme); this.el.addEventListener('click', this.onElementClickListener); } private setResetStyles() { const style = document.createElement('style'); style.innerText = `*{transition: unset !important;}`; style.setAttribute('data-hs-appearance-onload-styles', ''); document.head.appendChild(style); return style; } private addSystemThemeObserver() { if (HSThemeSwitch.systemThemeObserver) return; HSThemeSwitch.systemThemeObserver = (e: MediaQueryListEvent) => { window.$hsThemeSwitchCollection?.forEach((instance) => { if (localStorage.getItem('hs_theme') === 'auto') { instance.element.setAppearance('auto', false); } }); }; window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', HSThemeSwitch.systemThemeObserver); } private removeSystemThemeObserver() { if (!HSThemeSwitch.systemThemeObserver) return; window .matchMedia('(prefers-color-scheme: dark)') .removeEventListener('change', HSThemeSwitch.systemThemeObserver); HSThemeSwitch.systemThemeObserver = null; } private toggleObserveSystemTheme() { if (localStorage.getItem('hs_theme') === 'auto') { this.addSystemThemeObserver(); } else this.removeSystemThemeObserver(); } // Public methods public setAppearance( theme = this.theme, isSaveToLocalStorage = true, isSetDispatchEvent = true, ) { const html = document.querySelector('html'); const resetStyles = this.setResetStyles(); if (isSaveToLocalStorage) localStorage.setItem('hs_theme', theme); let computedTheme = theme; if (computedTheme === 'default') computedTheme = 'light'; if (computedTheme === 'auto') { computedTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } html.classList.remove('light', 'dark', 'default', 'auto'); if (theme === 'auto') { html.classList.add('auto', computedTheme); } else { html.classList.add(computedTheme); } setTimeout(() => resetStyles.remove()); if (isSetDispatchEvent) { window.dispatchEvent( new CustomEvent('on-hs-appearance-change', { detail: theme }), ); } } public destroy() { // Clear listeners if (this.type === 'change') { this.el.removeEventListener('change', this.onElementChangeListener); } if (this.type === 'click') { this.el.removeEventListener('click', this.onElementClickListener); } window.$hsThemeSwitchCollection = window.$hsThemeSwitchCollection.filter( ({ element }) => element.el !== this.el, ); } // Static methods static getInstance(target: HTMLElement | string, isInstance?: boolean) { const elInCollection = window.$hsThemeSwitchCollection.find( (el) => el.element.el === (typeof target === 'string' ? document.querySelector(target) : target), ); return elInCollection ? isInstance ? elInCollection : elInCollection.element.el : null; } static autoInit() { if (!window.$hsThemeSwitchCollection) window.$hsThemeSwitchCollection = []; if (window.$hsThemeSwitchCollection) { window.$hsThemeSwitchCollection = window.$hsThemeSwitchCollection.filter( ({ element }) => document.contains(element.el), ); } document .querySelectorAll('[data-hs-theme-switch]:not(.--prevent-on-load-init)') .forEach((el: HTMLElement) => { if ( !window.$hsThemeSwitchCollection.find( (elC) => (elC?.element?.el as HTMLElement) === el, ) ) { new HSThemeSwitch(el, { type: 'change' }); } }); document .querySelectorAll( '[data-hs-theme-click-value]:not(.--prevent-on-load-init)', ) .forEach((el: HTMLElement) => { if ( !window.$hsThemeSwitchCollection.find( (elC) => (elC?.element?.el as HTMLElement) === el, ) ) { new HSThemeSwitch(el, { type: 'click' }); } }); } } export default HSThemeSwitch;