@friendofsvelte/state 0.0.5 → 0.0.6

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
@@ -19,78 +19,65 @@ npm install @friendofsvelte/state
19
19
 
20
20
  ## Quick Start
21
21
 
22
- 1. Define your types in your app.d.ts:
22
+ 1. Define your state using `PersistentState`:
23
23
 
24
24
  ```typescript
25
- declare global {
26
- interface PodTypeRegistry {
27
- layout: {
28
- bg: string;
29
- };
30
- userSettings: {
31
- theme: 'light' | 'dark';
32
- fontSize: number;
33
- };
34
- }
35
- }
25
+ // new.svelte.ts / js
26
+ import { PersistentState } from '@friendofsvelte/state';
36
27
 
37
- export {};
28
+ export const box = new PersistentState('box', {
29
+ color: '#ff3e00',
30
+ dimensions: [100, 100]
31
+ }, 'sessionStorage');
38
32
  ```
39
33
 
40
34
  2. Use in your components:
41
35
 
42
36
  ```svelte
43
37
  <script lang="ts">
44
- import { pod } from '@friendofsvelte/state';
45
-
46
- // Initialize with value
47
- let app = pod('layout', 'localStorage', {
48
- bg: 'lightblue'
49
- });
50
-
51
- // Or use existing value
52
- let settings = pod('userSettings', 'sessionStorage');
38
+ import { box } from '$lib/new.svelte';
39
+
40
+ const listColors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown'];
41
+
42
+ function switchNextColor() {
43
+ const currentIndex = listColors.indexOf(box.current.color);
44
+ const nextIndex = currentIndex + 1;
45
+ if (nextIndex >= listColors.length) {
46
+ box.current.color = listColors[0];
47
+ } else {
48
+ box.current.color = listColors[nextIndex];
49
+ }
50
+ }
53
51
  </script>
54
52
 
55
- <div style="background-color: {app.bg}">
56
- <!-- Your content -->
53
+ <div
54
+ style="background-color: {box.current.color}; width: 100px; height: 100px; color: gray; text-align: center;"
55
+ class="m-2 rounded-2xl"
56
+ >
57
+ {box.current.color}
57
58
  </div>
59
+
60
+ <button onclick={switchNextColor} class="bg-gray-700 m-2 px-3 rounded-2xl text-gray-200">
61
+ Change color
62
+ </button>
58
63
  ```
59
64
 
60
65
  ## API Reference
61
66
 
62
- ### `pod<K>(key: K, storage: StorageType, context?: GetTypeFromRegistry<K>, override?: boolean)`
67
+ ### `PersistentState<T>(key: string, initial?: T, storageType: StorageType = 'localStorage')`
63
68
 
64
69
  Creates or retrieves a persistent state container.
65
70
 
66
71
  Parameters:
67
72
  - `key`: Unique identifier for the state container
68
- - `storage`: Storage type - 'localStorage' or 'sessionStorage'
69
- - `context`: (Optional) Initial state value
70
- - If `override` is `true`, it writes the `context` to storage regardless of the current stored value.
71
- - If `override` is `false` and there is no stored value, it writes the `context` to storage.
72
- - If `override` is `false` and there is a stored value, it uses the stored value instead of the `context`.
73
-
74
- The `pod` function passes the `override` parameter to the `track` function.
73
+ - `initial`: (Optional) Initial state value
74
+ - `storageType`: (Optional) Storage type - 'localStorage' or 'sessionStorage' (default: 'localStorage')
75
75
 
76
76
  Returns:
77
- - A reactive state object of type `GetTypeFromRegistry<K>`
77
+ - A reactive state object of type `T`
78
78
 
79
- ## Type Safety
79
+ > Inspired by: Rich-Harris' [local-storage-test](https://github.com/Rich-Harris/local-storage-test/blob/main/src/lib/storage.svelte.ts)
80
80
 
81
- Pod State provides complete type safety through TypeScript. The global `PodTypeRegistry` interface allows you to define types for all your state containers in one place:
82
-
83
- ```typescript
84
- interface PodTypeRegistry {
85
- layout: {
86
- bg: string;
87
- };
88
- userSettings: {
89
- theme: 'light' | 'dark';
90
- fontSize: number;
91
- };
92
- }
93
- ```
94
81
 
95
82
  ## Examples
96
83
 
