/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module utils/observablemixin
*/
import { type Emitter } from './emittermixin.js';
import type { Constructor, Mixed } from './mix.js';
/**
* A mixin that injects the "observable properties" and data binding functionality described in the
* {@link ~Observable} interface.
*
* This function creates a class that inherits from the provided `base` and implements `Observable` interface.
*
* ```ts
* class BaseClass { ... }
*
* class MyClass extends ObservableMixin( BaseClass ) {
* // This class derives from `BaseClass` and implements the `Observable` interface.
* }
* ```
*
* Read more about the concept of observables in the:
* * {@glink framework/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/architecture/core-editor-architecture Core editor architecture} guide,
* * {@glink framework/deep-dive/observables Observables deep-dive} guide.
*
* @label EXTENDS
*/
export declare function ObservableMixin>(base: Base): Mixed;
/**
* A mixin that injects the "observable properties" and data binding functionality described in the
* {@link ~Observable} interface.
*
* This function creates a class that implements `Observable` interface.
*
* ```ts
* class MyClass extends ObservableMixin() {
* // This class implements the `Observable` interface.
* }
* ```
*
* Read more about the concept of observables in the:
* * {@glink framework/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/architecture/core-editor-architecture Core editor architecture} guide,
* * {@glink framework/deep-dive/observables Observables deep dive} guide.
*
* @label NO_ARGUMENTS
*/
export declare function ObservableMixin(): {
new (): Observable;
prototype: Observable;
};
/**
* An interface which adds "observable properties" and data binding functionality.
*
* Can be easily implemented by a class by mixing the {@link module:utils/observablemixin~Observable} mixin.
*
* ```ts
* class MyClass extends ObservableMixin( OtherBaseClass ) {
* // This class now implements the `Observable` interface.
* }
* ```
*
* Read more about the usage of this interface in the:
* * {@glink framework/architecture/core-editor-architecture#event-system-and-observables Event system and observables}
* section of the {@glink framework/architecture/core-editor-architecture Core editor architecture} guide,
* * {@glink framework/deep-dive/observables Observables deep-dive} guide.
*/
export interface Observable extends Emitter {
/**
* Creates and sets the value of an observable property of this object. Such a property becomes a part
* of the state and is observable.
*
* This method throws the `observable-set-cannot-override` error if the observable instance already
* has a property with the given property name. This prevents from mistakenly overriding existing
* properties and methods, but means that `foo.set( 'bar', 1 )` may be slightly slower than `foo.bar = 1`.
*
* In TypeScript, those properties should be declared in class using `declare` keyword. In example:
*
* ```ts
* public declare myProp: number;
*
* constructor() {
* this.set( 'myProp', 2 );
* }
* ```
*
* @label KEY_VALUE
* @param name The property's name.
* @param value The property's value.
*/
set(name: K, value: this[K]): void;
/**
* Creates and sets the value of an observable properties of this object. Such a property becomes a part
* of the state and is observable.
*
* It accepts a single object literal containing key/value pairs with properties to be set.
*
* This method throws the `observable-set-cannot-override` error if the observable instance already
* has a property with the given property name. This prevents from mistakenly overriding existing
* properties and methods, but means that `foo.set( 'bar', 1 )` may be slightly slower than `foo.bar = 1`.
*
* In TypeScript, those properties should be declared in class using `declare` keyword. In example:
*
* ```ts
* public declare myProp1: number;
* public declare myProp2: string;
*
* constructor() {
* this.set( {
* 'myProp1: 2,
* 'myProp2: 'foo'
* } );
* }
* ```
* @label OBJECT
* @param values An object with `name=>value` pairs.
*/
set(values: object & {
readonly [K in keyof this]?: unknown;
}): void;
/**
* Binds {@link #set observable properties} to other objects implementing the
* {@link module:utils/observablemixin~Observable} interface.
*
* Read more in the {@glink framework/deep-dive/observables#property-bindings dedicated} guide
* covering the topic of property bindings with some additional examples.
*
* Consider two objects: a `button` and an associated `command` (both `Observable`).
*
* A simple property binding could be as follows:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled' );
* ```
*
* or even shorter:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* ```
*
* which works in the following way:
*
* * `button.isEnabled` **instantly equals** `command.isEnabled`,
* * whenever `command.isEnabled` changes, `button.isEnabled` will immediately reflect its value.
*
* **Note**: To release the binding, use {@link module:utils/observablemixin~Observable#unbind}.
*
* You can also "rename" the property in the binding by specifying the new name in the `to()` chain:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isWorking' );
* ```
*
* It is possible to bind more than one property at a time to shorten the code:
*
* ```ts
* button.bind( 'isEnabled', 'value' ).to( command );
* ```
*
* which corresponds to:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* button.bind( 'value' ).to( command );
* ```
*
* The binding can include more than one observable, combining multiple data sources in a custom callback:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled', ui, 'isVisible',
* ( isCommandEnabled, isUIVisible ) => isCommandEnabled && isUIVisible );
* ```
*
* Using a custom callback allows processing the value before passing it to the target property:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'value', value => value === 'heading1' );
* ```
*
* It is also possible to bind to the same property in an array of observables.
* To bind a `button` to multiple commands (also `Observables`) so that each and every one of them
* must be enabled for the button to become enabled, use the following code:
*
* ```ts
* button.bind( 'isEnabled' ).toMany( [ commandA, commandB, commandC ], 'isEnabled',
* ( isAEnabled, isBEnabled, isCEnabled ) => isAEnabled && isBEnabled && isCEnabled );
* ```
*
* @label SINGLE_BIND
* @param bindProperty Observable property that will be bound to other observable(s).
* @returns The bind chain with the `to()` and `toMany()` methods.
*/
bind(bindProperty: K): ObservableSingleBindChain;
/**
* Binds {@link #set observable properties} to other objects implementing the
* {@link module:utils/observablemixin~Observable} interface.
*
* Read more in the {@glink framework/deep-dive/observables#property-bindings dedicated} guide
* covering the topic of property bindings with some additional examples.
*
* Consider two objects: a `button` and an associated `command` (both `Observable`).
*
* A simple property binding could be as follows:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled' );
* ```
*
* or even shorter:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* ```
*
* which works in the following way:
*
* * `button.isEnabled` **instantly equals** `command.isEnabled`,
* * whenever `command.isEnabled` changes, `button.isEnabled` will immediately reflect its value.
*
* **Note**: To release the binding, use {@link module:utils/observablemixin~Observable#unbind}.
*
* You can also "rename" the property in the binding by specifying the new name in the `to()` chain:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isWorking' );
* ```
*
* It is possible to bind more than one property at a time to shorten the code:
*
* ```ts
* button.bind( 'isEnabled', 'value' ).to( command );
* ```
*
* which corresponds to:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* button.bind( 'value' ).to( command );
* ```
*
* The binding can include more than one observable, combining multiple data sources in a custom callback:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled', ui, 'isVisible',
* ( isCommandEnabled, isUIVisible ) => isCommandEnabled && isUIVisible );
* ```
*
* Using a custom callback allows processing the value before passing it to the target property:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'value', value => value === 'heading1' );
* ```
*
* It is also possible to bind to the same property in an array of observables.
* To bind a `button` to multiple commands (also `Observables`) so that each and every one of them
* must be enabled for the button to become enabled, use the following code:
*
* ```ts
* button.bind( 'isEnabled' ).toMany( [ commandA, commandB, commandC ], 'isEnabled',
* ( isAEnabled, isBEnabled, isCEnabled ) => isAEnabled && isBEnabled && isCEnabled );
* ```
*
* @label DUAL_BIND
* @param bindProperty1 Observable property that will be bound to other observable(s).
* @param bindProperty2 Observable property that will be bound to other observable(s).
* @returns The bind chain with the `to()` and `toMany()` methods.
*/
bind(bindProperty1: K1, bindProperty2: K2): ObservableDualBindChain;
/**
* Binds {@link #set observable properties} to other objects implementing the
* {@link module:utils/observablemixin~Observable} interface.
*
* Read more in the {@glink framework/deep-dive/observables#property-bindings dedicated} guide
* covering the topic of property bindings with some additional examples.
*
* Consider two objects: a `button` and an associated `command` (both `Observable`).
*
* A simple property binding could be as follows:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled' );
* ```
*
* or even shorter:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* ```
*
* which works in the following way:
*
* * `button.isEnabled` **instantly equals** `command.isEnabled`,
* * whenever `command.isEnabled` changes, `button.isEnabled` will immediately reflect its value.
*
* **Note**: To release the binding, use {@link module:utils/observablemixin~Observable#unbind}.
*
* You can also "rename" the property in the binding by specifying the new name in the `to()` chain:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isWorking' );
* ```
*
* It is possible to bind more than one property at a time to shorten the code:
*
* ```ts
* button.bind( 'isEnabled', 'value' ).to( command );
* ```
*
* which corresponds to:
*
* ```ts
* button.bind( 'isEnabled' ).to( command );
* button.bind( 'value' ).to( command );
* ```
*
* The binding can include more than one observable, combining multiple data sources in a custom callback:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'isEnabled', ui, 'isVisible',
* ( isCommandEnabled, isUIVisible ) => isCommandEnabled && isUIVisible );
* ```
*
* Using a custom callback allows processing the value before passing it to the target property:
*
* ```ts
* button.bind( 'isEnabled' ).to( command, 'value', value => value === 'heading1' );
* ```
*
* It is also possible to bind to the same property in an array of observables.
* To bind a `button` to multiple commands (also `Observables`) so that each and every one of them
* must be enabled for the button to become enabled, use the following code:
*
* ```ts
* button.bind( 'isEnabled' ).toMany( [ commandA, commandB, commandC ], 'isEnabled',
* ( isAEnabled, isBEnabled, isCEnabled ) => isAEnabled && isBEnabled && isCEnabled );
* ```
*
* @label MANY_BIND
* @param bindProperties Observable properties that will be bound to other observable(s).
* @returns The bind chain with the `to()` and `toMany()` methods.
*/
bind(...bindProperties: Array): ObservableMultiBindChain;
/**
* Removes the binding created with {@link #bind}.
*
* ```ts
* // Removes the binding for the 'a' property.
* A.unbind( 'a' );
*
* // Removes bindings for all properties.
* A.unbind();
* ```
*
* @param unbindProperties Observable properties to be unbound. All the bindings will
* be released if no properties are provided.
*/
unbind(...unbindProperties: Array): void;
/**
* Turns the given methods of this object into event-based ones. This means that the new method will fire an event
* (named after the method) and the original action will be plugged as a listener to that event.
*
* Read more in the {@glink framework/deep-dive/observables#decorating-object-methods dedicated} guide
* covering the topic of decorating methods with some additional examples.
*
* Decorating the method does not change its behavior (it only adds an event),
* but it allows to modify it later on by listening to the method's event.
*
* For example, to cancel the method execution the event can be {@link module:utils/eventinfo~EventInfo#stop stopped}:
*
* ```ts
* class Foo extends ObservableMixin() {
* constructor() {
* super();
* this.decorate( 'method' );
* }
*
* method() {
* console.log( 'called!' );
* }
* }
*
* const foo = new Foo();
* foo.on( 'method', ( evt ) => {
* evt.stop();
* }, { priority: 'high' } );
*
* foo.method(); // Nothing is logged.
* ```
*
*
* **Note**: The high {@link module:utils/priorities~PriorityString priority} listener
* has been used to execute this particular callback before the one which calls the original method
* (which uses the "normal" priority).
*
* It is also possible to change the returned value:
*
* ```ts
* foo.on( 'method', ( evt ) => {
* evt.return = 'Foo!';
* } );
*
* foo.method(); // -> 'Foo'
* ```
*
* Finally, it is possible to access and modify the arguments the method is called with:
*
* ```ts
* method( a, b ) {
* console.log( `${ a }, ${ b }` );
* }
*
* // ...
*
* foo.on( 'method', ( evt, args ) => {
* args[ 0 ] = 3;
*
* console.log( args[ 1 ] ); // -> 2
* }, { priority: 'high' } );
*
* foo.method( 1, 2 ); // -> '3, 2'
* ```
*
* @param methodName Name of the method to decorate.
*/
decorate(methodName: keyof this & string): void;
}
/**
* Fired when a property changed value.
*
* ```ts
* observable.set( 'prop', 1 );
*
* observable.on>( 'change:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `${ propertyName } has changed from ${ oldValue } to ${ newValue }` );
* } );
*
* observable.prop = 2; // -> 'prop has changed from 1 to 2'
* ```
*
* @eventName ~Observable#change:\{property\}
* @param {String} name The property name.
* @param {*} value The new property value.
* @param {*} oldValue The previous property value.
*/
export type ObservableChangeEvent = {
name: 'change' | `change:${string}`;
args: [name: string, value: TValue, oldValue: TValue];
};
/**
* Fired when a property value is going to be set but is not set yet (before the `change` event is fired).
*
* You can control the final value of the property by using
* the {@link module:utils/eventinfo~EventInfo#return event's `return` property}.
*
* ```ts
* observable.set( 'prop', 1 );
*
* observable.on>( 'set:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `Value is going to be changed from ${ oldValue } to ${ newValue }` );
* console.log( `Current property value is ${ observable[ propertyName ] }` );
*
* // Let's override the value.
* evt.return = 3;
* } );
*
* observable.on>( 'change:prop', ( evt, propertyName, newValue, oldValue ) => {
* console.log( `Value has changed from ${ oldValue } to ${ newValue }` );
* } );
*
* observable.prop = 2; // -> 'Value is going to be changed from 1 to 2'
* // -> 'Current property value is 1'
* // -> 'Value has changed from 1 to 3'
* ```
*
* **Note:** The event is fired even when the new value is the same as the old value.
*
* @eventName ~Observable#set:\{property\}
* @param {String} name The property name.
* @param {*} value The new property value.
* @param {*} oldValue The previous property value.
*/
export type ObservableSetEvent = {
name: 'set' | `set:${string}`;
args: [name: string, value: TValue, oldValue: TValue];
return: TValue;
};
/**
* Utility type that creates an event describing type from decorated method.
*
* ```ts
* class Foo extends ObservableMixin() {
* constructor() {
* super();
* this.decorate( 'method' );
* }
*
* method( a: number, b: number ): number {
* return a + b;
* }
* }
*
* type FooMethodEvent = DecoratedMethodEvent;
*
* const foo = new Foo();
*
* foo.on( 'method', ( evt, [ a, b ] ) => {
* // `a` and `b` are inferred as numbers.
* } )
* ```
*/
export type DecoratedMethodEvent) => any;
}, TName extends keyof TObservable & string> = {
name: TName;
args: [Parameters];
return: ReturnType;
};
export interface ObservableSingleBindChain {
toMany(observables: ReadonlyArray, key: K, callback: (...values: Array) => TVal): void;
to>(observable: O): void;
to>(observable: O, callback: (value: O[TKey]) => TVal): void;
to, K extends keyof O>(observable: O, key: K): void;
to(observable: O, key: K, callback: (value: O[K]) => TVal): void;
to, O2 extends ObservableWithProperty>(observable1: O1, observable2: O2, callback: (value1: O1[TKey], value2: O2[TKey]) => TVal): void;
to(observable1: O1, key1: K1, observable2: O2, key2: K2, callback: (value1: O1[K1], value2: O2[K2]) => TVal): void;
to, O2 extends ObservableWithProperty, O3 extends ObservableWithProperty>(observable1: O1, observable2: O2, observable3: O3, callback: (value1: O1[TKey], value2: O2[TKey], value3: O3[TKey]) => TVal): void;
to(observable1: O1, key1: K1, observable2: O2, key2: K2, observable3: O3, key3: K3, callback: (value1: O1[K1], value2: O2[K2], value3: O3[K3]) => TVal): void;
to, O2 extends ObservableWithProperty, O3 extends ObservableWithProperty, O4 extends ObservableWithProperty>(observable1: O1, observable2: O2, observable3: O3, observable4: O4, callback: (value1: O1[TKey], value2: O2[TKey], value3: O3[TKey], value4: O4[TKey]) => TVal): void;
to(observable1: O1, key1: K1, observable2: O2, key2: K2, observable3: O3, key3: K3, observable4: O4, key4: K4, callback: (value1: O1[K1], value2: O2[K2], value3: O3[K3], value4: O4[K4]) => TVal): void;
to, O2 extends ObservableWithProperty, O3 extends ObservableWithProperty, O4 extends ObservableWithProperty, O5 extends ObservableWithProperty>(observable1: O1, observable2: O2, observable3: O3, observable4: O4, observable5: O5, callback: (value1: O1[TKey], value2: O2[TKey], value3: O3[TKey], value4: O4[TKey], value5: O5[TKey]) => TVal): void;
to(observable1: O1, key1: K1, observable2: O2, key2: K2, observable3: O3, key3: K3, observable4: O4, key4: K4, observable5: O5, key5: K5, callback: (value1: O1[K1], value2: O2[K2], value3: O3[K3], value4: O4[K4], value5: O5[K5]) => TVal): void;
}
/**
* A helper type that can be used as a constraint, ensuring the type is both observable and have the given property.
*
* ```ts
* // Ensures that `obj` is `Observable` and have property named 'abc'.
* function f>( obj: O ) {}
*
* // Ensures that `obj` is `Observable` and have property named 'abc' with value `number`.
* function f>( obj: O ) {}
* ```
*/
export type ObservableWithProperty = undefined extends TVal ? Observable & {
[P in TKey]?: TVal;
} : Observable & {
[P in TKey]: TVal;
};
export interface ObservableDualBindChain {
to & ObservableWithProperty, K1 extends keyof O, K2 extends keyof O>(observable: O, key1: K1, key2: K2): void;
to & ObservableWithProperty>(observable: O): void;
}
export interface ObservableMultiBindChain {
to(observable: O, ...properties: Array): void;
}