import { values } from "mobx" import { modelAction } from "../action/modelAction" import type { AnyModel } from "../model/BaseModel" import { Model } from "../model/Model" import { modelIdKey } from "../model/metadata" import { model } from "../modelShared/modelDecorator" import { idProp } from "../modelShared/prop" import { typesArray } from "../types/arrayBased/typesArray" import { tProp } from "../types/tProp" import { typesUnchecked } from "../types/utility/typesUnchecked" import { namespace } from "../utils" const arraySetBase = Model({ [modelIdKey]: idProp, items: tProp(typesArray(typesUnchecked()), () => []), // will be properly checked by types.arraySet(subType) }) as abstract new ( data: any ) => AnyModel & { readonly $: { items: any[] } readonly items: any[] } /** * A set that is backed by an array. * Use `arraySet` to create it. */ @model(`${namespace}/ArraySet`) // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: model base defines these properties at runtime. export class ArraySet extends arraySetBase implements Set { @modelAction add(value: V): this { const items = this.items if (!items.includes(value)) { items.push(value) } return this } @modelAction clear(): void { this.items.length = 0 } @modelAction delete(value: V): boolean { const items = this.items const index = items.indexOf(value) if (index >= 0) { items.splice(index, 1) return true } else { return false } } forEach(callbackfn: (value: V, value2: V, set: Set) => void, thisArg?: any): void { // we cannot use the set implementation since we need to pass this as set const items = this.items const len = items.length for (let i = 0; i < len; i++) { const k = items[i] callbackfn.call(thisArg, k, k, this) } } has(value: V): boolean { return this.items.includes(value) } get size(): number { return this.items.length } *keys(): ReturnType["keys"]> { // yes, values for (const value of values(this.items)) { yield value as V } } *values(): ReturnType["values"]> { for (const value of values(this.items)) { yield value as V } } *entries(): ReturnType["entries"]> { for (const v of this.items) { yield [v, v] } } [Symbol.iterator](): ReturnType[typeof Symbol.iterator]> { return this.values() } readonly [Symbol.toStringTag] = "ArraySet" union(other: ReadonlySetLike): Set { const s = new Set(this) return s.union(other) } intersection(other: ReadonlySetLike): Set { const s = new Set(this) return s.intersection(other) } difference(other: ReadonlySetLike): Set { const s = new Set(this) return s.difference(other) } symmetricDifference(other: ReadonlySetLike): Set { const s = new Set(this) return s.symmetricDifference(other) } isSubsetOf(other: ReadonlySetLike): boolean { const s = new Set(this) return s.isSubsetOf(other) } isSupersetOf(other: ReadonlySetLike): boolean { const s = new Set(this) return s.isSupersetOf(other) } isDisjointFrom(other: ReadonlySetLike): boolean { const s = new Set(this) return s.isDisjointFrom(other) } } export interface ArraySet { readonly $: { items: V[] } readonly items: V[] } /** * Creates a new ArraySet model instance. * * @template V Value type. * @param [values] Optional initial values. */ export function arraySet(values?: ReadonlyArray | null): ArraySet { const initialArr: V[] = values ? values.slice() : [] return new ArraySet({ items: initialArr }) }