@@ -98,35 +85,28 @@ interface PodTypeRegistry {
98
85
 
99
86
  ```svelte
100
87
  <script lang="ts">
101
- import { pod } from '@friendofsvelte/state';
102
-
103
- let app = pod('layout', 'localStorage', {
104
- bg: 'lightblue'
105
- });
106
- </script>
88
+ import { PersistentState } from '@friendofsvelte/state';
107
89
 
108
- <button onclick={() => app.bg = 'lightgreen'}>
109
- Change Background
110
- </button>
111
- ```
112
-
113
- ### Shared State
90
+ const box = new PersistentState('box', {
91
+ color: '#ff3e00',
92
+ dimensions: [100, 100]
93
+ }, 'sessionStorage');
114
94
 
115
- ```svelte
116
- <!-- ComponentA.svelte -->
117
- <script>
118
- import { pod } from '@friendofsvelte/state';
119
- let settings = pod('userSettings', 'sessionStorage');
95
+ function switchNextColor() {
96
+ const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown'];
97
+ const currentIndex = colors.indexOf(box.current.color);
98
+ box.current.color = colors[(currentIndex + 1) % colors.length];
99
+ }
120
100
  </script>
121
101
 
122
- <!-- ComponentB.svelte -->
123
- <script>
124
- import { pod } from '@friendofsvelte/state';
125
- let settings = pod('userSettings');
126
- // Will automatically sync with ComponentA
127
- </script>
128
- ```
102
+ <div style="background-color: {box.current.color}; width: 100px; height: 100px; color: gray; text-align: center;" class="m-2 rounded-2xl">
103
+ {box.current.color}
104
+ </div>
129
105
 
106
+ <button onclick={switchNextColor} class="bg-gray-700 m-2 px-3 rounded-2xl text-gray-200">
107
+ Change color
108
+ </button>
109
+ ```
130
110
 
131
111
  ## Contributing
132
112
 
@@ -134,4 +114,4 @@ Contributions are welcome! Please feel free to submit a [Pull Request](https://g
134
114
 
135
115
  ## License
136
116
 
137
- MIT License - see LICENSE file for details
117
+ MIT License - see LICENSE file for details
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { pod } from './state.svelte';
1
+ export { PersistentState } from './storage.svelte.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Reexport your entry components here
2
- export { pod } from './state.svelte';
2
+ export { PersistentState } from './storage.svelte.js';
@@ -0,0 +1,5 @@
1
+ import { PersistentState } from "./storage.svelte.js";
2
+ export declare const box: PersistentState<{
3
+ color: string;
4
+ dimensions: number[];
5
+ }>;
@@ -0,0 +1,5 @@
1
+ import { PersistentState } from "./storage.svelte.js";
2
+ export const box = new PersistentState('box', {
3
+ color: '#ff3e00',
4
+ dimensions: [100, 100]
5
+ }, 'sessionStorage');
@@ -0,0 +1,7 @@
1
+ export type StorageType = 'localStorage' | 'sessionStorage';
2
+ export declare class PersistentState<T> {
3
+ #private;
4
+ constructor(key: string, initial?: T, storageType?: StorageType);
5
+ get current(): any;
6
+ set current(value: any);
7
+ }
@@ -0,0 +1,80 @@
1
+ // See our inspiration: https://github.com/Rich-Harris/local-storage-test/blob/main/src/lib/storage.svelte.ts
2
+ import { tick } from 'svelte';
3
+ export class PersistentState {
4
+ #key;
5
+ #version = $state(0);
6
+ #listeners = 0;
7
+ #value;
8
+ #storage;
9
+ #handler = (e) => {
10
+ if (e.storageArea !== this.#storage)
11
+ return;
12
+ if (e.key !== this.#key)
13
+ return;
14
+ this.#version += 1;
15
+ };
16
+ constructor(key, initial, storageType = 'localStorage') {
17
+ this.#key = key;
18
+ this.#value = initial;
19
+ this.#storage = storageType === 'localStorage' ? localStorage : sessionStorage;
20
+ if (typeof this.#storage !== 'undefined') {
21
+ if (this.#storage.getItem(key) === null) {
22
+ this.#storage.setItem(key, JSON.stringify(initial));
23
+ }
24
+ }
25
+ }
26
+ get current() {
27
+ this.#version;
28
+ const root = typeof this.#storage !== 'undefined'
29
+ ? JSON.parse(this.#storage.getItem(this.#key))
30
+ : this.#value;
31
+ const proxies = new WeakMap();
32
+ const proxy = (value) => {
33
+ if (typeof value !== 'object' || value === null) {
34
+ return value;
35
+ }
36
+ let p = proxies.get(value);
37
+ if (!p) {
38
+ p = new Proxy(value, {
39
+ get: (target, property) => {
40
+ this.#version;
41
+ return proxy(Reflect.get(target, property));
42
+ },
43
+ set: (target, property, value) => {
44
+ this.#version += 1;
45
+ Reflect.set(target, property, value);
46
+ if (typeof this.#storage !== 'undefined') {
47
+ this.#storage.setItem(this.#key, JSON.stringify(root));
48
+ }
49
+ return true;
50
+ }
51
+ });
52
+ proxies.set(value, p);
53
+ }
54
+ return p;
55
+ };
56
+ if ($effect.tracking()) {
57
+ $effect(() => {
58
+ if (this.#listeners === 0) {
59
+ window.addEventListener('storage', this.#handler);
60
+ }
61
+ this.#listeners += 1;
62
+ return () => {
63
+ tick().then(() => {
64
+ this.#listeners -= 1;
65
+ if (this.#listeners === 0) {
66
+ window.removeEventListener('storage', this.#handler);
67
+ }
68
+ });
69
+ };
70
+ });
71
+ }
72
+ return proxy(root);
73
+ }
74
+ set current(value) {
75
+ if (typeof this.#storage !== 'undefined') {
76
+ this.#storage.setItem(this.#key, JSON.stringify(value));
77
+ }
78
+ this.#version += 1;
79
+ }
80
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@friendofsvelte/state",
3
- "version": "0.0.5",
3
+ "description": "Persistent Svelte 5 State, localStorage & sessionStorage",
4
+ "version": "0.0.6",
4
5
  "scripts": {
5
6
  "dev": "vite dev",
6
7
  "build": "vite build && npm run prepack",
@@ -1,18 +0,0 @@
1
- type StorageType = 'localStorage' | 'sessionStorage';
2
- type TypeRegistry = {
3
- [K in PropertyKey]: unknown;
4
- };
5
- declare global {
6
- interface PodTypeRegistry extends TypeRegistry {
7
- }
8
- }
9
- type GetTypeFromRegistry<K extends keyof PodTypeRegistry> = PodTypeRegistry[K] extends never ? unknown : PodTypeRegistry[K];
10
- /**
11
- * Get a persistent state from storage, or initialize with an optional context.
12
- * @param key - The key to store the state.
13
- * @param storage - The storage type to use.
14
- * @param context - The initial state or override.
15
- * @param override - Whether to override the stored value with the provided context.
16
- */
17
- export declare function pod<K extends keyof PodTypeRegistry>(key: K, storage: StorageType, context?: GetTypeFromRegistry<K>, override?: boolean): GetTypeFromRegistry<K>;
18
- export {};
@@ -1,47 +0,0 @@
1
- import { getContext, setContext, untrack } from 'svelte';
2
- const EMPTY = Symbol('___empty____');
3
- function makeContextKey(key, storage) {
4
- return `${String(key)}__${storage}__pod`;
5
- }
6
- function track(key, storage, context, override = false) {
7
- const contextKey = makeContextKey(key, storage);
8
- if (context === EMPTY) {
9
- return getContext(contextKey);
10
- }
11
- let state = $state(context);
12
- if (typeof window !== 'undefined') {
13
- const storedValue = untrack(() => window[storage].getItem(String(key)));
14
- if (storedValue && !override) {
15
- try {
16
- state = JSON.parse(storedValue);
17
- }
18
- catch {
19
- state = {};
20
- }
21
- }
22
- else if (override || !storedValue) {
23
- state = context ?? {};
24
- const json = JSON.stringify($state.snapshot(state));
25
- window[storage].setItem(String(key), json);
26
- }
27
- if (context !== EMPTY) {
28
- $effect.pre(() => {
29
- const json = JSON.stringify($state.snapshot(state));
30
- window[storage].setItem(String(key), json);
31
- });
32
- }
33
- }
34
- return setContext(contextKey, state);
35
- }
36
- /**
37
- * Get a persistent state from storage, or initialize with an optional context.
38
- * @param key - The key to store the state.
39
- * @param storage - The storage type to use.
40
- * @param context - The initial state or override.
41
- * @param override - Whether to override the stored value with the provided context.
42
- */
43
- export function pod(key, storage,
44
- // @ts-expect-error: To allow the context to be optional
45
- context = EMPTY, override = false) {
46
- return track(key, storage, context, override);
47
- }