@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 +52 -72
- 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 +2 -1
- package/dist/state.svelte.d.ts +0 -18
- package/dist/state.svelte.js +0 -47
package/README.md
CHANGED
|
@@ -19,78 +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
|
-
- `
|
|
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 `
|
|
77
|
+
- A reactive state object of type `T`
|
|
78
78
|
|
|
79
|
-
|
|
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 {
|
|
102
|
-
|
|
103
|
-
let app = pod('layout', 'localStorage', {
|
|
104
|
-
bg: 'lightblue'
|
|
105
|
-
});
|
|
106
|
-
</script>
|
|
88
|
+
import { PersistentState } from '@friendofsvelte/state';
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
### Shared State
|
|
90
|
+
const box = new PersistentState('box', {
|
|
91
|
+
color: '#ff3e00',
|
|
92
|
+
dimensions: [100, 100]
|
|
93
|
+
}, 'sessionStorage');
|
|
114
94
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 {
|
|
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
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
|
-
}
|