@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.
@@ -0,0 +1,604 @@
1
+ import { createContext, useState, useRef, useCallback, useContext, useEffect } from 'react';
2
+ import { z } from 'zod';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import * as LabelPrimitive from '@radix-ui/react-label';
7
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
8
+ import { InfoIcon, EyeOffIcon, EyeIcon } from 'lucide-react';
9
+
10
+ var FormContext = createContext(null);
11
+ var SmartFormProvider = ({
12
+ children,
13
+ config,
14
+ initialData = {}
15
+ }) => {
16
+ const getStorageKey = () => {
17
+ return config.storageKey || "smart-form-data";
18
+ };
19
+ const loadFromStorage = () => {
20
+ if (!config.enableLocalStorage) return initialData;
21
+ try {
22
+ const stored = localStorage.getItem(getStorageKey());
23
+ if (stored) {
24
+ const parsedStored = JSON.parse(stored);
25
+ const merged = { ...initialData };
26
+ Object.keys(parsedStored).forEach((key) => {
27
+ const storedValue = parsedStored[key];
28
+ if (storedValue !== null && storedValue !== void 0 && storedValue !== "") {
29
+ merged[key] = storedValue;
30
+ }
31
+ });
32
+ return merged;
33
+ }
34
+ return initialData;
35
+ } catch (error) {
36
+ console.warn("Failed to load form data from localStorage:", error);
37
+ return initialData;
38
+ }
39
+ };
40
+ const saveToStorage = (data) => {
41
+ if (!config.enableLocalStorage) return;
42
+ try {
43
+ localStorage.setItem(getStorageKey(), JSON.stringify(data));
44
+ } catch (error) {
45
+ console.warn("Failed to save form data to localStorage:", error);
46
+ }
47
+ };
48
+ const clearStorage = () => {
49
+ if (!config.enableLocalStorage) return;
50
+ try {
51
+ localStorage.removeItem(getStorageKey());
52
+ } catch (error) {
53
+ console.warn("Failed to clear form data from localStorage:", error);
54
+ }
55
+ };
56
+ const [formData, setFormData] = useState(loadFromStorage);
57
+ const [errors, setErrors] = useState({});
58
+ const [isLoading, setIsLoading] = useState(false);
59
+ const [isDraftSaving, setIsDraftSaving] = useState(false);
60
+ const [validationRegistry, setValidationRegistry] = useState({});
61
+ const fieldRefs = useRef({});
62
+ const submitHooksRef = useRef(/* @__PURE__ */ new Map());
63
+ const updateField = useCallback((field, value) => {
64
+ setFormData((prev) => {
65
+ const newData = {
66
+ ...prev,
67
+ [field]: value
68
+ };
69
+ setTimeout(() => saveToStorage(newData), 0);
70
+ return newData;
71
+ });
72
+ if (errors[field]) {
73
+ setErrors((prev) => {
74
+ const newErrors = { ...prev };
75
+ delete newErrors[field];
76
+ return newErrors;
77
+ });
78
+ }
79
+ }, [errors, config.enableLocalStorage, saveToStorage]);
80
+ const validateField = useCallback((field, value) => {
81
+ const validation = validationRegistry[field];
82
+ if (!validation) return true;
83
+ try {
84
+ validation.parse(value);
85
+ return true;
86
+ } catch (error) {
87
+ if (error instanceof z.ZodError) {
88
+ setErrors((prev) => ({
89
+ ...prev,
90
+ [field]: error.issues[0]?.message || `Invalid ${field}`
91
+ }));
92
+ return false;
93
+ }
94
+ return false;
95
+ }
96
+ }, [validationRegistry]);
97
+ const registerValidation = useCallback((field, validation) => {
98
+ setValidationRegistry((prev) => ({
99
+ ...prev,
100
+ [field]: validation
101
+ }));
102
+ }, []);
103
+ const validateAllFields = useCallback(() => {
104
+ const allErrors = {};
105
+ let isValid = true;
106
+ for (const [field, validation] of Object.entries(validationRegistry)) {
107
+ if (validation && typeof validation.parse === "function") {
108
+ try {
109
+ validation.parse(formData[field]);
110
+ } catch (error) {
111
+ if (error instanceof z.ZodError) {
112
+ allErrors[field] = error.issues[0]?.message || `Invalid ${field}`;
113
+ isValid = false;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ setErrors(allErrors);
119
+ return isValid;
120
+ }, [formData, validationRegistry]);
121
+ const validateFields = useCallback((fields) => {
122
+ const allErrors = {};
123
+ let isValid = true;
124
+ for (const field of fields) {
125
+ const validation = validationRegistry[field];
126
+ if (validation) {
127
+ try {
128
+ validation.parse(formData[field]);
129
+ } catch (error) {
130
+ if (error instanceof z.ZodError) {
131
+ allErrors[field] = error.issues[0]?.message || `Invalid ${field}`;
132
+ isValid = false;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ setErrors((prev) => ({
138
+ ...prev,
139
+ ...allErrors
140
+ }));
141
+ return isValid;
142
+ }, [formData, validationRegistry]);
143
+ const submitForm = useCallback(async () => {
144
+ if (!validateAllFields()) {
145
+ return;
146
+ }
147
+ if (!config.api) {
148
+ console.warn("No API endpoint provided");
149
+ return;
150
+ }
151
+ if (config.logFormData) {
152
+ console.log("\u{1F4E4} Submitting form data:", formData);
153
+ console.log("\u{1F3AF} API endpoint:", config.api);
154
+ }
155
+ const toJsonSafe = (val) => {
156
+ if (val instanceof File) return val.name;
157
+ if (val instanceof Date) return val.toISOString();
158
+ if (Array.isArray(val)) {
159
+ return val.map((item) => item instanceof File ? item.name : toJsonSafe(item));
160
+ }
161
+ if (val && typeof val === "object") {
162
+ const out = {};
163
+ for (const [k, v] of Object.entries(val)) out[k] = toJsonSafe(v);
164
+ return out;
165
+ }
166
+ return val;
167
+ };
168
+ let jsonPayload = toJsonSafe(formData);
169
+ if (config.transformData) {
170
+ jsonPayload = config.transformData(jsonPayload);
171
+ }
172
+ setIsLoading(true);
173
+ try {
174
+ const headers = {
175
+ "Content-Type": "application/json"
176
+ };
177
+ if (config.authentication?.enable) {
178
+ const { accessTokenKey = "accessToken" } = config.authentication;
179
+ const token = localStorage.getItem(accessTokenKey);
180
+ if (token) {
181
+ headers.Authorization = `Bearer ${token}`;
182
+ }
183
+ }
184
+ const fetchOptions = {
185
+ method: config.method || "POST",
186
+ headers,
187
+ body: JSON.stringify(jsonPayload)
188
+ };
189
+ if (config.authentication?.enable) {
190
+ fetchOptions.credentials = "include";
191
+ }
192
+ let response = await fetch(config.api, fetchOptions);
193
+ if (response.status === 403 && config.authentication?.enable && config.authentication.refreshTokenEndpoint) {
194
+ const newAccessToken = await refreshAccessToken(config.authentication);
195
+ if (newAccessToken) {
196
+ headers.Authorization = `Bearer ${newAccessToken}`;
197
+ response = await fetch(config.api, {
198
+ ...fetchOptions,
199
+ headers
200
+ });
201
+ }
202
+ }
203
+ if (!response.ok) {
204
+ const errorResponse = await response.json().catch(() => ({ message: "Request failed" }));
205
+ console.log("Error response:", errorResponse);
206
+ throw new Error(JSON.stringify(errorResponse));
207
+ }
208
+ const result = await response.json();
209
+ if (config.onSuccess) {
210
+ config.onSuccess(result);
211
+ }
212
+ for (const hook of submitHooksRef.current.values()) {
213
+ try {
214
+ await hook();
215
+ } catch (hookError) {
216
+ console.error("Submit hook error:", hookError);
217
+ }
218
+ }
219
+ } catch (error) {
220
+ if (config.onError) {
221
+ let errorData;
222
+ try {
223
+ errorData = JSON.parse(error.message);
224
+ } catch {
225
+ let userFriendlyMessage = "An error occurred";
226
+ if (error.message === "Failed to fetch" || error.message?.includes("Failed to fetch")) {
227
+ userFriendlyMessage = "This service is temporarily unavailable. Please try again after some time.";
228
+ } else if (error.message?.includes("Network Error")) {
229
+ userFriendlyMessage = "Network error occurred. Please check your connection and try again.";
230
+ } else if (error.message?.includes("timeout")) {
231
+ userFriendlyMessage = "Request timed out. Please try again.";
232
+ } else if (error.message?.includes("CORS")) {
233
+ userFriendlyMessage = "Connection error. Please contact support if this persists.";
234
+ } else if (error.message && error.message !== "An error occurred") {
235
+ userFriendlyMessage = error.message;
236
+ }
237
+ errorData = {
238
+ message: userFriendlyMessage,
239
+ details: []
240
+ };
241
+ }
242
+ config.onError(errorData);
243
+ }
244
+ console.error("Form submission error:", error);
245
+ } finally {
246
+ setIsLoading(false);
247
+ }
248
+ }, [formData, config, validateAllFields]);
249
+ const validateFormAndGetResult = useCallback(() => {
250
+ return validateAllFields();
251
+ }, [validateAllFields]);
252
+ const refreshAccessToken = useCallback(async (authConfig) => {
253
+ const { refreshTokenEndpoint, accessTokenKey = "accessToken", refreshTokenKey = "refreshToken" } = authConfig;
254
+ if (!refreshTokenEndpoint) {
255
+ throw new Error("Refresh token endpoint not provided");
256
+ }
257
+ try {
258
+ const refreshToken = localStorage.getItem(refreshTokenKey);
259
+ if (!refreshToken) {
260
+ throw new Error("No refresh token available");
261
+ }
262
+ const response = await fetch(refreshTokenEndpoint, {
263
+ method: "POST",
264
+ headers: {
265
+ "Content-Type": "application/json"
266
+ },
267
+ body: JSON.stringify({ refreshToken })
268
+ });
269
+ if (!response.ok) {
270
+ throw new Error(`Token refresh failed: ${response.status}`);
271
+ }
272
+ const result = await response.json();
273
+ const newAccessToken = result.data?.accessToken || result.accessToken;
274
+ if (newAccessToken) {
275
+ localStorage.setItem(accessTokenKey, newAccessToken);
276
+ return newAccessToken;
277
+ }
278
+ throw new Error("No access token in refresh response");
279
+ } catch (error) {
280
+ console.error("Token refresh failed:", error);
281
+ localStorage.removeItem(accessTokenKey);
282
+ localStorage.removeItem(refreshTokenKey);
283
+ return null;
284
+ }
285
+ }, []);
286
+ const saveDraft = useCallback(async () => {
287
+ if (!config.saveDraftApi) {
288
+ console.warn("No save draft API endpoint provided");
289
+ return;
290
+ }
291
+ if (config.logFormData) {
292
+ console.log("\u{1F4BE} Saving draft data:", formData);
293
+ console.log("\u{1F3AF} Draft API endpoint:", config.saveDraftApi);
294
+ }
295
+ setIsDraftSaving(true);
296
+ try {
297
+ const headers = {
298
+ "Content-Type": "application/json"
299
+ };
300
+ if (config.authentication?.enable) {
301
+ const { accessTokenKey = "accessToken" } = config.authentication;
302
+ const token = localStorage.getItem(accessTokenKey);
303
+ if (token) {
304
+ headers.Authorization = `Bearer ${token}`;
305
+ }
306
+ }
307
+ const fetchOptions = {
308
+ method: "POST",
309
+ headers,
310
+ body: JSON.stringify(formData)
311
+ };
312
+ if (config.authentication?.enable) {
313
+ fetchOptions.credentials = "include";
314
+ }
315
+ let response = await fetch(config.saveDraftApi, fetchOptions);
316
+ if (response.status === 403 && config.authentication?.enable && config.authentication.refreshTokenEndpoint) {
317
+ const newAccessToken = await refreshAccessToken(config.authentication);
318
+ if (newAccessToken) {
319
+ headers.Authorization = `Bearer ${newAccessToken}`;
320
+ response = await fetch(config.saveDraftApi, {
321
+ ...fetchOptions,
322
+ headers
323
+ });
324
+ }
325
+ }
326
+ if (!response.ok) {
327
+ throw new Error(`HTTP error! status: ${response.status}`);
328
+ }
329
+ const result = await response.json();
330
+ console.log("Draft saved successfully:", result);
331
+ } catch (error) {
332
+ console.error("Draft save error:", error);
333
+ } finally {
334
+ setIsDraftSaving(false);
335
+ }
336
+ }, [formData, config]);
337
+ const resetForm = useCallback(() => {
338
+ setFormData(initialData);
339
+ setErrors({});
340
+ clearStorage();
341
+ }, [initialData, config.enableLocalStorage, clearStorage]);
342
+ const contextValue = {
343
+ formData,
344
+ errors,
345
+ isLoading,
346
+ isDraftSaving,
347
+ updateField,
348
+ validateField,
349
+ validateFields,
350
+ submitForm,
351
+ validateFormAndGetResult,
352
+ saveDraft,
353
+ resetForm,
354
+ fieldRefs,
355
+ registerValidation,
356
+ validationRegistry,
357
+ config,
358
+ setErrors,
359
+ registerSubmitHook: (key, hook) => {
360
+ submitHooksRef.current.set(key, hook);
361
+ },
362
+ unregisterSubmitHook: (key) => {
363
+ submitHooksRef.current.delete(key);
364
+ }
365
+ };
366
+ return /* @__PURE__ */ jsx(FormContext.Provider, { value: contextValue, children });
367
+ };
368
+ var useSmartForm = () => {
369
+ const context = useContext(FormContext);
370
+ if (!context) {
371
+ throw new Error("useSmartForm must be used within a SmartFormProvider");
372
+ }
373
+ return context;
374
+ };
375
+ var useFormField = (field) => {
376
+ const { formData, errors, updateField, fieldRefs, registerValidation } = useSmartForm();
377
+ return {
378
+ value: formData[field],
379
+ error: errors[field],
380
+ onChange: (value) => updateField(field, value),
381
+ fieldRef: (el) => fieldRefs.current[field] = el,
382
+ registerValidation
383
+ };
384
+ };
385
+ var FieldDetectionContext = createContext(null);
386
+ var useFieldDetection = () => {
387
+ const context = useContext(FieldDetectionContext);
388
+ return context;
389
+ };
390
+ function cn(...inputs) {
391
+ return twMerge(clsx(inputs));
392
+ }
393
+ function Input({ className, type, ...props }) {
394
+ return /* @__PURE__ */ jsx(
395
+ "input",
396
+ {
397
+ type,
398
+ "data-slot": "input",
399
+ className: cn(
400
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
401
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
402
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
403
+ className
404
+ ),
405
+ ...props
406
+ }
407
+ );
408
+ }
409
+ function Textarea({ className, ...props }) {
410
+ return /* @__PURE__ */ jsx(
411
+ "textarea",
412
+ {
413
+ "data-slot": "textarea",
414
+ className: cn(
415
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
416
+ className
417
+ ),
418
+ ...props
419
+ }
420
+ );
421
+ }
422
+ function Label({
423
+ className,
424
+ ...props
425
+ }) {
426
+ return /* @__PURE__ */ jsx(
427
+ LabelPrimitive.Root,
428
+ {
429
+ "data-slot": "label",
430
+ className: cn(
431
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
432
+ className
433
+ ),
434
+ ...props
435
+ }
436
+ );
437
+ }
438
+ function TooltipProvider({
439
+ delayDuration = 0,
440
+ ...props
441
+ }) {
442
+ return /* @__PURE__ */ jsx(
443
+ TooltipPrimitive.Provider,
444
+ {
445
+ "data-slot": "tooltip-provider",
446
+ delayDuration,
447
+ ...props
448
+ }
449
+ );
450
+ }
451
+ function Tooltip({
452
+ ...props
453
+ }) {
454
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) });
455
+ }
456
+ function TooltipTrigger({
457
+ ...props
458
+ }) {
459
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
460
+ }
461
+ function TooltipContent({
462
+ className,
463
+ sideOffset = 0,
464
+ children,
465
+ ...props
466
+ }) {
467
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
468
+ TooltipPrimitive.Content,
469
+ {
470
+ "data-slot": "tooltip-content",
471
+ sideOffset,
472
+ className: cn(
473
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
474
+ className
475
+ ),
476
+ ...props,
477
+ children: [
478
+ children,
479
+ /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
480
+ ]
481
+ }
482
+ ) });
483
+ }
484
+ var SmartInput = ({
485
+ field,
486
+ label,
487
+ type = "text",
488
+ placeholder,
489
+ validation,
490
+ className = "",
491
+ required = false,
492
+ defaultValue,
493
+ info,
494
+ subLabel
495
+ }) => {
496
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
497
+ const fieldDetection = useFieldDetection();
498
+ const hasRegistered = useRef(false);
499
+ const hasSetDefault = useRef(false);
500
+ const [showPassword, setShowPassword] = useState(false);
501
+ const togglePasswordVisibility = () => {
502
+ setShowPassword(!showPassword);
503
+ };
504
+ useEffect(() => {
505
+ if (validation && !hasRegistered.current) {
506
+ hasRegistered.current = true;
507
+ registerValidation(field, validation);
508
+ }
509
+ }, [validation, field, registerValidation]);
510
+ useEffect(() => {
511
+ if (fieldDetection?.registerField) {
512
+ fieldDetection.registerField(field);
513
+ }
514
+ }, [field, fieldDetection]);
515
+ useEffect(() => {
516
+ if (defaultValue !== void 0 && !hasSetDefault.current && (value === void 0 || value === null || value === "")) {
517
+ onChange(defaultValue);
518
+ hasSetDefault.current = true;
519
+ }
520
+ }, [defaultValue, value, onChange]);
521
+ const getPlaceholder = () => {
522
+ if (placeholder) return placeholder;
523
+ if (!label) return `Enter ${field}`;
524
+ if (type === "email") return `Enter ${label.toLowerCase()}`;
525
+ if (type === "tel") return `Enter ${label.toLowerCase()}`;
526
+ return `Enter ${label.toLowerCase()}`;
527
+ };
528
+ const renderInput = () => {
529
+ switch (type) {
530
+ case "textarea":
531
+ return /* @__PURE__ */ jsx(
532
+ Textarea,
533
+ {
534
+ ref: fieldRef,
535
+ value: value || "",
536
+ onChange: (e) => onChange(e.target.value),
537
+ className: `w-full ${error ? "border-destructive" : ""} ${className}`,
538
+ placeholder: getPlaceholder(),
539
+ rows: 4,
540
+ "data-field": field
541
+ }
542
+ );
543
+ case "password":
544
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
545
+ /* @__PURE__ */ jsx(
546
+ Input,
547
+ {
548
+ ref: fieldRef,
549
+ type: showPassword ? "text" : "password",
550
+ value: value || "",
551
+ onChange: (e) => onChange(e.target.value),
552
+ className: `w-full pr-10 ${error ? "border-destructive" : ""} ${className}`,
553
+ placeholder: getPlaceholder(),
554
+ "data-field": field
555
+ }
556
+ ),
557
+ /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ onClick: togglePasswordVisibility,
562
+ className: "absolute right-3 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-100 rounded transition-colors",
563
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "h-4 w-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "h-4 w-4 text-muted-foreground" })
564
+ }
565
+ )
566
+ ] });
567
+ default:
568
+ return /* @__PURE__ */ jsx(
569
+ Input,
570
+ {
571
+ ref: fieldRef,
572
+ type,
573
+ value: value || "",
574
+ onChange: (e) => onChange(e.target.value),
575
+ className: `w-full ${error ? "border-destructive" : ""} ${className}`,
576
+ placeholder: getPlaceholder(),
577
+ "data-field": field
578
+ }
579
+ );
580
+ }
581
+ };
582
+ return /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${className}`, children: [
583
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-1", children: [
584
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
585
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
586
+ label,
587
+ " ",
588
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
589
+ ] }),
590
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
591
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
592
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
593
+ ] }) })
594
+ ] }),
595
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
596
+ ] }),
597
+ renderInput(),
598
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
599
+ ] });
600
+ };
601
+
602
+ export { FieldDetectionContext, Input, Label, SmartFormProvider, SmartInput, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, cn, useFieldDetection, useFormField, useSmartForm };
603
+ //# sourceMappingURL=chunk-IG4XDQMV.js.map
604
+ //# sourceMappingURL=chunk-IG4XDQMV.js.map