import { action, computed, makeObservable, observable } from 'mobx'; type IndeterminateType = 'empty' | 'partial' | 'full'; interface IndeterminateStatus { type: IndeterminateType; toggle: () => IndeterminateStatus; toggleCheckbox: boolean; } const statuses: Readonly> = { empty: { type: 'empty', toggle: () => statuses.partial, toggleCheckbox: true, }, partial: { type: 'partial', toggle: () => statuses.full, toggleCheckbox: true, }, full: { type: 'full', toggle: () => statuses.empty, toggleCheckbox: false, }, }; export class IndeterminateMapState { readonly _map = observable.map([], { deep: false, }); constructor() { makeObservable(this, { toggle: action, setManyPartial: action, partialModeKeys: computed, partialModeKeysSet: computed, }); } toggle(key: string) { const { _map } = this; const currentStatus = _map.get(key); if (currentStatus == null) { return; } const nextStatus = currentStatus.toggle(); _map.set(key, nextStatus); return nextStatus; } setManyPartial(keys: string[]) { const { _map } = this; for (const key of keys) { if (!_map.has(key)) { _map.set(key, statuses.partial); } } } isPartial(key: string) { const { _map } = this; return _map.get(key)?.type === 'partial'; } get partialModeKeys() { return Array.from(this._map.entries()) .filter(([, value]) => value.type === 'partial') .map(([key]) => key); } get partialModeKeysSet() { const { partialModeKeys } = this; return new Set(partialModeKeys); } }