diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts
index 20618e4dec..c9a29170d9 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts
@@ -290,7 +290,7 @@ describe('UmbElementMixin', () => {
const check2: CheckType = value as string;
const check3: CheckType = value as null;
const check4: CheckType = value as undefined;
- expect(check).to.be.equal('hello');
+ expect(check).to.be.equal(null);
expect(check2 === check3 && check2 === check4).to.be.true; // Just to use the const for something.
});
// Because the source is potentially undefined, the controller could be undefined and the value of the callback method could be undefined [NL]
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
index a96ff31ce5..9741bbcebb 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts
@@ -35,6 +35,7 @@ export class UmbArrayState extends UmbDeepState {
*/
sortBy(sortMethod?: (a: T, b: T) => number) {
this.#sortMethod = sortMethod;
+ super.setValue(this.getValue().sort(this.#sortMethod));
return this;
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts
index a9a8b7f33a..f443fe02e1 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts
@@ -13,15 +13,19 @@ import { UmbBasicState } from './basic-state.js';
* Additionally the Subject ensures the data is unique, not updating any Observes unless there is an actual change of the content.
*/
export class UmbDeepState extends UmbBasicState {
+ #mute?: boolean;
+ #value: T;
+
constructor(initialData: T) {
super(deepFreeze(initialData));
+ this.#value = this._subject.getValue();
}
/**
- * @export
* @method createObservablePart
* @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return.
* @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different.
+ * @returns {Observable}
* @description - Creates an Observable from this State.
*/
asObservablePart(
@@ -39,9 +43,63 @@ export class UmbDeepState extends UmbBasicState {
setValue(data: T): void {
if (!this._subject) return;
const frozenData = deepFreeze(data);
- // Only update data if its different than current data.
- if (!jsonStringComparison(frozenData, this._subject.getValue())) {
+ this.#value = frozenData;
+ // Only update data if its not muted and is different than current data. [NL]
+ if (!this.#mute && !jsonStringComparison(frozenData, this._subject.getValue())) {
this._subject.next(frozenData);
}
}
+
+ getValue(): T {
+ return this.#value;
+ }
+
+ /**
+ * @method mute
+ * @description - Set mute for this state.
+ */
+ mute() {
+ if (this.#mute) return;
+ this.#mute = true;
+ }
+
+ /**
+ * @method unmute
+ * @description - Unset the mute of this state.
+ */
+ unmute() {
+ if (!this.#mute) return;
+ this.#mute = false;
+ // Only update data if it is different than current data. [NL]
+ if (!jsonStringComparison(this.#value, this._subject.getValue())) {
+ this._subject?.next(this.#value);
+ }
+ }
+
+ /**
+ * @method isMuted
+ * @description - Check if the state is muted.
+ * @returns {boolean} - Returns true if the state is muted.
+ */
+ isMuted() {
+ return this.#mute;
+ }
+
+ /**
+ * @method getMutePromise
+ * @description - Get a promise which resolves when the mute is unset.
+ * @returns {Promise}
+ */
+ getMutePromise() {
+ return new Promise((resolve) => {
+ if (!this.#mute) {
+ resolve();
+ return;
+ }
+ const subscription = this._subject.subscribe(() => {
+ subscription.unsubscribe();
+ resolve();
+ });
+ });
+ }
}
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts
index 76dc7705dd..010716e036 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts
@@ -41,4 +41,50 @@ describe('UmbObjectState', () => {
subject.update({ key: 'change_this_first_should_not_trigger_update' });
subject.update({ another: 'myNewValue' });
});
+
+ it('replays the latests value when unmuted.', (done) => {
+ let amountOfCallbacks = 0;
+
+ const observer = subject.asObservable();
+ observer.subscribe((value) => {
+ amountOfCallbacks++;
+ if (amountOfCallbacks === 1) {
+ // First callback gives us the initialized value.
+ expect(value.key).to.be.equal('some');
+ }
+ if (amountOfCallbacks === 2) {
+ // Second callback gives us the first change.
+ expect(value.key).to.be.equal('firstChange');
+ }
+ if (amountOfCallbacks === 3) {
+ // Third callback gives us the last change before unmuted.
+ expect(value.key).to.be.equal('thirdChange');
+ done();
+ }
+ });
+
+ subject.update({ key: 'firstChange' });
+ subject.mute();
+ subject.update({ key: 'secondChange' });
+ subject.update({ key: 'thirdChange' });
+ subject.unmute();
+ });
+
+ /*
+ it('replays latests unmuted value when muted.', (done) => {
+ const observer = subject.asObservable();
+ observer.subscribe((value) => {
+ expect(value).to.be.equal(initialData);
+ });
+
+ subject.mute();
+ subject.update({ key: 'firstChange' });
+
+ observer.subscribe((value) => {
+ expect(value).to.be.equal(initialData);
+ done();
+ });
+
+ });
+ */
});
diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts
index fdcafa1bac..b325d50b8a 100644
--- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts
+++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts
@@ -10,9 +10,6 @@ import { UmbDeepState } from './deep-state.js';
* The UmbObjectState provides methods to append data when the data is an Object.
*/
export class UmbObjectState extends UmbDeepState {
- #partialUpdateData?: Partial;
- #partialUpdateDebounce?: boolean;
-
/**
* @method update
* @param {Partial} partialData - A object containing some of the data to update in this Subject.
@@ -23,39 +20,8 @@ export class UmbObjectState extends UmbDeepState {
* const myState = new UmbObjectState(data);
* myState.update({value: 'myNewValue'});
*/
-
update(partialData: Partial) {
- this.setValue({ ...this._subject.getValue(), ...partialData });
+ this.setValue({ ...this.getValue(), ...partialData });
return this;
}
- /*
- update(partialData: Partial) {
- this.#partialUpdateData = { ...this.#partialUpdateData, ...partialData };
- this.#performUpdate();
- return this;
- }
-
- async #performUpdate() {
- if (this.#partialUpdateDebounce) return;
- this.#partialUpdateDebounce = true;
- await Promise.resolve();
- if (this.#partialUpdateData) {
- this.setValue({ ...this._subject.getValue(), ...this.#partialUpdateData });
- this.#partialUpdateData = undefined;
- }
- this.#partialUpdateDebounce = false;
- }
-
- //getValue? — should this also include the partial update? but be aware that getValue is used for setValue comparison....!! [NL]
-
- setValue(data: T): void {
- if (this.#partialUpdateData) {
- console.error('SetValue was called while in debouncing mode.');
- super.setValue({ ...data, ...this.#partialUpdateData });
- //this.#partialUpdateData = undefined; // maybe not, cause keeping this enables that to be merged in despite a another change coming from above.
- } else {
- super.setValue(data);
- }
- }
- */
}