resonare 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -14
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# Resonare [](https://www.npmjs.com/package/resonare)
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A state store for multi-dimensional themes and user preferences.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- Define and manage user preferences, e.g.:
|
|
7
|
+
- Define and manage themes and user preferences, e.g.:
|
|
8
8
|
- Color scheme: system, light, dark
|
|
9
9
|
- Contrast preference: standard, high
|
|
10
10
|
- Spacing: compact, comfortable, spacious
|
|
11
|
-
- Showing
|
|
12
|
-
- Sidebar width
|
|
11
|
+
- Showing, hiding, or collapsing sidebar/sections
|
|
13
12
|
- etc.
|
|
14
13
|
- Framework-agnostic
|
|
15
14
|
- Prevent flicker on page load
|
|
@@ -72,7 +71,7 @@ export const themeScript = createInlineThemeScript([
|
|
|
72
71
|
|
|
73
72
|
### 2. Inject inline script
|
|
74
73
|
|
|
75
|
-
Inject the script into the `<head>` of your HTML document
|
|
74
|
+
Inject the script into the `<head>` of your HTML document. The exact pattern differs by framework. Here are some examples.
|
|
76
75
|
|
|
77
76
|
```tsx
|
|
78
77
|
// React.js
|
|
@@ -268,17 +267,24 @@ export const themeScript = createInlineThemeScript([PARAM])
|
|
|
268
267
|
|
|
269
268
|
const themeStore = createThemeStore(PARAM.config)
|
|
270
269
|
|
|
271
|
-
if (typeof window !== 'undefined') {
|
|
272
|
-
themeStore.subscribe(PARAM.handler)
|
|
273
|
-
|
|
274
|
-
themeStore.restore()
|
|
275
|
-
|
|
276
|
-
themeStore.sync()
|
|
277
|
-
}
|
|
278
270
|
|
|
279
271
|
function ThemeSelect() {
|
|
280
272
|
const { themes, setThemes } = useResonare(themeStore)
|
|
281
273
|
|
|
274
|
+
React.useEffect(() => {
|
|
275
|
+
const unsubscribe = subscribe(PARAM.handler)
|
|
276
|
+
|
|
277
|
+
const stopSync = sync()
|
|
278
|
+
|
|
279
|
+
restore()
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
stopSync?.()
|
|
283
|
+
|
|
284
|
+
unsubscribe()
|
|
285
|
+
}
|
|
286
|
+
}, [subscribe, restore, sync])
|
|
287
|
+
|
|
282
288
|
return getThemesAndOptions(PARAM.config).map(([theme, options]) => (
|
|
283
289
|
<div key={theme}>
|
|
284
290
|
<label htmlFor={theme}>{theme}</label>
|
|
@@ -345,9 +351,9 @@ export function ThemeSelect({ persistedStateFromDb }) {
|
|
|
345
351
|
const stopSync = sync()
|
|
346
352
|
|
|
347
353
|
return () => {
|
|
348
|
-
unsubscribe()
|
|
349
|
-
|
|
350
354
|
stopSync?.()
|
|
355
|
+
|
|
356
|
+
unsubscribe()
|
|
351
357
|
}
|
|
352
358
|
}, [subscribe, sync])
|
|
353
359
|
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ type StorageAdapterCreator<Options> = (options: Options) => StorageAdapterCreate
|
|
|
21
21
|
* import { createThemeStore, localStorageAdapter } from 'resonare'
|
|
22
22
|
*
|
|
23
23
|
* const store = createThemeStore(
|
|
24
|
-
* {
|
|
24
|
+
* { colorMode: { options: ['light', 'dark'] },
|
|
25
25
|
* { storage: localStorageAdapter({ key: 'app', type: 'localStorage' }) },
|
|
26
26
|
* )
|
|
27
27
|
* ```
|
|
@@ -38,8 +38,8 @@ declare const localStorageAdapter: StorageAdapterCreator<{
|
|
|
38
38
|
* import { createThemeStore, memoryStorageAdapter } from 'resonare'
|
|
39
39
|
*
|
|
40
40
|
* const store = createThemeStore(
|
|
41
|
-
* {
|
|
42
|
-
* { storage: memoryStorageAdapter({ key: '
|
|
41
|
+
* { colorMode: { options: ['light', 'dark'] },
|
|
42
|
+
* { storage: memoryStorageAdapter({ key: 'app' }) },
|
|
43
43
|
* )
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
@@ -128,17 +128,19 @@ type ThemeScriptParameter = {
|
|
|
128
128
|
* const inlineScript = createInlineThemeScript([
|
|
129
129
|
* {
|
|
130
130
|
* key: 'my-app',
|
|
131
|
-
* config: {
|
|
131
|
+
* config: {
|
|
132
|
+
* colorMode: { options: ['light', 'dark'] },
|
|
133
|
+
* },
|
|
132
134
|
* handler: ({ resolvedThemes }) => {
|
|
133
|
-
* document.documentElement.dataset.
|
|
135
|
+
* document.documentElement.dataset.colorMode = String(
|
|
136
|
+
* resolvedThemes.colorMode,
|
|
137
|
+
* )
|
|
134
138
|
* },
|
|
135
139
|
* },
|
|
136
140
|
* ])
|
|
137
|
-
*
|
|
138
|
-
* <script>{inlineScript}</script>
|
|
139
141
|
* ```
|
|
140
142
|
*/
|
|
141
143
|
declare function createInlineThemeScript(themeScriptParameters: Array<ThemeScriptParameter>): string;
|
|
142
144
|
//#endregion
|
|
143
|
-
export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator,
|
|
145
|
+
export { StorageAdapter, StorageAdapterCreate, StorageAdapterCreator, ThemeScriptParameter, type ThemeStore, ThemeStoreConfig, Themes, createInlineThemeScript, createThemeStore, getDefaultThemes, getThemesAndOptions, localStorageAdapter, memoryStorageAdapter };
|
|
144
146
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/storage.ts","../src/index.ts"],"mappings":";;AAKA;;KAAY,cAAA;EACX,GAAA;EACA,GAAA,GAAM,KAAA,mBAAN;EAEA,SAAA,IAAa,KAAA,mBAAb;EAEA,KAAA,IAAS,EAAA,GAAK,KAAA;AAAA;AAAA,KAGH,oBAAA;EACX;AAAA;EAEA,eAAA,EAAiB,eAAA;AAAA,MACZ,cAAA;AAAA,KAEM,qBAAA,aACX,OAAA,EAAS,OAAA,KACL,oBAAA;AARL;;;;;;;;;;;;AAAA,cAsBa,mBAAA,EAAqB,qBAAA;EACjC,GAAA;EACA,IAAA;AAAA;;;;;;;;;;AAFD;;;;cAuDa,oBAAA,EAAsB,qBAAA;EAClC,GAAA;AAAA;;;KCnFI,UAAA;AAAA,KAEA,WAAA,WAAsB,UAAA;EAC1B,KAAA,EAAO,CAAA;EACP,KAAA,YAAiB,CAAA,EAAG,CAAA;AAAA;AAAA,KAGhB,WAAA,WAAsB,UAAA;EAExB,OAAA,EAAS,aAAA,CAAc,CAAA,GAAI,WAAA,CAAY,CAAA;EACvC,YAAA,GAAe,CAAA;AAAA;EAEb,YAAA,EAAc,CAAA;EAAG,OAAA;AAAA;AAAA,KAEV,gBAAA,GAAmB,MAAA,SAE9B,WAAA,WAAsB,WAAA,WAAsB,WAAA;AAAA,KAQjC,MAAA,WAAiB,gBAAA,kBAChB,CAAA,GAAI,CAAA,CAAE,CAAA;EAAa,OAAA,EAAS,aAAA;AAAA,IACrC,CAAA,SAAU,WAAA,GACT,CAAA,YACA,CAAA,GACD,CAAA,CAAE,CAAA;EAAa,YAAA;AAAA,IACd,CAAA,2BAEC,CAAA;AAAA,KAMD,QAAA,WAAmB,gBAAA,KAAqB,KAAA;EAC5C,MAAA,EAAQ,MAAA,CAAO,CAAA;EACf,cAAA,EAAgB,MAAA,CAAO,CAAA;AAAA;AAAA,KAGnB,yBAAA,WAAoC,gBAAA,kBAC5B,CAAA,GAAI,CAAA,CAAE,CAAA;EAAa,OAAA,EAAS,aAAA;AAAA,IACrC,CAAA;EAAY,KAAA,EAAO,aAAA;AAAA,IAClB,CAAA,yBAGG,CAAA;AAAA,KAEH,qBAAA,WACM,gBAAA,kBACM,CAAA,IACb,CAAA,CAAE,CAAA;EAAa,OAAA,EAAS,aAAA;AAAA,IACzB,CAAA,SAAU,UAAA,GACT,CAAA,GACA,CAAA,SAAU,WAAA,GACT,CAAA;EAAY,KAAA;AAAA,YAEX,CAAA;AAAA,KAID,aAAA,WAAwB,gBAAA,YACtB,yBAAA,CAA0B,CAAA,MAC/B,qBAAA,CAAsB,CAAA,EAAG,CAAA,GACzB,qBAAA,CAAsB,CAAA,EAAG,CAAA;AAAA,KAItB,cAAA,WAAyB,gBAAA;EAC7B,MAAA,EAAQ,OAAA,CAAQ,MAAA,CAAO,CAAA;EACvB,aAAA,EAAe,aAAA,CAAc,CAAA;AAAA;AAAA,KAGzB,iBAAA,WAA4B,gBAAA;EAChC,YAAA,GAAe,OAAA,CAAQ,cAAA,CAAe,CAAA;EACtC,OAAA,GAAU,oBAAA;AAAA;AAAA,
|
|
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,2 +1,2 @@
|
|
|
1
|
-
var e=`resonare
|
|
1
|
+
var e=`resonare`;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},s=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.#n=s,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 process.env.NODE_ENV!==`production`&&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=e=>{e.forEach(([e,t,n])=>{let r=JSON.parse(localStorage.getItem(e)||`{}`).themes??{};n(Object.entries(t).reduce((e,[t,n])=>{let i=n.options,a=i?.[0],o=r[t]??n.initialValue??(typeof a==`object`?a.value:a);e.resolvedThemes[t]=e.themes[t]=o;let s=i?.find(e=>typeof e==`object`&&!!e.media&&e.value===o);if(s){let[n,r,i]=s.media;e.resolvedThemes[t]=matchMedia(n).matches?r:i}return e},{themes:{},resolvedThemes:{}}))})};function c(t){return`(${s})([${t.map(({key:t=e,config:n,handler:r})=>`[${JSON.stringify(t)},${JSON.stringify(n)},${r}]`).join(`,`)}])`}export{c as createInlineThemeScript,o as createThemeStore,i as getDefaultThemes,r as getThemesAndOptions,t as localStorageAdapter,n as memoryStorageAdapter};
|
|
2
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","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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"}
|
|
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","PROD","console","warn","mediaQuery","mediaQueryList","matchMedia","addEventListener","signal","matches","#notify","listener","createThemeStore","restoreScript","params","forEach","handler","persistedThemes","JSON","parse","localStorage","getItem","reduce","acc","firstOption","currentValue","maybeSystemOption","find","Required","ThemeScriptParameter","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 * { colorMode: { 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 * { colorMode: { options: ['light', 'dark'] },\n * { storage: memoryStorageAdapter({ key: 'app' }) },\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\ntype 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\tif (!PROD) {\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\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 restoreScript = (\n\tparams: Array<[string, ThemeStoreConfig, Listener<any>]>,\n) => {\n\tparams.forEach(([key, config, handler]) => {\n\t\tconst persistedThemes =\n\t\t\tJSON.parse(localStorage.getItem(key) || '{}').themes ?? {}\n\n\t\thandler(\n\t\t\tObject.entries(config).reduce<{\n\t\t\t\tthemes: Record<string, ThemeValue>\n\t\t\t\tresolvedThemes: Record<string, ThemeValue>\n\t\t\t}>(\n\t\t\t\t(acc, [themeKey, themeConfig]) => {\n\t\t\t\t\tconst options = themeConfig.options\n\n\t\t\t\t\tconst firstOption = options?.[0]\n\n\t\t\t\t\tconst currentValue =\n\t\t\t\t\t\tpersistedThemes[themeKey] ??\n\t\t\t\t\t\tthemeConfig.initialValue ??\n\t\t\t\t\t\t(typeof firstOption === 'object' ? firstOption.value : firstOption!)\n\n\t\t\t\t\tacc.resolvedThemes[themeKey] = acc.themes[themeKey] = currentValue\n\n\t\t\t\t\tconst maybeSystemOption = options?.find(\n\t\t\t\t\t\t(option): option is Required<ThemeOption> =>\n\t\t\t\t\t\t\ttypeof option === 'object' &&\n\t\t\t\t\t\t\t!!option.media &&\n\t\t\t\t\t\t\toption.value === currentValue,\n\t\t\t\t\t)\n\n\t\t\t\t\tif (maybeSystemOption) {\n\t\t\t\t\t\tconst [mediaQuery, ifMatch, ifNotMatch] = maybeSystemOption.media\n\n\t\t\t\t\t\tacc.resolvedThemes[themeKey] = matchMedia(mediaQuery).matches\n\t\t\t\t\t\t\t? ifMatch\n\t\t\t\t\t\t\t: ifNotMatch\n\t\t\t\t\t}\n\n\t\t\t\t\treturn acc\n\t\t\t\t},\n\t\t\t\t{ themes: {}, resolvedThemes: {} },\n\t\t\t),\n\t\t)\n\t})\n}\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: {\n * colorMode: { options: ['light', 'dark'] },\n * },\n * handler: ({ resolvedThemes }) => {\n * document.documentElement.dataset.colorMode = String(\n * resolvedThemes.colorMode,\n * )\n * },\n * },\n * ])\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`[${JSON.stringify(key)},${JSON.stringify(config)},${handler}]`,\n\t)\n\n\treturn `(${restoreScript})([${serializedThemeScriptParameters.join(',')}])`\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonare",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Resonare",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theming",
|
|
@@ -34,21 +34,21 @@
|
|
|
34
34
|
"react-compiler-runtime": "1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@rolldown/plugin-babel": "0.2.
|
|
38
|
-
"@size-limit/preset-small-lib": "12.0
|
|
39
|
-
"@types/node": "25.
|
|
37
|
+
"@rolldown/plugin-babel": "0.2.3",
|
|
38
|
+
"@size-limit/preset-small-lib": "12.1.0",
|
|
39
|
+
"@types/node": "25.6.0",
|
|
40
40
|
"@types/react": "19.2.14",
|
|
41
41
|
"@vitejs/plugin-react": "6.0.1",
|
|
42
42
|
"babel-plugin-react-compiler": "1.0.0",
|
|
43
43
|
"cross-env": "10.1.0",
|
|
44
|
-
"jsdom": "29.0.
|
|
45
|
-
"react": "19.2.
|
|
46
|
-
"react-dom": "19.2.
|
|
47
|
-
"size-limit": "12.0
|
|
48
|
-
"tsdown": "0.21.
|
|
49
|
-
"typescript": "6.0.
|
|
50
|
-
"vite": "8.0.
|
|
51
|
-
"vitest": "4.1.
|
|
44
|
+
"jsdom": "29.0.2",
|
|
45
|
+
"react": "19.2.5",
|
|
46
|
+
"react-dom": "19.2.5",
|
|
47
|
+
"size-limit": "12.1.0",
|
|
48
|
+
"tsdown": "0.21.9",
|
|
49
|
+
"typescript": "6.0.3",
|
|
50
|
+
"vite": "8.0.9",
|
|
51
|
+
"vitest": "4.1.5"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"react": "^18.0.0 || ^19.0.0"
|