resonare 0.0.1 → 0.0.2

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/dist/index.d.ts CHANGED
@@ -1,2 +1,81 @@
1
- import { a as ThemeStoreRegistry, c as destroyThemeStore, d as getThemesAndOptions, i as ThemeStoreOptions, l as getDefaultThemes, n as ThemeConfig, o as Themes, r as ThemeStore, s as createThemeStore, t as ThemeAndOptions, u as getThemeStore } from "./index-C1bWhjq0.js";
2
- export { ThemeAndOptions, ThemeConfig, ThemeStore, ThemeStoreOptions, ThemeStoreRegistry, Themes, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions };
1
+ //#region src/storage.d.ts
2
+ type StorageAdapter = {
3
+ getItem: (key: string) => object | null | Promise<object | null>;
4
+ setItem: (key: string, value: object) => void | Promise<void>;
5
+ broadcast?: (key: string, value: object) => void;
6
+ watch?: (cb: (key: string | null, value: object) => void) => () => void;
7
+ };
8
+ type StorageAdapterCreate = ({
9
+ abortController
10
+ }: {
11
+ abortController: AbortController;
12
+ }) => StorageAdapter;
13
+ type StorageAdapterCreator<Options> = (options?: Options) => StorageAdapterCreate;
14
+ declare const localStorageAdapter: StorageAdapterCreator<{
15
+ storageType?: 'localStorage' | 'sessionStorage';
16
+ }>;
17
+ declare const memoryStorageAdapter: StorageAdapterCreator<never>;
18
+ //#endregion
19
+ //#region src/index.d.ts
20
+ type ThemeOption = {
21
+ value: string;
22
+ media?: [string, string, string];
23
+ };
24
+ type ThemeConfig = Record<string, {
25
+ options: Array<string | ThemeOption> | readonly (string | ThemeOption)[];
26
+ defaultOption?: string;
27
+ }>;
28
+ type Themes<T extends ThemeConfig> = { [K in keyof T]: T[K]['options'] extends Array<infer U> | readonly (infer U)[] ? U extends string ? U : U extends ThemeOption ? U['value'] : never : never };
29
+ type Listener<T extends ThemeConfig> = (value: {
30
+ themes: Themes<T>;
31
+ resolvedThemes: Themes<T>;
32
+ }) => void;
33
+ type ThemeKeysWithSystemOption<T extends ThemeConfig> = { [K in keyof T]: T[K]['options'] extends Array<infer U> | readonly (infer U)[] ? U extends {
34
+ media: [string, string, string];
35
+ } ? K : never : never }[keyof T];
36
+ type NonSystemOptionValues<T extends ThemeConfig, K$1 extends keyof T> = T[K$1]['options'] extends Array<infer U> | readonly (infer U)[] ? U extends string ? U : U extends ThemeOption ? U extends {
37
+ media: [string, string, string];
38
+ } ? never : U['value'] : never : never;
39
+ type SystemOptions<T extends ThemeConfig> = { [K in ThemeKeysWithSystemOption<T>]?: [NonSystemOptionValues<T, K>, NonSystemOptionValues<T, K>] };
40
+ type PersistedState<T extends ThemeConfig> = {
41
+ version: 1;
42
+ themes: Themes<T>;
43
+ systemOptions: SystemOptions<T>;
44
+ };
45
+ type ThemeStoreOptions<T extends ThemeConfig> = {
46
+ key?: string;
47
+ config: T;
48
+ initialState?: Partial<PersistedState<T>>;
49
+ storage?: StorageAdapterCreate;
50
+ };
51
+ type ThemeAndOptions<T extends ThemeConfig> = Array<{ [K in keyof T]: [K, Array<T[K]['options'] extends Array<infer U> | readonly (infer U)[] ? U extends string ? U : U extends ThemeOption ? U['value'] : never : never>] }[keyof T]>;
52
+ declare function getThemesAndOptions<T extends ThemeConfig>(config: T): ThemeAndOptions<T>;
53
+ declare function getDefaultThemes<T extends ThemeConfig>(config: T): Themes<T>;
54
+ declare class ThemeStore<T extends ThemeConfig> {
55
+ #private;
56
+ constructor({
57
+ key,
58
+ config,
59
+ initialState,
60
+ storage
61
+ }: ThemeStoreOptions<T>);
62
+ getThemes: () => Themes<T>;
63
+ getResolvedThemes: () => Themes<T>;
64
+ setThemes: (themes: Partial<Themes<T>> | ((currentThemes: Themes<T>) => Partial<Themes<T>>)) => Promise<void>;
65
+ updateSystemOption: <K$1 extends ThemeKeysWithSystemOption<T>>(themeKey: K$1, [ifMatch, ifNotMatch]: [NonSystemOptionValues<T, K$1>, NonSystemOptionValues<T, K$1>]) => void;
66
+ getStateToPersist: () => PersistedState<T>;
67
+ restore: () => Promise<void>;
68
+ subscribe: (callback: Listener<T>, {
69
+ immediate
70
+ }?: {
71
+ immediate?: boolean;
72
+ }) => (() => void);
73
+ sync: () => (() => void) | undefined;
74
+ ___destroy: () => void;
75
+ }
76
+ declare const createThemeStore: <T extends ThemeConfig>(options: ThemeStoreOptions<T>) => ThemeStore<T>;
77
+ declare const getThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => ThemeStoreRegistry[T];
78
+ declare const destroyThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => void;
79
+ interface ThemeStoreRegistry {}
80
+ //#endregion
81
+ export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator, ThemeAndOptions, ThemeConfig, type ThemeStore, ThemeStoreOptions, ThemeStoreRegistry, Themes, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
package/dist/index.js CHANGED
@@ -1,3 +1,246 @@
1
- import { a as getThemeStore, i as getDefaultThemes, n as createThemeStore, o as getThemesAndOptions, r as destroyThemeStore, t as ThemeStore } from "./src-BsTQDM3B.js";
1
+ //#region package.json
2
+ var name = "resonare";
2
3
 
