@stencil/store 1.4.1 → 2.0.0
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 +75 -2
- package/dist/index.js +44 -29
- package/dist/index.mjs +44 -29
- package/dist/observable-map.d.ts +3 -1
- package/dist/subscriptions/stencil.d.ts +2 -2
- package/dist/types.d.ts +2 -1
- package/package.json +18 -13
package/README.md
CHANGED
|
@@ -77,9 +77,53 @@ const MyGlobalCounter = () => {
|
|
|
77
77
|
|
|
78
78
|
## API
|
|
79
79
|
|
|
80
|
-
### `createStore<T>(initialState?: T, shouldUpdate?)`
|
|
80
|
+
### `createStore<T>(initialState?: T | (() => T), shouldUpdate?)`
|
|
81
81
|
|
|
82
82
|
Create a new store with the given initial state. The type is inferred from `initialState`, or can be passed as the generic type `T`.
|
|
83
|
+
`initialState` can be a function that returns the actual initial state. This feature is just in case you have deep objects that mutate
|
|
84
|
+
as otherwise we cannot keep track of those.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { reset, state } = createStore(() => ({
|
|
88
|
+
pageA: {
|
|
89
|
+
count: 1
|
|
90
|
+
},
|
|
91
|
+
pageB: {
|
|
92
|
+
count: 1
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
state.pageA.count = 2;
|
|
97
|
+
state.pageB.count = 3;
|
|
98
|
+
|
|
99
|
+
reset();
|
|
100
|
+
|
|
101
|
+
state.pageA.count; // 1
|
|
102
|
+
state.pageB.count; // 1
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Please, bear in mind that the object needs to be created inside the function, not just referenced. The following example won't work
|
|
106
|
+
as you might want it to, as the returned object is always the same one.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const object = {
|
|
110
|
+
pageA: {
|
|
111
|
+
count: 1
|
|
112
|
+
},
|
|
113
|
+
pageB: {
|
|
114
|
+
count: 1
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const { reset, state } = createStore(() => object);
|
|
118
|
+
|
|
119
|
+
state.pageA.count = 2;
|
|
120
|
+
state.pageB.count = 3;
|
|
121
|
+
|
|
122
|
+
reset();
|
|
123
|
+
|
|
124
|
+
state.pageA.count; // 2
|
|
125
|
+
state.pageB.count; // 3
|
|
126
|
+
```
|
|
83
127
|
|
|
84
128
|
By default, store performs a exact comparison (`===`) between the new value, and the previous one in order to prevent unnecessary rerenders, however, this behaviour can be changed by providing a `shouldUpdate` function through the second argument. When this function returns `false`, the value won't be updated. By providing a custom `shouldUpdate()` function, applications can create their own fine-grained change detection logic, beyond the default `===`. This may be useful for certain use-cases to avoid any expensive re-rendering.
|
|
85
129
|
|
|
@@ -93,7 +137,7 @@ Returns a `store` object with the following properties.
|
|
|
93
137
|
|
|
94
138
|
#### `store.state`
|
|
95
139
|
|
|
96
|
-
The state object is proxied, i. e. you can directly get and set properties
|
|
140
|
+
The state object is proxied, i. e. you can directly get and set properties. If you access the state object in the `render` function of your component, Store will automatically re-render it when the state object is changed.
|
|
97
141
|
|
|
98
142
|
Note: [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects are not supported by IE11 (not even with a polyfill), so you need to use the `store.get` and `store.set` methods of the API if you wish to support IE11.
|
|
99
143
|
|
|
@@ -121,6 +165,35 @@ Reset the store to its initial state.
|
|
|
121
165
|
|
|
122
166
|
Use the given subscriptions in the store. A subscription is an object that defines one or more of the properties `get`, `set` or `reset`.
|
|
123
167
|
|
|
168
|
+
```ts
|
|
169
|
+
const { reset, state, use } = createStore({ a: 1, b: 2});
|
|
170
|
+
|
|
171
|
+
const unlog = use({
|
|
172
|
+
get: (key) => {
|
|
173
|
+
console.log(`Someone's reading prop ${key}`);
|
|
174
|
+
},
|
|
175
|
+
set: (key, newValue, oldValue) => {
|
|
176
|
+
console.log(`Prop ${key} changed from ${oldValue} to ${newValue}`);
|
|
177
|
+
},
|
|
178
|
+
reset: () => {
|
|
179
|
+
console.log('Store got reset');
|
|
180
|
+
},
|
|
181
|
+
dispose: () => {
|
|
182
|
+
console.log('Store got disposed');
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
state.a; // Someone's reading prop a
|
|
187
|
+
state.b = 3; // Prop b changed from 2 to 3
|
|
188
|
+
reset(); // Store got reset
|
|
189
|
+
|
|
190
|
+
unlog();
|
|
191
|
+
|
|
192
|
+
state.a; // Nothing is logged
|
|
193
|
+
state.b = 5; // Nothing is logged
|
|
194
|
+
reset(); // Nothing is logged
|
|
195
|
+
```
|
|
196
|
+
|
|
124
197
|
#### `store.dispose()`
|
|
125
198
|
|
|
126
199
|
Resets the store and all the internal state of the store that should not survive between tests.
|
package/dist/index.js
CHANGED
|
@@ -41,36 +41,39 @@ const cleanupElements = debounce((map) => {
|
|
|
41
41
|
map.set(key, map.get(key).filter(isConnected));
|
|
42
42
|
}
|
|
43
43
|
}, 2000);
|
|
44
|
-
const stencilSubscription = (
|
|
45
|
-
|
|
46
|
-
if (typeof core.getRenderingRef === 'function') {
|
|
44
|
+
const stencilSubscription = () => {
|
|
45
|
+
if (typeof core.getRenderingRef !== 'function') {
|
|
47
46
|
// If we are not in a stencil project, we do nothing.
|
|
48
47
|
// This function is not really exported by @stencil/core.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
const elmsToUpdate = new Map();
|
|
51
|
+
return {
|
|
52
|
+
dispose: () => elmsToUpdate.clear(),
|
|
53
|
+
get: (propName) => {
|
|
53
54
|
const elm = core.getRenderingRef();
|
|
54
55
|
if (elm) {
|
|
55
56
|
appendToMap(elmsToUpdate, propName, elm);
|
|
56
57
|
}
|
|
57
|
-
}
|
|
58
|
-
|
|
58
|
+
},
|
|
59
|
+
set: (propName) => {
|
|
59
60
|
const elements = elmsToUpdate.get(propName);
|
|
60
61
|
if (elements) {
|
|
61
62
|
elmsToUpdate.set(propName, elements.filter(core.forceUpdate));
|
|
62
63
|
}
|
|
63
64
|
cleanupElements(elmsToUpdate);
|
|
64
|
-
}
|
|
65
|
-
|
|
65
|
+
},
|
|
66
|
+
reset: () => {
|
|
66
67
|
elmsToUpdate.forEach((elms) => elms.forEach(core.forceUpdate));
|
|
67
68
|
cleanupElements(elmsToUpdate);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
70
71
|
};
|
|
71
72
|
|
|
73
|
+
const unwrap = (val) => (typeof val === 'function' ? val() : val);
|
|
72
74
|
const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) => {
|
|
73
|
-
|
|
75
|
+
const unwrappedState = unwrap(defaultState);
|
|
76
|
+
let states = new Map(Object.entries(unwrappedState !== null && unwrappedState !== void 0 ? unwrappedState : {}));
|
|
74
77
|
const handlers = {
|
|
75
78
|
dispose: [],
|
|
76
79
|
get: [],
|
|
@@ -78,7 +81,10 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
78
81
|
reset: [],
|
|
79
82
|
};
|
|
80
83
|
const reset = () => {
|
|
81
|
-
|
|
84
|
+
var _a;
|
|
85
|
+
// When resetting the state, the default state may be a function - unwrap it to invoke it.
|
|
86
|
+
// otherwise, the state won't be properly reset
|
|
87
|
+
states = new Map(Object.entries((_a = unwrap(defaultState)) !== null && _a !== void 0 ? _a : {}));
|
|
82
88
|
handlers.reset.forEach((cb) => cb());
|
|
83
89
|
};
|
|
84
90
|
const dispose = () => {
|
|
@@ -100,7 +106,7 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
100
106
|
};
|
|
101
107
|
const state = (typeof Proxy === 'undefined'
|
|
102
108
|
? {}
|
|
103
|
-
: new Proxy(
|
|
109
|
+
: new Proxy(unwrappedState, {
|
|
104
110
|
get(_, propName) {
|
|
105
111
|
return get(propName);
|
|
106
112
|
},
|
|
@@ -133,23 +139,32 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
133
139
|
cb(newValue);
|
|
134
140
|
}
|
|
135
141
|
});
|
|
136
|
-
|
|
142
|
+
// We need to unwrap the defaultState because it might be a function.
|
|
143
|
+
// Otherwise we might not be sending the right reset value.
|
|
144
|
+
const unReset = on('reset', () => cb(unwrap(defaultState)[propName]));
|
|
137
145
|
return () => {
|
|
138
146
|
unSet();
|
|
139
147
|
unReset();
|
|
140
148
|
};
|
|
141
149
|
};
|
|
142
|
-
const use = (...subscriptions) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
const use = (...subscriptions) => {
|
|
151
|
+
const unsubs = subscriptions.reduce((unsubs, subscription) => {
|
|
152
|
+
if (subscription.set) {
|
|
153
|
+
unsubs.push(on('set', subscription.set));
|
|
154
|
+
}
|
|
155
|
+
if (subscription.get) {
|
|
156
|
+
unsubs.push(on('get', subscription.get));
|
|
157
|
+
}
|
|
158
|
+
if (subscription.reset) {
|
|
159
|
+
unsubs.push(on('reset', subscription.reset));
|
|
160
|
+
}
|
|
161
|
+
if (subscription.dispose) {
|
|
162
|
+
unsubs.push(on('dispose', subscription.dispose));
|
|
163
|
+
}
|
|
164
|
+
return unsubs;
|
|
165
|
+
}, []);
|
|
166
|
+
return () => unsubs.forEach((unsub) => unsub());
|
|
167
|
+
};
|
|
153
168
|
const forceUpdate = (key) => {
|
|
154
169
|
const oldValue = states.get(key);
|
|
155
170
|
handlers.set.forEach((cb) => cb(key, oldValue, oldValue));
|
|
@@ -176,7 +191,7 @@ const removeFromArray = (array, item) => {
|
|
|
176
191
|
|
|
177
192
|
const createStore = (defaultState, shouldUpdate) => {
|
|
178
193
|
const map = createObservableMap(defaultState, shouldUpdate);
|
|
179
|
-
stencilSubscription(
|
|
194
|
+
map.use(stencilSubscription());
|
|
180
195
|
return map;
|
|
181
196
|
};
|
|
182
197
|
|
package/dist/index.mjs
CHANGED
|
@@ -37,36 +37,39 @@ const cleanupElements = debounce((map) => {
|
|
|
37
37
|
map.set(key, map.get(key).filter(isConnected));
|
|
38
38
|
}
|
|
39
39
|
}, 2000);
|
|
40
|
-
const stencilSubscription = (
|
|
41
|
-
|
|
42
|
-
if (typeof getRenderingRef === 'function') {
|
|
40
|
+
const stencilSubscription = () => {
|
|
41
|
+
if (typeof getRenderingRef !== 'function') {
|
|
43
42
|
// If we are not in a stencil project, we do nothing.
|
|
44
43
|
// This function is not really exported by @stencil/core.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
const elmsToUpdate = new Map();
|
|
47
|
+
return {
|
|
48
|
+
dispose: () => elmsToUpdate.clear(),
|
|
49
|
+
get: (propName) => {
|
|
49
50
|
const elm = getRenderingRef();
|
|
50
51
|
if (elm) {
|
|
51
52
|
appendToMap(elmsToUpdate, propName, elm);
|
|
52
53
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
54
|
+
},
|
|
55
|
+
set: (propName) => {
|
|
55
56
|
const elements = elmsToUpdate.get(propName);
|
|
56
57
|
if (elements) {
|
|
57
58
|
elmsToUpdate.set(propName, elements.filter(forceUpdate));
|
|
58
59
|
}
|
|
59
60
|
cleanupElements(elmsToUpdate);
|
|
60
|
-
}
|
|
61
|
-
|
|
61
|
+
},
|
|
62
|
+
reset: () => {
|
|
62
63
|
elmsToUpdate.forEach((elms) => elms.forEach(forceUpdate));
|
|
63
64
|
cleanupElements(elmsToUpdate);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
66
67
|
};
|
|
67
68
|
|
|
69
|
+
const unwrap = (val) => (typeof val === 'function' ? val() : val);
|
|
68
70
|
const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) => {
|
|
69
|
-
|
|
71
|
+
const unwrappedState = unwrap(defaultState);
|
|
72
|
+
let states = new Map(Object.entries(unwrappedState !== null && unwrappedState !== void 0 ? unwrappedState : {}));
|
|
70
73
|
const handlers = {
|
|
71
74
|
dispose: [],
|
|
72
75
|
get: [],
|
|
@@ -74,7 +77,10 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
74
77
|
reset: [],
|
|
75
78
|
};
|
|
76
79
|
const reset = () => {
|
|
77
|
-
|
|
80
|
+
var _a;
|
|
81
|
+
// When resetting the state, the default state may be a function - unwrap it to invoke it.
|
|
82
|
+
// otherwise, the state won't be properly reset
|
|
83
|
+
states = new Map(Object.entries((_a = unwrap(defaultState)) !== null && _a !== void 0 ? _a : {}));
|
|
78
84
|
handlers.reset.forEach((cb) => cb());
|
|
79
85
|
};
|
|
80
86
|
const dispose = () => {
|
|
@@ -96,7 +102,7 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
96
102
|
};
|
|
97
103
|
const state = (typeof Proxy === 'undefined'
|
|
98
104
|
? {}
|
|
99
|
-
: new Proxy(
|
|
105
|
+
: new Proxy(unwrappedState, {
|
|
100
106
|
get(_, propName) {
|
|
101
107
|
return get(propName);
|
|
102
108
|
},
|
|
@@ -129,23 +135,32 @@ const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) =>
|
|
|
129
135
|
cb(newValue);
|
|
130
136
|
}
|
|
131
137
|
});
|
|
132
|
-
|
|
138
|
+
// We need to unwrap the defaultState because it might be a function.
|
|
139
|
+
// Otherwise we might not be sending the right reset value.
|
|
140
|
+
const unReset = on('reset', () => cb(unwrap(defaultState)[propName]));
|
|
133
141
|
return () => {
|
|
134
142
|
unSet();
|
|
135
143
|
unReset();
|
|
136
144
|
};
|
|
137
145
|
};
|
|
138
|
-
const use = (...subscriptions) =>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
const use = (...subscriptions) => {
|
|
147
|
+
const unsubs = subscriptions.reduce((unsubs, subscription) => {
|
|
148
|
+
if (subscription.set) {
|
|
149
|
+
unsubs.push(on('set', subscription.set));
|
|
150
|
+
}
|
|
151
|
+
if (subscription.get) {
|
|
152
|
+
unsubs.push(on('get', subscription.get));
|
|
153
|
+
}
|
|
154
|
+
if (subscription.reset) {
|
|
155
|
+
unsubs.push(on('reset', subscription.reset));
|
|
156
|
+
}
|
|
157
|
+
if (subscription.dispose) {
|
|
158
|
+
unsubs.push(on('dispose', subscription.dispose));
|
|
159
|
+
}
|
|
160
|
+
return unsubs;
|
|
161
|
+
}, []);
|
|
162
|
+
return () => unsubs.forEach((unsub) => unsub());
|
|
163
|
+
};
|
|
149
164
|
const forceUpdate = (key) => {
|
|
150
165
|
const oldValue = states.get(key);
|
|
151
166
|
handlers.set.forEach((cb) => cb(key, oldValue, oldValue));
|
|
@@ -172,7 +187,7 @@ const removeFromArray = (array, item) => {
|
|
|
172
187
|
|
|
173
188
|
const createStore = (defaultState, shouldUpdate) => {
|
|
174
189
|
const map = createObservableMap(defaultState, shouldUpdate);
|
|
175
|
-
stencilSubscription(
|
|
190
|
+
map.use(stencilSubscription());
|
|
176
191
|
return map;
|
|
177
192
|
};
|
|
178
193
|
|
package/dist/observable-map.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ObservableMap } from './types';
|
|
2
|
+
declare type Invocable<T> = T | (() => T);
|
|
2
3
|
export declare const createObservableMap: <T extends {
|
|
3
4
|
[key: string]: any;
|
|
4
|
-
}>(defaultState?: T
|
|
5
|
+
}>(defaultState?: Invocable<T>, shouldUpdate?: (newV: any, oldValue: any, prop: keyof T) => boolean) => ObservableMap<T>;
|
|
6
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const stencilSubscription: <T>(
|
|
1
|
+
import { Subscription } from '../types';
|
|
2
|
+
export declare const stencilSubscription: <T>() => Subscription<T>;
|
package/dist/types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface OnChangeHandler<StoreType> {
|
|
|
18
18
|
<Key extends keyof StoreType>(propName: Key, cb: (newValue: StoreType[Key]) => void): () => void;
|
|
19
19
|
}
|
|
20
20
|
export interface Subscription<StoreType> {
|
|
21
|
+
dispose?(): void;
|
|
21
22
|
get?<KeyFromStoreType extends keyof StoreType>(key: KeyFromStoreType): void;
|
|
22
23
|
set?<KeyFromStoreType extends keyof StoreType>(key: KeyFromStoreType, newValue: StoreType[KeyFromStoreType], oldValue: StoreType[KeyFromStoreType]): void;
|
|
23
24
|
reset?(): void;
|
|
@@ -88,7 +89,7 @@ export interface ObservableMap<T> {
|
|
|
88
89
|
* Registers a subscription that will be called whenever the user gets, sets, or
|
|
89
90
|
* resets a value.
|
|
90
91
|
*/
|
|
91
|
-
use(...plugins: Subscription<T>[]): void;
|
|
92
|
+
use(...plugins: Subscription<T>[]): () => void;
|
|
92
93
|
/**
|
|
93
94
|
* Force a rerender of the specified key, just like the value changed.
|
|
94
95
|
*/
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stencil/store",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Store is a lightweight shared state library by the StencilJS core team. Implements a simple key/value map that efficiently re-renders components when necessary.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "rm -rf dist && tsc -p . && npm run rollup",
|
|
10
|
-
"
|
|
10
|
+
"prettier": "prettier --write 'src/**/*.ts'",
|
|
11
|
+
"prettier.dry-run": "prettier --check 'src/**/*.ts'",
|
|
11
12
|
"release": "np",
|
|
12
13
|
"rollup": "rollup -c rollup.config.js",
|
|
13
14
|
"test": "jest",
|
|
14
|
-
"test.ci": "npm run test && npm run
|
|
15
|
-
"test.prettier": "prettier --check 'src/**/*.ts'",
|
|
15
|
+
"test.ci": "npm run test && npm run prettier.dry-run",
|
|
16
16
|
"version": "npm run build"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
@@ -26,24 +26,29 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
|
-
"author": "
|
|
29
|
+
"author": "Ionic Team",
|
|
30
30
|
"license": "MIT",
|
|
31
|
+
"homepage": "https://stenciljs.com/docs/stencil-store",
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"@stencil/core": ">=
|
|
33
|
+
"@stencil/core": ">=2.0.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@stencil/core": "2.
|
|
36
|
-
"@types/jest": "^
|
|
37
|
-
"jest": "
|
|
38
|
-
"jest-cli": "
|
|
36
|
+
"@stencil/core": "^2.8.0",
|
|
37
|
+
"@types/jest": "^28.1.1",
|
|
38
|
+
"jest": "^28.1.1",
|
|
39
|
+
"jest-cli": "^28.1.1",
|
|
39
40
|
"np": "^7.4.0",
|
|
40
41
|
"prettier": "^2.2.1",
|
|
41
42
|
"rollup": "^2.39.0",
|
|
42
|
-
"ts-jest": "^
|
|
43
|
-
"typescript": "^4.
|
|
43
|
+
"ts-jest": "^28.0.4",
|
|
44
|
+
"typescript": "^4.7.3"
|
|
44
45
|
},
|
|
45
46
|
"repository": {
|
|
46
47
|
"type": "git",
|
|
47
|
-
"url": "git://github.com/
|
|
48
|
+
"url": "git://github.com/ionic-team/stencil-store.git"
|
|
49
|
+
},
|
|
50
|
+
"volta": {
|
|
51
|
+
"node": "16.15.0",
|
|
52
|
+
"npm": "8.11.0"
|
|
48
53
|
}
|
|
49
54
|
}
|