diff --git a/package.json b/package.json index 1c7ca86..ab30d26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "indexed-collection", - "version": "1.8.0", + "version": "1.9.0", "description": "A zero-dependency library of classes that make filtering, sorting and observing changes to arrays easier and more efficient.", "license": "MIT", "keywords": [ diff --git a/src/collections/IndexedCollectionBase.ts b/src/collections/IndexedCollectionBase.ts index 6359fa6..9dabbf9 100644 --- a/src/collections/IndexedCollectionBase.ts +++ b/src/collections/IndexedCollectionBase.ts @@ -161,6 +161,24 @@ export abstract class IndexedCollectionBase return true; } + /** + * Move item before the specified item + * @param item The item to move + * @param before + */ + moveBefore(item: T, before: T): void { + this._allItemList.moveBefore(item, before); + } + + /** + * Move item after the specified item + * @param item The item to move + * @param after + */ + moveAfter(item: T, after: T): void { + this._allItemList.moveAfter(item, after); + } + get items(): readonly T[] { return this._allItemList.output; } diff --git a/src/core/internals/IInternalList.ts b/src/core/internals/IInternalList.ts index 1db3f31..5d1a6ad 100644 --- a/src/core/internals/IInternalList.ts +++ b/src/core/internals/IInternalList.ts @@ -29,6 +29,20 @@ export interface IInternalList { */ update(newItem: T, oldItem: T): void; + /** + * Move the item before another item + * @param item + * @param before + */ + moveBefore(item: T, before: T): void; + + /** + * Move the item after another item + * @param item + * @param after + */ + moveAfter(item: T, after: T): void; + readonly output: readonly T[]; readonly count: number; diff --git a/src/core/internals/InternalList.ts b/src/core/internals/InternalList.ts index ac584cc..aa0133a 100644 --- a/src/core/internals/InternalList.ts +++ b/src/core/internals/InternalList.ts @@ -39,7 +39,9 @@ export class InternalList implements IInternalList { } remove(item: T): void { - const index: number = this.source.findIndex(listItem => listItem === item); + const index: number = this.source.findIndex( + (listItem) => listItem === item + ); if (index >= 0) { this.source.splice(index, 1); this.invalidate(); @@ -48,11 +50,39 @@ export class InternalList implements IInternalList { update(newItem: T, oldItem: T): void { const index: number = this.source.findIndex( - listItem => listItem === oldItem + (listItem) => listItem === oldItem ); if (index >= 0) { this.source[index] = newItem; this.invalidate(); } } + + moveBefore(item: T, before: T): void { + const itemIndex: number = this.source.findIndex( + (listItem) => listItem === item + ); + const beforeIndex: number = this.source.findIndex( + (listItem) => listItem === before + ); + if (itemIndex >= 0 && beforeIndex >= 0) { + this.source.splice(itemIndex, 1); + this.source.splice(beforeIndex, 0, item); + this.invalidate(); + } + } + + moveAfter(item: T, after: T): void { + const itemIndex: number = this.source.findIndex( + (listItem) => listItem === item + ); + const afterIndex: number = this.source.findIndex( + (listItem) => listItem === after + ); + if (itemIndex >= 0 && afterIndex >= 0) { + this.source.splice(itemIndex, 1); + this.source.splice(afterIndex, 0, item); + this.invalidate(); + } + } } diff --git a/src/core/internals/InternalSetList.ts b/src/core/internals/InternalSetList.ts index cd4de34..bcf6a2f 100644 --- a/src/core/internals/InternalSetList.ts +++ b/src/core/internals/InternalSetList.ts @@ -51,4 +51,14 @@ export class InternalSetList implements IInternalList { this.invalidate(); } } + + moveBefore(_item: T, _before: T): void { + // There is no concept of order in a set + return; + } + + moveAfter(_item: T, _after: T): void { + // There is no concept of order in a set + return; + } } diff --git a/src/signals/Signal.ts b/src/signals/Signal.ts index 54ee877..58ef3f8 100644 --- a/src/signals/Signal.ts +++ b/src/signals/Signal.ts @@ -1,3 +1,7 @@ +/** + * Signal is a base class for all signals. + * + */ export abstract class Signal { protected constructor( public readonly type: symbol, diff --git a/src/signals/SignalObserver.ts b/src/signals/SignalObserver.ts index a6c699d..dd2d660 100644 --- a/src/signals/SignalObserver.ts +++ b/src/signals/SignalObserver.ts @@ -1,15 +1,22 @@ import { ISignalObserver, SignalHandler } from './ISignalObserver'; import { Signal, SignalType } from './Signal'; +/** + * Signal observer is a class that can be used to observe signals + * It supports multiple observers for a single signal type and vice versa + */ export class SignalObserver implements ISignalObserver { private readonly typeToHandleMap: Map>>; - private handlerToTypeMap: Map< - SignalHandler, - Set - > = new Map(); + private handlerToTypeMap: Map, Set> = + new Map(); constructor() { this.typeToHandleMap = new Map(); } + + /** + * Notify all observers of a signal by the signal's type + * @param signal + */ notifyObservers(signal: Signal): void { const handlers = this.typeToHandleMap.get(signal.type); if (handlers) { @@ -19,6 +26,11 @@ export class SignalObserver implements ISignalObserver { } } + /** + * Register an observer for a signal type + * @param type The type of a signal + * @param handler The handler to be called when the signal is emitted + */ registerObserver( type: symbol, handler: SignalHandler @@ -35,6 +47,11 @@ export class SignalObserver implements ISignalObserver { this.handlerToTypeMap.set(handler, types); } + /** + * Unregister an observer for a signal type + * @param handler The handle to be unregistered + * @param type (Optional) The type of a signal (if not provided, all types associated with the handle will be unregistered) + */ unregisterObserver( handler: SignalHandler, type?: symbol diff --git a/test/IndexedCollection.test.ts b/test/IndexedCollection.test.ts index a98add2..8317fa6 100644 --- a/test/IndexedCollection.test.ts +++ b/test/IndexedCollection.test.ts @@ -121,4 +121,18 @@ describe('mutable collection tests', () => { ); }); }); + + describe('move', () => { + beforeEach(() => { + carsArrayCollection.moveBefore(usedTeslaModel3, newTeslaModelX); + }); + + it('The number of items in the list have not changed', () => { + expect(carsArrayCollection.count).toEqual(allCars.length); + }); + + it('The order of the cars has changed', () => { + expect(carsArrayCollection.items).not.toEqual(allCars); + }); + }); }); diff --git a/test/internals/InternalList.test.ts b/test/internals/InternalList.test.ts index 376d7a4..a30bd96 100644 --- a/test/internals/InternalList.test.ts +++ b/test/internals/InternalList.test.ts @@ -46,6 +46,27 @@ describe('invalidation test', () => { expect(list.output).not.toBe(outputBefore); }); }); + + describe('moveBefore', () => { + beforeEach(() => { + list.moveBefore(7, 6); + }); + + test('source reorder the number according to the move', () => { + expect(list.source).toEqual([1, 7, 6]); + }); + }); + + describe('moveAfter', () => { + beforeEach(() => { + list.moveAfter(1, 6); + }); + + test('source reorder the number according to the move', () => { + console.log(list.source); + expect(list.source).toEqual([6, 1, 7]); + }); + }); }); describe('direct source mutation - invalidate should generate a new list after changes', () => {