UNPKG

preline

Version:

Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.

235 lines (185 loc) 6.08 kB
/* * HSThemeSwitch * @version: 2.6.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 { IThemeSwitchOptions, IThemeSwitch } from '../theme-switch/interfaces'; import HSBasePlugin from '../base-plugin'; import { ICollectionItem } from '../../interfaces'; class HSThemeSwitch extends HSBasePlugin<IThemeSwitchOptions> implements IThemeSwitch { public theme: string; public type: 'change' | 'click'; private themeSet: string[]; private onElementChangeListener: (evt: Event) => void; private onElementClickListener: () => void; 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.themeSet = ['light', 'dark', 'default']; 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() { window .matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', ({ matches }) => { if (matches) this.setAppearance('dark', false); else this.setAppearance('default', false); }); } private removeSystemThemeObserver() { window.matchMedia('(prefers-color-scheme: dark)').removeEventListener; } 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); if (theme === 'auto') theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default'; html.classList.remove('light', 'dark', 'default', 'auto'); html.classList.add(theme); 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' }); }); } } declare global { interface Window { HSThemeSwitch: Function; $hsThemeSwitchCollection: ICollectionItem<HSThemeSwitch>[]; } } window.addEventListener('load', () => { HSThemeSwitch.autoInit(); // Uncomment for debug // console.log('Theme switch collection:', window.$hsThemeSwitchCollection); }); if (window.$hsThemeSwitchCollection) { window.addEventListener( 'on-hs-appearance-change', (evt: Event & { detail: string }) => { window.$hsThemeSwitchCollection.forEach((el) => { (el.element.el as HTMLInputElement).checked = evt.detail === 'dark'; }); }, ); } if (typeof window !== 'undefined') { window.HSThemeSwitch = HSThemeSwitch; } export default HSThemeSwitch;