@gottheflag/nova 0.1.0-beta.1
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 +28 -0
- package/dist/adapters/index.d.ts +38 -0
- package/dist/adapters/index.js +129 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/chunk-7QVYU63E.js +6 -0
- package/dist/chunk-7QVYU63E.js.map +1 -0
- package/dist/core/index.d.ts +174 -0
- package/dist/core/index.js +248 -0
- package/dist/core/index.js.map +1 -0
- package/dist/loader/index.min.js +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Theme controller
|
|
2
|
+
|
|
3
|
+
A controller for managing the theming for applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @gottheflag/nova
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { Controller } from "@gottheflag/nova";
|
|
15
|
+
import { Button } from "@gottheflag/nova/adapters";
|
|
16
|
+
|
|
17
|
+
const ctl = new Controller(document, {
|
|
18
|
+
// all configs are optional
|
|
19
|
+
storage: { key: "theme" },
|
|
20
|
+
attribute: "data-theme",
|
|
21
|
+
initial: "dark",
|
|
22
|
+
dark: "dark",
|
|
23
|
+
light: "light",
|
|
24
|
+
system: "system",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
ctl.use(Button); // use adapters
|
|
28
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Adapter, Name } from '../core/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @example
|
|
5
|
+
* <button value="<prefix>light">Light</button>
|
|
6
|
+
* <button value="<prefix>dark">Dark</button>
|
|
7
|
+
*/
|
|
8
|
+
declare const Button: Adapter & {
|
|
9
|
+
registry: Map<Element, Name[]>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface Options$1 {
|
|
13
|
+
prefix?: string;
|
|
14
|
+
}
|
|
15
|
+
declare const Radio: Adapter<Options$1> & {
|
|
16
|
+
registry: Map<Element, Name[]>;
|
|
17
|
+
prefix?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface Options {
|
|
21
|
+
prefix?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @example
|
|
25
|
+
* ```html
|
|
26
|
+
* <select>
|
|
27
|
+
* <option value="theme:light">Light</option>
|
|
28
|
+
* <option value="theme:system">System</option>
|
|
29
|
+
* <option value="theme:dark">Dark</option>
|
|
30
|
+
* </select>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare const Select: Adapter<Options> & {
|
|
34
|
+
registry: Map<Element, Name[]>;
|
|
35
|
+
prefix?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { Button, Radio, Select };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { __name } from '../chunk-7QVYU63E.js';
|
|
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;
|
|
9
|
+
}
|
|
10
|
+
__name(parseThemeName, "parseThemeName");
|
|
11
|
+
var Button = {
|
|
12
|
+
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
|
+
});
|
|
22
|
+
},
|
|
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
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// 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 = {
|
|
45
|
+
name: "radio",
|
|
46
|
+
registry: /* @__PURE__ */ new Map(),
|
|
47
|
+
setup(_ctl, options) {
|
|
48
|
+
this.prefix = options?.prefix ?? PREFIX2;
|
|
49
|
+
},
|
|
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);
|
|
57
|
+
});
|
|
58
|
+
},
|
|
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
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// 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 = {
|
|
88
|
+
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
|
+
});
|
|
103
|
+
},
|
|
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
|
+
});
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
sync(ctl) {
|
|
116
|
+
const root = ctl.root;
|
|
117
|
+
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
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export { Button, Radio, Select };
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":"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","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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-7QVYU63E.js"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effective theme name (e.g. `day`, `night`, `mint`).
|
|
3
|
+
*/
|
|
4
|
+
type Name = string;
|
|
5
|
+
/**
|
|
6
|
+
* Theme state (`<dark>`, `<light>`, `<system>`).
|
|
7
|
+
* Different between state themes and effective themes:
|
|
8
|
+
* - State themes are the raw values used as identifiers (`<dark>`, `<light>`, `<system>`).
|
|
9
|
+
* - Effective themes are the resolved values (e.g. `<dark>`, `<light>`, `mint`).
|
|
10
|
+
*/
|
|
11
|
+
type State = "system" | "light" | "dark";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Raw configuration, passed by the user.
|
|
15
|
+
*/
|
|
16
|
+
interface RawConfig {
|
|
17
|
+
attribute?: string;
|
|
18
|
+
storage?: {
|
|
19
|
+
key: string;
|
|
20
|
+
};
|
|
21
|
+
system?: string;
|
|
22
|
+
light?: Name;
|
|
23
|
+
dark?: Name;
|
|
24
|
+
initial?: Name;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolved configuration by the controller.
|
|
28
|
+
*/
|
|
29
|
+
interface ResolvedConfig {
|
|
30
|
+
attribute: string;
|
|
31
|
+
storage: {
|
|
32
|
+
key: string;
|
|
33
|
+
};
|
|
34
|
+
system: string;
|
|
35
|
+
light: Name;
|
|
36
|
+
dark: Name;
|
|
37
|
+
initial?: Name;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Theme controller logic.
|
|
42
|
+
*/
|
|
43
|
+
declare class Controller {
|
|
44
|
+
private _root;
|
|
45
|
+
/**
|
|
46
|
+
* Resolved configuration.
|
|
47
|
+
*/
|
|
48
|
+
config: ResolvedConfig;
|
|
49
|
+
/**
|
|
50
|
+
* System theme proxy.
|
|
51
|
+
*/
|
|
52
|
+
private system;
|
|
53
|
+
private adapters;
|
|
54
|
+
constructor(_root?: Document | ParentNode, cfg?: RawConfig);
|
|
55
|
+
/**
|
|
56
|
+
* The root element to apply theme to.
|
|
57
|
+
*/
|
|
58
|
+
get root(): HTMLElement;
|
|
59
|
+
/**
|
|
60
|
+
* @param resolved resolve state and effective themes to their values.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
*
|
|
64
|
+
* system = config.system;
|
|
65
|
+
* light = config.light;
|
|
66
|
+
* dark = config.dark;
|
|
67
|
+
*
|
|
68
|
+
* resolved = true;
|
|
69
|
+
* [
|
|
70
|
+
* "system": light | dark,
|
|
71
|
+
* "light": light,
|
|
72
|
+
* "dark": dark
|
|
73
|
+
* ]
|
|
74
|
+
* resolved = false;
|
|
75
|
+
* [
|
|
76
|
+
* "system": system,
|
|
77
|
+
* "light": light,
|
|
78
|
+
* "dark": dark
|
|
79
|
+
* ]
|
|
80
|
+
*/
|
|
81
|
+
active(resolved?: boolean): State | Name | null;
|
|
82
|
+
/**
|
|
83
|
+
* Register an adapter.
|
|
84
|
+
*
|
|
85
|
+
* @description
|
|
86
|
+
* Adapters are toys used to change themes.
|
|
87
|
+
* They define where themes lives and how they work with the controller.
|
|
88
|
+
*
|
|
89
|
+
* @param adapter Adapter instance
|
|
90
|
+
* @param options Adapter options
|
|
91
|
+
*/
|
|
92
|
+
use<T extends Adapter<any>>(adapter: T, options?: T extends Adapter<infer O> ? O : never): this;
|
|
93
|
+
/**
|
|
94
|
+
* Set the effective theme from a theme state.
|
|
95
|
+
*
|
|
96
|
+
* @see {@link State}
|
|
97
|
+
* @param state Theme State
|
|
98
|
+
*/
|
|
99
|
+
set(state: State): void;
|
|
100
|
+
/**
|
|
101
|
+
* Read the stored theme state from local storage.
|
|
102
|
+
*/
|
|
103
|
+
get state(): State | null;
|
|
104
|
+
/**
|
|
105
|
+
* Sync the system listener.
|
|
106
|
+
*/
|
|
107
|
+
private syncSystemListener;
|
|
108
|
+
/**
|
|
109
|
+
* Applies the effective theme name to the root.
|
|
110
|
+
*
|
|
111
|
+
* @param theme Effective theme name (e.g. `<dark>`, `<light>`, `mint`)
|
|
112
|
+
*/
|
|
113
|
+
private apply;
|
|
114
|
+
/**
|
|
115
|
+
* Write theme state to local storage.
|
|
116
|
+
*
|
|
117
|
+
* @param theme Theme state
|
|
118
|
+
*/
|
|
119
|
+
private write;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface Adapter<Options = undefined> {
|
|
123
|
+
name: string;
|
|
124
|
+
/**
|
|
125
|
+
* Setup the adapter.
|
|
126
|
+
* Used to initiate any one-time setup.
|
|
127
|
+
*
|
|
128
|
+
* ---
|
|
129
|
+
* @remarks
|
|
130
|
+
* Called once, when the controller is instantiated.
|
|
131
|
+
*
|
|
132
|
+
* ---
|
|
133
|
+
* @param ctl Controller instance
|
|
134
|
+
* @param options Adapter options
|
|
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
|
|
159
|
+
*/
|
|
160
|
+
bind?(ctl: Controller): void;
|
|
161
|
+
/**
|
|
162
|
+
* Sync theme adapter state with the controller.
|
|
163
|
+
*
|
|
164
|
+
* ---
|
|
165
|
+
* @remarks
|
|
166
|
+
* Called every time the controller is instantiated.
|
|
167
|
+
*
|
|
168
|
+
* ---
|
|
169
|
+
* @param ctl the controller instance
|
|
170
|
+
*/
|
|
171
|
+
sync?(ctl: Controller): void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { type Adapter, Controller, type Name, type State };
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { __name } from '../chunk-7QVYU63E.js';
|
|
2
|
+
|
|
3
|
+
// src/core/system.ts
|
|
4
|
+
var System = class {
|
|
5
|
+
static {
|
|
6
|
+
__name(this, "System");
|
|
7
|
+
}
|
|
8
|
+
ctl;
|
|
9
|
+
/**
|
|
10
|
+
* Media query list instance.
|
|
11
|
+
*/
|
|
12
|
+
mql;
|
|
13
|
+
/**
|
|
14
|
+
* Handles the media query list listener.
|
|
15
|
+
*/
|
|
16
|
+
handler;
|
|
17
|
+
/**
|
|
18
|
+
* Controller instance.
|
|
19
|
+
*/
|
|
20
|
+
constructor(ctl) {
|
|
21
|
+
this.ctl = ctl;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the effective theme by system preference.
|
|
25
|
+
*/
|
|
26
|
+
get prefers() {
|
|
27
|
+
return matchMedia?.("(prefers-color-scheme: dark)").matches ? this.ctl.config.dark : this.ctl.config.light;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if the effective theme is dark.
|
|
31
|
+
*/
|
|
32
|
+
get prefersDark() {
|
|
33
|
+
return this.prefers === this.ctl.config.dark;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if the effective theme is light.
|
|
37
|
+
*/
|
|
38
|
+
get prefersLight() {
|
|
39
|
+
return this.prefers === this.ctl.config.light;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Start listening for system theme changes.
|
|
43
|
+
*
|
|
44
|
+
* @param onChange Callback to be called when the system theme changes.
|
|
45
|
+
*/
|
|
46
|
+
start(onChange) {
|
|
47
|
+
if (this.handler) return;
|
|
48
|
+
this.mql = matchMedia("(prefers-color-scheme: dark)");
|
|
49
|
+
this.handler = (e) => onChange(e.matches ? "dark" : "light");
|
|
50
|
+
this.mql.addEventListener?.("change", this.handler);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Stop listening for system theme changes.
|
|
54
|
+
*/
|
|
55
|
+
stop() {
|
|
56
|
+
if (!this.mql || !this.handler) return;
|
|
57
|
+
this.mql.removeEventListener?.("change", this.handler);
|
|
58
|
+
this.mql = this.handler = void 0;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
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
|
+
// src/core/controller.ts
|
|
86
|
+
var Controller = class {
|
|
87
|
+
static {
|
|
88
|
+
__name(this, "Controller");
|
|
89
|
+
}
|
|
90
|
+
_root;
|
|
91
|
+
/**
|
|
92
|
+
* Resolved configuration.
|
|
93
|
+
*/
|
|
94
|
+
config;
|
|
95
|
+
/**
|
|
96
|
+
* System theme proxy.
|
|
97
|
+
*/
|
|
98
|
+
system;
|
|
99
|
+
adapters = [];
|
|
100
|
+
constructor(_root = document, cfg = {}) {
|
|
101
|
+
this._root = _root;
|
|
102
|
+
this.config = {
|
|
103
|
+
attribute: cfg.attribute ?? "data-theme",
|
|
104
|
+
storage: {
|
|
105
|
+
key: cfg.storage?.key ?? "theme"
|
|
106
|
+
},
|
|
107
|
+
system: cfg.system ?? "system",
|
|
108
|
+
light: cfg.light ?? "light",
|
|
109
|
+
dark: cfg.dark ?? "dark",
|
|
110
|
+
initial: cfg.initial
|
|
111
|
+
};
|
|
112
|
+
this.system = new System(this);
|
|
113
|
+
this.syncSystemListener();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The root element to apply theme to.
|
|
117
|
+
*/
|
|
118
|
+
get root() {
|
|
119
|
+
return resolveRoot(this._root);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @param resolved resolve state and effective themes to their values.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
*
|
|
126
|
+
* system = config.system;
|
|
127
|
+
* light = config.light;
|
|
128
|
+
* dark = config.dark;
|
|
129
|
+
*
|
|
130
|
+
* resolved = true;
|
|
131
|
+
* [
|
|
132
|
+
* "system": light | dark,
|
|
133
|
+
* "light": light,
|
|
134
|
+
* "dark": dark
|
|
135
|
+
* ]
|
|
136
|
+
* resolved = false;
|
|
137
|
+
* [
|
|
138
|
+
* "system": system,
|
|
139
|
+
* "light": light,
|
|
140
|
+
* "dark": dark
|
|
141
|
+
* ]
|
|
142
|
+
*/
|
|
143
|
+
active(resolved = false) {
|
|
144
|
+
const state = this.state;
|
|
145
|
+
if (!state) {
|
|
146
|
+
return this.config.initial || null;
|
|
147
|
+
}
|
|
148
|
+
const theme = resolveStateOf(state, this.system, this.config);
|
|
149
|
+
if (!theme) return null;
|
|
150
|
+
return resolved ? theme : state;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Register an adapter.
|
|
154
|
+
*
|
|
155
|
+
* @description
|
|
156
|
+
* Adapters are toys used to change themes.
|
|
157
|
+
* They define where themes lives and how they work with the controller.
|
|
158
|
+
*
|
|
159
|
+
* @param adapter Adapter instance
|
|
160
|
+
* @param options Adapter options
|
|
161
|
+
*/
|
|
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);
|
|
175
|
+
}
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Set the effective theme from a theme state.
|
|
180
|
+
*
|
|
181
|
+
* @see {@link State}
|
|
182
|
+
* @param state Theme State
|
|
183
|
+
*/
|
|
184
|
+
set(state) {
|
|
185
|
+
const theme = resolveStateOf(state, this.system, this.config);
|
|
186
|
+
if (!theme) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.write(state);
|
|
190
|
+
this.syncSystemListener();
|
|
191
|
+
this.apply(theme);
|
|
192
|
+
for (const a of this.adapters) {
|
|
193
|
+
a.sync?.(this);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Read the stored theme state from local storage.
|
|
198
|
+
*/
|
|
199
|
+
get state() {
|
|
200
|
+
try {
|
|
201
|
+
return localStorage.getItem(this.config.storage.key);
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Sync the system listener.
|
|
208
|
+
*/
|
|
209
|
+
syncSystemListener() {
|
|
210
|
+
const state = this.active();
|
|
211
|
+
if (state === this.config.system) {
|
|
212
|
+
this.system.start((_state) => {
|
|
213
|
+
const theme = resolveStateOf("system", this.system, this.config);
|
|
214
|
+
if (!theme) return;
|
|
215
|
+
this.apply(theme);
|
|
216
|
+
for (const a of this.adapters) {
|
|
217
|
+
a.sync?.(this);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
this.system.stop();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Applies the effective theme name to the root.
|
|
226
|
+
*
|
|
227
|
+
* @param theme Effective theme name (e.g. `<dark>`, `<light>`, `mint`)
|
|
228
|
+
*/
|
|
229
|
+
apply(theme) {
|
|
230
|
+
this.root.setAttribute(this.config.attribute, theme);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Write theme state to local storage.
|
|
234
|
+
*
|
|
235
|
+
* @param theme Theme state
|
|
236
|
+
*/
|
|
237
|
+
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.`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export { Controller };
|
|
247
|
+
//# sourceMappingURL=index.js.map
|
|
248
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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}"]}
|
|
@@ -0,0 +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){}}()}();
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gottheflag/nova",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
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
|
+
"scripts": {
|
|
26
|
+
"dev": "concurrently -k -n TS,VITE -c blue,green \"tsc -p tsconfig.build.json --watch\" \"vite example\"",
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"prepare": "pnpm run build"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"theme",
|
|
33
|
+
"light",
|
|
34
|
+
"dark"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/gottheflag/nova"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/gottheflag/nova/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/gottheflag/nova#readme",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@swc/core": "^1.15.11",
|
|
49
|
+
"@types/node": "^25",
|
|
50
|
+
"concurrently": "^9.2.1",
|
|
51
|
+
"terser": "^5.46.0",
|
|
52
|
+
"tsup": "^8.5",
|
|
53
|
+
"tsx": "^4.21",
|
|
54
|
+
"typescript": "^5.9",
|
|
55
|
+
"vite": "^7.3.1"
|
|
56
|
+
}
|
|
57
|
+
}
|