@algodomain/smart-forms 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/dist/index.js ADDED
@@ -0,0 +1,821 @@
1
+ import { Button } from './chunk-6WAEAWTD.js';
2
+ export { SmartCheckbox, SmartDatePicker, SmartRadioGroup, SmartSelect, SmartTags } from './chunk-6WAEAWTD.js';
3
+ import { SmartFormProvider, useSmartForm, FieldDetectionContext, cn } from './chunk-IG4XDQMV.js';
4
+ export { SmartFormProvider, SmartInput, useFieldDetection, useFormField, useSmartForm } from './chunk-IG4XDQMV.js';
5
+ import React3, { createContext, isValidElement, useContext, useState, useMemo, useCallback, useRef, useEffect } from 'react';
6
+ import { ToastContainer, Bounce } from 'react-toastify';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { ChevronLeft, ChevronRight, Check, X } from 'lucide-react';
9
+ import { z } from 'zod';
10
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
11
+
12
+ var ToastContainerWrapper = () => /* @__PURE__ */ jsx(
13
+ ToastContainer,
14
+ {
15
+ position: "top-center",
16
+ autoClose: 2e3,
17
+ newestOnTop: false,
18
+ closeOnClick: true,
19
+ rtl: false,
20
+ pauseOnFocusLoss: true,
21
+ draggable: true,
22
+ pauseOnHover: true,
23
+ theme: "light",
24
+ transition: Bounce
25
+ }
26
+ );
27
+ var FormHeader = ({ title, subTitle, logo }) => {
28
+ if (!title && !subTitle && !logo) return null;
29
+ return /* @__PURE__ */ jsx("div", { className: "mb-4 border-b border-gray-200 pb-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center space-x-4", children: [
30
+ logo && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: logo }),
31
+ (title || subTitle) && /* @__PURE__ */ jsxs("div", { className: logo ? "text-left" : "text-center", children: [
32
+ title && /* @__PURE__ */ jsx("h1", { className: "text-xl font-bold text-gray-800", children: title }),
33
+ subTitle && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: subTitle })
34
+ ] })
35
+ ] }) });
36
+ };
37
+ var Section = ({ text }) => {
38
+ return /* @__PURE__ */ jsxs("div", { className: "relative my-6", children: [
39
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-gray-300" }) }),
40
+ /* @__PURE__ */ jsx("div", { className: "relative flex justify-center text-sm", children: /* @__PURE__ */ jsx("span", { className: "bg-white px-2 text-gray-500", children: text }) })
41
+ ] });
42
+ };
43
+ var Footer = ({ children, className = "" }) => {
44
+ return /* @__PURE__ */ jsx("div", { className: `mt-4 pt-4 border-t border-gray-200 ${className}`, children });
45
+ };
46
+ var BaseSmartForm = ({
47
+ children,
48
+ api,
49
+ method = "POST",
50
+ submitButtonText = "Submit",
51
+ submitButtonIcon,
52
+ allowSaveDraft = false,
53
+ saveDraftApi,
54
+ enableLocalStorage = false,
55
+ storageKey,
56
+ logFormData = false,
57
+ onSuccess,
58
+ onError,
59
+ transformData,
60
+ className = "max-w-2xl mx-auto p-6 bg-white",
61
+ title,
62
+ subTitle,
63
+ logo,
64
+ footer,
65
+ initialData = {},
66
+ showReset = false,
67
+ showProgressBar = true,
68
+ showTabNumbers = true,
69
+ authentication
70
+ }) => {
71
+ const config = {
72
+ api,
73
+ method,
74
+ submitButtonText,
75
+ submitButtonIcon,
76
+ allowSaveDraft,
77
+ saveDraftApi,
78
+ enableLocalStorage,
79
+ storageKey,
80
+ logFormData,
81
+ onSuccess,
82
+ onError,
83
+ transformData,
84
+ className,
85
+ title,
86
+ subTitle,
87
+ logo,
88
+ footer,
89
+ showReset,
90
+ showProgressBar,
91
+ showTabNumbers,
92
+ authentication
93
+ };
94
+ return /* @__PURE__ */ jsx(SmartFormProvider, { config, initialData, children: /* @__PURE__ */ jsxs("div", { className, children: [
95
+ /* @__PURE__ */ jsx(ToastContainerWrapper, {}),
96
+ /* @__PURE__ */ jsx(FormHeader, { title, subTitle, logo }),
97
+ children,
98
+ footer && /* @__PURE__ */ jsx("div", { className: "mt-4 pt-4 border-t border-gray-200", children: footer })
99
+ ] }) });
100
+ };
101
+ var LoadingSpinner = ({ className = "h-4 w-4" }) => /* @__PURE__ */ jsx("div", { className: `animate-spin rounded-full border-b-2 border-current ${className}` });
102
+ var SubmitButton = ({ onClick, disabled, children, className }) => /* @__PURE__ */ jsx(Button, { onClick, disabled, className, children: disabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
103
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
104
+ "Submitting..."
105
+ ] }) : children });
106
+ var DraftSaveButton = ({ onClick, disabled }) => /* @__PURE__ */ jsx(Button, { onClick, disabled, variant: "secondary", children: disabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
107
+ /* @__PURE__ */ jsx(LoadingSpinner, {}),
108
+ "Saving..."
109
+ ] }) : "Save Draft" });
110
+ var ResetButton = ({ onClick }) => /* @__PURE__ */ jsx(Button, { onClick, variant: "secondary", children: "Reset Form" });
111
+ var NavigationButtons = ({
112
+ onPrevious,
113
+ onNext,
114
+ onSubmit,
115
+ onSaveDraft,
116
+ onReset,
117
+ isLoading,
118
+ isDraftSaving,
119
+ config,
120
+ isFirstTab = false,
121
+ isLastTab = false
122
+ }) => {
123
+ const hasResetButton = !!onReset;
124
+ const hasDraftButton = config.allowSaveDraft && !!onSaveDraft;
125
+ const hasSubmitOrNextButton = isLastTab || !!onNext;
126
+ const rightSectionButtons = [hasResetButton, hasDraftButton, hasSubmitOrNextButton].filter(Boolean).length;
127
+ const hasPreviousButton = !!onPrevious;
128
+ const isSingleButton = rightSectionButtons === 1 && !hasPreviousButton;
129
+ return /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mt-8 pt-6 border-t border-gray-200", children: [
130
+ /* @__PURE__ */ jsx("div", { className: "flex gap-4", children: onPrevious && /* @__PURE__ */ jsxs(
131
+ Button,
132
+ {
133
+ onClick: onPrevious,
134
+ disabled: isFirstTab,
135
+ variant: "secondary",
136
+ children: [
137
+ /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
138
+ "Previous"
139
+ ]
140
+ }
141
+ ) }),
142
+ /* @__PURE__ */ jsxs("div", { className: isSingleButton ? "w-full" : "flex gap-4", children: [
143
+ onReset && /* @__PURE__ */ jsx(ResetButton, { onClick: onReset }),
144
+ config.allowSaveDraft && onSaveDraft && /* @__PURE__ */ jsx(DraftSaveButton, { onClick: onSaveDraft, disabled: isDraftSaving }),
145
+ isLastTab ? /* @__PURE__ */ jsxs(
146
+ SubmitButton,
147
+ {
148
+ onClick: onSubmit,
149
+ disabled: isLoading,
150
+ className: isSingleButton ? "w-full" : "",
151
+ children: [
152
+ config.submitButtonIcon,
153
+ config.submitButtonText || "Submit"
154
+ ]
155
+ }
156
+ ) : onNext && /* @__PURE__ */ jsxs(
157
+ Button,
158
+ {
159
+ onClick: onNext,
160
+ className: isSingleButton ? "w-full" : "",
161
+ children: [
162
+ "Next",
163
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
164
+ ]
165
+ }
166
+ )
167
+ ] })
168
+ ] });
169
+ };
170
+ var SimpleFormButtons = ({
171
+ onSubmit,
172
+ onSaveDraft,
173
+ onReset,
174
+ isLoading,
175
+ isDraftSaving,
176
+ config
177
+ }) => {
178
+ const hasResetButton = !!onReset;
179
+ const hasDraftButton = config.allowSaveDraft && !!onSaveDraft;
180
+ const hasSubmitButton = true;
181
+ const totalButtons = [hasResetButton, hasDraftButton, hasSubmitButton].filter(Boolean).length;
182
+ const isSingleButton = totalButtons === 1;
183
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-8 pt-6 border-t border-gray-200", children: /* @__PURE__ */ jsxs("div", { className: isSingleButton ? "w-full" : "flex gap-4", children: [
184
+ onReset && /* @__PURE__ */ jsx(ResetButton, { onClick: onReset }),
185
+ config.allowSaveDraft && onSaveDraft && /* @__PURE__ */ jsx(DraftSaveButton, { onClick: onSaveDraft, disabled: isDraftSaving }),
186
+ /* @__PURE__ */ jsxs(
187
+ SubmitButton,
188
+ {
189
+ onClick: onSubmit,
190
+ disabled: isLoading,
191
+ className: isSingleButton ? "w-full" : "",
192
+ children: [
193
+ config.submitButtonIcon,
194
+ config.submitButtonText || "Submit"
195
+ ]
196
+ }
197
+ )
198
+ ] }) });
199
+ };
200
+ var SubmitButton2 = () => {
201
+ const { isLoading, isDraftSaving, submitForm, saveDraft, resetForm, config } = useSmartForm();
202
+ const shouldShowReset = config.showReset || config.enableLocalStorage;
203
+ return /* @__PURE__ */ jsx(
204
+ SimpleFormButtons,
205
+ {
206
+ onSubmit: submitForm,
207
+ onSaveDraft: config.allowSaveDraft ? saveDraft : void 0,
208
+ onReset: shouldShowReset ? resetForm : void 0,
209
+ isLoading,
210
+ isDraftSaving,
211
+ config
212
+ }
213
+ );
214
+ };
215
+ var SmartForm = (props) => {
216
+ const { children, ...otherProps } = props;
217
+ const childArray = React3.Children.toArray(children);
218
+ const footerChildren = [];
219
+ const regularChildren = [];
220
+ childArray.forEach((child) => {
221
+ if (isValidElement(child) && child.type === Footer) {
222
+ footerChildren.push(child);
223
+ } else {
224
+ regularChildren.push(child);
225
+ }
226
+ });
227
+ return /* @__PURE__ */ jsxs(BaseSmartForm, { ...otherProps, children: [
228
+ /* @__PURE__ */ jsx("div", { className: "space-y-6", children: regularChildren }),
229
+ /* @__PURE__ */ jsx(SubmitButton2, {}),
230
+ footerChildren.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-4", children: footerChildren })
231
+ ] });
232
+ };
233
+ function Tabs({
234
+ className,
235
+ ...props
236
+ }) {
237
+ return /* @__PURE__ */ jsx(
238
+ TabsPrimitive.Root,
239
+ {
240
+ "data-slot": "tabs",
241
+ className: cn("flex flex-col gap-2", className),
242
+ ...props
243
+ }
244
+ );
245
+ }
246
+ function TabsList({
247
+ className,
248
+ ...props
249
+ }) {
250
+ return /* @__PURE__ */ jsx(
251
+ TabsPrimitive.List,
252
+ {
253
+ "data-slot": "tabs-list",
254
+ className: cn(
255
+ "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
256
+ className
257
+ ),
258
+ ...props
259
+ }
260
+ );
261
+ }
262
+ function TabsTrigger({
263
+ className,
264
+ ...props
265
+ }) {
266
+ return /* @__PURE__ */ jsx(
267
+ TabsPrimitive.Trigger,
268
+ {
269
+ "data-slot": "tabs-trigger",
270
+ className: cn(
271
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
272
+ className
273
+ ),
274
+ ...props
275
+ }
276
+ );
277
+ }
278
+ function TabsContent({
279
+ className,
280
+ ...props
281
+ }) {
282
+ return /* @__PURE__ */ jsx(
283
+ TabsPrimitive.Content,
284
+ {
285
+ "data-slot": "tabs-content",
286
+ className: cn("flex-1 outline-none", className),
287
+ ...props
288
+ }
289
+ );
290
+ }
291
+ var TabIndexContext = React3.createContext(null);
292
+ var useTabIndex = () => {
293
+ const context = useContext(TabIndexContext);
294
+ return context;
295
+ };
296
+ var TabIndexProvider = ({
297
+ children,
298
+ tabIndex
299
+ }) => {
300
+ return /* @__PURE__ */ jsx(TabIndexContext.Provider, { value: { tabIndex }, children });
301
+ };
302
+ var ExternalFormContext = createContext(null);
303
+ var Tab = ({ children }) => {
304
+ return /* @__PURE__ */ jsx(Fragment, { children });
305
+ };
306
+ var NavigationButtonsWrapper = ({
307
+ activeTab,
308
+ totalTabs,
309
+ onPrevious,
310
+ onNext,
311
+ onSubmit,
312
+ onSaveDraft,
313
+ onReset,
314
+ isLoading,
315
+ isDraftSaving,
316
+ config
317
+ }) => {
318
+ const isLastTab = activeTab === totalTabs - 1;
319
+ const isFirstTab = activeTab === 0;
320
+ return /* @__PURE__ */ jsx(
321
+ NavigationButtons,
322
+ {
323
+ onPrevious,
324
+ onNext,
325
+ onSubmit,
326
+ onSaveDraft,
327
+ onReset,
328
+ isLoading,
329
+ isDraftSaving,
330
+ config,
331
+ isFirstTab,
332
+ isLastTab
333
+ }
334
+ );
335
+ };
336
+ var MultiTabSmartForm = ({
337
+ children,
338
+ showProgressBar = true,
339
+ showTabNumbers = true,
340
+ ...baseProps
341
+ }) => {
342
+ const [activeTab, setActiveTab] = useState(0);
343
+ const [tabFields, setTabFields] = useState([]);
344
+ const [completedTabs, setCompletedTabs] = useState(/* @__PURE__ */ new Set());
345
+ const [validationErrorTabs, setValidationErrorTabs] = useState(/* @__PURE__ */ new Set());
346
+ const [externalTabFields, setExternalTabFields] = useState({});
347
+ const { footerChildren, regularChildren } = useMemo(() => {
348
+ const childArray = React3.Children.toArray(children);
349
+ const footerChildren2 = [];
350
+ const regularChildren2 = [];
351
+ childArray.forEach((child) => {
352
+ if (isValidElement(child) && child.type === Footer) {
353
+ footerChildren2.push(child);
354
+ } else {
355
+ regularChildren2.push(child);
356
+ }
357
+ });
358
+ return { footerChildren: footerChildren2, regularChildren: regularChildren2 };
359
+ }, [children]);
360
+ const tabs = [];
361
+ React3.Children.forEach(regularChildren, (child) => {
362
+ if (React3.isValidElement(child) && child.type === Tab) {
363
+ const props = child.props;
364
+ if (props.title) {
365
+ tabs.push(props.title);
366
+ }
367
+ }
368
+ });
369
+ const findSmartInputs = useCallback((children2) => {
370
+ const fields = [];
371
+ React3.Children.forEach(children2, (child) => {
372
+ if (React3.isValidElement(child)) {
373
+ const fieldProps = child.props;
374
+ if (fieldProps && typeof fieldProps === "object" && "field" in fieldProps && fieldProps.field) {
375
+ fields.push(fieldProps.field);
376
+ } else if (child.props && typeof child.props === "object" && "children" in child.props) {
377
+ const nestedFields = findSmartInputs(child.props.children);
378
+ fields.push(...nestedFields);
379
+ }
380
+ }
381
+ });
382
+ return fields;
383
+ }, []);
384
+ const registerTabFields = useCallback((tabIndex, fields) => {
385
+ setExternalTabFields((prev) => ({
386
+ ...prev,
387
+ [tabIndex]: fields
388
+ }));
389
+ }, []);
390
+ React3.useEffect(() => {
391
+ const fields = [];
392
+ React3.Children.forEach(regularChildren, (child) => {
393
+ if (React3.isValidElement(child) && child.type === Tab) {
394
+ const tabFieldsInTab = findSmartInputs(child.props.children);
395
+ fields.push(tabFieldsInTab);
396
+ }
397
+ });
398
+ setTabFields(fields);
399
+ }, [regularChildren, findSmartInputs]);
400
+ const getCombinedTabFields = useCallback((tabIndex) => {
401
+ const internalFields = tabFields[tabIndex] || [];
402
+ const externalFields = externalTabFields[tabIndex] || [];
403
+ return [...internalFields, ...externalFields];
404
+ }, [tabFields, externalTabFields]);
405
+ const config = {
406
+ ...baseProps,
407
+ showProgressBar,
408
+ showTabNumbers
409
+ };
410
+ const handleTabChange = useCallback((index) => {
411
+ setActiveTab(index);
412
+ }, []);
413
+ const handlePrevious = useCallback(() => {
414
+ if (activeTab > 0) {
415
+ setActiveTab(activeTab - 1);
416
+ }
417
+ }, [activeTab]);
418
+ const handleNext = useCallback(() => {
419
+ if (activeTab < tabs.length - 1) {
420
+ setActiveTab(activeTab + 1);
421
+ }
422
+ }, [activeTab, tabs.length]);
423
+ const externalFormContextValue = useMemo(() => ({
424
+ registerTabFields,
425
+ currentTabIndex: activeTab
426
+ }), [registerTabFields, activeTab]);
427
+ return /* @__PURE__ */ jsx(BaseSmartForm, { ...baseProps, children: /* @__PURE__ */ jsx(ExternalFormContext.Provider, { value: externalFormContextValue, children: /* @__PURE__ */ jsx(
428
+ MultiTabFormContent,
429
+ {
430
+ activeTab,
431
+ tabs,
432
+ getCombinedTabFields,
433
+ config,
434
+ onTabChange: handleTabChange,
435
+ onPrevious: handlePrevious,
436
+ onNext: handleNext,
437
+ completedTabs,
438
+ setCompletedTabs,
439
+ validationErrorTabs,
440
+ setValidationErrorTabs,
441
+ footerChildren,
442
+ children: regularChildren
443
+ }
444
+ ) }) });
445
+ };
446
+ var MultiTabFormContent = ({
447
+ activeTab,
448
+ tabs,
449
+ getCombinedTabFields,
450
+ config,
451
+ onTabChange,
452
+ onPrevious,
453
+ onNext,
454
+ completedTabs,
455
+ setCompletedTabs,
456
+ validationErrorTabs,
457
+ setValidationErrorTabs,
458
+ footerChildren,
459
+ children
460
+ }) => {
461
+ const { isLoading, isDraftSaving, submitForm, saveDraft, resetForm, validateFields, formData, validationRegistry, setErrors } = useSmartForm();
462
+ const [maxContentHeight, setMaxContentHeight] = useState(0);
463
+ const contentRefs = useRef([]);
464
+ const debounce = (func, wait) => {
465
+ let timeout;
466
+ return function executedFunction(...args) {
467
+ const later = () => {
468
+ clearTimeout(timeout);
469
+ func(...args);
470
+ };
471
+ clearTimeout(timeout);
472
+ timeout = setTimeout(later, wait);
473
+ };
474
+ };
475
+ React3.useEffect(() => {
476
+ const calculateMaxHeight = () => {
477
+ let maxHeight = 0;
478
+ contentRefs.current.forEach((ref) => {
479
+ if (ref) {
480
+ const height = ref.scrollHeight;
481
+ maxHeight = Math.max(maxHeight, height);
482
+ }
483
+ });
484
+ if (maxHeight > 0) {
485
+ setMaxContentHeight(maxHeight);
486
+ }
487
+ };
488
+ const timeoutId = setTimeout(calculateMaxHeight, 100);
489
+ window.addEventListener("resize", calculateMaxHeight);
490
+ return () => {
491
+ clearTimeout(timeoutId);
492
+ window.removeEventListener("resize", calculateMaxHeight);
493
+ };
494
+ }, [children, activeTab]);
495
+ React3.useEffect(() => {
496
+ const checkTabCompletion = debounce(() => {
497
+ tabs.forEach((_, tabIndex) => {
498
+ const tabFieldsInTab = getCombinedTabFields(tabIndex);
499
+ if (tabFieldsInTab.length === 0) {
500
+ setCompletedTabs((prev) => {
501
+ const newSet = new Set(prev);
502
+ newSet.delete(tabIndex);
503
+ return newSet;
504
+ });
505
+ return;
506
+ }
507
+ let allFieldsValid = true;
508
+ let hasAnyValue = false;
509
+ for (const field of tabFieldsInTab) {
510
+ const validation = validationRegistry[field];
511
+ const fieldValue = formData[field];
512
+ if (fieldValue !== void 0 && fieldValue !== null && fieldValue !== "") {
513
+ hasAnyValue = true;
514
+ }
515
+ if (validation) {
516
+ try {
517
+ validation.parse(fieldValue);
518
+ } catch (error) {
519
+ if (error instanceof z.ZodError) {
520
+ allFieldsValid = false;
521
+ break;
522
+ }
523
+ }
524
+ }
525
+ }
526
+ if (hasAnyValue && allFieldsValid) {
527
+ setCompletedTabs((prev) => /* @__PURE__ */ new Set([...prev, tabIndex]));
528
+ setValidationErrorTabs((prev) => {
529
+ const newSet = new Set(prev);
530
+ newSet.delete(tabIndex);
531
+ return newSet;
532
+ });
533
+ } else {
534
+ setCompletedTabs((prev) => {
535
+ const newSet = new Set(prev);
536
+ newSet.delete(tabIndex);
537
+ return newSet;
538
+ });
539
+ }
540
+ });
541
+ }, 300);
542
+ checkTabCompletion();
543
+ }, [formData, validationRegistry, getCombinedTabFields, tabs, setCompletedTabs, setValidationErrorTabs]);
544
+ const handleTabChangeWithErrorCheck = useCallback((index) => {
545
+ onTabChange(index);
546
+ }, [onTabChange]);
547
+ const handleNextWithValidation = useCallback(() => {
548
+ const currentTabFields = getCombinedTabFields(activeTab);
549
+ if (currentTabFields.length > 0) {
550
+ const allErrors = {};
551
+ let isValid = true;
552
+ for (const field of currentTabFields) {
553
+ const validation = validationRegistry[field];
554
+ if (validation) {
555
+ try {
556
+ validation.parse(formData[field]);
557
+ } catch (error) {
558
+ if (error instanceof z.ZodError) {
559
+ allErrors[field] = error.issues[0]?.message || `Invalid ${field}`;
560
+ isValid = false;
561
+ }
562
+ }
563
+ }
564
+ }
565
+ if (isValid) {
566
+ setCompletedTabs((prev) => /* @__PURE__ */ new Set([...prev, activeTab]));
567
+ setValidationErrorTabs((prev) => {
568
+ const newSet = new Set(prev);
569
+ newSet.delete(activeTab);
570
+ return newSet;
571
+ });
572
+ onNext();
573
+ } else {
574
+ setCompletedTabs((prev) => {
575
+ const newSet = new Set(prev);
576
+ newSet.delete(activeTab);
577
+ return newSet;
578
+ });
579
+ setValidationErrorTabs((prev) => /* @__PURE__ */ new Set([...prev, activeTab]));
580
+ validateFields(currentTabFields);
581
+ const firstErrorField = currentTabFields.find((field) => allErrors[field]);
582
+ if (firstErrorField) {
583
+ setTimeout(() => {
584
+ const element = document.querySelector(`[data-field="${firstErrorField}"]`);
585
+ if (element) {
586
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
587
+ element.focus();
588
+ }
589
+ }, 50);
590
+ }
591
+ }
592
+ } else {
593
+ onNext();
594
+ }
595
+ }, [activeTab, getCombinedTabFields, validateFields, onNext, setCompletedTabs, setValidationErrorTabs, validationRegistry, formData]);
596
+ const handleSubmitWithValidation = useCallback(async () => {
597
+ const allErrors = {};
598
+ let isValid = true;
599
+ let firstErrorTabIndex = -1;
600
+ for (const [field, validation] of Object.entries(validationRegistry)) {
601
+ if (validation && typeof validation.parse === "function") {
602
+ try {
603
+ validation.parse(formData[field]);
604
+ } catch (error) {
605
+ if (error instanceof z.ZodError) {
606
+ allErrors[field] = error.issues[0]?.message || `Invalid ${field}`;
607
+ isValid = false;
608
+ }
609
+ }
610
+ }
611
+ }
612
+ setErrors(allErrors);
613
+ if (isValid) {
614
+ await submitForm();
615
+ } else {
616
+ setValidationErrorTabs(/* @__PURE__ */ new Set());
617
+ for (let tabIndex = 0; tabIndex < tabs.length; tabIndex++) {
618
+ const tabFieldsInTab = getCombinedTabFields(tabIndex);
619
+ const hasErrorsInTab = tabFieldsInTab.some((field) => allErrors[field]);
620
+ if (hasErrorsInTab) {
621
+ setValidationErrorTabs((prev) => /* @__PURE__ */ new Set([...prev, tabIndex]));
622
+ if (firstErrorTabIndex === -1) {
623
+ firstErrorTabIndex = tabIndex;
624
+ }
625
+ }
626
+ }
627
+ if (firstErrorTabIndex !== -1) {
628
+ onTabChange(firstErrorTabIndex);
629
+ setTimeout(() => {
630
+ const tabFieldsInTab = getCombinedTabFields(firstErrorTabIndex);
631
+ const firstErrorField = tabFieldsInTab.find((field) => allErrors[field]);
632
+ if (firstErrorField) {
633
+ const element = document.querySelector(`[data-field="${firstErrorField}"]`);
634
+ if (element) {
635
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
636
+ element.focus();
637
+ }
638
+ }
639
+ }, 100);
640
+ }
641
+ }
642
+ }, [submitForm, getCombinedTabFields, onTabChange, tabs.length, validationRegistry, formData, setErrors, setValidationErrorTabs]);
643
+ const activeTabValue = tabs[activeTab] || tabs[0] || "";
644
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
645
+ /* @__PURE__ */ jsxs(Tabs, { value: activeTabValue, onValueChange: (value) => {
646
+ const newIndex = tabs.indexOf(value);
647
+ if (newIndex !== -1) {
648
+ handleTabChangeWithErrorCheck(newIndex);
649
+ }
650
+ }, className: "w-full", children: [
651
+ /* @__PURE__ */ jsx(TabsList, { className: `grid w-full mb-8`, style: { gridTemplateColumns: `repeat(${tabs.length}, 1fr)` }, children: tabs.map((tab, index) => /* @__PURE__ */ jsxs(TabsTrigger, { value: tab, className: "flex items-center gap-2", children: [
652
+ config.showTabNumbers && /* @__PURE__ */ jsxs("span", { children: [
653
+ index + 1,
654
+ "."
655
+ ] }),
656
+ tab,
657
+ completedTabs.has(index) && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-green-600" }),
658
+ validationErrorTabs.has(index) && /* @__PURE__ */ jsx(X, { className: "h-4 w-4 text-red-600" })
659
+ ] }, tab)) }),
660
+ React3.Children.map(children, (child, index) => {
661
+ if (React3.isValidElement(child) && child.type === Tab) {
662
+ const tabProps = child.props;
663
+ return /* @__PURE__ */ jsx(TabsContent, { value: tabProps.title, children: /* @__PURE__ */ jsx(TabIndexProvider, { tabIndex: index, children: /* @__PURE__ */ jsx(
664
+ "div",
665
+ {
666
+ ref: (el) => {
667
+ contentRefs.current[index] = el;
668
+ },
669
+ className: "space-y-6",
670
+ style: { minHeight: maxContentHeight > 0 ? `${maxContentHeight}px` : void 0 },
671
+ children: tabProps.children
672
+ }
673
+ ) }) }, index);
674
+ }
675
+ return null;
676
+ }),
677
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: "-9999px", top: "-9999px", visibility: "hidden" }, children: React3.Children.map(children, (child, index) => {
678
+ if (React3.isValidElement(child) && child.type === Tab) {
679
+ const tabProps = child.props;
680
+ return /* @__PURE__ */ jsx(TabIndexProvider, { tabIndex: index, children: /* @__PURE__ */ jsx(
681
+ "div",
682
+ {
683
+ ref: (el) => {
684
+ contentRefs.current[index] = el;
685
+ },
686
+ className: "space-y-6",
687
+ children: tabProps.children
688
+ }
689
+ ) }, `hidden-${index}`);
690
+ }
691
+ return null;
692
+ }) })
693
+ ] }),
694
+ /* @__PURE__ */ jsx(
695
+ NavigationButtonsWrapper,
696
+ {
697
+ activeTab,
698
+ totalTabs: tabs.length,
699
+ onPrevious,
700
+ onNext: handleNextWithValidation,
701
+ onSubmit: handleSubmitWithValidation,
702
+ onSaveDraft: config.allowSaveDraft ? saveDraft : void 0,
703
+ onReset: config.showReset || config.enableLocalStorage ? resetForm : void 0,
704
+ isLoading,
705
+ isDraftSaving,
706
+ config
707
+ }
708
+ ),
709
+ footerChildren.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-4", children: footerChildren }),
710
+ config.showProgressBar && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
711
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm text-muted-foreground mb-2", children: [
712
+ /* @__PURE__ */ jsx("span", { children: "Progress" }),
713
+ /* @__PURE__ */ jsxs("span", { children: [
714
+ Math.round((activeTab + 1) / tabs.length * 100),
715
+ "%"
716
+ ] })
717
+ ] }),
718
+ /* @__PURE__ */ jsx("div", { className: "w-full bg-muted rounded-full h-2", children: /* @__PURE__ */ jsx(
719
+ "div",
720
+ {
721
+ className: "bg-primary h-2 rounded-full transition-all duration-300",
722
+ style: { width: `${(activeTab + 1) / tabs.length * 100}%` }
723
+ }
724
+ ) })
725
+ ] })
726
+ ] });
727
+ };
728
+
729
+ // src/hooks/useFormWrapper.ts
730
+ var useFormWrapper = () => {
731
+ return useSmartForm();
732
+ };
733
+ var useExternalFormRegistration = () => {
734
+ const context = useContext(ExternalFormContext);
735
+ if (!context) {
736
+ throw new Error("useExternalFormRegistration must be used within a MultiTabSmartForm");
737
+ }
738
+ return context;
739
+ };
740
+ var useExternalFormFields = (fields, tabIndex) => {
741
+ const { registerTabFields } = useExternalFormRegistration();
742
+ const stableFields = useMemo(() => fields, [fields.join(",")]);
743
+ useEffect(() => {
744
+ if (stableFields.length > 0) {
745
+ registerTabFields(tabIndex, stableFields);
746
+ }
747
+ }, [stableFields, tabIndex, registerTabFields]);
748
+ };
749
+ var ExternalFieldProvider = ({
750
+ children,
751
+ registerField
752
+ }) => {
753
+ return /* @__PURE__ */ jsx(FieldDetectionContext.Provider, { value: { registerField }, children });
754
+ };
755
+
756
+ // src/useAutoDetectFields.tsx
757
+ var useAutoDetectFields = (tabIndex) => {
758
+ const { registerTabFields } = useExternalFormRegistration();
759
+ const hasRegistered = useRef(false);
760
+ const detectedFields = useRef(/* @__PURE__ */ new Set());
761
+ const registerField = useCallback((fieldName) => {
762
+ detectedFields.current.add(fieldName);
763
+ if (!hasRegistered.current) {
764
+ const fieldsArray = Array.from(detectedFields.current);
765
+ registerTabFields(tabIndex, fieldsArray);
766
+ hasRegistered.current = true;
767
+ }
768
+ }, [tabIndex, registerTabFields]);
769
+ return { registerField, ExternalFieldProvider };
770
+ };
771
+ var useExternalTab = () => {
772
+ const { registerTabFields } = useExternalFormRegistration();
773
+ const tabIndexContext = useTabIndex();
774
+ const detectedFields = useRef(/* @__PURE__ */ new Set());
775
+ const registrationTimeout = useRef(null);
776
+ const hasRegistered = useRef(false);
777
+ const tabIndex = tabIndexContext?.tabIndex ?? 0;
778
+ const registerField = useCallback((fieldName) => {
779
+ detectedFields.current.add(fieldName);
780
+ if (registrationTimeout.current) {
781
+ clearTimeout(registrationTimeout.current);
782
+ }
783
+ registrationTimeout.current = setTimeout(() => {
784
+ if (!hasRegistered.current) {
785
+ const fieldsArray = Array.from(detectedFields.current);
786
+ if (fieldsArray.length > 0) {
787
+ registerTabFields(tabIndex, fieldsArray);
788
+ hasRegistered.current = true;
789
+ }
790
+ }
791
+ }, 200);
792
+ }, [tabIndex, registerTabFields]);
793
+ useEffect(() => {
794
+ const forceRegistrationTimeout = setTimeout(() => {
795
+ if (!hasRegistered.current) {
796
+ const fieldsArray = Array.from(detectedFields.current);
797
+ if (fieldsArray.length > 0) {
798
+ registerTabFields(tabIndex, fieldsArray);
799
+ hasRegistered.current = true;
800
+ }
801
+ }
802
+ }, 1e3);
803
+ return () => {
804
+ clearTimeout(forceRegistrationTimeout);
805
+ if (registrationTimeout.current) {
806
+ clearTimeout(registrationTimeout.current);
807
+ }
808
+ };
809
+ }, [tabIndex, registerTabFields]);
810
+ return { registerField, ExternalFieldProvider };
811
+ };
812
+ var FormFieldGroup = ({
813
+ children,
814
+ className = ""
815
+ }) => {
816
+ return /* @__PURE__ */ jsx("div", { className: `flex flex-wrap gap-2 md:gap-4 mb-4 ${className}`, children });
817
+ };
818
+
819
+ export { BaseSmartForm, DraftSaveButton, ExternalFieldProvider, Footer, FormFieldGroup, FormHeader, LoadingSpinner, MultiTabSmartForm, NavigationButtons, ResetButton, Section, SimpleFormButtons, SmartForm, SubmitButton, Tab, TabIndexProvider, ToastContainerWrapper, useAutoDetectFields, useExternalFormFields, useExternalFormRegistration, useExternalTab, useFormWrapper, useTabIndex };
820
+ //# sourceMappingURL=index.js.map
821
+ //# sourceMappingURL=index.js.map