import { reactive, computed, watch, toRaw, isReactive } from "vue"; export const useFormUpdateTracker = (initialFormValue) => { const form = reactive(deepClone(initialFormValue)); let originalForm = deepClone(initialFormValue); const updatedFields = reactive({}); const getUpdatedFields = computed(() => { return updatedFields; }); const hasUpdatedFields = computed(() => { return Object.keys(updatedFields).length > 0; }); const handleNestedObjects = (obj, orig, pathArr = []) => { Object.keys(obj).forEach((key) => { const value = obj[key]; const origValue = orig ? orig[key] : undefined; const currentPath = pathArr.concat(key); const pathStr = currentPath.join("."); if (Array.isArray(value)) { if (!isEqual(value, origValue)) { updatedFields[pathStr] = deepClone(value); } else { delete updatedFields[pathStr]; } } else if (value && typeof value === "object" && !Array.isArray(value)) { handleNestedObjects(value, origValue, currentPath); } else { if (!isEqual(value, origValue)) { updatedFields[pathStr] = value; } else { delete updatedFields[pathStr]; } } }); }; watch( form, (newValue) => { handleNestedObjects(newValue, originalForm); }, { deep: true }, ); const resetUpdateForm = () => { Object.keys(form).forEach((key) => { form[key] = deepClone(originalForm[key]); }); Object.keys(updatedFields).forEach((key) => { delete updatedFields[key]; }); }; const setUpdateFormAsOriginal = () => { originalForm = deepClone(form); Object.keys(updatedFields).forEach((key) => { delete updatedFields[key]; }); }; return { form, getUpdatedFields, hasUpdatedFields, resetUpdateForm, setUpdateFormAsOriginal, }; }; function deepClone(obj) { if (obj && isReactive(obj)) { obj = toRaw(obj); } if (typeof structuredClone === "function") { try { return structuredClone(obj); } catch (e) { console.warn("structuredClone not supported, using JSON methods instead"); } } return JSON.parse(JSON.stringify(obj)); } function isEqual(a, b) { if (a === b) return true; if (typeof a !== typeof b) return false; if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; return a.every((item, i) => isEqual(item, b[i])); } if (typeof a === "object" && a && b) { const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return false; return aKeys.every((key) => isEqual(a[key], b[key])); } return false; }