@furystack/utils 5.0.0 → 6.0.1
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/esm/observable-value.d.ts +15 -3
- package/esm/observable-value.d.ts.map +1 -1
- package/esm/observable-value.js +19 -11
- package/esm/observable-value.js.map +1 -1
- package/esm/observable-value.spec.d.ts.map +1 -1
- package/esm/observable-value.spec.js +41 -15
- package/esm/observable-value.spec.js.map +1 -1
- package/esm/value-observer.d.ts +6 -1
- package/esm/value-observer.d.ts.map +1 -1
- package/esm/value-observer.js +4 -1
- package/esm/value-observer.js.map +1 -1
- package/package.json +2 -2
- package/src/observable-value.spec.ts +52 -16
- package/src/observable-value.ts +32 -11
- package/src/value-observer.ts +6 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Disposable } from './disposable.js';
|
|
2
|
+
import type { ValueObserverOptions } from './value-observer.js';
|
|
2
3
|
import { ValueObserver } from './value-observer.js';
|
|
3
4
|
/**
|
|
4
5
|
* Error thrown when you try to retrieve or set an observable value that is already disposed.
|
|
@@ -10,6 +11,15 @@ export declare class ObservableAlreadyDisposedError extends Error {
|
|
|
10
11
|
* Callback type for observable value changes
|
|
11
12
|
*/
|
|
12
13
|
export type ValueChangeCallback<T> = (next: T) => void;
|
|
14
|
+
export type ObservableValueOptions<T> = {
|
|
15
|
+
/**
|
|
16
|
+
* Defines a custom compare function to determine if the value should be updated and the observers should be notified
|
|
17
|
+
* @param lastValue the last value
|
|
18
|
+
* @param nextValue the next value
|
|
19
|
+
* @returns if the value should be updated and the observers should be notified
|
|
20
|
+
*/
|
|
21
|
+
compare: (lastValue: T, nextValue: T) => boolean;
|
|
22
|
+
};
|
|
13
23
|
/**
|
|
14
24
|
* Defines an ObservableValue value object.
|
|
15
25
|
*
|
|
@@ -42,10 +52,10 @@ export declare class ObservableValue<T> implements Disposable {
|
|
|
42
52
|
/**
|
|
43
53
|
* Subscribes to a value changes
|
|
44
54
|
* @param callback The callback method that will be called on each change
|
|
45
|
-
* @param
|
|
55
|
+
* @param options Additional ObservableValue options
|
|
46
56
|
* @returns The ValueObserver instance
|
|
47
57
|
*/
|
|
48
|
-
subscribe(callback: ValueChangeCallback<T>,
|
|
58
|
+
subscribe(callback: ValueChangeCallback<T>, options?: ValueObserverOptions<T>): ValueObserver<T>;
|
|
49
59
|
/**
|
|
50
60
|
* The observer will unsubscribe from the Observable
|
|
51
61
|
* @param observer The ValueObserver instance
|
|
@@ -67,9 +77,11 @@ export declare class ObservableValue<T> implements Disposable {
|
|
|
67
77
|
* @returns The subscribed observers
|
|
68
78
|
*/
|
|
69
79
|
getObservers(): readonly ValueObserver<T>[];
|
|
80
|
+
private readonly options;
|
|
70
81
|
/**
|
|
71
82
|
* @param initialValue Optional initial value
|
|
83
|
+
* @param options Additional options
|
|
72
84
|
*/
|
|
73
|
-
constructor(initialValue: T);
|
|
85
|
+
constructor(initialValue: T, options?: Partial<ObservableValueOptions<T>>);
|
|
74
86
|
}
|
|
75
87
|
//# sourceMappingURL=observable-value.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observable-value.d.ts","sourceRoot":"","sources":["../src/observable-value.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;GAEG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;;CAIxD;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAe,CAAC,CAAC,CAAE,YAAW,UAAU;IACnD,IAAW,UAAU,IAAI,OAAO,CAE/B;IAED,OAAO,CAAC,WAAW,CAAQ;IAE3B;;OAEG;IACI,OAAO;
|
|
1
|
+
{"version":3,"file":"observable-value.d.ts","sourceRoot":"","sources":["../src/observable-value.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;GAEG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;;CAIxD;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;AAEtD,MAAM,MAAM,sBAAsB,CAAC,CAAC,IAAI;IACtC;;;;;OAKG;IACH,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,OAAO,CAAA;CACjD,CAAA;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAe,CAAC,CAAC,CAAE,YAAW,UAAU;IACnD,IAAW,UAAU,IAAI,OAAO,CAE/B;IAED,OAAO,CAAC,WAAW,CAAQ;IAE3B;;OAEG;IACI,OAAO;IAMd,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,YAAY,CAAG;IAEvB;;;;;OAKG;IACI,SAAS,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC;IASpF;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAI7C;;;OAGG;IACI,QAAQ,IAAI,CAAC;IAOpB;;;OAGG;IACI,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAc3B;;;OAGG;IACI,YAAY;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IAEnD;;;OAGG;gBACS,YAAY,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;CAO1E"}
|
package/esm/observable-value.js
CHANGED
|
@@ -7,6 +7,7 @@ export class ObservableAlreadyDisposedError extends Error {
|
|
|
7
7
|
super('Observable already disposed');
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
const defaultComparer = (a, b) => a !== b;
|
|
10
11
|
/**
|
|
11
12
|
* Defines an ObservableValue value object.
|
|
12
13
|
*
|
|
@@ -38,24 +39,23 @@ export class ObservableValue {
|
|
|
38
39
|
dispose() {
|
|
39
40
|
this.observers.clear();
|
|
40
41
|
this._isDisposed = true;
|
|
42
|
+
// @ts-expect-error getting currentValue after disposing is not allowed
|
|
43
|
+
this.currentValue = null;
|
|
41
44
|
}
|
|
42
45
|
observers = new Set();
|
|
43
46
|
currentValue;
|
|
44
47
|
/**
|
|
45
48
|
* Subscribes to a value changes
|
|
46
49
|
* @param callback The callback method that will be called on each change
|
|
47
|
-
* @param
|
|
50
|
+
* @param options Additional ObservableValue options
|
|
48
51
|
* @returns The ValueObserver instance
|
|
49
52
|
*/
|
|
50
|
-
subscribe(callback,
|
|
53
|
+
subscribe(callback, options) {
|
|
51
54
|
if (this._isDisposed) {
|
|
52
55
|
throw new ObservableAlreadyDisposedError();
|
|
53
56
|
}
|
|
54
|
-
const observer = new ValueObserver(this, callback);
|
|
57
|
+
const observer = new ValueObserver(this, callback, options);
|
|
55
58
|
this.observers.add(observer);
|
|
56
|
-
if (getLast) {
|
|
57
|
-
callback(this.currentValue);
|
|
58
|
-
}
|
|
59
59
|
return observer;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
@@ -84,11 +84,13 @@ export class ObservableValue {
|
|
|
84
84
|
if (this._isDisposed) {
|
|
85
85
|
throw new ObservableAlreadyDisposedError();
|
|
86
86
|
}
|
|
87
|
-
if (this.currentValue
|
|
87
|
+
if (this.options.compare(this.currentValue, newValue)) {
|
|
88
88
|
this.currentValue = newValue;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
this.observers.forEach((observer) => {
|
|
90
|
+
if (observer.options?.filter?.(this.currentValue, newValue) !== false) {
|
|
91
|
+
observer.callback(newValue);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
@@ -98,10 +100,16 @@ export class ObservableValue {
|
|
|
98
100
|
getObservers() {
|
|
99
101
|
return [...this.observers];
|
|
100
102
|
}
|
|
103
|
+
options;
|
|
101
104
|
/**
|
|
102
105
|
* @param initialValue Optional initial value
|
|
106
|
+
* @param options Additional options
|
|
103
107
|
*/
|
|
104
|
-
constructor(initialValue) {
|
|
108
|
+
constructor(initialValue, options) {
|
|
109
|
+
this.options = {
|
|
110
|
+
compare: defaultComparer,
|
|
111
|
+
...options,
|
|
112
|
+
};
|
|
105
113
|
this.currentValue = initialValue;
|
|
106
114
|
}
|
|
107
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observable-value.js","sourceRoot":"","sources":["../src/observable-value.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"observable-value.js","sourceRoot":"","sources":["../src/observable-value.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;GAEG;AACH,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACvD;QACE,KAAK,CAAC,6BAA6B,CAAC,CAAA;IACtC,CAAC;CACF;AAiBD,MAAM,eAAe,GAAG,CAAI,CAAI,EAAE,CAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;AAElD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,eAAe;IAC1B,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,WAAW,CAAA;IACzB,CAAC;IAEO,WAAW,GAAG,KAAK,CAAA;IAE3B;;OAEG;IACI,OAAO;QACZ,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;QACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,uEAAuE;QACvE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;IACO,SAAS,GAA0B,IAAI,GAAG,EAAE,CAAA;IAC5C,YAAY,CAAG;IAEvB;;;;;OAKG;IACI,SAAS,CAAC,QAAgC,EAAE,OAAiC;QAClF,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,8BAA8B,EAAE,CAAA;QAC5C,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,aAAa,CAAI,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC9D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC5B,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED;;;;OAIG;IACI,WAAW,CAAC,QAA0B;QAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACxC,CAAC;IAED;;;OAGG;IACI,QAAQ;QACb,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,8BAA8B,EAAE,CAAA;QAC5C,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAED;;;OAGG;IACI,QAAQ,CAAC,QAAW;QACzB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,8BAA8B,EAAE,CAAA;QAC5C,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAA;YAC5B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAClC,IAAI,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;oBACtE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBAC7B,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,YAAY;QACjB,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAoC,CAAA;IAC/D,CAAC;IAEgB,OAAO,CAA2B;IAEnD;;;OAGG;IACH,YAAY,YAAe,EAAE,OAA4C;QACvE,IAAI,CAAC,OAAO,GAAG;YACb,OAAO,EAAE,eAAe;YACxB,GAAG,OAAO;SACX,CAAA;QACD,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observable-value.spec.d.ts","sourceRoot":"","sources":["../src/observable-value.spec.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"observable-value.spec.d.ts","sourceRoot":"","sources":["../src/observable-value.spec.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,eAAe,qCA6K1B,CAAA"}
|
|
@@ -6,22 +6,12 @@ import { ObservableValue } from './observable-value.js';
|
|
|
6
6
|
export const observableTests = describe('Observable', () => {
|
|
7
7
|
it('should be constructed with an undefined initial value', () => {
|
|
8
8
|
const v = new ObservableValue(undefined);
|
|
9
|
-
const doneCallback = vi.fn();
|
|
10
|
-
v.subscribe(() => {
|
|
11
|
-
expect(v.getValue()).toBe(undefined);
|
|
12
|
-
doneCallback();
|
|
13
|
-
}, true);
|
|
14
9
|
expect(v).toBeInstanceOf(ObservableValue);
|
|
15
|
-
expect(
|
|
10
|
+
expect(v.getValue()).toBe(undefined);
|
|
16
11
|
});
|
|
17
12
|
it('should be constructed with initial value', () => {
|
|
18
13
|
const v = new ObservableValue(1);
|
|
19
|
-
|
|
20
|
-
v.subscribe(() => {
|
|
21
|
-
expect(v.getValue()).toBe(1);
|
|
22
|
-
doneCallback();
|
|
23
|
-
}, true);
|
|
24
|
-
expect(doneCallback).toBeCalled();
|
|
14
|
+
expect(v.getValue()).toBe(1);
|
|
25
15
|
});
|
|
26
16
|
describe('Subscription callback', () => {
|
|
27
17
|
it('should be triggered only when a value is changed', () => {
|
|
@@ -30,19 +20,19 @@ export const observableTests = describe('Observable', () => {
|
|
|
30
20
|
v.subscribe(() => {
|
|
31
21
|
expect(v.getValue()).toBe(2);
|
|
32
22
|
doneCallback();
|
|
33
|
-
}
|
|
23
|
+
});
|
|
34
24
|
v.setValue(1);
|
|
35
25
|
v.setValue(1);
|
|
36
26
|
v.setValue(2);
|
|
37
27
|
expect(doneCallback).toBeCalledTimes(1);
|
|
38
28
|
});
|
|
39
|
-
it('should be triggered only on change
|
|
29
|
+
it('should be triggered only on change', () => {
|
|
40
30
|
const v = new ObservableValue(1);
|
|
41
31
|
const doneCallback = vi.fn();
|
|
42
32
|
v.subscribe((value) => {
|
|
43
33
|
expect(value).toBe(2);
|
|
44
34
|
doneCallback();
|
|
45
|
-
}
|
|
35
|
+
});
|
|
46
36
|
v.setValue(2);
|
|
47
37
|
expect(doneCallback).toBeCalledTimes(1);
|
|
48
38
|
});
|
|
@@ -119,5 +109,41 @@ export const observableTests = describe('Observable', () => {
|
|
|
119
109
|
expect(doneCallback).toBeCalledTimes(1);
|
|
120
110
|
});
|
|
121
111
|
});
|
|
112
|
+
describe('Custom Compare function', () => {
|
|
113
|
+
it('Should compare the values with the custom compare function', () => {
|
|
114
|
+
const v = new ObservableValue({ value: 2 }, {
|
|
115
|
+
compare: (a, b) => a.value !== b.value,
|
|
116
|
+
});
|
|
117
|
+
const onChange = vi.fn();
|
|
118
|
+
v.subscribe(onChange);
|
|
119
|
+
v.setValue({ value: 2 });
|
|
120
|
+
expect(v.getValue()).toEqual({ value: 2 });
|
|
121
|
+
expect(onChange).not.toBeCalled();
|
|
122
|
+
v.setValue({ value: 3 });
|
|
123
|
+
expect(v.getValue()).toEqual({ value: 3 });
|
|
124
|
+
expect(onChange).toBeCalledTimes(1);
|
|
125
|
+
expect(onChange).toBeCalledWith({ value: 3 });
|
|
126
|
+
v.setValue({ value: 3 });
|
|
127
|
+
expect(v.getValue()).toEqual({ value: 3 });
|
|
128
|
+
expect(onChange).toBeCalledTimes(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('Filtered subscriptions', () => {
|
|
132
|
+
it('should not trigger the callback if the filter returns false', () => {
|
|
133
|
+
const v = new ObservableValue({ shouldNotify: true, value: 1 });
|
|
134
|
+
const onChange = vi.fn();
|
|
135
|
+
v.subscribe(onChange, {
|
|
136
|
+
filter: (nextValue) => nextValue.shouldNotify,
|
|
137
|
+
});
|
|
138
|
+
v.setValue({ shouldNotify: false, value: 1 });
|
|
139
|
+
expect(onChange).not.toBeCalled();
|
|
140
|
+
v.setValue({ shouldNotify: false, value: 2 });
|
|
141
|
+
expect(onChange).not.toBeCalled();
|
|
142
|
+
expect(v.getValue()).toEqual({ shouldNotify: false, value: 2 });
|
|
143
|
+
v.setValue({ shouldNotify: true, value: 3 });
|
|
144
|
+
expect(onChange).toBeCalledTimes(1);
|
|
145
|
+
expect(onChange).toBeCalledWith({ shouldNotify: true, value: 3 });
|
|
146
|
+
});
|
|
147
|
+
});
|
|
122
148
|
});
|
|
123
149
|
//# sourceMappingURL=observable-value.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observable-value.spec.js","sourceRoot":"","sources":["../src/observable-value.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IACzD,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAG,IAAI,eAAe,CAAC,SAAS,CAAC,CAAA;QACxC,MAAM,
|
|
1
|
+
{"version":3,"file":"observable-value.spec.js","sourceRoot":"","sources":["../src/observable-value.spec.ts"],"names":[],"mappings":"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"}
|
package/esm/value-observer.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Disposable } from './disposable.js';
|
|
2
2
|
import type { ObservableValue, ValueChangeCallback } from './observable-value.js';
|
|
3
|
+
export type ValueObserverOptions<T> = {
|
|
4
|
+
filter?: (nextValue: T, lastValue: T) => boolean;
|
|
5
|
+
};
|
|
3
6
|
/**
|
|
4
7
|
* Defines a generic ValueObserver instance
|
|
5
8
|
*
|
|
@@ -25,6 +28,7 @@ import type { ObservableValue, ValueChangeCallback } from './observable-value.js
|
|
|
25
28
|
export declare class ValueObserver<T> implements Disposable {
|
|
26
29
|
readonly observable: ObservableValue<T>;
|
|
27
30
|
callback: ValueChangeCallback<T>;
|
|
31
|
+
readonly options?: ValueObserverOptions<T> | undefined;
|
|
28
32
|
/**
|
|
29
33
|
* Disposes the ValueObserver instance. Unsubscribes from the observable
|
|
30
34
|
*/
|
|
@@ -33,7 +37,8 @@ export declare class ValueObserver<T> implements Disposable {
|
|
|
33
37
|
* @constructs ValueObserver<T> the ValueObserver instance
|
|
34
38
|
* @param observable The related Observable object
|
|
35
39
|
* @param callback The callback that will be fired on change
|
|
40
|
+
* @param options Additional options
|
|
36
41
|
*/
|
|
37
|
-
constructor(observable: ObservableValue<T>, callback: ValueChangeCallback<T>);
|
|
42
|
+
constructor(observable: ObservableValue<T>, callback: ValueChangeCallback<T>, options?: ValueObserverOptions<T> | undefined);
|
|
38
43
|
}
|
|
39
44
|
//# sourceMappingURL=value-observer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-observer.d.ts","sourceRoot":"","sources":["../src/value-observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEjF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,aAAa,CAAC,CAAC,CAAE,YAAW,UAAU;
|
|
1
|
+
{"version":3,"file":"value-observer.d.ts","sourceRoot":"","sources":["../src/value-observer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEjF,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI;IACpC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,OAAO,CAAA;CACjD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,aAAa,CAAC,CAAC,CAAE,YAAW,UAAU;aAe/B,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IACvC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC;aACvB,OAAO,CAAC;IAhB1B;;OAEG;IACI,OAAO;IAId;;;;;OAKG;gBAEe,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,EACvC,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,qCAAyB;CAEpD"}
|
package/esm/value-observer.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
export class ValueObserver {
|
|
24
24
|
observable;
|
|
25
25
|
callback;
|
|
26
|
+
options;
|
|
26
27
|
/**
|
|
27
28
|
* Disposes the ValueObserver instance. Unsubscribes from the observable
|
|
28
29
|
*/
|
|
@@ -33,10 +34,12 @@ export class ValueObserver {
|
|
|
33
34
|
* @constructs ValueObserver<T> the ValueObserver instance
|
|
34
35
|
* @param observable The related Observable object
|
|
35
36
|
* @param callback The callback that will be fired on change
|
|
37
|
+
* @param options Additional options
|
|
36
38
|
*/
|
|
37
|
-
constructor(observable, callback) {
|
|
39
|
+
constructor(observable, callback, options) {
|
|
38
40
|
this.observable = observable;
|
|
39
41
|
this.callback = callback;
|
|
42
|
+
this.options = options;
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
//# sourceMappingURL=value-observer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-observer.js","sourceRoot":"","sources":["../src/value-observer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"value-observer.js","sourceRoot":"","sources":["../src/value-observer.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,aAAa;IAeN;IACT;IACS;IAhBlB;;OAEG;IACI,OAAO;QACZ,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC;IAED;;;;;OAKG;IACH,YACkB,UAA8B,EACvC,QAAgC,EACvB,OAAiC;QAFjC,eAAU,GAAV,UAAU,CAAoB;QACvC,aAAQ,GAAR,QAAQ,CAAwB;QACvB,YAAO,GAAP,OAAO,CAA0B;IAChD,CAAC;CACL"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "General utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://github.com/furystack/furystack",
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"typescript": "^5.4.
|
|
37
|
+
"typescript": "^5.4.3",
|
|
38
38
|
"vitest": "^1.4.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -7,24 +7,13 @@ import { ObservableValue } from './observable-value.js'
|
|
|
7
7
|
export const observableTests = describe('Observable', () => {
|
|
8
8
|
it('should be constructed with an undefined initial value', () => {
|
|
9
9
|
const v = new ObservableValue(undefined)
|
|
10
|
-
const doneCallback = vi.fn()
|
|
11
|
-
v.subscribe(() => {
|
|
12
|
-
expect(v.getValue()).toBe(undefined)
|
|
13
|
-
doneCallback()
|
|
14
|
-
}, true)
|
|
15
10
|
expect(v).toBeInstanceOf(ObservableValue)
|
|
16
|
-
expect(
|
|
11
|
+
expect(v.getValue()).toBe(undefined)
|
|
17
12
|
})
|
|
18
13
|
|
|
19
14
|
it('should be constructed with initial value', () => {
|
|
20
15
|
const v = new ObservableValue(1)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
v.subscribe(() => {
|
|
24
|
-
expect(v.getValue()).toBe(1)
|
|
25
|
-
doneCallback()
|
|
26
|
-
}, true)
|
|
27
|
-
expect(doneCallback).toBeCalled()
|
|
16
|
+
expect(v.getValue()).toBe(1)
|
|
28
17
|
})
|
|
29
18
|
|
|
30
19
|
describe('Subscription callback', () => {
|
|
@@ -35,21 +24,21 @@ export const observableTests = describe('Observable', () => {
|
|
|
35
24
|
v.subscribe(() => {
|
|
36
25
|
expect(v.getValue()).toBe(2)
|
|
37
26
|
doneCallback()
|
|
38
|
-
}
|
|
27
|
+
})
|
|
39
28
|
v.setValue(1)
|
|
40
29
|
v.setValue(1)
|
|
41
30
|
v.setValue(2)
|
|
42
31
|
expect(doneCallback).toBeCalledTimes(1)
|
|
43
32
|
})
|
|
44
33
|
|
|
45
|
-
it('should be triggered only on change
|
|
34
|
+
it('should be triggered only on change', () => {
|
|
46
35
|
const v = new ObservableValue(1)
|
|
47
36
|
const doneCallback = vi.fn()
|
|
48
37
|
|
|
49
38
|
v.subscribe((value) => {
|
|
50
39
|
expect(value).toBe(2)
|
|
51
40
|
doneCallback()
|
|
52
|
-
}
|
|
41
|
+
})
|
|
53
42
|
v.setValue(2)
|
|
54
43
|
expect(doneCallback).toBeCalledTimes(1)
|
|
55
44
|
})
|
|
@@ -141,4 +130,51 @@ export const observableTests = describe('Observable', () => {
|
|
|
141
130
|
expect(doneCallback).toBeCalledTimes(1)
|
|
142
131
|
})
|
|
143
132
|
})
|
|
133
|
+
|
|
134
|
+
describe('Custom Compare function', () => {
|
|
135
|
+
it('Should compare the values with the custom compare function', () => {
|
|
136
|
+
const v = new ObservableValue(
|
|
137
|
+
{ value: 2 },
|
|
138
|
+
{
|
|
139
|
+
compare: (a, b) => a.value !== b.value,
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
const onChange = vi.fn()
|
|
143
|
+
v.subscribe(onChange)
|
|
144
|
+
|
|
145
|
+
v.setValue({ value: 2 })
|
|
146
|
+
expect(v.getValue()).toEqual({ value: 2 })
|
|
147
|
+
expect(onChange).not.toBeCalled()
|
|
148
|
+
|
|
149
|
+
v.setValue({ value: 3 })
|
|
150
|
+
expect(v.getValue()).toEqual({ value: 3 })
|
|
151
|
+
expect(onChange).toBeCalledTimes(1)
|
|
152
|
+
expect(onChange).toBeCalledWith({ value: 3 })
|
|
153
|
+
|
|
154
|
+
v.setValue({ value: 3 })
|
|
155
|
+
expect(v.getValue()).toEqual({ value: 3 })
|
|
156
|
+
expect(onChange).toBeCalledTimes(1)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('Filtered subscriptions', () => {
|
|
161
|
+
it('should not trigger the callback if the filter returns false', () => {
|
|
162
|
+
const v = new ObservableValue({ shouldNotify: true, value: 1 })
|
|
163
|
+
const onChange = vi.fn()
|
|
164
|
+
v.subscribe(onChange, {
|
|
165
|
+
filter: (nextValue) => nextValue.shouldNotify,
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
v.setValue({ shouldNotify: false, value: 1 })
|
|
169
|
+
expect(onChange).not.toBeCalled()
|
|
170
|
+
|
|
171
|
+
v.setValue({ shouldNotify: false, value: 2 })
|
|
172
|
+
expect(onChange).not.toBeCalled()
|
|
173
|
+
expect(v.getValue()).toEqual({ shouldNotify: false, value: 2 })
|
|
174
|
+
|
|
175
|
+
v.setValue({ shouldNotify: true, value: 3 })
|
|
176
|
+
expect(onChange).toBeCalledTimes(1)
|
|
177
|
+
expect(onChange).toBeCalledWith({ shouldNotify: true, value: 3 })
|
|
178
|
+
})
|
|
179
|
+
})
|
|
144
180
|
})
|
package/src/observable-value.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Disposable } from './disposable.js'
|
|
2
|
+
import type { ValueObserverOptions } from './value-observer.js'
|
|
2
3
|
import { ValueObserver } from './value-observer.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -15,6 +16,18 @@ export class ObservableAlreadyDisposedError extends Error {
|
|
|
15
16
|
*/
|
|
16
17
|
export type ValueChangeCallback<T> = (next: T) => void
|
|
17
18
|
|
|
19
|
+
export type ObservableValueOptions<T> = {
|
|
20
|
+
/**
|
|
21
|
+
* Defines a custom compare function to determine if the value should be updated and the observers should be notified
|
|
22
|
+
* @param lastValue the last value
|
|
23
|
+
* @param nextValue the next value
|
|
24
|
+
* @returns if the value should be updated and the observers should be notified
|
|
25
|
+
*/
|
|
26
|
+
compare: (lastValue: T, nextValue: T) => boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultComparer = <T>(a: T, b: T) => a !== b
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* Defines an ObservableValue value object.
|
|
20
33
|
*
|
|
@@ -48,6 +61,8 @@ export class ObservableValue<T> implements Disposable {
|
|
|
48
61
|
public dispose() {
|
|
49
62
|
this.observers.clear()
|
|
50
63
|
this._isDisposed = true
|
|
64
|
+
// @ts-expect-error getting currentValue after disposing is not allowed
|
|
65
|
+
this.currentValue = null
|
|
51
66
|
}
|
|
52
67
|
private observers: Set<ValueObserver<T>> = new Set()
|
|
53
68
|
private currentValue: T
|
|
@@ -55,18 +70,15 @@ export class ObservableValue<T> implements Disposable {
|
|
|
55
70
|
/**
|
|
56
71
|
* Subscribes to a value changes
|
|
57
72
|
* @param callback The callback method that will be called on each change
|
|
58
|
-
* @param
|
|
73
|
+
* @param options Additional ObservableValue options
|
|
59
74
|
* @returns The ValueObserver instance
|
|
60
75
|
*/
|
|
61
|
-
public subscribe(callback: ValueChangeCallback<T>,
|
|
76
|
+
public subscribe(callback: ValueChangeCallback<T>, options?: ValueObserverOptions<T>) {
|
|
62
77
|
if (this._isDisposed) {
|
|
63
78
|
throw new ObservableAlreadyDisposedError()
|
|
64
79
|
}
|
|
65
|
-
const observer = new ValueObserver<T>(this, callback)
|
|
80
|
+
const observer = new ValueObserver<T>(this, callback, options)
|
|
66
81
|
this.observers.add(observer)
|
|
67
|
-
if (getLast) {
|
|
68
|
-
callback(this.currentValue)
|
|
69
|
-
}
|
|
70
82
|
return observer
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -98,11 +110,13 @@ export class ObservableValue<T> implements Disposable {
|
|
|
98
110
|
if (this._isDisposed) {
|
|
99
111
|
throw new ObservableAlreadyDisposedError()
|
|
100
112
|
}
|
|
101
|
-
if (this.currentValue
|
|
113
|
+
if (this.options.compare(this.currentValue, newValue)) {
|
|
102
114
|
this.currentValue = newValue
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
this.observers.forEach((observer) => {
|
|
116
|
+
if (observer.options?.filter?.(this.currentValue, newValue) !== false) {
|
|
117
|
+
observer.callback(newValue)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
|
|
@@ -114,10 +128,17 @@ export class ObservableValue<T> implements Disposable {
|
|
|
114
128
|
return [...this.observers] as ReadonlyArray<ValueObserver<T>>
|
|
115
129
|
}
|
|
116
130
|
|
|
131
|
+
private readonly options: ObservableValueOptions<T>
|
|
132
|
+
|
|
117
133
|
/**
|
|
118
134
|
* @param initialValue Optional initial value
|
|
135
|
+
* @param options Additional options
|
|
119
136
|
*/
|
|
120
|
-
constructor(initialValue: T) {
|
|
137
|
+
constructor(initialValue: T, options?: Partial<ObservableValueOptions<T>>) {
|
|
138
|
+
this.options = {
|
|
139
|
+
compare: defaultComparer,
|
|
140
|
+
...options,
|
|
141
|
+
}
|
|
121
142
|
this.currentValue = initialValue
|
|
122
143
|
}
|
|
123
144
|
}
|
package/src/value-observer.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Disposable } from './disposable.js'
|
|
2
2
|
import type { ObservableValue, ValueChangeCallback } from './observable-value.js'
|
|
3
3
|
|
|
4
|
+
export type ValueObserverOptions<T> = {
|
|
5
|
+
filter?: (nextValue: T, lastValue: T) => boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* Defines a generic ValueObserver instance
|
|
6
10
|
*
|
|
@@ -35,9 +39,11 @@ export class ValueObserver<T> implements Disposable {
|
|
|
35
39
|
* @constructs ValueObserver<T> the ValueObserver instance
|
|
36
40
|
* @param observable The related Observable object
|
|
37
41
|
* @param callback The callback that will be fired on change
|
|
42
|
+
* @param options Additional options
|
|
38
43
|
*/
|
|
39
44
|
constructor(
|
|
40
45
|
public readonly observable: ObservableValue<T>,
|
|
41
46
|
public callback: ValueChangeCallback<T>,
|
|
47
|
+
public readonly options?: ValueObserverOptions<T>,
|
|
42
48
|
) {}
|
|
43
49
|
}
|