@ckeditor/ckeditor5-utils
Version:
Miscellaneous utilities used by CKEditor 5.
434 lines (433 loc) • 14.8 kB
TypeScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
declare const Collection_base: {
new (): import("./emittermixin.js").Emitter;
prototype: import("./emittermixin.js").Emitter;
};
/**
* Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes
* in the collection (like in an array) or by their ids.
*
* If an object without an `id` property is being added to the collection, the `id` property will be generated
* automatically. Note that the automatically generated id is unique only within this single collection instance.
*
* By default an item in the collection is identified by its `id` property. The name of the identifier can be
* configured through the constructor of the collection.
*
* @typeParam T The type of the collection element.
*/
export default class Collection<T extends Record<string, any>> extends /* #__PURE__ */ Collection_base implements Iterable<T> {
/**
* The internal list of items in the collection.
*/
private readonly _items;
/**
* The internal map of items in the collection.
*/
private readonly _itemMap;
/**
* The name of the property which is considered to identify an item.
*/
private readonly _idProperty;
/**
* A collection instance this collection is bound to as a result
* of calling {@link #bindTo} method.
*/
private _bindToCollection?;
/**
* A helper mapping external items of a bound collection ({@link #bindTo})
* and actual items of this collection. It provides information
* necessary to properly remove items bound to another collection.
*
* See {@link #_bindToInternalToExternalMap}.
*/
private readonly _bindToExternalToInternalMap;
/**
* A helper mapping items of this collection to external items of a bound collection
* ({@link #bindTo}). It provides information necessary to manage the bindings, e.g.
* to avoid loops in two–way bindings.
*
* See {@link #_bindToExternalToInternalMap}.
*/
private readonly _bindToInternalToExternalMap;
/**
* Stores indexes of skipped items from bound external collection.
*/
private _skippedIndexesFromExternal;
/**
* Creates a new Collection instance.
*
* You can pass a configuration object as the argument of the constructor:
*
* ```ts
* const emptyCollection = new Collection<{ name: string }>( { idProperty: 'name' } );
* emptyCollection.add( { name: 'John' } );
* console.log( collection.get( 'John' ) ); // -> { name: 'John' }
* ```
*
* The collection is empty by default. You can add new items using the {@link #add} method:
*
* ```ts
* const collection = new Collection<{ id: string }>();
*
* collection.add( { id: 'John' } );
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
* ```
*
* @label NO_ITEMS
* @param options The options object.
* @param options.idProperty The name of the property which is used to identify an item.
* Items that do not have such a property will be assigned one when added to the collection.
*/
constructor(options?: {
readonly idProperty?: string;
});
/**
* Creates a new Collection instance with specified initial items.
*
* ```ts
* const collection = new Collection<{ id: string }>( [ { id: 'John' }, { id: 'Mike' } ] );
*
* console.log( collection.get( 0 ) ); // -> { id: 'John' }
* console.log( collection.get( 1 ) ); // -> { id: 'Mike' }
* console.log( collection.get( 'Mike' ) ); // -> { id: 'Mike' }
* ```
*
* You can always pass a configuration object as the last argument of the constructor:
*
* ```ts
* const nonEmptyCollection = new Collection<{ name: string }>( [ { name: 'John' } ], { idProperty: 'name' } );
* nonEmptyCollection.add( { name: 'George' } );
* console.log( collection.get( 'George' ) ); // -> { name: 'George' }
* console.log( collection.get( 'John' ) ); // -> { name: 'John' }
* ```
*
* @label INITIAL_ITEMS
* @param initialItems The initial items of the collection.
* @param options The options object.
* @param options.idProperty The name of the property which is used to identify an item.
* Items that do not have such a property will be assigned one when added to the collection.
*/
constructor(initialItems: Iterable<T>, options?: {
readonly idProperty?: string;
});
/**
* The number of items available in the collection.
*/
get length(): number;
/**
* Returns the first item from the collection or null when collection is empty.
*/
get first(): T | null;
/**
* Returns the last item from the collection or null when collection is empty.
*/
get last(): T | null;
/**
* Adds an item into the collection.
*
* If the item does not have an id, then it will be automatically generated and set on the item.
*
* @param item
* @param index The position of the item in the collection. The item
* is pushed to the collection when `index` not specified.
* @fires add
* @fires change
*/
add(item: T, index?: number): this;
/**
* Adds multiple items into the collection.
*
* Any item not containing an id will get an automatically generated one.
*
* @param items
* @param index The position of the insertion. Items will be appended if no `index` is specified.
* @fires add
* @fires change
*/
addMany(items: Iterable<T>, index?: number): this;
/**
* Gets an item by its ID or index.
*
* @param idOrIndex The item ID or index in the collection.
* @returns The requested item or `null` if such item does not exist.
*/
get(idOrIndex: string | number): T | null;
/**
* Returns a Boolean indicating whether the collection contains an item.
*
* @param itemOrId The item or its ID in the collection.
* @returns `true` if the collection contains the item, `false` otherwise.
*/
has(itemOrId: T | string): boolean;
/**
* Gets an index of an item in the collection.
* When an item is not defined in the collection, the index will equal -1.
*
* @param itemOrId The item or its ID in the collection.
* @returns The index of a given item.
*/
getIndex(itemOrId: T | string): number;
/**
* Removes an item from the collection.
*
* @param subject The item to remove, its ID or index in the collection.
* @returns The removed item.
* @fires remove
* @fires change
*/
remove(subject: T | number | string): T;
/**
* Executes the callback for each item in the collection and composes an array or values returned by this callback.
*
* @typeParam U The result type of the callback.
* @param callback
* @param ctx Context in which the `callback` will be called.
* @returns The result of mapping.
*/
map<U>(callback: (item: T, index: number) => U, ctx?: any): Array<U>;
/**
* Performs the specified action for each item in the collection.
*
* @param ctx Context in which the `callback` will be called.
*/
forEach(callback: (item: T, index: number) => unknown, ctx?: any): void;
/**
* Finds the first item in the collection for which the `callback` returns a true value.
*
* @param callback
* @param ctx Context in which the `callback` will be called.
* @returns The item for which `callback` returned a true value.
*/
find(callback: (item: T, index: number) => boolean, ctx?: any): T | undefined;
/**
* Returns an array with items for which the `callback` returned a true value.
*
* @param callback
* @param ctx Context in which the `callback` will be called.
* @returns The array with matching items.
*/
filter(callback: (item: T, index: number) => boolean, ctx?: any): Array<T>;
/**
* Removes all items from the collection and destroys the binding created using
* {@link #bindTo}.
*
* @fires remove
* @fires change
*/
clear(): void;
/**
* Binds and synchronizes the collection with another one.
*
* The binding can be a simple factory:
*
* ```ts
* class FactoryClass {
* public label: string;
*
* constructor( data: { label: string } ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection<{ label: string }>( { idProperty: 'label' } );
* const target = new Collection<FactoryClass>();
*
* target.bindTo( source ).as( FactoryClass );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 1 ).label ); // 'bar'
*
* source.remove( 0 );
* console.log( target.length ); // 1
* console.log( target.get( 0 ).label ); // 'bar'
* ```
*
* or the factory driven by a custom callback:
*
* ```ts
* class FooClass {
* public label: string;
*
* constructor( data: { label: string } ) {
* this.label = data.label;
* }
* }
*
* class BarClass {
* public label: string;
*
* constructor( data: { label: string } ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection<{ label: string }>( { idProperty: 'label' } );
* const target = new Collection<FooClass | BarClass>();
*
* target.bindTo( source ).using( ( item ) => {
* if ( item.label == 'foo' ) {
* return new FooClass( item );
* } else {
* return new BarClass( item );
* }
* } );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ) instanceof FooClass ); // true
* console.log( target.get( 1 ) instanceof BarClass ); // true
* ```
*
* or the factory out of property name:
*
* ```ts
* const source = new Collection<{ nested: { value: string } }>();
* const target = new Collection<{ value: string }>();
*
* target.bindTo( source ).using( 'nested' );
*
* source.add( { nested: { value: 'foo' } } );
* source.add( { nested: { value: 'bar' } } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ).value ); // 'foo'
* console.log( target.get( 1 ).value ); // 'bar'
* ```
*
* It's possible to skip specified items by returning null value:
*
* ```ts
* const source = new Collection<{ hidden: boolean }>();
* const target = new Collection<{ hidden: boolean }>();
*
* target.bindTo( source ).using( item => {
* if ( item.hidden ) {
* return null;
* }
*
* return item;
* } );
*
* source.add( { hidden: true } );
* source.add( { hidden: false } );
*
* console.log( source.length ); // 2
* console.log( target.length ); // 1
* ```
*
* **Note**: {@link #clear} can be used to break the binding.
*
* @typeParam S The type of `externalCollection` element.
* @param externalCollection A collection to be bound.
* @returns The binding chain object.
*/
bindTo<S extends Record<string, any>>(externalCollection: Collection<S>): CollectionBindToChain<S, T>;
/**
* Finalizes and activates a binding initiated by {@link #bindTo}.
*
* @param factory A function which produces collection items.
*/
private _setUpBindToBinding;
/**
* Returns an unique id property for a given `item`.
*
* The method will generate new id and assign it to the `item` if it doesn't have any.
*
* @param item Item to be added.
*/
private _getItemIdBeforeAdding;
/**
* Core {@link #remove} method implementation shared in other functions.
*
* In contrast this method **does not** fire the {@link #event:change} event.
*
* @param subject The item to remove, its id or index in the collection.
* @returns Returns an array with the removed item and its index.
* @fires remove
*/
private _remove;
/**
* Iterable interface.
*/
[Symbol.iterator](): Iterator<T>;
}
/**
* Fired when an item is added to the collection.
*
* @eventName ~Collection#add
* @param item The added item.
* @param index An index where the addition occurred.
*/
export type CollectionAddEvent<T = any> = {
name: 'add';
args: [item: T, index: number];
};
/**
* Fired when the collection was changed due to adding or removing items.
*
* @eventName ~Collection#change
* @param data Changed items.
*/
export type CollectionChangeEvent<T = any> = {
name: 'change';
args: [data: CollectionChangeEventData<T>];
};
/**
* A structure describing the {@link ~Collection#event:change `Collection#change`} event.
*/
export type CollectionChangeEventData<T = any> = {
/**
* A list of added items.
*/
added: Iterable<T>;
/**
* A list of removed items.
*/
removed: Iterable<T>;
/**
* An index where the addition or removal occurred.
*/
index: number;
};
/**
* Fired when an item is removed from the collection.
*
* @eventName ~Collection#remove
* @param item The removed item.
* @param index Index from which item was removed.
*/
export type CollectionRemoveEvent<T = any> = {
name: 'remove';
args: [item: T, index: number];
};
/**
* An object returned by the {@link module:utils/collection~Collection#bindTo `bindTo()`} method
* providing functions that specify the type of the binding.
*
* See the {@link module:utils/collection~Collection#bindTo `bindTo()`} documentation for examples.
*/
export interface CollectionBindToChain<S, T> {
/**
* Creates the class factory binding in which items of the source collection are passed to
* the constructor of the specified class.
*
* @param Class The class constructor used to create instances in the factory.
*/
as(Class: new (item: S) => T): void;
/**
* Creates a callback or a property binding.
*
* @param callbackOrProperty When the function is passed, it should return
* the collection items. When the string is provided, the property value is used to create the bound collection items.
*/
using(callbackOrProperty: keyof S | ((item: S) => T | null)): void;
}
export {};