@gottheflag/nova 0.1.0-beta.2 → 0.1.0-beta.4
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/LICENSE +201 -0
- package/README.md +28 -4
- package/dist/adapters/index.d.ts +24 -23
- package/dist/adapters/index.js +74 -103
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.d.ts +130 -71
- package/dist/core/index.js +155 -39
- package/dist/core/index.js.map +1 -1
- package/dist/loader/index.min.js +1 -1
- package/dist/storage/index.d.ts +87 -0
- package/dist/storage/index.js +88 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage-n1c3g6vy.d.ts +51 -0
- package/package.json +6 -2
- package/dist/chunk-7QVYU63E.js +0 -6
- package/dist/chunk-7QVYU63E.js.map +0 -1
package/dist/core/index.d.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) 2026 GTF
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Effective theme name (e.g. `day`, `night`, `mint`).
|
|
7
|
-
*/
|
|
8
|
-
type Name = string;
|
|
9
|
-
/**
|
|
10
|
-
* Theme state (`<dark>`, `<light>`, `<system>`).
|
|
11
|
-
* Different between state themes and effective themes:
|
|
12
|
-
* - State themes are the raw values used as identifiers (`<dark>`, `<light>`, `<system>`).
|
|
13
|
-
* - Effective themes are the resolved values (e.g. `<dark>`, `<light>`, `mint`).
|
|
14
|
-
*/
|
|
15
|
-
type State = "system" | "light" | "dark";
|
|
1
|
+
import { S as StateStorage, N as Name, b as NEvents, a as State } from '../storage-n1c3g6vy.js';
|
|
16
2
|
|
|
17
3
|
/**
|
|
18
4
|
* Copyright (c) 2026 GTF
|
|
@@ -23,27 +9,89 @@ type State = "system" | "light" | "dark";
|
|
|
23
9
|
* Raw configuration, passed by the user.
|
|
24
10
|
*/
|
|
25
11
|
interface RawConfig {
|
|
12
|
+
/**
|
|
13
|
+
* Theme attribute name.
|
|
14
|
+
*
|
|
15
|
+
* @default "data-theme"
|
|
16
|
+
* @example
|
|
17
|
+
* <html data-theme="light">
|
|
18
|
+
*/
|
|
26
19
|
attribute?: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Theme storage type.
|
|
22
|
+
*
|
|
23
|
+
* @description
|
|
24
|
+
* The controller will use the storage to persist the theme state.
|
|
25
|
+
*
|
|
26
|
+
* @default @see {@link LocalStorage}
|
|
27
|
+
*/
|
|
28
|
+
storage?: StateStorage;
|
|
29
|
+
/**
|
|
30
|
+
* System theme name.
|
|
31
|
+
*
|
|
32
|
+
* @default "system"
|
|
33
|
+
* @example "auto" | "sync" | ...
|
|
34
|
+
*/
|
|
30
35
|
system?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Light theme name.
|
|
38
|
+
*
|
|
39
|
+
* @default "light"
|
|
40
|
+
* @example "bright" | ...
|
|
41
|
+
*/
|
|
31
42
|
light?: Name;
|
|
43
|
+
/**
|
|
44
|
+
* Dark theme name.
|
|
45
|
+
*
|
|
46
|
+
* @default "dark"
|
|
47
|
+
* @example "dim" | ...
|
|
48
|
+
*/
|
|
32
49
|
dark?: Name;
|
|
50
|
+
/**
|
|
51
|
+
* Initial theme name.
|
|
52
|
+
*
|
|
53
|
+
* @default "dark"
|
|
54
|
+
* @example "light" | "mint" | ...
|
|
55
|
+
*/
|
|
33
56
|
initial?: Name;
|
|
57
|
+
/**
|
|
58
|
+
* Observe the DOM elements for changes.
|
|
59
|
+
* Automatically bind new matching elements.
|
|
60
|
+
*
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
observe?: boolean;
|
|
34
64
|
}
|
|
35
65
|
/**
|
|
36
66
|
* Resolved configuration by the controller.
|
|
37
67
|
*/
|
|
38
68
|
interface ResolvedConfig {
|
|
39
69
|
attribute: string;
|
|
40
|
-
storage:
|
|
41
|
-
key: string;
|
|
42
|
-
};
|
|
70
|
+
storage: StateStorage;
|
|
43
71
|
system: string;
|
|
44
72
|
light: Name;
|
|
45
73
|
dark: Name;
|
|
46
74
|
initial?: Name;
|
|
75
|
+
observe: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Copyright (c) 2026 GTF
|
|
80
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
81
|
+
*/
|
|
82
|
+
type Listener<T> = (event: T) => void;
|
|
83
|
+
/**
|
|
84
|
+
* A simple event emitter implementation.
|
|
85
|
+
*
|
|
86
|
+
* @description
|
|
87
|
+
* This class is suitable for use in a web application that wants to
|
|
88
|
+
* listen for changes in the theme state.
|
|
89
|
+
*/
|
|
90
|
+
declare class EventEmitter<M extends Record<string, unknown>> {
|
|
91
|
+
private _map;
|
|
92
|
+
on<K extends keyof M>(event: K, listener: Listener<M[K]>): () => void;
|
|
93
|
+
off<K extends keyof M>(event: K, listener: Listener<M[K]>): void;
|
|
94
|
+
emit<K extends keyof M>(event: K, payload: M[K]): void;
|
|
47
95
|
}
|
|
48
96
|
|
|
49
97
|
/**
|
|
@@ -54,7 +102,7 @@ interface ResolvedConfig {
|
|
|
54
102
|
/**
|
|
55
103
|
* Theme controller logic.
|
|
56
104
|
*/
|
|
57
|
-
declare class Controller {
|
|
105
|
+
declare class Controller extends EventEmitter<NEvents> {
|
|
58
106
|
private _root;
|
|
59
107
|
/**
|
|
60
108
|
* Resolved configuration.
|
|
@@ -64,7 +112,8 @@ declare class Controller {
|
|
|
64
112
|
* System theme proxy.
|
|
65
113
|
*/
|
|
66
114
|
private system;
|
|
67
|
-
private
|
|
115
|
+
private _adapters;
|
|
116
|
+
private _observer?;
|
|
68
117
|
constructor(_root?: Document | ParentNode, cfg?: RawConfig);
|
|
69
118
|
/**
|
|
70
119
|
* The root element to apply theme to.
|
|
@@ -103,18 +152,29 @@ declare class Controller {
|
|
|
103
152
|
* @param adapter Adapter instance
|
|
104
153
|
* @param options Adapter options
|
|
105
154
|
*/
|
|
106
|
-
use<
|
|
155
|
+
use<O>(def: AdapterDefinition<any, O>, options?: Partial<O>): this;
|
|
107
156
|
/**
|
|
108
157
|
* Set the effective theme from a theme state.
|
|
109
158
|
*
|
|
110
159
|
* @see {@link State}
|
|
111
160
|
* @param state Theme State
|
|
112
161
|
*/
|
|
113
|
-
set(state: State):
|
|
162
|
+
set(state: State): this;
|
|
114
163
|
/**
|
|
115
164
|
* Read the stored theme state from local storage.
|
|
116
165
|
*/
|
|
117
166
|
get state(): State | null;
|
|
167
|
+
/**
|
|
168
|
+
* Destroy the controller.
|
|
169
|
+
*
|
|
170
|
+
* @description
|
|
171
|
+
* This method disconnects the observer, stops the system listener.
|
|
172
|
+
*/
|
|
173
|
+
destroy(): void;
|
|
174
|
+
/**
|
|
175
|
+
* Sync all adapters.
|
|
176
|
+
*/
|
|
177
|
+
private syncAdapters;
|
|
118
178
|
/**
|
|
119
179
|
* Sync the system listener.
|
|
120
180
|
*/
|
|
@@ -131,6 +191,25 @@ declare class Controller {
|
|
|
131
191
|
* @param theme Theme state
|
|
132
192
|
*/
|
|
133
193
|
private write;
|
|
194
|
+
/**
|
|
195
|
+
* Start the DOM elements observer.
|
|
196
|
+
*
|
|
197
|
+
* @description
|
|
198
|
+
* This method starts a MutationObserver that watches for changes to
|
|
199
|
+
* the DOM elements that are registered as adapters. When a change
|
|
200
|
+
* is detected, the controller will bind the element to the adapter.
|
|
201
|
+
*/
|
|
202
|
+
private startObserver;
|
|
203
|
+
/**
|
|
204
|
+
* Bind a single DOM element to an adapter.
|
|
205
|
+
*
|
|
206
|
+
* @description
|
|
207
|
+
* This method checks if the element matches the adapter's selector.
|
|
208
|
+
* If it does, the adapter's `bind` method is called with the element
|
|
209
|
+
* and the adapter's options. If the adapter has a `sync` method, it
|
|
210
|
+
* is also called with the element and the adapter's options.
|
|
211
|
+
*/
|
|
212
|
+
private bindNode;
|
|
134
213
|
}
|
|
135
214
|
|
|
136
215
|
/**
|
|
@@ -138,56 +217,36 @@ declare class Controller {
|
|
|
138
217
|
* SPDX-License-Identifier: Apache-2.0
|
|
139
218
|
*/
|
|
140
219
|
|
|
141
|
-
interface
|
|
220
|
+
interface AdapterDefinition<E extends Element, O = void> {
|
|
142
221
|
name: string;
|
|
222
|
+
/** Default options — merged with options passed to `ctl.use()`. */
|
|
223
|
+
defaults?: Partial<O>;
|
|
224
|
+
/** CSS selector string, or a function that returns one given resolved options. */
|
|
225
|
+
selector: string | ((options: O) => string);
|
|
143
226
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* ---
|
|
148
|
-
* @remarks
|
|
149
|
-
* Called once, when the controller is instantiated.
|
|
150
|
-
*
|
|
151
|
-
* ---
|
|
152
|
-
* @param ctl Controller instance
|
|
153
|
-
* @param options Adapter options
|
|
227
|
+
* Attach event listeners to a single element.
|
|
228
|
+
* Called once per element — at `use()` time, or when a new element is observed.
|
|
154
229
|
*/
|
|
155
|
-
|
|
230
|
+
bind?(ctl: Controller, element: E, options: O): void;
|
|
156
231
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* ---
|
|
161
|
-
* @remarks
|
|
162
|
-
* Called once, when the controller is instantiated.
|
|
163
|
-
*
|
|
164
|
-
* ---
|
|
165
|
-
* @param ctl the controller instance
|
|
166
|
-
* @returns a list of themes that are available to this adapter
|
|
232
|
+
* Sync element UI state with the controller's current theme.
|
|
233
|
+
* Called after every `ctl.set()` and on initial `use()`.
|
|
167
234
|
*/
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Define the theme selection mechanism.
|
|
171
|
-
*
|
|
172
|
-
* ---
|
|
173
|
-
* @remarks
|
|
174
|
-
* Called every time the controller is instantiated.
|
|
175
|
-
*
|
|
176
|
-
* ---
|
|
177
|
-
* @param ctl the controller instance
|
|
178
|
-
*/
|
|
179
|
-
bind?(ctl: Controller): void;
|
|
180
|
-
/**
|
|
181
|
-
* Sync theme adapter state with the controller.
|
|
182
|
-
*
|
|
183
|
-
* ---
|
|
184
|
-
* @remarks
|
|
185
|
-
* Called every time the controller is instantiated.
|
|
186
|
-
*
|
|
187
|
-
* ---
|
|
188
|
-
* @param ctl the controller instance
|
|
189
|
-
*/
|
|
190
|
-
sync?(ctl: Controller): void;
|
|
235
|
+
sync?(ctl: Controller, element: E, options: O): void;
|
|
191
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Type helper for defining adapters with full inference.
|
|
239
|
+
* Zero runtime cost — returns the definition unchanged.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* export const MyAdapter = defineAdapter<HTMLButtonElement, MyOptions>({
|
|
243
|
+
* name: "my-adapter",
|
|
244
|
+
* defaults: { prefix: "theme:" },
|
|
245
|
+
* selector: (o) => `button[value="${o.prefix}"]`,
|
|
246
|
+
* bind(ctl, el, options) { ... },
|
|
247
|
+
* sync(ctl, el, options) { ... },
|
|
248
|
+
* });
|
|
249
|
+
*/
|
|
250
|
+
declare function defineAdapter<E extends Element, O = void>(definition: AdapterDefinition<E, O>): AdapterDefinition<E, O>;
|
|
192
251
|
|
|
193
|
-
export { type
|
|
252
|
+
export { type AdapterDefinition, Controller, EventEmitter, Name, type ResolvedConfig, State, defineAdapter };
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/storage/local.ts
|
|
5
|
+
var LocalStorage = class {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "LocalStorage");
|
|
8
|
+
}
|
|
9
|
+
key;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.key = options.key ?? "theme";
|
|
12
|
+
}
|
|
13
|
+
get() {
|
|
14
|
+
try {
|
|
15
|
+
return localStorage.getItem(this.key);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
set(state) {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem(this.key, state);
|
|
23
|
+
} catch {
|
|
24
|
+
console.warn("[LOCAL] Failed to write theme state.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
remove() {
|
|
28
|
+
try {
|
|
29
|
+
localStorage.removeItem(this.key);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/core/event.ts
|
|
36
|
+
var EventEmitter = class {
|
|
37
|
+
static {
|
|
38
|
+
__name(this, "EventEmitter");
|
|
39
|
+
}
|
|
40
|
+
_map = /* @__PURE__ */ new Map();
|
|
41
|
+
on(event, listener) {
|
|
42
|
+
let set = this._map.get(event);
|
|
43
|
+
if (!set) this._map.set(event, set = /* @__PURE__ */ new Set());
|
|
44
|
+
set.add(listener);
|
|
45
|
+
return () => this.off(event, listener);
|
|
46
|
+
}
|
|
47
|
+
off(event, listener) {
|
|
48
|
+
this._map.get(event)?.delete(listener);
|
|
49
|
+
}
|
|
50
|
+
emit(event, payload) {
|
|
51
|
+
this._map.get(event)?.forEach((listener) => listener(payload));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
2
54
|
|
|
3
55
|
// src/core/system.ts
|
|
4
56
|
var System = class {
|
|
@@ -83,7 +135,7 @@ function resolveRoot(root) {
|
|
|
83
135
|
__name(resolveRoot, "resolveRoot");
|
|
84
136
|
|
|
85
137
|
// src/core/controller.ts
|
|
86
|
-
var Controller = class {
|
|
138
|
+
var Controller = class extends EventEmitter {
|
|
87
139
|
static {
|
|
88
140
|
__name(this, "Controller");
|
|
89
141
|
}
|
|
@@ -96,21 +148,24 @@ var Controller = class {
|
|
|
96
148
|
* System theme proxy.
|
|
97
149
|
*/
|
|
98
150
|
system;
|
|
99
|
-
|
|
151
|
+
_adapters = [];
|
|
152
|
+
_observer;
|
|
100
153
|
constructor(_root = document, cfg = {}) {
|
|
101
|
-
this._root = _root;
|
|
154
|
+
super(), this._root = _root;
|
|
102
155
|
this.config = {
|
|
103
156
|
attribute: cfg.attribute ?? "data-theme",
|
|
104
|
-
storage:
|
|
105
|
-
key: cfg.storage?.key ?? "theme"
|
|
106
|
-
},
|
|
157
|
+
storage: cfg.storage ?? new LocalStorage(),
|
|
107
158
|
system: cfg.system ?? "system",
|
|
108
159
|
light: cfg.light ?? "light",
|
|
109
160
|
dark: cfg.dark ?? "dark",
|
|
110
|
-
initial: cfg.initial
|
|
161
|
+
initial: cfg.initial,
|
|
162
|
+
observe: cfg.observe ?? false
|
|
111
163
|
};
|
|
112
164
|
this.system = new System(this);
|
|
113
165
|
this.syncSystemListener();
|
|
166
|
+
if (this.config.observe) {
|
|
167
|
+
this.startObserver();
|
|
168
|
+
}
|
|
114
169
|
}
|
|
115
170
|
/**
|
|
116
171
|
* The root element to apply theme to.
|
|
@@ -159,20 +214,21 @@ var Controller = class {
|
|
|
159
214
|
* @param adapter Adapter instance
|
|
160
215
|
* @param options Adapter options
|
|
161
216
|
*/
|
|
162
|
-
use(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
adapter.bind?.(this);
|
|
172
|
-
adapter.sync?.(this);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
console.warn(`Adapter <${adapter.name}> failed to install.`, err);
|
|
217
|
+
use(def, options) {
|
|
218
|
+
const resolved = {
|
|
219
|
+
...def.defaults,
|
|
220
|
+
...options
|
|
221
|
+
};
|
|
222
|
+
const selector = typeof def.selector === "function" ? def.selector(resolved) : def.selector;
|
|
223
|
+
for (const el of this.root.querySelectorAll(selector)) {
|
|
224
|
+
def.bind?.(this, el, resolved);
|
|
225
|
+
def.sync?.(this, el, resolved);
|
|
175
226
|
}
|
|
227
|
+
this._adapters.push({
|
|
228
|
+
def,
|
|
229
|
+
options: resolved,
|
|
230
|
+
selector
|
|
231
|
+
});
|
|
176
232
|
return this;
|
|
177
233
|
}
|
|
178
234
|
/**
|
|
@@ -182,25 +238,44 @@ var Controller = class {
|
|
|
182
238
|
* @param state Theme State
|
|
183
239
|
*/
|
|
184
240
|
set(state) {
|
|
241
|
+
const from = this.state;
|
|
185
242
|
const theme = resolveStateOf(state, this.system, this.config);
|
|
186
|
-
if (!theme)
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
243
|
+
if (!theme) return this;
|
|
189
244
|
this.write(state);
|
|
190
245
|
this.syncSystemListener();
|
|
191
246
|
this.apply(theme);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
247
|
+
this.syncAdapters();
|
|
248
|
+
this.emit("change", {
|
|
249
|
+
from,
|
|
250
|
+
to: state,
|
|
251
|
+
theme
|
|
252
|
+
});
|
|
253
|
+
return this;
|
|
195
254
|
}
|
|
196
255
|
/**
|
|
197
256
|
* Read the stored theme state from local storage.
|
|
198
257
|
*/
|
|
199
258
|
get state() {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
259
|
+
return this.config.storage.get();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Destroy the controller.
|
|
263
|
+
*
|
|
264
|
+
* @description
|
|
265
|
+
* This method disconnects the observer, stops the system listener.
|
|
266
|
+
*/
|
|
267
|
+
destroy() {
|
|
268
|
+
this._observer?.disconnect();
|
|
269
|
+
this.system.stop();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Sync all adapters.
|
|
273
|
+
*/
|
|
274
|
+
syncAdapters() {
|
|
275
|
+
for (const { def, options, selector } of this._adapters) {
|
|
276
|
+
for (const el of this.root.querySelectorAll(selector)) {
|
|
277
|
+
def.sync?.(this, el, options);
|
|
278
|
+
}
|
|
204
279
|
}
|
|
205
280
|
}
|
|
206
281
|
/**
|
|
@@ -209,13 +284,11 @@ var Controller = class {
|
|
|
209
284
|
syncSystemListener() {
|
|
210
285
|
const state = this.active();
|
|
211
286
|
if (state === this.config.system) {
|
|
212
|
-
this.system.start((
|
|
287
|
+
this.system.start(() => {
|
|
213
288
|
const theme = resolveStateOf("system", this.system, this.config);
|
|
214
289
|
if (!theme) return;
|
|
215
290
|
this.apply(theme);
|
|
216
|
-
|
|
217
|
-
a.sync?.(this);
|
|
218
|
-
}
|
|
291
|
+
this.syncAdapters();
|
|
219
292
|
});
|
|
220
293
|
} else {
|
|
221
294
|
this.system.stop();
|
|
@@ -228,6 +301,9 @@ var Controller = class {
|
|
|
228
301
|
*/
|
|
229
302
|
apply(theme) {
|
|
230
303
|
this.root.setAttribute(this.config.attribute, theme);
|
|
304
|
+
this.emit("apply", {
|
|
305
|
+
theme
|
|
306
|
+
});
|
|
231
307
|
}
|
|
232
308
|
/**
|
|
233
309
|
* Write theme state to local storage.
|
|
@@ -235,10 +311,50 @@ var Controller = class {
|
|
|
235
311
|
* @param theme Theme state
|
|
236
312
|
*/
|
|
237
313
|
write(theme) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
314
|
+
this.config.storage.set(theme);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Start the DOM elements observer.
|
|
318
|
+
*
|
|
319
|
+
* @description
|
|
320
|
+
* This method starts a MutationObserver that watches for changes to
|
|
321
|
+
* the DOM elements that are registered as adapters. When a change
|
|
322
|
+
* is detected, the controller will bind the element to the adapter.
|
|
323
|
+
*/
|
|
324
|
+
startObserver() {
|
|
325
|
+
this._observer = new MutationObserver((mutations) => {
|
|
326
|
+
for (const mu of mutations) {
|
|
327
|
+
for (const node of mu.addedNodes) {
|
|
328
|
+
if (node instanceof Element) {
|
|
329
|
+
this.bindNode(node);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
this._observer.observe(this.root, {
|
|
335
|
+
childList: true,
|
|
336
|
+
subtree: true
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Bind a single DOM element to an adapter.
|
|
341
|
+
*
|
|
342
|
+
* @description
|
|
343
|
+
* This method checks if the element matches the adapter's selector.
|
|
344
|
+
* If it does, the adapter's `bind` method is called with the element
|
|
345
|
+
* and the adapter's options. If the adapter has a `sync` method, it
|
|
346
|
+
* is also called with the element and the adapter's options.
|
|
347
|
+
*/
|
|
348
|
+
bindNode(node) {
|
|
349
|
+
for (const { def, options, selector } of this._adapters) {
|
|
350
|
+
if (node.matches(selector)) {
|
|
351
|
+
def.bind?.(this, node, options);
|
|
352
|
+
def.sync?.(this, node, options);
|
|
353
|
+
}
|
|
354
|
+
for (const el of node.querySelectorAll(selector)) {
|
|
355
|
+
def.bind?.(this, el, options);
|
|
356
|
+
def.sync?.(this, el, options);
|
|
357
|
+
}
|
|
242
358
|
}
|
|
243
359
|
}
|
|
244
360
|
};
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/system.ts","../../src/core/utils.ts","../../src/core/controller.ts"],"names":["System","mql","handler","ctl","prefers","matchMedia","matches","config","dark","light","prefersDark","prefersLight","start","onChange","e","addEventListener","stop","removeEventListener","undefined","resolveStateOf","state","system","resolveRoot","root","HTMLElement","Document","documentElement","ShadowRoot","host","document","Controller","adapters","_root","cfg","attribute","storage","key","initial","syncSystemListener","active","resolved","theme","use","adapter","options","includes","console","warn","name","push","setup","discover","bind","sync","err","set","write","apply","a","localStorage","getItem","_state","setAttribute","setItem"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { Controller } from \"./controller.js\";\r\nimport { Name, State } from \"./types.js\";\r\n\r\n/**\r\n * System theme proxy.\r\n * \r\n * Used to detect system theme preference and listen for changes.\r\n */\r\nexport class System {\r\n\t/**\r\n\t * Media query list instance.\r\n\t */\r\n\tprivate mql?: MediaQueryList;\r\n\t/**\r\n\t * Handles the media query list listener.\r\n\t */\r\n\tprivate handler?: (e: MediaQueryListEvent) => void;\r\n\t/**\r\n\t * Controller instance.\r\n\t */\r\n\r\n\tconstructor(private ctl: Controller) { }\r\n\r\n\t/**\r\n\t * Get the effective theme by system preference.\r\n\t */\r\n\tget prefers(): Name {\r\n\t\treturn matchMedia?.(\"(prefers-color-scheme: dark)\").matches\r\n\t\t\t? this.ctl.config.dark\r\n\t\t\t: this.ctl.config.light;\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the effective theme is dark.\r\n\t */\r\n\tget prefersDark(): boolean {\r\n\t\treturn this.prefers === this.ctl.config.dark;\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the effective theme is light.\r\n\t */\r\n\tget prefersLight(): boolean {\r\n\t\treturn this.prefers === this.ctl.config.light;\r\n\t}\r\n\r\n\t/**\r\n\t * Start listening for system theme changes.\r\n\t * \r\n\t * @param onChange Callback to be called when the system theme changes.\r\n\t */\r\n\tstart(onChange: (state: Exclude<State, \"system\">) => void) {\r\n\t\tif (this.handler) return;\r\n\r\n\t\tthis.mql = matchMedia(\"(prefers-color-scheme: dark)\");\r\n\t\tthis.handler = e => onChange(e.matches ? \"dark\" : \"light\");\r\n\r\n\t\tthis.mql.addEventListener?.(\"change\", this.handler);\r\n\t}\r\n\r\n\t/**\r\n\t * Stop listening for system theme changes.\r\n\t */\r\n\tstop() {\r\n\t\tif (!this.mql || !this.handler) return;\r\n\r\n\t\tthis.mql.removeEventListener?.(\"change\", this.handler);\r\n\t\t\r\n\t\tthis.mql = this.handler = undefined;\r\n\t}\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { ResolvedConfig } from \"./config.js\";\r\nimport { System } from \"./system.js\";\r\nimport { State, type Name } from \"./types.js\";\r\n\r\n/**\r\n * Resolve a theme state to an effective theme name.\r\n * \r\n * @see {@link State}\r\n * \r\n * @param theme Theme state\r\n * @param system System instance.\r\n * @param config Resolved set of configuration.\r\n * @returns \r\n */\r\nexport function resolveStateOf(state: State, system: System, config: ResolvedConfig): Name | null {\r\n\tif (state === config.system) {\r\n\t\treturn system.prefers;\r\n\t}\r\n\r\n\tif (state === config.light || state === config.dark) {\r\n\t\treturn state;\r\n\t}\r\n\r\n\treturn typeof state === \"string\" ? state : null;\r\n}\r\n\r\nexport function resolveRoot(root: unknown): HTMLElement {\r\n\tif (root instanceof HTMLElement) {\r\n\t\treturn root\r\n\t} else if (root instanceof Document) {\r\n\t\treturn root.documentElement\r\n\t} else if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {\r\n\t\treturn root.host\r\n\t};\r\n\r\n\treturn document.documentElement;\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { Adapter } from \"./adapter.js\";\r\nimport { RawConfig, ResolvedConfig } from \"./config.js\";\r\nimport { System } from \"./system.js\";\r\nimport { Name, State } from \"./types.js\";\r\nimport { resolveRoot, resolveStateOf } from \"./utils.js\";\r\n\r\n/**\r\n * Theme controller logic.\r\n */\r\nexport class Controller {\r\n\t/**\r\n\t * Resolved configuration.\r\n\t */\r\n\tconfig: ResolvedConfig;\r\n\t/**\r\n\t * System theme proxy.\r\n\t */\r\n\tprivate system: System;\r\n\r\n\tprivate adapters: Adapter<any>[] = [];\r\n\r\n\tconstructor(\r\n\t\tprivate _root: Document | ParentNode = document,\r\n\t\tcfg: RawConfig = {}\r\n\t) {\r\n\t\tthis.config = {\r\n\t\t\tattribute: cfg.attribute ?? \"data-theme\",\r\n\t\t\tstorage: {\r\n\t\t\t\tkey: cfg.storage?.key ?? \"theme\",\r\n\t\t\t},\r\n\t\t\tsystem: cfg.system ?? \"system\",\r\n\t\t\tlight: cfg.light ?? \"light\",\r\n\t\t\tdark: cfg.dark ?? \"dark\",\r\n\t\t\tinitial: cfg.initial\r\n\t\t};\r\n\r\n\t\tthis.system = new System(this);\r\n\r\n\t\tthis.syncSystemListener();\r\n\t}\r\n\r\n\t/**\r\n\t * The root element to apply theme to.\r\n\t */\r\n\tget root(): HTMLElement {\r\n\t\treturn resolveRoot(this._root);\r\n\t}\r\n\r\n\t/**\r\n\t * @param resolved resolve state and effective themes to their values.\r\n\t * \r\n\t * @example\r\n\t * \r\n\t * system = config.system;\r\n\t * light = config.light;\r\n\t * dark = config.dark;\r\n\t * \r\n\t * resolved = true;\r\n\t * [\r\n\t * \t\t\"system\": light | dark,\r\n\t * \t\t\"light\": light,\r\n\t * \t\t\"dark\": dark\r\n\t * ]\r\n\t * resolved = false;\r\n\t * [\r\n\t * \t\t\"system\": system,\r\n\t * \t\t\"light\": light,\r\n\t * \t\t\"dark\": dark\r\n\t * ]\r\n\t */\r\n\tactive(resolved: boolean = false): State | Name | null {\r\n\t\tconst state = this.state;\r\n\r\n\t\tif (!state) {\r\n\t\t\treturn this.config.initial || null;\r\n\t\t}\r\n\r\n\t\tconst theme = resolveStateOf(state, this.system, this.config);\r\n\r\n\t\tif (!theme) return null;\r\n\r\n\r\n\t\treturn resolved ? theme : state;\r\n\t}\r\n\r\n\t/**\r\n\t * Register an adapter.\r\n\t * \r\n\t * @description\r\n\t * Adapters are toys used to change themes.\r\n\t * They define where themes lives and how they work with the controller.\r\n\t * \r\n\t * @param adapter Adapter instance\r\n\t * @param options Adapter options\r\n\t */\r\n\tuse<T extends Adapter<any>>(adapter: T, options?: T extends Adapter<infer O> ? O : never) {\r\n\t\tif (this.adapters.includes(adapter)) {\r\n\t\t\tconsole.warn(`Adapter <${adapter.name}> is already installed.`);\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tthis.adapters.push(adapter);\r\n\r\n\t\ttry {\r\n\t\t\tadapter.setup?.(this, options as any);\r\n\t\t\tadapter.discover?.(this);\r\n\t\t\tadapter.bind?.(this);\r\n\t\t\tadapter.sync?.(this);\r\n\t\t} catch (err) {\r\n\t\t\tconsole.warn(`Adapter <${adapter.name}> failed to install.`, err);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Set the effective theme from a theme state.\r\n\t * \r\n\t * @see {@link State}\r\n\t * @param state Theme State\r\n\t */\r\n\tset(state: State) {\r\n\t\tconst theme = resolveStateOf(state, this.system, this.config);\r\n\t\tif (!theme) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthis.write(state);\r\n\r\n\t\tthis.syncSystemListener();\r\n\r\n\t\tthis.apply(theme);\r\n\r\n\t\tfor (const a of this.adapters) {\r\n\t\t\ta.sync?.(this);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Read the stored theme state from local storage.\r\n\t */\r\n\tget state(): State | null {\r\n\t\ttry {\r\n\t\t\treturn localStorage.getItem(this.config.storage.key) as State | null;\r\n\t\t} catch { return null; }\r\n\t}\r\n\r\n\t/**\r\n\t * Sync the system listener.\r\n\t */\r\n\tprivate syncSystemListener() {\r\n\t\tconst state = this.active();\r\n\r\n\t\tif (state === this.config.system) {\r\n\t\t\tthis.system.start(_state => {\r\n\t\t\t\tconst theme = resolveStateOf(\r\n\t\t\t\t\t\"system\",\r\n\t\t\t\t\tthis.system,\r\n\t\t\t\t\tthis.config\r\n\t\t\t\t);\r\n\t\t\t\tif (!theme) return;\r\n\r\n\t\t\t\tthis.apply(theme);\r\n\r\n\t\t\t\tfor (const a of this.adapters) {\r\n\t\t\t\t\ta.sync?.(this);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tthis.system.stop();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Applies the effective theme name to the root.\r\n\t * \r\n\t * @param theme Effective theme name (e.g. `<dark>`, `<light>`, `mint`)\r\n\t */\r\n\tprivate apply(theme: Name) {\r\n\t\tthis.root.setAttribute(this.config.attribute, theme);\r\n\t}\r\n\r\n\t/**\r\n\t * Write theme state to local storage.\r\n\t * \r\n\t * @param theme Theme state\r\n\t */\r\n\tprivate write(theme: State) {\r\n\t\ttry {\r\n\t\t\tlocalStorage.setItem(this.config.storage.key, theme);\r\n\t\t} catch {\r\n\t\t\tconsole.warn(`Failed to write theme state to local storage.`);\r\n\t\t}\r\n\t}\r\n}"]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/local.ts","../../src/core/event.ts","../../src/core/system.ts","../../src/core/utils.ts","../../src/core/controller.ts"],"names":["LocalStorage","key","options","get","localStorage","getItem","set","state","setItem","console","warn","remove","removeItem","EventEmitter","_map","Map","on","event","listener","Set","add","off","delete","emit","payload","forEach","System","mql","handler","ctl","prefers","matchMedia","matches","config","dark","light","prefersDark","prefersLight","start","onChange","e","addEventListener","stop","removeEventListener","undefined","resolveStateOf","system","resolveRoot","root","HTMLElement","Document","documentElement","ShadowRoot","host","document","Controller","_adapters","_observer","_root","cfg","attribute","storage","initial","observe","syncSystemListener","startObserver","active","resolved","theme","use","def","defaults","selector","el","querySelectorAll","bind","sync","push","from","write","apply","syncAdapters","to","destroy","disconnect","setAttribute","MutationObserver","mutations","mu","node","addedNodes","Element","bindNode","childList","subtree"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { StateStorage } from \"../core/storage.js\";\r\nimport { State } from \"../core/types.js\";\r\n\r\nexport interface LocalStorageOptions {\r\n\t/**\r\n\t * Local storage key.\r\n\t * \r\n\t * @default \"theme\"\r\n\t */\r\n\tkey?: string;\r\n}\r\n\r\n/**\r\n * Simple local storage implementation that uses the browser's native `localStorage`.\r\n */\r\nexport class LocalStorage implements StateStorage {\r\n\treadonly key: string;\r\n\t\r\n\tconstructor(options: LocalStorageOptions = {}) {\r\n\t\tthis.key = options.key ?? \"theme\";\r\n\t}\r\n\t\r\n\tget(): State | null {\r\n\t\ttry {\r\n\t\t\treturn localStorage.getItem(this.key) as State | null;\r\n\t\t} catch {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\tset(state: State): void {\r\n\t\ttry {\r\n\t\t\tlocalStorage.setItem(this.key, state);\r\n\t\t} catch {\r\n\t\t\tconsole.warn(\"[LOCAL] Failed to write theme state.\");\r\n\t\t}\r\n\t}\r\n\r\n\tremove(): void {\r\n\t\ttry {\r\n\t\t\tlocalStorage.removeItem(this.key);\r\n\t\t} catch {}\r\n\t}\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nexport type Listener<T> = (event: T) => void;\r\n\r\n/**\r\n * A simple event emitter implementation.\r\n * \r\n * @description\r\n * This class is suitable for use in a web application that wants to\r\n * listen for changes in the theme state.\r\n */\r\nexport class EventEmitter<M extends Record<string, unknown>> {\r\n\tprivate _map = new Map<keyof M, Set<Listener<any>>>();\r\n\r\n\ton<K extends keyof M>(event: K, listener: Listener<M[ K ]>): () => void {\r\n\t\tlet set = this._map.get(event);\r\n\t\tif (!set) this._map.set(event, set = new Set());\r\n\r\n\t\tset.add(listener);\r\n\r\n\t\treturn () => this.off(event, listener);\r\n\t}\r\n\r\n\toff<K extends keyof M>(event: K, listener: Listener<M[ K ]>): void {\r\n\t\tthis._map.get(event)?.delete(listener);\r\n\t}\r\n\r\n\temit<K extends keyof M>(event: K, payload: M[ K ]): void {\r\n\t\tthis._map.get(event)?.forEach(listener => listener(payload));\r\n\t}\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { Controller } from \"./controller.js\";\r\nimport { Name, State } from \"./types.js\";\r\n\r\n/**\r\n * System theme proxy.\r\n * \r\n * Used to detect system theme preference and listen for changes.\r\n */\r\nexport class System {\r\n\t/**\r\n\t * Media query list instance.\r\n\t */\r\n\tprivate mql?: MediaQueryList;\r\n\t/**\r\n\t * Handles the media query list listener.\r\n\t */\r\n\tprivate handler?: (e: MediaQueryListEvent) => void;\r\n\t/**\r\n\t * Controller instance.\r\n\t */\r\n\r\n\tconstructor(private ctl: Controller) { }\r\n\r\n\t/**\r\n\t * Get the effective theme by system preference.\r\n\t */\r\n\tget prefers(): Name {\r\n\t\treturn matchMedia?.(\"(prefers-color-scheme: dark)\").matches\r\n\t\t\t? this.ctl.config.dark\r\n\t\t\t: this.ctl.config.light;\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the effective theme is dark.\r\n\t */\r\n\tget prefersDark(): boolean {\r\n\t\treturn this.prefers === this.ctl.config.dark;\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the effective theme is light.\r\n\t */\r\n\tget prefersLight(): boolean {\r\n\t\treturn this.prefers === this.ctl.config.light;\r\n\t}\r\n\r\n\t/**\r\n\t * Start listening for system theme changes.\r\n\t * \r\n\t * @param onChange Callback to be called when the system theme changes.\r\n\t */\r\n\tstart(onChange: (state: Exclude<State, \"system\">) => void) {\r\n\t\tif (this.handler) return;\r\n\r\n\t\tthis.mql = matchMedia(\"(prefers-color-scheme: dark)\");\r\n\t\tthis.handler = e => onChange(e.matches ? \"dark\" : \"light\");\r\n\r\n\t\tthis.mql.addEventListener?.(\"change\", this.handler);\r\n\t}\r\n\r\n\t/**\r\n\t * Stop listening for system theme changes.\r\n\t */\r\n\tstop() {\r\n\t\tif (!this.mql || !this.handler) return;\r\n\r\n\t\tthis.mql.removeEventListener?.(\"change\", this.handler);\r\n\t\t\r\n\t\tthis.mql = this.handler = undefined;\r\n\t}\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { ResolvedConfig } from \"./config.js\";\r\nimport { System } from \"./system.js\";\r\nimport { State, type Name } from \"./types.js\";\r\n\r\nexport const DEFAULT_PREFIX = \"theme:\";\r\n\r\n/**\r\n * Resolve a theme state to an effective theme name.\r\n * \r\n * @see {@link State}\r\n * \r\n * @param theme Theme state\r\n * @param system System instance.\r\n * @param config Resolved set of configuration.\r\n * @returns \r\n */\r\nexport function resolveStateOf(state: State, system: System, config: ResolvedConfig): Name | null {\r\n\tif (state === config.system) {\r\n\t\treturn system.prefers;\r\n\t}\r\n\r\n\tif (state === config.light || state === config.dark) {\r\n\t\treturn state;\r\n\t}\r\n\r\n\treturn typeof state === \"string\" ? state : null;\r\n}\r\n\r\n/**\r\n * Resolve a root element from a given value.\r\n * \r\n * @description\r\n * This function takes a root value and returns the corresponding\r\n * HTMLElement. If the value is already an HTMLElement, it is returned\r\n * directly. If the value is a Document, it returns the documentElement.\r\n * If the value is a ShadowRoot, it returns the host element.\r\n * \r\n * @param root Root value\r\n */\r\nexport function resolveRoot(root: unknown): HTMLElement {\r\n\tif (root instanceof HTMLElement) {\r\n\t\treturn root\r\n\t} else if (root instanceof Document) {\r\n\t\treturn root.documentElement\r\n\t} else if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {\r\n\t\treturn root.host\r\n\t};\r\n\r\n\treturn document.documentElement;\r\n}\r\n\r\n/**\r\n * Parse a value with a prefix.\r\n * \r\n * @description\r\n * This function takes a value and a prefix. If the value starts with\r\n * the prefix, it is sliced from the beginning. Otherwise, the value is\r\n * returned as-is.\r\n * \r\n * @param value Value to parse\r\n * @param prefix Prefix to check\r\n */\r\nexport function parseValue(value: string, prefix: string): string {\r\n\treturn value.startsWith(prefix)\r\n\t\t? value.slice(prefix.length)\r\n\t\t: value;\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { LocalStorage } from \"../storage/local.js\";\r\nimport { AdapterDefinition } from \"./adapter.js\";\r\nimport { RawConfig, ResolvedConfig } from \"./config.js\";\r\nimport { EventEmitter } from \"./event.js\";\r\nimport { System } from \"./system.js\";\r\nimport { Name, NEvents, State } from \"./types.js\";\r\nimport { resolveRoot, resolveStateOf } from \"./utils.js\";\r\n\r\ninterface RegisteredAdapter {\r\n\tdef: AdapterDefinition<any, any>;\r\n\toptions: any;\r\n\tselector: string;\r\n}\r\n\r\n/**\r\n * Theme controller logic.\r\n */\r\nexport class Controller extends EventEmitter<NEvents> {\r\n\t/**\r\n\t * Resolved configuration.\r\n\t */\r\n\tconfig: ResolvedConfig;\r\n\t/**\r\n\t * System theme proxy.\r\n\t */\r\n\tprivate system: System;\r\n\r\n\tprivate _adapters: RegisteredAdapter[] = [];\r\n\tprivate _observer?: MutationObserver;\r\n\r\n\tconstructor(\r\n\t\tprivate _root: Document | ParentNode = document,\r\n\t\tcfg: RawConfig = {}\r\n\t) {\r\n\t\tsuper();\r\n\t\t\r\n\t\tthis.config = {\r\n\t\t\tattribute: cfg.attribute ?? \"data-theme\",\r\n\t\t\tstorage: cfg.storage ?? new LocalStorage(),\r\n\t\t\tsystem: cfg.system ?? \"system\",\r\n\t\t\tlight: cfg.light ?? \"light\",\r\n\t\t\tdark: cfg.dark ?? \"dark\",\r\n\t\t\tinitial: cfg.initial,\r\n\t\t\tobserve: cfg.observe ?? false\r\n\t\t};\r\n\r\n\t\tthis.system = new System(this);\r\n\r\n\t\tthis.syncSystemListener();\r\n\r\n\t\tif (this.config.observe) {\r\n\t\t\tthis.startObserver();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * The root element to apply theme to.\r\n\t */\r\n\tget root(): HTMLElement {\r\n\t\treturn resolveRoot(this._root);\r\n\t}\r\n\r\n\t/**\r\n\t * @param resolved resolve state and effective themes to their values.\r\n\t * \r\n\t * @example\r\n\t * \r\n\t * system = config.system;\r\n\t * light = config.light;\r\n\t * dark = config.dark;\r\n\t * \r\n\t * resolved = true;\r\n\t * [\r\n\t * \t\t\"system\": light | dark,\r\n\t * \t\t\"light\": light,\r\n\t * \t\t\"dark\": dark\r\n\t * ]\r\n\t * resolved = false;\r\n\t * [\r\n\t * \t\t\"system\": system,\r\n\t * \t\t\"light\": light,\r\n\t * \t\t\"dark\": dark\r\n\t * ]\r\n\t */\r\n\tactive(resolved: boolean = false): State | Name | null {\r\n\t\tconst state = this.state;\r\n\r\n\t\tif (!state) {\r\n\t\t\treturn this.config.initial || null;\r\n\t\t}\r\n\r\n\t\tconst theme = resolveStateOf(\r\n\t\t\tstate,\r\n\t\t\tthis.system,\r\n\t\t\tthis.config\r\n\t\t);\r\n\r\n\t\tif (!theme) return null;\r\n\r\n\t\treturn resolved\r\n\t\t\t? theme\r\n\t\t\t: state;\r\n\t}\r\n\r\n\t/**\r\n\t * Register an adapter.\r\n\t * \r\n\t * @description\r\n\t * Adapters are toys used to change themes.\r\n\t * They define where themes lives and how they work with the controller.\r\n\t * \r\n\t * @param adapter Adapter instance\r\n\t * @param options Adapter options\r\n\t */\r\n\tuse<O>(\r\n\t\tdef: AdapterDefinition<any, O>,\r\n\t\toptions?: Partial<O>\r\n\t): this {\r\n\t\tconst resolved = { ...def.defaults, ...options } as O;\r\n\r\n\t\tconst selector = typeof def.selector === \"function\"\r\n\t\t\t? def.selector(resolved)\r\n\t\t\t: def.selector;\r\n\t\t\r\n\t\tfor (const el of this.root.querySelectorAll(selector)) {\r\n\t\t\tdef.bind?.(this, el, resolved);\r\n\t\t\tdef.sync?.(this, el, resolved);\r\n\t\t}\r\n\r\n\t\tthis._adapters.push({\r\n\t\t\tdef,\r\n\t\t\toptions: resolved,\r\n\t\t\tselector\r\n\t\t});\r\n\t\t\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Set the effective theme from a theme state.\r\n\t * \r\n\t * @see {@link State}\r\n\t * @param state Theme State\r\n\t */\r\n\tset(state: State) {\r\n\t\tconst from = this.state;\r\n\t\tconst theme = resolveStateOf(state, this.system, this.config);\r\n\r\n\t\tif (!theme) return this;\r\n\r\n\t\tthis.write(state);\r\n\t\tthis.syncSystemListener();\r\n\t\tthis.apply(theme);\r\n\t\tthis.syncAdapters();\r\n\r\n\t\tthis.emit(\"change\", {\r\n\t\t\tfrom,\r\n\t\t\tto: state,\r\n\t\t\ttheme\r\n\t\t});\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Read the stored theme state from local storage.\r\n\t */\r\n\tget state(): State | null {\r\n\t\treturn this.config.storage.get();\r\n\t}\r\n\r\n\t/**\r\n\t * Destroy the controller.\r\n\t * \r\n\t * @description\r\n\t * This method disconnects the observer, stops the system listener.\r\n\t */\r\n\tdestroy(): void {\r\n\t\tthis._observer?.disconnect();\r\n\t\tthis.system.stop();\r\n\t}\r\n\r\n\t/**\r\n\t * Sync all adapters.\r\n\t */\r\n\tprivate syncAdapters(): void {\r\n\t\tfor (const { def, options, selector } of this._adapters) {\r\n\t\t\tfor (const el of this.root.querySelectorAll(selector)) {\r\n\t\t\t\tdef.sync?.(this, el, options);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Sync the system listener.\r\n\t */\r\n\tprivate syncSystemListener() {\r\n\t\tconst state = this.active();\r\n\r\n\t\tif (state === this.config.system) {\r\n\t\t\tthis.system.start(() => {\r\n\t\t\t\tconst theme = resolveStateOf(\r\n\t\t\t\t\t\"system\",\r\n\t\t\t\t\tthis.system,\r\n\t\t\t\t\tthis.config\r\n\t\t\t\t);\r\n\t\t\t\tif (!theme) return;\r\n\r\n\t\t\t\tthis.apply(theme);\r\n\r\n\t\t\t\tthis.syncAdapters();\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tthis.system.stop();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Applies the effective theme name to the root.\r\n\t * \r\n\t * @param theme Effective theme name (e.g. `<dark>`, `<light>`, `mint`)\r\n\t */\r\n\tprivate apply(theme: Name) {\r\n\t\tthis.root.setAttribute(this.config.attribute, theme);\r\n\t\tthis.emit(\"apply\", { theme });\r\n\t}\r\n\r\n\t/**\r\n\t * Write theme state to local storage.\r\n\t * \r\n\t * @param theme Theme state\r\n\t */\r\n\tprivate write(theme: State) {\r\n\t\tthis.config.storage.set(theme);\r\n\t}\r\n\r\n\t/**\r\n\t * Start the DOM elements observer.\r\n\t * \r\n\t * @description\r\n\t * This method starts a MutationObserver that watches for changes to\r\n\t * the DOM elements that are registered as adapters. When a change\r\n\t * is detected, the controller will bind the element to the adapter.\r\n\t */\r\n\tprivate startObserver(): void {\r\n\t\tthis._observer = new MutationObserver((mutations) => {\r\n\t\t\tfor (const mu of mutations) {\r\n\t\t\t\tfor (const node of mu.addedNodes) {\r\n\t\t\t\t\tif (node instanceof Element) {\r\n\t\t\t\t\t\tthis.bindNode(node);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis._observer.observe(this.root, { childList: true, subtree: true });\r\n\t}\r\n\r\n\t/**\r\n\t * Bind a single DOM element to an adapter.\r\n\t * \r\n\t * @description\r\n\t * This method checks if the element matches the adapter's selector.\r\n\t * If it does, the adapter's `bind` method is called with the element\r\n\t * and the adapter's options. If the adapter has a `sync` method, it\r\n\t * is also called with the element and the adapter's options.\r\n\t */\r\n\tprivate bindNode(node: Element): void {\r\n\t\tfor (const { def, options, selector } of this._adapters) {\r\n\t\t\tif (node.matches(selector)) {\r\n\t\t\t\tdef.bind?.(this, node, options);\r\n\t\t\t\tdef.sync?.(this, node, options);\r\n\t\t\t}\r\n\r\n\t\t\tfor (const el of node.querySelectorAll(selector)) {\r\n\t\t\t\tdef.bind?.(this, el, options);\r\n\t\t\t\tdef.sync?.(this, el, options);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"]}
|
package/dist/loader/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(){"use strict"
|
|
1
|
+
!function(){"use strict";var e=Object.defineProperty,t=(t,a,r)=>((t,a,r)=>a in t?e(t,a,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[a]=r)(t,a+"",r),a=class{constructor(e={}){var a;t(this,"key"),this.key=null!=(a=e.key)?a:"theme"}get(){try{return localStorage.getItem(this.key)}catch(e){return null}}set(e){try{localStorage.setItem(this.key,e)}catch(e){console.warn("[LOCAL] Failed to write theme state.")}}remove(){try{localStorage.removeItem(this.key)}catch(e){}}};e(a,"name",{value:"LocalStorage",configurable:!0});var r=a;!function(e=document.documentElement,t){try{let a=t.storage,r=t.attribute,l=t.system,c=t.light,i=t.dark,n=t.initial;if(e&&e.hasAttribute(r))return;let o=a.get(),s=null;o===l?s=null!=matchMedia&&matchMedia("(prefers-color-scheme: dark)").matches?i:c:o===c||o===i?s=o:!o&&n&&(s=n),s&&e.setAttribute(r,s)}catch(e){}}(document.documentElement,{storage:new r,attribute:"data-theme",system:"system",light:"light",dark:"dark",initial:"dark",observe:!1})}();
|