react-form-draft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +33 -0
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/index.cjs +545 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +542 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactHookForm = require('react-hook-form');
|
|
5
|
+
|
|
6
|
+
// src/hook/useFormDraft.ts
|
|
7
|
+
|
|
8
|
+
// src/core/compare.ts
|
|
9
|
+
function isObjectLike(value) {
|
|
10
|
+
return typeof value === "object" && value !== null;
|
|
11
|
+
}
|
|
12
|
+
function isDate(value) {
|
|
13
|
+
return value instanceof Date;
|
|
14
|
+
}
|
|
15
|
+
function deepEqual(left, right) {
|
|
16
|
+
if (Object.is(left, right)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (isDate(left) && isDate(right)) {
|
|
20
|
+
return left.getTime() === right.getTime();
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
23
|
+
if (left.length !== right.length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return left.every((item, index) => deepEqual(item, right[index]));
|
|
27
|
+
}
|
|
28
|
+
if (!isObjectLike(left) || !isObjectLike(right)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const leftKeys = Object.keys(left);
|
|
32
|
+
const rightKeys = Object.keys(right);
|
|
33
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return leftKeys.every((key) => deepEqual(left[key], right[key]));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/utils/objectPath.ts
|
|
40
|
+
function isObjectLike2(value) {
|
|
41
|
+
return typeof value === "object" && value !== null;
|
|
42
|
+
}
|
|
43
|
+
function isNumericSegment(segment) {
|
|
44
|
+
return /^\d+$/.test(segment);
|
|
45
|
+
}
|
|
46
|
+
function cloneBranch(value) {
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return value.map((item) => cloneBranch(item));
|
|
49
|
+
}
|
|
50
|
+
if (isObjectLike2(value)) {
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
Object.entries(value).map(([key, nestedValue]) => [key, cloneBranch(nestedValue)])
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function getPathSegments(path) {
|
|
58
|
+
return path.split(".").filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
function getValueAtPath(source, path) {
|
|
61
|
+
const segments = getPathSegments(path);
|
|
62
|
+
let current = source;
|
|
63
|
+
for (const segment of segments) {
|
|
64
|
+
if (Array.isArray(current) && isNumericSegment(segment)) {
|
|
65
|
+
current = current[Number(segment)];
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!isObjectLike2(current)) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
current = current[segment];
|
|
72
|
+
}
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
function setValueAtPath(target, path, value) {
|
|
76
|
+
const segments = getPathSegments(path);
|
|
77
|
+
if (segments.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let current = target;
|
|
81
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
82
|
+
const segment = segments[index];
|
|
83
|
+
if (segment === void 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const isLast = index === segments.length - 1;
|
|
87
|
+
const nextSegment = segments[index + 1];
|
|
88
|
+
const arrayContainer = isNumericSegment(nextSegment ?? "");
|
|
89
|
+
if (Array.isArray(current)) {
|
|
90
|
+
const numericIndex = Number(segment);
|
|
91
|
+
if (isLast) {
|
|
92
|
+
current[numericIndex] = cloneBranch(value);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const nextValue2 = current[numericIndex];
|
|
96
|
+
if (!isObjectLike2(nextValue2) && !Array.isArray(nextValue2)) {
|
|
97
|
+
current[numericIndex] = arrayContainer ? [] : {};
|
|
98
|
+
}
|
|
99
|
+
current = current[numericIndex];
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (isLast) {
|
|
103
|
+
current[segment] = cloneBranch(value);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const nextValue = current[segment];
|
|
107
|
+
if (!isObjectLike2(nextValue) && !Array.isArray(nextValue)) {
|
|
108
|
+
current[segment] = arrayContainer ? [] : {};
|
|
109
|
+
}
|
|
110
|
+
current = current[segment];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function removeValueAtPath(target, path) {
|
|
114
|
+
const segments = getPathSegments(path);
|
|
115
|
+
if (segments.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let current = target;
|
|
119
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
120
|
+
const segment = segments[index];
|
|
121
|
+
if (segment === void 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(current) && isNumericSegment(segment)) {
|
|
125
|
+
current = current[Number(segment)];
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!isObjectLike2(current)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
current = current[segment];
|
|
132
|
+
}
|
|
133
|
+
const lastSegment = segments[segments.length - 1];
|
|
134
|
+
if (lastSegment === void 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(current) && isNumericSegment(lastSegment)) {
|
|
138
|
+
current.splice(Number(lastSegment), 1);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (isObjectLike2(current)) {
|
|
142
|
+
delete current[lastSegment];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function cloneValueMap(value) {
|
|
146
|
+
return cloneBranch(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/core/filterValues.ts
|
|
150
|
+
function filterValues(values, options) {
|
|
151
|
+
const { include, exclude } = options;
|
|
152
|
+
const hasInclude = Boolean(include?.length);
|
|
153
|
+
const hasExclude = Boolean(exclude?.length);
|
|
154
|
+
let filtered = hasInclude ? {} : cloneValueMap(values);
|
|
155
|
+
if (hasInclude && include) {
|
|
156
|
+
for (const path of include) {
|
|
157
|
+
const value = getValueAtPath(values, path);
|
|
158
|
+
if (value !== void 0) {
|
|
159
|
+
setValueAtPath(filtered, path, value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (hasExclude && exclude) {
|
|
164
|
+
for (const path of exclude) {
|
|
165
|
+
removeValueAtPath(filtered, path);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return filtered;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/core/mergeValues.ts
|
|
172
|
+
function isObjectLike3(value) {
|
|
173
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
174
|
+
}
|
|
175
|
+
function mergeValues(base, override) {
|
|
176
|
+
if (!isObjectLike3(base) || !isObjectLike3(override)) {
|
|
177
|
+
return override;
|
|
178
|
+
}
|
|
179
|
+
const result = { ...base };
|
|
180
|
+
for (const [key, value] of Object.entries(override)) {
|
|
181
|
+
const currentValue = result[key];
|
|
182
|
+
if (isObjectLike3(currentValue) && isObjectLike3(value)) {
|
|
183
|
+
result[key] = mergeValues(currentValue, value);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
result[key] = value;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/utils/safeJsonParse.ts
|
|
192
|
+
function safeJsonParse(value) {
|
|
193
|
+
try {
|
|
194
|
+
return JSON.parse(value);
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/core/serialization.ts
|
|
201
|
+
function isObjectLike4(value) {
|
|
202
|
+
return typeof value === "object" && value !== null;
|
|
203
|
+
}
|
|
204
|
+
function serializeDraftRecord(record) {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.stringify(record);
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function deserializeDraftRecord(value) {
|
|
212
|
+
const parsed = safeJsonParse(value);
|
|
213
|
+
if (!isObjectLike4(parsed)) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (typeof parsed.savedAt !== "number") {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (parsed.version !== null && typeof parsed.version !== "string" && parsed.version !== void 0) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (!isObjectLike4(parsed.values)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
savedAt: parsed.savedAt,
|
|
227
|
+
version: typeof parsed.version === "string" ? parsed.version : null,
|
|
228
|
+
values: parsed.values
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/core/persist.ts
|
|
233
|
+
function persistDraft(options) {
|
|
234
|
+
const { exclude, include, key, storage, values, version } = options;
|
|
235
|
+
if (storage.isAvailable?.() === false) {
|
|
236
|
+
return { ok: false, reason: "unavailable" };
|
|
237
|
+
}
|
|
238
|
+
const filteredValues = filterValues(values, { include, exclude });
|
|
239
|
+
const record = {
|
|
240
|
+
version: version ?? null,
|
|
241
|
+
savedAt: Date.now(),
|
|
242
|
+
values: filteredValues
|
|
243
|
+
};
|
|
244
|
+
const serializedRecord = serializeDraftRecord(record);
|
|
245
|
+
if (serializedRecord === null) {
|
|
246
|
+
return { ok: false, reason: "serialize_failed" };
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
storage.setItem(key, serializedRecord);
|
|
250
|
+
return { ok: true, record };
|
|
251
|
+
} catch {
|
|
252
|
+
return { ok: false, reason: "storage_failed" };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/core/restore.ts
|
|
257
|
+
function clearDraftRecord(key, storage) {
|
|
258
|
+
if (storage.isAvailable?.() === false) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
storage.removeItem(key);
|
|
263
|
+
return true;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function loadDraft(options) {
|
|
269
|
+
const { key, removeCorruptedDraft = true, storage, version } = options;
|
|
270
|
+
if (storage.isAvailable?.() === false) {
|
|
271
|
+
return { status: "unavailable", record: null };
|
|
272
|
+
}
|
|
273
|
+
let rawValue = null;
|
|
274
|
+
try {
|
|
275
|
+
rawValue = storage.getItem(key);
|
|
276
|
+
} catch {
|
|
277
|
+
return { status: "storage_error", record: null };
|
|
278
|
+
}
|
|
279
|
+
if (rawValue === null) {
|
|
280
|
+
return { status: "missing", record: null };
|
|
281
|
+
}
|
|
282
|
+
const draftRecord = deserializeDraftRecord(rawValue);
|
|
283
|
+
if (draftRecord === null) {
|
|
284
|
+
if (removeCorruptedDraft) {
|
|
285
|
+
clearDraftRecord(key, storage);
|
|
286
|
+
}
|
|
287
|
+
return { status: "invalid", record: null };
|
|
288
|
+
}
|
|
289
|
+
if ((version ?? null) !== draftRecord.version) {
|
|
290
|
+
return { status: "version_mismatch", record: null };
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
status: "found",
|
|
294
|
+
record: draftRecord
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/utils/isBrowser.ts
|
|
299
|
+
function isBrowser() {
|
|
300
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/storage/localStorageAdapter.ts
|
|
304
|
+
function getLocalStorage() {
|
|
305
|
+
if (!isBrowser()) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
return window.localStorage;
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function createLocalStorageAdapter() {
|
|
315
|
+
return {
|
|
316
|
+
getItem(key) {
|
|
317
|
+
const storage = getLocalStorage();
|
|
318
|
+
if (storage === null) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
return storage.getItem(key);
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
setItem(key, value) {
|
|
328
|
+
const storage = getLocalStorage();
|
|
329
|
+
if (storage === null) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
storage.setItem(key, value);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
removeItem(key) {
|
|
338
|
+
const storage = getLocalStorage();
|
|
339
|
+
if (storage === null) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
storage.removeItem(key);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
isAvailable() {
|
|
348
|
+
return getLocalStorage() !== null;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/utils/debounce.ts
|
|
354
|
+
function createDebouncedCallback(callback, waitMs) {
|
|
355
|
+
let timerId = null;
|
|
356
|
+
const clear = () => {
|
|
357
|
+
if (timerId !== null) {
|
|
358
|
+
clearTimeout(timerId);
|
|
359
|
+
timerId = null;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
return {
|
|
363
|
+
schedule() {
|
|
364
|
+
clear();
|
|
365
|
+
timerId = setTimeout(() => {
|
|
366
|
+
timerId = null;
|
|
367
|
+
callback();
|
|
368
|
+
}, waitMs);
|
|
369
|
+
},
|
|
370
|
+
flush() {
|
|
371
|
+
if (timerId === null) {
|
|
372
|
+
callback();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
clear();
|
|
376
|
+
callback();
|
|
377
|
+
},
|
|
378
|
+
cancel() {
|
|
379
|
+
clear();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/hook/useFormDraft.ts
|
|
385
|
+
function toDraftMeta(key, record) {
|
|
386
|
+
return {
|
|
387
|
+
key,
|
|
388
|
+
savedAt: record.savedAt,
|
|
389
|
+
version: record.version
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function useFormDraft(options) {
|
|
393
|
+
const {
|
|
394
|
+
autoRestore = false,
|
|
395
|
+
clearOnSubmit = true,
|
|
396
|
+
debounceMs = 500,
|
|
397
|
+
exclude,
|
|
398
|
+
form,
|
|
399
|
+
include,
|
|
400
|
+
key,
|
|
401
|
+
removeCorruptedDraft = true,
|
|
402
|
+
resetOptions,
|
|
403
|
+
storage,
|
|
404
|
+
version
|
|
405
|
+
} = options;
|
|
406
|
+
const resolvedStorage = react.useMemo(() => storage ?? createLocalStorageAdapter(), [storage]);
|
|
407
|
+
const watchedValues = reactHookForm.useWatch({ control: form.control });
|
|
408
|
+
const [draftMeta, setDraftMeta] = react.useState(null);
|
|
409
|
+
const [hasDraft, setHasDraft] = react.useState(false);
|
|
410
|
+
const [status, setStatus] = react.useState("idle");
|
|
411
|
+
const [lastSavedAt, setLastSavedAt] = react.useState(null);
|
|
412
|
+
const initializedRef = react.useRef(false);
|
|
413
|
+
const draftRef = react.useRef(null);
|
|
414
|
+
const lastPersistedValuesRef = react.useRef(null);
|
|
415
|
+
const debouncedSaveRef = react.useRef(null);
|
|
416
|
+
const persistCurrentValues = react.useCallback(
|
|
417
|
+
(force) => {
|
|
418
|
+
const currentValues = form.getValues();
|
|
419
|
+
const filteredValues = filterValues(currentValues, {
|
|
420
|
+
include,
|
|
421
|
+
exclude
|
|
422
|
+
});
|
|
423
|
+
if (!force && deepEqual(filteredValues, lastPersistedValuesRef.current)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const result = persistDraft({
|
|
427
|
+
key,
|
|
428
|
+
values: currentValues,
|
|
429
|
+
storage: resolvedStorage,
|
|
430
|
+
version,
|
|
431
|
+
include,
|
|
432
|
+
exclude
|
|
433
|
+
});
|
|
434
|
+
if (!result.ok) {
|
|
435
|
+
setStatus("error");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
draftRef.current = result.record;
|
|
439
|
+
lastPersistedValuesRef.current = result.record.values;
|
|
440
|
+
setDraftMeta(toDraftMeta(key, result.record));
|
|
441
|
+
setHasDraft(true);
|
|
442
|
+
setLastSavedAt(result.record.savedAt);
|
|
443
|
+
setStatus("saved");
|
|
444
|
+
},
|
|
445
|
+
[exclude, form, include, key, resolvedStorage, version]
|
|
446
|
+
);
|
|
447
|
+
const clearDraftState = react.useCallback(() => {
|
|
448
|
+
draftRef.current = null;
|
|
449
|
+
lastPersistedValuesRef.current = null;
|
|
450
|
+
setHasDraft(false);
|
|
451
|
+
setDraftMeta(null);
|
|
452
|
+
setLastSavedAt(null);
|
|
453
|
+
setStatus("cleared");
|
|
454
|
+
}, []);
|
|
455
|
+
const clearDraft = react.useCallback(() => {
|
|
456
|
+
debouncedSaveRef.current?.cancel();
|
|
457
|
+
clearDraftRecord(key, resolvedStorage);
|
|
458
|
+
clearDraftState();
|
|
459
|
+
}, [clearDraftState, key, resolvedStorage]);
|
|
460
|
+
const restoreDraft = react.useCallback(() => {
|
|
461
|
+
const existingDraft = draftRef.current;
|
|
462
|
+
if (existingDraft === null) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
const mergedValues = mergeValues(
|
|
466
|
+
form.getValues(),
|
|
467
|
+
existingDraft.values
|
|
468
|
+
);
|
|
469
|
+
form.reset(mergedValues, resetOptions);
|
|
470
|
+
lastPersistedValuesRef.current = existingDraft.values;
|
|
471
|
+
setStatus("restored");
|
|
472
|
+
return true;
|
|
473
|
+
}, [form, resetOptions]);
|
|
474
|
+
react.useEffect(() => {
|
|
475
|
+
debouncedSaveRef.current?.cancel();
|
|
476
|
+
debouncedSaveRef.current = createDebouncedCallback(() => {
|
|
477
|
+
persistCurrentValues(false);
|
|
478
|
+
}, debounceMs);
|
|
479
|
+
return () => {
|
|
480
|
+
debouncedSaveRef.current?.cancel();
|
|
481
|
+
};
|
|
482
|
+
}, [debounceMs, persistCurrentValues]);
|
|
483
|
+
react.useEffect(() => {
|
|
484
|
+
const result = loadDraft({
|
|
485
|
+
key,
|
|
486
|
+
storage: resolvedStorage,
|
|
487
|
+
version,
|
|
488
|
+
removeCorruptedDraft
|
|
489
|
+
});
|
|
490
|
+
if (result.status === "found") {
|
|
491
|
+
draftRef.current = result.record;
|
|
492
|
+
lastPersistedValuesRef.current = result.record.values;
|
|
493
|
+
setDraftMeta(toDraftMeta(key, result.record));
|
|
494
|
+
setHasDraft(true);
|
|
495
|
+
setLastSavedAt(result.record.savedAt);
|
|
496
|
+
if (autoRestore) {
|
|
497
|
+
const mergedValues = mergeValues(
|
|
498
|
+
form.getValues(),
|
|
499
|
+
result.record.values
|
|
500
|
+
);
|
|
501
|
+
form.reset(mergedValues, resetOptions);
|
|
502
|
+
setStatus("restored");
|
|
503
|
+
}
|
|
504
|
+
} else if (result.status === "invalid" || result.status === "storage_error") {
|
|
505
|
+
setStatus("error");
|
|
506
|
+
}
|
|
507
|
+
initializedRef.current = true;
|
|
508
|
+
}, [autoRestore, form, key, removeCorruptedDraft, resetOptions, resolvedStorage, version]);
|
|
509
|
+
react.useEffect(() => {
|
|
510
|
+
if (!initializedRef.current) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (!form.formState.isDirty) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
debouncedSaveRef.current?.schedule();
|
|
517
|
+
}, [form.formState.isDirty, watchedValues]);
|
|
518
|
+
react.useEffect(() => {
|
|
519
|
+
if (!clearOnSubmit) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!form.formState.isSubmitSuccessful) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
clearDraft();
|
|
526
|
+
}, [clearDraft, clearOnSubmit, form.formState.isSubmitSuccessful]);
|
|
527
|
+
return {
|
|
528
|
+
hasDraft,
|
|
529
|
+
draftMeta,
|
|
530
|
+
lastSavedAt,
|
|
531
|
+
status,
|
|
532
|
+
restoreDraft,
|
|
533
|
+
discardDraft: clearDraft,
|
|
534
|
+
clearDraft,
|
|
535
|
+
saveNow: () => {
|
|
536
|
+
debouncedSaveRef.current?.cancel();
|
|
537
|
+
persistCurrentValues(true);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
exports.createLocalStorageAdapter = createLocalStorageAdapter;
|
|
543
|
+
exports.useFormDraft = useFormDraft;
|
|
544
|
+
//# sourceMappingURL=index.cjs.map
|
|
545
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/compare.ts","../src/utils/objectPath.ts","../src/core/filterValues.ts","../src/core/mergeValues.ts","../src/utils/safeJsonParse.ts","../src/core/serialization.ts","../src/core/persist.ts","../src/core/restore.ts","../src/utils/isBrowser.ts","../src/storage/localStorageAdapter.ts","../src/utils/debounce.ts","../src/hook/useFormDraft.ts"],"names":["isObjectLike","nextValue","useMemo","useWatch","useState","useRef","useCallback","useEffect"],"mappings":";;;;;;;;AAAA,SAAS,aAAa,KAAA,EAAkD;AACtE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAEA,SAAS,OAAO,KAAA,EAA+B;AAC7C,EAAA,OAAO,KAAA,YAAiB,IAAA;AAC1B;AAEO,SAAS,SAAA,CAAU,MAAe,KAAA,EAAyB;AAChE,EAAA,IAAI,MAAA,CAAO,EAAA,CAAG,IAAA,EAAM,KAAK,CAAA,EAAG;AAC1B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,CAAO,IAAI,CAAA,IAAK,MAAA,CAAO,KAAK,CAAA,EAAG;AACjC,IAAA,OAAO,IAAA,CAAK,OAAA,EAAQ,KAAM,KAAA,CAAM,OAAA,EAAQ;AAAA,EAC1C;AAEA,EAAA,IAAI,MAAM,OAAA,CAAQ,IAAI,KAAK,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/C,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ;AAChC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAC,IAAA,EAAM,KAAA,KAAU,UAAU,IAAA,EAAM,KAAA,CAAM,KAAK,CAAC,CAAC,CAAA;AAAA,EAClE;AAEA,EAAA,IAAI,CAAC,YAAA,CAAa,IAAI,KAAK,CAAC,YAAA,CAAa,KAAK,CAAA,EAAG;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AACjC,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA;AAEnC,EAAA,IAAI,QAAA,CAAS,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ;AACxC,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,QAAA,CAAS,KAAA,CAAM,CAAC,GAAA,KAAQ,SAAA,CAAU,IAAA,CAAK,GAAG,CAAA,EAAG,KAAA,CAAM,GAAG,CAAC,CAAC,CAAA;AACjE;;;ACnCA,SAASA,cAAa,KAAA,EAAkD;AACtE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAEA,SAAS,iBAAiB,OAAA,EAA0B;AAClD,EAAA,OAAO,OAAA,CAAQ,KAAK,OAAO,CAAA;AAC7B;AAEA,SAAS,YAAY,KAAA,EAAyB;AAC5C,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,OAAO,MAAM,GAAA,CAAI,CAAC,IAAA,KAAS,WAAA,CAAY,IAAI,CAAC,CAAA;AAAA,EAC9C;AAEA,EAAA,IAAIA,aAAAA,CAAa,KAAK,CAAA,EAAG;AACvB,IAAA,OAAO,MAAA,CAAO,WAAA;AAAA,MACZ,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,WAAW,MAAM,CAAC,GAAA,EAAK,WAAA,CAAY,WAAW,CAAC,CAAC;AAAA,KACnF;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAEO,SAAS,gBAAgB,IAAA,EAAwB;AACtD,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AACvC;AAEO,SAAS,cAAA,CAAe,QAAiB,IAAA,EAAuB;AACrE,EAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AACrC,EAAA,IAAI,OAAA,GAAmB,MAAA;AAEvB,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,MAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,gBAAA,CAAiB,OAAO,CAAA,EAAG;AACvD,MAAA,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAC,CAAA;AACjC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAACA,aAAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,OAAA,GAAU,QAAQ,OAAO,CAAA;AAAA,EAC3B;AAEA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,cAAA,CAAe,MAAA,EAAuB,IAAA,EAAc,KAAA,EAAsB;AACxF,EAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AAErC,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,OAAA,GAA+C,MAAA;AAEnD,EAAA,KAAA,IAAS,QAAQ,CAAA,EAAG,KAAA,GAAQ,QAAA,CAAS,MAAA,EAAQ,SAAS,CAAA,EAAG;AACvD,IAAA,MAAM,OAAA,GAAU,SAAS,KAAK,CAAA;AAE9B,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,KAAA,KAAU,QAAA,CAAS,MAAA,GAAS,CAAA;AAC3C,IAAA,MAAM,WAAA,GAAc,QAAA,CAAS,KAAA,GAAQ,CAAC,CAAA;AACtC,IAAA,MAAM,cAAA,GAAiB,gBAAA,CAAiB,WAAA,IAAe,EAAE,CAAA;AAEzD,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,MAAM,YAAA,GAAe,OAAO,OAAO,CAAA;AAEnC,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,OAAA,CAAQ,YAAY,CAAA,GAAI,WAAA,CAAY,KAAK,CAAA;AACzC,QAAA;AAAA,MACF;AAEA,MAAA,MAAMC,UAAAA,GAAY,QAAQ,YAAY,CAAA;AAEtC,MAAA,IAAI,CAACD,cAAaC,UAAS,CAAA,IAAK,CAAC,KAAA,CAAM,OAAA,CAAQA,UAAS,CAAA,EAAG;AACzD,QAAA,OAAA,CAAQ,YAAY,CAAA,GAAI,cAAA,GAAiB,KAAK,EAAC;AAAA,MACjD;AAEA,MAAA,OAAA,GAAU,QAAQ,YAAY,CAAA;AAC9B,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAA,CAAQ,OAAO,CAAA,GAAI,WAAA,CAAY,KAAK,CAAA;AACpC,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,QAAQ,OAAO,CAAA;AAEjC,IAAA,IAAI,CAACD,cAAa,SAAS,CAAA,IAAK,CAAC,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,EAAG;AACzD,MAAA,OAAA,CAAQ,OAAO,CAAA,GAAI,cAAA,GAAiB,KAAK,EAAC;AAAA,IAC5C;AAEA,IAAA,OAAA,GAAU,QAAQ,OAAO,CAAA;AAAA,EAC3B;AACF;AAEO,SAAS,iBAAA,CAAkB,QAAuB,IAAA,EAAoB;AAC3E,EAAA,MAAM,QAAA,GAAW,gBAAgB,IAAI,CAAA;AAErC,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,OAAA,GAA2D,MAAA;AAE/D,EAAA,KAAA,IAAS,QAAQ,CAAA,EAAG,KAAA,GAAQ,SAAS,MAAA,GAAS,CAAA,EAAG,SAAS,CAAA,EAAG;AAC3D,IAAA,MAAM,OAAA,GAAU,SAAS,KAAK,CAAA;AAE9B,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,MAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,gBAAA,CAAiB,OAAO,CAAA,EAAG;AACvD,MAAA,OAAA,GAAU,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAC,CAAA;AACjC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAACA,aAAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,MAAA;AAAA,IACF;AAEA,IAAA,OAAA,GAAU,QAAQ,OAAO,CAAA;AAAA,EAC3B;AAEA,EAAA,MAAM,WAAA,GAAc,QAAA,CAAS,QAAA,CAAS,MAAA,GAAS,CAAC,CAAA;AAEhD,EAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,MAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,gBAAA,CAAiB,WAAW,CAAA,EAAG;AAC3D,IAAA,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,WAAW,CAAA,EAAG,CAAC,CAAA;AACrC,IAAA;AAAA,EACF;AAEA,EAAA,IAAIA,aAAAA,CAAa,OAAO,CAAA,EAAG;AACzB,IAAA,OAAO,QAAQ,WAAW,CAAA;AAAA,EAC5B;AACF;AAEO,SAAS,cAAuC,KAAA,EAAa;AAClE,EAAA,OAAO,YAAY,KAAK,CAAA;AAC1B;;;ACrIO,SAAS,YAAA,CACd,QACA,OAAA,EACe;AACf,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,OAAA;AAC7B,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,EAAS,MAAM,CAAA;AAC1C,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,EAAS,MAAM,CAAA;AAE1C,EAAA,IAAI,QAAA,GAA0B,UAAA,GAAa,EAAC,GAAI,cAAc,MAAM,CAAA;AAEpE,EAAA,IAAI,cAAc,OAAA,EAAS;AACzB,IAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,MAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,MAAA,EAAQ,IAAI,CAAA;AAEzC,MAAA,IAAI,UAAU,MAAA,EAAW;AACvB,QAAA,cAAA,CAAe,QAAA,EAAU,MAAM,KAAK,CAAA;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,cAAc,OAAA,EAAS;AACzB,IAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,MAAA,iBAAA,CAAkB,UAAU,IAAI,CAAA;AAAA,IAClC;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;;;ACvCA,SAASA,cAAa,KAAA,EAAkD;AACtE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAQO,SAAS,WAAA,CACd,MACA,QAAA,EACS;AACT,EAAA,IAAI,CAACA,aAAAA,CAAa,IAAI,KAAK,CAACA,aAAAA,CAAa,QAAQ,CAAA,EAAG;AAClD,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAwB,EAAE,GAAG,IAAA,EAAK;AAExC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACnD,IAAA,MAAM,YAAA,GAAe,OAAO,GAAG,CAAA;AAE/B,IAAA,IAAIA,aAAAA,CAAa,YAAY,CAAA,IAAKA,aAAAA,CAAa,KAAK,CAAA,EAAG;AACrD,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,WAAA,CAAY,YAAA,EAAc,KAAK,CAAA;AAC7C,MAAA;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,EAChB;AAEA,EAAA,OAAO,MAAA;AACT;;;AClCO,SAAS,cAAiB,KAAA,EAAyB;AACxD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;ACHA,SAASA,cAAa,KAAA,EAAkD;AACtE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA;AAChD;AAEO,SAAS,qBACd,MAAA,EACe;AACf,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,EAC9B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEO,SAAS,uBACd,KAAA,EAC6B;AAC7B,EAAA,MAAM,MAAA,GAAS,cAAuB,KAAK,CAAA;AAE3C,EAAA,IAAI,CAACA,aAAAA,CAAa,MAAM,CAAA,EAAG;AACzB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,MAAA,CAAO,OAAA,KAAY,QAAA,EAAU;AACtC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,CAAO,YAAY,IAAA,IAAQ,OAAO,OAAO,OAAA,KAAY,QAAA,IAAY,MAAA,CAAO,OAAA,KAAY,MAAA,EAAW;AACjG,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,CAACA,aAAAA,CAAa,MAAA,CAAO,MAAM,CAAA,EAAG;AAChC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,SAAS,OAAO,MAAA,CAAO,OAAA,KAAY,QAAA,GAAW,OAAO,OAAA,GAAU,IAAA;AAAA,IAC/D,QAAQ,MAAA,CAAO;AAAA,GACjB;AACF;;;ACzBO,SAAS,aACd,OAAA,EAC6B;AAC7B,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAS,KAAK,OAAA,EAAS,MAAA,EAAQ,SAAQ,GAAI,OAAA;AAE5D,EAAA,IAAI,OAAA,CAAQ,WAAA,IAAc,KAAM,KAAA,EAAO;AACrC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,aAAA,EAAc;AAAA,EAC5C;AAEA,EAAA,MAAM,iBAAiB,YAAA,CAAa,MAAA,EAAQ,EAAE,OAAA,EAAS,SAAS,CAAA;AAChE,EAAA,MAAM,MAAA,GAA+B;AAAA,IACnC,SAAS,OAAA,IAAW,IAAA;AAAA,IACpB,OAAA,EAAS,KAAK,GAAA,EAAI;AAAA,IAClB,MAAA,EAAQ;AAAA,GACV;AACA,EAAA,MAAM,gBAAA,GAAmB,qBAAqB,MAAM,CAAA;AAEpD,EAAA,IAAI,qBAAqB,IAAA,EAAM;AAC7B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,kBAAA,EAAmB;AAAA,EACjD;AAEA,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,OAAA,CAAQ,KAAK,gBAAgB,CAAA;AACrC,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,MAAA,EAAO;AAAA,EAC5B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,gBAAA,EAAiB;AAAA,EAC/C;AACF;;;AC3BO,SAAS,gBAAA,CAAiB,KAAa,OAAA,EAAuC;AACnF,EAAA,IAAI,OAAA,CAAQ,WAAA,IAAc,KAAM,KAAA,EAAO;AACrC,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,OAAA,CAAQ,WAAW,GAAG,CAAA;AACtB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEO,SAAS,UACd,OAAA,EAC0B;AAC1B,EAAA,MAAM,EAAE,GAAA,EAAK,oBAAA,GAAuB,IAAA,EAAM,OAAA,EAAS,SAAQ,GAAI,OAAA;AAE/D,EAAA,IAAI,OAAA,CAAQ,WAAA,IAAc,KAAM,KAAA,EAAO;AACrC,IAAA,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAe,MAAA,EAAQ,IAAA,EAAK;AAAA,EAC/C;AAEA,EAAA,IAAI,QAAA,GAA0B,IAAA;AAE9B,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAE,MAAA,EAAQ,eAAA,EAAiB,MAAA,EAAQ,IAAA,EAAK;AAAA,EACjD;AAEA,EAAA,IAAI,aAAa,IAAA,EAAM;AACrB,IAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,MAAA,EAAQ,IAAA,EAAK;AAAA,EAC3C;AAEA,EAAA,MAAM,WAAA,GAAc,uBAAgC,QAAQ,CAAA;AAE5D,EAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,IAAA,IAAI,oBAAA,EAAsB;AACxB,MAAA,gBAAA,CAAiB,KAAK,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,MAAA,EAAQ,IAAA,EAAK;AAAA,EAC3C;AAEA,EAAA,IAAA,CAAK,OAAA,IAAW,IAAA,MAAU,WAAA,CAAY,OAAA,EAAS;AAC7C,IAAA,OAAO,EAAE,MAAA,EAAQ,kBAAA,EAAoB,MAAA,EAAQ,IAAA,EAAK;AAAA,EACpD;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,OAAA;AAAA,IACR,MAAA,EAAQ;AAAA,GACV;AACF;;;ACtEO,SAAS,SAAA,GAAqB;AACnC,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,QAAA,KAAa,WAAA;AAC9D;;;ACEA,SAAS,eAAA,GAAkC;AACzC,EAAA,IAAI,CAAC,WAAU,EAAG;AAChB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,MAAA,CAAO,YAAA;AAAA,EAChB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAOO,SAAS,yBAAA,GAAiD;AAC/D,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AACX,MAAA,MAAM,UAAU,eAAA,EAAgB;AAEhC,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,IAAI;AACF,QAAA,OAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,MAC5B,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,MAAM,UAAU,eAAA,EAAgB;AAEhC,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,OAAA,CAAQ,OAAA,CAAQ,KAAK,KAAK,CAAA;AAAA,MAC5B,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,MAAM,UAAU,eAAA,EAAgB;AAEhC,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,OAAA,CAAQ,WAAW,GAAG,CAAA;AAAA,MACxB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA;AAAA,IACA,WAAA,GAAc;AACZ,MAAA,OAAO,iBAAgB,KAAM,IAAA;AAAA,IAC/B;AAAA,GACF;AACF;;;AC5DO,SAAS,uBAAA,CACd,UACA,MAAA,EACmB;AACnB,EAAA,IAAI,OAAA,GAAgD,IAAA;AAEpD,EAAA,MAAM,QAAQ,MAAY;AACxB,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,YAAA,CAAa,OAAO,CAAA;AACpB,MAAA,OAAA,GAAU,IAAA;AAAA,IACZ;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,QAAA,GAAW;AACT,MAAA,KAAA,EAAM;AACN,MAAA,OAAA,GAAU,WAAW,MAAM;AACzB,QAAA,OAAA,GAAU,IAAA;AACV,QAAA,QAAA,EAAS;AAAA,MACX,GAAG,MAAM,CAAA;AAAA,IACX,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,QAAA,EAAS;AACT,QAAA;AAAA,MACF;AAEA,MAAA,KAAA,EAAM;AACN,MAAA,QAAA,EAAS;AAAA,IACX,CAAA;AAAA,IACA,MAAA,GAAS;AACP,MAAA,KAAA,EAAM;AAAA,IACR;AAAA,GACF;AACF;;;ACpBA,SAAS,WAAA,CAAY,KAAa,MAAA,EAA+C;AAC/E,EAAA,OAAO;AAAA,IACL,GAAA;AAAA,IACA,SAAS,MAAA,CAAO,OAAA;AAAA,IAChB,SAAS,MAAA,CAAO;AAAA,GAClB;AACF;AAQO,SAAS,aACd,OAAA,EACoB;AACpB,EAAA,MAAM;AAAA,IACJ,WAAA,GAAc,KAAA;AAAA,IACd,aAAA,GAAgB,IAAA;AAAA,IAChB,UAAA,GAAa,GAAA;AAAA,IACb,OAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAA;AAAA,IACA,oBAAA,GAAuB,IAAA;AAAA,IACvB,YAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AACJ,EAAA,MAAM,eAAA,GAAkBE,cAAQ,MAAM,OAAA,IAAW,2BAA0B,EAAG,CAAC,OAAO,CAAC,CAAA;AACvF,EAAA,MAAM,gBAAgBC,sBAAA,CAAS,EAAE,OAAA,EAAS,IAAA,CAAK,SAAS,CAAA;AACxD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIC,eAA2B,IAAI,CAAA;AACjE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,eAAsB,MAAM,CAAA;AACxD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAClE,EAAA,MAAM,cAAA,GAAiBC,aAAO,KAAK,CAAA;AACnC,EAAA,MAAM,QAAA,GAAWA,aAA0C,IAAI,CAAA;AAC/D,EAAA,MAAM,sBAAA,GAAyBA,aAA6B,IAAI,CAAA;AAChE,EAAA,MAAM,gBAAA,GAAmBA,aAAiC,IAAI,CAAA;AAE9D,EAAA,MAAM,oBAAA,GAAuBC,iBAAA;AAAA,IAC3B,CAAC,KAAA,KAAyB;AACxB,MAAA,MAAM,aAAA,GAAgB,KAAK,SAAA,EAAU;AACrC,MAAA,MAAM,cAAA,GAAiB,aAAa,aAAA,EAAe;AAAA,QACjD,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,IAAI,CAAC,KAAA,IAAS,SAAA,CAAU,cAAA,EAAgB,sBAAA,CAAuB,OAAO,CAAA,EAAG;AACvE,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,SAAS,YAAA,CAAa;AAAA,QAC1B,GAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR,OAAA,EAAS,eAAA;AAAA,QACT,OAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,QAAA,SAAA,CAAU,OAAO,CAAA;AACjB,QAAA;AAAA,MACF;AAEA,MAAA,QAAA,CAAS,UAAU,MAAA,CAAO,MAAA;AAC1B,MAAA,sBAAA,CAAuB,OAAA,GAAU,OAAO,MAAA,CAAO,MAAA;AAC/C,MAAA,YAAA,CAAa,WAAA,CAAY,GAAA,EAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5C,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,cAAA,CAAe,MAAA,CAAO,OAAO,OAAO,CAAA;AACpC,MAAA,SAAA,CAAU,OAAO,CAAA;AAAA,IACnB,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,iBAAiB,OAAO;AAAA,GACxD;AAEA,EAAA,MAAM,eAAA,GAAkBA,kBAAY,MAAY;AAC9C,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,sBAAA,CAAuB,OAAA,GAAU,IAAA;AACjC,IAAA,WAAA,CAAY,KAAK,CAAA;AACjB,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,SAAA,CAAU,SAAS,CAAA;AAAA,EACrB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,kBAAY,MAAY;AACzC,IAAA,gBAAA,CAAiB,SAAS,MAAA,EAAO;AACjC,IAAA,gBAAA,CAAiB,KAAK,eAAe,CAAA;AACrC,IAAA,eAAA,EAAgB;AAAA,EAClB,CAAA,EAAG,CAAC,eAAA,EAAiB,GAAA,EAAK,eAAe,CAAC,CAAA;AAE1C,EAAA,MAAM,YAAA,GAAeA,kBAAY,MAAe;AAC9C,IAAA,MAAM,gBAAgB,QAAA,CAAS,OAAA;AAE/B,IAAA,IAAI,kBAAkB,IAAA,EAAM;AAC1B,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,MAAM,YAAA,GAAe,WAAA;AAAA,MACnB,KAAK,SAAA,EAAU;AAAA,MACf,aAAA,CAAc;AAAA,KAChB;AAEA,IAAA,IAAA,CAAK,KAAA,CAAM,cAA8B,YAAY,CAAA;AACrD,IAAA,sBAAA,CAAuB,UAAU,aAAA,CAAc,MAAA;AAC/C,IAAA,SAAA,CAAU,UAAU,CAAA;AACpB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,EAAG,CAAC,IAAA,EAAM,YAAY,CAAC,CAAA;AAEvB,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,gBAAA,CAAiB,SAAS,MAAA,EAAO;AACjC,IAAA,gBAAA,CAAiB,OAAA,GAAU,wBAAwB,MAAM;AACvD,MAAA,oBAAA,CAAqB,KAAK,CAAA;AAAA,IAC5B,GAAG,UAAU,CAAA;AAEb,IAAA,OAAO,MAAM;AACX,MAAA,gBAAA,CAAiB,SAAS,MAAA,EAAO;AAAA,IACnC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,UAAA,EAAY,oBAAoB,CAAC,CAAA;AAErC,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAS,SAAA,CAAyB;AAAA,MACtC,GAAA;AAAA,MACA,OAAA,EAAS,eAAA;AAAA,MACT,OAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,IAAI,MAAA,CAAO,WAAW,OAAA,EAAS;AAC7B,MAAA,QAAA,CAAS,UAAU,MAAA,CAAO,MAAA;AAC1B,MAAA,sBAAA,CAAuB,OAAA,GAAU,OAAO,MAAA,CAAO,MAAA;AAC/C,MAAA,YAAA,CAAa,WAAA,CAAY,GAAA,EAAK,MAAA,CAAO,MAAM,CAAC,CAAA;AAC5C,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA,cAAA,CAAe,MAAA,CAAO,OAAO,OAAO,CAAA;AAEpC,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,MAAM,YAAA,GAAe,WAAA;AAAA,UACnB,KAAK,SAAA,EAAU;AAAA,UACf,OAAO,MAAA,CAAO;AAAA,SAChB;AAEA,QAAA,IAAA,CAAK,KAAA,CAAM,cAA8B,YAAY,CAAA;AACrD,QAAA,SAAA,CAAU,UAAU,CAAA;AAAA,MACtB;AAAA,IACF,WAAW,MAAA,CAAO,MAAA,KAAW,SAAA,IAAa,MAAA,CAAO,WAAW,eAAA,EAAiB;AAC3E,MAAA,SAAA,CAAU,OAAO,CAAA;AAAA,IACnB;AAEA,IAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AAAA,EAC3B,CAAA,EAAG,CAAC,WAAA,EAAa,IAAA,EAAM,KAAK,oBAAA,EAAsB,YAAA,EAAc,eAAA,EAAiB,OAAO,CAAC,CAAA;AAEzF,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AAC3B,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS;AAC3B,MAAA;AAAA,IACF;AAEA,IAAA,gBAAA,CAAiB,SAAS,QAAA,EAAS;AAAA,EACrC,GAAG,CAAC,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,aAAa,CAAC,CAAA;AAE1C,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,kBAAA,EAAoB;AACtC,MAAA;AAAA,IACF;AAEA,IAAA,UAAA,EAAW;AAAA,EACb,GAAG,CAAC,UAAA,EAAY,eAAe,IAAA,CAAK,SAAA,CAAU,kBAAkB,CAAC,CAAA;AAEjE,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA,EAAc,UAAA;AAAA,IACd,UAAA;AAAA,IACA,SAAS,MAAM;AACb,MAAA,gBAAA,CAAiB,SAAS,MAAA,EAAO;AACjC,MAAA,oBAAA,CAAqB,IAAI,CAAA;AAAA,IAC3B;AAAA,GACF;AACF","file":"index.cjs","sourcesContent":["function isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isDate(value: unknown): value is Date {\n return value instanceof Date;\n}\n\nexport function deepEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) {\n return true;\n }\n\n if (isDate(left) && isDate(right)) {\n return left.getTime() === right.getTime();\n }\n\n if (Array.isArray(left) && Array.isArray(right)) {\n if (left.length !== right.length) {\n return false;\n }\n\n return left.every((item, index) => deepEqual(item, right[index]));\n }\n\n if (!isObjectLike(left) || !isObjectLike(right)) {\n return false;\n }\n\n const leftKeys = Object.keys(left);\n const rightKeys = Object.keys(right);\n\n if (leftKeys.length !== rightKeys.length) {\n return false;\n }\n\n return leftKeys.every((key) => deepEqual(left[key], right[key]));\n}\n","import type { DraftValueMap } from '../types/public';\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isNumericSegment(segment: string): boolean {\n return /^\\d+$/.test(segment);\n}\n\nfunction cloneBranch(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => cloneBranch(item));\n }\n\n if (isObjectLike(value)) {\n return Object.fromEntries(\n Object.entries(value).map(([key, nestedValue]) => [key, cloneBranch(nestedValue)]),\n );\n }\n\n return value;\n}\n\nexport function getPathSegments(path: string): string[] {\n return path.split('.').filter(Boolean);\n}\n\nexport function getValueAtPath(source: unknown, path: string): unknown {\n const segments = getPathSegments(path);\n let current: unknown = source;\n\n for (const segment of segments) {\n if (Array.isArray(current) && isNumericSegment(segment)) {\n current = current[Number(segment)];\n continue;\n }\n\n if (!isObjectLike(current)) {\n return undefined;\n }\n\n current = current[segment];\n }\n\n return current;\n}\n\nexport function setValueAtPath(target: DraftValueMap, path: string, value: unknown): void {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return;\n }\n\n let current: Record<string, unknown> | unknown[] = target;\n\n for (let index = 0; index < segments.length; index += 1) {\n const segment = segments[index];\n\n if (segment === undefined) {\n return;\n }\n\n const isLast = index === segments.length - 1;\n const nextSegment = segments[index + 1];\n const arrayContainer = isNumericSegment(nextSegment ?? '');\n\n if (Array.isArray(current)) {\n const numericIndex = Number(segment);\n\n if (isLast) {\n current[numericIndex] = cloneBranch(value);\n return;\n }\n\n const nextValue = current[numericIndex];\n\n if (!isObjectLike(nextValue) && !Array.isArray(nextValue)) {\n current[numericIndex] = arrayContainer ? [] : {};\n }\n\n current = current[numericIndex] as Record<string, unknown> | unknown[];\n continue;\n }\n\n if (isLast) {\n current[segment] = cloneBranch(value);\n return;\n }\n\n const nextValue = current[segment];\n\n if (!isObjectLike(nextValue) && !Array.isArray(nextValue)) {\n current[segment] = arrayContainer ? [] : {};\n }\n\n current = current[segment] as Record<string, unknown> | unknown[];\n }\n}\n\nexport function removeValueAtPath(target: DraftValueMap, path: string): void {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return;\n }\n\n let current: Record<string, unknown> | unknown[] | undefined = target;\n\n for (let index = 0; index < segments.length - 1; index += 1) {\n const segment = segments[index];\n\n if (segment === undefined) {\n return;\n }\n\n if (Array.isArray(current) && isNumericSegment(segment)) {\n current = current[Number(segment)] as Record<string, unknown> | unknown[] | undefined;\n continue;\n }\n\n if (!isObjectLike(current)) {\n return;\n }\n\n current = current[segment] as Record<string, unknown> | unknown[] | undefined;\n }\n\n const lastSegment = segments[segments.length - 1];\n\n if (lastSegment === undefined) {\n return;\n }\n\n if (Array.isArray(current) && isNumericSegment(lastSegment)) {\n current.splice(Number(lastSegment), 1);\n return;\n }\n\n if (isObjectLike(current)) {\n delete current[lastSegment];\n }\n}\n\nexport function cloneValueMap<T extends DraftValueMap>(value: T): T {\n return cloneBranch(value) as T;\n}\n","import type { DraftValueMap } from '../types/public';\nimport { cloneValueMap, getValueAtPath, removeValueAtPath, setValueAtPath } from '../utils/objectPath';\n\nexport interface FilterValuesOptions {\n include?: ReadonlyArray<string>;\n exclude?: ReadonlyArray<string>;\n}\n\n/**\n * Apply include/exclude rules to a form value object.\n *\n * Precedence is intentional: include creates the candidate subset first, then\n * exclude removes fields from that subset.\n */\nexport function filterValues<TValues extends DraftValueMap>(\n values: TValues,\n options: FilterValuesOptions,\n): DraftValueMap {\n const { include, exclude } = options;\n const hasInclude = Boolean(include?.length);\n const hasExclude = Boolean(exclude?.length);\n\n let filtered: DraftValueMap = hasInclude ? {} : cloneValueMap(values);\n\n if (hasInclude && include) {\n for (const path of include) {\n const value = getValueAtPath(values, path);\n\n if (value !== undefined) {\n setValueAtPath(filtered, path, value);\n }\n }\n }\n\n if (hasExclude && exclude) {\n for (const path of exclude) {\n removeValueAtPath(filtered, path);\n }\n }\n\n return filtered;\n}\n","import type { DraftValueMap } from '../types/public';\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Merge restored draft values on top of the current form snapshot.\n *\n * This keeps excluded or non-persisted fields intact during restore instead of\n * replacing the entire form state with a filtered payload.\n */\nexport function mergeValues<TValues extends DraftValueMap>(\n base: TValues,\n override: DraftValueMap,\n): TValues {\n if (!isObjectLike(base) || !isObjectLike(override)) {\n return override as TValues;\n }\n\n const result: DraftValueMap = { ...base };\n\n for (const [key, value] of Object.entries(override)) {\n const currentValue = result[key];\n\n if (isObjectLike(currentValue) && isObjectLike(value)) {\n result[key] = mergeValues(currentValue, value);\n continue;\n }\n\n result[key] = value;\n }\n\n return result as TValues;\n}\n","export function safeJsonParse<T>(value: string): T | null {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n}\n","import type { DraftRecord, DraftValueMap } from '../types/public';\nimport { safeJsonParse } from '../utils/safeJsonParse';\n\nfunction isObjectLike(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function serializeDraftRecord<TValues extends DraftValueMap>(\n record: DraftRecord<TValues>,\n): string | null {\n try {\n return JSON.stringify(record);\n } catch {\n return null;\n }\n}\n\nexport function deserializeDraftRecord<TValues extends DraftValueMap>(\n value: string,\n): DraftRecord<TValues> | null {\n const parsed = safeJsonParse<unknown>(value);\n\n if (!isObjectLike(parsed)) {\n return null;\n }\n\n if (typeof parsed.savedAt !== 'number') {\n return null;\n }\n\n if (parsed.version !== null && typeof parsed.version !== 'string' && parsed.version !== undefined) {\n return null;\n }\n\n if (!isObjectLike(parsed.values)) {\n return null;\n }\n\n return {\n savedAt: parsed.savedAt,\n version: typeof parsed.version === 'string' ? parsed.version : null,\n values: parsed.values as TValues,\n };\n}\n","import type { DraftRecord, DraftStorageAdapter, DraftValueMap } from '../types/public';\n\nimport { filterValues } from './filterValues';\nimport { serializeDraftRecord } from './serialization';\n\nexport interface PersistDraftOptions<TValues extends DraftValueMap> {\n key: string;\n values: TValues;\n storage: DraftStorageAdapter;\n version?: string | null;\n include?: ReadonlyArray<string>;\n exclude?: ReadonlyArray<string>;\n}\n\nexport type PersistDraftResult<TValues extends DraftValueMap> =\n | { ok: true; record: DraftRecord<TValues> }\n | { ok: false; reason: 'unavailable' | 'serialize_failed' | 'storage_failed' };\n\nexport function persistDraft<TValues extends DraftValueMap>(\n options: PersistDraftOptions<TValues>,\n): PersistDraftResult<TValues> {\n const { exclude, include, key, storage, values, version } = options;\n\n if (storage.isAvailable?.() === false) {\n return { ok: false, reason: 'unavailable' };\n }\n\n const filteredValues = filterValues(values, { include, exclude }) as TValues;\n const record: DraftRecord<TValues> = {\n version: version ?? null,\n savedAt: Date.now(),\n values: filteredValues,\n };\n const serializedRecord = serializeDraftRecord(record);\n\n if (serializedRecord === null) {\n return { ok: false, reason: 'serialize_failed' };\n }\n\n try {\n storage.setItem(key, serializedRecord);\n return { ok: true, record };\n } catch {\n return { ok: false, reason: 'storage_failed' };\n }\n}\n","import type { DraftRecord, DraftStorageAdapter, DraftValueMap } from '../types/public';\n\nimport { deserializeDraftRecord } from './serialization';\n\nexport interface LoadDraftOptions {\n key: string;\n storage: DraftStorageAdapter;\n version?: string | null;\n removeCorruptedDraft?: boolean;\n}\n\nexport type LoadDraftResult<TValues extends DraftValueMap> =\n | { status: 'found'; record: DraftRecord<TValues> }\n | {\n status: 'invalid' | 'missing' | 'storage_error' | 'unavailable' | 'version_mismatch';\n record: null;\n };\n\nexport function clearDraftRecord(key: string, storage: DraftStorageAdapter): boolean {\n if (storage.isAvailable?.() === false) {\n return false;\n }\n\n try {\n storage.removeItem(key);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function loadDraft<TValues extends DraftValueMap>(\n options: LoadDraftOptions,\n): LoadDraftResult<TValues> {\n const { key, removeCorruptedDraft = true, storage, version } = options;\n\n if (storage.isAvailable?.() === false) {\n return { status: 'unavailable', record: null };\n }\n\n let rawValue: string | null = null;\n\n try {\n rawValue = storage.getItem(key);\n } catch {\n return { status: 'storage_error', record: null };\n }\n\n if (rawValue === null) {\n return { status: 'missing', record: null };\n }\n\n const draftRecord = deserializeDraftRecord<TValues>(rawValue);\n\n if (draftRecord === null) {\n if (removeCorruptedDraft) {\n clearDraftRecord(key, storage);\n }\n\n return { status: 'invalid', record: null };\n }\n\n if ((version ?? null) !== draftRecord.version) {\n return { status: 'version_mismatch', record: null };\n }\n\n return {\n status: 'found',\n record: draftRecord,\n };\n}\n","export function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n","import type { DraftStorageAdapter } from './types';\n\nimport { isBrowser } from '../utils/isBrowser';\n\nfunction getLocalStorage(): Storage | null {\n if (!isBrowser()) {\n return null;\n }\n\n try {\n return window.localStorage;\n } catch {\n return null;\n }\n}\n\n/**\n * Safe localStorage adapter used by default by `useFormDraft`.\n *\n * Reads and writes no-op when `window.localStorage` is unavailable or throws.\n */\nexport function createLocalStorageAdapter(): DraftStorageAdapter {\n return {\n getItem(key) {\n const storage = getLocalStorage();\n\n if (storage === null) {\n return null;\n }\n\n try {\n return storage.getItem(key);\n } catch {\n return null;\n }\n },\n setItem(key, value) {\n const storage = getLocalStorage();\n\n if (storage === null) {\n return;\n }\n\n try {\n storage.setItem(key, value);\n } catch {\n // Intentionally swallow storage errors so consumers never crash.\n }\n },\n removeItem(key) {\n const storage = getLocalStorage();\n\n if (storage === null) {\n return;\n }\n\n try {\n storage.removeItem(key);\n } catch {\n // Intentionally swallow storage errors so consumers never crash.\n }\n },\n isAvailable() {\n return getLocalStorage() !== null;\n },\n };\n}\n","export interface DebouncedCallback {\n schedule(): void;\n flush(): void;\n cancel(): void;\n}\n\nexport function createDebouncedCallback(\n callback: () => void,\n waitMs: number,\n): DebouncedCallback {\n let timerId: ReturnType<typeof setTimeout> | null = null;\n\n const clear = (): void => {\n if (timerId !== null) {\n clearTimeout(timerId);\n timerId = null;\n }\n };\n\n return {\n schedule() {\n clear();\n timerId = setTimeout(() => {\n timerId = null;\n callback();\n }, waitMs);\n },\n flush() {\n if (timerId === null) {\n callback();\n return;\n }\n\n clear();\n callback();\n },\n cancel() {\n clear();\n },\n };\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useWatch } from 'react-hook-form';\nimport type { FieldValues } from 'react-hook-form';\n\nimport { deepEqual } from '../core/compare';\nimport { filterValues } from '../core/filterValues';\nimport { mergeValues } from '../core/mergeValues';\nimport { persistDraft } from '../core/persist';\nimport { clearDraftRecord, loadDraft } from '../core/restore';\nimport { createLocalStorageAdapter } from '../storage/localStorageAdapter';\nimport type {\n DraftMeta,\n DraftRecord,\n DraftStatus,\n DraftValueMap,\n UseFormDraftOptions,\n UseFormDraftResult,\n} from '../types/public';\nimport { createDebouncedCallback, type DebouncedCallback } from '../utils/debounce';\n\nfunction toDraftMeta(key: string, record: DraftRecord<DraftValueMap>): DraftMeta {\n return {\n key,\n savedAt: record.savedAt,\n version: record.version,\n };\n}\n\n/**\n * Persist and restore React Hook Form values as a browser draft.\n *\n * The hook defaults to manual restore mode so consuming applications can decide\n * whether to auto-restore or show their own confirmation UI.\n */\nexport function useFormDraft<TFieldValues extends FieldValues>(\n options: UseFormDraftOptions<TFieldValues>,\n): UseFormDraftResult {\n const {\n autoRestore = false,\n clearOnSubmit = true,\n debounceMs = 500,\n exclude,\n form,\n include,\n key,\n removeCorruptedDraft = true,\n resetOptions,\n storage,\n version,\n } = options;\n const resolvedStorage = useMemo(() => storage ?? createLocalStorageAdapter(), [storage]);\n const watchedValues = useWatch({ control: form.control });\n const [draftMeta, setDraftMeta] = useState<DraftMeta | null>(null);\n const [hasDraft, setHasDraft] = useState(false);\n const [status, setStatus] = useState<DraftStatus>('idle');\n const [lastSavedAt, setLastSavedAt] = useState<number | null>(null);\n const initializedRef = useRef(false);\n const draftRef = useRef<DraftRecord<DraftValueMap> | null>(null);\n const lastPersistedValuesRef = useRef<DraftValueMap | null>(null);\n const debouncedSaveRef = useRef<DebouncedCallback | null>(null);\n\n const persistCurrentValues = useCallback(\n (force: boolean): void => {\n const currentValues = form.getValues() as DraftValueMap;\n const filteredValues = filterValues(currentValues, {\n include: include as ReadonlyArray<string> | undefined,\n exclude: exclude as ReadonlyArray<string> | undefined,\n });\n\n if (!force && deepEqual(filteredValues, lastPersistedValuesRef.current)) {\n return;\n }\n\n const result = persistDraft({\n key,\n values: currentValues,\n storage: resolvedStorage,\n version,\n include: include as ReadonlyArray<string> | undefined,\n exclude: exclude as ReadonlyArray<string> | undefined,\n });\n\n if (!result.ok) {\n setStatus('error');\n return;\n }\n\n draftRef.current = result.record;\n lastPersistedValuesRef.current = result.record.values;\n setDraftMeta(toDraftMeta(key, result.record));\n setHasDraft(true);\n setLastSavedAt(result.record.savedAt);\n setStatus('saved');\n },\n [exclude, form, include, key, resolvedStorage, version],\n );\n\n const clearDraftState = useCallback((): void => {\n draftRef.current = null;\n lastPersistedValuesRef.current = null;\n setHasDraft(false);\n setDraftMeta(null);\n setLastSavedAt(null);\n setStatus('cleared');\n }, []);\n\n const clearDraft = useCallback((): void => {\n debouncedSaveRef.current?.cancel();\n clearDraftRecord(key, resolvedStorage);\n clearDraftState();\n }, [clearDraftState, key, resolvedStorage]);\n\n const restoreDraft = useCallback((): boolean => {\n const existingDraft = draftRef.current;\n\n if (existingDraft === null) {\n return false;\n }\n\n const mergedValues = mergeValues(\n form.getValues() as DraftValueMap,\n existingDraft.values,\n );\n\n form.reset(mergedValues as TFieldValues, resetOptions);\n lastPersistedValuesRef.current = existingDraft.values;\n setStatus('restored');\n return true;\n }, [form, resetOptions]);\n\n useEffect(() => {\n debouncedSaveRef.current?.cancel();\n debouncedSaveRef.current = createDebouncedCallback(() => {\n persistCurrentValues(false);\n }, debounceMs);\n\n return () => {\n debouncedSaveRef.current?.cancel();\n };\n }, [debounceMs, persistCurrentValues]);\n\n useEffect(() => {\n const result = loadDraft<DraftValueMap>({\n key,\n storage: resolvedStorage,\n version,\n removeCorruptedDraft,\n });\n\n if (result.status === 'found') {\n draftRef.current = result.record;\n lastPersistedValuesRef.current = result.record.values;\n setDraftMeta(toDraftMeta(key, result.record));\n setHasDraft(true);\n setLastSavedAt(result.record.savedAt);\n\n if (autoRestore) {\n const mergedValues = mergeValues(\n form.getValues() as DraftValueMap,\n result.record.values,\n );\n\n form.reset(mergedValues as TFieldValues, resetOptions);\n setStatus('restored');\n }\n } else if (result.status === 'invalid' || result.status === 'storage_error') {\n setStatus('error');\n }\n\n initializedRef.current = true;\n }, [autoRestore, form, key, removeCorruptedDraft, resetOptions, resolvedStorage, version]);\n\n useEffect(() => {\n if (!initializedRef.current) {\n return;\n }\n\n if (!form.formState.isDirty) {\n return;\n }\n\n debouncedSaveRef.current?.schedule();\n }, [form.formState.isDirty, watchedValues]);\n\n useEffect(() => {\n if (!clearOnSubmit) {\n return;\n }\n\n if (!form.formState.isSubmitSuccessful) {\n return;\n }\n\n clearDraft();\n }, [clearDraft, clearOnSubmit, form.formState.isSubmitSuccessful]);\n\n return {\n hasDraft,\n draftMeta,\n lastSavedAt,\n status,\n restoreDraft,\n discardDraft: clearDraft,\n clearDraft,\n saveNow: () => {\n debouncedSaveRef.current?.cancel();\n persistCurrentValues(true);\n },\n };\n}\n"]}
|