@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.
@@ -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
- storage?: {
28
- key: string;
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 adapters;
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<T extends Adapter<any>>(adapter: T, options?: T extends Adapter<infer O> ? O : never): this;
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): void;
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 Adapter<Options = undefined> {
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
- * Setup the adapter.
145
- * Used to initiate any one-time setup.
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
- setup?(ctl: Controller, options: Options): void;
230
+ bind?(ctl: Controller, element: E, options: O): void;
156
231
  /**
157
- * Discover themes that are available to this adapter. \
158
- * Used to validate, or for example do a one-time setup.
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
- discover?(ctl: Controller): void;
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 Adapter, Controller, type Name, type State };
252
+ export { type AdapterDefinition, Controller, EventEmitter, Name, type ResolvedConfig, State, defineAdapter };
@@ -1,4 +1,56 @@
1
- import { __name } from '../chunk-7QVYU63E.js';
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
- adapters = [];
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(adapter, options) {
163
- if (this.adapters.includes(adapter)) {
164
- console.warn(`Adapter <${adapter.name}> is already installed.`);
165
- return this;
166
- }
167
- this.adapters.push(adapter);
168
- try {
169
- adapter.setup?.(this, options);
170
- adapter.discover?.(this);
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
- for (const a of this.adapters) {
193
- a.sync?.(this);
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
- try {
201
- return localStorage.getItem(this.config.storage.key);
202
- } catch {
203
- return null;
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((_state) => {
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
- for (const a of this.adapters) {
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
- try {
239
- localStorage.setItem(this.config.storage.key, theme);
240
- } catch {
241
- console.warn(`Failed to write theme state to local storage.`);
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
  };
@@ -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}"]}
@@ -1 +1 @@
1
- !function(){"use strict";!function(l=document.documentElement,t){var e,n,u,i,a,o,r;try{let d=null!=(n=null==(e=null==t?void 0:t.storage)?void 0:e.key)?n:"theme",c=null!=(u=null==t?void 0:t.attribute)?u:"data-theme",m=null!=(i=null==t?void 0:t.system)?i:"system",s=null!=(a=null==t?void 0:t.light)?a:"light",h=null!=(o=null==t?void 0:t.dark)?o:"dark",v=null!=(r=null==t?void 0:t.initial)?r:h;if(l&&l.hasAttribute(c))return;let g=localStorage.getItem(d),f=null;g===m?f=null!=matchMedia&&matchMedia("(prefers-color-scheme: dark)").matches?h:s:g===s||g===h?f=g:!g&&v&&(f=v),f&&l.setAttribute(c,f)}catch(l){}}()}();
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})}();