UNPKG

mobx-keystone-mindreframer

Version:

A MobX powered state management solution based on data trees with first class support for Typescript, snapshots, patches and much more

128 lines (108 loc) 3.04 kB
import { action, intercept, IObservableArray, ISetWillChange, isObservableArray, observable, ObservableSet, observe, } from "mobx" import { assertIsObservableArray, assertIsSet, failure, inDevMode } from "../utils" import { Lock } from "../utils/lock" import { tag } from "../utils/tag" const observableSetBackedByObservableArray = action( <T>(array: IObservableArray<T>): ObservableSet<T> & { dataObject: typeof array } => { if (inDevMode()) { if (!isObservableArray(array)) { throw failure("assertion failed: expected an observable array") } } const set = observable.set(array) ;(set as any).dataObject = array if (set.size !== array.length) { throw failure("arrays backing a set cannot contain duplicate values") } const mutationLock = new Lock() // for speed reasons we will just assume distinct values are only once in the array // when the array changes the set changes observe( array, action( mutationLock.unlockedFn((change: any /*IArrayDidChange<T>*/) => { switch (change.type) { case "splice": { { const removed = change.removed for (let i = 0; i < removed.length; i++) { set.delete(removed[i]) } } { const added = change.added for (let i = 0; i < added.length; i++) { set.add(added[i]) } } break } case "update": { set.delete(change.oldValue) set.add(change.newValue) break } } }) ) ) // when the set changes also change the array intercept( set, action((change: ISetWillChange<T>) => { if (!mutationLock.isLocked) { return null // already changed } switch (change.type) { case "add": { array.push(change.newValue) break } case "delete": { const i = array.indexOf(change.oldValue) if (i >= 0) { array.splice(i, 1) } break } } return change }) ) return set as any } ) const asSetTag = tag((array: Array<any>) => { assertIsObservableArray(array, "array") return observableSetBackedByObservableArray(array) }) /** * Wraps an observable array to offer a set like interface. * * @param array */ export function asSet<T>(array: Array<T>): ObservableSet<T> & { dataObject: typeof array } { return asSetTag.for(array) as any } /** * Converts a set to an array. If the set is a collection wrapper it will return the backed array. * * @param set */ export function setToArray<T>(set: Set<T>): Array<T> { assertIsSet(set, "set") const dataObject = (set as any).dataObject if (dataObject) { return dataObject } return Array.from(set.values()) }