/** * @module YArray */ import { YEvent, AbstractType, typeListGet, typeListToArray, typeListForEach, typeListCreateIterator, typeListInsertGenerics, typeListPushGenerics, typeListDelete, typeListMap, YArrayRefID, callTypeObservers, transact, warnPrematureAccess, ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line } from '../internals.js' import { typeListSlice } from './AbstractType.js' /** * Event that describes the changes on a YArray * @template T * @extends YEvent> */ export class YArrayEvent extends YEvent {} /** * A shared Array implementation. * @template T * @extends AbstractType> * @implements {Iterable} */ export class YArray extends AbstractType { constructor () { super() /** * @type {Array?} * @private */ this._prelimContent = [] /** * @type {Array} */ this._searchMarker = [] } /** * Construct a new YArray containing the specified items. * @template {Object|Array|number|null|string|Uint8Array} T * @param {Array} items * @return {YArray} */ static from (items) { /** * @type {YArray} */ const a = new YArray() a.push(items) return a } /** * Integrate this type into the Yjs instance. * * * Save this struct in the os * * This type is sent to other client * * Observer functions are fired * * @param {Doc} y The Yjs instance * @param {Item} item */ _integrate (y, item) { super._integrate(y, item) this.insert(0, /** @type {Array} */ (this._prelimContent)) this._prelimContent = null } /** * @return {YArray} */ _copy () { return new YArray() } /** * Makes a copy of this data type that can be included somewhere else. * * Note that the content is only readable _after_ it has been included somewhere in the Ydoc. * * @return {YArray} */ clone () { /** * @type {YArray} */ const arr = new YArray() arr.insert(0, this.toArray().map(el => el instanceof AbstractType ? /** @type {typeof el} */ (el.clone()) : el )) return arr } get length () { this.doc ?? warnPrematureAccess() return this._length } /** * Creates YArrayEvent and calls observers. * * @param {Transaction} transaction * @param {Set} parentSubs Keys changed on this type. `null` if list was modified. */ _callObserver (transaction, parentSubs) { super._callObserver(transaction, parentSubs) callTypeObservers(this, transaction, new YArrayEvent(this, transaction)) } /** * Inserts new content at an index. * * Important: This function expects an array of content. Not just a content * object. The reason for this "weirdness" is that inserting several elements * is very efficient when it is done as a single operation. * * @example * // Insert character 'a' at position 0 * yarray.insert(0, ['a']) * // Insert numbers 1, 2 at position 1 * yarray.insert(1, [1, 2]) * * @param {number} index The index to insert content at. * @param {Array} content The array of content */ insert (index, content) { if (this.doc !== null) { transact(this.doc, transaction => { typeListInsertGenerics(transaction, this, index, /** @type {any} */ (content)) }) } else { /** @type {Array} */ (this._prelimContent).splice(index, 0, ...content) } } /** * Appends content to this YArray. * * @param {Array} content Array of content to append. * * @todo Use the following implementation in all types. */ push (content) { if (this.doc !== null) { transact(this.doc, transaction => { typeListPushGenerics(transaction, this, /** @type {any} */ (content)) }) } else { /** @type {Array} */ (this._prelimContent).push(...content) } } /** * Prepends content to this YArray. * * @param {Array} content Array of content to prepend. */ unshift (content) { this.insert(0, content) } /** * Deletes elements starting from an index. * * @param {number} index Index at which to start deleting elements * @param {number} length The number of elements to remove. Defaults to 1. */ delete (index, length = 1) { if (this.doc !== null) { transact(this.doc, transaction => { typeListDelete(transaction, this, index, length) }) } else { /** @type {Array} */ (this._prelimContent).splice(index, length) } } /** * Returns the i-th element from a YArray. * * @param {number} index The index of the element to return from the YArray * @return {T} */ get (index) { return typeListGet(this, index) } /** * Transforms this YArray to a JavaScript Array. * * @return {Array} */ toArray () { return typeListToArray(this) } /** * Returns a portion of this YArray into a JavaScript Array selected * from start to end (end not included). * * @param {number} [start] * @param {number} [end] * @return {Array} */ slice (start = 0, end = this.length) { return typeListSlice(this, start, end) } /** * Transforms this Shared Type to a JSON object. * * @return {Array} */ toJSON () { return this.map(c => c instanceof AbstractType ? c.toJSON() : c) } /** * Returns an Array with the result of calling a provided function on every * element of this YArray. * * @template M * @param {function(T,number,YArray):M} f Function that produces an element of the new Array * @return {Array} A new array with each element being the result of the * callback function */ map (f) { return typeListMap(this, /** @type {any} */ (f)) } /** * Executes a provided function once on every element of this YArray. * * @param {function(T,number,YArray):void} f A function to execute on every element of this YArray. */ forEach (f) { typeListForEach(this, f) } /** * @return {IterableIterator} */ [Symbol.iterator] () { return typeListCreateIterator(this) } /** * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder */ _write (encoder) { encoder.writeTypeRef(YArrayRefID) } } /** * @param {UpdateDecoderV1 | UpdateDecoderV2} _decoder * * @private * @function */ export const readYArray = _decoder => new YArray()