preline
Version:
Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.
190 lines (152 loc) • 4.86 kB
text/typescript
/*
* HSThemeSwitch
* @version: 2.5.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;
private readonly themeSet: string[];
constructor(el: HTMLElement, 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.themeSet = ['light', 'dark', 'default'];
this.init();
}
private init() {
this.createCollection(window.$hsThemeSwitchCollection, this);
if (this.theme !== 'default') this.setAppearance();
}
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;
}
// 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 }),
);
}
// Static methods
static getInstance(target: HTMLElement | string) {
const elInCollection = window.$hsThemeSwitchCollection.find(
(el) =>
el.element.el ===
(typeof target === 'string' ? document.querySelector(target) : target),
);
return elInCollection ? elInCollection.element : null;
}
static autoInit() {
if (!window.$hsThemeSwitchCollection) window.$hsThemeSwitchCollection = [];
const toggleObserveSystemTheme = (el: HSThemeSwitch) => {
if (localStorage.getItem('hs_theme') === 'auto')
el.addSystemThemeObserver();
else el.removeSystemThemeObserver();
};
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,
)
) {
const switchTheme = new HSThemeSwitch(el);
(switchTheme.el as HTMLInputElement).checked =
switchTheme.theme === 'dark';
toggleObserveSystemTheme(switchTheme);
switchTheme.el.addEventListener('change', (evt) => {
const theme = (evt.target as HTMLInputElement).checked
? 'dark'
: 'default';
switchTheme.setAppearance(theme);
toggleObserveSystemTheme(switchTheme);
});
}
});
document
.querySelectorAll(
'[data-hs-theme-click-value]:not(.--prevent-on-load-init)',
)
.forEach((el: HTMLElement) => {
const theme = el.getAttribute('data-hs-theme-click-value');
const switchTheme = new HSThemeSwitch(el);
toggleObserveSystemTheme(switchTheme);
switchTheme.el.addEventListener('click', () => {
switchTheme.setAppearance(theme);
toggleObserveSystemTheme(switchTheme);
});
});
}
}
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;