form-snapshots 1.0.6

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/dist/index.js ADDED
@@ -0,0 +1,1421 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // form-snapshots/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FormSnapshotTable: () => FormSnapshotTable,
24
+ FormSnapshotsClient: () => FormSnapshotsClient,
25
+ FormSnapshotsDevtools: () => FormSnapshotsDevtools,
26
+ FormSnapshotsProvider: () => FormSnapshotsProvider,
27
+ db: () => db,
28
+ useFormSnapshots: () => useFormSnapshots,
29
+ useFormSnapshotsConfig: () => useFormSnapshotsConfig,
30
+ useFormSnapshotsList: () => useFormSnapshotsList,
31
+ useObjectFormSnapshots: () => useObjectFormSnapshots,
32
+ useRHFFormSnapshots: () => useRHFFormSnapshots
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // form-snapshots/hooks/use-form-snapshots.ts
37
+ var import_react2 = require("react");
38
+
39
+ // form-snapshots/client.ts
40
+ var FormSnapshotsClient = class {
41
+ constructor(params) {
42
+ this.storage = params.storage;
43
+ this.formName = params.formName;
44
+ this.snapshotsLimit = params.snapshotsLimit;
45
+ }
46
+ async initSession() {
47
+ const now = Date.now();
48
+ const cutoff = now - this.snapshotsLimit;
49
+ await this.storage.pruneOlderThan(cutoff);
50
+ const existing = await this.storage.findActiveSession(this.formName);
51
+ if (existing) return existing;
52
+ return this.storage.createSession(this.formName, {});
53
+ }
54
+ async saveSnapshot(sessionId, snapshot) {
55
+ await this.storage.updateSession(sessionId, {
56
+ data: JSON.stringify(snapshot),
57
+ updatedAt: Date.now()
58
+ });
59
+ }
60
+ async markSubmitted(sessionId) {
61
+ await this.storage.updateSession(sessionId, {
62
+ submitted: true,
63
+ updatedAt: Date.now()
64
+ });
65
+ }
66
+ async deleteSession(sessionId) {
67
+ if (typeof this.storage.deleteSession === "function") {
68
+ await this.storage.deleteSession(sessionId);
69
+ }
70
+ }
71
+ async setSubmissionResult(params) {
72
+ const { sessionId, statusCode, errorMessage } = params;
73
+ await this.storage.updateSession(sessionId, {
74
+ statusCode: statusCode ?? null,
75
+ errorMessage: errorMessage ?? null,
76
+ updatedAt: Date.now()
77
+ });
78
+ }
79
+ async getLatestSnapshot() {
80
+ const latest = await this.storage.getLatestSession(this.formName);
81
+ if (!latest) return null;
82
+ return JSON.parse(latest.data);
83
+ }
84
+ async openNewSessionFromSnapshot(snapshot) {
85
+ return this.storage.createSession(this.formName, snapshot);
86
+ }
87
+ };
88
+
89
+ // form-snapshots/local-db/db.ts
90
+ var import_dexie = require("dexie");
91
+ var db = new import_dexie.Dexie("FormSnapshotsDatabase");
92
+ db.version(1).stores({
93
+ formSessions: "++id, formName, createdAt, submitted, [formName+createdAt]"
94
+ });
95
+ db.version(2).stores({
96
+ formSessions: "++id, formName, createdAt, updatedAt, submitted, [formName+createdAt]"
97
+ });
98
+
99
+ // form-snapshots/dexie-storage.ts
100
+ function mapToBase(session) {
101
+ return {
102
+ id: session.id,
103
+ formName: session.formName,
104
+ data: session.data,
105
+ createdAt: session.createdAt,
106
+ updatedAt: session.updatedAt,
107
+ submitted: session.submitted,
108
+ statusCode: session.statusCode ?? null,
109
+ errorMessage: session.errorMessage ?? null
110
+ };
111
+ }
112
+ var DexieFormSnapshotsStorage = class {
113
+ async findActiveSession(formName) {
114
+ const existing = await db.formSessions.where("formName").equals(formName).filter((s) => !s.submitted).last();
115
+ return existing ? mapToBase(existing) : null;
116
+ }
117
+ async createSession(formName, snapshot) {
118
+ const now = Date.now();
119
+ const id = await db.formSessions.add({
120
+ formName,
121
+ data: JSON.stringify(snapshot),
122
+ createdAt: now,
123
+ updatedAt: now,
124
+ submitted: false,
125
+ statusCode: null,
126
+ errorMessage: null
127
+ });
128
+ const created = await db.formSessions.get(id);
129
+ if (!created) {
130
+ throw new Error("Failed to create form session");
131
+ }
132
+ return mapToBase(created);
133
+ }
134
+ async updateSession(id, patch) {
135
+ await db.formSessions.update(id, patch);
136
+ }
137
+ async getLatestSession(formName) {
138
+ const sessions = await db.formSessions.where("formName").equals(formName).sortBy("updatedAt");
139
+ const latest = sessions[sessions.length - 1];
140
+ return latest ? mapToBase(latest) : null;
141
+ }
142
+ async listSessions(formName) {
143
+ const sessions = await db.formSessions.where("formName").equals(formName).sortBy("updatedAt");
144
+ return sessions.map(mapToBase);
145
+ }
146
+ async pruneOlderThan(cutoffMs) {
147
+ await db.formSessions.where("createdAt").below(cutoffMs).delete();
148
+ }
149
+ async deleteSession(id) {
150
+ await db.formSessions.delete(id);
151
+ }
152
+ };
153
+
154
+ // form-snapshots/dom-snapshot.ts
155
+ function toStringValue(val) {
156
+ if (val == null) return "";
157
+ if (typeof val === "object") return JSON.stringify(val);
158
+ return String(val);
159
+ }
160
+ function applyValueToInput(input, val) {
161
+ if (input instanceof HTMLInputElement) {
162
+ if (input.type === "checkbox") {
163
+ input.checked = Boolean(val);
164
+ input.dispatchEvent(new Event("change", { bubbles: true }));
165
+ return;
166
+ }
167
+ if (input.type === "radio") {
168
+ input.checked = input.value === val;
169
+ if (input.checked) input.dispatchEvent(new Event("change", { bubbles: true }));
170
+ return;
171
+ }
172
+ }
173
+ input.value = toStringValue(val);
174
+ input.dispatchEvent(new Event("input", { bubbles: true }));
175
+ }
176
+ function restoreSnapshotToForm(form, snapshot) {
177
+ for (const el of Array.from(form.elements)) {
178
+ const input = el;
179
+ if (!input.name || !(input.name in snapshot)) continue;
180
+ applyValueToInput(input, snapshot[input.name]);
181
+ }
182
+ }
183
+ function snapshotFromDOMForm(form) {
184
+ const snapshot = {};
185
+ for (const el of Array.from(form.elements)) {
186
+ const input = el;
187
+ if (!input.name) continue;
188
+ readInputIntoSnapshot(input, snapshot);
189
+ }
190
+ return snapshot;
191
+ }
192
+ function readInputIntoSnapshot(input, snapshot) {
193
+ if (input instanceof HTMLInputElement) {
194
+ if (input.type === "checkbox") {
195
+ snapshot[input.name] = input.checked;
196
+ } else if (input.type === "radio") {
197
+ if (input.checked) snapshot[input.name] = input.value;
198
+ } else if (input.type !== "file") {
199
+ snapshot[input.name] = input.value;
200
+ }
201
+ } else {
202
+ snapshot[input.name] = input.value;
203
+ }
204
+ }
205
+
206
+ // form-snapshots/context.tsx
207
+ var import_react = require("react");
208
+ var import_jsx_runtime = require("react/jsx-runtime");
209
+ var FormSnapshotsConfigContext = (0, import_react.createContext)(
210
+ void 0
211
+ );
212
+ function FormSnapshotsProvider({
213
+ value,
214
+ children
215
+ }) {
216
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormSnapshotsConfigContext.Provider, { value: value ?? {}, children });
217
+ }
218
+ function useFormSnapshotsConfig() {
219
+ return (0, import_react.useContext)(FormSnapshotsConfigContext) ?? {};
220
+ }
221
+
222
+ // form-snapshots/hooks/use-form-snapshots.ts
223
+ function applyExcludeFields(snapshot, excludeFields) {
224
+ if (!excludeFields || excludeFields.length === 0) return snapshot;
225
+ function pruneByPrefix(obj, paths, parentPath = "") {
226
+ const result = {};
227
+ for (const [key, value] of Object.entries(obj)) {
228
+ const path = parentPath ? `${parentPath}.${key}` : key;
229
+ const isExcluded = paths.some(
230
+ (p) => path === p || path.startsWith(`${p}.`)
231
+ );
232
+ if (isExcluded) {
233
+ continue;
234
+ }
235
+ if (value && typeof value === "object" && !Array.isArray(value)) {
236
+ result[key] = pruneByPrefix(
237
+ value,
238
+ paths,
239
+ path
240
+ );
241
+ continue;
242
+ }
243
+ result[key] = value;
244
+ }
245
+ return result;
246
+ }
247
+ return pruneByPrefix(snapshot, excludeFields);
248
+ }
249
+ function useFormSnapshots(formName, options) {
250
+ const { snapshotsLimit, getValues, applyValues, excludeFields, discardOnSubmit } = options ?? {};
251
+ const formRef = (0, import_react2.useRef)(null);
252
+ const sessionIdRef = (0, import_react2.useRef)(null);
253
+ const lastSubmittedSessionIdRef = (0, import_react2.useRef)(null);
254
+ const clientRef = (0, import_react2.useRef)(null);
255
+ const [isSubmitted, setIsSubmitted] = (0, import_react2.useState)(false);
256
+ const storageRef = (0, import_react2.useRef)(null);
257
+ const {
258
+ defaultSnapshotsLimit,
259
+ excludeFields: globalExcludeFields,
260
+ discardOnSubmit: globalDiscardOnSubmit
261
+ } = useFormSnapshotsConfig();
262
+ const effectiveSnapshotsLimit = snapshotsLimit ?? defaultSnapshotsLimit ?? 24 * 60 * 60 * 1e3;
263
+ const effectiveDiscardOnSubmit = typeof discardOnSubmit === "boolean" ? discardOnSubmit : globalDiscardOnSubmit ?? false;
264
+ const mergedExcludeFields = globalExcludeFields || excludeFields ? Array.from(
265
+ /* @__PURE__ */ new Set([...globalExcludeFields ?? [], ...excludeFields ?? []])
266
+ ) : void 0;
267
+ const getValuesRef = (0, import_react2.useRef)(getValues);
268
+ const applyValuesRef = (0, import_react2.useRef)(applyValues);
269
+ (0, import_react2.useEffect)(() => {
270
+ getValuesRef.current = getValues;
271
+ }, [getValues]);
272
+ (0, import_react2.useEffect)(() => {
273
+ applyValuesRef.current = applyValues;
274
+ }, [applyValues]);
275
+ (0, import_react2.useEffect)(() => {
276
+ let cancelled = false;
277
+ async function init() {
278
+ if (!storageRef.current) {
279
+ storageRef.current = new DexieFormSnapshotsStorage();
280
+ }
281
+ const client = new FormSnapshotsClient({
282
+ storage: storageRef.current,
283
+ formName,
284
+ snapshotsLimit: effectiveSnapshotsLimit
285
+ });
286
+ clientRef.current = client;
287
+ const session = await client.initSession();
288
+ if (cancelled) return;
289
+ sessionIdRef.current = session.id;
290
+ if (session.data && session.data !== "{}") {
291
+ requestAnimationFrame(() => {
292
+ if (cancelled) return;
293
+ const snapshot = JSON.parse(session.data);
294
+ if (applyValuesRef.current) {
295
+ applyValuesRef.current(snapshot);
296
+ } else if (formRef.current) {
297
+ restoreSnapshotToForm(formRef.current, snapshot);
298
+ }
299
+ });
300
+ }
301
+ }
302
+ init();
303
+ return () => {
304
+ cancelled = true;
305
+ };
306
+ }, [formName, effectiveSnapshotsLimit]);
307
+ const handleBlur = (0, import_react2.useCallback)(() => {
308
+ const sessionId = sessionIdRef.current;
309
+ if (sessionId === null) return;
310
+ let snapshot;
311
+ if (getValuesRef.current) {
312
+ snapshot = getValuesRef.current();
313
+ } else {
314
+ const form = formRef.current;
315
+ if (!form) return;
316
+ snapshot = snapshotFromDOMForm(form);
317
+ }
318
+ snapshot = applyExcludeFields(snapshot, mergedExcludeFields);
319
+ if (!clientRef.current) return;
320
+ clientRef.current.saveSnapshot(sessionId, snapshot);
321
+ }, []);
322
+ const clearFormState = (0, import_react2.useCallback)(() => {
323
+ if (applyValuesRef.current) {
324
+ applyValuesRef.current({});
325
+ } else if (formRef.current) {
326
+ formRef.current.reset();
327
+ }
328
+ }, []);
329
+ const wrapSubmit = (0, import_react2.useCallback)(
330
+ (userSubmit) => (e) => {
331
+ e.preventDefault();
332
+ handleBlur();
333
+ const sessionId = sessionIdRef.current;
334
+ const client = clientRef.current;
335
+ if (sessionId !== null && client) {
336
+ if (effectiveDiscardOnSubmit) {
337
+ client.deleteSession(sessionId);
338
+ lastSubmittedSessionIdRef.current = null;
339
+ } else {
340
+ client.markSubmitted(sessionId);
341
+ lastSubmittedSessionIdRef.current = sessionId;
342
+ }
343
+ sessionIdRef.current = null;
344
+ }
345
+ if (effectiveDiscardOnSubmit) {
346
+ clearFormState();
347
+ }
348
+ setIsSubmitted(true);
349
+ userSubmit?.(e);
350
+ },
351
+ [handleBlur, clearFormState, effectiveDiscardOnSubmit]
352
+ );
353
+ const markSubmitResult = (0, import_react2.useCallback)(
354
+ async (params) => {
355
+ const client = clientRef.current;
356
+ const sessionId = lastSubmittedSessionIdRef.current;
357
+ if (!client || sessionId == null) return;
358
+ await client.setSubmissionResult({
359
+ sessionId,
360
+ statusCode: params.statusCode,
361
+ errorMessage: params.errorMessage
362
+ });
363
+ },
364
+ []
365
+ );
366
+ const wrapSubmitAsync = (0, import_react2.useCallback)(
367
+ (userSubmit) => async (e) => {
368
+ e.preventDefault();
369
+ handleBlur();
370
+ const sessionId = sessionIdRef.current;
371
+ const client = clientRef.current;
372
+ if (sessionId !== null && client) {
373
+ if (effectiveDiscardOnSubmit) {
374
+ await client.deleteSession(sessionId);
375
+ lastSubmittedSessionIdRef.current = null;
376
+ } else {
377
+ await client.markSubmitted(sessionId);
378
+ lastSubmittedSessionIdRef.current = sessionId;
379
+ }
380
+ sessionIdRef.current = null;
381
+ }
382
+ if (effectiveDiscardOnSubmit) {
383
+ clearFormState();
384
+ }
385
+ setIsSubmitted(true);
386
+ if (!userSubmit) return;
387
+ try {
388
+ const result = await userSubmit(e);
389
+ if (result && typeof result === "object") {
390
+ await markSubmitResult({
391
+ statusCode: "statusCode" in result ? result.statusCode : null,
392
+ errorMessage: "errorMessage" in result ? result.errorMessage : null
393
+ });
394
+ }
395
+ } catch (error) {
396
+ console.error("wrapSubmitAsync userSubmit error:", error);
397
+ }
398
+ },
399
+ [handleBlur, markSubmitResult, clearFormState, effectiveDiscardOnSubmit]
400
+ );
401
+ const restoreLatest = (0, import_react2.useCallback)(async () => {
402
+ if (!clientRef.current) return;
403
+ const snapshot = await clientRef.current.getLatestSnapshot();
404
+ if (!snapshot) return;
405
+ if (applyValuesRef.current) {
406
+ applyValuesRef.current(snapshot);
407
+ } else {
408
+ if (!formRef.current) return;
409
+ restoreSnapshotToForm(formRef.current, snapshot);
410
+ }
411
+ const newSession = await clientRef.current.openNewSessionFromSnapshot(
412
+ snapshot
413
+ );
414
+ sessionIdRef.current = newSession.id;
415
+ setIsSubmitted(false);
416
+ }, []);
417
+ return {
418
+ formRef,
419
+ handleBlur,
420
+ wrapSubmit,
421
+ wrapSubmitAsync,
422
+ restoreLatest,
423
+ isSubmitted,
424
+ markSubmitResult
425
+ };
426
+ }
427
+
428
+ // form-snapshots/hooks/use-form-snapshots-list.ts
429
+ var import_dexie_react_hooks = require("dexie-react-hooks");
430
+ function useFormSnapshotsList(formName, options) {
431
+ const {
432
+ onlySubmitted = false,
433
+ onlySuccessful = false,
434
+ maxAgeMs,
435
+ limit
436
+ } = options ?? {};
437
+ const items = (0, import_dexie_react_hooks.useLiveQuery)(async () => {
438
+ const now = Date.now();
439
+ const cutoff = maxAgeMs ? now - maxAgeMs : void 0;
440
+ let query = db.formSessions.where("formName").equals(formName);
441
+ let sessions = await query.toArray();
442
+ if (onlySubmitted) {
443
+ sessions = sessions.filter((s) => s.submitted);
444
+ }
445
+ if (onlySuccessful) {
446
+ sessions = sessions.filter((s) => {
447
+ if (s.statusCode == null) return false;
448
+ return s.statusCode >= 200 && s.statusCode < 300;
449
+ });
450
+ }
451
+ if (cutoff != null) {
452
+ sessions = sessions.filter((s) => s.createdAt >= cutoff);
453
+ }
454
+ sessions.sort((a, b) => b.updatedAt - a.updatedAt);
455
+ if (typeof limit === "number") {
456
+ sessions = sessions.slice(0, limit);
457
+ }
458
+ return sessions.map((s) => ({
459
+ id: s.id,
460
+ formName: s.formName,
461
+ createdAt: s.createdAt,
462
+ updatedAt: s.updatedAt,
463
+ submitted: s.submitted,
464
+ statusCode: s.statusCode ?? null,
465
+ errorMessage: s.errorMessage ?? null,
466
+ ageMs: now - s.updatedAt
467
+ }));
468
+ }, [formName, onlySubmitted, onlySuccessful, maxAgeMs, limit]);
469
+ const isLoading = items === void 0;
470
+ return { items: items ?? [], isLoading };
471
+ }
472
+
473
+ // form-snapshots/devtools.tsx
474
+ var import_react4 = require("react");
475
+ var import_dexie_react_hooks2 = require("dexie-react-hooks");
476
+
477
+ // form-snapshots/snapshot-table.tsx
478
+ var import_react3 = require("react");
479
+
480
+ // form-snapshots/styles.ts
481
+ var STYLE_ID = "form-snapshots-styles";
482
+ var SNAPSHOT_TABLE_CSS = `:root {
483
+ /* light \u2013 neutral-like palette */
484
+ --fs-st-border: #e5e5e5; /* neutral-200 */
485
+ --fs-st-border-soft: #d4d4d4; /* neutral-300 */
486
+ --fs-st-header-bg: #f5f5f5; /* neutral-100 */
487
+ --fs-st-key-text: #737373; /* neutral-500 */
488
+ --fs-st-muted-text: #737373; /* neutral-500 */
489
+ --fs-st-muted-strong: #a3a3a3; /* neutral-400 */
490
+ --fs-st-chip-bg: #e5e5e5; /* neutral-200 */
491
+ --fs-st-chip-text: #171717; /* neutral-900 */
492
+ --fs-st-object-bg: #f5f5f5; /* neutral-100 */
493
+ --fs-st-badge-true-bg: rgba(22, 163, 74, 0.1);
494
+ --fs-st-badge-true-text: #166534;
495
+ --fs-st-badge-false-border: #d4d4d4;
496
+ --fs-st-badge-false-text: #525252; /* neutral-600 */
497
+ --fs-st-text-main: #171717; /* neutral-900 */
498
+ }
499
+
500
+ html.dark {
501
+ /* dark \u2013 neutral-900/950 style */
502
+ --fs-st-border: #404040; /* neutral-700 */
503
+ --fs-st-border-soft: #262626; /* neutral-800 */
504
+ --fs-st-header-bg: #171717; /* neutral-900 */
505
+ --fs-st-key-text: #a3a3a3; /* neutral-400 */
506
+ --fs-st-muted-text: #a3a3a3; /* neutral-400 */
507
+ --fs-st-muted-strong: #737373; /* neutral-500 */
508
+ --fs-st-chip-bg: #262626; /* neutral-800 */
509
+ --fs-st-chip-text: #f5f5f5; /* neutral-100 */
510
+ --fs-st-object-bg: #0a0a0a; /* neutral-950ish */
511
+ --fs-st-badge-true-bg: rgba(22, 163, 74, 0.25);
512
+ --fs-st-badge-true-text: #bbf7d0;
513
+ --fs-st-badge-false-border: #404040;
514
+ --fs-st-badge-false-text: #e5e5e5; /* neutral-200 */
515
+ --fs-st-text-main: #f5f5f5; /* neutral-100 */
516
+ }
517
+ .fs-snapshot-wrapper {
518
+ border-radius: 6px;
519
+ border: 1px solid var(--fs-st-border);
520
+ overflow: hidden;
521
+ color: var(--fs-st-text-main);
522
+ }
523
+
524
+ .fs-snapshot-table {
525
+ width: 100%;
526
+ font-size: 12px;
527
+ border-collapse: collapse;
528
+ }
529
+
530
+ .fs-snapshot-header-row {
531
+ border-bottom: 1px solid var(--fs-st-border);
532
+ background: var(--fs-st-header-bg);
533
+ }
534
+
535
+ .fs-snapshot-header-cell {
536
+ padding: 4px 6px;
537
+ text-align: left;
538
+ font-weight: 500;
539
+ color: var(--fs-st-muted-text);
540
+ }
541
+
542
+ .fs-snapshot-row {
543
+ border-bottom: 1px solid var(--fs-st-border-soft);
544
+ }
545
+
546
+ .fs-snapshot-row:last-child {
547
+ border-bottom: none;
548
+ }
549
+
550
+ .fs-snapshot-key {
551
+ padding: 4px 6px;
552
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
553
+ "Liberation Mono", "Courier New", monospace;
554
+ font-size: 10px;
555
+ color: var(--fs-st-key-text);
556
+ vertical-align: top;
557
+ }
558
+
559
+ .fs-snapshot-value {
560
+ padding: 4px 6px;
561
+ vertical-align: top;
562
+ }
563
+
564
+ .fs-snapshot-empty {
565
+ font-size: 12px;
566
+ color: var(--fs-st-muted-text);
567
+ font-style: italic;
568
+ }
569
+
570
+ .fs-snapshot-muted {
571
+ color: var(--fs-st-muted-strong);
572
+ font-style: italic;
573
+ }
574
+
575
+ .fs-snapshot-text {
576
+ word-break: break-all;
577
+ }
578
+
579
+ .fs-snapshot-array {
580
+ display: flex;
581
+ flex-wrap: wrap;
582
+ gap: 4px;
583
+ }
584
+
585
+ .fs-snapshot-chip {
586
+ background: var(--fs-st-chip-bg);
587
+ color: var(--fs-st-chip-text);
588
+ font-size: 10px;
589
+ padding: 2px 6px;
590
+ border-radius: 999px;
591
+ }
592
+
593
+ .fs-snapshot-object {
594
+ max-height: 128px;
595
+ overflow: auto;
596
+ border-radius: 4px;
597
+ background: var(--fs-st-object-bg);
598
+ padding: 4px;
599
+ font-size: 10px;
600
+ }
601
+
602
+ .fs-snapshot-badge {
603
+ display: inline-flex;
604
+ align-items: center;
605
+ border-radius: 3px;
606
+ padding: 2px 6px;
607
+ font-size: 10px;
608
+ }
609
+
610
+ .fs-snapshot-badge-true {
611
+ background: var(--fs-st-badge-true-bg);
612
+ color: var(--fs-st-badge-true-text);
613
+ }
614
+
615
+ .fs-snapshot-badge-false {
616
+ background: transparent;
617
+ color: var(--fs-st-badge-false-text);
618
+ border: 1px solid var(--fs-st-badge-false-border);
619
+ }
620
+ `;
621
+ var DEVTOOLS_CSS = `:root {
622
+ /* light mode \u2013 Tailwind neutral-ish */
623
+ --fs-dev-bg-surface: rgba(250, 250, 250, 0.95); /* neutral-50 */
624
+ --fs-dev-bg-panel: rgba(250, 250, 250, 0.98); /* neutral-50 */
625
+ --fs-dev-bg-header: #f5f5f5; /* neutral-100 */
626
+ --fs-dev-bg-header-muted: #f5f5f5;
627
+ --fs-dev-bg-toggle-count: #e5e5e5; /* neutral-200 */
628
+ --fs-dev-bg-close-hover: #e5e5e5; /* neutral-200 */
629
+ --fs-dev-bg-tag: #e5e5e5; /* neutral-200 */
630
+ --fs-dev-bg-status: #e5e5e5; /* neutral-200 */
631
+ --fs-dev-bg-snapshot: #f5f5f5; /* neutral-100 */
632
+
633
+ --fs-dev-border-strong: #e5e5e5; /* neutral-200 */
634
+ --fs-dev-border-subtle: #e5e5e5; /* neutral-200 */
635
+ --fs-dev-border-soft: #d4d4d4; /* neutral-300 */
636
+ --fs-dev-border-snapshot: #d4d4d4; /* neutral-300 */
637
+
638
+ --fs-dev-text-primary: #171717; /* neutral-900 */
639
+ --fs-dev-text-muted: #737373; /* neutral-500 */
640
+ --fs-dev-text-soft: #525252; /* neutral-600 */
641
+ --fs-dev-text-error: #b91c1c;
642
+
643
+ --fs-dev-badge-success-bg: rgba(22, 163, 74, 0.1);
644
+ --fs-dev-badge-success-text: #166534;
645
+ --fs-dev-badge-warn-bg: rgba(217, 119, 6, 0.12);
646
+ --fs-dev-badge-warn-text: #92400e;
647
+
648
+ --fs-dev-shadow-toggle: 0 1px 3px rgba(0, 0, 0, 0.08);
649
+ --fs-dev-shadow-toggle-hover: 0 2px 6px rgba(0, 0, 0, 0.12);
650
+ --fs-dev-shadow-panel: 0 8px 24px rgba(0, 0, 0, 0.12);
651
+ }
652
+
653
+ html.dark .fs-color-scheme-root:root,
654
+ html.dark {
655
+ /* dark mode \u2013 neutral-900/950 style */
656
+ --fs-dev-bg-surface: rgba(23, 23, 23, 0.95); /* neutral-900 */
657
+ --fs-dev-bg-panel: rgba(23, 23, 23, 0.98); /* neutral-900 */
658
+ --fs-dev-bg-header: #171717; /* neutral-900 */
659
+ --fs-dev-bg-header-muted: #171717;
660
+ --fs-dev-bg-toggle-count: #262626; /* neutral-800 */
661
+ --fs-dev-bg-close-hover: #404040; /* neutral-700 */
662
+ --fs-dev-bg-tag: #262626; /* neutral-800 */
663
+ --fs-dev-bg-status: #171717; /* neutral-900 */
664
+ --fs-dev-bg-snapshot: #0a0a0a; /* neutral-950ish */
665
+
666
+ --fs-dev-border-strong: #404040; /* neutral-700 */
667
+ --fs-dev-border-subtle: #262626; /* neutral-800 */
668
+ --fs-dev-border-soft: #262626; /* neutral-800 */
669
+ --fs-dev-border-snapshot: #404040; /* neutral-700 */
670
+
671
+ --fs-dev-text-primary: #fafafa; /* neutral-50 */
672
+ --fs-dev-text-muted: #a3a3a3; /* neutral-400 */
673
+ --fs-dev-text-soft: #d4d4d4; /* neutral-300 */
674
+ --fs-dev-text-error: #fecaca;
675
+
676
+ --fs-dev-badge-success-bg: rgba(22, 163, 74, 0.25);
677
+ --fs-dev-badge-success-text: #bbf7d0;
678
+ --fs-dev-badge-warn-bg: rgba(217, 119, 6, 0.3);
679
+ --fs-dev-badge-warn-text: #fed7aa;
680
+
681
+ --fs-dev-shadow-toggle: 0 1px 3px rgba(0, 0, 0, 0.6);
682
+ --fs-dev-shadow-toggle-hover: 0 2px 6px rgba(0, 0, 0, 0.7);
683
+ --fs-dev-shadow-panel: 0 20px 40px rgba(0, 0, 0, 0.8);
684
+ }
685
+ .fs-devtools-toggle {
686
+ position: fixed;
687
+ bottom: 16px;
688
+ right: 16px;
689
+ z-index: 9999;
690
+ display: inline-flex;
691
+ align-items: center;
692
+ gap: 4px;
693
+ border-radius: 999px;
694
+ border: 1px solid var(--fs-dev-border-strong);
695
+ background: var(--fs-dev-bg-surface);
696
+ padding: 4px 8px;
697
+ font-size: 12px;
698
+ color: var(--fs-dev-text-soft);
699
+ box-shadow: var(--fs-dev-shadow-toggle);
700
+ cursor: pointer;
701
+ }
702
+
703
+ .fs-devtools-toggle:hover {
704
+ box-shadow: var(--fs-dev-shadow-toggle-hover);
705
+ color: var(--fs-dev-text-primary);
706
+ }
707
+
708
+ .fs-devtools-toggle-count {
709
+ margin-left: 4px;
710
+ border-radius: 999px;
711
+ background: var(--fs-dev-bg-toggle-count);
712
+ padding: 0 4px;
713
+ font-size: 10px;
714
+ color: var(--fs-dev-text-primary);
715
+ }
716
+
717
+ .fs-devtools-panel {
718
+ position: fixed;
719
+ bottom: 56px;
720
+ overflow: hidden;
721
+ right: 16px;
722
+ z-index: 9999;
723
+ width: 420px;
724
+ max-height: 60vh;
725
+ border-radius: 12px;
726
+ color: var(--fs-dev-text-primary);
727
+ border: 1px solid var(--fs-dev-border-strong);
728
+ background: var(--fs-dev-bg-panel);
729
+ box-shadow: var(--fs-dev-shadow-panel);
730
+ display: flex;
731
+ flex-direction: column;
732
+ font-size: 14px;
733
+ }
734
+
735
+ .fs-devtools-header {
736
+ display: flex;
737
+ align-items: center;
738
+ justify-content: space-between;
739
+ padding: 6px 8px;
740
+ border-bottom: 1px solid var(--fs-dev-border-subtle);
741
+ background: var(--fs-dev-bg-header);
742
+ }
743
+
744
+ .fs-devtools-header-left {
745
+ display: flex;
746
+ align-items: center;
747
+ gap: 6px;
748
+ }
749
+
750
+ .fs-devtools-header-text {
751
+ display: flex;
752
+ flex-direction: column;
753
+ }
754
+
755
+ .fs-devtools-header-title {
756
+ font-size: 12px;
757
+ font-weight: 600;
758
+ color: var(--fs-dev-text-primary);
759
+ }
760
+
761
+ .fs-devtools-header-subtitle {
762
+ font-size: 11px;
763
+ color: var(--fs-dev-text-muted);
764
+ }
765
+
766
+ .fs-devtools-header-actions {
767
+ display: flex;
768
+ align-items: center;
769
+ gap: 4px;
770
+ }
771
+
772
+ .fs-devtools-clear {
773
+ border-radius: 999px;
774
+ padding: 2px 8px;
775
+ font-size: 11px;
776
+ border: 1px solid var(--fs-dev-border-subtle);
777
+ background: transparent;
778
+ color: var(--fs-dev-text-muted);
779
+ cursor: pointer;
780
+ }
781
+
782
+ .fs-devtools-clear:hover:enabled {
783
+ background: var(--fs-dev-bg-close-hover);
784
+ color: var(--fs-dev-text-primary);
785
+ }
786
+
787
+ .fs-devtools-clear:disabled {
788
+ opacity: 0.5;
789
+ cursor: default;
790
+ }
791
+
792
+ .fs-devtools-close {
793
+ display: inline-flex;
794
+ align-items: center;
795
+ justify-content: center;
796
+ border-radius: 999px;
797
+ padding: 4px;
798
+ background: transparent;
799
+ border: none;
800
+ cursor: pointer;
801
+ color: var(--fs-dev-text-muted);
802
+ }
803
+
804
+ .fs-devtools-close:hover {
805
+ background: var(--fs-dev-bg-close-hover);
806
+ color: var(--fs-dev-text-primary);
807
+ }
808
+
809
+ .fs-devtools-filters {
810
+ display: flex;
811
+ align-items: center;
812
+ gap: 6px;
813
+ padding: 6px 8px;
814
+ border-bottom: 1px solid var(--fs-dev-border-subtle);
815
+ }
816
+
817
+ .fs-devtools-select {
818
+ flex: 1;
819
+ border-radius: 4px;
820
+ border: 1px solid var(--fs-dev-border-strong);
821
+ background: var(--fs-dev-bg-panel);
822
+ padding: 2px 6px;
823
+ font-size: 12px;
824
+ }
825
+
826
+ .fs-devtools-filter-label {
827
+ display: inline-flex;
828
+ align-items: center;
829
+ gap: 4px;
830
+ font-size: 11px;
831
+ color: var(--fs-dev-text-muted);
832
+ }
833
+
834
+ .fs-devtools-body {
835
+ flex: 1;
836
+ overflow: auto;
837
+ }
838
+
839
+ .fs-devtools-empty {
840
+ padding: 10px 12px;
841
+ font-size: 12px;
842
+ color: var(--fs-dev-text-muted);
843
+ }
844
+
845
+ .fs-devtools-list {
846
+ list-style: none;
847
+ margin: 0;
848
+ padding: 0;
849
+ border-top: 1px solid var(--fs-dev-border-soft);
850
+ }
851
+
852
+ .fs-devtools-item {
853
+ padding: 6px 8px;
854
+ border-bottom: 1px solid var(--fs-dev-border-soft);
855
+ }
856
+
857
+ .fs-devtools-item-toggle {
858
+ width: 100%;
859
+ display: flex;
860
+ align-items: center;
861
+ justify-content: space-between;
862
+ gap: 8px;
863
+ background: transparent;
864
+ border: none;
865
+ padding: 0;
866
+ cursor: pointer;
867
+ text-align: left;
868
+ }
869
+
870
+ .fs-devtools-item-main {
871
+ display: flex;
872
+ align-items: flex-start;
873
+ gap: 6px;
874
+ min-width: 0;
875
+ }
876
+
877
+ .fs-devtools-item-text {
878
+ min-width: 0;
879
+ }
880
+
881
+ .fs-devtools-item-tags {
882
+ display: flex;
883
+ align-items: center;
884
+ gap: 4px;
885
+ }
886
+
887
+ .fs-devtools-tag {
888
+ display: inline-flex;
889
+ align-items: center;
890
+ border-radius: 999px;
891
+ background: var(--fs-dev-bg-tag);
892
+ padding: 2px 6px;
893
+ font-size: 10px;
894
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
895
+ "Courier New", monospace;
896
+ color: var(--fs-dev-text-primary);
897
+ }
898
+
899
+ .fs-devtools-id {
900
+ font-size: 10px;
901
+ color: var(--fs-dev-text-muted);
902
+ }
903
+
904
+ .fs-devtools-meta {
905
+ margin-top: 2px;
906
+ display: flex;
907
+ align-items: center;
908
+ gap: 6px;
909
+ font-size: 11px;
910
+ color: var(--fs-dev-text-muted);
911
+ }
912
+
913
+ .fs-devtools-delete {
914
+ border: none;
915
+ background: transparent;
916
+ padding: 0;
917
+ font-size: 11px;
918
+ color: var(--fs-dev-text-soft);
919
+ cursor: pointer;
920
+ }
921
+
922
+ .fs-devtools-delete:hover {
923
+ text-decoration: underline;
924
+ color: var(--fs-dev-text-primary);
925
+ }
926
+
927
+ .fs-devtools-badge {
928
+ border-radius: 999px;
929
+ padding: 2px 6px;
930
+ font-size: 10px;
931
+ }
932
+
933
+ .fs-devtools-badge-success {
934
+ background: var(--fs-dev-badge-success-bg);
935
+ color: var(--fs-dev-badge-success-text);
936
+ }
937
+
938
+ .fs-devtools-badge-warn {
939
+ background: var(--fs-dev-badge-warn-bg);
940
+ color: var(--fs-dev-badge-warn-text);
941
+ }
942
+
943
+ .fs-devtools-status {
944
+ border-radius: 999px;
945
+ background: var(--fs-dev-bg-status);
946
+ padding: 2px 6px;
947
+ font-size: 10px;
948
+ color: var(--fs-dev-text-primary);
949
+ }
950
+
951
+ .fs-devtools-error {
952
+ margin-left: 8px;
953
+ max-width: 40%;
954
+ font-size: 11px;
955
+ color: var(--fs-dev-text-error);
956
+ white-space: nowrap;
957
+ overflow: hidden;
958
+ text-overflow: ellipsis;
959
+ }
960
+
961
+ .fs-devtools-snapshot {
962
+ margin-top: 6px;
963
+ border-radius: 6px;
964
+ background: var(--fs-dev-bg-snapshot);
965
+ font-size: 12px;
966
+ }
967
+ `;
968
+ function ensureFormSnapshotsStyles() {
969
+ if (typeof document === "undefined") return;
970
+ if (document.getElementById(STYLE_ID)) return;
971
+ const style = document.createElement("style");
972
+ style.id = STYLE_ID;
973
+ style.type = "text/css";
974
+ style.appendChild(
975
+ document.createTextNode(`${SNAPSHOT_TABLE_CSS}
976
+
977
+ ${DEVTOOLS_CSS}`)
978
+ );
979
+ (document.head || document.documentElement).appendChild(style);
980
+ }
981
+
982
+ // form-snapshots/snapshot-table.tsx
983
+ var import_jsx_runtime2 = require("react/jsx-runtime");
984
+ function isPrimitive(val) {
985
+ return val === null || val === void 0 || typeof val === "string" || typeof val === "number" || typeof val === "boolean";
986
+ }
987
+ var snapshotCache = /* @__PURE__ */ new Map();
988
+ function parseSnapshot(data) {
989
+ const cached = snapshotCache.get(data);
990
+ if (cached) return cached;
991
+ let snapshot = {};
992
+ try {
993
+ snapshot = JSON.parse(data);
994
+ } catch {
995
+ snapshot = {};
996
+ }
997
+ const parsed = {
998
+ snapshot,
999
+ fieldCount: Object.keys(snapshot).length
1000
+ };
1001
+ snapshotCache.set(data, parsed);
1002
+ return parsed;
1003
+ }
1004
+ function FormSnapshotTable({
1005
+ data,
1006
+ emptyMessage = "No data captured yet.",
1007
+ className
1008
+ }) {
1009
+ ensureFormSnapshotsStyles();
1010
+ const { snapshot, fieldCount } = (0, import_react3.useMemo)(() => parseSnapshot(data), [data]);
1011
+ if (fieldCount === 0) {
1012
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fs-snapshot-empty", children: emptyMessage });
1013
+ }
1014
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1015
+ "div",
1016
+ {
1017
+ className: [
1018
+ "fs-snapshot-wrapper",
1019
+ className
1020
+ ].filter(Boolean).join(" "),
1021
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "fs-snapshot-table", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { className: "fs-snapshot-header-row", children: [
1023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "fs-snapshot-header-cell", children: "Field" }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "fs-snapshot-header-cell", children: "Value" })
1025
+ ] }) }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: Object.entries(snapshot).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1027
+ "tr",
1028
+ {
1029
+ className: "fs-snapshot-row",
1030
+ children: [
1031
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "fs-snapshot-key", children: key }),
1032
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "fs-snapshot-value", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SnapshotValueCell, { value }) })
1033
+ ]
1034
+ },
1035
+ key
1036
+ )) })
1037
+ ] })
1038
+ }
1039
+ );
1040
+ }
1041
+ function SnapshotValueCell({ value }) {
1042
+ if (isPrimitive(value)) {
1043
+ if (value === null || value === void 0 || value === "") {
1044
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fs-snapshot-muted", children: "\u2014" });
1045
+ }
1046
+ if (typeof value === "boolean") {
1047
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1048
+ "span",
1049
+ {
1050
+ className: value ? "fs-snapshot-badge fs-snapshot-badge-true" : "fs-snapshot-badge fs-snapshot-badge-false",
1051
+ children: String(value)
1052
+ }
1053
+ );
1054
+ }
1055
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fs-snapshot-text", children: String(value) });
1056
+ }
1057
+ if (Array.isArray(value)) {
1058
+ if (value.length === 0) {
1059
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "fs-snapshot-muted", children: "\u2014" });
1060
+ }
1061
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fs-snapshot-array", children: value.map((v, idx) => (
1062
+ // eslint-disable-next-line react/no-array-index-key
1063
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1064
+ "span",
1065
+ {
1066
+ className: "fs-snapshot-chip",
1067
+ children: String(v)
1068
+ },
1069
+ idx
1070
+ )
1071
+ )) });
1072
+ }
1073
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pre", { className: "fs-snapshot-object", children: JSON.stringify(value, null, 2) });
1074
+ }
1075
+
1076
+ // form-snapshots/devtools.tsx
1077
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1078
+ var import_meta = {};
1079
+ function formatDateShort(ms) {
1080
+ return new Intl.DateTimeFormat("en-GB", {
1081
+ month: "short",
1082
+ day: "2-digit",
1083
+ hour: "2-digit",
1084
+ minute: "2-digit"
1085
+ }).format(new Date(ms));
1086
+ }
1087
+ function formatAge(ms) {
1088
+ const seconds = Math.floor(ms / 1e3);
1089
+ const minutes = Math.floor(seconds / 60);
1090
+ const hours = Math.floor(minutes / 60);
1091
+ const days = Math.floor(hours / 24);
1092
+ if (days > 0) return `${days}d ${hours % 24}h`;
1093
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
1094
+ if (minutes > 0) return `${minutes}m`;
1095
+ return `${seconds}s`;
1096
+ }
1097
+ function FormSnapshotsDevtools() {
1098
+ ensureFormSnapshotsStyles();
1099
+ const isProd = typeof import_meta !== "undefined" && import_meta.env?.PROD;
1100
+ if (isProd) {
1101
+ return null;
1102
+ }
1103
+ const [open, setOpen] = (0, import_react4.useState)(false);
1104
+ const [selectedForm, setSelectedForm] = (0, import_react4.useState)("all");
1105
+ const [onlySubmitted, setOnlySubmitted] = (0, import_react4.useState)(false);
1106
+ const [onlyErrors, setOnlyErrors] = (0, import_react4.useState)(false);
1107
+ const [expandedId, setExpandedId] = (0, import_react4.useState)(null);
1108
+ const [isClearing, setIsClearing] = (0, import_react4.useState)(false);
1109
+ const sessions = (0, import_dexie_react_hooks2.useLiveQuery)(() => {
1110
+ return db.formSessions.toCollection().sortBy("updatedAt").then((all) => all.reverse());
1111
+ }, []);
1112
+ const now = Date.now();
1113
+ const formNames = (0, import_react4.useMemo)(() => {
1114
+ if (!sessions) return [];
1115
+ return Array.from(new Set(sessions.map((s) => s.formName))).sort();
1116
+ }, [sessions]);
1117
+ const filtered = (0, import_react4.useMemo)(() => {
1118
+ if (!sessions) return [];
1119
+ return sessions.filter((s) => {
1120
+ if (selectedForm !== "all" && s.formName !== selectedForm) {
1121
+ return false;
1122
+ }
1123
+ if (onlySubmitted && !s.submitted) {
1124
+ return false;
1125
+ }
1126
+ if (onlyErrors && (s.statusCode == null || s.statusCode < 400)) {
1127
+ return false;
1128
+ }
1129
+ return true;
1130
+ });
1131
+ }, [sessions, selectedForm, onlySubmitted, onlyErrors]);
1132
+ const handleDeleteSession = async (id) => {
1133
+ try {
1134
+ await db.formSessions.delete(id);
1135
+ } catch (error) {
1136
+ console.error("Failed to delete form session", error);
1137
+ }
1138
+ };
1139
+ const handleClearAll = async () => {
1140
+ if (!sessions || sessions.length === 0) return;
1141
+ setIsClearing(true);
1142
+ try {
1143
+ const ids = sessions.map((s) => s.id);
1144
+ await db.formSessions.bulkDelete(ids);
1145
+ } catch (error) {
1146
+ console.error("Failed to clear form sessions", error);
1147
+ } finally {
1148
+ setIsClearing(false);
1149
+ }
1150
+ };
1151
+ const total = sessions?.length ?? 0;
1152
+ if (!sessions || sessions.length === 0) {
1153
+ return null;
1154
+ }
1155
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1157
+ "button",
1158
+ {
1159
+ type: "button",
1160
+ onClick: () => setOpen((v) => !v),
1161
+ className: "fs-devtools-toggle",
1162
+ children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HistoryIcon, { style: { width: 12, height: 12 } }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Form Snapshot Devtools" }),
1165
+ total > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fs-devtools-toggle-count", children: total })
1166
+ ]
1167
+ }
1168
+ ),
1169
+ open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-panel", children: [
1170
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-header", children: [
1171
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-header-left", children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HistoryIcon, { style: { width: 14, height: 14 } }),
1173
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-header-text", children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fs-devtools-header-title", children: "Form Snapshot Devtools" }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fs-devtools-header-subtitle", children: [
1176
+ filtered.length,
1177
+ " / ",
1178
+ total,
1179
+ " session"
1180
+ ] })
1181
+ ] })
1182
+ ] }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-header-actions", children: [
1184
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1185
+ "button",
1186
+ {
1187
+ type: "button",
1188
+ onClick: handleClearAll,
1189
+ className: "fs-devtools-clear",
1190
+ disabled: isClearing || !sessions || sessions.length === 0,
1191
+ children: "Clear all"
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ onClick: () => setOpen(false),
1199
+ className: "fs-devtools-close",
1200
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(XIcon, { style: { width: 12, height: 12 } })
1201
+ }
1202
+ )
1203
+ ] })
1204
+ ] }),
1205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-filters", children: [
1206
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FilterIcon, { style: { width: 12, height: 12 } }),
1207
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1208
+ "select",
1209
+ {
1210
+ className: "fs-devtools-select",
1211
+ value: selectedForm,
1212
+ onChange: (e) => setSelectedForm(e.target.value),
1213
+ children: [
1214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "all", children: "All forms" }),
1215
+ formNames.map((name) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: name, children: name }, name))
1216
+ ]
1217
+ }
1218
+ ),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "fs-devtools-filter-label", children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1221
+ "input",
1222
+ {
1223
+ type: "checkbox",
1224
+ checked: onlySubmitted,
1225
+ onChange: (e) => setOnlySubmitted(e.target.checked)
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "submitted" })
1229
+ ] }),
1230
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "fs-devtools-filter-label", children: [
1231
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1232
+ "input",
1233
+ {
1234
+ type: "checkbox",
1235
+ checked: onlyErrors,
1236
+ onChange: (e) => setOnlyErrors(e.target.checked)
1237
+ }
1238
+ ),
1239
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "errors" })
1240
+ ] })
1241
+ ] }),
1242
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fs-devtools-body", children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fs-devtools-empty", children: "No sessions match the selected filters." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "fs-devtools-list", children: filtered.map((s) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1243
+ "li",
1244
+ {
1245
+ className: "fs-devtools-item",
1246
+ children: [
1247
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1248
+ "button",
1249
+ {
1250
+ type: "button",
1251
+ onClick: () => setExpandedId(
1252
+ (current) => current === s.id ? null : s.id
1253
+ ),
1254
+ className: "fs-devtools-item-toggle",
1255
+ children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-item-main", children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1258
+ ChevronDownIcon,
1259
+ {
1260
+ style: {
1261
+ width: 12,
1262
+ height: 12,
1263
+ transform: expandedId === s.id ? "rotate(0deg)" : "rotate(-90deg)",
1264
+ transition: "transform 120ms ease-out"
1265
+ }
1266
+ }
1267
+ ),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-item-text", children: [
1269
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-item-tags", children: [
1270
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fs-devtools-tag", children: s.formName }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fs-devtools-id", children: [
1272
+ "#",
1273
+ s.id
1274
+ ] })
1275
+ ] }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fs-devtools-meta", children: [
1277
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: formatDateShort(s.updatedAt) }),
1278
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
1279
+ "\xB7 ",
1280
+ formatAge(now - s.updatedAt),
1281
+ " ago"
1282
+ ] }),
1283
+ s.submitted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fs-devtools-badge fs-devtools-badge-success", children: "submitted" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "fs-devtools-badge fs-devtools-badge-warn", children: "in progress" }),
1284
+ s.statusCode != null && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "fs-devtools-status", children: [
1285
+ "status ",
1286
+ s.statusCode
1287
+ ] }),
1288
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1289
+ "button",
1290
+ {
1291
+ type: "button",
1292
+ className: "fs-devtools-delete",
1293
+ onClick: (e) => {
1294
+ e.stopPropagation();
1295
+ void handleDeleteSession(s.id);
1296
+ },
1297
+ children: "Delete"
1298
+ }
1299
+ )
1300
+ ] })
1301
+ ] })
1302
+ ] }),
1303
+ typeof s.errorMessage === "string" && s.errorMessage && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fs-devtools-error", children: s.errorMessage })
1304
+ ]
1305
+ }
1306
+ ),
1307
+ expandedId === s.id && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fs-devtools-snapshot", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1308
+ FormSnapshotTable,
1309
+ {
1310
+ data: s.data,
1311
+ emptyMessage: "This session does not contain any field data yet."
1312
+ }
1313
+ ) })
1314
+ ]
1315
+ },
1316
+ s.id
1317
+ )) }) })
1318
+ ] })
1319
+ ] });
1320
+ }
1321
+ function HistoryIcon(props) {
1322
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1323
+ "svg",
1324
+ {
1325
+ viewBox: "0 0 24 24",
1326
+ fill: "none",
1327
+ stroke: "currentColor",
1328
+ strokeWidth: "2",
1329
+ strokeLinecap: "round",
1330
+ strokeLinejoin: "round",
1331
+ "aria-hidden": "true",
1332
+ ...props,
1333
+ children: [
1334
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 3v6h6" }),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3.05 13A9 9 0 1 0 9 3.05" }),
1336
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 7v5l3 3" })
1337
+ ]
1338
+ }
1339
+ );
1340
+ }
1341
+ function XIcon(props) {
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1343
+ "svg",
1344
+ {
1345
+ viewBox: "0 0 24 24",
1346
+ fill: "none",
1347
+ stroke: "currentColor",
1348
+ strokeWidth: "2",
1349
+ strokeLinecap: "round",
1350
+ strokeLinejoin: "round",
1351
+ "aria-hidden": "true",
1352
+ ...props,
1353
+ children: [
1354
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1355
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1356
+ ]
1357
+ }
1358
+ );
1359
+ }
1360
+ function FilterIcon(props) {
1361
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1362
+ "svg",
1363
+ {
1364
+ viewBox: "0 0 24 24",
1365
+ fill: "none",
1366
+ stroke: "currentColor",
1367
+ strokeWidth: "2",
1368
+ strokeLinecap: "round",
1369
+ strokeLinejoin: "round",
1370
+ "aria-hidden": "true",
1371
+ ...props,
1372
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" })
1373
+ }
1374
+ );
1375
+ }
1376
+ function ChevronDownIcon(props) {
1377
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1378
+ "svg",
1379
+ {
1380
+ viewBox: "0 0 24 24",
1381
+ fill: "none",
1382
+ stroke: "currentColor",
1383
+ strokeWidth: "2",
1384
+ strokeLinecap: "round",
1385
+ strokeLinejoin: "round",
1386
+ "aria-hidden": "true",
1387
+ ...props,
1388
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "6 9 12 15 18 9" })
1389
+ }
1390
+ );
1391
+ }
1392
+
1393
+ // form-snapshots/adapters.ts
1394
+ function useRHFFormSnapshots(formName, form, options) {
1395
+ return useFormSnapshots(formName, {
1396
+ ...options,
1397
+ getValues: () => form.getValues(),
1398
+ applyValues: (snap) => form.reset(snap)
1399
+ });
1400
+ }
1401
+ function useObjectFormSnapshots(formName, state, setState, options) {
1402
+ return useFormSnapshots(formName, {
1403
+ ...options,
1404
+ getValues: () => state,
1405
+ applyValues: (snap) => setState(snap)
1406
+ });
1407
+ }
1408
+ // Annotate the CommonJS export names for ESM import in node:
1409
+ 0 && (module.exports = {
1410
+ FormSnapshotTable,
1411
+ FormSnapshotsClient,
1412
+ FormSnapshotsDevtools,
1413
+ FormSnapshotsProvider,
1414
+ db,
1415
+ useFormSnapshots,
1416
+ useFormSnapshotsConfig,
1417
+ useFormSnapshotsList,
1418
+ useObjectFormSnapshots,
1419
+ useRHFFormSnapshots
1420
+ });
1421
+ //# sourceMappingURL=index.js.map