UNPKG

@akala/core

Version:
341 lines 11.3 kB
import { Event } from "../events/shared.js"; import { formatters } from "../formatters/index.js"; import { isPromiseLike } from "../promiseHelpers.js"; import { watcher } from "./shared.js"; /** * ObservableArray class that extends Event. * @template T */ export class ObservableArray extends Event { array; /** * Constructor for ObservableArray. * @param {Array<T> | ObservableArray<T>} array - The array to observe. */ constructor(array) { super(Event.maxListeners, null); if (watcher in array) return array[watcher]; this.array = array; Object.defineProperty(array, watcher, { value: this, enumerable: false, configurable: false }); for (let i = 0; i < array.length; i++) { Object.defineProperty(this, i, { get: () => this.array[i], set: (value) => this.array[i] = value, configurable: true, }); } } [watcher] = this; get length() { return this.array.length; } set length(value) { const oldItems = this.array.slice(value); this.emit({ action: 'pop', oldItems: oldItems }); this.array.length = value; } /** * Pushes items to the array. * @param {...T[]} items - The items to push. * @returns {number} The new length of the array. */ push(...items) { const arrayLength = this.array.length; this.array.push(...items); const finalLength = arrayLength + items.length; for (let i = arrayLength; i < finalLength; i++) { Object.defineProperty(this, i, { get: () => this.array[i], set: (value) => this.array[i] = value, configurable: true, }); } this.emit({ action: 'push', newItems: items }); return finalLength; } /** * Shifts items from the array. * @param {number} [count=1] - The number of items to shift. * @returns {void} */ shift(count = 1) { const items = this.array.splice(0, count); for (let i = this.array.length - count; i < this.array.length; i++) { delete this[i]; } this.emit({ action: 'shift', oldItems: items }); } /** * Pops items from the array. * @param {number} [count=1] - The number of items to pop. * @returns {T[]} The popped items. */ pop(count = 1) { const arrayLength = this.array.length; const items = this.array.splice(this.array.length - count, count); for (let i = arrayLength - count; i < arrayLength; i++) { delete this[i]; } this.emit({ action: 'pop', oldItems: items }); return items; } /** * Unshifts items to the array. * @param {...T[]} items - The items to unshift. * @returns {number} The new length of the array. */ unshift(...items) { this.array.unshift(...items); const finalLength = this.array.length + items.length; for (let i = this.array.length; i < finalLength; i++) { Object.defineProperty(this, i, { get: () => this.array[i], set: (value) => this.array[i] = value, configurable: true, }); } this.emit({ action: 'unshift', newItems: items }); return finalLength; } ; /** * Replaces an item in the array. * @param {number} index - The index of the item to replace. * @param {T} item - The new item. * @returns {void} */ replace(index, item) { return this.replaceN([{ index, item }]); } /** * Replaces multiple items in the array. * @param {{ index: number, item: T }[]} x - The items to replace. * @returns {void} */ replaceN(x) { this.emit({ action: 'replace', replacedItems: x.map(({ index, item }) => { const oldItem = this.array[index]; this.array.splice(index, 1, item); return { index: index, newItem: item, oldItem }; }), }); } /** * Sorts the array. * @param {(a: T, b: T) => number} [comparer] - The comparer function. * @returns {ObservableArray<T>} The sorted array. */ sort(comparer) { const notSorted = this.array.slice(0); this.array.sort(comparer); const event = { action: 'replace', replacedItems: this.array.map((x, i) => x == notSorted[i] ? null : ({ index: i, oldItem: notSorted[i], newItem: x })).filter(x => x) }; if (event.replacedItems.length) this.emit(event); return this; } /** * Adds a listener to the array. * @param {(args_0: ObservableArrayEventMap<T>) => void} listener - The listener function. * @param {{ triggerAtRegistration?: boolean, once?: boolean }} [options] - The options for the listener. * @returns {Subscription} The subscription. */ addListener(listener, options) { const sub = super.addListener(listener, options); if (options?.triggerAtRegistration) listener({ action: 'init', newItems: this.array.slice(0) }); return sub; } subcription; /** * Splices the array. * @param {number} start - The start index. * @param {number} [deleteCount] - The number of items to delete. * @param {...T[]} replaceItems - The items to replace. * @returns {T[]} The deleted items. */ splice(start, deleteCount, ...replaceItems) { if (deleteCount === (replaceItems?.length ?? 0)) { if (deleteCount === 0) return replaceItems || []; const oldItems = this.array.splice(start, deleteCount, ...replaceItems); this.emit({ action: 'replace', replacedItems: oldItems.map((oldItem, i) => ({ oldItem, newItem: replaceItems[i], index: i + start })) }); return oldItems; } else if (deleteCount < (replaceItems?.length ?? 0)) { const oldItems = this.splice(start, deleteCount, ...replaceItems.slice(0, deleteCount)); this.push(...replaceItems.slice(deleteCount)); return oldItems; } else { const oldItems = this.splice(start, replaceItems.length, ...replaceItems); oldItems.push(...this.pop(deleteCount - replaceItems.length)); return oldItems; } } /** * Replaces the array with a new array. * @param {T[] | ObservableArray<T>} values - The new array. * @returns {void} */ replaceArray(values) { this.subcription?.(); const array = Array.isArray(values) ? values : values.array; this.splice(0, this.length, ...array); if (!Array.isArray(values)) this.subcription = values.addListener(ev => { switch (ev.action) { case "pop": this.pop(ev.oldItems.length); break; case "push": this.push(...ev.newItems); break; case "shift": this.shift(ev.oldItems.length); break; case "unshift": this.unshift(...ev.newItems); break; case "replace": for (let i = 0; i < ev.replacedItems.length; i++) this.replace(ev.replacedItems[i].index, ev.replacedItems[i].newItem); break; case "init": this.replaceArray(ev.newItems); break; } }); } indexOf(...args) { return this.array.indexOf(...args); } /** * Maps the array. * @param {...Parameters<Array<T>['map']<U>>} args - The arguments for the map function. * @returns {ReturnType<Array<T>['map']<U>>} The mapped array. */ map(callbackfn, thisArg) { return this.array.map(callbackfn, thisArg); } reduce(callbackfn, initialValue) { return this.array.reduce(callbackfn, initialValue); } /** * Filters the array. * @param {...Parameters<Array<T>['filter']>} args - The arguments for the filter function. * @returns {ReturnType<Array<T>['filter']>} The filtered array. */ filter(...args) { return this.array.filter(...args); } /** * Finds an element in the array. * @param {...Parameters<Array<T>['find']>} args - The arguments for the find function. * @returns {ReturnType<Array<T>['find']>} The found element. */ find(...args) { return this.array.find(...args); } /** * Finds an element index in the array. * @param {...Parameters<Array<T>['findIndex']>} args - The arguments for the find function. * @returns {ReturnType<Array<T>['findIndex']>} The found element index. */ findIndex(...args) { return this.array.findIndex(...args); } /** * Iterates over the array. * @param {...Parameters<Array<T>['forEach']>} args - The arguments for the forEach function. * @returns {ReturnType<Array<T>['forEach']>} The result of the iteration. */ forEach(...args) { return this.array.forEach(...args); } [Symbol.iterator]() { return this.array[Symbol.iterator](); } /** * Converts the array to a string. * @returns {string} The string representation of the array. */ toString() { return this.array.toString(); } [Symbol.dispose]() { this.subcription?.(); super[Symbol.dispose](); } } /** * AsyncArrayFormatter class that implements Formatter. * @template T */ export class AsyncArrayFormatter { promise; value; result = new ObservableArray([]); /** * Formats the value. * @param {T[] | ObservableArray<T>} value - The value to format. * @returns {ObservableArray<T>} The formatted value. */ format(value) { if (!isPromiseLike(value)) { if (this.value != value) { this.value = value; this.result.replaceArray(this.value); } } else { if (this.promise !== value) { this.promise = value; this.value = null; if (this.result.length) this.result.replaceArray([]); value.then(v => { this.value = v; this.result.replaceArray(v); }, err => console.debug('a watched promise failed with err %O', err)); } } return this.result; } constructor() { } } formatters.register('asyncArray', AsyncArrayFormatter); //# sourceMappingURL=array.js.map