@akala/core
Version:
341 lines • 11.3 kB
JavaScript
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