@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/fields.js ADDED
@@ -0,0 +1,1384 @@
1
+ import { Button, Popover, PopoverTrigger, PopoverContent } from './chunk-6WAEAWTD.js';
2
+ export { SmartCheckbox, SmartDatePicker, SmartRadioGroup, SmartSelect, SmartTags } from './chunk-6WAEAWTD.js';
3
+ import { useFormField, useFieldDetection, Label, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, useSmartForm, cn, Input } from './chunk-IG4XDQMV.js';
4
+ export { SmartFormProvider, SmartInput, useFormField, useSmartForm } from './chunk-IG4XDQMV.js';
5
+ import * as React from 'react';
6
+ import React__default, { createContext, useRef, useEffect, useMemo, useState, useCallback, useContext } from 'react';
7
+ import { InfoIcon, PlusIcon, CheckIcon, Bold, Italic, Underline, List, ListOrdered, ChevronsUpDown, Check, Plus, XIcon, SearchIcon } from 'lucide-react';
8
+ import { Command as Command$1 } from 'cmdk';
9
+ import { jsxs, jsx } from 'react/jsx-runtime';
10
+ import { Slot } from '@radix-ui/react-slot';
11
+ import { cva } from 'class-variance-authority';
12
+ import * as SliderPrimitive from '@radix-ui/react-slider';
13
+
14
+ function Command({
15
+ className,
16
+ ...props
17
+ }) {
18
+ return /* @__PURE__ */ jsx(
19
+ Command$1,
20
+ {
21
+ "data-slot": "command",
22
+ className: cn(
23
+ "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
24
+ className
25
+ ),
26
+ ...props
27
+ }
28
+ );
29
+ }
30
+ function CommandInput({
31
+ className,
32
+ ...props
33
+ }) {
34
+ return /* @__PURE__ */ jsxs(
35
+ "div",
36
+ {
37
+ "data-slot": "command-input-wrapper",
38
+ className: "flex h-9 items-center gap-2 border-b px-3",
39
+ children: [
40
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
41
+ /* @__PURE__ */ jsx(
42
+ Command$1.Input,
43
+ {
44
+ "data-slot": "command-input",
45
+ className: cn(
46
+ "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
47
+ className
48
+ ),
49
+ ...props
50
+ }
51
+ )
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ function CommandList({
57
+ className,
58
+ ...props
59
+ }) {
60
+ return /* @__PURE__ */ jsx(
61
+ Command$1.List,
62
+ {
63
+ "data-slot": "command-list",
64
+ className: cn(
65
+ "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
66
+ className
67
+ ),
68
+ ...props
69
+ }
70
+ );
71
+ }
72
+ function CommandEmpty({
73
+ ...props
74
+ }) {
75
+ return /* @__PURE__ */ jsx(
76
+ Command$1.Empty,
77
+ {
78
+ "data-slot": "command-empty",
79
+ className: "py-6 text-center text-sm",
80
+ ...props
81
+ }
82
+ );
83
+ }
84
+ function CommandGroup({
85
+ className,
86
+ ...props
87
+ }) {
88
+ return /* @__PURE__ */ jsx(
89
+ Command$1.Group,
90
+ {
91
+ "data-slot": "command-group",
92
+ className: cn(
93
+ "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
94
+ className
95
+ ),
96
+ ...props
97
+ }
98
+ );
99
+ }
100
+ function CommandItem({
101
+ className,
102
+ ...props
103
+ }) {
104
+ return /* @__PURE__ */ jsx(
105
+ Command$1.Item,
106
+ {
107
+ "data-slot": "command-item",
108
+ className: cn(
109
+ "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
110
+ className
111
+ ),
112
+ ...props
113
+ }
114
+ );
115
+ }
116
+ var badgeVariants = cva(
117
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
118
+ {
119
+ variants: {
120
+ variant: {
121
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
122
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
123
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
124
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
125
+ }
126
+ },
127
+ defaultVariants: {
128
+ variant: "default"
129
+ }
130
+ }
131
+ );
132
+ function Badge({
133
+ className,
134
+ variant,
135
+ asChild = false,
136
+ ...props
137
+ }) {
138
+ const Comp = asChild ? Slot : "span";
139
+ return /* @__PURE__ */ jsx(
140
+ Comp,
141
+ {
142
+ "data-slot": "badge",
143
+ className: cn(badgeVariants({ variant }), className),
144
+ ...props
145
+ }
146
+ );
147
+ }
148
+ function Combobox({
149
+ options,
150
+ value = "",
151
+ onChange,
152
+ placeholder = "Select option...",
153
+ searchPlaceholder = "Search...",
154
+ noResultsText = "No results found.",
155
+ width = "100%",
156
+ isDisabled = false,
157
+ allowCustom = false
158
+ }) {
159
+ const [open, setOpen] = React__default.useState(false);
160
+ const [searchValue, setSearchValue] = useState("");
161
+ const containerStyles = {
162
+ width
163
+ };
164
+ const combinedOptions = allowCustom ? [
165
+ ...options,
166
+ ...searchValue && !options.some(
167
+ (opt) => opt.label.toLowerCase() === searchValue.toLowerCase()
168
+ ) ? [
169
+ {
170
+ value: searchValue.toLowerCase().replace(/\s+/g, "-"),
171
+ label: searchValue
172
+ }
173
+ ] : []
174
+ ] : options;
175
+ const filteredOptions = combinedOptions.filter(
176
+ (option) => option.label.toLowerCase().includes(searchValue.toLowerCase())
177
+ );
178
+ return /* @__PURE__ */ jsx("div", { style: containerStyles, children: /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
179
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
180
+ Button,
181
+ {
182
+ variant: "outline",
183
+ role: "combobox",
184
+ "aria-expanded": open,
185
+ style: containerStyles,
186
+ className: `flex items-center justify-between relative ${isDisabled ? "cursor-not-allowed opacity-50" : ""}`,
187
+ disabled: isDisabled,
188
+ children: [
189
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0 text-left", children: /* @__PURE__ */ jsx("div", { className: "truncate", children: value ? combinedOptions.find((option) => option.value === value)?.label || value : placeholder }) }),
190
+ /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-2 h-4 w-4 flex-shrink-0 opacity-50" })
191
+ ]
192
+ }
193
+ ) }),
194
+ /* @__PURE__ */ jsx(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", children: /* @__PURE__ */ jsxs(Command, { className: "w-full", children: [
195
+ /* @__PURE__ */ jsx(
196
+ CommandInput,
197
+ {
198
+ value: searchValue,
199
+ onValueChange: setSearchValue,
200
+ placeholder: searchPlaceholder,
201
+ className: "h-9"
202
+ }
203
+ ),
204
+ /* @__PURE__ */ jsx(CommandList, { children: filteredOptions.length > 0 ? /* @__PURE__ */ jsx(CommandGroup, { className: "max-h-[200px] overflow-y-auto", children: filteredOptions.map((option) => /* @__PURE__ */ jsxs(
205
+ CommandItem,
206
+ {
207
+ value: option.value,
208
+ onSelect: (currentValue) => {
209
+ onChange(currentValue === value ? "" : currentValue);
210
+ setOpen(false);
211
+ setSearchValue("");
212
+ },
213
+ className: "flex items-center",
214
+ style: { width: "100%" },
215
+ children: [
216
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full min-w-0", children: [
217
+ /* @__PURE__ */ jsx(
218
+ Check,
219
+ {
220
+ className: `mr-2 h-4 w-4 flex-shrink-0 ${value === option.value ? "opacity-100" : "opacity-0"}`
221
+ }
222
+ ),
223
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: option.label })
224
+ ] }),
225
+ option.badge && /* @__PURE__ */ jsx(
226
+ Badge,
227
+ {
228
+ variant: "outline",
229
+ className: "ml-2 text-[10px] px-2 py-0 h-5 font-normal",
230
+ children: option.badge
231
+ }
232
+ )
233
+ ]
234
+ },
235
+ option.value
236
+ )) }) : /* @__PURE__ */ jsxs(CommandEmpty, { children: [
237
+ allowCustom && searchValue && /* @__PURE__ */ jsxs(
238
+ Button,
239
+ {
240
+ variant: "ghost",
241
+ className: "w-full justify-start",
242
+ onClick: () => {
243
+ const newOption = {
244
+ value: searchValue.toLowerCase().replace(/\s+/g, "-")};
245
+ onChange(newOption.value);
246
+ setOpen(false);
247
+ setSearchValue("");
248
+ },
249
+ children: [
250
+ /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
251
+ 'Add "',
252
+ searchValue,
253
+ '" as a new option'
254
+ ]
255
+ }
256
+ ),
257
+ !searchValue && noResultsText
258
+ ] }) })
259
+ ] }) })
260
+ ] }) });
261
+ }
262
+ var SmartCombobox = ({
263
+ field,
264
+ label,
265
+ options,
266
+ className = "",
267
+ placeholder,
268
+ allowCustom = false,
269
+ validation,
270
+ required = false,
271
+ defaultValue,
272
+ info,
273
+ subLabel
274
+ }) => {
275
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
276
+ const fieldDetection = useFieldDetection();
277
+ const hasRegistered = useRef(false);
278
+ const hasSetDefault = useRef(false);
279
+ useEffect(() => {
280
+ if (validation && !hasRegistered.current) {
281
+ hasRegistered.current = true;
282
+ registerValidation(field, validation);
283
+ }
284
+ }, [validation, field, registerValidation]);
285
+ useEffect(() => {
286
+ if (fieldDetection?.registerField) {
287
+ fieldDetection.registerField(field);
288
+ }
289
+ }, [field, fieldDetection]);
290
+ useEffect(() => {
291
+ if (defaultValue !== void 0 && !hasSetDefault.current && (value === void 0 || value === null || value === "")) {
292
+ onChange(defaultValue);
293
+ hasSetDefault.current = true;
294
+ }
295
+ }, [defaultValue, value, onChange]);
296
+ return /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${className}`, children: [
297
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
298
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
299
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
300
+ label,
301
+ " ",
302
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
303
+ ] }),
304
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
305
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
306
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
307
+ ] }) })
308
+ ] }),
309
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
310
+ ] }),
311
+ /* @__PURE__ */ jsx("div", { ref: fieldRef, "data-field": field, children: /* @__PURE__ */ jsx(
312
+ Combobox,
313
+ {
314
+ options: (options || []).map((o) => ({ value: o.value, label: o.label })),
315
+ value: value || "",
316
+ onChange: (newValue) => onChange(newValue),
317
+ placeholder: placeholder || (label ? `Select ${label.toLowerCase()}` : `Select ${field}`),
318
+ allowCustom
319
+ }
320
+ ) }),
321
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
322
+ ] });
323
+ };
324
+ var SmartFileUpload = ({
325
+ field,
326
+ label,
327
+ className = "",
328
+ validation,
329
+ required = false,
330
+ multiple = false,
331
+ accept,
332
+ maxFiles,
333
+ maxSizeBytes,
334
+ uploadApi,
335
+ uploadOnSelect = false,
336
+ onSuccess,
337
+ onError,
338
+ defaultValue,
339
+ info,
340
+ subLabel
341
+ }) => {
342
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
343
+ const { registerSubmitHook, unregisterSubmitHook } = useSmartForm();
344
+ const fieldDetection = useFieldDetection();
345
+ const hasRegistered = useRef(false);
346
+ const hasSetDefault = useRef(false);
347
+ const inputRef = useRef(null);
348
+ useEffect(() => {
349
+ if (validation && !hasRegistered.current) {
350
+ hasRegistered.current = true;
351
+ registerValidation(field, validation);
352
+ }
353
+ }, [validation, field, registerValidation]);
354
+ useEffect(() => {
355
+ if (fieldDetection?.registerField) {
356
+ fieldDetection.registerField(field);
357
+ }
358
+ }, [field, fieldDetection]);
359
+ useEffect(() => {
360
+ if (defaultValue !== void 0 && !hasSetDefault.current && (value === void 0 || value === null || value === "")) {
361
+ onChange(defaultValue);
362
+ hasSetDefault.current = true;
363
+ }
364
+ }, [defaultValue, value, onChange]);
365
+ const normalizedFiles = useMemo(() => {
366
+ const filesAny = Array.isArray(value) ? value : value ? [value] : [];
367
+ return filesAny.filter((f) => f instanceof File);
368
+ }, [value]);
369
+ const [previewUrls, setPreviewUrls] = useState(/* @__PURE__ */ new Map());
370
+ useEffect(() => {
371
+ const newUrls = /* @__PURE__ */ new Map();
372
+ normalizedFiles.forEach((file) => {
373
+ if (file.type.startsWith("image/")) {
374
+ const key = `${file.name}-${file.size}-${file.lastModified}`;
375
+ if (!previewUrls.has(key)) {
376
+ newUrls.set(key, URL.createObjectURL(file));
377
+ } else {
378
+ newUrls.set(key, previewUrls.get(key));
379
+ }
380
+ }
381
+ });
382
+ previewUrls.forEach((url, key) => {
383
+ if (!newUrls.has(key)) {
384
+ URL.revokeObjectURL(url);
385
+ }
386
+ });
387
+ setPreviewUrls(newUrls);
388
+ }, [normalizedFiles]);
389
+ useEffect(() => {
390
+ return () => {
391
+ previewUrls.forEach((url) => URL.revokeObjectURL(url));
392
+ };
393
+ }, []);
394
+ const removeFileAt = (index) => {
395
+ if (!multiple) {
396
+ onChange(null);
397
+ if (inputRef.current) inputRef.current.value = "";
398
+ return;
399
+ }
400
+ const next = normalizedFiles.filter((_, i) => i !== index);
401
+ onChange(next);
402
+ if (next.length === 0 && inputRef.current) inputRef.current.value = "";
403
+ };
404
+ const handleFiles = (filesList) => {
405
+ if (!filesList) return;
406
+ let files = Array.from(filesList);
407
+ if (maxFiles && files.length > maxFiles) {
408
+ files = files.slice(0, maxFiles);
409
+ }
410
+ if (maxSizeBytes) {
411
+ files = files.filter((f) => f.size <= maxSizeBytes);
412
+ }
413
+ if (multiple) {
414
+ onChange(files);
415
+ } else {
416
+ onChange(files[0] || null);
417
+ }
418
+ };
419
+ const onInputChange = (e) => {
420
+ handleFiles(e.target.files);
421
+ };
422
+ const clearSelection = () => {
423
+ onChange(multiple ? [] : null);
424
+ if (inputRef.current) {
425
+ inputRef.current.value = "";
426
+ }
427
+ };
428
+ const performUpload = async () => {
429
+ if (!uploadApi) return;
430
+ const filesAny = Array.isArray(value) ? value : value ? [value] : [];
431
+ const files = filesAny.filter((f) => f instanceof File);
432
+ if (files.length === 0) return;
433
+ const fd = new FormData();
434
+ if (multiple) {
435
+ for (const f of files) fd.append(`${field}[]`, f);
436
+ } else if (files[0]) {
437
+ fd.append(field, files[0]);
438
+ }
439
+ try {
440
+ const response = await fetch(uploadApi, {
441
+ method: "POST",
442
+ body: fd
443
+ });
444
+ const maybeJson = (() => {
445
+ try {
446
+ return response.headers.get("content-type")?.includes("application/json") ? true : false;
447
+ } catch {
448
+ return false;
449
+ }
450
+ })();
451
+ const result = maybeJson ? await response.json() : await response.text();
452
+ if (!response.ok) {
453
+ if (onError) onError(result);
454
+ return;
455
+ }
456
+ if (onSuccess) onSuccess(result);
457
+ } catch (err) {
458
+ if (onError) onError(err);
459
+ }
460
+ };
461
+ useEffect(() => {
462
+ if (!uploadOnSelect || !uploadApi) return;
463
+ const filesAny = Array.isArray(value) ? value : value ? [value] : [];
464
+ const files = filesAny.filter((f) => f instanceof File);
465
+ if (files.length > 0) {
466
+ void performUpload();
467
+ }
468
+ }, [value, uploadOnSelect, uploadApi]);
469
+ useEffect(() => {
470
+ const key = `smart-file-upload:${field}`;
471
+ if (uploadApi && !uploadOnSelect) {
472
+ registerSubmitHook(key, performUpload);
473
+ return () => unregisterSubmitHook(key);
474
+ }
475
+ return () => unregisterSubmitHook(key);
476
+ }, [field, uploadApi, uploadOnSelect, registerSubmitHook, unregisterSubmitHook, value, multiple]);
477
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex-1 min-w-0", className), children: [
478
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
479
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
480
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
481
+ label,
482
+ " ",
483
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
484
+ ] }),
485
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
486
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
487
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
488
+ ] }) })
489
+ ] }),
490
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
491
+ ] }),
492
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", error && "text-destructive"), children: [
493
+ /* @__PURE__ */ jsx(
494
+ Input,
495
+ {
496
+ ref: (el) => {
497
+ inputRef.current = el;
498
+ if (typeof fieldRef === "function") fieldRef(el);
499
+ },
500
+ type: "file",
501
+ accept,
502
+ multiple,
503
+ onChange: onInputChange,
504
+ className: cn(error && "border-destructive"),
505
+ "data-field": field
506
+ }
507
+ ),
508
+ (Array.isArray(value) ? value.length > 0 : !!value) && /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: clearSelection, children: "Clear" })
509
+ ] }),
510
+ normalizedFiles.length === 0 ? /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No file selected" }) : /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1", children: normalizedFiles.map((f, idx) => {
511
+ const isImage = f.type.startsWith("image/");
512
+ const key = `${f.name}-${f.size}-${f.lastModified}`;
513
+ const previewUrl = isImage ? previewUrls.get(key) : null;
514
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between rounded border px-2 py-1", children: [
515
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center min-w-0 mr-2", children: [
516
+ isImage && previewUrl ? /* @__PURE__ */ jsx("div", { className: "relative group mr-2", children: /* @__PURE__ */ jsx(
517
+ "img",
518
+ {
519
+ src: previewUrl,
520
+ alt: `Preview of ${f.name}`,
521
+ className: "w-8 h-8 object-cover rounded border"
522
+ }
523
+ ) }) : /* @__PURE__ */ jsx("div", { className: "w-8 h-8 mr-2 flex items-center justify-center bg-gray-100 rounded border", children: "\u{1F4C4}" }),
524
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
525
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: f.name }),
526
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
527
+ (f.size / 1024).toFixed(1),
528
+ " KB"
529
+ ] })
530
+ ] })
531
+ ] }),
532
+ /* @__PURE__ */ jsx(
533
+ Button,
534
+ {
535
+ type: "button",
536
+ variant: "ghost",
537
+ size: "sm",
538
+ onClick: () => removeFileAt(idx),
539
+ "aria-label": `Remove ${f.name}`,
540
+ children: "\u2715"
541
+ }
542
+ )
543
+ ] }, `${f.name}-${idx}`);
544
+ }) }),
545
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
546
+ ] });
547
+ };
548
+ function Slider({
549
+ className,
550
+ defaultValue,
551
+ value,
552
+ min = 0,
553
+ max = 100,
554
+ ...props
555
+ }) {
556
+ const _values = React.useMemo(
557
+ () => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
558
+ [value, defaultValue, min, max]
559
+ );
560
+ return /* @__PURE__ */ jsxs(
561
+ SliderPrimitive.Root,
562
+ {
563
+ "data-slot": "slider",
564
+ defaultValue,
565
+ value,
566
+ min,
567
+ max,
568
+ className: cn(
569
+ "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
570
+ className
571
+ ),
572
+ ...props,
573
+ children: [
574
+ /* @__PURE__ */ jsx(
575
+ SliderPrimitive.Track,
576
+ {
577
+ "data-slot": "slider-track",
578
+ className: cn(
579
+ "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
580
+ ),
581
+ children: /* @__PURE__ */ jsx(
582
+ SliderPrimitive.Range,
583
+ {
584
+ "data-slot": "slider-range",
585
+ className: cn(
586
+ "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
587
+ )
588
+ }
589
+ )
590
+ }
591
+ ),
592
+ Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ jsx(
593
+ SliderPrimitive.Thumb,
594
+ {
595
+ "data-slot": "slider-thumb",
596
+ className: "border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
597
+ },
598
+ index
599
+ ))
600
+ ]
601
+ }
602
+ );
603
+ }
604
+ var SmartSlider = ({
605
+ field,
606
+ label,
607
+ min = 0,
608
+ max = 100,
609
+ step = 1,
610
+ className = "",
611
+ validation,
612
+ required = false,
613
+ showValue = true,
614
+ valueFormatter = (value) => value.toString(),
615
+ defaultValue,
616
+ info,
617
+ subLabel
618
+ }) => {
619
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
620
+ const fieldDetection = useFieldDetection();
621
+ const hasRegistered = useRef(false);
622
+ const hasSetDefault = useRef(false);
623
+ useEffect(() => {
624
+ if (validation && !hasRegistered.current) {
625
+ hasRegistered.current = true;
626
+ registerValidation(field, validation);
627
+ }
628
+ }, [validation, field, registerValidation]);
629
+ useEffect(() => {
630
+ if (fieldDetection?.registerField) {
631
+ fieldDetection.registerField(field);
632
+ }
633
+ }, [field, fieldDetection]);
634
+ useEffect(() => {
635
+ if (defaultValue !== void 0 && !hasSetDefault.current && (value === void 0 || value === null || value === "")) {
636
+ onChange(defaultValue);
637
+ hasSetDefault.current = true;
638
+ }
639
+ }, [defaultValue, value, onChange]);
640
+ const handleValueChange = (values) => {
641
+ const newValue = values[0];
642
+ onChange(newValue);
643
+ };
644
+ const currentValue = value !== void 0 ? value : min;
645
+ return /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${className}`, children: [
646
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
647
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
648
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
649
+ label,
650
+ " ",
651
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" }),
652
+ showValue && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-muted-foreground", children: [
653
+ "(",
654
+ valueFormatter(currentValue),
655
+ ")"
656
+ ] })
657
+ ] }),
658
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
659
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
660
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
661
+ ] }) })
662
+ ] }),
663
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
664
+ ] }),
665
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
666
+ /* @__PURE__ */ jsx(
667
+ Slider,
668
+ {
669
+ ref: fieldRef,
670
+ value: [currentValue],
671
+ onValueChange: handleValueChange,
672
+ min,
673
+ max,
674
+ step,
675
+ className: `w-full ${error ? "border-destructive" : ""}`,
676
+ "data-field": field
677
+ }
678
+ ),
679
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm text-muted-foreground", children: [
680
+ /* @__PURE__ */ jsx("span", { children: valueFormatter(min) }),
681
+ /* @__PURE__ */ jsx("span", { children: valueFormatter(max) })
682
+ ] })
683
+ ] }),
684
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
685
+ ] });
686
+ };
687
+ var SmartDualRangeSlider = ({
688
+ minField,
689
+ maxField,
690
+ label,
691
+ min = 0,
692
+ max = 100,
693
+ step = 1,
694
+ className = "",
695
+ minValidation,
696
+ maxValidation,
697
+ required = false,
698
+ showValues = true,
699
+ valueFormatter = (value) => value.toString(),
700
+ minLabel = "Min",
701
+ maxLabel = "Max",
702
+ defaultMinValue,
703
+ defaultMaxValue,
704
+ info,
705
+ subLabel
706
+ }) => {
707
+ const minFormField = useFormField(minField);
708
+ const maxFormField = useFormField(maxField);
709
+ const fieldDetection = useFieldDetection();
710
+ const hasRegisteredMin = useRef(false);
711
+ const hasRegisteredMax = useRef(false);
712
+ const hasSetDefaultMin = useRef(false);
713
+ const hasSetDefaultMax = useRef(false);
714
+ useEffect(() => {
715
+ if (minValidation && !hasRegisteredMin.current) {
716
+ hasRegisteredMin.current = true;
717
+ minFormField.registerValidation(minField, minValidation);
718
+ }
719
+ }, [minValidation, minField, minFormField]);
720
+ useEffect(() => {
721
+ if (maxValidation && !hasRegisteredMax.current) {
722
+ hasRegisteredMax.current = true;
723
+ maxFormField.registerValidation(maxField, maxValidation);
724
+ }
725
+ }, [maxValidation, maxField, maxFormField]);
726
+ useEffect(() => {
727
+ if (fieldDetection?.registerField) {
728
+ fieldDetection.registerField(minField);
729
+ fieldDetection.registerField(maxField);
730
+ }
731
+ }, [minField, maxField, fieldDetection]);
732
+ useEffect(() => {
733
+ if (defaultMinValue !== void 0 && !hasSetDefaultMin.current && (minFormField.value === void 0 || minFormField.value === null || minFormField.value === "")) {
734
+ minFormField.onChange(defaultMinValue);
735
+ hasSetDefaultMin.current = true;
736
+ }
737
+ }, [defaultMinValue, minFormField.value]);
738
+ useEffect(() => {
739
+ if (defaultMaxValue !== void 0 && !hasSetDefaultMax.current && (maxFormField.value === void 0 || maxFormField.value === null || maxFormField.value === "")) {
740
+ maxFormField.onChange(defaultMaxValue);
741
+ hasSetDefaultMax.current = true;
742
+ }
743
+ }, [defaultMaxValue, maxFormField.value]);
744
+ const handleValueChange = (values) => {
745
+ const [minValue, maxValue] = values;
746
+ minFormField.onChange(minValue);
747
+ maxFormField.onChange(maxValue);
748
+ };
749
+ const currentMin = minFormField.value !== void 0 ? minFormField.value : min;
750
+ const currentMax = maxFormField.value !== void 0 ? maxFormField.value : max;
751
+ const combinedError = minFormField.error || maxFormField.error;
752
+ return /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${className}`, children: [
753
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
754
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
755
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
756
+ label,
757
+ " ",
758
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
759
+ ] }),
760
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
761
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
762
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
763
+ ] }) })
764
+ ] }),
765
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
766
+ ] }),
767
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
768
+ /* @__PURE__ */ jsx(
769
+ Slider,
770
+ {
771
+ ref: minFormField.fieldRef,
772
+ value: [currentMin, currentMax],
773
+ onValueChange: handleValueChange,
774
+ min,
775
+ max,
776
+ step,
777
+ className: `w-full ${combinedError ? "border-destructive" : ""}`,
778
+ "data-min-field": minField,
779
+ "data-max-field": maxField
780
+ }
781
+ ),
782
+ showValues && /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center text-sm", children: [
783
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center", children: /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
784
+ minLabel,
785
+ ": ",
786
+ valueFormatter(currentMin)
787
+ ] }) }),
788
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center", children: /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
789
+ maxLabel,
790
+ ": ",
791
+ valueFormatter(currentMax)
792
+ ] }) })
793
+ ] })
794
+ ] }),
795
+ combinedError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: combinedError })
796
+ ] });
797
+ };
798
+ var TagsContext = createContext({
799
+ value: void 0,
800
+ setValue: void 0,
801
+ open: false,
802
+ onOpenChange: () => {
803
+ },
804
+ width: void 0,
805
+ setWidth: void 0
806
+ });
807
+ var useTagsContext = () => {
808
+ const context = useContext(TagsContext);
809
+ if (!context) {
810
+ throw new Error("useTagsContext must be used within a TagsProvider");
811
+ }
812
+ return context;
813
+ };
814
+ var Tags = ({
815
+ value,
816
+ setValue,
817
+ open: controlledOpen,
818
+ onOpenChange: controlledOnOpenChange,
819
+ children,
820
+ className
821
+ }) => {
822
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
823
+ const [width, setWidth] = useState();
824
+ const ref = useRef(null);
825
+ const open = controlledOpen ?? uncontrolledOpen;
826
+ const onOpenChange = controlledOnOpenChange ?? setUncontrolledOpen;
827
+ useEffect(() => {
828
+ if (!ref.current) {
829
+ return;
830
+ }
831
+ const resizeObserver = new ResizeObserver((entries) => {
832
+ setWidth(entries[0].contentRect.width);
833
+ });
834
+ resizeObserver.observe(ref.current);
835
+ return () => {
836
+ resizeObserver.disconnect();
837
+ };
838
+ }, []);
839
+ return /* @__PURE__ */ jsx(
840
+ TagsContext.Provider,
841
+ {
842
+ value: { value, setValue, open, onOpenChange, width, setWidth },
843
+ children: /* @__PURE__ */ jsx(Popover, { onOpenChange, open, children: /* @__PURE__ */ jsx("div", { className: cn("relative w-full", className), ref, children }) })
844
+ }
845
+ );
846
+ };
847
+ var TagsTrigger = ({
848
+ className,
849
+ children,
850
+ ...props
851
+ }) => /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
852
+ Button,
853
+ {
854
+ className: cn("h-auto w-full justify-between p-2", className),
855
+ role: "combobox",
856
+ variant: "outline",
857
+ ...props,
858
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1", children: [
859
+ children,
860
+ /* @__PURE__ */ jsx("span", { className: "px-2 py-px text-muted-foreground" })
861
+ ] })
862
+ }
863
+ ) });
864
+ var TagsValue = ({
865
+ className,
866
+ children,
867
+ onRemove,
868
+ ...props
869
+ }) => {
870
+ const handleRemove = (event) => {
871
+ event.preventDefault();
872
+ event.stopPropagation();
873
+ onRemove?.();
874
+ };
875
+ return /* @__PURE__ */ jsxs(Badge, { className: cn("flex items-center gap-2", className), ...props, children: [
876
+ children,
877
+ onRemove && // biome-ignore lint/a11y/noStaticElementInteractions: "This is a clickable badge"
878
+ // biome-ignore lint/a11y/useKeyWithClickEvents: "This is a clickable badge"
879
+ /* @__PURE__ */ jsx(
880
+ "div",
881
+ {
882
+ className: "size-auto cursor-pointer hover:text-muted-foreground",
883
+ onClick: handleRemove,
884
+ children: /* @__PURE__ */ jsx(XIcon, { size: 12 })
885
+ }
886
+ )
887
+ ] });
888
+ };
889
+ var TagsContent = ({
890
+ className,
891
+ children,
892
+ ...props
893
+ }) => {
894
+ const { width } = useTagsContext();
895
+ return /* @__PURE__ */ jsx(
896
+ PopoverContent,
897
+ {
898
+ className: cn("p-0", className),
899
+ style: { width },
900
+ ...props,
901
+ children: /* @__PURE__ */ jsx(Command, { children })
902
+ }
903
+ );
904
+ };
905
+ var TagsInput = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandInput, { className: cn("h-9", className), ...props });
906
+ var TagsList = ({ className, ...props }) => /* @__PURE__ */ jsx(CommandList, { className: cn("max-h-[200px]", className), ...props });
907
+ var TagsEmpty = ({
908
+ children,
909
+ className,
910
+ ...props
911
+ }) => /* @__PURE__ */ jsx(CommandEmpty, { ...props, children: children ?? "No tags found." });
912
+ var TagsGroup = CommandGroup;
913
+ var TagsItem = ({ className, ...props }) => /* @__PURE__ */ jsx(
914
+ CommandItem,
915
+ {
916
+ className: cn("cursor-pointer items-center justify-between", className),
917
+ ...props
918
+ }
919
+ );
920
+ var SmartAutoSuggestTags = ({
921
+ field,
922
+ label,
923
+ options: initialOptions,
924
+ className = "",
925
+ placeholder = "Select tags...",
926
+ validation,
927
+ required = false,
928
+ allowCreate = true,
929
+ maxTags,
930
+ defaultValue,
931
+ onTagCreate,
932
+ info,
933
+ subLabel
934
+ }) => {
935
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
936
+ const fieldDetection = useFieldDetection();
937
+ const hasRegistered = useRef(false);
938
+ const hasSetDefault = useRef(false);
939
+ const [tags, setTags] = useState(initialOptions);
940
+ const [newTag, setNewTag] = useState("");
941
+ const selected = Array.isArray(value) ? value : [];
942
+ useEffect(() => {
943
+ if (validation && !hasRegistered.current) {
944
+ hasRegistered.current = true;
945
+ registerValidation(field, validation);
946
+ }
947
+ }, [validation, field, registerValidation]);
948
+ useEffect(() => {
949
+ if (fieldDetection?.registerField) {
950
+ fieldDetection.registerField(field);
951
+ }
952
+ }, [field, fieldDetection]);
953
+ useEffect(() => {
954
+ if (defaultValue !== void 0 && !hasSetDefault.current && (!value || Array.isArray(value) && value.length === 0)) {
955
+ onChange(defaultValue);
956
+ hasSetDefault.current = true;
957
+ }
958
+ }, [defaultValue, value, onChange]);
959
+ useEffect(() => {
960
+ setTags(initialOptions);
961
+ }, [initialOptions]);
962
+ const handleRemove = (tagId) => {
963
+ if (!selected.includes(tagId)) {
964
+ return;
965
+ }
966
+ const newSelected = selected.filter((v) => v !== tagId);
967
+ onChange(newSelected);
968
+ };
969
+ const handleSelect = (tagId) => {
970
+ if (selected.includes(tagId)) {
971
+ handleRemove(tagId);
972
+ return;
973
+ }
974
+ if (maxTags && selected.length >= maxTags) {
975
+ return;
976
+ }
977
+ const newSelected = [...selected, tagId];
978
+ onChange(newSelected);
979
+ };
980
+ const handleCreateTag = () => {
981
+ if (!newTag.trim()) return;
982
+ if (!allowCreate) return;
983
+ const tagId = newTag.toLowerCase().replace(/\s+/g, "-");
984
+ const newTagOption = {
985
+ id: tagId,
986
+ label: newTag
987
+ };
988
+ setTags((prev) => [...prev, newTagOption]);
989
+ const newSelected = [...selected, tagId];
990
+ onChange(newSelected);
991
+ if (onTagCreate) {
992
+ onTagCreate(newTagOption);
993
+ }
994
+ setNewTag("");
995
+ };
996
+ const filteredTags = tags.filter(
997
+ (tag) => tag.label.toLowerCase().includes(newTag.toLowerCase())
998
+ );
999
+ return /* @__PURE__ */ jsxs("div", { className: `flex-1 min-w-0 ${className}`, children: [
1000
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
1001
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1002
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
1003
+ label,
1004
+ " ",
1005
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" }),
1006
+ maxTags && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
1007
+ "(",
1008
+ selected.length,
1009
+ "/",
1010
+ maxTags,
1011
+ ")"
1012
+ ] })
1013
+ ] }),
1014
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1015
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
1016
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
1017
+ ] }) })
1018
+ ] }),
1019
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
1020
+ ] }),
1021
+ /* @__PURE__ */ jsx("div", { ref: fieldRef, "data-field": field, children: /* @__PURE__ */ jsx("div", { className: cn(
1022
+ "w-full rounded-md border border-input bg-background text-sm ring-offset-background",
1023
+ "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
1024
+ error && "border-destructive focus-within:ring-destructive"
1025
+ ), children: /* @__PURE__ */ jsxs(Tags, { className: "w-full", children: [
1026
+ /* @__PURE__ */ jsxs(TagsTrigger, { className: "w-full px-3 py-2 border-0 bg-transparent", children: [
1027
+ selected.length === 0 && placeholder && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: placeholder }),
1028
+ selected.map((tagId) => {
1029
+ const tag = tags.find((t) => t.id === tagId);
1030
+ return tag ? /* @__PURE__ */ jsx(
1031
+ TagsValue,
1032
+ {
1033
+ onRemove: () => handleRemove(tagId),
1034
+ className: "px-2 py-1 text-sm gap-1",
1035
+ children: tag.label
1036
+ },
1037
+ tagId
1038
+ ) : null;
1039
+ })
1040
+ ] }),
1041
+ /* @__PURE__ */ jsxs(TagsContent, { children: [
1042
+ /* @__PURE__ */ jsx(
1043
+ TagsInput,
1044
+ {
1045
+ onValueChange: setNewTag,
1046
+ placeholder: `Search ${placeholder.toLowerCase()}...`,
1047
+ value: newTag
1048
+ }
1049
+ ),
1050
+ /* @__PURE__ */ jsxs(TagsList, { children: [
1051
+ filteredTags.length === 0 && allowCreate && newTag.trim() && /* @__PURE__ */ jsx(TagsEmpty, { children: /* @__PURE__ */ jsxs(
1052
+ "button",
1053
+ {
1054
+ className: "mx-auto flex cursor-pointer items-center gap-2 text-sm",
1055
+ onClick: handleCreateTag,
1056
+ type: "button",
1057
+ children: [
1058
+ /* @__PURE__ */ jsx(PlusIcon, { className: "text-muted-foreground", size: 14 }),
1059
+ "Create new tag: ",
1060
+ newTag
1061
+ ]
1062
+ }
1063
+ ) }),
1064
+ filteredTags.length === 0 && (!allowCreate || !newTag.trim()) && /* @__PURE__ */ jsx(TagsEmpty, { children: /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "No tags found" }) }),
1065
+ /* @__PURE__ */ jsx(TagsGroup, { children: filteredTags.map((tag) => /* @__PURE__ */ jsxs(
1066
+ TagsItem,
1067
+ {
1068
+ onSelect: handleSelect,
1069
+ value: tag.id,
1070
+ disabled: maxTags ? selected.length >= maxTags && !selected.includes(tag.id) : false,
1071
+ children: [
1072
+ tag.label,
1073
+ selected.includes(tag.id) && /* @__PURE__ */ jsx(CheckIcon, { className: "text-muted-foreground", size: 14 })
1074
+ ]
1075
+ },
1076
+ tag.id
1077
+ )) })
1078
+ ] })
1079
+ ] })
1080
+ ] }) }) }),
1081
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
1082
+ ] });
1083
+ };
1084
+ var sanitizeHtml = (html) => {
1085
+ const tempDiv = document.createElement("div");
1086
+ tempDiv.innerHTML = html;
1087
+ const allowedTags = ["P", "STRONG", "B", "EM", "I", "U", "UL", "OL", "LI", "BR", "DIV"];
1088
+ const cleanNode = (node) => {
1089
+ if (node.nodeType === Node.TEXT_NODE) {
1090
+ return node.cloneNode(false);
1091
+ }
1092
+ if (node.nodeType === Node.ELEMENT_NODE) {
1093
+ const element = node;
1094
+ const tagName = element.tagName.toUpperCase();
1095
+ if (tagName === "DIV") {
1096
+ const p = document.createElement("p");
1097
+ Array.from(element.childNodes).forEach((child) => {
1098
+ const cleanedChild = cleanNode(child);
1099
+ if (cleanedChild) p.appendChild(cleanedChild);
1100
+ });
1101
+ return p;
1102
+ }
1103
+ if (!allowedTags.includes(tagName)) {
1104
+ const fragment = document.createDocumentFragment();
1105
+ Array.from(element.childNodes).forEach((child) => {
1106
+ const cleanedChild = cleanNode(child);
1107
+ if (cleanedChild) fragment.appendChild(cleanedChild);
1108
+ });
1109
+ return fragment;
1110
+ }
1111
+ const cleanElement = document.createElement(tagName.toLowerCase());
1112
+ Array.from(element.childNodes).forEach((child) => {
1113
+ const cleanedChild = cleanNode(child);
1114
+ if (cleanedChild) cleanElement.appendChild(cleanedChild);
1115
+ });
1116
+ return cleanElement;
1117
+ }
1118
+ return null;
1119
+ };
1120
+ const cleanedDiv = document.createElement("div");
1121
+ Array.from(tempDiv.childNodes).forEach((child) => {
1122
+ const cleanedChild = cleanNode(child);
1123
+ if (cleanedChild) cleanedDiv.appendChild(cleanedChild);
1124
+ });
1125
+ return cleanedDiv.innerHTML;
1126
+ };
1127
+ var SmartBasicRichTextbox = ({
1128
+ field,
1129
+ label,
1130
+ placeholder = "Enter text...",
1131
+ validation,
1132
+ className = "",
1133
+ required = false,
1134
+ defaultValue,
1135
+ minHeight = "150px",
1136
+ maxHeight = "400px",
1137
+ info,
1138
+ subLabel
1139
+ }) => {
1140
+ const { value, error, onChange, fieldRef, registerValidation } = useFormField(field);
1141
+ const fieldDetection = useFieldDetection();
1142
+ const hasRegistered = useRef(false);
1143
+ const hasSetDefault = useRef(false);
1144
+ const editorRef = useRef(null);
1145
+ const [isFocused, setIsFocused] = useState(false);
1146
+ useEffect(() => {
1147
+ if (validation && !hasRegistered.current) {
1148
+ hasRegistered.current = true;
1149
+ registerValidation(field, validation);
1150
+ }
1151
+ }, [validation, field, registerValidation]);
1152
+ useEffect(() => {
1153
+ if (fieldDetection?.registerField) {
1154
+ fieldDetection.registerField(field);
1155
+ }
1156
+ }, [field, fieldDetection]);
1157
+ useEffect(() => {
1158
+ if (defaultValue !== void 0 && !hasSetDefault.current && (value === void 0 || value === null || value === "")) {
1159
+ onChange(defaultValue);
1160
+ hasSetDefault.current = true;
1161
+ }
1162
+ }, [defaultValue, value, onChange]);
1163
+ useEffect(() => {
1164
+ if (editorRef.current && value !== void 0) {
1165
+ const sanitized = sanitizeHtml(value || "");
1166
+ if (editorRef.current.innerHTML !== sanitized) {
1167
+ editorRef.current.innerHTML = sanitized;
1168
+ }
1169
+ }
1170
+ }, [value]);
1171
+ const handleInput = useCallback(() => {
1172
+ if (editorRef.current) {
1173
+ const html = editorRef.current.innerHTML;
1174
+ const sanitized = sanitizeHtml(html);
1175
+ onChange(sanitized);
1176
+ }
1177
+ }, [onChange]);
1178
+ const handleKeyDown = useCallback((e) => {
1179
+ if (e.key === "Enter" && !e.shiftKey) {
1180
+ e.preventDefault();
1181
+ document.execCommand("insertParagraph", false);
1182
+ return;
1183
+ }
1184
+ }, []);
1185
+ const execCommand = useCallback((command, value2) => {
1186
+ document.execCommand(command, false, value2);
1187
+ editorRef.current?.focus();
1188
+ handleInput();
1189
+ }, [handleInput]);
1190
+ const toggleBold = () => execCommand("bold");
1191
+ const toggleItalic = () => execCommand("italic");
1192
+ const toggleUnderline = () => execCommand("underline");
1193
+ const toggleBulletList = () => execCommand("insertUnorderedList");
1194
+ const toggleNumberedList = () => execCommand("insertOrderedList");
1195
+ const isFormatActive = useCallback((format) => {
1196
+ return document.queryCommandState(format);
1197
+ }, []);
1198
+ const [activeFormats, setActiveFormats] = useState({
1199
+ bold: false,
1200
+ italic: false,
1201
+ underline: false,
1202
+ bulletList: false,
1203
+ numberedList: false
1204
+ });
1205
+ const updateActiveFormats = useCallback(() => {
1206
+ if (isFocused) {
1207
+ setActiveFormats({
1208
+ bold: isFormatActive("bold"),
1209
+ italic: isFormatActive("italic"),
1210
+ underline: isFormatActive("underline"),
1211
+ bulletList: isFormatActive("insertUnorderedList"),
1212
+ numberedList: isFormatActive("insertOrderedList")
1213
+ });
1214
+ }
1215
+ }, [isFocused, isFormatActive]);
1216
+ useEffect(() => {
1217
+ if (isFocused) {
1218
+ document.addEventListener("selectionchange", updateActiveFormats);
1219
+ return () => document.removeEventListener("selectionchange", updateActiveFormats);
1220
+ }
1221
+ }, [isFocused, updateActiveFormats]);
1222
+ const handleFocus = () => {
1223
+ setIsFocused(true);
1224
+ updateActiveFormats();
1225
+ };
1226
+ const handleBlur = () => {
1227
+ setIsFocused(false);
1228
+ };
1229
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex-1 min-w-0", className), children: [
1230
+ label && /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
1231
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1232
+ /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium text-foreground", children: [
1233
+ label,
1234
+ " ",
1235
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
1236
+ ] }),
1237
+ info && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1238
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(InfoIcon, { className: "h-4 w-4 text-muted-foreground cursor-pointer mr-2" }) }),
1239
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { className: "max-w-xs", children: info }) })
1240
+ ] }) })
1241
+ ] }),
1242
+ subLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: subLabel })
1243
+ ] }),
1244
+ /* @__PURE__ */ jsxs(
1245
+ "div",
1246
+ {
1247
+ ref: fieldRef,
1248
+ "data-field": field,
1249
+ className: cn(
1250
+ "w-full rounded-md border border-input bg-background",
1251
+ "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2",
1252
+ error && "border-destructive focus-within:ring-destructive"
1253
+ ),
1254
+ children: [
1255
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 p-1.5 border-b border-border bg-muted/30", children: [
1256
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1257
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1258
+ Button,
1259
+ {
1260
+ type: "button",
1261
+ variant: "ghost",
1262
+ size: "sm",
1263
+ className: cn(
1264
+ "h-8 w-8 p-0 hover:bg-accent hover:text-accent-foreground",
1265
+ activeFormats.bold && "bg-primary text-primary-foreground hover:bg-primary/90"
1266
+ ),
1267
+ onClick: toggleBold,
1268
+ children: /* @__PURE__ */ jsx(Bold, { className: "h-4 w-4" })
1269
+ }
1270
+ ) }),
1271
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Bold (Ctrl+B)" }) })
1272
+ ] }) }),
1273
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1274
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1275
+ Button,
1276
+ {
1277
+ type: "button",
1278
+ variant: "ghost",
1279
+ size: "sm",
1280
+ className: cn(
1281
+ "h-8 w-8 p-0 hover:bg-accent hover:text-accent-foreground",
1282
+ activeFormats.italic && "bg-primary text-primary-foreground hover:bg-primary/90"
1283
+ ),
1284
+ onClick: toggleItalic,
1285
+ children: /* @__PURE__ */ jsx(Italic, { className: "h-4 w-4" })
1286
+ }
1287
+ ) }),
1288
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Italic (Ctrl+I)" }) })
1289
+ ] }) }),
1290
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1291
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1292
+ Button,
1293
+ {
1294
+ type: "button",
1295
+ variant: "ghost",
1296
+ size: "sm",
1297
+ className: cn(
1298
+ "h-8 w-8 p-0 hover:bg-accent hover:text-accent-foreground",
1299
+ activeFormats.underline && "bg-primary text-primary-foreground hover:bg-primary/90"
1300
+ ),
1301
+ onClick: toggleUnderline,
1302
+ children: /* @__PURE__ */ jsx(Underline, { className: "h-4 w-4" })
1303
+ }
1304
+ ) }),
1305
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Underline (Ctrl+U)" }) })
1306
+ ] }) }),
1307
+ /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-border mx-1" }),
1308
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1309
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1310
+ Button,
1311
+ {
1312
+ type: "button",
1313
+ variant: "ghost",
1314
+ size: "sm",
1315
+ className: cn(
1316
+ "h-8 w-8 p-0 hover:bg-accent hover:text-accent-foreground",
1317
+ activeFormats.bulletList && "bg-primary text-primary-foreground hover:bg-primary/90"
1318
+ ),
1319
+ onClick: toggleBulletList,
1320
+ children: /* @__PURE__ */ jsx(List, { className: "h-4 w-4" })
1321
+ }
1322
+ ) }),
1323
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Bullet List" }) })
1324
+ ] }) }),
1325
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
1326
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1327
+ Button,
1328
+ {
1329
+ type: "button",
1330
+ variant: "ghost",
1331
+ size: "sm",
1332
+ className: cn(
1333
+ "h-8 w-8 p-0 hover:bg-accent hover:text-accent-foreground",
1334
+ activeFormats.numberedList && "bg-primary text-primary-foreground hover:bg-primary/90"
1335
+ ),
1336
+ onClick: toggleNumberedList,
1337
+ children: /* @__PURE__ */ jsx(ListOrdered, { className: "h-4 w-4" })
1338
+ }
1339
+ ) }),
1340
+ /* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("p", { children: "Numbered List" }) })
1341
+ ] }) })
1342
+ ] }),
1343
+ /* @__PURE__ */ jsx(
1344
+ "div",
1345
+ {
1346
+ ref: editorRef,
1347
+ contentEditable: true,
1348
+ onInput: handleInput,
1349
+ onKeyDown: handleKeyDown,
1350
+ onFocus: handleFocus,
1351
+ onBlur: handleBlur,
1352
+ className: cn(
1353
+ "w-full px-3 py-2 text-sm text-foreground",
1354
+ "bg-background",
1355
+ "focus:outline-none",
1356
+ "overflow-y-auto",
1357
+ "[&>p]:my-2 [&>p]:leading-relaxed",
1358
+ "[&>ul]:my-2 [&>ul]:ml-6 [&>ul]:list-disc [&>ul]:list-inside",
1359
+ "[&_ul]:my-2 [&_ul]:ml-6 [&_ul]:list-disc [&_ul]:list-inside",
1360
+ "[&>ol]:my-2 [&>ol]:ml-6 [&>ol]:list-decimal [&>ol]:list-inside",
1361
+ "[&_ol]:my-2 [&_ol]:ml-6 [&_ol]:list-decimal [&_ol]:list-inside",
1362
+ "[&>li]:my-1 [&_li]:my-1",
1363
+ "[&_strong]:font-semibold [&_b]:font-semibold",
1364
+ "[&_em]:italic [&_i]:italic",
1365
+ "[&_u]:underline",
1366
+ !value && "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground empty:before:pointer-events-none"
1367
+ ),
1368
+ style: {
1369
+ minHeight,
1370
+ maxHeight
1371
+ },
1372
+ "data-placeholder": placeholder
1373
+ }
1374
+ )
1375
+ ]
1376
+ }
1377
+ ),
1378
+ error && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm mt-1", children: error })
1379
+ ] });
1380
+ };
1381
+
1382
+ export { SmartAutoSuggestTags, SmartBasicRichTextbox, SmartCombobox, SmartDualRangeSlider, SmartFileUpload, SmartSlider };
1383
+ //# sourceMappingURL=fields.js.map
1384
+ //# sourceMappingURL=fields.js.map