@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 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 and Store will automatically take care of component re-rendering when the state object is changed.
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 = ({ on }) => {
45
- const elmsToUpdate = new Map();
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
- on('dispose', () => {
50
- elmsToUpdate.clear();
51
- });
52
- on('get', (propName) => {
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
- on('set', (propName) => {
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
- on('reset', () => {
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
- let states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
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
- states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
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(defaultState, {
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
- const unReset = on('reset', () => cb(defaultState[propName]));
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) => subscriptions.forEach((subscription) => {
143
- if (subscription.set) {
144
- on('set', subscription.set);
145
- }
146
- if (subscription.get) {
147
- on('get', subscription.get);
148
- }
149
- if (subscription.reset) {
150
- on('reset', subscription.reset);
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(map);
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 = ({ on }) => {
41
- const elmsToUpdate = new Map();
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
- on('dispose', () => {
46
- elmsToUpdate.clear();
47
- });
48
- on('get', (propName) => {
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
- on('set', (propName) => {
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
- on('reset', () => {
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
- let states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
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
- states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
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(defaultState, {
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
- const unReset = on('reset', () => cb(defaultState[propName]));
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) => subscriptions.forEach((subscription) => {
139
- if (subscription.set) {
140
- on('set', subscription.set);
141
- }
142
- if (subscription.get) {
143
- on('get', subscription.get);
144
- }
145
- if (subscription.reset) {
146
- on('reset', subscription.reset);
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(map);
190
+ map.use(stencilSubscription());
176
191
  return map;
177
192
  };
178
193
 
@@ -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, shouldUpdate?: (newV: any, oldValue: any, prop: keyof T) => boolean) => ObservableMap<T>;
5
+ }>(defaultState?: Invocable<T>, shouldUpdate?: (newV: any, oldValue: any, prop: keyof T) => boolean) => ObservableMap<T>;
6
+ export {};
@@ -1,2 +1,2 @@
1
- import { ObservableMap } from '../types';
2
- export declare const stencilSubscription: <T>({ on }: ObservableMap<T>) => void;
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": "1.4.1",
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
- "lint.prettier": "prettier --write 'src/**/*.ts'",
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 test.prettier",
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": "Manu Mtz.-Almeida",
29
+ "author": "Ionic Team",
30
30
  "license": "MIT",
31
+ "homepage": "https://stenciljs.com/docs/stencil-store",
31
32
  "peerDependencies": {
32
- "@stencil/core": ">=1.9.0"
33
+ "@stencil/core": ">=2.0.0"
33
34
  },
34
35
  "devDependencies": {
35
- "@stencil/core": "2.4.0",
36
- "@types/jest": "^24.9.1",
37
- "jest": "26.6.3",
38
- "jest-cli": "26.6.3",
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": "^26.5.1",
43
- "typescript": "^4.1.5"
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/manucorporat/stencil-store.git"
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
  }