@gottheflag/nova 0.1.0-beta.2 → 0.1.0-beta.3

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/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Theme controller
2
2
 
3
- [![Socket Badge](https://badge.socket.dev/npm/package/@gottheflag/nova/0.1.0-beta.1)](https://badge.socket.dev/npm/package/@gottheflag/nova/0.1.0-beta.1)
4
-
5
3
  A controller for managing the theming for applications.
6
4
 
7
5
  ## Installation
@@ -15,16 +13,38 @@ pnpm add @gottheflag/nova
15
13
  ```js
16
14
  import { Controller } from "@gottheflag/nova";
17
15
  import { Button } from "@gottheflag/nova/adapters";
16
+ import { LocalStorage } from "@gottheflag/nova/storage";
18
17
 
19
18
  const ctl = new Controller(document, {
20
19
  // all configs are optional
21
- storage: { key: "theme" },
20
+ storage: new LocalStorage({
21
+ key: "theme",
22
+ }),
22
23
  attribute: "data-theme",
23
24
  initial: "dark",
24
25
  dark: "dark",
25
26
  light: "light",
26
27
  system: "system",
28
+ observe: true,
29
+ });
30
+
31
+ // use adapters
32
+ ctl.use(Button, {
33
+ prefix: "theme:",
34
+ animate: {
35
+ enabled: true,
36
+ duration: 1000,
37
+ easing: "ease-in-out",
38
+ },
27
39
  });
28
40
 
29
- ctl.use(Button); // use adapters
41
+ // listen for changes
42
+ ctl.on("change", ({ theme }) => {
43
+ console.log("theme changed", theme);
44
+ });
45
+
46
+ // listen for applied theme
47
+ ctl.on("apply", ({ theme }) => {
48
+ console.log("theme applied", theme);
49
+ });
30
50
  ```
@@ -1,38 +1,41 @@
1
- import { Adapter, Name } from '../core/index.js';
1
+ import { AdapterDefinition } from '../core/index.js';
2
2
 
3
3
  /**
4
4
  * Copyright (c) 2026 GTF
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
-
7
+ type Easing = "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out";
8
+ interface ButtonOptions {
9
+ prefix?: string;
10
+ animate?: {
11
+ enabled?: boolean;
12
+ duration?: number;
13
+ easing?: Easing;
14
+ };
15
+ }
8
16
  /**
9
17
  * @example
10
- * <button value="<prefix>light">Light</button>
11
- * <button value="<prefix>dark">Dark</button>
12
- */
13
- declare const Button: Adapter & {
14
- registry: Map<Element, Name[]>;
15
- };
16
-
17
- /**
18
- * Copyright (c) 2026 GTF
19
- * SPDX-License-Identifier: Apache-2.0
18
+ * <button value="theme:light">Light</button>
19
+ * <button value="theme:dark">Dark</button>
20
20
  */
21
+ declare const Button: AdapterDefinition<HTMLButtonElement, ButtonOptions>;
21
22
 
22
- interface Options$1 {
23
+ interface RadioOptions {
23
24
  prefix?: string;
24
25
  }
25
- declare const Radio: Adapter<Options$1> & {
26
- registry: Map<Element, Name[]>;
27
- prefix?: string;
28
- };
26
+ /**
27
+ * @example
28
+ * <input type="radio" name="theme" value="theme:light">
29
+ * <input type="radio" name="theme" value="theme:dark">
30
+ * <input type="radio" name="theme" value="theme:system">
31
+ */
32
+ declare const Radio: AdapterDefinition<HTMLInputElement, RadioOptions>;
29
33
 
30
34
  /**
31
35
  * Copyright (c) 2026 GTF
32
36
  * SPDX-License-Identifier: Apache-2.0
33
37
  */
34
-
35
- interface Options {
38
+ interface SelectOptions {
36
39
  prefix?: string;
37
40
  }
38
41
  /**
@@ -45,9 +48,6 @@ interface Options {
45
48
  * </select>
46
49
  * ```
47
50
  */
48
- declare const Select: Adapter<Options> & {
49
- registry: Map<Element, Name[]>;
50
- prefix?: string;
51
- };
51
+ declare const Select: AdapterDefinition<HTMLSelectElement, SelectOptions>;
52
52
 
53
53
  export { Button, Radio, Select };
@@ -1,128 +1,91 @@
1
- import { __name } from '../chunk-7QVYU63E.js';
1
+ import { __name, DEFAULT_PREFIX, parseValue } from '../chunk-NYMA6GFH.js';
2
2
 
3
- // src/adapters/button.ts
4
- var PREFIX = "theme:";
5
- function parseThemeName(btn) {
6
- const value = btn.value;
7
- const name = value.slice(PREFIX.length);
8
- return name ? name : value;
3
+ // src/core/adapter.ts
4
+ function defineAdapter(definition) {
5
+ return definition;
9
6
  }
10
- __name(parseThemeName, "parseThemeName");
11
- var Button = {
7
+ __name(defineAdapter, "defineAdapter");
8
+
9
+ // src/adapters/button.ts
10
+ var Button = defineAdapter({
12
11
  name: "button",
13
- registry: /* @__PURE__ */ new Map(),
14
- discover(ctl) {
15
- const root = ctl.root;
16
- root.querySelectorAll(`button[value^="${PREFIX}"]`).forEach((btn) => {
17
- const detected = [];
18
- const theme = parseThemeName(btn);
19
- if (theme) detected.push(theme);
20
- this.registry.set(btn, detected);
21
- });
12
+ defaults: {
13
+ prefix: DEFAULT_PREFIX,
14
+ animate: {
15
+ enabled: false,
16
+ duration: 600,
17
+ easing: "ease-in-out"
18
+ }
22
19
  },
23
- bind(ctl) {
24
- const root = ctl.root;
25
- root.querySelectorAll(`button[value^="${PREFIX}"]`).forEach((btn) => {
26
- const theme = parseThemeName(btn);
27
- btn.addEventListener("click", () => {
28
- if (this.registry.get(btn)?.includes(theme)) {
29
- ctl.set(theme);
30
- }
20
+ selector: /* @__PURE__ */ __name((o) => `button[value^="${o.prefix ?? DEFAULT_PREFIX}"]`, "selector"),
21
+ bind(ctl, el, { prefix = DEFAULT_PREFIX, animate }) {
22
+ el.addEventListener("click", async (event) => {
23
+ const theme = parseValue(el.value, prefix);
24
+ if (theme === ctl.state) return;
25
+ if (!animate?.enabled || !document.startViewTransition) {
26
+ ctl.set(theme);
27
+ return;
28
+ }
29
+ const { clientX: x, clientY: y } = event;
30
+ const radius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
31
+ const transition = document.startViewTransition(() => ctl.set(theme));
32
+ await transition.ready;
33
+ document.documentElement.animate({
34
+ clipPath: [
35
+ `circle(0px at ${x}px ${y}px)`,
36
+ `circle(${radius}px at ${x}px ${y}px)`
37
+ ]
38
+ }, {
39
+ duration: animate.duration ?? 500,
40
+ easing: animate.easing ?? "ease-in-out",
41
+ pseudoElement: "::view-transition-new(root)"
31
42
  });
32
43
  });
33
44
  }
34
- };
45
+ });
35
46
 
36
47
  // src/adapters/radio.ts
37
- var PREFIX2 = "theme:";
38
- function parseThemeName2(btn, prefix) {
39
- const value = btn.value;
40
- const name = value.slice(prefix.length);
41
- return name ? name : value;
42
- }
43
- __name(parseThemeName2, "parseThemeName");
44
- var Radio = {
48
+ var Radio = defineAdapter({
45
49
  name: "radio",
46
- registry: /* @__PURE__ */ new Map(),
47
- setup(_ctl, options) {
48
- this.prefix = options?.prefix ?? PREFIX2;
50
+ defaults: {
51
+ prefix: DEFAULT_PREFIX
49
52
  },
50
- discover(ctl) {
51
- const root = ctl.root;
52
- root.querySelectorAll(`input[type="radio"][value^="${PREFIX2}"]`).forEach((radio) => {
53
- const detected = [];
54
- const theme = parseThemeName2(radio, this.prefix);
55
- if (theme) detected.push(theme);
56
- this.registry.set(radio, detected);
53
+ selector: /* @__PURE__ */ __name((o) => `input[type="radio"][value^="${o.prefix}"]`, "selector"),
54
+ bind(ctl, el, { prefix = DEFAULT_PREFIX }) {
55
+ el.addEventListener("change", () => {
56
+ if (!el.checked) return;
57
+ const theme = parseValue(el.value, prefix);
58
+ if (theme === ctl.state) return;
59
+ ctl.set(theme);
57
60
  });
58
61
  },
59
- bind(ctl) {
60
- const root = ctl.root;
61
- root.querySelectorAll(`input[type="radio"][value^="${PREFIX2}"]`).forEach((radio) => {
62
- radio.addEventListener("change", (e) => {
63
- const target = e.currentTarget;
64
- const theme = parseThemeName2(target, this.prefix);
65
- if (theme) ctl.set(theme);
66
- });
67
- });
68
- },
69
- sync(ctl) {
70
- const root = ctl.root;
71
- const active = ctl.active();
72
- root.querySelectorAll(`input[type="radio"][value^="${PREFIX2}"]`).forEach((radio) => {
73
- const theme = parseThemeName2(radio, this.prefix);
74
- radio.checked = theme === active;
75
- });
62
+ sync(ctl, el, { prefix = DEFAULT_PREFIX }) {
63
+ el.checked = parseValue(el.value, prefix) === ctl.active();
76
64
  }
77
- };
65
+ });
78
66
 
79
67
  // src/adapters/select.ts
80
- var PREFIX3 = "theme:";
81
- function parseThemeName3(btn, prefix) {
82
- const value = btn.value;
83
- const name = value.slice(prefix.length);
84
- return name ? name : value;
85
- }
86
- __name(parseThemeName3, "parseThemeName");
87
- var Select = {
68
+ var Select = defineAdapter({
88
69
  name: "select",
89
- registry: /* @__PURE__ */ new Map(),
90
- setup(_ctl, options) {
91
- this.prefix = options?.prefix ?? PREFIX3;
92
- },
93
- discover(ctl) {
94
- const root = ctl.root;
95
- root.querySelectorAll(`select:has(option[value^="${this.prefix}"])`).forEach((sel) => {
96
- const detected = [];
97
- Array.from(sel.options).forEach((option) => {
98
- const theme = parseThemeName3(option, this.prefix);
99
- if (theme) detected.push(theme);
100
- });
101
- this.registry.set(sel, detected);
102
- });
70
+ defaults: {
71
+ prefix: DEFAULT_PREFIX
103
72
  },
104
- bind(ctl) {
105
- const root = ctl.root;
106
- root.querySelectorAll(`select:has(option[value^="${this.prefix}"])`).forEach((sel) => {
107
- sel.addEventListener("change", (e) => {
108
- const target = e.currentTarget;
109
- const option = target.options[target.selectedIndex];
110
- const theme = parseThemeName3(option, this.prefix);
111
- if (theme) ctl.set(theme);
112
- });
73
+ selector: /* @__PURE__ */ __name((o) => `select:has(option[value^="${o.prefix}"])`, "selector"),
74
+ bind(ctl, el, { prefix = DEFAULT_PREFIX }) {
75
+ el.addEventListener("change", () => {
76
+ const option = el.options[el.selectedIndex];
77
+ const theme = parseValue(option.value, prefix);
78
+ if (theme === ctl.state) return;
79
+ ctl.set(theme);
113
80
  });
114
81
  },
115
- sync(ctl) {
116
- const root = ctl.root;
82
+ sync(ctl, el, { prefix = DEFAULT_PREFIX }) {
117
83
  const active = ctl.active();
118
- root.querySelectorAll(`select:has(option[value^="${this.prefix}"])`).forEach((sel) => {
119
- Array.from(sel.options).forEach((option) => {
120
- const theme = parseThemeName3(option, this.prefix);
121
- option.selected = theme === active;
122
- });
123
- });
84
+ for (const option of el.options) {
85
+ option.selected = parseValue(option.value, prefix) === active;
86
+ }
124
87
  }
125
- };
88
+ });
126
89
 
127
90
  export { Button, Radio, Select };
128
91
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/button.ts","../../src/adapters/radio.ts","../../src/adapters/select.ts"],"names":["PREFIX","parseThemeName","btn","value","name","slice","length","Button","registry","Map","discover","ctl","root","querySelectorAll","forEach","detected","theme","push","set","bind","addEventListener","get","includes","prefix","Radio","setup","_ctl","options","radio","e","target","currentTarget","sync","active","checked","Select","sel","Array","from","option","selectedIndex","selected"],"mappings":";;;AASA,IAAMA,MAAAA,GAAS,QAAA;AAEf,SAASC,eAAeC,GAAAA,EAAsB;AAC1C,EAAA,MAAMC,QAAQD,GAAAA,CAAIC,KAAAA;AAClB,EAAA,MAAMC,IAAAA,GAAOD,KAAAA,CAAME,KAAAA,CAAML,MAAAA,CAAOM,MAAM,CAAA;AAEtC,EAAA,OAAOF,OAAOA,IAAAA,GAAOD,KAAAA;AACzB;AALSF,MAAAA,CAAAA,cAAAA,EAAAA,gBAAAA,CAAAA;AAYF,IAAMM,MAAAA,GAET;EACAH,IAAAA,EAAM,QAAA;AAENI,EAAAA,QAAAA,sBAAcC,GAAAA,EAAAA;AAEdC,EAAAA,QAAAA,CAASC,GAAAA,EAAe;AACpB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,iBAAoC,CAAA,eAAA,EAAkBb,MAAAA,IAAU,CAAA,CAAEc,OAAAA,CAAQZ,CAAAA,GAAAA,KAAAA;AAC3E,MAAA,MAAMa,WAAmB,EAAA;AAEzB,MAAA,MAAMC,KAAAA,GAAQf,eAAeC,GAAAA,CAAAA;AAC7B,MAAA,IAAIc,KAAAA,EAAOD,QAAAA,CAASE,IAAAA,CAAKD,KAAAA,CAAAA;AAEzB,MAAA,IAAA,CAAKR,QAAAA,CAASU,GAAAA,CAAIhB,GAAAA,EAAKa,QAAAA,CAAAA;IAC3B,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAI,EAAAA,IAAAA,CAAKR,GAAAA,EAAe;AAChB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,iBAAoC,CAAA,eAAA,EAAkBb,MAAAA,IAAU,CAAA,CAAEc,OAAAA,CAAQZ,CAAAA,GAAAA,KAAAA;AAC3E,MAAA,MAAMc,KAAAA,GAAQf,eAAeC,GAAAA,CAAAA;AAE7BA,MAAAA,GAAAA,CAAIkB,gBAAAA,CAAiB,SAAS,MAAA;AAC1B,QAAA,IAAI,KAAKZ,QAAAA,CAASa,GAAAA,CAAInB,GAAAA,CAAAA,EAAMoB,QAAAA,CAASN,KAAAA,CAAAA,EAAQ;AACzCL,UAAAA,GAAAA,CAAIO,IAAIF,KAAAA,CAAAA;AACZ,QAAA;MACJ,CAAA,CAAA;IACJ,CAAA,CAAA;AACJ,EAAA;AACJ;;;AC/CA,IAAMhB,OAAAA,GAAS,QAAA;AAEf,SAASC,eAAAA,CAAeC,KAAuBqB,MAAAA,EAAc;AACzD,EAAA,MAAMpB,QAAQD,GAAAA,CAAIC,KAAAA;AAClB,EAAA,MAAMC,IAAAA,GAAOD,KAAAA,CAAME,KAAAA,CAAMkB,MAAAA,CAAOjB,MAAM,CAAA;AAEtC,EAAA,OAAOF,OAAOA,IAAAA,GAAOD,KAAAA;AACzB;AALSF,MAAAA,CAAAA,eAAAA,EAAAA,gBAAAA,CAAAA;AAWF,IAAMuB,KAAAA,GAGT;EACApB,IAAAA,EAAM,OAAA;AAENI,EAAAA,QAAAA,sBAAcC,GAAAA,EAAAA;AAEdgB,EAAAA,KAAAA,CAAMC,MAAkBC,OAAAA,EAAO;AAC3B,IAAA,IAAA,CAAKJ,MAAAA,GAASI,SAASJ,MAAAA,IAAUvB,OAAAA;AACrC,EAAA,CAAA;AAEAU,EAAAA,QAAAA,CAASC,GAAAA,EAAe;AACpB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,iBAAmC,CAAA,4BAAA,EAA+Bb,OAAAA,IAAU,CAAA,CAAEc,OAAAA,CAAQc,CAAAA,KAAAA,KAAAA;AACvF,MAAA,MAAMb,WAAmB,EAAA;AAEzB,MAAA,MAAMC,KAAAA,GAAQf,eAAAA,CAAe2B,KAAAA,EAAO,IAAA,CAAKL,MAAM,CAAA;AAC/C,MAAA,IAAIP,KAAAA,EAAOD,QAAAA,CAASE,IAAAA,CAAKD,KAAAA,CAAAA;AAEzB,MAAA,IAAA,CAAKR,QAAAA,CAASU,GAAAA,CAAIU,KAAAA,EAAOb,QAAAA,CAAAA;IAC7B,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAI,EAAAA,IAAAA,CAAKR,GAAAA,EAAe;AAChB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,iBAAmC,CAAA,4BAAA,EAA+Bb,OAAAA,IAAU,CAAA,CAAEc,OAAAA,CAAQc,CAAAA,KAAAA,KAAAA;AACvFA,MAAAA,KAAAA,CAAMR,gBAAAA,CAAiB,QAAA,EAAUS,CAAAA,CAAAA,KAAAA;AAC7B,QAAA,MAAMC,SAASD,CAAAA,CAAEE,aAAAA;AACjB,QAAA,MAAMf,KAAAA,GAAQf,eAAAA,CAAe6B,MAAAA,EAAQ,IAAA,CAAKP,MAAM,CAAA;AAEhD,QAAA,IAAIP,KAAAA,EAAOL,GAAAA,CAAIO,GAAAA,CAAIF,KAAAA,CAAAA;MACvB,CAAA,CAAA;IACJ,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAgB,EAAAA,IAAAA,CAAKrB,GAAAA,EAAe;AAChB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AACjB,IAAA,MAAMqB,MAAAA,GAAStB,IAAIsB,MAAAA,EAAM;AAEzBrB,IAAAA,IAAAA,CAAKC,iBAAmC,CAAA,4BAAA,EAA+Bb,OAAAA,IAAU,CAAA,CAAEc,OAAAA,CAAQc,CAAAA,KAAAA,KAAAA;AACvF,MAAA,MAAMZ,KAAAA,GAAQf,eAAAA,CAAe2B,KAAAA,EAAO,IAAA,CAAKL,MAAM,CAAA;AAC/CK,MAAAA,KAAAA,CAAMM,UAAUlB,KAAAA,KAAUiB,MAAAA;IAC9B,CAAA,CAAA;AACJ,EAAA;AACJ;;;AC5DA,IAAMjC,OAAAA,GAAS,QAAA;AAEf,SAASC,eAAAA,CAAeC,KAAwBqB,MAAAA,EAAc;AAC1D,EAAA,MAAMpB,QAAQD,GAAAA,CAAIC,KAAAA;AAClB,EAAA,MAAMC,IAAAA,GAAOD,KAAAA,CAAME,KAAAA,CAAMkB,MAAAA,CAAOjB,MAAM,CAAA;AAEtC,EAAA,OAAOF,OAAOA,IAAAA,GAAOD,KAAAA;AACzB;AALSF,MAAAA,CAAAA,eAAAA,EAAAA,gBAAAA,CAAAA;AAqBF,IAAMkC,MAAAA,GAGT;EACA/B,IAAAA,EAAM,QAAA;AAENI,EAAAA,QAAAA,sBAAcC,GAAAA,EAAAA;AAEdgB,EAAAA,KAAAA,CAAMC,MAAkBC,OAAAA,EAAO;AAC3B,IAAA,IAAA,CAAKJ,MAAAA,GAASI,SAASJ,MAAAA,IAAUvB,OAAAA;AACrC,EAAA,CAAA;AAEAU,EAAAA,QAAAA,CAASC,GAAAA,EAAe;AACpB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,gBAAAA,CAAoC,6BAA6B,IAAA,CAAKU,MAAM,KAAK,CAAA,CAAET,OAAAA,CAAQsB,CAAAA,GAAAA,KAAAA;AAC5F,MAAA,MAAMrB,WAAmB,EAAA;AAEzBsB,MAAAA,KAAAA,CAAMC,KAAKF,GAAAA,CAAIT,OAAO,CAAA,CAAEb,OAAAA,CAAQyB,CAAAA,MAAAA,KAAAA;AAC5B,QAAA,MAAMvB,KAAAA,GAAQf,eAAAA,CAAesC,MAAAA,EAAQ,IAAA,CAAKhB,MAAM,CAAA;AAChD,QAAA,IAAIP,KAAAA,EAAOD,QAAAA,CAASE,IAAAA,CAAKD,KAAAA,CAAAA;MAC7B,CAAA,CAAA;AAEA,MAAA,IAAA,CAAKR,QAAAA,CAASU,GAAAA,CAAIkB,GAAAA,EAAKrB,QAAAA,CAAAA;IAC3B,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAI,EAAAA,IAAAA,CAAKR,GAAAA,EAAe;AAChB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AAEjBA,IAAAA,IAAAA,CAAKC,gBAAAA,CAAoC,6BAA6B,IAAA,CAAKU,MAAM,KAAK,CAAA,CAAET,OAAAA,CAAQsB,CAAAA,GAAAA,KAAAA;AAC5FA,MAAAA,GAAAA,CAAIhB,gBAAAA,CAAiB,QAAA,EAAUS,CAAAA,CAAAA,KAAAA;AAC3B,QAAA,MAAMC,SAASD,CAAAA,CAAEE,aAAAA;AACjB,QAAA,MAAMQ,MAAAA,GAAST,MAAAA,CAAOH,OAAAA,CAASG,MAAAA,CAAOU,aAAa,CAAA;AAEnD,QAAA,MAAMxB,KAAAA,GAAQf,eAAAA,CAAesC,MAAAA,EAA6B,IAAA,CAAKhB,MAAM,CAAA;AACrE,QAAA,IAAIP,KAAAA,EAAOL,GAAAA,CAAIO,GAAAA,CAAIF,KAAAA,CAAAA;MACvB,CAAA,CAAA;IACJ,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAgB,EAAAA,IAAAA,CAAKrB,GAAAA,EAAe;AAChB,IAAA,MAAMC,OAAOD,GAAAA,CAAIC,IAAAA;AACjB,IAAA,MAAMqB,MAAAA,GAAStB,IAAIsB,MAAAA,EAAM;AAEzBrB,IAAAA,IAAAA,CAAKC,gBAAAA,CAAoC,6BAA6B,IAAA,CAAKU,MAAM,KAAK,CAAA,CAAET,OAAAA,CAAQsB,CAAAA,GAAAA,KAAAA;AAC5FC,MAAAA,KAAAA,CAAMC,KAAKF,GAAAA,CAAIT,OAAO,CAAA,CAAEb,OAAAA,CAAQyB,CAAAA,MAAAA,KAAAA;AAC5B,QAAA,MAAMvB,KAAAA,GAAQf,eAAAA,CAAesC,MAAAA,EAAQ,IAAA,CAAKhB,MAAM,CAAA;AAChDgB,QAAAA,MAAAA,CAAOE,WAAWzB,KAAAA,KAAUiB,MAAAA;MAChC,CAAA,CAAA;IACJ,CAAA,CAAA;AACJ,EAAA;AACJ","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 \"../core/controller.js\";\r\nimport type { Adapter } from \"../core/adapter.js\";\r\nimport { State, type Name } from \"../core/types.js\";\r\n\r\nconst PREFIX = \"theme:\";\r\n\r\nfunction parseThemeName(btn: HTMLButtonElement): State {\r\n const value = btn.value;\r\n const name = value.slice(PREFIX.length) as State;\r\n\r\n return name ? name : value as State;\r\n}\r\n\r\n/**\r\n * @example\r\n * <button value=\"<prefix>light\">Light</button>\r\n * <button value=\"<prefix>dark\">Dark</button>\r\n */\r\nexport const Button: Adapter & {\r\n registry: Map<Element, Name[]>;\r\n} = {\r\n name: \"button\",\r\n\r\n registry: new Map<Element, Name[]>(),\r\n\r\n discover(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLButtonElement>(`button[value^=\"${PREFIX}\"]`).forEach(btn => {\r\n const detected: Name[] = [];\r\n\r\n const theme = parseThemeName(btn);\r\n if (theme) detected.push(theme);\r\n\r\n this.registry.set(btn, detected);\r\n });\r\n },\r\n\r\n bind(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLButtonElement>(`button[value^=\"${PREFIX}\"]`).forEach(btn => {\r\n const theme = parseThemeName(btn);\r\n\r\n btn.addEventListener(\"click\", () => {\r\n if (this.registry.get(btn)?.includes(theme)) {\r\n ctl.set(theme);\r\n }\r\n });\r\n });\r\n },\r\n};\r\n","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport type { Adapter } from '../core/adapter.js';\r\nimport { Controller } from '../core/controller.js';\r\nimport { Name, State } from '../core/types.js';\r\n\r\nconst PREFIX = \"theme:\";\r\n\r\nfunction parseThemeName(btn: HTMLInputElement, prefix: string): State {\r\n const value = btn.value;\r\n const name = value.slice(prefix.length) as State;\r\n\r\n return name ? name : value as State;\r\n}\r\n\r\ninterface Options {\r\n prefix?: string;\r\n};\r\n\r\nexport const Radio: Adapter<Options> & {\r\n registry: Map<Element, Name[]>;\r\n prefix?: string;\r\n} = {\r\n name: 'radio',\r\n\r\n registry: new Map<Element, Name[]>(),\r\n\r\n setup(_ctl: Controller, options) {\r\n this.prefix = options?.prefix ?? PREFIX;\r\n },\r\n\r\n discover(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLInputElement>(`input[type=\"radio\"][value^=\"${PREFIX}\"]`).forEach(radio => {\r\n const detected: Name[] = [];\r\n\r\n const theme = parseThemeName(radio, this.prefix!);\r\n if (theme) detected.push(theme);\r\n\r\n this.registry.set(radio, detected);\r\n });\r\n },\r\n\r\n bind(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLInputElement>(`input[type=\"radio\"][value^=\"${PREFIX}\"]`).forEach(radio => { \r\n radio.addEventListener('change', e => {\r\n const target = e.currentTarget as HTMLInputElement;\r\n const theme = parseThemeName(target, this.prefix!);\r\n \r\n if (theme) ctl.set(theme);\r\n });\r\n });\r\n },\r\n\r\n sync(ctl: Controller) {\r\n const root = ctl.root;\r\n const active = ctl.active();\r\n\r\n root.querySelectorAll<HTMLInputElement>(`input[type=\"radio\"][value^=\"${PREFIX}\"]`).forEach(radio => {\r\n const theme = parseThemeName(radio, this.prefix!);\r\n radio.checked = theme === active;\r\n });\r\n }\r\n};\r\n","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport type { Adapter } from \"../core/adapter.js\";\r\nimport { Controller } from \"../core/controller.js\";\r\nimport { State, type Name } from \"../core/types.js\";\r\n\r\nconst PREFIX = \"theme:\";\r\n\r\nfunction parseThemeName(btn: HTMLOptionElement, prefix: string): State {\r\n const value = btn.value;\r\n const name = value.slice(prefix.length) as State;\r\n\r\n return name ? name : value as State;\r\n}\r\n\r\ninterface Options {\r\n prefix?: string;\r\n};\r\n\r\n/**\r\n * @example\r\n * ```html\r\n * <select>\r\n * <option value=\"theme:light\">Light</option>\r\n * <option value=\"theme:system\">System</option>\r\n * <option value=\"theme:dark\">Dark</option>\r\n * </select>\r\n * ```\r\n */\r\nexport const Select: Adapter<Options> & {\r\n registry: Map<Element, Name[]>;\r\n prefix?: string;\r\n} = {\r\n name: \"select\",\r\n\r\n registry: new Map<Element, Name[]>(),\r\n\r\n setup(_ctl: Controller, options) {\r\n this.prefix = options?.prefix ?? PREFIX;\r\n },\r\n\r\n discover(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLSelectElement>(`select:has(option[value^=\"${this.prefix}\"])`).forEach(sel => {\r\n const detected: Name[] = [];\r\n\r\n Array.from(sel.options).forEach(option => {\r\n const theme = parseThemeName(option, this.prefix!);\r\n if (theme) detected.push(theme);\r\n });\r\n\r\n this.registry.set(sel, detected);\r\n });\r\n },\r\n\r\n bind(ctl: Controller) {\r\n const root = ctl.root;\r\n\r\n root.querySelectorAll<HTMLSelectElement>(`select:has(option[value^=\"${this.prefix}\"])`).forEach(sel => {\r\n sel.addEventListener(\"change\", e => {\r\n const target = e.currentTarget as HTMLSelectElement;\r\n const option = target.options[ target.selectedIndex ];\r\n\r\n const theme = parseThemeName(option as HTMLOptionElement, this.prefix!);\r\n if (theme) ctl.set(theme);\r\n });\r\n });\r\n },\r\n\r\n sync(ctl: Controller) {\r\n const root = ctl.root;\r\n const active = ctl.active();\r\n\r\n root.querySelectorAll<HTMLSelectElement>(`select:has(option[value^=\"${this.prefix}\"])`).forEach(sel => {\r\n Array.from(sel.options).forEach(option => {\r\n const theme = parseThemeName(option, this.prefix!);\r\n option.selected = theme === active;\r\n });\r\n });\r\n }\r\n};\r\n"]}
1
+ {"version":3,"sources":["../../src/core/adapter.ts","../../src/adapters/button.ts","../../src/adapters/radio.ts","../../src/adapters/select.ts"],"names":["defineAdapter","definition","Button","name","defaults","prefix","DEFAULT_PREFIX","animate","enabled","duration","easing","selector","o","bind","ctl","el","addEventListener","event","theme","parseValue","value","state","document","startViewTransition","set","clientX","x","clientY","y","radius","Math","hypot","max","innerWidth","innerHeight","transition","ready","documentElement","clipPath","pseudoElement","Radio","checked","sync","active","Select","option","options","selectedIndex","selected"],"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 type { Controller } from \"./controller.js\";\r\n\r\nexport interface AdapterDefinition<\r\n\tE extends Element,\r\n\tO = void\r\n> {\r\n\tname: string;\r\n\r\n\t/** Default options — merged with options passed to `ctl.use()`. */\r\n\tdefaults?: Partial<O>;\r\n\r\n\t/** CSS selector string, or a function that returns one given resolved options. */\r\n\tselector: string | ((options: O) => string);\r\n\r\n\t/**\r\n\t * Attach event listeners to a single element.\r\n\t * Called once per element — at `use()` time, or when a new element is observed.\r\n\t */\r\n\tbind?(ctl: Controller, element: E, options: O): void;\r\n\r\n\t/**\r\n\t * Sync element UI state with the controller's current theme.\r\n\t * Called after every `ctl.set()` and on initial `use()`.\r\n\t */\r\n\tsync?(ctl: Controller, element: E, options: O): void;\r\n}\r\n\r\n/**\r\n * Type helper for defining adapters with full inference.\r\n * Zero runtime cost — returns the definition unchanged.\r\n * \r\n * @example\r\n * export const MyAdapter = defineAdapter<HTMLButtonElement, MyOptions>({\r\n * name: \"my-adapter\",\r\n * defaults: { prefix: \"theme:\" },\r\n * selector: (o) => `button[value=\"${o.prefix}\"]`,\r\n * bind(ctl, el, options) { ... },\r\n * sync(ctl, el, options) { ... },\r\n * });\r\n */\r\nexport function defineAdapter<\r\n\tE extends Element,\r\n\tO = void\r\n>(\r\n\tdefinition: AdapterDefinition<E, O>\r\n): AdapterDefinition<E, O> {\r\n\treturn definition;\r\n}","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { defineAdapter } from \"../core/adapter.js\";\r\nimport { State } from \"../core/types.js\";\r\nimport { DEFAULT_PREFIX, parseValue } from \"../core/utils.js\";\r\n\r\ntype Easing = \"linear\" | \"ease\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\";\r\n\r\ninterface ButtonOptions {\r\n prefix?: string;\r\n animate?: {\r\n enabled?: boolean;\r\n duration?: number;\r\n easing?: Easing;\r\n }\r\n}\r\n\r\n/**\r\n * @example\r\n * <button value=\"theme:light\">Light</button>\r\n * <button value=\"theme:dark\">Dark</button>\r\n */\r\nexport const Button = defineAdapter<HTMLButtonElement, ButtonOptions>({\r\n name: \"button\",\r\n\r\n defaults: {\r\n prefix: DEFAULT_PREFIX,\r\n animate: {\r\n enabled: false,\r\n duration: 600,\r\n easing: \"ease-in-out\"\r\n }\r\n },\r\n\r\n selector: (o) => `button[value^=\"${o.prefix ?? DEFAULT_PREFIX}\"]`,\r\n\r\n bind(ctl, el, { prefix = DEFAULT_PREFIX, animate }) {\r\n el.addEventListener(\"click\", async (event) => {\r\n const theme = parseValue(el.value, prefix) as State;\r\n\r\n if (theme === ctl.state) return;\r\n\r\n if (!animate?.enabled || !document.startViewTransition) {\r\n ctl.set(theme);\r\n return;\r\n }\r\n\r\n const { clientX: x, clientY: y } = event;\r\n\r\n const radius = Math.hypot(\r\n Math.max(x, innerWidth - x),\r\n Math.max(y, innerHeight - y)\r\n );\r\n\r\n const transition = document.startViewTransition(() => ctl.set(theme));\r\n\r\n await transition.ready;\r\n\r\n document.documentElement.animate(\r\n {\r\n clipPath: [\r\n `circle(0px at ${x}px ${y}px)`,\r\n `circle(${radius}px at ${x}px ${y}px)`\r\n ]\r\n },\r\n {\r\n duration: animate.duration ?? 500,\r\n easing: animate.easing ?? \"ease-in-out\",\r\n pseudoElement: \"::view-transition-new(root)\"\r\n }\r\n );\r\n });\r\n },\r\n});\r\n","// /**\r\n// * Copyright (c) 2026 GTF\r\n// * SPDX-License-Identifier: Apache-2.0\r\n// */\r\n\r\nimport { defineAdapter } from \"../core/adapter.js\";\r\nimport { State } from \"../core/types.js\";\r\nimport { DEFAULT_PREFIX, parseValue } from \"../core/utils.js\";\r\n\r\ninterface RadioOptions {\r\n prefix?: string;\r\n}\r\n\r\n/**\r\n * @example\r\n * <input type=\"radio\" name=\"theme\" value=\"theme:light\">\r\n * <input type=\"radio\" name=\"theme\" value=\"theme:dark\">\r\n * <input type=\"radio\" name=\"theme\" value=\"theme:system\">\r\n */\r\nexport const Radio = defineAdapter<HTMLInputElement, RadioOptions>({\r\n name: \"radio\",\r\n\r\n defaults: {\r\n prefix: DEFAULT_PREFIX,\r\n },\r\n\r\n selector: (o) => `input[type=\"radio\"][value^=\"${o.prefix}\"]`,\r\n\r\n bind(ctl, el, { prefix = DEFAULT_PREFIX }) {\r\n el.addEventListener(\"change\", () => {\r\n if (!el.checked) return;\r\n\r\n const theme = parseValue(el.value, prefix) as State;\r\n\r\n if (theme === ctl.state) return;\r\n\r\n ctl.set(theme);\r\n });\r\n },\r\n\r\n sync(ctl, el, { prefix = DEFAULT_PREFIX }) {\r\n el.checked = parseValue(el.value, prefix) === ctl.active();\r\n }\r\n});","/**\r\n * Copyright (c) 2026 GTF\r\n * SPDX-License-Identifier: Apache-2.0\r\n */\r\n\r\nimport { defineAdapter } from \"../core/adapter.js\";\r\nimport { State } from \"../core/types.js\";\r\nimport { DEFAULT_PREFIX, parseValue } from \"../core/utils.js\";\r\n\r\ninterface SelectOptions {\r\n prefix?: string;\r\n}\r\n\r\n/**\r\n * @example\r\n * ```html\r\n * <select>\r\n * <option value=\"theme:light\">Light</option>\r\n * <option value=\"theme:system\">System</option>\r\n * <option value=\"theme:dark\">Dark</option>\r\n * </select>\r\n * ```\r\n */\r\nexport const Select = defineAdapter<HTMLSelectElement, SelectOptions>({\r\n name: \"select\",\r\n\r\n defaults: {\r\n prefix: DEFAULT_PREFIX,\r\n },\r\n \r\n selector: (o) => `select:has(option[value^=\"${o.prefix}\"])`,\r\n\r\n bind(ctl, el, { prefix = DEFAULT_PREFIX }) {\r\n el.addEventListener(\"change\", () => {\r\n const option = el.options[ el.selectedIndex ];\r\n const theme = parseValue(option.value, prefix) as State;\r\n\r\n if (theme === ctl.state) return;\r\n\r\n ctl.set(theme);\r\n });\r\n },\r\n\r\n sync(ctl, el, { prefix = DEFAULT_PREFIX }) {\r\n const active = ctl.active();\r\n\r\n for (const option of el.options) {\r\n option.selected = parseValue(option.value, prefix) === active;\r\n }\r\n }\r\n});"]}
@@ -0,0 +1,34 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/core/utils.ts
5
+ var DEFAULT_PREFIX = "theme:";
6
+ function resolveStateOf(state, system, config) {
7
+ if (state === config.system) {
8
+ return system.prefers;
9
+ }
10
+ if (state === config.light || state === config.dark) {
11
+ return state;
12
+ }
13
+ return typeof state === "string" ? state : null;
14
+ }
15
+ __name(resolveStateOf, "resolveStateOf");
16
+ function resolveRoot(root) {
17
+ if (root instanceof HTMLElement) {
18
+ return root;
19
+ } else if (root instanceof Document) {
20
+ return root.documentElement;
21
+ } else if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
22
+ return root.host;
23
+ }
24
+ return document.documentElement;
25
+ }
26
+ __name(resolveRoot, "resolveRoot");
27
+ function parseValue(value, prefix) {
28
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
29
+ }
30
+ __name(parseValue, "parseValue");
31
+
32
+ export { DEFAULT_PREFIX, __name, parseValue, resolveRoot, resolveStateOf };
33
+ //# sourceMappingURL=chunk-NYMA6GFH.js.map
34
+ //# sourceMappingURL=chunk-NYMA6GFH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/utils.ts"],"names":["DEFAULT_PREFIX","resolveStateOf","state","system","config","prefers","light","dark","resolveRoot","root","HTMLElement","Document","documentElement","ShadowRoot","host","document","parseValue","value","prefix","startsWith","slice","length"],"mappings":";;;;AASO,IAAMA,cAAAA,GAAiB;AAYvB,SAASC,cAAAA,CAAeC,KAAAA,EAAcC,MAAAA,EAAgBC,MAAAA,EAAsB;AAClF,EAAA,IAAIF,KAAAA,KAAUE,OAAOD,MAAAA,EAAQ;AAC5B,IAAA,OAAOA,MAAAA,CAAOE,OAAAA;AACf,EAAA;AAEA,EAAA,IAAIH,KAAAA,KAAUE,MAAAA,CAAOE,KAAAA,IAASJ,KAAAA,KAAUE,OAAOG,IAAAA,EAAM;AACpD,IAAA,OAAOL,KAAAA;AACR,EAAA;AAEA,EAAA,OAAO,OAAOA,KAAAA,KAAU,QAAA,GAAWA,KAAAA,GAAQ,IAAA;AAC5C;AAVgBD,MAAAA,CAAAA,cAAAA,EAAAA,gBAAAA,CAAAA;AAuBT,SAASO,YAAYC,IAAAA,EAAa;AACxC,EAAA,IAAIA,gBAAgBC,WAAAA,EAAa;AAChC,IAAA,OAAOD,IAAAA;AACR,EAAA,CAAA,MAAA,IAAWA,gBAAgBE,QAAAA,EAAU;AACpC,IAAA,OAAOF,IAAAA,CAAKG,eAAAA;AACb,EAAA,CAAA,MAAA,IAAWH,IAAAA,YAAgBI,UAAAA,IAAcJ,IAAAA,CAAKK,IAAAA,YAAgBJ,WAAAA,EAAa;AAC1E,IAAA,OAAOD,IAAAA,CAAKK,IAAAA;AACb,EAAA;AAEA,EAAA,OAAOC,QAAAA,CAASH,eAAAA;AACjB;AAVgBJ,MAAAA,CAAAA,WAAAA,EAAAA,aAAAA,CAAAA;AAuBT,SAASQ,UAAAA,CAAWC,OAAeC,MAAAA,EAAc;AACvD,EAAA,OAAOD,KAAAA,CAAME,WAAWD,MAAAA,CAAAA,GACrBD,MAAMG,KAAAA,CAAMF,MAAAA,CAAOG,MAAM,CAAA,GACzBJ,KAAAA;AACJ;AAJgBD,MAAAA,CAAAA,UAAAA,EAAAA,YAAAA,CAAAA","file":"chunk-NYMA6GFH.js","sourcesContent":["/**\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}"]}
@@ -1,3 +1,40 @@
1
+ /**
2
+ * Copyright (c) 2026 GTF
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ interface AdapterDefinition<E extends Element, O = void> {
7
+ name: string;
8
+ /** Default options — merged with options passed to `ctl.use()`. */
9
+ defaults?: Partial<O>;
10
+ /** CSS selector string, or a function that returns one given resolved options. */
11
+ selector: string | ((options: O) => string);
12
+ /**
13
+ * Attach event listeners to a single element.
14
+ * Called once per element — at `use()` time, or when a new element is observed.
15
+ */
16
+ bind?(ctl: Controller, element: E, options: O): void;
17
+ /**
18
+ * Sync element UI state with the controller's current theme.
19
+ * Called after every `ctl.set()` and on initial `use()`.
20
+ */
21
+ sync?(ctl: Controller, element: E, options: O): void;
22
+ }
23
+ /**
24
+ * Type helper for defining adapters with full inference.
25
+ * Zero runtime cost — returns the definition unchanged.
26
+ *
27
+ * @example
28
+ * export const MyAdapter = defineAdapter<HTMLButtonElement, MyOptions>({
29
+ * name: "my-adapter",
30
+ * defaults: { prefix: "theme:" },
31
+ * selector: (o) => `button[value="${o.prefix}"]`,
32
+ * bind(ctl, el, options) { ... },
33
+ * sync(ctl, el, options) { ... },
34
+ * });
35
+ */
36
+ declare function defineAdapter<E extends Element, O = void>(definition: AdapterDefinition<E, O>): AdapterDefinition<E, O>;
37
+
1
38
  /**
2
39
  * Copyright (c) 2026 GTF
3
40
  * SPDX-License-Identifier: Apache-2.0
@@ -13,6 +50,40 @@ type Name = string;
13
50
  * - Effective themes are the resolved values (e.g. `<dark>`, `<light>`, `mint`).
14
51
  */
15
52
  type State = "system" | "light" | "dark";
53
+ /**
54
+ * Emitted when the theme state changes.
55
+ */
56
+ interface ChangeEvent {
57
+ from: State | null;
58
+ to: State;
59
+ theme: Name;
60
+ }
61
+ interface ApplyEvent {
62
+ theme: Name;
63
+ }
64
+ type NEvents = {
65
+ change: ChangeEvent;
66
+ apply: ApplyEvent;
67
+ };
68
+
69
+ /**
70
+ * Copyright (c) 2026 GTF
71
+ * SPDX-License-Identifier: Apache-2.0
72
+ */
73
+
74
+ /**
75
+ * A storage interface for persisting the theme state.
76
+ *
77
+ * @description
78
+ * The theme state is persisted in a storage mechanism that is
79
+ * specific to the application. For example, a web application might
80
+ * use cookies, local storage, or a database.
81
+ */
82
+ interface StateStorage {
83
+ get(): State | null;
84
+ set(state: State): void;
85
+ remove(): void;
86
+ }
16
87
 
17
88
  /**
18
89
  * Copyright (c) 2026 GTF
@@ -23,27 +94,89 @@ type State = "system" | "light" | "dark";
23
94
  * Raw configuration, passed by the user.
24
95
  */
25
96
  interface RawConfig {
97
+ /**
98
+ * Theme attribute name.
99
+ *
100
+ * @default "data-theme"
101
+ * @example
102
+ * <html data-theme="light">
103
+ */
26
104
  attribute?: string;
27
- storage?: {
28
- key: string;
29
- };
105
+ /**
106
+ * Theme storage type.
107
+ *
108
+ * @description
109
+ * The controller will use the storage to persist the theme state.
110
+ *
111
+ * @default @see {@link LocalStorage}
112
+ */
113
+ storage?: StateStorage;
114
+ /**
115
+ * System theme name.
116
+ *
117
+ * @default "system"
118
+ * @example "auto" | "sync" | ...
119
+ */
30
120
  system?: string;
121
+ /**
122
+ * Light theme name.
123
+ *
124
+ * @default "light"
125
+ * @example "bright" | ...
126
+ */
31
127
  light?: Name;
128
+ /**
129
+ * Dark theme name.
130
+ *
131
+ * @default "dark"
132
+ * @example "dim" | ...
133
+ */
32
134
  dark?: Name;
135
+ /**
136
+ * Initial theme name.
137
+ *
138
+ * @default "dark"
139
+ * @example "light" | "mint" | ...
140
+ */
33
141
  initial?: Name;
142
+ /**
143
+ * Observe the DOM elements for changes.
144
+ * Automatically bind new matching elements.
145
+ *
146
+ * @default false
147
+ */
148
+ observe?: boolean;
34
149
  }
35
150
  /**
36
151
  * Resolved configuration by the controller.
37
152
  */
38
153
  interface ResolvedConfig {
39
154
  attribute: string;
40
- storage: {
41
- key: string;
42
- };
155
+ storage: StateStorage;
43
156
  system: string;
44
157
  light: Name;
45
158
  dark: Name;
46
159
  initial?: Name;
160
+ observe: boolean;
161
+ }
162
+
163
+ /**
164
+ * Copyright (c) 2026 GTF
165
+ * SPDX-License-Identifier: Apache-2.0
166
+ */
167
+ type Listener<T> = (event: T) => void;
168
+ /**
169
+ * A simple event emitter implementation.
170
+ *
171
+ * @description
172
+ * This class is suitable for use in a web application that wants to
173
+ * listen for changes in the theme state.
174
+ */
175
+ declare class EventEmitter<M extends Record<string, unknown>> {
176
+ private _map;
177
+ on<K extends keyof M>(event: K, listener: Listener<M[K]>): () => void;
178
+ off<K extends keyof M>(event: K, listener: Listener<M[K]>): void;
179
+ emit<K extends keyof M>(event: K, payload: M[K]): void;
47
180
  }
48
181
 
49
182
  /**
@@ -54,7 +187,7 @@ interface ResolvedConfig {
54
187
  /**
55
188
  * Theme controller logic.
56
189
  */
57
- declare class Controller {
190
+ declare class Controller extends EventEmitter<NEvents> {
58
191
  private _root;
59
192
  /**
60
193
  * Resolved configuration.
@@ -64,7 +197,8 @@ declare class Controller {
64
197
  * System theme proxy.
65
198
  */
66
199
  private system;
67
- private adapters;
200
+ private _adapters;
201
+ private _observer?;
68
202
  constructor(_root?: Document | ParentNode, cfg?: RawConfig);
69
203
  /**
70
204
  * The root element to apply theme to.
@@ -103,18 +237,29 @@ declare class Controller {
103
237
  * @param adapter Adapter instance
104
238
  * @param options Adapter options
105
239
  */
106
- use<T extends Adapter<any>>(adapter: T, options?: T extends Adapter<infer O> ? O : never): this;
240
+ use<O>(def: AdapterDefinition<any, O>, options?: Partial<O>): this;
107
241
  /**
108
242
  * Set the effective theme from a theme state.
109
243
  *
110
244
  * @see {@link State}
111
245
  * @param state Theme State
112
246
  */
113
- set(state: State): void;
247
+ set(state: State): this;
114
248
  /**
115
249
  * Read the stored theme state from local storage.
116
250
  */
117
251
  get state(): State | null;
252
+ /**
253
+ * Destroy the controller.
254
+ *
255
+ * @description
256
+ * This method disconnects the observer, stops the system listener.
257
+ */
258
+ destroy(): void;
259
+ /**
260
+ * Sync all adapters.
261
+ */
262
+ private syncAdapters;
118
263
  /**
119
264
  * Sync the system listener.
120
265
  */
@@ -131,63 +276,25 @@ declare class Controller {
131
276
  * @param theme Theme state
132
277
  */
133
278
  private write;
134
- }
135
-
136
- /**
137
- * Copyright (c) 2026 GTF
138
- * SPDX-License-Identifier: Apache-2.0
139
- */
140
-
141
- interface Adapter<Options = undefined> {
142
- name: string;
143
- /**
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
154
- */
155
- setup?(ctl: Controller, options: Options): void;
156
279
  /**
157
- * Discover themes that are available to this adapter. \
158
- * Used to validate, or for example do a one-time setup.
280
+ * Start the DOM elements observer.
159
281
  *
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
167
- */
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
282
+ * @description
283
+ * This method starts a MutationObserver that watches for changes to
284
+ * the DOM elements that are registered as adapters. When a change
285
+ * is detected, the controller will bind the element to the adapter.
178
286
  */
179
- bind?(ctl: Controller): void;
287
+ private startObserver;
180
288
  /**
181
- * Sync theme adapter state with the controller.
289
+ * Bind a single DOM element to an adapter.
182
290
  *
183
- * ---
184
- * @remarks
185
- * Called every time the controller is instantiated.
186
- *
187
- * ---
188
- * @param ctl the controller instance
291
+ * @description
292
+ * This method checks if the element matches the adapter's selector.
293
+ * If it does, the adapter's `bind` method is called with the element
294
+ * and the adapter's options. If the adapter has a `sync` method, it
295
+ * is also called with the element and the adapter's options.
189
296
  */
190
- sync?(ctl: Controller): void;
297
+ private bindNode;
191
298
  }
192
299
 
193
- export { type Adapter, Controller, type Name, type State };
300
+ export { type AdapterDefinition, Controller, EventEmitter, type Name, type ResolvedConfig, type State, defineAdapter };
@@ -1,4 +1,55 @@
1
- import { __name } from '../chunk-7QVYU63E.js';
1
+ import { __name, resolveRoot, resolveStateOf } from '../chunk-NYMA6GFH.js';
2
+
3
+ // src/storage/local.ts
4
+ var LocalStorage = class {
5
+ static {
6
+ __name(this, "LocalStorage");
7
+ }
8
+ key;
9
+ constructor(options = {}) {
10
+ this.key = options.key ?? "theme";
11
+ }
12
+ get() {
13
+ try {
14
+ return localStorage.getItem(this.key);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ set(state) {
20
+ try {
21
+ localStorage.setItem(this.key, state);
22
+ } catch {
23
+ console.warn("[LOCAL] Failed to write theme state.");
24
+ }
25
+ }
26
+ remove() {
27
+ try {
28
+ localStorage.removeItem(this.key);
29
+ } catch {
30
+ }
31
+ }
32
+ };
33
+
34
+ // src/core/event.ts
35
+ var EventEmitter = class {
36
+ static {
37
+ __name(this, "EventEmitter");
38
+ }
39
+ _map = /* @__PURE__ */ new Map();
40
+ on(event, listener) {
41
+ let set = this._map.get(event);
42
+ if (!set) this._map.set(event, set = /* @__PURE__ */ new Set());
43
+ set.add(listener);
44
+ return () => this.off(event, listener);
45
+ }
46
+ off(event, listener) {
47
+ this._map.get(event)?.delete(listener);
48
+ }
49
+ emit(event, payload) {
50
+ this._map.get(event)?.forEach((listener) => listener(payload));
51
+ }
52
+ };
2
53
 
3
54
  // src/core/system.ts
4
55
  var System = class {
@@ -59,31 +110,8 @@ var System = class {
59
110
  }
60
111
  };
61
112
 
62
- // src/core/utils.ts
63
- function resolveStateOf(state, system, config) {
64
- if (state === config.system) {
65
- return system.prefers;
66
- }
67
- if (state === config.light || state === config.dark) {
68
- return state;
69
- }
70
- return typeof state === "string" ? state : null;
71
- }
72
- __name(resolveStateOf, "resolveStateOf");
73
- function resolveRoot(root) {
74
- if (root instanceof HTMLElement) {
75
- return root;
76
- } else if (root instanceof Document) {
77
- return root.documentElement;
78
- } else if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
79
- return root.host;
80
- }
81
- return document.documentElement;
82
- }
83
- __name(resolveRoot, "resolveRoot");
84
-
85
113
  // src/core/controller.ts
86
- var Controller = class {
114
+ var Controller = class extends EventEmitter {
87
115
  static {
88
116
  __name(this, "Controller");
89
117
  }
@@ -96,21 +124,24 @@ var Controller = class {
96
124
  * System theme proxy.
97
125
  */
98
126
  system;
99
- adapters = [];
127
+ _adapters = [];
128
+ _observer;
100
129
  constructor(_root = document, cfg = {}) {
101
- this._root = _root;
130
+ super(), this._root = _root;
102
131
  this.config = {
103
132
  attribute: cfg.attribute ?? "data-theme",
104
- storage: {
105
- key: cfg.storage?.key ?? "theme"
106
- },
133
+ storage: cfg.storage ?? new LocalStorage(),
107
134
  system: cfg.system ?? "system",
108
135
  light: cfg.light ?? "light",
109
136
  dark: cfg.dark ?? "dark",
110
- initial: cfg.initial
137
+ initial: cfg.initial,
138
+ observe: cfg.observe ?? false
111
139
  };
112
140
  this.system = new System(this);
113
141
  this.syncSystemListener();
142
+ if (this.config.observe) {
143
+ this.startObserver();
144
+ }
114
145
  }
115
146
  /**
116
147
  * The root element to apply theme to.
@@ -159,20 +190,21 @@ var Controller = class {
159
190
  * @param adapter Adapter instance
160
191
  * @param options Adapter options
161
192
  */
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);
193
+ use(def, options) {
194
+ const resolved = {
195
+ ...def.defaults,
196
+ ...options
197
+ };
198
+ const selector = typeof def.selector === "function" ? def.selector(resolved) : def.selector;
199
+ for (const el of this.root.querySelectorAll(selector)) {
200
+ def.bind?.(this, el, resolved);
201
+ def.sync?.(this, el, resolved);
175
202
  }
203
+ this._adapters.push({
204
+ def,
205
+ options: resolved,
206
+ selector
207
+ });
176
208
  return this;
177
209
  }
178
210
  /**
@@ -182,25 +214,44 @@ var Controller = class {
182
214
  * @param state Theme State
183
215
  */
184
216
  set(state) {
217
+ const from = this.state;
185
218
  const theme = resolveStateOf(state, this.system, this.config);
186
- if (!theme) {
187
- return;
188
- }
219
+ if (!theme) return this;
189
220
  this.write(state);
190
221
  this.syncSystemListener();
191
222
  this.apply(theme);
192
- for (const a of this.adapters) {
193
- a.sync?.(this);
194
- }
223
+ this.syncAdapters();
224
+ this.emit("change", {
225
+ from,
226
+ to: state,
227
+ theme
228
+ });
229
+ return this;
195
230
  }
196
231
  /**
197
232
  * Read the stored theme state from local storage.
198
233
  */
199
234
  get state() {
200
- try {
201
- return localStorage.getItem(this.config.storage.key);
202
- } catch {
203
- return null;
235
+ return this.config.storage.get();
236
+ }
237
+ /**
238
+ * Destroy the controller.
239
+ *
240
+ * @description
241
+ * This method disconnects the observer, stops the system listener.
242
+ */
243
+ destroy() {
244
+ this._observer?.disconnect();
245
+ this.system.stop();
246
+ }
247
+ /**
248
+ * Sync all adapters.
249
+ */
250
+ syncAdapters() {
251
+ for (const { def, options, selector } of this._adapters) {
252
+ for (const el of this.root.querySelectorAll(selector)) {
253
+ def.sync?.(this, el, options);
254
+ }
204
255
  }
205
256
  }
206
257
  /**
@@ -209,13 +260,11 @@ var Controller = class {
209
260
  syncSystemListener() {
210
261
  const state = this.active();
211
262
  if (state === this.config.system) {
212
- this.system.start((_state) => {
263
+ this.system.start(() => {
213
264
  const theme = resolveStateOf("system", this.system, this.config);
214
265
  if (!theme) return;
215
266
  this.apply(theme);
216
- for (const a of this.adapters) {
217
- a.sync?.(this);
218
- }
267
+ this.syncAdapters();
219
268
  });
220
269
  } else {
221
270
  this.system.stop();
@@ -228,6 +277,9 @@ var Controller = class {
228
277
  */
229
278
  apply(theme) {
230
279
  this.root.setAttribute(this.config.attribute, theme);
280
+ this.emit("apply", {
281
+ theme
282
+ });
231
283
  }
232
284
  /**
233
285
  * Write theme state to local storage.
@@ -235,10 +287,50 @@ var Controller = class {
235
287
  * @param theme Theme state
236
288
  */
237
289
  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.`);
290
+ this.config.storage.set(theme);
291
+ }
292
+ /**
293
+ * Start the DOM elements observer.
294
+ *
295
+ * @description
296
+ * This method starts a MutationObserver that watches for changes to
297
+ * the DOM elements that are registered as adapters. When a change
298
+ * is detected, the controller will bind the element to the adapter.
299
+ */
300
+ startObserver() {
301
+ this._observer = new MutationObserver((mutations) => {
302
+ for (const mu of mutations) {
303
+ for (const node of mu.addedNodes) {
304
+ if (node instanceof Element) {
305
+ this.bindNode(node);
306
+ }
307
+ }
308
+ }
309
+ });
310
+ this._observer.observe(this.root, {
311
+ childList: true,
312
+ subtree: true
313
+ });
314
+ }
315
+ /**
316
+ * Bind a single DOM element to an adapter.
317
+ *
318
+ * @description
319
+ * This method checks if the element matches the adapter's selector.
320
+ * If it does, the adapter's `bind` method is called with the element
321
+ * and the adapter's options. If the adapter has a `sync` method, it
322
+ * is also called with the element and the adapter's options.
323
+ */
324
+ bindNode(node) {
325
+ for (const { def, options, selector } of this._adapters) {
326
+ if (node.matches(selector)) {
327
+ def.bind?.(this, node, options);
328
+ def.sync?.(this, node, options);
329
+ }
330
+ for (const el of node.querySelectorAll(selector)) {
331
+ def.bind?.(this, el, options);
332
+ def.sync?.(this, el, options);
333
+ }
242
334
  }
243
335
  }
244
336
  };
@@ -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/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","Controller","system","_adapters","_observer","_root","document","cfg","attribute","storage","initial","observe","syncSystemListener","startObserver","root","resolveRoot","active","resolved","theme","resolveStateOf","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 { 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})}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gottheflag/nova",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.3",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -20,10 +20,14 @@
20
20
  "./adapters": {
21
21
  "types": "./dist/adapters/index.d.ts",
22
22
  "import": "./dist/adapters/index.js"
23
+ },
24
+ "./storage": {
25
+ "types": "./dist/storage/index.d.ts",
26
+ "import": "./dist/storage/index.js"
23
27
  }
24
28
  },
25
29
  "scripts": {
26
- "dev": "concurrently -k -n TS,VITE -c blue,green \"tsc -p tsconfig.build.json --watch\" \"vite example\"",
30
+ "test": "concurrently -k -n TS,VITE -c blue,green \"tsc -p tsconfig.build.json --watch\" \"vite example\"",
27
31
  "build": "tsup",
28
32
  "typecheck": "tsc --noEmit",
29
33
  "prepare": "pnpm run build"
@@ -1,6 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- export { __name };
5
- //# sourceMappingURL=chunk-7QVYU63E.js.map
6
- //# sourceMappingURL=chunk-7QVYU63E.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-7QVYU63E.js"}