3
- export { ThemeStore, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions };
4
+ //#endregion
5
+ //#region src/storage.ts
6
+ const localStorageAdapter = ({ storageType = "localStorage" } = {}) => {
7
+ return ({ abortController }) => {
8
+ return {
9
+ getItem: (key) => {
10
+ return JSON.parse(window[storageType].getItem(key) || "null");
11
+ },
12
+ setItem: (key, value) => {
13
+ window[storageType].setItem(key, JSON.stringify(value));
14
+ },
15
+ watch: (cb) => {
16
+ const controller = new AbortController();
17
+ window.addEventListener("storage", (e) => {
18
+ if (e.storageArea !== window[storageType]) return;
19
+ cb(e.key, JSON.parse(e.newValue));
20
+ }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
21
+ return () => {
22
+ controller.abort();
23
+ };
24
+ }
25
+ };
26
+ };
27
+ };
28
+ const memoryStorageAdapter = () => {
29
+ return ({ abortController }) => {
30
+ const storage = /* @__PURE__ */ new Map();
31
+ const channel = new BroadcastChannel(name);
32
+ return {
33
+ getItem: (key) => {
34
+ return storage.get(key) || null;
35
+ },
36
+ setItem: (key, value) => {
37
+ storage.set(key, value);
38
+ },
39
+ broadcast: (key, value) => {
40
+ channel.postMessage({
41
+ key,
42
+ value
43
+ });
44
+ },
45
+ watch: (cb) => {
46
+ const controller = new AbortController();
47
+ channel.addEventListener("message", (e) => {
48
+ cb(e.data.key, e.data.value);
49
+ }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
50
+ return () => {
51
+ controller.abort();
52
+ };
53
+ }
54
+ };
55
+ };
56
+ };
57
+
58
+ //#endregion
59
+ //#region src/index.ts
60
+ const PACKAGE_NAME = "resonare";
61
+ function getThemesAndOptions(config) {
62
+ return Object.entries(config).map(([themeKey, { options }]) => {
63
+ return [themeKey, options.map((option) => typeof option === "string" ? option : option.value)];
64
+ });
65
+ }
66
+ function getDefaultThemes(config) {
67
+ return Object.fromEntries(Object.entries(config).map(([themeKey, { options, defaultOption }]) => {
68
+ return [themeKey, defaultOption ?? (typeof options[0] === "string" ? options[0] : options[0].value)];
69
+ }));
70
+ }
71
+ const isClient = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
72
+ var ThemeStore = class {
73
+ #defaultThemes;
74
+ #currentThemes;
75
+ #options;
76
+ #systemOptions;
77
+ #storage;
78
+ #listeners = /* @__PURE__ */ new Set();
79
+ #mediaQueryCache;
80
+ #abortController = new AbortController();
81
+ constructor({ key = PACKAGE_NAME, config, initialState = {}, storage = localStorageAdapter() }) {
82
+ const keyedConfig = {};
83
+ const systemOptions = { ...initialState.systemOptions };
84
+ Object.entries(config).forEach(([themeKey, { options }]) => {
85
+ keyedConfig[themeKey] = keyedConfig[themeKey] || {};
86
+ options.forEach((option) => {
87
+ if (typeof option === "string") keyedConfig[themeKey][option] = { value: option };
88
+ else {
89
+ if (option.media && !Object.hasOwn(systemOptions, themeKey)) systemOptions[themeKey] = [option.media[1], option.media[2]];
90
+ keyedConfig[themeKey][option.value] = option;
91
+ }
92
+ });
93
+ });
94
+ this.#options = {
95
+ key,
96
+ config: keyedConfig,
97
+ storage
98
+ };
99
+ this.#systemOptions = systemOptions;
100
+ this.#defaultThemes = getDefaultThemes(config);
101
+ this.#currentThemes = {
102
+ ...this.#defaultThemes,
103
+ ...initialState.themes
104
+ };
105
+ this.#storage = this.#options.storage({ abortController: this.#abortController });
106
+ this.#mediaQueryCache = {};
107
+ }
108
+ getThemes = () => {
109
+ return this.#currentThemes;
110
+ };
111
+ getResolvedThemes = () => {
112
+ return this.#resolveThemes();
113
+ };
114
+ setThemes = async (themes) => {
115
+ const updatedThemes = typeof themes === "function" ? themes(this.#currentThemes) : themes;
116
+ this.#setThemesAndNotify({
117
+ ...this.#currentThemes,
118
+ ...updatedThemes
119
+ });
120
+ const stateToPersist = this.getStateToPersist();
121
+ await this.#storage.setItem(this.#options.key, stateToPersist);
122
+ this.#storage.broadcast?.(this.#options.key, stateToPersist);
123
+ };
124
+ updateSystemOption = (themeKey, [ifMatch, ifNotMatch]) => {
125
+ this.#systemOptions[themeKey] = [ifMatch, ifNotMatch];
126
+ this.setThemes({ ...this.#currentThemes });
127
+ };
128
+ getStateToPersist = () => {
129
+ return {
130
+ version: 1,
131
+ themes: this.#currentThemes,
132
+ systemOptions: this.#systemOptions
133
+ };
134
+ };
135
+ restore = async () => {
136
+ let persistedState = await this.#storage.getItem(this.#options.key);
137
+ if (!persistedState) {
138
+ this.#setThemesAndNotify({ ...this.#defaultThemes });
139
+ return;
140
+ }
141
+ if (!Object.hasOwn(persistedState, "version")) persistedState = {
142
+ version: 1,
143
+ themes: persistedState,
144
+ systemOptions: this.#systemOptions
145
+ };
146
+ this.#systemOptions = {
147
+ ...this.#systemOptions,
148
+ ...persistedState.systemOptions
149
+ };
150
+ this.#setThemesAndNotify({
151
+ ...this.#defaultThemes,
152
+ ...persistedState.themes
153
+ });
154
+ };
155
+ subscribe = (callback, { immediate = false } = {}) => {
156
+ if (immediate) callback({
157
+ themes: this.#currentThemes,
158
+ resolvedThemes: this.#resolveThemes()
159
+ });
160
+ this.#listeners.add(callback);
161
+ return () => {
162
+ this.#listeners.delete(callback);
163
+ };
164
+ };
165
+ sync = () => {
166
+ if (!this.#storage.watch) {
167
+ console.warn(`[${PACKAGE_NAME}] No watch method was provided for storage.`);
168
+ return;
169
+ }
170
+ return this.#storage.watch((key, persistedState) => {
171
+ if (key !== this.#options.key) return;
172
+ this.#systemOptions = persistedState.systemOptions;
173
+ this.#setThemesAndNotify(persistedState.themes);
174
+ });
175
+ };
176
+ ___destroy = () => {
177
+ this.#listeners.clear();
178
+ this.#abortController.abort();
179
+ };
180
+ #setThemesAndNotify = (themes) => {
181
+ this.#currentThemes = themes;
182
+ this.#notify();
183
+ };
184
+ #resolveThemes = () => {
185
+ return Object.fromEntries(Object.entries(this.#currentThemes).map(([themeKey, optionKey]) => {
186
+ const option = this.#options.config[themeKey][optionKey];
187
+ return [themeKey, this.#resolveThemeOption({
188
+ themeKey,
189
+ option
190
+ })];
191
+ }));
192
+ };
193
+ #resolveThemeOption = ({ themeKey, option }) => {
194
+ if (!option.media) return option.value;
195
+ if (!isClient) {
196
+ console.warn(`[${PACKAGE_NAME}] Option with key "media" cannot be resolved in server environment.`);
197
+ return option.value;
198
+ }
199
+ const [mediaQuery] = option.media;
200
+ if (!this.#mediaQueryCache[mediaQuery]) {
201
+ const mediaQueryList = window.matchMedia(mediaQuery);
202
+ this.#mediaQueryCache[mediaQuery] = mediaQueryList;
203
+ mediaQueryList.addEventListener("change", () => {
204
+ if (this.#currentThemes[themeKey] === option.value) this.#setThemesAndNotify({ ...this.#currentThemes });
205
+ }, { signal: this.#abortController.signal });
206
+ }
207
+ const [ifMatch, ifNotMatch] = this.#systemOptions[themeKey];
208
+ return this.#mediaQueryCache[mediaQuery].matches ? ifMatch : ifNotMatch;
209
+ };
210
+ #notify = () => {
211
+ for (const listener of this.#listeners) listener({
212
+ themes: this.#currentThemes,
213
+ resolvedThemes: this.#resolveThemes()
214
+ });
215
+ };
216
+ };
217
+ var Registry = class {
218
+ #registry = /* @__PURE__ */ new Map();
219
+ create = (options) => {
220
+ const storeKey = options.key || PACKAGE_NAME;
221
+ let themeStore = this.#registry.get(storeKey);
222
+ if (!themeStore) {
223
+ themeStore = new ThemeStore(options);
224
+ this.#registry.set(storeKey, themeStore);
225
+ }
226
+ return themeStore;
227
+ };
228
+ get = (key) => {
229
+ const storeKey = key || PACKAGE_NAME;
230
+ if (!this.#registry.has(storeKey)) throw new Error(`[${PACKAGE_NAME}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`);
231
+ return this.#registry.get(storeKey);
232
+ };
233
+ destroy = (key) => {
234
+ const storeKey = key || PACKAGE_NAME;
235
+ if (!this.#registry.has(storeKey)) throw new Error(`[${PACKAGE_NAME}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`);
236
+ this.#registry.get(storeKey).___destroy();
237
+ this.#registry.delete(storeKey);
238
+ };
239
+ };
240
+ const registry = new Registry();
241
+ const createThemeStore = registry.create;
242
+ const getThemeStore = registry.get;
243
+ const destroyThemeStore = registry.destroy;
244
+
245
+ //#endregion
246
+ export { createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as ThemeConfig, o as Themes, r as ThemeStore } from "./index-C1bWhjq0.js";
1
+ import { ThemeConfig, ThemeStore, Themes } from "./index.js";
2
2
 
3
3
  //#region src/react.d.ts
4
4
  declare function useResonare<T extends ThemeConfig>(getStore: () => ThemeStore<T>, {
@@ -9,14 +9,14 @@ declare function useResonare<T extends ThemeConfig>(getStore: () => ThemeStore<T
9
9
  themes: Themes<T>;
10
10
  resolvedThemes: Themes<T>;
11
11
  setThemes: (themes: Partial<Themes<T>> | ((currentThemes: Themes<T>) => Partial<Themes<T>>)) => Promise<void>;
12
- updateSystemOption: <K extends { [K_1 in keyof T]: T[K_1]["options"] extends (infer U)[] ? U extends {
12
+ updateSystemOption: <K extends { [K_1 in keyof T]: T[K_1]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends {
13
13
  media: [string, string, string];
14
- } ? K_1 : never : never }[keyof T]>(themeKey: K, [ifMatch, ifNotMatch]: [T[K]["options"] extends (infer U)[] ? U extends string ? U : U extends {
14
+ } ? K_1 : never : never }[keyof T]>(themeKey: K, [ifMatch, ifNotMatch]: [T[K]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends string ? U : U extends {
15
15
  value: string;
16
16
  media?: [string, string, string];
17
17
  } ? U extends {
18
18
  media: [string, string, string];
19
- } ? never : U["value"] : never : never, T[K]["options"] extends (infer U)[] ? U extends string ? U : U extends {
19
+ } ? never : U["value"] : never : never, T[K]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends string ? U : U extends {
20
20
  value: string;
21
21
  media?: [string, string, string];
22
22
  } ? U extends {
@@ -25,14 +25,14 @@ declare function useResonare<T extends ThemeConfig>(getStore: () => ThemeStore<T
25
25
  getStateToPersist: () => {
26
26
  version: 1;
27
27
  themes: Themes<T>;
28
- systemOptions: { [K_1 in { [K in keyof T]: T[K]["options"] extends (infer U)[] ? U extends {
28
+ systemOptions: { [K_1 in { [K in keyof T]: T[K]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends {
29
29
  media: [string, string, string];
30
- } ? K : never : never }[keyof T]]?: [T[K_1]["options"] extends (infer U)[] ? U extends string ? U : U extends {
30
+ } ? K : never : never }[keyof T]]?: [T[K_1]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends string ? U : U extends {
31
31
  value: string;
32
32
  media?: [string, string, string];
33
33
  } ? U extends {
34
34
  media: [string, string, string];
35
- } ? never : U["value"] : never : never, T[K_1]["options"] extends (infer U)[] ? U extends string ? U : U extends {
35
+ } ? never : U["value"] : never : never, T[K_1]["options"] extends (infer U)[] | readonly (infer U)[] ? U extends string ? U : U extends {
36
36
  value: string;
37
37
  media?: [string, string, string];
38
38
  } ? U extends {
@@ -1 +1,7 @@
1
- var resonare=(function(e){var t=`resonare`;let n=({storageType:e=`localStorage`}={})=>({abortController:t})=>({getItem:t=>JSON.parse(window[e].getItem(t)||`null`),setItem:(t,n)=>{window[e].setItem(t,JSON.stringify(n))},watch:n=>{let r=new AbortController;return window.addEventListener(`storage`,t=>{t.storageArea===window[e]&&n(t.key,JSON.parse(t.newValue))},{signal:AbortSignal.any([t.signal,r.signal])}),()=>{r.abort()}}}),r=()=>({abortController:e})=>{let n=new Map,r=new BroadcastChannel(t);return{getItem:e=>n.get(e)||null,setItem:(e,t)=>{n.set(e,t)},broadcast:(e,t)=>{r.postMessage({key:e,value:t})},watch:t=>{let n=new AbortController;return r.addEventListener(`message`,e=>{t(e.data.key,e.data.value)},{signal:AbortSignal.any([e.signal,n.signal])}),()=>{n.abort()}}}},i=`resonare`;function a(e){return Object.entries(e).map(([e,{options:t}])=>[e,t.map(e=>typeof e==`string`?e:e.value)])}function o(e){return Object.fromEntries(Object.entries(e).map(([e,{options:t,defaultOption:n}])=>[e,n??(typeof t[0]==`string`?t[0]:t[0].value)]))}let s=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;var c=class{#e;#t;#n;#r;#i;#a=new Set;#o;#s=new AbortController;constructor({key:e=i,config:t,initialState:r={},storage:a=n()}){let s={},c={...r.systemOptions};Object.entries(t).forEach(([e,{options:t}])=>{s[e]=s[e]||{},t.forEach(t=>{typeof t==`string`?s[e][t]={value:t}:(t.media&&!Object.hasOwn(c,e)&&(c[e]=[t.media[1],t.media[2]]),s[e][t.value]=t)})}),this.#n={key:e,config:s,storage:a},this.#r=c,this.#e=o(t),this.#t={...this.#e,...r.themes},this.#i=this.#n.storage({abortController:this.#s}),this.#o={}}getThemes=()=>this.#t;getResolvedThemes=()=>this.#l();setThemes=async e=>{let t=typeof e==`function`?e(this.#t):e;this.#c({...this.#t,...t});let n=this.getStateToPersist();await this.#i.setItem(this.#n.key,n),this.#i.broadcast?.(this.#n.key,n)};updateSystemOption=(e,[t,n])=>{this.#r[e]=[t,n],this.setThemes({...this.#t})};getStateToPersist=()=>({version:1,themes:this.#t,systemOptions:this.#r});restore=async()=>{let e=await this.#i.getItem(this.#n.key);if(!e){this.#c({...this.#e});return}Object.hasOwn(e,`version`)||(e={version:1,themes:e,systemOptions:this.#r}),this.#r={...this.#r,...e.systemOptions},this.#c({...this.#e,...e.themes})};subscribe=(e,{immediate:t=!1}={})=>(t&&e({themes:this.#t,resolvedThemes:this.#l()}),this.#a.add(e),()=>{this.#a.delete(e)});sync=()=>{if(!this.#i.watch){console.warn(`[${i}] No watch method was provided for storage.`);return}return this.#i.watch((e,t)=>{e===this.#n.key&&(this.#r=t.systemOptions,this.#c(t.themes))})};___destroy=()=>{this.#a.clear(),this.#s.abort()};#c=e=>{this.#t=e,this.#d()};#l=()=>Object.fromEntries(Object.entries(this.#t).map(([e,t])=>{let n=this.#n.config[e][t];return[e,this.#u({themeKey:e,option:n})]}));#u=({themeKey:e,option:t})=>{if(!t.media)return t.value;if(!s)return console.warn(`[${i}] Option with key "media" cannot be resolved in server environment.`),t.value;let[n]=t.media;if(!this.#o[n]){let r=window.matchMedia(n);this.#o[n]=r,r.addEventListener(`change`,()=>{this.#t[e]===t.value&&this.#c({...this.#t})},{signal:this.#s.signal})}let[r,a]=this.#r[e];return this.#o[n].matches?r:a};#d=()=>{for(let e of this.#a)e({themes:this.#t,resolvedThemes:this.#l()})}};let l=new class{#e=new Map;create=e=>{let t=e.key||i;this.#e.has(t)&&this.destroy(t);let n=new c(e);return this.#e.set(t,n),n};get=e=>{let t=e||i;if(!this.#e.has(t))throw Error(`[${i}] Theme store with key '${t}' could not be found. Please run \`createThemeStore\` with key '${t}' first.`);return this.#e.get(t)};destroy=e=>{let t=e||i;if(!this.#e.has(t))throw Error(`[${i}] Theme store with key '${t}' could not be found. Please run \`createThemeStore\` with key '${t}' first.`);this.#e.get(t).___destroy(),this.#e.delete(t)}},u=l.create,d=l.get,f=l.destroy;return e.createThemeStore=u,e.destroyThemeStore=f,e.getThemeStore=d,e.getThemesAndOptions=a,e.localStorageAdapter=n,e.memoryStorageAdapter=r,e})({});
1
+ /**
2
+ * resonare v0.0.2
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ var resonare=(function(e){var t=`resonare`;let n=({storageType:e=`localStorage`}={})=>({abortController:t})=>({getItem:t=>JSON.parse(window[e].getItem(t)||`null`),setItem:(t,n)=>{window[e].setItem(t,JSON.stringify(n))},watch:n=>{let r=new AbortController;return window.addEventListener(`storage`,t=>{t.storageArea===window[e]&&n(t.key,JSON.parse(t.newValue))},{signal:AbortSignal.any([t.signal,r.signal])}),()=>{r.abort()}}}),r=()=>({abortController:e})=>{let n=new Map,r=new BroadcastChannel(t);return{getItem:e=>n.get(e)||null,setItem:(e,t)=>{n.set(e,t)},broadcast:(e,t)=>{r.postMessage({key:e,value:t})},watch:t=>{let n=new AbortController;return r.addEventListener(`message`,e=>{t(e.data.key,e.data.value)},{signal:AbortSignal.any([e.signal,n.signal])}),()=>{n.abort()}}}},i=`resonare`;function a(e){return Object.entries(e).map(([e,{options:t}])=>[e,t.map(e=>typeof e==`string`?e:e.value)])}function o(e){return Object.fromEntries(Object.entries(e).map(([e,{options:t,defaultOption:n}])=>[e,n??(typeof t[0]==`string`?t[0]:t[0].value)]))}let s=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;var c=class{#e;#t;#n;#r;#i;#a=new Set;#o;#s=new AbortController;constructor({key:e=i,config:t,initialState:r={},storage:a=n()}){let s={},c={...r.systemOptions};Object.entries(t).forEach(([e,{options:t}])=>{s[e]=s[e]||{},t.forEach(t=>{typeof t==`string`?s[e][t]={value:t}:(t.media&&!Object.hasOwn(c,e)&&(c[e]=[t.media[1],t.media[2]]),s[e][t.value]=t)})}),this.#n={key:e,config:s,storage:a},this.#r=c,this.#e=o(t),this.#t={...this.#e,...r.themes},this.#i=this.#n.storage({abortController:this.#s}),this.#o={}}getThemes=()=>this.#t;getResolvedThemes=()=>this.#l();setThemes=async e=>{let t=typeof e==`function`?e(this.#t):e;this.#c({...this.#t,...t});let n=this.getStateToPersist();await this.#i.setItem(this.#n.key,n),this.#i.broadcast?.(this.#n.key,n)};updateSystemOption=(e,[t,n])=>{this.#r[e]=[t,n],this.setThemes({...this.#t})};getStateToPersist=()=>({version:1,themes:this.#t,systemOptions:this.#r});restore=async()=>{let e=await this.#i.getItem(this.#n.key);if(!e){this.#c({...this.#e});return}Object.hasOwn(e,`version`)||(e={version:1,themes:e,systemOptions:this.#r}),this.#r={...this.#r,...e.systemOptions},this.#c({...this.#e,...e.themes})};subscribe=(e,{immediate:t=!1}={})=>(t&&e({themes:this.#t,resolvedThemes:this.#l()}),this.#a.add(e),()=>{this.#a.delete(e)});sync=()=>{if(!this.#i.watch){console.warn(`[${i}] No watch method was provided for storage.`);return}return this.#i.watch((e,t)=>{e===this.#n.key&&(this.#r=t.systemOptions,this.#c(t.themes))})};___destroy=()=>{this.#a.clear(),this.#s.abort()};#c=e=>{this.#t=e,this.#d()};#l=()=>Object.fromEntries(Object.entries(this.#t).map(([e,t])=>{let n=this.#n.config[e][t];return[e,this.#u({themeKey:e,option:n})]}));#u=({themeKey:e,option:t})=>{if(!t.media)return t.value;if(!s)return console.warn(`[${i}] Option with key "media" cannot be resolved in server environment.`),t.value;let[n]=t.media;if(!this.#o[n]){let r=window.matchMedia(n);this.#o[n]=r,r.addEventListener(`change`,()=>{this.#t[e]===t.value&&this.#c({...this.#t})},{signal:this.#s.signal})}let[r,a]=this.#r[e];return this.#o[n].matches?r:a};#d=()=>{for(let e of this.#a)e({themes:this.#t,resolvedThemes:this.#l()})}};let l=new class{#e=new Map;create=e=>{let t=e.key||i,n=this.#e.get(t);return n||(n=new c(e),this.#e.set(t,n)),n};get=e=>{let t=e||i;if(!this.#e.has(t))throw Error(`[${i}] Theme store with key '${t}' could not be found. Please run \`createThemeStore\` with key '${t}' first.`);return this.#e.get(t)};destroy=e=>{let t=e||i;if(!this.#e.has(t))throw Error(`[${i}] Theme store with key '${t}' could not be found. Please run \`createThemeStore\` with key '${t}' first.`);this.#e.get(t).___destroy(),this.#e.delete(t)}},u=l.create,d=l.get,f=l.destroy;return e.createThemeStore=u,e.destroyThemeStore=f,e.getDefaultThemes=o,e.getThemeStore=d,e.getThemesAndOptions=a,e.localStorageAdapter=n,e.memoryStorageAdapter=r,e})({});
package/global.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  interface Window {
2
- resonare: typeof import('./dist/umd')
2
+ resonare: typeof import('./dist/index')
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resonare",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Resonare",
5
5
  "keywords": [
6
6
  "theming"
@@ -19,19 +19,12 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "import": "./dist/index.js"
21
21
  },
22
- "./global": {
23
- "types": "./dist/umd.d.ts"
24
- },
25
22
  "./raw": {
26
23
  "import": "./dist/resonare.iife.min.js"
27
24
  },
28
25
  "./react": {
29
26
  "types": "./dist/react.d.ts",
30
27
  "import": "./dist/react.js"
31
- },
32
- "./storage": {
33
- "types": "./dist/storage.d.ts",
34
- "import": "./dist/storage.js"
35
28
  }
36
29
  },
37
30
  "jsdelivr": "./dist/resonare.iife.min.js",
@@ -49,13 +42,13 @@
49
42
  "@types/react": "19.2.7",
50
43
  "@vitejs/plugin-react": "5.1.2",
51
44
  "babel-plugin-react-compiler": "1.0.0",
52
- "jsdom": "27.3.0",
53
- "react": "19.2.1",
54
- "react-dom": "19.2.1",
45
+ "jsdom": "27.4.0",
46
+ "react": "19.2.3",
47
+ "react-dom": "19.2.3",
55
48
  "size-limit": "12.0.0",
56
- "tsdown": "0.17.2",
49
+ "tsdown": "0.18.4",
57
50
  "typescript": "5.9.3",
58
- "vitest": "4.0.15"
51
+ "vitest": "4.0.16"
59
52
  },
60
53
  "peerDependencies": {
61
54
  "react": "^18.0.0 || ^19.0.0"
@@ -1,65 +0,0 @@
1
- import { n as StorageAdapterCreate } from "./storage-BP1DGXF0.js";
2
-
3
- //#region src/index.d.ts
4
- type ThemeOption = {
5
- value: string;
6
- media?: [string, string, string];
7
- };
8
- type ThemeConfig = Record<string, {
9
- options: Array<string | ThemeOption>;
10
- defaultOption?: string;
11
- }>;
12
- type Themes<T extends ThemeConfig> = { [K in keyof T]: T[K]['options'] extends Array<infer U> ? U extends string ? U : U extends ThemeOption ? U['value'] : never : never };
13
- type Listener<T extends ThemeConfig> = (value: {
14
- themes: Themes<T>;
15
- resolvedThemes: Themes<T>;
16
- }) => void;
17
- type ThemeKeysWithSystemOption<T extends ThemeConfig> = { [K in keyof T]: T[K]['options'] extends Array<infer U> ? U extends {
18
- media: [string, string, string];
19
- } ? K : never : never }[keyof T];
20
- type NonSystemOptionValues<T extends ThemeConfig, K$1 extends keyof T> = T[K$1]['options'] extends Array<infer U> ? U extends string ? U : U extends ThemeOption ? U extends {
21
- media: [string, string, string];
22
- } ? never : U['value'] : never : never;
23
- type SystemOptions<T extends ThemeConfig> = { [K in ThemeKeysWithSystemOption<T>]?: [NonSystemOptionValues<T, K>, NonSystemOptionValues<T, K>] };
24
- type PersistedState<T extends ThemeConfig> = {
25
- version: 1;
26
- themes: Themes<T>;
27
- systemOptions: SystemOptions<T>;
28
- };
29
- type ThemeStoreOptions<T extends ThemeConfig> = {
30
- key?: string;
31
- config: T;
32
- initialState?: Partial<PersistedState<T>>;
33
- storage?: StorageAdapterCreate;
34
- };
35
- type ThemeAndOptions<T extends ThemeConfig> = Array<{ [K in keyof T]: [K, Array<T[K]['options'] extends Array<infer U> ? U extends string ? U : U extends ThemeOption ? U['value'] : never : never>] }[keyof T]>;
36
- declare function getThemesAndOptions<T extends ThemeConfig>(config: T): ThemeAndOptions<T>;
37
- declare function getDefaultThemes<T extends ThemeConfig>(config: T): Themes<T>;
38
- declare class ThemeStore<T extends ThemeConfig> {
39
- #private;
40
- constructor({
41
- key,
42
- config,
43
- initialState,
44
- storage
45
- }: ThemeStoreOptions<T>);
46
- getThemes: () => Themes<T>;
47
- getResolvedThemes: () => Themes<T>;
48
- setThemes: (themes: Partial<Themes<T>> | ((currentThemes: Themes<T>) => Partial<Themes<T>>)) => Promise<void>;
49
- updateSystemOption: <K$1 extends ThemeKeysWithSystemOption<T>>(themeKey: K$1, [ifMatch, ifNotMatch]: [NonSystemOptionValues<T, K$1>, NonSystemOptionValues<T, K$1>]) => void;
50
- getStateToPersist: () => PersistedState<T>;
51
- restore: () => Promise<void>;
52
- subscribe: (callback: Listener<T>, {
53
- immediate
54
- }?: {
55
- immediate?: boolean;
56
- }) => (() => void);
57
- sync: () => (() => void) | undefined;
58
- ___destroy: () => void;
59
- }
60
- declare const createThemeStore: <T extends ThemeConfig>(options: ThemeStoreOptions<T>) => ThemeStore<T>;
61
- declare const getThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => ThemeStoreRegistry[T];
62
- declare const destroyThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => void;
63
- interface ThemeStoreRegistry {}
64
- //#endregion
65
- export { ThemeStoreRegistry as a, destroyThemeStore as c, getThemesAndOptions as d, ThemeStoreOptions as i, getDefaultThemes as l, ThemeConfig as n, Themes as o, ThemeStore as r, createThemeStore as s, ThemeAndOptions as t, getThemeStore as u };
@@ -1,188 +0,0 @@
1
- import { t as localStorageAdapter } from "./storage-ByzkGDod.js";
2
-
3
- //#region src/index.ts
4
- const PACKAGE_NAME = "resonare";
5
- function getThemesAndOptions(config) {
6
- return Object.entries(config).map(([themeKey, { options }]) => {
7
- return [themeKey, options.map((option) => typeof option === "string" ? option : option.value)];
8
- });
9
- }
10
- function getDefaultThemes(config) {
11
- return Object.fromEntries(Object.entries(config).map(([themeKey, { options, defaultOption }]) => {
12
- return [themeKey, defaultOption ?? (typeof options[0] === "string" ? options[0] : options[0].value)];
13
- }));
14
- }
15
- const isClient = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined");
16
- var ThemeStore = class {
17
- #defaultThemes;
18
- #currentThemes;
19
- #options;
20
- #systemOptions;
21
- #storage;
22
- #listeners = /* @__PURE__ */ new Set();
23
- #mediaQueryCache;
24
- #abortController = new AbortController();
25
- constructor({ key = PACKAGE_NAME, config, initialState = {}, storage = localStorageAdapter() }) {
26
- const keyedConfig = {};
27
- const systemOptions = { ...initialState.systemOptions };
28
- Object.entries(config).forEach(([themeKey, { options }]) => {
29
- keyedConfig[themeKey] = keyedConfig[themeKey] || {};
30
- options.forEach((option) => {
31
- if (typeof option === "string") keyedConfig[themeKey][option] = { value: option };
32
- else {
33
- if (option.media && !Object.hasOwn(systemOptions, themeKey)) systemOptions[themeKey] = [option.media[1], option.media[2]];
34
- keyedConfig[themeKey][option.value] = option;
35
- }
36
- });
37
- });
38
- this.#options = {
39
- key,
40
- config: keyedConfig,
41
- storage
42
- };
43
- this.#systemOptions = systemOptions;
44
- this.#defaultThemes = getDefaultThemes(config);
45
- this.#currentThemes = {
46
- ...this.#defaultThemes,
47
- ...initialState.themes
48
- };
49
- this.#storage = this.#options.storage({ abortController: this.#abortController });
50
- this.#mediaQueryCache = {};
51
- }
52
- getThemes = () => {
53
- return this.#currentThemes;
54
- };
55
- getResolvedThemes = () => {
56
- return this.#resolveThemes();
57
- };
58
- setThemes = async (themes) => {
59
- const updatedThemes = typeof themes === "function" ? themes(this.#currentThemes) : themes;
60
- this.#setThemesAndNotify({
61
- ...this.#currentThemes,
62
- ...updatedThemes
63
- });
64
- const stateToPersist = this.getStateToPersist();
65
- await this.#storage.setItem(this.#options.key, stateToPersist);
66
- this.#storage.broadcast?.(this.#options.key, stateToPersist);
67
- };
68
- updateSystemOption = (themeKey, [ifMatch, ifNotMatch]) => {
69
- this.#systemOptions[themeKey] = [ifMatch, ifNotMatch];
70
- this.setThemes({ ...this.#currentThemes });
71
- };
72
- getStateToPersist = () => {
73
- return {
74
- version: 1,
75
- themes: this.#currentThemes,
76
- systemOptions: this.#systemOptions
77
- };
78
- };
79
- restore = async () => {
80
- let persistedState = await this.#storage.getItem(this.#options.key);
81
- if (!persistedState) {
82
- this.#setThemesAndNotify({ ...this.#defaultThemes });
83
- return;
84
- }
85
- if (!Object.hasOwn(persistedState, "version")) persistedState = {
86
- version: 1,
87
- themes: persistedState,
88
- systemOptions: this.#systemOptions
89
- };
90
- this.#systemOptions = {
91
- ...this.#systemOptions,
92
- ...persistedState.systemOptions
93
- };
94
- this.#setThemesAndNotify({
95
- ...this.#defaultThemes,
96
- ...persistedState.themes
97
- });
98
- };
99
- subscribe = (callback, { immediate = false } = {}) => {
100
- if (immediate) callback({
101
- themes: this.#currentThemes,
102
- resolvedThemes: this.#resolveThemes()
103
- });
104
- this.#listeners.add(callback);
105
- return () => {
106
- this.#listeners.delete(callback);
107
- };
108
- };
109
- sync = () => {
110
- if (!this.#storage.watch) {
111
- console.warn(`[${PACKAGE_NAME}] No watch method was provided for storage.`);
112
- return;
113
- }
114
- return this.#storage.watch((key, persistedState) => {
115
- if (key !== this.#options.key) return;
116
- this.#systemOptions = persistedState.systemOptions;
117
- this.#setThemesAndNotify(persistedState.themes);
118
- });
119
- };
120
- ___destroy = () => {
121
- this.#listeners.clear();
122
- this.#abortController.abort();
123
- };
124
- #setThemesAndNotify = (themes) => {
125
- this.#currentThemes = themes;
126
- this.#notify();
127
- };
128
- #resolveThemes = () => {
129
- return Object.fromEntries(Object.entries(this.#currentThemes).map(([themeKey, optionKey]) => {
130
- const option = this.#options.config[themeKey][optionKey];
131
- return [themeKey, this.#resolveThemeOption({
132
- themeKey,
133
- option
134
- })];
135
- }));
136
- };
137
- #resolveThemeOption = ({ themeKey, option }) => {
138
- if (!option.media) return option.value;
139
- if (!isClient) {
140
- console.warn(`[${PACKAGE_NAME}] Option with key "media" cannot be resolved in server environment.`);
141
- return option.value;
142
- }
143
- const [mediaQuery] = option.media;
144
- if (!this.#mediaQueryCache[mediaQuery]) {
145
- const mediaQueryList = window.matchMedia(mediaQuery);
146
- this.#mediaQueryCache[mediaQuery] = mediaQueryList;
147
- mediaQueryList.addEventListener("change", () => {
148
- if (this.#currentThemes[themeKey] === option.value) this.#setThemesAndNotify({ ...this.#currentThemes });
149
- }, { signal: this.#abortController.signal });
150
- }
151
- const [ifMatch, ifNotMatch] = this.#systemOptions[themeKey];
152
- return this.#mediaQueryCache[mediaQuery].matches ? ifMatch : ifNotMatch;
153
- };
154
- #notify = () => {
155
- for (const listener of this.#listeners) listener({
156
- themes: this.#currentThemes,
157
- resolvedThemes: this.#resolveThemes()
158
- });
159
- };
160
- };
161
- var Registry = class {
162
- #registry = /* @__PURE__ */ new Map();
163
- create = (options) => {
164
- const storeKey = options.key || PACKAGE_NAME;
165
- if (this.#registry.has(storeKey)) this.destroy(storeKey);
166
- const themeStore = new ThemeStore(options);
167
- this.#registry.set(storeKey, themeStore);
168
- return themeStore;
169
- };
170
- get = (key) => {
171
- const storeKey = key || PACKAGE_NAME;
172
- if (!this.#registry.has(storeKey)) throw new Error(`[${PACKAGE_NAME}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`);
173
- return this.#registry.get(storeKey);
174
- };
175
- destroy = (key) => {
176
- const storeKey = key || PACKAGE_NAME;
177
- if (!this.#registry.has(storeKey)) throw new Error(`[${PACKAGE_NAME}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`);
178
- this.#registry.get(storeKey).___destroy();
179
- this.#registry.delete(storeKey);
180
- };
181
- };
182
- const registry = new Registry();
183
- const createThemeStore = registry.create;
184
- const getThemeStore = registry.get;
185
- const destroyThemeStore = registry.destroy;
186
-
187
- //#endregion
188
- export { getThemeStore as a, getDefaultThemes as i, createThemeStore as n, getThemesAndOptions as o, destroyThemeStore as r, ThemeStore as t };
@@ -1,19 +0,0 @@
1
- //#region src/storage.d.ts
2
- type StorageAdapter = {
3
- getItem: (key: string) => object | null | Promise<object | null>;
4
- setItem: (key: string, value: object) => void | Promise<void>;
5
- broadcast?: (key: string, value: object) => void;
6
- watch?: (cb: (key: string | null, value: object) => void) => () => void;
7
- };
8
- type StorageAdapterCreate = ({
9
- abortController
10
- }: {
11
- abortController: AbortController;
12
- }) => StorageAdapter;
13
- type StorageAdapterCreator<Options> = (options?: Options) => StorageAdapterCreate;
14
- declare const localStorageAdapter: StorageAdapterCreator<{
15
- storageType?: 'localStorage' | 'sessionStorage';
16
- }>;
17
- declare const memoryStorageAdapter: StorageAdapterCreator<never>;
18
- //#endregion
19
- export { memoryStorageAdapter as a, localStorageAdapter as i, StorageAdapterCreate as n, StorageAdapterCreator as r, StorageAdapter as t };
@@ -1,59 +0,0 @@
1
- //#region package.json
2
- var name = "resonare";
3
-
4
- //#endregion
5
- //#region src/storage.ts
6
- const localStorageAdapter = ({ storageType = "localStorage" } = {}) => {
7
- return ({ abortController }) => {
8
- return {
9
- getItem: (key) => {
10
- return JSON.parse(window[storageType].getItem(key) || "null");
11
- },
12
- setItem: (key, value) => {
13
- window[storageType].setItem(key, JSON.stringify(value));
14
- },
15
- watch: (cb) => {
16
- const controller = new AbortController();
17
- window.addEventListener("storage", (e) => {
18
- if (e.storageArea !== window[storageType]) return;
19
- cb(e.key, JSON.parse(e.newValue));
20
- }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
21
- return () => {
22
- controller.abort();
23
- };
24
- }
25
- };
26
- };
27
- };
28
- const memoryStorageAdapter = () => {
29
- return ({ abortController }) => {
30
- const storage = /* @__PURE__ */ new Map();
31
- const channel = new BroadcastChannel(name);
32
- return {
33
- getItem: (key) => {
34
- return storage.get(key) || null;
35
- },
36
- setItem: (key, value) => {
37
- storage.set(key, value);
38
- },
39
- broadcast: (key, value) => {
40
- channel.postMessage({
41
- key,
42
- value
43
- });
44
- },
45
- watch: (cb) => {
46
- const controller = new AbortController();
47
- channel.addEventListener("message", (e) => {
48
- cb(e.data.key, e.data.value);
49
- }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
50
- return () => {
51
- controller.abort();
52
- };
53
- }
54
- };
55
- };
56
- };
57
-
58
- //#endregion
59
- export { memoryStorageAdapter as n, localStorageAdapter as t };
package/dist/storage.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { a as memoryStorageAdapter, i as localStorageAdapter, n as StorageAdapterCreate, r as StorageAdapterCreator, t as StorageAdapter } from "./storage-BP1DGXF0.js";
2
- export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator, localStorageAdapter, memoryStorageAdapter };
package/dist/storage.js DELETED
@@ -1,3 +0,0 @@
1
- import { n as memoryStorageAdapter, t as localStorageAdapter } from "./storage-ByzkGDod.js";
2
-
3
- export { localStorageAdapter, memoryStorageAdapter };
package/dist/umd.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { a as memoryStorageAdapter, i as localStorageAdapter } from "./storage-BP1DGXF0.js";
2
- import { c as destroyThemeStore, d as getThemesAndOptions, s as createThemeStore, u as getThemeStore } from "./index-C1bWhjq0.js";
3
- export { createThemeStore, destroyThemeStore, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
package/dist/umd.js DELETED
@@ -1,4 +0,0 @@
1
- import { n as memoryStorageAdapter, t as localStorageAdapter } from "./storage-ByzkGDod.js";
2
- import { a as getThemeStore, n as createThemeStore, o as getThemesAndOptions, r as destroyThemeStore } from "./src-BsTQDM3B.js";
3
-
4
- export { createThemeStore, destroyThemeStore, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };