darkify-js 1.1.12 → 1.1.13-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/darkify.d.ts +39 -10
- package/dist/darkify.esm.js +98 -30
- package/dist/darkify.umd.js +2 -2
- package/dist/plugins/index.d.ts +54 -0
- package/dist/plugins/index.js +226 -0
- package/dist/plugins/index.umd.js +1 -0
- package/package.json +20 -7
package/dist/darkify.d.ts
CHANGED
|
@@ -1,39 +1,68 @@
|
|
|
1
|
+
interface DarkifyPlugin {
|
|
2
|
+
el?: HTMLElement | ShadowRoot;
|
|
3
|
+
render(): void | HTMLElement | ShadowRoot;
|
|
4
|
+
onThemeChange?: (theme: string) => void;
|
|
5
|
+
onDestroy?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface DarkifyPluginElement {
|
|
9
|
+
new (host: any, options?: object): DarkifyPlugin;
|
|
10
|
+
pluginId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type StorageType = 'local' | 'session' | 'none';
|
|
14
|
+
|
|
1
15
|
interface Options {
|
|
2
16
|
autoMatchTheme: boolean;
|
|
3
|
-
useLocalStorage: boolean;
|
|
4
|
-
useSessionStorage: boolean;
|
|
5
17
|
useColorScheme: [string, string?];
|
|
18
|
+
useStorage: StorageType;
|
|
19
|
+
usePlugins?: (DarkifyPluginElement | [DarkifyPluginElement, any])[];
|
|
6
20
|
}
|
|
7
21
|
|
|
8
22
|
declare class Darkify {
|
|
9
23
|
private static readonly storageKey;
|
|
10
24
|
readonly options: Options;
|
|
25
|
+
private plugins;
|
|
11
26
|
theme: string;
|
|
12
|
-
|
|
13
|
-
_meta
|
|
27
|
+
private _elm;
|
|
28
|
+
private _meta;
|
|
29
|
+
private _style;
|
|
14
30
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param
|
|
31
|
+
* Creates a new Darkify instance for managing dark/light theme
|
|
32
|
+
* @param element - Button ID (recommended) or HTML element selector
|
|
33
|
+
* @param options - Configuration options for customizing behavior
|
|
17
34
|
* @see {@link https://github.com/emrocode/darkify-js/wiki|Documentation}
|
|
18
35
|
*/
|
|
19
36
|
constructor(element: string, options: Partial<Options>);
|
|
20
37
|
private init;
|
|
38
|
+
private initPlugins;
|
|
39
|
+
private notifyPlugins;
|
|
40
|
+
private getStorage;
|
|
21
41
|
private getOsPreference;
|
|
22
42
|
private createAttribute;
|
|
23
43
|
private updateTags;
|
|
24
44
|
private savePreference;
|
|
25
45
|
private syncThemeBetweenTabs;
|
|
46
|
+
private setTheme;
|
|
26
47
|
/**
|
|
27
48
|
* Toggles the theme between light and dark modes
|
|
28
|
-
* @returns {void}
|
|
29
49
|
*/
|
|
30
50
|
toggleTheme(): void;
|
|
31
51
|
/**
|
|
32
|
-
* Retrieves the
|
|
33
|
-
* @returns
|
|
52
|
+
* Retrieves the currently active theme
|
|
53
|
+
* @returns The current theme name ('light' or 'dark')
|
|
34
54
|
*/
|
|
35
55
|
getCurrentTheme(): string;
|
|
56
|
+
/**
|
|
57
|
+
* Destroys the Darkify instance and cleans up all resources
|
|
58
|
+
*
|
|
59
|
+
* Removes all event listeners (system theme changes, click handlers, storage events),
|
|
60
|
+
* destroys all active plugins, removes injected DOM elements (<style> and <meta> tags),
|
|
61
|
+
* and frees associated resources.
|
|
62
|
+
* Call this method when the instance is no longer needed to prevent memory leaks.
|
|
63
|
+
*/
|
|
64
|
+
destroy(): void;
|
|
36
65
|
}
|
|
37
66
|
|
|
38
67
|
export { Darkify as default };
|
|
39
|
-
export type { Options };
|
|
68
|
+
export type { DarkifyPlugin, Options };
|
package/dist/darkify.esm.js
CHANGED
|
@@ -1,30 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
*
|
|
3
3
|
* @author Emilio Romero <emrocode@gmail.com>
|
|
4
|
-
* @version 1.1.
|
|
4
|
+
* @version 1.1.13-beta.0
|
|
5
5
|
* @license MIT
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
class EventListenerManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.listeners = [];
|
|
10
|
+
}
|
|
11
|
+
addListener(target, event, handler, options) {
|
|
12
|
+
target.addEventListener(event, handler, options);
|
|
13
|
+
this.listeners.push({ target, event, handler, options });
|
|
14
|
+
}
|
|
15
|
+
clearListeners() {
|
|
16
|
+
this.listeners.forEach(({ target, event, handler, options }) => {
|
|
17
|
+
target.removeEventListener(event, handler, options);
|
|
18
|
+
});
|
|
19
|
+
this.listeners = [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
8
22
|
|
|
9
23
|
const defaultOptions = {
|
|
10
24
|
autoMatchTheme: true,
|
|
11
|
-
useLocalStorage: true,
|
|
12
|
-
useSessionStorage: false,
|
|
13
25
|
useColorScheme: ['#ffffff', '#000000'],
|
|
26
|
+
useStorage: 'local',
|
|
14
27
|
};
|
|
15
28
|
|
|
29
|
+
const isBrowser = typeof window !== 'undefined';
|
|
30
|
+
|
|
16
31
|
class Darkify {
|
|
17
32
|
constructor(element, options) {
|
|
18
33
|
this.options = defaultOptions;
|
|
34
|
+
this.plugins = [];
|
|
19
35
|
this.theme = 'light';
|
|
20
36
|
if (!isBrowser)
|
|
21
37
|
return;
|
|
38
|
+
this._elm = new EventListenerManager();
|
|
22
39
|
const opts = Object.assign(Object.assign({}, defaultOptions), options);
|
|
23
|
-
if (opts.useLocalStorage && opts.useSessionStorage) {
|
|
24
|
-
opts.useSessionStorage = false;
|
|
25
|
-
}
|
|
26
40
|
this.options = opts;
|
|
27
|
-
this.theme = this.getOsPreference(
|
|
41
|
+
this.theme = this.getOsPreference();
|
|
28
42
|
this._style = document.createElement('style');
|
|
29
43
|
this._meta = document.createElement('meta');
|
|
30
44
|
this.init(element);
|
|
@@ -32,30 +46,59 @@ class Darkify {
|
|
|
32
46
|
this.syncThemeBetweenTabs();
|
|
33
47
|
}
|
|
34
48
|
init(element) {
|
|
35
|
-
window
|
|
36
|
-
.matchMedia('(prefers-color-scheme: dark)')
|
|
37
|
-
.addEventListener('change', ({ matches: isDark }) => {
|
|
49
|
+
this._elm.addListener(window.matchMedia('(prefers-color-scheme: dark)'), 'change', ({ matches: isDark }) => {
|
|
38
50
|
this.theme = isDark ? 'dark' : 'light';
|
|
39
51
|
this.createAttribute();
|
|
40
52
|
});
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
this.initPlugins();
|
|
54
|
+
const hasWidget = this.plugins.some(p => p.el !== undefined);
|
|
55
|
+
if (element && !hasWidget) {
|
|
56
|
+
this._elm.addListener(document, 'DOMContentLoaded', () => {
|
|
43
57
|
const htmlElement = document.querySelector(element);
|
|
44
|
-
|
|
58
|
+
if (htmlElement) {
|
|
59
|
+
this._elm.addListener(htmlElement, 'click', () => this.toggleTheme());
|
|
60
|
+
}
|
|
45
61
|
});
|
|
46
62
|
}
|
|
47
63
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
initPlugins() {
|
|
65
|
+
var _a;
|
|
66
|
+
(_a = this.options.usePlugins) === null || _a === void 0 ? void 0 : _a.forEach(p => {
|
|
67
|
+
const [Plugin, pluginOptions] = Array.isArray(p) ? p : [p, undefined];
|
|
68
|
+
const plugin = new Plugin(this, pluginOptions);
|
|
69
|
+
const renderedNode = plugin.render();
|
|
70
|
+
if (renderedNode instanceof HTMLElement || renderedNode instanceof ShadowRoot) {
|
|
71
|
+
plugin.el = renderedNode;
|
|
72
|
+
}
|
|
73
|
+
this.plugins.push(plugin);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
notifyPlugins(theme) {
|
|
77
|
+
this.plugins.forEach(plugin => {
|
|
78
|
+
var _a;
|
|
79
|
+
(_a = plugin.onThemeChange) === null || _a === void 0 ? void 0 : _a.call(plugin, theme);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
getStorage() {
|
|
83
|
+
const { useStorage } = this.options;
|
|
84
|
+
if (useStorage === 'none')
|
|
85
|
+
return;
|
|
86
|
+
return useStorage === 'local' ? window.localStorage : window.sessionStorage;
|
|
87
|
+
}
|
|
88
|
+
getOsPreference() {
|
|
89
|
+
const storage = this.getStorage();
|
|
90
|
+
if (storage) {
|
|
91
|
+
const stored = storage.getItem(Darkify.storageKey);
|
|
92
|
+
if (stored)
|
|
93
|
+
return stored;
|
|
94
|
+
}
|
|
95
|
+
if (this.options.autoMatchTheme) {
|
|
96
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
97
|
+
}
|
|
98
|
+
return 'light';
|
|
56
99
|
}
|
|
57
100
|
createAttribute() {
|
|
58
|
-
const dataTheme = document.
|
|
101
|
+
const dataTheme = document.documentElement;
|
|
59
102
|
const { useColorScheme } = this.options;
|
|
60
103
|
const css = `/**! Darkify / A simple dark mode toggle library **/\n:root:where([data-theme="${this.theme}"]),[data-theme="${this.theme}"]{color-scheme:${this.theme}}`;
|
|
61
104
|
dataTheme.dataset.theme = this.theme;
|
|
@@ -65,36 +108,61 @@ class Darkify {
|
|
|
65
108
|
updateTags(css, useColorScheme) {
|
|
66
109
|
const [lightColor, darkColor] = useColorScheme;
|
|
67
110
|
this._meta.name = 'theme-color';
|
|
111
|
+
this._meta.media = `(prefers-color-scheme: ${this.theme})`;
|
|
68
112
|
this._meta.content = this.theme === 'light' ? lightColor : (darkColor !== null && darkColor !== void 0 ? darkColor : lightColor);
|
|
69
113
|
this._style.innerHTML = css;
|
|
70
|
-
const head = document.
|
|
114
|
+
const head = document.head;
|
|
71
115
|
if (!this._meta.parentNode)
|
|
72
116
|
head.appendChild(this._meta);
|
|
73
117
|
if (!this._style.parentNode)
|
|
74
118
|
head.appendChild(this._style);
|
|
75
119
|
}
|
|
76
120
|
savePreference() {
|
|
77
|
-
const {
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
const { useStorage } = this.options;
|
|
122
|
+
if (useStorage === 'none')
|
|
123
|
+
return;
|
|
124
|
+
const storage = useStorage === 'local';
|
|
125
|
+
const STO = storage ? window.localStorage : window.sessionStorage;
|
|
126
|
+
const OTS = storage ? window.sessionStorage : window.localStorage;
|
|
80
127
|
OTS.removeItem(Darkify.storageKey);
|
|
81
128
|
STO.setItem(Darkify.storageKey, this.theme);
|
|
82
129
|
}
|
|
83
130
|
syncThemeBetweenTabs() {
|
|
84
|
-
|
|
131
|
+
this._elm.addListener(window, 'storage', (e) => {
|
|
85
132
|
if (e.key === Darkify.storageKey && e.newValue) {
|
|
86
133
|
this.theme = e.newValue;
|
|
87
134
|
this.createAttribute();
|
|
135
|
+
this.notifyPlugins(e.newValue);
|
|
88
136
|
}
|
|
89
137
|
});
|
|
90
138
|
}
|
|
91
|
-
|
|
92
|
-
this.theme =
|
|
139
|
+
setTheme(newTheme) {
|
|
140
|
+
this.theme = newTheme;
|
|
93
141
|
this.createAttribute();
|
|
142
|
+
this.notifyPlugins(newTheme);
|
|
143
|
+
}
|
|
144
|
+
toggleTheme() {
|
|
145
|
+
this.setTheme(this.theme === 'light' ? 'dark' : 'light');
|
|
94
146
|
}
|
|
95
147
|
getCurrentTheme() {
|
|
96
148
|
return this.theme;
|
|
97
149
|
}
|
|
150
|
+
destroy() {
|
|
151
|
+
var _a, _b, _c, _d;
|
|
152
|
+
this._elm.clearListeners();
|
|
153
|
+
(_b = (_a = this._style) === null || _a === void 0 ? void 0 : _a.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(this._style);
|
|
154
|
+
(_d = (_c = this._meta) === null || _c === void 0 ? void 0 : _c.parentNode) === null || _d === void 0 ? void 0 : _d.removeChild(this._meta);
|
|
155
|
+
if (this.plugins.length > 0) {
|
|
156
|
+
this.plugins.forEach(plugin => {
|
|
157
|
+
var _a;
|
|
158
|
+
if (plugin.el) {
|
|
159
|
+
(plugin.el instanceof ShadowRoot ? plugin.el.host : plugin.el).remove();
|
|
160
|
+
}
|
|
161
|
+
(_a = plugin.onDestroy) === null || _a === void 0 ? void 0 : _a.call(plugin);
|
|
162
|
+
});
|
|
163
|
+
this.plugins = [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
98
166
|
}
|
|
99
167
|
Darkify.storageKey = 'theme';
|
|
100
168
|
|
package/dist/darkify.umd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
*
|
|
3
3
|
* @author Emilio Romero <emrocode@gmail.com>
|
|
4
|
-
* @version 1.1.
|
|
4
|
+
* @version 1.1.13-beta.0
|
|
5
5
|
* @license MIT
|
|
6
6
|
*/
|
|
7
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Darkify=t()}(this,function(){"use strict";
|
|
7
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Darkify=t()}(this,function(){"use strict";class e{constructor(){this.listeners=[]}addListener(e,t,s,i){e.addEventListener(t,s,i),this.listeners.push({target:e,event:t,handler:s,options:i})}clearListeners(){this.listeners.forEach(({target:e,event:t,handler:s,options:i})=>{e.removeEventListener(t,s,i)}),this.listeners=[]}}const t={autoMatchTheme:!0,useColorScheme:["#ffffff","#000000"],useStorage:"local"},s="undefined"!=typeof window;class i{constructor(i,n){if(this.options=t,this.plugins=[],this.theme="light",!s)return;this._elm=new e;const o=Object.assign(Object.assign({},t),n);this.options=o,this.theme=this.getOsPreference(),this._style=document.createElement("style"),this._meta=document.createElement("meta"),this.init(i),this.createAttribute(),this.syncThemeBetweenTabs()}init(e){this._elm.addListener(window.matchMedia("(prefers-color-scheme: dark)"),"change",({matches:e})=>{this.theme=e?"dark":"light",this.createAttribute()}),this.initPlugins();const t=this.plugins.some(e=>void 0!==e.el);e&&!t&&this._elm.addListener(document,"DOMContentLoaded",()=>{const t=document.querySelector(e);t&&this._elm.addListener(t,"click",()=>this.toggleTheme())})}initPlugins(){var e;null===(e=this.options.usePlugins)||void 0===e||e.forEach(e=>{const[t,s]=Array.isArray(e)?e:[e,void 0],i=new t(this,s),n=i.render();(n instanceof HTMLElement||n instanceof ShadowRoot)&&(i.el=n),this.plugins.push(i)})}notifyPlugins(e){this.plugins.forEach(t=>{var s;null===(s=t.onThemeChange)||void 0===s||s.call(t,e)})}getStorage(){const{useStorage:e}=this.options;if("none"!==e)return"local"===e?window.localStorage:window.sessionStorage}getOsPreference(){const e=this.getStorage();if(e){const t=e.getItem(i.storageKey);if(t)return t}return this.options.autoMatchTheme&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}createAttribute(){const e=document.documentElement,{useColorScheme:t}=this.options,s=`/**! Darkify / A simple dark mode toggle library **/\n:root:where([data-theme="${this.theme}"]),[data-theme="${this.theme}"]{color-scheme:${this.theme}}`;e.dataset.theme=this.theme,this.updateTags(s,t),this.savePreference()}updateTags(e,t){const[s,i]=t;this._meta.name="theme-color",this._meta.media=`(prefers-color-scheme: ${this.theme})`,this._meta.content="light"===this.theme?s:null!=i?i:s,this._style.innerHTML=e;const n=document.head;this._meta.parentNode||n.appendChild(this._meta),this._style.parentNode||n.appendChild(this._style)}savePreference(){const{useStorage:e}=this.options;if("none"===e)return;const t="local"===e,s=t?window.localStorage:window.sessionStorage;(t?window.sessionStorage:window.localStorage).removeItem(i.storageKey),s.setItem(i.storageKey,this.theme)}syncThemeBetweenTabs(){this._elm.addListener(window,"storage",e=>{e.key===i.storageKey&&e.newValue&&(this.theme=e.newValue,this.createAttribute(),this.notifyPlugins(e.newValue))})}setTheme(e){this.theme=e,this.createAttribute(),this.notifyPlugins(e)}toggleTheme(){this.setTheme("light"===this.theme?"dark":"light")}getCurrentTheme(){return this.theme}destroy(){var e,t,s,i;this._elm.clearListeners(),null===(t=null===(e=this._style)||void 0===e?void 0:e.parentNode)||void 0===t||t.removeChild(this._style),null===(i=null===(s=this._meta)||void 0===s?void 0:s.parentNode)||void 0===i||i.removeChild(this._meta),this.plugins.length>0&&(this.plugins.forEach(e=>{var t;e.el&&(e.el instanceof ShadowRoot?e.el.host:e.el).remove(),null===(t=e.onDestroy)||void 0===t||t.call(e)}),this.plugins=[])}}return i.storageKey="theme",i});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
interface DarkifyPlugin {
|
|
2
|
+
el?: HTMLElement | ShadowRoot;
|
|
3
|
+
render(): void | HTMLElement | ShadowRoot;
|
|
4
|
+
onThemeChange?: (theme: string) => void;
|
|
5
|
+
onDestroy?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ThemeWidgetOptions {
|
|
9
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
10
|
+
size?: 'small' | 'medium' | 'large';
|
|
11
|
+
shortcut?: string;
|
|
12
|
+
}
|
|
13
|
+
declare class ThemeWidget implements DarkifyPlugin {
|
|
14
|
+
el?: ShadowRoot;
|
|
15
|
+
private _host;
|
|
16
|
+
static readonly pluginId = "d-widget";
|
|
17
|
+
private options;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a theme toggle widget button
|
|
20
|
+
* @param host - The darkify instance that controls theme state
|
|
21
|
+
* @param options - Widget configuration (position, size, shortcut)
|
|
22
|
+
*/
|
|
23
|
+
constructor(host: any, options?: ThemeWidgetOptions);
|
|
24
|
+
render(): ShadowRoot;
|
|
25
|
+
onThemeChange(theme: string): void;
|
|
26
|
+
onDestroy(): void;
|
|
27
|
+
private getPositionVars;
|
|
28
|
+
private getSizeVar;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface KeyboardShortcutOptions {
|
|
32
|
+
key?: string;
|
|
33
|
+
ctrl?: boolean;
|
|
34
|
+
shift?: boolean;
|
|
35
|
+
target?: 'body' | 'input' | 'all';
|
|
36
|
+
}
|
|
37
|
+
declare class KeyboardShortcut implements DarkifyPlugin {
|
|
38
|
+
private _host;
|
|
39
|
+
private options;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a keyboard shortcut listener for theme toggling
|
|
42
|
+
* @param host - The darkify instance that controls theme state
|
|
43
|
+
* @param options - Shortcut configuration (key, ctrl, shift, target)
|
|
44
|
+
*/
|
|
45
|
+
constructor(host: any, options?: KeyboardShortcutOptions);
|
|
46
|
+
private handleKeyDown;
|
|
47
|
+
render(): void;
|
|
48
|
+
onDestroy(): void;
|
|
49
|
+
private matches;
|
|
50
|
+
private isTyping;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { KeyboardShortcut, ThemeWidget };
|
|
54
|
+
export type { KeyboardShortcutOptions, ThemeWidgetOptions };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
class ThemeWidget {
|
|
2
|
+
constructor(host, options) {
|
|
3
|
+
var _a, _b, _c;
|
|
4
|
+
this._host = host;
|
|
5
|
+
this.options = {
|
|
6
|
+
position: (_a = options === null || options === void 0 ? void 0 : options.position) !== null && _a !== void 0 ? _a : 'bottom-right',
|
|
7
|
+
size: (_b = options === null || options === void 0 ? void 0 : options.size) !== null && _b !== void 0 ? _b : 'medium',
|
|
8
|
+
shortcut: (_c = options === null || options === void 0 ? void 0 : options.shortcut) !== null && _c !== void 0 ? _c : '',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
render() {
|
|
12
|
+
var _a;
|
|
13
|
+
const container = document.createElement('div');
|
|
14
|
+
container.id = ThemeWidget.pluginId;
|
|
15
|
+
this.el = container.attachShadow({ mode: 'open' });
|
|
16
|
+
const positionVars = this.getPositionVars();
|
|
17
|
+
const sizeVars = this.getSizeVar();
|
|
18
|
+
const sheet = new CSSStyleSheet();
|
|
19
|
+
sheet.replaceSync(`
|
|
20
|
+
:host {
|
|
21
|
+
--widget-safe-top: env(safe-area-inset-top, 0px);
|
|
22
|
+
--widget-safe-right: env(safe-area-inset-right, 0px);
|
|
23
|
+
--widget-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
24
|
+
--widget-safe-left: env(safe-area-inset-left, 0px);
|
|
25
|
+
--widget-margin: 24px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.d-wrapper {
|
|
29
|
+
--margin: max(var(--widget-margin), var(--widget-safe-bottom));
|
|
30
|
+
${positionVars}
|
|
31
|
+
${sizeVars}
|
|
32
|
+
position: fixed;
|
|
33
|
+
top: calc(var(--pos-top) + var(--widget-safe-top));
|
|
34
|
+
right: calc(var(--pos-right) + var(--widget-safe-right));
|
|
35
|
+
bottom: calc(var(--pos-bottom) + var(--widget-safe-bottom));
|
|
36
|
+
left: calc(var(--pos-left) + var(--widget-safe-left));
|
|
37
|
+
z-index: 9999;
|
|
38
|
+
max-width: fit-content;
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
column-gap: 0.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.d-button {
|
|
45
|
+
--icon-size: calc(var(--size) * 0.4);
|
|
46
|
+
position: relative;
|
|
47
|
+
width: var(--size);
|
|
48
|
+
height: var(--size);
|
|
49
|
+
border-radius: 50%;
|
|
50
|
+
border: none;
|
|
51
|
+
border-bottom: 2px solid var(--widget-accent, hsl(0,0%,0%,0.30));
|
|
52
|
+
box-shadow: 0 1px 3px 0 hsla(210,6%,25%,0.30), 0 4px 8px 3px hsla(210,6%,25%,0.30);
|
|
53
|
+
background-color: transparent;
|
|
54
|
+
color: canvastext;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
font-size: var(--icon-size);
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
overflow: hidden;
|
|
62
|
+
transition: transform 0.4s ease-in-out;
|
|
63
|
+
user-select: none;
|
|
64
|
+
-webkit-user-select: none;
|
|
65
|
+
-webkit-tap-highlight-color: transparent;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.d-button::before {
|
|
69
|
+
content: "";
|
|
70
|
+
position: absolute;
|
|
71
|
+
inset: 0;
|
|
72
|
+
z-index: -1;
|
|
73
|
+
border: none;
|
|
74
|
+
border-radius: 50%;
|
|
75
|
+
background-color: canvas;
|
|
76
|
+
filter: invert(90%);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.d-button.light { --widget-accent: hsl(45,99%,54%); }
|
|
80
|
+
.d-button.dark { --widget-accent: hsl(200,29%,43%); }
|
|
81
|
+
|
|
82
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
83
|
+
.d-button {
|
|
84
|
+
animation: dEnter 0.4s cubic-bezier(0.34,1.56,0.64,1);
|
|
85
|
+
animation-fill-mode: backwards;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes dEnter {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
transform: scale(0.6);
|
|
93
|
+
}
|
|
94
|
+
to {
|
|
95
|
+
opacity: 1;
|
|
96
|
+
transform: scale(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.d-kbd {
|
|
101
|
+
position: relative;
|
|
102
|
+
padding: 0.25em 0.4em;
|
|
103
|
+
font-size: 11px;
|
|
104
|
+
font-family: ui-monospace, monospace;
|
|
105
|
+
line-height: 1;
|
|
106
|
+
letter-spacing: -0.025em;
|
|
107
|
+
background-color: canvas;
|
|
108
|
+
color: canvastext;
|
|
109
|
+
filter: invert(90%);
|
|
110
|
+
border: none;
|
|
111
|
+
border-bottom: 2px solid hsl(from canvastext h s l / calc(alpha * 0.5));
|
|
112
|
+
border-radius: 0.25rem;
|
|
113
|
+
box-shadow: 0 0 2px hsla(0,0%,0%,0.10);
|
|
114
|
+
user-select: none;
|
|
115
|
+
-webkit-user-select: none;
|
|
116
|
+
}
|
|
117
|
+
`);
|
|
118
|
+
this.el.adoptedStyleSheets = [sheet];
|
|
119
|
+
const wrapper = document.createElement('div');
|
|
120
|
+
wrapper.className = 'd-wrapper';
|
|
121
|
+
const button = document.createElement('button');
|
|
122
|
+
button.className = 'd-button';
|
|
123
|
+
button.setAttribute('aria-label', 'Toggle theme');
|
|
124
|
+
button.addEventListener('click', () => this._host.toggleTheme());
|
|
125
|
+
const icon = document.createElement('span');
|
|
126
|
+
icon.className = 'd-icon';
|
|
127
|
+
button.appendChild(icon);
|
|
128
|
+
const isLeftPosition = (_a = this.options.position) === null || _a === void 0 ? void 0 : _a.includes('left');
|
|
129
|
+
if (this.options.shortcut) {
|
|
130
|
+
const kbd = document.createElement('kbd');
|
|
131
|
+
kbd.className = 'd-kbd';
|
|
132
|
+
kbd.textContent = this.options.shortcut;
|
|
133
|
+
if (isLeftPosition) {
|
|
134
|
+
wrapper.appendChild(button);
|
|
135
|
+
wrapper.appendChild(kbd);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
wrapper.appendChild(kbd);
|
|
139
|
+
wrapper.appendChild(button);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
wrapper.appendChild(button);
|
|
144
|
+
}
|
|
145
|
+
this.el.appendChild(wrapper);
|
|
146
|
+
document.body.appendChild(container);
|
|
147
|
+
this.onThemeChange(this._host.getCurrentTheme());
|
|
148
|
+
return this.el;
|
|
149
|
+
}
|
|
150
|
+
onThemeChange(theme) {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
const button = (_a = this.el) === null || _a === void 0 ? void 0 : _a.querySelector('button.d-button');
|
|
153
|
+
const icon = (_b = this.el) === null || _b === void 0 ? void 0 : _b.querySelector('span.d-icon');
|
|
154
|
+
if (button && icon) {
|
|
155
|
+
button.className = `d-button ${theme}`;
|
|
156
|
+
icon.textContent = theme === 'light' ? '🌞' : '🌚';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
onDestroy() {
|
|
160
|
+
if (this.el && this.el.host) {
|
|
161
|
+
this.el.host.remove();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getPositionVars() {
|
|
165
|
+
const vars = {
|
|
166
|
+
'top-left': '--pos-top:var(--margin);--pos-right:auto;--pos-bottom:auto;--pos-left:var(--margin);',
|
|
167
|
+
'top-right': '--pos-top:var(--margin);--pos-right:var(--margin);--pos-bottom:auto;--pos-left:auto;',
|
|
168
|
+
'bottom-left': '--pos-top:auto;--pos-right:auto;--pos-bottom:calc(var(--margin)*2);--pos-left:var(--margin);',
|
|
169
|
+
'bottom-right': '--pos-top:auto;--pos-right:var(--margin);--pos-bottom:calc(var(--margin)*2);--pos-left:auto;',
|
|
170
|
+
};
|
|
171
|
+
return vars[this.options.position];
|
|
172
|
+
}
|
|
173
|
+
getSizeVar() {
|
|
174
|
+
const vars = {
|
|
175
|
+
small: '--size:36px;',
|
|
176
|
+
medium: '--size:56px;',
|
|
177
|
+
large: '--size:72px;',
|
|
178
|
+
};
|
|
179
|
+
return vars[this.options.size];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
ThemeWidget.pluginId = 'd-widget';
|
|
183
|
+
|
|
184
|
+
class KeyboardShortcut {
|
|
185
|
+
constructor(host, options) {
|
|
186
|
+
var _a, _b, _c, _d;
|
|
187
|
+
this.handleKeyDown = (e) => {
|
|
188
|
+
if (this.options.target === 'body' && this.isTyping(e))
|
|
189
|
+
return;
|
|
190
|
+
if (this.options.target === 'input' && !this.isTyping(e))
|
|
191
|
+
return;
|
|
192
|
+
if (this.matches(e)) {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
this._host.toggleTheme();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
this._host = host;
|
|
198
|
+
this.options = {
|
|
199
|
+
key: (_a = options === null || options === void 0 ? void 0 : options.key) !== null && _a !== void 0 ? _a : 'd',
|
|
200
|
+
ctrl: (_b = options === null || options === void 0 ? void 0 : options.ctrl) !== null && _b !== void 0 ? _b : false,
|
|
201
|
+
shift: (_c = options === null || options === void 0 ? void 0 : options.shift) !== null && _c !== void 0 ? _c : false,
|
|
202
|
+
target: (_d = options === null || options === void 0 ? void 0 : options.target) !== null && _d !== void 0 ? _d : 'body',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
render() {
|
|
206
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
onDestroy() {
|
|
210
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
211
|
+
}
|
|
212
|
+
matches(e) {
|
|
213
|
+
return (e.key.toLowerCase() === this.options.key.toLowerCase() &&
|
|
214
|
+
(!this.options.ctrl || e.ctrlKey || e.metaKey) &&
|
|
215
|
+
(!this.options.shift || e.shiftKey) &&
|
|
216
|
+
(this.options.ctrl || (!e.ctrlKey && !e.metaKey && !e.altKey)));
|
|
217
|
+
}
|
|
218
|
+
isTyping(e) {
|
|
219
|
+
const target = e.target;
|
|
220
|
+
const tagName = target.tagName.toLowerCase();
|
|
221
|
+
const isEditable = target.isContentEditable || tagName === 'input' || tagName === 'textarea';
|
|
222
|
+
return isEditable;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { KeyboardShortcut, ThemeWidget };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).DarkifyPlugins={})}(this,function(t){"use strict";class e{constructor(t,e){var n,o,i;this._host=t,this.options={position:null!==(n=null==e?void 0:e.position)&&void 0!==n?n:"bottom-right",size:null!==(o=null==e?void 0:e.size)&&void 0!==o?o:"medium",shortcut:null!==(i=null==e?void 0:e.shortcut)&&void 0!==i?i:""}}render(){var t;const n=document.createElement("div");n.id=e.pluginId,this.el=n.attachShadow({mode:"open"});const o=this.getPositionVars(),i=this.getSizeVar(),s=new CSSStyleSheet;s.replaceSync(`\n :host {\n --widget-safe-top: env(safe-area-inset-top, 0px);\n --widget-safe-right: env(safe-area-inset-right, 0px);\n --widget-safe-bottom: env(safe-area-inset-bottom, 0px);\n --widget-safe-left: env(safe-area-inset-left, 0px);\n --widget-margin: 24px;\n }\n\n .d-wrapper {\n --margin: max(var(--widget-margin), var(--widget-safe-bottom));\n ${o}\n ${i}\n position: fixed;\n top: calc(var(--pos-top) + var(--widget-safe-top));\n right: calc(var(--pos-right) + var(--widget-safe-right));\n bottom: calc(var(--pos-bottom) + var(--widget-safe-bottom));\n left: calc(var(--pos-left) + var(--widget-safe-left));\n z-index: 9999;\n max-width: fit-content;\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n }\n\n .d-button {\n --icon-size: calc(var(--size) * 0.4);\n position: relative;\n width: var(--size);\n height: var(--size);\n border-radius: 50%;\n border: none;\n border-bottom: 2px solid var(--widget-accent, hsl(0,0%,0%,0.30));\n box-shadow: 0 1px 3px 0 hsla(210,6%,25%,0.30), 0 4px 8px 3px hsla(210,6%,25%,0.30);\n background-color: transparent;\n color: canvastext;\n cursor: pointer;\n font-size: var(--icon-size);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n transition: transform 0.4s ease-in-out;\n user-select: none;\n -webkit-user-select: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n .d-button::before {\n content: "";\n position: absolute;\n inset: 0;\n z-index: -1;\n border: none;\n border-radius: 50%;\n background-color: canvas;\n filter: invert(90%);\n }\n\n .d-button.light { --widget-accent: hsl(45,99%,54%); }\n .d-button.dark { --widget-accent: hsl(200,29%,43%); }\n\n @media (prefers-reduced-motion: no-preference) {\n .d-button {\n animation: dEnter 0.4s cubic-bezier(0.34,1.56,0.64,1);\n animation-fill-mode: backwards;\n }\n }\n\n @keyframes dEnter {\n from {\n opacity: 0;\n transform: scale(0.6);\n }\n to {\n opacity: 1;\n transform: scale(1);\n }\n }\n\n .d-kbd {\n position: relative;\n padding: 0.25em 0.4em;\n font-size: 11px;\n font-family: ui-monospace, monospace;\n line-height: 1;\n letter-spacing: -0.025em;\n background-color: canvas;\n color: canvastext;\n filter: invert(90%);\n border: none;\n border-bottom: 2px solid hsl(from canvastext h s l / calc(alpha * 0.5));\n border-radius: 0.25rem;\n box-shadow: 0 0 2px hsla(0,0%,0%,0.10);\n user-select: none;\n -webkit-user-select: none;\n }\n `),this.el.adoptedStyleSheets=[s];const a=document.createElement("div");a.className="d-wrapper";const r=document.createElement("button");r.className="d-button",r.setAttribute("aria-label","Toggle theme"),r.addEventListener("click",()=>this._host.toggleTheme());const l=document.createElement("span");l.className="d-icon",r.appendChild(l);const d=null===(t=this.options.position)||void 0===t?void 0:t.includes("left");if(this.options.shortcut){const t=document.createElement("kbd");t.className="d-kbd",t.textContent=this.options.shortcut,d?(a.appendChild(r),a.appendChild(t)):(a.appendChild(t),a.appendChild(r))}else a.appendChild(r);return this.el.appendChild(a),document.body.appendChild(n),this.onThemeChange(this._host.getCurrentTheme()),this.el}onThemeChange(t){var e,n;const o=null===(e=this.el)||void 0===e?void 0:e.querySelector("button.d-button"),i=null===(n=this.el)||void 0===n?void 0:n.querySelector("span.d-icon");o&&i&&(o.className=`d-button ${t}`,i.textContent="light"===t?"🌞":"🌚")}onDestroy(){this.el&&this.el.host&&this.el.host.remove()}getPositionVars(){return{"top-left":"--pos-top:var(--margin);--pos-right:auto;--pos-bottom:auto;--pos-left:var(--margin);","top-right":"--pos-top:var(--margin);--pos-right:var(--margin);--pos-bottom:auto;--pos-left:auto;","bottom-left":"--pos-top:auto;--pos-right:auto;--pos-bottom:calc(var(--margin)*2);--pos-left:var(--margin);","bottom-right":"--pos-top:auto;--pos-right:var(--margin);--pos-bottom:calc(var(--margin)*2);--pos-left:auto;"}[this.options.position]}getSizeVar(){return{small:"--size:36px;",medium:"--size:56px;",large:"--size:72px;"}[this.options.size]}}e.pluginId="d-widget";t.KeyboardShortcut=class{constructor(t,e){var n,o,i,s;this.handleKeyDown=t=>{"body"===this.options.target&&this.isTyping(t)||("input"!==this.options.target||this.isTyping(t))&&this.matches(t)&&(t.preventDefault(),this._host.toggleTheme())},this._host=t,this.options={key:null!==(n=null==e?void 0:e.key)&&void 0!==n?n:"d",ctrl:null!==(o=null==e?void 0:e.ctrl)&&void 0!==o&&o,shift:null!==(i=null==e?void 0:e.shift)&&void 0!==i&&i,target:null!==(s=null==e?void 0:e.target)&&void 0!==s?s:"body"}}render(){document.addEventListener("keydown",this.handleKeyDown)}onDestroy(){document.removeEventListener("keydown",this.handleKeyDown)}matches(t){return t.key.toLowerCase()===this.options.key.toLowerCase()&&(!this.options.ctrl||t.ctrlKey||t.metaKey)&&(!this.options.shift||t.shiftKey)&&(this.options.ctrl||!t.ctrlKey&&!t.metaKey&&!t.altKey)}isTyping(t){const e=t.target,n=e.tagName.toLowerCase();return e.isContentEditable||"input"===n||"textarea"===n}},t.ThemeWidget=e});
|
package/package.json
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "darkify-js",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.13-beta.0",
|
|
4
4
|
"description": "A simple dark mode toggle library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/darkify.umd.js",
|
|
7
7
|
"module": "dist/darkify.esm.js",
|
|
8
8
|
"types": "dist/darkify.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/darkify.esm.js",
|
|
12
|
+
"require": "./dist/darkify.umd.js",
|
|
13
|
+
"types": "./dist/darkify.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./plugins": {
|
|
16
|
+
"import": "./dist/plugins/index.mjs",
|
|
17
|
+
"require": "./dist/plugins/index.umd.js",
|
|
18
|
+
"types": "./dist/plugins/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
9
21
|
"files": [
|
|
10
22
|
"dist"
|
|
11
23
|
],
|
|
@@ -14,7 +26,8 @@
|
|
|
14
26
|
"_bundle": "rollup -c rollup.config.ts --configPlugin typescript",
|
|
15
27
|
"build": "npm run _cls && npm run _bundle",
|
|
16
28
|
"format": "prettier --write src/",
|
|
17
|
-
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
|
29
|
+
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest",
|
|
30
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
18
31
|
},
|
|
19
32
|
"repository": {
|
|
20
33
|
"type": "git",
|
|
@@ -33,15 +46,15 @@
|
|
|
33
46
|
"author": "Emilio Romero <emrocode@gmail.com>",
|
|
34
47
|
"license": "MIT",
|
|
35
48
|
"devDependencies": {
|
|
36
|
-
"@rollup/plugin-terser": "^0.
|
|
49
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
37
50
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
38
51
|
"@types/jest": "^30.0.0",
|
|
39
|
-
"jest": "^30.
|
|
40
|
-
"jest-environment-jsdom": "^30.
|
|
52
|
+
"jest": "^30.3.0",
|
|
53
|
+
"jest-environment-jsdom": "^30.3.0",
|
|
41
54
|
"prettier": "^3.8.1",
|
|
42
|
-
"rollup": "^4.
|
|
55
|
+
"rollup": "^4.59.0",
|
|
43
56
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
44
|
-
"rollup-plugin-dts": "^6.
|
|
57
|
+
"rollup-plugin-dts": "^6.4.0",
|
|
45
58
|
"ts-jest": "^29.4.6",
|
|
46
59
|
"ts-node": "^10.9.2",
|
|
47
60
|
"tslib": "^2.8.1",
|