@gottheflag/nova 0.1.0-beta.1 → 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 +25 -3
- package/dist/adapters/index.d.ts +31 -16
- package/dist/adapters/index.js +66 -103
- package/dist/adapters/index.js.map +1 -1
- package/dist/chunk-NYMA6GFH.js +34 -0
- package/dist/chunk-NYMA6GFH.js.map +1 -0
- package/dist/core/index.d.ts +183 -57
- package/dist/core/index.js +154 -62
- package/dist/core/index.js.map +1 -1
- package/dist/loader/index.min.js +1 -1
- package/package.json +61 -57
- package/dist/chunk-7QVYU63E.js +0 -6
- package/dist/chunk-7QVYU63E.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,16 +13,38 @@ pnpm add @gottheflag/nova
|
|
|
13
13
|
```js
|
|
14
14
|
import { Controller } from "@gottheflag/nova";
|
|
15
15
|
import { Button } from "@gottheflag/nova/adapters";
|
|
16
|
+
import { LocalStorage } from "@gottheflag/nova/storage";
|
|
16
17
|
|
|
17
18
|
const ctl = new Controller(document, {
|
|
18
19
|
// all configs are optional
|
|
19
|
-
storage: {
|
|
20
|
+
storage: new LocalStorage({
|
|
21
|
+
key: "theme",
|
|
22
|
+
}),
|
|
20
23
|
attribute: "data-theme",
|
|
21
24
|
initial: "dark",
|
|
22
25
|
dark: "dark",
|
|
23
26
|
light: "light",
|
|
24
27
|
system: "system",
|
|
28
|
+
observe: true,
|
|
25
29
|
});
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
// use adapters
|
|
32
|
+
ctl.use(Button, {
|
|
33
|
+
prefix: "theme:",
|
|
34
|
+
animate: {
|
|
35
|
+
enabled: true,
|
|
36
|
+
duration: 1000,
|
|
37
|
+
easing: "ease-in-out",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
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
|
+
});
|
|
50
|
+
```
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AdapterDefinition } from '../core/index.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) 2026 GTF
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
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
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* @example
|
|
5
|
-
* <button value="
|
|
6
|
-
* <button value="
|
|
18
|
+
* <button value="theme:light">Light</button>
|
|
19
|
+
* <button value="theme:dark">Dark</button>
|
|
7
20
|
*/
|
|
8
|
-
declare const Button:
|
|
9
|
-
registry: Map<Element, Name[]>;
|
|
10
|
-
};
|
|
21
|
+
declare const Button: AdapterDefinition<HTMLButtonElement, ButtonOptions>;
|
|
11
22
|
|
|
12
|
-
interface
|
|
23
|
+
interface RadioOptions {
|
|
13
24
|
prefix?: string;
|
|
14
25
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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>;
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Copyright (c) 2026 GTF
|
|
36
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
37
|
+
*/
|
|
38
|
+
interface SelectOptions {
|
|
21
39
|
prefix?: string;
|
|
22
40
|
}
|
|
23
41
|
/**
|
|
@@ -30,9 +48,6 @@ interface Options {
|
|
|
30
48
|
* </select>
|
|
31
49
|
* ```
|
|
32
50
|
*/
|
|
33
|
-
declare const Select:
|
|
34
|
-
registry: Map<Element, Name[]>;
|
|
35
|
-
prefix?: string;
|
|
36
|
-
};
|
|
51
|
+
declare const Select: AdapterDefinition<HTMLSelectElement, SelectOptions>;
|
|
37
52
|
|
|
38
53
|
export { Button, Radio, Select };
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,128 +1,91 @@
|
|
|
1
|
-
import { __name } from '../chunk-
|
|
1
|
+
import { __name, DEFAULT_PREFIX, parseValue } from '../chunk-NYMA6GFH.js';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
11
|
-
|
|
7
|
+
__name(defineAdapter, "defineAdapter");
|
|
8
|
+
|
|
9
|
+
// src/adapters/button.ts
|
|
10
|
+
var Button = defineAdapter({
|
|
12
11
|
name: "button",
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const theme =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
this.prefix = options?.prefix ?? PREFIX2;
|
|
50
|
+
defaults: {
|
|
51
|
+
prefix: DEFAULT_PREFIX
|
|
49
52
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const theme =
|
|
55
|
-
if (theme
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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":";;;AAIA,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":["import { 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","import 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","import 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":";;;AA6CO,SAASA,cAIfC,UAAAA,EAAmC;AAEnC,EAAA,OAAOA,UAAAA;AACR;AAPgBD,MAAAA,CAAAA,aAAAA,EAAAA,eAAAA,CAAAA;;;ACpBT,IAAME,SAASF,aAAAA,CAAgD;EAClEG,IAAAA,EAAM,QAAA;EAENC,QAAAA,EAAU;IACNC,MAAAA,EAAQC,cAAAA;IACRC,OAAAA,EAAS;MACLC,OAAAA,EAAS,KAAA;MACTC,QAAAA,EAAU,GAAA;MACVC,MAAAA,EAAQ;AACZ;AACJ,GAAA;AAEAC,EAAAA,QAAAA,0BAAWC,CAAAA,KAAM,CAAA,eAAA,EAAkBA,CAAAA,CAAEP,MAAAA,IAAUC,cAAAA,CAAAA,EAAAA,CAAAA,EAArC,UAAA,CAAA;AAEVO,EAAAA,IAAAA,CAAKC,KAAKC,EAAAA,EAAI,EAAEV,MAAAA,GAASC,cAAAA,EAAgBC,SAAO,EAAE;AAC9CQ,IAAAA,EAAAA,CAAGC,gBAAAA,CAAiB,OAAA,EAAS,OAAOC,KAAAA,KAAAA;AAChC,MAAA,MAAMC,KAAAA,GAAQC,UAAAA,CAAWJ,EAAAA,CAAGK,KAAAA,EAAOf,MAAAA,CAAAA;AAEnC,MAAA,IAAIa,KAAAA,KAAUJ,IAAIO,KAAAA,EAAO;AAEzB,MAAA,IAAI,CAACd,OAAAA,EAASC,OAAAA,IAAW,CAACc,SAASC,mBAAAA,EAAqB;AACpDT,QAAAA,GAAAA,CAAIU,IAAIN,KAAAA,CAAAA;AACR,QAAA;AACJ,MAAA;AAEA,MAAA,MAAM,EAAEO,OAAAA,EAASC,CAAAA,EAAGC,OAAAA,EAASC,GAAC,GAAKX,KAAAA;AAEnC,MAAA,MAAMY,MAAAA,GAASC,IAAAA,CAAKC,KAAAA,CAChBD,IAAAA,CAAKE,IAAIN,CAAAA,EAAGO,UAAAA,GAAaP,CAAAA,CAAAA,EACzBI,IAAAA,CAAKE,GAAAA,CAAIJ,CAAAA,EAAGM,WAAAA,GAAcN,CAAAA,CAAAA,CAAAA;AAG9B,MAAA,MAAMO,aAAab,QAAAA,CAASC,mBAAAA,CAAoB,MAAMT,GAAAA,CAAIU,GAAAA,CAAIN,KAAAA,CAAAA,CAAAA;AAE9D,MAAA,MAAMiB,UAAAA,CAAWC,KAAAA;AAEjBd,MAAAA,QAAAA,CAASe,gBAAgB9B,OAAAA,CACrB;QACI+B,QAAAA,EAAU;UACN,CAAA,cAAA,EAAiBZ,CAAAA,MAAOE,CAAAA,CAAAA,GAAAA,CAAAA;AACxB,UAAA,CAAA,OAAA,EAAUC,MAAAA,CAAAA,MAAAA,EAAeH,CAAAA,CAAAA,GAAAA,EAAOE,CAAAA,CAAAA,GAAAA;;OAExC,EACA;AACInB,QAAAA,QAAAA,EAAUF,QAAQE,QAAAA,IAAY,GAAA;AAC9BC,QAAAA,MAAAA,EAAQH,QAAQG,MAAAA,IAAU,aAAA;QAC1B6B,aAAAA,EAAe;OACnB,CAAA;IAER,CAAA,CAAA;AACJ,EAAA;AACJ,CAAA;;;ACzDO,IAAMC,QAAQxC,aAAAA,CAA8C;EAC/DG,IAAAA,EAAM,OAAA;EAENC,QAAAA,EAAU;IACNC,MAAAA,EAAQC;AACZ,GAAA;AAEAK,EAAAA,QAAAA,kBAAU,MAAA,CAAA,CAACC,CAAAA,KAAM,CAAA,4BAAA,EAA+BA,CAAAA,CAAEP,MAAM,CAAA,EAAA,CAAA,EAA9C,UAAA,CAAA;AAEVQ,EAAAA,IAAAA,CAAKC,GAAAA,EAAKC,EAAAA,EAAI,EAAEV,MAAAA,GAASC,gBAAc,EAAE;AACrCS,IAAAA,EAAAA,CAAGC,gBAAAA,CAAiB,UAAU,MAAA;AAC1B,MAAA,IAAI,CAACD,GAAG0B,OAAAA,EAAS;AAEjB,MAAA,MAAMvB,KAAAA,GAAQC,UAAAA,CAAWJ,EAAAA,CAAGK,KAAAA,EAAOf,MAAAA,CAAAA;AAEnC,MAAA,IAAIa,KAAAA,KAAUJ,IAAIO,KAAAA,EAAO;AAEzBP,MAAAA,GAAAA,CAAIU,IAAIN,KAAAA,CAAAA;IACZ,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAwB,EAAAA,IAAAA,CAAK5B,GAAAA,EAAKC,EAAAA,EAAI,EAAEV,MAAAA,GAASC,gBAAc,EAAE;AACrCS,IAAAA,EAAAA,CAAG0B,UAAUtB,UAAAA,CAAWJ,EAAAA,CAAGK,OAAOf,MAAAA,CAAAA,KAAYS,IAAI6B,MAAAA,EAAM;AAC5D,EAAA;AACJ,CAAA;;;ACpBO,IAAMC,SAAS5C,aAAAA,CAAgD;EAClEG,IAAAA,EAAM,QAAA;EAENC,QAAAA,EAAU;IACNC,MAAAA,EAAQC;AACZ,GAAA;AAEAK,EAAAA,QAAAA,kBAAU,MAAA,CAAA,CAACC,CAAAA,KAAM,CAAA,0BAAA,EAA6BA,CAAAA,CAAEP,MAAM,CAAA,GAAA,CAAA,EAA5C,UAAA,CAAA;AAEVQ,EAAAA,IAAAA,CAAKC,GAAAA,EAAKC,EAAAA,EAAI,EAAEV,MAAAA,GAASC,gBAAc,EAAE;AACrCS,IAAAA,EAAAA,CAAGC,gBAAAA,CAAiB,UAAU,MAAA;AAC1B,MAAA,MAAM6B,MAAAA,GAAS9B,EAAAA,CAAG+B,OAAAA,CAAS/B,EAAAA,CAAGgC,aAAa,CAAA;AAC3C,MAAA,MAAM7B,KAAAA,GAAQC,UAAAA,CAAW0B,MAAAA,CAAOzB,KAAAA,EAAOf,MAAAA,CAAAA;AAEvC,MAAA,IAAIa,KAAAA,KAAUJ,IAAIO,KAAAA,EAAO;AAEzBP,MAAAA,GAAAA,CAAIU,IAAIN,KAAAA,CAAAA;IACZ,CAAA,CAAA;AACJ,EAAA,CAAA;AAEAwB,EAAAA,IAAAA,CAAK5B,GAAAA,EAAKC,EAAAA,EAAI,EAAEV,MAAAA,GAASC,gBAAc,EAAE;AACrC,IAAA,MAAMqC,MAAAA,GAAS7B,IAAI6B,MAAAA,EAAM;AAEzB,IAAA,KAAA,MAAWE,MAAAA,IAAU9B,GAAG+B,OAAAA,EAAS;AAC7BD,MAAAA,MAAAA,CAAOG,QAAAA,GAAW7B,UAAAA,CAAW0B,MAAAA,CAAOzB,KAAAA,EAAOf,MAAAA,CAAAA,KAAYsC,MAAAA;AAC3D,IAAA;AACJ,EAAA;AACJ,CAAA","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}"]}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
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
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Copyright (c) 2026 GTF
|
|
40
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
41
|
+
*/
|
|
1
42
|
/**
|
|
2
43
|
* Effective theme name (e.g. `day`, `night`, `mint`).
|
|
3
44
|
*/
|
|
@@ -9,38 +50,144 @@ type Name = string;
|
|
|
9
50
|
* - Effective themes are the resolved values (e.g. `<dark>`, `<light>`, `mint`).
|
|
10
51
|
*/
|
|
11
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
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Copyright (c) 2026 GTF
|
|
90
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
91
|
+
*/
|
|
12
92
|
|
|
13
93
|
/**
|
|
14
94
|
* Raw configuration, passed by the user.
|
|
15
95
|
*/
|
|
16
96
|
interface RawConfig {
|
|
97
|
+
/**
|
|
98
|
+
* Theme attribute name.
|
|
99
|
+
*
|
|
100
|
+
* @default "data-theme"
|
|
101
|
+
* @example
|
|
102
|
+
* <html data-theme="light">
|
|
103
|
+
*/
|
|
17
104
|
attribute?: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
*/
|
|
21
120
|
system?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Light theme name.
|
|
123
|
+
*
|
|
124
|
+
* @default "light"
|
|
125
|
+
* @example "bright" | ...
|
|
126
|
+
*/
|
|
22
127
|
light?: Name;
|
|
128
|
+
/**
|
|
129
|
+
* Dark theme name.
|
|
130
|
+
*
|
|
131
|
+
* @default "dark"
|
|
132
|
+
* @example "dim" | ...
|
|
133
|
+
*/
|
|
23
134
|
dark?: Name;
|
|
135
|
+
/**
|
|
136
|
+
* Initial theme name.
|
|
137
|
+
*
|
|
138
|
+
* @default "dark"
|
|
139
|
+
* @example "light" | "mint" | ...
|
|
140
|
+
*/
|
|
24
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;
|
|
25
149
|
}
|
|
26
150
|
/**
|
|
27
151
|
* Resolved configuration by the controller.
|
|
28
152
|
*/
|
|
29
153
|
interface ResolvedConfig {
|
|
30
154
|
attribute: string;
|
|
31
|
-
storage:
|
|
32
|
-
key: string;
|
|
33
|
-
};
|
|
155
|
+
storage: StateStorage;
|
|
34
156
|
system: string;
|
|
35
157
|
light: Name;
|
|
36
158
|
dark: Name;
|
|
37
159
|
initial?: Name;
|
|
160
|
+
observe: boolean;
|
|
38
161
|
}
|
|
39
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;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Copyright (c) 2026 GTF
|
|
184
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
185
|
+
*/
|
|
186
|
+
|
|
40
187
|
/**
|
|
41
188
|
* Theme controller logic.
|
|
42
189
|
*/
|
|
43
|
-
declare class Controller {
|
|
190
|
+
declare class Controller extends EventEmitter<NEvents> {
|
|
44
191
|
private _root;
|
|
45
192
|
/**
|
|
46
193
|
* Resolved configuration.
|
|
@@ -50,7 +197,8 @@ declare class Controller {
|
|
|
50
197
|
* System theme proxy.
|
|
51
198
|
*/
|
|
52
199
|
private system;
|
|
53
|
-
private
|
|
200
|
+
private _adapters;
|
|
201
|
+
private _observer?;
|
|
54
202
|
constructor(_root?: Document | ParentNode, cfg?: RawConfig);
|
|
55
203
|
/**
|
|
56
204
|
* The root element to apply theme to.
|
|
@@ -89,18 +237,29 @@ declare class Controller {
|
|
|
89
237
|
* @param adapter Adapter instance
|
|
90
238
|
* @param options Adapter options
|
|
91
239
|
*/
|
|
92
|
-
use<
|
|
240
|
+
use<O>(def: AdapterDefinition<any, O>, options?: Partial<O>): this;
|
|
93
241
|
/**
|
|
94
242
|
* Set the effective theme from a theme state.
|
|
95
243
|
*
|
|
96
244
|
* @see {@link State}
|
|
97
245
|
* @param state Theme State
|
|
98
246
|
*/
|
|
99
|
-
set(state: State):
|
|
247
|
+
set(state: State): this;
|
|
100
248
|
/**
|
|
101
249
|
* Read the stored theme state from local storage.
|
|
102
250
|
*/
|
|
103
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;
|
|
104
263
|
/**
|
|
105
264
|
* Sync the system listener.
|
|
106
265
|
*/
|
|
@@ -117,58 +276,25 @@ declare class Controller {
|
|
|
117
276
|
* @param theme Theme state
|
|
118
277
|
*/
|
|
119
278
|
private write;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
interface Adapter<Options = undefined> {
|
|
123
|
-
name: string;
|
|
124
279
|
/**
|
|
125
|
-
*
|
|
126
|
-
* Used to initiate any one-time setup.
|
|
127
|
-
*
|
|
128
|
-
* ---
|
|
129
|
-
* @remarks
|
|
130
|
-
* Called once, when the controller is instantiated.
|
|
280
|
+
* Start the DOM elements observer.
|
|
131
281
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
setup?(ctl: Controller, options: Options): void;
|
|
137
|
-
/**
|
|
138
|
-
* Discover themes that are available to this adapter. \
|
|
139
|
-
* Used to validate, or for example do a one-time setup.
|
|
140
|
-
*
|
|
141
|
-
* ---
|
|
142
|
-
* @remarks
|
|
143
|
-
* Called once, when the controller is instantiated.
|
|
144
|
-
*
|
|
145
|
-
* ---
|
|
146
|
-
* @param ctl the controller instance
|
|
147
|
-
* @returns a list of themes that are available to this adapter
|
|
148
|
-
*/
|
|
149
|
-
discover?(ctl: Controller): void;
|
|
150
|
-
/**
|
|
151
|
-
* Define the theme selection mechanism.
|
|
152
|
-
*
|
|
153
|
-
* ---
|
|
154
|
-
* @remarks
|
|
155
|
-
* Called every time the controller is instantiated.
|
|
156
|
-
*
|
|
157
|
-
* ---
|
|
158
|
-
* @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.
|
|
159
286
|
*/
|
|
160
|
-
|
|
287
|
+
private startObserver;
|
|
161
288
|
/**
|
|
162
|
-
*
|
|
289
|
+
* Bind a single DOM element to an adapter.
|
|
163
290
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* @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.
|
|
170
296
|
*/
|
|
171
|
-
|
|
297
|
+
private bindNode;
|
|
172
298
|
}
|
|
173
299
|
|
|
174
|
-
export { type
|
|
300
|
+
export { type AdapterDefinition, Controller, EventEmitter, type Name, type ResolvedConfig, type State, defineAdapter };
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1,55 @@
|
|
|
1
|
-
import { __name } from '../chunk-
|
|
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
|
-
|
|
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(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
adapter.bind?.(this);
|
|
172
|
-
adapter.sync?.(this);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
console.warn(`Adapter <${adapter.name}> failed to install.`, err);
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
};
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/system.ts","../../src/core/utils.ts","../../src/core/controller.ts"],"names":["System","mql","handler","ctl","prefers","matchMedia","matches","config","dark","light","prefersDark","prefersLight","start","onChange","e","addEventListener","stop","removeEventListener","undefined","resolveStateOf","state","system","resolveRoot","root","HTMLElement","Document","documentElement","ShadowRoot","host","document","Controller","adapters","_root","cfg","attribute","storage","key","initial","syncSystemListener","active","resolved","theme","use","adapter","options","includes","console","warn","name","push","setup","discover","bind","sync","err","set","write","apply","a","localStorage","getItem","_state","setAttribute","setItem"],"mappings":"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","file":"index.js","sourcesContent":["import { 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}","import { 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}","import { 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}"]}
|
package/dist/loader/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(){"use strict"
|
|
1
|
+
!function(){"use strict";var e=Object.defineProperty,t=(t,a,r)=>((t,a,r)=>a in t?e(t,a,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[a]=r)(t,a+"",r),a=class{constructor(e={}){var a;t(this,"key"),this.key=null!=(a=e.key)?a:"theme"}get(){try{return localStorage.getItem(this.key)}catch(e){return null}}set(e){try{localStorage.setItem(this.key,e)}catch(e){console.warn("[LOCAL] Failed to write theme state.")}}remove(){try{localStorage.removeItem(this.key)}catch(e){}}};e(a,"name",{value:"LocalStorage",configurable:!0});var r=a;!function(e=document.documentElement,t){try{let a=t.storage,r=t.attribute,l=t.system,c=t.light,i=t.dark,n=t.initial;if(e&&e.hasAttribute(r))return;let o=a.get(),s=null;o===l?s=null!=matchMedia&&matchMedia("(prefers-color-scheme: dark)").matches?i:c:o===c||o===i?s=o:!o&&n&&(s=n),s&&e.setAttribute(r,s)}catch(e){}}(document.documentElement,{storage:new r,attribute:"data-theme",system:"system",light:"light",dark:"dark",initial:"dark",observe:!1})}();
|
package/package.json
CHANGED
|
@@ -1,57 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@gottheflag/nova",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"license": "
|
|
6
|
-
"author": {
|
|
7
|
-
"name": "GTF",
|
|
8
|
-
"email": "dev@gottheflag.sa",
|
|
9
|
-
"url": "https://gottheflag.sa"
|
|
10
|
-
},
|
|
11
|
-
"description": "Theme state controller.",
|
|
12
|
-
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
],
|
|
15
|
-
"exports": {
|
|
16
|
-
".": {
|
|
17
|
-
"types": "./dist/core/index.d.ts",
|
|
18
|
-
"import": "./dist/core/index.js"
|
|
19
|
-
},
|
|
20
|
-
"./adapters": {
|
|
21
|
-
"types": "./dist/adapters/index.d.ts",
|
|
22
|
-
"import": "./dist/adapters/index.js"
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
},
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@gottheflag/nova",
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "GTF",
|
|
8
|
+
"email": "dev@gottheflag.sa",
|
|
9
|
+
"url": "https://gottheflag.sa"
|
|
10
|
+
},
|
|
11
|
+
"description": "Theme state controller.",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/core/index.d.ts",
|
|
18
|
+
"import": "./dist/core/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./adapters": {
|
|
21
|
+
"types": "./dist/adapters/index.d.ts",
|
|
22
|
+
"import": "./dist/adapters/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./storage": {
|
|
25
|
+
"types": "./dist/storage/index.d.ts",
|
|
26
|
+
"import": "./dist/storage/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "concurrently -k -n TS,VITE -c blue,green \"tsc -p tsconfig.build.json --watch\" \"vite example\"",
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"prepare": "pnpm run build"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"theme",
|
|
37
|
+
"light",
|
|
38
|
+
"dark"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/gottheflag/nova"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/gottheflag/nova/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/gottheflag/nova#readme",
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@swc/core": "^1.15.11",
|
|
53
|
+
"@types/node": "^25",
|
|
54
|
+
"concurrently": "^9.2.1",
|
|
55
|
+
"terser": "^5.46.0",
|
|
56
|
+
"tsup": "^8.5",
|
|
57
|
+
"tsx": "^4.21",
|
|
58
|
+
"typescript": "^5.9",
|
|
59
|
+
"vite": "^7.3.1"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/chunk-7QVYU63E.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-7QVYU63E.js"}
|