import { computed, readonly, type Ref, ref, toRef } from 'vue'; import isEqual from 'dequal'; const clone = (value: T): T => { if (value !== null && typeof value === 'object') { // nested reactivity means that structuredClone() isn't possible, the next // closest thing is to stringify the object into JSON and parse it back. return JSON.parse(JSON.stringify(value)) as T; } return value; }; const writableObject = , K extends keyof T>( upstream: Ref, keys: K[] ) => { const scratch = ref( ((): T => { const ro = readonly(upstream); const cloned: Record = {}; for (const key in upstream.value) { if (keys.includes(key as any)) { cloned[key] = clone(upstream.value[key]); } else { cloned[key] = toRef(() => ro.value[key]); } } return cloned as T; })() ) as Ref; return { scratch: scratch as Readonly>, hasChanges: computed(() => { return !isEqual(upstream.value, scratch.value); }), resetChanges: () => { for (const key in upstream.value) { scratch.value[key] = clone(upstream.value[key]); } }, }; };