@slimr/react 3.0.69 → 3.0.71
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 +18 -0
- package/cjs/Observable.d.ts +60 -0
- package/cjs/Observable.js +94 -0
- package/cjs/Observable.js.map +1 -0
- package/cjs/Observable.ts +110 -0
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +1 -0
- package/cjs/index.js.map +1 -1
- package/cjs/index.ts +1 -0
- package/esm/Observable.d.ts +60 -0
- package/esm/Observable.js +90 -0
- package/esm/Observable.js.map +1 -0
- package/esm/Observable.ts +110 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/index.ts +1 -0
- package/package.json +7 -7
- package/src/Observable.ts +110 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -46,6 +46,24 @@ const MyComponent = forwardRef((props, ref1) => {
|
|
|
46
46
|
})
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Observable
|
|
50
|
+
|
|
51
|
+
Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { Observable } from '@slimr/react';
|
|
55
|
+
const myObservable = new Observable('myObservable', 0);
|
|
56
|
+
myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
57
|
+
setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
58
|
+
setTimeout(() => { myObservable.set(50); }, 2000);
|
|
59
|
+
setTimeout(() => { myObservable.set(last => last + 1); }, 3000);
|
|
60
|
+
|
|
61
|
+
function MyComponent() {
|
|
62
|
+
myObservable.use();
|
|
63
|
+
return <div>{myObservable.value}</div>;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
49
67
|
### useDeepCompareMemo and useShallowCompareMemo
|
|
50
68
|
|
|
51
69
|
like react-use's useDeepEffects, but for memos
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
type ObservableSetter<T> = (value: T) => T;
|
|
2
|
+
/**
|
|
3
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
4
|
+
*
|
|
5
|
+
* @usage
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
8
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
9
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
10
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
11
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
12
|
+
*
|
|
13
|
+
* function MyComponent() {
|
|
14
|
+
* myObservable.use();
|
|
15
|
+
* return <div>{myObservable.value}</div>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class Observable<T> {
|
|
20
|
+
name: string;
|
|
21
|
+
private _value;
|
|
22
|
+
private subscribers;
|
|
23
|
+
constructor(name: string, initialValue: T);
|
|
24
|
+
publish(): Promise<void[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Sets a new value for the observable and notifies subscribers if the value changed.
|
|
27
|
+
*
|
|
28
|
+
* @param newValueOrSetter - The new value or a function that takes the current value and returns the new value.
|
|
29
|
+
* @returns The new value.
|
|
30
|
+
*/
|
|
31
|
+
set(setter: ObservableSetter<T>): Promise<void>;
|
|
32
|
+
set(newValue: T): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Register a callback to be called when the observable value changes.
|
|
35
|
+
* @param cb - The callback to be called when the value changes.
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
subscribe(cb: (value: T) => void): () => void;
|
|
39
|
+
unsubscribe(cb: (value: T) => void): void;
|
|
40
|
+
/**
|
|
41
|
+
* A React hook to use this observable in a component
|
|
42
|
+
*
|
|
43
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
44
|
+
* when the value changes.
|
|
45
|
+
*
|
|
46
|
+
* @usage
|
|
47
|
+
* ```tsx
|
|
48
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
49
|
+
*
|
|
50
|
+
* function MyComponent() {
|
|
51
|
+
* myObservable.use();
|
|
52
|
+
* return <div>{myObservable.value}</div>;
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
use(): void;
|
|
57
|
+
get val(): T;
|
|
58
|
+
set val(newValue: T);
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// this file has exports to create an observable and useObservable react hook
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Observable = void 0;
|
|
5
|
+
const util_1 = require("@slimr/util");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
// @ts-expect-error
|
|
8
|
+
globalThis.observables = {};
|
|
9
|
+
/**
|
|
10
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
11
|
+
*
|
|
12
|
+
* @usage
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
15
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
16
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
17
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
18
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
19
|
+
*
|
|
20
|
+
* function MyComponent() {
|
|
21
|
+
* myObservable.use();
|
|
22
|
+
* return <div>{myObservable.value}</div>;
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
class Observable {
|
|
27
|
+
name;
|
|
28
|
+
_value;
|
|
29
|
+
subscribers;
|
|
30
|
+
constructor(name, initialValue) {
|
|
31
|
+
this.name = name;
|
|
32
|
+
this._value = initialValue;
|
|
33
|
+
this.subscribers = new Set();
|
|
34
|
+
// @ts-expect-error
|
|
35
|
+
globalThis.observables[name] = this;
|
|
36
|
+
}
|
|
37
|
+
async publish() {
|
|
38
|
+
return Promise.all([...this.subscribers].map(subscriber => subscriber(this._value)));
|
|
39
|
+
}
|
|
40
|
+
async set(newValueOrSetter) {
|
|
41
|
+
const newValue = typeof newValueOrSetter === 'function'
|
|
42
|
+
? newValueOrSetter(this._value)
|
|
43
|
+
: newValueOrSetter;
|
|
44
|
+
if (!(0, util_1.areEqualDeep)(this._value, newValue)) {
|
|
45
|
+
this._value = newValue;
|
|
46
|
+
await this.publish();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a callback to be called when the observable value changes.
|
|
51
|
+
* @param cb - The callback to be called when the value changes.
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
subscribe(cb) {
|
|
55
|
+
// promisify the callback so it behaves like an async function even if it's not
|
|
56
|
+
// so that we can await all subscribers in publish()
|
|
57
|
+
const cbPromise = async (newValue) => cb(newValue);
|
|
58
|
+
this.subscribers.add(cbPromise);
|
|
59
|
+
return () => this.unsubscribe(cb);
|
|
60
|
+
}
|
|
61
|
+
unsubscribe(cb) {
|
|
62
|
+
this.subscribers.delete(cb);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A React hook to use this observable in a component
|
|
66
|
+
*
|
|
67
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
68
|
+
* when the value changes.
|
|
69
|
+
*
|
|
70
|
+
* @usage
|
|
71
|
+
* ```tsx
|
|
72
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
73
|
+
*
|
|
74
|
+
* function MyComponent() {
|
|
75
|
+
* myObservable.use();
|
|
76
|
+
* return <div>{myObservable.value}</div>;
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
use() {
|
|
81
|
+
const [_, setValue] = (0, react_1.useState)(this.val);
|
|
82
|
+
(0, react_1.useEffect)(() => this.subscribe(setValue), []);
|
|
83
|
+
}
|
|
84
|
+
get val() {
|
|
85
|
+
// Freeze the value to avoid cases where the internal object may be mutated
|
|
86
|
+
// without triggering subscribers.
|
|
87
|
+
return Object.freeze(this._value);
|
|
88
|
+
}
|
|
89
|
+
set val(newValue) {
|
|
90
|
+
this.set(newValue);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.Observable = Observable;
|
|
94
|
+
//# sourceMappingURL=Observable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Observable.js","sourceRoot":"","sources":["../src/Observable.ts"],"names":[],"mappings":";AAAA,6EAA6E;;;AAE7E,sCAA2C;AAC3C,iCAA4C;AAI5C,mBAAmB;AACnB,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC;AAE5B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,UAAU;IACrB,IAAI,CAAS;IACL,MAAM,CAAI;IACV,WAAW,CAA0B;IAE7C,YAAY,IAAY,EAAE,YAAe;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;QAC7B,mBAAmB;QACnB,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC;IAUD,KAAK,CAAC,GAAG,CAAC,gBAAqB;QAC7B,MAAM,QAAQ,GACZ,OAAO,gBAAgB,KAAK,UAAU;YACpC,CAAC,CAAE,gBAAwC,CAAC,IAAI,CAAC,MAAM,CAAC;YACxD,CAAC,CAAC,gBAAgB,CAAC;QACvB,IAAI,CAAC,IAAA,mBAAY,EAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;YACvB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,EAAsB;QAC9B,+EAA+E;QAC/E,oDAAoD;QACpD,MAAM,SAAS,GAAG,KAAK,EAAE,QAAW,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,WAAW,CAAC,EAAsB;QAChC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,GAAG;QACD,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,IAAA,iBAAS,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,GAAG;QACL,2EAA2E;QAC3E,kCAAkC;QAClC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,GAAG,CAAC,QAAW;QACjB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;CACF;AAlFD,gCAkFC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// this file has exports to create an observable and useObservable react hook
|
|
2
|
+
|
|
3
|
+
import { areEqualDeep } from '@slimr/util';
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
type ObservableSetter<T> = (value: T) => T;
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
globalThis.observables = {};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
13
|
+
*
|
|
14
|
+
* @usage
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
17
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
18
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
19
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
20
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
21
|
+
*
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* myObservable.use();
|
|
24
|
+
* return <div>{myObservable.value}</div>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class Observable<T> {
|
|
29
|
+
name: string;
|
|
30
|
+
private _value: T;
|
|
31
|
+
private subscribers: Set<(value: T) => void>;
|
|
32
|
+
|
|
33
|
+
constructor(name: string, initialValue: T) {
|
|
34
|
+
this.name = name;
|
|
35
|
+
this._value = initialValue;
|
|
36
|
+
this.subscribers = new Set();
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
globalThis.observables[name] = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async publish() {
|
|
42
|
+
return Promise.all([...this.subscribers].map(subscriber => subscriber(this._value)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sets a new value for the observable and notifies subscribers if the value changed.
|
|
47
|
+
*
|
|
48
|
+
* @param newValueOrSetter - The new value or a function that takes the current value and returns the new value.
|
|
49
|
+
* @returns The new value.
|
|
50
|
+
*/
|
|
51
|
+
async set(setter: ObservableSetter<T>): Promise<void>;
|
|
52
|
+
async set(newValue: T): Promise<void>;
|
|
53
|
+
async set(newValueOrSetter: any): Promise<void> {
|
|
54
|
+
const newValue =
|
|
55
|
+
typeof newValueOrSetter === 'function'
|
|
56
|
+
? (newValueOrSetter as ObservableSetter<T>)(this._value)
|
|
57
|
+
: newValueOrSetter;
|
|
58
|
+
if (!areEqualDeep(this._value, newValue)) {
|
|
59
|
+
this._value = newValue;
|
|
60
|
+
await this.publish();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a callback to be called when the observable value changes.
|
|
66
|
+
* @param cb - The callback to be called when the value changes.
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
subscribe(cb: (value: T) => void): () => void {
|
|
70
|
+
// promisify the callback so it behaves like an async function even if it's not
|
|
71
|
+
// so that we can await all subscribers in publish()
|
|
72
|
+
const cbPromise = async (newValue: T) => cb(newValue);
|
|
73
|
+
this.subscribers.add(cbPromise);
|
|
74
|
+
return () => this.unsubscribe(cb);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
unsubscribe(cb: (value: T) => void): void {
|
|
78
|
+
this.subscribers.delete(cb);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A React hook to use this observable in a component
|
|
83
|
+
*
|
|
84
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
85
|
+
* when the value changes.
|
|
86
|
+
*
|
|
87
|
+
* @usage
|
|
88
|
+
* ```tsx
|
|
89
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
90
|
+
*
|
|
91
|
+
* function MyComponent() {
|
|
92
|
+
* myObservable.use();
|
|
93
|
+
* return <div>{myObservable.value}</div>;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
use() {
|
|
98
|
+
const [_, setValue] = useState(this.val);
|
|
99
|
+
useEffect(() => this.subscribe(setValue), [])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get val(): T {
|
|
103
|
+
// Freeze the value to avoid cases where the internal object may be mutated
|
|
104
|
+
// without triggering subscribers.
|
|
105
|
+
return Object.freeze(this._value);
|
|
106
|
+
}
|
|
107
|
+
set val(newValue: T) {
|
|
108
|
+
this.set(newValue);
|
|
109
|
+
}
|
|
110
|
+
}
|
package/cjs/index.d.ts
CHANGED
package/cjs/index.js
CHANGED
|
@@ -23,5 +23,6 @@ __exportStar(require("react-use"), exports);
|
|
|
23
23
|
__exportStar(require("./merge-refs.js"), exports);
|
|
24
24
|
__exportStar(require("./useColorScheme.js"), exports);
|
|
25
25
|
__exportStar(require("./useMemos.js"), exports);
|
|
26
|
+
__exportStar(require("./Observable.js"), exports);
|
|
26
27
|
__exportStar(require("./useSet2.js"), exports);
|
|
27
28
|
//# sourceMappingURL=index.js.map
|
package/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA4B;AAC5B,kDAA+B;AAC/B,gDAA6B;AAC7B,gDAA6B;AAC7B,6CAA0B;AAC1B,4CAAyB;AAEzB,kDAA+B;AAC/B,sDAAmC;AACnC,gDAA6B;AAC7B,+CAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA4B;AAC5B,kDAA+B;AAC/B,gDAA6B;AAC7B,gDAA6B;AAC7B,6CAA0B;AAC1B,4CAAyB;AAEzB,kDAA+B;AAC/B,sDAAmC;AACnC,gDAA6B;AAC7B,kDAA+B;AAC/B,+CAA4B"}
|
package/cjs/index.ts
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
type ObservableSetter<T> = (value: T) => T;
|
|
2
|
+
/**
|
|
3
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
4
|
+
*
|
|
5
|
+
* @usage
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
8
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
9
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
10
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
11
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
12
|
+
*
|
|
13
|
+
* function MyComponent() {
|
|
14
|
+
* myObservable.use();
|
|
15
|
+
* return <div>{myObservable.value}</div>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class Observable<T> {
|
|
20
|
+
name: string;
|
|
21
|
+
private _value;
|
|
22
|
+
private subscribers;
|
|
23
|
+
constructor(name: string, initialValue: T);
|
|
24
|
+
publish(): Promise<void[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Sets a new value for the observable and notifies subscribers if the value changed.
|
|
27
|
+
*
|
|
28
|
+
* @param newValueOrSetter - The new value or a function that takes the current value and returns the new value.
|
|
29
|
+
* @returns The new value.
|
|
30
|
+
*/
|
|
31
|
+
set(setter: ObservableSetter<T>): Promise<void>;
|
|
32
|
+
set(newValue: T): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Register a callback to be called when the observable value changes.
|
|
35
|
+
* @param cb - The callback to be called when the value changes.
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
subscribe(cb: (value: T) => void): () => void;
|
|
39
|
+
unsubscribe(cb: (value: T) => void): void;
|
|
40
|
+
/**
|
|
41
|
+
* A React hook to use this observable in a component
|
|
42
|
+
*
|
|
43
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
44
|
+
* when the value changes.
|
|
45
|
+
*
|
|
46
|
+
* @usage
|
|
47
|
+
* ```tsx
|
|
48
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
49
|
+
*
|
|
50
|
+
* function MyComponent() {
|
|
51
|
+
* myObservable.use();
|
|
52
|
+
* return <div>{myObservable.value}</div>;
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
use(): void;
|
|
57
|
+
get val(): T;
|
|
58
|
+
set val(newValue: T);
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// this file has exports to create an observable and useObservable react hook
|
|
2
|
+
import { areEqualDeep } from '@slimr/util';
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
// @ts-expect-error
|
|
5
|
+
globalThis.observables = {};
|
|
6
|
+
/**
|
|
7
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
8
|
+
*
|
|
9
|
+
* @usage
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
12
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
13
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
14
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
15
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
16
|
+
*
|
|
17
|
+
* function MyComponent() {
|
|
18
|
+
* myObservable.use();
|
|
19
|
+
* return <div>{myObservable.value}</div>;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class Observable {
|
|
24
|
+
name;
|
|
25
|
+
_value;
|
|
26
|
+
subscribers;
|
|
27
|
+
constructor(name, initialValue) {
|
|
28
|
+
this.name = name;
|
|
29
|
+
this._value = initialValue;
|
|
30
|
+
this.subscribers = new Set();
|
|
31
|
+
// @ts-expect-error
|
|
32
|
+
globalThis.observables[name] = this;
|
|
33
|
+
}
|
|
34
|
+
async publish() {
|
|
35
|
+
return Promise.all([...this.subscribers].map(subscriber => subscriber(this._value)));
|
|
36
|
+
}
|
|
37
|
+
async set(newValueOrSetter) {
|
|
38
|
+
const newValue = typeof newValueOrSetter === 'function'
|
|
39
|
+
? newValueOrSetter(this._value)
|
|
40
|
+
: newValueOrSetter;
|
|
41
|
+
if (!areEqualDeep(this._value, newValue)) {
|
|
42
|
+
this._value = newValue;
|
|
43
|
+
await this.publish();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register a callback to be called when the observable value changes.
|
|
48
|
+
* @param cb - The callback to be called when the value changes.
|
|
49
|
+
* @returns
|
|
50
|
+
*/
|
|
51
|
+
subscribe(cb) {
|
|
52
|
+
// promisify the callback so it behaves like an async function even if it's not
|
|
53
|
+
// so that we can await all subscribers in publish()
|
|
54
|
+
const cbPromise = async (newValue) => cb(newValue);
|
|
55
|
+
this.subscribers.add(cbPromise);
|
|
56
|
+
return () => this.unsubscribe(cb);
|
|
57
|
+
}
|
|
58
|
+
unsubscribe(cb) {
|
|
59
|
+
this.subscribers.delete(cb);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A React hook to use this observable in a component
|
|
63
|
+
*
|
|
64
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
65
|
+
* when the value changes.
|
|
66
|
+
*
|
|
67
|
+
* @usage
|
|
68
|
+
* ```tsx
|
|
69
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
70
|
+
*
|
|
71
|
+
* function MyComponent() {
|
|
72
|
+
* myObservable.use();
|
|
73
|
+
* return <div>{myObservable.value}</div>;
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
use() {
|
|
78
|
+
const [_, setValue] = useState(this.val);
|
|
79
|
+
useEffect(() => this.subscribe(setValue), []);
|
|
80
|
+
}
|
|
81
|
+
get val() {
|
|
82
|
+
// Freeze the value to avoid cases where the internal object may be mutated
|
|
83
|
+
// without triggering subscribers.
|
|
84
|
+
return Object.freeze(this._value);
|
|
85
|
+
}
|
|
86
|
+
set val(newValue) {
|
|
87
|
+
this.set(newValue);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=Observable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Observable.js","sourceRoot":"","sources":["../src/Observable.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI5C,mBAAmB;AACnB,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC;AAE5B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,UAAU;IACrB,IAAI,CAAS;IACL,MAAM,CAAI;IACV,WAAW,CAA0B;IAE7C,YAAY,IAAY,EAAE,YAAe;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;QAC7B,mBAAmB;QACnB,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC;IAUD,KAAK,CAAC,GAAG,CAAC,gBAAqB;QAC7B,MAAM,QAAQ,GACZ,OAAO,gBAAgB,KAAK,UAAU;YACpC,CAAC,CAAE,gBAAwC,CAAC,IAAI,CAAC,MAAM,CAAC;YACxD,CAAC,CAAC,gBAAgB,CAAC;QACvB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;YACvB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,EAAsB;QAC9B,+EAA+E;QAC/E,oDAAoD;QACpD,MAAM,SAAS,GAAG,KAAK,EAAE,QAAW,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,WAAW,CAAC,EAAsB;QAChC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,GAAG;QACD,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,GAAG;QACL,2EAA2E;QAC3E,kCAAkC;QAClC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,GAAG,CAAC,QAAW;QACjB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// this file has exports to create an observable and useObservable react hook
|
|
2
|
+
|
|
3
|
+
import { areEqualDeep } from '@slimr/util';
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
type ObservableSetter<T> = (value: T) => T;
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
globalThis.observables = {};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
13
|
+
*
|
|
14
|
+
* @usage
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
17
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
18
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
19
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
20
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
21
|
+
*
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* myObservable.use();
|
|
24
|
+
* return <div>{myObservable.value}</div>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class Observable<T> {
|
|
29
|
+
name: string;
|
|
30
|
+
private _value: T;
|
|
31
|
+
private subscribers: Set<(value: T) => void>;
|
|
32
|
+
|
|
33
|
+
constructor(name: string, initialValue: T) {
|
|
34
|
+
this.name = name;
|
|
35
|
+
this._value = initialValue;
|
|
36
|
+
this.subscribers = new Set();
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
globalThis.observables[name] = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async publish() {
|
|
42
|
+
return Promise.all([...this.subscribers].map(subscriber => subscriber(this._value)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sets a new value for the observable and notifies subscribers if the value changed.
|
|
47
|
+
*
|
|
48
|
+
* @param newValueOrSetter - The new value or a function that takes the current value and returns the new value.
|
|
49
|
+
* @returns The new value.
|
|
50
|
+
*/
|
|
51
|
+
async set(setter: ObservableSetter<T>): Promise<void>;
|
|
52
|
+
async set(newValue: T): Promise<void>;
|
|
53
|
+
async set(newValueOrSetter: any): Promise<void> {
|
|
54
|
+
const newValue =
|
|
55
|
+
typeof newValueOrSetter === 'function'
|
|
56
|
+
? (newValueOrSetter as ObservableSetter<T>)(this._value)
|
|
57
|
+
: newValueOrSetter;
|
|
58
|
+
if (!areEqualDeep(this._value, newValue)) {
|
|
59
|
+
this._value = newValue;
|
|
60
|
+
await this.publish();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a callback to be called when the observable value changes.
|
|
66
|
+
* @param cb - The callback to be called when the value changes.
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
subscribe(cb: (value: T) => void): () => void {
|
|
70
|
+
// promisify the callback so it behaves like an async function even if it's not
|
|
71
|
+
// so that we can await all subscribers in publish()
|
|
72
|
+
const cbPromise = async (newValue: T) => cb(newValue);
|
|
73
|
+
this.subscribers.add(cbPromise);
|
|
74
|
+
return () => this.unsubscribe(cb);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
unsubscribe(cb: (value: T) => void): void {
|
|
78
|
+
this.subscribers.delete(cb);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A React hook to use this observable in a component
|
|
83
|
+
*
|
|
84
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
85
|
+
* when the value changes.
|
|
86
|
+
*
|
|
87
|
+
* @usage
|
|
88
|
+
* ```tsx
|
|
89
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
90
|
+
*
|
|
91
|
+
* function MyComponent() {
|
|
92
|
+
* myObservable.use();
|
|
93
|
+
* return <div>{myObservable.value}</div>;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
use() {
|
|
98
|
+
const [_, setValue] = useState(this.val);
|
|
99
|
+
useEffect(() => this.subscribe(setValue), [])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get val(): T {
|
|
103
|
+
// Freeze the value to avoid cases where the internal object may be mutated
|
|
104
|
+
// without triggering subscribers.
|
|
105
|
+
return Object.freeze(this._value);
|
|
106
|
+
}
|
|
107
|
+
set val(newValue: T) {
|
|
108
|
+
this.set(newValue);
|
|
109
|
+
}
|
|
110
|
+
}
|
package/esm/index.d.ts
CHANGED
package/esm/index.js
CHANGED
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AAEzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AAEzB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,qBAAqB,CAAA;AACnC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,cAAc,CAAA"}
|
package/esm/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slimr/react",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.71",
|
|
4
4
|
"author": "Brian Dombrowski",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"private": false,
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"start": "nodemon -w src -e '*' -x 'npm run build && cd ../demo && npm start'"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@slimr/forms": "^5.0.
|
|
35
|
-
"@slimr/markdown": "^2.1.
|
|
36
|
-
"@slimr/router": "^2.1.
|
|
37
|
-
"@slimr/styled": "^2.1.
|
|
38
|
-
"@slimr/swr": "^2.1.
|
|
39
|
-
"@slimr/util": "^3.2.
|
|
34
|
+
"@slimr/forms": "^5.0.45",
|
|
35
|
+
"@slimr/markdown": "^2.1.87",
|
|
36
|
+
"@slimr/router": "^2.1.73",
|
|
37
|
+
"@slimr/styled": "^2.1.84",
|
|
38
|
+
"@slimr/swr": "^2.1.72",
|
|
39
|
+
"@slimr/util": "^3.2.73",
|
|
40
40
|
"react-use": "17"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// this file has exports to create an observable and useObservable react hook
|
|
2
|
+
|
|
3
|
+
import { areEqualDeep } from '@slimr/util';
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
type ObservableSetter<T> = (value: T) => T;
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
globalThis.observables = {};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Provides a variable with observable capabilities, like pub/sub and React hook integration.
|
|
13
|
+
*
|
|
14
|
+
* @usage
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
17
|
+
* myObservable.subscribe((newValue) => { console.log('New value:', newValue); });
|
|
18
|
+
* setTimeout(() => { myObservable.val = 42; }, 1000);
|
|
19
|
+
* setTimeout(() => { myObservable.set(50); }, 2000);
|
|
20
|
+
* setTimeout(() => { myObservable.set(last => last + 1); }, 1000);
|
|
21
|
+
*
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* myObservable.use();
|
|
24
|
+
* return <div>{myObservable.value}</div>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class Observable<T> {
|
|
29
|
+
name: string;
|
|
30
|
+
private _value: T;
|
|
31
|
+
private subscribers: Set<(value: T) => void>;
|
|
32
|
+
|
|
33
|
+
constructor(name: string, initialValue: T) {
|
|
34
|
+
this.name = name;
|
|
35
|
+
this._value = initialValue;
|
|
36
|
+
this.subscribers = new Set();
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
globalThis.observables[name] = this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async publish() {
|
|
42
|
+
return Promise.all([...this.subscribers].map(subscriber => subscriber(this._value)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sets a new value for the observable and notifies subscribers if the value changed.
|
|
47
|
+
*
|
|
48
|
+
* @param newValueOrSetter - The new value or a function that takes the current value and returns the new value.
|
|
49
|
+
* @returns The new value.
|
|
50
|
+
*/
|
|
51
|
+
async set(setter: ObservableSetter<T>): Promise<void>;
|
|
52
|
+
async set(newValue: T): Promise<void>;
|
|
53
|
+
async set(newValueOrSetter: any): Promise<void> {
|
|
54
|
+
const newValue =
|
|
55
|
+
typeof newValueOrSetter === 'function'
|
|
56
|
+
? (newValueOrSetter as ObservableSetter<T>)(this._value)
|
|
57
|
+
: newValueOrSetter;
|
|
58
|
+
if (!areEqualDeep(this._value, newValue)) {
|
|
59
|
+
this._value = newValue;
|
|
60
|
+
await this.publish();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a callback to be called when the observable value changes.
|
|
66
|
+
* @param cb - The callback to be called when the value changes.
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
subscribe(cb: (value: T) => void): () => void {
|
|
70
|
+
// promisify the callback so it behaves like an async function even if it's not
|
|
71
|
+
// so that we can await all subscribers in publish()
|
|
72
|
+
const cbPromise = async (newValue: T) => cb(newValue);
|
|
73
|
+
this.subscribers.add(cbPromise);
|
|
74
|
+
return () => this.unsubscribe(cb);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
unsubscribe(cb: (value: T) => void): void {
|
|
78
|
+
this.subscribers.delete(cb);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A React hook to use this observable in a component
|
|
83
|
+
*
|
|
84
|
+
* It does not return the value, but subscribes to changes and forces re-render
|
|
85
|
+
* when the value changes.
|
|
86
|
+
*
|
|
87
|
+
* @usage
|
|
88
|
+
* ```tsx
|
|
89
|
+
* const myObservable = new Observable('myObservable', 0);
|
|
90
|
+
*
|
|
91
|
+
* function MyComponent() {
|
|
92
|
+
* myObservable.use();
|
|
93
|
+
* return <div>{myObservable.value}</div>;
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
use() {
|
|
98
|
+
const [_, setValue] = useState(this.val);
|
|
99
|
+
useEffect(() => this.subscribe(setValue), [])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get val(): T {
|
|
103
|
+
// Freeze the value to avoid cases where the internal object may be mutated
|
|
104
|
+
// without triggering subscribers.
|
|
105
|
+
return Object.freeze(this._value);
|
|
106
|
+
}
|
|
107
|
+
set val(newValue: T) {
|
|
108
|
+
this.set(newValue);
|
|
109
|
+
}
|
|
110
|
+
}
|