resonare 0.0.18 → 0.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Resonare [![Version](https://img.shields.io/npm/v/resonare.svg?labelColor=black&color=blue)](https://www.npmjs.com/package/resonare)
2
2
 
3
- A configuration-based store for restoring user preferences without flash of inaccurate styles.
3
+ A configuration-based store for restoring user preferences without a flash of incorrect styles.
4
4
 
5
5
  ## Features
6
6
 
@@ -38,50 +38,12 @@ pnpm add resonare
38
38
 
39
39
  ## Basic Usage
40
40
 
41
- It's recommended to initialize Resonare in a synchronous script to avoid flicker on page load.
42
-
43
- Load via CDN:
44
-
45
- ```html
46
- <script src="https://unpkg.com/resonare"></script>
47
- <!-- or -->
48
- <script src="https://cdn.jsdelivr.net/npm/resonare"></script>
49
-
50
- <script>
51
- ;(() => {
52
- const themeStore = window.resonare.createThemeStore({
53
- config: {
54
- colorScheme: {
55
- options: [
56
- {
57
- value: 'system',
58
- media: ['(prefers-color-scheme: dark)', 'dark', 'light'],
59
- },
60
- 'light',
61
- 'dark',
62
- ],
63
- },
64
- },
65
- })
41
+ ### 1. Create inline script
66
42
 
67
- themeStore.subscribe(({ resolvedThemes }) => {
68
- Object.entries(resolvedThemes).forEach(([key, value]) => {
69
- document.documentElement.dataset[key] = value
70
- })
71
- })
72
-
73
- themeStore.restore()
74
-
75
- themeStore.sync()
76
- })()
77
- </script>
78
- ```
79
-
80
- Alternatively, inline the stringified version to reduce the number of HTTP requests:
43
+ When storing theme selection in `localStorage`, an inline `<script>` that restores user preferences and updates the DOM before first paint is required to prevent flicker.
81
44
 
82
45
  ```ts
83
- import type { ThemeStore, ThemeStoreConfig } from 'resonare'
84
- import { resonareInlineScript } from 'resonare/inline-script'
46
+ import { createInlineThemeScript, type ThemeStoreConfig } from 'resonare'
85
47
 
86
48
  const CONFIG = {
87
49
  colorScheme: {
@@ -96,42 +58,49 @@ const CONFIG = {
96
58
  },
97
59
  } as const satisfies ThemeStoreConfig
98
60
 
99
- declare module 'resonare' {
100
- interface ThemeStoreRegistry {
101
- resonare: ThemeStore<typeof CONFIG>
102
- }
103
- }
61
+ export const themeScript = createInlineThemeScript([
62
+ {
63
+ config: CONFIG,
64
+ handler: ({ resolvedThemes }) => {
65
+ Object.entries(resolvedThemes).forEach(([key, value]) => {
66
+ document.documentElement.dataset[key] = value
67
+ })
68
+ },
69
+ },
70
+ ])
71
+ ```
104
72
 
105
- function initTheme({ config }: { config: ThemeStoreConfig }) {
106
- const themeStore = window.resonare.createThemeStore({ config })
73
+ ### 2. Inject inline script
107
74
 
108
- themeStore.subscribe(({ resolvedThemes }) => {
109
- Object.entries(resolvedThemes).forEach(([key, value]) => {
110
- document.documentElement.dataset[key] = value
111
- })
112
- })
75
+ Inject the script into the `<head>` of your HTML document as shown below. The exact pattern differs by framework; see the examples below.
113
76
 
114
- themeStore.restore()
77
+ ```tsx
78
+ // React.js
79
+ <script>{themeScript}</script>
115
80
 
116
- themeStore.sync()
117
- }
81
+ // TanStack Router/Start
82
+ import { ScriptOnce } from '@tanstack/router'
83
+
84
+ <ScriptOnce>{themeScript}</ScriptOnce>
118
85
 
119
- export const themeScript = `${resonareInlineScript};
120
- (${initTheme.toString()})(${JSON.stringify({ config: CONFIG })})`
86
+ // Astro.js
87
+ <script is:inline set:html={themeScript} />
121
88
  ```
122
89
 
123
- Add a triple-slash directive to any `.d.ts` file in your project (e.g. `env.d.ts`):
90
+ ### 3. Initialize the store
124
91
 
125
- ```ts
126
- /// <reference types="resonare/global" />
127
- ```
92
+ Refer to the [Framework Integration](#framework-integration) section for examples.
128
93
 
129
94
  ## API
130
95
 
131
96
  ### `createThemeStore`
132
97
 
133
98
  ```ts
134
- import { createThemeStore, type ThemeStore, type ThemeStoreConfig } from 'resonare'
99
+ import {
100
+ createThemeStore,
101
+ localStorageAdapter,
102
+ type ThemeStoreConfig,
103
+ } from 'resonare'
135
104
 
136
105
  const CONFIG = {
137
106
  colorScheme: {
@@ -164,57 +133,13 @@ const CONFIG = {
164
133
  },
165
134
  } as const satisfies ThemeStoreConfig
166
135
 
167
- declare module 'resonare' {
168
- interface ThemeStoreRegistry {
169
- resonare: ThemeStore<typeof CONFIG>
170
- }
171
- }
172
-
173
- const themeStore = createThemeStore({
174
- // optional, default 'resonare'
175
- // should be unique, also used as client storage key
176
- key: 'resonare',
177
-
178
- // required, specify theme and options
179
- config: CONFIG,
180
-
136
+ const themeStore = createThemeStore(CONFIG, {
181
137
  // optional, useful for server-side persistence
182
138
  initialState: persistedStateFromDb, // persisted state returned by themeStore.toPersist()
183
139
 
184
- // optional, specify your own client storage
140
+ // optional, specify your own client storage or null to disable client-side persistence
185
141
  // localStorage is used by default
186
- storage: ({ abortController }) => ({
187
- get: (key: string) => {
188
- return JSON.parse(localStorage.getItem(key) || 'null')
189
- },
190
-
191
- set: (key: string, value: object) => {
192
- localStorage.setItem(key, JSON.stringify(value))
193
- },
194
-
195
- watch: (cb: (key: string, value: unknown) => void) => {
196
- const controller = new AbortController()
197
-
198
- window.addEventListener(
199
- 'storage',
200
- (e) => {
201
- if (e.storageArea !== window.localStorage) return
202
-
203
- cb(e.key, JSON.parse(e.newValue!))
204
- },
205
- {
206
- signal: AbortSignal.any([
207
- abortController.signal,
208
- controller.signal,
209
- ]),
210
- },
211
- )
212
-
213
- return () => {
214
- controller.abort()
215
- }
216
- },
217
- })
142
+ storage: localStorageAdapter({ key: 'resonare' }),
218
143
  })
219
144
 
220
145
  // get current theme selection
@@ -235,11 +160,13 @@ themeStore.toPersist()
235
160
  // restore persisted state from client-side storage
236
161
  themeStore.restore()
237
162
 
238
- // sync theme selection across tabs/windows if supported by the storage adapter
239
- themeStore.sync()
163
+ // sync user preferences across tabs/windows if supported by the storage adapter
164
+ const stopSync = themeStore.sync()
165
+
166
+ stopSync?.()
240
167
 
241
168
  // subscribe to theme changes
242
- themeStore.subscribe(({ themes, resolvedThemes }) => {
169
+ const unsubscribe = themeStore.subscribe(({ themes, resolvedThemes }) => {
243
170
  Object.entries(resolvedThemes).forEach(([key, value]) => {
244
171
  if (key === 'sidebarWidth') {
245
172
  document.documentElement.style.setProperty('--sidebar-width', `${value}px`)
@@ -248,36 +175,19 @@ themeStore.subscribe(({ themes, resolvedThemes }) => {
248
175
  }
249
176
  })
250
177
  })
251
- ```
252
-
253
- ### `getThemeStore`
254
-
255
- ```ts
256
- import { getThemeStore } from 'resonare'
257
-
258
- // get an existing theme store by key
259
- const themeStore = getThemeStore('resonare')
260
- ```
261
-
262
- ### `destroyThemeStore`
263
178
 
264
- ```ts
265
- import { destroyThemeStore } from 'resonare'
179
+ unsubscribe()
266
180
 
267
- // destroy an existing theme store by key
268
- destroyThemeStore('resonare')
181
+ // destroy the store and clean up event listeners
182
+ themeStore.destroy()
269
183
  ```
270
184
 
271
- ## Framework Integrations
272
-
273
- ### React
185
+ ### `createInlineThemeScript`
274
186
 
275
- Ensure that you have initialized Resonare as per instructions under [Basic Usage](#basic-usage).
187
+ Generates a self-contained script string that restores persisted user preferences before first paint.
276
188
 
277
- ```tsx
278
- import * as React from 'react'
279
- import { getThemesAndOptions, type ThemeStoreConfig } from 'resonare'
280
- import { useResonare } from 'resonare/react'
189
+ ```ts
190
+ import { createInlineThemeScript, type ThemeStoreConfig } from 'resonare'
281
191
 
282
192
  const CONFIG = {
283
193
  colorScheme: {
@@ -292,12 +202,84 @@ const CONFIG = {
292
202
  },
293
203
  } as const satisfies ThemeStoreConfig
294
204
 
205
+ const script = createInlineThemeScript([
206
+ {
207
+ config: CONFIG,
208
+ handler: ({ resolvedThemes }) => {
209
+ Object.entries(resolvedThemes).forEach(([key, value]) => {
210
+ document.documentElement.dataset[key] = value
211
+ })
212
+ },
213
+ },
214
+ ])
215
+ ```
216
+
217
+ ## Framework Integration
218
+
219
+ ### React
220
+
221
+ #### Client-side persistence
222
+
223
+ ```tsx
224
+ import {
225
+ createInlineThemeScript,
226
+ createThemeStore,
227
+ getThemesAndOptions,
228
+ type ThemeScriptParameter,
229
+ } from 'resonare'
230
+ import { useResonare } from 'resonare/react'
231
+
232
+ const PARAM = {
233
+ key: 'demo',
234
+ config: {
235
+ colorScheme: {
236
+ options: [
237
+ {
238
+ value: 'system',
239
+ media: ['(prefers-color-scheme: dark)', 'dark', 'light'],
240
+ },
241
+ 'light',
242
+ 'dark',
243
+ ],
244
+ },
245
+ contrast: {
246
+ options: [
247
+ {
248
+ value: 'system',
249
+ media: [
250
+ '(prefers-contrast: more) and (forced-colors: none)',
251
+ 'high',
252
+ 'standard',
253
+ ],
254
+ },
255
+ 'standard',
256
+ 'high',
257
+ ],
258
+ },
259
+ },
260
+ handler: ({ resolvedThemes }) => {
261
+ Object.entries(resolvedThemes).forEach(([key, value]) => {
262
+ document.documentElement.dataset[key] = String(value)
263
+ })
264
+ },
265
+ } as const satisfies ThemeScriptParameter
266
+
267
+ export const themeScript = createInlineThemeScript([PARAM])
268
+
269
+ const themeStore = createThemeStore(PARAM.config)
270
+
271
+ if (typeof window !== 'undefined') {
272
+ themeStore.subscribe(PARAM.handler)
273
+
274
+ themeStore.restore()
275
+
276
+ themeStore.sync()
277
+ }
278
+
295
279
  function ThemeSelect() {
296
- const { themes, setThemes } = useResonare(() =>
297
- window.resonare.getThemeStore(),
298
- )
280
+ const { themes, setThemes } = useResonare(themeStore)
299
281
 
300
- return getThemesAndOptions(CONFIG).map(([theme, options]) => (
282
+ return getThemesAndOptions(PARAM.config).map(([theme, options]) => (
301
283
  <div key={theme}>
302
284
  <label htmlFor={theme}>{theme}</label>
303
285
  <select
@@ -319,11 +301,12 @@ function ThemeSelect() {
319
301
  }
320
302
  ```
321
303
 
322
- ## Server-side Persistence
304
+ #### Server-side persistence
323
305
 
324
- Use `memoryStorageAdapter` to avoid storing any data client-side. The synchronous script is not required.
306
+ The inline script is not required.
325
307
 
326
308
  ```tsx
309
+ import * as React from 'react'
327
310
  import {
328
311
  createThemeStore,
329
312
  getThemesAndOptions,
@@ -331,7 +314,6 @@ import {
331
314
  type ThemeStoreConfig,
332
315
  } from 'resonare'
333
316
  import { useResonare } from 'resonare/react'
334
- import * as React from 'react'
335
317
 
336
318
  const CONFIG = {
337
319
  colorScheme: {
@@ -344,16 +326,30 @@ const CONFIG = {
344
326
 
345
327
  export function ThemeSelect({ persistedStateFromDb }) {
346
328
  const [themeStore] = React.useState(() =>
347
- createThemeStore({
348
- config: CONFIG,
329
+ createThemeStore(CONFIG, {
349
330
  initialState: persistedStateFromDb,
350
- storage: memoryStorageAdapter(),
331
+ // pass null instead if syncing across tabs/windows is not needed
332
+ storage: memoryStorageAdapter({ key: 'resonare' }),
351
333
  }),
352
334
  )
353
335
 
354
- const { themes, setThemes } = useResonare(() => themeStore, {
355
- initOnMount: true,
356
- })
336
+ const { themes, setThemes, subscribe, sync } = useResonare(themeStore)
337
+
338
+ React.useEffect(() => {
339
+ const unsubscribe = subscribe(({ resolvedThemes }) => {
340
+ Object.entries(resolvedThemes).forEach(([key, value]) => {
341
+ document.documentElement.dataset[key] = String(value)
342
+ })
343
+ })
344
+
345
+ const stopSync = sync()
346
+
347
+ return () => {
348
+ unsubscribe()
349
+
350
+ stopSync?.()
351
+ }
352
+ }, [subscribe, sync])
357
353
 
358
354
  return getThemesAndOptions(CONFIG).map(([theme, options]) => (
359
355
  <div key={theme}>
@@ -364,7 +360,6 @@ export function ThemeSelect({ persistedStateFromDb }) {
364
360
  onChange={async (e) => {
365
361
  setThemes({ [theme]: e.target.value })
366
362
 
367
- // save to server-side storage
368
363
  await saveToDb(themeStore.toPersist())
369
364
  }}
370
365
  value={themes[theme]}
package/dist/index.d.ts CHANGED
@@ -1,20 +1,51 @@
1
1
  //#region src/storage.d.ts
2
+ /**
3
+ * Pluggable persistence for `createThemeStore`.
4
+ */
2
5
  type StorageAdapter = {
3
- get: (key: string) => object | null;
4
- set: (key: string, value: object) => void;
5
- broadcast?: (key: string, value: object) => void;
6
- watch?: (cb: (key: string | null, value: object) => void) => () => void;
6
+ get: () => object | null;
7
+ set: (value: object) => void; /** Optional: notify other tabs/contexts after local `set` operation. */
8
+ broadcast?: (value: object) => void; /** Optional: subscribes to other tabs/contexts' updates, returns `unsubscribe` function. */
9
+ watch?: (cb: (value: object) => void) => () => void;
7
10
  };
8
11
  type StorageAdapterCreate = ({
9
12
  abortController
10
13
  }: {
11
14
  abortController: AbortController;
12
15
  }) => StorageAdapter;
13
- type StorageAdapterCreator<Options> = (options?: Options) => StorageAdapterCreate;
16
+ type StorageAdapterCreator<Options> = (options: Options) => StorageAdapterCreate;
17
+ /**
18
+ * Persists theme store in `localStorage` or `sessionStorage`.
19
+ * @example
20
+ * ```ts
21
+ * import { createThemeStore, localStorageAdapter } from 'resonare'
22
+ *
23
+ * const store = createThemeStore(
24
+ * { mode: { options: ['light', 'dark'] },
25
+ * { storage: localStorageAdapter({ key: 'app', type: 'localStorage' }) },
26
+ * )
27
+ * ```
28
+ */
14
29
  declare const localStorageAdapter: StorageAdapterCreator<{
30
+ key: string;
15
31
  type?: 'localStorage' | 'sessionStorage';
16
32
  }>;
17
- declare const memoryStorageAdapter: StorageAdapterCreator<never>;
33
+ /**
34
+ * In-memory persistence and sync via `BroadcastChannel`.
35
+ * Useful with server-side persistence.
36
+ * @example
37
+ * ```ts
38
+ * import { createThemeStore, memoryStorageAdapter } from 'resonare'
39
+ *
40
+ * const store = createThemeStore(
41
+ * { mode: { options: ['light', 'dark'] } },
42
+ * { storage: memoryStorageAdapter({ key: 'preview' }) },
43
+ * )
44
+ * ```
45
+ */
46
+ declare const memoryStorageAdapter: StorageAdapterCreator<{
47
+ key: string;
48
+ }>;
18
49
  //#endregion
19
50
  //#region src/index.d.ts
20
51
  type ThemeValue = string | number | boolean;
@@ -51,13 +82,10 @@ type NonSystemOptionValues<T extends ThemeStoreConfig, K extends keyof T> = T[K]
51
82
  } ? never : U['value'] : never : never;
52
83
  type SystemOptions<T extends ThemeStoreConfig> = { [K in ThemeKeysWithSystemOption<T>]?: [NonSystemOptionValues<T, K>, NonSystemOptionValues<T, K>] };
53
84
  type PersistedState<T extends ThemeStoreConfig> = {
54
- version: 1;
55
85
  themes: Partial<Themes<T>>;
56
86
  systemOptions: SystemOptions<T>;
57
87
  };
58
- type ThemeStoreConstructor<T extends ThemeStoreConfig> = {
59
- key?: string;
60
- config: T;
88
+ type ThemeStoreOptions<T extends ThemeStoreConfig> = {
61
89
  initialState?: Partial<PersistedState<T>>;
62
90
  storage?: StorageAdapterCreate | null;
63
91
  };
@@ -68,30 +96,49 @@ declare function getThemesAndOptions<T extends ThemeStoreConfig>(config: T): The
68
96
  declare function getDefaultThemes<T extends ThemeStoreConfig>(config: T): Themes<T>;
69
97
  declare class ThemeStore<T extends ThemeStoreConfig> {
70
98
  #private;
71
- constructor({
72
- key,
73
- config,
99
+ constructor(config: T, {
74
100
  initialState,
75
101
  storage
76
- }: ThemeStoreConstructor<T>);
102
+ }?: ThemeStoreOptions<T>);
77
103
  getThemes: () => Themes<T>;
78
104
  getResolvedThemes: () => Themes<T>;
79
105
  setThemes: (themes: Partial<Themes<T>> | ((currentThemes: Themes<T>) => Partial<Themes<T>>)) => void;
80
106
  updateSystemOption: <K extends ThemeKeysWithSystemOption<T>>(themeKey: K, [ifMatch, ifNotMatch]: [NonSystemOptionValues<T, K>, NonSystemOptionValues<T, K>]) => void;
81
107
  toPersist: () => PersistedState<T>;
82
108
  restore: () => void;
83
- subscribe: (callback: Listener<T>, {
84
- immediate
85
- }?: {
86
- immediate?: boolean;
87
- }) => (() => void);
109
+ subscribe: (callback: Listener<T>) => (() => void);
88
110
  sync: () => (() => void) | undefined;
89
- ___destroy: () => void;
111
+ /** Clears subscribers and aborts media-query listeners tied to this store instance. */
112
+ destroy: () => void;
90
113
  }
91
- declare const createThemeStore: <T extends ThemeStoreConfig>(params: ThemeStoreConstructor<T>) => ThemeStore<T>;
92
- declare const getThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => ThemeStoreRegistry[T] | undefined;
93
- declare const destroyThemeStore: <T extends keyof ThemeStoreRegistry>(key?: T | undefined) => void;
94
- interface ThemeStoreRegistry {}
114
+ declare function createThemeStore<T extends ThemeStoreConfig>(config: T, options?: ThemeStoreOptions<T>): ThemeStore<T>;
115
+ type ThemeScriptParameter = {
116
+ /** `localStorage` key; defaults to the 'resonare'. */key?: string;
117
+ config: ThemeStoreConfig;
118
+ handler: Listener<ThemeStoreConfig>;
119
+ };
120
+ /**
121
+ * Creates an IIFE script string that reads persisted themes from `localStorage` and runs your handlers immediately.
122
+ *
123
+ * Useful for avoiding flash of incorrect styles.
124
+ * @example
125
+ * ```tsx
126
+ * import { createInlineThemeScript } from 'resonare'
127
+ *
128
+ * const inlineScript = createInlineThemeScript([
129
+ * {
130
+ * key: 'my-app',
131
+ * config: { options: ['light', 'dark'] },
132
+ * handler: ({ resolvedThemes }) => {
133
+ * document.documentElement.dataset.mode = String(resolvedThemes.mode)
134
+ * },
135
+ * },
136
+ * ])
137
+ *
138
+ * <script>{inlineScript}</script>
139
+ * ```
140
+ */
141
+ declare function createInlineThemeScript(themeScriptParameters: Array<ThemeScriptParameter>): string;
95
142
  //#endregion
96
- export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator, ThemeAndOptions, ThemeStore, ThemeStoreConfig, ThemeStoreRegistry, Themes, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
143
+ export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator, ThemeAndOptions, ThemeScriptParameter, type ThemeStore, ThemeStoreConfig, Themes, createInlineThemeScript, createThemeStore, getDefaultThemes, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
97
144
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/storage.ts","../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/storage.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,244 +1,2 @@
1
- //#region package.json
2
- var name = "resonare";
3
- //#endregion
4
- //#region src/storage.ts
5
- const localStorageAdapter = ({ type = "localStorage" } = {}) => {
6
- return ({ abortController }) => {
7
- return {
8
- get: (key) => {
9
- return JSON.parse(window[type].getItem(key) || "null");
10
- },
11
- set: (key, value) => {
12
- window[type].setItem(key, JSON.stringify(value));
13
- },
14
- watch: (cb) => {
15
- const controller = new AbortController();
16
- window.addEventListener("storage", (e) => {
17
- if (e.storageArea !== window[type]) return;
18
- cb(e.key, JSON.parse(e.newValue));
19
- }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
20
- return () => {
21
- controller.abort();
22
- };
23
- }
24
- };
25
- };
26
- };
27
- const memoryStorageAdapter = () => {
28
- return ({ abortController }) => {
29
- const storage = /* @__PURE__ */ new Map();
30
- const channel = new BroadcastChannel(name);
31
- return {
32
- get: (key) => {
33
- return storage.get(key) || null;
34
- },
35
- set: (key, value) => {
36
- storage.set(key, value);
37
- },
38
- broadcast: (key, value) => {
39
- channel.postMessage({
40
- key,
41
- value
42
- });
43
- },
44
- watch: (cb) => {
45
- const controller = new AbortController();
46
- channel.addEventListener("message", (e) => {
47
- cb(e.data.key, e.data.value);
48
- }, { signal: AbortSignal.any([abortController.signal, controller.signal]) });
49
- return () => {
50
- controller.abort();
51
- };
52
- }
53
- };
54
- };
55
- };
56
- //#endregion
57
- //#region src/index.ts
58
- function getThemesAndOptions(config) {
59
- return Object.entries(config).map(([themeKey, themeConfig]) => {
60
- return [themeKey, (themeConfig.options || []).map((option) => typeof option === "object" ? option.value : option)];
61
- });
62
- }
63
- function getDefaultThemes(config) {
64
- return Object.fromEntries(Object.entries(config).map(([themeKey, themeConfig]) => {
65
- return [themeKey, themeConfig.initialValue ?? (typeof themeConfig.options[0] === "object" ? themeConfig.options[0].value : themeConfig.options[0])];
66
- }));
67
- }
68
- var ThemeStore = class {
69
- #defaultThemes;
70
- #currentThemes;
71
- #options;
72
- #systemOptions;
73
- #storage;
74
- #listeners = /* @__PURE__ */ new Set();
75
- #mediaQueryCache;
76
- #abortController = new AbortController();
77
- constructor({ key = name, config, initialState = {}, storage = localStorageAdapter() }) {
78
- const systemOptions = { ...initialState.systemOptions };
79
- this.#options = {
80
- key,
81
- config: Object.fromEntries(Object.entries(config).map(([themeKey, themeConfig]) => {
82
- const entries = (themeConfig.options || []).map((option) => {
83
- if (typeof option === "object") {
84
- if (option.media && !Object.hasOwn(systemOptions, themeKey)) systemOptions[themeKey] = [option.media[1], option.media[2]];
85
- return [String(option.value), option];
86
- }
87
- return [String(option), { value: option }];
88
- });
89
- return [themeKey, Object.fromEntries(entries)];
90
- }))
91
- };
92
- this.#systemOptions = systemOptions;
93
- this.#defaultThemes = getDefaultThemes(config);
94
- this.#currentThemes = {
95
- ...this.#defaultThemes,
96
- ...initialState.themes
97
- };
98
- this.#storage = storage?.({ abortController: this.#abortController }) ?? null;
99
- this.#mediaQueryCache = {};
100
- }
101
- getThemes = () => {
102
- return this.#currentThemes;
103
- };
104
- getResolvedThemes = () => {
105
- return this.#resolveThemes();
106
- };
107
- setThemes = (themes) => {
108
- const updatedThemes = typeof themes === "function" ? themes(this.#currentThemes) : themes;
109
- this.#setThemesAndNotify({
110
- ...this.#currentThemes,
111
- ...updatedThemes
112
- });
113
- const stateToPersist = this.toPersist();
114
- if (this.#storage) {
115
- this.#storage.set(this.#options.key, stateToPersist);
116
- this.#storage.broadcast?.(this.#options.key, stateToPersist);
117
- }
118
- };
119
- updateSystemOption = (themeKey, [ifMatch, ifNotMatch]) => {
120
- this.#systemOptions[themeKey] = [ifMatch, ifNotMatch];
121
- this.setThemes({ ...this.#currentThemes });
122
- };
123
- toPersist = () => {
124
- return {
125
- version: 1,
126
- themes: this.#currentThemes,
127
- systemOptions: this.#systemOptions
128
- };
129
- };
130
- restore = () => {
131
- let persistedState = this.#storage?.get(this.#options.key);
132
- if (!persistedState) {
133
- this.#setThemesAndNotify({ ...this.#defaultThemes });
134
- return;
135
- }
136
- if (!Object.hasOwn(persistedState, "version")) persistedState = {
137
- version: 1,
138
- themes: persistedState,
139
- systemOptions: this.#systemOptions
140
- };
141
- this.#systemOptions = {
142
- ...this.#systemOptions,
143
- ...persistedState.systemOptions
144
- };
145
- this.#setThemesAndNotify({
146
- ...this.#defaultThemes,
147
- ...persistedState.themes
148
- });
149
- };
150
- subscribe = (callback, { immediate = false } = {}) => {
151
- if (immediate) callback({
152
- themes: this.#currentThemes,
153
- resolvedThemes: this.#resolveThemes()
154
- });
155
- this.#listeners.add(callback);
156
- return () => {
157
- this.#listeners.delete(callback);
158
- };
159
- };
160
- sync = () => {
161
- if (!this.#storage?.watch) return;
162
- return this.#storage.watch((key, persistedState) => {
163
- if (key !== this.#options.key) return;
164
- this.#systemOptions = persistedState.systemOptions;
165
- this.#setThemesAndNotify(persistedState.themes);
166
- });
167
- };
168
- ___destroy = () => {
169
- this.#listeners.clear();
170
- this.#abortController.abort();
171
- };
172
- #setThemesAndNotify = (themes) => {
173
- this.#currentThemes = themes;
174
- this.#notify();
175
- };
176
- #resolveThemes = () => {
177
- return Object.fromEntries(Object.entries(this.#currentThemes).map(([themeKey, optionKey]) => {
178
- const option = this.#options.config[themeKey]?.[optionKey];
179
- return [themeKey, option ? this.#resolveThemeOption({
180
- themeKey,
181
- option
182
- }) : optionKey];
183
- }));
184
- };
185
- #resolveThemeOption = ({ themeKey, option }) => {
186
- if (!option.media) return option.value;
187
- if (!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined")) {
188
- console.warn(`[${name}] Option with key "media" cannot be resolved in server environment.`);
189
- return option.value;
190
- }
191
- const [mediaQuery] = option.media;
192
- if (!this.#mediaQueryCache[mediaQuery]) {
193
- const mediaQueryList = window.matchMedia(mediaQuery);
194
- this.#mediaQueryCache[mediaQuery] = mediaQueryList;
195
- mediaQueryList.addEventListener("change", () => {
196
- if (this.#currentThemes[themeKey] === option.value) this.#setThemesAndNotify({ ...this.#currentThemes });
197
- }, { signal: this.#abortController.signal });
198
- }
199
- const [ifMatch, ifNotMatch] = this.#systemOptions[themeKey];
200
- return this.#mediaQueryCache[mediaQuery].matches ? ifMatch : ifNotMatch;
201
- };
202
- #notify = () => {
203
- for (const listener of this.#listeners) listener({
204
- themes: this.#currentThemes,
205
- resolvedThemes: this.#resolveThemes()
206
- });
207
- };
208
- };
209
- var Registry = class {
210
- #registry = /* @__PURE__ */ new Map();
211
- create = (params) => {
212
- const storeKey = params.key || name;
213
- let store = this.#registry.get(storeKey);
214
- if (!store) {
215
- store = new ThemeStore(params);
216
- this.#registry.set(storeKey, store);
217
- }
218
- return store;
219
- };
220
- get = (key) => {
221
- const storeKey = key || name;
222
- const store = this.#registry.get(storeKey);
223
- if (!store) {
224
- console.error(`[${name}] Theme store with key '${storeKey}' could not be found. Please run \`createThemeStore\` with key '${storeKey}' first.`);
225
- return;
226
- }
227
- return store;
228
- };
229
- destroy = (key) => {
230
- const storeKey = key || name;
231
- const store = this.#registry.get(storeKey);
232
- if (!store) return;
233
- store.___destroy();
234
- this.#registry.delete(storeKey);
235
- };
236
- };
237
- const registry = new Registry();
238
- const createThemeStore = registry.create;
239
- const getThemeStore = registry.get;
240
- const destroyThemeStore = registry.destroy;
241
- //#endregion
242
- export { ThemeStore, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
243
-
1
+ var e=`resonare-monorepo`;const t=({key:e,type:t=`localStorage`})=>({abortController:n})=>({get:()=>JSON.parse(window[t].getItem(e)||`null`),set:n=>{window[t].setItem(e,JSON.stringify(n))},watch:r=>{let i=new AbortController;return window.addEventListener(`storage`,n=>{n.storageArea===window[t]&&n.key===e&&r(JSON.parse(n.newValue))},{signal:AbortSignal.any([n.signal,i.signal])}),()=>{i.abort()}}}),n=({key:t})=>({abortController:n})=>{let r=new Map,i=new BroadcastChannel(e);return{get:()=>r.get(t)||null,set:e=>{r.set(t,e)},broadcast:e=>{i.postMessage({key:t,value:e})},watch:e=>{let r=new AbortController;return i.addEventListener(`message`,n=>{n.data.key===t&&e(n.data.value)},{signal:AbortSignal.any([n.signal,r.signal])}),()=>{r.abort()}}}};function r(e){return Object.entries(e).map(([e,t])=>[e,(t.options||[]).map(e=>typeof e==`object`?e.value:e)])}function i(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t.initialValue??(typeof t.options[0]==`object`?t.options[0].value:t.options[0])]))}var a=class{#e;#t;#n;#r;#i;#a=new Set;#o;#s=new AbortController;constructor(n,{initialState:r={},storage:a=t({key:e})}={}){let o={...r.systemOptions};this.#n=Object.fromEntries(Object.entries(n).map(([e,t])=>{let n=(t.options||[]).map(t=>typeof t==`object`?(t.media&&!Object.hasOwn(o,e)&&(o[e]=[t.media[1],t.media[2]]),[String(t.value),t]):[String(t),{value:t}]);return[e,Object.fromEntries(n)]})),this.#r=o,this.#e=i(n),this.#t={...this.#e,...r.themes},this.#i=a?.({abortController:this.#s})??null,this.#o={}}getThemes=()=>this.#t;getResolvedThemes=()=>this.#l();setThemes=e=>{let t=typeof e==`function`?e(this.#t):e;this.#c({...this.#t,...t});let n=this.toPersist();this.#i&&(this.#i.set(n),this.#i.broadcast?.(n))};updateSystemOption=(e,[t,n])=>{this.#r[e]=[t,n],this.setThemes({...this.#t})};toPersist=()=>({themes:this.#t,systemOptions:this.#r});restore=()=>{let e=this.#i?.get();if(!e){this.#c({...this.#e});return}this.#r={...this.#r,...e.systemOptions},this.#c({...this.#e,...e.themes})};subscribe=e=>(this.#a.add(e),()=>{this.#a.delete(e)});sync=()=>{if(this.#i?.watch)return this.#i.watch(e=>{this.#r=e.systemOptions,this.#c(e.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[e]?.[t];return[e,n?this.#u({themeKey:e,option:n}):t]}));#u=({themeKey:t,option:n})=>{if(!n.media)return n.value;if(!(typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0))return console.warn(`[${e}] Option with key "media" cannot be resolved in server environment.`),n.value;let[r]=n.media;if(!this.#o[r]){let e=window.matchMedia(r);this.#o[r]=e,e.addEventListener(`change`,()=>{this.#t[t]===n.value&&this.#c({...this.#t})},{signal:this.#s.signal})}let[i,a]=this.#r[t];return this.#o[r].matches?i:a};#d=()=>{for(let e of this.#a)e({themes:this.#t,resolvedThemes:this.#l()})}};function o(e,t={}){return new a(e,t)}const s=(({key:e,config:t})=>{let n=JSON.parse(localStorage.getItem(e)||`{"themes":{}}`).themes;return Object.entries(t).reduce((e,[t,r])=>{let i=r.options,a=i?.[0],o=n[t]??r.initialValue??(typeof a==`object`?a.value:a);if(e.themes[t]=o,i){let n=i.find(e=>typeof e==`object`&&!!e.media&&e.value===o);if(n){let[r,i,a]=n.media;e.resolvedThemes[t]=matchMedia(r).matches?i:a}else e.resolvedThemes[t]=o}else e.resolvedThemes[t]=o;return e},{themes:{},resolvedThemes:{}})}).toString();function c(t){return`(()=>{var r=${s};[${t.map(({key:t=e,config:n,handler:r})=>`{key:${JSON.stringify(t)},config:${JSON.stringify(n)},h:${r.toString()}}`).join(`,`)}].forEach((c)=>c.h(r(c)))})()`}export{c as createInlineThemeScript,o as createThemeStore,i as getDefaultThemes,r as getThemesAndOptions,t as localStorageAdapter,n as memoryStorageAdapter};
244
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["name","PACKAGE_NAME","type","StorageAdapter","get","key","set","value","broadcast","watch","cb","StorageAdapterCreate","abortController","AbortController","StorageAdapterCreator","options","Options","localStorageAdapter","JSON","parse","window","getItem","setItem","stringify","controller","addEventListener","e","storageArea","newValue","signal","AbortSignal","any","abort","memoryStorageAdapter","storage","Map","channel","BroadcastChannel","postMessage","data","name","PACKAGE_NAME","type","localStorageAdapter","StorageAdapter","StorageAdapterCreate","ThemeValue","ThemeOption","value","T","media","ThemeConfig","options","ReadonlyArray","initialValue","ThemeStoreConfig","Record","KeyedThemeStoreConfig","Themes","K","U","Listener","themes","resolvedThemes","ThemeKeysWithSystemOption","NonSystemOptionValues","SystemOptions","PersistedState","version","Partial","systemOptions","ThemeStoreConstructor","key","config","initialState","storage","ThemeAndOptions","Array","getThemesAndOptions","Object","entries","map","themeKey","themeConfig","option","getDefaultThemes","fromEntries","ThemeStore","defaultThemes","currentThemes","listeners","Set","mediaQueryCache","MediaQueryList","abortController","AbortController","constructor","keyedConfig","hasOwn","String","getThemes","getResolvedThemes","resolveThemes","setThemes","updatedThemes","setThemesAndNotify","stateToPersist","toPersist","set","broadcast","updateSystemOption","ifMatch","ifNotMatch","restore","persistedState","get","subscribe","callback","immediate","add","delete","sync","watch","___destroy","clear","abort","#setThemesAndNotify","notify","#resolveThemes","optionKey","resolveThemeOption","#resolveThemeOption","IIFE","window","document","createElement","console","warn","mediaQuery","mediaQueryList","matchMedia","addEventListener","signal","matches","#notify","listener","Registry","registry","Map","create","params","storeKey","store","ThemeStoreRegistry","error","destroy","createThemeStore","getThemeStore","destroyThemeStore"],"sources":["../package.json","../src/storage.ts","../src/index.ts"],"sourcesContent":["","import { name as PACKAGE_NAME } from '../package.json' with { type: 'json' }\n\nexport type StorageAdapter = {\n\tget: (key: string) => object | null\n\tset: (key: string, value: object) => void\n\t// del: (key: string) => void\n\tbroadcast?: (key: string, value: object) => void\n\twatch?: (cb: (key: string | null, value: object) => void) => () => void\n}\n\nexport type StorageAdapterCreate = ({\n\tabortController,\n}: {\n\tabortController: AbortController\n}) => StorageAdapter\n\nexport type StorageAdapterCreator<Options> = (\n\toptions?: Options,\n) => StorageAdapterCreate\n\nexport const localStorageAdapter: StorageAdapterCreator<{\n\ttype?: 'localStorage' | 'sessionStorage'\n}> = ({ type = 'localStorage' } = {}) => {\n\treturn ({ abortController }) => {\n\t\treturn {\n\t\t\tget: (key: string) => {\n\t\t\t\treturn JSON.parse(window[type].getItem(key) || 'null')\n\t\t\t},\n\n\t\t\tset: (key: string, value: object) => {\n\t\t\t\twindow[type].setItem(key, JSON.stringify(value))\n\t\t\t},\n\n\t\t\t// del: (key: string) => {\n\t\t\t// \twindow[type].removeItem(key)\n\t\t\t// },\n\n\t\t\twatch: (cb) => {\n\t\t\t\tconst controller = new AbortController()\n\n\t\t\t\twindow.addEventListener(\n\t\t\t\t\t'storage',\n\t\t\t\t\t(e) => {\n\t\t\t\t\t\tif (e.storageArea !== window[type]) return\n\n\t\t\t\t\t\tcb(e.key, JSON.parse(e.newValue!))\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tsignal: AbortSignal.any([\n\t\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t\t]),\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcontroller.abort()\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n\nexport const memoryStorageAdapter: StorageAdapterCreator<never> = () => {\n\treturn ({ abortController }) => {\n\t\tconst storage = new Map<string, object>()\n\t\tconst channel = new BroadcastChannel(PACKAGE_NAME)\n\n\t\treturn {\n\t\t\tget: (key: string) => {\n\t\t\t\treturn storage.get(key) || null\n\t\t\t},\n\n\t\t\tset: (key: string, value: object) => {\n\t\t\t\tstorage.set(key, value)\n\t\t\t},\n\n\t\t\t// del: (key: string) => {\n\t\t\t// \tstorage.delete(key)\n\t\t\t// },\n\n\t\t\tbroadcast: (key: string, value: object) => {\n\t\t\t\tchannel.postMessage({ key, value })\n\t\t\t},\n\n\t\t\twatch: (cb) => {\n\t\t\t\tconst controller = new AbortController()\n\n\t\t\t\tchannel.addEventListener(\n\t\t\t\t\t'message',\n\t\t\t\t\t(e) => {\n\t\t\t\t\t\tcb(e.data.key, e.data.value)\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tsignal: AbortSignal.any([\n\t\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t\t]),\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcontroller.abort()\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n","import { name as PACKAGE_NAME } from '../package.json' with { type: 'json' }\nimport {\n\tlocalStorageAdapter,\n\ttype StorageAdapter,\n\ttype StorageAdapterCreate,\n} from './storage'\n\ntype ThemeValue = string | number | boolean\n\ntype ThemeOption<T extends ThemeValue = string> = {\n\tvalue: T\n\tmedia?: [string, T, T]\n}\n\ntype ThemeConfig<T extends ThemeValue = string> =\n\t| {\n\t\t\toptions: ReadonlyArray<T | ThemeOption<T>>\n\t\t\tinitialValue?: T\n\t }\n\t| { initialValue: T; options?: never }\n\nexport type ThemeStoreConfig = Record<\n\tstring,\n\tThemeConfig<string> | ThemeConfig<number> | ThemeConfig<boolean>\n>\n\n// { [themeKey]: { [optionKey]: ThemeOption } }\ntype KeyedThemeStoreConfig<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: Record<string, ThemeOption>\n}\n\nexport type Themes<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: T[K] extends { options: ReadonlyArray<infer U> }\n\t\t? U extends ThemeOption\n\t\t\t? U['value']\n\t\t\t: U\n\t\t: T[K] extends { initialValue: infer U }\n\t\t\t? U extends string\n\t\t\t\t? string\n\t\t\t\t: U extends number\n\t\t\t\t\t? number\n\t\t\t\t\t: boolean\n\t\t\t: never\n}\n\ntype Listener<T extends ThemeStoreConfig> = (value: {\n\tthemes: Themes<T>\n\tresolvedThemes: Themes<T>\n}) => void\n\ntype ThemeKeysWithSystemOption<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: T[K] extends { options: ReadonlyArray<infer U> }\n\t\t? U extends { media: ReadonlyArray<unknown> }\n\t\t\t? K\n\t\t\t: never\n\t\t: never\n}[keyof T]\n\ntype NonSystemOptionValues<\n\tT extends ThemeStoreConfig,\n\tK extends keyof T,\n> = T[K] extends { options: ReadonlyArray<infer U> }\n\t? U extends ThemeValue\n\t\t? U\n\t\t: U extends ThemeOption\n\t\t\t? U extends { media: [string, string, string] }\n\t\t\t\t? never\n\t\t\t\t: U['value']\n\t\t\t: never\n\t: never\n\ntype SystemOptions<T extends ThemeStoreConfig> = {\n\t[K in ThemeKeysWithSystemOption<T>]?: [\n\t\tNonSystemOptionValues<T, K>,\n\t\tNonSystemOptionValues<T, K>,\n\t]\n}\n\ntype PersistedState<T extends ThemeStoreConfig> = {\n\tversion: 1\n\tthemes: Partial<Themes<T>>\n\tsystemOptions: SystemOptions<T>\n}\n\ntype ThemeStoreConstructor<T extends ThemeStoreConfig> = {\n\tkey?: string\n\tconfig: T\n\tinitialState?: Partial<PersistedState<T>>\n\tstorage?: StorageAdapterCreate | null\n}\n\nexport type ThemeAndOptions<T extends ThemeStoreConfig> = Array<\n\t{\n\t\t[K in keyof T]: [\n\t\t\tK,\n\t\t\tArray<\n\t\t\t\tT[K] extends { options: ReadonlyArray<infer U> }\n\t\t\t\t\t? U extends ThemeOption\n\t\t\t\t\t\t? U['value']\n\t\t\t\t\t\t: U\n\t\t\t\t\t: never\n\t\t\t>,\n\t\t]\n\t}[keyof T]\n>\n\nexport function getThemesAndOptions<T extends ThemeStoreConfig>(config: T) {\n\treturn Object.entries(config).map(([themeKey, themeConfig]) => {\n\t\treturn [\n\t\t\tthemeKey,\n\t\t\t(themeConfig.options || []).map((option) =>\n\t\t\t\ttypeof option === 'object' ? option.value : option,\n\t\t\t),\n\t\t]\n\t}) as ThemeAndOptions<T>\n}\n\nexport function getDefaultThemes<T extends ThemeStoreConfig>(config: T) {\n\treturn Object.fromEntries(\n\t\tObject.entries(config).map(([themeKey, themeConfig]) => {\n\t\t\treturn [\n\t\t\t\tthemeKey,\n\t\t\t\tthemeConfig.initialValue ??\n\t\t\t\t\t(typeof themeConfig.options[0] === 'object'\n\t\t\t\t\t\t? themeConfig.options[0].value\n\t\t\t\t\t\t: themeConfig.options[0]),\n\t\t\t]\n\t\t}),\n\t) as Themes<T>\n}\n\nexport class ThemeStore<T extends ThemeStoreConfig> {\n\t#defaultThemes: Themes<T>\n\t#currentThemes: Themes<T>\n\n\t#options: {\n\t\tkey: string\n\t\tconfig: KeyedThemeStoreConfig<T>\n\t}\n\n\t#systemOptions: SystemOptions<T>\n\n\t#storage: StorageAdapter | null\n\n\t#listeners: Set<Listener<T>> = new Set<Listener<T>>()\n\n\t#mediaQueryCache: Record<string, MediaQueryList>\n\n\t#abortController = new AbortController()\n\n\tconstructor({\n\t\tkey = PACKAGE_NAME,\n\t\tconfig,\n\t\tinitialState = {},\n\t\tstorage = localStorageAdapter(),\n\t}: ThemeStoreConstructor<T>) {\n\t\tconst systemOptions: Record<string, [ThemeValue, ThemeValue]> = {\n\t\t\t...initialState.systemOptions,\n\t\t}\n\n\t\tconst keyedConfig = Object.fromEntries(\n\t\t\tObject.entries(config).map(([themeKey, themeConfig]) => {\n\t\t\t\tconst entries = (themeConfig.options || []).map((option) => {\n\t\t\t\t\tif (typeof option === 'object') {\n\t\t\t\t\t\tif (option.media && !Object.hasOwn(systemOptions, themeKey)) {\n\t\t\t\t\t\t\tsystemOptions[themeKey] = [option.media[1], option.media[2]]\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn [String(option.value), option]\n\t\t\t\t\t}\n\n\t\t\t\t\treturn [String(option), { value: option }]\n\t\t\t\t})\n\t\t\t\treturn [themeKey, Object.fromEntries(entries)]\n\t\t\t}),\n\t\t) as KeyedThemeStoreConfig<T>\n\n\t\tthis.#options = {\n\t\t\tkey,\n\t\t\tconfig: keyedConfig,\n\t\t}\n\n\t\tthis.#systemOptions = systemOptions as SystemOptions<T>\n\n\t\tthis.#defaultThemes = getDefaultThemes(config)\n\n\t\tthis.#currentThemes = { ...this.#defaultThemes, ...initialState.themes }\n\n\t\tthis.#storage =\n\t\t\tstorage?.({\n\t\t\t\tabortController: this.#abortController,\n\t\t\t}) ?? null\n\n\t\tthis.#mediaQueryCache = {}\n\t}\n\n\tgetThemes = (): Themes<T> => {\n\t\treturn this.#currentThemes\n\t}\n\n\tgetResolvedThemes = (): Themes<T> => {\n\t\treturn this.#resolveThemes()\n\t}\n\n\tsetThemes = (\n\t\tthemes:\n\t\t\t| Partial<Themes<T>>\n\t\t\t| ((currentThemes: Themes<T>) => Partial<Themes<T>>),\n\t): void => {\n\t\tconst updatedThemes =\n\t\t\ttypeof themes === 'function' ? themes(this.#currentThemes) : themes\n\n\t\tthis.#setThemesAndNotify({ ...this.#currentThemes, ...updatedThemes })\n\n\t\tconst stateToPersist = this.toPersist()\n\n\t\tif (this.#storage) {\n\t\t\tthis.#storage.set(this.#options.key, stateToPersist)\n\t\t\tthis.#storage.broadcast?.(this.#options.key, stateToPersist)\n\t\t}\n\t}\n\n\tupdateSystemOption = <K extends ThemeKeysWithSystemOption<T>>(\n\t\tthemeKey: K,\n\t\t[ifMatch, ifNotMatch]: [\n\t\t\tNonSystemOptionValues<T, K>,\n\t\t\tNonSystemOptionValues<T, K>,\n\t\t],\n\t): void => {\n\t\tthis.#systemOptions[themeKey] = [ifMatch, ifNotMatch]\n\n\t\tthis.setThemes({ ...this.#currentThemes })\n\t}\n\n\ttoPersist = (): PersistedState<T> => {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tthemes: this.#currentThemes,\n\t\t\tsystemOptions: this.#systemOptions,\n\t\t}\n\t}\n\n\trestore = (): void => {\n\t\tlet persistedState = this.#storage?.get(this.#options.key)\n\n\t\tif (!persistedState) {\n\t\t\tthis.#setThemesAndNotify({ ...this.#defaultThemes })\n\t\t\treturn\n\t\t}\n\n\t\t// for backward compatibility\n\t\tif (!Object.hasOwn(persistedState, 'version')) {\n\t\t\tpersistedState = {\n\t\t\t\tversion: 1,\n\t\t\t\tthemes: persistedState,\n\t\t\t\tsystemOptions: this.#systemOptions,\n\t\t\t}\n\t\t}\n\n\t\tthis.#systemOptions = {\n\t\t\t...this.#systemOptions,\n\t\t\t...persistedState.systemOptions,\n\t\t}\n\n\t\tthis.#setThemesAndNotify({\n\t\t\t...this.#defaultThemes,\n\t\t\t...persistedState.themes,\n\t\t})\n\t}\n\n\tsubscribe = (\n\t\tcallback: Listener<T>,\n\t\t{ immediate = false }: { immediate?: boolean } = {},\n\t): (() => void) => {\n\t\tif (immediate) {\n\t\t\tcallback({\n\t\t\t\tthemes: this.#currentThemes,\n\t\t\t\tresolvedThemes: this.#resolveThemes(),\n\t\t\t})\n\t\t}\n\n\t\tthis.#listeners.add(callback)\n\n\t\treturn () => {\n\t\t\tthis.#listeners.delete(callback)\n\t\t}\n\t}\n\n\tsync = (): (() => void) | undefined => {\n\t\tif (!this.#storage?.watch) {\n\t\t\t// if (this.#storage) {\n\t\t\t// \tconsole.warn(\n\t\t\t// \t\t`[${PACKAGE_NAME}] No watch method was provided for storage.`,\n\t\t\t// \t)\n\t\t\t// } else {\n\t\t\t// \tconsole.warn(`[${PACKAGE_NAME}] No storage was provided.`)\n\t\t\t// }\n\n\t\t\treturn\n\t\t}\n\n\t\treturn this.#storage.watch((key, persistedState) => {\n\t\t\tif (key !== this.#options.key) return\n\n\t\t\tthis.#systemOptions = (persistedState as PersistedState<T>).systemOptions\n\n\t\t\tthis.#setThemesAndNotify((persistedState as PersistedState<T>).themes)\n\t\t})\n\t}\n\n\t___destroy = (): void => {\n\t\tthis.#listeners.clear()\n\t\tthis.#abortController.abort()\n\t}\n\n\t#setThemesAndNotify = (themes: Themes<T>): void => {\n\t\tthis.#currentThemes = themes\n\t\tthis.#notify()\n\t}\n\n\t#resolveThemes = (): Themes<T> => {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(this.#currentThemes).map(([themeKey, optionKey]) => {\n\t\t\t\tconst option = this.#options.config[themeKey]?.[optionKey]\n\n\t\t\t\treturn [\n\t\t\t\t\tthemeKey,\n\t\t\t\t\toption ? this.#resolveThemeOption({ themeKey, option }) : optionKey,\n\t\t\t\t]\n\t\t\t}),\n\t\t) as Themes<T>\n\t}\n\n\t#resolveThemeOption = ({\n\t\tthemeKey,\n\t\toption,\n\t}: {\n\t\tthemeKey: string\n\t\toption: ThemeOption\n\t}): string => {\n\t\tif (!option.media) return option.value\n\n\t\tif (!IIFE) {\n\t\t\tif (\n\t\t\t\t!(\n\t\t\t\t\ttypeof window !== 'undefined' &&\n\t\t\t\t\ttypeof window.document !== 'undefined' &&\n\t\t\t\t\ttypeof window.document.createElement !== 'undefined'\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[${PACKAGE_NAME}] Option with key \"media\" cannot be resolved in server environment.`,\n\t\t\t\t)\n\n\t\t\t\treturn option.value\n\t\t\t}\n\t\t}\n\n\t\tconst [mediaQuery] = option.media\n\n\t\tif (!this.#mediaQueryCache[mediaQuery]) {\n\t\t\tconst mediaQueryList = window.matchMedia(mediaQuery)\n\n\t\t\tthis.#mediaQueryCache[mediaQuery] = mediaQueryList\n\n\t\t\tmediaQueryList.addEventListener(\n\t\t\t\t'change',\n\t\t\t\t() => {\n\t\t\t\t\tif (this.#currentThemes[themeKey] === option.value) {\n\t\t\t\t\t\tthis.#setThemesAndNotify({ ...this.#currentThemes })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal: this.#abortController.signal },\n\t\t\t)\n\t\t}\n\n\t\tconst [ifMatch, ifNotMatch] = this.#systemOptions[themeKey]!\n\n\t\treturn this.#mediaQueryCache[mediaQuery].matches ? ifMatch : ifNotMatch\n\t}\n\n\t#notify = (): void => {\n\t\tfor (const listener of this.#listeners) {\n\t\t\tlistener({\n\t\t\t\tthemes: this.#currentThemes,\n\t\t\t\tresolvedThemes: this.#resolveThemes(),\n\t\t\t})\n\t\t}\n\t}\n}\n\nclass Registry {\n\t#registry = new Map<string, ThemeStore<ThemeStoreConfig>>()\n\n\tcreate = <T extends ThemeStoreConfig>(\n\t\tparams: ThemeStoreConstructor<T>,\n\t): ThemeStore<T> => {\n\t\tconst storeKey = params.key || PACKAGE_NAME\n\n\t\tlet store = this.#registry.get(storeKey) as ThemeStore<T>\n\n\t\tif (!store) {\n\t\t\tstore = new ThemeStore<T>(params)\n\n\t\t\tthis.#registry.set(storeKey, store as ThemeStore<ThemeStoreConfig>)\n\t\t}\n\n\t\treturn store\n\t}\n\n\tget = <T extends keyof ThemeStoreRegistry>(\n\t\tkey?: T,\n\t): ThemeStoreRegistry[T] | undefined => {\n\t\tconst storeKey = key || PACKAGE_NAME\n\n\t\tconst store = this.#registry.get(storeKey)\n\n\t\tif (!store) {\n\t\t\tif (IIFE) {\n\t\t\t\tconsole.error(`Theme store '${storeKey}' not found.`)\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[${PACKAGE_NAME}] Theme store with key '${storeKey}' could not be found. Please run \\`createThemeStore\\` with key '${storeKey}' first.`,\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treturn store as ThemeStoreRegistry[T]\n\t}\n\n\tdestroy = <T extends keyof ThemeStoreRegistry>(key?: T) => {\n\t\tconst storeKey = key || PACKAGE_NAME\n\n\t\tconst store = this.#registry.get(storeKey)\n\n\t\tif (!store) return\n\n\t\tstore.___destroy()\n\n\t\tthis.#registry.delete(storeKey)\n\t}\n}\n\nconst registry = new Registry()\n\nexport const createThemeStore = registry.create\nexport const getThemeStore = registry.get\nexport const destroyThemeStore = registry.destroy\n\nexport * from './storage'\n\nexport interface ThemeStoreRegistry {}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["name","PACKAGE_NAME","type","StorageAdapter","get","set","value","broadcast","watch","cb","StorageAdapterCreate","abortController","AbortController","StorageAdapterCreator","options","Options","localStorageAdapter","key","JSON","parse","window","getItem","setItem","stringify","controller","addEventListener","e","storageArea","newValue","signal","AbortSignal","any","abort","memoryStorageAdapter","storage","Map","channel","BroadcastChannel","postMessage","data","name","PACKAGE_NAME","type","localStorageAdapter","StorageAdapter","StorageAdapterCreate","ThemeValue","ThemeOption","value","T","media","ThemeConfig","options","ReadonlyArray","initialValue","ThemeStoreConfig","Record","KeyedThemeStoreConfig","Themes","K","U","Listener","themes","resolvedThemes","ThemeKeysWithSystemOption","NonSystemOptionValues","SystemOptions","PersistedState","Partial","systemOptions","ThemeStoreOptions","initialState","storage","ThemeAndOptions","Array","getThemesAndOptions","config","Object","entries","map","themeKey","themeConfig","option","getDefaultThemes","fromEntries","ThemeStore","defaultThemes","currentThemes","keyedConfig","listeners","Set","mediaQueryCache","MediaQueryList","abortController","AbortController","constructor","key","hasOwn","String","getThemes","getResolvedThemes","resolveThemes","setThemes","updatedThemes","setThemesAndNotify","stateToPersist","toPersist","set","broadcast","updateSystemOption","ifMatch","ifNotMatch","restore","persistedState","get","subscribe","callback","add","delete","sync","watch","destroy","clear","abort","#setThemesAndNotify","notify","#resolveThemes","optionKey","resolveThemeOption","#resolveThemeOption","window","document","createElement","console","warn","mediaQuery","mediaQueryList","matchMedia","addEventListener","signal","matches","#notify","listener","createThemeStore","restoreThemesString","persistedThemes","JSON","parse","localStorage","getItem","reduce","acc","firstOption","mediaOption","find","Required","toString","ThemeScriptParameter","handler","createInlineThemeScript","themeScriptParameters","serializedThemeScriptParameters","stringify","join"],"sources":["../../package.json","../src/storage.ts","../src/index.ts"],"sourcesContent":["","import { name as PACKAGE_NAME } from '../../package.json' with { type: 'json' }\n\n/**\n * Pluggable persistence for `createThemeStore`.\n */\nexport type StorageAdapter = {\n\tget: () => object | null\n\tset: (value: object) => void\n\t/** Optional: notify other tabs/contexts after local `set` operation. */\n\tbroadcast?: (value: object) => void\n\t/** Optional: subscribes to other tabs/contexts' updates, returns `unsubscribe` function. */\n\twatch?: (cb: (value: object) => void) => () => void\n}\n\nexport type StorageAdapterCreate = ({\n\tabortController,\n}: {\n\tabortController: AbortController\n}) => StorageAdapter\n\nexport type StorageAdapterCreator<Options> = (\n\toptions: Options,\n) => StorageAdapterCreate\n\n/**\n * Persists theme store in `localStorage` or `sessionStorage`.\n * @example\n * ```ts\n * import { createThemeStore, localStorageAdapter } from 'resonare'\n *\n * const store = createThemeStore(\n * { mode: { options: ['light', 'dark'] },\n * { storage: localStorageAdapter({ key: 'app', type: 'localStorage' }) },\n * )\n * ```\n */\nexport const localStorageAdapter: StorageAdapterCreator<{\n\tkey: string\n\ttype?: 'localStorage' | 'sessionStorage'\n}> = ({ key, type = 'localStorage' }) => {\n\treturn ({ abortController }) => {\n\t\treturn {\n\t\t\tget: () => {\n\t\t\t\treturn JSON.parse(window[type].getItem(key) || 'null')\n\t\t\t},\n\n\t\t\tset: (value: object) => {\n\t\t\t\twindow[type].setItem(key, JSON.stringify(value))\n\t\t\t},\n\n\t\t\twatch: (cb) => {\n\t\t\t\tconst controller = new AbortController()\n\n\t\t\t\twindow.addEventListener(\n\t\t\t\t\t'storage',\n\t\t\t\t\t(e) => {\n\t\t\t\t\t\tif (e.storageArea !== window[type]) return\n\n\t\t\t\t\t\tif (e.key !== key) return\n\n\t\t\t\t\t\tcb(JSON.parse(e.newValue!))\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tsignal: AbortSignal.any([\n\t\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t\t]),\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcontroller.abort()\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n\n/**\n * In-memory persistence and sync via `BroadcastChannel`.\n * Useful with server-side persistence.\n * @example\n * ```ts\n * import { createThemeStore, memoryStorageAdapter } from 'resonare'\n *\n * const store = createThemeStore(\n * { mode: { options: ['light', 'dark'] } },\n * { storage: memoryStorageAdapter({ key: 'preview' }) },\n * )\n * ```\n */\nexport const memoryStorageAdapter: StorageAdapterCreator<{\n\tkey: string\n}> = ({ key }) => {\n\treturn ({ abortController }) => {\n\t\tconst storage = new Map<string, object>()\n\t\tconst channel = new BroadcastChannel(PACKAGE_NAME)\n\n\t\treturn {\n\t\t\tget: () => {\n\t\t\t\treturn storage.get(key) || null\n\t\t\t},\n\n\t\t\tset: (value: object) => {\n\t\t\t\tstorage.set(key, value)\n\t\t\t},\n\n\t\t\tbroadcast: (value: object) => {\n\t\t\t\tchannel.postMessage({ key, value })\n\t\t\t},\n\n\t\t\twatch: (cb) => {\n\t\t\t\tconst controller = new AbortController()\n\n\t\t\t\tchannel.addEventListener(\n\t\t\t\t\t'message',\n\t\t\t\t\t(e) => {\n\t\t\t\t\t\tif (e.data.key !== key) return\n\n\t\t\t\t\t\tcb(e.data.value)\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tsignal: AbortSignal.any([\n\t\t\t\t\t\t\tabortController.signal,\n\t\t\t\t\t\t\tcontroller.signal,\n\t\t\t\t\t\t]),\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcontroller.abort()\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n","import { name as PACKAGE_NAME } from '../../package.json' with { type: 'json' }\nimport {\n\tlocalStorageAdapter,\n\ttype StorageAdapter,\n\ttype StorageAdapterCreate,\n} from './storage'\n\nexport * from './storage'\n\ntype ThemeValue = string | number | boolean\n\ntype ThemeOption<T extends ThemeValue = string> = {\n\tvalue: T\n\tmedia?: [string, T, T]\n}\n\ntype ThemeConfig<T extends ThemeValue = string> =\n\t| {\n\t\t\toptions: ReadonlyArray<T | ThemeOption<T>>\n\t\t\tinitialValue?: T\n\t }\n\t| { initialValue: T; options?: never }\n\nexport type ThemeStoreConfig = Record<\n\tstring,\n\tThemeConfig<string> | ThemeConfig<number> | ThemeConfig<boolean>\n>\n\n// { [themeKey]: { [optionKey]: ThemeOption } }\ntype KeyedThemeStoreConfig<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: Record<string, ThemeOption>\n}\n\nexport type Themes<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: T[K] extends { options: ReadonlyArray<infer U> }\n\t\t? U extends ThemeOption\n\t\t\t? U['value']\n\t\t\t: U\n\t\t: T[K] extends { initialValue: infer U }\n\t\t\t? U extends string\n\t\t\t\t? string\n\t\t\t\t: U extends number\n\t\t\t\t\t? number\n\t\t\t\t\t: boolean\n\t\t\t: never\n}\n\ntype Listener<T extends ThemeStoreConfig> = (value: {\n\tthemes: Themes<T>\n\tresolvedThemes: Themes<T>\n}) => void\n\ntype ThemeKeysWithSystemOption<T extends ThemeStoreConfig> = {\n\t[K in keyof T]: T[K] extends { options: ReadonlyArray<infer U> }\n\t\t? U extends { media: ReadonlyArray<unknown> }\n\t\t\t? K\n\t\t\t: never\n\t\t: never\n}[keyof T]\n\ntype NonSystemOptionValues<\n\tT extends ThemeStoreConfig,\n\tK extends keyof T,\n> = T[K] extends { options: ReadonlyArray<infer U> }\n\t? U extends ThemeValue\n\t\t? U\n\t\t: U extends ThemeOption\n\t\t\t? U extends { media: [string, string, string] }\n\t\t\t\t? never\n\t\t\t\t: U['value']\n\t\t\t: never\n\t: never\n\ntype SystemOptions<T extends ThemeStoreConfig> = {\n\t[K in ThemeKeysWithSystemOption<T>]?: [\n\t\tNonSystemOptionValues<T, K>,\n\t\tNonSystemOptionValues<T, K>,\n\t]\n}\n\ntype PersistedState<T extends ThemeStoreConfig> = {\n\tthemes: Partial<Themes<T>>\n\tsystemOptions: SystemOptions<T>\n}\n\ntype ThemeStoreOptions<T extends ThemeStoreConfig> = {\n\tinitialState?: Partial<PersistedState<T>>\n\tstorage?: StorageAdapterCreate | null\n}\n\nexport type ThemeAndOptions<T extends ThemeStoreConfig> = Array<\n\t{\n\t\t[K in keyof T]: [\n\t\t\tK,\n\t\t\tArray<\n\t\t\t\tT[K] extends { options: ReadonlyArray<infer U> }\n\t\t\t\t\t? U extends ThemeOption\n\t\t\t\t\t\t? U['value']\n\t\t\t\t\t\t: U\n\t\t\t\t\t: never\n\t\t\t>,\n\t\t]\n\t}[keyof T]\n>\n\nexport function getThemesAndOptions<T extends ThemeStoreConfig>(config: T) {\n\treturn Object.entries(config).map(([themeKey, themeConfig]) => {\n\t\treturn [\n\t\t\tthemeKey,\n\t\t\t(themeConfig.options || []).map((option) =>\n\t\t\t\ttypeof option === 'object' ? option.value : option,\n\t\t\t),\n\t\t]\n\t}) as ThemeAndOptions<T>\n}\n\nexport function getDefaultThemes<T extends ThemeStoreConfig>(config: T) {\n\treturn Object.fromEntries(\n\t\tObject.entries(config).map(([themeKey, themeConfig]) => {\n\t\t\treturn [\n\t\t\t\tthemeKey,\n\t\t\t\tthemeConfig.initialValue ??\n\t\t\t\t\t(typeof themeConfig.options[0] === 'object'\n\t\t\t\t\t\t? themeConfig.options[0].value\n\t\t\t\t\t\t: themeConfig.options[0]),\n\t\t\t]\n\t\t}),\n\t) as Themes<T>\n}\n\nclass ThemeStore<T extends ThemeStoreConfig> {\n\t#defaultThemes: Themes<T>\n\t#currentThemes: Themes<T>\n\n\t#keyedConfig: KeyedThemeStoreConfig<T>\n\n\t#systemOptions: SystemOptions<T>\n\n\t#storage: StorageAdapter | null\n\n\t#listeners: Set<Listener<T>> = new Set<Listener<T>>()\n\n\t#mediaQueryCache: Record<string, MediaQueryList>\n\n\t#abortController = new AbortController()\n\n\tconstructor(\n\t\tconfig: T,\n\t\t{\n\t\t\tinitialState = {},\n\t\t\tstorage = localStorageAdapter({ key: PACKAGE_NAME }),\n\t\t}: ThemeStoreOptions<T> = {},\n\t) {\n\t\tconst systemOptions: Record<string, [ThemeValue, ThemeValue]> = {\n\t\t\t...initialState.systemOptions,\n\t\t}\n\n\t\tconst keyedConfig = Object.fromEntries(\n\t\t\tObject.entries(config).map(([themeKey, themeConfig]) => {\n\t\t\t\tconst entries = (themeConfig.options || []).map((option) => {\n\t\t\t\t\tif (typeof option === 'object') {\n\t\t\t\t\t\tif (option.media && !Object.hasOwn(systemOptions, themeKey)) {\n\t\t\t\t\t\t\tsystemOptions[themeKey] = [option.media[1], option.media[2]]\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn [String(option.value), option]\n\t\t\t\t\t}\n\n\t\t\t\t\treturn [String(option), { value: option }]\n\t\t\t\t})\n\t\t\t\treturn [themeKey, Object.fromEntries(entries)]\n\t\t\t}),\n\t\t) as KeyedThemeStoreConfig<T>\n\n\t\tthis.#keyedConfig = keyedConfig\n\n\t\tthis.#systemOptions = systemOptions as SystemOptions<T>\n\n\t\tthis.#defaultThemes = getDefaultThemes(config)\n\n\t\tthis.#currentThemes = { ...this.#defaultThemes, ...initialState.themes }\n\n\t\tthis.#storage =\n\t\t\tstorage?.({\n\t\t\t\tabortController: this.#abortController,\n\t\t\t}) ?? null\n\n\t\tthis.#mediaQueryCache = {}\n\t}\n\n\tgetThemes = (): Themes<T> => {\n\t\treturn this.#currentThemes\n\t}\n\n\tgetResolvedThemes = (): Themes<T> => {\n\t\treturn this.#resolveThemes()\n\t}\n\n\tsetThemes = (\n\t\tthemes:\n\t\t\t| Partial<Themes<T>>\n\t\t\t| ((currentThemes: Themes<T>) => Partial<Themes<T>>),\n\t): void => {\n\t\tconst updatedThemes =\n\t\t\ttypeof themes === 'function' ? themes(this.#currentThemes) : themes\n\n\t\tthis.#setThemesAndNotify({ ...this.#currentThemes, ...updatedThemes })\n\n\t\tconst stateToPersist = this.toPersist()\n\n\t\tif (this.#storage) {\n\t\t\tthis.#storage.set(stateToPersist)\n\t\t\tthis.#storage.broadcast?.(stateToPersist)\n\t\t}\n\t}\n\n\tupdateSystemOption = <K extends ThemeKeysWithSystemOption<T>>(\n\t\tthemeKey: K,\n\t\t[ifMatch, ifNotMatch]: [\n\t\t\tNonSystemOptionValues<T, K>,\n\t\t\tNonSystemOptionValues<T, K>,\n\t\t],\n\t): void => {\n\t\tthis.#systemOptions[themeKey] = [ifMatch, ifNotMatch]\n\n\t\tthis.setThemes({ ...this.#currentThemes })\n\t}\n\n\ttoPersist = (): PersistedState<T> => {\n\t\treturn {\n\t\t\tthemes: this.#currentThemes,\n\t\t\tsystemOptions: this.#systemOptions,\n\t\t}\n\t}\n\n\trestore = (): void => {\n\t\tconst persistedState = this.#storage?.get()\n\n\t\tif (!persistedState) {\n\t\t\tthis.#setThemesAndNotify({ ...this.#defaultThemes })\n\t\t\treturn\n\t\t}\n\n\t\tthis.#systemOptions = {\n\t\t\t...this.#systemOptions,\n\t\t\t...persistedState.systemOptions,\n\t\t}\n\n\t\tthis.#setThemesAndNotify({\n\t\t\t...this.#defaultThemes,\n\t\t\t...persistedState.themes,\n\t\t})\n\t}\n\n\tsubscribe = (callback: Listener<T>): (() => void) => {\n\t\tthis.#listeners.add(callback)\n\n\t\treturn () => {\n\t\t\tthis.#listeners.delete(callback)\n\t\t}\n\t}\n\n\tsync = (): (() => void) | undefined => {\n\t\tif (!this.#storage?.watch) return\n\n\t\treturn this.#storage.watch((persistedState) => {\n\t\t\tthis.#systemOptions = (persistedState as PersistedState<T>).systemOptions\n\n\t\t\tthis.#setThemesAndNotify((persistedState as PersistedState<T>).themes)\n\t\t})\n\t}\n\n\t/** Clears subscribers and aborts media-query listeners tied to this store instance. */\n\tdestroy = (): void => {\n\t\tthis.#listeners.clear()\n\t\tthis.#abortController.abort()\n\t}\n\n\t#setThemesAndNotify = (themes: Themes<T>): void => {\n\t\tthis.#currentThemes = themes\n\t\tthis.#notify()\n\t}\n\n\t#resolveThemes = (): Themes<T> => {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(this.#currentThemes).map(([themeKey, optionKey]) => {\n\t\t\t\tconst option = this.#keyedConfig[themeKey]?.[optionKey]\n\n\t\t\t\treturn [\n\t\t\t\t\tthemeKey,\n\t\t\t\t\toption ? this.#resolveThemeOption({ themeKey, option }) : optionKey,\n\t\t\t\t]\n\t\t\t}),\n\t\t) as Themes<T>\n\t}\n\n\t#resolveThemeOption = ({\n\t\tthemeKey,\n\t\toption,\n\t}: {\n\t\tthemeKey: string\n\t\toption: ThemeOption\n\t}): string => {\n\t\tif (!option.media) return option.value\n\n\t\tif (\n\t\t\t!(\n\t\t\t\ttypeof window !== 'undefined' &&\n\t\t\t\ttypeof window.document !== 'undefined' &&\n\t\t\t\ttypeof window.document.createElement !== 'undefined'\n\t\t\t)\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t`[${PACKAGE_NAME}] Option with key \"media\" cannot be resolved in server environment.`,\n\t\t\t)\n\n\t\t\treturn option.value\n\t\t}\n\n\t\tconst [mediaQuery] = option.media\n\n\t\tif (!this.#mediaQueryCache[mediaQuery]) {\n\t\t\tconst mediaQueryList = window.matchMedia(mediaQuery)\n\n\t\t\tthis.#mediaQueryCache[mediaQuery] = mediaQueryList\n\n\t\t\tmediaQueryList.addEventListener(\n\t\t\t\t'change',\n\t\t\t\t() => {\n\t\t\t\t\tif (this.#currentThemes[themeKey] === option.value) {\n\t\t\t\t\t\tthis.#setThemesAndNotify({ ...this.#currentThemes })\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal: this.#abortController.signal },\n\t\t\t)\n\t\t}\n\n\t\tconst [ifMatch, ifNotMatch] = this.#systemOptions[themeKey]!\n\n\t\treturn this.#mediaQueryCache[mediaQuery].matches ? ifMatch : ifNotMatch\n\t}\n\n\t#notify = (): void => {\n\t\tfor (const listener of this.#listeners) {\n\t\t\tlistener({\n\t\t\t\tthemes: this.#currentThemes,\n\t\t\t\tresolvedThemes: this.#resolveThemes(),\n\t\t\t})\n\t\t}\n\t}\n}\n\nexport type { ThemeStore }\n\nexport function createThemeStore<T extends ThemeStoreConfig>(\n\tconfig: T,\n\toptions: ThemeStoreOptions<T> = {},\n): ThemeStore<T> {\n\treturn new ThemeStore<T>(config, options)\n}\n\nconst restoreThemesString = (({\n\tkey,\n\tconfig,\n}: {\n\tkey: string\n\tconfig: ThemeStoreConfig\n}) => {\n\tconst persistedThemes = JSON.parse(\n\t\tlocalStorage.getItem(key) || '{\"themes\":{}}',\n\t).themes\n\n\treturn Object.entries(config).reduce<{\n\t\tthemes: Record<string, ThemeValue>\n\t\tresolvedThemes: Record<string, ThemeValue>\n\t}>(\n\t\t(acc, [themeKey, themeConfig]) => {\n\t\t\tconst options = themeConfig.options\n\n\t\t\tconst firstOption = options?.[0]\n\n\t\t\tconst initialValue =\n\t\t\t\tpersistedThemes[themeKey] ??\n\t\t\t\tthemeConfig.initialValue ??\n\t\t\t\t(typeof firstOption === 'object' ? firstOption.value : firstOption!)\n\n\t\t\tacc.themes[themeKey] = initialValue\n\n\t\t\tif (options) {\n\t\t\t\tconst mediaOption = options.find(\n\t\t\t\t\t(option): option is Required<ThemeOption> =>\n\t\t\t\t\t\ttypeof option === 'object' &&\n\t\t\t\t\t\t!!option.media &&\n\t\t\t\t\t\toption.value === initialValue,\n\t\t\t\t)\n\n\t\t\t\tif (mediaOption) {\n\t\t\t\t\tconst [mediaQuery, ifMatch, ifNotMatch] = mediaOption.media\n\n\t\t\t\t\tacc.resolvedThemes[themeKey] = matchMedia(mediaQuery).matches\n\t\t\t\t\t\t? ifMatch\n\t\t\t\t\t\t: ifNotMatch\n\t\t\t\t} else {\n\t\t\t\t\tacc.resolvedThemes[themeKey] = initialValue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tacc.resolvedThemes[themeKey] = initialValue\n\t\t\t}\n\n\t\t\treturn acc\n\t\t},\n\t\t{\n\t\t\tthemes: {},\n\t\t\tresolvedThemes: {},\n\t\t},\n\t)\n}).toString()\n\nexport type ThemeScriptParameter = {\n\t/** `localStorage` key; defaults to the 'resonare'. */\n\tkey?: string\n\tconfig: ThemeStoreConfig\n\thandler: Listener<ThemeStoreConfig>\n}\n\n/**\n * Creates an IIFE script string that reads persisted themes from `localStorage` and runs your handlers immediately.\n *\n * Useful for avoiding flash of incorrect styles.\n * @example\n * ```tsx\n * import { createInlineThemeScript } from 'resonare'\n *\n * const inlineScript = createInlineThemeScript([\n * {\n * key: 'my-app',\n * config: { options: ['light', 'dark'] },\n * handler: ({ resolvedThemes }) => {\n * document.documentElement.dataset.mode = String(resolvedThemes.mode)\n * },\n * },\n * ])\n *\n * <script>{inlineScript}</script>\n * ```\n */\nexport function createInlineThemeScript(\n\tthemeScriptParameters: Array<ThemeScriptParameter>,\n) {\n\tconst serializedThemeScriptParameters = themeScriptParameters.map(\n\t\t({ key = PACKAGE_NAME, config, handler }) =>\n\t\t\t`{key:${JSON.stringify(key)},config:${JSON.stringify(config)},h:${handler.toString()}}`,\n\t)\n\n\treturn `(()=>{var r=${restoreThemesString};[${serializedThemeScriptParameters.join(',')}].forEach((c)=>c.h(r(c)))})()`\n}\n"],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -1,35 +1,29 @@
1
1
  import { ThemeStore, ThemeStoreConfig, Themes } from "./index.js";
2
2
 
3
3
  //#region src/react.d.ts
4
- declare function useResonare<T extends ThemeStoreConfig>(getStore: () => ThemeStore<T>, {
5
- initOnMount
6
- }?: {
7
- initOnMount?: boolean | undefined;
8
- }): {
4
+ /**
5
+ * Subscribes a React component to a theme store.
6
+ * @example
7
+ * ```tsx
8
+ * export function ThemeToggle() {
9
+ * const { themes, resolvedThemes, setThemes } = useResonare(store)
10
+ *
11
+ * // ...
12
+ * }
13
+ * ```
14
+ */
15
+ declare function useResonare<T extends ThemeStoreConfig>(store: ThemeStore<T>): {
9
16
  themes: Themes<T>;
10
17
  resolvedThemes: Themes<T>;
18
+ destroy: () => void;
19
+ restore: () => void;
11
20
  setThemes: (themes: Partial<Themes<T>> | ((currentThemes: Themes<T>) => Partial<Themes<T>>)) => void;
12
- updateSystemOption: <K extends { [K_1 in keyof T]: T[K_1] extends {
13
- options: ReadonlyArray<infer U>;
14
- } ? U extends {
15
- media: ReadonlyArray<unknown>;
16
- } ? K_1 : never : never }[keyof T]>(themeKey: K, [ifMatch, ifNotMatch]: [T[K] extends {
17
- options: ReadonlyArray<infer U>;
18
- } ? U extends string | number | boolean ? U : U extends {
19
- value: string;
20
- media?: [string, string, string];
21
- } ? U extends {
22
- media: [string, string, string];
23
- } ? never : U["value"] : never : never, T[K] extends {
24
- options: ReadonlyArray<infer U>;
25
- } ? U extends string | number | boolean ? U : U extends {
26
- value: string;
27
- media?: [string, string, string];
28
- } ? U extends {
29
- media: [string, string, string];
30
- } ? never : U["value"] : never : never]) => void;
21
+ subscribe: (callback: (value: {
22
+ themes: Themes<T>;
23
+ resolvedThemes: Themes<T>;
24
+ }) => void) => (() => void);
25
+ sync: () => (() => void) | undefined;
31
26
  toPersist: () => {
32
- version: 1;
33
27
  themes: Partial<Themes<T>>;
34
28
  systemOptions: { [K_1 in { [K in keyof T]: T[K] extends {
35
29
  options: ReadonlyArray<infer U>;
@@ -51,16 +45,25 @@ declare function useResonare<T extends ThemeStoreConfig>(getStore: () => ThemeSt
51
45
  media: [string, string, string];
52
46
  } ? never : U["value"] : never : never] };
53
47
  };
54
- restore: () => void;
55
- sync: () => (() => void) | undefined;
56
- subscribe: (callback: (value: {
57
- themes: Themes<T>;
58
- resolvedThemes: Themes<T>;
59
- }) => void, {
60
- immediate
61
- }?: {
62
- immediate?: boolean;
63
- }) => (() => void);
48
+ updateSystemOption: <K extends { [K_1 in keyof T]: T[K_1] extends {
49
+ options: ReadonlyArray<infer U>;
50
+ } ? U extends {
51
+ media: ReadonlyArray<unknown>;
52
+ } ? K_1 : never : never }[keyof T]>(themeKey: K, [ifMatch, ifNotMatch]: [T[K] extends {
53
+ options: ReadonlyArray<infer U>;
54
+ } ? U extends string | number | boolean ? U : U extends {
55
+ value: string;
56
+ media?: [string, string, string];
57
+ } ? U extends {
58
+ media: [string, string, string];
59
+ } ? never : U["value"] : never : never, T[K] extends {
60
+ options: ReadonlyArray<infer U>;
61
+ } ? U extends string | number | boolean ? U : U extends {
62
+ value: string;
63
+ media?: [string, string, string];
64
+ } ? U extends {
65
+ media: [string, string, string];
66
+ } ? never : U["value"] : never : never]) => void;
64
67
  };
65
68
  //#endregion
66
69
  export { useResonare };
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","names":[],"sources":["../src/react.ts"],"mappings":";;;iBAkBgB,WAAA,WAAsB,gBAAA,CAAA,CACrC,QAAA,QAAgB,UAAA,CAAW,CAAA;EACzB;AAAA"}
1
+ {"version":3,"file":"react.d.ts","names":[],"sources":["../src/react.ts"],"mappings":";;;;;AAcA;;;;;;;;;iBAAgB,WAAA,WAAsB,gBAAA,CAAA,CAAkB,KAAA,EAAO,UAAA,CAAW,CAAA"}
package/dist/react.js CHANGED
@@ -1,71 +1,2 @@
1
- import { c } from "react-compiler-runtime";
2
- import * as React from "react";
3
- //#region src/react.ts
4
- function noop() {}
5
- const emptyObject = {};
6
- const emptyStore = {
7
- getThemes: () => emptyObject,
8
- getResolvedThemes: () => emptyObject,
9
- setThemes: noop,
10
- updateSystemOption: noop,
11
- toPersist: noop,
12
- restore: noop,
13
- sync: noop,
14
- subscribe: () => noop
15
- };
16
- function useResonare(getStore, t0) {
17
- const $ = c(13);
18
- let t1;
19
- if ($[0] !== t0) {
20
- t1 = t0 === void 0 ? {} : t0;
21
- $[0] = t0;
22
- $[1] = t1;
23
- } else t1 = $[1];
24
- const { initOnMount: t2 } = t1;
25
- const initOnMount = t2 === void 0 ? false : t2;
26
- const [isMounted, setIsMounted] = React.useState(initOnMount);
27
- let t3;
28
- let t4;
29
- if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
30
- t3 = () => {
31
- setIsMounted(true);
32
- };
33
- t4 = [];
34
- $[2] = t3;
35
- $[3] = t4;
36
- } else {
37
- t3 = $[2];
38
- t4 = $[3];
39
- }
40
- React.useEffect(t3, t4);
41
- const { getThemes, getResolvedThemes, setThemes, updateSystemOption, toPersist, restore, sync, subscribe } = isMounted ? getStore() : emptyStore;
42
- const themes = React.useSyncExternalStore(subscribe, getThemes, getThemes);
43
- const t5 = getResolvedThemes();
44
- let t6;
45
- if ($[4] !== restore || $[5] !== setThemes || $[6] !== subscribe || $[7] !== sync || $[8] !== t5 || $[9] !== themes || $[10] !== toPersist || $[11] !== updateSystemOption) {
46
- t6 = {
47
- themes,
48
- resolvedThemes: t5,
49
- setThemes,
50
- updateSystemOption,
51
- toPersist,
52
- restore,
53
- sync,
54
- subscribe
55
- };
56
- $[4] = restore;
57
- $[5] = setThemes;
58
- $[6] = subscribe;
59
- $[7] = sync;
60
- $[8] = t5;
61
- $[9] = themes;
62
- $[10] = toPersist;
63
- $[11] = updateSystemOption;
64
- $[12] = t6;
65
- } else t6 = $[12];
66
- return t6;
67
- }
68
- //#endregion
69
- export { useResonare };
70
-
1
+ import{c as e}from"react-compiler-runtime";import*as t from"react";function n(n){let r=e(12),{destroy:i,getResolvedThemes:a,getThemes:o,restore:s,setThemes:c,subscribe:l,sync:u,toPersist:d,updateSystemOption:f}=n,p=t.useSyncExternalStore(l,o,o),m;r[0]===a?m=r[1]:(m=a(),r[0]=a,r[1]=m);let h;return r[2]!==i||r[3]!==s||r[4]!==c||r[5]!==l||r[6]!==u||r[7]!==m||r[8]!==p||r[9]!==d||r[10]!==f?(h={themes:p,resolvedThemes:m,destroy:i,restore:s,setThemes:c,subscribe:l,sync:u,toPersist:d,updateSystemOption:f},r[2]=i,r[3]=s,r[4]=c,r[5]=l,r[6]=u,r[7]=m,r[8]=p,r[9]=d,r[10]=f,r[11]=h):h=r[11],h}export{n as useResonare};
71
2
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":["React","ThemeStore","ThemeStoreConfig","noop","emptyObject","emptyStore","getThemes","getResolvedThemes","setThemes","updateSystemOption","toPersist","restore","sync","subscribe","useResonare","getStore","t0","$","_c","t1","undefined","initOnMount","t2","isMounted","setIsMounted","useState","t3","t4","Symbol","for","useEffect","T","themes","useSyncExternalStore","t5","t6","resolvedThemes"],"sources":["../src/react.ts"],"sourcesContent":["import * as React from 'react'\nimport type { ThemeStore, ThemeStoreConfig } from '.'\n\nfunction noop() {}\nconst emptyObject = {}\n\nconst emptyStore = {\n\tgetThemes: () => emptyObject,\n\tgetResolvedThemes: () => emptyObject,\n\tsetThemes: noop,\n\tupdateSystemOption: noop,\n\ttoPersist: noop,\n\trestore: noop,\n\tsync: noop,\n\t// clear: noop,\n\tsubscribe: () => noop,\n}\n\nexport function useResonare<T extends ThemeStoreConfig>(\n\tgetStore: () => ThemeStore<T>,\n\t{ initOnMount = false } = {},\n) {\n\tconst [isMounted, setIsMounted] = React.useState(initOnMount)\n\n\tReact.useEffect(() => {\n\t\tsetIsMounted(true)\n\t}, [])\n\n\tconst {\n\t\tgetThemes,\n\t\tgetResolvedThemes,\n\t\tsetThemes,\n\t\tupdateSystemOption,\n\t\ttoPersist,\n\t\trestore,\n\t\tsync,\n\t\t// clear,\n\t\tsubscribe,\n\t} = isMounted ? getStore() : (emptyStore as unknown as ThemeStore<T>)\n\n\tconst themes = React.useSyncExternalStore(subscribe, getThemes, getThemes)\n\n\treturn {\n\t\tthemes,\n\t\tresolvedThemes: getResolvedThemes(),\n\t\tsetThemes,\n\t\tupdateSystemOption,\n\t\ttoPersist,\n\t\trestore,\n\t\tsync,\n\t\t// clear,\n\t\tsubscribe,\n\t}\n}\n"],"mappings":";;;AAGA,SAASG,OAAO;AAChB,MAAMC,cAAc,EAAE;AAEtB,MAAMC,aAAa;CAClBC,iBAAiBF;CACjBG,yBAAyBH;CACzBI,WAAWL;CACXM,oBAAoBN;CACpBO,WAAWP;CACXQ,SAASR;CACTS,MAAMT;CAENU,iBAAiBV;CACjB;AAED,SAAOW,YAAAC,UAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,GAAA;CAAA,IAAAC;AAAA,KAAAF,EAAA,OAAAD,IAAA;AAENG,OAAAH,OAAAI,KAAAA,IAAA,EAA4B,GAA5BJ;AAA4BC,IAAA,KAAAD;AAAAC,IAAA,KAAAE;OAAAA,MAAAF,EAAA;CAA5B,MAAA,EAAAI,aAAAC,OAAAH;CAAE,MAAAE,cAAAC,OAAAF,KAAAA,IAAA,QAAAE;CAEF,MAAA,CAAAC,WAAAC,gBAAkCxB,MAAKyB,SAAUJ,YAAY;CAAA,IAAAK;CAAA,IAAAC;AAAA,KAAAV,EAAA,OAAAW,OAAAC,IAAA,4BAAA,EAAA;AAE7CH,aAAA;AACfF,gBAAa,KAAK;;AAChBG,OAAA,EAAE;AAAAV,IAAA,KAAAS;AAAAT,IAAA,KAAAU;QAAA;AAAAD,OAAAT,EAAA;AAAAU,OAAAV,EAAA;;AAFLjB,OAAK8B,UAAWJ,IAEbC,GAAG;CAEN,MAAA,EAAArB,WAAAC,mBAAAC,WAAAC,oBAAAC,WAAAC,SAAAC,MAAAC,cAUIU,YAAYR,UAAqD,GAAvCV;CAE9B,MAAA2B,SAAehC,MAAKiC,qBAAsBpB,WAAWP,WAAWA,UAAU;CAIzD,MAAA4B,KAAA3B,mBAAmB;CAAA,IAAA4B;AAAA,KAAAlB,EAAA,OAAAN,WAAAM,EAAA,OAAAT,aAAAS,EAAA,OAAAJ,aAAAI,EAAA,OAAAL,QAAAK,EAAA,OAAAiB,MAAAjB,EAAA,OAAAe,UAAAf,EAAA,QAAAP,aAAAO,EAAA,QAAAR,oBAAA;AAF7B0B,OAAA;GAAAH;GAAAI,gBAEUF;GAAmB1B;GAAAC;GAAAC;GAAAC;GAAAC;GAAAC;GAQnC;AAAAI,IAAA,KAAAN;AAAAM,IAAA,KAAAT;AAAAS,IAAA,KAAAJ;AAAAI,IAAA,KAAAL;AAAAK,IAAA,KAAAiB;AAAAjB,IAAA,KAAAe;AAAAf,IAAA,MAAAP;AAAAO,IAAA,MAAAR;AAAAQ,IAAA,MAAAkB;OAAAA,MAAAlB,EAAA;AAAA,QAVMkB"}
1
+ {"version":3,"file":"react.js","names":["React","ThemeStore","ThemeStoreConfig","useResonare","store","$","_c","destroy","getResolvedThemes","getThemes","restore","setThemes","subscribe","sync","toPersist","updateSystemOption","themes","useSyncExternalStore","t0","t1","resolvedThemes"],"sources":["../src/react.ts"],"sourcesContent":["import * as React from 'react'\nimport type { ThemeStore, ThemeStoreConfig } from '.'\n\n/**\n * Subscribes a React component to a theme store.\n * @example\n * ```tsx\n * export function ThemeToggle() {\n * const { themes, resolvedThemes, setThemes } = useResonare(store)\n *\n * // ...\n * }\n * ```\n */\nexport function useResonare<T extends ThemeStoreConfig>(store: ThemeStore<T>) {\n\tconst {\n\t\tdestroy,\n\t\tgetResolvedThemes,\n\t\tgetThemes,\n\t\trestore,\n\t\tsetThemes,\n\t\tsubscribe,\n\t\tsync,\n\t\ttoPersist,\n\t\tupdateSystemOption,\n\t} = store\n\n\tconst themes = React.useSyncExternalStore(subscribe, getThemes, getThemes)\n\n\treturn {\n\t\tthemes,\n\t\tresolvedThemes: getResolvedThemes(),\n\t\tdestroy,\n\t\trestore,\n\t\tsetThemes,\n\t\tsubscribe,\n\t\tsync,\n\t\ttoPersist,\n\t\tupdateSystemOption,\n\t}\n}\n"],"mappings":"mEAcA,SAAOG,EAAAC,EAAA,CAAA,IAAAC,EAAAC,EAAA,GAAA,CACN,CAAAC,UAAAC,oBAAAC,YAAAC,UAAAC,YAAAC,YAAAC,OAAAC,YAAAC,sBAUIX,EAEJY,EAAehB,EAAKiB,qBAAsBL,EAAWH,EAAWA,EAAU,CAAAS,EAAAb,EAAA,KAAAG,EAItCU,EAAAb,EAAA,IAAnBa,EAAAV,GAAmB,CAAAH,EAAA,GAAAG,EAAAH,EAAA,GAAAa,GAAA,IAAAC,EAQnC,OARmCd,EAAA,KAAAE,GAAAF,EAAA,KAAAK,GAAAL,EAAA,KAAAM,GAAAN,EAAA,KAAAO,GAAAP,EAAA,KAAAQ,GAAAR,EAAA,KAAAa,GAAAb,EAAA,KAAAW,GAAAX,EAAA,KAAAS,GAAAT,EAAA,MAAAU,GAF7BI,EAAA,CAAAH,SAAAI,eAEUF,EAAmBX,UAAAG,UAAAC,YAAAC,YAAAC,OAAAC,YAAAC,qBAQnC,CAAAV,EAAA,GAAAE,EAAAF,EAAA,GAAAK,EAAAL,EAAA,GAAAM,EAAAN,EAAA,GAAAO,EAAAP,EAAA,GAAAQ,EAAAR,EAAA,GAAAa,EAAAb,EAAA,GAAAW,EAAAX,EAAA,GAAAS,EAAAT,EAAA,IAAAU,EAAAV,EAAA,IAAAc,GAAAA,EAAAd,EAAA,IAVMc"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resonare",
3
- "version": "0.0.18",
3
+ "version": "0.1.1",
4
4
  "description": "Resonare",
5
5
  "keywords": [
6
6
  "theming",
@@ -21,23 +21,14 @@
21
21
  "types": "./dist/index.d.ts",
22
22
  "import": "./dist/index.js"
23
23
  },
24
- "./global": {
25
- "types": "./global.d.ts"
26
- },
27
- "./inline-script": {
28
- "import": "./dist/inline-script.ts"
29
- },
30
24
  "./react": {
31
25
  "types": "./dist/react.d.ts",
32
26
  "import": "./dist/react.js"
33
27
  }
34
28
  },
35
- "jsdelivr": "./dist/resonare.iife.min.js",
36
- "unpkg": "./dist/resonare.iife.min.js",
37
29
  "types": "./dist/index.d.ts",
38
30
  "files": [
39
- "dist",
40
- "global.d.ts"
31
+ "dist"
41
32
  ],
42
33
  "dependencies": {
43
34
  "react-compiler-runtime": "1.0.0"
@@ -45,7 +36,7 @@
45
36
  "devDependencies": {
46
37
  "@rolldown/plugin-babel": "0.2.2",
47
38
  "@size-limit/preset-small-lib": "12.0.1",
48
- "@types/node": "25.5.0",
39
+ "@types/node": "25.5.2",
49
40
  "@types/react": "19.2.14",
50
41
  "@vitejs/plugin-react": "6.0.1",
51
42
  "babel-plugin-react-compiler": "1.0.0",
@@ -54,10 +45,10 @@
54
45
  "react": "19.2.4",
55
46
  "react-dom": "19.2.4",
56
47
  "size-limit": "12.0.1",
57
- "tsdown": "0.21.4",
58
- "typescript": "5.9.3",
59
- "vite": "8.0.1",
60
- "vitest": "4.1.0"
48
+ "tsdown": "0.21.7",
49
+ "typescript": "6.0.2",
50
+ "vite": "8.0.3",
51
+ "vitest": "4.1.2"
61
52
  },
62
53
  "peerDependencies": {
63
54
  "react": "^18.0.0 || ^19.0.0"
@@ -1 +0,0 @@
1
- export const resonareInlineScript = "/**\n* resonare v0.0.18\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/\nvar resonare=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`resonare`;let n=({type:e=`localStorage`}={})=>({abortController:t})=>({get:t=>JSON.parse(window[e].getItem(t)||`null`),set:(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()}}});function r(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t.initialValue??(typeof t.options[0]==`object`?t.options[0].value:t.options[0])]))}var i=class{#e;#t;#n;#r;#i;#a=new Set;#o;#s=new AbortController;constructor({key:e=t,config:i,initialState:a={},storage:o=n()}){let s={...a.systemOptions};this.#n={key:e,config:Object.fromEntries(Object.entries(i).map(([e,t])=>{let n=(t.options||[]).map(t=>typeof t==`object`?(t.media&&!Object.hasOwn(s,e)&&(s[e]=[t.media[1],t.media[2]]),[String(t.value),t]):[String(t),{value:t}]);return[e,Object.fromEntries(n)]}))},this.#r=s,this.#e=r(i),this.#t={...this.#e,...a.themes},this.#i=o?.({abortController:this.#s})??null,this.#o={}}getThemes=()=>this.#t;getResolvedThemes=()=>this.#l();setThemes=e=>{let t=typeof e==`function`?e(this.#t):e;this.#c({...this.#t,...t});let n=this.toPersist();this.#i&&(this.#i.set(this.#n.key,n),this.#i.broadcast?.(this.#n.key,n))};updateSystemOption=(e,[t,n])=>{this.#r[e]=[t,n],this.setThemes({...this.#t})};toPersist=()=>({version:1,themes:this.#t,systemOptions:this.#r});restore=()=>{let e=this.#i?.get(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)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,n?this.#u({themeKey:e,option:n}):t]}));#u=({themeKey:e,option:t})=>{if(!t.media)return 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,i]=this.#r[e];return this.#o[n].matches?r:i};#d=()=>{for(let e of this.#a)e({themes:this.#t,resolvedThemes:this.#l()})}};let a=new class{#e=new Map;create=e=>{let n=e.key||t,r=this.#e.get(n);return r||(r=new i(e),this.#e.set(n,r)),r};get=e=>{let n=e||t,r=this.#e.get(n);if(!r){console.error(`Theme store '${n}' not found.`);return}return r};destroy=e=>{let n=e||t,r=this.#e.get(n);r&&(r.___destroy(),this.#e.delete(n))}},o=a.create,s=a.get,c=a.destroy;return e.createThemeStore=o,e.destroyThemeStore=c,e.getThemeStore=s,e})({});"
@@ -1,7 +0,0 @@
1
- /**
2
- * resonare v0.0.18
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){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`resonare`;let n=({type:e=`localStorage`}={})=>({abortController:t})=>({get:t=>JSON.parse(window[e].getItem(t)||`null`),set:(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()}}});function r(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t.initialValue??(typeof t.options[0]==`object`?t.options[0].value:t.options[0])]))}var i=class{#e;#t;#n;#r;#i;#a=new Set;#o;#s=new AbortController;constructor({key:e=t,config:i,initialState:a={},storage:o=n()}){let s={...a.systemOptions};this.#n={key:e,config:Object.fromEntries(Object.entries(i).map(([e,t])=>{let n=(t.options||[]).map(t=>typeof t==`object`?(t.media&&!Object.hasOwn(s,e)&&(s[e]=[t.media[1],t.media[2]]),[String(t.value),t]):[String(t),{value:t}]);return[e,Object.fromEntries(n)]}))},this.#r=s,this.#e=r(i),this.#t={...this.#e,...a.themes},this.#i=o?.({abortController:this.#s})??null,this.#o={}}getThemes=()=>this.#t;getResolvedThemes=()=>this.#l();setThemes=e=>{let t=typeof e==`function`?e(this.#t):e;this.#c({...this.#t,...t});let n=this.toPersist();this.#i&&(this.#i.set(this.#n.key,n),this.#i.broadcast?.(this.#n.key,n))};updateSystemOption=(e,[t,n])=>{this.#r[e]=[t,n],this.setThemes({...this.#t})};toPersist=()=>({version:1,themes:this.#t,systemOptions:this.#r});restore=()=>{let e=this.#i?.get(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)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,n?this.#u({themeKey:e,option:n}):t]}));#u=({themeKey:e,option:t})=>{if(!t.media)return 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,i]=this.#r[e];return this.#o[n].matches?r:i};#d=()=>{for(let e of this.#a)e({themes:this.#t,resolvedThemes:this.#l()})}};let a=new class{#e=new Map;create=e=>{let n=e.key||t,r=this.#e.get(n);return r||(r=new i(e),this.#e.set(n,r)),r};get=e=>{let n=e||t,r=this.#e.get(n);if(!r){console.error(`Theme store '${n}' not found.`);return}return r};destroy=e=>{let n=e||t,r=this.#e.get(n);r&&(r.___destroy(),this.#e.delete(n))}},o=a.create,s=a.get,c=a.destroy;return e.createThemeStore=o,e.destroyThemeStore=c,e.getThemeStore=s,e})({});
package/global.d.ts DELETED
@@ -1,3 +0,0 @@
1
- interface Window {
2
- resonare: typeof import('./dist/index')
3
- }