resonare 0.0.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/LICENSE.md +21 -0
- package/README.md +332 -0
- package/dist/index-C1bWhjq0.d.ts +65 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/react.d.ts +54 -0
- package/dist/react.js +71 -0
- package/dist/resonare.iife.min.js +1 -0
- package/dist/src-BsTQDM3B.js +188 -0
- package/dist/storage-BP1DGXF0.d.ts +19 -0
- package/dist/storage-ByzkGDod.js +59 -0
- package/dist/storage.d.ts +2 -0
- package/dist/storage.js +3 -0
- package/dist/umd.d.ts +3 -0
- package/dist/umd.js +4 -0
- package/global.d.ts +3 -0
- package/package.json +77 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Si Phuoc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Resonare [](https://www.npmjs.com/package/resonare)
|
|
2
|
+
|
|
3
|
+
A configuration-based theme store for building deeply personal user interface.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Define and manage multi-dimensional themes, eg:
|
|
8
|
+
- Color scheme: system, light, dark
|
|
9
|
+
- Contrast preference: standard, high
|
|
10
|
+
- Spacing: compact, comfortable, spacious
|
|
11
|
+
- etc
|
|
12
|
+
- Framework-agnostic
|
|
13
|
+
- Prevent theme flicker on page load
|
|
14
|
+
- Honor system preferences
|
|
15
|
+
- Create sections with independent theming
|
|
16
|
+
- Sync theme selection across tabs and windows
|
|
17
|
+
- Flexible persistence options, defaulting to localStorage
|
|
18
|
+
- SSR-friendly
|
|
19
|
+
|
|
20
|
+
## Demo
|
|
21
|
+
|
|
22
|
+
- [Demo](https://resonare.universse.workers.dev)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
To install:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm i resonare
|
|
30
|
+
# or
|
|
31
|
+
yarn add resonare
|
|
32
|
+
# or
|
|
33
|
+
pnpm add resonare
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Basic Usage
|
|
37
|
+
|
|
38
|
+
It's recommended to initialize Resonare in a synchronous script to avoid theme flicker on page load. If your project's bundler supports importing static asset as string, you can inline the minified version of Resonare to reduce the number of HTTP requests. Check out the demo for example of this pattern with Vite.
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://unpkg.com/resonare"></script>
|
|
42
|
+
<!-- or -->
|
|
43
|
+
<script src="https://cdn.jsdelivr.net/npm/resonare"></script>
|
|
44
|
+
|
|
45
|
+
<script>
|
|
46
|
+
;(async () => {
|
|
47
|
+
const themeStore = window.resonare.createThemeStore({
|
|
48
|
+
config: {
|
|
49
|
+
colorScheme: {
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
value: 'system',
|
|
53
|
+
media: ['(prefers-color-scheme: dark)', 'dark', 'light'],
|
|
54
|
+
},
|
|
55
|
+
'light',
|
|
56
|
+
'dark',
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
themeStore.subscribe(({ resolvedThemes }) => {
|
|
63
|
+
for (const [theme, optionKey] of Object.entries(resolvedThemes)) {
|
|
64
|
+
document.documentElement.dataset[theme] = optionKey
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await themeStore.restore()
|
|
69
|
+
themeStore.sync()
|
|
70
|
+
})()
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If you are using TypeScript, add `node_modules/resonare/global.d.ts` to `include` in `tsconfig.json`.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"include": [
|
|
79
|
+
"node_modules/resonare/global.d.ts",
|
|
80
|
+
// ...
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API
|
|
86
|
+
|
|
87
|
+
### `createThemeStore`
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createThemeStore, type ThemeConfig, type ThemeStore } from 'resonare'
|
|
91
|
+
|
|
92
|
+
const config = {
|
|
93
|
+
colorScheme: {
|
|
94
|
+
options: [
|
|
95
|
+
{
|
|
96
|
+
value: 'system',
|
|
97
|
+
media: ['(prefers-color-scheme: dark)', 'dark', 'light'],
|
|
98
|
+
},
|
|
99
|
+
'light',
|
|
100
|
+
'dark',
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
contrast: {
|
|
104
|
+
options: [
|
|
105
|
+
{
|
|
106
|
+
value: 'system',
|
|
107
|
+
media: [
|
|
108
|
+
'(prefers-contrast: more) and (forced-colors: none)',
|
|
109
|
+
'high',
|
|
110
|
+
'standard',
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
'standard',
|
|
114
|
+
'high',
|
|
115
|
+
],
|
|
116
|
+
defaultOption: 'standard',
|
|
117
|
+
}
|
|
118
|
+
} as const satisfies ThemeConfig
|
|
119
|
+
|
|
120
|
+
declare module 'resonare' {
|
|
121
|
+
interface ThemeStoreRegistry {
|
|
122
|
+
resonare: ThemeStore<typeof config>
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
const themeStore = createThemeStore({
|
|
128
|
+
// optional, default 'resonare'
|
|
129
|
+
// should be unique, also used as client storage key
|
|
130
|
+
key: 'resonare',
|
|
131
|
+
|
|
132
|
+
// required, specify theme and options
|
|
133
|
+
config,
|
|
134
|
+
|
|
135
|
+
// optional, useful for SSR
|
|
136
|
+
initialState: {
|
|
137
|
+
themes: {
|
|
138
|
+
colorScheme: 'dark',
|
|
139
|
+
contrast: 'high',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// optional, specify your own client storage
|
|
144
|
+
// localStorage is used by default
|
|
145
|
+
storage: ({ abortController }) => ({
|
|
146
|
+
getItem: (key: string) => {
|
|
147
|
+
return JSON.parse(localStorage.getItem(key) || 'null')
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
setItem: (key: string, value: object) => {
|
|
151
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
watch: (cb: (key: string, value: unknown) => void) => {
|
|
155
|
+
const controller = new AbortController()
|
|
156
|
+
|
|
157
|
+
window.addEventListener(
|
|
158
|
+
'storage',
|
|
159
|
+
(e) => {
|
|
160
|
+
if (e.storageArea !== localStorage) return
|
|
161
|
+
|
|
162
|
+
cb(e.key, JSON.parse(e.newValue!))
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
signal: AbortSignal.any([
|
|
166
|
+
abortController.signal,
|
|
167
|
+
controller.signal,
|
|
168
|
+
]),
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return () => {
|
|
173
|
+
controller.abort()
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### `getThemeStore`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { getThemeStore } from 'resonare'
|
|
184
|
+
|
|
185
|
+
// Get an existing theme store by key
|
|
186
|
+
const themeStore = getThemeStore('resonare')
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `destroyThemeStore`
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { destroyThemeStore } from 'resonare'
|
|
193
|
+
|
|
194
|
+
// Get an existing theme store by key
|
|
195
|
+
const themeStore = destroyThemeStore('resonare')
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### ThemeStore Methods
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
interface ThemeStore<T> {
|
|
202
|
+
// get current theme selection
|
|
203
|
+
getThemes(): Record<string, string>
|
|
204
|
+
|
|
205
|
+
// get resolved theme selection (after media queries)
|
|
206
|
+
getResolvedThemes(): Record<string, string>
|
|
207
|
+
|
|
208
|
+
// update theme
|
|
209
|
+
setThemes(themes: Partial<Record<string, string>>): Promise<void>
|
|
210
|
+
|
|
211
|
+
// get state to persist, useful for server-side persistence
|
|
212
|
+
// to restore, pass the returned object to initialState
|
|
213
|
+
getStateToPersist(): object
|
|
214
|
+
|
|
215
|
+
// restore persisted theme selection from client storage
|
|
216
|
+
restore(): Promise<void>
|
|
217
|
+
|
|
218
|
+
// sync theme selection across tabs/windows
|
|
219
|
+
sync(): () => void
|
|
220
|
+
|
|
221
|
+
// subscribe to theme changes
|
|
222
|
+
subscribe(
|
|
223
|
+
callback: ({
|
|
224
|
+
themes,
|
|
225
|
+
resolvedThemes,
|
|
226
|
+
}: {
|
|
227
|
+
themes: Record<string, string>
|
|
228
|
+
resolvedThemes: Record<string, string>
|
|
229
|
+
}) => void,
|
|
230
|
+
options?: { immediate?: boolean }
|
|
231
|
+
): () => void
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### React Integration
|
|
236
|
+
|
|
237
|
+
Ensure that you have initialized Resonare as per instructions under [Basic Usage](#basic-usage).
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import * as React from 'react'
|
|
241
|
+
import { getThemesAndOptions } from 'resonare'
|
|
242
|
+
import { useResonare } from 'resonare/react'
|
|
243
|
+
|
|
244
|
+
function ThemeSelect() {
|
|
245
|
+
const { themes, setThemes } = useResonare(() =>
|
|
246
|
+
window.resonare.getThemeStore(),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return getThemesAndOptions(config).map(([theme, options]) => (
|
|
250
|
+
<div key={theme}>
|
|
251
|
+
<label htmlFor={theme}>{theme}</label>
|
|
252
|
+
<select
|
|
253
|
+
id={theme}
|
|
254
|
+
name={theme}
|
|
255
|
+
onChange={(e) => {
|
|
256
|
+
setThemes({ [theme]: e.target.value })
|
|
257
|
+
}}
|
|
258
|
+
value={themes[theme]}
|
|
259
|
+
>
|
|
260
|
+
{options.map((option) => (
|
|
261
|
+
<option key={option} value={option}>
|
|
262
|
+
{option}
|
|
263
|
+
</option>
|
|
264
|
+
))}
|
|
265
|
+
</select>
|
|
266
|
+
</div>
|
|
267
|
+
))
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Server-side persistence
|
|
272
|
+
|
|
273
|
+
If you are storing theme selection on the server, you can choose to use `memoryStorageAdapter` to avoid storing any data client-side. There's no need to initialize Resonare in a synchronous script. Ensure you pass the persisted theme selection when initializing Resonare as `initialState`.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import {
|
|
277
|
+
createThemeStore,
|
|
278
|
+
getThemesAndOptions,
|
|
279
|
+
memoryStorageAdapter,
|
|
280
|
+
type ThemeConfig,
|
|
281
|
+
type Themes,
|
|
282
|
+
} from 'resonare'
|
|
283
|
+
import { useResonare } from 'resonare/react'
|
|
284
|
+
import * as React from 'react'
|
|
285
|
+
|
|
286
|
+
const config = {
|
|
287
|
+
colorScheme: {
|
|
288
|
+
options: ['light', 'dark'],
|
|
289
|
+
},
|
|
290
|
+
contrast: {
|
|
291
|
+
options: ['standard', 'high'],
|
|
292
|
+
},
|
|
293
|
+
} as const satisfies ThemeConfig
|
|
294
|
+
|
|
295
|
+
export function ThemeSelect({
|
|
296
|
+
persistedServerThemes,
|
|
297
|
+
}: { persistedServerThemes: Themes<typeof config> }) {
|
|
298
|
+
const [themeStore] = React.useState(() =>
|
|
299
|
+
createThemeStore({
|
|
300
|
+
config,
|
|
301
|
+
initialState: {
|
|
302
|
+
themes: persistedServerThemes,
|
|
303
|
+
},
|
|
304
|
+
storage: memoryStorageAdapter(),
|
|
305
|
+
}),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
const { themes, setThemes } = useResonare(() => themeStore, {
|
|
309
|
+
initOnMount: true,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
return getThemesAndOptions(config).map(([theme, options]) => (
|
|
313
|
+
<div key={theme}>
|
|
314
|
+
<label htmlFor={theme}>{theme}</label>
|
|
315
|
+
<select
|
|
316
|
+
id={theme}
|
|
317
|
+
name={theme}
|
|
318
|
+
onChange={(e) => {
|
|
319
|
+
setThemes({ [theme]: e.target.value })
|
|
320
|
+
}}
|
|
321
|
+
value={themes[theme]}
|
|
322
|
+
>
|
|
323
|
+
{options.map((option) => (
|
|
324
|
+
<option key={option} value={option}>
|
|
325
|
+
{option}
|
|
326
|
+
</option>
|
|
327
|
+
))}
|
|
328
|
+
</select>
|
|
329
|
+
</div>
|
|
330
|
+
))
|
|
331
|
+
}
|
|
332
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as getThemeStore, i as getDefaultThemes, n as createThemeStore, o as getThemesAndOptions, r as destroyThemeStore, t as ThemeStore } from "./src-BsTQDM3B.js";
|
|
2
|
+
|
|
3
|
+
export { ThemeStore, createThemeStore, destroyThemeStore, getDefaultThemes, getThemeStore, getThemesAndOptions };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { n as ThemeConfig, o as Themes, r as ThemeStore } from "./index-C1bWhjq0.js";
|
|
2
|
+
|
|
3
|
+
//#region src/react.d.ts
|
|
4
|
+
declare function useResonare<T extends ThemeConfig>(getStore: () => ThemeStore<T>, {
|
|
5
|
+
initOnMount
|
|
6
|
+
}?: {
|
|
7
|
+
initOnMount?: boolean | undefined;
|
|
8
|
+
}): {
|
|
9
|
+
themes: Themes<T>;
|
|
10
|
+
resolvedThemes: Themes<T>;
|
|
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 {
|
|
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 {
|
|
15
|
+
value: string;
|
|
16
|
+
media?: [string, string, string];
|
|
17
|
+
} ? U extends {
|
|
18
|
+
media: [string, string, string];
|
|
19
|
+
} ? never : U["value"] : never : never, T[K]["options"] extends (infer U)[] ? U extends string ? U : U extends {
|
|
20
|
+
value: string;
|
|
21
|
+
media?: [string, string, string];
|
|
22
|
+
} ? U extends {
|
|
23
|
+
media: [string, string, string];
|
|
24
|
+
} ? never : U["value"] : never : never]) => void;
|
|
25
|
+
getStateToPersist: () => {
|
|
26
|
+
version: 1;
|
|
27
|
+
themes: Themes<T>;
|
|
28
|
+
systemOptions: { [K_1 in { [K in keyof T]: T[K]["options"] extends (infer U)[] ? U extends {
|
|
29
|
+
media: [string, string, string];
|
|
30
|
+
} ? K : never : never }[keyof T]]?: [T[K_1]["options"] extends (infer U)[] ? U extends string ? U : U extends {
|
|
31
|
+
value: string;
|
|
32
|
+
media?: [string, string, string];
|
|
33
|
+
} ? U extends {
|
|
34
|
+
media: [string, string, string];
|
|
35
|
+
} ? never : U["value"] : never : never, T[K_1]["options"] extends (infer U)[] ? U extends string ? U : U extends {
|
|
36
|
+
value: string;
|
|
37
|
+
media?: [string, string, string];
|
|
38
|
+
} ? U extends {
|
|
39
|
+
media: [string, string, string];
|
|
40
|
+
} ? never : U["value"] : never : never] };
|
|
41
|
+
};
|
|
42
|
+
restore: () => Promise<void>;
|
|
43
|
+
sync: () => (() => void) | undefined;
|
|
44
|
+
subscribe: (callback: (value: {
|
|
45
|
+
themes: Themes<T>;
|
|
46
|
+
resolvedThemes: Themes<T>;
|
|
47
|
+
}) => void, {
|
|
48
|
+
immediate
|
|
49
|
+
}?: {
|
|
50
|
+
immediate?: boolean;
|
|
51
|
+
}) => (() => void);
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { useResonare };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { c } from "react-compiler-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/react.ts
|
|
5
|
+
function noop() {}
|
|
6
|
+
const emptyObject = {};
|
|
7
|
+
const emptyStore = {
|
|
8
|
+
getThemes: () => emptyObject,
|
|
9
|
+
getResolvedThemes: () => emptyObject,
|
|
10
|
+
setThemes: noop,
|
|
11
|
+
updateSystemOption: noop,
|
|
12
|
+
getStateToPersist: noop,
|
|
13
|
+
restore: noop,
|
|
14
|
+
sync: noop,
|
|
15
|
+
subscribe: () => noop
|
|
16
|
+
};
|
|
17
|
+
function useResonare(getStore, t0) {
|
|
18
|
+
const $ = c(13);
|
|
19
|
+
let t1;
|
|
20
|
+
if ($[0] !== t0) {
|
|
21
|
+
t1 = t0 === void 0 ? {} : t0;
|
|
22
|
+
$[0] = t0;
|
|
23
|
+
$[1] = t1;
|
|
24
|
+
} else t1 = $[1];
|
|
25
|
+
const { initOnMount: t2 } = t1;
|
|
26
|
+
const initOnMount = t2 === void 0 ? false : t2;
|
|
27
|
+
const [isMounted, setIsMounted] = React.useState(initOnMount);
|
|
28
|
+
let t3;
|
|
29
|
+
let t4;
|
|
30
|
+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
|
31
|
+
t3 = () => {
|
|
32
|
+
setIsMounted(true);
|
|
33
|
+
};
|
|
34
|
+
t4 = [];
|
|
35
|
+
$[2] = t3;
|
|
36
|
+
$[3] = t4;
|
|
37
|
+
} else {
|
|
38
|
+
t3 = $[2];
|
|
39
|
+
t4 = $[3];
|
|
40
|
+
}
|
|
41
|
+
React.useEffect(t3, t4);
|
|
42
|
+
const { getThemes, getResolvedThemes, setThemes, updateSystemOption, getStateToPersist, restore, sync, subscribe } = isMounted ? getStore() : emptyStore;
|
|
43
|
+
const themes = React.useSyncExternalStore(subscribe, getThemes, getThemes);
|
|
44
|
+
const t5 = getResolvedThemes();
|
|
45
|
+
let t6;
|
|
46
|
+
if ($[4] !== getStateToPersist || $[5] !== restore || $[6] !== setThemes || $[7] !== subscribe || $[8] !== sync || $[9] !== t5 || $[10] !== themes || $[11] !== updateSystemOption) {
|
|
47
|
+
t6 = {
|
|
48
|
+
themes,
|
|
49
|
+
resolvedThemes: t5,
|
|
50
|
+
setThemes,
|
|
51
|
+
updateSystemOption,
|
|
52
|
+
getStateToPersist,
|
|
53
|
+
restore,
|
|
54
|
+
sync,
|
|
55
|
+
subscribe
|
|
56
|
+
};
|
|
57
|
+
$[4] = getStateToPersist;
|
|
58
|
+
$[5] = restore;
|
|
59
|
+
$[6] = setThemes;
|
|
60
|
+
$[7] = subscribe;
|
|
61
|
+
$[8] = sync;
|
|
62
|
+
$[9] = t5;
|
|
63
|
+
$[10] = themes;
|
|
64
|
+
$[11] = updateSystemOption;
|
|
65
|
+
$[12] = t6;
|
|
66
|
+
} else t6 = $[12];
|
|
67
|
+
return t6;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { useResonare };
|
|
@@ -0,0 +1 @@
|
|
|
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})({});
|
|
@@ -0,0 +1,188 @@
|
|
|
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 };
|
|
@@ -0,0 +1,19 @@
|
|
|
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 };
|
|
@@ -0,0 +1,59 @@
|
|
|
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 };
|
|
@@ -0,0 +1,2 @@
|
|
|
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
ADDED
package/dist/umd.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
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 };
|
package/global.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "resonare",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Resonare",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"theming"
|
|
7
|
+
],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/universse/resonare.git",
|
|
11
|
+
"directory": "resonare"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Si Phuoc <phuoc317049@gmail.com>",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./global": {
|
|
23
|
+
"types": "./dist/umd.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./raw": {
|
|
26
|
+
"import": "./dist/resonare.iife.min.js"
|
|
27
|
+
},
|
|
28
|
+
"./react": {
|
|
29
|
+
"types": "./dist/react.d.ts",
|
|
30
|
+
"import": "./dist/react.js"
|
|
31
|
+
},
|
|
32
|
+
"./storage": {
|
|
33
|
+
"types": "./dist/storage.d.ts",
|
|
34
|
+
"import": "./dist/storage.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"jsdelivr": "./dist/resonare.iife.min.js",
|
|
38
|
+
"unpkg": "./dist/resonare.iife.min.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"global.d.ts"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"react-compiler-runtime": "1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@size-limit/preset-small-lib": "12.0.0",
|
|
49
|
+
"@types/react": "19.2.7",
|
|
50
|
+
"@vitejs/plugin-react": "5.1.2",
|
|
51
|
+
"babel-plugin-react-compiler": "1.0.0",
|
|
52
|
+
"jsdom": "27.3.0",
|
|
53
|
+
"react": "19.2.1",
|
|
54
|
+
"react-dom": "19.2.1",
|
|
55
|
+
"size-limit": "12.0.0",
|
|
56
|
+
"tsdown": "0.17.2",
|
|
57
|
+
"typescript": "5.9.3",
|
|
58
|
+
"vitest": "4.0.15"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"react": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsdown",
|
|
73
|
+
"dev": "tsdown --watch",
|
|
74
|
+
"size": "size-limit",
|
|
75
|
+
"test": "vitest"
|
|
76
|
+
}
|
|
77
|
+
}
|