@friendofsvelte/state 0.0.4 → 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 +52 -74
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/new.svelte.d.ts +5 -0
- package/dist/new.svelte.js +5 -0
- package/dist/storage.svelte.d.ts +7 -0
- package/dist/storage.svelte.js +80 -0
- package/package.json +6 -1
- package/dist/state.svelte.d.ts +0 -18
- package/dist/state.svelte.js +0 -47
package/README.md
CHANGED
|
@@ -19,73 +19,65 @@ npm install @friendofsvelte/state
|
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
|
-
1. Define your
|
|
22
|
+
1. Define your state using `PersistentState`:
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
|
-
|
|
26
|
-
|
|
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 {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
56
|
-
|
|
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
|
-
### `
|
|
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
|
-
- `
|
|
69
|
-
- `
|
|
73
|
+
- `initial`: (Optional) Initial state value
|
|
74
|
+
- `storageType`: (Optional) Storage type - 'localStorage' or 'sessionStorage' (default: 'localStorage')
|
|
70
75
|
|
|
71
76
|
Returns:
|
|
72
|
-
- A reactive state object of type `
|
|
73
|
-
|
|
74
|
-
## Type Safety
|
|
77
|
+
- A reactive state object of type `T`
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
> Inspired by: Rich-Harris' [local-storage-test](https://github.com/Rich-Harris/local-storage-test/blob/main/src/lib/storage.svelte.ts)
|
|
77
80
|
|
|
78
|
-
```typescript
|
|
79
|
-
interface PodTypeRegistry {
|
|
80
|
-
layout: {
|
|
81
|
-
bg: string;
|
|
82
|
-
};
|
|
83
|
-
userSettings: {
|
|
84
|
-
theme: 'light' | 'dark';
|
|
85
|
-
fontSize: number;
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
81
|
|
|
90
82
|
## Examples
|
|
91
83
|
|
|
@@ -93,47 +85,33 @@ interface PodTypeRegistry {
|
|
|
93
85
|
|
|
94
86
|
```svelte
|
|
95
87
|
<script lang="ts">
|
|
96
|
-
import {
|
|
97
|
-
|
|
98
|
-
let app = pod('layout', 'localStorage', {
|
|
99
|
-
bg: 'lightblue'
|
|
100
|
-
});
|
|
101
|
-
</script>
|
|
102
|
-
|
|
103
|
-
<button onclick={() => app.bg = 'lightgreen'}>
|
|
104
|
-
Change Background
|
|
105
|
-
</button>
|
|
106
|
-
```
|
|
88
|
+
import { PersistentState } from '@friendofsvelte/state';
|
|
107
89
|
|
|
108
|
-
|
|
90
|
+
const box = new PersistentState('box', {
|
|
91
|
+
color: '#ff3e00',
|
|
92
|
+
dimensions: [100, 100]
|
|
93
|
+
}, 'sessionStorage');
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</script>
|
|
116
|
-
|
|
117
|
-
<!-- ComponentB.svelte -->
|
|
118
|
-
<script>
|
|
119
|
-
import { pod } from '@friendofsvelte/state';
|
|
120
|
-
let settings = pod('userSettings');
|
|
121
|
-
// Will automatically sync with ComponentA
|
|
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
|
+
}
|
|
122
100
|
</script>
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Testing
|
|
126
101
|
|
|
127
|
-
|
|
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>
|
|
128
105
|
|
|
129
|
-
|
|
130
|
-
|
|
106
|
+
<button onclick={switchNextColor} class="bg-gray-700 m-2 px-3 rounded-2xl text-gray-200">
|
|
107
|
+
Change color
|
|
108
|
+
</button>
|
|
131
109
|
```
|
|
132
110
|
|
|
133
111
|
## Contributing
|
|
134
112
|
|
|
135
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
113
|
+
Contributions are welcome! Please feel free to submit a [Pull Request](https://github.com/friendofsvelte/state/pulls).
|
|
136
114
|
|
|
137
115
|
## License
|
|
138
116
|
|
|
139
|
-
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 {
|
|
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 {
|
|
2
|
+
export { PersistentState } from './storage.svelte.js';
|
|
@@ -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
|
-
"
|
|
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",
|
|
@@ -60,5 +61,9 @@
|
|
|
60
61
|
"typescript-eslint": "^8.20.0",
|
|
61
62
|
"vite": "^6.0.0",
|
|
62
63
|
"vitest": "^3.0.0"
|
|
64
|
+
},
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "https://github.com/friendofsvelte/state.git"
|
|
63
68
|
}
|
|
64
69
|
}
|
package/dist/state.svelte.d.ts
DELETED
|
@@ -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 {};
|
package/dist/state.svelte.js
DELETED
|
@@ -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
|
-
}
|